1

Android 12提出了一个新的隐私设置来禁用对相机和麦克风传感器的访问,这在文档中被称为切换。

正如文档中提到的:

系统提醒用户设备范围的切换已关闭

但是,它似乎只在请求相机权限时提醒用户,而不是在尝试使用生物识别技术对用户进行身份验证时(Pixel 手机上的面部身份验证,你猜怎么着!?它使用相机)。[我正在使用 AndroidX 生物识别库]

有什么方法可以查明用户是否在未请求任何许可的情况下阻止了相机访问?

我猜文档中的注释没有考虑到应用程序可能使用面部身份验证:

注意:只要您遵循隐私最佳实践,本节中提到的切换不需要更改应用程序的逻辑。

笔记:

  • 当相机访问被阻止时,您无法在“设置”中注册新面孔。设置应用程序没有显示任何错误,只是一个空白的相机源
  • 我正在使用 Pixel 4 (Android 12)
  • 如果相机访问被阻止(Pixel 5),“通过扫描 QR 码加入 Wi-Fi”功能不起作用,也不会向用户显示反馈
4

1 回答 1

3

所以,我也在寻找一个解决方案 - 有一个生物识别库,并且在 DM 中出现的报告很少有同样的问题 - 当相机“静音”时,FaceUnlock 在 Pixel 4 上不起作用

现在,现在仍然修复,但也许我的研究可以帮助某人。

1. 我检查了 PrivacyToggle 的新 API。
Android 12 引入了一种新SensorPrivacyManager的 withsupportsSensorToggle()方法 - 如果设备能够“静音”摄像头或麦克风,它会返回 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 - missing ,服务拒绝了这个调用android.permission.OBSERVE_SENSOR_PRIVACY,即使我们在 Manifest 中声明了它。至少在模拟器上。

2. 我们可以尝试识别一个新的“sensor-in-use”指标

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 相关,我认为可能存在 Tiles 确定当前隐私切换状态的方式。但是这个 API 使用了一个非常有趣的解决方案——它不使用Settings.GlobalorSettings.Security部分,而是保存所有的首选项,"system/sensor_privacy.xml"并且不能被 3rd 方应用程序访问。

请参阅SensorPrivacyService.java

我相信存在一种如何找到相机被阻止的方法,但似乎需要进行更深入的研究

2021 年 10 月 28 日更新

所以在对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 && checkIsPrivacyToggled(SensorPrivacyManager.Sensors.MICROPHONE)
    }

    fun isCameraBlocked(): Boolean {
        return Utils.isAtLeastS && 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
    }
}
于 2021-10-27T11:44:33.327 回答