Add the app notifications filter
This commit is contained in:
@ -23,9 +23,11 @@ import com.karumi.dexter.listener.multi.MultiplePermissionsListener
|
||||
import com.tommasoberlose.anotherwidget.R
|
||||
import com.tommasoberlose.anotherwidget.global.Constants
|
||||
import com.tommasoberlose.anotherwidget.global.Preferences
|
||||
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
|
||||
import com.tommasoberlose.anotherwidget.helpers.AlarmHelper
|
||||
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
|
||||
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
|
||||
import com.tommasoberlose.anotherwidget.ui.activities.AppNotificationsFilterActivity
|
||||
import com.tommasoberlose.anotherwidget.ui.activities.MusicPlayersFilterActivity
|
||||
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
|
||||
import kotlinx.android.synthetic.main.glance_provider_settings_layout.view.*
|
||||
@ -94,6 +96,9 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
|
||||
view.action_filter_notifications_app.isVisible = provider == Constants.GlanceProviderId.NOTIFICATIONS
|
||||
if (provider == Constants.GlanceProviderId.NOTIFICATIONS) {
|
||||
checkLastNotificationsPermission(view)
|
||||
view.action_filter_notifications_app.setOnClickListener {
|
||||
context.startActivity(Intent(context, AppNotificationsFilterActivity::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
/* GREETINGS */
|
||||
@ -200,7 +205,7 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
|
||||
|
||||
private fun checkNotificationPermission(view: View) {
|
||||
when {
|
||||
NotificationManagerCompat.getEnabledListenerPackages(context).contains(context.packageName) -> {
|
||||
ActiveNotificationsHelper.checkNotificationAccess(context) -> {
|
||||
view.warning_container.isVisible = false
|
||||
MediaPlayerHelper.updatePlayingMediaInfo(context)
|
||||
}
|
||||
@ -220,7 +225,7 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
|
||||
|
||||
private fun checkLastNotificationsPermission(view: View) {
|
||||
when {
|
||||
NotificationManagerCompat.getEnabledListenerPackages(context).contains(context.packageName) -> {
|
||||
ActiveNotificationsHelper.checkNotificationAccess(context) -> {
|
||||
view.warning_container.isVisible = false
|
||||
}
|
||||
Preferences.showNotifications -> {
|
||||
|
@ -138,6 +138,7 @@ object Preferences : KotprefModel() {
|
||||
var mediaPlayerArtist by stringPref(default = "")
|
||||
var mediaPlayerPackage by stringPref(default = "")
|
||||
var musicPlayersFilter by stringPref(default = "")
|
||||
var appNotificationsFilter by stringPref(default = "")
|
||||
|
||||
// Integrations
|
||||
var installedIntegrations by intPref(default = 0)
|
||||
|
@ -1,15 +1,14 @@
|
||||
package com.tommasoberlose.anotherwidget.helpers
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.service.notification.StatusBarNotification
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.chibatching.kotpref.Kotpref
|
||||
import com.chibatching.kotpref.blockingBulk
|
||||
import com.google.gson.Gson
|
||||
import com.tommasoberlose.anotherwidget.global.Preferences
|
||||
import java.lang.Exception
|
||||
import com.tommasoberlose.anotherwidget.receivers.NotificationListener
|
||||
|
||||
object ActiveNotificationsHelper {
|
||||
fun showLastNotification(): Boolean {
|
||||
@ -25,4 +24,22 @@ object ActiveNotificationsHelper {
|
||||
remove(Preferences::lastNotificationIcon)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkNotificationAccess(context: Context): Boolean {
|
||||
val contentResolver: ContentResolver = context.contentResolver
|
||||
val enabledNotificationListeners =
|
||||
Settings.Secure.getString(contentResolver, "enabled_notification_listeners")
|
||||
val packageName: String = context.packageName
|
||||
return NotificationManagerCompat.getEnabledListenerPackages(context).contains(packageName) && (enabledNotificationListeners != null && enabledNotificationListeners.contains(NotificationListener::class.java.name))
|
||||
}
|
||||
|
||||
fun isAppAccepted(appPkg: String): Boolean = Preferences.appNotificationsFilter == "" || Preferences.appNotificationsFilter.contains(appPkg)
|
||||
|
||||
fun toggleAppFilter(appPkg: String) {
|
||||
if (Preferences.appNotificationsFilter == "" || !Preferences.appNotificationsFilter.contains(appPkg)) {
|
||||
Preferences.appNotificationsFilter = Preferences.appNotificationsFilter.split(",").union(listOf(appPkg)).joinToString(separator = ",")
|
||||
} else {
|
||||
Preferences.appNotificationsFilter = Preferences.appNotificationsFilter.split(",").filter { it != appPkg }.joinToString(separator = ",")
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ import android.media.MediaMetadata
|
||||
import android.media.session.MediaController
|
||||
import android.media.session.MediaSessionManager
|
||||
import android.media.session.PlaybackState
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.chibatching.kotpref.Kotpref
|
||||
import com.chibatching.kotpref.blockingBulk
|
||||
import com.chibatching.kotpref.bulk
|
||||
@ -16,7 +15,7 @@ import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
|
||||
import java.lang.Exception
|
||||
|
||||
object MediaPlayerHelper {
|
||||
fun isSomeonePlaying(context: Context) = Preferences.showMusic && NotificationManagerCompat.getEnabledListenerPackages(context).contains(context.packageName) && Preferences.mediaPlayerTitle != ""
|
||||
fun isSomeonePlaying(context: Context) = Preferences.showMusic && ActiveNotificationsHelper.checkNotificationAccess(context) && Preferences.mediaPlayerTitle != ""
|
||||
|
||||
fun getMediaInfo(): String {
|
||||
return if (Preferences.mediaPlayerArtist == "") {
|
||||
@ -28,7 +27,7 @@ object MediaPlayerHelper {
|
||||
|
||||
fun updatePlayingMediaInfo(context: Context) {
|
||||
Kotpref.init(context)
|
||||
if (NotificationManagerCompat.getEnabledListenerPackages(context).contains(context.packageName)) {
|
||||
if (ActiveNotificationsHelper.checkNotificationAccess(context)) {
|
||||
val list = try {
|
||||
(context.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager).getActiveSessions(
|
||||
ComponentName(context.packageName, NotificationListener::class.java.name)
|
||||
@ -89,7 +88,7 @@ object MediaPlayerHelper {
|
||||
}
|
||||
}
|
||||
|
||||
fun isMusicPlayerAccepted(appPkg: String): Boolean = Preferences.musicPlayersFilter.contains(appPkg)
|
||||
fun isMusicPlayerAccepted(appPkg: String): Boolean = Preferences.musicPlayersFilter == "" || Preferences.musicPlayersFilter.contains(appPkg)
|
||||
|
||||
fun toggleMusicPlayerFilter(appPkg: String) {
|
||||
if (Preferences.musicPlayersFilter == "" || !Preferences.musicPlayersFilter.contains(appPkg)) {
|
||||
|
@ -7,6 +7,7 @@ import android.media.session.MediaSession
|
||||
import android.os.Build
|
||||
import android.service.notification.NotificationListenerService
|
||||
import android.service.notification.StatusBarNotification
|
||||
import android.util.Log
|
||||
import com.tommasoberlose.anotherwidget.global.Actions
|
||||
import com.tommasoberlose.anotherwidget.global.Preferences
|
||||
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
|
||||
@ -30,7 +31,7 @@ class NotificationListener : NotificationListenerService() {
|
||||
val isGroupHeader = sbn.notification.flags and Notification.FLAG_GROUP_SUMMARY != 0
|
||||
val isOngoing = sbn.notification.flags and Notification.FLAG_ONGOING_EVENT != 0
|
||||
|
||||
if (bundle.containsKey(Notification.EXTRA_TITLE) && !isGroupHeader && !isOngoing) {
|
||||
if (bundle.containsKey(Notification.EXTRA_TITLE) && !isGroupHeader && !isOngoing && ActiveNotificationsHelper.isAppAccepted(sbn.packageName)) {
|
||||
Preferences.lastNotificationId = sbn.id
|
||||
Preferences.lastNotificationTitle = bundle.getString(Notification.EXTRA_TITLE) ?: ""
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
|
@ -0,0 +1,143 @@
|
||||
package com.tommasoberlose.anotherwidget.ui.activities
|
||||
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.bumptech.glide.Glide
|
||||
import com.tommasoberlose.anotherwidget.R
|
||||
import com.tommasoberlose.anotherwidget.databinding.ActivityAppNotificationsFilterBinding
|
||||
import com.tommasoberlose.anotherwidget.global.Preferences
|
||||
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
|
||||
import com.tommasoberlose.anotherwidget.ui.viewmodels.AppNotificationsViewModel
|
||||
import kotlinx.android.synthetic.main.activity_app_notifications_filter.*
|
||||
import kotlinx.coroutines.*
|
||||
import net.idik.lib.slimadapter.SlimAdapter
|
||||
|
||||
|
||||
class AppNotificationsFilterActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var adapter: SlimAdapter
|
||||
private lateinit var viewModel: AppNotificationsViewModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProvider(this).get(AppNotificationsViewModel::class.java)
|
||||
val binding = DataBindingUtil.setContentView<ActivityAppNotificationsFilterBinding>(this, R.layout.activity_app_notifications_filter)
|
||||
|
||||
list_view.setHasFixedSize(true)
|
||||
val mLayoutManager = LinearLayoutManager(this)
|
||||
list_view.layoutManager = mLayoutManager
|
||||
|
||||
adapter = SlimAdapter.create()
|
||||
adapter
|
||||
.register<ResolveInfo>(R.layout.application_info_layout) { item, injector ->
|
||||
injector
|
||||
.text(R.id.text, item.loadLabel(viewModel.pm))
|
||||
.with<ImageView>(R.id.icon) {
|
||||
Glide
|
||||
.with(this)
|
||||
.load(item.loadIcon(viewModel.pm))
|
||||
.centerCrop()
|
||||
.into(it)
|
||||
}
|
||||
.visible(R.id.checkBox)
|
||||
.clicked(R.id.item) {
|
||||
toggleApp(item)
|
||||
adapter.notifyItemRangeChanged(0, adapter.data.size)
|
||||
}
|
||||
.clicked(R.id.checkBox) {
|
||||
toggleApp(item)
|
||||
adapter.notifyItemRangeChanged(0, adapter.data.size)
|
||||
}
|
||||
.checked(R.id.checkBox, ActiveNotificationsHelper.isAppAccepted(item.activityInfo.packageName))
|
||||
}
|
||||
.attachTo(list_view)
|
||||
|
||||
setupListener()
|
||||
subscribeUi(binding, viewModel)
|
||||
|
||||
search.requestFocus()
|
||||
}
|
||||
|
||||
private var filterJob: Job? = null
|
||||
|
||||
private fun subscribeUi(binding: ActivityAppNotificationsFilterBinding, viewModel: AppNotificationsViewModel) {
|
||||
binding.viewModel = viewModel
|
||||
binding.lifecycleOwner = this
|
||||
|
||||
viewModel.appList.observe(this, Observer {
|
||||
updateList(list = it)
|
||||
loader.visibility = View.INVISIBLE
|
||||
})
|
||||
|
||||
viewModel.searchInput.observe(this, Observer { search ->
|
||||
updateList(search = search)
|
||||
clear_search.isVisible = search.isNotBlank()
|
||||
})
|
||||
|
||||
viewModel.appNotificationsFilter.observe(this, {
|
||||
updateList()
|
||||
clear_selection.isVisible = Preferences.appNotificationsFilter != ""
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateList(list: List<ResolveInfo>? = viewModel.appList.value, search: String? = viewModel.searchInput.value) {
|
||||
loader.visibility = View.VISIBLE
|
||||
filterJob?.cancel()
|
||||
filterJob = lifecycleScope.launch(Dispatchers.IO) {
|
||||
if (list != null && list.isNotEmpty()) {
|
||||
delay(200)
|
||||
val filteredList: List<ResolveInfo> = if (search == null || search == "") {
|
||||
list
|
||||
} else {
|
||||
list.filter {
|
||||
it.loadLabel(viewModel.pm).contains(search, true)
|
||||
}
|
||||
}.sortedWith { app1, app2 ->
|
||||
if (ActiveNotificationsHelper.isAppAccepted(app1.activityInfo.packageName) && ActiveNotificationsHelper.isAppAccepted(app2.activityInfo.packageName)) {
|
||||
app1.loadLabel(viewModel.pm).toString().compareTo(app2.loadLabel(viewModel.pm).toString(), ignoreCase = true)
|
||||
} else if (ActiveNotificationsHelper.isAppAccepted(app1.activityInfo.packageName)) {
|
||||
-1
|
||||
} else if (ActiveNotificationsHelper.isAppAccepted(app2.activityInfo.packageName)) {
|
||||
1
|
||||
} else {
|
||||
app1.loadLabel(viewModel.pm).toString().compareTo(app2.loadLabel(viewModel.pm).toString(), ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
adapter.updateData(filteredList)
|
||||
loader.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupListener() {
|
||||
action_back.setOnClickListener {
|
||||
onBackPressed()
|
||||
}
|
||||
|
||||
clear_search.setOnClickListener {
|
||||
viewModel.searchInput.value = ""
|
||||
}
|
||||
|
||||
clear_selection.setOnClickListener {
|
||||
Preferences.appNotificationsFilter = ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleApp(app: ResolveInfo) {
|
||||
ActiveNotificationsHelper.toggleAppFilter(app.activityInfo.packageName)
|
||||
}
|
||||
}
|
@ -142,7 +142,7 @@ class MusicPlayersFilterActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
clear_selection.setOnClickListener {
|
||||
Preferences.musicPlayersFilter = ","
|
||||
Preferences.musicPlayersFilter = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ import com.tommasoberlose.anotherwidget.components.GlanceSettingsDialog
|
||||
import com.tommasoberlose.anotherwidget.databinding.FragmentGlanceSettingsBinding
|
||||
import com.tommasoberlose.anotherwidget.global.Constants
|
||||
import com.tommasoberlose.anotherwidget.global.Preferences
|
||||
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
|
||||
import com.tommasoberlose.anotherwidget.helpers.AlarmHelper
|
||||
import com.tommasoberlose.anotherwidget.helpers.GlanceProviderHelper
|
||||
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
|
||||
@ -121,9 +122,7 @@ class GlanceTabFragment : Fragment() {
|
||||
when (provider) {
|
||||
Constants.GlanceProviderId.PLAYING_SONG -> {
|
||||
when {
|
||||
NotificationManagerCompat.getEnabledListenerPackages(requireContext())
|
||||
.contains(
|
||||
requireContext().packageName) -> {
|
||||
ActiveNotificationsHelper.checkNotificationAccess(requireContext()) -> {
|
||||
MediaPlayerHelper.updatePlayingMediaInfo(requireContext())
|
||||
injector.visibility(R.id.error_icon, View.GONE)
|
||||
injector.visibility(R.id.info_icon, View.VISIBLE)
|
||||
@ -167,9 +166,7 @@ class GlanceTabFragment : Fragment() {
|
||||
}
|
||||
Constants.GlanceProviderId.NOTIFICATIONS -> {
|
||||
when {
|
||||
NotificationManagerCompat.getEnabledListenerPackages(requireContext())
|
||||
.contains(
|
||||
requireContext().packageName) -> {
|
||||
ActiveNotificationsHelper.checkNotificationAccess(requireContext()) -> {
|
||||
injector.visibility(R.id.error_icon, View.GONE)
|
||||
injector.visibility(R.id.info_icon, View.VISIBLE)
|
||||
injector.text(R.id.label,
|
||||
@ -305,6 +302,7 @@ class GlanceTabFragment : Fragment() {
|
||||
adapter.updateData(
|
||||
GlanceProviderHelper.getGlanceProviders(requireContext())
|
||||
.mapNotNull { GlanceProviderHelper.getGlanceProviderById(requireContext(), it) }
|
||||
.filterNot { it.id == Constants.GlanceProviderId.GREETINGS.id }
|
||||
)
|
||||
providers_list.isNestedScrollingEnabled = false
|
||||
|
||||
|
@ -72,8 +72,6 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
|
||||
subscribeUi(viewModel)
|
||||
|
||||
// Viewpager
|
||||
pager.adapter = ViewPagerAdapter(requireActivity())
|
||||
pager.offscreenPageLimit = 4
|
||||
@ -89,17 +87,20 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
}.attach()
|
||||
|
||||
// Init clock
|
||||
time.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
|
||||
time.setTextSize(TypedValue.COMPLEX_UNIT_SP, Preferences.clockTextSize.toPixel(requireContext()))
|
||||
time_am_pm.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
|
||||
time_am_pm.setTextSize(TypedValue.COMPLEX_UNIT_SP, Preferences.clockTextSize.toPixel(requireContext()) / 5 * 2)
|
||||
if (Preferences.showClock) {
|
||||
time.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
|
||||
time.setTextSize(TypedValue.COMPLEX_UNIT_SP,
|
||||
Preferences.clockTextSize.toPixel(requireContext()))
|
||||
time_am_pm.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
|
||||
time_am_pm.setTextSize(TypedValue.COMPLEX_UNIT_SP,
|
||||
Preferences.clockTextSize.toPixel(requireContext()) / 5 * 2)
|
||||
}
|
||||
time_container.isVisible = Preferences.showClock
|
||||
|
||||
preview.layoutParams = preview.layoutParams.apply {
|
||||
height = PREVIEW_BASE_HEIGHT.toPixel(requireContext()) + if (Preferences.showClock) 100.toPixel(requireContext()) else 0
|
||||
}
|
||||
subscribeUi(viewModel)
|
||||
updateUI()
|
||||
|
||||
// Warnings
|
||||
if (getString(R.string.xiaomi_manufacturer).equals(Build.MANUFACTURER, ignoreCase = true) && Preferences.showXiaomiWarning) {
|
||||
@ -361,7 +362,7 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
tabs?.getTabAt(4)?.orCreateBadge?.apply {
|
||||
backgroundColor = ContextCompat.getColor(requireContext(), R.color.errorColorText)
|
||||
badgeGravity = BadgeDrawable.TOP_END
|
||||
}?.isVisible = ((Preferences.showMusic || Preferences.showNotifications) && !NotificationManagerCompat.getEnabledListenerPackages(requireContext()).contains(requireContext().packageName)) ||
|
||||
}?.isVisible = ((Preferences.showMusic || Preferences.showNotifications) && !ActiveNotificationsHelper.checkNotificationAccess(requireContext())) ||
|
||||
(Preferences.showDailySteps && !(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || requireActivity().checkGrantedPermission(Manifest.permission.ACTIVITY_RECOGNITION))) ||
|
||||
(AlarmHelper.isAlarmProbablyWrong(requireContext()))
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
package com.tommasoberlose.anotherwidget.ui.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ResolveInfo
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.chibatching.kotpref.livedata.asLiveData
|
||||
import com.tommasoberlose.anotherwidget.global.Preferences
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class AppNotificationsViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
val pm: PackageManager by lazy { application.packageManager }
|
||||
val appList: MutableLiveData<List<ResolveInfo>> = MutableLiveData()
|
||||
val searchInput: MutableLiveData<String> = MutableLiveData("")
|
||||
var appNotificationsFilter = Preferences.asLiveData(Preferences::appNotificationsFilter)
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val mainIntent = Intent(Intent.ACTION_MAIN, null).apply {
|
||||
addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
}
|
||||
|
||||
val app = application.packageManager.queryIntentActivities(mainIntent, 0)
|
||||
val sortedApp = app.sortedWith(Comparator { app1: ResolveInfo, app2: ResolveInfo ->
|
||||
app1.loadLabel(pm).toString().compareTo(app2.loadLabel(pm).toString())
|
||||
})
|
||||
withContext(Dispatchers.Main) {
|
||||
appList.postValue(sortedApp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user