From fea1b776e96d10879769d3a3c17f297146605062 Mon Sep 17 00:00:00 2001 From: Todd Kennedy Date: Tue, 28 Jun 2016 14:09:55 -0700 Subject: [PATCH] Implement new ephemeral resolver design 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 Bug: 30805203 Bug: 30273584 Change-Id: I6644bbb4f180d2d22c63af04b9857577516344a9 (cherry picked from commit 8e2d9d1d9050e93b15c54e992698325c7d4aa57c) --- core/java/android/content/Intent.java | 8 + core/java/android/content/pm/ResolveInfo.java | 6 - .../android/server/am/ActivityStarter.java | 90 +++++++---- .../server/pm/PackageManagerService.java | 140 +++++++++++------- 4 files changed, 156 insertions(+), 88 deletions(-) diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 4d9db98587ce3..1cf23ae8d040f 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4313,6 +4313,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 diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java index c9be6edab424c..b5df4d75a2384 100644 --- a/core/java/android/content/pm/ResolveInfo.java +++ b/core/java/android/content/pm/ResolveInfo.java @@ -67,12 +67,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. */ diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 5252446990825..6c612268331e1 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -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) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2ed64f18349c0..41b2a08b35c78 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -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; @@ -4723,20 +4724,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); @@ -4777,9 +4764,9 @@ public class PackageManagerService extends IPackageManager.Stub { false, false, false, userId); } - private boolean isEphemeralAllowed( - Intent intent, List resolvedActivites, int userId) { + Intent intent, List resolvedActivities, int userId, + boolean skipPackageCheck) { // Short circuit and return early if possible. if (DISABLE_EPHEMERAL_APPS) { return false; @@ -4794,18 +4781,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) { @@ -4827,19 +4817,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 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; @@ -4856,6 +4846,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) { @@ -5259,8 +5253,12 @@ public class PackageManagerService extends IPackageManager.Stub { } // reader + boolean sortResult = false; + boolean addEphemeral = false; + boolean matchEphemeralPackage = false; + List result; + final String pkgName = intent.getPackage(); synchronized (mPackages) { - final String pkgName = intent.getPackage(); if (pkgName == null) { List matchingFilters = getMatchingCrossProfileIntentFilters(intent, resolvedType, userId); @@ -5268,15 +5266,16 @@ public class PackageManagerService extends IPackageManager.Stub { ResolveInfo xpResolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent, resolvedType, flags, userId); if (xpResolveInfo != null) { - List result = new ArrayList(1); - result.add(xpResolveInfo); - return filterIfNotSystemUser(result, userId); + List xpResult = new ArrayList(1); + xpResult.add(xpResolveInfo); + return filterIfNotSystemUser(xpResult, userId); } // Check for results in the current profile. - List 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); @@ -5288,7 +5287,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)) { @@ -5304,28 +5303,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(); } - return result; } - final PackageParser.Package pkg = mPackages.get(pkgName); - if (pkg != null) { - return filterIfNotSystemUser( - mActivities.queryIntentForPackage( - intent, resolvedType, flags, pkg.activities, userId), - userId); - } - return new ArrayList(); } + 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); + } + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + if (sortResult) { + Collections.sort(result, mResolvePrioritySorter); + } + return result; } private static class CrossProfileDomainInfo { @@ -9190,15 +9222,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);