Android 10之前的隐藏方式:
con.getPackageManager().setComponentEnabledSetting(new ComponentName(con,activityAliasName,PackageManager.COMPONENT_ENABLED_STATE_DISABLED,PackageManager.DONT_KILL_APP);
Android 10之后,该隐藏图标方式失效,隐藏成功后依然会在桌面上保留其图标,点击后会跳转到应用详情页。
那这就很难受了,在我看来,目前设计的并不太合理,能不能隐藏应该由用户决定,而不是强制不允许隐藏,不过目前官方也设置了控制开关,可以修改系统设置来去掉该限制,只不过需要adb或能修改系统设置的APP或ROOT权限才能修改。接下来就是具体分析了。
具体分析
- getActivityList
//frameworks/base/core/java/android/content/pm/LauncherApps.java
public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
logErrorForInvalidProfileAccess(user);
try {
return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(),
packageName, user), user);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
- getLauncherActivities
//frameworks/base/services/core/java/com/android/server/pm/LauncherAppsService.java
@Override
public ParceledListSlice<LauncherActivityInfoInternal> getLauncherActivities(
String callingPackage, String packageName, UserHandle user) throws RemoteException {
//查询包含ACTION_MAIN和CATEGORY_LAUNCHER的activity
ParceledListSlice<LauncherActivityInfoInternal> launcherActivities =
queryActivitiesForUser(callingPackage,
new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setPackage(packageName),
user);
//检测系统设置(系统设置控制开关控制是否走新版限制逻辑)
if (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED, 1) == 0) {
return launcherActivities;
}
//没有找到入口activity
if (launcherActivities == null) {
// Cannot access profile, so we don't even return any hidden apps.
return null;
}
final int callingUid = injectBinderCallingUid();
final long ident = injectClearCallingIdentity();
try {
//托管配置文件可以隐藏APP
if (mUm.getUserInfo(user.getIdentifier()).isManagedProfile()) {
// Managed profile should not show hidden apps
return launcherActivities;
}
//设备管理员可以隐藏APP
if (mDpm.getDeviceOwnerComponentOnAnyUser() != null) {
// Device owner devices should not show hidden apps
return launcherActivities;
}
final ArrayList<LauncherActivityInfoInternal> result = new ArrayList<>(
launcherActivities.getList());
if (packageName != null) {
// If this hidden app should not be shown, return the original list.
// Otherwise, inject hidden activity that forwards user to app details page.
//可能是如果大于0,表示有有效的启动入口,则不用注入隐藏的APP详情页入口
if (result.size() > 0) {
return launcherActivities;
}
final ApplicationInfo appInfo = mPackageManagerInternal.getApplicationInfo(
packageName, /* flags= */ 0, callingUid, user.getIdentifier());
//根据APP信息进行判断是否注入隐藏APP详情页入口
if (shouldShowSyntheticActivity(user, appInfo)) {
LauncherActivityInfoInternal info = getHiddenAppActivityInfo(packageName,
callingUid, user);
if (info != null) {
result.add(info);
}
}
return new ParceledListSlice<>(result);
}
final HashSet<String> visiblePackages = new HashSet<>();
for (LauncherActivityInfoInternal info : result) {
visiblePackages.add(info.getActivityInfo().packageName);
}
final List<ApplicationInfo> installedPackages =
mPackageManagerInternal.getInstalledApplications(/* flags= */ 0,
user.getIdentifier(), callingUid);
for (ApplicationInfo applicationInfo : installedPackages) {
if (!visiblePackages.contains(applicationInfo.packageName)) {
if (!shouldShowSyntheticActivity(user, applicationInfo)) {
continue;
}
LauncherActivityInfoInternal info = getHiddenAppActivityInfo(
applicationInfo.packageName, callingUid, user);
if (info != null) {
result.add(info);
}
}
}
return new ParceledListSlice<>(result);
} finally {
injectRestoreCallingIdentity(ident);
}
}
- shouldShowSyntheticActivity
//frameworks/base/services/core/java/com/android/server/pm/LauncherAppsService.java
private boolean shouldShowSyntheticActivity(UserHandle user, ApplicationInfo appInfo) {
//没有app信息,是系统APP或者更新的系统APP,则不添加隐藏详情页入口
if (appInfo == null || appInfo.isSystemApp() || appInfo.isUpdatedSystemApp()) {
return false;
}
//是管理员APP也不用添加该入口
if (isManagedProfileAdmin(user, appInfo.packageName)) {
return false;
}
final AndroidPackage pkg = mPackageManagerInternal.getPackage(appInfo.packageName);
if (pkg == null) {
// Should not happen, but we shouldn't be failing if it does
return false;
}
// If app does not have any default enabled launcher activity or any permissions,
// the app can legitimately have no icon so we do not show the synthetic activity.
return requestsPermissions(pkg) && hasDefaultEnableLauncherActivity(
appInfo.packageName);
}
根据以上源码,可以发现该机制有个全局设置开关,并且有多种情况可以不受该机制限制。因此总结出如下解决方案:
- 若是你的APP有adb权限或ROOT权限,可以直接修改这个全局开关,修改命令如下:
adb shell settings put global show_hidden_icon_apps_enabled 0
- 或者你的APP有设备管理员权限
- 或者你的APP是系统APP
- 或者你的APP没有任何默认启动入口
- 或者你的APP没有请求任何权限
- 或者你的APP涉及到Managed profile(这个具体不太清楚怎么用)
- 或者你的APP是Xposed模块,可以直接注入系统服务拦截相关API,或者注入可以修改系统设置的APP(比如:设置)直接修改这个开关
- 或者你是系统源码开发者,可以直接修改该处代码即可
- 其它更多还未探索出的方式(比如利用某些逻辑漏洞)…
参考