Skip to content

Commit

Permalink
v6.3
Browse files Browse the repository at this point in the history
  • Loading branch information
BinTianqi committed Jan 1, 2025
2 parents 9a0fad9 + 65bf0f7 commit 4e6393b
Show file tree
Hide file tree
Showing 37 changed files with 2,811 additions and 2,722 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ on:
- '**.md'
tags-ignore:
- '**'
branches-ignore:
- 'master'

jobs:
build:
Expand Down Expand Up @@ -58,9 +56,12 @@ jobs:
name: OwnDroid-CI-${{ env.SHORT_SHA }}-release-signed
path: app/build/outputs/apk/release/app-release.apk

- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@v4

upload-telegram:
name: Upload Builds
if: ${{ success() }}
if: ${{ success() && github.ref_name == 'dev' }}
runs-on: ubuntu-latest
needs:
- build
Expand Down
18 changes: 18 additions & 0 deletions Readme-en.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<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.md](LICENSE.md)
Expand Down
20 changes: 19 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
- 应用:禁止安装/卸载应用...
- 用户:禁止添加/删除/切换用户...
- 媒体:禁止调整亮度、禁止调整音量...
- 其他:禁止修改账号、禁止修改语言、禁止恢复出场设置、禁用调试功能...
- 其他:禁止修改账号、禁止修改语言、禁止恢复出厂设置、禁用调试功能...
- 用户管理
- 用户信息
- 启动/切换/停止/删除用户
Expand All @@ -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)
Expand Down
15 changes: 4 additions & 11 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ android {
applicationId = "com.bintianqi.owndroid"
minSdk = 21
targetSdk = 34
versionCode = 34
versionName = "6.2"
versionCode = 35
versionName = "6.3"
multiDexEnabled = false
}

Expand Down Expand Up @@ -54,15 +54,6 @@ android {
compose = true
aidl = true
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
excludes += "/META-INF/**.version"
excludes += "kotlin/**"
excludes += "**.bin"
excludes += "kotlin-tooling-metadata.json"
}
}
androidResources {
generateLocaleConfig = true
}
Expand All @@ -86,6 +77,7 @@ dependencies {
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.accompanist.drawablepainter)
implementation(libs.accompanist.permissions)
implementation(libs.androidx.material3)
implementation(libs.androidx.navigation.compose)
implementation(libs.shizuku.provider)
Expand All @@ -95,4 +87,5 @@ dependencies {
implementation(libs.androidx.fragment)
implementation(libs.hiddenApiBypass)
implementation(libs.serialization)
implementation(kotlin("reflect"))
}
29 changes: 11 additions & 18 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-sdk tools:overrideLibrary="rikka.shizuku.provider,rikka.shizuku.api,rikka.shizuku.shared,rikka.shizuku.aidl"/>
<application
android:dataExtractionRules="@xml/data_extraction_rules"
Expand Down Expand Up @@ -52,17 +56,6 @@
android:windowSoftInputMode="adjustResize|stateHidden"
android:theme="@style/Theme.Transparent">
</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
android:name=".InstallAppActivity"
android:exported="true"
Expand Down Expand Up @@ -103,14 +96,14 @@
android:permission="android.permission.BIND_DEVICE_ADMIN">
</receiver>
<receiver
android:name=".StopLockTaskModeReceiver"
android:description="@string/app_name">
</receiver>
<receiver
android:name=".AutomationReceiver"
android:exported="true">
android:name=".ApiReceiver"
android:exported="true">
<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>
</receiver>
<provider
Expand Down
1 change: 0 additions & 1 deletion app/src/main/aidl/com/bintianqi/owndroid/IUserService.aidl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.bintianqi.owndroid;

interface IUserService {
void destroy() = 16777114;
String execute(String command) = 1;
int getUid() = 2;
}
51 changes: 51 additions & 0 deletions app/src/main/java/com/bintianqi/owndroid/ApiReceiver.kt
Original file line number Diff line number Diff line change
@@ -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"
}
}
107 changes: 27 additions & 80 deletions app/src/main/java/com/bintianqi/owndroid/Auth.kt
Original file line number Diff line number Diff line change
@@ -1,55 +1,34 @@
package com.bintianqi.owndroid

