Skip to content

Commit 4e6393b

Browse files
committed
v6.3
2 parents 9a0fad9 + 65bf0f7 commit 4e6393b

37 files changed

+2811
-2722
lines changed

Diff for: .github/workflows/build.yml

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ on:
66
- '**.md'
77
tags-ignore:
88
- '**'
9-
branches-ignore:
10-
- 'master'
119

1210
jobs:
1311
build:
@@ -58,9 +56,12 @@ jobs:
5856
name: OwnDroid-CI-${{ env.SHORT_SHA }}-release-signed
5957
path: app/build/outputs/apk/release/app-release.apk
6058

59+
- name: Generate and submit dependency graph
60+
uses: gradle/actions/dependency-submission@v4
61+
6162
upload-telegram:
6263
name: Upload Builds
63-
if: ${{ success() }}
64+
if: ${{ success() && github.ref_name == 'dev' }}
6465
runs-on: ubuntu-latest
6566
needs:
6667
- build

Diff for: Readme-en.md

+18
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,24 @@ Use Android Device owner privilege to manage your device.
4343
- Set screen timeout
4444
- ...
4545

46+
## API
47+
48+
| ID | Description | Extras | Minimum Android version |
49+
|:---------:|------------------|---------------------------------------|:-----------------------:|
50+
| HIDE | Hide an app | `package`: package name of target app | |
51+
| UNHIDE | Unhide an app | `package`: package name of target app | |
52+
| SUSPEND | Suspend an app | `package`: package name of target app | 7 |
53+
| UNSUSPEND | Unsuspend an app | `package`: package name of target app | 7 |
54+
| LOCK | Lock screen | | |
55+
56+
Use this API in adb shell
57+
```shell
58+
am broadcast -a com.bintianqi.owndroid.action.<ID> -n com.bintianqi.owndroid/.ApiReceiver --es key <API_KEY>
59+
# Example
60+
am broadcast -a com.bintianqi.owndroid.action.HIDE -n com.bintianqi.owndroid/.ApiReceiver --es key abcdefg --es package com.example.app
61+
```
62+
If the return value is 0, the operation is successful.
63+
4664
## License
4765

4866
[License.md](LICENSE.md)

Diff for: Readme.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
- 应用:禁止安装/卸载应用...
3232
- 用户:禁止添加/删除/切换用户...
3333
- 媒体:禁止调整亮度、禁止调整音量...
34-
- 其他:禁止修改账号、禁止修改语言、禁止恢复出场设置、禁用调试功能...
34+
- 其他:禁止修改账号、禁止修改语言、禁止恢复出厂设置、禁用调试功能...
3535
- 用户管理
3636
- 用户信息
3737
- 启动/切换/停止/删除用户
@@ -43,6 +43,24 @@
4343
- 设置屏幕超时
4444
- ...
4545

46+
## API
47+
48+
| ID | 描述 | Extras | 最小安卓版本 |
49+
|:---------:|----------|--------------------|:------:|
50+
| HIDE | 隐藏一个应用 | `package`: 目标应用的包名 | |
51+
| UNHIDE | 取消隐藏一个应用 | `package`: 目标应用的包名 | |
52+
| SUSPEND | 挂起一个应用 | `package`: 目标应用的包名 | 7 |
53+
| UNSUSPEND | 取消挂起一个应用 | `package`: 目标应用的包名 | 7 |
54+
| LOCK | 锁屏 | | |
55+
56+
在adb shell中使用API
57+
```shell
58+
am broadcast -a com.bintianqi.owndroid.action.<ID> -n com.bintianqi.owndroid/.ApiReceiver --es key <API_KEY>
59+
# 示例
60+
am broadcast -a com.bintianqi.owndroid.action.HIDE -n com.bintianqi.owndroid/.ApiReceiver --es key abcdefg --es package com.example.app
61+
```
62+
如果返回值为0,操作成功
63+
4664
## 许可证
4765

4866
[License.md](LICENSE.md)

Diff for: app/build.gradle.kts

+4-11
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ android {
2424
applicationId = "com.bintianqi.owndroid"
2525
minSdk = 21
2626
targetSdk = 34
27-
versionCode = 34
28-
versionName = "6.2"
27+
versionCode = 35
28+
versionName = "6.3"
2929
multiDexEnabled = false
3030
}
3131

