Add notification visibility settings
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
package se.ajpanton.notificationsmaster
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
import se.ajpanton.notificationsmaster.databinding.FragmentAppVisibilityBinding
|
||||
import se.ajpanton.notificationsmaster.visibility.AppVisibilityPolicy
|
||||
import se.ajpanton.notificationsmaster.visibility.NotificationSurface
|
||||
import se.ajpanton.notificationsmaster.visibility.VisibilityExceptionRule
|
||||
import se.ajpanton.notificationsmaster.visibility.VisibilityPolicyStore
|
||||
import java.util.Collections
|
||||
|
||||
class AppVisibilityFragment : Fragment(R.layout.fragment_app_visibility) {
|
||||
private var binding: FragmentAppVisibilityBinding? = null
|
||||
private lateinit var store: VisibilityPolicyStore
|
||||
private lateinit var packageName: String
|
||||
private val adapter = ExceptionAdapter(::editException, ::deleteException)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding = FragmentAppVisibilityBinding.bind(view)
|
||||
store = VisibilityPolicyStore(requireContext())
|
||||
packageName = requireArguments().getString(ARG_PACKAGE)!!
|
||||
binding!!.appName.text = InstalledApps.name(requireContext(), packageName)
|
||||
binding!!.packageName.text = packageName
|
||||
binding!!.exceptions.layoutManager = LinearLayoutManager(requireContext())
|
||||
binding!!.exceptions.adapter = adapter
|
||||
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(UP or DOWN, 0) {
|
||||
override fun onMove(list: RecyclerView, from: RecyclerView.ViewHolder, to: RecyclerView.ViewHolder): Boolean {
|
||||
adapter.move(from.bindingAdapterPosition, to.bindingAdapterPosition)
|
||||
saveApp { copy(exceptions = adapter.rules()) }
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(holder: RecyclerView.ViewHolder, direction: Int) = Unit
|
||||
}).attachToRecyclerView(binding!!.exceptions)
|
||||
binding!!.addException.setOnClickListener { showExceptionDialog(null) }
|
||||
bind()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
activity?.title = getString(R.string.page_notification_visibility)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun bind() {
|
||||
val policy = appPolicy()
|
||||
bindSurface(binding!!.hideUnlocked, NotificationSurface.UNLOCKED_STATUSBAR, policy)
|
||||
bindSurface(binding!!.hideLockscreen, NotificationSurface.LOCKSCREEN_COLLAPSED, policy)
|
||||
bindSurface(binding!!.hideAod, NotificationSurface.AOD, policy)
|
||||
adapter.submit(policy.exceptions)
|
||||
}
|
||||
|
||||
private fun bindSurface(toggle: SwitchMaterial, surface: NotificationSurface, policy: AppVisibilityPolicy) {
|
||||
toggle.setOnCheckedChangeListener(null)
|
||||
toggle.isChecked = surface in policy.blockedSurfaces
|
||||
toggle.setOnCheckedChangeListener { _, hidden ->
|
||||
saveApp {
|
||||
copy(blockedSurfaces = blockedSurfaces.toMutableSet().apply {
|
||||
if (hidden) add(surface) else remove(surface)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun appPolicy() = store.load().apps.firstOrNull { it.packageName == packageName }
|
||||
?: AppVisibilityPolicy(packageName)
|
||||
|
||||
private fun saveApp(change: AppVisibilityPolicy.() -> AppVisibilityPolicy) {
|
||||
store.update { policy ->
|
||||
val updated = (policy.apps.firstOrNull { it.packageName == packageName }
|
||||
?: AppVisibilityPolicy(packageName)).change()
|
||||
val apps = policy.apps.filterNot { it.packageName == packageName }.toMutableList()
|
||||
if (updated.blockedSurfaces.isNotEmpty() || updated.exceptions.isNotEmpty()) apps += updated
|
||||
policy.copy(apps = apps.sortedBy { it.packageName })
|
||||
}
|
||||
}
|
||||
|
||||
private fun editException(position: Int) = showExceptionDialog(position)
|
||||
|
||||
private fun deleteException(position: Int) {
|
||||
saveApp { copy(exceptions = exceptions.toMutableList().apply { removeAt(position) }) }
|
||||
bind()
|
||||
}
|
||||
|
||||
private fun showExceptionDialog(position: Int?) {
|
||||
val existing = position?.let { appPolicy().exceptions[it] }
|
||||
val content = LinearLayout(requireContext()).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(dp(24), 0, dp(24), 0)
|
||||
}
|
||||
val pattern = EditText(requireContext()).apply {
|
||||
hint = "Regular expression"
|
||||
setText(existing?.pattern)
|
||||
maxLines = 4
|
||||
}
|
||||
content.addView(pattern)
|
||||
val toggles = listOf(
|
||||
NotificationSurface.UNLOCKED_STATUSBAR to "Hide in unlocked status bar",
|
||||
NotificationSurface.LOCKSCREEN_COLLAPSED to "Hide on collapsed lockscreen",
|
||||
NotificationSurface.AOD to "Hide on always-on display",
|
||||
).associate { (surface, label) ->
|
||||
surface to SwitchMaterial(requireContext()).apply {
|
||||
text = label
|
||||
isChecked = surface in existing?.blockedSurfaces.orEmpty()
|
||||
content.addView(this)
|
||||
}
|
||||
}
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(if (existing == null) "Add text exception" else "Edit text exception")
|
||||
.setView(content)
|
||||
.setNegativeButton("Cancel", null)
|
||||
.setPositiveButton("Save", null)
|
||||
.create()
|
||||
dialog.setOnShowListener {
|
||||
dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
val rule = runCatching {
|
||||
VisibilityExceptionRule(
|
||||
pattern.text.toString(),
|
||||
toggles.filterValues { it.isChecked }.keys,
|
||||
).also { require(it.hasValidPattern()) }
|
||||
}.getOrElse {
|
||||
pattern.error = "Enter a valid, non-empty regular expression"
|
||||
return@setOnClickListener
|
||||
}
|
||||
saveApp {
|
||||
copy(exceptions = exceptions.toMutableList().apply {
|
||||
if (position == null) add(rule) else set(position, rule)
|
||||
})
|
||||
}
|
||||
bind()
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun dp(value: Int) = (value * resources.displayMetrics.density).toInt()
|
||||
|
||||
private class ExceptionAdapter(
|
||||
private val edit: (Int) -> Unit,
|
||||
private val delete: (Int) -> Unit,
|
||||
) : RecyclerView.Adapter<ExceptionHolder>() {
|
||||
private val items = mutableListOf<VisibilityExceptionRule>()
|
||||
|
||||
fun submit(rules: List<VisibilityExceptionRule>) {
|
||||
items.clear()
|
||||
items.addAll(rules)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun move(from: Int, to: Int) {
|
||||
Collections.swap(items, from, to)
|
||||
notifyItemMoved(from, to)
|
||||
}
|
||||
|
||||
fun rules() = items.toList()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExceptionHolder {
|
||||
val row = LinearLayout(parent.context).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = android.view.Gravity.CENTER_VERTICAL
|
||||
setPadding(0, dp(parent, 6), 0, dp(parent, 6))
|
||||
isClickable = true
|
||||
isFocusable = true
|
||||
background = context.obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackground))
|
||||
.let { values -> values.getDrawable(0).also { values.recycle() } }
|
||||
}
|
||||
val handle = TextView(parent.context).apply {
|
||||
text = "↕"
|
||||
textSize = 22f
|
||||
gravity = android.view.Gravity.CENTER
|
||||
contentDescription = "Drag to reorder"
|
||||
layoutParams = LinearLayout.LayoutParams(dp(parent, 40), dp(parent, 48))
|
||||
}
|
||||
val textColumn = LinearLayout(parent.context).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
}
|
||||
val pattern = TextView(parent.context).apply { textSize = 15f }
|
||||
val summary = TextView(parent.context).apply { textSize = 12f }
|
||||
textColumn.addView(pattern)
|
||||
textColumn.addView(summary)
|
||||
val remove = Button(parent.context).apply {
|
||||
text = "Delete"
|
||||
isAllCaps = false
|
||||
}
|
||||
row.addView(handle)
|
||||
row.addView(textColumn)
|
||||
row.addView(remove)
|
||||
return ExceptionHolder(row, pattern, summary, remove)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ExceptionHolder, position: Int) {
|
||||
val rule = items[position]
|
||||
holder.pattern.text = rule.pattern
|
||||
holder.summary.text = surfaceSummary(rule.blockedSurfaces)
|
||||
holder.itemView.setOnClickListener {
|
||||
holder.bindingAdapterPosition.takeIf { it != RecyclerView.NO_POSITION }?.let(edit)
|
||||
}
|
||||
holder.remove.setOnClickListener {
|
||||
holder.bindingAdapterPosition.takeIf { it != RecyclerView.NO_POSITION }?.let(delete)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
private fun surfaceSummary(surfaces: Set<NotificationSurface>): String {
|
||||
val names = listOf(
|
||||
NotificationSurface.UNLOCKED_STATUSBAR to "status bar",
|
||||
NotificationSurface.LOCKSCREEN_COLLAPSED to "lockscreen",
|
||||
NotificationSurface.AOD to "AOD",
|
||||
).filter { it.first in surfaces }.joinToString { it.second }
|
||||
return if (names.isEmpty()) "Show on all surfaces" else "Hide: $names"
|
||||
}
|
||||
|
||||
private fun dp(parent: ViewGroup, value: Int) =
|
||||
(value * parent.resources.displayMetrics.density).toInt()
|
||||
}
|
||||
|
||||
private class ExceptionHolder(
|
||||
view: View,
|
||||
val pattern: TextView,
|
||||
val summary: TextView,
|
||||
val remove: Button,
|
||||
) : RecyclerView.ViewHolder(view)
|
||||
|
||||
companion object {
|
||||
private const val ARG_PACKAGE = "package"
|
||||
private const val UP = ItemTouchHelper.UP
|
||||
private const val DOWN = ItemTouchHelper.DOWN
|
||||
|
||||
fun newInstance(packageName: String) = AppVisibilityFragment().apply {
|
||||
arguments = Bundle().apply { putString(ARG_PACKAGE, packageName) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@@ -166,6 +167,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
page == Page.PROFILES -> ProfilesFragment()
|
||||
page == Page.RULES -> RulesFragment()
|
||||
page == Page.APPS -> AppsFragment()
|
||||
page == Page.NOTIFICATION_VISIBILITY -> NotificationVisibilityFragment()
|
||||
page == Page.LOG_DISPLAY -> LogDisplayFragment()
|
||||
page == Page.FILTER_LOGGING -> FilterLoggingFragment()
|
||||
page == Page.FILTER_APPS -> FilterAppsFragment.newInstance()
|
||||
@@ -281,6 +283,14 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
|
||||
binding.navViewDrawer.visibility = View.VISIBLE
|
||||
}
|
||||
(binding.contentFrame.layoutParams as RelativeLayout.LayoutParams).also { params ->
|
||||
params.removeRule(if (permanentSidebar) RelativeLayout.BELOW else RelativeLayout.ALIGN_PARENT_TOP)
|
||||
params.addRule(
|
||||
if (permanentSidebar) RelativeLayout.ALIGN_PARENT_TOP else RelativeLayout.BELOW,
|
||||
if (permanentSidebar) RelativeLayout.TRUE else R.id.toolbar,
|
||||
)
|
||||
binding.contentFrame.layoutParams = params
|
||||
}
|
||||
updateNavigationHeaderHeight(binding.navViewDrawer)
|
||||
updateNavigationHeaderHeight(binding.navViewPermanent)
|
||||
applyPageTitleVisibility()
|
||||
@@ -362,6 +372,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
PROFILES(R.id.nav_profiles, R.string.page_profiles),
|
||||
RULES(R.id.nav_rules, R.string.page_rules),
|
||||
APPS(R.id.nav_apps, R.string.page_apps),
|
||||
NOTIFICATION_VISIBILITY(R.id.nav_notification_visibility, R.string.page_notification_visibility),
|
||||
LOG_DISPLAY(R.id.nav_log_display, R.string.page_log_display),
|
||||
FILTER_LOGGING(R.id.nav_filter_logging, R.string.page_filter_logging),
|
||||
FILTER_APPS(R.id.nav_filter_apps, R.string.page_filter_apps),
|
||||
|
||||
@@ -7,6 +7,7 @@ import se.ajpanton.notificationsmaster.module.VisibilityPolicySync
|
||||
import se.ajpanton.notificationsmaster.settings.AppFilterSettingsStore
|
||||
import se.ajpanton.notificationsmaster.settings.NotificationListenerComponentController
|
||||
import se.ajpanton.notificationsmaster.settings.PerAppEventSettingsStore
|
||||
import se.ajpanton.notificationsmaster.visibility.VisibilityPolicyStore
|
||||
|
||||
class NotificationLogApplication : Application() {
|
||||
override fun onCreate() {
|
||||
@@ -31,6 +32,7 @@ class NotificationLogApplication : Application() {
|
||||
).mapTo(mutableSetOf()) { it.packageName }
|
||||
AppFilterSettingsStore(this).removeUninstalledPackages(installedPackages)
|
||||
PerAppEventSettingsStore(this).removeUninstalledPackages(installedPackages)
|
||||
VisibilityPolicyStore(this).removeUninstalledPackages(installedPackages)
|
||||
EncryptedNotificationLogStore(this).removeOrphanedImages()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package se.ajpanton.notificationsmaster
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import se.ajpanton.notificationsmaster.databinding.FragmentNotificationVisibilityBinding
|
||||
import se.ajpanton.notificationsmaster.visibility.AppVisibilityPolicy
|
||||
import se.ajpanton.notificationsmaster.visibility.NotificationSurface
|
||||
import se.ajpanton.notificationsmaster.visibility.VisibilityPolicyStore
|
||||
|
||||
class NotificationVisibilityFragment : Fragment(R.layout.fragment_notification_visibility) {
|
||||
private var binding: FragmentNotificationVisibilityBinding? = null
|
||||
private lateinit var store: VisibilityPolicyStore
|
||||
private val adapter = AppAdapter(::openApp)
|
||||
private var apps = emptyList<InstalledApp>()
|
||||
private var appPolicies = emptyMap<String, AppVisibilityPolicy>()
|
||||
private var loadGeneration = 0
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding = FragmentNotificationVisibilityBinding.bind(view)
|
||||
store = VisibilityPolicyStore(requireContext())
|
||||
binding!!.appList.layoutManager = LinearLayoutManager(requireContext())
|
||||
binding!!.appList.adapter = adapter
|
||||
binding!!.appSearch.doAfterTextChanged { showApps() }
|
||||
bindEnabled()
|
||||
loadApps()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
activity?.title = getString(R.string.page_notification_visibility)
|
||||
if (binding != null) {
|
||||
bindEnabled()
|
||||
showApps()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
loadGeneration++
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun bindEnabled() {
|
||||
val toggle = binding?.visibilityEnabled ?: return
|
||||
val policy = store.load()
|
||||
appPolicies = policy.apps.associateBy(AppVisibilityPolicy::packageName)
|
||||
toggle.setOnCheckedChangeListener(null)
|
||||
toggle.isChecked = policy.enabled
|
||||
toggle.setOnCheckedChangeListener { _, enabled -> store.update { it.copy(enabled = enabled) } }
|
||||
}
|
||||
|
||||
private fun loadApps() {
|
||||
val currentBinding = binding ?: return
|
||||
val generation = ++loadGeneration
|
||||
val context = requireContext().applicationContext
|
||||
currentBinding.appListLoading.visibility = View.VISIBLE
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
val loaded = withContext(Dispatchers.IO) { InstalledApps.all(context) }
|
||||
if (binding === currentBinding && generation == loadGeneration) {
|
||||
apps = loaded
|
||||
currentBinding.appListLoading.visibility = View.GONE
|
||||
showApps()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showApps() {
|
||||
val currentBinding = binding ?: return
|
||||
val terms = currentBinding.appSearch.text.toString().lowercase().trim()
|
||||
.split(Regex("\\s+")).filter(String::isNotEmpty)
|
||||
adapter.submit(apps.filter { app ->
|
||||
val searchable = "${app.name}\n${app.packageName}".lowercase()
|
||||
terms.all(searchable::contains)
|
||||
}.map { app -> AppRow(app, appPolicies[app.packageName]) })
|
||||
}
|
||||
|
||||
private fun openApp(app: InstalledApp) = parentFragmentManager.commit {
|
||||
replace(R.id.content_frame, AppVisibilityFragment.newInstance(app.packageName))
|
||||
addToBackStack(null)
|
||||
}
|
||||
|
||||
private data class AppRow(val app: InstalledApp, val policy: AppVisibilityPolicy?)
|
||||
|
||||
private class AppAdapter(private val open: (InstalledApp) -> Unit) : RecyclerView.Adapter<AppHolder>() {
|
||||
private var rows = emptyList<AppRow>()
|
||||
|
||||
fun submit(newRows: List<AppRow>) {
|
||||
rows = newRows
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppHolder {
|
||||
val row = LinearLayout(parent.context).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
isClickable = true
|
||||
isFocusable = true
|
||||
setPadding(0, dp(parent, 8), 0, dp(parent, 8))
|
||||
background = context.obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackground))
|
||||
.let { values -> values.getDrawable(0).also { values.recycle() } }
|
||||
}
|
||||
val name = TextView(parent.context).apply {
|
||||
textSize = 16f
|
||||
setTypeface(typeface, android.graphics.Typeface.BOLD)
|
||||
}
|
||||
val packageName = TextView(parent.context).apply { textSize = 12f }
|
||||
val summary = TextView(parent.context).apply { textSize = 13f }
|
||||
row.addView(name)
|
||||
row.addView(packageName)
|
||||
row.addView(summary)
|
||||
return AppHolder(row, name, packageName, summary)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: AppHolder, position: Int) {
|
||||
val row = rows[position]
|
||||
holder.name.text = row.app.name
|
||||
holder.packageName.text = row.app.packageName
|
||||
holder.summary.text = summary(row.policy)
|
||||
holder.itemView.setOnClickListener { open(row.app) }
|
||||
}
|
||||
|
||||
override fun getItemCount() = rows.size
|
||||
|
||||
private fun summary(policy: AppVisibilityPolicy?): String {
|
||||
if (policy == null) return "Visible on all surfaces"
|
||||
val hidden = listOf(
|
||||
NotificationSurface.UNLOCKED_STATUSBAR to "status bar",
|
||||
NotificationSurface.LOCKSCREEN_COLLAPSED to "lockscreen",
|
||||
NotificationSurface.AOD to "AOD",
|
||||
).filter { it.first in policy.blockedSurfaces }.joinToString { it.second }
|
||||
val defaults = if (hidden.isEmpty()) "Visible on all surfaces" else "Hidden: $hidden"
|
||||
return if (policy.exceptions.isEmpty()) defaults else "$defaults • ${policy.exceptions.size} text exception" +
|
||||
if (policy.exceptions.size == 1) "" else "s"
|
||||
}
|
||||
|
||||
private fun dp(parent: ViewGroup, value: Int) =
|
||||
(value * parent.resources.displayMetrics.density).toInt()
|
||||
}
|
||||
|
||||
private class AppHolder(
|
||||
view: View,
|
||||
val name: TextView,
|
||||
val packageName: TextView,
|
||||
val summary: TextView,
|
||||
) : RecyclerView.ViewHolder(view)
|
||||
}
|
||||
@@ -58,6 +58,16 @@ class VisibilityPolicyStore(context: Context) {
|
||||
return stored
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun update(change: (VisibilityPolicy) -> VisibilityPolicy) = save(change(load()))
|
||||
|
||||
@Synchronized
|
||||
fun removeUninstalledPackages(installedPackages: Set<String>) {
|
||||
val current = load()
|
||||
val retained = current.apps.filter { it.packageName in installedPackages }
|
||||
if (retained.size != current.apps.size) save(current.copy(apps = retained))
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val FILE_NAME = "visibility-policy.bin"
|
||||
const val MAX_CIPHER_TEXT_BYTES = 1024 * 1024
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/page_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceHeadline6"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/package_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceBody2" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="Hide by default"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/hide_unlocked"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Unlocked status bar" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/hide_lockscreen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Collapsed lockscreen" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/hide_aod"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Always-on display" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Ordered text exceptions"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/add_exception"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Add" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="The first matching expression replaces the defaults above. Drag to reorder."
|
||||
android:textAppearance="?attr/textAppearanceBody2" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/exceptions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_weight="1"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="16dp" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/page_padding">
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/visibility_enabled"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Enable notification visibility rules" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Requires LSPosed. Unsupported surfaces remain unchanged."
|
||||
android:textAppearance="?attr/textAppearanceBody2" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/app_search"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="Filter apps"
|
||||
android:inputType="text"
|
||||
android:maxLines="1" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/app_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:fastScrollAlwaysVisible="true"
|
||||
android:fastScrollEnabled="true"
|
||||
android:paddingBottom="16dp" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/app_list_loading"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
@@ -21,6 +21,9 @@
|
||||
<item
|
||||
android:id="@+id/nav_apps"
|
||||
android:title="@string/navigation_indented_apps" />
|
||||
<item
|
||||
android:id="@+id/nav_notification_visibility"
|
||||
android:title="@string/navigation_indented_notification_visibility" />
|
||||
</group>
|
||||
<group android:checkableBehavior="single">
|
||||
<item
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<string name="notification_listener_label">Notifications Master listener</string>
|
||||
<string name="navigation_open">Open navigation</string>
|
||||
<string name="navigation_close">Close navigation</string>
|
||||
<string name="navigation_alerts_header">Alert control</string>
|
||||
<string name="navigation_alerts_header">Notification control</string>
|
||||
<string name="navigation_logging_header">Logging</string>
|
||||
<string name="page_view_logs">View logs</string>
|
||||
<string name="page_settings">Settings</string>
|
||||
@@ -13,6 +13,7 @@
|
||||
<string name="page_profiles">Profiles</string>
|
||||
<string name="page_rules">Rules</string>
|
||||
<string name="page_apps">Apps</string>
|
||||
<string name="page_notification_visibility">Notification visibility</string>
|
||||
<string name="page_filter_apps">Filter apps</string>
|
||||
<string name="page_log_display">Log display</string>
|
||||
<string name="page_filter_logging">Filter logging</string>
|
||||
@@ -21,6 +22,7 @@
|
||||
<string name="navigation_indented_profiles">    Profiles</string>
|
||||
<string name="navigation_indented_rules">    Rules</string>
|
||||
<string name="navigation_indented_apps">    Apps</string>
|
||||
<string name="navigation_indented_notification_visibility">    Visibility</string>
|
||||
<string name="navigation_indented_filter_logging">    Filter logging</string>
|
||||
<string name="navigation_indented_filter_apps">    Filter apps</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user