Skip to content

Start in Background Permission

panwj edited this page Nov 8, 2019 · 10 revisions

关于start in background 权限问题

关于小米手机后台弹activity

在小米手机的应用权限管理中有一个“后台弹出界面权限”,该项权限会限制当APP处在后台时弹出Activity的动作,该权限在某些机型上是默认关闭的, 如果不给权限的情况下在后台启动activity,会无反应,且在日志中会有 ‘ExtraActivityManagerService: MIUILOG- Permission Denied Activity’提示

关于小米设备后台启动activity比较好的链接:https://juejin.im/post/5d328ce3e51d454fbf540aad

关于android 10后台启动activity限制比较好的分析链接: https://juejin.im/post/5d788982f265da03ca11983c

小米机型解决方案

我们可以判断是否开起了start in background权限,如果没有开启,跳转到相应的界面进行授权,这时需要注意一个问题,小米的有些机型start in background权限授予界面是单独的一个页面,有的则是在应用详情页面,所以,对于跳转授权的页面我们需要适配一下, 具体如下:

判断是否授予start in background的方法如下:

public static boolean canBackgroundStart(Context context) {
        try {
            AppOpsManager ops = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            int op = 10021; // >= 23
            // ops.checkOpNoThrow(op, uid, packageName)
            Method method = ops.getClass().getMethod("checkOpNoThrow", new Class[]
                    {int.class, int.class, String.class}
            );
            Integer result = (Integer) method.invoke(ops, op, android.os.Process.myUid(), context.getPackageName());
            return result == AppOpsManager.MODE_ALLOWED;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

跳转到相应授权界面的方法如下:

private void openOpsSettings() {
        try {
            if (DeviceUtils.isMIUI() && !PermissionHelper.canBackgroundStart(getApplicationContext())) {
                try {
                    Intent intent = new Intent();
                    intent.setAction("miui.intent.action.APP_PERM_EDITOR");
                    intent.addCategory(Intent.CATEGORY_DEFAULT);
                    intent.putExtra("extra_pkgname",  getApplicationContext().getPackageName());
                    this.startActivityForResult(intent, REQUEST_DRAW_CODE);
                } catch (Exception e) {
                    e.printStackTrace();
                    if (!PermissionHelper.isHoldAlertWindowPermission(getApplicationContext())) {
                        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                                Uri.parse("package:" + getApplicationContext().getPackageName()));
                        this.startActivityForResult(intent, REQUEST_DRAW_CODE);
                        return;
                    } else {
                        SharedPreferencesUtil.put(getApplicationContext(), Constants.PREF_UPDATE_GUIDE_FLOAT_KEY, false);
                    }
                }
                return;
            }

            if (!PermissionHelper.isHoldAlertWindowPermission(getApplicationContext())) {
                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                        Uri.parse("package:" + getApplicationContext().getPackageName()));
                this.startActivityForResult(intent, REQUEST_DRAW_CODE);
                return;
            } else {
                SharedPreferencesUtil.put(getApplicationContext(), Constants.PREF_UPDATE_GUIDE_FLOAT_KEY, false);
            }

            if (!PermissionHelper.canBackgroundStart(getApplicationContext())) {
                Intent intent = new Intent();
                intent.setAction("miui.intent.action.APP_PERM_EDITOR");
                intent.addCategory(Intent.CATEGORY_DEFAULT);
                intent.putExtra("extra_pkgname",  getApplicationContext().getPackageName());
                this.startActivityForResult(intent, REQUEST_DRAW_CODE);
            }
        } catch (Exception e) {
            try {
                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                Uri uri = Uri.fromParts("package", getApplicationContext().getPackageName(), null);
                intent.setData(uri);
                this.startActivityForResult(intent, REQUEST_DRAW_CODE);
            } catch (Exception e1) {
                e.printStackTrace();
            }
        }
    }

android 10关于start in background权限的现象与解决办法

