Refactor code of API

Rename Automation API to API
Add API section to READMEs
This commit is contained in:
BinTianqi
2024-12-21 17:11:41 +08:00
parent 6d531f8fd5
commit 873896ec10
14 changed files with 147 additions and 135 deletions

View File

@@ -43,6 +43,24 @@ Use Android Device owner privilege to manage your device.
- Set screen timeout - 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.<ID> -n com.bintianqi.owndroid/.ApiReceiver --es key <API_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
[License.md](LICENSE.md) [License.md](LICENSE.md)

View File

@@ -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.<ID> -n com.bintianqi.owndroid/.ApiReceiver --es key <API_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) [License.md](LICENSE.md)

View File

@@ -52,17 +52,6 @@
android:windowSoftInputMode="adjustResize|stateHidden" android:windowSoftInputMode="adjustResize|stateHidden"
android:theme="@style/Theme.Transparent"> android:theme="@style/Theme.Transparent">
</activity> </activity>
<activity
android:name=".AutomationActivity"
android:exported="true"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:windowSoftInputMode="adjustResize|stateHidden"
android:theme="@style/Theme.Transparent">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
<activity <activity
android:name=".InstallAppActivity" android:name=".InstallAppActivity"
android:exported="true" android:exported="true"
@@ -107,10 +96,14 @@
android:description="@string/app_name"> android:description="@string/app_name">
</receiver> </receiver>
<receiver <receiver
android:name=".AutomationReceiver" android:name=".ApiReceiver"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="com.bintianqi.owndroid.action.HIDE"/>
<action android:name="com.bintianqi.owndroid.action.UNHIDE"/>
<action android:name="com.bintianqi.owndroid.action.SUSPEND"/>
<action android:name="com.bintianqi.owndroid.action.UNSUSPEND"/>
<action android:name="com.bintianqi.owndroid.action.LOCK"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<provider <provider

View File

@@ -0,0 +1,51 @@
package com.bintianqi.owndroid
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.bintianqi.owndroid.dpm.getDPM
import com.bintianqi.owndroid.dpm.getReceiver
class ApiReceiver: BroadcastReceiver() {
@SuppressLint("NewApi")
override fun onReceive(context: Context, intent: Intent) {
val sharedPrefs = context.getSharedPreferences("data", Context.MODE_PRIVATE)
if(sharedPrefs.getBoolean("enable_api", false)) return
val key = sharedPrefs.getString("api_key", null)
if(key != null && key == intent.getStringExtra("key")) {
val dpm = context.getDPM()
val receiver = context.getReceiver()
val app = intent.getStringExtra("package") ?: ""
try {
val ok = when(intent.action) {
"com.bintianqi.owndroid.action.HIDE" -> 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"
}
}

View File

@@ -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()
}
}
}

View File

@@ -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"
}

View File

@@ -299,7 +299,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable(route = "Options") { SettingsOptions(navCtrl) } composable(route = "Options") { SettingsOptions(navCtrl) }
composable(route = "Appearance") { Appearance(navCtrl, vm) } composable(route = "Appearance") { Appearance(navCtrl, vm) }
composable(route = "AuthSettings") { AuthSettings(navCtrl) } composable(route = "AuthSettings") { AuthSettings(navCtrl) }
composable(route = "Automation") { Automation(navCtrl) } composable(route = "ApiSettings") { ApiSettings(navCtrl) }
composable(route = "About") { About(navCtrl) } composable(route = "About") { About(navCtrl) }
composable(route = "PackageSelector") { PackageSelector(navCtrl) } composable(route = "PackageSelector") { PackageSelector(navCtrl) }

View File

