Android 中要禁用 APP 或四大組件会烙,可使用PackageManager
提供的方法:
/**
* Set the enabled setting for an application
* This setting will override any enabled state which may have been set by the application in
* its manifest. It also overrides the enabled state set in the manifest for any of the
* application's components. It does not override any enabled state set by
* {@link #setComponentEnabledSetting} for any of the application's components.
*
* @param packageName The package name of the application to enable
* @param newState The new enabled state for the application.
* @param flags Optional behavior flags.
*/
public abstract void setApplicationEnabledSetting(String packageName,
@EnabledState int newState, @EnabledFlags int flags);
/**
* Set the enabled setting for a package component (activity, receiver, service, provider).
* This setting will override any enabled state which may have been set by the component in its
* manifest.
*
* @param componentName The component to enable
* @param newState The new enabled state for the component.
* @param flags Optional behavior flags.
*/
public abstract void setComponentEnabledSetting(ComponentName componentName,
@EnabledState int newState, @EnabledFlags int flags);
要想使用這兩個 API 得先獲取權(quán)限丘喻。
權(quán)限獲取
獲取權(quán)限又有幾種情況幾個步驟,如下:
1鲫剿、在 Manifest 里面聲明權(quán)限:
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
2掐暮、使用系統(tǒng)簽名
在申明上面的權(quán)限時,AS 會提示:
Permission is only granted to system apps less... (Ctrl+F1)
Permissions with the protection level signature, privileged or signatureOrSystem are only granted to system apps. If an app is a regular non-system app, it will never be able to use these permissions.
這就是說愿卒,這個權(quán)限只有系統(tǒng) APP 可以獲取到缚去,這時就需要使用系統(tǒng)簽名來簽名 APP 了易结。
如何給 APP 系統(tǒng)簽名柜候,方法有多種搞动,比如:
1、APP 源碼放在系統(tǒng)源碼中編譯渣刷;
2鹦肿、APK 文件放在系統(tǒng)源碼中重簽名;
3辅柴、導(dǎo)出系統(tǒng)簽名文件箩溃,給 APK 簽名瞭吃;
4、使用系統(tǒng)簽名歪架,制作 keystore,放入 AS 使用牡拇。
相關(guān)方法穆律,網(wǎng)上文章很多,我也在公眾號推送過我自己的方法峦耘,有興趣可以去查閱剔蹋。
3、添加 sharedUserId
如果你還想在當(dāng)前 APP 中去操作其他 APP辅髓,在上面都配置好的前提下泣崩,還可能會出現(xiàn)類似如下的錯誤信息:
java.lang.SecurityException: Permission Denial: attempt to change component state from pid=27978, uid=10111, package uid=10087
這時洛口,就需要再給 APP 添加 sharedUserId
,讓 APP 運(yùn)行在 system 進(jìn)程中:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:sharedUserId="android.uid.system">
實際添加了 “
android.uid.system
” 后,上一步的uses-permission
可以省略,因為 system 進(jìn)程默認(rèn)有這個權(quán)限。
添加好權(quán)限后,接下來就是使用了。
既然是 PackageManager 的方法硝皂,而且這兩個方法也不是靜態(tài)的贝或,那就要先獲取 PackageManager 的實例了咪奖。這兩個方法還是 abstract 的,那就說明 PackageManager 也是 abstract 的酱床,不能 new 羊赵,那如何獲取實例呢?
我們知道系統(tǒng)很多 Manager 都是單實例不能 new 的扇谣,PackageManager 也一樣昧捷,那是不是也是通過 getSystemService 獲取呢?并不是罐寨,注釋給出來了:
/**
* Class for retrieving various kinds of information related to the application
* packages that are currently installed on the device.
*
* You can find this class through {@link Context#getPackageManager}.
*/
public abstract class PackageManager {
知道如何獲取實例之后靡挥,我們就可以開始調(diào)用了,先看如何禁用 APP鸯绿。
禁用 APP
在開始操作前跋破,我們應(yīng)該先了解下這個方法及其參數(shù)定義和作用:
setApplicationEnabledSetting(String packageName,
@EnabledState int newState, @EnabledFlags int flags)
1、packageName
楞慈,要禁用的 APP 的包名幔烛。
2啃擦、newState
囊蓝,就是要設(shè)置成的狀態(tài),它的取值通過注解@EnabledState
進(jìn)行限制令蛉,有如下幾種:
/** @hide */
@IntDef(prefix = { "COMPONENT_ENABLED_STATE_" }, value = {
//將APP或組件設(shè)置為manifest定義的狀態(tài)聚霜。
COMPONENT_ENABLED_STATE_DEFAULT,
//啟用APP或組件,忽略manifest的定義珠叔。
COMPONENT_ENABLED_STATE_ENABLED,
//禁用APP或組件蝎宇,忽略manifest的定義。
COMPONENT_ENABLED_STATE_DISABLED,
//以用戶身份禁用APP祷安,忽略manifest的定義姥芥。不能用于組件操作。
COMPONENT_ENABLED_STATE_DISABLED_USER,
//禁用APP直到用戶想用才出現(xiàn)汇鞭。也就是說凉唐,正常情況下庸追,用戶看不到(比如在Launcher上);但是特殊情況下台囱,用戶還是能看到并選擇到(比如輸入法APP)淡溯。不能用于組件操作。
COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EnabledState {}
3簿训、flags
咱娶,是 Optional behavior flags,它的取值由注解@EnabledFlags
限制:
/** @hide */
@IntDef(flag = true, prefix = { "DONT_KILL_APP" }, value = {
//僅對組件的操作起作用强品,用于指示禁用組件時膘侮,不 kill 組件所在的 APP。
DONT_KILL_APP
})
@Retention(RetentionPolicy.SOURCE)
public @interface EnabledFlags {}
因此择懂,這里我們禁用 APP 時不需要關(guān)注第三個參數(shù)喻喳,置 0
即可,代碼如下:
PackageManager pm = context.getPackageManager();
pm.setApplicationEnabledSetting(
pkgName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
0
);
如果要重新啟用APP困曙,只需按前面的限制修改第二個參數(shù)即可表伦。
PS:
這里禁用 APP 之后,APP 圖標(biāo)會從 Launcher 上消失慷丽,在Settings > APP List 中可看到 APP 變?yōu)?disable 狀態(tài)蹦哼,用戶無法使用。
如果只是想讓用戶無法使用要糊,不讓圖標(biāo)消失纲熏,可參考如下文章:
https://blog.csdn.net/qq_25815655/article/details/78355259
禁用組件
與禁用 APP,只需要 APP 包名不同锄俄,禁用組件時局劲,還需要知道組件的名字,也就是這兩個方法第一個參數(shù)不一樣的原因奶赠。
setComponentEnabledSetting(ComponentName componentName,
@EnabledState int newState, @EnabledFlags int flags)
這里四大組件(activity, receiver, service, provider
)都可以禁用鱼填,具體的參數(shù)說明就不再提了。
最后毅戈,禁用代碼如下:
PackageManager pm = context.getPackageManager();
ComponentName name = new ComponentName(pkg, clazz);
pm.setComponentEnabledSetting(name,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
補(bǔ)充
前面說了苹丸,PackageManager 是抽象類,它通過AIDL類 IPackageManager 與 PackageManagerService 通信苇经,上面兩個方法的具體實現(xiàn)赘理,也在 Service 中。
PackageManagerService 的路徑如下:
frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
兩個方法的實現(xiàn)如下:
public void setApplicationEnabledSetting(String appPackageName,
int newState, int flags, int userId, String callingPackage) {
if (!sUserManager.exists(userId)) return;
if (callingPackage == null) {
callingPackage = Integer.toString(Binder.getCallingUid());
}
setEnabledSetting(appPackageName, null, newState, flags, userId, callingPackage);
}
@Override
public void setComponentEnabledSetting(ComponentName componentName,
int newState, int flags, int userId) {
if (!sUserManager.exists(userId)) return;
setEnabledSetting(componentName.getPackageName(),
componentName.getClassName(), newState, flags, userId, null);
}
我們可以看到它們最后都是調(diào)用了 setEnabledSetting 方法扇单,這個方法的實現(xiàn)代碼很多商模,這里我將它貼出來就不一一分析了,有興許可以自己研究下。
private void setEnabledSetting(final String packageName, String className, int newState,
final int flags, int userId, String callingPackage) {
if (!(newState == COMPONENT_ENABLED_STATE_DEFAULT
|| newState == COMPONENT_ENABLED_STATE_ENABLED
|| newState == COMPONENT_ENABLED_STATE_DISABLED
|| newState == COMPONENT_ENABLED_STATE_DISABLED_USER
|| newState == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
throw new IllegalArgumentException("Invalid new component state: "
+ newState);
}
PackageSetting pkgSetting;
final int callingUid = Binder.getCallingUid();
final int permission;
if (callingUid == Process.SYSTEM_UID) {
permission = PackageManager.PERMISSION_GRANTED;
} else {
permission = mContext.checkCallingOrSelfPermission(
android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
}
enforceCrossUserPermission(callingUid, userId,
false /* requireFullPermission */, true /* checkShell */, "set enabled");
final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
boolean sendNow = false;
boolean isApp = (className == null);
final boolean isCallerInstantApp = (getInstantAppPackageName(callingUid) != null);
String componentName = isApp ? packageName : className;
int packageUid = -1;
ArrayList<String> components;
// reader
synchronized (mPackages) {
pkgSetting = mSettings.mPackages.get(packageName);
if (pkgSetting == null) {
if (!isCallerInstantApp) {
if (className == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
throw new IllegalArgumentException(
"Unknown component: " + packageName + "/" + className);
} else {
// throw SecurityException to prevent leaking package information
throw new SecurityException(
"Attempt to change component state; "
+ "pid=" + Binder.getCallingPid()
+ ", uid=" + callingUid
+ (className == null
? ", package=" + packageName
: ", component=" + packageName + "/" + className));
}
}
}
// Limit who can change which apps
if (!UserHandle.isSameApp(callingUid, pkgSetting.appId)) {
// Don't allow apps that don't have permission to modify other apps
if (!allowedByPermission
|| filterAppAccessLPr(pkgSetting, callingUid, userId)) {
throw new SecurityException(
"Attempt to change component state; "
+ "pid=" + Binder.getCallingPid()
+ ", uid=" + callingUid
+ (className == null
? ", package=" + packageName
: ", component=" + packageName + "/" + className));
}
// Don't allow changing protected packages.
if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
throw new SecurityException("Cannot disable a protected package: " + packageName);
}
}
synchronized (mPackages) {
if (callingUid == Process.SHELL_UID
&& (pkgSetting.pkgFlags & ApplicationInfo.FLAG_TEST_ONLY) == 0) {
// Shell can only change whole packages between ENABLED and DISABLED_USER states
// unless it is a test package.
int oldState = pkgSetting.getEnabled(userId);
if (className == null
&&
(oldState == COMPONENT_ENABLED_STATE_DISABLED_USER
|| oldState == COMPONENT_ENABLED_STATE_DEFAULT
|| oldState == COMPONENT_ENABLED_STATE_ENABLED)
&&
(newState == COMPONENT_ENABLED_STATE_DISABLED_USER
|| newState == COMPONENT_ENABLED_STATE_DEFAULT
|| newState == COMPONENT_ENABLED_STATE_ENABLED)) {
// ok
} else {
throw new SecurityException(
"Shell cannot change component state for " + packageName + "/"
+ className + " to " + newState);
}
}
}
if (className == null) {
// We're dealing with an application/package level state change
synchronized (mPackages) {
if (pkgSetting.getEnabled(userId) == newState) {
// Nothing to do
return;
}
}
// If we're enabling a system stub, there's a little more work to do.
// Prior to enabling the package, we need to decompress the APK(s) to the
// data partition and then replace the version on the system partition.
final PackageParser.Package deletedPkg = pkgSetting.pkg;
final boolean isSystemStub = deletedPkg.isStub
&& deletedPkg.isSystemApp();
if (isSystemStub
&& (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
final File codePath = decompressPackage(deletedPkg);
if (codePath == null) {
Slog.e(TAG, "couldn't decompress pkg: " + pkgSetting.name);
return;
}
// TODO remove direct parsing of the package object during internal cleanup
// of scan package
// We need to call parse directly here for no other reason than we need
// the new package in order to disable the old one [we use the information
// for some internal optimization to optionally create a new package setting
// object on replace]. However, we can't get the package from the scan
// because the scan modifies live structures and we need to remove the
// old [system] package from the system before a scan can be attempted.
// Once scan is indempotent we can remove this parse and use the package
// object we scanned, prior to adding it to package settings.
final PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setDisplayMetrics(mMetrics);
pp.setCallback(mPackageParserCallback);
final PackageParser.Package tmpPkg;
try {
final int parseFlags = mDefParseFlags
| PackageParser.PARSE_MUST_BE_APK
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR;
tmpPkg = pp.parsePackage(codePath, parseFlags);
} catch (PackageParserException e) {
Slog.w(TAG, "Failed to parse compressed system package:" + pkgSetting.name, e);
return;
}
synchronized (mInstallLock) {
// Disable the stub and remove any package entries
removePackageLI(deletedPkg, true);
synchronized (mPackages) {
disableSystemPackageLPw(deletedPkg, tmpPkg);
}
final PackageParser.Package newPkg;
try (PackageFreezer freezer =
freezePackage(deletedPkg.packageName, "setEnabledSetting")) {
final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| PackageParser.PARSE_ENFORCE_CODE;
newPkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/,
0 /*currentTime*/, null /*user*/);
prepareAppDataAfterInstallLIF(newPkg);
synchronized (mPackages) {
try {
updateSharedLibrariesLPr(newPkg, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "updateAllSharedLibrariesLPw failed: ", e);
}
updatePermissionsLPw(newPkg.packageName, newPkg,
UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
mSettings.writeLPr();
}
} catch (PackageManagerException e) {
// Whoops! Something went wrong; try to roll back to the stub
Slog.w(TAG, "Failed to install compressed system package:"
+ pkgSetting.name, e);
// Remove the failed install
removeCodePathLI(codePath);
// Install the system package
try (PackageFreezer freezer =
freezePackage(deletedPkg.packageName, "setEnabledSetting")) {
synchronized (mPackages) {
// NOTE: The system package always needs to be enabled; even
// if it's for a compressed stub. If we don't, installing the
// system package fails during scan [scanning checks the disabled
// packages]. We will reverse this later, after we've "installed"
// the stub.
// This leaves us in a fragile state; the stub should never be
// enabled, so, cross your fingers and hope nothing goes wrong
// until we can disable the package later.
enableSystemPackageLPw(deletedPkg);
}
installPackageFromSystemLIF(new File(deletedPkg.codePath),
false /*isPrivileged*/, null /*allUserHandles*/,
null /*origUserHandles*/, null /*origPermissionsState*/,
true /*writeSettings*/);
} catch (PackageManagerException pme) {
Slog.w(TAG, "Failed to restore system package:"
+ deletedPkg.packageName, pme);
} finally {
synchronized (mPackages) {
mSettings.disableSystemPackageLPw(
deletedPkg.packageName, true /*replaced*/);
mSettings.writeLPr();
}
}
return;
}
clearAppDataLIF(newPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
| FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
clearAppProfilesLIF(newPkg, UserHandle.USER_ALL);
mDexManager.notifyPackageUpdated(newPkg.packageName,
newPkg.baseCodePath, newPkg.splitCodePaths);
}
}
if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
// Don't care about who enables an app.
callingPackage = null;
}
synchronized (mPackages) {
pkgSetting.setEnabled(newState, userId, callingPackage);
}
} else {
synchronized (mPackages) {
// We're dealing with a component level state change
// First, verify that this is a valid class name.
PackageParser.Package pkg = pkgSetting.pkg;
if (pkg == null || !pkg.hasComponentClassName(className)) {
if (pkg != null &&
pkg.applicationInfo.targetSdkVersion >=
Build.VERSION_CODES.JELLY_BEAN) {
throw new IllegalArgumentException("Component class " + className
+ " does not exist in " + packageName);
} else {
Slog.w(TAG, "Failed setComponentEnabledSetting: component class "
+ className + " does not exist in " + packageName);
}
}
switch (newState) {
case COMPONENT_ENABLED_STATE_ENABLED:
if (!pkgSetting.enableComponentLPw(className, userId)) {
return;
}
break;
case COMPONENT_ENABLED_STATE_DISABLED:
if (!pkgSetting.disableComponentLPw(className, userId)) {
return;
}
break;
case COMPONENT_ENABLED_STATE_DEFAULT:
if (!pkgSetting.restoreComponentLPw(className, userId)) {
return;
}
break;
default:
Slog.e(TAG, "Invalid new component state: " + newState);
return;
}
}
}
synchronized (mPackages) {
scheduleWritePackageRestrictionsLocked(userId);
updateSequenceNumberLP(pkgSetting, new int[] { userId });
final long callingId = Binder.clearCallingIdentity();
try {
updateInstantAppInstallerLocked(packageName);
} finally {
Binder.restoreCallingIdentity(callingId);
}
components = mPendingBroadcasts.get(userId, packageName);
final boolean newPackage = components == null;
if (newPackage) {
components = new ArrayList<String>();
}
if (!components.contains(componentName)) {
components.add(componentName);
}
if ((flags&PackageManager.DONT_KILL_APP) == 0) {
sendNow = true;
// Purge entry from pending broadcast list if another one exists already
// since we are sending one right away.
mPendingBroadcasts.remove(userId, packageName);
} else {
if (newPackage) {
mPendingBroadcasts.put(userId, packageName, components);
}
if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
// Schedule a message
mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY);
}
}
}
long callingId = Binder.clearCallingIdentity();
try {
if (sendNow) {
packageUid = UserHandle.getUid(userId, pkgSetting.appId);
sendPackageChangedBroadcast(packageName,
(flags&PackageManager.DONT_KILL_APP) != 0, components, packageUid);
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}