diff options
| author | 2018-10-10 09:26:32 +0100 | |
|---|---|---|
| committer | 2018-10-11 14:19:04 +0000 | |
| commit | cf134ebfb716199aa20e19db2e9fdf1d7eb263d5 (patch) | |
| tree | 33b4c8b897f58fd5537431e980d137f55fb9fa90 | |
| parent | bfec1e9843e31c1716dfb435cd38b38938683a0d (diff) | |
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
5 files changed, 233 insertions, 12 deletions
diff --git a/core/java/android/app/AppDetailsActivity.java b/core/java/android/app/AppDetailsActivity.java new file mode 100644 index 000000000000..cd36e634f54b --- /dev/null +++ b/core/java/android/app/AppDetailsActivity.java @@ -0,0 +1,36 @@ +/* + * 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(); + } +} diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index b5b4432bbdb2..7b4c6fc64a69 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -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; @@ -205,6 +206,29 @@ public abstract class PackageManagerInternal { @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, * PersistableBundle, String)}. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 1fa5190ef8df..94fe0ba4ebba 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -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; diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 93230401eb20..a08c189cb125 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -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 diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index e10827bc6101..433c68282507 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -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 @@ -22253,6 +22270,14 @@ public class PackageManagerService extends IPackageManager.Stub } @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); if (packageSetting == null) { |