@@ -32,9 +32,11 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.edit
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.InfoCard
import com.bintianqi.owndroid.ui.MyScaffold import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.SwitchItem import com.bintianqi.owndroid.ui.SwitchItem
import java.security.SecureRandom 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.options, "", R.drawable.tune_fill0) { navCtrl.navigate("Options") }
FunctionItem(R.string.appearance, "", R.drawable.format_paint_fill0) { navCtrl.navigate("Appearance") } 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.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") } FunctionItem(R.string.about, "", R.drawable.info_fill0) { navCtrl.navigate("About") }
} }
} }
@@ -154,15 +156,23 @@ fun AuthSettings(navCtrl: NavHostController) {
} }
@Composable @Composable
fun Automation(navCtrl: NavHostController) { fun ApiSettings(navCtrl: NavHostController) {
val context = LocalContext.current val context = LocalContext.current
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE) val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
MyScaffold(R.string.automation_api, 8.dp, navCtrl) { MyScaffold(R.string.api, 8.dp, navCtrl) {
Spacer(Modifier.padding(vertical = 5.dp)) var enabled by remember { mutableStateOf(sharedPref.getBoolean("enable_api", false)) }
LaunchedEffect(enabled) {
sharedPref.edit {
putBoolean("enable_api", enabled)
if(!enabled) remove("api_key")
}
}
SwitchItem(R.string.enable, "", null, enabled, { enabled = it }, padding = false)
if(enabled) {
var key by remember { mutableStateOf("") } var key by remember { mutableStateOf("") }
OutlinedTextField( OutlinedTextField(
value = key, onValueChange = { key = it }, label = { Text("Key")}, value = key, onValueChange = { key = it }, label = { Text(stringResource(R.string.api_key)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp), readOnly = true,
trailingIcon = { trailingIcon = {
IconButton( IconButton(
onClick = { onClick = {
@@ -176,21 +186,16 @@ fun Automation(navCtrl: NavHostController) {
} }
) )
Button( Button(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
onClick = { onClick = {
sharedPref.edit().putString("automation_key", key).apply() sharedPref.edit().putString("api_key", key).apply()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
}, }
enabled = key.length >= 20
) { ) {
Text(stringResource(R.string.apply)) Text(stringResource(R.string.apply))
} }
SwitchItem( if(sharedPref.contains("api_key")) InfoCard(R.string.api_key_exist)
R.string.automation_debug, "", null, }
{ sharedPref.getBoolean("automation_debug", false) },
{ sharedPref.edit().putBoolean("automation_debug", it).apply() },
padding = false
)
} }
} }

View File

@@ -27,7 +27,6 @@ import java.text.SimpleDateFormat
import java.time.Instant import java.time.Instant
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@@ -143,7 +142,3 @@ fun parseTimestamp(timestamp: Long): String {
val Long.humanReadableDate: String val Long.humanReadableDate: String
get() = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(Date(this)) get() = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(Date(this))
val Long.humanReadableTime: String
get() = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date(this))

View File

@@ -555,8 +555,9 @@
<string name="lock_in_background">Блокировать при переключении в фоновый режим</string> <string name="lock_in_background">Блокировать при переключении в фоновый режим</string>
<string name="clear_storage">Очистить хранилище</string> <string name="clear_storage">Очистить хранилище</string>
<string name="automation_api">API автоматизации</string> <!--TODO-->
<string name="automation_debug">Режим отладки</string> <string name="api_key">API key</string>
<string name="api_key_exist">The API key already exists, setting a new key will overwrite the current key.</string>
<!--Разрешения Android--> <!--Разрешения Android-->
<string name="permission_POST_NOTIFICATIONS">Отправлять уведомления</string> <string name="permission_POST_NOTIFICATIONS">Отправлять уведомления</string>

View File

@@ -550,8 +550,9 @@
<string name="lock_in_background">Arka plana geçince kilitle</string> <string name="lock_in_background">Arka plana geçince kilitle</string>
<string name="clear_storage">Depolamayı temizle</string> <string name="clear_storage">Depolamayı temizle</string>
<string name="automation_api">Automation API</string> <!--TODO--> <!--TODO-->
<string name="automation_debug">Debug mode</string> <!--TODO--> <string name="api_key">API key</string>
<string name="api_key_exist">The API key already exists, setting a new key will overwrite the current key.</string>
<!--AndroidPermission--> <!--AndroidPermission-->
<string name="permission_POST_NOTIFICATIONS">Bildirim gönder</string> <string name="permission_POST_NOTIFICATIONS">Bildirim gönder</string>

View File

@@ -540,8 +540,8 @@
<string name="lock_in_background">处于后台时锁定</string> <string name="lock_in_background">处于后台时锁定</string>
<string name="clear_storage">清除存储空间</string> <string name="clear_storage">清除存储空间</string>
<string name="automation_api">自动化API</string> <string name="api_key">API密钥</string>
<string name="automation_debug">调试模式</string> <string name="api_key_exist">API密钥已存在设置新的密钥将会覆盖当前密钥</string>
<!--AndroidPermission--> <!--AndroidPermission-->
<string name="permission_POST_NOTIFICATIONS">发送通知</string> <string name="permission_POST_NOTIFICATIONS">发送通知</string>

View File

@@ -63,6 +63,7 @@
<string name="alias">Alias</string> <string name="alias">Alias</string>
<string name="unknown_error">Unknown error</string> <string name="unknown_error">Unknown error</string>
<string name="permission_denied">Permission denied</string> <string name="permission_denied">Permission denied</string>
<string name="api" translatable="false">API</string>
<!--Permissions--> <!--Permissions-->
<string name="click_to_activate">Click to activate</string> <string name="click_to_activate">Click to activate</string>
@@ -554,8 +555,8 @@
<string name="lock_in_background">Lock when switch to background</string> <string name="lock_in_background">Lock when switch to background</string>
<string name="clear_storage">Clear storage</string> <string name="clear_storage">Clear storage</string>
<string name="automation_api">Automation API</string> <string name="api_key">API key</string>
<string name="automation_debug">Debug mode</string> <string name="api_key_exist">The API key already exists, setting a new key will overwrite the current key.</string>
<!--AndroidPermission--> <!--AndroidPermission-->
<string name="permission_POST_NOTIFICATIONS">Post notifications</string> <string name="permission_POST_NOTIFICATIONS">Post notifications</string>