I was updating one of my clients app, and testing it how it behaves on Android O (API 26). The app requires permissions to draw over system windows (android.permission.SYSTEM_ALERT_WINDOW). On Android 6 (API 23) and up, you are obligated to request ‘special’ permissions while the app is running.
The ‘SYSTEM_ALERT_WINDOW’ permission is a special permission that breaks the rules set by the new permission model available on Android 6 (API 23) and up. It’s request involves calling Settings.canDrawOverlays() and if it returns ‘false‘ starting ‘Settings‘ where the user can choose to grant your app the ability to draw overlays or not.
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // On API 23 and later ask the user to grant us permission to draw system overlay // windows. if (!Settings.canDrawOverlays(this)) { Intent intent = new Intent( Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); startActivityForResult(intent, REQUEST_PERMISSION_SYSTEM_OVERLAY_RESULT); } } |
There is a bug with Settings.canDrawOverlays() (only) on API 26 where it will always return ‘false’ disregarding the actual user decision. The workaround provided here is a bit ugly, but does not involves restarting the app (which will be quite annoying for the user) after the permission is granted.
The code below first checks the result of System.canDrawOverlays() if it returns ‘true’ it continues with the rest of the application flow. If it returns ‘false’ a check if we are running on Android O (API 26) is performed. If that’s the case, we are calling our ‘workaround’ method.
The ‘workaround’ method tries to add an invisible overlay window on the screen, and if that’s OK we assume that we have a permission to draw overlays, else an exception is thrown.
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; if(requestCode == REQUEST_PERMISSION_SYSTEM_OVERLAY_RESULT) { if(Settings.canDrawOverlays(this)) { m_permissionSystemOverlayWindowGranted = true; if(m_permissionReadPhoneStateGranted && m_permissionProcessOutgoingCallsGranted) { startService(new Intent(this, EstatePlusService.class)); m_layoutNoPermissions.setVisibility(View.INVISIBLE); m_progressBar.setVisibility(View.INVISIBLE); m_layoutLogin.setVisibility(View.VISIBLE); } } else if(Build.VERSION.SDK_INT == Build.VERSION_CODES.O) { // NOTE: This is a workaround to fix the bug in Android O where the // Settings.canDrawOverlays() will always return 'false' if(canDrawOverlays(this)) { m_permissionSystemOverlayWindowGranted = true; if(m_permissionReadPhoneStateGranted && m_permissionProcessOutgoingCallsGranted) { startService(new Intent(this, EstatePlusService.class)); m_layoutNoPermissions.setVisibility(View.INVISIBLE); m_progressBar.setVisibility(View.INVISIBLE); m_layoutLogin.setVisibility(View.VISIBLE); } } } } } /** * Workaround for Android O */ public static boolean canDrawOverlays(Context context) { try { WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); if (windowManager == null) { return false; } final 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); windowManager.addView(viewToAdd, params); windowManager.removeView(viewToAdd); return true; } catch (Exception e) { e.printStackTrace(); } return false; } |