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

Commit 63c02696 authored by Jakob Schneider's avatar Jakob Schneider
Browse files

Implement uninstall behaviour for archiving

Test: ArchiveManagerTest
Bug: 282952870

Change-Id: I8d9d432afa313866e9810a52b48b9c6277e80ff2
parent 272f482d
Loading
Loading
Loading
Loading
+90 −10
Original line number Diff line number Diff line
@@ -16,14 +16,28 @@

package com.android.server.pm;

import static android.content.pm.PackageManager.DELETE_KEEP_DATA;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.IntentSender;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Binder;
import android.os.UserHandle;
import android.text.TextUtils;

import com.android.internal.annotations.GuardedBy;
import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo;
import com.android.server.pm.pkg.PackageStateInternal;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
@@ -35,37 +49,103 @@ import java.util.Objects;
 */
final class ArchiveManager {

    private final Context mContext;
    private final PackageManagerService mPm;

    ArchiveManager(PackageManagerService mPm) {
    @Nullable
    private LauncherApps mLauncherApps;

    ArchiveManager(Context context, PackageManagerService mPm) {
        this.mContext = context;
        this.mPm = mPm;
    }

    void archiveApp(
            @NonNull String packageName,
            @NonNull String callerPackageName,
            int userId,
            @NonNull UserHandle user,
            @NonNull IntentSender intentSender) throws PackageManager.NameNotFoundException {
        Objects.requireNonNull(packageName);
        Objects.requireNonNull(callerPackageName);
        Objects.requireNonNull(user);
        Objects.requireNonNull(intentSender);

        Computer snapshot = mPm.snapshotComputer();
        int callingUid = Binder.getCallingUid();
        int userId = user.getIdentifier();
        String callingPackageName = snapshot.getNameForUid(callingUid);
        snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "archiveApp");
        snapshot.enforceCrossUserPermission(callingUid, userId, true, true,
                "archiveApp");
        verifyCaller(callerPackageName, callingPackageName);

        PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
        PackageStateInternal ps = getPackageState(packageName, snapshot, callingUid, user);
        verifyInstallOwnership(packageName, callingPackageName, ps.getInstallSource());

        List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList(
                ps.getPackageName(),
                new UserHandle(userId));
        // TODO(b/291569242) Verify that this list is not empty and return failure with intentsender

        storeArchiveState(ps, mainActivities, userId);

        // TODO(b/278553670) Add special strings for the delete dialog
        mPm.mInstallerService.uninstall(
                new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
                callerPackageName, DELETE_KEEP_DATA, intentSender, userId);
    }

