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

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

Merge "Adding ArchiveManager skeleton with some basic validations"

parents 84747191 05d8d9a8
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));
    }
}