Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c8b6f22a authored by Piyush Singhania's avatar Piyush Singhania
Browse files

Fix: Fix excessive binder calls in SystemUI.

The updateAvailableAppsAndShortcutsAsync() method in LaunchApp.kt
made excessive API calls, causing high system load.

A loop repeatedly invoked getIntentForIntentSender() and other APIs,
leading to increased binder calls.

To optimize, server-side aggregation was implemented in LauncherAppsService,
consolidating multiple requests into a single operation.

This significantly reduces performance overhead and system load.

Flag: android.app.optimize_get_apps_and_shortcuts
Test: atest LauncherAppsTest
Bug: 364133831
Change-Id: Id7f7e309f5dafb518f63370aed473276a920e886
parent f88743fc
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -205,3 +205,14 @@ flag {
     bug: "395544023"
     is_fixed_read_only: true
}

flag {
     namespace: "backstage_power"
     name: "optimize_get_apps_and_shortcuts"
     description: "Get Available Apps and Shortcuts"
     is_fixed_read_only: true
     bug: "364133831"
     metadata {
         purpose: PURPOSE_BUGFIX
     }
}
+97 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.content.pm;

import android.content.ComponentName;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;

/**
 * Holds information about how to launch an app, including its {@link ComponentName}
 * and the associated {@link Intent}.
 *
 * <p>This is a parcelable data class and should be used when passing app launch details
 * across process boundaries or between system components.</p>
 *
 * @hide
 */
public class AppLaunchInfo implements Parcelable {
    private final ComponentName mComponentName;
    private final Intent mLaunchIntent;

    /**
     * Constructs a new {@link AppLaunchInfo} instance.
     *
     * @param componentName the component to be launched
     * @param launchIntent the intent used to launch the component
     */
    public AppLaunchInfo(ComponentName componentName, Intent launchIntent) {
        this.mComponentName = componentName;
        this.mLaunchIntent = launchIntent;
    }

