OSDN Git Service

Implement new ephemeral resolver design
authorTodd Kennedy <toddke@google.com>
Tue, 28 Jun 2016 21:09:55 +0000 (14:09 -0700)
committerTodd Kennedy <toddke@google.com>
Thu, 21 Jul 2016 21:55:23 +0000 (14:55 -0700)
In the new design, the ephemeral installer can be returned from
queryIntentActivities which means any intent resolution could potentially
return the installer. Additionally, the new design calls for a platform
defined broadcast receiver that receives the status from the ephemeral
installer. This receiver then starts the final intent -- either to launch
the ephemeral application or to launch the fallback.

For more detail, see go/ephemeral-design

Change-Id: I6644bbb4f180d2d22c63af04b9857577516344a9

core/java/android/content/Intent.java
core/java/android/content/pm/ResolveInfo.java
services/core/java/com/android/server/am/ActivityStarter.java
services/core/java/com/android/server/pm/PackageManagerService.java

index 43a8a82..d537739 100644 (file)
@@ -4314,6 +4314,14 @@ public class Intent implements Parcelable, Cloneable {
     public static final int FLAG_DEBUG_TRIAGED_MISSING = 0x00000100;
 
     /**
+     * Internal flag used to indicate ephemeral applications should not be
+     * considered when resolving the intent.
+     *
+     * @hide
+     */
+    public static final int FLAG_IGNORE_EPHEMERAL = 0x00000200;
+
+    /**
      * If set, the new activity is not kept in the history stack.  As soon as
      * the user navigates away from it, the activity is finished.  This may also
      * be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory
index c9be6ed..b5df4d7 100644 (file)
@@ -68,12 +68,6 @@ public class ResolveInfo implements Parcelable {
     public EphemeralResolveInfo ephemeralResolveInfo;
 
     /**
-     * A ResolveInfo that points at the ephemeral installer.
-     * @hide
-     */
-    public ResolveInfo ephemeralInstaller;
-
-    /**
      * The IntentFilter that was matched for this ResolveInfo.
      */
     public IntentFilter filter;
index 399ca9e..2ee6de2 100644 (file)
@@ -103,6 +103,7 @@ import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PowerManagerInternal;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -132,6 +133,9 @@ class ActivityStarter {
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
     private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
 
+    // TODO b/30204367 remove when the platform fully supports ephemeral applications
+    private static final boolean USE_DEFAULT_EPHEMERAL_LAUNCHER = false;
+
     private final ActivityManagerService mService;
     private final ActivityStackSupervisor mSupervisor;
     private ActivityStartInterceptor mInterceptor;
@@ -456,39 +460,13 @@ class ActivityStarter {
         // starts either the intent we resolved here [on install error] or the ephemeral
         // app [on install success].
         if (rInfo != null && rInfo.ephemeralResolveInfo != null) {
-            // Create a pending intent to start the intent resolved here.
-            final IIntentSender failureTarget = mService.getIntentSenderLocked(
-                    ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
-                    Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent },
-                    new String[]{ resolvedType },
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
-                            | PendingIntent.FLAG_IMMUTABLE, null);
-
-            // Create a pending intent to start the ephemeral application; force it to be
-            // directed to the ephemeral package.
-            ephemeralIntent.setPackage(rInfo.ephemeralResolveInfo.getPackageName());
-            final IIntentSender ephemeralTarget = mService.getIntentSenderLocked(
-                    ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
-                    Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ ephemeralIntent },
-                    new String[]{ resolvedType },
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
-                            | PendingIntent.FLAG_IMMUTABLE, null);
-
-            int flags = intent.getFlags();
-            intent = new Intent();
-            intent.setFlags(flags
-                    | Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            intent.putExtra(Intent.EXTRA_PACKAGE_NAME,
-                    rInfo.ephemeralResolveInfo.getPackageName());
-            intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, new IntentSender(failureTarget));
-            intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(ephemeralTarget));
-
+            intent = buildEphemeralInstallerIntent(intent, ephemeralIntent,
+                    rInfo.ephemeralResolveInfo.getPackageName(), callingPackage, resolvedType,
+                    userId);
             resolvedType = null;
             callingUid = realCallingUid;
             callingPid = realCallingPid;
 
-            rInfo = rInfo.ephemeralInstaller;
             aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/);
         }
 
@@ -543,6 +521,60 @@ class ActivityStarter {
         return err;
     }
 
