Loading app/src/main/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/> <!-- Only required on SDK <= 28 --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <!-- To install packages downloaded through ntfy; craazyy! --> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <!-- To reschedule the websocket retry --> <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" /> <!-- To override DND --> <application android:name=".app.Application" Loading app/src/main/java/io/heckel/ntfy/db/Repository.kt +6 −0 Original line number Diff line number Diff line Loading @@ -218,6 +218,11 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas return sharedPrefs.getInt(SHARED_PREFS_MIN_PRIORITY, 1) // 1/low means all priorities } fun getDnsOverridePriority(): Int { return sharedPrefs.getInt(SHARED_PREFS_DND_OVERRIDE_PRIORITY, 5) } fun getAutoDownloadMaxSize(): Long { val defaultValue = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { AUTO_DOWNLOAD_NEVER // Need to request permission on older versions Loading Loading @@ -435,6 +440,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion" const val SHARED_PREFS_MUTED_UNTIL_TIMESTAMP = "MutedUntil" const val SHARED_PREFS_MIN_PRIORITY = "MinPriority" const val SHARED_PREFS_DND_OVERRIDE_PRIORITY = "DndOverridePriority" const val SHARED_PREFS_AUTO_DOWNLOAD_MAX_SIZE = "AutoDownload" const val SHARED_PREFS_AUTO_DELETE_SECONDS = "AutoDelete" const val SHARED_PREFS_CONNECTION_PROTOCOL = "ConnectionProtocol" Loading app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +11 −4 Original line number Diff line number Diff line package io.heckel.ntfy.msg import android.app.* import android.content.* import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.TaskStackBuilder import android.content.Context import android.content.Intent import android.graphics.BitmapFactory import android.media.RingtoneManager import android.net.Uri Loading @@ -10,15 +14,15 @@ import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import io.heckel.ntfy.R import io.heckel.ntfy.db.* import io.heckel.ntfy.db.Notification import io.heckel.ntfy.util.Log import io.heckel.ntfy.ui.Colors import io.heckel.ntfy.ui.DetailActivity import io.heckel.ntfy.ui.MainActivity import io.heckel.ntfy.util.* class NotificationService(val context: Context) { private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager private val repository = Repository.getInstance(context) fun display(subscription: Subscription, notification: Notification) { Log.d(TAG, "Displaying notification $notification") Loading Loading @@ -218,6 +222,7 @@ class NotificationService(val context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Note: To change a notification channel, you must delete the old one and create a new one! val dndOverridePriority = repository.getDnsOverridePriority() val pause = 300L val channel = when (priority) { 1 -> NotificationChannel(CHANNEL_ID_MIN, context.getString(R.string.channel_notifications_min_name), NotificationManager.IMPORTANCE_MIN) Loading @@ -225,6 +230,7 @@ class NotificationService(val context: Context) { 4 -> { val channel = NotificationChannel(CHANNEL_ID_HIGH, context.getString(R.string.channel_notifications_high_name), NotificationManager.IMPORTANCE_HIGH) channel.enableVibration(true) channel.setBypassDnd(dndOverridePriority >= 4) channel.vibrationPattern = longArrayOf( pause, 100, pause, 100, pause, 100, pause, 2000 Loading @@ -235,6 +241,7 @@ class NotificationService(val context: Context) { val channel = NotificationChannel(CHANNEL_ID_MAX, context.getString(R.string.channel_notifications_max_name), NotificationManager.IMPORTANCE_HIGH) // IMPORTANCE_MAX does not exist channel.enableLights(true) channel.enableVibration(true) channel.setBypassDnd(dndOverridePriority >= 4) channel.vibrationPattern = longArrayOf( pause, 100, pause, 100, pause, 100, pause, 2000, Loading app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +3 −2 Original line number Diff line number Diff line Loading @@ -30,7 +30,6 @@ import io.heckel.ntfy.app.Application import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.firebase.FirebaseMessenger import io.heckel.ntfy.util.Log import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.NotificationDispatcher import io.heckel.ntfy.service.SubscriberService Loading Loading @@ -376,7 +375,9 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc true } R.id.main_menu_docs -> { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_docs_url)))) //startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_docs_url)))) val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS) startActivity(intent) true } else -> super.onOptionsItemSelected(item) Loading app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +30 −0 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ package io.heckel.ntfy.ui import android.Manifest import android.app.AlertDialog import android.app.NotificationManager import android.content.ClipData import android.content.ClipboardManager import android.content.Context Loading Loading @@ -120,6 +121,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere class SettingsFragment : PreferenceFragmentCompat() { private lateinit var repository: Repository private lateinit var serviceManager: SubscriberServiceManager private lateinit var notificationManager: NotificationManager private var autoDownloadSelection = AUTO_DOWNLOAD_SELECTION_NOT_SET override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { Loading @@ -129,6 +131,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere repository = Repository.getInstance(requireActivity()) serviceManager = SubscriberServiceManager(requireActivity()) autoDownloadSelection = repository.getAutoDownloadMaxSize() // Only used for <= Android P, due to permissions request notificationManager = requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Important note: We do not use the default shared prefs to store settings. Every // preferenceDataStore is overridden to use the repository. This is convenient, because Loading Loading @@ -200,6 +203,33 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } } // DND override priority val dndOverrideEnabled = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && notificationManager.isNotificationPolicyAccessGranted val dndOverridePriorityPrefId = context?.getString(R.string.settings_notifications_dnd_override_priority_key) ?: return val dndOverridePriority: ListPreference? = findPreference(minPriorityPrefId) dndOverridePriority?.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M dndOverridePriority?.value = repository.getDnsOverridePriority().toString() dndOverridePriority?.preferenceDataStore = object : PreferenceDataStore() { override fun putString(key: String?, value: String?) { val dndOverridePriorityValue = value?.toIntOrNull() ?:return //repository.setMinPriority(minPriorityValue) } override fun getString(key: String?, defValue: String?): String { return repository.getDnsOverridePriority().toString() } } dndOverridePriority?.summaryProvider = Preference.SummaryProvider<ListPreference> { pref -> val priorityValue = pref.value.toIntOrNull() ?: 1 // 1/low means all priorities when (priorityValue) { 1 -> getString(R.string.settings_notifications_min_priority_summary_any) 5 -> getString(R.string.settings_notifications_min_priority_summary_max) else -> { val minPriorityString = toPriorityString(requireContext(), priorityValue) getString(R.string.settings_notifications_min_priority_summary_x_or_higher, priorityValue, minPriorityString) } } } // Auto download val autoDownloadPrefId = context?.getString(R.string.settings_notifications_auto_download_key) ?: return val autoDownload: ListPreference? = findPreference(autoDownloadPrefId) Loading Loading
app/src/main/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/> <!-- Only required on SDK <= 28 --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <!-- To install packages downloaded through ntfy; craazyy! --> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <!-- To reschedule the websocket retry --> <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" /> <!-- To override DND --> <application android:name=".app.Application" Loading
app/src/main/java/io/heckel/ntfy/db/Repository.kt +6 −0 Original line number Diff line number Diff line Loading @@ -218,6 +218,11 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas return sharedPrefs.getInt(SHARED_PREFS_MIN_PRIORITY, 1) // 1/low means all priorities } fun getDnsOverridePriority(): Int { return sharedPrefs.getInt(SHARED_PREFS_DND_OVERRIDE_PRIORITY, 5) } fun getAutoDownloadMaxSize(): Long { val defaultValue = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { AUTO_DOWNLOAD_NEVER // Need to request permission on older versions Loading Loading @@ -435,6 +440,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion" const val SHARED_PREFS_MUTED_UNTIL_TIMESTAMP = "MutedUntil" const val SHARED_PREFS_MIN_PRIORITY = "MinPriority" const val SHARED_PREFS_DND_OVERRIDE_PRIORITY = "DndOverridePriority" const val SHARED_PREFS_AUTO_DOWNLOAD_MAX_SIZE = "AutoDownload" const val SHARED_PREFS_AUTO_DELETE_SECONDS = "AutoDelete" const val SHARED_PREFS_CONNECTION_PROTOCOL = "ConnectionProtocol" Loading
app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +11 −4 Original line number Diff line number Diff line package io.heckel.ntfy.msg import android.app.* import android.content.* import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.TaskStackBuilder import android.content.Context import android.content.Intent import android.graphics.BitmapFactory import android.media.RingtoneManager import android.net.Uri Loading @@ -10,15 +14,15 @@ import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import io.heckel.ntfy.R import io.heckel.ntfy.db.* import io.heckel.ntfy.db.Notification import io.heckel.ntfy.util.Log import io.heckel.ntfy.ui.Colors import io.heckel.ntfy.ui.DetailActivity import io.heckel.ntfy.ui.MainActivity import io.heckel.ntfy.util.* class NotificationService(val context: Context) { private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager private val repository = Repository.getInstance(context) fun display(subscription: Subscription, notification: Notification) { Log.d(TAG, "Displaying notification $notification") Loading Loading @@ -218,6 +222,7 @@ class NotificationService(val context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Note: To change a notification channel, you must delete the old one and create a new one! val dndOverridePriority = repository.getDnsOverridePriority() val pause = 300L val channel = when (priority) { 1 -> NotificationChannel(CHANNEL_ID_MIN, context.getString(R.string.channel_notifications_min_name), NotificationManager.IMPORTANCE_MIN) Loading @@ -225,6 +230,7 @@ class NotificationService(val context: Context) { 4 -> { val channel = NotificationChannel(CHANNEL_ID_HIGH, context.getString(R.string.channel_notifications_high_name), NotificationManager.IMPORTANCE_HIGH) channel.enableVibration(true) channel.setBypassDnd(dndOverridePriority >= 4) channel.vibrationPattern = longArrayOf( pause, 100, pause, 100, pause, 100, pause, 2000 Loading @@ -235,6 +241,7 @@ class NotificationService(val context: Context) { val channel = NotificationChannel(CHANNEL_ID_MAX, context.getString(R.string.channel_notifications_max_name), NotificationManager.IMPORTANCE_HIGH) // IMPORTANCE_MAX does not exist channel.enableLights(true) channel.enableVibration(true) channel.setBypassDnd(dndOverridePriority >= 4) channel.vibrationPattern = longArrayOf( pause, 100, pause, 100, pause, 100, pause, 2000, Loading
app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +3 −2 Original line number Diff line number Diff line Loading @@ -30,7 +30,6 @@ import io.heckel.ntfy.app.Application import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.firebase.FirebaseMessenger import io.heckel.ntfy.util.Log import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.NotificationDispatcher import io.heckel.ntfy.service.SubscriberService Loading Loading @@ -376,7 +375,9 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc true } R.id.main_menu_docs -> { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_docs_url)))) //startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_docs_url)))) val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS) startActivity(intent) true } else -> super.onOptionsItemSelected(item) Loading
app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +30 −0 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ package io.heckel.ntfy.ui import android.Manifest import android.app.AlertDialog import android.app.NotificationManager import android.content.ClipData import android.content.ClipboardManager import android.content.Context Loading Loading @@ -120,6 +121,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere class SettingsFragment : PreferenceFragmentCompat() { private lateinit var repository: Repository private lateinit var serviceManager: SubscriberServiceManager private lateinit var notificationManager: NotificationManager private var autoDownloadSelection = AUTO_DOWNLOAD_SELECTION_NOT_SET override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { Loading @@ -129,6 +131,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere repository = Repository.getInstance(requireActivity()) serviceManager = SubscriberServiceManager(requireActivity()) autoDownloadSelection = repository.getAutoDownloadMaxSize() // Only used for <= Android P, due to permissions request notificationManager = requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Important note: We do not use the default shared prefs to store settings. Every // preferenceDataStore is overridden to use the repository. This is convenient, because Loading Loading @@ -200,6 +203,33 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } } // DND override priority val dndOverrideEnabled = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && notificationManager.isNotificationPolicyAccessGranted val dndOverridePriorityPrefId = context?.getString(R.string.settings_notifications_dnd_override_priority_key) ?: return val dndOverridePriority: ListPreference? = findPreference(minPriorityPrefId) dndOverridePriority?.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M dndOverridePriority?.value = repository.getDnsOverridePriority().toString() dndOverridePriority?.preferenceDataStore = object : PreferenceDataStore() { override fun putString(key: String?, value: String?) { val dndOverridePriorityValue = value?.toIntOrNull() ?:return //repository.setMinPriority(minPriorityValue) } override fun getString(key: String?, defValue: String?): String { return repository.getDnsOverridePriority().toString() } } dndOverridePriority?.summaryProvider = Preference.SummaryProvider<ListPreference> { pref -> val priorityValue = pref.value.toIntOrNull() ?: 1 // 1/low means all priorities when (priorityValue) { 1 -> getString(R.string.settings_notifications_min_priority_summary_any) 5 -> getString(R.string.settings_notifications_min_priority_summary_max) else -> { val minPriorityString = toPriorityString(requireContext(), priorityValue) getString(R.string.settings_notifications_min_priority_summary_x_or_higher, priorityValue, minPriorityString) } } } // Auto download val autoDownloadPrefId = context?.getString(R.string.settings_notifications_auto_download_key) ?: return val autoDownload: ListPreference? = findPreference(autoDownloadPrefId) Loading