import android.content.Context
import androidx.activity.compose.BackHandler
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.AuthenticationCallback
import androidx.biometric.BiometricPrompt.PromptInfo.Builder
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.bintianqi.owndroid.ui.Animations
import androidx.navigation.NavHostController
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
fun AuthScreen(activity: FragmentActivity, showAuth: MutableState<Boolean>) {
val context = activity.applicationContext
val coroutineScope = rememberCoroutineScope()
var canStartAuth by remember { mutableStateOf(true) }
var fallback by remember { mutableStateOf(false) }
var startFade by remember { mutableStateOf(false) }
val alpha by animateFloatAsState(
targetValue = if(startFade) 0F else 1F,
label = "AuthScreenFade",
animationSpec = Animations.authScreenFade
)
val onAuthSucceed = {
startFade = true
coroutineScope.launch {
delay(300)
showAuth.value = false
}
}
val promptInfo = Builder()
.setTitle(context.getText(R.string.authenticate))
.setSubtitle(context.getText(R.string.auth_with_bio))
.setConfirmationRequired(true)
fun Authenticate(activity: FragmentActivity, navCtrl: NavHostController) {
BackHandler { activity.moveTaskToBack(true) }
var status by rememberSaveable { mutableIntStateOf(0) } // 0:Prompt automatically, 1:Authenticating, 2:Prompt manually
val onAuthSucceed = { navCtrl.navigateUp() }
val callback = object: AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
Expand All @@ -59,81 +38,49 @@ fun AuthScreen(activity: FragmentActivity, showAuth: MutableState<Boolean>) {
super.onAuthenticationError(errorCode, errString)
when(errorCode) {
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> onAuthSucceed()
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> fallback = true
else -> canStartAuth = true
else -> status = 2
}
}
}
LaunchedEffect(fallback) {
if(fallback) {
val fallbackPromptInfo = promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.setSubtitle(context.getText(R.string.auth_with_password))
.build()
val executor = ContextCompat.getMainExecutor(context)
val biometricPrompt = BiometricPrompt(activity, executor, callback)
biometricPrompt.authenticate(fallbackPromptInfo)
LaunchedEffect(Unit) {
if(status == 0) {
delay(300)
startAuth(activity, callback)
status = 1
}
}
Surface(
modifier = Modifier
.fillMaxSize()
.alpha(alpha)
.background(if(isSystemInDarkTheme()) Color.Black else Color.White)
) {
Scaffold { paddingValues ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)
modifier = Modifier.fillMaxSize().padding(paddingValues)
) {
LaunchedEffect(Unit) {
delay(300)
startAuth(activity, promptInfo, callback)
canStartAuth = false
}
Text(
text = stringResource(R.string.authenticate),
style = MaterialTheme.typography.headlineLarge,
color = MaterialTheme.colorScheme.onBackground
)
Button(
onClick = {
startAuth(activity, promptInfo, callback)
canStartAuth = false
startAuth(activity, callback)
status = 1
},
enabled = canStartAuth
enabled = status != 1
) {
Text(text = stringResource(R.string.start))
}
}
}
}

private fun startAuth(activity: FragmentActivity, basicPromptInfo: Builder, callback: AuthenticationCallback) {
fun startAuth(activity: FragmentActivity, callback: AuthenticationCallback) {
val context = activity.applicationContext
val promptInfo = basicPromptInfo
val bioManager = BiometricManager.from(context)
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
if(sharedPref.getBoolean("bio_auth", false)) {
when(BiometricManager.BIOMETRIC_SUCCESS) {
bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) ->
promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.setNegativeButtonText(context.getText(R.string.use_password))
bioManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) ->
promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK)
.setNegativeButtonText(context.getText(R.string.use_password))
else -> promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.setSubtitle(context.getText(R.string.auth_with_password))
}
}else{
promptInfo
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.setSubtitle(context.getText(R.string.auth_with_password))
val promptInfo = Builder().setTitle(context.getText(R.string.authenticate))
if(sharedPref.getInt("biometrics_auth", 0) != 0) {
promptInfo.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL or BiometricManager.Authenticators.BIOMETRIC_WEAK)
} else {
promptInfo.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
}
val executor = ContextCompat.getMainExecutor(context)
val biometricPrompt = BiometricPrompt(activity, executor, callback)
biometricPrompt.authenticate(promptInfo.build())
BiometricPrompt(activity, executor, callback).authenticate(promptInfo.build())
}
Loading

0 comments on commit 4e6393b

Please sign in to comment.