Merge "Implement 2-phase resolution"

This commit is contained in:
Todd Kennedy
2016-11-16 23:05:30 +00:00
committed by Android (Google) Code Review
10 changed files with 459 additions and 199 deletions

View File

@@ -3834,6 +3834,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

View File

@@ -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;
}
}

View File

@@ -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.

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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