blob: 96a0d18bbc3e8f37dc2e6f8c6733b7911ada23f2 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.EphemeralIntentFilter;
import android.content.pm.EphemeralRequest;
import android.content.pm.EphemeralResolveInfo;
import android.content.pm.EphemeralResponse;
import android.content.pm.EphemeralResolveInfo.EphemeralDigest;
import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;
import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
/** @hide */
public abstract class EphemeralResolver {
public static EphemeralResponse doEphemeralResolutionPhaseOne(Context context,
EphemeralResolverConnection connection, EphemeralRequest requestObj) {
final Intent intent = requestObj.origIntent;
final EphemeralDigest digest =
new EphemeralDigest(intent.getData().getHost(), 5 /*maxDigests*/);
final int[] shaPrefix = digest.getDigestPrefix();
final List<EphemeralResolveInfo> ephemeralResolveInfoList =
connection.getEphemeralResolveInfoList(shaPrefix);
if (ephemeralResolveInfoList == null || ephemeralResolveInfoList.size() == 0) {
// No hash prefix match; there are no ephemeral apps for this domain.
return null;
}
final String token = UUID.randomUUID().toString();
return EphemeralResolver.filterEphemeralIntent(ephemeralResolveInfoList,
intent, requestObj.resolvedType, requestObj.userId,
intent.getPackage(), digest, token);
}
public static void doEphemeralResolutionPhaseTwo(Context context,
EphemeralResolverConnection connection, EphemeralRequest requestObj,
ActivityInfo ephemeralInstaller, Handler callbackHandler) {
final Intent intent = requestObj.origIntent;
final String hostName = intent.getData().getHost();
final EphemeralDigest digest = new EphemeralDigest(hostName, 5 /*maxDigests*/);
final PhaseTwoCallback callback = new PhaseTwoCallback() {
@Override
void onPhaseTwoResolved(EphemeralResolveInfo ephemeralResolveInfo,
int sequence) {
final String packageName;
final String splitName;
final int versionCode;
if (ephemeralResolveInfo != null) {
final ArrayList<EphemeralResolveInfo> ephemeralResolveInfoList =
new ArrayList<EphemeralResolveInfo>(1);
ephemeralResolveInfoList.add(ephemeralResolveInfo);
final EphemeralResponse ephemeralIntentInfo =
EphemeralResolver.filterEphemeralIntent(
ephemeralResolveInfoList, intent, null /*resolvedType*/,
0 /*userId*/, intent.getPackage(), digest,
requestObj.responseObj.token);
if (ephemeralIntentInfo != null
&& ephemeralIntentInfo.resolveInfo != null) {
packageName = ephemeralIntentInfo.resolveInfo.getPackageName();
splitName = ephemeralIntentInfo.splitName;
versionCode = ephemeralIntentInfo.resolveInfo.getVersionCode();
} else {
packageName = null;
splitName = null;
versionCode = -1;
}
} else {
packageName = null;
splitName = null;
versionCode = -1;
}
final Intent installerIntent = buildEphemeralInstallerIntent(
requestObj.launchIntent,
requestObj.origIntent,
requestObj.callingPackage,
requestObj.resolvedType,
requestObj.userId,
packageName,
splitName,
versionCode,
requestObj.responseObj.token,
false /*needsPhaseTwo*/);
installerIntent.setComponent(new ComponentName(
ephemeralInstaller.packageName, ephemeralInstaller.name));
context.startActivity(installerIntent);
}
};
connection.getEphemeralIntentFilterList(
hostName, callback, callbackHandler, 0 /*sequence*/);
}
/**
* Builds and returns an intent to launch the ephemeral installer.
*/
public static Intent buildEphemeralInstallerIntent(Intent launchIntent, Intent origIntent,
String callingPackage, String resolvedType, int userId, String ephemeralPackageName,
String ephemeralSplitName, int versionCode, String token, boolean needsPhaseTwo) {
// Construct the intent that launches the ephemeral installer
int flags = launchIntent.getFlags();
final Intent intent = new Intent();
intent.setFlags(flags
| Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NO_HISTORY
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
// TODO: Remove when the platform has fully implemented ephemeral apps
intent.setData(origIntent.getData().buildUpon().clearQuery().build());
intent.putExtra(Intent.EXTRA_EPHEMERAL_TOKEN, token);
intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost());
if (!needsPhaseTwo) {
// We have all of the data we need; just start the installer without a second phase
final Intent nonEphemeralIntent = new Intent(origIntent);
nonEphemeralIntent.setFlags(
nonEphemeralIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL);
// Intent that is launched if the ephemeral package couldn't be installed
// for any reason.
try {
final IIntentSender failureIntentTarget = ActivityManagerNative.getDefault()
.getIntentSender(
ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
null /*token*/, null /*resultWho*/, 1 /*requestCode*/,
new Intent[] { nonEphemeralIntent },
new String[] { resolvedType },
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_IMMUTABLE,
null /*bOptions*/, userId);
intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE,
new IntentSender(failureIntentTarget));
} catch (RemoteException ignore) { /* ignore; same process */ }
// Success intent goes back to the installer
final Intent ephemeralIntent = new Intent(launchIntent)
.setComponent(null)
.setPackage(ephemeralPackageName);
// Intent that is eventually launched if the ephemeral package was
// installed successfully. This will actually be launched by a platform
// broadcast receiver.
try {
final IIntentSender successIntentTarget = ActivityManagerNative.getDefault()
.getIntentSender(
ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
null /*token*/, null /*resultWho*/, 0 /*requestCode*/,
new Intent[] { ephemeralIntent },
new String[] { resolvedType },
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_IMMUTABLE,
null /*bOptions*/, userId);
intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS,
new IntentSender(successIntentTarget));
} catch (RemoteException ignore) { /* ignore; same process */ }
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, ephemeralPackageName);
intent.putExtra(Intent.EXTRA_SPLIT_NAME, ephemeralSplitName);
intent.putExtra(Intent.EXTRA_VERSION_CODE, versionCode);
}
return intent;
}
private static EphemeralResponse filterEphemeralIntent(
List<EphemeralResolveInfo> ephemeralResolveInfoList,
Intent intent, String resolvedType, int userId, String packageName,
EphemeralDigest digest, String token) {
final int[] shaPrefix = digest.getDigestPrefix();
final byte[][] digestBytes = digest.getDigestBytes();
// Go in reverse order so we match the narrowest scope first.
for (int i = shaPrefix.length - 1; i >= 0 ; --i) {
for (EphemeralResolveInfo ephemeralInfo : ephemeralResolveInfoList) {
if (!Arrays.equals(digestBytes[i], ephemeralInfo.getDigestBytes())) {
continue;
}
if (packageName != null
&& !packageName.equals(ephemeralInfo.getPackageName())) {
continue;
}
final List<EphemeralIntentFilter> ephemeralFilters =
ephemeralInfo.getIntentFilters();
// No filters; we need to start phase two
if (ephemeralFilters == null || ephemeralFilters.isEmpty()) {
return new EphemeralResponse(ephemeralInfo,
new IntentFilter(Intent.ACTION_VIEW) /*intentFilter*/,
null /*splitName*/, token, true /*needsPhase2*/);
}
// We have a domain match; resolve the filters to see if anything matches.
final PackageManagerService.EphemeralIntentResolver ephemeralResolver =
new PackageManagerService.EphemeralIntentResolver();
for (int j = ephemeralFilters.size() - 1; j >= 0; --j) {
final EphemeralIntentFilter ephemeralFilter = ephemeralFilters.get(j);
final List<IntentFilter> splitFilters = ephemeralFilter.getFilters();
if (splitFilters == null || splitFilters.isEmpty()) {
continue;
}
for (int k = splitFilters.size() - 1; k >= 0; --k) {
final EphemeralResponse intentInfo =
new EphemeralResponse(ephemeralInfo,
splitFilters.get(k), ephemeralFilter.getSplitName(),
token, false /*needsPhase2*/);
ephemeralResolver.addFilter(intentInfo);
}
}
List<EphemeralResponse> matchedResolveInfoList = ephemeralResolver.queryIntent(
intent, resolvedType, false /*defaultOnly*/, false /*visibleToEphemeral*/,
false /*isEphemeral*/, userId);
if (!matchedResolveInfoList.isEmpty()) {
return matchedResolveInfoList.get(0);
}
}
}
// Hash or filter mis-match; no ephemeral apps for this domain.
return null;
}
}