Merge "Update owner under multi-user scenarios" into udc-dev
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 2b4ea70..7766896 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -581,7 +581,7 @@
 
     /**
      * Indicate the user intervention is required because the update ownership enforcement is
-     * enabled, and remind the update owner will retain.
+     * enabled, and remind the update owner is a different package.
      *
      * @see PackageInstaller.SessionParams#setRequestUpdateOwnership
      * @see InstallSourceInfo#getUpdateOwnerPackageName
@@ -2978,8 +2978,7 @@
          * permission. Default to {@code false}.
          *
          * The update ownership enforcement can only be enabled on initial installation. Set
-         * this to {@code true} on package update indicates the installer package wants to be
-         * the update owner if the update ownership enforcement has enabled.
+         * this to {@code true} on package update is a no-op.
          *
          * Note: To enable the update ownership enforcement, the installer must have the
          * {@link android.Manifest.permission#ENFORCE_UPDATE_OWNERSHIP ENFORCE_UPDATE_OWNERSHIP}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 23cce6a..db05b95 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1607,6 +1607,13 @@
      */
     public static final int INSTALL_REQUEST_UPDATE_OWNERSHIP = 1 << 25;
 
+    /**
+     * Flag parameter for {@link PackageInstaller.SessionParams} to indicate that this
+     * session is from a managed user or profile.
+     * @hide
+     */
+    public static final int INSTALL_FROM_MANAGED_USER_OR_PROFILE = 1 << 26;
+
     /** @hide */
     @IntDef(flag = true, value = {
             DONT_KILL_APP,
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index ab680b0..6669e35 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -38,9 +38,6 @@
     <!-- 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 [DO NOT TRANSLATE][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>
-    <!-- TODO(b/244413073) Revise the description after getting UX input and UXR on this. -->
     <!-- Message for updating an existing app with update owner reminder [DO NOT TRANSLATE][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] -->
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index fd41f5f..d41cfbc2 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -144,18 +144,11 @@
 
             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));
-                }
+            if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
+                    && mPendingUserActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP) {
+                viewToEnable.setText(
+                        getString(R.string.install_confirm_question_update_owner_reminder,
+                                existingUpdateOwnerLabel, requestedUpdateOwnerLabel));
             }
 
             mOk.setText(R.string.update);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 58a609e..5984360 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -65,6 +65,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -2190,6 +2191,11 @@
                 && UserHandle.getAppId(uid) == pkg.getUid();
     }
 
