diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt index 01f3524..71d8267 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt @@ -103,14 +103,18 @@ fun AppSettingsScreen(navController: NavController) { val showResetDialog = remember { mutableStateOf(false) } val showIrkDialog = remember { mutableStateOf(false) } val showEncKeyDialog = remember { mutableStateOf(false) } + val showCameraDialog = remember { mutableStateOf(false) } val irkValue = remember { mutableStateOf("") } val encKeyValue = remember { mutableStateOf("") } + val cameraPackageValue = remember { mutableStateOf("") } val irkError = remember { mutableStateOf(null) } val encKeyError = remember { mutableStateOf(null) } + val cameraPackageError = remember { mutableStateOf(null) } LaunchedEffect(Unit) { val savedIrk = sharedPreferences.getString(AACPManager.Companion.ProximityKeyType.IRK.name, null) val savedEncKey = sharedPreferences.getString(AACPManager.Companion.ProximityKeyType.ENC_KEY.name, null) + val savedCameraPackage = sharedPreferences.getString("custom_camera_package", null) if (savedIrk != null) { try { @@ -131,6 +135,9 @@ fun AppSettingsScreen(navController: NavController) { e.printStackTrace() } } + if (savedCameraPackage != null) { + cameraPackageValue.value = savedCameraPackage + } } val showPhoneBatteryInWidget = remember { @@ -292,6 +299,18 @@ fun AppSettingsScreen(navController: NavController) { independent = true ) + Spacer(modifier = Modifier.height(16.dp)) + + NavigationButton( + to = "", + title = stringResource(R.string.camera_control), + name = stringResource(R.string.set_custom_camera_package), + navController = navController, + onClick = { showCameraDialog.value = true }, + independent = true, + description = stringResource(R.string.camera_control_app_description) + ) + Spacer(modifier = Modifier.height(16.dp)) StyledToggle( @@ -877,6 +896,86 @@ fun AppSettingsScreen(navController: NavController) { } ) } + + if (showCameraDialog.value) { + AlertDialog( + onDismissRequest = { showCameraDialog.value = false }, + title = { + Text( + stringResource(R.string.set_custom_camera_package), + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Medium + ) + }, + text = { + Column { + Text( + stringResource(R.string.enter_custom_camera_package), + fontFamily = FontFamily(Font(R.font.sf_pro)), + modifier = Modifier.padding(bottom = 8.dp) + ) + + OutlinedTextField( + value = cameraPackageValue.value, + onValueChange = { + cameraPackageValue.value = it + cameraPackageError.value = null + }, + modifier = Modifier.fillMaxWidth(), + isError = cameraPackageError.value != null, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Ascii, + capitalization = KeyboardCapitalization.None + ), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5), + unfocusedBorderColor = if (isDarkTheme) Color.Gray else Color.LightGray + ), + supportingText = { + if (cameraPackageError.value != null) { + Text(cameraPackageError.value!!, color = MaterialTheme.colorScheme.error) + } + }, + label = { Text(stringResource(R.string.custom_camera_package)) } + ) + } + }, + confirmButton = { + val successText = stringResource(R.string.custom_camera_package_set_success) + TextButton( + onClick = { + if (cameraPackageValue.value.isBlank()) { + sharedPreferences.edit { remove("custom_camera_package") } + Toast.makeText(context, successText, Toast.LENGTH_SHORT).show() + showCameraDialog.value = false + return@TextButton + } + + sharedPreferences.edit { putString("custom_camera_package", cameraPackageValue.value) } + Toast.makeText(context, successText, Toast.LENGTH_SHORT).show() + showCameraDialog.value = false + } + ) { + Text( + "Save", + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Medium + ) + } + }, + dismissButton = { + TextButton( + onClick = { showCameraDialog.value = false } + ) { + Text( + "Cancel", + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Medium + ) + } + } + ) + } } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AppListenerService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AppListenerService.kt index 6ef6716..a7ad977 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AppListenerService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AppListenerService.kt @@ -28,19 +28,47 @@ import kotlin.io.encoding.ExperimentalEncodingApi private const val TAG="AppListenerService" -val cameraPackages = setOf( +val cameraPackages = mutableSetOf( "com.google.android.GoogleCamera", "com.sec.android.app.camera", "com.android.camera", "com.oppo.camera", "com.motorola.camera2", - "org.codeaurora.snapcam", - "com.nothing.camera" + "org.codeaurora.snapcam" ) var cameraOpen = false +private var currentCustomPackage: String? = null class AppListenerService : AccessibilityService() { + private lateinit var prefs: android.content.SharedPreferences + private val preferenceChangeListener = android.content.SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key -> + if (key == "custom_camera_package") { + val newPackage = sharedPreferences.getString(key, null) + currentCustomPackage?.let { cameraPackages.remove(it) } + if (newPackage != null && newPackage.isNotBlank()) { + cameraPackages.add(newPackage) + } + currentCustomPackage = newPackage + } + } + + override fun onCreate() { + super.onCreate() + prefs = getSharedPreferences("settings", MODE_PRIVATE) + val customPackage = prefs.getString("custom_camera_package", null) + if (customPackage != null && customPackage.isNotBlank()) { + cameraPackages.add(customPackage) + currentCustomPackage = customPackage + } + prefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener) + } + + override fun onDestroy() { + super.onDestroy() + prefs.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener) + } + override fun onAccessibilityEvent(ev: AccessibilityEvent?) { try { if (ev?.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index e0ead26..4906b50 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -177,6 +177,11 @@ Camera Remote Camera Control Capture a photo, start or stop recording, and more using either Press Once or Press and Hold. When using AirPods for camera actions, if you select Press Once, media control gestures will be unavailable, and if you select Press and Hold, listening mode and Digital Assistant gestures will be unavailable. + Set a custom app package for camera detection + Set Custom Camera appid + Enter the application id of the camera app: + Custom Camera appid + Custom camera appid set successfully Camera listener Listener service for LibrePods to detect when the camera is active to activate camera control on AirPods.