diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index a060c63..fec0baa 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -101,6 +101,8 @@ import com.bintianqi.owndroid.dpm.CrossProfileIntentFilter import com.bintianqi.owndroid.dpm.CrossProfileIntentFilterScreen import com.bintianqi.owndroid.dpm.CrossProfilePackages import com.bintianqi.owndroid.dpm.CrossProfileWidgetProviders +import com.bintianqi.owndroid.dpm.DefaultInputMethod +import com.bintianqi.owndroid.dpm.DefaultInputMethodScreen import com.bintianqi.owndroid.dpm.DelegatedAdmins import com.bintianqi.owndroid.dpm.DelegatedAdminsScreen import com.bintianqi.owndroid.dpm.DeleteWorkProfile @@ -363,6 +365,10 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) { HardwareMonitorScreen(vm.hardwareProperties, vm::getHardwareProperties, vm::setHpRefreshInterval, ::navigateUp) } + composable { + DefaultInputMethodScreen(vm::getCurrentInputMethod, vm.inputMethodList, + vm::getInputMethods, vm::setDefaultInputMethod, ::navigateUp) + } composable { ChangeTimeScreen(vm::setTime, ::navigateUp) } composable { ChangeTimeZoneScreen(vm::setTimeZone, ::navigateUp) } composable { diff --git a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt index 9b0a2cf..bdb23b3 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt @@ -45,7 +45,9 @@ import android.os.Bundle import android.os.HardwarePropertiesManager import android.os.UserHandle import android.os.UserManager +import android.provider.Settings import android.telephony.data.ApnSetting +import android.view.inputmethod.InputMethodManager import androidx.annotation.RequiresApi import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb @@ -103,10 +105,12 @@ import com.bintianqi.owndroid.dpm.activateOrgProfileCommand import com.bintianqi.owndroid.dpm.delegatedScopesList import com.bintianqi.owndroid.dpm.doUserOperationWithContext import com.bintianqi.owndroid.dpm.getPackageInstaller +import com.bintianqi.owndroid.dpm.globalSettings import com.bintianqi.owndroid.dpm.handlePrivilegeChange import com.bintianqi.owndroid.dpm.isValidPackageName import com.bintianqi.owndroid.dpm.parsePackageInstallerMessage import com.bintianqi.owndroid.dpm.runtimePermissions +import com.bintianqi.owndroid.dpm.secureSettings import com.bintianqi.owndroid.dpm.temperatureTypes import com.rosan.dhizuku.api.Dhizuku import com.rosan.dhizuku.api.DhizukuRequestPermissionListener @@ -717,7 +721,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) { commonCriteriaMode = if (VERSION.SDK_INT >= 30 && (privilege.device || privilege.org)) DPM.isCommonCriteriaModeEnabled(DAR) else false, usbSignalEnabled = if (VERSION.SDK_INT >= 31) DPM.isUsbDataSignalingEnabled else false, - canDisableUsbSignal = if (VERSION.SDK_INT >= 31) DPM.canUsbDataSignalingBeDisabled() else false + canDisableUsbSignal = if (VERSION.SDK_INT >= 31) DPM.canUsbDataSignalingBeDisabled() else false, + stayOnWhilePluggedIn = Settings.Global.getInt( + application.contentResolver, Settings.Global.STAY_ON_WHILE_PLUGGED_IN) != 0 ) } fun setCameraDisabled(disabled: Boolean) { @@ -782,6 +788,28 @@ class MyViewModel(application: Application): AndroidViewModel(application) { DPM.isUsbDataSignalingEnabled = enabled systemOptionsStatus.update { it.copy(usbSignalEnabled = DPM.isUsbDataSignalingEnabled) } } + fun setStayOnWhilePluggedIn(status: Boolean) { + DPM.setGlobalSetting( + DAR, Settings.Global.STAY_ON_WHILE_PLUGGED_IN, if (status) "15" else "0" + ) + systemOptionsStatus.update { it.copy(stayOnWhilePluggedIn = status) } + } + fun getGlobalSettings(): Map { + return globalSettings.associate { + it.setting to (Settings.Global.getInt(application.contentResolver, it.setting, 0) == 1) + } + } + fun setGlobalSetting(name: String, status: Boolean) { + DPM.setGlobalSetting(DAR, name, if (status) "1" else "0") + } + fun getSecureSettings(): Map { + return secureSettings.associate { + it.setting to (Settings.Secure.getInt(application.contentResolver, it.setting, 0) == 1) + } + } + fun setSecureSetting(name: String, status: Boolean) { + DPM.setSecureSetting(DAR, name, if (status) "1" else "0") + } fun setKeyguardDisabled(disabled: Boolean): Boolean { return DPM.setKeyguardDisabled(DAR, disabled) } @@ -816,6 +844,21 @@ class MyViewModel(application: Application): AndroidViewModel(application) { delay(hpRefreshInterval) } } + fun getCurrentInputMethod(): String { + return Settings.Secure.getString( + application.contentResolver, Settings.Secure.DEFAULT_INPUT_METHOD) + } + val inputMethodList = MutableStateFlow(listOf>()) + fun getInputMethods() { + val imm = application.getSystemService(InputMethodManager::class.java) + inputMethodList.value = imm.inputMethodList.map { + it.id to getAppInfo(it.packageName) + } + } + fun setDefaultInputMethod(id: String) { + DPM.setSecureSetting(DAR, Settings.Secure.DEFAULT_INPUT_METHOD, id) + getCurrentInputMethod() + } @RequiresApi(28) fun setTime(time: Long, useCurrentTz: Boolean): Boolean { val offset = if (useCurrentTz) { diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt index 8f0fd09..ebc2fbd 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt @@ -24,11 +24,13 @@ import android.net.Uri import android.os.Build.VERSION import android.os.HardwarePropertiesManager import android.os.UserManager +import android.provider.Settings import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -39,6 +41,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.pager.HorizontalPager @@ -69,6 +72,7 @@ import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.PrimaryTabRow +import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider import androidx.compose.material3.Tab @@ -86,6 +90,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -93,6 +98,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager @@ -120,12 +126,14 @@ import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.ListItem +import com.bintianqi.owndroid.ui.MyLazyScaffold import com.bintianqi.owndroid.ui.MyScaffold import com.bintianqi.owndroid.ui.MySmallTitleScaffold import com.bintianqi.owndroid.ui.NavIcon import com.bintianqi.owndroid.ui.Notes import com.bintianqi.owndroid.ui.SwitchItem import com.bintianqi.owndroid.yesOrNo +import com.google.accompanist.drawablepainter.rememberDrawablePainter import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow @@ -153,6 +161,9 @@ fun SystemManagerScreen( FunctionItem(R.string.keyguard, icon = R.drawable.screen_lock_portrait_fill0) { onNavigate(Keyguard) } if(VERSION.SDK_INT >= 24 && privilege.device && !privilege.dhizuku) FunctionItem(R.string.hardware_monitor, icon = R.drawable.memory_fill0) { onNavigate(HardwareMonitor) } + FunctionItem(R.string.default_input_method, icon = R.drawable.keyboard_fill0) { + onNavigate(DefaultInputMethod) + } if(VERSION.SDK_INT >= 24 && privilege.device) { FunctionItem(R.string.reboot, icon = R.drawable.restart_alt_fill0) { dialog = 1 } } @@ -314,17 +325,41 @@ data class SystemOptionsStatus( val btContactSharingDisabled: Boolean = false, val commonCriteriaMode: Boolean = false, val usbSignalEnabled: Boolean = true, - val canDisableUsbSignal: Boolean = true + val canDisableUsbSignal: Boolean = true, + val stayOnWhilePluggedIn: Boolean = false ) @Serializable object SystemOptions +class GlobalSetting(val icon: Int, val name: Int, val setting: String) // also for secure settings + +// STAY_ON_WHILE_PLUGGED_IN is set separately +val globalSettings = listOf( + GlobalSetting(R.drawable.cell_tower_fill0, R.string.data_roaming, Settings.Global.DATA_ROAMING), + GlobalSetting(R.drawable.adb_fill0, R.string.enable_adb, Settings.Global.ADB_ENABLED), + GlobalSetting(R.drawable.usb_fill0, R.string.enable_usb_mass_storage, + Settings.Global.USB_MASS_STORAGE_ENABLED), + GlobalSetting(R.drawable.wifi_password_fill0, R.string.lockdown_admin_configured_network, + Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN) +) + +val secureSettings = listOf( + GlobalSetting(R.drawable.light_off_fill0, R.string.skip_first_use_hints, + Settings.Secure.SKIP_FIRST_USE_HINTS) +) + @Composable fun SystemOptionsScreen(vm: MyViewModel, onNavigateUp: () -> Unit) { val privilege by Privilege.status.collectAsStateWithLifecycle() var dialog by rememberSaveable { mutableIntStateOf(0) } val status by vm.systemOptionsStatus.collectAsStateWithLifecycle() - LaunchedEffect(Unit) { vm.getSystemOptionsStatus() } + val globalSettingsStatus = remember { mutableStateMapOf() } + val secureSettingsStatus = remember { mutableStateMapOf() } + LaunchedEffect(Unit) { + vm.getSystemOptionsStatus() + globalSettingsStatus.putAll(vm.getGlobalSettings()) + secureSettingsStatus.putAll(vm.getSecureSettings()) + } MyScaffold(R.string.options, onNavigateUp, 0.dp) { SwitchItem(R.string.disable_cam, status.cameraDisabled, vm::setCameraDisabled, R.drawable.no_photography_fill0) @@ -366,6 +401,20 @@ fun SystemOptionsScreen(vm: MyViewModel, onNavigateUp: () -> Unit) { SwitchItem(R.string.enable_usb_signal, status.usbSignalEnabled, vm::setUsbSignalEnabled, R.drawable.usb_fill0) } + SwitchItem(R.string.stay_on_while_plugged_in, status.stayOnWhilePluggedIn, + vm::setStayOnWhilePluggedIn, R.drawable.mobile_phone_fill0) + globalSettings.forEach { + SwitchItem(it.name, globalSettingsStatus[it.setting] ?: false, { state -> + vm.setGlobalSetting(it.setting, state) + globalSettingsStatus[it.setting] = state + }, it.icon) + } + secureSettings.forEach { + SwitchItem(it.name, secureSettingsStatus[it.setting] ?: false, { state -> + vm.setSecureSetting(it.setting, state) + secureSettingsStatus[it.setting] = state + }, it.icon) + } if (VERSION.SDK_INT < 34) { Row( Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding), @@ -531,6 +580,47 @@ fun HardwareMonitorScreen( } } +@Serializable object DefaultInputMethod + +@Composable +fun DefaultInputMethodScreen( + getCurrentIm: () -> String, imListState: StateFlow>>, + getImList: () -> Unit, setIm: (String) -> Unit, navigateUp: () -> Unit +) { + val context = LocalContext.current + val imList by imListState.collectAsStateWithLifecycle() + var selectedIm by remember { mutableStateOf("") } + LaunchedEffect(Unit) { + selectedIm = getCurrentIm() + getImList() + } + MyLazyScaffold(R.string.default_input_method, navigateUp) { + items(imList) { (id, info) -> + Row( + Modifier.fillMaxWidth().clickable { selectedIm = id }.padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton(selectedIm == id, { selectedIm = id }) + Image(rememberDrawablePainter(info.icon), null, Modifier.size(40.dp)) + Column(Modifier.padding(start = 8.dp)) { + Text(info.label) + Text(id, Modifier.alpha(0.7F), style = typography.bodyMedium) + } + } + } + item { + Spacer(Modifier.height(8.dp)) + Button({ + setIm(selectedIm) + context.showOperationResultToast(true) + }, Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)) { + Text(stringResource(R.string.apply)) + } + Spacer(Modifier.height(BottomPadding)) + } + } +} + @Serializable object ChangeTime @OptIn(ExperimentalMaterial3Api::class) diff --git a/app/src/main/res/drawable/light_off_fill0.xml b/app/src/main/res/drawable/light_off_fill0.xml new file mode 100644 index 0000000..6cb2da8 --- /dev/null +++ b/app/src/main/res/drawable/light_off_fill0.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 0f8975e..7d0321b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -134,6 +134,10 @@ 禁止蓝牙分享联系人 通用标准模式 启用USB信号 + 充电时保持亮屏 + 启用ADB + 启用USB大容量存储 + 跳过首次使用提示 锁屏 立即锁屏 锁屏 @@ -148,7 +152,8 @@ CPU使用情况 活动 总计 - Fan speeds + 风扇速度 + 默认输入法 错误报告 请求错误报告? 重启 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1ba89ac..eebaca9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -143,6 +143,10 @@ Disable bluetooth contact sharing Common criteria mode Enable USB signal + Stay on while plugged in + Enable ADB + Enable USB mass storage + Skip first use hints Keyguard Lock screen now Lock screen @@ -158,6 +162,7 @@ Active Total Fan speeds + Default input method Bug report Request bug report? Reboot