Generate data for user confirmation snippet
Compute the dialog title, message to show based on whether app is uninstalled from primary user or clone user.
Bug: 182205982
Test: builds successfully
Test: No CTS Tests. Flag to use new app is turned off by default
Change-Id: I5faf670edc98da58678dad6cd25e05eadc16e3c2
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
index abf8a34..56681d9 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
@@ -40,9 +40,11 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
+import com.android.packageinstaller.R;
import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallReady;
import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
+import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
import java.util.List;
public class UninstallRepository {
@@ -57,9 +59,11 @@
private ApplicationInfo mTargetAppInfo;
private ActivityInfo mTargetActivityInfo;
private Intent mIntent;
+ private CharSequence mTargetAppLabel;
private String mTargetPackageName;
private String mCallingActivity;
private boolean mUninstallFromAllUsers;
+ private boolean mIsClonedApp;
public UninstallRepository(Context context) {
mContext = context;
@@ -168,6 +172,96 @@
return new UninstallReady();
}
+ public UninstallStage generateUninstallDetails() {
+ UninstallUserActionRequired.Builder uarBuilder = new UninstallUserActionRequired.Builder();
+ StringBuilder messageBuilder = new StringBuilder();
+
+ mTargetAppLabel = mTargetAppInfo.loadSafeLabel(mPackageManager);
+
+ // If the Activity label differs from the App label, then make sure the user
+ // knows the Activity belongs to the App being uninstalled.
+ if (mTargetActivityInfo != null) {
+ final CharSequence activityLabel = mTargetActivityInfo.loadSafeLabel(mPackageManager);
+ if (CharSequence.compare(activityLabel, mTargetAppLabel) != 0) {
+ messageBuilder.append(
+ mContext.getString(R.string.uninstall_activity_text, activityLabel));
+ messageBuilder.append(" ").append(mTargetAppLabel).append(".\n\n");
+ }
+ }
+
+ final boolean isUpdate =
+ (mTargetAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ final UserHandle myUserHandle = Process.myUserHandle();
+ boolean isSingleUser = isSingleUser();
+
+ if (isUpdate) {
+ messageBuilder.append(mContext.getString(
+ isSingleUser ? R.string.uninstall_update_text :
+ R.string.uninstall_update_text_multiuser));
+ } else if (mUninstallFromAllUsers && !isSingleUser) {
+ messageBuilder.append(mContext.getString(
+ R.string.uninstall_application_text_all_users));
+ } else if (isCloneProfile(mUninstalledUser)) {
+ mIsClonedApp = true;
+ messageBuilder.append(mContext.getString(
+ R.string.uninstall_application_text_current_user_clone_profile));
+ } else if (myUserHandle.equals(UserHandle.SYSTEM)
+ && hasClonedInstance(mTargetAppInfo.packageName)) {
+ messageBuilder.append(mContext.getString(
+ R.string.uninstall_application_text_with_clone_instance, mTargetAppLabel));
+ } else {
+ messageBuilder.append(mContext.getString(R.string.uninstall_application_text));
+ }
+
+ uarBuilder.setMessage(messageBuilder.toString());
+
+ if (mIsClonedApp) {
+ uarBuilder.setTitle(mContext.getString(R.string.cloned_app_label, mTargetAppLabel));
+ } else {
+ uarBuilder.setTitle(mTargetAppLabel.toString());
+ }
+
+ return uarBuilder.build();
+ }
+
+ /**
+ * Returns whether there is only one "full" user on this device.
+ *
+ * <p><b>Note:</b> on devices that use {@link android.os.UserManager#isHeadlessSystemUserMode()
+ * headless system user mode}, the system user is not "full", so it's not be considered in the
+ * calculation.</p>
+ */
+ private boolean isSingleUser() {
+ final int userCount = mUserManager.getUserCount();
+ return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2);
+ }
+
+ private boolean hasClonedInstance(String packageName) {
+ // Check if clone user is present on the device.
+ UserHandle cloneUser = null;
+ List<UserHandle> profiles = mUserManager.getUserProfiles();
+ for (UserHandle userHandle : profiles) {
+ if (!userHandle.equals(UserHandle.SYSTEM) && isCloneProfile(userHandle)) {
+ cloneUser = userHandle;
+ break;
+ }
+ }
+ // Check if another instance of given package exists in clone user profile.
+ try {
+ return cloneUser != null
+ && mPackageManager.getPackageUidAsUser(packageName,
+ PackageManager.PackageInfoFlags.of(0), cloneUser.getIdentifier()) > 0;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ private boolean isCloneProfile(UserHandle userHandle) {
+ UserManager customUserManager = mContext.createContextAsUser(userHandle, 0)
+ .getSystemService(UserManager.class);
+ return customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE);
+ }
+
public static class CallerInfo {
private final String mActivityName;
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java
new file mode 100644
index 0000000..3051dc4
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 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
+ *
+ * https://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.packageinstaller.v2.model.uninstallstagedata;
+
+public class UninstallUserActionRequired extends UninstallStage {
+
+ private final int mStage = UninstallStage.STAGE_USER_ACTION_REQUIRED;
+ private final String mTitle;
+ private final String mMessage;
+
+ public UninstallUserActionRequired(String title, String message) {
+ mTitle = title;
+ mMessage = message;
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public String getMessage() {
+ return mMessage;
+ }
+
+ @Override
+ public int getStageCode() {
+ return mStage;
+ }
+
+ public static class Builder {
+
+ private String mTitle;
+ private String mMessage;
+
+ public Builder setTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ public Builder setMessage(String message) {
+ mMessage = message;
+ return this;
+ }
+
+ public UninstallUserActionRequired build() {
+ return new UninstallUserActionRequired(mTitle, mMessage);
+ }
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
index e524bca..1697832 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
@@ -42,6 +42,9 @@
public void preprocessIntent(Intent intent, CallerInfo callerInfo) {
UninstallStage stage = mRepository.performPreUninstallChecks(intent, callerInfo);
+ if (stage.getStageCode() != UninstallStage.STAGE_ABORTED) {
+ stage = mRepository.generateUninstallDetails();
+ }
mCurrentUninstallStage.setValue(stage);
}
}