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

Commit de13b994 authored by Sumedh Sen's avatar Sumedh Sen
Browse files

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
parent 1e55fa42
Loading
Loading
Loading
Loading
+94 −0
Original line number Diff line number Diff line
@@ -40,9 +40,11 @@ import android.os.Process;
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 @@ public class UninstallRepository {
    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 @@ public class UninstallRepository {
        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;
+62 −0
Original line number Diff line number Diff line
/*
 * 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);
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -42,6 +42,9 @@ public class UninstallViewModel extends AndroidViewModel {

    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);
    }
}