为什么在Android O方法中当用户授予绘制叠加层并返回我的应用程序的权限时,Settings.canDrawOverlays()会返回"false"?

Nik*_*lay 24 permissions android

我有父母的MDM应用程序来控制孩子的设备,它使用权限SYSTEM_ALERT_WINDOW在禁止的动作执行时在孩子的设备上显示警告.在安装期间的设备M +上,应用程序使用以下方法检查权限:

Settings.canDrawOverlays(getApplicationContext()) 
Run Code Online (Sandbox Code Playgroud)

如果此方法返回false应用程序打开系统对话框,用户可以在其中授予权限:

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

Android O中,当用户成功授予权限并通过按返回按钮返回应用程序时,方法canDrawOverlays()仍会返回false,直到用户不关闭应用程序并再次打开它或在最近的应用程序对话框中选择它.我在Android Studio中使用Android O在最新版本的虚拟设备上进行了测试,因为我没有真正的设备.

我做了一些研究,另外用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);
Run Code Online (Sandbox Code Playgroud)

所以:

  • 当应用程序没有此权限时,模式为"2"(MODE_ERRORED)(canDrawOverlays()返回false)当用户
  • 授予权限并返回给应用程序,模式为"1"(MODE_IGNORED)(canDrawOverlays()返回false)
  • 如果您现在重新打开应用程序,模式为"0"(MODE_ALLOWED)(canDrawOverlays()返回true)

拜托,有人能向我解释这种行为吗?我可以依赖mode == 1操作"android:system_alert_window"并假设用户已授予权限吗?

Ch4*_*t4r 18

我遇到了同样的问题.我使用一种尝试添加不可见叠加层的解决方法.如果抛出异常,则不授予权限.它可能不是最好的解决方案,但它确实有效.我无法告诉你有关AppOps解决方案的任何信息,但它看起来很可靠.

/**
 * 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;
    }
}
Run Code Online (Sandbox Code Playgroud)


l0v*_*0v3 6

我也发现了checkOp的这个问题.在我的情况下,我有流程,只有在未设置权限时才允许重定向到设置.并且只有在重定向到设置时才会设置AppOps.

假设仅在某些内容发生更改时才调用AppOps回调,并且只有一个开关可以更改.这意味着,如果调用回调,则用户必须授予权限.

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

应用程序恢复后,检查canDrawOverlays()启动对我有用.我肯定会重新启动应用程序并检查是否通过标准方式授予了权限.

它绝对不是一个完美的解决方案,但它应该有效,直到我们从谷歌了解更多.

编辑: 我问谷歌:https://issuetracker.google.com/issues/66072795

编辑2: 谷歌修复此问题.但似乎Android O版本仍会受到影响.


Thu*_*ick 6

这是我的一体化解决方案,这是其他解决方案的组合,但适用于大多数情况
首先根据Android Docs使用标准检查检查
第二次检查是使用AppOpsManager
第三次也是最后一次检查是否所有其他方法都失败是尝试显示覆盖,如果失败了它绝对不会工作;)

static boolean canDrawOverlays(Context context) {

    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M && Settings.canDrawOverlays(context)) return true;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {//USING APP OPS MANAGER
        AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        if (manager != null) {
            try {
                int result = manager.checkOp(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW, Binder.getCallingUid(), context.getPackageName());
                return result == AppOpsManager.MODE_ALLOWED;
            } catch (Exception ignore) {
            }
        }
    }

    try {//IF This Fails, we definitely can't do it
        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 ignore) {
    }
    return false;

}
Run Code Online (Sandbox Code Playgroud)

  • 我建议谨慎使用这种方法,因为如果“addView”方法抛出异常,“WindowManager”本身会产生巨大的内存泄漏。我们的一个应用程序因此遭受了多次 ANR,并且花了足够长的时间才找到其原因,这就是这个答案。您可以在此处查看详细的问题和答案:/sf/ask/4282877301/#64334332 (2认同)

Vik*_*dar 5

实际上在 Android 8.0 上它会返回,true但只有当您等待 5 到 15 秒并再次查询使用Settings.canDrawOverlays(context)方法的权限时才会返回。

因此,您需要做的是ProgressDialog向用户显示一条解释问题的消息并运行 aCountDownTimer以使用Settings.canDrawOverlays(context)in检查覆盖权限onTick方法。

这是示例代码:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 100 && !Settings.canDrawOverlays(getApplicationContext())) {
        //TODO show non cancellable dialog
        new CountDownTimer(15000, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                if (Settings.canDrawOverlays(getApplicationContext())) {
                    this.cancel(); // cancel the timer
                    // Overlay permission granted
                    //TODO dismiss dialog and continue
                }
            }

            @Override
            public void onFinish() {
                //TODO dismiss dialog
                if (Settings.canDrawOverlays(getApplicationContext())) {
                    //TODO Overlay permission granted
                } else {
                    //TODO user may have denied it.
                }
            }
        }.start();
    }
}
Run Code Online (Sandbox Code Playgroud)