OSDN Git Service

ShortcutManager: Make sure persisted default launcher still exists.
authorMakoto Onuki <omakoto@google.com>
Thu, 30 Jun 2016 00:34:02 +0000 (17:34 -0700)
committerMakoto Onuki <omakoto@google.com>
Fri, 1 Jul 2016 00:30:30 +0000 (17:30 -0700)
- Also make sure to ignore unexported activities.
(e.g. unexported activities shouldn't have shortcuts.)

- Also add unit tests for package manager related operations.

- Also remove stale TODOs (per-activity shortcut count check is
implemented already.)

Bug 29699769
Bug 29516954

Change-Id: Ia18301baf6bec1ad71ae195d9ae3d10bd8386fc4

services/core/java/com/android/server/pm/ShortcutPackage.java
services/core/java/com/android/server/pm/ShortcutParser.java
services/core/java/com/android/server/pm/ShortcutService.java
services/tests/servicestests/AndroidManifest.xml
services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest4.java
services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java [new file with mode: 0644]
services/tests/servicestests/src/com/android/server/pm/ShortcutTestActivity.java [new file with mode: 0644]

index 3028386..1528eb5 100644 (file)
@@ -260,9 +260,6 @@ class ShortcutPackage extends ShortcutPackageItem {
             }
         }
 
-        // TODO Check max dynamic count.
-        // mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount);
-
         // If it was originally pinned, the new one should be pinned too.
         if (wasPinned) {
             newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
index 858e1cd..0762c0b 100644 (file)
@@ -20,7 +20,6 @@ import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.res.TypedArray;
@@ -79,7 +78,7 @@ public class ShortcutParser {
                 }
 
                 final ActivityInfo activityInfoWithMetadata =
-                        service.injectGetActivityInfoWithMetadata(
+                        service.getActivityInfoWithMetadata(
                         activityInfoNoMetadata.getComponentName(), userId);
                 if (activityInfoWithMetadata != null) {
                     result = parseShortcutsOneFile(
index 22d0d3c..c6949e4 100644 (file)
@@ -232,6 +232,13 @@ public class ShortcutService extends IShortcutService.Stub {
 
     private final Object mLock = new Object();
 
+    private static List<ResolveInfo> EMPTY_RESOLVE_INFO = new ArrayList<>(0);
+
+    private static Predicate<ResolveInfo> ACTIVITY_NOT_EXPORTED =
+            ri -> !ri.activityInfo.exported;
+
+    private static Predicate<PackageInfo> PACKAGE_NOT_INSTALLED = pi -> !isInstalled(pi);
+
     private final Handler mHandler;
 
     @GuardedBy("mLock")
@@ -318,8 +325,9 @@ public class ShortcutService extends IShortcutService.Stub {
         int RESOURCE_NAME_LOOKUP = 10;
         int GET_LAUNCHER_ACTIVITY = 11;
         int CHECK_LAUNCHER_ACTIVITY = 12;
+        int IS_ACTIVITY_ENABLED = 13;
 
-        int COUNT = CHECK_LAUNCHER_ACTIVITY + 1;
+        int COUNT = IS_ACTIVITY_ENABLED + 1;
     }
 
     final Object mStatLock = new Object();
@@ -348,11 +356,11 @@ public class ShortcutService extends IShortcutService.Stub {
     }
 
     public ShortcutService(Context context) {
-        this(context, BackgroundThread.get().getLooper());
+        this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false);
     }
 
     @VisibleForTesting
-    ShortcutService(Context context, Looper looper) {
+    ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis) {
         mContext = Preconditions.checkNotNull(context);
         LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
         mHandler = new Handler(looper);
@@ -363,6 +371,10 @@ public class ShortcutService extends IShortcutService.Stub {
         mUsageStatsManagerInternal = Preconditions.checkNotNull(
                 LocalServices.getService(UsageStatsManagerInternal.class));
 
+        if (onlyForPackageManagerApis) {
+            return; // Don't do anything further.  For unit tests only.
+        }
+
         mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
 
         injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
@@ -1571,11 +1583,6 @@ public class ShortcutService extends IShortcutService.Stub {
                     removeIcon(userId, target);
                 }
 
-                if (source.getActivity() != null &&
-                        !source.getActivity().equals(target.getActivity())) {
-                    // TODO When activity is changing, check the dynamic count.
-                }
-
                 // Note copyNonNullFieldsFrom() does the "updatable with?" check too.
                 target.copyNonNullFieldsFrom(source);
                 target.setTimestamp(injectCurrentTimeMillis());
@@ -1915,9 +1922,16 @@ public class ShortcutService extends IShortcutService.Stub {
             } else {
                 detected = user.getDefaultLauncherComponent();
 
-                // TODO: Make sure it's still enabled.
-                if (DEBUG) {
-                    Slog.v(TAG, "Cached launcher: " + detected);
+                if (detected != null) {
+                    if (injectIsActivityEnabledAndExported(detected, userId)) {
+                        if (DEBUG) {
+                            Slog.v(TAG, "Cached launcher: " + detected);
+                        }
+                    } else {
+                        Slog.w(TAG, "Cached launcher " + detected + " no longer exists");
+                        detected = null;
+                        user.setDefaultLauncherComponent(null);
+                    }
                 }
             }
 
@@ -2363,6 +2377,10 @@ public class ShortcutService extends IShortcutService.Stub {
                         return; // Don't delete shadow information.
                     }
                     if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Uninstalled: " + spi.getPackageName()
+                                    + " user " + spi.getPackageUserId());
+                        }
                         gonePackages.add(PackageWithUser.of(spi));
                     }
                 });
@@ -2457,21 +2475,26 @@ public class ShortcutService extends IShortcutService.Stub {
 
     // === PackageManager interaction ===
 
+    /**
+     * Returns {@link PackageInfo} unless it's uninstalled or disabled.
+     */
     @Nullable
-    PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
-        return injectPackageInfo(packageName, userId, true);
+    final PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
+        return getPackageInfo(packageName, userId, true);
     }
 
+    /**
+     * Returns {@link PackageInfo} unless it's uninstalled or disabled.
+     */
     @Nullable
-    PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) {
-        return injectPackageInfo(packageName, userId, false);
+    final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) {
+        return getPackageInfo(packageName, userId, false);
     }
 
     int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
         final long token = injectClearCallingIdentity();
         try {
-            return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS
-                    , userId);
+            return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS, userId);
         } catch (RemoteException e) {
             // Shouldn't happen.
             Slog.wtf(TAG, "RemoteException", e);
@@ -2481,16 +2504,30 @@ public class ShortcutService extends IShortcutService.Stub {
         }
     }
 
+    /**
+     * Returns {@link PackageInfo} unless it's uninstalled or disabled.
+     */
     @Nullable
     @VisibleForTesting
-    PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
+    final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId,
+            boolean getSignatures) {
+        return isInstalledOrNull(injectPackageInfoWithUninstalled(
+                packageName, userId, getSignatures));
+    }
+
+    /**
+     * Do not use directly; this returns uninstalled packages too.
+     */
+    @Nullable
+    @VisibleForTesting
+    PackageInfo injectPackageInfoWithUninstalled(String packageName, @UserIdInt int userId,
             boolean getSignatures) {
         final long start = injectElapsedRealtime();
         final long token = injectClearCallingIdentity();
         try {
-            return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
-                            | (getSignatures ? PackageManager.GET_SIGNATURES : 0)
-                    , userId);
+            return mIPackageManager.getPackageInfo(
+                    packageName, PACKAGE_MATCH_FLAGS
+                            | (getSignatures ? PackageManager.GET_SIGNATURES : 0), userId);
         } catch (RemoteException e) {
             // Shouldn't happen.
             Slog.wtf(TAG, "RemoteException", e);
@@ -2504,9 +2541,22 @@ public class ShortcutService extends IShortcutService.Stub {
         }
     }
 
+    /**
+     * Returns {@link ApplicationInfo} unless it's uninstalled or disabled.
+     */
     @Nullable
     @VisibleForTesting
