Merge "Implement 2-phase resolution"
This commit is contained in:
@@ -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
|
||||
|
||||
48
core/java/android/content/pm/EphemeralRequest.java
Normal file
48
core/java/android/content/pm/EphemeralRequest.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
51
core/java/android/content/pm/EphemeralResponse.java
Normal file
51
core/java/android/content/pm/EphemeralResponse.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
249
services/core/java/com/android/server/pm/EphemeralResolver.java
Normal file
249
services/core/java/com/android/server/pm/EphemeralResolver.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user