diff --git a/Readme-en.md b/Readme-en.md index 7dc6876..f36a9fb 100644 --- a/Readme-en.md +++ b/Readme-en.md @@ -43,6 +43,24 @@ Use Android Device owner privilege to manage your device. - Set screen timeout - ... +## API + +| ID | Description | Extras | Minimum Android version | +|:---------:|------------------|---------------------------------------|:-----------------------:| +| HIDE | Hide an app | `package`: package name of target app | | +| UNHIDE | Unhide an app | `package`: package name of target app | | +| SUSPEND | Suspend an app | `package`: package name of target app | 7 | +| UNSUSPEND | Unsuspend an app | `package`: package name of target app | 7 | +| LOCK | Lock screen | | | + +Use this API in adb shell +```shell +am broadcast -a com.bintianqi.owndroid.action. -n com.bintianqi.owndroid/.ApiReceiver --es key +# Example +am broadcast -a com.bintianqi.owndroid.action.HIDE -n com.bintianqi.owndroid/.ApiReceiver --es key abcdefg --es package com.example.app +``` +If the return value is 0, the operation is successful. + ## License [License.md](LICENSE.md) diff --git a/Readme.md b/Readme.md index 384a296..7d8e2db 100644 --- a/Readme.md +++ b/Readme.md @@ -43,6 +43,24 @@ - 设置屏幕超时 - ... +## API + +| ID | 描述 | Extras | 最小安卓版本 | +|:---------:|----------|--------------------|:------:| +| HIDE | 隐藏一个应用 | `package`: 目标应用的包名 | | +| UNHIDE | 取消隐藏一个应用 | `package`: 目标应用的包名 | | +| SUSPEND | 挂起一个应用 | `package`: 目标应用的包名 | 7 | +| UNSUSPEND | 取消挂起一个应用 | `package`: 目标应用的包名 | 7 | +| LOCK | 锁屏 | | | + +在adb shell中使用API +```shell +am broadcast -a com.bintianqi.owndroid.action. -n com.bintianqi.owndroid/.ApiReceiver --es key +# 示例 +am broadcast -a com.bintianqi.owndroid.action.HIDE -n com.bintianqi.owndroid/.ApiReceiver --es key abcdefg --es package com.example.app +``` +如果返回值为0,操作成功 + ## 许可证 [License.md](LICENSE.md) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9bfdde6..0ca96a2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -52,17 +52,6 @@ android:windowSoftInputMode="adjustResize|stateHidden" android:theme="@style/Theme.Transparent"> - - - - - + android:name=".ApiReceiver" + android:exported="true"> - + + + + + dpm.setApplicationHidden(receiver, app, true) + "com.bintianqi.owndroid.action.UNHIDE" -> dpm.setApplicationHidden(receiver, app, false) + "com.bintianqi.owndroid.action.SUSPEND" -> dpm.setPackagesSuspended(receiver, arrayOf(app), true).isEmpty() + "com.bintianqi.owndroid.action.UNSUSPEND" -> dpm.setPackagesSuspended(receiver, arrayOf(app), false).isEmpty() + "com.bintianqi.owndroid.action.LOCK" -> { dpm.lockNow(); true } + else -> { + Log.w(TAG, "Invalid action") + resultData = "Invalid action" + false + } + } + if(!ok) resultCode = 1 + } catch(e: Exception) { + e.printStackTrace() + val message = (e::class.qualifiedName ?: "Exception") + ": " + (e.message ?: "") + Log.w(TAG, message) + resultCode = 1 + resultData = message + } + } else { + Log.w(TAG, "Unauthorized") + resultCode = 1 + resultData = "Unauthorized" + } + } + companion object { + private const val TAG = "API" + } +} diff --git a/app/src/main/java/com/bintianqi/owndroid/AutomationActivity.kt b/app/src/main/java/com/bintianqi/owndroid/AutomationActivity.kt deleted file mode 100644 index 2245e64..0000000 --- a/app/src/main/java/com/bintianqi/owndroid/AutomationActivity.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.bintianqi.owndroid - -import android.app.AlertDialog -import android.content.Context -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.ui.platform.LocalContext - -class AutomationActivity: ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val result = handleTask(applicationContext, this.intent) - val sharedPrefs = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE) - if(sharedPrefs.getBoolean("automation_debug", false)) { - setContent { - AlertDialog.Builder(LocalContext.current) - .setMessage(result) - .setOnDismissListener { finish() } - .setPositiveButton(R.string.confirm) { _, _ -> finish() } - .show() - } - } else { - finish() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/bintianqi/owndroid/AutomationReceiver.kt b/app/src/main/java/com/bintianqi/owndroid/AutomationReceiver.kt deleted file mode 100644 index c151e09..0000000 --- a/app/src/main/java/com/bintianqi/owndroid/AutomationReceiver.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.bintianqi.owndroid - -import android.annotation.SuppressLint -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import com.bintianqi.owndroid.dpm.getDPM -import com.bintianqi.owndroid.dpm.getReceiver - -class AutomationReceiver: BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - handleTask(context, intent) - } -} - -@SuppressLint("NewApi") -fun handleTask(context: Context, intent: Intent): String { - val sharedPrefs = context.getSharedPreferences("data", Context.MODE_PRIVATE) - val key = sharedPrefs.getString("automation_key", "") - if(key == null || key != intent.getStringExtra("key")) { - return "Wrong key" - } - val operation = intent.getStringExtra("operation") - val dpm = context.getDPM() - val receiver = context.getReceiver() - val app = intent.getStringExtra("app") - val restriction = intent.getStringExtra("restriction") - try { - when(operation) { - "suspend" -> dpm.setPackagesSuspended(receiver, arrayOf(app), true) - "unsuspend" -> dpm.setPackagesSuspended(receiver, arrayOf(app), false) - "hide" -> dpm.setApplicationHidden(receiver, app, true) - "unhide" -> dpm.setApplicationHidden(receiver, app, false) - "lock" -> dpm.lockNow() - "reboot" -> dpm.reboot(receiver) - "addUserRestriction" -> dpm.addUserRestriction(receiver, restriction) - "clearUserRestriction" -> dpm.clearUserRestriction(receiver, restriction) - else -> return "Operation not defined" - } - } catch(e: Exception) { - return e.message ?: "Failed to get error message" - } - return "No error, or error is unhandled" -} diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index ff60f00..576fecb 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -299,7 +299,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable(route = "Options") { SettingsOptions(navCtrl) } composable(route = "Appearance") { Appearance(navCtrl, vm) } composable(route = "AuthSettings") { AuthSettings(navCtrl) } - composable(route = "Automation") { Automation(navCtrl) } + composable(route = "ApiSettings") { ApiSettings(navCtrl) } composable(route = "About") { About(navCtrl) } composable(route = "PackageSelector") { PackageSelector(navCtrl) } diff --git a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt index ffeb552..5823f21 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt @@ -82,7 +82,7 @@ class Receiver : DeviceAdminReceiver() { val installAppDone = MutableStateFlow(false) -class PackageInstallerReceiver:BroadcastReceiver(){ +class PackageInstallerReceiver: BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val toastText = when(intent.getIntExtra(EXTRA_STATUS, 999)){ STATUS_PENDING_USER_ACTION -> R.string.status_pending_action diff --git a/app/src/main/java/com/bintianqi/owndroid/Settings.kt b/app/src/main/java/com/bintianqi/owndroid/Settings.kt index 19f1924..ce8af0b 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Settings.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Settings.kt @@ -32,9 +32,11 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp +import androidx.core.content.edit import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import com.bintianqi.owndroid.ui.FunctionItem +import com.bintianqi.owndroid.ui.InfoCard import com.bintianqi.owndroid.ui.MyScaffold import com.bintianqi.owndroid.ui.SwitchItem import java.security.SecureRandom @@ -45,7 +47,7 @@ fun Settings(navCtrl: NavHostController) { FunctionItem(R.string.options, "", R.drawable.tune_fill0) { navCtrl.navigate("Options") } FunctionItem(R.string.appearance, "", R.drawable.format_paint_fill0) { navCtrl.navigate("Appearance") } FunctionItem(R.string.security, "", R.drawable.lock_fill0) { navCtrl.navigate("AuthSettings") } - FunctionItem(R.string.automation_api, "", R.drawable.apps_fill0) { navCtrl.navigate("Automation") } + FunctionItem(R.string.api, "", R.drawable.apps_fill0) { navCtrl.navigate("ApiSettings") } FunctionItem(R.string.about, "", R.drawable.info_fill0) { navCtrl.navigate("About") } } } @@ -154,43 +156,46 @@ fun AuthSettings(navCtrl: NavHostController) { } @Composable -fun Automation(navCtrl: NavHostController) { +fun ApiSettings(navCtrl: NavHostController) { val context = LocalContext.current val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE) - MyScaffold(R.string.automation_api, 8.dp, navCtrl) { - Spacer(Modifier.padding(vertical = 5.dp)) - var key by remember { mutableStateOf("") } - OutlinedTextField( - value = key, onValueChange = { key = it }, label = { Text("Key")}, - modifier = Modifier.fillMaxWidth(), - trailingIcon = { - IconButton( - onClick = { - val charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - val sr = SecureRandom() - key = (1..20).map { charset[sr.nextInt(charset.length)] }.joinToString("") - } - ) { - Icon(painter = painterResource(R.drawable.casino_fill0), contentDescription = "Random") - } + MyScaffold(R.string.api, 8.dp, navCtrl) { + var enabled by remember { mutableStateOf(sharedPref.getBoolean("enable_api", false)) } + LaunchedEffect(enabled) { + sharedPref.edit { + putBoolean("enable_api", enabled) + if(!enabled) remove("api_key") } - ) - Button( - modifier = Modifier.fillMaxWidth(), - onClick = { - sharedPref.edit().putString("automation_key", key).apply() - Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - }, - enabled = key.length >= 20 - ) { - Text(stringResource(R.string.apply)) } - SwitchItem( - R.string.automation_debug, "", null, - { sharedPref.getBoolean("automation_debug", false) }, - { sharedPref.edit().putBoolean("automation_debug", it).apply() }, - padding = false - ) + SwitchItem(R.string.enable, "", null, enabled, { enabled = it }, padding = false) + if(enabled) { + var key by remember { mutableStateOf("") } + OutlinedTextField( + value = key, onValueChange = { key = it }, label = { Text(stringResource(R.string.api_key)) }, + modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp), readOnly = true, + trailingIcon = { + IconButton( + onClick = { + val charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + val sr = SecureRandom() + key = (1..20).map { charset[sr.nextInt(charset.length)] }.joinToString("") + } + ) { + Icon(painter = painterResource(R.drawable.casino_fill0), contentDescription = "Random") + } + } + ) + Button( + modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp), + onClick = { + sharedPref.edit().putString("api_key", key).apply() + Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() + } + ) { + Text(stringResource(R.string.apply)) + } + if(sharedPref.contains("api_key")) InfoCard(R.string.api_key_exist) + } } } diff --git a/app/src/main/java/com/bintianqi/owndroid/Utils.kt b/app/src/main/java/com/bintianqi/owndroid/Utils.kt index bf3df3c..879df73 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Utils.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Utils.kt @@ -27,7 +27,6 @@ import java.text.SimpleDateFormat import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle import java.util.Date import java.util.Locale @@ -143,7 +142,3 @@ fun parseTimestamp(timestamp: Long): String { val Long.humanReadableDate: String get() = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(Date(this)) - -val Long.humanReadableTime: String - get() = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date(this)) - diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c826eb1..4608d0d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -555,8 +555,9 @@ Блокировать при переключении в фоновый режим Очистить хранилище - API автоматизации - Режим отладки + + API key + The API key already exists, setting a new key will overwrite the current key. Отправлять уведомления diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index f837cf3..80d2991 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -550,8 +550,9 @@ Arka plana geçince kilitle Depolamayı temizle - Automation API - Debug mode + + API key + The API key already exists, setting a new key will overwrite the current key. Bildirim gönder diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 333c0e9..74350c2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -540,8 +540,8 @@ 处于后台时锁定 清除存储空间 - 自动化API - 调试模式 + API密钥 + API密钥已存在,设置新的密钥将会覆盖当前密钥 发送通知 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c97bc09..f69941e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -63,6 +63,7 @@ Alias Unknown error Permission denied + API Click to activate @@ -553,9 +554,9 @@ Authenticate Lock when switch to background Clear storage - - Automation API - Debug mode + + API key + The API key already exists, setting a new key will overwrite the current key. Post notifications