-    ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
+    final ApplicationInfo getApplicationInfo(String packageName, @UserIdInt int userId) {
+        return isInstalledOrNull(injectApplicationInfoWithUninstalled(packageName, userId));
+    }
+
+    /**
+     * Do not use directly; this returns uninstalled packages too.
+     */
+    @Nullable
+    @VisibleForTesting
+    ApplicationInfo injectApplicationInfoWithUninstalled(
+            String packageName, @UserIdInt int userId) {
         final long start = injectElapsedRealtime();
         final long token = injectClearCallingIdentity();
         try {
@@ -2522,13 +2572,27 @@ public class ShortcutService extends IShortcutService.Stub {
         }
     }
 
+    /**
+     * Returns {@link ActivityInfo} with its metadata unless it's uninstalled or disabled.
+     */
+    @Nullable
+    final ActivityInfo getActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId) {
+        return isInstalledOrNull(injectGetActivityInfoWithMetadataWithUninstalled(
+                activity, userId));
+    }
+
+    /**
+     * Do not use directly; this returns uninstalled packages too.
+     */
     @Nullable
-    ActivityInfo injectGetActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId) {
+    @VisibleForTesting
+    ActivityInfo injectGetActivityInfoWithMetadataWithUninstalled(
+            ComponentName activity, @UserIdInt int userId) {
         final long start = injectElapsedRealtime();
         final long token = injectClearCallingIdentity();
         try {
             return mIPackageManager.getActivityInfo(activity,
-                    PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA, userId);
+                    (PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA), userId);
         } catch (RemoteException e) {
             // Shouldn't happen.
             Slog.wtf(TAG, "RemoteException", e);
@@ -2540,18 +2604,20 @@ public class ShortcutService extends IShortcutService.Stub {
         }
     }
 
-    @Nullable
+    /**
+     * Return all installed and enabled packages.
+     */
+    @NonNull
     @VisibleForTesting
-    List<PackageInfo> injectInstalledPackages(@UserIdInt int userId) {
+    final List<PackageInfo> getInstalledPackages(@UserIdInt int userId) {
         final long start = injectElapsedRealtime();
         final long token = injectClearCallingIdentity();
         try {
-            final ParceledListSlice<PackageInfo> parceledList =
-                    mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId);
-            if (parceledList == null) {
-                return Collections.emptyList();
-            }
-            return parceledList.getList();
+            final List<PackageInfo> all = injectGetPackagesWithUninstalled(userId);
+
+            all.removeIf(PACKAGE_NOT_INSTALLED);
+
+            return all;
         } catch (RemoteException e) {
             // Shouldn't happen.
             Slog.wtf(TAG, "RemoteException", e);
@@ -2563,17 +2629,31 @@ public class ShortcutService extends IShortcutService.Stub {
         }
     }
 
+    /**
+     * Do not use directly; this returns uninstalled packages too.
+     */
+    @NonNull
+    @VisibleForTesting
+    List<PackageInfo> injectGetPackagesWithUninstalled(@UserIdInt int userId)
+            throws RemoteException {
+        final ParceledListSlice<PackageInfo> parceledList =
+                mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId);
+        if (parceledList == null) {
+            return Collections.emptyList();
+        }
+        return parceledList.getList();
+    }
+
     private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime,
             Consumer<ApplicationInfo> callback) {
         if (DEBUG) {
             Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime);
         }
