Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e9910224 authored by Todd Kennedy's avatar Todd Kennedy
Browse files

Load splits on-demand

A split may be declared in an application's base manifest, but,
defined in a feature split. When resolving such a component,
invoke the installer to download and install the necessary split(s)

At the moment, this only works for instant apps. However, the
implementation is generic and could be applied to any application.

Bug: 25119046
Test: cts-tradefed run commandAndExit cts-dev -m CtsAppSecurityHostTestCases -t android.appsecurity.cts.EphemeralTest
Change-Id: I6598abb34becfd049fc03199813226736e5057b1
parent a38649b1
Loading
Loading
Loading
Loading
+28 −5
Original line number Diff line number Diff line
@@ -18,13 +18,19 @@ package android.content.pm;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
import android.content.IntentFilter;

/**
 * Ephemeral application resolution response.
 * Auxiliary application resolution response.
 * <p>
 * Used when resolution occurs, but, the target is not actually on the device.
 * This happens resolving instant apps that haven't been installed yet or if
 * the application consists of multiple feature splits and the needed split
 * hasn't been installed.
 * @hide
 */
public final class EphemeralResponse extends IntentFilter {
public final class AuxiliaryResolveInfo extends IntentFilter {
    /** Resolved information returned from the external ephemeral resolver */
    public final EphemeralResolveInfo resolveInfo;
    /** The resolved package. Copied from {@link #resolveInfo}. */
@@ -32,11 +38,14 @@ public final class EphemeralResponse extends IntentFilter {
    /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */
    public final String splitName;
    /** Whether or not ephemeral resolution needs the second phase */
    public final boolean needsPhase2;
    public final boolean needsPhaseTwo;
    /** Opaque token to track the ephemeral application resolution */
    public final String token;
    /** The version code of the package */
    public final int versionCode;

    public EphemeralResponse(@NonNull EphemeralResolveInfo resolveInfo,
    /** Create a response for installing an instant application. */
    public AuxiliaryResolveInfo(@NonNull EphemeralResolveInfo resolveInfo,
            @NonNull IntentFilter orig,
            @Nullable String splitName,
            @NonNull String token,
@@ -46,6 +55,20 @@ public final class EphemeralResponse extends IntentFilter {
        this.packageName = resolveInfo.getPackageName();
        this.splitName = splitName;
        this.token = token;
        this.needsPhase2 = needsPhase2;
        this.needsPhaseTwo = needsPhase2;
        this.versionCode = resolveInfo.getVersionCode();
    }

    /** Create a response for installing a split on demand. */
    public AuxiliaryResolveInfo(@NonNull String packageName,
            @Nullable String splitName,
            int versionCode) {
        super();
        this.packageName = packageName;
        this.splitName = splitName;
        this.versionCode = versionCode;
        this.resolveInfo = null;
        this.token = null;
        this.needsPhaseTwo = false;
    }
}
 No newline at end of file
+3 −6
Original line number Diff line number Diff line
@@ -24,24 +24,21 @@ import android.content.Intent;
 */
public final class EphemeralRequest {
    /** Response from the first phase of ephemeral application resolution */
    public final EphemeralResponse responseObj;
    public final AuxiliaryResolveInfo responseObj;
    /** The original intent that triggered ephemeral application resolution */
    public final Intent origIntent;
    /** Resolved type of the intent */
    public final String resolvedType;
    /** The intent that would launch if there were no ephemeral applications */
    public final Intent launchIntent;
    /** The name of the package requesting the ephemeral application */
    public final String callingPackage;
    /** ID of the user requesting the ephemeral application */
    public final int userId;

    public EphemeralRequest(EphemeralResponse responseObj, Intent origIntent,
            String resolvedType, Intent launchIntent, String callingPackage, int userId) {
    public EphemeralRequest(AuxiliaryResolveInfo responseObj, Intent origIntent,
            String resolvedType, String callingPackage, int userId) {
        this.responseObj = responseObj;
        this.origIntent = origIntent;
        this.resolvedType = resolvedType;
        this.launchIntent = launchIntent;
        this.callingPackage = callingPackage;
        this.userId = userId;
    }
+2 −3
Original line number Diff line number Diff line
@@ -213,12 +213,11 @@ public abstract class PackageManagerInternal {
     * @param responseObj The response of the first phase of ephemeral resolution
     * @param origIntent The original intent that triggered ephemeral resolution
     * @param resolvedType The resolved type of the intent
     * @param launchIntent The intent that would launch if there was no ephemeral application
     * @param callingPackage The name of the package requesting the ephemeral application
     * @param userId The ID of the user that triggered ephemeral resolution
     */
    public abstract void requestEphemeralResolutionPhaseTwo(EphemeralResponse responseObj,
            Intent origIntent, String resolvedType, Intent launchIntent, String callingPackage,
    public abstract void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
            Intent origIntent, String resolvedType, String callingPackage,
            int userId);

    /**
+4 −3
Original line number Diff line number Diff line
@@ -61,11 +61,12 @@ public class ResolveInfo implements Parcelable {
    public ProviderInfo providerInfo;

    /**
     * The ephemeral application that corresponds to this resolution match. This will
     * only be set in specific circumstances.
     * An auxiliary response that may modify the resolved information. This is
     * only set under certain circumstances; such as when resolving instant apps
     * or components defined in un-installed splits.
     * @hide
     */
    public EphemeralResponse ephemeralResponse;
    public AuxiliaryResolveInfo auxiliaryInfo;

    /**
     * The IntentFilter that was matched for this ResolveInfo.
+21 −16
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.am.ActivityStackSupervisor.TAG_TASKS;
import static com.android.server.am.EventLogTags.AM_NEW_INTENT;

import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AppGlobals;
@@ -98,7 +99,9 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
@@ -456,22 +459,9 @@ 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.ephemeralResponse != null) {
            final String packageName =
                    rInfo.ephemeralResponse.resolveInfo.getPackageName();
            final String splitName = rInfo.ephemeralResponse.splitName;
            final boolean needsPhaseTwo = rInfo.ephemeralResponse.needsPhase2;
            final String token = rInfo.ephemeralResponse.token;
            final int versionCode = rInfo.ephemeralResponse.resolveInfo.getVersionCode();
            if (needsPhaseTwo) {
                // request phase two resolution
                mService.getPackageManagerInternalLocked().requestEphemeralResolutionPhaseTwo(
                        rInfo.ephemeralResponse, ephemeralIntent, resolvedType, intent,
                        callingPackage, userId);
            }
            intent = EphemeralResolver.buildEphemeralInstallerIntent(intent, ephemeralIntent,
                    callingPackage, resolvedType, userId, packageName, splitName, versionCode,
                    token, needsPhaseTwo);
        if (rInfo != null && rInfo.auxiliaryInfo != null) {
            intent = createLaunchIntent(rInfo.auxiliaryInfo, ephemeralIntent,
                    callingPackage, resolvedType, userId);
            resolvedType = null;
            callingUid = realCallingUid;
            callingPid = realCallingPid;
@@ -530,6 +520,21 @@ class ActivityStarter {
        return err;
    }

    /** Creates a launch intent for the given auxiliary resolution data. */
    private @NonNull Intent createLaunchIntent(@NonNull AuxiliaryResolveInfo auxiliaryResponse,
            Intent originalIntent, String callingPackage,
            String resolvedType, int userId) {
        if (auxiliaryResponse.needsPhaseTwo) {
            // request phase two resolution
            mService.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo(
                    auxiliaryResponse, originalIntent, resolvedType, callingPackage, userId);
        }
        return EphemeralResolver.buildEphemeralInstallerIntent(originalIntent,
                callingPackage, resolvedType, userId, auxiliaryResponse.packageName,
                auxiliaryResponse.splitName, auxiliaryResponse.versionCode,
                auxiliaryResponse.token, auxiliaryResponse.needsPhaseTwo);
    }

    void postStartActivityUncheckedProcessing(
            ActivityRecord r, int result, int prevFocusedStackId, ActivityRecord sourceRecord,
            ActivityStack targetStack) {
Loading