Revert "Revert "Adds generic intent Instant App resolution""
This reverts commit 860b8ba719.
The original change that was reverted contained a bug that allowed an
http view/browsable intent used to query for browsers to be considered
as a candidate for instant apps. This was resulting in an attempt to
bind to the instant app resolver while holding a lock on mPackages.
This change ensures that PMS doesn't bind while checking for the browser
status of a package in both the instant app filtering code and by adding
the FLAG_IGNORE_EPHEMERAL to the canonical browser intent.
Reason for revert: Applying fix
Change-Id: I4896b3a15416a11fdc3f6c191e552c4ce8963623
Fixes: 63117034
Fixes: 71916178
Test: Manual using test app at google_experimental/users/patb/InstantAppsInP
Test: atest android.appsecurity.cts.EphemeralTest passes after modification
This commit is contained in:
@@ -10050,6 +10050,7 @@ package android.content {
|
||||
field public static final int FLAG_ACTIVITY_FORWARD_RESULT = 33554432; // 0x2000000
|
||||
field public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 1048576; // 0x100000
|
||||
field public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 4096; // 0x1000
|
||||
field public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 2048; // 0x800
|
||||
field public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; // 0x8000000
|
||||
field public static final int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; // 0x80000
|
||||
field public static final int FLAG_ACTIVITY_NEW_TASK = 268435456; // 0x10000000
|
||||
|
||||
@@ -311,8 +311,10 @@ package android.app {
|
||||
ctor public InstantAppResolverService();
|
||||
method public final void attachBaseContext(android.content.Context);
|
||||
method public final android.os.IBinder onBind(android.content.Intent);
|
||||
method public void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback);
|
||||
method public void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback);
|
||||
method public deprecated void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback);
|
||||
method public void onGetInstantAppIntentFilter(android.content.Intent, int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback);
|
||||
method public deprecated void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback);
|
||||
method public void onGetInstantAppResolveInfo(android.content.Intent, int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback);
|
||||
}
|
||||
|
||||
public static final class InstantAppResolverService.InstantAppResolutionCallback {
|
||||
@@ -821,12 +823,14 @@ package android.content {
|
||||
field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
|
||||
field public static final java.lang.String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS";
|
||||
field public static final java.lang.String EXTRA_FORCE_FACTORY_RESET = "android.intent.extra.FORCE_FACTORY_RESET";
|
||||
field public static final java.lang.String EXTRA_INSTANT_APP_BUNDLES = "android.intent.extra.INSTANT_APP_BUNDLES";
|
||||
field public static final java.lang.String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID";
|
||||
field public static final java.lang.String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
|
||||
field public static final java.lang.String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME";
|
||||
field public static final java.lang.String EXTRA_REASON = "android.intent.extra.REASON";
|
||||
field public static final java.lang.String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
|
||||
field public static final java.lang.String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
|
||||
field public static final java.lang.String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
|
||||
}
|
||||
|
||||
public class IntentFilter implements android.os.Parcelable {
|
||||
@@ -869,6 +873,7 @@ package android.content.pm {
|
||||
ctor public InstantAppResolveInfo(android.content.pm.InstantAppResolveInfo.InstantAppDigest, java.lang.String, java.util.List<android.content.pm.InstantAppIntentFilter>, int);
|
||||
ctor public InstantAppResolveInfo(android.content.pm.InstantAppResolveInfo.InstantAppDigest, java.lang.String, java.util.List<android.content.pm.InstantAppIntentFilter>, long, android.os.Bundle);
|
||||
ctor public InstantAppResolveInfo(java.lang.String, java.lang.String, java.util.List<android.content.pm.InstantAppIntentFilter>);
|
||||
ctor public InstantAppResolveInfo(android.os.Bundle);
|
||||
method public int describeContents();
|
||||
method public byte[] getDigestBytes();
|
||||
method public int getDigestPrefix();
|
||||
@@ -877,6 +882,7 @@ package android.content.pm {
|
||||
method public long getLongVersionCode();
|
||||
method public java.lang.String getPackageName();
|
||||
method public deprecated int getVersionCode();
|
||||
method public boolean shouldLetInstallerDecide();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator<android.content.pm.InstantAppResolveInfo> CREATOR;
|
||||
}
|
||||
@@ -888,6 +894,7 @@ package android.content.pm {
|
||||
method public int[] getDigestPrefix();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator<android.content.pm.InstantAppResolveInfo.InstantAppDigest> CREATOR;
|
||||
field public static final android.content.pm.InstantAppResolveInfo.InstantAppDigest UNDEFINED;
|
||||
}
|
||||
|
||||
public final class IntentFilterVerificationInfo implements android.os.Parcelable {
|
||||
|
||||
@@ -17,20 +17,10 @@
|
||||
package android.app;
|
||||
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.Service;
|
||||
import android.app.InstantAppResolverService.InstantAppResolutionCallback;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.EphemeralResolveInfo;
|
||||
import android.content.pm.InstantAppResolveInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.IRemoteCallback;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -85,7 +75,6 @@ public abstract class EphemeralResolverService extends InstantAppResolverService
|
||||
return super.getLooper();
|
||||
}
|
||||
|
||||
@Override
|
||||
void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
|
||||
InstantAppResolutionCallback callback) {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
@@ -101,7 +90,6 @@ public abstract class EphemeralResolverService extends InstantAppResolverService
|
||||
callback.onInstantAppResolveInfo(resultList);
|
||||
}
|
||||
|
||||
@Override
|
||||
void _onGetInstantAppIntentFilter(int[] digestPrefix, String token,
|
||||
String hostName, InstantAppResolutionCallback callback) {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
|
||||
package android.app;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.IRemoteCallback;
|
||||
|
||||
/** @hide */
|
||||
oneway interface IInstantAppResolver {
|
||||
void getInstantAppResolveInfoList(in int[] digestPrefix,
|
||||
void getInstantAppResolveInfoList(in Intent sanitizedIntent, in int[] hostDigestPrefix,
|
||||
String token, int sequence, IRemoteCallback callback);
|
||||
|
||||
void getInstantAppIntentFilterList(in int[] digestPrefix,
|
||||
String token, String hostName, IRemoteCallback callback);
|
||||
void getInstantAppIntentFilterList(in Intent sanitizedIntent, in int[] hostDigestPrefix,
|
||||
String token, IRemoteCallback callback);
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package android.app;
|
||||
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.InstantAppResolveInfo;
|
||||
@@ -35,6 +34,7 @@ import android.util.Slog;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -53,23 +53,65 @@ public abstract class InstantAppResolverService extends Service {
|
||||
Handler mHandler;
|
||||
|
||||
/**
|
||||
* Called to retrieve resolve info for instant applications.
|
||||
* Called to retrieve resolve info for instant applications immediately.
|
||||
*
|
||||
* @param digestPrefix The hash prefix of the instant app's domain.
|
||||
* @deprecated should implement {@link #onGetInstantAppResolveInfo(Intent, int[], String,
|
||||
* InstantAppResolutionCallback)}
|
||||
*/
|
||||
@Deprecated
|
||||
public void onGetInstantAppResolveInfo(
|
||||
int digestPrefix[], String token, InstantAppResolutionCallback callback) {
|
||||
throw new IllegalStateException("Must define");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to retrieve intent filters for instant applications.
|
||||
* Called to retrieve intent filters for instant applications from potentially expensive
|
||||
* sources.
|
||||
*
|
||||
* @param digestPrefix The hash prefix of the instant app's domain.
|
||||
* @deprecated should implement {@link #onGetInstantAppIntentFilter(Intent, int[], String,
|
||||
* InstantAppResolutionCallback)}
|
||||
*/
|
||||
@Deprecated
|
||||
public void onGetInstantAppIntentFilter(
|
||||
int digestPrefix[], String token, InstantAppResolutionCallback callback) {
|
||||
throw new IllegalStateException("Must define");
|
||||
throw new IllegalStateException("Must define onGetInstantAppIntentFilter");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to retrieve resolve info for instant applications immediately.
|
||||
*
|
||||
* @param sanitizedIntent The sanitized {@link Intent} used for resolution.
|
||||
* @param hostDigestPrefix The hash prefix of the instant app's domain.
|
||||
*/
|
||||
public void onGetInstantAppResolveInfo(Intent sanitizedIntent, int[] hostDigestPrefix,
|
||||
String token, InstantAppResolutionCallback callback) {
|
||||
// if not overridden, forward to old methods and filter out non-web intents
|
||||
if (sanitizedIntent.isBrowsableWebIntent()) {
|
||||
onGetInstantAppResolveInfo(hostDigestPrefix, token, callback);
|
||||
} else {
|
||||
callback.onInstantAppResolveInfo(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to retrieve intent filters for instant applications from potentially expensive
|
||||
* sources.
|
||||
*
|
||||
* @param sanitizedIntent The sanitized {@link Intent} used for resolution.
|
||||
* @param hostDigestPrefix The hash prefix of the instant app's domain or null if no host is
|
||||
* defined.
|
||||
*/
|
||||
public void onGetInstantAppIntentFilter(Intent sanitizedIntent, int[] hostDigestPrefix,
|
||||
String token, InstantAppResolutionCallback callback) {
|
||||
Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden");
|
||||
// if not overridden, forward to old methods and filter out non-web intents
|
||||
if (sanitizedIntent.isBrowsableWebIntent()) {
|
||||
onGetInstantAppIntentFilter(hostDigestPrefix, token, callback);
|
||||
} else {
|
||||
callback.onInstantAppResolveInfo(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,8 +131,8 @@ public abstract class InstantAppResolverService extends Service {
|
||||
public final IBinder onBind(Intent intent) {
|
||||
return new IInstantAppResolver.Stub() {
|
||||
@Override
|
||||
public void getInstantAppResolveInfoList(
|
||||
int digestPrefix[], String token, int sequence, IRemoteCallback callback) {
|
||||
public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix,
|
||||
String token, int sequence, IRemoteCallback callback) {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Slog.v(TAG, "[" + token + "] Phase1 called; posting");
|
||||
}
|
||||
@@ -98,14 +140,14 @@ public abstract class InstantAppResolverService extends Service {
|
||||
args.arg1 = callback;
|
||||
args.arg2 = digestPrefix;
|
||||
args.arg3 = token;
|
||||
mHandler.obtainMessage(
|
||||
ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, args)
|
||||
.sendToTarget();
|
||||
args.arg4 = sanitizedIntent;
|
||||
mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO,
|
||||
sequence, 0, args).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getInstantAppIntentFilterList(
|
||||
int digestPrefix[], String token, String hostName, IRemoteCallback callback) {
|
||||
public void getInstantAppIntentFilterList(Intent sanitizedIntent,
|
||||
int[] digestPrefix, String token, IRemoteCallback callback) {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Slog.v(TAG, "[" + token + "] Phase2 called; posting");
|
||||
}
|
||||
@@ -113,9 +155,9 @@ public abstract class InstantAppResolverService extends Service {
|
||||
args.arg1 = callback;
|
||||
args.arg2 = digestPrefix;
|
||||
args.arg3 = token;
|
||||
args.arg4 = hostName;
|
||||
mHandler.obtainMessage(
|
||||
ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, callback).sendToTarget();
|
||||
args.arg4 = sanitizedIntent;
|
||||
mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER,
|
||||
callback).sendToTarget();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -142,29 +184,9 @@ public abstract class InstantAppResolverService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
|
||||
InstantAppResolutionCallback callback) {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Slog.d(TAG, "[" + token + "] Phase1 request;"
|
||||
+ " prefix: " + Arrays.toString(digestPrefix));
|
||||
}
|
||||
onGetInstantAppResolveInfo(digestPrefix, token, callback);
|
||||
}
|
||||
@Deprecated
|
||||
void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName,
|
||||
InstantAppResolutionCallback callback) {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Slog.d(TAG, "[" + token + "] Phase2 request;"
|
||||
+ " prefix: " + Arrays.toString(digestPrefix));
|
||||
}
|
||||
onGetInstantAppIntentFilter(digestPrefix, token, callback);
|
||||
}
|
||||
|
||||
private final class ServiceHandler extends Handler {
|
||||
public static final int MSG_GET_INSTANT_APP_RESOLVE_INFO = 1;
|
||||
public static final int MSG_GET_INSTANT_APP_INTENT_FILTER = 2;
|
||||
|
||||
public ServiceHandler(Looper looper) {
|
||||
super(looper, null /*callback*/, true /*async*/);
|
||||
}
|
||||
@@ -179,9 +201,13 @@ public abstract class InstantAppResolverService extends Service {
|
||||
final IRemoteCallback callback = (IRemoteCallback) args.arg1;
|
||||
final int[] digestPrefix = (int[]) args.arg2;
|
||||
final String token = (String) args.arg3;
|
||||
final Intent intent = (Intent) args.arg4;
|
||||
final int sequence = message.arg1;
|
||||
_onGetInstantAppResolveInfo(
|
||||
digestPrefix, token,
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Slog.d(TAG, "[" + token + "] Phase1 request;"
|
||||
+ " prefix: " + Arrays.toString(digestPrefix));
|
||||
}
|
||||
onGetInstantAppResolveInfo(intent, digestPrefix, token,
|
||||
new InstantAppResolutionCallback(sequence, callback));
|
||||
} break;
|
||||
|
||||
@@ -190,9 +216,12 @@ public abstract class InstantAppResolverService extends Service {
|
||||
final IRemoteCallback callback = (IRemoteCallback) args.arg1;
|
||||
final int[] digestPrefix = (int[]) args.arg2;
|
||||
final String token = (String) args.arg3;
|
||||
final String hostName = (String) args.arg4;
|
||||
_onGetInstantAppIntentFilter(
|
||||
digestPrefix, token, hostName,
|
||||
final Intent intent = (Intent) args.arg4;
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Slog.d(TAG, "[" + token + "] Phase2 request;"
|
||||
+ " prefix: " + Arrays.toString(digestPrefix));
|
||||
}
|
||||
onGetInstantAppIntentFilter(intent, digestPrefix, token,
|
||||
new InstantAppResolutionCallback(-1 /*sequence*/, callback));
|
||||
} break;
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
@@ -4472,7 +4473,15 @@ public class Intent implements Parcelable, Cloneable {
|
||||
public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION";
|
||||
|
||||
/**
|
||||
* A {@link Bundle} of metadata that describes the instanta application that needs to be
|
||||
* An array of {@link Bundle}s containing details about resolved instant apps..
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final String EXTRA_INSTANT_APP_BUNDLES =
|
||||
"android.intent.extra.INSTANT_APP_BUNDLES";
|
||||
|
||||
/**
|
||||
* A {@link Bundle} of metadata that describes the instant application that needs to be
|
||||
* installed. This data is populated from the response to
|
||||
* {@link android.content.pm.InstantAppResolveInfo#getExtras()} as provided by the registered
|
||||
* instant application resolver.
|
||||
@@ -4481,6 +4490,16 @@ public class Intent implements Parcelable, Cloneable {
|
||||
public static final String EXTRA_INSTANT_APP_EXTRAS =
|
||||
"android.intent.extra.INSTANT_APP_EXTRAS";
|
||||
|
||||
/**
|
||||
* A boolean value indicating that the instant app resolver was unable to state with certainty
|
||||
* that it did or did not have an app for the sanitized {@link Intent} defined at
|
||||
* {@link #EXTRA_INTENT}.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final String EXTRA_UNKNOWN_INSTANT_APP =
|
||||
"android.intent.extra.UNKNOWN_INSTANT_APP";
|
||||
|
||||
/**
|
||||
* The version code of the app to install components from.
|
||||
* @deprecated Use {@link #EXTRA_LONG_VERSION_CODE).
|
||||
@@ -5029,6 +5048,7 @@ public class Intent implements Parcelable, Cloneable {
|
||||
FLAG_GRANT_PREFIX_URI_PERMISSION,
|
||||
FLAG_DEBUG_TRIAGED_MISSING,
|
||||
FLAG_IGNORE_EPHEMERAL,
|
||||
FLAG_ACTIVITY_MATCH_EXTERNAL,
|
||||
FLAG_ACTIVITY_NO_HISTORY,
|
||||
FLAG_ACTIVITY_SINGLE_TOP,
|
||||
FLAG_ACTIVITY_NEW_TASK,
|
||||
@@ -5072,6 +5092,7 @@ public class Intent implements Parcelable, Cloneable {
|
||||
FLAG_INCLUDE_STOPPED_PACKAGES,
|
||||
FLAG_DEBUG_TRIAGED_MISSING,
|
||||
FLAG_IGNORE_EPHEMERAL,
|
||||
FLAG_ACTIVITY_MATCH_EXTERNAL,
|
||||
FLAG_ACTIVITY_NO_HISTORY,
|
||||
FLAG_ACTIVITY_SINGLE_TOP,
|
||||
FLAG_ACTIVITY_NEW_TASK,
|
||||
@@ -5475,6 +5496,14 @@ public class Intent implements Parcelable, Cloneable {
|
||||
*/
|
||||
public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 0x00001000;
|
||||
|
||||
|
||||
/**
|
||||
* If set, resolution of this intent may take place via an instant app not
|
||||
* yet on the device if there does not yet exist an app on device to
|
||||
* resolve it.
|
||||
*/
|
||||
public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 0x00000800;
|
||||
|
||||
/**
|
||||
* If set, when sending a broadcast only registered receivers will be
|
||||
* called -- no BroadcastReceiver components will be launched.
|
||||
@@ -10028,6 +10057,25 @@ public class Intent implements Parcelable, Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean hasWebURI() {
|
||||
if (getData() == null) {
|
||||
return false;
|
||||
}
|
||||
final String scheme = getScheme();
|
||||
if (TextUtils.isEmpty(scheme)) {
|
||||
return false;
|
||||
}
|
||||
return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean isBrowsableWebIntent() {
|
||||
return ACTION_VIEW.equals(mAction)
|
||||
&& hasCategory(CATEGORY_BROWSABLE)
|
||||
&& hasWebURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,10 @@ import android.annotation.Nullable;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Auxiliary application resolution response.
|
||||
@@ -31,56 +35,95 @@ import android.content.IntentFilter;
|
||||
* hasn't been installed.
|
||||
* @hide
|
||||
*/
|
||||
public final class AuxiliaryResolveInfo extends IntentFilter {
|
||||
/** Resolved information returned from the external instant resolver */
|
||||
public final InstantAppResolveInfo resolveInfo;
|
||||
/** The resolved package. Copied from {@link #resolveInfo}. */
|
||||
public final String packageName;
|
||||
public final class AuxiliaryResolveInfo {
|
||||
/** The activity to launch if there's an installation failure. */
|
||||
public final ComponentName installFailureActivity;
|
||||
/** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */
|
||||
public final String splitName;
|
||||
/** Whether or not instant resolution needs the second phase */
|
||||
public final boolean needsPhaseTwo;
|
||||
/** Opaque token to track the instant application resolution */
|
||||
public final String token;
|
||||
/** The version code of the package */
|
||||
public final long versionCode;
|
||||
/** An intent to start upon failure to install */
|
||||
public final Intent failureIntent;
|
||||
/** The matching filters for this resolve info. */
|
||||
public final List<AuxiliaryFilter> filters;
|
||||
|
||||
/** Create a response for installing an instant application. */
|
||||
public AuxiliaryResolveInfo(@NonNull InstantAppResolveInfo resolveInfo,
|
||||
@NonNull IntentFilter orig,
|
||||
@Nullable String splitName,
|
||||
@NonNull String token,
|
||||
public AuxiliaryResolveInfo(@NonNull String token,
|
||||
boolean needsPhase2,
|
||||
@Nullable Intent failureIntent) {
|
||||
super(orig);
|
||||
this.resolveInfo = resolveInfo;
|
||||
this.packageName = resolveInfo.getPackageName();
|
||||
this.splitName = splitName;
|
||||
@Nullable Intent failureIntent,
|
||||
@Nullable List<AuxiliaryFilter> filters) {
|
||||
this.token = token;
|
||||
this.needsPhaseTwo = needsPhase2;
|
||||
this.versionCode = resolveInfo.getVersionCode();
|
||||
this.failureIntent = failureIntent;
|
||||
this.filters = filters;
|
||||
this.installFailureActivity = null;
|
||||
}
|
||||
|
||||
/** Create a response for installing a split on demand. */
|
||||
public AuxiliaryResolveInfo(@NonNull String packageName,
|
||||
@Nullable String splitName,
|
||||
@Nullable ComponentName failureActivity,
|
||||
long versionCode,
|
||||
@Nullable Intent failureIntent) {
|
||||
public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity,
|
||||
@Nullable Intent failureIntent,
|
||||
@Nullable List<AuxiliaryFilter> filters) {
|
||||
super();
|
||||
this.packageName = packageName;
|
||||
this.installFailureActivity = failureActivity;
|
||||
this.splitName = splitName;
|
||||
this.versionCode = versionCode;
|
||||
this.resolveInfo = null;
|
||||
this.filters = filters;
|
||||
this.token = null;
|
||||
this.needsPhaseTwo = false;
|
||||
this.failureIntent = failureIntent;
|
||||
}
|
||||
|
||||
/** Create a response for installing a split on demand. */
|
||||
public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity,
|
||||
String packageName, long versionCode, String splitName) {
|
||||
this(failureActivity, null, Collections.singletonList(
|
||||
new AuxiliaryResolveInfo.AuxiliaryFilter(packageName, versionCode, splitName)));
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static final class AuxiliaryFilter extends IntentFilter {
|
||||
/** Resolved information returned from the external instant resolver */
|
||||
public final InstantAppResolveInfo resolveInfo;
|
||||
/** The resolved package. Copied from {@link #resolveInfo}. */
|
||||
public final String packageName;
|
||||
/** The version code of the package */
|
||||
public final long versionCode;
|
||||
/** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */
|
||||
public final String splitName;
|
||||
/** The extras to pass on to the installer for this filter. */
|
||||
public final Bundle extras;
|
||||
|
||||
public AuxiliaryFilter(IntentFilter orig, InstantAppResolveInfo resolveInfo,
|
||||
String splitName, Bundle extras) {
|
||||
super(orig);
|
||||
this.resolveInfo = resolveInfo;
|
||||
this.packageName = resolveInfo.getPackageName();
|
||||
this.versionCode = resolveInfo.getLongVersionCode();
|
||||
this.splitName = splitName;
|
||||
this.extras = extras;
|
||||
}
|
||||
|
||||
public AuxiliaryFilter(InstantAppResolveInfo resolveInfo,
|
||||
String splitName, Bundle extras) {
|
||||
this.resolveInfo = resolveInfo;
|
||||
this.packageName = resolveInfo.getPackageName();
|
||||
this.versionCode = resolveInfo.getLongVersionCode();
|
||||
this.splitName = splitName;
|
||||
this.extras = extras;
|
||||
}
|
||||
|
||||
public AuxiliaryFilter(String packageName, long versionCode, String splitName) {
|
||||
this.resolveInfo = null;
|
||||
this.packageName = packageName;
|
||||
this.versionCode = versionCode;
|
||||
this.splitName = splitName;
|
||||
this.extras = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AuxiliaryFilter{"
|
||||
+ "packageName='" + packageName + '\''
|
||||
+ ", versionCode=" + versionCode
|
||||
+ ", splitName='" + splitName + '\'' + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package android.content.pm;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
@@ -26,11 +27,35 @@ import android.os.Parcelable;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Information about an instant application.
|
||||
* Describes an externally resolvable instant application. There are three states that this class
|
||||
* can represent: <p/>
|
||||
* <ul>
|
||||
* <li>
|
||||
* The first, usable only for non http/s intents, implies that the resolver cannot
|
||||
* immediately resolve this intent and would prefer that resolution be deferred to the
|
||||
* instant app installer. Represent this state with {@link #InstantAppResolveInfo(Bundle)}.
|
||||
* If the {@link android.content.Intent} has the scheme set to http/s and a set of digest
|
||||
* prefixes were passed into one of the resolve methods in
|
||||
* {@link android.app.InstantAppResolverService}, this state cannot be used.
|
||||
* </li>
|
||||
* <li>
|
||||
* The second represents a partial match and is constructed with any of the other
|
||||
* constructors. By setting one or more of the {@link Nullable}arguments to null, you
|
||||
* communicate to the resolver in response to
|
||||
* {@link android.app.InstantAppResolverService#onGetInstantAppResolveInfo(Intent, int[],
|
||||
* String, InstantAppResolverService.InstantAppResolutionCallback)}
|
||||
* that you need a 2nd round of resolution to complete the request.
|
||||
* </li>
|
||||
* <li>
|
||||
* The third represents a complete match and is constructed with all @Nullable parameters
|
||||
* populated.
|
||||
* </li>
|
||||
* </ul>
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@@ -38,6 +63,8 @@ public final class InstantAppResolveInfo implements Parcelable {
|
||||
/** Algorithm that will be used to generate the domain digest */
|
||||
private static final String SHA_ALGORITHM = "SHA-256";
|
||||
|
||||
private static final byte[] EMPTY_DIGEST = new byte[0];
|
||||
|
||||
private final InstantAppDigest mDigest;
|
||||
private final String mPackageName;
|
||||
/** The filters used to match domain */
|
||||
@@ -46,15 +73,30 @@ public final class InstantAppResolveInfo implements Parcelable {
|
||||
private final long mVersionCode;
|
||||
/** Data about the app that should be passed along to the Instant App installer on resolve */
|
||||
private final Bundle mExtras;
|
||||
/**
|
||||
* A flag that indicates that the resolver is aware that an app may match, but would prefer
|
||||
* that the installer get the sanitized intent to decide. This should not be used for
|
||||
* resolutions that include a host and will be ignored in such cases.
|
||||
*/
|
||||
private final boolean mShouldLetInstallerDecide;
|
||||
|
||||
/** Constructor for intent-based InstantApp resolution results. */
|
||||
public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName,
|
||||
@Nullable List<InstantAppIntentFilter> filters, int versionCode) {
|
||||
this(digest, packageName, filters, (long) versionCode, null /* extras */);
|
||||
}
|
||||
|
||||
/** Constructor for intent-based InstantApp resolution results with extras. */
|
||||
public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName,
|
||||
@Nullable List<InstantAppIntentFilter> filters, long versionCode,
|
||||
@Nullable Bundle extras) {
|
||||
this(digest, packageName, filters, versionCode, extras, false);
|
||||
}
|
||||
|
||||
/** Constructor for intent-based InstantApp resolution results with extras. */
|
||||
private InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName,
|
||||
@Nullable List<InstantAppIntentFilter> filters, long versionCode,
|
||||
@Nullable Bundle extras, boolean shouldLetInstallerDecide) {
|
||||
// validate arguments
|
||||
if ((packageName == null && (filters != null && filters.size() != 0))
|
||||
|| (packageName != null && (filters == null || filters.size() == 0))) {
|
||||
@@ -62,7 +104,7 @@ public final class InstantAppResolveInfo implements Parcelable {
|
||||
}
|
||||
mDigest = digest;
|
||||
if (filters != null) {
|
||||
mFilters = new ArrayList<InstantAppIntentFilter>(filters.size());
|
||||
mFilters = new ArrayList<>(filters.size());
|
||||
mFilters.addAll(filters);
|
||||
} else {
|
||||
mFilters = null;
|
||||
@@ -70,25 +112,48 @@ public final class InstantAppResolveInfo implements Parcelable {
|
||||
mPackageName = packageName;
|
||||
mVersionCode = versionCode;
|
||||
mExtras = extras;
|
||||
mShouldLetInstallerDecide = shouldLetInstallerDecide;
|
||||
}
|
||||
|
||||
/** Constructor for intent-based InstantApp resolution results by hostname. */
|
||||
public InstantAppResolveInfo(@NonNull String hostName, @Nullable String packageName,
|
||||
@Nullable List<InstantAppIntentFilter> filters) {
|
||||
this(new InstantAppDigest(hostName), packageName, filters, -1 /*versionCode*/,
|
||||
null /* extras */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that creates a "let the installer decide" response with optional included
|
||||
* extras.
|
||||
*/
|
||||
public InstantAppResolveInfo(@Nullable Bundle extras) {
|
||||
this(InstantAppDigest.UNDEFINED, null, null, -1, extras, true);
|
||||
}
|
||||
|
||||
InstantAppResolveInfo(Parcel in) {
|
||||
mDigest = in.readParcelable(null /*loader*/);
|
||||
mPackageName = in.readString();
|
||||
mFilters = new ArrayList<InstantAppIntentFilter>();
|
||||
in.readList(mFilters, null /*loader*/);
|
||||
mVersionCode = in.readLong();
|
||||
mShouldLetInstallerDecide = in.readBoolean();
|
||||
mExtras = in.readBundle();
|
||||
if (mShouldLetInstallerDecide) {
|
||||
mDigest = InstantAppDigest.UNDEFINED;
|
||||
mPackageName = null;
|
||||
mFilters = Collections.emptyList();
|
||||
mVersionCode = -1;
|
||||
} else {
|
||||
mDigest = in.readParcelable(null /*loader*/);
|
||||
mPackageName = in.readString();
|
||||
mFilters = new ArrayList<>();
|
||||
in.readList(mFilters, null /*loader*/);
|
||||
mVersionCode = in.readLong();
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the installer should be notified that it should query for packages. */
|
||||
public boolean shouldLetInstallerDecide() {
|
||||
return mShouldLetInstallerDecide;
|
||||
}
|
||||
|
||||
public byte[] getDigestBytes() {
|
||||
return mDigest.getDigestBytes()[0];
|
||||
return mDigest.mDigestBytes.length > 0 ? mDigest.getDigestBytes()[0] : EMPTY_DIGEST;
|
||||
}
|
||||
|
||||
public int getDigestPrefix() {
|
||||
@@ -127,11 +192,15 @@ public final class InstantAppResolveInfo implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeBoolean(mShouldLetInstallerDecide);
|
||||
out.writeBundle(mExtras);
|
||||
if (mShouldLetInstallerDecide) {
|
||||
return;
|
||||
}
|
||||
out.writeParcelable(mDigest, flags);
|
||||
out.writeString(mPackageName);
|
||||
out.writeList(mFilters);
|
||||
out.writeLong(mVersionCode);
|
||||
out.writeBundle(mExtras);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<InstantAppResolveInfo> CREATOR
|
||||
@@ -159,7 +228,9 @@ public final class InstantAppResolveInfo implements Parcelable {
|
||||
@SystemApi
|
||||
public static final class InstantAppDigest implements Parcelable {
|
||||
private static final int DIGEST_MASK = 0xfffff000;
|
||||
private static final int DIGEST_PREFIX_COUNT = 5;
|
||||
|
||||
public static final InstantAppDigest UNDEFINED =
|
||||
new InstantAppDigest(new byte[][]{}, new int[]{});
|
||||
/** Full digest of the domain hashes */
|
||||
private final byte[][] mDigestBytes;
|
||||
/** The first 4 bytes of the domain hashes */
|
||||
@@ -186,6 +257,11 @@ public final class InstantAppResolveInfo implements Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
private InstantAppDigest(byte[][] digestBytes, int[] prefix) {
|
||||
this.mDigestPrefix = prefix;
|
||||
this.mDigestBytes = digestBytes;
|
||||
}
|
||||
|
||||
private static byte[][] generateDigest(String hostName, int maxDigests) {
|
||||
ArrayList<byte[]> digests = new ArrayList<>();
|
||||
try {
|
||||
|
||||
@@ -103,11 +103,14 @@ public class ResolverListController {
|
||||
List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null;
|
||||
for (int i = 0, N = intents.size(); i < N; i++) {
|
||||
final Intent intent = intents.get(i);
|
||||
final List<ResolveInfo> infos = mpm.queryIntentActivities(intent,
|
||||
PackageManager.MATCH_DEFAULT_ONLY
|
||||
| (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
|
||||
| (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0)
|
||||
| PackageManager.MATCH_INSTANT);
|
||||
int flags = PackageManager.MATCH_DEFAULT_ONLY
|
||||
| (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
|
||||
| (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0);
|
||||
if (intent.isBrowsableWebIntent()
|
||||
|| (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
|
||||
flags |= PackageManager.MATCH_INSTANT;
|
||||
}
|
||||
final List<ResolveInfo> infos = mpm.queryIntentActivities(intent, flags);
|
||||
// Remove any activities that are not exported.
|
||||
int totalSize = infos.size();
|
||||
for (int j = totalSize - 1; j >= 0 ; j--) {
|
||||
|
||||
@@ -1251,10 +1251,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
|
||||
synchronized (mService) {
|
||||
try {
|
||||
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent");
|
||||
int modifiedFlags = flags
|
||||
| PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS;
|
||||
if (intent.isBrowsableWebIntent()
|
||||
|| (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
|
||||
modifiedFlags |= PackageManager.MATCH_INSTANT;
|
||||
}
|
||||
return mService.getPackageManagerInternalLocked().resolveIntent(
|
||||
intent, resolvedType, PackageManager.MATCH_INSTANT
|
||||
| PackageManager.MATCH_DEFAULT_ONLY | flags
|
||||
| ActivityManagerService.STOCK_PM_FLAGS, userId, true);
|
||||
intent, resolvedType, modifiedFlags, userId, true);
|
||||
|
||||
} finally {
|
||||
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
|
||||
|
||||
@@ -31,6 +31,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
import static android.content.Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
|
||||
@@ -789,7 +790,7 @@ 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.auxiliaryInfo != null) {
|
||||
if (rInfo != null && rInfo.isInstantAppAvailable) {
|
||||
intent = createLaunchIntent(rInfo.auxiliaryInfo, ephemeralIntent,
|
||||
callingPackage, verificationBundle, resolvedType, userId);
|
||||
resolvedType = null;
|
||||
@@ -849,22 +850,27 @@ class ActivityStarter {
|
||||
/**
|
||||
* Creates a launch intent for the given auxiliary resolution data.
|
||||
*/
|
||||
private @NonNull Intent createLaunchIntent(@NonNull AuxiliaryResolveInfo auxiliaryResponse,
|
||||
private @NonNull Intent createLaunchIntent(@Nullable AuxiliaryResolveInfo auxiliaryResponse,
|
||||
Intent originalIntent, String callingPackage, Bundle verificationBundle,
|
||||
String resolvedType, int userId) {
|
||||
if (auxiliaryResponse.needsPhaseTwo) {
|
||||
if (auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo) {
|
||||
// request phase two resolution
|
||||
mService.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo(
|
||||
auxiliaryResponse, originalIntent, resolvedType, callingPackage,
|
||||
verificationBundle, userId);
|
||||
}
|
||||
return InstantAppResolver.buildEphemeralInstallerIntent(
|
||||
Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, originalIntent,
|
||||
auxiliaryResponse.failureIntent, callingPackage, verificationBundle,
|
||||
resolvedType, userId, auxiliaryResponse.packageName, auxiliaryResponse.splitName,
|
||||
auxiliaryResponse.installFailureActivity, auxiliaryResponse.versionCode,
|
||||
auxiliaryResponse.token, auxiliaryResponse.resolveInfo.getExtras(),
|
||||
auxiliaryResponse.needsPhaseTwo);
|
||||
originalIntent,
|
||||
InstantAppResolver.sanitizeIntent(originalIntent),
|
||||
auxiliaryResponse == null ? null : auxiliaryResponse.failureIntent,
|
||||
callingPackage,
|
||||
verificationBundle,
|
||||
resolvedType,
|
||||
userId,
|
||||
auxiliaryResponse == null ? null : auxiliaryResponse.installFailureActivity,
|
||||
auxiliaryResponse == null ? null : auxiliaryResponse.token,
|
||||
auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo,
|
||||
auxiliaryResponse == null ? null : auxiliaryResponse.filters);
|
||||
}
|
||||
|
||||
void postStartActivityProcessing(ActivityRecord r, int result, ActivityStack targetStack) {
|
||||
@@ -924,12 +930,12 @@ class ActivityStarter {
|
||||
// Don't modify the client's object!
|
||||
intent = new Intent(intent);
|
||||
if (componentSpecified
|
||||
&& intent.getData() != null
|
||||
&& Intent.ACTION_VIEW.equals(intent.getAction())
|
||||
&& !Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE.equals(intent.getAction())
|
||||
&& !Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE.equals(intent.getAction())
|
||||
&& mService.getPackageManagerInternalLocked()
|
||||
.isInstantAppInstallerComponent(intent.getComponent())) {
|
||||
// intercept intents targeted directly to the ephemeral installer the
|
||||
// ephemeral installer should never be started with a raw URL; instead
|
||||
// ephemeral installer should never be started with a raw Intent; instead
|
||||
// adjust the intent so it looks like a "normal" instant app launch
|
||||
intent.setComponent(null /*component*/);
|
||||
componentSpecified = false;
|
||||
|
||||
@@ -43,6 +43,7 @@ import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@@ -84,8 +85,8 @@ final class EphemeralResolverConnection implements DeathRecipient {
|
||||
mIntent = new Intent(action).setComponent(componentName);
|
||||
}
|
||||
|
||||
public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(int hashPrefix[],
|
||||
String token) throws ConnectionException {
|
||||
public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(Intent sanitizedIntent,
|
||||
int hashPrefix[], String token) throws ConnectionException {
|
||||
throwIfCalledOnMainThread();
|
||||
IInstantAppResolver target = null;
|
||||
try {
|
||||
@@ -98,7 +99,7 @@ final class EphemeralResolverConnection implements DeathRecipient {
|
||||
}
|
||||
try {
|
||||
return mGetEphemeralResolveInfoCaller
|
||||
.getEphemeralResolveInfoList(target, hashPrefix, token);
|
||||
.getEphemeralResolveInfoList(target, sanitizedIntent, hashPrefix, token);
|
||||
} catch (TimeoutException e) {
|
||||
throw new ConnectionException(ConnectionException.FAILURE_CALL);
|
||||
} catch (RemoteException ignore) {
|
||||
@@ -111,26 +112,22 @@ final class EphemeralResolverConnection implements DeathRecipient {
|
||||
return null;
|
||||
}
|
||||
|
||||
public final void getInstantAppIntentFilterList(int hashPrefix[], String token,
|
||||
String hostName, PhaseTwoCallback callback, Handler callbackHandler,
|
||||
final long startTime) throws ConnectionException {
|
||||
public final void getInstantAppIntentFilterList(Intent sanitizedIntent, int hashPrefix[],
|
||||
String token, PhaseTwoCallback callback, Handler callbackHandler, final long startTime)
|
||||
throws ConnectionException {
|
||||
final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() {
|
||||
@Override
|
||||
public void sendResult(Bundle data) throws RemoteException {
|
||||
final ArrayList<InstantAppResolveInfo> resolveList =
|
||||
data.getParcelableArrayList(
|
||||
InstantAppResolverService.EXTRA_RESOLVE_INFO);
|
||||
callbackHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onPhaseTwoResolved(resolveList, startTime);
|
||||
}
|
||||
});
|
||||
callbackHandler.post(() -> callback.onPhaseTwoResolved(resolveList, startTime));
|
||||
}
|
||||
};
|
||||
try {
|
||||
getRemoteInstanceLazy(token)
|
||||
.getInstantAppIntentFilterList(hashPrefix, token, hostName, remoteCallback);
|
||||
.getInstantAppIntentFilterList(sanitizedIntent, hashPrefix, token,
|
||||
remoteCallback);
|
||||
} catch (TimeoutException e) {
|
||||
throw new ConnectionException(ConnectionException.FAILURE_BIND);
|
||||
} catch (InterruptedException e) {
|
||||
@@ -337,10 +334,11 @@ final class EphemeralResolverConnection implements DeathRecipient {
|
||||
}
|
||||
|
||||
public List<InstantAppResolveInfo> getEphemeralResolveInfoList(
|
||||
IInstantAppResolver target, int hashPrefix[], String token)
|
||||
IInstantAppResolver target, Intent sanitizedIntent, int hashPrefix[], String token)
|
||||
throws RemoteException, TimeoutException {
|
||||
final int sequence = onBeforeRemoteCall();
|
||||
target.getInstantAppResolveInfoList(hashPrefix, token, sequence, mCallback);
|
||||
target.getInstantAppResolveInfoList(sanitizedIntent, hashPrefix, token, sequence,
|
||||
mCallback);
|
||||
return getResultTimed(sequence);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,11 +40,14 @@ import android.content.pm.InstantAppIntentFilter;
|
||||
import android.content.pm.InstantAppResolveInfo;
|
||||
import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
|
||||
import android.metrics.LogMaker;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto;
|
||||
@@ -53,8 +56,12 @@ import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/** @hide */
|
||||
@@ -79,6 +86,7 @@ public abstract class InstantAppResolver {
|
||||
public @interface ResolutionStatus {}
|
||||
|
||||
private static MetricsLogger sMetricsLogger;
|
||||
|
||||
private static MetricsLogger getLogger() {
|
||||
if (sMetricsLogger == null) {
|
||||
sMetricsLogger = new MetricsLogger();
|
||||
@@ -86,26 +94,49 @@ public abstract class InstantAppResolver {
|
||||
return sMetricsLogger;
|
||||
}
|
||||
|
||||
public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(Context context,
|
||||
/**
|
||||
* Returns an intent with potential PII removed from the original intent. Fields removed
|
||||
* include extras and the host + path of the data, if defined.
|
||||
*/
|
||||
public static Intent sanitizeIntent(Intent origIntent) {
|
||||
final Intent sanitizedIntent;
|
||||
sanitizedIntent = new Intent(origIntent.getAction());
|
||||
Set<String> categories = origIntent.getCategories();
|
||||
if (categories != null) {
|
||||
for (String category : categories) {
|
||||
sanitizedIntent.addCategory(category);
|
||||
}
|
||||
}
|
||||
Uri sanitizedUri = origIntent.getData() == null
|
||||
? null
|
||||
: Uri.fromParts(origIntent.getScheme(), "", "");
|
||||
sanitizedIntent.setDataAndType(sanitizedUri, origIntent.getType());
|
||||
sanitizedIntent.addFlags(origIntent.getFlags());
|
||||
sanitizedIntent.setPackage(origIntent.getPackage());
|
||||
return sanitizedIntent;
|
||||
}
|
||||
|
||||
public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(
|
||||
EphemeralResolverConnection connection, InstantAppRequest requestObj) {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final String token = UUID.randomUUID().toString();
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Log.d(TAG, "[" + token + "] Phase1; resolving");
|
||||
}
|
||||
final Intent intent = requestObj.origIntent;
|
||||
final InstantAppDigest digest =
|
||||
new InstantAppDigest(intent.getData().getHost(), 5 /*maxDigests*/);
|
||||
final Intent origIntent = requestObj.origIntent;
|
||||
final Intent sanitizedIntent = sanitizeIntent(origIntent);
|
||||
|
||||
final InstantAppDigest digest = getInstantAppDigest(origIntent);
|
||||
final int[] shaPrefix = digest.getDigestPrefix();
|
||||
AuxiliaryResolveInfo resolveInfo = null;
|
||||
@ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS;
|
||||
try {
|
||||
final List<InstantAppResolveInfo> instantAppResolveInfoList =
|
||||
connection.getInstantAppResolveInfoList(shaPrefix, token);
|
||||
connection.getInstantAppResolveInfoList(sanitizedIntent, shaPrefix, token);
|
||||
if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
|
||||
resolveInfo = InstantAppResolver.filterInstantAppIntent(
|
||||
instantAppResolveInfoList, intent, requestObj.resolvedType,
|
||||
requestObj.userId, intent.getPackage(), digest, token);
|
||||
instantAppResolveInfoList, origIntent, requestObj.resolvedType,
|
||||
requestObj.userId, origIntent.getPackage(), digest, token);
|
||||
}
|
||||
} catch (ConnectionException e) {
|
||||
if (e.failure == ConnectionException.FAILURE_BIND) {
|
||||
@@ -135,6 +166,12 @@ public abstract class InstantAppResolver {
|
||||
return resolveInfo;
|
||||
}
|
||||
|
||||
private static InstantAppDigest getInstantAppDigest(Intent origIntent) {
|
||||
return origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())
|
||||
? new InstantAppDigest(origIntent.getData().getHost(), 5 /*maxDigests*/)
|
||||
: InstantAppDigest.UNDEFINED;
|
||||
}
|
||||
|
||||
public static void doInstantAppResolutionPhaseTwo(Context context,
|
||||
EphemeralResolverConnection connection, InstantAppRequest requestObj,
|
||||
ActivityInfo instantAppInstaller, Handler callbackHandler) {
|
||||
@@ -143,73 +180,53 @@ public abstract class InstantAppResolver {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Log.d(TAG, "[" + token + "] Phase2; resolving");
|
||||
}
|
||||
final Intent intent = requestObj.origIntent;
|
||||
final String hostName = intent.getData().getHost();
|
||||
final InstantAppDigest digest = new InstantAppDigest(hostName, 5 /*maxDigests*/);
|
||||
final Intent origIntent = requestObj.origIntent;
|
||||
final Intent sanitizedIntent = sanitizeIntent(origIntent);
|
||||
final InstantAppDigest digest = getInstantAppDigest(origIntent);
|
||||
final int[] shaPrefix = digest.getDigestPrefix();
|
||||
|
||||
final PhaseTwoCallback callback = new PhaseTwoCallback() {
|
||||
@Override
|
||||
void onPhaseTwoResolved(List<InstantAppResolveInfo> instantAppResolveInfoList,
|
||||
long startTime) {
|
||||
final String packageName;
|
||||
final String splitName;
|
||||
final long versionCode;
|
||||
final Intent failureIntent;
|
||||
final Bundle extras;
|
||||
if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
|
||||
final AuxiliaryResolveInfo instantAppIntentInfo =
|
||||
InstantAppResolver.filterInstantAppIntent(
|
||||
instantAppResolveInfoList, intent, null /*resolvedType*/,
|
||||
0 /*userId*/, intent.getPackage(), digest, token);
|
||||
if (instantAppIntentInfo != null
|
||||
&& instantAppIntentInfo.resolveInfo != null) {
|
||||
packageName = instantAppIntentInfo.resolveInfo.getPackageName();
|
||||
splitName = instantAppIntentInfo.splitName;
|
||||
versionCode = instantAppIntentInfo.resolveInfo.getVersionCode();
|
||||
instantAppResolveInfoList, origIntent, null /*resolvedType*/,
|
||||
0 /*userId*/, origIntent.getPackage(), digest, token);
|
||||
if (instantAppIntentInfo != null) {
|
||||
failureIntent = instantAppIntentInfo.failureIntent;
|
||||
extras = instantAppIntentInfo.resolveInfo.getExtras();
|
||||
} else {
|
||||
packageName = null;
|
||||
splitName = null;
|
||||
versionCode = -1;
|
||||
failureIntent = null;
|
||||
extras = null;
|
||||
}
|
||||
} else {
|
||||
packageName = null;
|
||||
splitName = null;
|
||||
versionCode = -1;
|
||||
failureIntent = null;
|
||||
extras = null;
|
||||
}
|
||||
final Intent installerIntent = buildEphemeralInstallerIntent(
|
||||
Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE,
|
||||
requestObj.origIntent,
|
||||
sanitizedIntent,
|
||||
failureIntent,
|
||||
requestObj.callingPackage,
|
||||
requestObj.verificationBundle,
|
||||
requestObj.resolvedType,
|
||||
requestObj.userId,
|
||||
packageName,
|
||||
splitName,
|
||||
requestObj.responseObj.installFailureActivity,
|
||||
versionCode,
|
||||
token,
|
||||
extras,
|
||||
false /*needsPhaseTwo*/);
|
||||
false /*needsPhaseTwo*/,
|
||||
requestObj.responseObj.filters);
|
||||
installerIntent.setComponent(new ComponentName(
|
||||
instantAppInstaller.packageName, instantAppInstaller.name));
|
||||
|
||||
logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token,
|
||||
packageName != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE);
|
||||
requestObj.responseObj.filters != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE);
|
||||
|
||||
context.startActivity(installerIntent);
|
||||
}
|
||||
};
|
||||
try {
|
||||
connection.getInstantAppIntentFilterList(
|
||||
shaPrefix, token, hostName, callback, callbackHandler, startTime);
|
||||
connection.getInstantAppIntentFilterList(sanitizedIntent, shaPrefix, token, callback,
|
||||
callbackHandler, startTime);
|
||||
} catch (ConnectionException e) {
|
||||
@ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE;
|
||||
if (e.failure == ConnectionException.FAILURE_BIND) {
|
||||
@@ -231,23 +248,20 @@ public abstract class InstantAppResolver {
|
||||
* Builds and returns an intent to launch the instant installer.
|
||||
*/
|
||||
public static Intent buildEphemeralInstallerIntent(
|
||||
@NonNull String action,
|
||||
@NonNull Intent origIntent,
|
||||
@NonNull Intent failureIntent,
|
||||
@NonNull Intent sanitizedIntent,
|
||||
@Nullable Intent failureIntent,
|
||||
@NonNull String callingPackage,
|
||||
@Nullable Bundle verificationBundle,
|
||||
@NonNull String resolvedType,
|
||||
int userId,
|
||||
@NonNull String instantAppPackageName,
|
||||
@Nullable String instantAppSplitName,
|
||||
@Nullable ComponentName installFailureActivity,
|
||||
long versionCode,
|
||||
@Nullable String token,
|
||||
@Nullable Bundle extras,
|
||||
boolean needsPhaseTwo) {
|
||||
boolean needsPhaseTwo,
|
||||
List<AuxiliaryResolveInfo.AuxiliaryFilter> filters) {
|
||||
// Construct the intent that launches the instant installer
|
||||
int flags = origIntent.getFlags();
|
||||
final Intent intent = new Intent(action);
|
||||
final Intent intent = new Intent();
|
||||
intent.setFlags(flags
|
||||
| Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
@@ -260,20 +274,23 @@ public abstract class InstantAppResolver {
|
||||
intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost());
|
||||
}
|
||||
intent.putExtra(Intent.EXTRA_INSTANT_APP_ACTION, origIntent.getAction());
|
||||
if (extras != null) {
|
||||
intent.putExtra(Intent.EXTRA_INSTANT_APP_EXTRAS, extras);
|
||||
}
|
||||
intent.putExtra(Intent.EXTRA_INTENT, sanitizedIntent);
|
||||
|
||||
// We have all of the data we need; just start the installer without a second phase
|
||||
if (!needsPhaseTwo) {
|
||||
// Intent that is launched if the package couldn't be installed for any reason.
|
||||
if (needsPhaseTwo) {
|
||||
intent.setAction(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE);
|
||||
} else {
|
||||
// We have all of the data we need; just start the installer without a second phase
|
||||
if (failureIntent != null || installFailureActivity != null) {
|
||||
// Intent that is launched if the package couldn't be installed for any reason.
|
||||
try {
|
||||
final Intent onFailureIntent;
|
||||
if (installFailureActivity != null) {
|
||||
onFailureIntent = new Intent();
|
||||
onFailureIntent.setComponent(installFailureActivity);
|
||||
onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName);
|
||||
if (filters != null && filters.size() == 1) {
|
||||
onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME,
|
||||
filters.get(0).splitName);
|
||||
}
|
||||
onFailureIntent.putExtra(Intent.EXTRA_INTENT, origIntent);
|
||||
} else {
|
||||
onFailureIntent = failureIntent;
|
||||
@@ -309,17 +326,35 @@ public abstract class InstantAppResolver {
|
||||
intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS,
|
||||
new IntentSender(successIntentTarget));
|
||||
} catch (RemoteException ignore) { /* ignore; same process */ }
|
||||
|
||||
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, instantAppPackageName);
|
||||
intent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName);
|
||||
intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) (versionCode & 0x7fffffff));
|
||||
intent.putExtra(Intent.EXTRA_LONG_VERSION_CODE, versionCode);
|
||||
intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage);
|
||||
if (verificationBundle != null) {
|
||||
intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle);
|
||||
}
|
||||
}
|
||||
intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage);
|
||||
|
||||
if (filters != null) {
|
||||
Bundle resolvableFilters[] = new Bundle[filters.size()];
|
||||
for (int i = 0, max = filters.size(); i < max; i++) {
|
||||
Bundle resolvableFilter = new Bundle();
|
||||
AuxiliaryResolveInfo.AuxiliaryFilter filter = filters.get(i);
|
||||
resolvableFilter.putBoolean(Intent.EXTRA_UNKNOWN_INSTANT_APP,
|
||||
filter.resolveInfo != null
|
||||
&& filter.resolveInfo.shouldLetInstallerDecide());
|
||||
resolvableFilter.putString(Intent.EXTRA_PACKAGE_NAME, filter.packageName);
|
||||
resolvableFilter.putString(Intent.EXTRA_SPLIT_NAME, filter.splitName);
|
||||
resolvableFilter.putLong(Intent.EXTRA_LONG_VERSION_CODE, filter.versionCode);
|
||||
resolvableFilter.putBundle(Intent.EXTRA_INSTANT_APP_EXTRAS, filter.extras);
|
||||
resolvableFilters[i] = resolvableFilter;
|
||||
if (i == 0) {
|
||||
// for backwards compat, always set the first result on the intent and add
|
||||
// the int version code
|
||||
intent.putExtras(resolvableFilter);
|
||||
intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) filter.versionCode);
|
||||
}
|
||||
}
|
||||
intent.putExtra(Intent.EXTRA_INSTANT_APP_BUNDLES, resolvableFilters);
|
||||
}
|
||||
intent.setAction(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE);
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
|
||||
@@ -330,69 +365,134 @@ public abstract class InstantAppResolver {
|
||||
final int[] shaPrefix = digest.getDigestPrefix();
|
||||
final byte[][] digestBytes = digest.getDigestBytes();
|
||||
final Intent failureIntent = new Intent(origIntent);
|
||||
boolean requiresSecondPhase = false;
|
||||
failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL);
|
||||
failureIntent.setLaunchToken(token);
|
||||
// Go in reverse order so we match the narrowest scope first.
|
||||
for (int i = shaPrefix.length - 1; i >= 0 ; --i) {
|
||||
for (InstantAppResolveInfo instantAppInfo : instantAppResolveInfoList) {
|
||||
if (!Arrays.equals(digestBytes[i], instantAppInfo.getDigestBytes())) {
|
||||
ArrayList<AuxiliaryResolveInfo.AuxiliaryFilter> filters = null;
|
||||
boolean isWebIntent = origIntent.isBrowsableWebIntent();
|
||||
for (InstantAppResolveInfo instantAppResolveInfo : instantAppResolveInfoList) {
|
||||
if (shaPrefix.length > 0 && instantAppResolveInfo.shouldLetInstallerDecide()) {
|
||||
Slog.e(TAG, "InstantAppResolveInfo with mShouldLetInstallerDecide=true when digest"
|
||||
+ " provided; ignoring");
|
||||
continue;
|
||||
}
|
||||
byte[] filterDigestBytes = instantAppResolveInfo.getDigestBytes();
|
||||
// Only include matching digests if we have a prefix and we're either dealing with a
|
||||
// web intent or the resolveInfo specifies digest details.
|
||||
if (shaPrefix.length > 0 && (isWebIntent || filterDigestBytes.length > 0)) {
|
||||
boolean matchFound = false;
|
||||
// Go in reverse order so we match the narrowest scope first.
|
||||
for (int i = shaPrefix.length - 1; i >= 0; --i) {
|
||||
if (Arrays.equals(digestBytes[i], filterDigestBytes)) {
|
||||
matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchFound) {
|
||||
continue;
|
||||
}
|
||||
if (packageName != null
|
||||
&& !packageName.equals(instantAppInfo.getPackageName())) {
|
||||
continue;
|
||||
}
|
||||
// We matched a resolve info; resolve the filters to see if anything matches completely.
|
||||
List<AuxiliaryResolveInfo.AuxiliaryFilter> matchFilters = computeResolveFilters(
|
||||
origIntent, resolvedType, userId, packageName, token, instantAppResolveInfo);
|
||||
if (matchFilters != null) {
|
||||
if (matchFilters.isEmpty()) {
|
||||
requiresSecondPhase = true;
|
||||
}
|
||||
final List<InstantAppIntentFilter> instantAppFilters =
|
||||
instantAppInfo.getIntentFilters();
|
||||
// No filters; we need to start phase two
|
||||
if (instantAppFilters == null || instantAppFilters.isEmpty()) {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Log.d(TAG, "No app filters; go to phase 2");
|
||||
}
|
||||
return new AuxiliaryResolveInfo(instantAppInfo,
|
||||
new IntentFilter(Intent.ACTION_VIEW) /*intentFilter*/,
|
||||
null /*splitName*/, token, true /*needsPhase2*/,
|
||||
null /*failureIntent*/);
|
||||
}
|
||||
// We have a domain match; resolve the filters to see if anything matches.
|
||||
final PackageManagerService.EphemeralIntentResolver instantAppResolver =
|
||||
new PackageManagerService.EphemeralIntentResolver();
|
||||
for (int j = instantAppFilters.size() - 1; j >= 0; --j) {
|
||||
final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j);
|
||||
final List<IntentFilter> splitFilters = instantAppFilter.getFilters();
|
||||
if (splitFilters == null || splitFilters.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (int k = splitFilters.size() - 1; k >= 0; --k) {
|
||||
final AuxiliaryResolveInfo intentInfo =
|
||||
new AuxiliaryResolveInfo(instantAppInfo,
|
||||
splitFilters.get(k), instantAppFilter.getSplitName(),
|
||||
token, false /*needsPhase2*/, failureIntent);
|
||||
instantAppResolver.addFilter(intentInfo);
|
||||
}
|
||||
}
|
||||
List<AuxiliaryResolveInfo> matchedResolveInfoList = instantAppResolver.queryIntent(
|
||||
origIntent, resolvedType, false /*defaultOnly*/, userId);
|
||||
if (!matchedResolveInfoList.isEmpty()) {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
final AuxiliaryResolveInfo info = matchedResolveInfoList.get(0);
|
||||
Log.d(TAG, "[" + token + "] Found match;"
|
||||
+ " package: " + info.packageName
|
||||
+ ", split: " + info.splitName
|
||||
+ ", versionCode: " + info.versionCode);
|
||||
}
|
||||
return matchedResolveInfoList.get(0);
|
||||
} else if (DEBUG_EPHEMERAL) {
|
||||
Log.d(TAG, "[" + token + "] No matches found"
|
||||
+ " package: " + instantAppInfo.getPackageName()
|
||||
+ ", versionCode: " + instantAppInfo.getVersionCode());
|
||||
if (filters == null) {
|
||||
filters = new ArrayList<>(matchFilters);
|
||||
} else {
|
||||
filters.addAll(matchFilters);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filters != null && !filters.isEmpty()) {
|
||||
return new AuxiliaryResolveInfo(token, requiresSecondPhase, failureIntent, filters);
|
||||
}
|
||||
// Hash or filter mis-match; no instant apps for this domain.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one of three states: <p/>
|
||||
* <ul>
|
||||
* <li>{@code null} if there are no matches will not be; resolution is unnecessary.</li>
|
||||
* <li>An empty list signifying that a 2nd phase of resolution is required.</li>
|
||||
* <li>A populated list meaning that matches were found and should be sent directly to the
|
||||
* installer</li>
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
private static List<AuxiliaryResolveInfo.AuxiliaryFilter> computeResolveFilters(
|
||||
Intent origIntent, String resolvedType, int userId, String packageName, String token,
|
||||
InstantAppResolveInfo instantAppInfo) {
|
||||
if (instantAppInfo.shouldLetInstallerDecide()) {
|
||||
return Collections.singletonList(
|
||||
new AuxiliaryResolveInfo.AuxiliaryFilter(
|
||||
instantAppInfo, null /* splitName */,
|
||||
instantAppInfo.getExtras()));
|
||||
}
|
||||
if (packageName != null
|
||||
&& !packageName.equals(instantAppInfo.getPackageName())) {
|
||||
return null;
|
||||
}
|
||||
final List<InstantAppIntentFilter> instantAppFilters =
|
||||
instantAppInfo.getIntentFilters();
|
||||
if (instantAppFilters == null || instantAppFilters.isEmpty()) {
|
||||
// No filters on web intent; no matches, 2nd phase unnecessary.
|
||||
if (origIntent.isBrowsableWebIntent()) {
|
||||
return null;
|
||||
}
|
||||
// No filters; we need to start phase two
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Log.d(TAG, "No app filters; go to phase 2");
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final PackageManagerService.EphemeralIntentResolver instantAppResolver =
|
||||
new PackageManagerService.EphemeralIntentResolver();
|
||||
for (int j = instantAppFilters.size() - 1; j >= 0; --j) {
|
||||
final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j);
|
||||
final List<IntentFilter> splitFilters = instantAppFilter.getFilters();
|
||||
if (splitFilters == null || splitFilters.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (int k = splitFilters.size() - 1; k >= 0; --k) {
|
||||
IntentFilter filter = splitFilters.get(k);
|
||||
Iterator<IntentFilter.AuthorityEntry> authorities =
|
||||
filter.authoritiesIterator();
|
||||
// ignore http/s-only filters.
|
||||
if ((authorities == null || !authorities.hasNext())
|
||||
&& (filter.hasDataScheme("http") || filter.hasDataScheme("https"))
|
||||
&& filter.hasAction(Intent.ACTION_VIEW)
|
||||
&& filter.hasCategory(Intent.CATEGORY_BROWSABLE)) {
|
||||
continue;
|
||||
}
|
||||
instantAppResolver.addFilter(
|
||||
new AuxiliaryResolveInfo.AuxiliaryFilter(
|
||||
filter,
|
||||
instantAppInfo,
|
||||
instantAppFilter.getSplitName(),
|
||||
instantAppInfo.getExtras()
|
||||
));
|
||||
}
|
||||
}
|
||||
List<AuxiliaryResolveInfo.AuxiliaryFilter> matchedResolveInfoList =
|
||||
instantAppResolver.queryIntent(
|
||||
origIntent, resolvedType, false /*defaultOnly*/, userId);
|
||||
if (!matchedResolveInfoList.isEmpty()) {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList);
|
||||
}
|
||||
return matchedResolveInfoList;
|
||||
} else if (DEBUG_EPHEMERAL) {
|
||||
Log.d(TAG, "[" + token + "] No matches found"
|
||||
+ " package: " + instantAppInfo.getPackageName()
|
||||
+ ", versionCode: " + instantAppInfo.getVersionCode());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void logMetrics(int action, long startTime, String token,
|
||||
@ResolutionStatus int status) {
|
||||
final LogMaker logMaker = new LogMaker(action)
|
||||
|
||||
@@ -582,6 +582,7 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
sBrowserIntent.setAction(Intent.ACTION_VIEW);
|
||||
sBrowserIntent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
sBrowserIntent.setData(Uri.parse("http:"));
|
||||
sBrowserIntent.addFlags(Intent.FLAG_IGNORE_EPHEMERAL);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3594,24 +3595,35 @@ Slog.e("TODD",
|
||||
}
|
||||
|
||||
private @Nullable ActivityInfo getInstantAppInstallerLPr() {
|
||||
final Intent intent = new Intent(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE);
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE);
|
||||
String[] orderedActions = Build.IS_ENG
|
||||
? new String[]{
|
||||
Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE + "_TEST",
|
||||
Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE,
|
||||
Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE}
|
||||
: new String[]{
|
||||
Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE,
|
||||
Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE};
|
||||
|
||||
final int resolveFlags =
|
||||
MATCH_DIRECT_BOOT_AWARE
|
||||
| MATCH_DIRECT_BOOT_UNAWARE
|
||||
| (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0);
|
||||
List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE,
|
||||
resolveFlags, UserHandle.USER_SYSTEM);
|
||||
// temporarily look for the old action
|
||||
if (matches.isEmpty()) {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Slog.d(TAG, "Ephemeral installer not found with new action; try old one");
|
||||
}
|
||||
intent.setAction(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE);
|
||||
| MATCH_DIRECT_BOOT_UNAWARE
|
||||
| Intent.FLAG_IGNORE_EPHEMERAL
|
||||
| (!Build.IS_ENG ? MATCH_SYSTEM_ONLY : 0);
|
||||
final Intent intent = new Intent();
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE);
|
||||
List<ResolveInfo> matches = null;
|
||||
for (String action : orderedActions) {
|
||||
intent.setAction(action);
|
||||
matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE,
|
||||
resolveFlags, UserHandle.USER_SYSTEM);
|
||||
if (matches.isEmpty()) {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Slog.d(TAG, "Instant App installer not found with " + action);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Iterator<ResolveInfo> iter = matches.iterator();
|
||||
while (iter.hasNext()) {
|
||||
@@ -3619,7 +3631,8 @@ Slog.e("TODD",
|
||||
final PackageSetting ps = mSettings.mPackages.get(rInfo.activityInfo.packageName);
|
||||
if (ps != null) {
|
||||
final PermissionsState permissionsState = ps.getPermissionsState();
|
||||
if (permissionsState.hasPermission(Manifest.permission.INSTALL_PACKAGES, 0)) {
|
||||
if (permissionsState.hasPermission(Manifest.permission.INSTALL_PACKAGES, 0)
|
||||
|| Build.IS_ENG) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -4778,10 +4791,7 @@ Slog.e("TODD",
|
||||
flags |= PackageManager.MATCH_INSTANT;
|
||||
} else {
|
||||
final boolean wantMatchInstant = (flags & PackageManager.MATCH_INSTANT) != 0;
|
||||
final boolean allowMatchInstant =
|
||||
(wantInstantApps
|
||||
&& Intent.ACTION_VIEW.equals(intent.getAction())
|
||||
&& hasWebURI(intent))
|
||||
final boolean allowMatchInstant = wantInstantApps
|
||||
|| (wantMatchInstant && canViewInstantApps(callingUid, userId));
|
||||
flags &= ~(PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY
|
||||
| PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY);
|
||||
@@ -5971,8 +5981,14 @@ Slog.e("TODD",
|
||||
if (!skipPackageCheck && intent.getPackage() != null) {
|
||||
return false;
|
||||
}
|
||||
final boolean isWebUri = hasWebURI(intent);
|
||||
if (!isWebUri || intent.getData().getHost() == null) {
|
||||
if (!intent.isBrowsableWebIntent()) {
|
||||
// for non web intents, we should not resolve externally if an app already exists to
|
||||
// handle it or if the caller didn't explicitly request it.
|
||||
if ((resolvedActivities != null && resolvedActivities.size() != 0)
|
||||
|| (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) == 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (intent.getData() == null || TextUtils.isEmpty(intent.getData().getHost())) {
|
||||
return false;
|
||||
}
|
||||
// Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution.
|
||||
@@ -6370,7 +6386,7 @@ Slog.e("TODD",
|
||||
if (matches.get(i).getTargetUserId() == targetUserId) return true;
|
||||
}
|
||||
}
|
||||
if (hasWebURI(intent)) {
|
||||
if (intent.hasWebURI()) {
|
||||
// cross-profile app linking works only towards the parent.
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final UserInfo parent = getProfileParent(sourceUserId);
|
||||
@@ -6545,7 +6561,7 @@ Slog.e("TODD",
|
||||
sortResult = true;
|
||||
}
|
||||
}
|
||||
if (hasWebURI(intent)) {
|
||||
if (intent.hasWebURI()) {
|
||||
CrossProfileDomainInfo xpDomainInfo = null;
|
||||
final UserInfo parent = getProfileParent(userId);
|
||||
if (parent != null) {
|
||||
@@ -6631,7 +6647,6 @@ Slog.e("TODD",
|
||||
if (ps.getInstantApp(userId)) {
|
||||
final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
|
||||
final int status = (int)(packedStatus >> 32);
|
||||
final int linkGeneration = (int)(packedStatus & 0xFFFFFFFF);
|
||||
if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
|
||||
// there's a local instant application installed, but, the user has
|
||||
// chosen to never use it; skip resolution and don't acknowledge
|
||||
@@ -6663,9 +6678,8 @@ Slog.e("TODD",
|
||||
null /*responseObj*/, intent /*origIntent*/, resolvedType,
|
||||
null /*callingPackage*/, userId, null /*verificationBundle*/,
|
||||
resolveForStart);
|
||||
auxiliaryResponse =
|
||||
InstantAppResolver.doInstantAppResolutionPhaseOne(
|
||||
mContext, mInstantAppResolverConnection, requestObject);
|
||||
auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne(
|
||||
mInstantAppResolverConnection, requestObject);
|
||||
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
|
||||
} else {
|
||||
// we have an instant application locally, but, we can't admit that since
|
||||
@@ -6674,35 +6688,40 @@ Slog.e("TODD",
|
||||
// instant application available externally. when it comes time to start
|
||||
// the instant application, we'll do the right thing.
|
||||
final ApplicationInfo ai = localInstantApp.activityInfo.applicationInfo;
|
||||
auxiliaryResponse = new AuxiliaryResolveInfo(
|
||||
ai.packageName, null /*splitName*/, null /*failureActivity*/,
|
||||
ai.versionCode, null /*failureIntent*/);
|
||||
auxiliaryResponse = new AuxiliaryResolveInfo(null /* failureActivity */,
|
||||
ai.packageName, ai.versionCode, null /* splitName */);
|
||||
}
|
||||
}
|
||||
if (auxiliaryResponse != null) {
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
|
||||
}
|
||||
final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo);
|
||||
final PackageSetting ps =
|
||||
mSettings.mPackages.get(mInstantAppInstallerActivity.packageName);
|
||||
if (ps != null) {
|
||||
ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo(
|
||||
mInstantAppInstallerActivity, 0, ps.readUserState(userId), userId);
|
||||
ephemeralInstaller.activityInfo.launchToken = auxiliaryResponse.token;
|
||||
ephemeralInstaller.auxiliaryInfo = auxiliaryResponse;
|
||||
// make sure this resolver is the default
|
||||
ephemeralInstaller.isDefault = true;
|
||||
ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
|
||||
| IntentFilter.MATCH_ADJUSTMENT_NORMAL;
|
||||
// add a non-generic filter
|
||||
ephemeralInstaller.filter = new IntentFilter(intent.getAction());
|
||||
ephemeralInstaller.filter.addDataPath(
|
||||
intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL);
|
||||
ephemeralInstaller.isInstantAppAvailable = true;
|
||||
result.add(ephemeralInstaller);
|
||||
}
|
||||
if (intent.isBrowsableWebIntent() && auxiliaryResponse == null) {
|
||||
return result;
|
||||
}
|
||||
final PackageSetting ps = mSettings.mPackages.get(mInstantAppInstallerActivity.packageName);
|
||||
if (ps == null) {
|
||||
return result;
|
||||
}
|
||||
final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo);
|
||||
ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo(
|
||||
mInstantAppInstallerActivity, 0, ps.readUserState(userId), userId);
|
||||
ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
|
||||
| IntentFilter.MATCH_ADJUSTMENT_NORMAL;
|
||||
// add a non-generic filter
|
||||
ephemeralInstaller.filter = new IntentFilter();
|
||||
if (intent.getAction() != null) {
|
||||
ephemeralInstaller.filter.addAction(intent.getAction());
|
||||
}
|
||||
if (intent.getData() != null && intent.getData().getPath() != null) {
|
||||
ephemeralInstaller.filter.addDataPath(
|
||||
intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL);
|
||||
}
|
||||
ephemeralInstaller.isInstantAppAvailable = true;
|
||||
// make sure this resolver is the default
|
||||
ephemeralInstaller.isDefault = true;
|
||||
ephemeralInstaller.auxiliaryInfo = auxiliaryResponse;
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
|
||||
}
|
||||
|
||||
result.add(ephemeralInstaller);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -6817,10 +6836,11 @@ Slog.e("TODD",
|
||||
final ResolveInfo info = resolveInfos.get(i);
|
||||
// allow activities that are defined in the provided package
|
||||
if (allowDynamicSplits
|
||||
&& info.activityInfo != null
|
||||
&& info.activityInfo.splitName != null
|
||||
&& !ArrayUtils.contains(info.activityInfo.applicationInfo.splitNames,
|
||||
info.activityInfo.splitName)) {
|
||||
if (mInstantAppInstallerInfo == null) {
|
||||
if (mInstantAppInstallerActivity == null) {
|
||||
if (DEBUG_INSTALL) {
|
||||
Slog.v(TAG, "No installer - not adding it to the ResolveInfo list");
|
||||
}
|
||||
@@ -6832,14 +6852,15 @@ Slog.e("TODD",
|
||||
if (DEBUG_INSTALL) {
|
||||
Slog.v(TAG, "Adding installer to the ResolveInfo list");
|
||||
}
|
||||
final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo);
|
||||
final ResolveInfo installerInfo = new ResolveInfo(
|
||||
mInstantAppInstallerInfo);
|
||||
final ComponentName installFailureActivity = findInstallFailureActivity(
|
||||
info.activityInfo.packageName, filterCallingUid, userId);
|
||||
installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
|
||||
info.activityInfo.packageName, info.activityInfo.splitName,
|
||||
installFailureActivity,
|
||||
info.activityInfo.packageName,
|
||||
info.activityInfo.applicationInfo.versionCode,
|
||||
null /*failureIntent*/);
|
||||
info.activityInfo.splitName);
|
||||
installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
|
||||
| IntentFilter.MATCH_ADJUSTMENT_NORMAL;
|
||||
// add a non-generic filter
|
||||
@@ -6856,6 +6877,7 @@ Slog.e("TODD",
|
||||
installerInfo.priority = info.priority;
|
||||
installerInfo.preferredOrder = info.preferredOrder;
|
||||
installerInfo.isDefault = info.isDefault;
|
||||
installerInfo.isInstantAppAvailable = true;
|
||||
resolveInfos.set(i, installerInfo);
|
||||
continue;
|
||||
}
|
||||
@@ -6913,17 +6935,6 @@ Slog.e("TODD",
|
||||
return resolveInfos.size() > 0 && resolveInfos.get(0).priority >= 0;
|
||||
}
|
||||
|
||||
private static boolean hasWebURI(Intent intent) {
|
||||
if (intent.getData() == null) {
|
||||
return false;
|
||||
}
|
||||
final String scheme = intent.getScheme();
|
||||
if (TextUtils.isEmpty(scheme)) {
|
||||
return false;
|
||||
}
|
||||
return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS);
|
||||
}
|
||||
|
||||
private List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent,
|
||||
int matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo,
|
||||
int userId) {
|
||||
@@ -7596,11 +7607,13 @@ Slog.e("TODD",
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
|
||||
}
|
||||
final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo);
|
||||
final ResolveInfo installerInfo = new ResolveInfo(
|
||||
mInstantAppInstallerInfo);
|
||||
installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
|
||||
info.serviceInfo.packageName, info.serviceInfo.splitName,
|
||||
null /*failureActivity*/, info.serviceInfo.applicationInfo.versionCode,
|
||||
null /*failureIntent*/);
|
||||
null /* installFailureActivity */,
|
||||
info.serviceInfo.packageName,
|
||||
info.serviceInfo.applicationInfo.versionCode,
|
||||
info.serviceInfo.splitName);
|
||||
// make sure this resolver is the default
|
||||
installerInfo.isDefault = true;
|
||||
installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
|
||||
@@ -7716,11 +7729,13 @@ Slog.e("TODD",
|
||||
if (DEBUG_EPHEMERAL) {
|
||||
Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
|
||||
}
|
||||
final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo);
|
||||
final ResolveInfo installerInfo = new ResolveInfo(
|
||||
mInstantAppInstallerInfo);
|
||||
installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
|
||||
info.providerInfo.packageName, info.providerInfo.splitName,
|
||||
null /*failureActivity*/, info.providerInfo.applicationInfo.versionCode,
|
||||
null /*failureIntent*/);
|
||||
null /*failureActivity*/,
|
||||
info.providerInfo.packageName,
|
||||
info.providerInfo.applicationInfo.versionCode,
|
||||
info.providerInfo.splitName);
|
||||
// make sure this resolver is the default
|
||||
installerInfo.isDefault = true;
|
||||
installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
|
||||
@@ -11794,7 +11809,7 @@ Slog.e("TODD",
|
||||
mInstantAppInstallerActivity.exported = true;
|
||||
mInstantAppInstallerActivity.enabled = true;
|
||||
mInstantAppInstallerInfo.activityInfo = mInstantAppInstallerActivity;
|
||||
mInstantAppInstallerInfo.priority = 0;
|
||||
mInstantAppInstallerInfo.priority = 1;
|
||||
mInstantAppInstallerInfo.preferredOrder = 1;
|
||||
mInstantAppInstallerInfo.isDefault = true;
|
||||
mInstantAppInstallerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
|
||||
@@ -13244,7 +13259,8 @@ Slog.e("TODD",
|
||||
}
|
||||
|
||||
static final class EphemeralIntentResolver
|
||||
extends IntentResolver<AuxiliaryResolveInfo, AuxiliaryResolveInfo> {
|
||||
extends IntentResolver<AuxiliaryResolveInfo.AuxiliaryFilter,
|
||||
AuxiliaryResolveInfo.AuxiliaryFilter> {
|
||||
/**
|
||||
* 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
|
||||
@@ -13259,18 +13275,19 @@ Slog.e("TODD",
|
||||
final ArrayMap<String, Pair<Integer, InstantAppResolveInfo>> mOrderResult = new ArrayMap<>();
|
||||
|
||||
@Override
|
||||
protected AuxiliaryResolveInfo[] newArray(int size) {
|
||||
return new AuxiliaryResolveInfo[size];
|
||||
protected AuxiliaryResolveInfo.AuxiliaryFilter[] newArray(int size) {
|
||||
return new AuxiliaryResolveInfo.AuxiliaryFilter[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isPackageForFilter(String packageName, AuxiliaryResolveInfo responseObj) {
|
||||
protected boolean isPackageForFilter(String packageName,
|
||||
AuxiliaryResolveInfo.AuxiliaryFilter responseObj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AuxiliaryResolveInfo newResult(AuxiliaryResolveInfo responseObj, int match,
|
||||
int userId) {
|
||||
protected AuxiliaryResolveInfo.AuxiliaryFilter newResult(
|
||||
AuxiliaryResolveInfo.AuxiliaryFilter responseObj, int match, int userId) {
|
||||
if (!sUserManager.exists(userId)) {
|
||||
return null;
|
||||
}
|
||||
@@ -13291,7 +13308,7 @@ Slog.e("TODD",
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void filterResults(List<AuxiliaryResolveInfo> results) {
|
||||
protected void filterResults(List<AuxiliaryResolveInfo.AuxiliaryFilter> results) {
|
||||
// only do work if ordering is enabled [most of the time it won't be]
|
||||
if (mOrderResult.size() == 0) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user