+    /**
+     * Builds and returns an intent to launch the ephemeral installer.
+     */
+    private Intent buildEphemeralInstallerIntent(Intent launchIntent, Intent origIntent,
+            String ephemeralPackage, String callingPackage, String resolvedType, int userId) {
+        final Intent nonEphemeralIntent = new Intent(origIntent);
+        nonEphemeralIntent.setFlags(nonEphemeralIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL);
+        // Intent that is launched if the ephemeral package couldn't be installed
+        // for any reason.
+        final IIntentSender failureIntentTarget = mService.getIntentSenderLocked(
+                ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
+                Binder.getCallingUid(), userId, null /*token*/, null /*resultWho*/, 1,
+                new Intent[]{ nonEphemeralIntent }, new String[]{ resolvedType },
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+                | PendingIntent.FLAG_IMMUTABLE, null /*bOptions*/);
+
+        final Intent ephemeralIntent;
+        if (USE_DEFAULT_EPHEMERAL_LAUNCHER) {
+            // Force the intent to be directed to the ephemeral package
+            ephemeralIntent = new Intent(origIntent);
+            ephemeralIntent.setPackage(ephemeralPackage);
+        } else {
+            // Success intent goes back to the installer
+            // TODO; do we need any extras for the installer?
+            ephemeralIntent = new Intent(launchIntent);
+            ephemeralIntent.setData(null);
+        }
+
+        // Intent that is eventually launched if the ephemeral package was
+        // installed successfully. This will actually be launched by a platform
+        // broadcast receiver.
+        final IIntentSender successIntentTarget = mService.getIntentSenderLocked(
+                ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
+                Binder.getCallingUid(), userId, null /*token*/, null /*resultWho*/, 0,
+                new Intent[]{ ephemeralIntent }, new String[]{ resolvedType },
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+                | PendingIntent.FLAG_IMMUTABLE, null /*bOptions*/);
+
+        // Finally build the actual intent to launch the ephemeral installer
+        int flags = launchIntent.getFlags();
+        final Intent intent = new Intent();
+        intent.setFlags(flags
+                | Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_CLEAR_TASK
+                | Intent.FLAG_ACTIVITY_NO_HISTORY
+                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, ephemeralPackage);
+        intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, new IntentSender(failureIntentTarget));
+        intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(successIntentTarget));
+        // TODO: Remove when the platform has fully implemented ephemeral apps
+        intent.setData(origIntent.getData());
+        return intent;
+    }
+
     void postStartActivityUncheckedProcessing(
             ActivityRecord r, int result, int prevFocusedStackId, ActivityRecord sourceRecord,
             ActivityStack targetStack) {
index de0515a..3d1d76e 100644 (file)
@@ -179,6 +179,7 @@ import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
+import android.os.PatternMatcher;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -4727,20 +4728,6 @@ public class PackageManagerService extends IPackageManager.Stub {
 
             final ResolveInfo bestChoice =
                     chooseBestActivity(intent, resolvedType, flags, query, userId);
-
-            if (isEphemeralAllowed(intent, query, userId)) {
-                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral");
-                final EphemeralResolveInfo ai =
-                        getEphemeralResolveInfo(intent, resolvedType, userId);
-                if (ai != null) {
-                    if (DEBUG_EPHEMERAL) {
-                        Slog.v(TAG, "Returning an EphemeralResolveInfo");
-                    }
-                    bestChoice.ephemeralInstaller = mEphemeralInstallerInfo;
-                    bestChoice.ephemeralResolveInfo = ai;
-                }
-                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-            }
             return bestChoice;
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -4781,9 +4768,9 @@ public class PackageManagerService extends IPackageManager.Stub {
                 false, false, false, userId);
     }
 
-
     private boolean isEphemeralAllowed(
-            Intent intent, List<ResolveInfo> resolvedActivites, int userId) {
+            Intent intent, List<ResolveInfo> resolvedActivities, int userId,
+            boolean skipPackageCheck) {
         // Short circuit and return early if possible.
         if (DISABLE_EPHEMERAL_APPS) {
             return false;
@@ -4798,18 +4785,21 @@ public class PackageManagerService extends IPackageManager.Stub {
         if (intent.getComponent() != null) {
             return false;
         }
-        if (intent.getPackage() != null) {
+        if ((intent.getFlags() & Intent.FLAG_IGNORE_EPHEMERAL) != 0) {
+            return false;
+        }
+        if (!skipPackageCheck && intent.getPackage() != null) {
             return false;
         }
         final boolean isWebUri = hasWebURI(intent);
-        if (!isWebUri) {
+        if (!isWebUri || intent.getData().getHost() == null) {
             return false;
         }
         // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution.
         synchronized (mPackages) {
-            final int count = resolvedActivites.size();
+            final int count = (resolvedActivities == null ? 0 : resolvedActivities.size());
             for (int n = 0; n < count; n++) {
-                ResolveInfo info = resolvedActivites.get(n);
+                ResolveInfo info = resolvedActivities.get(n);
                 String packageName = info.activityInfo.packageName;
                 PackageSetting ps = mSettings.mPackages.get(packageName);
                 if (ps != null) {
@@ -4831,19 +4821,19 @@ public class PackageManagerService extends IPackageManager.Stub {
         return true;
     }
 
-    private EphemeralResolveInfo getEphemeralResolveInfo(Intent intent, String resolvedType,
-            int userId) {
-        final int ephemeralPrefixMask = Global.getInt(mContext.getContentResolver(),
+    private static EphemeralResolveInfo getEphemeralResolveInfo(
+            Context context, EphemeralResolverConnection resolverConnection, Intent intent,
+            String resolvedType, int userId, String packageName) {
+        final int ephemeralPrefixMask = Global.getInt(context.getContentResolver(),
                 Global.EPHEMERAL_HASH_PREFIX_MASK, DEFAULT_EPHEMERAL_HASH_PREFIX_MASK);
-        final int ephemeralPrefixCount = Global.getInt(mContext.getContentResolver(),
+        final int ephemeralPrefixCount = Global.getInt(context.getContentResolver(),
                 Global.EPHEMERAL_HASH_PREFIX_COUNT, DEFAULT_EPHEMERAL_HASH_PREFIX_COUNT);
         final EphemeralDigest digest = new EphemeralDigest(intent.getData(), ephemeralPrefixMask,
                 ephemeralPrefixCount);
         final int[] shaPrefix = digest.getDigestPrefix();
         final byte[][] digestBytes = digest.getDigestBytes();
         final List<EphemeralResolveInfo> ephemeralResolveInfoList =
-                mEphemeralResolverConnection.getEphemeralResolveInfoList(
-                        shaPrefix, ephemeralPrefixMask);
+                resolverConnection.getEphemeralResolveInfoList(shaPrefix, ephemeralPrefixMask);
         if (ephemeralResolveInfoList == null || ephemeralResolveInfoList.size() == 0) {
             // No hash prefix match; there are no ephemeral apps for this domain.
             return null;
@@ -4860,6 +4850,10 @@ public class PackageManagerService extends IPackageManager.Stub {
                 if (filters.isEmpty()) {
                     continue;
                 }
+                if (packageName != null
+                        && !packageName.equals(ephemeralApplication.getPackageName())) {
+                    continue;
+                }
                 // We have a domain match; resolve the filters to see if anything matches.
                 final EphemeralIntentResolver ephemeralResolver = new EphemeralIntentResolver();
                 for (int j = filters.size() - 1; j >= 0; --j) {
@@ -5263,8 +5257,12 @@ public class PackageManagerService extends IPackageManager.Stub {
         }
 
         // reader
+        boolean sortResult = false;
+        boolean addEphemeral = false;
+        boolean matchEphemeralPackage = false;
+        List<ResolveInfo> result;
+        final String pkgName = intent.getPackage();
         synchronized (mPackages) {
-            final String pkgName = intent.getPackage();
             if (pkgName == null) {
                 List<CrossProfileIntentFilter> matchingFilters =
                         getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
@@ -5272,15 +5270,16 @@ public class PackageManagerService extends IPackageManager.Stub {
                 ResolveInfo xpResolveInfo  = querySkipCurrentProfileIntents(matchingFilters, intent,
                         resolvedType, flags, userId);
                 if (xpResolveInfo != null) {
-                    List<ResolveInfo> result = new ArrayList<ResolveInfo>(1);
-                    result.add(xpResolveInfo);
-                    return filterIfNotSystemUser(result, userId);
+                    List<ResolveInfo> xpResult = new ArrayList<ResolveInfo>(1);
+                    xpResult.add(xpResolveInfo);
+                    return filterIfNotSystemUser(xpResult, userId);
                 }
 
                 // Check for results in the current profile.
-                List<ResolveInfo> result = mActivities.queryIntent(
-                        intent, resolvedType, flags, userId);
-                result = filterIfNotSystemUser(result, userId);
+                result = filterIfNotSystemUser(mActivities.queryIntent(
+                        intent, resolvedType, flags, userId), userId);
+                addEphemeral =
+                        isEphemeralAllowed(intent, result, userId, false /*skipPackageCheck*/);
 
                 // Check for cross profile results.
                 boolean hasNonNegativePriorityResult = hasNonNegativePriority(result);
@@ -5292,7 +5291,7 @@ public class PackageManagerService extends IPackageManager.Stub {
                             Collections.singletonList(xpResolveInfo), userId).size() > 0;
                     if (isVisibleToUser) {
                         result.add(xpResolveInfo);
-                        Collections.sort(result, mResolvePrioritySorter);
+                        sortResult = true;
                     }
                 }
                 if (hasWebURI(intent)) {
@@ -5308,28 +5307,61 @@ public class PackageManagerService extends IPackageManager.Stub {
                             // in the result.
                             result.remove(xpResolveInfo);
                         }
-                        if (result.size() == 0) {
+                        if (result.size() == 0 && !addEphemeral) {
                             result.add(xpDomainInfo.resolveInfo);
                             return result;
                         }
-                    } else if (result.size() <= 1) {
-                        return result;
                     }
-                    result = filterCandidatesWithDomainPreferredActivitiesLPr(intent, flags, result,
-                            xpDomainInfo, userId);
-                    Collections.sort(result, mResolvePrioritySorter);
+                    if (result.size() > 1 || addEphemeral) {
+                        result = filterCandidatesWithDomainPreferredActivitiesLPr(
+                                intent, flags, result, xpDomainInfo, userId);
+                        sortResult = true;
+                    }
+                }
+            } else {
+                final PackageParser.Package pkg = mPackages.get(pkgName);
+                if (pkg != null) {
+                    result = filterIfNotSystemUser(
+                            mActivities.queryIntentForPackage(
+                                    intent, resolvedType, flags, pkg.activities, userId),
+                            userId);
+                } else {
+                    // the caller wants to resolve for a particular package; however, there
+                    // were no installed results, so, try to find an ephemeral result
+                    addEphemeral = isEphemeralAllowed(
+                            intent, null /*result*/, userId, true /*skipPackageCheck*/);
+                    matchEphemeralPackage = true;
+                    result = new ArrayList<ResolveInfo>();
                 }
-                return result;
             }
-            final PackageParser.Package pkg = mPackages.get(pkgName);
-            if (pkg != null) {
-                return filterIfNotSystemUser(
-                        mActivities.queryIntentForPackage(
-                                intent, resolvedType, flags, pkg.activities, userId),
-                        userId);
+        }
+        if (addEphemeral) {
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral");
+            final EphemeralResolveInfo ai = getEphemeralResolveInfo(
+                    mContext, mEphemeralResolverConnection, intent, resolvedType, userId,
+                    matchEphemeralPackage ? pkgName : null);
+            if (ai != null) {
+                if (DEBUG_EPHEMERAL) {
+                    Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
+                }
+                final ResolveInfo ephemeralInstaller = new ResolveInfo(mEphemeralInstallerInfo);
+                ephemeralInstaller.ephemeralResolveInfo = ai;
+                // make sure this resolver is the default
+                ephemeralInstaller.isDefault = true;
+                ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
+                        | IntentFilter.MATCH_ADJUSTMENT_NORMAL;
+                // add a non-generic filter
+                ephemeralInstaller.filter = new IntentFilter(intent.getAction());
+                ephemeralInstaller.filter.addDataPath(
+                        intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL);
+                result.add(ephemeralInstaller);
             }
-            return new ArrayList<ResolveInfo>();
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+        if (sortResult) {
+            Collections.sort(result, mResolvePrioritySorter);
         }
+        return result;
     }
 
     private static class CrossProfileDomainInfo {
@@ -9194,15 +9226,17 @@ public class PackageManagerService extends IPackageManager.Stub {
         mEphemeralInstallerActivity.packageName = pkg.applicationInfo.packageName;
         mEphemeralInstallerActivity.processName = pkg.applicationInfo.packageName;
         mEphemeralInstallerActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
-        mEphemeralInstallerActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS |
-                ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+        mEphemeralInstallerActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
+                ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
         mEphemeralInstallerActivity.theme = 0;
         mEphemeralInstallerActivity.exported = true;
         mEphemeralInstallerActivity.enabled = true;
         mEphemeralInstallerInfo.activityInfo = mEphemeralInstallerActivity;
         mEphemeralInstallerInfo.priority = 0;
-        mEphemeralInstallerInfo.preferredOrder = 0;
-        mEphemeralInstallerInfo.match = 0;
+        mEphemeralInstallerInfo.preferredOrder = 1;
+        mEphemeralInstallerInfo.isDefault = true;
+        mEphemeralInstallerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
+                | IntentFilter.MATCH_ADJUSTMENT_NORMAL;
 
         if (DEBUG_EPHEMERAL) {
             Slog.d(TAG, "Set ephemeral installer activity: " + mEphemeralInstallerComponent);