diff options
10 files changed, 459 insertions, 199 deletions
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index a9e987a45b2b..c87de9ade034 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3835,6 +3835,18 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_EPHEMERAL_FAILURE = "android.intent.extra.EPHEMERAL_FAILURE"; /** + * The host name that triggered an ephemeral resolution. + * @hide + */ + public static final String EXTRA_EPHEMERAL_HOSTNAME = "android.intent.extra.EPHEMERAL_HOSTNAME"; + + /** + * An opaque token to track ephemeral resolution. + * @hide + */ + public static final String EXTRA_EPHEMERAL_TOKEN = "android.intent.extra.EPHEMERAL_TOKEN"; + + /** * A Bundle forming a mapping of potential target package names to different extras Bundles * to add to the default intent extras in {@link #EXTRA_INTENT} when used with * {@link #ACTION_CHOOSER}. Each key should be a package name. The package need not diff --git a/core/java/android/content/pm/EphemeralRequest.java b/core/java/android/content/pm/EphemeralRequest.java new file mode 100644 index 000000000000..7f2b3ee1245b --- /dev/null +++ b/core/java/android/content/pm/EphemeralRequest.java @@ -0,0 +1,48 @@ +/* + * 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 android.content.pm; + +import android.content.Intent; + +/** + * Information needed to make an ephemeral application resolution request. + * @hide + */ +public final class EphemeralRequest { + /** Response from the first phase of ephemeral application resolution */ + public final EphemeralResponse responseObj; + /** The original intent that triggered ephemeral application resolution */ + public final Intent origIntent; + /** Resolved type of the intent */ + public final String resolvedType; + /** The intent that would launch if there were no ephemeral applications */ + public final Intent launchIntent; + /** The name of the package requesting the ephemeral application */ + public final String callingPackage; + /** ID of the user requesting the ephemeral application */ + public final int userId; + + public EphemeralRequest(EphemeralResponse responseObj, Intent origIntent, + String resolvedType, Intent launchIntent, String callingPackage, int userId) { + this.responseObj = responseObj; + this.origIntent = origIntent; + this.resolvedType = resolvedType; + this.launchIntent = launchIntent; + this.callingPackage = callingPackage; + this.userId = userId; + } +}
\ No newline at end of file diff --git a/core/java/android/content/pm/EphemeralResolveInfo.java b/core/java/android/content/pm/EphemeralResolveInfo.java index 3bed06b4816f..f6200886cd71 100644 --- a/core/java/android/content/pm/EphemeralResolveInfo.java +++ b/core/java/android/content/pm/EphemeralResolveInfo.java @@ -137,28 +137,6 @@ public final class EphemeralResolveInfo implements Parcelable { } }; - /** @hide */ - public static final class EphemeralResolveIntentInfo extends IntentFilter { - private final EphemeralResolveInfo mResolveInfo; - private final String mSplitName; - - public EphemeralResolveIntentInfo(@NonNull IntentFilter orig, - @NonNull EphemeralResolveInfo resolveInfo, - @Nullable String splitName) { - super(orig); - mResolveInfo = resolveInfo; - mSplitName = splitName; - } - - public EphemeralResolveInfo getEphemeralResolveInfo() { - return mResolveInfo; - } - - public String getSplitName() { - return mSplitName; - } - } - /** * Helper class to generate and store each of the digests and prefixes * sent to the Ephemeral Resolver. diff --git a/core/java/android/content/pm/EphemeralResponse.java b/core/java/android/content/pm/EphemeralResponse.java new file mode 100644 index 000000000000..6e569f7d4667 --- /dev/null +++ b/core/java/android/content/pm/EphemeralResponse.java @@ -0,0 +1,51 @@ +/* + * 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 android.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.IntentFilter; + +/** + * Ephemeral application resolution response. + * @hide + */ +public final class EphemeralResponse extends IntentFilter { + /** Resolved information returned from the external ephemeral resolver */ + public final EphemeralResolveInfo resolveInfo; + /** The resolved package. Copied from {@link #resolveInfo}. */ + public final String packageName; + /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */ + public final String splitName; + /** Whether or not ephemeral resolution needs the second phase */ + public final boolean needsPhase2; + /** Opaque token to track the ephemeral application resolution */ + public final String token; + + public EphemeralResponse(@NonNull EphemeralResolveInfo resolveInfo, + @NonNull IntentFilter orig, + @Nullable String splitName, + @NonNull String token, + boolean needsPhase2) { + super(orig); + this.resolveInfo = resolveInfo; + this.packageName = resolveInfo.getPackageName(); + this.splitName = splitName; + this.token = token; + this.needsPhase2 = needsPhase2; + } +}
\ No newline at end of file diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index 2aa7ac6ff5b6..ad0a6b25c4f1 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -17,6 +17,7 @@ package android.content.pm; import android.content.ComponentName; +import android.content.Intent; import android.content.pm.PackageManager.NameNotFoundException; import android.util.SparseArray; @@ -208,4 +209,16 @@ public abstract class PackageManagerInternal { */ public abstract String getNameForUid(int uid); + /** + * Request to perform the second phase of ephemeral resolution. + * @param responseObj The response of the first phase of ephemeral resolution + * @param origIntent The original intent that triggered ephemeral resolution + * @param resolvedType The resolved type of the intent + * @param launchIntent The intent that would launch if there was no ephemeral application + * @param callingPackage The name of the package requesting the ephemeral application + * @param userId The ID of the user that triggered ephemeral resolution + */ + public abstract void requestEphemeralResolutionPhaseTwo(EphemeralResponse responseObj, + Intent origIntent, String resolvedType, Intent launchIntent, String callingPackage, + int userId); } diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java index 86dbe8a64ed8..f8b4570be6cc 100644 --- a/core/java/android/content/pm/ResolveInfo.java +++ b/core/java/android/content/pm/ResolveInfo.java @@ -18,7 +18,6 @@ package android.content.pm; import android.content.ComponentName; import android.content.IntentFilter; -import android.content.pm.EphemeralResolveInfo.EphemeralResolveIntentInfo; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; @@ -66,7 +65,7 @@ public class ResolveInfo implements Parcelable { * only be set in specific circumstances. * @hide */ - public EphemeralResolveIntentInfo ephemeralIntentInfo; + public EphemeralResponse ephemeralResponse; /** * 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 709c3d0dba5e..ccd94cb475a3 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -118,6 +118,7 @@ import android.view.Display; import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.app.IVoiceInteractor; import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch; +import com.android.server.pm.EphemeralResolver; import com.android.server.wm.WindowManagerService; import java.util.ArrayList; @@ -135,9 +136,6 @@ 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; @@ -457,13 +455,21 @@ class ActivityStarter { // Instead, launch the ephemeral installer. Once the installer is finished, it // starts either the intent we resolved here [on install error] or the ephemeral // app [on install success]. - if (rInfo != null && rInfo.ephemeralIntentInfo != null) { + if (rInfo != null && rInfo.ephemeralResponse != null) { final String packageName = - rInfo.ephemeralIntentInfo.getEphemeralResolveInfo().getPackageName(); - final String splitName = rInfo.ephemeralIntentInfo.getSplitName(); - intent = buildEphemeralInstallerIntent(intent, ephemeralIntent, - packageName, splitName, callingPackage, resolvedType, - userId); + rInfo.ephemeralResponse.resolveInfo.getPackageName(); + final String splitName = rInfo.ephemeralResponse.splitName; + final boolean needsPhaseTwo = rInfo.ephemeralResponse.needsPhase2; + final String token = rInfo.ephemeralResponse.token; + if (needsPhaseTwo) { + // request phase two resolution + mService.getPackageManagerInternalLocked().requestEphemeralResolutionPhaseTwo( + rInfo.ephemeralResponse, ephemeralIntent, resolvedType, intent, + callingPackage, userId); + } + intent = EphemeralResolver.buildEphemeralInstallerIntent(intent, ephemeralIntent, + callingPackage, resolvedType, userId, packageName, splitName, token, + needsPhaseTwo); resolvedType = null; callingUid = realCallingUid; callingPid = realCallingPid; @@ -522,60 +528,6 @@ class ActivityStarter { return err; } - /** - * Builds and returns an intent to launch the ephemeral installer. - */ - private Intent buildEphemeralInstallerIntent(Intent launchIntent, Intent origIntent, - String ephemeralPackageName, String ephemeralSplitName, 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(ephemeralPackageName); - } else { - // Success intent goes back to the installer - ephemeralIntent = new Intent(launchIntent); - } - - // 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, ephemeralPackageName); - intent.putExtra(Intent.EXTRA_SPLIT_NAME, ephemeralSplitName); - 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().buildUpon().clearQuery().build()); - return intent; - } - void postStartActivityUncheckedProcessing( ActivityRecord r, int result, int prevFocusedStackId, ActivityRecord sourceRecord, ActivityStack targetStack) { diff --git a/services/core/java/com/android/server/pm/EphemeralResolver.java b/services/core/java/com/android/server/pm/EphemeralResolver.java new file mode 100644 index 000000000000..82f796daceb0 --- /dev/null +++ b/services/core/java/com/android/server/pm/EphemeralResolver.java @@ -0,0 +1,249 @@ +/* + * 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.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.IIntentSender; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.pm.ActivityInfo; +import android.content.pm.EphemeralIntentFilter; +import android.content.pm.EphemeralRequest; +import android.content.pm.EphemeralResolveInfo; +import android.content.pm.EphemeralResponse; +import android.content.pm.EphemeralResolveInfo.EphemeralDigest; +import android.os.Binder; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; + +import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +/** @hide */ +public abstract class EphemeralResolver { + + /** TODO b/30204367 remove when the platform fully supports ephemeral applications */ + public static final boolean USE_DEFAULT_EPHEMERAL_LAUNCHER = false; + + public static EphemeralResponse doEphemeralResolutionPhaseOne(Context context, + EphemeralResolverConnection connection, EphemeralRequest requestObj) { + final Intent intent = requestObj.origIntent; + final EphemeralDigest digest = + new EphemeralDigest(intent.getData().getHost(), 5 /*maxDigests*/); + final int[] shaPrefix = digest.getDigestPrefix(); + final List<EphemeralResolveInfo> ephemeralResolveInfoList = + connection.getEphemeralResolveInfoList(shaPrefix); + if (ephemeralResolveInfoList == null || ephemeralResolveInfoList.size() == 0) { + // No hash prefix match; there are no ephemeral apps for this domain. + return null; + } + + final String token = UUID.randomUUID().toString(); + return EphemeralResolver.filterEphemeralIntent(ephemeralResolveInfoList, + intent, requestObj.resolvedType, requestObj.userId, + intent.getPackage(), digest, token); + } + + public static void doEphemeralResolutionPhaseTwo(Context context, + EphemeralResolverConnection connection, EphemeralRequest requestObj, + ActivityInfo ephemeralInstaller, Handler callbackHandler) { + final Intent intent = requestObj.origIntent; + final EphemeralDigest digest = + new EphemeralDigest(intent.getData().getHost(), 5 /*maxDigests*/); + final int[] shaPrefix = digest.getDigestPrefix(); + final byte[][] digestBytes = digest.getDigestBytes(); + + final PhaseTwoCallback callback = new PhaseTwoCallback() { + @Override + void onPhaseTwoResolved(List<EphemeralResolveInfo> ephemeralResolveInfoList, + int sequence) { + final String packageName; + final String splitName; + if (ephemeralResolveInfoList != null + && ephemeralResolveInfoList.size() > 0) { + final EphemeralResponse ephemeralIntentInfo = + EphemeralResolver.filterEphemeralIntent( + ephemeralResolveInfoList, intent, null /*resolvedType*/, + 0 /*userId*/, intent.getPackage(), digest, + requestObj.responseObj.token); + if (ephemeralIntentInfo != null + && ephemeralIntentInfo.resolveInfo != null) { + packageName = ephemeralIntentInfo.resolveInfo.getPackageName(); + splitName = ephemeralIntentInfo.splitName; + } else { + packageName = null; + splitName = null; + } + } else { + packageName = null; + splitName = null; + } + final Intent installerIntent = buildEphemeralInstallerIntent( + requestObj.launchIntent, + requestObj.origIntent, + requestObj.callingPackage, + requestObj.resolvedType, + requestObj.userId, + packageName, + splitName, + requestObj.responseObj.token, + false /*needsPhaseTwo*/); + installerIntent.setComponent(new ComponentName( + ephemeralInstaller.packageName, ephemeralInstaller.name)); + context.startActivity(installerIntent); + } + }; + connection.getEphemeralIntentFilterList( + shaPrefix, callback, callbackHandler, 0 /*sequence*/); + } + + /** + * Builds and returns an intent to launch the ephemeral installer. + */ + public static Intent buildEphemeralInstallerIntent(Intent launchIntent, Intent origIntent, + String callingPackage, String resolvedType, int userId, String ephemeralPackageName, + String ephemeralSplitName, String token, boolean needsPhaseTwo) { + // Construct the intent that launches 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); + // TODO: Remove when the platform has fully implemented ephemeral apps + intent.setData(origIntent.getData().buildUpon().clearQuery().build()); + intent.putExtra(Intent.EXTRA_EPHEMERAL_TOKEN, token); + intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost()); + + if (!needsPhaseTwo) { + // We have all of the data we need; just start the installer without a second phase + 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. + try { + final IIntentSender failureIntentTarget = ActivityManagerNative.getDefault() + .getIntentSender( + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, + null /*token*/, null /*resultWho*/, 1 /*requestCode*/, + new Intent[] { nonEphemeralIntent }, + new String[] { resolvedType }, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_IMMUTABLE, + null /*bOptions*/, userId); + intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, + new IntentSender(failureIntentTarget)); + } catch (RemoteException ignore) { /* ignore; same process */ } + + final Intent ephemeralIntent; + if (EphemeralResolver.USE_DEFAULT_EPHEMERAL_LAUNCHER) { + // Force the intent to be directed to the ephemeral package + ephemeralIntent = new Intent(origIntent); + ephemeralIntent.setPackage(ephemeralPackageName); + } else { + // Success intent goes back to the installer + ephemeralIntent = new Intent(launchIntent); + } + + // Intent that is eventually launched if the ephemeral package was + // installed successfully. This will actually be launched by a platform + // broadcast receiver. + try { + final IIntentSender successIntentTarget = ActivityManagerNative.getDefault() + .getIntentSender( + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, + null /*token*/, null /*resultWho*/, 0 /*requestCode*/, + new Intent[] { ephemeralIntent }, + new String[] { resolvedType }, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_IMMUTABLE, + null /*bOptions*/, userId); + intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, + new IntentSender(successIntentTarget)); + } catch (RemoteException ignore) { /* ignore; same process */ } + + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, ephemeralPackageName); + intent.putExtra(Intent.EXTRA_SPLIT_NAME, ephemeralSplitName); + } + + return intent; + } + + private static EphemeralResponse filterEphemeralIntent( + List<EphemeralResolveInfo> ephemeralResolveInfoList, + Intent intent, String resolvedType, int userId, String packageName, + EphemeralDigest digest, String token) { + final int[] shaPrefix = digest.getDigestPrefix(); + final byte[][] digestBytes = digest.getDigestBytes(); + // Go in reverse order so we match the narrowest scope first. + for (int i = shaPrefix.length - 1; i >= 0 ; --i) { + for (EphemeralResolveInfo ephemeralInfo : ephemeralResolveInfoList) { + if (!Arrays.equals(digestBytes[i], ephemeralInfo.getDigestBytes())) { + continue; + } + if (packageName != null + && !packageName.equals(ephemeralInfo.getPackageName())) { + continue; + } + final List<EphemeralIntentFilter> ephemeralFilters = + ephemeralInfo.getIntentFilters(); + // No filters; we need to start phase two + if (ephemeralFilters == null || ephemeralFilters.isEmpty()) { + return new EphemeralResponse(ephemeralInfo, + new IntentFilter(Intent.ACTION_VIEW) /*intentFilter*/, + null /*splitName*/, token, true /*needsPhase2*/); + } + // We have a domain match; resolve the filters to see if anything matches. + final PackageManagerService.EphemeralIntentResolver ephemeralResolver = + new PackageManagerService.EphemeralIntentResolver(); + for (int j = ephemeralFilters.size() - 1; j >= 0; --j) { + final EphemeralIntentFilter ephemeralFilter = ephemeralFilters.get(j); + final List<IntentFilter> splitFilters = ephemeralFilter.getFilters(); + if (splitFilters == null || splitFilters.isEmpty()) { + continue; + } + for (int k = splitFilters.size() - 1; k >= 0; --k) { + final EphemeralResponse intentInfo = + new EphemeralResponse(ephemeralInfo, + splitFilters.get(k), ephemeralFilter.getSplitName(), + token, false /*needsPhase2*/); + ephemeralResolver.addFilter(intentInfo); + } + } + List<EphemeralResponse> matchedResolveInfoList = ephemeralResolver + .queryIntent(intent, resolvedType, false /*defaultOnly*/, userId); + if (!matchedResolveInfoList.isEmpty()) { + return matchedResolveInfoList.get(0); + } + } + } + // Hash or filter mis-match; no ephemeral apps for this domain. + return null; + } +} diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java index ecc03d7c5fbb..20d9813e250a 100644 --- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java +++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java @@ -23,8 +23,10 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.EphemeralResolveInfo; +import android.content.pm.EphemeralResponse; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; @@ -54,8 +56,6 @@ final class EphemeralResolverConnection { private final Object mLock = new Object(); private final GetEphemeralResolveInfoCaller mGetEphemeralResolveInfoCaller = new GetEphemeralResolveInfoCaller(); - private final GetEphemeralIntentFilterCaller mGetEphemeralIntentFilterCaller = - new GetEphemeralIntentFilterCaller(); private final ServiceConnection mServiceConnection = new MyServiceConnection(); private final Context mContext; /** Intent used to bind to the service */ @@ -84,19 +84,27 @@ final class EphemeralResolverConnection { return null; } - public final List<EphemeralResolveInfo> getEphemeralIntentFilterList(int digestPrefix[]) { - throwIfCalledOnMainThread(); + public final void getEphemeralIntentFilterList(int digestPrefix[], PhaseTwoCallback callback, + Handler callbackHandler, final int sequence) { + final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) throws RemoteException { + final ArrayList<EphemeralResolveInfo> ephemeralResolveInfoList = + data.getParcelableArrayList(EphemeralResolverService.EXTRA_RESOLVE_INFO); + callbackHandler.post(new Runnable() { + @Override + public void run() { + callback.onPhaseTwoResolved(ephemeralResolveInfoList, sequence); + } + }); + } + }; try { - return mGetEphemeralIntentFilterCaller.getEphemeralIntentFilterList( - getRemoteInstanceLazy(), digestPrefix); + getRemoteInstanceLazy() + .getEphemeralIntentFilterList(remoteCallback, digestPrefix, sequence); } catch (RemoteException re) { } catch (TimeoutException te) { - } finally { - synchronized (mLock) { - mLock.notifyAll(); - } } - return null; } public void dump(FileDescriptor fd, PrintWriter pw, String prefix) { @@ -161,6 +169,14 @@ final class EphemeralResolverConnection { } } + /** + * Asynchronous callback when results come back from ephemeral resolution phase two. + */ + public abstract static class PhaseTwoCallback { + abstract void onPhaseTwoResolved(List<EphemeralResolveInfo> ephemeralResolveInfoList, + int sequence); + } + private final class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { @@ -205,32 +221,4 @@ final class EphemeralResolverConnection { return getResultTimed(sequence); } } - - private static final class GetEphemeralIntentFilterCaller - extends TimedRemoteCaller<List<EphemeralResolveInfo>> { - private final IRemoteCallback mCallback; - - public GetEphemeralIntentFilterCaller() { - super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); - mCallback = new IRemoteCallback.Stub() { - @Override - public void sendResult(Bundle data) throws RemoteException { - final ArrayList<EphemeralResolveInfo> resolveList = - data.getParcelableArrayList( - EphemeralResolverService.EXTRA_RESOLVE_INFO); - int sequence = - data.getInt(EphemeralResolverService.EXTRA_SEQUENCE, -1); - onRemoteMethodResult(resolveList, sequence); - } - }; - } - - public List<EphemeralResolveInfo> getEphemeralIntentFilterList( - IEphemeralResolver target, int digestPrefix[]) - throws RemoteException, TimeoutException { - final int sequence = onBeforeRemoteCall(); - target.getEphemeralIntentFilterList(mCallback, digestPrefix, sequence); - return getResultTimed(sequence); - } - } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 271107f0b4e3..ee3f42b7808e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -121,10 +121,9 @@ import android.content.pm.ApplicationInfo; import android.content.pm.AppsQueryHelper; import android.content.pm.ComponentInfo; import android.content.pm.EphemeralApplicationInfo; -import android.content.pm.EphemeralIntentFilter; +import android.content.pm.EphemeralRequest; import android.content.pm.EphemeralResolveInfo; -import android.content.pm.EphemeralResolveInfo.EphemeralDigest; -import android.content.pm.EphemeralResolveInfo.EphemeralResolveIntentInfo; +import android.content.pm.EphemeralResponse; import android.content.pm.FeatureInfo; import android.content.pm.IOnPermissionsChangeListener; import android.content.pm.IPackageDataObserver; @@ -1069,6 +1068,7 @@ public class PackageManagerService extends IPackageManager.Stub { static final int START_INTENT_FILTER_VERIFICATIONS = 17; static final int INTENT_FILTER_VERIFIED = 18; static final int WRITE_PACKAGE_LIST = 19; + static final int EPHEMERAL_RESOLUTION_PHASE_TWO = 20; static final int WRITE_SETTINGS_DELAY = 10*1000; // 10 seconds @@ -1639,6 +1639,13 @@ public class PackageManagerService extends IPackageManager.Stub { break; } + case EPHEMERAL_RESOLUTION_PHASE_TWO: { + EphemeralResolver.doEphemeralResolutionPhaseTwo(mContext, + mEphemeralResolverConnection, + (EphemeralRequest) msg.obj, + mEphemeralInstallerActivity, + mHandler); + } } } } @@ -4951,60 +4958,13 @@ public class PackageManagerService extends IPackageManager.Stub { return true; } - private static EphemeralResolveIntentInfo getEphemeralIntentInfo( - Context context, EphemeralResolverConnection resolverConnection, Intent intent, - String resolvedType, int userId, String packageName) { - final EphemeralDigest digest = - new EphemeralDigest(intent.getData().getHost(), 5 /*maxDigests*/); - final int[] shaPrefix = digest.getDigestPrefix(); - final byte[][] digestBytes = digest.getDigestBytes(); - final List<EphemeralResolveInfo> ephemeralResolveInfoList = - resolverConnection.getEphemeralResolveInfoList(shaPrefix); - if (ephemeralResolveInfoList == null || ephemeralResolveInfoList.size() == 0) { - // No hash prefix match; there are no ephemeral apps for this domain. - return null; - } - - // Go in reverse order so we match the narrowest scope first. - for (int i = shaPrefix.length - 1; i >= 0 ; --i) { - for (EphemeralResolveInfo ephemeralApplication : ephemeralResolveInfoList) { - if (!Arrays.equals(digestBytes[i], ephemeralApplication.getDigestBytes())) { - continue; - } - final List<EphemeralIntentFilter> ephemeralFilters = - ephemeralApplication.getIntentFilters(); - // No filters; this should never happen. - if (ephemeralFilters.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 = ephemeralFilters.size() - 1; j >= 0; --j) { - final EphemeralIntentFilter ephemeralFilter = ephemeralFilters.get(j); - final List<IntentFilter> splitFilters = ephemeralFilter.getFilters(); - if (splitFilters == null || splitFilters.isEmpty()) { - continue; - } - for (int k = splitFilters.size() - 1; k >= 0; --k) { - final EphemeralResolveIntentInfo intentInfo = - new EphemeralResolveIntentInfo(splitFilters.get(k), - ephemeralApplication, ephemeralFilter.getSplitName()); - ephemeralResolver.addFilter(intentInfo); - } - } - List<EphemeralResolveIntentInfo> matchedResolveInfoList = ephemeralResolver - .queryIntent(intent, resolvedType, false /*defaultOnly*/, userId); - if (!matchedResolveInfoList.isEmpty()) { - return matchedResolveInfoList.get(0); - } - } - } - // Hash or filter mis-match; no ephemeral apps for this domain. - return null; + private void requestEphemeralResolutionPhaseTwo(EphemeralResponse responseObj, + Intent origIntent, String resolvedType, Intent launchIntent, String callingPackage, + int userId) { + final Message msg = mHandler.obtainMessage(EPHEMERAL_RESOLUTION_PHASE_TWO, + new EphemeralRequest(responseObj, origIntent, resolvedType, launchIntent, + callingPackage, userId)); + mHandler.sendMessage(msg); } private ResolveInfo chooseBestActivity(Intent intent, String resolvedType, @@ -5480,15 +5440,17 @@ public class PackageManagerService extends IPackageManager.Stub { } if (addEphemeral) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral"); - final EphemeralResolveIntentInfo intentInfo = getEphemeralIntentInfo( - mContext, mEphemeralResolverConnection, intent, resolvedType, userId, - matchEphemeralPackage ? pkgName : null); + final EphemeralRequest requestObject = new EphemeralRequest( + null /*responseObj*/, intent /*origIntent*/, resolvedType, + null /*launchIntent*/, null /*callingPackage*/, userId); + final EphemeralResponse intentInfo = EphemeralResolver.doEphemeralResolutionPhaseOne( + mContext, mEphemeralResolverConnection, requestObject); if (intentInfo != null) { if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } final ResolveInfo ephemeralInstaller = new ResolveInfo(mEphemeralInstallerInfo); - ephemeralInstaller.ephemeralIntentInfo = intentInfo; + ephemeralInstaller.ephemeralResponse = intentInfo; // make sure this resolver is the default ephemeralInstaller.isDefault = true; ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART @@ -11475,8 +11437,8 @@ public class PackageManagerService extends IPackageManager.Stub { private int mFlags; } - private static final class EphemeralIntentResolver - extends IntentResolver<EphemeralResolveIntentInfo, EphemeralResolveIntentInfo> { + static final class EphemeralIntentResolver + extends IntentResolver<EphemeralResponse, EphemeralResponse> { /** * The result that has the highest defined order. Ordering applies on a * per-package basis. Mapping is from package name to Pair of order and @@ -11491,46 +11453,46 @@ public class PackageManagerService extends IPackageManager.Stub { final ArrayMap<String, Pair<Integer, EphemeralResolveInfo>> mOrderResult = new ArrayMap<>(); @Override - protected EphemeralResolveIntentInfo[] newArray(int size) { - return new EphemeralResolveIntentInfo[size]; + protected EphemeralResponse[] newArray(int size) { + return new EphemeralResponse[size]; } @Override - protected boolean isPackageForFilter(String packageName, EphemeralResolveIntentInfo info) { + protected boolean isPackageForFilter(String packageName, EphemeralResponse responseObj) { return true; } @Override - protected EphemeralResolveIntentInfo newResult(EphemeralResolveIntentInfo info, int match, + protected EphemeralResponse newResult(EphemeralResponse responseObj, int match, int userId) { if (!sUserManager.exists(userId)) { return null; } - final String packageName = info.getEphemeralResolveInfo().getPackageName(); - final Integer order = info.getOrder(); + final String packageName = responseObj.resolveInfo.getPackageName(); + final Integer order = responseObj.getOrder(); final Pair<Integer, EphemeralResolveInfo> lastOrderResult = mOrderResult.get(packageName); // ordering is enabled and this item's order isn't high enough if (lastOrderResult != null && lastOrderResult.first >= order) { return null; } - final EphemeralResolveInfo res = info.getEphemeralResolveInfo(); + final EphemeralResolveInfo res = responseObj.resolveInfo; if (order > 0) { // non-zero order, enable ordering mOrderResult.put(packageName, new Pair<>(order, res)); } - return info; + return responseObj; } @Override - protected void filterResults(List<EphemeralResolveIntentInfo> results) { + protected void filterResults(List<EphemeralResponse> results) { // only do work if ordering is enabled [most of the time it won't be] if (mOrderResult.size() == 0) { return; } int resultSize = results.size(); for (int i = 0; i < resultSize; i++) { - final EphemeralResolveInfo info = results.get(i).getEphemeralResolveInfo(); + final EphemeralResolveInfo info = results.get(i).resolveInfo; final String packageName = info.getPackageName(); final Pair<Integer, EphemeralResolveInfo> savedInfo = mOrderResult.get(packageName); if (savedInfo == null) { @@ -21313,6 +21275,14 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); public String getNameForUid(int uid) { return PackageManagerService.this.getNameForUid(uid); } + + @Override + public void requestEphemeralResolutionPhaseTwo(EphemeralResponse responseObj, + Intent origIntent, String resolvedType, Intent launchIntent, + String callingPackage, int userId) { + PackageManagerService.this.requestEphemeralResolutionPhaseTwo( + responseObj, origIntent, resolvedType, launchIntent, callingPackage, userId); + } } @Override |