-        final List<PackageInfo> list = injectInstalledPackages(userId);
+        final List<PackageInfo> list = getInstalledPackages(userId);
         for (int i = list.size() - 1; i >= 0; i--) {
             final PackageInfo pi = list.get(i);
 
-            if (((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0)
-                    && (pi.lastUpdateTime >= lastScanTime)) {
+            if (pi.lastUpdateTime >= lastScanTime) {
                 if (DEBUG) {
                     Slog.d(TAG, "Found updated package " + pi.packageName);
                 }
@@ -2582,13 +2662,37 @@ public class ShortcutService extends IShortcutService.Stub {
         }
     }
 
-    private boolean isApplicationFlagSet(String packageName, int userId, int flags) {
-        final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
+    private boolean isApplicationFlagSet(@NonNull String packageName, int userId, int flags) {
+        final ApplicationInfo ai = injectApplicationInfoWithUninstalled(packageName, userId);
         return (ai != null) && ((ai.flags & flags) == flags);
     }
 
+    private static boolean isInstalled(@Nullable ApplicationInfo ai) {
+        return (ai != null) && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
+    }
+
+    private static boolean isInstalled(@Nullable PackageInfo pi) {
+        return (pi != null) && isInstalled(pi.applicationInfo);
+    }
+
+    private static boolean isInstalled(@Nullable ActivityInfo ai) {
+        return (ai != null) && isInstalled(ai.applicationInfo);
+    }
+
+    private static ApplicationInfo isInstalledOrNull(ApplicationInfo ai) {
+        return isInstalled(ai) ? ai : null;
+    }
+
+    private static PackageInfo isInstalledOrNull(PackageInfo pi) {
+        return isInstalled(pi) ? pi : null;
+    }
+
+    private static ActivityInfo isInstalledOrNull(ActivityInfo ai) {
+        return isInstalled(ai) ? ai : null;
+    }
+
     boolean isPackageInstalled(String packageName, int userId) {
-        return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
+        return getApplicationInfo(packageName, userId) != null;
     }
 
     @Nullable
@@ -2619,20 +2723,46 @@ public class ShortcutService extends IShortcutService.Stub {
         return intent;
     }
 
+    /**
+     * Same as queryIntentActivitiesAsUser, except it makes sure the package is installed,
+     * and only returns exported activities.
+     */
+    @NonNull
+    @VisibleForTesting
+    List<ResolveInfo> queryActivities(@NonNull Intent baseIntent,
+            @NonNull String packageName, @Nullable ComponentName activity, int userId) {
+
+        baseIntent.setPackage(Preconditions.checkNotNull(packageName));
+        if (activity != null) {
+            baseIntent.setComponent(activity);
+        }
+
+        final List<ResolveInfo> resolved =
+                mContext.getPackageManager().queryIntentActivitiesAsUser(
+                        baseIntent, PACKAGE_MATCH_FLAGS, userId);
+        if (resolved == null || resolved.size() == 0) {
+            return EMPTY_RESOLVE_INFO;
+        }
+        // Make sure the package is installed.
+        if (!isInstalled(resolved.get(0).activityInfo)) {
+            return EMPTY_RESOLVE_INFO;
+        }
+        resolved.removeIf(ACTIVITY_NOT_EXPORTED);
+        return resolved;
+    }
+
+    /**
+     * Return the main activity that is enabled and exported.  If multiple activities are found,
+     * return the first one.
+     */
     @Nullable
     ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) {
         final long start = injectElapsedRealtime();
         final long token = injectClearCallingIdentity();
         try {
-            final Intent intent = getMainActivityIntent();
-            intent.setPackage(packageName);
-
             final List<ResolveInfo> resolved =
-                    mContext.getPackageManager().queryIntentActivitiesAsUser(
-                            intent, PACKAGE_MATCH_FLAGS, userId);
-
-            return (resolved == null || resolved.size() == 0)
-                    ? null : resolved.get(0).activityInfo.getComponentName();
+                    queryActivities(getMainActivityIntent(), packageName, null, userId);
+            return resolved.size() == 0 ? null : resolved.get(0).activityInfo.getComponentName();
         } finally {
             injectRestoreCallingIdentity(token);
 
@@ -2640,19 +2770,17 @@ public class ShortcutService extends IShortcutService.Stub {
         }
     }
 
+    /**
+     * Return whether an activity is enabled, exported and main.
+     */
     boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) {
         final long start = injectElapsedRealtime();
         final long token = injectClearCallingIdentity();
         try {
-            final Intent intent = getMainActivityIntent();
-            intent.setPackage(activity.getPackageName());
-            intent.setComponent(activity);
-
             final List<ResolveInfo> resolved =
-                    mContext.getPackageManager().queryIntentActivitiesAsUser(
-                            intent, PACKAGE_MATCH_FLAGS, userId);
-
-            return resolved != null && resolved.size() > 0;
+                    queryActivities(getMainActivityIntent(), activity.getPackageName(),
+                            activity, userId);
+            return resolved.size() > 0;
         } finally {
             injectRestoreCallingIdentity(token);
 
@@ -2660,23 +2788,37 @@ public class ShortcutService extends IShortcutService.Stub {
         }
     }
 
+    /**
+     * Return all the enabled, exported and main activities from a package.
+     */
     @NonNull
     List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) {
         final long start = injectElapsedRealtime();
         final long token = injectClearCallingIdentity();
         try {
-            final Intent intent = getMainActivityIntent();
-            intent.setPackage(packageName);
+            return queryActivities(getMainActivityIntent(), packageName, null, userId);
+        } finally {
+            injectRestoreCallingIdentity(token);
 
-            final List<ResolveInfo> resolved =
-                    mContext.getPackageManager().queryIntentActivitiesAsUser(
-                            intent, PACKAGE_MATCH_FLAGS, userId);
+            logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start);
+        }
+    }
 
-            return (resolved != null) ? resolved : new ArrayList<>(0);
+    /**
+     * Return whether an activity is enabled and exported.
+     */
+    @VisibleForTesting
+    boolean injectIsActivityEnabledAndExported(
+            @NonNull ComponentName activity, @UserIdInt int userId) {
+        final long start = injectElapsedRealtime();
+        final long token = injectClearCallingIdentity();
+        try {
+            return queryActivities(new Intent(), activity.getPackageName(), activity, userId)
+                    .size() > 0;
         } finally {
             injectRestoreCallingIdentity(token);
 
-            logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start);
+            logDurationStat(Stats.IS_ACTIVITY_ENABLED, start);
         }
     }
 
@@ -2839,6 +2981,7 @@ public class ShortcutService extends IShortcutService.Stub {
                 dumpStatLS(pw, p, Stats.RESOURCE_NAME_LOOKUP, "resourceNameLookup");
                 dumpStatLS(pw, p, Stats.GET_LAUNCHER_ACTIVITY, "getLauncherActivity");
                 dumpStatLS(pw, p, Stats.CHECK_LAUNCHER_ACTIVITY, "checkLauncherActivity");
+                dumpStatLS(pw, p, Stats.IS_ACTIVITY_ENABLED, "isActivityEnabled");
             }
 
             for (int i = 0; i < mUsers.size(); i++) {
index 7017d81..b8ace28 100644 (file)
         <service android:name="com.android.server.job.MockPriorityJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE" />
 
-        <activity android:name="com.android.server.pm.ShortcutManagerTest$ShortcutActivity" />
-        <activity android:name="com.android.server.pm.ShortcutManagerTest$ShortcutActivity2" />
-        <activity android:name="com.android.server.pm.ShortcutManagerTest$ShortcutActivity3" />
+        <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity" />
+        <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity2" />
+        <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity3" />
+
+        <activity android:name="com.android.server.pm.ShortcutTestActivity"
+            android:enabled="true" android:exported="true" />
+
+        <activity-alias android:name="a.ShortcutEnabled"
+            android:targetActivity="com.android.server.pm.ShortcutTestActivity"
+            android:enabled="true" android:exported="true">
+        </activity-alias>
+        <activity-alias android:name="a.ShortcutDisabled"
+            android:targetActivity="com.android.server.pm.ShortcutTestActivity"
+            android:enabled="false" android:exported="true">
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_5"/>
+        </activity-alias>
+        <activity-alias android:name="a.ShortcutUnexported"
+            android:targetActivity="com.android.server.pm.ShortcutTestActivity"
+            android:enabled="true" android:exported="false">
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_5"/>
+        </activity-alias>
+        <activity-alias android:name="a.Shortcut1"
+            android:targetActivity="com.android.server.pm.ShortcutTestActivity"
+            android:enabled="true" android:exported="true">
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_1"/>
+        </activity-alias>
+
+        <activity-alias android:name="a.DisabledMain"
+            android:targetActivity="com.android.server.pm.ShortcutTestActivity"
+            android:enabled="false" android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+
+        <activity-alias android:name="a.UnexportedMain"
+            android:targetActivity="com.android.server.pm.ShortcutTestActivity"
+            android:enabled="true" android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+
     </application>
 
     <instrumentation
index b6084d5..cdb1944 100644 (file)
@@ -103,8 +103,6 @@ import java.util.Set;
 import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
 
 public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
     protected static final String TAG = "ShortcutManagerTest";
@@ -206,7 +204,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
         IUidObserver mUidObserver;
 
         public ShortcutServiceTestable(ServiceContext context, Looper looper) {
-            super(context, looper);
+            super(context, looper, /* onyForPackageManagerApis */ false);
             mContext = context;
         }
 
@@ -301,24 +299,26 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
         }
 
         @Override
-        PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
+        PackageInfo injectPackageInfoWithUninstalled(String packageName, @UserIdInt int userId,
                 boolean getSignatures) {
             return getInjectedPackageInfo(packageName, userId, getSignatures);
         }
 
         @Override
-        ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
-            PackageInfo pi = injectPackageInfo(packageName, userId, /* getSignatures= */ false);
+        ApplicationInfo injectApplicationInfoWithUninstalled(
+                String packageName, @UserIdInt int userId) {
+            PackageInfo pi = injectPackageInfoWithUninstalled(
+                    packageName, userId, /* getSignatures= */ false);
             return pi != null ? pi.applicationInfo : null;
         }
 
         @Override
-        List<PackageInfo> injectInstalledPackages(@UserIdInt int userId) {
-            return getInstalledPackages(userId);
+        List<PackageInfo> injectGetPackagesWithUninstalled(@UserIdInt int userId) {
+            return BaseShortcutManagerTest.this.getInstalledPackagesWithUninstalled(userId);
         }
 
         @Override
-        ActivityInfo injectGetActivityInfoWithMetadata(ComponentName activity,
+        ActivityInfo injectGetActivityInfoWithMetadataWithUninstalled(ComponentName activity,
                 @UserIdInt int userId) {
             final PackageInfo pi = mContext.injectGetActivitiesWithMetadata(
                     activity.getPackageName(), userId);
@@ -370,6 +370,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
         }
 
         @Override
+        boolean injectIsActivityEnabledAndExported(ComponentName activity, @UserIdInt int userId) {
+            return mEnabledActivityChecker.test(activity, userId);
+        }
+
+        @Override
         XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
             return mContext.injectXmlMetaData(activityInfo, key);
         }
@@ -951,7 +956,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
         }
     }
 
