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

Commit 05d8d9a8 authored by Jakob Schneider's avatar Jakob Schneider
Browse files

Adding ArchiveManager skeleton with some basic validations

Bug: 278553670
Test: ArchiveManagerTest

Change-Id: Ice999833bce8579460d2a854bb709e67988579c2
parent 52e281a9
Loading
Loading
Loading
Loading
+97 −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
 *
 *      http://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.server.pm;

import android.annotation.NonNull;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.text.TextUtils;

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

import java.util.Objects;

/**
 * Responsible archiving apps and returning information about archived apps.
 *
 * <p> An archived app is in a state where the app is not fully on the device. APKs are removed
 * while the data directory is kept. Archived apps are included in the list of launcher apps where
 * tapping them re-installs the full app.
 */
final class ArchiveManager {

    private final PackageManagerService mPm;

    ArchiveManager(PackageManagerService mPm) {
        this.mPm = mPm;
    }

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

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

        PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
        if (ps == null) {
            throw new PackageManager.NameNotFoundException(
                    TextUtils.formatSimple("Package %s not found.", packageName));
        }

        verifyInstallOwnership(packageName, callingPackageName, ps);

        // TODO(b/278553670) Complete implementations
        throw new UnsupportedOperationException("Method not implemented.");
    }

    private static void verifyCaller(String callerPackageName, String callingPackageName) {
        if (!TextUtils.equals(callingPackageName, callerPackageName)) {
            throw new SecurityException(
                    TextUtils.formatSimple(
                            "The callerPackageName %s set by the caller doesn't match the "
                                    + "caller's own package name %s.",
                            callerPackageName,
                            callingPackageName));
        }
    }

    private static void verifyInstallOwnership(String packageName, String callingPackageName,
            PackageStateInternal ps) {
        if (!TextUtils.equals(ps.getInstallSource().mInstallerPackageName,
                callingPackageName)) {
            throw new SecurityException(
                    TextUtils.formatSimple("Caller is not the installer of record for %s.",
                            packageName));
        }
        String updateOwnerPackageName = ps.getInstallSource().mUpdateOwnerPackageName;
        if (updateOwnerPackageName != null
                && !TextUtils.equals(updateOwnerPackageName, callingPackageName)) {
            throw new SecurityException(
                    TextUtils.formatSimple("Caller is not the update owner for %s.", packageName));
        }
    }
}
+144 −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
 *
 *      http://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.server.pm;

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

import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;

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

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

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

@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
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 =
            InstallSource.create(
                    CALLER_PACKAGE,
                    CALLER_PACKAGE,
                    CALLER_PACKAGE,
                    Binder.getCallingUid(),
                    CALLER_PACKAGE,
                    /* installerAttributionTag= */ null,
                    /* packageSource= */ 0);

    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);
        when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
    }

    @Test
    public void archiveApp_callerPackageNameIncorrect() {
        Exception e = assertThrows(
                SecurityException.class,
                () -> mArchiveManager.archiveApp(PACKAGE, "different", USER_ID, mIntentSender)
        );
        assertThat(e).hasMessageThat().isEqualTo(
                String.format(
                        "The callerPackageName %s set by the caller doesn't match the "
                                + "caller's own package name %s.",
                        "different",
                        CALLER_PACKAGE));
    }

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

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

    @Test
    public void archiveApp_callerNotInstallerOfRecord() {
        InstallSource otherInstallSource =
                InstallSource.create(
                        CALLER_PACKAGE,
                        CALLER_PACKAGE,
                        /* installerPackageName= */ "different",
                        Binder.getCallingUid(),
                        CALLER_PACKAGE,
                        /* installerAttributionTag= */ null,
                        /* packageSource= */ 0);
        when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);

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

    @Test
    public void archiveApp_callerNotUpdateOwner() {
        InstallSource otherInstallSource =
                InstallSource.create(
                        CALLER_PACKAGE,
                        CALLER_PACKAGE,
                        CALLER_PACKAGE,
                        Binder.getCallingUid(),
                        /* updateOwnerPackageName= */ "different",
                        /* installerAttributionTag= */ null,
                        /* packageSource= */ 0);
        when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);

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