    @NonNull
    private static PackageStateInternal getPackageState(String packageName,
            Computer snapshot, int callingUid, UserHandle user)
            throws PackageManager.NameNotFoundException {
        PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid,
                user.getIdentifier());
        if (ps == null) {
            throw new PackageManager.NameNotFoundException(
                    TextUtils.formatSimple("Package %s not found.", packageName));
        }
        return ps;
    }

    private LauncherApps getLauncherApps() {
        if (mLauncherApps == null) {
            mLauncherApps = mContext.getSystemService(LauncherApps.class);
        }
        return mLauncherApps;
    }

        verifyInstallOwnership(packageName, callingPackageName, ps);
    private void storeArchiveState(PackageStateInternal ps,
            List<LauncherActivityInfo> mainActivities, int userId)
            throws PackageManager.NameNotFoundException {
        List<ArchiveActivityInfo> activityInfos = new ArrayList<>();
        for (int i = 0; i < mainActivities.size(); i++) {
            // TODO(b/278553670) Extract and store launcher icons
            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
                    mainActivities.get(i).getLabel().toString(),
                    Path.of("/TODO"), null);
            activityInfos.add(activityInfo);
        }
        // TODO(b/278553670) Adapt installer check verifyInstallOwnership and check for null there
        InstallSource installSource = ps.getInstallSource();
        String installerPackageName = installSource.mUpdateOwnerPackageName != null
                ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName;

        // TODO(b/278553670) Complete implementations
        throw new UnsupportedOperationException("Method not implemented.");
        synchronized (mPm.mLock) {
            getPackageSetting(ps.getPackageName(), userId).modifyUserState(userId).setArchiveState(
                    new ArchiveState(activityInfos, installerPackageName));
        }
    }

    @NonNull
    @GuardedBy("mPm.mLock")
    private PackageSetting getPackageSetting(String packageName, int userId)
            throws PackageManager.NameNotFoundException {
        PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
        if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
            throw new PackageManager.NameNotFoundException(
                    TextUtils.formatSimple("Package %s not found.", packageName));
        }
        return ps;
    }

    private static void verifyCaller(String callerPackageName, String callingPackageName) {
@@ -80,14 +160,14 @@ final class ArchiveManager {
    }

    private static void verifyInstallOwnership(String packageName, String callingPackageName,
            PackageStateInternal ps) {
        if (!TextUtils.equals(ps.getInstallSource().mInstallerPackageName,
            InstallSource installSource) {
        if (!TextUtils.equals(installSource.mInstallerPackageName,
                callingPackageName)) {
            throw new SecurityException(
                    TextUtils.formatSimple("Caller is not the installer of record for %s.",
                            packageName));
        }
        String updateOwnerPackageName = ps.getInstallSource().mUpdateOwnerPackageName;
        String updateOwnerPackageName = installSource.mUpdateOwnerPackageName;
        if (updateOwnerPackageName != null
                && !TextUtils.equals(updateOwnerPackageName, callingPackageName)) {
            throw new SecurityException(
+250 −0
Original line number Diff line number Diff line
@@ -16,28 +16,48 @@

package com.android.server.pm;

import static android.content.pm.PackageManager.DELETE_KEEP_DATA;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.IntentSender;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Binder;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageStateInternal;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -45,13 +65,24 @@ public class ArchiveManagerTest {

    private static final String PACKAGE = "com.example";
    private static final String CALLER_PACKAGE = "com.vending";
    private static final int USER_ID = 1;

    @Mock private IntentSender mIntentSender;
    @Mock private PackageManagerService mPm;
    @Mock private Computer mComputer;
    @Mock private PackageStateInternal mPackageState;
    private InstallSource mInstallSource =
    @Rule
    public final MockSystemRule mMockSystem = new MockSystemRule();

    @Mock
    private IntentSender mIntentSender;
    @Mock
    private Computer mComputer;
    @Mock
    private Context mContext;
    @Mock
    private LauncherApps mLauncherApps;
    @Mock
    private PackageInstallerService mInstallerService;
    @Mock
    private PackageStateInternal mPackageState;

    private final InstallSource mInstallSource =
            InstallSource.create(
                    CALLER_PACKAGE,
                    CALLER_PACKAGE,
@@ -61,25 +92,48 @@ public class ArchiveManagerTest {
                    /* installerAttributionTag= */ null,
                    /* packageSource= */ 0);

    private final List<LauncherActivityInfo> mLauncherActivityInfos = createLauncherActivities();

    private PackageSetting mPackageSetting;

    private PackageManagerService mPm;
    private ArchiveManager mArchiveManager;
    private int mCallingUid;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mArchiveManager = new ArchiveManager(mPm);
        mCallingUid = Binder.getCallingUid();
        when(mPm.snapshotComputer()).thenReturn(mComputer);
        when(mComputer.getNameForUid(eq(mCallingUid))).thenReturn(CALLER_PACKAGE);
        when(mComputer.getPackageStateInternal(eq(PACKAGE))).thenReturn(mPackageState);
        mMockSystem.system().stageNominalSystemState();
        when(mMockSystem.mocks().getInjector().getPackageInstallerService()).thenReturn(
                mInstallerService);
        mPm = spy(new PackageManagerService(mMockSystem.mocks().getInjector(),
                /* factoryTest= */false,
                MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint,
                /* isEngBuild= */ false,
                /* isUserDebugBuild= */false,
                Build.VERSION_CODES.CUR_DEVELOPMENT,
                Build.VERSION.INCREMENTAL));

        when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
                mPackageState);
        when(mPackageState.getPackageName()).thenReturn(PACKAGE);
        when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
        mPackageSetting = createBasicPackageSetting();
        when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
                mPackageSetting);
        when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
        when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
                mLauncherActivityInfos);
        doReturn(mComputer).when(mPm).snapshotComputer();
        when(mComputer.getNameForUid(eq(Binder.getCallingUid()))).thenReturn(CALLER_PACKAGE);
        mArchiveManager = new ArchiveManager(mContext, mPm);
    }

    @Test
    public void archiveApp_callerPackageNameIncorrect() {
        Exception e = assertThrows(
                SecurityException.class,
                () -> mArchiveManager.archiveApp(PACKAGE, "different", USER_ID, mIntentSender)
                () -> mArchiveManager.archiveApp(PACKAGE, "different", UserHandle.CURRENT,
                        mIntentSender)
        );
        assertThat(e).hasMessageThat().isEqualTo(
                String.format(
@@ -91,11 +145,25 @@ public class ArchiveManagerTest {

    @Test
    public void archiveApp_packageNotInstalled() {
        when(mComputer.getPackageStateInternal(eq(PACKAGE))).thenReturn(null);
        when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
                null);

        Exception e = assertThrows(
                PackageManager.NameNotFoundException.class,
                () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, USER_ID, mIntentSender)
                () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
                        mIntentSender)
        );
        assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
    }

    @Test
    public void archiveApp_packageNotInstalledForUser() {
        mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);

        Exception e = assertThrows(
                PackageManager.NameNotFoundException.class,
                () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
                        mIntentSender)
        );
        assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
    }
@@ -115,7 +183,8 @@ public class ArchiveManagerTest {

        Exception e = assertThrows(
                SecurityException.class,
                () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, USER_ID, mIntentSender)
                () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, Process.myUserHandle(),
                        mIntentSender)
        );
        assertThat(e).hasMessageThat().isEqualTo(
                String.format("Caller is not the installer of record for %s.", PACKAGE));
@@ -136,9 +205,46 @@ public class ArchiveManagerTest {

        Exception e = assertThrows(
                SecurityException.class,
                () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, USER_ID, mIntentSender)
                () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
                        mIntentSender)
        );
        assertThat(e).hasMessageThat().isEqualTo(
                String.format("Caller is not the update owner for %s.", PACKAGE));
    }

    @Test
    public void archiveApp_success() throws PackageManager.NameNotFoundException {
        List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
        for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
            // TODO(b/278553670) Extract and store launcher icons
            ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
                    mainActivity.getLabel().toString(),
                    Path.of("/TODO"), null);
            activityInfos.add(activityInfo);
        }

        mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, mIntentSender);
        verify(mInstallerService).uninstall(
                eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
                eq(CALLER_PACKAGE), eq(DELETE_KEEP_DATA), eq(mIntentSender),
                eq(UserHandle.CURRENT.getIdentifier()));
        assertThat(mPackageSetting.readUserState(
                UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo(
                new ArchiveState(activityInfos, CALLER_PACKAGE));
    }

    private static List<LauncherActivityInfo> createLauncherActivities() {
        LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);
        when(activity1.getLabel()).thenReturn("activity1");
        LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);
        when(activity2.getLabel()).thenReturn("activity2");
        return List.of(activity1, activity2);
    }

    private PackageSetting createBasicPackageSetting() {
        return new PackageSettingBuilder()
                .setName(PACKAGE).setCodePath("/data/app/" + PACKAGE + "-randompath")
                .setInstallState(UserHandle.CURRENT.getIdentifier(), /* installed= */ true)
                .build();
    }
}