-    private List<PackageInfo> getInstalledPackages(int userId) {
+    private List<PackageInfo> getInstalledPackagesWithUninstalled(int userId) {
         final ArrayList<PackageInfo> ret = new ArrayList<>();
 
         addPackageInfo(getInjectedPackageInfo(CALLING_PACKAGE_1, userId, false), ret);
@@ -990,6 +995,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
                 ai.name = cn.getClassName();
                 ai.metaData = new Bundle();
                 ai.metaData.putInt(ShortcutParser.METADATA_KEY, activities.get(cn));
+                ai.applicationInfo = ret.applicationInfo;
                 list.add(ai);
             }
             ret.activities = list.toArray(new ActivityInfo[list.size()]);
@@ -1462,14 +1468,18 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
         return infoList.get(0);
     }
 
-    protected Intent genPackageAddIntent(String pakcageName, int userId) {
+    protected Intent genPackageAddIntent(String packageName, int userId) {
+        installPackage(userId, packageName);
+
         Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED);
-        i.setData(Uri.parse("package:" + pakcageName));
+        i.setData(Uri.parse("package:" + packageName));
         i.putExtra(Intent.EXTRA_USER_HANDLE, userId);
         return i;
     }
 
     protected Intent genPackageDeleteIntent(String pakcageName, int userId) {
+        uninstallPackage(userId, pakcageName);
+
         Intent i = new Intent(Intent.ACTION_PACKAGE_REMOVED);
         i.setData(Uri.parse("package:" + pakcageName));
         i.putExtra(Intent.EXTRA_USER_HANDLE, userId);
@@ -1477,6 +1487,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
     }
 
     protected Intent genPackageUpdateIntent(String pakcageName, int userId) {
+        installPackage(userId, pakcageName);
+
         Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED);
         i.setData(Uri.parse("package:" + pakcageName));
         i.putExtra(Intent.EXTRA_USER_HANDLE, userId);
