summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ricky Wai <rickywai@google.com> 2018-10-10 09:26:32 +0100
committer Ricky Wai <rickywai@google.com> 2018-10-11 14:19:04 +0000
commitcf134ebfb716199aa20e19db2e9fdf1d7eb263d5 (patch)
tree33b4c8b897f58fd5537431e980d137f55fb9fa90
parentbfec1e9843e31c1716dfb435cd38b38938683a0d (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
-rw-r--r--core/java/android/app/AppDetailsActivity.java36
-rw-r--r--core/java/android/content/pm/PackageManagerInternal.java24
-rw-r--r--core/java/android/content/pm/PackageParser.java75
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java79
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java31
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) {