Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why in Android 8 method Settings.canDrawOverlays() returns "false" when user has granted permission to draw overlays and returns to my application?

I'm developing the MDM app for parents to control children's devices and it uses permission SYSTEM_ALERT_WINDOW to display warnings on device if forbidden action has performed. On devices with SDK 23+ (Android 6.0) during installation the app checks the permission using this method:

Settings.canDrawOverlays(getApplicationContext())  

and if this method returns false the app opens system dialog where user can grant the permission:

Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,                     Uri.parse("package:" + getPackageName())); startActivityForResult(intent, REQUEST_CODE); 

But on device with SDK 26 (Android 8.0), when user has successfully granted permission and returned to the app by pressing back button, method canDrawOverlays() still returns false, until user doesn't close the app and starts it again or just chooses it in the recent apps dialog. I tested it on latest version of virtual device with Android 8 in Android Studio because I didn't have real device.

I've done a little research and additionally check the permission with AppOpsManager:

AppOpsManager appOpsMgr = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE); int mode = appOpsMgr.checkOpNoThrow("android:system_alert_window", android.os.Process.myUid(), getPackageName()); Log.d(TAG, "android:system_alert_window: mode=" + mode); 

And so:

  • when the application does not have this permission, the mode is "2" (MODE_ERRORED) (canDrawOverlays() returns false) when the user
  • granted permission and returned to the application, the mode is "1" (MODE_IGNORED) (canDrawOverlays() returns false)
  • and if you now restart the app, the mode is "0" (MODE_ALLOWED) (canDrawOverlays() returns true)

Please, can anyone explain this behavior to me? Can I rely on mode == 1 of operation "android:system_alert_window" and assume that the user has granted permission?

like image 294
Nikolay Avatar asked Sep 12 '17 10:09

Nikolay


2 Answers

I ran into the same problem. I use a workaround which tries to add an invisible overlay. If an exception is thrown the permission isn't granted. It might not be the best solution, but it works. I can't tell you anything about the AppOps solution, but it looks reliable.

Edit October 2020: As mentioned in the comments there might be a memory leak inside the WindowManager when the SecurityException is thrown, causing the workaround view not to be removed (even with explicit calls to removeView). For normal uses this should not be much of a problem, but avoid running too many checks in the same app session (Without testing it I'd assume anything below hundred should be alright).

/**  * Workaround for Android O  */ public static boolean canDrawOverlays(Context context) {     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true;     else if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {         return Settings.canDrawOverlays(context);     } else {         if (Settings.canDrawOverlays(context)) return true;         try {             WindowManager mgr = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);             if (mgr == null) return false; //getSystemService might return null             View viewToAdd = new View(context);             WindowManager.LayoutParams params = new WindowManager.LayoutParams(0, 0, android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?                     WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,                     WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);             viewToAdd.setLayoutParams(params);             mgr.addView(viewToAdd, params);             mgr.removeView(viewToAdd);             return true;         } catch (Exception e) {             e.printStackTrace();         }         return false;     } } 
like image 63
Ch4t4r Avatar answered Sep 25 '22 03:09

Ch4t4r


I've found this problems with checkOp too. In my case I have the flow which allows to redirect to settings only when the permission is not set. And the AppOps is set only when redirecting to settings.

Assuming that AppOps callback is called only when something is changed and there is only one switch, which can be changed. That means, if callback is called, user has to grant the permission.

if (VERSION.SDK_INT >= VERSION_CODES.O &&                 (AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW.equals(op) &&                       packageName.equals(mContext.getPackageName()))) {     // proceed to back to your app } 

After the app is restored, checking with canDrawOverlays() starts worked for me. For sure I restart the app and check if permission is granted via standard way.

It's definitely not a perfect solution, but it should work, till we know more about this from Google.

EDIT: I asked google: https://issuetracker.google.com/issues/66072795

EDIT 2: Google fixes this. But it seem that the Android O version will be affected still.

like image 24
l0v3 Avatar answered Sep 22 '22 03:09

l0v3