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

Commit cf134ebf authored by Ricky Wai's avatar Ricky Wai
Browse files

Return app hidden details activity in launcher api

If a normal app does not have launcher icon, launcher api
will return app details activity instead, so user will
be noticed that the app is still installed.

Bug: 111348460

Test: Installed an app without launcher activity, an app icon is being
shown in launcher allapps, and it forwards user to app details page.

Change-Id: I9c17f5edfdefe19727145e7176d7e113286c997d
parent bfec1e98
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
/*
 * Copyright 2018 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.app;

import android.content.Intent;
import android.os.Bundle;

/**
 * Helper activity that forwards you to app details page.
 *
 * @hide
 */
public class AppDetailsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(android.net.Uri.fromParts("package", getPackageName(), null));
        startActivity(intent);
        finish();
    }
}
+24 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.content.pm;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager.ApplicationInfoFlags;
@@ -204,6 +205,29 @@ public abstract class PackageManagerInternal {
    public abstract PackageInfo getPackageInfo(String packageName,
            @PackageInfoFlags int flags, int filterCallingUid, int userId);

    /**
     * Return a List of all application packages that are installed on the
     * device, for a specific user. If flag GET_UNINSTALLED_PACKAGES has been
     * set, a list of all applications including those deleted with
     * {@code DONT_DELETE_DATA} (partially installed apps with data directory)
     * will be returned.
     *
     * @param flags Additional option flags to modify the data returned.
     * @param userId The user for whom the installed applications are to be
     *            listed
     * @param callingUid The uid of the original caller app
     * @return A List of ApplicationInfo objects, one for each installed
     *         application. In the unlikely case there are no installed
     *         packages, an empty list is returned. If flag
     *         {@code MATCH_UNINSTALLED_PACKAGES} is set, the application
     *         information is retrieved from the list of uninstalled
     *         applications (which includes installed applications as well as
     *         applications with data directory i.e. applications which had been
     *         deleted with {@code DONT_DELETE_DATA} flag set).
     */
    public abstract List<ApplicationInfo> getInstalledApplications(
            @ApplicationInfoFlags int flags, @UserIdInt int userId, int callingUid);

    /**
     * Retrieve launcher extras for a suspended package provided to the system in
     * {@link PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
+69 −6
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.annotation.Nullable;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityTaskManager;
import android.app.AppDetailsActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -3876,6 +3877,11 @@ public class PackageParser {
            }
        }

        // Add a hidden app detail activity which forwards user to App Details page.
        Activity a = generateAppDetailsHiddenActivity(owner, flags, outError,
                owner.baseHardwareAccelerated);
        owner.activities.add(a);

        if (hasActivityOrder) {
            Collections.sort(owner.activities, (a1, a2) -> Integer.compare(a2.order, a1.order));
        }
@@ -4115,9 +4121,14 @@ public class PackageParser {
                return false;
            }
        } else {
            outInfo.name
            String outInfoName
                = buildClassName(owner.applicationInfo.packageName, name, outError);
            if (outInfo.name == null) {
            if (AppDetailsActivity.class.getName().equals(outInfoName)) {
                outError[0] = tag + " invalid android:name";
                return false;
            }
            outInfo.name = outInfoName;
            if (outInfoName == null) {
                return false;
            }
        }
@@ -4156,6 +4167,45 @@ public class PackageParser {
        return true;
    }

    /**
     * Generate activity object that forwards user to App Details page automatically.
     * This activity should be invisible to user and user should not know or see it.
     */
    private @NonNull PackageParser.Activity generateAppDetailsHiddenActivity(
            PackageParser.Package owner, int flags, String[] outError,
            boolean hardwareAccelerated) {

        // Build custom App Details activity info instead of parsing it from xml
        Activity a = new Activity(owner, AppDetailsActivity.class.getName(), new ActivityInfo());
        a.owner = owner;
        a.setPackageName(owner.packageName);

        a.info.theme = 0;
        a.info.exported = true;
        a.info.name = AppDetailsActivity.class.getName();
        a.info.processName = owner.applicationInfo.processName;
        a.info.uiOptions = a.info.applicationInfo.uiOptions;
        a.info.taskAffinity = buildTaskAffinityName(owner.packageName, owner.packageName,
                ":app_details", outError);
        a.info.enabled = true;
        a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
        a.info.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NONE;
        a.info.maxRecents = ActivityTaskManager.getDefaultAppRecentsLimitStatic();
        a.info.configChanges = getActivityConfigChanges(0, 0);
        a.info.softInputMode = 0;
        a.info.persistableMode = ActivityInfo.PERSIST_NEVER;
        a.info.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
        a.info.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
        a.info.lockTaskLaunchMode = 0;
        a.info.encryptionAware = a.info.directBootAware = false;
        a.info.rotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
        a.info.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
        if (hardwareAccelerated) {
            a.info.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED;
        }
        return a;
    }

    private Activity parseActivity(Package owner, Resources res,
            XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs,
            boolean receiver, boolean hardwareAccelerated)
@@ -7178,10 +7228,16 @@ public class PackageParser {
        ComponentName componentName;
        String componentShortName;

        public Component(Package _owner) {
            owner = _owner;
            intents = null;
            className = null;
        public Component(Package owner, ArrayList<II> intents, String className) {
            this.owner = owner;
            this.intents = intents;
            this.className = className;
        }

        public Component(Package owner) {
            this.owner = owner;
            this.intents = null;
            this.className = null;
        }

        public Component(final ParsePackageItemArgs args, final PackageItemInfo outInfo) {
@@ -7646,6 +7702,13 @@ public class PackageParser {
            return mHasMaxAspectRatio;
        }

        // To construct custom activity which does not exist in manifest
        Activity(final Package owner, final String className, final ActivityInfo info) {
            super(owner, new ArrayList<>(0), className);
            this.info = info;
            this.info.applicationInfo = owner.applicationInfo;
        }

        public Activity(final ParseComponentArgs args, final ActivityInfo _info) {
            super(args, _info);
            info = _info;
+76 −3
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppDetailsActivity;
import android.app.AppGlobals;
import android.app.IApplicationThread;
import android.app.PendingIntent;
@@ -65,7 +66,9 @@ import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;

/**
@@ -74,6 +77,7 @@ import java.util.List;
 */
public class LauncherAppsService extends SystemService {

    private static final boolean SHOW_HIDDEN_APP_ENABLED = false;
    private final LauncherAppsImpl mLauncherAppsImpl;

    public LauncherAppsService(Context context) {
@@ -281,15 +285,84 @@ public class LauncherAppsService extends SystemService {
            }
        }

        private ResolveInfo getHiddenAppActivityInfo(String packageName, int callingUid,
                UserHandle user) {
            Intent intent = new Intent();
            intent.setComponent(new ComponentName(packageName, AppDetailsActivity.class.getName()));
            final PackageManagerInternal pmInt =
                    LocalServices.getService(PackageManagerInternal.class);
            List<ResolveInfo> apps = pmInt.queryIntentActivities(intent,
                    PackageManager.MATCH_DIRECT_BOOT_AWARE
                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
                    callingUid, user.getIdentifier());
            if (apps.size() > 0) {
                return apps.get(0);
            }
            return null;
        }

        @Override
        public ParceledListSlice<ResolveInfo> getLauncherActivities(String callingPackage,
                String packageName, UserHandle user)
                throws RemoteException {
            return queryActivitiesForUser(callingPackage,
                String packageName, UserHandle user) throws RemoteException {
            ParceledListSlice<ResolveInfo> launcherActivities = queryActivitiesForUser(
                    callingPackage,
                    new Intent(Intent.ACTION_MAIN)
                            .addCategory(Intent.CATEGORY_LAUNCHER)
                            .setPackage(packageName),
                    user);
            if (!SHOW_HIDDEN_APP_ENABLED) {
                return launcherActivities;
            }

            final int callingUid = injectBinderCallingUid();
            final ArrayList<ResolveInfo> result = new ArrayList<>(launcherActivities.getList());
            if (packageName != null) {
                // If target package has launcher activities, then return those launcher
                // activities. Otherwise, return hidden activity that forwards user to app
                // details page.
                if (result.size() > 0) {
                    return launcherActivities;
                }
                ResolveInfo info = getHiddenAppActivityInfo(packageName, callingUid, user);
                if (info != null) {
                    result.add(info);
                }
                return new ParceledListSlice<>(result);
            }

            long ident = injectClearCallingIdentity();
            try {
                final HashSet<String> visiblePackages = new HashSet<>();
                for (ResolveInfo info : result) {
                    visiblePackages.add(info.activityInfo.packageName);
                }
                final PackageManagerInternal pmInt =
                        LocalServices.getService(PackageManagerInternal.class);
                List<ApplicationInfo> installedPackages = pmInt.getInstalledApplications(0,
                        user.getIdentifier(), callingUid);
                for (ApplicationInfo applicationInfo : installedPackages) {
                    if (!visiblePackages.contains(applicationInfo.packageName)) {
                        if (!shouldShowHiddenApp(applicationInfo)) {
                            continue;
                        }
                        ResolveInfo info = getHiddenAppActivityInfo(applicationInfo.packageName,
                                callingUid, user);
                        if (info != null) {
                            result.add(info);
                        }
                    }
                }
                return new ParceledListSlice<>(result);
            } finally {
                injectRestoreCallingIdentity(ident);
            }
        }

        private static boolean shouldShowHiddenApp(ApplicationInfo appInfo) {
            if (appInfo.isSystemApp() || appInfo.isUpdatedSystemApp()) {
                return false;
            }
            return true;
        }

        @Override
+28 −3
Original line number Diff line number Diff line
@@ -121,6 +121,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppDetailsActivity;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.ResourcesManager;
@@ -7861,10 +7862,16 @@ public class PackageManagerService extends IPackageManager.Stub
    @Override
    public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, int userId) {
        final int callingUid = Binder.getCallingUid();
        return new ParceledListSlice<>(
                getInstalledApplicationsListInternal(flags, userId, callingUid));
    }
    private List<ApplicationInfo> getInstalledApplicationsListInternal(int flags, int userId,
            int callingUid) {
        if (getInstantAppPackageName(callingUid) != null) {
            return ParceledListSlice.emptyList();
            return Collections.emptyList();
        }
        if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
        if (!sUserManager.exists(userId)) return Collections.emptyList();
        flags = updateFlagsForApplication(flags, userId, null);
        final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
@@ -7929,7 +7936,7 @@ public class PackageManagerService extends IPackageManager.Stub
                }
            }
            return new ParceledListSlice<>(list);
            return list;
        }
    }
@@ -19354,6 +19361,16 @@ public class PackageManagerService extends IPackageManager.Stub
                throw new SecurityException("Cannot disable a protected package: " + packageName);
            }
        }
        // Only allow apps with CHANGE_COMPONENT_ENABLED_STATE permission to change hidden
        // app details activity
        if (AppDetailsActivity.class.getName().equals(className)) {
            if (mContext.checkCallingOrSelfPermission(
                    android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE)
                    != PackageManager.PERMISSION_GRANTED) {
                Slog.e(TAG, "Cannot disable a protected component: " + packageName);
                return;
            }
        }
        synchronized (mPackages) {
            if (callingUid == Process.SHELL_UID
@@ -22252,6 +22269,14 @@ public class PackageManagerService extends IPackageManager.Stub
                    permName, packageName, flagMask, flagValues, userId);
        }
        @Override
        public List<ApplicationInfo> getInstalledApplications(int flags, int userId,
                int callingUid) {
            return PackageManagerService.this.getInstalledApplicationsListInternal(flags, userId,
                    callingUid);
        }
        @Override
        public boolean isPlatformSigned(String packageName) {
            PackageSetting packageSetting = mSettings.mPackages.get(packageName);