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

Commit 32a2a1f8 authored by Winson Chiu's avatar Winson Chiu
Browse files

Add PackageInstaller.SessionParams.setPermissionState

Allows an installer to request specific permissions be granted
or denied during installation.

This deprecates and replaces the old setGrantedRuntimePermissions.
The usage of that method is now mapped to call setPermissionState
with PERMISSION_STATE_GRANTED.

However, there is no equivalent in the new API for granting all
requested permissions. This is probably not a great pattern to maintain,
but the behavior has been preserved for existing callers like shell
by reusing the existing flag and effectively disabling the new API
if it's used.

Modern app stores should use setPermissionState, and privilieged stores
that need to maintain compatibility should use a compat utility to
branch the logic between the old and new APIs.

Bug: 266955680

Test: atest PackageInstallerSessionTest

Change-Id: I27775eab73462f053a517a4185f6e3c509e16ba7
parent 0d5fb943
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -12279,6 +12279,7 @@ package android.content.pm {
    method public void setOriginatingUid(int);
    method public void setOriginatingUri(@Nullable android.net.Uri);
    method public void setPackageSource(int);
    method @NonNull @RequiresPermission(value="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS", conditional=true) public android.content.pm.PackageInstaller.SessionParams setPermissionState(@NonNull String, int);
    method public void setReferrerUri(@Nullable android.net.Uri);
    method @RequiresPermission(android.Manifest.permission.ENFORCE_UPDATE_OWNERSHIP) public void setRequestUpdateOwnership(boolean);
    method public void setRequireUserAction(int);
@@ -12288,6 +12289,9 @@ package android.content.pm {
    field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionParams> CREATOR;
    field public static final int MODE_FULL_INSTALL = 1; // 0x1
    field public static final int MODE_INHERIT_EXISTING = 2; // 0x2
    field public static final int PERMISSION_STATE_DEFAULT = 0; // 0x0
    field public static final int PERMISSION_STATE_DENIED = 2; // 0x2
    field public static final int PERMISSION_STATE_GRANTED = 1; // 0x1
    field @NonNull public static final java.util.Set<java.lang.String> RESTRICTED_PERMISSIONS_ALL;
    field public static final int USER_ACTION_NOT_REQUIRED = 2; // 0x2
    field public static final int USER_ACTION_REQUIRED = 1; // 0x1
+1 −1
Original line number Diff line number Diff line
@@ -3787,7 +3787,7 @@ package android.content.pm {
    method @RequiresPermission(allOf={android.Manifest.permission.INSTALL_PACKAGES, "com.android.permission.USE_INSTALLER_V2"}) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams);
    method public void setEnableRollback(boolean);
    method public void setEnableRollback(boolean, int);
    method @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS) public void setGrantedRuntimePermissions(String[]);
    method @Deprecated @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS) public void setGrantedRuntimePermissions(String[]);
    method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setInstallAsApex();
    method public void setInstallAsInstantApp(boolean);
    method public void setInstallAsVirtualPreload();
+197 −8
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@ import static android.content.pm.PackageInfo.INSTALL_LOCATION_AUTO;
import static android.content.pm.PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
import static android.content.pm.PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;

import static com.android.internal.util.XmlUtils.writeStringAttribute;

import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.CurrentTimeMillisLong;
@@ -79,14 +81,18 @@ import android.os.UserHandle;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.Log;

import com.android.internal.content.InstallLocationUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DataClass;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.modules.utils.TypedXmlSerializer;

