Biometrics prompt (face authentication) when camera access is blocked (Android 12 - Pixel) - android

Android 12 came up with a new Privacy Settings to disable access to the Camera and Mic sensors, which is referred as Toggles in the docs.
As it is mentioned in the docs:
the system reminds the user that the device-wide toggle is turned off
However, it seems that it only reminds the user when requesting the Camera permission and not when trying to authenticate the user using biometrics (face authentication on Pixel phones, which guess what!? It uses the camera). [I'm using AndroidX biometrics library]
Is there any way to find out if the Camera access has been blocked by the user without requesting any permission?
I guess the note in the docs didn't take into account that the app might use face authentication:
Note: The toggles mentioned in this section shouldn't require changes to your app's logic, as long as you follow privacy best practices.
Notes:
You can't register a new face in Settings when camera access is blocked. The Settings app does not show any error, just a blank camera feed
I am using Pixel 4 (Android 12)
The feature 'Join Wi-Fi by scanning a QR code' does not work and neither shows a feedback to the user if Camera access is blocked (Pixel 5)

So, I also looking for a solution - a have a biometric library and few reports appear in DM with the same problem - FaceUnlock doesn't work on Pixel 4 when the camera 'muted'
For now, still now fix, but maybe my research can help someone.
1. I checked the new API for PrivacyToggle's.
Android 12 introduces a new SensorPrivacyManager with supportsSensorToggle() method - it returns TRUE in case of device able to 'mute' camera or mic.
val sensorPrivacyManager = applicationContext
.getSystemService(SensorPrivacyManager::class.java)
as SensorPrivacyManager
val supportsMicrophoneToggle = sensorPrivacyManager
.supportsSensorToggle(Sensors.MICROPHONE)
val supportsCameraToggle = sensorPrivacyManager
.supportsSensorToggle(Sensors.CAMERA)
If you look into SensorPrivacyManager, you can find that it provides some more useful methods, so I develop the next code:
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
}
Unfortunately, the service rejects this call due to SecurityException - missing android.permission.OBSERVE_SENSOR_PRIVACY, even if we declare it in Manifest.
At least on emulator.
2. We can try to identify a new "sensor-in-use" indicator
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
}
}
}
I didn't test this code (no real device), but as for me - it's not very useful, because we can check the camera indicator only AFTER we start Biometric Auth flow, when I need to understand is camera accessible BEFORE Biometric Auth started.
3. Because of PrivicyToogle related to QuickSettings, I decide that perhaps exists a way how Tiles determinate current Privacy Toggle state.
But this API use a very interesting solution - it does not use Settings.Global or Settings.Security section, instead, all preferences saved in "system/sensor_privacy.xml" and not accessible for 3rd party apps.
See SensorPrivacyService.java
I believe that exists a way how to find that Camera is blocked, but seems like some deeper research required
UPDATED 28/10/2021
So after some digging in AOSP sources, I found that APP_OP_CAMERA permission reflects the "blocking" state.
Just call if(SensorPrivacyCheck.isCameraBlocked()){ return } - this call also notify the system to show the "Unblock" dialog
Example
Solution:
#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
}
}

Related

On Android, how to check the phone screen is casting?

I need to check if the Android phone my app runs on is using casting which is enabled outside of my app.
It seems CastSession or SessionManager can provide the session related to my app which is not helpful for me.
For example, I can start casting with an app called xx which will cast or mirror the entire screen of my phone. Now, I need to notify when I open my app that the phone's screen is casting/mirroring so I can prevent showing specific content on my app.
I checked it with the code below:
val isCastingEnabledLiveData = MutableLiveData<Boolean>()
fun isCastingEnabled(context: Context): Boolean {
val mediaRouter = MediaRouter.getInstance(context)
if (mediaRouter.routes.size <= 1) {
isCastingEnabledLiveData.value = false
return
}
val selector = MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build()
mediaRouter.addCallback(selector, object : MediaRouter.Callback() {
override fun onRouteChanged(router: MediaRouter?, route: MediaRouter.RouteInfo?) {
super.onRouteChanged(router, route)
isCastingEnabledLiveData.value = if (route != mediaRouter.defaultRoute) {
route?.connectionState != MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED
} else false
}
})
}
you can check whether the phone screen is casting or not by using the MediaRouter class.
Here is an example of how you could check if the phone screen is casting:
MediaRouter mediaRouter = (MediaRouter)
getSystemService(Context.MEDIA_ROUTER_SERVICE);
MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
if(route.isDefault()){
// Screen is not casting
} else {
// Screen is casting
}
This code uses the getSelectedRoute() method of the MediaRouter class to get the currently selected route. If the returned RouteInfo object is the default route, then the screen is not casting, otherwise it is.
Please note that this code uses the getSystemService(Context.MEDIA_ROUTER_SERVICE) method to get an instance of the MediaRouter class, so it should be called from an Activity or Service context.
Additionally, you could also use MediaRouter.Callback and MediaRouter.addCallback to set a callback to monitor the state of the casting, so you could get the updates on the casting state change as well.

