Android setPackagesSuspended 暂停应用
setPackagesSuspended 是 PackageManager 的一个 public 方法,它可以用来暂停应用。
应用被暂停之后会进入 Suspended 状态,无法点击打开,会弹出一个系统对话框,提示应用已被暂停。应用的后台活动比如播放音乐也会被暂停。
Android 数字健康(com.google.android.apps.wellbeing)就是利用 setPackagesSuspended 方法暂停应用,达到限制应用使用的效果。
setPackagesSuspended
setPackagesSuspended 方法如下:
@SystemApi
@RequiresPermission(Manifest.permission.SUSPEND_APPS)
public String[] setPackagesSuspended(String[] packageNames, boolean suspended,
@Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras,
String dialogMessage) {
throw new UnsupportedOperationException("setPackagesSuspended not implemented");
}
可以看出 setPackagesSuspended 是系统 api 方法,而且需要在 manifest 定义权限 Manifest.permission.SUSPEND_APPS。
Manifest.permission.SUSPEND_APPS 权限也是系统级别的。普通应用没有权限,只用系统应用可以使用 setPackagesSuspended。同时 setPackagesSuspended 也是 hide 方法。
需要注意的是,在 Android 的 GTS 测试用例中,有一条用例只允许谷歌的数字健康和三星的数字健康这 2 个应用的包名可以使用 SUSPEND_APPS 权限。如果有其他应用试图使用这个权限,会导致 GTS 用例不过。所以一般来说,除了这 2 个应用,其他应用都不应该使用 setPackagesSuspended 挂起应用的方案,除非能修改 GTS 测试用例。
setPackagesSuspended 的使用方法如下:
getPackageManager().setPackagesSuspended(packageNames, true, null, null, dialogMessage);
getPackageManager 是 Context 类的抽象方法,本质上是调用了 ContextImpl 的 getPackageManager 方法。
getPackageManager() 方法如下:
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
可以看出构造了一个 ApplicationPackageManager,传入 context 和 IPackageManager。IPackageManager 是一个 binder 接口,从 ServiceManager 通过 getService 获取。
ApplicationPackageManager setPackagesSuspended
查看 ApplicationPackageManager 的 setPackagesSuspended 方法。代码如下:
@Override
public String[] setPackagesSuspended(String[] packageNames, boolean suspended,
PersistableBundle appExtras, PersistableBundle launcherExtras,
String dialogMessage) {
try {
return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras,
launcherExtras, dialogMessage, mContext.getOpPackageName(),
mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
可以看出远程调用了 PMS 的 setPackagesSuspendedAsUser,传入了应用自身的包名和 uid。
PMS 的 setPackagesSuspendedAsUser
PMS 的 setPackagesSuspendedAsUser 方法如下:
@Override
public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
PersistableBundle appExtras, PersistableBundle launcherExtras,
SuspendDialogInfo dialogInfo, String callingPackage, int userId) {
for (int i = 0; i < packageNames.length; i++) {
...
synchronized (mPackages) {
pkgSetting.setSuspended(suspended, callingPackage, dialogInfo, appExtras,
launcherExtras, userId);
}
...
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(
new String[changedPackagesList.size()]);V
sendPackagesSuspendedForUser(
changedPackages, changedUids.toArray(), userId, suspended, launcherExtras);
sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, appExtras, userId);
synchronized (mPackages) {
scheduleWritePackageRestrictionsLocked(userId);
}
}
return unactionedPackages.toArray(new String[unactionedPackages.size()]);
}
可以看出以上代码调用了 pkgSetting.setSuspended,然后调用了 sendPackagesSuspendedForUser、sendMyPackageSuspendedOrUnsuspended。
setPackagesSuspendedAsUser 返回了 unactionedPackages,也就是没有被 suspend 的应用。
pkgSetting 的 setSuspended 方法其实就是 PackageSettingBase 的 setSuspended。
PackageSettingBase 的 setSuspended 方法会将 PackageUserState 的 suspended 改变。
existingUserState.suspended = suspended;
在 PackageParser 的 updateApplicationInfo 会更新应用的 applicationInfo。
if (state.suspended) {
ai.flags |= ApplicationInfo.FLAG_SUSPENDED;
} else {
ai.flags &= ~ApplicationInfo.FLAG_SUSPENDED;
}
可以看出会根据 PackageUserState 的 suspended 属性添加或者删除 ApplicationInfo.FLAG_SUSPENDED。
FWK 中的其他地方判断一个应用是否被暂停了就是依靠这个 ApplicationInfo.FLAG_SUSPENDED 标记。
比如 ActivityStartInterceptor 应用启动拦截的 interceptSuspendedPackageIfNeeded 方法。
// Do not intercept if the package is not suspended
if (mAInfo == null || mAInfo.applicationInfo == null ||
(mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) {
return false;
}
如果应用没有 ApplicationInfo.FLAG_SUSPENDED(mAInfo.applicationInfo.flags & FLAG_SUSPENDED),说明它没有被暂停,不要拦截它的启动。
sendPackagesSuspendedForUser
sendPackagesSuspendedForUser 代码如下:
private void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
boolean suspended, PersistableBundle launcherExtras) {
final Bundle extras = new Bundle(3);
extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
if (launcherExtras != null) {
extras.putBundle(Intent.EXTRA_LAUNCHER_EXTRAS,
new Bundle(launcherExtras.deepCopy()));
}
sendPackageBroadcast(
suspended ? Intent.ACTION_PACKAGES_SUSPENDED
: Intent.ACTION_PACKAGES_UNSUSPENDED,
null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
new int[] {userId}, null);
}
可以看出以上代码将参数封装到 bundle 中,然后发送了广播。广播 Action 是 Intent.ACTION_PACKAGES_SUSPENDED 或者 Intent.ACTION_PACKAGES_UNSUSPENDED。
sendPackageBroadcast 会调用 doSendBroadcast,然后调用 AMS 的 broadcastIntent。
sendMyPackageSuspendedOrUnsuspended
sendMyPackageSuspendedOrUnsuspended 方法如下:
private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended,
PersistableBundle appExtras, int userId) {
...
if (suspended) {
action = Intent.ACTION_MY_PACKAGE_SUSPENDED;
if (appExtras != null) {
final Bundle bundledAppExtras = new Bundle(appExtras.deepCopy());
intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, bundledAppExtras);
}
} else {
action = Intent.ACTION_MY_PACKAGE_UNSUSPENDED;
}
...
}
sendMyPackageSuspendedOrUnsuspended 也是发送广播,只不过它发送了 Intent.ACTION_MY_PACKAGE_SUSPENDED 和 Intent.ACTION_MY_PACKAGE_UNSUSPENDED。
被暂停的应用可以通过注册这两个 Action 的广播来通知自己是否被暂停了。另外被暂停的应用也可以使用 isPackageSuspended 方法判断自己是否处于暂停状态。
ACTION_PACKAGES_SUSPENDED 广播接收
查看 AMS 的 broadcastIntentLocked 方法,里面对 ACTION_PACKAGES_SUSPENDED 广播做了处理:
case Intent.ACTION_PACKAGES_SUSPENDED:
case Intent.ACTION_PACKAGES_UNSUSPENDED:
final boolean suspended = Intent.ACTION_PACKAGES_SUSPENDED.equals(
intent.getAction());
final String[] packageNames = intent.getStringArrayExtra(
Intent.EXTRA_CHANGED_PACKAGE_LIST);
final int userHandle = intent.getIntExtra(
Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
mAtmInternal.onPackagesSuspendedChanged(packageNames, suspended,
userHandle);
break;
可以看出以上代码调用了 mAtmInternal.onPackagesSuspendedChanged 方法。
mAtmInternal 是ActivityTaskManagerService (ATMS),它调用了 RecentTasks 的 onPackagesSuspendedChanged 方法。
@Override
public void onPackagesSuspendedChanged(String[] packages, boolean suspended, int userId) {
synchronized (mGlobalLock) {
mRecentTasks.onPackagesSuspendedChanged(packages, suspended, userId);
}
}
RecentTasks 的 onPackagesSuspendedChanged 方法如下:
void onPackagesSuspendedChanged(String[] packages, boolean suspended, int userId) {
...
if (suspended) {
mSupervisor.removeTaskByIdLocked(tr.taskId, false,
REMOVE_FROM_RECENTS, "suspended-package");
}
...
}
可以看出如果应用被暂停,它会从 tasks 中移除。
除了 AMS, NMS(NotificationManagerService)、WMS(WindowManagerService)和其他一些地方也注册了 ACTION_PACKAGES_SUSPENDED 广播。
总结
- PacakgeManager 的 setPackagesSuspended 方法可以暂停或者恢复应用。
- setPackagesSuspended 通过 PMS 改变 ApplicationInfo 的 FLAG_SUSPENDED 标记和发送 ACTION_PACKAGES_SUSPENDED、ACTION_MY_PACKAGE_SUSPENDED 通知 FWK 的其他服务(AMS 等)实现应用暂停的效果。