import java.io.Closeable;
import java.io.File;
@@ -101,6 +107,7 @@ import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -2203,6 +2210,38 @@ public class PackageInstaller {
         */
        public static final int USER_ACTION_NOT_REQUIRED = 2;

        /** @hide */
        @IntDef(prefix = {"PERMISSION_STATE_"}, value = {
                PERMISSION_STATE_DEFAULT,
                PERMISSION_STATE_GRANTED,
                PERMISSION_STATE_DENIED,
        })
        @Retention(RetentionPolicy.SOURCE)
        public @interface PermissionState {}

        /**
         * Value is passed by the installer to {@link #setPermissionState(String, int)} to set
         * the state of a permission. This indicates no preference by the installer, relying on
         * the device's default policy to set the grant state of the permission.
         */
        public static final int PERMISSION_STATE_DEFAULT = 0;

        /**
         * Value is passed by the installer to {@link #setPermissionState(String, int)} to set
         * the state of a permission. This indicates the installers wants to automatically grant
         * the permission to the package being installed. The user and other actors in the system
         * may still be able to deny the permission after installation.
         */
        public static final int PERMISSION_STATE_GRANTED = 1;

        /**
         * Value is passed by the installer to {@link #setPermissionState(String, int)} to set
         * the state of a permission. This indicates the installers wants to deny the permission
         * by default to the package being installed. The user and other actors in the system may
         * still be able to grant the permission after installation.
         */
        public static final int PERMISSION_STATE_DENIED = 2;

        /** {@hide} */
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
        public int mode = MODE_INVALID;
@@ -2247,8 +2286,6 @@ public class PackageInstaller {
        /** {@hide} */
        public String volumeUuid;
        /** {@hide} */
        public String[] grantedRuntimePermissions;
        /** {@hide} */
        public List<String> whitelistedRestrictedPermissions;
        /** {@hide} */
        public int autoRevokePermissionsMode = MODE_DEFAULT;
@@ -2273,6 +2310,13 @@ public class PackageInstaller {
        /** {@hide} */
        public boolean applicationEnabledSettingPersistent = false;

        private final ArrayMap<String, Integer> mPermissionStates;

        /**
         * @see #getFinalPermissionStates()
         */
        private ArrayMap<String, Integer> mFinalPermissionStates;

        /**
         * Construct parameters for a new package install session.
         *
@@ -2282,6 +2326,7 @@ public class PackageInstaller {
         */
        public SessionParams(int mode) {
            this.mode = mode;
            mPermissionStates = new ArrayMap<>();
        }

        /** {@hide} */
@@ -2300,7 +2345,8 @@ public class PackageInstaller {
            referrerUri = source.readParcelable(null, android.net.Uri.class);
            abiOverride = source.readString();
            volumeUuid = source.readString();
            grantedRuntimePermissions = source.readStringArray();
            mPermissionStates = new ArrayMap<>();
            source.readMap(mPermissionStates, null, String.class, Integer.class);
            whitelistedRestrictedPermissions = source.createStringArrayList();
            autoRevokePermissionsMode = source.readInt();
            installerPackageName = source.readString();
@@ -2335,7 +2381,7 @@ public class PackageInstaller {
            ret.referrerUri = referrerUri;  // not a copy, but immutable.
            ret.abiOverride = abiOverride;
            ret.volumeUuid = volumeUuid;
            ret.grantedRuntimePermissions = grantedRuntimePermissions;
            ret.mPermissionStates.putAll(mPermissionStates);
            ret.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions;
            ret.autoRevokePermissionsMode = autoRevokePermissionsMode;
            ret.installerPackageName = installerPackageName;
@@ -2459,13 +2505,93 @@ public class PackageInstaller {
         * @param permissions The permissions to grant or null to grant all runtime
         *     permissions.
         *
         * @deprecated Prefer {@link #setPermissionState(String, int)} instead starting in
         * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}.
         * @hide
         */
        @Deprecated
        @SystemApi
        @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS)
        public void setGrantedRuntimePermissions(String[] permissions) {
            installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
            this.grantedRuntimePermissions = permissions;
            if (permissions == null) {
                // The new API has no mechanism to grant all requested permissions
                installFlags |= PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS;
                mPermissionStates.clear();
            } else {
                installFlags &= ~PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS;
                // Otherwise call the new API to grant the permissions specified
                for (String permission : permissions) {
                    setPermissionState(permission, PERMISSION_STATE_GRANTED);
                }
            }
        }

        /**
         * Sets the state of permissions for the package at installation.
         * <p/>
         * Granting any runtime permissions require the
         * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be
         * held by the caller. Revoking runtime permissions is not allowed, even during app update
         * sessions.
         * <p/>
         * Holders without the permission are allowed to change the following special permissions:
         * <p/>
         * On platform {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE UPSIDE_DOWN_CAKE}:
         * <ul>
         *     <li>{@link Manifest.permission#USE_FULL_SCREEN_INTENT}</li>
         * </ul>
         * Install time permissions, which cannot be revoked by the user, cannot be changed by the
         * installer.
         * <p/>
         * See <a href="https://developer.android.com/guide/topics/permissions/overview">
         * Permissions on Android</a> for more information.
         *
         * @param permissionName The permission to change state for.
         * @param state          Either {@link #PERMISSION_STATE_DEFAULT},
         *                       {@link #PERMISSION_STATE_GRANTED},
         *                       or {@link #PERMISSION_STATE_DENIED} to set the permission to.
         *
         * @return This object for easier chaining.
         */
        @RequiresPermission(value = android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS,
                conditional = true)
        @NonNull
        public SessionParams setPermissionState(@NonNull String permissionName,
                @PermissionState int state) {
            if (TextUtils.isEmpty(permissionName)) {
                throw new IllegalArgumentException("Provided permissionName cannot be "
                        + (permissionName == null ? "null" : "empty"));
            }

            if (mFinalPermissionStates != null) {
                Log.wtf(TAG, "Requested permission " + permissionName + " but final permissions"
                        + " were already decided for this session: " + mFinalPermissionStates);
            }

            switch (state) {
                case PERMISSION_STATE_DEFAULT:
                    mPermissionStates.remove(permissionName);
                    break;
                case PERMISSION_STATE_GRANTED:
                case PERMISSION_STATE_DENIED:
                    mPermissionStates.put(permissionName, state);
                    break;
                default:
                    throw new IllegalArgumentException("Unexpected permission state int: " + state);
            }

            return this;
        }

        /** @hide */
        public void setPermissionStates(Collection<String> grantPermissions,
                Collection<String> denyPermissions) {
            for (String grantPermission : grantPermissions) {
                mPermissionStates.put(grantPermission, PERMISSION_STATE_GRANTED);
            }
            for (String denyPermission : denyPermissions) {
                mPermissionStates.put(denyPermission, PERMISSION_STATE_DENIED);
            }
        }

        /**
@@ -2882,6 +3008,69 @@ public class PackageInstaller {
            }
        }

        /**
         * This is only for use by system server. If you need the actual grant state, use
         * {@link #getFinalPermissionStates()}.
         * <p/>
         * This is implemented here to avoid exposing the raw permission sets to external callers,
         * so that enforcement done in the either of the final methods is the single source of truth
         * for default grant/deny policy.
         *
         * @hide
         */
        public void writePermissionStateXml(@NonNull TypedXmlSerializer out,
                @NonNull String grantTag, @NonNull String denyTag, @NonNull String attrName)
                throws IOException {
            for (int index = 0; index < mPermissionStates.size(); index++) {
                var permissionName = mPermissionStates.keyAt(index);
                var state = mPermissionStates.valueAt(index);
                String tag = state == PERMISSION_STATE_GRANTED ? grantTag : denyTag;
                out.startTag(null, tag);
                writeStringAttribute(out, attrName, permissionName);
                out.endTag(null, tag);
            }
        }

        /**
         * Snapshot of final permission states taken when this method is first called, to separate
         * what the caller wanted and the effective state that should be applied to the session.
         *
         * This prevents someone from adding more permissions after the fact.
         *
         * @hide
         */
        @NonNull
        public ArrayMap<String, Integer> getFinalPermissionStates() {
            if (mFinalPermissionStates == null) {
                mFinalPermissionStates = new ArrayMap<>(mPermissionStates);
                if (!mFinalPermissionStates.containsKey(
                        Manifest.permission.USE_FULL_SCREEN_INTENT)) {
                    mFinalPermissionStates.put(Manifest.permission.USE_FULL_SCREEN_INTENT,
                            PERMISSION_STATE_GRANTED);
                }
            }
            return mFinalPermissionStates;
        }

        /** @hide */
        @Nullable
        public String[] getLegacyGrantedRuntimePermissions() {
            if ((installFlags & PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS) != 0) {
                return null;
            }

            var grantedPermissions = new ArrayList<String>();
            for (int index = 0; index < mPermissionStates.size(); index++) {
                var permissionName = mPermissionStates.keyAt(index);
                var state = mPermissionStates.valueAt(index);
                if (state == PERMISSION_STATE_GRANTED) {
                    grantedPermissions.add(permissionName);
                }
            }

            return grantedPermissions.toArray(ArrayUtils.emptyArray(String.class));
        }

        /** {@hide} */
        public void dump(IndentingPrintWriter pw) {
            pw.printPair("mode", mode);
@@ -2898,7 +3087,7 @@ public class PackageInstaller {
            pw.printPair("referrerUri", referrerUri);
            pw.printPair("abiOverride", abiOverride);
            pw.printPair("volumeUuid", volumeUuid);
            pw.printPair("grantedRuntimePermissions", grantedRuntimePermissions);
            pw.printPair("mPermissionStates", mPermissionStates);
            pw.printPair("packageSource", packageSource);
            pw.printPair("whitelistedRestrictedPermissions", whitelistedRestrictedPermissions);
            pw.printPair("autoRevokePermissions", autoRevokePermissionsMode);
@@ -2936,7 +3125,7 @@ public class PackageInstaller {
            dest.writeParcelable(referrerUri, flags);
            dest.writeString(abiOverride);
            dest.writeString(volumeUuid);
            dest.writeStringArray(grantedRuntimePermissions);
            dest.writeMap(mPermissionStates);
            dest.writeStringList(whitelistedRestrictedPermissions);
            dest.writeInt(autoRevokePermissionsMode);
            dest.writeString(installerPackageName);
+10 −7
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.dex.ArtManager;
import android.content.pm.pkg.FrameworkPackageUserState;
import android.content.pm.verify.domain.DomainVerificationManager;
@@ -1385,7 +1386,7 @@ public abstract class PackageManager {
            INSTALL_FROM_ADB,
            INSTALL_ALL_USERS,
            INSTALL_REQUEST_DOWNGRADE,
            INSTALL_GRANT_RUNTIME_PERMISSIONS,
            INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS,
            INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
            INSTALL_FORCE_VOLUME_UUID,
            INSTALL_FORCE_PERMISSION_PROMPT,
@@ -1462,14 +1463,16 @@ public abstract class PackageManager {
    public static final int INSTALL_REQUEST_DOWNGRADE = 0x00000080;

    /**
     * Flag parameter for {@link #installPackage} to indicate that all runtime
     * permissions should be granted to the package. If {@link #INSTALL_ALL_USERS}
     * is set the runtime permissions will be granted to all users, otherwise
     * only to the owner.
     * Flag parameter for package install to indicate that all requested permissions should be
     * granted to the package. If {@link #INSTALL_ALL_USERS} is set the runtime permissions will
     * be granted to all users, otherwise only to the owner.
     * <p/>
     * If this flag is set, {@link SessionParams#setPermissionState(String, int)} should not be
     * called.
     *
     * @hide
     */
    public static final int INSTALL_GRANT_RUNTIME_PERMISSIONS = 0x00000100;
    public static final int INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS = 0x00000100;

    /** {@hide} */
    public static final int INSTALL_FORCE_VOLUME_UUID = 0x00000200;
@@ -1598,7 +1601,7 @@ public abstract class PackageManager {
    public static final int INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK = 0x01000000;

    /**
     * Flag parameter for {@link PackageInstaller.SessionParams} to indicate that the
     * Flag parameter for {@link SessionParams} to indicate that the
     * update ownership enforcement is requested.
     * @hide
     */
+8 −6
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
import android.os.UserHandle;
import android.util.ArrayMap;

import com.android.internal.util.Preconditions;

@@ -47,7 +48,8 @@ final class InstallArgs {
    final String mVolumeUuid;
    final UserHandle mUser;
    final String mAbiOverride;
    final String[] mInstallGrantPermissions;
    @NonNull
    final ArrayMap<String, Integer> mPermissionStates;
    final List<String> mAllowlistedRestrictedPermissions;
    final int mAutoRevokePermissionsMode;
    /** If non-null, drop an async trace when the install completes */
@@ -68,8 +70,8 @@ final class InstallArgs {

    InstallArgs(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
            int installFlags, InstallSource installSource, String volumeUuid,
            UserHandle user, String[] instructionSets,
            String abiOverride, String[] installGrantPermissions,
            UserHandle user, String[] instructionSets, String abiOverride,
            @NonNull ArrayMap<String, Integer> permissionStates,
            List<String> allowlistedRestrictedPermissions,
            int autoRevokePermissionsMode, String traceMethod, int traceCookie,
            SigningDetails signingDetails, int installReason, int installScenario,
@@ -84,7 +86,7 @@ final class InstallArgs {
        mUser = user;
        mInstructionSets = instructionSets;
        mAbiOverride = abiOverride;
        mInstallGrantPermissions = installGrantPermissions;
        mPermissionStates = permissionStates;
        mAllowlistedRestrictedPermissions = allowlistedRestrictedPermissions;
        mAutoRevokePermissionsMode = autoRevokePermissionsMode;
        mTraceMethod = traceMethod;
@@ -103,8 +105,8 @@ final class InstallArgs {
     * when cleaning up old installs, or used as a move source.
     */
    InstallArgs(String codePath, String[] instructionSets) {
        this(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY,
                null, null, instructionSets, null, null, null, MODE_DEFAULT, null, 0,
        this(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY, null, null,
                instructionSets, null, new ArrayMap<>(), null, MODE_DEFAULT, null, 0,
                SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN,
                PackageManager.INSTALL_SCENARIO_DEFAULT, false, DataLoaderType.NONE,
                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, false);
Loading