index 8e26c10..e2e31e9 100644 (file)
@@ -55,7 +55,6 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
@@ -86,7 +85,6 @@ import com.android.frameworks.servicestests.R;
 import com.android.server.pm.ShortcutService.ConfigConstants;
 import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
 import com.android.server.pm.ShortcutUser.PackageWithUser;
-import com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.ShortcutListAsserter;
 
 import org.mockito.ArgumentCaptor;
 
@@ -94,9 +92,6 @@ import java.io.File;
 import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
-import java.util.function.BiFunction;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
 
 /**
  * Tests for ShortcutService and ShortcutManager.
index a36c0ad..54c4b22 100644 (file)
@@ -24,7 +24,9 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.PersistableBundle;
+import android.test.suitebuilder.annotation.SmallTest;
 
+@SmallTest
 public class ShortcutManagerTest4 extends BaseShortcutManagerTest {
 
     private static Bundle sIntentExtras = makeBundle(
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java
new file mode 100644 (file)
index 0000000..29c98dc
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2016 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 com.android.server.pm;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.ShortcutServiceInternal;
+import android.content.res.XmlResourceParser;
+import android.os.Looper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.LocalServices;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for all the IPackageManager related methods in {@link ShortcutService}.
+ *
+ * All the tests here actually talks to the real IPackageManager, so we can't test complicated
+ * cases.  Instead we just make sure they all work reasonably without at least crashing.
+ */
+@SmallTest
+public class ShortcutManagerTest5 extends BaseShortcutManagerTest {
+    private ShortcutService mShortcutService;
+
+    private String mMyPackage;
+    private int mMyUserId;
+
+    public static class ShortcutEnabled extends Activity {
+    }
+
+    public static class ShortcutDisabled extends Activity {
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+        mShortcutService = new ShortcutService(getTestContext(), Looper.getMainLooper(),
+                /* onyForPackageManagerApis */ true);
+
+        mMyPackage = getTestContext().getPackageName();
+        mMyUserId = android.os.Process.myUserHandle().getIdentifier();
+    }
+
+    public void testGetPackageUid() {
+        assertTrue(mShortcutService.injectGetPackageUid(
+                mMyPackage, mMyUserId) != 0);
+
+        assertEquals(-1, mShortcutService.injectGetPackageUid(
+                "no.such.package", mMyUserId));
+    }
+
+    public void testGetPackageInfo() {
+        PackageInfo pi = mShortcutService.getPackageInfo(
+                mMyPackage, mMyUserId, /*signature*/ false);
+        assertEquals(mMyPackage, pi.packageName);
+        assertNull(pi.signatures);
+
+        pi = mShortcutService.getPackageInfo(
+                mMyPackage, mMyUserId, /*signature*/ true);
+        assertEquals(mMyPackage, pi.packageName);
+        assertNotNull(pi.signatures);
+
+        pi = mShortcutService.getPackageInfo(
+                "no.such.package", mMyUserId, /*signature*/ true);
+        assertNull(pi);
+    }
+
+    public void testGetApplicationInfo() {
+        ApplicationInfo ai = mShortcutService.getApplicationInfo(
+                mMyPackage, mMyUserId);
+        assertEquals(mMyPackage, ai.packageName);
+
+        ai = mShortcutService.getApplicationInfo(
+                "no.such.package", mMyUserId);
+        assertNull(ai);
+    }
+
+    public void testGetActivityInfoWithMetadata() {
+        // Disabled activity
+        ActivityInfo ai = mShortcutService.getActivityInfoWithMetadata(
+                new ComponentName(mMyPackage, "ShortcutDisabled"), mMyUserId);
+        assertNull(ai);
+
+        // Nonexistent
+        ai = mShortcutService.getActivityInfoWithMetadata(
+                new ComponentName("no.such.package", "ShortcutDisabled"), mMyUserId);
+        assertNull(ai);
+
+        // Existent, with no metadata.
+        ai = mShortcutService.getActivityInfoWithMetadata(
+                new ComponentName(mMyPackage, "a.ShortcutEnabled"), mMyUserId);
+        assertEquals(mMyPackage, ai.packageName);
+        assertEquals("a.ShortcutEnabled", ai.name);
+        assertNull(ai.loadXmlMetaData(getTestContext().getPackageManager(),
+                "android.app.shortcuts"));
+
+        // Existent, with a shortcut metadata.
+        ai = mShortcutService.getActivityInfoWithMetadata(
+                new ComponentName(mMyPackage, "a.Shortcut1"), mMyUserId);
+        assertEquals(mMyPackage, ai.packageName);
+        assertEquals("a.Shortcut1", ai.name);
+        XmlResourceParser meta = ai.loadXmlMetaData(getTestContext().getPackageManager(),
+                "android.app.shortcuts");
+        assertNotNull(meta);
+        meta.close();
+    }
+
+    public void testGetInstalledPackages() {
+        List<PackageInfo> apks = mShortcutService.getInstalledPackages(mMyUserId);
+
+        Set<String> expectedPackages = set("com.android.settings", mMyPackage);
+        for (PackageInfo pi : apks) {
+            expectedPackages.remove(pi.packageName);
+        }
+        assertEquals(set(), expectedPackages);
+    }
+
+    public void testGetDefaultMainActivity() {
+        ComponentName cn = mShortcutService.injectGetDefaultMainActivity(
+                "com.android.settings", mMyUserId);
+
+        assertEquals(
+                ComponentName.unflattenFromString("com.android.settings/.Settings"),
+                cn);
+
+        // This package has no main activity.
+        assertNull(mShortcutService.injectGetDefaultMainActivity(
+                mMyPackage, mMyUserId));
+
+        // Nonexistent.
+        assertNull(mShortcutService.injectGetDefaultMainActivity(
+                "no.such.package", mMyUserId));
+    }
+
+    public void testIsMainActivity() {
+        assertTrue(mShortcutService.injectIsMainActivity(
+                ComponentName.unflattenFromString("com.android.settings/.Settings"), mMyUserId));
+        assertFalse(mShortcutService.injectIsMainActivity(
+                ComponentName.unflattenFromString("com.android.settings/.xxx"), mMyUserId));
+        assertFalse(mShortcutService.injectIsMainActivity(
+                ComponentName.unflattenFromString("no.such.package/.xxx"), mMyUserId));
+
+        assertFalse(mShortcutService.injectIsMainActivity(
+                new ComponentName(mMyPackage, "a.DisabledMain"), mMyUserId));
+        assertFalse(mShortcutService.injectIsMainActivity(
+                new ComponentName(mMyPackage, "a.UnexportedMain"), mMyUserId));
+
+    }
+
+    public void testGetMainActivities() {
+        assertEquals(1, mShortcutService.injectGetMainActivities(
+                "com.android.settings", mMyUserId).size());
+
+        // This APK has no main activities.
+        assertEquals(0, mShortcutService.injectGetMainActivities(
+                mMyPackage, mMyUserId).size());
+    }
+
+    public void testIsActivityEnabledAndExported() {
+        assertTrue(mShortcutService.injectIsActivityEnabledAndExported(
+                ComponentName.unflattenFromString("com.android.settings/.Settings"), mMyUserId));
+        assertFalse(mShortcutService.injectIsActivityEnabledAndExported(
+                ComponentName.unflattenFromString("com.android.settings/.xxx"), mMyUserId));
+        assertFalse(mShortcutService.injectIsActivityEnabledAndExported(
+                ComponentName.unflattenFromString("no.such.package/.xxx"), mMyUserId));
+
+        assertTrue(mShortcutService.injectIsActivityEnabledAndExported(
+                new ComponentName(mMyPackage, "com.android.server.pm.ShortcutTestActivity"),
+                mMyUserId));
+
+        assertTrue(mShortcutService.injectIsActivityEnabledAndExported(
+                new ComponentName(mMyPackage, "a.ShortcutEnabled"), mMyUserId));
+
+        assertFalse(mShortcutService.injectIsActivityEnabledAndExported(
+                new ComponentName(mMyPackage, "a.ShortcutDisabled"), mMyUserId));
+        assertFalse(mShortcutService.injectIsActivityEnabledAndExported(
+                new ComponentName(mMyPackage, "a.ShortcutUnexported"), mMyUserId));
+
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutTestActivity.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutTestActivity.java
new file mode 100644 (file)
index 0000000..d82b0d5
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 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 com.android.server.pm;
+
+import android.app.Activity;
+
+public class ShortcutTestActivity extends Activity {
+}