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

Commit a7651690 authored by Jackal Guo's avatar Jackal Guo
Browse files

Revise the logic of computing user intervention (2/n)

In computeUserActionRequirement(), it now honors update ownership
enforcement. If the app being installed has the update owner, the
user intervention will be required once the update isn't performed
by the update owner. In this Cl, we also revise the PackageInstaler
app to reflect the result to users.

Bug: 244413073
Test: atest CtsPackageInstallTestCases:UpdateOwnershipEnforcementTest
Change-Id: Ie620013a4adf861956226d34637dbd3a0dd9a3ea
parent 6b11d1e5
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -3592,6 +3592,9 @@ package android.content.pm {
    field public static final int LOCATION_DATA_APP = 0; // 0x0
    field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
    field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
    field public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; // 0x0
    field public static final int REASON_OWNERSHIP_CHANGED = 1; // 0x1
    field public static final int REASON_REMIND_OWNERSHIP = 2; // 0x2
  }
  public static class PackageInstaller.InstallInfo {
@@ -3621,6 +3624,7 @@ package android.content.pm {
    method public boolean getInstallAsFullApp(boolean);
    method public boolean getInstallAsInstantApp(boolean);
    method public boolean getInstallAsVirtualPreload();
    method public int getPendingUserActionReason();
    method public boolean getRequestDowngrade();
    method public int getRollbackDataPolicy();
    method @NonNull public java.util.Set<java.lang.String> getWhitelistedRestrictedPermissions();
+66 −3
Original line number Diff line number Diff line
@@ -544,6 +544,46 @@ public class PackageInstaller {
    @Retention(RetentionPolicy.SOURCE)
    @interface PackageSourceType{}

    /**
     * Indicate the user intervention is required when the installer attempts to commit the session.
     * This is the default case.
     *
     * @hide
     */
    @SystemApi
    public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0;

    /**
     * Indicate the user intervention is required because the update ownership enforcement is
     * enabled, and the update owner will change.
     *
     * @see PackageInstaller.SessionParams#setRequestUpdateOwnership
     * @see InstallSourceInfo#getUpdateOwnerPackageName
     * @hide
     */
    @SystemApi
    public static final int REASON_OWNERSHIP_CHANGED = 1;

    /**
     * Indicate the user intervention is required because the update ownership enforcement is
     * enabled, and remind the update owner will retain.
     *
     * @see PackageInstaller.SessionParams#setRequestUpdateOwnership
     * @see InstallSourceInfo#getUpdateOwnerPackageName
     * @hide
     */
    @SystemApi
    public static final int REASON_REMIND_OWNERSHIP = 2;

    /** @hide */
    @IntDef(prefix = { "REASON_" }, value = {
            REASON_CONFIRM_PACKAGE_CHANGE,
            REASON_OWNERSHIP_CHANGED,
            REASON_REMIND_OWNERSHIP,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface UserActionReason {}

    /** Default set of checksums - includes all available checksums.
     * @see Session#requestChecksums  */
    private static final int DEFAULT_CHECKSUMS =
@@ -2686,9 +2726,18 @@ public class PackageInstaller {
         *              Android S ({@link android.os.Build.VERSION_CODES#S API 31})</li>
         *          </ul>
         *     </li>
         *     <li>The installer is the {@link InstallSourceInfo#getInstallingPackageName()
         *     installer of record} of an existing version of the app (in other words, this install
         *     session is an app update) or the installer is updating itself.</li>
         *     <li>The installer is:
         *         <ul>
         *             <li>The {@link InstallSourceInfo#getUpdateOwnerPackageName() update owner}
         *             of an existing version of the app (in other words, this install session is
         *             an app update) if the update ownership enforcement is enabled.</li>
         *             <li>The {@link InstallSourceInfo#getInstallingPackageName() installer of
         *             record} of an existing version of the app (in other words, this install
         *             session is an app update) if the update ownership enforcement isn't
         *             enabled.</li>
         *             <li>Updating itself.</li>
         *         </ul>
         *     </li>>
         *     <li>The installer declares the
         *     {@link android.Manifest.permission#UPDATE_PACKAGES_WITHOUT_USER_ACTION
         *     UPDATE_PACKAGES_WITHOUT_USER_ACTION} permission.</li>
@@ -3025,6 +3074,9 @@ public class PackageInstaller {
        /** @hide */
        public boolean keepApplicationEnabledSetting;

        /** @hide */
        public int pendingUserActionReason;

        /** {@hide} */
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        public SessionInfo() {
@@ -3079,6 +3131,7 @@ public class PackageInstaller {
            installerUid = source.readInt();
            packageSource = source.readInt();
            keepApplicationEnabledSetting = source.readBoolean();
            pendingUserActionReason = source.readInt();
        }

        /**
@@ -3633,6 +3686,15 @@ public class PackageInstaller {
            return (installFlags & PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0;
        }

        /**
         * Return the reason for requiring the user action.
         * @hide
         */
        @SystemApi
        public @UserActionReason int getPendingUserActionReason() {
            return pendingUserActionReason;
        }

        @Override
        public int describeContents() {
            return 0;
@@ -3683,6 +3745,7 @@ public class PackageInstaller {
            dest.writeInt(installerUid);
            dest.writeInt(packageSource);
            dest.writeBoolean(keepApplicationEnabledSetting);
            dest.writeInt(pendingUserActionReason);
        }

        public static final Parcelable.Creator<SessionInfo>
+5 −0
Original line number Diff line number Diff line
@@ -37,6 +37,11 @@
    <string name="install_confirm_question">Do you want to install this app?</string>
    <!-- Message for updating an existing app [CHAR LIMIT=NONE] -->
    <string name="install_confirm_question_update">Do you want to update this app?</string>
    <!-- TODO(b/244413073) Revise the description after getting UX input and UXR on this. -->
    <!-- Message for updating an existing app when updating owner changed [CHAR LIMIT=NONE] -->
    <string name="install_confirm_question_update_owner_changed">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nBy updating, you\'ll get future updates from <xliff:g id="new_update_owner">%2$s</xliff:g> instead.</string>
    <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
    <string name="install_confirm_question_update_owner_reminder">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nDo you want to install this update from <xliff:g id="new_update_owner">%2$s</xliff:g>.</string>
    <!-- [CHAR LIMIT=100] -->
    <string name="install_failed">App not installed.</string>
    <!-- Reason displayed when installation fails because the package was blocked
+47 −1
Original line number Diff line number Diff line
@@ -33,10 +33,12 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
@@ -47,9 +49,11 @@ import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
@@ -88,6 +92,7 @@ public class PackageInstallerActivity extends AlertActivity {
    private int mOriginatingUid = Process.INVALID_UID;
    private String mOriginatingPackage; // The package name corresponding to #mOriginatingUid
    private int mActivityResultCode = Activity.RESULT_CANCELED;
    private int mPendingUserActionReason = -1;

    private final boolean mLocalLOGV = false;
    PackageManager mPm;
@@ -132,10 +137,27 @@ public class PackageInstallerActivity extends AlertActivity {
    private boolean mEnableOk = false;

    private void startInstallConfirm() {
        View viewToEnable;
        TextView viewToEnable;

        if (mAppInfo != null) {
            viewToEnable = requireViewById(R.id.install_confirm_question_update);

            final CharSequence existingUpdateOwnerLabel = getExistingUpdateOwnerLabel();
            final CharSequence requestedUpdateOwnerLabel = getApplicationLabel(mCallingPackage);
            if (!TextUtils.isEmpty(existingUpdateOwnerLabel)) {
                if (mPendingUserActionReason == PackageInstaller.REASON_OWNERSHIP_CHANGED) {
                    viewToEnable.setText(
                            getString(R.string.install_confirm_question_update_owner_changed,
                                    existingUpdateOwnerLabel, requestedUpdateOwnerLabel));
                } else if (mPendingUserActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP
                        || mPendingUserActionReason
                        == PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE) {
                    viewToEnable.setText(
                            getString(R.string.install_confirm_question_update_owner_reminder,
                                    existingUpdateOwnerLabel, requestedUpdateOwnerLabel));
                }
            }

            mOk.setText(R.string.update);
        } else {
            // This is a new application with no permissions.
@@ -149,6 +171,27 @@ public class PackageInstallerActivity extends AlertActivity {
        mOk.setFilterTouchesWhenObscured(true);
    }

    private CharSequence getExistingUpdateOwnerLabel() {
        try {
            final String packageName = mPkgInfo.packageName;
            final InstallSourceInfo sourceInfo = mPm.getInstallSourceInfo(packageName);
            final String existingUpdateOwner = sourceInfo.getUpdateOwnerPackageName();
            return getApplicationLabel(existingUpdateOwner);
        } catch (NameNotFoundException e) {
            return null;
        }
    }

    private CharSequence getApplicationLabel(String packageName) {
        try {
            final ApplicationInfo appInfo = mPm.getApplicationInfo(packageName,
                    ApplicationInfoFlags.of(0));
            return mPm.getApplicationLabel(appInfo);
        } catch (NameNotFoundException e) {
            return null;
        }
    }

    /**
     * Replace any dialog shown by the dialog with the one for the given {@link #createDialog(int)}.
     *
@@ -344,6 +387,7 @@ public class PackageInstallerActivity extends AlertActivity {
            packageSource = Uri.fromFile(new File(resolvedBaseCodePath));
            mOriginatingURI = null;
            mReferrerURI = null;
            mPendingUserActionReason = info.getPendingUserActionReason();
        } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(action)) {
            final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
                    -1 /* defaultValue */);
@@ -358,11 +402,13 @@ public class PackageInstallerActivity extends AlertActivity {
            packageSource = info;
            mOriginatingURI = null;
            mReferrerURI = null;
            mPendingUserActionReason = info.getPendingUserActionReason();
        } else {
            mSessionId = -1;
            packageSource = intent.getData();
            mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
            mPendingUserActionReason = PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE;
        }

        // if there's nothing to do, quietly slip into the ether
+67 −9
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.PreapprovalDetails;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageInstaller.UserActionReason;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.pm.PackageManagerInternal;
@@ -447,6 +448,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    @GuardedBy("mLock")
    private boolean mHasDeviceAdminReceiver;

    @GuardedBy("mLock")
    private int mUserActionRequirement;

    static class FileEntry {
        private final int mIndex;
        private final InstallationFile mFile;
@@ -843,10 +847,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    private static final int USER_ACTION_NOT_NEEDED = 0;
    private static final int USER_ACTION_REQUIRED = 1;
    private static final int USER_ACTION_PENDING_APK_PARSING = 2;

    @IntDef({USER_ACTION_NOT_NEEDED, USER_ACTION_REQUIRED, USER_ACTION_PENDING_APK_PARSING})
    @interface
    UserActionRequirement {}
    private static final int USER_ACTION_REQUIRED_UPDATE_OWNER_CHANGED = 3;
    private static final int USER_ACTION_REQUIRED_UPDATE_OWNER_RETAINED = 4;

    @IntDef({
            USER_ACTION_NOT_NEEDED,
            USER_ACTION_REQUIRED,
            USER_ACTION_PENDING_APK_PARSING,
            USER_ACTION_REQUIRED_UPDATE_OWNER_CHANGED,
            USER_ACTION_REQUIRED_UPDATE_OWNER_RETAINED
    })
    @interface UserActionRequirement {}

    /**
     * Checks if the permissions still need to be confirmed.
@@ -900,8 +911,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        final String existingInstallerPackageName = existingInstallSourceInfo != null
                ? existingInstallSourceInfo.getInstallingPackageName()
                : null;
        final String existingUpdateOwnerPackageName = existingInstallSourceInfo != null
                ? existingInstallSourceInfo.getUpdateOwnerPackageName()
                : null;
        final boolean isInstallerOfRecord = isUpdate
                && Objects.equals(existingInstallerPackageName, getInstallerPackageName());
        final boolean isUpdateOwner = Objects.equals(existingUpdateOwnerPackageName,
                getInstallerPackageName());
        final boolean isSelfUpdate = targetPackageUid == mInstallerUid;
        final boolean isPermissionGranted = isInstallPermissionGranted
                || (isUpdatePermissionGranted && isUpdate)
@@ -909,16 +925,35 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                || (isInstallDpcPackagesPermissionGranted && hasDeviceAdminReceiver);
        final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID);
        final boolean isInstallerSystem = (mInstallerUid == Process.SYSTEM_UID);
        final boolean isInstallerShell = (mInstallerUid == Process.SHELL_UID);
        final boolean isUpdateOwnershipEnforcementEnabled =
                mPm.isUpdateOwnershipEnforcementAvailable()
                        && existingUpdateOwnerPackageName != null;

        // Device owners and affiliated profile owners are allowed to silently install packages, so
        // the permission check is waived if the installer is the device owner.
        final boolean noUserActionNecessary = isPermissionGranted || isInstallerRoot
                || isInstallerSystem || isInstallerDeviceOwnerOrAffiliatedProfileOwner();
        final boolean noUserActionNecessary = isInstallerRoot || isInstallerSystem
                || isInstallerDeviceOwnerOrAffiliatedProfileOwner();

        if (noUserActionNecessary) {
            return USER_ACTION_NOT_NEEDED;
        }

        if (isUpdateOwnershipEnforcementEnabled
                && !isApexSession()
                && !isUpdateOwner
                && !isInstallerShell) {
            final boolean isRequestUpdateOwner =
                    (params.installFlags & PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0;

            return isRequestUpdateOwner ? USER_ACTION_REQUIRED_UPDATE_OWNER_CHANGED
                    : USER_ACTION_REQUIRED_UPDATE_OWNER_RETAINED;
        }

        if (isPermissionGranted) {
            return USER_ACTION_NOT_NEEDED;
        }

        if (snapshot.isInstallDisabledForPackage(getInstallerPackageName(), mInstallerUid,
                userId)) {
            // show the installer to account for device policy or unknown sources use cases
@@ -927,13 +962,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

        if (params.requireUserAction == SessionParams.USER_ACTION_NOT_REQUIRED
                && isUpdateWithoutUserActionPermissionGranted
                && (isInstallerOfRecord || isSelfUpdate)) {
                && ((isUpdateOwnershipEnforcementEnabled ? isUpdateOwner
                : isInstallerOfRecord) || isSelfUpdate)) {
            return USER_ACTION_PENDING_APK_PARSING;
        }

        return USER_ACTION_REQUIRED;
    }

    private void updateUserActionRequirement(int requirement) {
        synchronized (mLock) {
            mUserActionRequirement = requirement;
        }
    }

    @SuppressWarnings("GuardedBy" /*mPm.mInstaller is {@code final} field*/)
    public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
            Context context, PackageManagerService pm,
@@ -1122,6 +1164,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            info.installerUid = mInstallerUid;
            info.packageSource = params.packageSource;
            info.keepApplicationEnabledSetting = params.keepApplicationEnabledSetting;
            info.pendingUserActionReason = userActionRequirementToReason(mUserActionRequirement);
        }
        return info;
    }
@@ -2222,7 +2265,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        @UserActionRequirement int userActionRequirement = USER_ACTION_NOT_NEEDED;
        // TODO(b/159331446): Move this to makeSessionActiveForInstall and update javadoc
        userActionRequirement = session.computeUserActionRequirement();
        if (userActionRequirement == USER_ACTION_REQUIRED) {
        session.updateUserActionRequirement(userActionRequirement);
        if (userActionRequirement == USER_ACTION_REQUIRED
                || userActionRequirement == USER_ACTION_REQUIRED_UPDATE_OWNER_CHANGED
                || userActionRequirement == USER_ACTION_REQUIRED_UPDATE_OWNER_RETAINED) {
            session.sendPendingUserActionIntent(target);
            return true;
        }
@@ -2255,6 +2301,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        return false;
    }

    private static @UserActionReason int userActionRequirementToReason(
            @UserActionRequirement int requirement) {
        switch (requirement) {
            case USER_ACTION_REQUIRED_UPDATE_OWNER_CHANGED:
                return PackageInstaller.REASON_OWNERSHIP_CHANGED;
            case USER_ACTION_REQUIRED_UPDATE_OWNER_RETAINED:
                return PackageInstaller.REASON_REMIND_OWNERSHIP;
            default:
                return PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE;
        }
    }

    /**
     * Find out any session needs user action.
     *