    protected AppLaunchInfo(Parcel in) {
        mComponentName = in.readParcelable(ComponentName.class.getClassLoader());
        mLaunchIntent = in.readParcelable(Intent.class.getClassLoader());
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(mComponentName, flags);
        dest.writeParcelable(mLaunchIntent, flags);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * Parcelable creator used to instantiate {@link AppLaunchInfo} objects from a {@link Parcel}.
     */
    public static final Creator<AppLaunchInfo> CREATOR = new Creator<AppLaunchInfo>() {
        @Override
        public AppLaunchInfo createFromParcel(Parcel in) {
            return new AppLaunchInfo(in);
        }

        @Override
        public AppLaunchInfo[] newArray(int size) {
            return new AppLaunchInfo[size];
        }
    };

    /**
     * Returns the {@link ComponentName} of the app to be launched.
     *
     * @return the target component
     */
    public ComponentName getComponentName() {
        return mComponentName;
    }

    /**
     * Returns the {@link Intent} that can be used to launch the app.
     *
     * @return the launch intent
     */
    public Intent getLaunchIntent() {
        return mLaunchIntent;
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -134,4 +134,8 @@ interface ILauncherApps {

    /** Saves view capture data to the wm trace directory. */
    void saveViewCaptureData();

    ParceledListSlice getAvailableShortcuts(String callingPackage, in UserHandle user);

    ParceledListSlice getActivityLaunchIntentForAllApps(String callingPackage, in UserHandle user);
}
+82 −0
Original line number Diff line number Diff line
@@ -1058,6 +1058,88 @@ public class LauncherApps {
        }
    }

    /**
     * Retrieves a map of available applications for a given user.
     * <p>
     * This method queries the system service to obtain a list of available applications
     * for the specified {@link UserHandle}. The returned map contains {@link ComponentName}
     * as keys and corresponding launch {@link Intent}s as values.
     * </p>
     *
     * <p><b>Permissions Required:</b></p>
     * <ul>
     *     <li>{@link android.Manifest.permission#INTERACT_ACROSS_USERS}</li>
     *     <li>{@link android.Manifest.permission#MANAGE_USERS}</li>
     * </ul>
     *
     * @param user The {@link UserHandle} representing the user for whom available apps are queried.
     * @return A map where the keys are {@link ComponentName} instances representing app components,
     *         and the values are {@link Intent} instances used to launch these apps.
     * @throws SecurityException If the caller lacks the necessary permissions.
     * @hide
     *
     */
    public @NonNull Map<ComponentName, Intent> getActivityLaunchIntentForAllApps(
            @NonNull UserHandle user) {
        logErrorForInvalidProfileAccess(user);
        try {
            ParceledListSlice<AppLaunchInfo> infos =
                    mService.getActivityLaunchIntentForAllApps(mContext.getPackageName(), user);
            if (infos == null || infos.getList().isEmpty()) {
                return new HashMap<>();
            }
            List<AppLaunchInfo> appLaunchInfos =
                    infos.getList();
            Map<ComponentName, Intent> convertedApps = new HashMap<>(appLaunchInfos.size());

            for (AppLaunchInfo info : appLaunchInfos) {
                if (info.getComponentName() != null) {
                    convertedApps.put(info.getComponentName(), info.getLaunchIntent());
                }
            }
            return convertedApps;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Retrieves a list of available shortcuts for a given user.
     * <p>
     * This method queries the system service to fetch the available {@link ShortcutInfo}
     * objects associated with the specified {@link UserHandle}. The returned list contains
     * shortcut information that can be used for launching apps or specific actions.
     * </p>
     *
     * <p><b>Permissions Required:</b></p>
     * <ul>
     *     <li>{@link android.Manifest.permission#INTERACT_ACROSS_USERS}</li>
     *     <li>{@link android.Manifest.permission#MANAGE_USERS}</li>
     * </ul>
     *
     * @param user The {@link UserHandle} representing the user for whom available shortcuts
     * are queried.
     * @return A list of {@link ShortcutInfo} objects representing the available shortcuts
     * for the specified user.
     * @throws SecurityException If the caller lacks the necessary permissions.
     * @hide
     *
     */
    public @NonNull List<ShortcutInfo> getAvailableShortcuts(@NonNull UserHandle user) {
        logErrorForInvalidProfileAccess(user);
        try {
            ParceledListSlice<ShortcutInfo> shortcuts = mService.getAvailableShortcuts(
                    mContext.getPackageName(), user);
            if (shortcuts == null || shortcuts.getList().isEmpty()) {
                return Collections.EMPTY_LIST;
            }
            return shortcuts.getList();

        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Starts an activity to show the details of the specified session.
     *
+114 −0
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.LocusId;
import android.content.pm.ActivityInfo;
import android.content.pm.AppLaunchInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ILauncherApps;
import android.content.pm.IOnAppsChangedListener;
@@ -1895,6 +1896,119 @@ public class LauncherAppsService extends SystemService {
                    user.getIdentifier());
        }

        /**
         * Returns a list of all available applications.
         *
         */
        @Override
        public ParceledListSlice<AppLaunchInfo> getActivityLaunchIntentForAllApps(
                String callingPackage, UserHandle user) {
            if (!android.app.Flags.optimizeGetAppsAndShortcuts()) {
                return new ParceledListSlice<>(new ArrayList<>());
            }
            List<AppLaunchInfo> availableApps = new ArrayList<>();
            final int userId = user.getIdentifier();

            if (!mUserManagerInternal.isUserUnlocked(userId)) {
                Log.d(TAG, "Unable to get apps, user " + userId + " not unlocked");
                return new ParceledListSlice<>(availableApps);
            }
            try {
                List<LauncherActivityInfoInternal> appInfos = getLauncherActivities(callingPackage,
                        null, user).getList();
                if (appInfos == null) {
                    return new ParceledListSlice<>(availableApps);
                }
                int numAppInfos = appInfos.size();
                for (int i = 0; i < numAppInfos; i++) {
                    LauncherActivityInfoInternal appInfo = appInfos.get(i);
                    try {
                        PendingIntent intent = getActivityLaunchIntent(callingPackage,
                                appInfo.getComponentName(), user);
                        if (intent != null) {
                            Intent targetIntent = mActivityManagerInternal
                                    .getIntentForIntentSender(intent.getTarget());
                            if (targetIntent != null) {
                                Intent sourcedIntent = new Intent(targetIntent);
                                AppLaunchInfo info = new AppLaunchInfo(
                                        appInfo.getComponentName(), sourcedIntent);
                                availableApps.add(info);
                            }
                        }
                    } catch (RuntimeException e) {
                        Log.e(TAG, "Error getting launch intent for " + appInfo.getComponentName(),
                                e);
                    }
                }
            } catch (RemoteException re) {
                throw re.rethrowFromSystemServer();
            }

            return new ParceledListSlice<>(availableApps);
        }

        /**
         * Returns a list of all available shortcuts.
         *
         */
        @Override
        public ParceledListSlice<ShortcutInfo> getAvailableShortcuts(String callingPackage,
                UserHandle user) {
            List<ShortcutInfo> availableShortcuts = new ArrayList<>();
            if (!android.app.Flags.optimizeGetAppsAndShortcuts()) {
                return new ParceledListSlice<>(availableShortcuts);
            }
            int userId = user.getIdentifier();

            if (!mUserManagerInternal.isUserUnlocked(userId)) {
                Log.d(TAG, "Unable to get shortcuts, user " + userId + " not unlocked");
                return new ParceledListSlice<>(availableShortcuts);
            }
            try {
                List<ShortcutInfo> shortcutInfos = getAllShortcutsForUser(callingPackage, user);
                if (shortcutInfos == null) {
                    return new ParceledListSlice<>(availableShortcuts);
                }
                List<LauncherActivityInfoInternal> appInfos = getLauncherActivities(callingPackage,
                        null, user).getList();
                if (appInfos == null) {
                    return new ParceledListSlice<>(availableShortcuts);
                }
                int numAppInfos = appInfos.size();
                for (int i = 0; i < numAppInfos; i++) {
                    LauncherActivityInfoInternal appInfo = appInfos.get(i);
                    String packageName = appInfo.getComponentName().getPackageName();
                    for (int j = shortcutInfos.size() - 1; j >= 0; j--) {
                        ShortcutInfo shortcut = shortcutInfos.get(j);
                        if (shortcut.getPackage().equals(packageName)) {
                            availableShortcuts.add(shortcut);
                        }
                    }
                }
            } catch (RemoteException re) {
                throw re.rethrowFromSystemServer();
            }
            return new ParceledListSlice<>(availableShortcuts);
        }

        /**
         * Returns all shortcuts for the given user.
         */
        private List<ShortcutInfo> getAllShortcutsForUser(String callingPackage, UserHandle user) {
            ShortcutQuery query = new ShortcutQuery();
            query.setQueryFlags(
                    ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC);
            try {
                return getShortcuts(callingPackage, new ShortcutQueryWrapper(query),
                                user).getList();
            } catch (SecurityException | IllegalStateException e) {
                Log.e(TAG, "Failed to query for shortcuts", e);
                return null;
            } catch (Exception e) {
                throw e; // Rethrow other exceptions
            }
        }

        /**
         * Returns the main activity launch intent for the given component package.
         */