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

Commit 6df6883a authored by Jakob Schneider's avatar Jakob Schneider Committed by Android (Google) Code Review
Browse files

Merge "Implement uninstall behaviour for archiving" into main

parents 9b788d80 63c02696
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();
    }
}