Android13 Launcher3 App list hides single or multiple apk

1 Requirements Overview

When the business requirement requires that the main page does not display one or more specific apks, we need to hide this apk in the launcher. This article takes hiding the three apks of Aqua Mail, Calculator, and FileCommander as examples to explain in detail.

2 Implement the core class of functions

Code Path

packages\apps\Launcher3\src\com\android\launcher3\model\LoaderTask.java

packages\apps\Launcher3\quickstep\src\com\android\launcher3\appprediction\PredictionRowView.java

3 Core code analysis

3.1 LoaderTask loading allapps process

Loading the apk list is loaded in LoaderTask, we only need to shield the apk required by our business at the place where all apk lists are loaded to realize this function. Look at the run() method

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);
    }

Follow up in this method and find loadAllApps(), here is the specific method of loading all apks, continue to track

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;
    }

This is to load all the apk logic, we only need to add the business logic we need to hide.

 // 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);
}

The red mark is the business logic we added, and this function is realized. However, it should be noted that in the list of apks displayed on each screen and Hotseat, do not add the apk we need to hide into this list (how to configure the apk displayed on each screen and the apk displayed on Hotseat , please read the series of articles to explain in detail)

The following is the HideApkUtils.java class

package com.android.launcher3.util;

import java.util.LinkedList;
import java.util.List;

/**
 *by Hogan 2023.3.17
 * Function: hide the specified apk list
 */

public class HideApkUtils {

    private static final List<String> mHiddenPackageMap = new LinkedList<>();

    private HideApkUtils() {
        addHideAppList();
    }

    //The apk package name that needs to be hidden, add it here
    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 loads and displays the search prediction row view process under the search box

1. Find the setPredictedApps() method, which is used to set the content of the prediction option and update the view:

 public void setPredictedApps(List<ItemInfo> items) {
        if (!FeatureFlags.ENABLE_APP_PREDICTIONS_WHILE_VISIBLE.get()
                 & amp; & amp; !mActivityContext.isBindingItems()
                 & amp; & amp; isShown()
                 & amp; & amp; getWindowVisibility() == View.VISIBLE) {
            mPendingPredictedItems = items;
            return;
        }
        applyPredictedApps(items);
    }

In this method, the applyPredictedApps() method is called

 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();
    }

This is exactly loading search prediction apk, we can add business logic code here:

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();
}

The code marked in red is where the modification was added.

4 Complete the business rendering

4.1 The previous picture is not hidden, and the red line marks the apk that needs to be hidden

4.2 Effect picture after hiding