Adding ArchiveManager skeleton with some basic validations

Bug: 278553670
Test: ArchiveManagerTest

Change-Id: Ice999833bce8579460d2a854bb709e67988579c2
This commit is contained in:
Jakob Schneider
2023-05-24 16:46:58 +01:00
parent 52e281a91e
commit 05d8d9a893
2 changed files with 241 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
/*
* 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));
}
}
}

View File

@@ -0,0 +1,144 @@
/*
* 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));
}
}