-
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的权限。