Запрос биометрии (проверка подлинности лица) при блокировке доступа к камере (Android 12 — Pixel)

#android #android-biometric-prompt #android-12

Вопрос:

В Android 12 появились новые настройки конфиденциальности для отключения доступа к датчикам камеры и микрофона, которые в документах называются переключателями.

Как указано в документах:

система напоминает пользователю, что переключатель для всего устройства выключен

Однако, похоже, что он напоминает пользователю только при запросе разрешения камеры, а не при попытке аутентификации пользователя с использованием биометрии (проверка подлинности лица на телефонах Pixel, угадайте, что !? Он использует камеру). [Я использую библиотеку биометрии AndroidX]

Есть ли какой-либо способ узнать, был ли доступ к камере заблокирован пользователем без запроса какого-либо разрешения?

Я предполагаю, что примечание в документах не учитывало, что приложение может использовать проверку подлинности лица:

Примечание. Упомянутые в этом разделе переключатели не должны требовать внесения изменений в логику вашего приложения, если вы соблюдаете рекомендации по обеспечению конфиденциальности.

Примечания:

  • Вы не можете зарегистрировать новое лицо в настройках, когда доступ к камере заблокирован. Приложение настроек не показывает никаких ошибок, просто пустой канал камеры
  • Я использую Pixel 4 (Android 12)
  • Функция «Подключение к Wi-Fi путем сканирования QR-кода» не работает и не отображает обратную связь с пользователем, если доступ к камере заблокирован (пиксель 5)

Ответ №1:

Итак, я также ищу решение — у меня есть биометрическая библиотека, и в DM появляется несколько отчетов с той же проблемой — FaceUnlock не работает на Pixel 4, когда камера «отключена»

На данный момент все еще исправлено, но, возможно, мои исследования могут кому-то помочь.

1. Я проверил новый API для PrivacyToggle.
Android 12 вводит новый SensorPrivacyManager supportsSensorToggle() метод with — он возвращает TRUE в случае, если устройство может «отключить» камеру или микрофон.

 val sensorPrivacyManager = applicationContext
        .getSystemService(SensorPrivacyManager::class.java)
        as SensorPrivacyManager
val supportsMicrophoneToggle = sensorPrivacyManager
        .supportsSensorToggle(Sensors.MICROPHONE)
val supportsCameraToggle = sensorPrivacyManager
        .supportsSensorToggle(Sensors.CAMERA)
 

Если вы заглянете в SensorPrivacyManager, вы обнаружите, что он предоставляет еще несколько полезных методов, поэтому я разрабатываю следующий код:

 fun isCameraAccessible(): Boolean {
        return !checkIsPrivacyToggled(SensorPrivacyManager.Sensors.CAMERA)
    }

    @SuppressLint("PrivateApi")
    private fun checkIsPrivacyToggled(sensor: Int): Boolean {
        val sensorPrivacyManager: SensorPrivacyManager =
            appContext.getSystemService(SensorPrivacyManager::class.java)
        if (sensorPrivacyManager.supportsSensorToggle(sensor)) {

            val userHandleField = UserHandle::class.java.getDeclaredField("USER_CURRENT")

            userHandleField.isAccessible = true

                    val userHandle = userHandleField.get(null) as Int

                    val m = SensorPrivacyManager::class.java.getDeclaredMethod(
                        "isSensorPrivacyEnabled",
                        Int::class.javaPrimitiveType,
                        Int::class.javaPrimitiveType
                    )

                    m.isAccessible = true

                    return m.invoke(
                        sensorPrivacyManager,
                        sensor,
                        userHandle
                    ) as Boolean

        }
        return false
    }
 

К сожалению, служба отклоняет этот вызов из-за SecurityException — отсутствует android.permission.OBSERVE_SENSOR_PRIVACY , даже если мы объявим его в манифесте.
По крайней мере, на эмуляторе.

2. Мы можем попытаться определить новый индикатор «используемого датчика»

 fun checkForIndicator(){
findViewById<View>(Window.ID_ANDROID_CONTENT)?.let {
                it.setOnApplyWindowInsetsListener { view, windowInsets ->
                    val indicatorBounds = windowInsets.privacyIndicatorBounds
                     if(indicatorBounds !=null){
                         Toast.makeText(view.context, "Camera-in-use detected", Toast.LENGTH_LONG).show()
                     }
                    // change your UI to avoid overlapping
                    windowInsets
                }
            }
}
 

Я не тестировал этот код (нет реального устройства), но, как по мне, это не очень полезно, потому что мы можем проверить индикатор камеры только ПОСЛЕ запуска потока биометрической аутентификации, когда мне нужно понять, доступна ли камера ДО начала биометрической аутентификации.

3. Из-за PrivicyToogle, связанного с QuickSettings, я решаю, что, возможно, существует способ, которым плитки определяют текущее состояние переключения конфиденциальности. Но в этом API используется очень интересное решение — он не использует Settings.Global Settings.Security раздел or, вместо этого все настройки сохраняются "system/sensor_privacy.xml" и недоступны для сторонних приложений.

См. SensorPrivacyService.java

Я считаю, что существует способ обнаружить, что камера заблокирована, но, похоже, требуется более глубокое исследование

ОБНОВЛЕНО 28/10/2021

Итак, после некоторого изучения источников AOSP я обнаружил, что разрешение APP_OP_CAMERA отражает состояние «блокировки».

Просто позвоните if(SensorPrivacyCheck.isCameraBlocked()){ return } — этот вызов также уведомляет систему о появлении диалогового окна «Разблокировать»

Пример

Решение:

 @TargetApi(Build.VERSION_CODES.S)
@RestrictTo(RestrictTo.Scope.LIBRARY)
object SensorPrivacyCheck {
    fun isMicrophoneBlocked(): Boolean {
        return Utils.isAtLeastS amp;amp; checkIsPrivacyToggled(SensorPrivacyManager.Sensors.MICROPHONE)
    }

    fun isCameraBlocked(): Boolean {
        return Utils.isAtLeastS amp;amp; checkIsPrivacyToggled(SensorPrivacyManager.Sensors.CAMERA)
    }

    @SuppressLint("PrivateApi", "BlockedPrivateApi")
    private fun checkIsPrivacyToggled(sensor: Int): Boolean {
        val sensorPrivacyManager: SensorPrivacyManager =
            AndroidContext.appContext.getSystemService(SensorPrivacyManager::class.java)
        if (sensorPrivacyManager.supportsSensorToggle(sensor)) {
            try {
                val permissionToOp: String =
                    AppOpCompatConstants.getAppOpFromPermission(
                        if (sensor == SensorPrivacyManager.Sensors.CAMERA)
                            Manifest.permission.CAMERA else Manifest.permission.RECORD_AUDIO
                    ) ?: return false

                val noteOp: Int = try {
                    AppOpsManagerCompat.noteOpNoThrow(
                        AndroidContext.appContext,
                        permissionToOp,
                        Process.myUid(),
                        AndroidContext.appContext.packageName
                    )
                } catch (ignored: Throwable) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
                        PermissionUtils.appOpPermissionsCheckMiui(
                            permissionToOp,
                            Process.myUid(),
                            AndroidContext.appContext.packageName
                        ) else AppOpsManagerCompat.MODE_IGNORED
                }
                return noteOp != AppOpsManagerCompat.MODE_ALLOWED
            } catch (e: Throwable) {
                e.printStackTrace()
            }
        }
        return false
    }
}
 

Комментарии:

1. Спасибо, я подумал о первом подходе, но я предположил, что получу какое-то исключение, и действительно, оно выдает SecurityException.

2. Итак, я нашел решение. См. Сообщение об обновлении