mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-05-02 02:50:35 +00:00
android: add camera control, finally
i got too lazy to find out how to listen to app openings earlier, wasn't too hard
This commit is contained in:
@@ -110,6 +110,7 @@ import me.kavishdevar.librepods.screens.AccessibilitySettingsScreen
|
|||||||
import me.kavishdevar.librepods.screens.AdaptiveStrengthScreen
|
import me.kavishdevar.librepods.screens.AdaptiveStrengthScreen
|
||||||
import me.kavishdevar.librepods.screens.AirPodsSettingsScreen
|
import me.kavishdevar.librepods.screens.AirPodsSettingsScreen
|
||||||
import me.kavishdevar.librepods.screens.AppSettingsScreen
|
import me.kavishdevar.librepods.screens.AppSettingsScreen
|
||||||
|
import me.kavishdevar.librepods.screens.CameraControlScreen
|
||||||
import me.kavishdevar.librepods.screens.DebugScreen
|
import me.kavishdevar.librepods.screens.DebugScreen
|
||||||
import me.kavishdevar.librepods.screens.HeadTrackingScreen
|
import me.kavishdevar.librepods.screens.HeadTrackingScreen
|
||||||
import me.kavishdevar.librepods.screens.HearingAidAdjustmentsScreen
|
import me.kavishdevar.librepods.screens.HearingAidAdjustmentsScreen
|
||||||
@@ -394,6 +395,9 @@ fun Main() {
|
|||||||
composable("adaptive_strength") {
|
composable("adaptive_strength") {
|
||||||
AdaptiveStrengthScreen(navController)
|
AdaptiveStrengthScreen(navController)
|
||||||
}
|
}
|
||||||
|
composable("camera_control") {
|
||||||
|
CameraControlScreen(navController)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,8 +83,7 @@ fun CallControlSettings(hazeState: HazeState) {
|
|||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = textColor.copy(alpha = 0.6f)
|
color = textColor.copy(alpha = 0.6f)
|
||||||
),
|
)
|
||||||
modifier = Modifier.padding(16.dp, bottom = 4.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,16 @@ data class SelectItem(
|
|||||||
val enabled: Boolean = true
|
val enabled: Boolean = true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class SelectItem2(
|
||||||
|
val name: String,
|
||||||
|
val description: String? = null,
|
||||||
|
val iconRes: Int? = null,
|
||||||
|
val selected: () -> Boolean,
|
||||||
|
val onClick: () -> Unit,
|
||||||
|
val enabled: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StyledSelectList(
|
fun StyledSelectList(
|
||||||
items: List<SelectItem>,
|
items: List<SelectItem>,
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ enum class StemAction {
|
|||||||
PLAY_PAUSE,
|
PLAY_PAUSE,
|
||||||
PREVIOUS_TRACK,
|
PREVIOUS_TRACK,
|
||||||
NEXT_TRACK,
|
NEXT_TRACK,
|
||||||
CAMERA_SHUTTER,
|
|
||||||
DIGITAL_ASSISTANT,
|
DIGITAL_ASSISTANT,
|
||||||
CYCLE_NOISE_CONTROL_MODES;
|
CYCLE_NOISE_CONTROL_MODES;
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* LibrePods - AirPods liberated from Apple’s ecosystem
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 LibrePods contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package me.kavishdevar.librepods.screens
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.view.accessibility.AccessibilityManager
|
||||||
|
import android.accessibilityservice.AccessibilityServiceInfo
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.kyant.backdrop.backdrops.layerBackdrop
|
||||||
|
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
|
||||||
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import me.kavishdevar.librepods.R
|
||||||
|
import me.kavishdevar.librepods.composables.SelectItem
|
||||||
|
import me.kavishdevar.librepods.composables.StyledIconButton
|
||||||
|
import me.kavishdevar.librepods.composables.StyledScaffold
|
||||||
|
import me.kavishdevar.librepods.composables.StyledSelectList
|
||||||
|
import me.kavishdevar.librepods.composables.StyledSlider
|
||||||
|
import me.kavishdevar.librepods.services.ServiceManager
|
||||||
|
import me.kavishdevar.librepods.services.AppListenerService
|
||||||
|
import me.kavishdevar.librepods.utils.AACPManager
|
||||||
|
import me.kavishdevar.librepods.utils.AACPManager.Companion.StemPressType
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
|
private var debounceJob: Job? = null
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
@ExperimentalHazeMaterialsApi
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalEncodingApi::class)
|
||||||
|
@Composable
|
||||||
|
fun CameraControlScreen(navController: NavController) {
|
||||||
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
val context = LocalContext.current
|
||||||
|
val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
val service = ServiceManager.getService()!!
|
||||||
|
var currentCameraAction by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
sharedPreferences.getString("camera_action", null)?.let { StemPressType.valueOf(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isAppListenerServiceEnabled(context: Context): Boolean {
|
||||||
|
val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
|
||||||
|
val enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
|
||||||
|
val serviceComponent = ComponentName(context, AppListenerService::class.java)
|
||||||
|
return enabledServices.any { it.resolveInfo.serviceInfo.packageName == serviceComponent.packageName && it.resolveInfo.serviceInfo.name == serviceComponent.className }
|
||||||
|
}
|
||||||
|
|
||||||
|
val cameraOptions = listOf(
|
||||||
|
SelectItem(
|
||||||
|
name = stringResource(R.string.off),
|
||||||
|
selected = currentCameraAction == null,
|
||||||
|
onClick = {
|
||||||
|
sharedPreferences.edit { remove("camera_action") }
|
||||||
|
currentCameraAction = null
|
||||||
|
}
|
||||||
|
),
|
||||||
|
SelectItem(
|
||||||
|
name = stringResource(R.string.press_once),
|
||||||
|
selected = currentCameraAction == StemPressType.SINGLE_PRESS,
|
||||||
|
onClick = {
|
||||||
|
if (!isAppListenerServiceEnabled(context)) {
|
||||||
|
context.startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
|
||||||
|
} else {
|
||||||
|
sharedPreferences.edit { putString("camera_action", StemPressType.SINGLE_PRESS.name) }
|
||||||
|
currentCameraAction = StemPressType.SINGLE_PRESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
SelectItem(
|
||||||
|
name = stringResource(R.string.press_and_hold_airpods),
|
||||||
|
selected = currentCameraAction == StemPressType.LONG_PRESS,
|
||||||
|
onClick = {
|
||||||
|
if (!isAppListenerServiceEnabled(context)) {
|
||||||
|
context.startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
|
||||||
|
} else {
|
||||||
|
sharedPreferences.edit { putString("camera_action", StemPressType.LONG_PRESS.name) }
|
||||||
|
currentCameraAction = StemPressType.LONG_PRESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val backdrop = rememberLayerBackdrop()
|
||||||
|
|
||||||
|
StyledScaffold(
|
||||||
|
title = stringResource(R.string.camera_control),
|
||||||
|
navigationButton = {
|
||||||
|
StyledIconButton(
|
||||||
|
onClick = { navController.popBackStack() },
|
||||||
|
icon = "",
|
||||||
|
darkMode = isDarkTheme,
|
||||||
|
backdrop = backdrop
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { spacerHeight ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.layerBackdrop(backdrop)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(spacerHeight))
|
||||||
|
StyledSelectList(items = cameraOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -189,6 +189,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
|
|
||||||
var leftLongPressAction: StemAction = StemAction.defaultActions[StemPressType.LONG_PRESS]!!,
|
var leftLongPressAction: StemAction = StemAction.defaultActions[StemPressType.LONG_PRESS]!!,
|
||||||
var rightLongPressAction: StemAction = StemAction.defaultActions[StemPressType.LONG_PRESS]!!,
|
var rightLongPressAction: StemAction = StemAction.defaultActions[StemPressType.LONG_PRESS]!!,
|
||||||
|
|
||||||
|
var cameraAction: AACPManager.Companion.StemPressType? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private lateinit var config: ServiceConfig
|
private lateinit var config: ServiceConfig
|
||||||
@@ -469,6 +471,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
"right_long_press_action",
|
"right_long_press_action",
|
||||||
StemAction.defaultActions[StemPressType.LONG_PRESS]!!.name
|
StemAction.defaultActions[StemPressType.LONG_PRESS]!!.name
|
||||||
)
|
)
|
||||||
|
if (!contains("camera_action")) putString("camera_action", "SINGLE_PRESS")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -735,22 +738,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun cameraOpened() {
|
fun cameraOpened() {
|
||||||
Log.d(TAG, "Camera opened, gonna handle stem presses and take action if enabled")
|
Log.d(TAG, "Camera opened, gonna handle stem presses and take action if enabled")
|
||||||
val isCameraShutterUsed = listOf(
|
cameraActive = true
|
||||||
config.leftSinglePressAction,
|
setupStemActions()
|
||||||
config.rightSinglePressAction,
|
|
||||||
config.leftDoublePressAction,
|
|
||||||
config.rightDoublePressAction,
|
|
||||||
config.leftTriplePressAction,
|
|
||||||
config.rightTriplePressAction,
|
|
||||||
config.leftLongPressAction,
|
|
||||||
config.rightLongPressAction
|
|
||||||
).any { it == StemAction.CAMERA_SHUTTER }
|
|
||||||
|
|
||||||
if (isCameraShutterUsed) {
|
|
||||||
Log.d(TAG, "Camera opened, setting up stem actions")
|
|
||||||
cameraActive = true
|
|
||||||
setupStemActions(isCameraActive = true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@@ -761,27 +750,27 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
|
|
||||||
fun isCustomAction(
|
fun isCustomAction(
|
||||||
action: StemAction?,
|
action: StemAction?,
|
||||||
default: StemAction?,
|
default: StemAction?
|
||||||
isCameraActive: Boolean = false
|
|
||||||
): Boolean {
|
): Boolean {
|
||||||
Log.d(TAG, "Checking if action $action is custom against default $default, camera active: $isCameraActive")
|
return action != default
|
||||||
return action != default && (action != StemAction.CAMERA_SHUTTER || isCameraActive)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setupStemActions(isCameraActive: Boolean = false) {
|
fun setupStemActions() {
|
||||||
val singlePressDefault = StemAction.defaultActions[StemPressType.SINGLE_PRESS]
|
val singlePressDefault = StemAction.defaultActions[StemPressType.SINGLE_PRESS]
|
||||||
val doublePressDefault = StemAction.defaultActions[StemPressType.DOUBLE_PRESS]
|
val doublePressDefault = StemAction.defaultActions[StemPressType.DOUBLE_PRESS]
|
||||||
val triplePressDefault = StemAction.defaultActions[StemPressType.TRIPLE_PRESS]
|
val triplePressDefault = StemAction.defaultActions[StemPressType.TRIPLE_PRESS]
|
||||||
val longPressDefault = StemAction.defaultActions[StemPressType.LONG_PRESS]
|
val longPressDefault = StemAction.defaultActions[StemPressType.LONG_PRESS]
|
||||||
|
|
||||||
val singlePressCustomized = isCustomAction(config.leftSinglePressAction, singlePressDefault, isCameraActive) ||
|
val singlePressCustomized = isCustomAction(config.leftSinglePressAction, singlePressDefault) ||
|
||||||
isCustomAction(config.rightSinglePressAction, singlePressDefault, isCameraActive)
|
isCustomAction(config.rightSinglePressAction, singlePressDefault) ||
|
||||||
val doublePressCustomized = isCustomAction(config.leftDoublePressAction, doublePressDefault, isCameraActive) ||
|
(cameraActive && config.cameraAction == StemPressType.SINGLE_PRESS)
|
||||||
isCustomAction(config.rightDoublePressAction, doublePressDefault, isCameraActive)
|
val doublePressCustomized = isCustomAction(config.leftDoublePressAction, doublePressDefault) ||
|
||||||
val triplePressCustomized = isCustomAction(config.leftTriplePressAction, triplePressDefault, isCameraActive) ||
|
isCustomAction(config.rightDoublePressAction, doublePressDefault)
|
||||||
isCustomAction(config.rightTriplePressAction, triplePressDefault, isCameraActive)
|
val triplePressCustomized = isCustomAction(config.leftTriplePressAction, triplePressDefault) ||
|
||||||
val longPressCustomized = isCustomAction(config.leftLongPressAction, longPressDefault, isCameraActive) ||
|
isCustomAction(config.rightTriplePressAction, triplePressDefault)
|
||||||
isCustomAction(config.rightLongPressAction, longPressDefault, isCameraActive)
|
val longPressCustomized = isCustomAction(config.leftLongPressAction, longPressDefault) ||
|
||||||
|
isCustomAction(config.rightLongPressAction, longPressDefault) ||
|
||||||
|
(cameraActive && config.cameraAction == StemPressType.LONG_PRESS)
|
||||||
Log.d(TAG, "Setting up stem actions: " +
|
Log.d(TAG, "Setting up stem actions: " +
|
||||||
"Single Press Customized: $singlePressCustomized, " +
|
"Single Press Customized: $singlePressCustomized, " +
|
||||||
"Double Press Customized: $doublePressCustomized, " +
|
"Double Press Customized: $doublePressCustomized, " +
|
||||||
@@ -963,12 +952,14 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
override fun onStemPressReceived(stemPress: ByteArray) {
|
override fun onStemPressReceived(stemPress: ByteArray) {
|
||||||
val (stemPressType, bud) = aacpManager.parseStemPressResponse(stemPress)
|
val (stemPressType, bud) = aacpManager.parseStemPressResponse(stemPress)
|
||||||
|
|
||||||
Log.d("AirPodsParser", "Stem press received: $stemPressType on $bud")
|
Log.d("AirPodsParser", "Stem press received: $stemPressType on $bud, cameraActive: $cameraActive, cameraAction: ${config.cameraAction}")
|
||||||
|
if (cameraActive && config.cameraAction != null && stemPressType == config.cameraAction) {
|
||||||
val action = getActionFor(bud, stemPressType)
|
Runtime.getRuntime().exec(arrayOf("su", "-c", "input keyevent 27"))
|
||||||
Log.d("AirPodsParser", "$bud $stemPressType action: $action")
|
} else {
|
||||||
|
val action = getActionFor(bud, stemPressType)
|
||||||
action?.let { executeStemAction(it) }
|
Log.d("AirPodsParser", "$bud $stemPressType action: $action")
|
||||||
|
action?.let { executeStemAction(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
override fun onAudioSourceReceived(audioSource: ByteArray) {
|
override fun onAudioSourceReceived(audioSource: ByteArray) {
|
||||||
Log.d("AirPodsParser", "Audio source changed mac: ${aacpManager.audioSource?.mac}, type: ${aacpManager.audioSource?.type?.name}")
|
Log.d("AirPodsParser", "Audio source changed mac: ${aacpManager.audioSource?.mac}, type: ${aacpManager.audioSource?.type?.name}")
|
||||||
@@ -1024,7 +1015,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
StemAction.PLAY_PAUSE -> MediaController.sendPlayPause()
|
StemAction.PLAY_PAUSE -> MediaController.sendPlayPause()
|
||||||
StemAction.PREVIOUS_TRACK -> MediaController.sendPreviousTrack()
|
StemAction.PREVIOUS_TRACK -> MediaController.sendPreviousTrack()
|
||||||
StemAction.NEXT_TRACK -> MediaController.sendNextTrack()
|
StemAction.NEXT_TRACK -> MediaController.sendNextTrack()
|
||||||
StemAction.CAMERA_SHUTTER -> Runtime.getRuntime().exec(arrayOf("su", "-c", "input keyevent 27"))
|
|
||||||
StemAction.DIGITAL_ASSISTANT -> {
|
StemAction.DIGITAL_ASSISTANT -> {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
val intent = Intent(Intent.ACTION_VOICE_COMMAND).apply {
|
val intent = Intent(Intent.ACTION_VOICE_COMMAND).apply {
|
||||||
@@ -1171,7 +1161,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
rightTriplePressAction = StemAction.fromString(sharedPreferences.getString("right_triple_press_action", "PREVIOUS_TRACK") ?: "PREVIOUS_TRACK")!!,
|
rightTriplePressAction = StemAction.fromString(sharedPreferences.getString("right_triple_press_action", "PREVIOUS_TRACK") ?: "PREVIOUS_TRACK")!!,
|
||||||
|
|
||||||
leftLongPressAction = StemAction.fromString(sharedPreferences.getString("left_long_press_action", "CYCLE_NOISE_CONTROL_MODES") ?: "CYCLE_NOISE_CONTROL_MODES")!!,
|
leftLongPressAction = StemAction.fromString(sharedPreferences.getString("left_long_press_action", "CYCLE_NOISE_CONTROL_MODES") ?: "CYCLE_NOISE_CONTROL_MODES")!!,
|
||||||
rightLongPressAction = StemAction.fromString(sharedPreferences.getString("right_long_press_action", "DIGITAL_ASSISTANT") ?: "DIGITAL_ASSISTANT")!!
|
rightLongPressAction = StemAction.fromString(sharedPreferences.getString("right_long_press_action", "DIGITAL_ASSISTANT") ?: "DIGITAL_ASSISTANT")!!,
|
||||||
|
|
||||||
|
cameraAction = sharedPreferences.getString("camera_action", null)?.let { AACPManager.Companion.StemPressType.valueOf(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1252,6 +1244,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
)!!
|
)!!
|
||||||
setupStemActions()
|
setupStemActions()
|
||||||
}
|
}
|
||||||
|
"camera_action" -> config.cameraAction = preferences.getString(key, null)?.let { AACPManager.Companion.StemPressType.valueOf(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == "mac_address") {
|
if (key == "mac_address") {
|
||||||
@@ -1780,6 +1773,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
handleIncomingCallOnceConnected = false
|
handleIncomingCallOnceConnected = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,15 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalEncodingApi::class)
|
||||||
|
|
||||||
package me.kavishdevar.librepods.services
|
package me.kavishdevar.librepods.services
|
||||||
|
|
||||||
|
|
||||||
import android.accessibilityservice.AccessibilityService
|
import android.accessibilityservice.AccessibilityService
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.accessibility.AccessibilityEvent
|
import android.view.accessibility.AccessibilityEvent
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
private const val TAG="AppListenerService"
|
private const val TAG="AppListenerService"
|
||||||
|
|
||||||
@@ -35,12 +38,28 @@ val cameraPackages = setOf(
|
|||||||
"com.nothing.camera"
|
"com.nothing.camera"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var cameraOpen = false
|
||||||
|
|
||||||
class AppListenerService : AccessibilityService() {
|
class AppListenerService : AccessibilityService() {
|
||||||
override fun onAccessibilityEvent(ev: AccessibilityEvent?) {
|
override fun onAccessibilityEvent(ev: AccessibilityEvent?) {
|
||||||
try {
|
try {
|
||||||
if (ev?.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
|
if (ev?.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
|
||||||
val pkg = ev.packageName?.toString() ?: return
|
val pkg = ev.packageName?.toString() ?: return
|
||||||
Log.d(TAG, "Opened: $pkg")
|
if (pkg == "com.android.systemui") return // after camera opens, systemui is opened, probably for the privacy indicators
|
||||||
|
Log.d(TAG, "Package: $pkg, cameraOpen: $cameraOpen")
|
||||||
|
if (pkg in cameraPackages) {
|
||||||
|
Log.d(TAG, "Camera app opened: $pkg")
|
||||||
|
if (!cameraOpen) cameraOpen = true
|
||||||
|
ServiceManager.getService()?.cameraOpened()
|
||||||
|
} else {
|
||||||
|
if (cameraOpen) {
|
||||||
|
cameraOpen = false
|
||||||
|
ServiceManager.getService()?.cameraClosed()
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "ignoring")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Log.d(TAG, "Opened: $pkg")
|
||||||
}
|
}
|
||||||
} catch(e: Exception) {
|
} catch(e: Exception) {
|
||||||
Log.e(TAG, "Error in onAccessibilityEvent: ${e.message}")
|
Log.e(TAG, "Error in onAccessibilityEvent: ${e.message}")
|
||||||
|
|||||||
@@ -177,4 +177,6 @@
|
|||||||
<string name="camera_remote">Camera Remote</string>
|
<string name="camera_remote">Camera Remote</string>
|
||||||
<string name="camera_control">Camera Control</string>
|
<string name="camera_control">Camera Control</string>
|
||||||
<string name="camera_control_description">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.</string>
|
<string name="camera_control_description">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.</string>
|
||||||
|
<string name="app_listener_service_label">Camera listener</string>
|
||||||
|
<string name="app_listener_service_description">Listener service for LibrePods to detect when the camera is active to activate camera control on AirPods.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,7 @@
|
|||||||
android:accessibilityFeedbackType="feedbackGeneric"
|
android:accessibilityFeedbackType="feedbackGeneric"
|
||||||
android:notificationTimeout="50"
|
android:notificationTimeout="50"
|
||||||
android:canRetrieveWindowContent="false"
|
android:canRetrieveWindowContent="false"
|
||||||
|
android:label="@string/app_listener_service_label"
|
||||||
|
android:description="@string/app_listener_service_description"
|
||||||
|
android:settingsActivity="me.kavishdevar.librepods.MainActivity"
|
||||||
android:accessibilityFlags="flagReportViewIds"/>
|
android:accessibilityFlags="flagReportViewIds"/>
|
||||||
Reference in New Issue
Block a user