+    private boolean isCallerFromManagedUserOrProfile(@UserIdInt int userId) {
+        final var dpmi = mInjector.getLocalService(DevicePolicyManagerInternal.class);
+        return dpmi != null && dpmi.isUserOrganizationManaged(userId);
+    }
+
     public final boolean isComponentVisibleToInstantApp(@Nullable ComponentName component) {
         if (isComponentVisibleToInstantApp(component, TYPE_ACTIVITY)) {
             return true;
@@ -5002,8 +5008,15 @@
         updateOwnerPackageName = installSource.mUpdateOwnerPackageName;
         if (updateOwnerPackageName != null) {
             final PackageStateInternal ps = mSettings.getPackage(updateOwnerPackageName);
+            final boolean isCallerSystemOrUpdateOwner = callingUid == Process.SYSTEM_UID
+                            || isCallerSameApp(updateOwnerPackageName, callingUid);
+            // Except for package visibility filtering, we also hide update owner if the installer
+            // is in the managed user or profile. As we don't enforce the update ownership for the
+            // managed user and profile, knowing there's an update owner is meaningless in that
+            // user unless the installer is the owner.
             if (ps == null
-                    || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
+                    || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)
+                    || (!isCallerSystemOrUpdateOwner && isCallerFromManagedUserOrProfile(userId))) {
                 updateOwnerPackageName = null;
             }
         }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4b2be2f..a868470 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -102,6 +102,7 @@
 import android.app.ApplicationExitInfo;
 import android.app.ApplicationPackageManager;
 import android.app.BroadcastOptions;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.backup.IBackupManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -144,6 +145,7 @@
 import android.stats.storage.StorageEnums;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -317,10 +319,16 @@
 
         InstallSource installSource = request.getInstallSource();
         final boolean isApex = (scanFlags & SCAN_AS_APEX) != 0;
+        final boolean pkgAlreadyExists = oldPkgSetting != null;
+        final boolean isAllowUpdateOwnership = parsedPackage.isAllowUpdateOwnership();
+        final String oldUpdateOwner =
+                pkgAlreadyExists ? oldPkgSetting.getInstallSource().mUpdateOwnerPackageName : null;
         final String updateOwnerFromSysconfig = isApex || !pkgSetting.isSystem() ? null
                 : mPm.mInjector.getSystemConfig().getSystemAppUpdateOwnerPackageName(
                         parsedPackage.getPackageName());
-        // For new install (standard install), the installSource isn't null.
+        final boolean isUpdateOwnershipEnabled = oldUpdateOwner != null;
+
+        // For standard install (install via session), the installSource isn't null.
         if (installSource != null) {
             // If this is part of a standard install, set the initiating package name, else rely on
             // previous device state.
@@ -334,39 +342,68 @@
             }
 
             // Handle the update ownership enforcement for APK
-            if (updateOwnerFromSysconfig != null) {
-                // For system app, we always use the update owner from sysconfig if presented.
-                installSource = installSource.setUpdateOwnerPackageName(updateOwnerFromSysconfig);
-            } else if (!parsedPackage.isAllowUpdateOwnership()) {
+            if (!isAllowUpdateOwnership) {
                 // If the app wants to opt-out of the update ownership enforcement via manifest,
                 // it overrides the installer's use of #setRequestUpdateOwnership.
                 installSource = installSource.setUpdateOwnerPackageName(null);
             } else if (!isApex) {
-                final boolean isUpdate = oldPkgSetting != null;
-                final String oldUpdateOwner =
-                        isUpdate ? oldPkgSetting.getInstallSource().mUpdateOwnerPackageName : null;
-                final boolean isUpdateOwnershipEnabled = oldUpdateOwner != null;
+                // User installer UID as "current" userId if present; otherwise, use the userId
+                // from InstallRequest.
+                final int userId = installSource.mInstallerPackageUid != Process.INVALID_UID
+                        ? UserHandle.getUserId(installSource.mInstallerPackageUid)
+                        : request.getUserId();
+                // Whether the parsedPackage is installed on the userId
+                // If the oldPkgSetting doesn't exist, this package isn't installed for all users.
+                final boolean isUpdate = pkgAlreadyExists && (userId >= UserHandle.USER_SYSTEM
+                        // If userID >= 0, we could check via oldPkgSetting.getInstalled(userId).
+                        ? oldPkgSetting.getInstalled(userId)
+                        // When userId is -1 (USER_ALL) and it's not installed for any user,
+                        // treat it as not installed.
+                        : oldPkgSetting.getNotInstalledUserIds().length
+                                <= (UserManager.isHeadlessSystemUserMode() ? 1 : 0));
                 final boolean isRequestUpdateOwnership = (request.getInstallFlags()
                         & PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0;
+                final boolean isSameUpdateOwner =
+                        TextUtils.equals(oldUpdateOwner, installSource.mInstallerPackageName);
 
-                // Here we assign the update owner for the package, and the rules are:
-                // -. If the installer doesn't request update ownership on initial installation,
-                //    keep the update owner as null.
-                // -. If the installer doesn't want to be the owner to provide the subsequent
-                //    update (doesn't request to be the update owner), e.g., non-store installer
-                //    (file manager), ADB, or DO/PO, we should not update the owner.
-                // -. Else, the installer requests update ownership on initial installation or
-                //    update, we use installSource.mUpdateOwnerPackageName as the update owner.
-                if (!isRequestUpdateOwnership || (isUpdate && !isUpdateOwnershipEnabled)) {
-                    installSource = installSource.setUpdateOwnerPackageName(oldUpdateOwner);
+                // Here we handle the update owner for the package, and the rules are:
+                // -. Only enabling update ownership enforcement on initial installation if the
+                //    installer has requested.
+                // -. Once the installer changes and users agree to proceed, clear the update
+                //    owner (package state in other users are taken into account as well).
+                if (!isUpdate) {
+                    if (!isRequestUpdateOwnership) {
+                        installSource = installSource.setUpdateOwnerPackageName(null);
+                    } else if ((!isUpdateOwnershipEnabled && pkgAlreadyExists)
+                            || (isUpdateOwnershipEnabled && !isSameUpdateOwner)) {
+                        installSource = installSource.setUpdateOwnerPackageName(null);
+                    }
+                } else if (!isSameUpdateOwner || !isUpdateOwnershipEnabled) {
+                    installSource = installSource.setUpdateOwnerPackageName(null);
                 }
             }
 
             pkgSetting.setInstallSource(installSource);
-        // non-standard install (addForInit and install existing packages), installSource is null.
-        } else if (updateOwnerFromSysconfig != null) {
-            // For system app, we always use the update owner from sysconfig if presented.
-            pkgSetting.setUpdateOwnerPackage(updateOwnerFromSysconfig);
+        // For non-standard install (addForInit), installSource is null.
+        } else if (pkgSetting.isSystem()) {
+            // We still honor the manifest attr if the system app wants to opt-out of it.
+            if (!isAllowUpdateOwnership) {
+                pkgSetting.setUpdateOwnerPackage(null);
+            } else {
+                final boolean isSameUpdateOwner = isUpdateOwnershipEnabled
+                        && TextUtils.equals(oldUpdateOwner, updateOwnerFromSysconfig);
+
+                // Here we handle the update owner for the system package, and the rules are:
+                // -. We use the update owner from sysconfig as the initial value.
+                // -. Once an app becomes to system app later via OTA, only retains the update
+                //    owner if it's consistence with sysconfig.
+                // -. Clear the update owner when update owner changes from sysconfig.
+                if (!pkgAlreadyExists || isSameUpdateOwner) {
+                    pkgSetting.setUpdateOwnerPackage(updateOwnerFromSysconfig);
+                } else {
+                    pkgSetting.setUpdateOwnerPackage(null);
+                }
+            }
         }
 
         if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
@@ -641,6 +678,18 @@
             }
 
             if (installed) {
+                final String updateOwner = pkgSetting.getInstallSource().mUpdateOwnerPackageName;
+                final var dpmi = mInjector.getLocalService(DevicePolicyManagerInternal.class);
+                final boolean isFromManagedUserOrProfile =
+                        dpmi != null && dpmi.isUserOrganizationManaged(userId);
+                // Here we handle the update owner when install existing package, and the rules are:
+                // -. Retain the update owner when enable a system app in managed user or profile.
+                // -. Retain the update owner if the installer is the same.
+                // -. Clear the update owner when update owner changes.
+                if (!preLockSnapshot.isCallerSameApp(updateOwner, callingUid)
+                        && (!pkgSetting.isSystem() || !isFromManagedUserOrProfile)) {
+                    pkgSetting.setUpdateOwnerPackage(null);
+                }
                 if (pkgSetting.getPkg() != null) {
                     final PermissionManagerServiceInternal.PackageInstalledParams.Builder
                             permissionParamsBuilder =
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 77ffa16..adc0b0b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -926,6 +926,11 @@
             requestedInstallerPackageName = null;
         }
 
+        final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
+        if (dpmi != null && dpmi.isUserOrganizationManaged(userId)) {
+            params.installFlags |= PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE;
+        }
+
         if (isApex || mContext.checkCallingOrSelfPermission(
                 Manifest.permission.ENFORCE_UPDATE_OWNERSHIP) == PackageManager.PERMISSION_DENIED) {
             params.installFlags &= ~PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index fb6ae8b..fa535c3 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -868,15 +868,13 @@
     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;
-    private static final int USER_ACTION_REQUIRED_UPDATE_OWNER_CHANGED = 3;
-    private static final int USER_ACTION_REQUIRED_UPDATE_OWNER_RETAINED = 4;
+    private static final int USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER = 3;
 
     @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
+            USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER,
     })
     @interface UserActionRequirement {}
 
@@ -937,7 +935,7 @@
                 : null;
         final boolean isInstallerOfRecord = isUpdate
                 && Objects.equals(existingInstallerPackageName, getInstallerPackageName());
-        final boolean isUpdateOwner = Objects.equals(existingUpdateOwnerPackageName,
+        final boolean isUpdateOwner = TextUtils.equals(existingUpdateOwnerPackageName,
                 getInstallerPackageName());
         final boolean isSelfUpdate = targetPackageUid == mInstallerUid;
         final boolean isPermissionGranted = isInstallPermissionGranted
@@ -947,6 +945,8 @@
         final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID);
         final boolean isInstallerSystem = (mInstallerUid == Process.SYSTEM_UID);
         final boolean isInstallerShell = (mInstallerUid == Process.SHELL_UID);
+        final boolean isFromManagedUserOrProfile =
+                (params.installFlags & PackageManager.INSTALL_FROM_MANAGED_USER_OR_PROFILE) != 0;
         final boolean isUpdateOwnershipEnforcementEnabled =
                 mPm.isUpdateOwnershipEnforcementAvailable()
                         && existingUpdateOwnerPackageName != null;
@@ -963,12 +963,10 @@
         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;
+                && !isInstallerShell
+                // We don't enforce the update ownership for the managed user and profile.
+                && !isFromManagedUserOrProfile) {
+            return USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER;
         }
 
         if (isPermissionGranted) {
@@ -2361,8 +2359,7 @@
         userActionRequirement = session.computeUserActionRequirement();
         session.updateUserActionRequirement(userActionRequirement);
         if (userActionRequirement == USER_ACTION_REQUIRED
-                || userActionRequirement == USER_ACTION_REQUIRED_UPDATE_OWNER_CHANGED
-                || userActionRequirement == USER_ACTION_REQUIRED_UPDATE_OWNER_RETAINED) {
+                || userActionRequirement == USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER) {
             session.sendPendingUserActionIntent(target);
             return true;
         }
@@ -2425,9 +2422,7 @@
     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:
+            case USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER:
                 return PackageInstaller.REASON_REMIND_OWNERSHIP;
             default:
                 return PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE;