Allow ephemeral provider/installer
The ephemeral provider is a service that simply determines whether or
not an ephemeral application is available. The ephemeral installer
does the heavy lifting of installing the ephemeral application.
Bug: 25119046
Change-Id: I591f4c2c3f2b149d299fa8b4f359f2582d9199cb
diff --git a/Android.mk b/Android.mk
index 71bba0f..cc0749c5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -281,6 +281,7 @@
core/java/com/android/internal/app/IAppOpsService.aidl \
core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl \
core/java/com/android/internal/app/IBatteryStats.aidl \
+ core/java/com/android/internal/app/IEphemeralResolver.aidl \
core/java/com/android/internal/app/IProcessStats.aidl \
core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl \
diff --git a/api/system-current.txt b/api/system-current.txt
index 65ecdc1..485cef7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8489,6 +8489,7 @@
field public static final java.lang.String ACTION_INPUT_METHOD_CHANGED = "android.intent.action.INPUT_METHOD_CHANGED";
field public static final java.lang.String ACTION_INSERT = "android.intent.action.INSERT";
field public static final java.lang.String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
+ field public static final java.lang.String ACTION_INSTALL_EPHEMERAL_PACKAGE = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE";
field public static final java.lang.String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
field public static final java.lang.String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
field public static final java.lang.String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED";
@@ -8536,6 +8537,7 @@
field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
field public static final java.lang.String ACTION_QUICK_CLOCK = "android.intent.action.QUICK_CLOCK";
field public static final java.lang.String ACTION_REBOOT = "android.intent.action.REBOOT";
+ field public static final java.lang.String ACTION_RESOLVE_EPHEMERAL_PACKAGE = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
field public static final java.lang.String ACTION_RUN = "android.intent.action.RUN";
field public static final java.lang.String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
field public static final java.lang.String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON";
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 30fe531..9d941fd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1426,6 +1426,36 @@
public static final String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
/**
+ * Activity Action: Launch ephemeral installer.
+ * <p>
+ * Input: The data must be a http: URI that the ephemeral application is registered
+ * to handle.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE
+ = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE";
+
+ /**
+ * Service Action: Resolve ephemeral application.
+ * <p>
+ * The system will have a persistent connection to this service.
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE
+ = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
+
+ /**
* Used as a string extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
* package. Specifies the installer package name; this package will receive the
* {@link #ACTION_APP_ERROR} intent.
diff --git a/core/java/com/android/internal/app/EphemeralResolveInfo.aidl b/core/java/com/android/internal/app/EphemeralResolveInfo.aidl
new file mode 100644
index 0000000..529527b
--- /dev/null
+++ b/core/java/com/android/internal/app/EphemeralResolveInfo.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2015, 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.internal.app;
+
+parcelable EphemeralResolveInfo;
diff --git a/core/java/com/android/internal/app/EphemeralResolveInfo.java b/core/java/com/android/internal/app/EphemeralResolveInfo.java
new file mode 100644
index 0000000..0e7ef05
--- /dev/null
+++ b/core/java/com/android/internal/app/EphemeralResolveInfo.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 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.internal.app;
+
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information that is returned when resolving ephemeral
+ * applications.
+ */
+public final class EphemeralResolveInfo implements Parcelable {
+ public static final String SHA_ALGORITHM = "SHA-256";
+ private byte[] mDigestBytes;
+ private int mDigestPrefix;
+ private final List<IntentFilter> mFilters = new ArrayList<IntentFilter>();
+
+ public EphemeralResolveInfo(Uri uri, List<IntentFilter> filters) {
+ generateDigest(uri);
+ mFilters.addAll(filters);
+ }
+
+ private EphemeralResolveInfo(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public byte[] getDigestBytes() {
+ return mDigestBytes;
+ }
+
+ public int getDigestPrefix() {
+ return mDigestPrefix;
+ }
+
+ public List<IntentFilter> getFilters() {
+ return mFilters;
+ }
+
+ private void generateDigest(Uri uri) {
+ try {
+ final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM);
+ final byte[] hostBytes = uri.getHost().getBytes();
+ final byte[] digestBytes = digest.digest(hostBytes);
+ mDigestBytes = digestBytes;
+ mDigestPrefix =
+ digestBytes[0] << 24
+ | digestBytes[1] << 16
+ | digestBytes[2] << 8
+ | digestBytes[3] << 0;
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("could not find digest algorithm");
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (mDigestBytes == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(mDigestBytes.length);
+ out.writeByteArray(mDigestBytes);
+ }
+ out.writeInt(mDigestPrefix);
+ out.writeList(mFilters);
+ }
+
+ private void readFromParcel(Parcel in) {
+ int digestBytesSize = in.readInt();
+ if (digestBytesSize > 0) {
+ mDigestBytes = new byte[digestBytesSize];
+ in.readByteArray(mDigestBytes);
+ }
+ mDigestPrefix = in.readInt();
+ in.readList(mFilters, null /*loader*/);
+ }
+
+ public static final Parcelable.Creator<EphemeralResolveInfo> CREATOR
+ = new Parcelable.Creator<EphemeralResolveInfo>() {
+ public EphemeralResolveInfo createFromParcel(Parcel in) {
+ return new EphemeralResolveInfo(in);
+ }
+
+ public EphemeralResolveInfo[] newArray(int size) {
+ return new EphemeralResolveInfo[size];
+ }
+ };
+}
diff --git a/core/java/com/android/internal/app/EphemeralResolverService.java b/core/java/com/android/internal/app/EphemeralResolverService.java
new file mode 100644
index 0000000..65530f2
--- /dev/null
+++ b/core/java/com/android/internal/app/EphemeralResolverService.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 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.internal.app;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+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 java.util.List;
+
+/**
+ * Base class for implementing the resolver service.
+ * @hide
+ */
+public abstract class EphemeralResolverService extends Service {
+ public static final String EXTRA_RESOLVE_INFO = "com.android.internal.app.RESOLVE_INFO";
+ public static final String EXTRA_SEQUENCE = "com.android.internal.app.SEQUENCE";
+ private Handler mHandler;
+
+ /**
+ * Called to retrieve resolve info for ephemeral applications.
+ *
+ * @param digestPrefix The hash prefix of the ephemeral's domain.
+ */
+ protected abstract List<EphemeralResolveInfo> getEphemeralResolveInfoList(int digestPrefix);
+
+ @Override
+ protected final void attachBaseContext(Context base) {
+ super.attachBaseContext(base);
+ mHandler = new ServiceHandler(base.getMainLooper());
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return new IEphemeralResolver.Stub() {
+ @Override
+ public void getEphemeralResolveInfoList(
+ IRemoteCallback callback, int digestPrefix, int sequence) {
+ mHandler.obtainMessage(ServiceHandler.MSG_GET_EPHEMERAL_RESOLVE_INFO,
+ digestPrefix, sequence, callback)
+ .sendToTarget();
+ }
+ };
+ }
+
+ private final class ServiceHandler extends Handler {
+ public static final int MSG_GET_EPHEMERAL_RESOLVE_INFO = 1;
+
+ public ServiceHandler(Looper looper) {
+ super(looper, null /*callback*/, true /*async*/);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void handleMessage(Message message) {
+ final int action = message.what;
+ switch (action) {
+ case MSG_GET_EPHEMERAL_RESOLVE_INFO: {
+ final IRemoteCallback callback = (IRemoteCallback) message.obj;
+ final List<EphemeralResolveInfo> resolveInfo =
+ getEphemeralResolveInfoList(message.arg1);
+ final Bundle data = new Bundle();
+ data.putInt(EXTRA_SEQUENCE, message.arg2);
+ data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo);
+ try {
+ callback.sendResult(data);
+ } catch (RemoteException e) {
+ }
+ } break;
+
+ default: {
+ throw new IllegalArgumentException("Unknown message: " + action);
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/IEphemeralResolver.aidl b/core/java/com/android/internal/app/IEphemeralResolver.aidl
new file mode 100644
index 0000000..40429ee
--- /dev/null
+++ b/core/java/com/android/internal/app/IEphemeralResolver.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 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.internal.app;
+
+import android.content.Intent;
+import android.os.IRemoteCallback;
+
+oneway interface IEphemeralResolver {
+ void getEphemeralResolveInfoList(IRemoteCallback callback, int digestPrefix, int sequence);
+}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b87d9e2..057790a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1091,6 +1091,14 @@
This feature should be disabled for most devices. -->
<integer name="config_virtualKeyQuietTimeMillis">0</integer>
+ <!-- A list of potential packages, in priority order, that may contain an
+ ephemeral resolver. Each package will be be queried for a component
+ that has been granted the PACKAGE_EPHEMERAL_AGENT permission.
+ This may be empty if ephemeral apps are not supported. -->
+ <string-array name="config_ephemeralResolverPackage" translatable="false">
+ <!-- Add packages here -->
+ </string-array>
+
<!-- Component name of the default wallpaper. This will be ImageWallpaper if not
specified -->
<string name="default_wallpaper_component" translatable="false">@null</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6820c25..ec11021 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -627,6 +627,7 @@
<java-symbol type="string" name="widget_default_package_name" />
<java-symbol type="string" name="widget_default_class_name" />
<java-symbol type="string" name="emergency_calls_only" />
+ <java-symbol type="array" name="config_ephemeralResolverPackage" />
<java-symbol type="string" name="enable_accessibility_canceled" />
<java-symbol type="string" name="eventTypeAnniversary" />
<java-symbol type="string" name="eventTypeBirthday" />
diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
new file mode 100644
index 0000000..628ad0e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2015 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.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.TimedRemoteCaller;
+
+import com.android.internal.app.EphemeralResolverService;
+import com.android.internal.app.EphemeralResolveInfo;
+import com.android.internal.app.IEphemeralResolver;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Represents a remote ephemeral resolver. It is responsible for binding to the remote
+ * service and handling all interactions in a timely manner.
+ * @hide
+ */
+final class EphemeralResolverConnection {
+ // This is running in a critical section and the timeout must be sufficiently low
+ private static final long BIND_SERVICE_TIMEOUT_MS =
+ ("eng".equals(Build.TYPE)) ? 300 : 200;
+
+ private final Object mLock = new Object();
+ private final GetEphemeralResolveInfoCaller mGetEphemeralResolveInfoCaller =
+ new GetEphemeralResolveInfoCaller();
+ private final ServiceConnection mServiceConnection = new MyServiceConnection();
+ private final Context mContext;
+ /** Intent used to bind to the service */
+ private final Intent mIntent;
+
+ private IEphemeralResolver mRemoteInstance;
+
+ public EphemeralResolverConnection(Context context, ComponentName componentName) {
+ mContext = context;
+ mIntent = new Intent().setComponent(componentName);
+ }
+
+ public final List<EphemeralResolveInfo> getEphemeralResolveInfoList(int hashPrefix) {
+ throwIfCalledOnMainThread();
+ try {
+ return mGetEphemeralResolveInfoCaller.getEphemeralResolveInfoList(
+ getRemoteInstanceLazy(), hashPrefix);
+ } catch (RemoteException re) {
+ } catch (TimeoutException te) {
+ } finally {
+ synchronized (mLock) {
+ mLock.notifyAll();
+ }
+ }
+ return null;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String prefix) {
+ synchronized (mLock) {
+ pw.append(prefix).append("bound=")
+ .append((mRemoteInstance != null) ? "true" : "false").println();
+
+ pw.flush();
+
+ try {
+ getRemoteInstanceLazy().asBinder().dump(fd, new String[] { prefix });
+ } catch (TimeoutException te) {
+ /* ignore */
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+ }
+
+ private IEphemeralResolver getRemoteInstanceLazy() throws TimeoutException {
+ synchronized (mLock) {
+ if (mRemoteInstance != null) {
+ return mRemoteInstance;
+ }
+ bindLocked();
+ return mRemoteInstance;
+ }
+ }
+
+ private void bindLocked() throws TimeoutException {
+ if (mRemoteInstance != null) {
+ return;
+ }
+
+ mContext.bindServiceAsUser(mIntent, mServiceConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, UserHandle.SYSTEM);
+
+ final long startMillis = SystemClock.uptimeMillis();
+ while (true) {
+ if (mRemoteInstance != null) {
+ break;
+ }
+ final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
+ final long remainingMillis = BIND_SERVICE_TIMEOUT_MS - elapsedMillis;
+ if (remainingMillis <= 0) {
+ throw new TimeoutException("Didn't bind to resolver in time.");
+ }
+ try {
+ mLock.wait(remainingMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+
+ mLock.notifyAll();
+ }
+
+ private void throwIfCalledOnMainThread() {
+ if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
+ throw new RuntimeException("Cannot invoke on the main thread");
+ }
+ }
+
+ private final class MyServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mRemoteInstance = IEphemeralResolver.Stub.asInterface(service);
+ mLock.notifyAll();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (mLock) {
+ mRemoteInstance = null;
+ }
+ }
+ }
+
+ private static final class GetEphemeralResolveInfoCaller
+ extends TimedRemoteCaller<List<EphemeralResolveInfo>> {
+ private final IRemoteCallback mCallback;
+
+ public GetEphemeralResolveInfoCaller() {
+ 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> getEphemeralResolveInfoList(
+ IEphemeralResolver target, int hashPrefix)
+ throws RemoteException, TimeoutException {
+ final int sequence = onBeforeRemoteCall();
+ target.getEphemeralResolveInfoList(mCallback, hashPrefix, sequence);
+ return getResultTimed(sequence);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 02a6204..5d20291 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -209,6 +209,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.EphemeralResolveInfo;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.NativeLibraryHelper;
@@ -252,6 +253,7 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
@@ -301,6 +303,7 @@
private static final boolean DEBUG_VERIFY = false;
private static final boolean DEBUG_DEXOPT = false;
private static final boolean DEBUG_ABI_SELECTION = false;
+ private static final boolean DEBUG_EPHEMERAL = false;
static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false;
@@ -598,6 +601,16 @@
private final ComponentName mIntentFilterVerifierComponent;
private int mIntentFilterVerificationToken = 0;
+ /** Component that knows whether or not an ephemeral application exists */
+ final ComponentName mEphemeralResolverComponent;
+ /** The service connection to the ephemeral resolver */
+ final EphemeralResolverConnection mEphemeralResolverConnection;
+
+ /** Component used to install ephemeral applications */
+ final ComponentName mEphemeralInstallerComponent;
+ final ActivityInfo mEphemeralInstallerActivity = new ActivityInfo();
+ final ResolveInfo mEphemeralInstallerInfo = new ResolveInfo();
+
final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
= new SparseArray<IntentFilterVerificationState>();
@@ -2344,6 +2357,33 @@
mIntentFilterVerifier = new IntentVerifierProxy(mContext,
mIntentFilterVerifierComponent);
+ final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr();
+ final ComponentName ephemeralInstallerComponent = getEphemeralInstallerLPr();
+ // both the installer and resolver must be present to enable ephemeral
+ if (ephemeralInstallerComponent != null && ephemeralResolverComponent != null) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.i(TAG, "Ephemeral activated; resolver: " + ephemeralResolverComponent
+ + " installer:" + ephemeralInstallerComponent);
+ }
+ mEphemeralResolverComponent = ephemeralResolverComponent;
+ mEphemeralInstallerComponent = ephemeralInstallerComponent;
+ setUpEphemeralInstallerActivityLP(mEphemeralInstallerComponent);
+ mEphemeralResolverConnection =
+ new EphemeralResolverConnection(mContext, mEphemeralResolverComponent);
+ } else {
+ if (DEBUG_EPHEMERAL) {
+ final String missingComponent =
+ (ephemeralResolverComponent == null)
+ ? (ephemeralInstallerComponent == null)
+ ? "resolver and installer"
+ : "resolver"
+ : "installer";
+ Slog.i(TAG, "Ephemeral deactivated; missing " + missingComponent);
+ }
+ mEphemeralResolverComponent = null;
+ mEphemeralInstallerComponent = null;
+ mEphemeralResolverConnection = null;
+ }
} // synchronized (mPackages)
} // synchronized (mInstallLock)
@@ -2482,6 +2522,89 @@
return verifierComponentName;
}
+ private ComponentName getEphemeralResolverLPr() {
+ final String[] packageArray =
+ mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage);
+ if (packageArray.length == 0) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Ephemeral resolver NOT found; empty package list");
+ }
+ return null;
+ }
+
+ Intent resolverIntent = new Intent(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE);
+ final List<ResolveInfo> resolvers = queryIntentServices(resolverIntent,
+ null /*resolvedType*/, 0 /*flags*/, UserHandle.USER_SYSTEM);
+
+ final int N = resolvers.size();
+ if (N == 0) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Ephemeral resolver NOT found; no matching intent filters");
+ }
+ return null;
+ }
+
+ final Set<String> possiblePackages = new ArraySet<>(Arrays.asList(packageArray));
+ for (int i = 0; i < N; i++) {
+ final ResolveInfo info = resolvers.get(i);
+
+ if (info.serviceInfo == null) {
+ continue;
+ }
+
+ final String packageName = info.serviceInfo.packageName;
+ if (!possiblePackages.contains(packageName)) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Ephemeral resolver not in allowed package list;"
+ + " pkg: " + packageName + ", info:" + info);
+ }
+ continue;
+ }
+
+ if (DEBUG_EPHEMERAL) {
+ Slog.v(TAG, "Ephemeral resolver found;"
+ + " pkg: " + packageName + ", info:" + info);
+ }
+ return new ComponentName(packageName, info.serviceInfo.name);
+ }
+ if (DEBUG_EPHEMERAL) {
+ Slog.v(TAG, "Ephemeral resolver NOT found");
+ }
+ return null;
+ }
+
+ private ComponentName getEphemeralInstallerLPr() {
+ Intent installerIntent = new Intent(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE);
+ installerIntent.addCategory(Intent.CATEGORY_DEFAULT);
+ installerIntent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE);
+ final List<ResolveInfo> installers = queryIntentActivities(installerIntent,
+ PACKAGE_MIME_TYPE, 0 /*flags*/, 0 /*userId*/);
+
+ ComponentName ephemeralInstaller = null;
+
+ final int N = installers.size();
+ for (int i = 0; i < N; i++) {
+ final ResolveInfo info = installers.get(i);
+ final String packageName = info.activityInfo.packageName;
+
+ if (!info.activityInfo.applicationInfo.isSystemApp()) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Ephemeral installer is not system app;"
+ + " pkg: " + packageName + ", info:" + info);
+ }
+ continue;
+ }
+
+ if (ephemeralInstaller != null) {
+ throw new RuntimeException("There must only be one ephemeral installer");
+ }
+
+ ephemeralInstaller = new ComponentName(packageName, info.activityInfo.name);
+ }
+
+ return ephemeralInstaller;
+ }
+
private void primeDomainVerificationsLPw(int userId) {
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.d(TAG, "Priming domain verifications in user " + userId);
@@ -4263,8 +4386,97 @@
false, false, false, userId);
}
+ private boolean isEphemeralAvailable(Intent intent, String resolvedType, int userId) {
+ MessageDigest digest = null;
+ try {
+ digest = MessageDigest.getInstance(EphemeralResolveInfo.SHA_ALGORITHM);
+ } catch (NoSuchAlgorithmException e) {
+ // If we can't create a digest, ignore ephemeral apps.
+ return false;
+ }
+
+ final byte[] hostBytes = intent.getData().getHost().getBytes();
+ final byte[] digestBytes = digest.digest(hostBytes);
+ int shaPrefix =
+ digestBytes[0] << 24
+ | digestBytes[1] << 16
+ | digestBytes[2] << 8
+ | digestBytes[3] << 0;
+ final List<EphemeralResolveInfo> ephemeralResolveInfoList =
+ mEphemeralResolverConnection.getEphemeralResolveInfoList(shaPrefix);
+ if (ephemeralResolveInfoList == null || ephemeralResolveInfoList.size() == 0) {
+ // No hash prefix match; there are no ephemeral apps for this domain.
+ return false;
+ }
+ for (int i = ephemeralResolveInfoList.size() - 1; i >= 0; --i) {
+ EphemeralResolveInfo ephemeralApplication = ephemeralResolveInfoList.get(i);
+ if (!Arrays.equals(digestBytes, ephemeralApplication.getDigestBytes())) {
+ continue;
+ }
+ final List<IntentFilter> filters = ephemeralApplication.getFilters();
+ // No filters; this should never happen.
+ if (filters.isEmpty()) {
+ continue;
+ }
+ // We have a domain match; resolve the filters to see if anything matches.
+ final EphemeralIntentResolver ephemeralResolver = new EphemeralIntentResolver();
+ for (int j = filters.size() - 1; j >= 0; --j) {
+ ephemeralResolver.addFilter(filters.get(j));
+ }
+ List<ResolveInfo> ephemeralResolveList = ephemeralResolver.queryIntent(
+ intent, resolvedType, false /*defaultOnly*/, userId);
+ return !ephemeralResolveList.isEmpty();
+ }
+ // Hash or filter mis-match; no ephemeral apps for this domain.
+ return false;
+ }
+
private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
int flags, List<ResolveInfo> query, int userId) {
+ final boolean isWebUri = hasWebURI(intent);
+ // Check whether or not an ephemeral app exists to handle the URI.
+ if (isWebUri && mEphemeralResolverConnection != null) {
+ // Deny ephemeral apps if the user choose _ALWAYS or _ALWAYS_ASK for intent resolution.
+ boolean hasAlwaysHandler = false;
+ synchronized (mPackages) {
+ final int count = query.size();
+ for (int n=0; n<count; n++) {
+ ResolveInfo info = query.get(n);
+ String packageName = info.activityInfo.packageName;
+ PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps != null) {
+ // Try to get the status from User settings first
+ long packedStatus = getDomainVerificationStatusLPr(ps, userId);
+ int status = (int) (packedStatus >> 32);
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+ || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
+ hasAlwaysHandler = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // Only consider installing an ephemeral app if there isn't already a verified handler.
+ // We've determined that there's an ephemeral app available for the URI, ignore any
+ // ResolveInfo's and just return the ephemeral installer
+ if (!hasAlwaysHandler && isEphemeralAvailable(intent, resolvedType, userId)) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.v(TAG, "Resolving to the ephemeral installer");
+ }
+ // ditch the result and return a ResolveInfo to launch the ephemeral installer
+ ResolveInfo ri = new ResolveInfo(mEphemeralInstallerInfo);
+ ri.activityInfo = new ActivityInfo(ri.activityInfo);
+ // make a deep copy of the applicationInfo
+ ri.activityInfo.applicationInfo = new ApplicationInfo(
+ ri.activityInfo.applicationInfo);
+ if (userId != 0) {
+ ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,
+ UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));
+ }
+ return ri;
+ }
+ }
if (query != null) {
final int N = query.size();
if (N == 1) {
@@ -7773,6 +7985,30 @@
}
}
+ private void setUpEphemeralInstallerActivityLP(ComponentName installerComponent) {
+ final PackageParser.Package pkg = mPackages.get(installerComponent.getPackageName());
+
+ // Set up information for ephemeral installer activity
+ mEphemeralInstallerActivity.applicationInfo = pkg.applicationInfo;
+ mEphemeralInstallerActivity.name = mEphemeralInstallerComponent.getClassName();
+ mEphemeralInstallerActivity.packageName = pkg.applicationInfo.packageName;
+ mEphemeralInstallerActivity.processName = pkg.applicationInfo.packageName;
+ mEphemeralInstallerActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+ mEphemeralInstallerActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS |
+ ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+ mEphemeralInstallerActivity.theme = 0;
+ mEphemeralInstallerActivity.exported = true;
+ mEphemeralInstallerActivity.enabled = true;
+ mEphemeralInstallerInfo.activityInfo = mEphemeralInstallerActivity;
+ mEphemeralInstallerInfo.priority = 0;
+ mEphemeralInstallerInfo.preferredOrder = 0;
+ mEphemeralInstallerInfo.match = 0;
+
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Set ephemeral installer activity: " + mEphemeralInstallerComponent);
+ }
+ }
+
private static String calculateBundledApkRoot(final String codePathString) {
final File codePath = new File(codePathString);
final File codeRoot;
@@ -9329,7 +9565,28 @@
private final ArrayMap<ComponentName, PackageParser.Provider> mProviders
= new ArrayMap<ComponentName, PackageParser.Provider>();
private int mFlags;
- };
+ }
+
+ private static final class EphemeralIntentResolver
+ extends IntentResolver<IntentFilter, ResolveInfo> {
+ @Override
+ protected IntentFilter[] newArray(int size) {
+ return new IntentFilter[size];
+ }
+
+ @Override
+ protected boolean isPackageForFilter(String packageName, IntentFilter info) {
+ return true;
+ }
+
+ @Override
+ protected ResolveInfo newResult(IntentFilter info, int match, int userId) {
+ if (!sUserManager.exists(userId)) return null;
+ final ResolveInfo res = new ResolveInfo();
+ res.filter = info;
+ return res;
+ }
+ }
private static final Comparator<ResolveInfo> mResolvePrioritySorter =
new Comparator<ResolveInfo>() {