How do I capture the URI from a registerActivityResult?

I'm currently attempting to write a view in an android app that will open a camera on a button press, let the user record a video, and stash the URI in a member attached to said view. I'm very hopeful this is possible, but unsure because I can't find many docs regarding the issue.
I'm using the top answer here as a reference for capturing the URI, and it's very comprehensive, but not working in my case for reasons of which I am unsure.
I declare my variable at the top of the file as so
private var videoUri: Uri? = null
The rest of the relevant code happens here:
val recordVideoResult = registerForActivityResult(ActivityResultContracts.CaptureVideo()) { uri: Uri? ->
uri?.let {
this.videoUri = uri
}
}
val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
launchCamera()
} else {
Toast.makeText(this, "Can't open camera because permission is denied. Please provide access in settings.", Toast.LENGTH_SHORT).show()
}
}
fun launchCamera() {
if (packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) { // First check if camera is available in the device
when {
ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED -> {
recordVideoResult.launch(this.uri)
}
else -> {
// You can directly ask for the permission.
// The registered ActivityResultCallback gets the result of this request.
requestPermissionLauncher.launch(
Manifest.permission.CAMERA
)
}
}
}
}
My IDE and compiler yell at me with the below error, which looks like a bug to me, but will not go away.
I'm very unsure what I'm doing wrong. Forgive me if it's incredibly obvious, I've been writing Kotlin for less than a week.
You've got the input and output of CaptureVideo mixed up. The CaptureVideo contract says:
An ActivityResultContract to take a video saving it into the provided content-Uri.
Returns true if the video was saved into the given Uri.
So the input is a Uri - that's what you need to pass to launch. The output is a Boolean. This can be confirmed by looking at the class itself, which shows the complete type - ActivityResultContract<Uri, Boolean>.
This means what you actually need to do is:
Setup a FileProvider. This is how you generate a Uri from a File that your app owns that is suitable to send to the launch of a CaptureVideo Intent. You're the one who picks where the video is stored, not the camera app.
Generate the appropriate File associated with where you want to save your video and store it as a variable in your class like you were previously doing with your videoUri, e.g., in a variable named file (as per the note on the Launching an Intent documentation, you'll probably also want to store that in onSaveInstanceState() in case your process is destroyed while the camera is up).
Use the FileProvider APIs to generate a Uri from your File and pass that to your call to launch.
val contentUri = FileProvider.getUriForFile(
this, // your Activity or
"com.example.myapp.fileprovider", // The android:authorities value from your manifest
file) // The File you want to store the video into
recordVideoResult.launch(contentUri)
Change your call to registerForActivityResult to account for the success Boolean result:
val recordVideoResult = registerForActivityResult(ActivityResultContracts.CaptureVideo()) { success ->
if (success) {
// Now your file contains the fully captured video
} else {
// The user cancelled taking a video
}
}

How to detect chromebook mode programmatically

This question is part of this #3
Some Chromebooks are laptop and we can also turn it into tablet mode. See images from here
So my question is how to programmatically detect the mode(laptop or tablet) of Chromebook.
For this, I did this, but this is only working in Lenovo Flex11, in other Chromebook it is not working
context.getResources().getConfiguration().hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES;
if this condition returns true that means Chromebook is in tablet mode else in laptop mode
I need to check this because if the Chromebook is in laptop mode than I have to show a prediction bar only in particular Activity. If it is in tablet mode the soft keyboard will appear and the prediction bar is managed by candidate view of InputMethodService
We use a combination of two different things. The first is we detect if the device is in desktop mode using the following code snippet :
fun isDesktopMode() : Boolean {
var hasMouse = false
val hasKeyboard = resources.configuration.keyboard == KEYBOARD_QWERTY
val isKeyboardUseable = resources.configuration.hardKeyboardHidden == HARDKEYBOARDHIDDEN_NO
// Check each input device to see if it is a mouse
val inputManager = getSystemService(Context.INPUT_SERVICE) as InputManager
for (deviceId in inputManager.inputDeviceIds) {
val sourceMask = inputManager.getInputDevice(deviceId).sources
if ((sourceMask or SOURCE_MOUSE == sourceMask) ||
(sourceMask or SOURCE_TOUCHPAD == sourceMask) ||
(sourceMask or SOURCE_TRACKBALL == sourceMask)) {
hasMouse = true
}
}
return hasMouse && hasKeyboard && isKeyboardUseable
}
To detect if a keyboard is plugged in and/or available we use the following listener for listening to whether or not a keyboard has become active:
val inputManager = getSystemService(Context.INPUT_SERVICE) as InputManager
val inputDeviceListener = object: InputManager.InputDeviceListener {
override fun onInputDeviceRemoved(deviceId: Int) {
// Be careful checking device here as it is no longer attached to the system
}
override fun onInputDeviceAdded(deviceId: Int) {
// If you want to learn more about what has been attached, check the InputDevice
// using the id.
val sourceMask = inputManager.getInputDevice(deviceId).sources
if (sourceMask or SOURCE_KEYBOARD == sourceMask) {
//Keyboard has been Added
append_to_log("A keyboard has been added.")
}
}
override fun onInputDeviceChanged(deviceId: Int) {
// Best practice is to check for what you care about when things change (ie. are
// there any keyboards/mice still attached and usable. Users may have multiple
// devices attached (integrated keyboard a and bluetooth keyboard) and
// removing one does not mean the other is no longer available.
isInDesktopMode = isDesktopMode()
}
}
inputManager.registerInputDeviceListener(inputDeviceListener, null)
This post is licensed under Apache 2.0.
https://developers.google.com/open-source/devplat

hasSystemFeature(PackageManager.FEATURE_CAMERA) returns true for device with no camera

I have a application which uses camera functionality in it but part of its functionality can also run without camera feature. SO I have put this in my manifest.
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false"/>
and in my code I check whether the device has camera or not using this
final boolean deviceHasCameraFlag = pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
Now I am testing my code on a tablet which runs Android 4.0(ICS) and has no camera. But still I get True value for the deviceHasCameraFlag. Is this weird or am I missing something.
I tried different things and even tried the same thing on Bluetooth feature as Tablet even doesn't have Bluetooth feature. It works fine for Bluetooth but gives me true for camera.
Which device is it? The answer you get is a bug, and 4.0 is very old nowadays. Many tablets that still run this version were not crafted correctly, both hardware and software featuring multiple problems.
Regardless, you should always be prepared to handle failure on Camera.open() or Camera.open(0): for example, in some cases other software on your device will not release the camera gracefully.
So, in your case you have a false positive, you try to open the camera, it fails, and you continue as if there is no camera on the device, even if PackageManager thinks that PackageManager.FEATURE_CAMERA is availabe.
Though I have accepted Alex's answer I still want to put this one collectively as what can be the best solution in such condition.
What I found was in case of some low standard android devices
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)
returns true even if camera doesn't exist and that seems to be a device bug for me which in unchecked.
So whenever there is scenario that you need to check if camera exists for a device or not best practice is something that I am putting below (best practice as per my knowledge if there is something more interesting and best solution that this you are welcome to put it here on this post)
int numberOfCameras = Camera.getNumberOfCameras();
context = this;
PackageManager pm = context.getPackageManager();
final boolean deviceHasCameraFlag = pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
if( !deviceHasCameraFlag || numberOfCameras==0 )
{
Log.e(TAG, "Device has no camera" + numberOfCameras);
Toast.makeText(getApplicationContext(), "Device has no camera", Toast.LENGTH_SHORT).show();
captureButton.setEnabled(false);
}
else
{
Log.e(TAG, "Device has camera" + deviceHasCameraFlag + numberOfCameras);
}
In this I am checking both number of cameras as well as device has camera feature Boolean , so in any case it would not fail my condition.
In my case I had this code:
public boolean hasCameraSupport() {
boolean hasSupport = false;
if(getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) { //<- this constant caused problems
hasSupport = true;
}
return hasSupport;
}
and it kept returning false on a Genymotion device running Android 4.1.1 (API 16). Once I changed the constant PackageManager.FEATURE_CAMERA_ANY to PackageManager.FEATURE_CAMERA, my problems went away. I am guessing that not all devices/API levels support PackageManager.FEATURE_CAMERA_ANY.
I got it you will try this one definitely it will work....
import android.hardware.Camera;
int numCameras = Camera.getNumberOfCameras();
if (numCameras > 0) {
System.out.println("camera");
} else {
System.out.println("No Camera");
}
For CameraX, if the FEATURE_CAMERA_ANY method is still returning true when there is no Camera on device, you can add the below method. So whether FEATURE_CAMERA_ANY returns true or false when CameraX is getting initialized, Below method will make sure to do what you want if a camera is actually not available on device.
private CameraSelector cameraSelector;
private ProcessCameraProvider cameraAvailableCheck;
private ListenableFuture<ProcessCameraProvider> cameraAvailableCheckFuture;
private void checkIfAnyCameraExist()
{
cameraAvailableCheckFuture = ProcessCameraProvider.getInstance(context);
cameraAvailableCheckFuture.addListener(new Runnable() {
#Override
public void run() {
try {
cameraAvailableCheck = cameraAvailableCheckFuture.get();
if ((cameraAvailableCheck.hasCamera(cameraSelector.DEFAULT_BACK_CAMERA) || cameraAvailableCheck.hasCamera(cameraSelector.DEFAULT_FRONT_CAMERA) ))
{
//Do what you want if at least back OR front camera exist
}
else
{
//Do what you want if any camera does not exist
}
}
catch (ExecutionException | InterruptedException | CameraInfoUnavailableException e)
{
// No errors need to be handled for this Future.
// This should never be reached.
}
}
}, ContextCompat.getMainExecutor(this));
}
Please try this code:
private boolean isDeviceSupportCamera() {
if (getApplicationContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_CAMERA)) {
// this device has a camera
return true;
} else {
// no camera on this device
return false;
}
}
Still it does't work then please let me know

