-
Notifications
You must be signed in to change notification settings - Fork 0
Start in Background Permission
在小米手机的应用权限管理中有一个"后台弹出界面权限",该项权限会限制当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上后台是不能启动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.
默认情况下,是拒绝后台启动的。也就是说,用户启动的话,禁止在后台直接启动的。 具体的判断代码是在 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的权限。