diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d92713..998b4e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,10 +61,9 @@ jobs: upload-telegram: name: Upload Builds - if: ${{ success() && github.ref_name == 'dev' }} + if: ${{ github.ref_name == 'dev' }} runs-on: ubuntu-latest - needs: - - build + needs: ["build"] steps: - name: Download Artifacts uses: actions/download-artifact@v4 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c77bc25..fbd3c4c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -57,6 +57,9 @@ android { androidResources { generateLocaleConfig = true } + dependenciesInfo { + includeInApk = false + } } kotlin { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5385ad3..daf3620 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,21 +2,6 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/java/com/bintianqi/owndroid/Auth.kt b/app/src/main/java/com/bintianqi/owndroid/Auth.kt index 25a7d58..1171ab9 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Auth.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Auth.kt @@ -1,6 +1,7 @@ package com.bintianqi.owndroid import android.content.Context +import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt @@ -18,6 +19,7 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity @@ -26,6 +28,7 @@ import kotlinx.coroutines.delay @Composable fun Authenticate(activity: FragmentActivity, navCtrl: NavHostController) { + val context = LocalContext.current BackHandler { activity.moveTaskToBack(true) } var status by rememberSaveable { mutableIntStateOf(0) } // 0:Prompt automatically, 1:Authenticating, 2:Prompt manually val onAuthSucceed = { navCtrl.navigateUp() } @@ -37,7 +40,11 @@ fun Authenticate(activity: FragmentActivity, navCtrl: NavHostController) { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) when(errorCode) { - BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> onAuthSucceed() + BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL, BiometricPrompt.ERROR_NO_SPACE, BiometricPrompt.ERROR_HW_NOT_PRESENT, + BiometricPrompt.ERROR_VENDOR, BiometricPrompt.ERROR_NO_BIOMETRICS -> { + Toast.makeText(context, R.string.skipped_authentication, Toast.LENGTH_SHORT).show() + onAuthSucceed() + } else -> status = 2 } } diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index c0b9a60..c1cf922 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -195,7 +195,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { LaunchedEffect(backToHome) { if(backToHome) { navCtrl.navigateUp(); backToHomeStateFlow.value = false } } - NavHost( + @Suppress("NewApi") NavHost( navController = navCtrl, startDestination = "HomePage", modifier = Modifier @@ -226,6 +226,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable(route = "HardwareMonitor") { HardwareMonitor(navCtrl) } composable(route = "ChangeTime") { ChangeTime(navCtrl) } composable(route = "ChangeTimeZone") { ChangeTimeZone(navCtrl) } + //composable(route = "KeyPairs") { KeyPairs(navCtrl) } composable(route = "PermissionPolicy") { PermissionPolicy(navCtrl) } composable(route = "MTEPolicy") { MTEPolicy(navCtrl) } composable(route = "NearbyStreamingPolicy") { NearbyStreamingPolicy(navCtrl) } 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 0e210a0..89d5032 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt @@ -361,7 +361,7 @@ fun processSecurityLogs(securityEvents: List, outputS val buffer = outputStream.bufferedWriter() securityEvents.forEachIndexed { index, event -> val item = buildJsonObject { - put("time_nanos", event.timeNanos) + put("time", event.timeNanos / 1000) put("tag", event.tag) if(VERSION.SDK_INT >= 28) put("level", event.logLevel) if(VERSION.SDK_INT >= 28) put("id", event.id) diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt index 6440e9f..d42225d 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt @@ -173,6 +173,8 @@ fun SystemManage(navCtrl: NavHostController) { FunctionItem(R.string.change_time, icon = R.drawable.schedule_fill0) { navCtrl.navigate("ChangeTime") } FunctionItem(R.string.change_timezone, icon = R.drawable.schedule_fill0) { navCtrl.navigate("ChangeTimeZone") } } + /*if(VERSION.SDK_INT >= 28 && (deviceOwner || profileOwner)) + FunctionItem(R.string.key_pairs, icon = R.drawable.key_vertical_fill0) { navCtrl.navigate("KeyPairs") }*/ if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) { FunctionItem(R.string.permission_policy, icon = R.drawable.key_fill0) { navCtrl.navigate("PermissionPolicy") } } @@ -269,7 +271,7 @@ fun SystemOptions(navCtrl: NavHostController) { ) } else { SwitchItem(R.string.require_auto_time, icon = R.drawable.schedule_fill0, - getState = { dpm.autoTimeRequired}, onCheckedChange = { dpm.setAutoTimeRequired(receiver,it) }, padding = false) + getState = { dpm.autoTimeRequired }, onCheckedChange = { dpm.setAutoTimeRequired(receiver,it) }, padding = false) } } if(deviceOwner || (profileOwner && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && !dpm.isManagedProfile(receiver))))) { @@ -630,6 +632,186 @@ fun ChangeTimeZone(navCtrl: NavHostController) { ) } +/*@RequiresApi(28) +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun KeyPairs(navCtrl: NavHostController) { + val context = LocalContext.current + val dpm = context.getDPM() + val receiver = context.getReceiver() + var alias by remember { mutableStateOf("") } + var purpose by remember { mutableIntStateOf(0) } + //var keySpecType by remember { mutableIntStateOf() } + var ecStdName by remember { mutableStateOf("") } + var rsaKeySize by remember { mutableStateOf("") } + var rsaExponent by remember { mutableStateOf("") } + var algorithm by remember { mutableStateOf("") } + var idAttestationFlags by remember { mutableIntStateOf(0) } + MyScaffold(R.string.key_pairs, 8.dp, navCtrl) { + OutlinedTextField( + value = alias, onValueChange = { alias = it }, label = { Text(stringResource(R.string.alias)) }, + modifier = Modifier.fillMaxWidth() + ) + Text(stringResource(R.string.algorithm), style = typography.titleLarge) + SingleChoiceSegmentedButtonRow { + *//*SegmentedButton( + algorithm == "DH", { algorithm = "DH" }, + shape = SegmentedButtonDefaults.itemShape(index = 0, count = 4) + ) { + Text("DH") + } + SegmentedButton( + algorithm == "DSA", { algorithm = "DSA" }, + shape = SegmentedButtonDefaults.itemShape(index = 1, count = 4) + ) { + Text("DSA") + }*//* + SegmentedButton( + algorithm == "EC", { algorithm = "EC" }, + shape = SegmentedButtonDefaults.itemShape(index = 0, count = 2) + ) { + Text("EC") + } + SegmentedButton( + algorithm == "RSA", { algorithm = "RSA" }, + shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2) + ) { + Text("RSA") + } + } + AnimatedVisibility(algorithm != "") { + Text(stringResource(R.string.key_specification), style = typography.titleLarge) + } + AnimatedVisibility(algorithm == "EC") { + OutlinedTextField( + value = ecStdName, onValueChange = { ecStdName = it }, label = { Text(stringResource(R.string.standard_name)) }, + modifier = Modifier.fillMaxWidth() + ) + } + AnimatedVisibility(algorithm == "RSA") { + Column { + OutlinedTextField( + value = rsaKeySize, onValueChange = { rsaKeySize = it }, label = { Text(stringResource(R.string.key_size)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = rsaExponent, onValueChange = { rsaExponent = it }, label = { Text(stringResource(R.string.exponent)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.fillMaxWidth() + ) + } + } + Text(stringResource(R.string.key_purpose), style = typography.titleLarge) + FlowRow { + if(VERSION.SDK_INT >= 23) { + InputChip( + purpose and KeyProperties.PURPOSE_ENCRYPT != 0, + { purpose = purpose xor KeyProperties.PURPOSE_ENCRYPT }, + { Text(stringResource(R.string.kp_encrypt)) }, + Modifier.padding(horizontal = 4.dp) + ) + InputChip( + purpose and KeyProperties.PURPOSE_DECRYPT != 0, + { purpose = purpose xor KeyProperties.PURPOSE_DECRYPT }, + { Text(stringResource(R.string.kp_decrypt)) }, + Modifier.padding(horizontal = 4.dp) + ) + InputChip( + purpose and KeyProperties.PURPOSE_SIGN != 0, + { purpose = purpose xor KeyProperties.PURPOSE_SIGN }, + { Text(stringResource(R.string.kp_sign)) }, + Modifier.padding(horizontal = 4.dp) + ) + InputChip( + purpose and KeyProperties.PURPOSE_VERIFY != 0, + { purpose = purpose xor KeyProperties.PURPOSE_VERIFY }, + { Text(stringResource(R.string.kp_verify)) }, + Modifier.padding(horizontal = 4.dp) + ) + } + if(VERSION.SDK_INT >= 28) InputChip( + purpose and KeyProperties.PURPOSE_WRAP_KEY != 0, + { purpose = purpose xor KeyProperties.PURPOSE_WRAP_KEY }, + { Text(stringResource(R.string.kp_wrap)) }, + Modifier.padding(horizontal = 4.dp) + ) + if(VERSION.SDK_INT >= 31) { + InputChip( + purpose and KeyProperties.PURPOSE_AGREE_KEY != 0, + { purpose = purpose xor KeyProperties.PURPOSE_AGREE_KEY }, + { Text(stringResource(R.string.kp_agree)) }, + Modifier.padding(horizontal = 4.dp) + ) + InputChip( + purpose and KeyProperties.PURPOSE_ATTEST_KEY != 0, + { purpose = purpose xor KeyProperties.PURPOSE_ATTEST_KEY }, + { Text(stringResource(R.string.kp_attest)) }, + Modifier.padding(horizontal = 4.dp) + ) + } + } + Text(stringResource(R.string.attestation_record_identifiers), style = typography.titleLarge) + FlowRow { + InputChip( + idAttestationFlags and DevicePolicyManager.ID_TYPE_BASE_INFO != 0, + { idAttestationFlags = idAttestationFlags xor DevicePolicyManager.ID_TYPE_BASE_INFO }, + { Text(stringResource(R.string.base_info)) }, + Modifier.padding(horizontal = 4.dp) + ) + InputChip( + idAttestationFlags and DevicePolicyManager.ID_TYPE_SERIAL != 0, + { idAttestationFlags = idAttestationFlags xor DevicePolicyManager.ID_TYPE_SERIAL }, + { Text(stringResource(R.string.serial_number)) }, + Modifier.padding(horizontal = 4.dp) + ) + InputChip( + idAttestationFlags and DevicePolicyManager.ID_TYPE_IMEI != 0, + { idAttestationFlags = idAttestationFlags xor DevicePolicyManager.ID_TYPE_IMEI }, + { Text("IMEI") }, + Modifier.padding(horizontal = 4.dp) + ) + InputChip( + idAttestationFlags and DevicePolicyManager.ID_TYPE_MEID != 0, + { idAttestationFlags = idAttestationFlags xor DevicePolicyManager.ID_TYPE_MEID }, + { Text("MEID") }, + Modifier.padding(horizontal = 4.dp) + ) + if(VERSION.SDK_INT >= 30) InputChip( + idAttestationFlags and DevicePolicyManager.ID_TYPE_INDIVIDUAL_ATTESTATION != 0, + { idAttestationFlags = idAttestationFlags xor DevicePolicyManager.ID_TYPE_INDIVIDUAL_ATTESTATION }, + { Text(stringResource(R.string.individual_certificate)) }, + Modifier.padding(horizontal = 4.dp) + ) + } + Button( + onClick = { + try { + val aps = if(algorithm == "EC") ECGenParameterSpec(ecStdName) + else RSAKeyGenParameterSpec(rsaKeySize.toInt(), rsaExponent.toBigInteger()) + val keySpec = KeyGenParameterSpec.Builder(alias, purpose).run { + setAlgorithmParameterSpec(aps) + this.setAttestationChallenge() + build() + } + dpm.generateKeyPair(receiver, algorithm, keySpec, idAttestationFlags) + } catch(e: Exception) { + AlertDialog.Builder(context) + .setTitle(R.string.error) + .setMessage(e.message ?: "") + .setPositiveButton(R.string.confirm) { dialog, _ -> dialog.dismiss() } + .show() + } + }, + modifier = Modifier.fillMaxWidth(), + enabled = alias != "" && purpose != 0 && + ((algorithm == "EC") || (algorithm == "RSA" && rsaKeySize.all { it.isDigit() } && rsaExponent.all { it.isDigit() })) + ) { + Text(stringResource(R.string.generate)) + } + } +}*/ + @RequiresApi(23) @Composable fun PermissionPolicy(navCtrl: NavHostController) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 92a9c79..70b7ee8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -169,6 +169,25 @@ Change timezone Timezone ID Auto timezone should be disabled before set a custom timezone. + Permission policy Auto grant Auto deny @@ -585,6 +604,7 @@ Authenticate Lock when switch to background Clear storage + Skipped authentication because it is unavailable. API key The API key already exists, setting a new key will overwrite the current key.