Android UserManager: Check if user is owner (admin)

Im developing an app with the latest android version (4.2.1 API-Level 17) for tablets with multiuser capabilities.
I want to restrict certain features (like the access to the app preferences) to the owner of the tablet (that is the user who can add and remove other user accounts)
is there any way i can find out if the current user is the owner?
i read through the UserManager and UserHandle API docs but couldn't find a function that allows me to check for it.
have i missed something or is there another way to do that?
Similar but without reflection:
static boolean isAdminUser(Context context)
{
UserHandle uh = Process.myUserHandle();
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
if(null != um)
{
long userSerialNumber = um.getSerialNumberForUser(uh);
Log.d(TAG, "userSerialNumber = " + userSerialNumber);
return 0 == userSerialNumber;
}
else
return false;
}
You can create an extension property in Kotlin to make it simpler:
val UserManager.isCurrentUserDeviceOwner: Boolean
get() = if (SDK_INT >= 23) isSystemUser
else if (SDK_INT >= 17) getSerialNumberForUser(Process.myUserHandle()) == 0L
else true
Then, using it is as simple as the following:
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
if (userManager.isCurrentUserDeviceOwner) TODO() else TODO()
You can further reduce boilerplate by using global system services definitions that makes userManager and other Android System Services available anywhere in your Kotlin code, with code included in this library I made: https://github.com/LouisCAD/Splitties/tree/master/systemservices
After researching further i found out that the multiuser api is not functional yet, it cant really be used for anything. there is a hack though for checking if the user is the owner using reflections:
public boolean isCurrentUserOwner(Context context)
{
try
{
Method getUserHandle = UserManager.class.getMethod("getUserHandle");
int userHandle = (Integer) getUserHandle.invoke(context.getSystemService(Context.USER_SERVICE));
return userHandle == 0;
}
catch (Exception ex)
{
return false;
}
}
This works for me on the Nexus 7 and Nexus 10 with Android 4.2.1
Its very dirty. so i wouldnt recommend using it unless you are making an app thats device and version specific

Categories

Resources