1 需求概述
当业务需求要求要主页面不显示特定的一个或多个apk时,我们需要在launcher中隐藏此apk。本文以隐藏 Aqua Mail,Calculator,FileCommander三个apk为例来详细讲解。
2 实现功能核心类
代码路径
packages\apps\Launcher3\src\com\android\launcher3\model\LoaderTask.java
packages\apps\Launcher3\quickstep\src\com\android\launcher3\appprediction\PredictionRowView.java
3 核心代码分析
3.1 LoaderTask加载allapps流程
加载apk列表是在LoaderTask中进行加载的,我们只需要在加载所有apk列表的地方,屏蔽我们业务需要的apk即可实现此功能。看run()方法
public void run() {
synchronized (this) {
// Skip fast if we are already stopped.
if (mStopped) {
return;
}
}
Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
TimingLogger logger = new TimingLogger(TAG, "run");
LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
Trace.beginSection("LoadWorkspace");
try {
loadWorkspace(allShortcuts, memoryLogger);
} finally {
Trace.endSection();
}
logASplit(logger, "loadWorkspace");
// Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
// sanitizeData should not be invoked if the workspace is loaded from a db different
// from the main db as defined in the invariant device profile.
// (e.g. both grid preview and minimal device mode uses a different db)
if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
verifyNotStopped();
sanitizeData();
logASplit(logger, "sanitizeData");
}
verifyNotStopped();
mResults.bindWorkspace(true /* incrementBindId */);
logASplit(logger, "bindWorkspace");
mModelDelegate.workspaceLoadComplete();
// Notify the installer packages of packages with active installs on the first screen.
sendFirstScreenActiveInstallsBroadcast();
logASplit(logger, "sendFirstScreenActiveInstallsBroadcast");
// Take a break
waitForIdle();
logASplit(logger, "step 1 complete");
verifyNotStopped();
// second step
Trace.beginSection("LoadAllApps");
List<LauncherActivityInfo> allActivityList;
try {
allActivityList = loadAllApps();
} finally {
Trace.endSection();
}
logASplit(logger, "loadAllApps");
verifyNotStopped();
mResults.bindAllApps();
logASplit(logger, "bindAllApps");
verifyNotStopped();
IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
setIgnorePackages(updateHandler);
updateHandler.updateIcons(allActivityList,
LauncherActivityCachingLogic.newInstance(mApp.getContext()),
mApp.getModel()::onPackageIconsUpdated);
logASplit(logger, "update icon cache");
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
verifyNotStopped();
logASplit(logger, "save shortcuts in icon cache");
updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
mApp.getModel()::onPackageIconsUpdated);
}
// Take a break
waitForIdle();
logASplit(logger, "step 2 complete");
verifyNotStopped();
// third step
List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
logASplit(logger, "loadDeepShortcuts");
verifyNotStopped();
mResults.bindDeepShortcuts();
logASplit(logger, "bindDeepShortcuts");
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
verifyNotStopped();
logASplit(logger, "save deep shortcuts in icon cache");
updateHandler.updateIcons(allDeepShortcuts,
new ShortcutCachingLogic(), (pkgs, user) -> { });
}
// Take a break
waitForIdle();
logASplit(logger, "step 3 complete");
verifyNotStopped();
// fourth step
List<ComponentWithLabelAndIcon> allWidgetsList =
mBgDataModel.widgetsModel.update(mApp, null);
logASplit(logger, "load widgets");
verifyNotStopped();
mResults.bindWidgets();
logASplit(logger, "bindWidgets");
verifyNotStopped();
updateHandler.updateIcons(allWidgetsList,
new ComponentWithIconCachingLogic(mApp.getContext(), true),
mApp.getModel()::onWidgetLabelsUpdated);
logASplit(logger, "save widgets in icon cache");
// fifth step
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
loadFolderNames();
}
verifyNotStopped();
updateHandler.finish();
logASplit(logger, "finish icon update");
mModelDelegate.modelLoadComplete();
transaction.commit();
memoryLogger.clearLogs();
} catch (CancellationException e) {
// Loader stopped, ignore
logASplit(logger, "Cancelled");
} catch (Exception e) {
memoryLogger.printLogs();
throw e;
} finally {
logger.dumpToLog();
}
TraceHelper.INSTANCE.endSection(traceToken);
}
在此方法里面跟下去,找到loadAllApps(),这里就是具体执行加载所有apk的方法了,继续跟踪
private List<LauncherActivityInfo> loadAllApps() {
final List<UserHandle> profiles = mUserCache.getUserProfiles();
List<LauncherActivityInfo> allActivityList = new ArrayList<>();
// Clear the list of apps
mBgAllAppsList.clear();
List<IconRequestInfo<AppInfo>> iconRequestInfos = new ArrayList<>();
for (UserHandle user : profiles) {
// Query for the set of apps
final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
// Fail if we don't have any apps
// TODO: Fix this. Only fail for the current user.
if (apps == null || apps.isEmpty()) {
return allActivityList;
}
boolean quietMode = mUserManagerState.isUserQuiet(user);
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfo app = apps.get(i);
AppInfo appInfo = new AppInfo(app, user, quietMode);
boolean isHideApk = HideApkUtils.getInstall().isHidedApkPackageName(app.getComponentName().getPackageName());
if(isHideApk){
continue;
}
iconRequestInfos.add(new IconRequestInfo<>(
appInfo, app, /* useLowResIcon= */ false));
mBgAllAppsList.add(
appInfo, app, !FeatureFlags.ENABLE_BULK_ALL_APPS_ICON_LOADING.get());
}
allActivityList.addAll(apps);
}
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
// get all active sessions and add them to the all apps list
for (PackageInstaller.SessionInfo info :
mSessionHelper.getAllVerifiedSessions()) {
AppInfo promiseAppInfo = mBgAllAppsList.addPromiseApp(
mApp.getContext(),
PackageInstallInfo.fromInstallingState(info),
!FeatureFlags.ENABLE_BULK_ALL_APPS_ICON_LOADING.get());
if (promiseAppInfo != null) {
iconRequestInfos.add(new IconRequestInfo<>(
promiseAppInfo,
/* launcherActivityInfo= */ null,
promiseAppInfo.usingLowResIcon()));
}
}
}
if (FeatureFlags.ENABLE_BULK_ALL_APPS_ICON_LOADING.get()) {
Trace.beginSection("LoadAllAppsIconsInBulk");
try {
mIconCache.getTitlesAndIconsInBulk(iconRequestInfos);
iconRequestInfos.forEach(iconRequestInfo ->
mBgAllAppsList.updateSectionName(iconRequestInfo.itemInfo));
} finally {
Trace.endSection();
}
}
mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED,
mUserManagerState.isAnyProfileQuietModeEnabled());
mBgAllAppsList.setFlags(FLAG_HAS_SHORTCUT_PERMISSION,
hasShortcutsPermission(mApp.getContext()));
mBgAllAppsList.setFlags(FLAG_QUIET_MODE_CHANGE_PERMISSION,
mApp.getContext().checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
== PackageManager.PERMISSION_GRANTED);
mBgAllAppsList.getAndResetChangeFlag();
return allActivityList;
}
这里面就是加载所有apk逻辑了,我们只需要添加我们需要隐藏的业务逻辑即可。
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfo app = apps.get(i);
AppInfo appInfo = new AppInfo(app, user, quietMode);
boolean isHideApk = HideApkUtils.getInstall().isHidedApkPackageName(app.getComponentName().getPackageName());
if(isHideApk){
continue;
}
iconRequestInfos.add(new IconRequestInfo<>(
appInfo, app, /* useLowResIcon= */ false));
mBgAllAppsList.add(
appInfo, app, !FeatureFlags.ENABLE_BULK_ALL_APPS_ICON_LOADING.get());
}
allActivityList.addAll(apps);
}
标红的即我们加的业务逻辑,此功能就实现了。但是需要注意的是,在配每一屏显示与Hotseat显示的apk列表中,不要把我们需要隐藏的apk加入这表中即可(如何配置每一屏显示的apk与Hotseat显示的apk,请看系列文章会详细讲解)
以下是HideApkUtils.java类
package com.android.launcher3.util;
import java.util.LinkedList;
import java.util.List;
/**
* by Hogan 2023.3.17
* 功能:隐藏指定的apk列表
*/
public class HideApkUtils {
private static final List<String> mHiddenPackageMap = new LinkedList<>();
private HideApkUtils() {
addHideAppList();
}
//需要隐藏的apk包名,添加到这里
private void addHideAppList() {
mHiddenPackageMap.add("org.kman.AquaMail");
mHiddenPackageMap.add("com.google.android.calculator");
mHiddenPackageMap.add("com.mobisystems.fileman");
// mHiddenPackageMap.add("com.mobisystems.office");
// mHiddenPackageMap.add("com.hht.factory");
}
private static class HideApkUtilsHolder {
private static final HideApkUtils INSTALL = new HideApkUtils();
}
public static HideApkUtils getInstall() {
return HideApkUtilsHolder.INSTALL;
}
public boolean isHidedApkPackageName(String apkPkg) {
return mHiddenPackageMap.contains(apkPkg);
}
}
3.2 PredictionRowView 加载显示搜索框下的搜索预测行视图流程
1. 找到setPredictedApps()方法,此方法是用于设置预测选项的内容并更新视图的:
public void setPredictedApps(List<ItemInfo> items) {
if (!FeatureFlags.ENABLE_APP_PREDICTIONS_WHILE_VISIBLE.get()
&& !mActivityContext.isBindingItems()
&& isShown()
&& getWindowVisibility() == View.VISIBLE) {
mPendingPredictedItems = items;
return;
}
applyPredictedApps(items);
}
在这个方法里面调用了 applyPredictedApps()方法
private void applyPredictedApps(List<ItemInfo> items) {
List<ItemInfo> itemInfoList =new ArrayList<>();
for(int i=0;i<items.size();i++){
String pkg = items.get(i).getTargetPackage();
boolean isHideApk = HideApkUtils.getInstall().isHidedApkPackageName(pkg);
if(isHideApk){
continue;
}
itemInfoList.add(items.get(i));
}
mPendingPredictedItems = null;
mPredictedApps.clear();
mPredictedApps.addAll(itemInfoList.stream()
.filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
.map(itemInfo -> (WorkspaceItemInfo) itemInfo).collect(Collectors.toList()));
applyPredictionApps();
}
这正是加载搜索预测apk,我们可以在这里加上业务逻辑代码即可:
private void applyPredictedApps(List<ItemInfo> items) {
List<ItemInfo> itemInfoList =new ArrayList<>();
for(int i=0;i<items.size();i++){
String pkg = items.get(i).getTargetPackage();
boolean isHideApk = HideApkUtils.getInstall().isHidedApkPackageName(pkg);
if(isHideApk){
continue;
}
itemInfoList.add(items.get(i));
}
mPendingPredictedItems = null;
mPredictedApps.clear();
mPredictedApps.addAll(itemInfoList.stream()
.filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
.map(itemInfo -> (WorkspaceItemInfo) itemInfo).collect(Collectors.toList()));
applyPredictionApps();
}
标红的代码就是加入修改之处。
4 完成业务效果图
4.1 未隐藏前图,红线标注为需要隐藏的apk
4.2 隐藏后的效果图