Port OneUI miscellaneous display settings
This commit is contained in:
@@ -170,6 +170,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
page == Page.NOTIFICATION_VISIBILITY -> NotificationVisibilityFragment()
|
||||
page == Page.CARDS_LAYOUT -> CardsLayoutFragment()
|
||||
page == Page.DEBUG_NOTIFICATIONS -> DebugNotificationsFragment()
|
||||
page == Page.MISCELLANEOUS -> MiscellaneousFragment()
|
||||
page == Page.LOG_DISPLAY -> LogDisplayFragment()
|
||||
page == Page.FILTER_LOGGING -> FilterLoggingFragment()
|
||||
page == Page.FILTER_APPS -> FilterAppsFragment.newInstance()
|
||||
@@ -377,6 +378,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
NOTIFICATION_VISIBILITY(R.id.nav_notification_visibility, R.string.page_notification_visibility),
|
||||
CARDS_LAYOUT(R.id.nav_cards_layout, R.string.page_cards_layout),
|
||||
DEBUG_NOTIFICATIONS(R.id.nav_debug_notifications, R.string.page_debug_notifications),
|
||||
MISCELLANEOUS(R.id.nav_miscellaneous, R.string.page_miscellaneous),
|
||||
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),
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package se.ajpanton.notificationsmaster
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
import se.ajpanton.notificationsmaster.databinding.FragmentMiscellaneousBinding
|
||||
import se.ajpanton.notificationsmaster.visibility.SystemUiMiscSettings
|
||||
import se.ajpanton.notificationsmaster.visibility.VisibilityPolicyStore
|
||||
|
||||
class MiscellaneousFragment : Fragment(R.layout.fragment_miscellaneous) {
|
||||
private var binding: FragmentMiscellaneousBinding? = null
|
||||
private lateinit var store: VisibilityPolicyStore
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding = FragmentMiscellaneousBinding.bind(view)
|
||||
store = VisibilityPolicyStore(requireContext())
|
||||
val settings = store.load().systemUiMisc
|
||||
bind(binding!!.keepAodVisibleDuringCalls, settings.keepAodVisibleDuringCalls) { current, checked ->
|
||||
current.copy(keepAodVisibleDuringCalls = checked)
|
||||
}
|
||||
bind(binding!!.showLockscreenBatteryPercent, settings.showLockscreenBatteryPercentWhenUnplugged) {
|
||||
current, checked ->
|
||||
current.copy(showLockscreenBatteryPercentWhenUnplugged = checked)
|
||||
}
|
||||
bind(binding!!.showAodBatteryPercent, settings.showAodBatteryPercentWhenUnplugged) { current, checked ->
|
||||
current.copy(showAodBatteryPercentWhenUnplugged = checked)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
activity?.title = getString(R.string.page_miscellaneous)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun bind(
|
||||
toggle: SwitchMaterial,
|
||||
initial: Boolean,
|
||||
update: (SystemUiMiscSettings, Boolean) -> SystemUiMiscSettings,
|
||||
) {
|
||||
toggle.isChecked = initial
|
||||
toggle.setOnCheckedChangeListener { _, checked ->
|
||||
store.update { policy -> policy.copy(systemUiMisc = update(policy.systemUiMisc, checked)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ class NotificationsMasterModule : XposedModule() {
|
||||
OneUiLockscreenBackend(this),
|
||||
OneUiLockscreenCardsBackend(this),
|
||||
SamsungAodPluginBackend(this),
|
||||
OneUiMiscBackend(this),
|
||||
).filter { it.install(param.classLoader) }
|
||||
ProcessVisibilityPolicyCache.installWhenReady {
|
||||
systemUiBackends.orEmpty().forEach(VisibilitySurfaceBackend::onPolicyChanged)
|
||||
|
||||
@@ -0,0 +1,422 @@
|
||||
package se.ajpanton.notificationsmaster.module
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.ColorStateList
|
||||
import android.media.AudioManager
|
||||
import android.os.BatteryManager
|
||||
import android.os.Build
|
||||
import android.telecom.TelecomManager
|
||||
import android.telephony.TelephonyManager
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import io.github.libxposed.api.XposedInterface
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
import java.text.NumberFormat
|
||||
import java.util.WeakHashMap
|
||||
|
||||
/** OneUI information-display options ported from StatusBarTweak's Misc page. */
|
||||
internal class OneUiMiscBackend(private val framework: XposedInterface) : VisibilitySurfaceBackend {
|
||||
private val callVisibility = AodCallVisibilityController(framework)
|
||||
private val lockscreenBattery = LockscreenBatteryController(framework)
|
||||
private val aodBattery = AodBatteryController(framework)
|
||||
|
||||
override fun install(classLoader: ClassLoader): Boolean {
|
||||
if (!Build.MANUFACTURER.equals("samsung", ignoreCase = true)) return false
|
||||
val installed = listOf(
|
||||
callVisibility.install(classLoader),
|
||||
lockscreenBattery.install(classLoader),
|
||||
aodBattery.install(classLoader),
|
||||
).count { it }
|
||||
if (installed > 0) Log.i(TAG, "Installed $installed OneUI miscellaneous controllers")
|
||||
return installed > 0
|
||||
}
|
||||
|
||||
override fun onPolicyChanged() = aodBattery.refresh()
|
||||
|
||||
private companion object { const val TAG = "NotificationsMaster" }
|
||||
}
|
||||
|
||||
private class AodCallVisibilityController(private val framework: XposedInterface) {
|
||||
fun install(classLoader: ClassLoader): Boolean {
|
||||
var hooks = 0
|
||||
listOf(
|
||||
"com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks" to "suppressAmbientDisplay",
|
||||
"com.android.systemui.statusbar.CommandQueue" to "suppressAmbientDisplay",
|
||||
"com.android.systemui.statusbar.phone.SecLsScrimControlHelper" to "setFrontScrimToBlack",
|
||||
"com.android.systemui.doze.DozeSuppressor\$1" to "onAlwaysOnSuppressedChanged",
|
||||
"com.android.systemui.doze.AODUi\$1" to "onAlwaysOnSuppressedChanged",
|
||||
).forEach { (className, methodName) ->
|
||||
classLoader.findClass(className)?.methodsNamed(methodName)?.forEach { method ->
|
||||
framework.intercept(method) { chain ->
|
||||
val value = chain.args.firstOrNull() as? Boolean
|
||||
if (value == true && shouldForceAodForCall()) {
|
||||
chain.proceed(chain.args.toTypedArray().apply { this[0] = false })
|
||||
} else chain.proceed()
|
||||
}
|
||||
hooks++
|
||||
}
|
||||
}
|
||||
classLoader.findClass("com.android.systemui.doze.DozeMachine")?.let { type ->
|
||||
listOf("requestState", "transitionTo").forEach { name ->
|
||||
type.methodsNamed(name).forEach { method ->
|
||||
framework.intercept(method) { chain ->
|
||||
val requested = chain.args.firstOrNull() as? Enum<*>
|
||||
val replacement = requested?.takeIf { shouldForceAodForCall() }?.aodReplacement()
|
||||
if (replacement == null) chain.proceed() else {
|
||||
chain.proceed(chain.args.toTypedArray().apply { this[0] = replacement })
|
||||
}
|
||||
}
|
||||
hooks++
|
||||
}
|
||||
}
|
||||
}
|
||||
classLoader.findClass("com.android.systemui.plugins.aod.PluginAODSystemUIConfiguration")
|
||||
?.methodsNamed("get")?.forEach { method ->
|
||||
framework.intercept(method) { chain ->
|
||||
val result = chain.proceed()
|
||||
if (shouldForceAodForCall() && chain.args.firstOrNull() == PLUGIN_KEY_PHONE_STATE &&
|
||||
(result as? Int ?: chain.args.getOrNull(1) as? Int) != TelephonyManager.CALL_STATE_IDLE
|
||||
) TelephonyManager.CALL_STATE_IDLE else result
|
||||
}
|
||||
hooks++
|
||||
}
|
||||
return hooks > 0
|
||||
}
|
||||
|
||||
private fun Enum<*>.aodReplacement(): Enum<*>? {
|
||||
if (name != "DOZE" && name != "FINISH") return null
|
||||
return runCatching {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
java.lang.Enum.valueOf(declaringJavaClass as Class<out Enum<*>>, "DOZE_AOD")
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
private fun shouldForceAodForCall(): Boolean {
|
||||
if (!ProcessVisibilityPolicyCache.systemUiMisc.keepAodVisibleDuringCalls) return false
|
||||
return currentApplicationContext()?.let(::isInCallLikeState) == true
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@Suppress("DEPRECATION")
|
||||
private fun isInCallLikeState(context: Context): Boolean {
|
||||
if (context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
|
||||
if (runCatching { context.getSystemService(TelecomManager::class.java)?.isInCall == true }.getOrDefault(false)) {
|
||||
return true
|
||||
}
|
||||
if (runCatching {
|
||||
context.getSystemService(TelephonyManager::class.java)?.callState !=
|
||||
TelephonyManager.CALL_STATE_IDLE
|
||||
}.getOrDefault(false)
|
||||
) return true
|
||||
}
|
||||
return runCatching {
|
||||
context.getSystemService(AudioManager::class.java)?.mode in
|
||||
setOf(AudioManager.MODE_IN_CALL, AudioManager.MODE_IN_COMMUNICATION)
|
||||
}.getOrDefault(false)
|
||||
}
|
||||
|
||||
private companion object { const val PLUGIN_KEY_PHONE_STATE = 0x2 }
|
||||
}
|
||||
|
||||
private class LockscreenBatteryController(private val framework: XposedInterface) {
|
||||
private var failed = false
|
||||
|
||||
fun install(classLoader: ClassLoader): Boolean {
|
||||
val eventClass = classLoader.findClass("com.android.systemui.statusbar.IndicationEventType") ?: return false
|
||||
val batteryEvent = eventClass.staticField("BATTERY") ?: return false
|
||||
val restingEvent = eventClass.staticField("BATTERY_RESTING") ?: return false
|
||||
val controller = classLoader.findClass(
|
||||
"com.android.systemui.statusbar.KeyguardSecIndicationController",
|
||||
) ?: return false
|
||||
val methods = controller.methodsNamed("addInitialIndication")
|
||||
methods.forEach { method ->
|
||||
framework.intercept(method) { chain ->
|
||||
val result = chain.proceed()
|
||||
runCatching {
|
||||
val target = chain.thisObject
|
||||
val context = target.field("mContext") as? Context ?: return@runCatching
|
||||
val state = visibleLockscreenBattery(context, target) ?: return@runCatching
|
||||
val text = state.formattedPercent()
|
||||
val color = target.field("mInitialTextColorState") as? ColorStateList
|
||||
target.invokeNamed("addIndicationTimeout", batteryEvent, text, color, false)
|
||||
target.invokeNamed("addIndication", restingEvent, text)
|
||||
}.onFailure(::logFailureOnce)
|
||||
result
|
||||
}
|
||||
}
|
||||
return methods.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun visibleLockscreenBattery(context: Context, controller: Any): BatteryState? {
|
||||
if (!ProcessVisibilityPolicyCache.systemUiMisc.showLockscreenBatteryPercentWhenUnplugged) return null
|
||||
if (controller.field("mDozing") == true) return null
|
||||
return currentBatteryState(context)?.takeUnless { it.plugged || it.charged }
|
||||
}
|
||||
|
||||
private fun logFailureOnce(error: Throwable) {
|
||||
if (failed) return
|
||||
failed = true
|
||||
Log.w(TAG, "Could not update the OneUI lockscreen battery indication", error)
|
||||
}
|
||||
}
|
||||
|
||||
private class AodBatteryController(private val framework: XposedInterface) {
|
||||
private var manager: Any? = null
|
||||
private var host: FrameLayout? = null
|
||||
private val pendingRetries = mutableSetOf<Runnable>()
|
||||
private val savedText = WeakHashMap<TextView, Pair<CharSequence, Int>>()
|
||||
private val savedVisibility = WeakHashMap<View, Int>()
|
||||
private var failed = false
|
||||
|
||||
fun install(classLoader: ClassLoader): Boolean {
|
||||
var hooks = 0
|
||||
classLoader.findClass("com.android.systemui.doze.PluginAODManager\$6")
|
||||
?.methodsNamed("setBottomArea")?.forEach { method ->
|
||||
framework.intercept(method) { chain ->
|
||||
val result = chain.proceed()
|
||||
manager = chain.thisObject.field("this\$0")
|
||||
host = resolveBottomDozeArea(manager)
|
||||
update(host, chain.args.firstOrNull { it is View } as? View ?: result as? View)
|
||||
result
|
||||
}
|
||||
hooks++
|
||||
}
|
||||
classLoader.findClass("com.android.systemui.doze.PluginAODManager")
|
||||
?.methodsNamed("setIsDozing")?.forEach { method ->
|
||||
framework.intercept(method) { chain ->
|
||||
val result = chain.proceed()
|
||||
manager = chain.thisObject
|
||||
host = resolveBottomDozeArea(manager)
|
||||
val target = host?.getChildAt(0)
|
||||
if (chain.args.firstOrNull() == true) update(host, target) else clear(host, target)
|
||||
result
|
||||
}
|
||||
hooks++
|
||||
}
|
||||
classLoader.findClass(
|
||||
"com.android.systemui.statusbar.KeyguardSecIndicationController\$SecKeyguardCallback",
|
||||
)?.methodsNamed("onRefreshBatteryInfo")?.forEach { method ->
|
||||
framework.intercept(method) { chain ->
|
||||
val result = chain.proceed()
|
||||
update(host, host?.getChildAt(0))
|
||||
result
|
||||
}
|
||||
hooks++
|
||||
}
|
||||
return hooks > 0
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
host?.post { update(host, host?.getChildAt(0)) }
|
||||
}
|
||||
|
||||
private fun update(host: FrameLayout?, source: View?) {
|
||||
runCatching {
|
||||
val frame = host ?: return
|
||||
val state = currentBatteryState(frame.context)
|
||||
if (!ProcessVisibilityPolicyCache.systemUiMisc.showAodBatteryPercentWhenUnplugged ||
|
||||
state == null || state.plugged || state.charged
|
||||
) return clear(frame, source)
|
||||
val text = state.formattedPercent().toString()
|
||||
val component = pluginRoot(source, frame).field("p") ?: return clear(frame, source)
|
||||
val componentView = component.field("c") as? View ?: return clear(frame, source)
|
||||
val indication = componentView.field("d") as? TextView ?: return clear(frame, source)
|
||||
cancelRetries(frame)
|
||||
apply(componentView, indication, text)
|
||||
RETRY_DELAYS.forEach { delay ->
|
||||
val retry = object : Runnable {
|
||||
override fun run() {
|
||||
pendingRetries -= this
|
||||
if (this@AodBatteryController.host !== frame) return
|
||||
val current = currentBatteryState(frame.context)
|
||||
if (!ProcessVisibilityPolicyCache.systemUiMisc.showAodBatteryPercentWhenUnplugged ||
|
||||
current == null || current.plugged || current.charged
|
||||
) {
|
||||
clear(frame, source)
|
||||
return
|
||||
}
|
||||
val root = pluginRoot(source, frame).field("p").field("c") as? View ?: return
|
||||
val textView = root.field("d") as? TextView ?: return
|
||||
apply(root, textView, current.formattedPercent().toString())
|
||||
}
|
||||
}
|
||||
pendingRetries += retry
|
||||
frame.postDelayed(retry, delay)
|
||||
}
|
||||
}.onFailure(::logFailureOnce)
|
||||
}
|
||||
|
||||
private fun apply(componentView: View, indication: TextView, text: String) {
|
||||
savedText.putIfAbsent(indication, indication.text to indication.visibility)
|
||||
indication.text = text
|
||||
indication.visibility = View.VISIBLE
|
||||
hide(componentView.field("k") as? View)
|
||||
hide(componentView.field("c") as? View)
|
||||
hide(componentView.field("l") as? View)
|
||||
forceVisible(indication)
|
||||
forceVisible(componentView.field("b") as? View)
|
||||
forceVisible(componentView)
|
||||
}
|
||||
|
||||
private fun clear(host: FrameLayout?, source: View?) {
|
||||
cancelRetries(host)
|
||||
val componentView = pluginRoot(source, host).field("p").field("c") as? View
|
||||
(componentView?.field("d") as? TextView)?.let { view ->
|
||||
savedText.remove(view)?.let { (text, visibility) ->
|
||||
view.text = text
|
||||
view.visibility = visibility
|
||||
}
|
||||
}
|
||||
listOf("k", "c", "l").forEach { name ->
|
||||
(componentView?.field(name) as? View)?.let { restoreVisibility(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun hide(view: View?) {
|
||||
view ?: return
|
||||
rememberVisibility(view)
|
||||
view.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun rememberVisibility(view: View) {
|
||||
savedVisibility.putIfAbsent(view, view.visibility)
|
||||
}
|
||||
|
||||
private fun restoreVisibility(view: View) {
|
||||
savedVisibility.remove(view)?.let { view.visibility = it }
|
||||
}
|
||||
|
||||
private fun forceVisible(start: View?) {
|
||||
var view = start
|
||||
while (view != null) {
|
||||
view.visibility = View.VISIBLE
|
||||
view.alpha = 1f
|
||||
view.translationX = 0f
|
||||
view.translationY = 0f
|
||||
view.scaleX = 1f
|
||||
view.scaleY = 1f
|
||||
view.requestLayout()
|
||||
view.invalidate()
|
||||
view = view.parent as? View
|
||||
}
|
||||
}
|
||||
|
||||
private fun pluginRoot(source: View?, host: FrameLayout?): Any? = when {
|
||||
source is ViewGroup -> source
|
||||
host?.childCount ?: 0 > 0 -> host?.getChildAt(0)
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun resolveBottomDozeArea(manager: Any?): FrameLayout? = runCatching {
|
||||
val panel = manager.field("mPanelViewControllerLazy").invokeNamed("get")
|
||||
val controller = panel.field("mKeyguardSecBottomAreaViewController")
|
||||
val bottomView = controller.invokeNamed("getView")
|
||||
bottomView.field("bottomDozeArea\$delegate").invokeNamed("getValue") as? FrameLayout
|
||||
}.getOrNull()
|
||||
|
||||
private fun cancelRetries(host: FrameLayout?) {
|
||||
pendingRetries.forEach { host?.removeCallbacks(it) }
|
||||
pendingRetries.clear()
|
||||
}
|
||||
|
||||
private fun logFailureOnce(error: Throwable) {
|
||||
if (failed) return
|
||||
failed = true
|
||||
Log.w(TAG, "Could not update the OneUI AOD battery indication", error)
|
||||
}
|
||||
|
||||
private companion object { val RETRY_DELAYS = longArrayOf(96L, 224L, 480L) }
|
||||
}
|
||||
|
||||
private data class BatteryState(val level: Int, val plugged: Boolean, val charged: Boolean) {
|
||||
fun formattedPercent(): CharSequence = NumberFormat.getPercentInstance().format(level / 100.0)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun currentBatteryState(context: Context): BatteryState? {
|
||||
val app = context.applicationContext
|
||||
val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
|
||||
val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
app.registerReceiver(null, filter, Context.RECEIVER_EXPORTED)
|
||||
} else {
|
||||
app.registerReceiver(null, filter)
|
||||
} ?: return null
|
||||
val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
|
||||
val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)
|
||||
if (level < 0 || scale <= 0) return null
|
||||
val percent = Math.round((level * 100f) / scale).coerceIn(0, 100)
|
||||
val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
||||
val plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0 ||
|
||||
status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL
|
||||
return BatteryState(percent, plugged, status == BatteryManager.BATTERY_STATUS_FULL || percent >= 100)
|
||||
}
|
||||
|
||||
private fun currentApplicationContext(): Context? = runCatching {
|
||||
Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null) as? Context
|
||||
}.getOrNull()
|
||||
|
||||
private fun ClassLoader.findClass(name: String) = runCatching { Class.forName(name, false, this) }.getOrNull()
|
||||
|
||||
private fun Class<*>.methodsNamed(name: String): List<Method> = declaredMethods.filter { it.name == name }.onEach {
|
||||
it.isAccessible = true
|
||||
}
|
||||
|
||||
private fun Class<*>.staticField(name: String): Any? = findField(name)?.get(null)
|
||||
|
||||
private fun Any?.field(name: String): Any? = this?.javaClass?.findField(name)?.get(this)
|
||||
|
||||
private fun Class<*>.findField(name: String): Field? {
|
||||
var type: Class<*>? = this
|
||||
while (type != null) {
|
||||
runCatching { type.getDeclaredField(name) }.getOrNull()?.let {
|
||||
it.isAccessible = true
|
||||
return it
|
||||
}
|
||||
type = type.superclass
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun Any?.invokeNamed(name: String, vararg args: Any?): Any? {
|
||||
val target = this ?: return null
|
||||
var type: Class<*>? = target.javaClass
|
||||
while (type != null) {
|
||||
type.declaredMethods.firstOrNull { method ->
|
||||
method.name == name && method.parameterCount == args.size &&
|
||||
method.parameterTypes.indices.all { method.parameterTypes[it].accepts(args[it]) }
|
||||
}?.let { method ->
|
||||
method.isAccessible = true
|
||||
return method.invoke(target, *args)
|
||||
}
|
||||
type = type.superclass
|
||||
}
|
||||
error("${target.javaClass.name}.$name(${args.size}) is unavailable")
|
||||
}
|
||||
|
||||
private fun Class<*>.accepts(value: Any?): Boolean {
|
||||
if (value == null) return !isPrimitive
|
||||
val boxed = when (this) {
|
||||
Boolean::class.javaPrimitiveType -> Boolean::class.java
|
||||
Int::class.javaPrimitiveType -> Int::class.java
|
||||
Long::class.javaPrimitiveType -> Long::class.java
|
||||
Float::class.javaPrimitiveType -> Float::class.java
|
||||
Double::class.javaPrimitiveType -> Double::class.java
|
||||
else -> this
|
||||
}
|
||||
return boxed.isInstance(value)
|
||||
}
|
||||
|
||||
private fun XposedInterface.intercept(method: Method, callback: (XposedInterface.Chain) -> Any?) {
|
||||
hook(method).intercept(callback)
|
||||
}
|
||||
|
||||
private const val TAG = "NotificationsMaster"
|
||||
+1
@@ -35,6 +35,7 @@ internal object ProcessVisibilityPolicyCache {
|
||||
val unlockedIconLimit get() = policy.unlockedIconLimit
|
||||
val lockscreenCards get() = policy.lockscreenCards
|
||||
val aodCards get() = policy.aodCards
|
||||
val systemUiMisc get() = policy.systemUiMisc
|
||||
|
||||
private fun tryInstall() {
|
||||
if (installed) return
|
||||
|
||||
@@ -40,6 +40,12 @@ data class AppVisibilityPolicy(
|
||||
init { require(packageName.isNotBlank()) }
|
||||
}
|
||||
|
||||
data class SystemUiMiscSettings(
|
||||
val keepAodVisibleDuringCalls: Boolean = false,
|
||||
val showLockscreenBatteryPercentWhenUnplugged: Boolean = true,
|
||||
val showAodBatteryPercentWhenUnplugged: Boolean = true,
|
||||
)
|
||||
|
||||
data class VisibilityPolicy(
|
||||
val generation: Long = 0,
|
||||
val enabled: Boolean = true,
|
||||
@@ -47,6 +53,7 @@ data class VisibilityPolicy(
|
||||
val unlockedIconLimit: Int? = null,
|
||||
val lockscreenCards: CardsGridSettings = CardsGridSettings.LOCKSCREEN_DEFAULT,
|
||||
val aodCards: CardsGridSettings = CardsGridSettings.AOD_DEFAULT,
|
||||
val systemUiMisc: SystemUiMiscSettings = SystemUiMiscSettings(),
|
||||
) {
|
||||
init {
|
||||
require(generation >= 0)
|
||||
@@ -74,6 +81,7 @@ class CompiledVisibilityPolicy(policy: VisibilityPolicy) {
|
||||
val unlockedIconLimit = policy.unlockedIconLimit
|
||||
val lockscreenCards = policy.lockscreenCards
|
||||
val aodCards = policy.aodCards
|
||||
val systemUiMisc = policy.systemUiMisc
|
||||
private val enabled = policy.enabled
|
||||
private val apps = policy.apps.associate { app ->
|
||||
app.packageName to CompiledAppPolicy(
|
||||
|
||||
@@ -13,6 +13,7 @@ internal object VisibilityPolicyJson {
|
||||
.put("unlockedIconLimit", policy.unlockedIconLimit ?: JSONObject.NULL)
|
||||
.put("lockscreenCards", policy.lockscreenCards.toJson())
|
||||
.put("aodCards", policy.aodCards.toJson())
|
||||
.put("systemUiMisc", policy.systemUiMisc.toJson())
|
||||
.put("apps", JSONArray().apply { policy.apps.forEach { put(it.toJson()) } })
|
||||
.toString().encodeToByteArray()
|
||||
|
||||
@@ -28,9 +29,22 @@ internal object VisibilityPolicyJson {
|
||||
?: CardsGridSettings.LOCKSCREEN_DEFAULT,
|
||||
aodCards = root.optJSONObject("aodCards")?.toCardsGridSettings()
|
||||
?: CardsGridSettings.AOD_DEFAULT,
|
||||
systemUiMisc = root.optJSONObject("systemUiMisc")?.toSystemUiMiscSettings()
|
||||
?: SystemUiMiscSettings(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun SystemUiMiscSettings.toJson() = JSONObject()
|
||||
.put("keepAodVisibleDuringCalls", keepAodVisibleDuringCalls)
|
||||
.put("showLockscreenBatteryPercentWhenUnplugged", showLockscreenBatteryPercentWhenUnplugged)
|
||||
.put("showAodBatteryPercentWhenUnplugged", showAodBatteryPercentWhenUnplugged)
|
||||
|
||||
private fun JSONObject.toSystemUiMiscSettings() = SystemUiMiscSettings(
|
||||
keepAodVisibleDuringCalls = getBoolean("keepAodVisibleDuringCalls"),
|
||||
showLockscreenBatteryPercentWhenUnplugged = getBoolean("showLockscreenBatteryPercentWhenUnplugged"),
|
||||
showAodBatteryPercentWhenUnplugged = getBoolean("showAodBatteryPercentWhenUnplugged"),
|
||||
)
|
||||
|
||||
private fun CardsGridSettings.toJson() = JSONObject()
|
||||
.put("maxRows", maxRows)
|
||||
.put("maxIconsPerRow", maxIconsPerRow)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/page_padding">
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/keep_aod_visible_during_calls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/keep_aod_visible_during_calls" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/keep_aod_visible_during_calls_hint"
|
||||
android:textAppearance="?attr/textAppearanceBody2" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/show_lockscreen_battery_percent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/show_lockscreen_battery_percent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/show_lockscreen_battery_percent_hint"
|
||||
android:textAppearance="?attr/textAppearanceBody2" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/show_aod_battery_percent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/show_aod_battery_percent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/show_aod_battery_percent_hint"
|
||||
android:textAppearance="?attr/textAppearanceBody2" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -32,6 +32,9 @@
|
||||
<item
|
||||
android:id="@+id/nav_debug_notifications"
|
||||
android:title="@string/navigation_indented_debug_notifications" />
|
||||
<item
|
||||
android:id="@+id/nav_miscellaneous"
|
||||
android:title="@string/navigation_indented_miscellaneous" />
|
||||
</group>
|
||||
<group android:checkableBehavior="single">
|
||||
<item
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<string name="page_notification_visibility">Notifications</string>
|
||||
<string name="page_debug_notifications">Debug icons</string>
|
||||
<string name="page_cards_layout">Cards layout</string>
|
||||
<string name="page_miscellaneous">Miscellaneous</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>
|
||||
@@ -31,6 +32,13 @@
|
||||
<string name="navigation_indented_apps">    Apps</string>
|
||||
<string name="navigation_indented_cards_layout">    Cards layout</string>
|
||||
<string name="navigation_indented_debug_notifications">    Debug icons</string>
|
||||
<string name="navigation_indented_miscellaneous">    Miscellaneous</string>
|
||||
<string name="navigation_indented_filter_logging">    Filter logging</string>
|
||||
<string name="navigation_indented_filter_apps">    Filter apps</string>
|
||||
<string name="keep_aod_visible_during_calls">Keep AOD visible during calls</string>
|
||||
<string name="keep_aod_visible_during_calls_hint">Prevents OneUI from hiding the always-on display while a call is active.</string>
|
||||
<string name="show_lockscreen_battery_percent">Show battery % on lockscreen when unplugged</string>
|
||||
<string name="show_lockscreen_battery_percent_hint">Shows the battery percentage even when the phone is not charging.</string>
|
||||
<string name="show_aod_battery_percent">Show battery % on AOD when unplugged</string>
|
||||
<string name="show_aod_battery_percent_hint">Shows the battery percentage in the bottom AOD area even when the phone is not charging.</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user