@@ -54,15 +54,6 @@ android {
5454
compose = true
5555
aidl = true
5656
}
57-
packaging {
58-
resources {
59-
excludes += "/META-INF/{AL2.0,LGPL2.1}"
60-
excludes += "/META-INF/**.version"
61-
excludes += "kotlin/**"
62-
excludes += "**.bin"
63-
excludes += "kotlin-tooling-metadata.json"
64-
}
65-
}
6657
androidResources {
6758
generateLocaleConfig = true
6859
}
@@ -86,6 +77,7 @@ dependencies {
8677
implementation(libs.androidx.activity.compose)
8778
implementation(platform(libs.androidx.compose.bom))
8879
implementation(libs.accompanist.drawablepainter)
80+
implementation(libs.accompanist.permissions)
8981
implementation(libs.androidx.material3)
9082
implementation(libs.androidx.navigation.compose)
9183
implementation(libs.shizuku.provider)
@@ -95,4 +87,5 @@ dependencies {
9587
implementation(libs.androidx.fragment)
9688
implementation(libs.hiddenApiBypass)
9789
implementation(libs.serialization)
90+
implementation(kotlin("reflect"))
9891
}

Diff for: app/src/main/AndroidManifest.xml

+11-18
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
2121
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
2222
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
23+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
24+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
25+
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
26+
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
2327
<uses-sdk tools:overrideLibrary="rikka.shizuku.provider,rikka.shizuku.api,rikka.shizuku.shared,rikka.shizuku.aidl"/>
2428
<application
2529
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -52,17 +56,6 @@
5256
android:windowSoftInputMode="adjustResize|stateHidden"
5357
android:theme="@style/Theme.Transparent">
5458
</activity>
55-
<activity
56-
android:name=".AutomationActivity"
57-
android:exported="true"
58-
android:launchMode="singleInstance"
59-
android:excludeFromRecents="true"
60-
android:windowSoftInputMode="adjustResize|stateHidden"
61-
android:theme="@style/Theme.Transparent">
62-
<intent-filter>
63-
<action android:name="android.intent.action.MAIN"/>
64-
</intent-filter>
65-
</activity>
6659
<activity
6760
android:name=".InstallAppActivity"
6861
android:exported="true"
@@ -103,14 +96,14 @@
10396
android:permission="android.permission.BIND_DEVICE_ADMIN">
10497
</receiver>
10598
<receiver
106-
android:name=".StopLockTaskModeReceiver"
107-
android:description="@string/app_name">
108-
</receiver>
109-
<receiver
110-
android:name=".AutomationReceiver"
111-
android:exported="true">
99+
android:name=".ApiReceiver"
100+
android:exported="true">
112101
<intent-filter>
113-
<action android:name="android.intent.action.MAIN"/>
102+
<action android:name="com.bintianqi.owndroid.action.HIDE"/>
103+
<action android:name="com.bintianqi.owndroid.action.UNHIDE"/>
104+
<action android:name="com.bintianqi.owndroid.action.SUSPEND"/>
105+
<action android:name="com.bintianqi.owndroid.action.UNSUSPEND"/>
106+
<action android:name="com.bintianqi.owndroid.action.LOCK"/>
114107
</intent-filter>
115108
</receiver>
116109
<provider
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.bintianqi.owndroid;
22

33
interface IUserService {
4-
void destroy() = 16777114;
54
String execute(String command) = 1;
65
int getUid() = 2;
76
}
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.bintianqi.owndroid
2+
3+
import android.annotation.SuppressLint
4+
import android.content.BroadcastReceiver
5+
import android.content.Context
6+
import android.content.Intent
7+
import android.util.Log
8+
import com.bintianqi.owndroid.dpm.getDPM
9+
import com.bintianqi.owndroid.dpm.getReceiver
10+
11+
class ApiReceiver: BroadcastReceiver() {
12+
@SuppressLint("NewApi")
13+
override fun onReceive(context: Context, intent: Intent) {
14+
val sharedPrefs = context.getSharedPreferences("data", Context.MODE_PRIVATE)
15+
if(sharedPrefs.getBoolean("enable_api", false)) return
16+
val key = sharedPrefs.getString("api_key", null)
17+
if(key != null && key == intent.getStringExtra("key")) {
18+
val dpm = context.getDPM()
19+
val receiver = context.getReceiver()
20+
val app = intent.getStringExtra("package") ?: ""
21+
try {
22+
val ok = when(intent.action) {
23+
"com.bintianqi.owndroid.action.HIDE" -> dpm.setApplicationHidden(receiver, app, true)
24+
"com.bintianqi.owndroid.action.UNHIDE" -> dpm.setApplicationHidden(receiver, app, false)
25+
"com.bintianqi.owndroid.action.SUSPEND" -> dpm.setPackagesSuspended(receiver, arrayOf(app), true).isEmpty()
26+
"com.bintianqi.owndroid.action.UNSUSPEND" -> dpm.setPackagesSuspended(receiver, arrayOf(app), false).isEmpty()
27+
"com.bintianqi.owndroid.action.LOCK" -> { dpm.lockNow(); true }
28+
else -> {
29+
Log.w(TAG, "Invalid action")
30+
resultData = "Invalid action"
31+
false
32+
}
33+
}
34+
if(!ok) resultCode = 1
35+
} catch(e: Exception) {
36+
e.printStackTrace()
37+
val message = (e::class.qualifiedName ?: "Exception") + ": " + (e.message ?: "")
38+
Log.w(TAG, message)
39+
resultCode = 1
40+
resultData = message
41+
}
42+
} else {
43+
Log.w(TAG, "Unauthorized")
44+
resultCode = 1
45+
resultData = "Unauthorized"
46+
}
47+
}
48+
companion object {
49+
private const val TAG = "API"
50+
}
51+
}

Diff for: app/src/main/java/com/bintianqi/owndroid/Auth.kt

+27-80
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,34 @@
11
package com.bintianqi.owndroid
22

33
import android.content.Context
4+
import androidx.activity.compose.BackHandler
45
import androidx.biometric.BiometricManager
56
import androidx.biometric.BiometricPrompt
67
import androidx.biometric.BiometricPrompt.AuthenticationCallback
78
import androidx.biometric.BiometricPrompt.PromptInfo.Builder
8-
import androidx.compose.animation.core.animateFloatAsState
9-
import androidx.compose.foundation.background
10-
import androidx.compose.foundation.isSystemInDarkTheme
119
import androidx.compose.foundation.layout.Arrangement
1210
import androidx.compose.foundation.layout.Column
1311
import androidx.compose.foundation.layout.fillMaxSize
12+
import androidx.compose.foundation.layout.padding
1413
import androidx.compose.material3.Button
1514
import androidx.compose.material3.MaterialTheme
16-
import androidx.compose.material3.Surface
15+
import androidx.compose.material3.Scaffold
1716
import androidx.compose.material3.Text
1817
import androidx.compose.runtime.*
18+
import androidx.compose.runtime.saveable.rememberSaveable
1919
import androidx.compose.ui.Alignment
2020
import androidx.compose.ui.Modifier
21-
import androidx.compose.ui.draw.alpha
22-
import androidx.compose.ui.graphics.Color
2321
import androidx.compose.ui.res.stringResource
2422
import androidx.core.content.ContextCompat
2523
import androidx.fragment.app.FragmentActivity
26-
import com.bintianqi.owndroid.ui.Animations
24+
import androidx.navigation.NavHostController
2725
import kotlinx.coroutines.delay
28-
import kotlinx.coroutines.launch
2926

3027
@Composable
31-
fun AuthScreen(activity: FragmentActivity, showAuth: MutableState<Boolean>) {
32-
val context = activity.applicationContext
33-
val coroutineScope = rememberCoroutineScope()
34-
var canStartAuth by remember { mutableStateOf(true) }
35-
var fallback by remember { mutableStateOf(false) }
36-
var startFade by remember { mutableStateOf(false) }
37-
val alpha by animateFloatAsState(
38-
targetValue = if(startFade) 0F else 1F,
39-
label = "AuthScreenFade",
40-
animationSpec = Animations.authScreenFade
41-
)
42-
val onAuthSucceed = {
43-
startFade = true
44-
coroutineScope.launch {
45-
delay(300)
46-
showAuth.value = false
47-
}
48-
}
49-
val promptInfo = Builder()
50-
.setTitle(context.getText(R.string.authenticate))
51-
.setSubtitle(context.getText(R.string.auth_with_bio))
52-
.setConfirmationRequired(true)
28+
fun Authenticate(activity: FragmentActivity, navCtrl: NavHostController) {
29+
BackHandler { activity.moveTaskToBack(true) }
30+
var status by rememberSaveable { mutableIntStateOf(0) } // 0:Prompt automatically, 1:Authenticating, 2:Prompt manually
31+
val onAuthSucceed = { navCtrl.navigateUp() }
5332
val callback = object: AuthenticationCallback() {
5433
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
5534
super.onAuthenticationSucceeded(result)
@@ -59,81 +38,49 @@ fun AuthScreen(activity: FragmentActivity, showAuth: MutableState<Boolean>) {
5938
super.onAuthenticationError(errorCode, errString)
6039
when(errorCode) {
6140
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> onAuthSucceed()
62-
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> fallback = true
63-
else -> canStartAuth = true
41+
else -> status = 2
6442
}
6543
}
6644
}
67-
LaunchedEffect(fallback) {
68-
if(fallback) {
69-
val fallbackPromptInfo = promptInfo
70-
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
71-
.setSubtitle(context.getText(R.string.auth_with_password))
72-
.build()
73-
val executor = ContextCompat.getMainExecutor(context)
74-
val biometricPrompt = BiometricPrompt(activity, executor, callback)
75-
biometricPrompt.authenticate(fallbackPromptInfo)
45+
LaunchedEffect(Unit) {
46+
if(status == 0) {
47+
delay(300)
48+
startAuth(activity, callback)
49+
status = 1
7650
}
7751
}
78-
Surface(
79-
modifier = Modifier
80-
.fillMaxSize()
81-
.alpha(alpha)
82-
.background(if(isSystemInDarkTheme()) Color.Black else Color.White)
83-
) {
52+
Scaffold { paddingValues ->
8453
Column(
8554
horizontalAlignment = Alignment.CenterHorizontally,
8655
verticalArrangement = Arrangement.Center,
87-
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)
56+
modifier = Modifier.fillMaxSize().padding(paddingValues)
8857
) {
89-
LaunchedEffect(Unit) {
90-
delay(300)
91-
startAuth(activity, promptInfo, callback)
92-
canStartAuth = false
93-
}
9458
Text(
9559
text = stringResource(R.string.authenticate),
9660
style = MaterialTheme.typography.headlineLarge,
97-
color = MaterialTheme.colorScheme.onBackground
9861
)
9962
Button(
10063
onClick = {
101-
startAuth(activity, promptInfo, callback)
102-
canStartAuth = false
64+
startAuth(activity, callback)
65+
status = 1
10366
},
104-
enabled = canStartAuth
67+
enabled = status != 1
10568
) {
10669
Text(text = stringResource(R.string.start))
10770
}
10871
}
10972
}
11073
}
11174

112-
private fun startAuth(activity: FragmentActivity, basicPromptInfo: Builder, callback: AuthenticationCallback) {
75+
fun startAuth(activity: FragmentActivity, callback: AuthenticationCallback) {
11376
val context = activity.applicationContext
114-
val promptInfo = basicPromptInfo
115-
val bioManager = BiometricManager.from(context)
11677
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
117-
if(sharedPref.getBoolean("bio_auth", false)) {
118-
when(BiometricManager.BIOMETRIC_SUCCESS) {
119-
bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) ->
120-
promptInfo
121-
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
122-
.setNegativeButtonText(context.getText(R.string.use_password))
123-
bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) ->
124-
promptInfo
125-
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK)
126-
.setNegativeButtonText(context.getText(R.string.use_password))
127-
else -> promptInfo
128-
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
129-
.setSubtitle(context.getText(R.string.auth_with_password))
130-
}
131-
}else{
132-
promptInfo
133-
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
134-
.setSubtitle(context.getText(R.string.auth_with_password))
78+
val promptInfo = Builder().setTitle(context.getText(R.string.authenticate))
79+
if(sharedPref.getInt("biometrics_auth", 0) != 0) {
80+
promptInfo.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL or BiometricManager.Authenticators.BIOMETRIC_WEAK)
81+
} else {
82+
promptInfo.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
13583
}
13684
val executor = ContextCompat.getMainExecutor(context)
137-
val biometricPrompt = BiometricPrompt(activity, executor, callback)
138-
biometricPrompt.authenticate(promptInfo.build())
85+
BiometricPrompt(activity, executor, callback).authenticate(promptInfo.build())
13986
}

0 commit comments

Comments
 (0)