blob: e7f1d16e1dfd672b47730bd85e03790c3b3e1ecb [file] [log] [blame]
/*
* 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 android.content.Intent.FLAG_RECEIVER_FOREGROUND;
import static android.content.pm.PackageManager.DELETE_ARCHIVE;
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.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
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.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelableException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.text.TextUtils;
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 com.android.server.pm.pkg.PackageUserStateImpl;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
public class PackageArchiverTest {
private static final String PACKAGE = "com.example";
private static final String CALLER_PACKAGE = "com.caller";
private static final String INSTALLER_PACKAGE = "com.installer";
private static final Path ICON_PATH = Path.of("icon.png");
@Rule
public final MockSystemRule rule = new MockSystemRule();
@Mock
private IntentSender mIntentSender;
@Mock
private Computer mComputer;
@Mock
private Context mContext;
@Mock
private LauncherApps mLauncherApps;
@Mock
private ActivityManager mActivityManager;
@Mock
private PackageManager mPackageManager;
@Mock
private PackageInstallerService mInstallerService;
@Mock
private PackageStateInternal mPackageState;
@Mock
private Bitmap mIcon;
private final InstallSource mInstallSource =
InstallSource.create(
INSTALLER_PACKAGE,
INSTALLER_PACKAGE,
INSTALLER_PACKAGE,
Binder.getCallingUid(),
INSTALLER_PACKAGE,
/* installerAttributionTag= */ null,
/* packageSource= */ 0);
private final List<LauncherActivityInfo> mLauncherActivityInfos = createLauncherActivities();
private final int mUserId = UserHandle.CURRENT.getIdentifier();
private PackageUserStateImpl mUserState;
private PackageSetting mPackageSetting;
private PackageManagerService mPackageManagerService;
private PackageArchiver mArchiveManager;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
rule.system().stageNominalSystemState();
when(rule.mocks().getInjector().getPackageInstallerService()).thenReturn(
mInstallerService);
mPackageManagerService = spy(new PackageManagerService(rule.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(mComputer.getPackageStateFiltered(eq(INSTALLER_PACKAGE), anyInt(),
anyInt())).thenReturn(mock(PackageStateInternal.class));
when(mPackageState.getPackageName()).thenReturn(PACKAGE);
when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
mPackageSetting = createBasicPackageSetting();
when(rule.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
mPackageSetting);
mUserState = new PackageUserStateImpl().setInstalled(true);
mPackageSetting.setUserState(mUserId, mUserState);
when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState);
when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
mLauncherActivityInfos);
when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100);
doReturn(mComputer).when(mPackageManagerService).snapshotComputer();
when(mComputer.getPackageUid(eq(CALLER_PACKAGE), eq(0L), eq(mUserId))).thenReturn(
Binder.getCallingUid());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn(
mock(Resources.class));
doReturn(new ParceledListSlice<>(List.of(mock(ResolveInfo.class))))
.when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(),
eq(mUserId));
mArchiveManager = spy(new PackageArchiver(mContext, mPackageManagerService));
doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE),
any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
doReturn(mIcon).when(mArchiveManager).decodeIcon(
any(ArchiveState.ArchiveActivityInfo.class));
}
@Test
public void archiveApp_callerPackageNameIncorrect() {
Exception e = assertThrows(
SecurityException.class,
() -> mArchiveManager.requestArchive(PACKAGE, "different", mIntentSender,
UserHandle.CURRENT));
assertThat(e).hasMessageThat().isEqualTo(
String.format(
"The UID %s of callerPackageName set by the caller doesn't match the "
+ "caller's actual UID %s.",
0,
Binder.getCallingUid()));
}
@Test
public void archiveApp_packageNotInstalled() {
when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
null);
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s not found.", PACKAGE));
}
@Test
public void archiveApp_packageNotInstalledForUser() throws IntentSender.SendIntentException {
mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mIntentSender).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any(),
any(), any());
Intent value = intentCaptor.getValue();
assertThat(value.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)).isEqualTo(PACKAGE);
assertThat(value.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)).isEqualTo(
PackageInstaller.STATUS_FAILURE);
assertThat(value.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)).isEqualTo(
String.format("Package %s not found.", PACKAGE));
}
@Test
public void archiveApp_noInstallerFound() {
InstallSource otherInstallSource =
InstallSource.create(
CALLER_PACKAGE,
CALLER_PACKAGE,
/* installerPackageName= */ null,
Binder.getCallingUid(),
/* updateOwnerPackageName= */ null,
/* installerAttributionTag= */ null,
/* packageSource= */ 0);
when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo("No installer found");
}
@Test
public void archiveApp_installerDoesntSupportUnarchival() {
doReturn(new ParceledListSlice<>(List.of()))
.when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(),
eq(mUserId));
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
"Installer does not support unarchival");
}
@Test
public void archiveApp_noMainActivities() {
when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
List.of());
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE));
}
@Test
public void archiveApp_storeIconFails() throws IntentSender.SendIntentException, IOException {
IOException e = new IOException("IO");
doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mIntentSender).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any(),
any(), any());
Intent value = intentCaptor.getValue();
assertThat(value.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)).isEqualTo(PACKAGE);
assertThat(value.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)).isEqualTo(
PackageInstaller.STATUS_FAILURE);
assertThat(value.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)).isEqualTo(
e.toString());
}
@Test
public void archiveApp_success() {
mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
verify(mInstallerService).uninstall(
eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
eq(CALLER_PACKAGE), eq(DELETE_ARCHIVE | DELETE_KEEP_DATA), eq(mIntentSender),
eq(UserHandle.CURRENT.getIdentifier()), anyInt());
assertThat(mPackageSetting.readUserState(
UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo(
createArchiveState());
}
@Test
public void unarchiveApp_callerPackageNameIncorrect() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
Exception e = assertThrows(
SecurityException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, "different",
UserHandle.CURRENT));
assertThat(e).hasMessageThat().isEqualTo(
String.format(
"The UID %s of callerPackageName set by the caller doesn't match the "
+ "caller's actual UID %s.",
0,
Binder.getCallingUid()));
}
@Test
public void unarchiveApp_packageNotInstalled() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
null);
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s not found.", PACKAGE));
}
@Test
public void unarchiveApp_notArchived_missingArchiveState() {
mUserState.setInstalled(false);
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s is not currently archived.", PACKAGE));
}
@Test
public void unarchiveApp_notArchived_stillInstalled() {
mUserState.setArchiveState(createArchiveState());
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s is not currently archived.", PACKAGE));
}
@Test
public void unarchiveApp_noInstallerFound() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
InstallSource otherInstallSource =
InstallSource.create(
CALLER_PACKAGE,
CALLER_PACKAGE,
/* installerPackageName= */ null,
Binder.getCallingUid(),
/* updateOwnerPackageName= */ null,
/* installerAttributionTag= */ null,
/* packageSource= */ 0);
when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("No installer found to unarchive app %s.", PACKAGE));
}
@Test
public void unarchiveApp_success() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext).sendOrderedBroadcastAsUser(
intentCaptor.capture(),
eq(UserHandle.CURRENT),
/* receiverPermission = */ isNull(),
eq(AppOpsManager.OP_NONE),
any(Bundle.class),
/* resultReceiver= */ isNull(),
/* scheduler= */ isNull(),
/* initialCode= */ eq(0),
/* initialData= */ isNull(),
/* initialExtras= */ isNull());
Intent intent = intentCaptor.getValue();
assertThat(intent.getFlags() & FLAG_RECEIVER_FOREGROUND).isNotEqualTo(0);
assertThat(intent.getStringExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME)).isEqualTo(
PACKAGE);
assertThat(
intent.getBooleanExtra(PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS, true)).isFalse();
assertThat(intent.getPackage()).isEqualTo(INSTALLER_PACKAGE);
}
@Test
public void getArchivedAppIcon_packageNotInstalled() {
when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
null);
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s not found.", PACKAGE));
}
@Test
public void getArchivedAppIcon_notArchived() {
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s is not currently archived.", PACKAGE));
}
@Test
public void getArchivedAppIcon_success() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isEqualTo(
mIcon);
}
private static ArchiveState createArchiveState() {
List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
mainActivity.getLabel().toString(),
mainActivity.getComponentName(),
ICON_PATH, null);
activityInfos.add(activityInfo);
}
return new ArchiveState(activityInfos, INSTALLER_PACKAGE);
}
private static List<LauncherActivityInfo> createLauncherActivities() {
ActivityInfo activityInfo = mock(ActivityInfo.class);
LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);
when(activity1.getLabel()).thenReturn("activity1");
when(activity1.getComponentName()).thenReturn(new ComponentName("pkg1", "class1"));
when(activity1.getActivityInfo()).thenReturn(activityInfo);
LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);
when(activity2.getLabel()).thenReturn("activity2");
when(activity2.getComponentName()).thenReturn(new ComponentName("pkg2", "class2"));
when(activity2.getActivityInfo()).thenReturn(activityInfo);
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();
}
}