From 4090fa6be8992fafed232bd7889fc6d55883a8f0 Mon Sep 17 00:00:00 2001 From: Kyujin Cho Date: Fri, 16 Feb 2024 22:46:57 +0900 Subject: [PATCH] feat: Quick Settings tiles to instantly control VoLTE setting (#253) --- app/src/main/AndroidManifest.xml | 46 +++++++++- .../dev/bluehouse/enablevolte/HomeActivity.kt | 2 +- .../enablevolte/IMSStatusQSTileService.kt | 78 ++++++++++++++++ .../java/dev/bluehouse/enablevolte/Moder.kt | 8 ++ .../java/dev/bluehouse/enablevolte/Utils.kt | 2 +- .../VoLTEConfigToggleQSTileService.kt | 90 +++++++++++++++++++ .../dev/bluehouse/enablevolte/pages/Config.kt | 40 +++++++++ app/src/main/res/values-zh-rCN/strings.xml | 11 +++ app/src/main/res/values/strings.xml | 11 +++ 9 files changed, 285 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/dev/bluehouse/enablevolte/IMSStatusQSTileService.kt create mode 100644 app/src/main/java/dev/bluehouse/enablevolte/VoLTEConfigToggleQSTileService.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 63993e8..b6efb38 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.EnableVoLTE" - tools:targetApi="31" + tools:targetApi="33" android:localeConfig="@xml/locales_config"> + + + + + + + + + + + + + + + + + + + + + + + + Tile.STATE_ACTIVE + false -> Tile.STATE_INACTIVE + null -> Tile.STATE_UNAVAILABLE + } + qsTile.subtitle = getString( + when (imsActivated) { + true -> R.string.registered + false -> R.string.unregistered + null -> R.string.unknown + }, + ) + qsTile.updateTile() + } + + override fun onStartListening() { + super.onStartListening() + this.refreshStatus() + } + + override fun onClick() { + super.onClick() + moder?.restartIMSRegistration() + this.refreshStatus() + } +} diff --git a/app/src/main/java/dev/bluehouse/enablevolte/Moder.kt b/app/src/main/java/dev/bluehouse/enablevolte/Moder.kt index 70a4f78..b07e846 100644 --- a/app/src/main/java/dev/bluehouse/enablevolte/Moder.kt +++ b/app/src/main/java/dev/bluehouse/enablevolte/Moder.kt @@ -77,6 +77,11 @@ open class Moder { } class CarrierModer(private val context: Context) : Moder() { + fun getActiveSubscriptionInfoForSimSlotIndex(index: Int): SubscriptionInfo? { + val sub = this.loadCachedInterface { sub } + return sub.getActiveSubscriptionInfoForSimSlotIndex(index, null, null) + } + val subscriptions: List get() { val sub = this.loadCachedInterface { sub } @@ -260,6 +265,9 @@ class SubscriptionModer(val subscriptionId: Int) : Moder() { return config.get(key) } + val simSlotIndex: Int + get() = this.loadCachedInterface { sub }.getSlotIndex(subscriptionId) + val isVoLteConfigEnabled: Boolean get() = this.getBooleanValue(CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL) diff --git a/app/src/main/java/dev/bluehouse/enablevolte/Utils.kt b/app/src/main/java/dev/bluehouse/enablevolte/Utils.kt index cee7991..359dd5d 100644 --- a/app/src/main/java/dev/bluehouse/enablevolte/Utils.kt +++ b/app/src/main/java/dev/bluehouse/enablevolte/Utils.kt @@ -33,7 +33,7 @@ fun checkShizukuPermission(code: Int): ShizukuStatus { } val SubscriptionInfo.uniqueName: String - get() = "${this.subscriptionId} - ${this.displayName}" + get() = "${this.displayName} (SIM ${this.simSlotIndex + 1})" fun getLatestAppVersion(handler: (String) -> Unit) { "https://api.github.com/repos/kyujin-cho/pixel-volte-patch/releases" diff --git a/app/src/main/java/dev/bluehouse/enablevolte/VoLTEConfigToggleQSTileService.kt b/app/src/main/java/dev/bluehouse/enablevolte/VoLTEConfigToggleQSTileService.kt new file mode 100644 index 0000000..ac333e6 --- /dev/null +++ b/app/src/main/java/dev/bluehouse/enablevolte/VoLTEConfigToggleQSTileService.kt @@ -0,0 +1,90 @@ +package dev.bluehouse.enablevolte + +import android.service.quicksettings.Tile +import android.service.quicksettings.TileService +import android.telephony.CarrierConfigManager +import org.lsposed.hiddenapibypass.HiddenApiBypass +import java.lang.IllegalStateException + +class SIM1VoLTEConfigToggleQSTileService : VoLTEConfigToggleQSTileService(0) +class SIM2VoLTEConfigToggleQSTileService : VoLTEConfigToggleQSTileService(1) + +open class VoLTEConfigToggleQSTileService(private val simSlotIndex: Int) : TileService() { + private val TAG = "SIM${simSlotIndex}VoLTEConfigToggleQSTileService" + + init { + HiddenApiBypass.addHiddenApiExemptions("L") + HiddenApiBypass.addHiddenApiExemptions("I") + } + + private val moder: SubscriptionModer? get() { + val carrierModer = CarrierModer(this.applicationContext) + + try { + if (checkShizukuPermission(0) == ShizukuStatus.GRANTED && carrierModer.deviceSupportsIMS) { + carrierModer.subscriptions + val sub = carrierModer.getActiveSubscriptionInfoForSimSlotIndex(this.simSlotIndex) + ?: return null + return SubscriptionModer(sub.subscriptionId) + } + } catch (_: IllegalStateException) {} + return null + } + + private val volteEnabled: Boolean? get() { + /* + * true: VoLTE enabled + * false: VoLTE disabled + * null: cannot determine status (Shizuku not running or permission not granted, SIM slot not active, ...) + */ + val moder = this.moder ?: return null + try { + return moder.isVoLteConfigEnabled + } catch (_: IllegalStateException) {} + return null + } + + override fun onTileAdded() { + super.onTileAdded() + if (this.volteEnabled == null) { + qsTile.state = Tile.STATE_UNAVAILABLE + } + } + + override fun onStartListening() { + super.onStartListening() + qsTile.state = when (this.volteEnabled) { + true -> Tile.STATE_ACTIVE + false -> Tile.STATE_INACTIVE + null -> Tile.STATE_UNAVAILABLE + } + qsTile.subtitle = getString( + when (this.volteEnabled) { + true -> R.string.enabled + false -> R.string.disabled + null -> R.string.unknown + }, + ) + qsTile.updateTile() + } + + private fun toggleVoLTEStatus() { + val moder = this.moder ?: return + val volteEnabled = this.volteEnabled ?: return + moder.updateCarrierConfig(CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL, !volteEnabled) + moder.restartIMSRegistration() + qsTile.state = if (volteEnabled) Tile.STATE_INACTIVE else Tile.STATE_ACTIVE + qsTile.subtitle = getString(if (volteEnabled) R.string.disabled else R.string.enabled) + qsTile.updateTile() + } + + // Called when the user taps on your tile in an active or inactive state. + override fun onClick() { + super.onClick() + if (isLocked) { + unlockAndRun { toggleVoLTEStatus() } + } else { + toggleVoLTEStatus() + } + } +} diff --git a/app/src/main/java/dev/bluehouse/enablevolte/pages/Config.kt b/app/src/main/java/dev/bluehouse/enablevolte/pages/Config.kt index ce3ae1e..baed722 100644 --- a/app/src/main/java/dev/bluehouse/enablevolte/pages/Config.kt +++ b/app/src/main/java/dev/bluehouse/enablevolte/pages/Config.kt @@ -1,6 +1,10 @@ package dev.bluehouse.enablevolte.pages +import android.app.StatusBarManager +import android.content.ComponentName +import android.graphics.drawable.Icon import android.os.Build +import android.os.Build.VERSION import android.telephony.CarrierConfigManager import android.util.Log import android.widget.Toast @@ -71,6 +75,8 @@ fun Config(navController: NavController, subId: Int) { var reversedConfigurableItems by rememberSaveable { mutableStateOf>(mapOf()) } var loading by rememberSaveable { mutableStateOf(true) } val scope = rememberCoroutineScope() + val simSlotIndex = moder.simSlotIndex + val statusBarManager: StatusBarManager = context.getSystemService(StatusBarManager::class.java) fun loadFlags() { Log.d(TAG, "loadFlags") @@ -337,6 +343,40 @@ fun Config(navController: NavController, subId: Int) { } } + if (VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + HeaderText(text = stringResource(R.string.qstile)) + ClickablePropertyView( + label = stringResource(R.string.add_status_tile), + value = "", + ) { + statusBarManager.requestAddTileService( + ComponentName( + context, + // TODO: what happens if someone tries to use this feature from a triple(or even dual)-SIM phone? + Class.forName("dev.bluehouse.enablevolte.SIM${simSlotIndex + 1}IMSStatusQSTileService"), + ), + context.getString(R.string.qs_status_tile_title, (simSlotIndex + 1).toString()), + Icon.createWithResource(context, R.drawable.ic_launcher_foreground), + {}, + {}, + ) + } + ClickablePropertyView( + label = stringResource(R.string.add_toggle_tile), + value = "", + ) { + statusBarManager.requestAddTileService( + ComponentName( + context, + Class.forName("dev.bluehouse.enablevolte.SIM${simSlotIndex + 1}VoLTEConfigToggleQSTileService"), + ), + context.getString(R.string.qs_toggle_tile_title, (simSlotIndex + 1).toString()), + Icon.createWithResource(context, R.drawable.ic_launcher_foreground), + {}, + {}, + ) + } + } HeaderText(text = stringResource(R.string.miscellaneous)) ClickablePropertyView( label = stringResource(R.string.reset_all_settings), diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 8df022e..6f358a3 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -33,6 +33,8 @@ 已注册 未注册 + Enabled + Disabled 有新版本 %1$s 可用! 启用/禁用功能 显示设置 @@ -67,6 +69,15 @@ 已加载 %1$s 共 %2$s 更改值 搜索 + Quick Settings Tile + IMS Status (SIM %1$s) + IMS Status (SIM 1) + IMS Status (SIM 2) + VoLTE Config (SIM %1$s) + VoLTE Config (SIM 1) + VoLTE Config (SIM 2) + Add IMS status display tile + Add VoLTE config toggle tile SIM 配置 配置转储查看器 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7b9938d..1c90b69 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -32,6 +32,8 @@ Registered Unregistered + Enabled + Disabled Newer version %1$s available! Enable/Disable Feature Cosmetic Toggles @@ -66,6 +68,15 @@ Loaded %1$s of %2$s Edit Value Search + Quick Settings Tile + IMS Status (SIM %1$s) + IMS Status (SIM 1) + IMS Status (SIM 2) + VoLTE Config (SIM %1$s) + VoLTE Config (SIM 1) + VoLTE Config (SIM 2) + Add IMS status display tile + Add VoLTE config toggle tile SIM Config Config Dump Viewer