Adding ArchiveManager skeleton with some basic validations
Bug: 278553670 Test: ArchiveManagerTest Change-Id: Ice999833bce8579460d2a854bb709e67988579c2
This commit is contained in:
97
services/core/java/com/android/server/pm/ArchiveManager.java
Normal file
97
services/core/java/com/android/server/pm/ArchiveManager.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user