经过测试,android 10的设置中没有找到start in background权限的设置开关,但是在android 10上后台是不能启动activity的,以上提供的判断start in background权限是否开启,目前只适用与小米,那么android 10设备怎么做才能在后台启动activity呢?

我们首先需要明白以下两个点:

点一: Go 设备上的 SYSTEM_ALERT_WINDOW 在 Android Q(Go 版本)设备上运行的应用无法获得 SYSTEM_ALERT_WINDOW 权限。这是因为绘制叠加层窗口会使用过多的内存,这对低内存 Android 设备的性能十分有害。

如果 Go 设备上的应用发送具有 ACTION_MANAGE_OVERLAY_PERMISSION 操作的 intent,则系统会自动拒绝此请求,并将用户转到设置屏幕,上面会显示不允许授予此权限,原因是它会减慢设备的运行速度。如果 Go 设备上的应用调用 Settings.canDrawOverlays(),则此方法始终返回 false。同样,这些限制不适用于在设备升级到 Android Q 之前便已收到 SYSTEM_ALERT_WINDOW 权限的应用。

带来的问题: android Q(go版本),将无法显示悬浮菜单

点二: 后台启动activity

小米: 在授权页面有一个start in background的授权条目,只有授予了权限才能在后台启动activity pixel: android 10, 在设置或者其他页面没找到相关权限的地方,按照官网提示,setting--->开发者选项中也没有找到限制后台启动的选项,经过测试发现,如果没有授予显示在应用上层(display pop-up window)的权限, 在后台无法启动页面,但授予权限后在后台可以启动activity,且在日志中有如下提示:Background activity start for screen.recorder allowed because SYSTEM_ALERT_WINDOW permission is granted.

ps1: 结合1, android Q(go版本),新安装应用或者升级上来的但不具有display pop-up window权限的应用,无法跳转到ACTION_MANAGE_OVERLAY_PERMISSION授权页面,因此无法开启该权限,所以在android Q(go版本)无法在后台弹出activity, 且无法使用悬浮窗

ps2:android Q在后台弹出activity会有该log: Background activity start for screen.recorder allowed because SYSTEM_ALERT_WINDOW permission is granted.

关于android 10后台启动activity限制分析

默认情况下,是拒绝后台启动的。也就是说,用户启动的话,禁止在后台直接启动的。 具体的判断代码是在 shouldAbortBackgroundActivityStart 方法中,因为这个方法算是判断的核心方法了,因此贴了完整代码。

boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid,
            final String callingPackage, int realCallingUid, int realCallingPid,
            WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent,
            boolean allowBackgroundActivityStart, Intent intent) {
        // don't abort for the most important UIDs
        final int callingAppId = UserHandle.getAppId(callingUid);
        if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID
                || callingAppId == Process.NFC_UID) {
            return false;
        }
        // don't abort if the callingUid has a visible window or is a persistent system process
        final int callingUidProcState = mService.getUidState(callingUid);
        final boolean callingUidHasAnyVisibleWindow =
                mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid);
        final boolean isCallingUidForeground = callingUidHasAnyVisibleWindow
                || callingUidProcState == ActivityManager.PROCESS_STATE_TOP
                || callingUidProcState == ActivityManager.PROCESS_STATE_BOUND_TOP;
        final boolean isCallingUidPersistentSystemProcess =
                callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
        if (callingUidHasAnyVisibleWindow || isCallingUidPersistentSystemProcess) {
            return false;
        }
        // take realCallingUid into consideration
        final int realCallingUidProcState = (callingUid == realCallingUid)
                ? callingUidProcState
                : mService.getUidState(realCallingUid);
        final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid)
                ? callingUidHasAnyVisibleWindow
                : mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(realCallingUid);
        final boolean isRealCallingUidForeground = (callingUid == realCallingUid)
                ? isCallingUidForeground
                : realCallingUidHasAnyVisibleWindow
                        || realCallingUidProcState == ActivityManager.PROCESS_STATE_TOP;
        final int realCallingAppId = UserHandle.getAppId(realCallingUid);
        final boolean isRealCallingUidPersistentSystemProcess = (callingUid == realCallingUid)
                ? isCallingUidPersistentSystemProcess
                : (realCallingAppId == Process.SYSTEM_UID)
                        || realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
        if (realCallingUid != callingUid) {
            // don't abort if the realCallingUid has a visible window
            if (realCallingUidHasAnyVisibleWindow) {
                return false;
            }
            // if the realCallingUid is a persistent system process, abort if the IntentSender
            // wasn't whitelisted to start an activity
            if (isRealCallingUidPersistentSystemProcess && allowBackgroundActivityStart) {
                return false;
            }
            // don't abort if the realCallingUid is an associated companion app
            if (mService.isAssociatedCompanionApp(UserHandle.getUserId(realCallingUid),
                    realCallingUid)) {
                return false;
            }
        }
        // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
        if (mService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
                == PERMISSION_GRANTED) {
            return false;
        }
        // don't abort if the caller has the same uid as the recents component
        if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
            return false;
        }
        // don't abort if the callingUid is the device owner
        if (mService.isDeviceOwner(callingUid)) {
            return false;
        }
        // don't abort if the callingUid has companion device
        final int callingUserId = UserHandle.getUserId(callingUid);
        if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) {
            return false;
        }
        // If we don't have callerApp at this point, no caller was provided to startActivity().
        // That's the case for PendingIntent-based starts, since the creator's process might not be
        // up and alive. If that's the case, we retrieve the WindowProcessController for the send()
        // caller, so that we can make the decision based on its foreground/whitelisted state.
        int callerAppUid = callingUid;
        if (callerApp == null) {
            callerApp = mService.getProcessController(realCallingPid, realCallingUid);
            callerAppUid = realCallingUid;
        }
        // don't abort if the callerApp or other processes of that uid are whitelisted in any way
        if (callerApp != null) {
            // first check the original calling process
            if (callerApp.areBackgroundActivityStartsAllowed()) {
                return false;
            }
            // only if that one wasn't whitelisted, check the other ones
            final ArraySet<WindowProcessController> uidProcesses =
                    mService.mProcessMap.getProcesses(callerAppUid);
            if (uidProcesses != null) {
                for (int i = uidProcesses.size() - 1; i >= 0; i--) {
                    final WindowProcessController proc = uidProcesses.valueAt(i);
                    if (proc != callerApp && proc.areBackgroundActivityStartsAllowed()) {
                        return false;
                    }
                }
            }
        }
        // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
        if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
            Slog.w(TAG, "Background activity start for " + callingPackage
                    + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
            return false;
        }
        // anything that has fallen through would currently be aborted
        Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
                + "; callingUid: " + callingUid
                + "; isCallingUidForeground: " + isCallingUidForeground
                + "; isCallingUidPersistentSystemProcess: " + isCallingUidPersistentSystemProcess
                + "; realCallingUid: " + realCallingUid
                + "; isRealCallingUidForeground: " + isRealCallingUidForeground
                + "; isRealCallingUidPersistentSystemProcess: "
                + isRealCallingUidPersistentSystemProcess
                + "; originatingPendingIntent: " + originatingPendingIntent
                + "; isBgStartWhitelisted: " + allowBackgroundActivityStart
                + "; intent: " + intent
                + "; callerApp: " + callerApp
                + "]");
        // log aborted activity start to TRON
        if (mService.isActivityStartsLoggingEnabled()) {
            mSupervisor.getActivityMetricsLogger().logAbortedBgActivityStart(intent, callerApp,
                    callingUid, callingPackage, callingUidProcState, callingUidHasAnyVisibleWindow,
                    realCallingUid, realCallingUidProcState, realCallingUidHasAnyVisibleWindow,
                    (originatingPendingIntent != null));
        }
        return true;
    }

一个是前面显式设置的白名单属性allowBackgroundActivityStart, 另一个是应用的SYSTEM_ALERT_WINDOW权限,还有一个就是后台启动Activity的权限。

Clone this wiki locally