diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d7db538..06b7c4e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build APK +name: CI build on: workflow_dispatch: @@ -62,9 +62,6 @@ 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 runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4811e75 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,42 @@ +name: Release + +on: + workflow_dispatch: + push: + tags: + - '*' + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - name: Build APK + run: | + echo "${{ secrets.KEY_BASE64 }}" | base64 --decode - > app/release.jks + ./gradlew clean assembleRelease --no-build-cache --no-configuration-cache --no-daemon -PStoreFile="$(pwd)/app/release.jks" -PStorePassword="${{ secrets.KEYSTORE_PASSWORD }}" -PKeyPassword="${{ secrets.KEY_PASSWORD }}" -PKeyAlias="${{ secrets.KEY_ALIAS }}" + + - name: Create release + run: gh release create ${{ github.ref_name }} app/build/outputs/apk/release/app-release.apk#OwnDroid-${{ github.ref_name }}.apk -d --notes-from-tag + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Build APK (testkey) + run: ./gradlew build + + - name: Upload testkey APK + run: gh release upload ${{ github.ref_name }} app/build/outputs/apk/release/app-release.apk#OwnDroid-${{ github.ref_name }}-testkey.apk + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@v4 \ No newline at end of file diff --git a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt index c5b28ae..b94ad82 100644 --- a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt @@ -143,7 +143,7 @@ private fun AppInstaller( else Icon(Icons.Default.PlayArrow, null) }, onClick = { - if(SharedPrefs(context).lockPassword.isNullOrEmpty()) onStartInstall() else appLockDialog = true + if(SharedPrefs(context).lockPasswordHash.isNullOrEmpty()) onStartInstall() else appLockDialog = true }, expanded = !installing ) diff --git a/app/src/main/java/com/bintianqi/owndroid/AppLock.kt b/app/src/main/java/com/bintianqi/owndroid/AppLock.kt index 756fd4d..2bc5739 100644 --- a/app/src/main/java/com/bintianqi/owndroid/AppLock.kt +++ b/app/src/main/java/com/bintianqi/owndroid/AppLock.kt @@ -44,7 +44,7 @@ fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) { var input by remember { mutableStateOf("") } var isError by remember { mutableStateOf(false) } fun unlock() { - if(input == sp.lockPassword) { + if(input.hash() == sp.lockPasswordHash) { fm.clearFocus() onSucceed() } else { diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index 28f5b05..8065891 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -489,7 +489,7 @@ fun Home(vm: MyViewModel) { } DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> - if(event == Lifecycle.Event.ON_CREATE && !SharedPrefs(context).lockPassword.isNullOrEmpty()) { + if(event == Lifecycle.Event.ON_CREATE && !SharedPrefs(context).lockPasswordHash.isNullOrEmpty()) { navController.navigate(AppLock) } } diff --git a/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt b/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt index 9885a86..e189c97 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt @@ -28,7 +28,7 @@ class ManageSpaceActivity: FragmentActivity() { setContent { val theme by vm.theme.collectAsStateWithLifecycle() OwnDroidTheme(theme) { - var appLockDialog by remember { mutableStateOf(!SharedPrefs(this).lockPassword.isNullOrEmpty()) } + var appLockDialog by remember { mutableStateOf(!SharedPrefs(this).lockPasswordHash.isNullOrEmpty()) } if(appLockDialog) { Dialog(::finish) { AppLockDialog({ appLockDialog = false }, ::finish) diff --git a/app/src/main/java/com/bintianqi/owndroid/Settings.kt b/app/src/main/java/com/bintianqi/owndroid/Settings.kt index c315486..30a293f 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Settings.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Settings.kt @@ -164,7 +164,7 @@ fun AppLockSettingsScreen(onNavigateUp: () -> Unit) = MyScaffold(R.string.app_lo var confirmPassword by remember { mutableStateOf("") } var allowBiometrics by remember { mutableStateOf(sp.biometricsUnlock) } val fr = FocusRequester() - val alreadySet = !sp.lockPassword.isNullOrEmpty() + val alreadySet = !sp.lockPasswordHash.isNullOrEmpty() val isInputLegal = password.length !in 1..3 && (alreadySet || (password.isNotEmpty() && password.isNotBlank())) Column(Modifier.widthIn(max = 300.dp).align(Alignment.CenterHorizontally)) { OutlinedTextField( @@ -187,7 +187,7 @@ fun AppLockSettingsScreen(onNavigateUp: () -> Unit) = MyScaffold(R.string.app_lo Button( onClick = { fm.clearFocus() - if(password.isNotEmpty()) sp.lockPassword = password + if(password.isNotEmpty()) sp.lockPasswordHash = password.hash() sp.biometricsUnlock = allowBiometrics onNavigateUp() }, @@ -199,7 +199,7 @@ fun AppLockSettingsScreen(onNavigateUp: () -> Unit) = MyScaffold(R.string.app_lo if(alreadySet) FilledTonalButton( onClick = { fm.clearFocus() - sp.lockPassword = "" + sp.lockPasswordHash = "" sp.biometricsUnlock = false onNavigateUp() }, diff --git a/app/src/main/java/com/bintianqi/owndroid/SharedPrefs.kt b/app/src/main/java/com/bintianqi/owndroid/SharedPrefs.kt index 07d5b1f..2cf2856 100644 --- a/app/src/main/java/com/bintianqi/owndroid/SharedPrefs.kt +++ b/app/src/main/java/com/bintianqi/owndroid/SharedPrefs.kt @@ -19,7 +19,7 @@ class SharedPrefs(context: Context) { /** -1: follow system, 0: off, 1: on */ var darkTheme by IntSharedPref("theme.dark", -1) var blackTheme by BooleanSharedPref("theme.black") - var lockPassword by StringSharedPref("lock.password") + var lockPasswordHash by StringSharedPref("lock.password.sha256") var biometricsUnlock by BooleanSharedPref("lock.biometrics") var applicationsListView by BooleanSharedPref("applications.list_view", true) } diff --git a/app/src/main/java/com/bintianqi/owndroid/Utils.kt b/app/src/main/java/com/bintianqi/owndroid/Utils.kt index 0bda227..68fab38 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Utils.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Utils.kt @@ -20,6 +20,7 @@ import kotlinx.serialization.json.Json import java.io.FileNotFoundException import java.io.IOException import java.io.InputStream +import java.security.MessageDigest import java.text.SimpleDateFormat import java.time.Instant import java.time.ZoneId @@ -89,11 +90,6 @@ fun Context.showOperationResultToast(success: Boolean) { Toast.makeText(this, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show() } -@SuppressLint("PrivateApi") -fun getContext(): Context { - return Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null) as Context -} - const val APK_MIME = "application/vnd.android.package-archive" inline fun serializableNavTypePair() = @@ -130,3 +126,9 @@ fun NavHostController.navigate(route: T, args: Bundle) { } val HorizontalPadding = 16.dp + +@OptIn(ExperimentalStdlibApi::class) +fun String.hash(): String { + val md = MessageDigest.getInstance("SHA-256") + return md.digest(this.encodeToByteArray()).toHexString() +} diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt index f34262e..5ca1902 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt @@ -137,71 +137,6 @@ fun Context.getReceiver(): ComponentName { val dhizukuErrorStatus = MutableStateFlow(0) -fun Context.resetDevicePolicy() { - val dpm = getDPM() - val receiver = getReceiver() - RestrictionData.getAllRestrictions().forEach { - dpm.clearUserRestriction(receiver, it.id) - } - dpm.accountTypesWithManagementDisabled?.forEach { - dpm.setAccountManagementDisabled(receiver, it, false) - } - if (VERSION.SDK_INT >= 30) { - dpm.setConfiguredNetworksLockdownState(receiver, false) - dpm.setAutoTimeZoneEnabled(receiver, true) - dpm.setAutoTimeEnabled(receiver, true) - dpm.setCommonCriteriaModeEnabled(receiver, false) - try { - val frp = FactoryResetProtectionPolicy.Builder().setFactoryResetProtectionEnabled(false).setFactoryResetProtectionAccounts(listOf()) - dpm.setFactoryResetProtectionPolicy(receiver, frp.build()) - } catch(_: Exception) {} - dpm.setUserControlDisabledPackages(receiver, listOf()) - } - if (VERSION.SDK_INT >= 33) { - dpm.minimumRequiredWifiSecurityLevel = DevicePolicyManager.WIFI_SECURITY_OPEN - dpm.wifiSsidPolicy = null - } - if (VERSION.SDK_INT >= 28) { - dpm.getOverrideApns(receiver).forEach { dpm.removeOverrideApn(receiver, it.id) } - dpm.setKeepUninstalledPackages(receiver, listOf()) - } - dpm.setCameraDisabled(receiver, false) - dpm.setScreenCaptureDisabled(receiver, false) - dpm.setMasterVolumeMuted(receiver, false) - try { - if(VERSION.SDK_INT >= 31) dpm.isUsbDataSignalingEnabled = true - } catch (_: Exception) { } - if (VERSION.SDK_INT >= 23) { - dpm.setPermissionPolicy(receiver, DevicePolicyManager.PERMISSION_POLICY_PROMPT) - dpm.setSystemUpdatePolicy(receiver, SystemUpdatePolicy.createAutomaticInstallPolicy()) - } - if (VERSION.SDK_INT >= 24) { - dpm.setAlwaysOnVpnPackage(receiver, null, false) - dpm.setPackagesSuspended(receiver, arrayOf(), false) - } - dpm.setPermittedInputMethods(receiver, null) - dpm.setPermittedAccessibilityServices(receiver, null) - packageManager.getInstalledApplications(0).forEach { - if (dpm.isUninstallBlocked(receiver, it.packageName)) dpm.setUninstallBlocked(receiver, it.packageName, false) - } - if (VERSION.SDK_INT >= 26) { - dpm.setRequiredStrongAuthTimeout(receiver, 0) - dpm.clearResetPasswordToken(receiver) - } - if (VERSION.SDK_INT >= 31) { - dpm.requiredPasswordComplexity = DevicePolicyManager.PASSWORD_COMPLEXITY_NONE - } - dpm.setKeyguardDisabledFeatures(receiver, 0) - dpm.setMaximumTimeToLock(receiver, 0) - dpm.setPasswordExpirationTimeout(receiver, 0) - dpm.setMaximumFailedPasswordsForWipe(receiver, 0) - dpm.setPasswordHistoryLength(receiver, 0) - if (VERSION.SDK_INT < 31) { - dpm.setPasswordQuality(receiver, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) - } - dpm.setRecommendedGlobalProxy(receiver, null) -} - data class PermissionItem( val permission: String, @StringRes val label: Int, diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt index 3ea452c..74ad8f5 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt @@ -763,7 +763,7 @@ fun WifiSecurityLevelScreen(onNavigateUp: () -> Unit) { val dpm = context.getDPM() var selectedWifiSecLevel by remember { mutableIntStateOf(0) } LaunchedEffect(Unit) { selectedWifiSecLevel = dpm.minimumRequiredWifiSecurityLevel } - MySmallTitleScaffold(R.string.min_wifi_security_level, onNavigateUp, 0.dp) { + MyScaffold(R.string.min_wifi_security_level, onNavigateUp, 0.dp) { FullWidthRadioButtonItem(R.string.wifi_security_open, selectedWifiSecLevel == WIFI_SECURITY_OPEN) { selectedWifiSecLevel = WIFI_SECURITY_OPEN } FullWidthRadioButtonItem("WEP, WPA(2)-PSK", selectedWifiSecLevel == WIFI_SECURITY_PERSONAL) { selectedWifiSecLevel = WIFI_SECURITY_PERSONAL } FullWidthRadioButtonItem("WPA-EAP", selectedWifiSecLevel == WIFI_SECURITY_ENTERPRISE_EAP) { selectedWifiSecLevel = WIFI_SECURITY_ENTERPRISE_EAP } @@ -809,8 +809,6 @@ fun WifiSsidPolicyScreen(onNavigateUp: () -> Unit) { AnimatedVisibility(selectedPolicyType != -1) { var inputSsid by remember { mutableStateOf("") } Column(Modifier.padding(horizontal = HorizontalPadding)) { - Text(stringResource(R.string.ssid_list_is)) - if(ssidList.isEmpty()) Text(stringResource(R.string.none)) Column(modifier = Modifier.animateContentSize()) { for(i in ssidList) { ListItem(i.bytes.decodeToString()) { ssidList -= i } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt index 91d314c..cfaaf5b 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt @@ -247,7 +247,13 @@ fun WorkModesScreen( Icon(Icons.Default.MoreVert, null) } DropdownMenu(expanded, { expanded = false }) { - DropdownMenuItem({ Text(stringResource(R.string.deactivate)) }, { dialog = 4 }) + DropdownMenuItem( + { Text(stringResource(R.string.deactivate)) }, + { + expanded = false + dialog = 4 + } + ) if(!privilege.dhizuku && VERSION.SDK_INT >= 28) DropdownMenuItem( { Text(stringResource(R.string.transfer_ownership)) }, { @@ -404,7 +410,10 @@ fun WorkModesScreen( Text(stringResource(R.string.confirm)) } }, - onDismissRequest = {} + onDismissRequest = { + dialog = 0 + if(operationSucceed && !params.canNavigateUp) onActivate() + } ) if(dialog == 4) AlertDialog( title = { Text(stringResource(R.string.deactivate)) }, @@ -422,6 +431,7 @@ fun WorkModesScreen( dpm.clearProfileOwner(ComponentName(context, Receiver::class.java)) } } + dialog = 0 updatePrivilege(context) handlePrivilegeChange(context) onDeactivate() @@ -498,7 +508,7 @@ fun activateUsingDhizuku(context: Context, callback: (Boolean, Boolean, String?) callback(true, false, null) } } - if(Dhizuku.init()) { + if(Dhizuku.init(context)) { if(Dhizuku.isPermissionGranted()) { doTransfer() } else { @@ -519,7 +529,7 @@ fun activateDhizukuMode(context: Context, callback: (Boolean, Boolean, String?) SharedPrefs(context).dhizuku = true callback(true, true, null) } - if(Dhizuku.init()) { + if(Dhizuku.init(context)) { if(Dhizuku.isPermissionGranted()) { onSucceed() } else { diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt index 059f63b..0ab683a 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt @@ -1,14 +1,19 @@ package com.bintianqi.owndroid.ui -import android.widget.Toast import androidx.annotation.DrawableRes import androidx.annotation.StringRes -import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.rememberScrollState @@ -18,27 +23,39 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.outlined.Info -import androidx.compose.material3.* +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Checkbox +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.runtime.* +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +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.draw.rotate import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.writeClipBoard import com.bintianqi.owndroid.zhCN -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch @Composable fun FunctionItem( diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 7fd66b3..df8d9e6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -85,7 +85,6 @@ Изменить состояние пакета Предоставить разрешения Добавить делегированного админа - Сбросить политику устройства Информация об устройстве Поддержка аттестации идентификатора устройства Поддержка аттестации уникального устройства @@ -247,7 +246,6 @@ Открытая Блокировка сети, настроенной администратором Политика SSID Wi-Fi - Список SSID: Уже существует Сетевая статистика Тип diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 55e310e..4f20ba7 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -84,7 +84,6 @@ Paket Durumunu Değiştir İzinleri Ver Yetkilendirilmiş Yönetici Ekle - Cihaz Politikasını Sıfırla Cihaz Bilgisi Cihaz Kimliği Doğrulamasını Destekler Benzersiz Cihaz Doğrulamasını Destekler @@ -244,7 +243,6 @@ Açık Yönetici Tarafından Yapılandırılan Ağı Kilitle Wi-Fi SSID Politikası - SSID Listesi: Zaten Mevcut Ağ İstatistikleri Tür diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 723b63d..d30e27f 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -82,7 +82,6 @@ 修改软件包状态 授予权限 添加委托管理员 - 重置设备策略 设备信息 支持设备ID认证 支持唯一设备认证 @@ -241,7 +240,6 @@ 开放 锁定由管理员配置的网络 Wi-Fi SSID策略 - SSID列表: 已经存在 网络统计 类型 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 33de75e..96a1231 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -87,7 +87,6 @@ Change package state Grant permissions Add delegated admin - Reset device policy Device info Support Device ID attestation Support unique device attestation @@ -270,7 +269,6 @@ Open Lockdown admin configured network Wi-Fi SSID policy - SSID list: Already exist Network stats Type