blob: 319e3ac784e455003b2aa526b3b420350b9e8945 [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 android.content.pm.cts;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.DONT_KILL_APP;
import static android.content.pm.PackageManager.MATCH_ARCHIVED_PACKAGES;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
import android.app.Instrumentation;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.Flags;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.pm.cts.PackageManagerShellCommandInstallTest.PackageBroadcastReceiver;
import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.text.TextUtils;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.SearchCondition;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
import com.android.compatibility.common.util.FeatureUtil;
import com.android.compatibility.common.util.SystemUtil;
import com.google.common.truth.Expect;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@AppModeFull
@RunWith(AndroidJUnit4.class)
public class PackageInstallerArchiveTest {
private static final String SAMPLE_APK_BASE = "/data/local/tmp/cts/content/";
private static final String PACKAGE_NAME = "android.content.cts.mocklauncherapp";
private static final String NO_ACTIVITY_PACKAGE_NAME =
"android.content.cts.IntentResolutionTest";
private static final String ACTIVITY_NAME = PACKAGE_NAME + ".Launcher";
private static final String APK_PATH = SAMPLE_APK_BASE + "CtsContentMockLauncherTestApp.apk";
private static final String NO_ACTIVITY_APK_PATH =
SAMPLE_APK_BASE + "CtsIntentResolutionTestApp.apk";
private static final int TIMEOUT = 30000;
private static final int SECOND = 1000;
private static CompletableFuture<Integer> sUnarchiveId;
private static CompletableFuture<String> sUnarchiveReceiverPackageName;
private static CompletableFuture<Boolean> sUnarchiveReceiverAllUsers;
@Rule
public final Expect expect = Expect.create();
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Rule
public AbandonAllPackageSessionsRule mAbandonSessionsRule = new AbandonAllPackageSessionsRule();
private Context mContext;
private UiDevice mUiDevice;
private PackageManager mPackageManager;
private PackageInstaller mPackageInstaller;
private StorageStatsManager mStorageStatsManager;
private ArchiveIntentSender mArchiveIntentSender;
private UnarchiveIntentSender mUnarchiveIntentSender;
private AppOpsManager mAppOpsManager;
@Before
public void setup() throws Exception {
assumeTrue("Form factor is not supported", isFormFactorSupported());
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
mUiDevice = UiDevice.getInstance(
androidx.test.InstrumentationRegistry.getInstrumentation());
mContext = instrumentation.getContext();
mPackageManager = mContext.getPackageManager();
mPackageInstaller = mPackageManager.getPackageInstaller();
mStorageStatsManager = mContext.getSystemService(StorageStatsManager.class);
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mArchiveIntentSender = new ArchiveIntentSender();
mUnarchiveIntentSender = new UnarchiveIntentSender();
sUnarchiveId = new CompletableFuture<>();
sUnarchiveReceiverPackageName = new CompletableFuture<>();
sUnarchiveReceiverAllUsers = new CompletableFuture<>();
mContext.getPackageManager().setComponentEnabledSetting(
new ComponentName(mContext, UnarchiveBroadcastReceiver.class),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
}
@After
public void uninstall() {
uninstallPackage(PACKAGE_NAME);
}
@Test
public void archiveApp_dataIsKept() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
// This creates a data directory which will be verified later.
launchTestActivity();
PackageInfo packageInfo = getPackageInfo();
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mPackage.get(5, TimeUnit.SECONDS)).isEqualTo(PACKAGE_NAME);
assertThat(mArchiveIntentSender.mStatus.get(10, TimeUnit.MILLISECONDS)).isEqualTo(
PackageInstaller.STATUS_SUCCESS);
StorageStats stats =
runWithShellPermissionIdentity(() ->
mStorageStatsManager.queryStatsForPackage(
packageInfo.applicationInfo.storageUuid,
packageInfo.packageName,
UserHandle.of(UserHandle.myUserId())),
Manifest.permission.PACKAGE_USAGE_STATS);
// This number is bound to fluctuate as the data created during app startup will change
// over time. We only need to verify that the data directory is kept.
assertTrue(stats.getDataBytes() > 0L);
}
@Test
public void archiveApp_getApplicationIcon() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get(5, TimeUnit.SECONDS)).isEqualTo(
PackageInstaller.STATUS_SUCCESS);
ApplicationInfo applicationInfo = mPackageManager.getPackageInfo(PACKAGE_NAME,
PackageInfoFlags.of(MATCH_ARCHIVED_PACKAGES)).applicationInfo;
Drawable actualIcon = mPackageManager.getApplicationIcon(applicationInfo);
Bitmap actualBitmap = drawableToBitmap(actualIcon);
assertThat(actualBitmap).isNotNull();
recycleBitmap(actualIcon, actualBitmap);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ARCHIVING)
public void archiveApp_uninstallationWorksCorrectly() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get(5, TimeUnit.SECONDS)).isEqualTo(
PackageInstaller.STATUS_SUCCESS);
uninstallPackage(PACKAGE_NAME);
assertThrows(PackageManager.NameNotFoundException.class,
() -> mPackageManager.getPackageInfo(PACKAGE_NAME,
PackageInfoFlags.of(MATCH_ARCHIVED_PACKAGES)));
}
@Test
public void archiveApp_noInstaller() throws NameNotFoundException {
installPackageWithNoInstaller(PACKAGE_NAME, APK_PATH);
PackageManager.NameNotFoundException e =
runWithShellPermissionIdentity(
() -> assertThrows(
PackageManager.NameNotFoundException.class,
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender))),
Manifest.permission.DELETE_PACKAGES);
assertThat(e).hasMessageThat().isEqualTo("No installer found");
}
@Test
public void archiveApp_installerDoesntSupportUnarchival() throws NameNotFoundException {
installPackage(PACKAGE_NAME, APK_PATH);
mContext.getPackageManager().setComponentEnabledSetting(
new ComponentName(mContext, UnarchiveBroadcastReceiver.class),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
PackageManager.NameNotFoundException e =
runWithShellPermissionIdentity(
() -> assertThrows(
PackageManager.NameNotFoundException.class,
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender))),
Manifest.permission.DELETE_PACKAGES);
assertThat(e).hasMessageThat().isEqualTo("Installer does not support unarchival");
}
@Test
public void archiveApp_systemApp() throws NameNotFoundException {
PackageManager.NameNotFoundException e =
runWithShellPermissionIdentity(
() -> assertThrows(
PackageManager.NameNotFoundException.class,
() -> mPackageInstaller.requestArchive("android",
new IntentSender((IIntentSender) mArchiveIntentSender))),
Manifest.permission.DELETE_PACKAGES);
assertThat(e).hasMessageThat().isEqualTo("System apps cannot be archived.");
}
@Test
public void matchArchivedPackages() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get(5, TimeUnit.SECONDS)).isEqualTo(
PackageInstaller.STATUS_SUCCESS);
assertThat(mPackageManager.getPackageInfo(PACKAGE_NAME,
PackageInfoFlags.of(
MATCH_UNINSTALLED_PACKAGES)).applicationInfo.isArchived).isTrue();
assertThat(mPackageManager.getPackageInfo(PACKAGE_NAME,
PackageInfoFlags.of(MATCH_ARCHIVED_PACKAGES)).applicationInfo.isArchived).isTrue();
assertThat(mPackageManager.getApplicationInfo(PACKAGE_NAME,
ApplicationInfoFlags.of(
MATCH_UNINSTALLED_PACKAGES)).isArchived).isTrue();
assertThat(mPackageManager.getApplicationInfo(PACKAGE_NAME,
ApplicationInfoFlags.of(MATCH_ARCHIVED_PACKAGES)).isArchived).isTrue();
// Ensure fully installed app are returned too.
assertThat(mPackageManager.getInstalledPackages(
PackageInfoFlags.of(MATCH_ARCHIVED_PACKAGES)).size()).isAtLeast(2);
assertThrows(NameNotFoundException.class,
() -> mPackageManager.getPackageInfo(PACKAGE_NAME, /* flags= */ 0));
assertThrows(NameNotFoundException.class,
() -> mPackageManager.getApplicationInfo(PACKAGE_NAME, /* flags= */ 0));
assertThat(
mPackageManager.getInstalledApplications(
ApplicationInfoFlags.of(MATCH_ARCHIVED_PACKAGES))
.stream()
.anyMatch(
applicationInfo ->
applicationInfo.packageName.equals(PACKAGE_NAME)
&& applicationInfo.isArchived))
.isTrue();
}
@Test
public void archiveApp_missingPermissions() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
SecurityException e =
assertThrows(
SecurityException.class,
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)));
assertThat(e).hasMessageThat().isEqualTo("You need the "
+ "com.android.permission.DELETE_PACKAGES or "
+ "com.android.permission.REQUEST_DELETE_PACKAGES permission to request an "
+ "archival."
);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ARCHIVING)
public void archiveApp_getArchiveTimeMillis() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
final long timestampBeforeArchive = System.currentTimeMillis();
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get()).isEqualTo(PackageInstaller.STATUS_SUCCESS);
final long timestampAfterArchive = System.currentTimeMillis();
// Test that the archiveTimeMillis field is valid
PackageInfo pi = mPackageManager.getPackageInfo(PACKAGE_NAME,
PackageInfoFlags.of(MATCH_ARCHIVED_PACKAGES));
assertThat(pi).isNotNull();
assertThat(pi.getArchiveTimeMillis()).isGreaterThan(timestampBeforeArchive);
assertThat(pi.getArchiveTimeMillis()).isLessThan(timestampAfterArchive);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ARCHIVING)
public void archiveApp_archiveStateClearedAfterUpdate() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get()).isEqualTo(PackageInstaller.STATUS_SUCCESS);
// Test that the archiveTimeMillis field is valid
PackageInfo pi = mPackageManager.getPackageInfo(PACKAGE_NAME,
PackageInfoFlags.of(MATCH_ARCHIVED_PACKAGES));
assertThat(pi).isNotNull();
assertThat(pi.getArchiveTimeMillis()).isGreaterThan(0);
assertThat(pi.applicationInfo.isArchived).isTrue();
// reinstall the app
installPackage(PACKAGE_NAME, APK_PATH);
pi = mPackageManager.getPackageInfo(PACKAGE_NAME, PackageInfoFlags.of(0));
assertThat(pi).isNotNull();
assertThat(pi.getArchiveTimeMillis()).isEqualTo(0);
assertThat(pi.applicationInfo.isArchived).isFalse();
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ARCHIVING)
public void unarchiveApp() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get(5, TimeUnit.SECONDS)).isEqualTo(
PackageInstaller.STATUS_SUCCESS);
SessionListener sessionListener = new SessionListener();
mPackageInstaller.registerSessionCallback(sessionListener,
new Handler(Looper.getMainLooper()));
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestUnarchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mUnarchiveIntentSender)),
Manifest.permission.INSTALL_PACKAGES);
assertThat(sUnarchiveReceiverPackageName.get(5, TimeUnit.SECONDS)).isEqualTo(PACKAGE_NAME);
assertThat(sUnarchiveReceiverAllUsers.get(10, TimeUnit.MILLISECONDS)).isFalse();
int unarchiveId = sUnarchiveId.get(10, TimeUnit.MILLISECONDS);
int draftSessionId = sessionListener.mSessionIdCreated.get(5, TimeUnit.SECONDS);
PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(
draftSessionId);
assertThat(unarchiveId).isEqualTo(draftSessionId);
assertThat(sessionInfo.appPackageName).isEqualTo(PACKAGE_NAME);
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setUnarchiveId(unarchiveId);
params.appPackageName = PACKAGE_NAME;
int sessionId = mPackageInstaller.createSession(params);
assertThat(unarchiveId).isEqualTo(sessionId);
mPackageInstaller.abandonSession(sessionId);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ARCHIVING)
public void unarchiveApp_repeatedCallsDeduplicated() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get(5, TimeUnit.SECONDS)).isEqualTo(
PackageInstaller.STATUS_SUCCESS);
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestUnarchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mUnarchiveIntentSender)),
Manifest.permission.INSTALL_PACKAGES);
int unarchiveId1 = sUnarchiveId.get(5, TimeUnit.SECONDS);
sUnarchiveId = new CompletableFuture<>();
sUnarchiveReceiverPackageName = new CompletableFuture<>();
sUnarchiveReceiverAllUsers = new CompletableFuture<>();
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestUnarchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mUnarchiveIntentSender)),
Manifest.permission.INSTALL_PACKAGES);
int unarchiveId2 = sUnarchiveId.get(5, TimeUnit.SECONDS);
assertThat(unarchiveId1).isEqualTo(unarchiveId2);
mPackageInstaller.abandonSession(unarchiveId1);
}
@Test
@Ignore("b/312496640")
public void unarchiveApp_missingPermissions() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
assertThat(
SystemUtil.runShellCommand(String.format("pm archive %s", PACKAGE_NAME))).isEqualTo(
"Success\n");
SecurityException e =
assertThrows(
SecurityException.class,
() -> mPackageInstaller.requestUnarchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mUnarchiveIntentSender)));
assertThat(e).hasMessageThat().isEqualTo("You need the "
+ "com.android.permission.INSTALL_PACKAGES or "
+ "com.android.permission.REQUEST_INSTALL_PACKAGES permission to request an "
+ "unarchival."
);
}
// TODO(b/312452414) Move to PackageInstallerActivity directory.
@Test
@RequiresFlagsEnabled(Flags.FLAG_ARCHIVING)
public void unarchiveApp_weakPermissions() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get(5, TimeUnit.SECONDS)).isEqualTo(
PackageInstaller.STATUS_SUCCESS);
SessionListener sessionListener = new SessionListener();
mPackageInstaller.registerSessionCallback(sessionListener,
new Handler(Looper.getMainLooper()));
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestUnarchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mUnarchiveIntentSender)),
Manifest.permission.REQUEST_INSTALL_PACKAGES);
assertThat(mUnarchiveIntentSender.mPackage.get(5, TimeUnit.SECONDS)).isEqualTo(
PACKAGE_NAME);
assertThat(mUnarchiveIntentSender.mStatus.get(10, TimeUnit.MILLISECONDS)).isEqualTo(
PackageInstaller.STATUS_PENDING_USER_ACTION);
Intent extraIntent = mUnarchiveIntentSender.mPermissionIntent.get(10,
TimeUnit.MILLISECONDS);
extraIntent.addFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
prepareDevice();
mContext.startActivity(extraIntent);
mUiDevice.waitForIdle();
assertThat(waitFor(Until.findObject(By.textContains("Restore")))).isNotNull();
UiObject2 clickableView = mUiDevice.findObject(By.text("Restore"));
if (clickableView == null) {
Assert.fail("Restore button not shown");
}
clickableView.click();
assertThat(sUnarchiveReceiverPackageName.get(10, TimeUnit.SECONDS)).isEqualTo(PACKAGE_NAME);
int unarchiveId = sUnarchiveId.get(10, TimeUnit.MILLISECONDS);
mPackageInstaller.abandonSession(unarchiveId);
}
private UiObject2 waitFor(SearchCondition<UiObject2> condition)
throws InterruptedException {
final long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < TIMEOUT) {
try {
var result = mUiDevice.wait(condition, SECOND);
if (result == null) {
continue;
}
return result;
} catch (Throwable e) {
Thread.sleep(1000);
}
}
Assert.fail("Unable to wait for the activity");
return null;
}
private void prepareDevice() throws Exception {
mUiDevice.waitForIdle();
// wake up the screen
mUiDevice.wakeUp();
// unlock the keyguard or the expected window is by systemui or other alert window
mUiDevice.pressMenu();
// dismiss the system alert window for requesting permissions
mUiDevice.pressBack();
// return to home/launcher to prevent from being obscured by systemui or other alert window
mUiDevice.pressHome();
// Wait for device idle
mUiDevice.waitForIdle();
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ARCHIVING)
public void reportUnarchivalStatus_success() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get(5, TimeUnit.SECONDS)).isEqualTo(
PackageInstaller.STATUS_SUCCESS);
SessionListener sessionListener = new SessionListener();
mPackageInstaller.registerSessionCallback(sessionListener,
new Handler(Looper.getMainLooper()));
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestUnarchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mUnarchiveIntentSender)),
Manifest.permission.INSTALL_PACKAGES);
int unarchiveId = sUnarchiveId.get(5, TimeUnit.SECONDS);
runWithShellPermissionIdentity(
() -> mPackageInstaller.reportUnarchivalStatus(unarchiveId,
PackageInstaller.UNARCHIVAL_OK, /* requiredStorageBytes= */ 0,
/* userActionIntent= */ null),
Manifest.permission.INSTALL_PACKAGES);
assertThat(mUnarchiveIntentSender.mPackage.get(5, TimeUnit.SECONDS)).isEqualTo(
PACKAGE_NAME);
assertThat(mUnarchiveIntentSender.mStatus.get(10, TimeUnit.MILLISECONDS)).isEqualTo(
PackageInstaller.UNARCHIVAL_OK);
mPackageInstaller.abandonSession(unarchiveId);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ARCHIVING)
public void reportUnarchivalStatus_error() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get(5, TimeUnit.SECONDS)).isEqualTo(
PackageInstaller.STATUS_SUCCESS);
SessionListener sessionListener = new SessionListener();
mPackageInstaller.registerSessionCallback(sessionListener,
new Handler(Looper.getMainLooper()));
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestUnarchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mUnarchiveIntentSender)),
Manifest.permission.INSTALL_PACKAGES);
int unarchiveId = sUnarchiveId.get(5, TimeUnit.SECONDS);
int draftSessionId = sessionListener.mSessionIdCreated.get(10, TimeUnit.MILLISECONDS);
runWithShellPermissionIdentity(
() -> mPackageInstaller.reportUnarchivalStatus(unarchiveId,
PackageInstaller.UNARCHIVAL_GENERIC_ERROR, /* requiredStorageBytes= */ 0,
/* userActionIntent= */ null),
Manifest.permission.INSTALL_PACKAGES);
assertThat(mUnarchiveIntentSender.mPackage.get(5, TimeUnit.SECONDS)).isEqualTo(
PACKAGE_NAME);
assertThat(mUnarchiveIntentSender.mStatus.get(10, TimeUnit.MILLISECONDS)).isEqualTo(
PackageInstaller.UNARCHIVAL_GENERIC_ERROR);
assertThat(sessionListener.mSessionIdFinished.get(5, TimeUnit.SECONDS)).isEqualTo(
draftSessionId);
}
@Test
public void archiveApp_noMainActivity() throws NameNotFoundException {
// To ensure the installer is set.
uninstallPackage(NO_ACTIVITY_PACKAGE_NAME);
installPackage(NO_ACTIVITY_PACKAGE_NAME, NO_ACTIVITY_APK_PATH);
PackageManager.NameNotFoundException e =
runWithShellPermissionIdentity(
() -> assertThrows(
PackageManager.NameNotFoundException.class,
() -> mPackageInstaller.requestArchive(NO_ACTIVITY_PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender))),
Manifest.permission.DELETE_PACKAGES);
assertThat(e).hasMessageThat()
.isEqualTo(TextUtils.formatSimple("The app %s does not have a main activity.",
NO_ACTIVITY_PACKAGE_NAME));
// Reset for other PM tests.
installPackageWithNoInstaller(NO_ACTIVITY_PACKAGE_NAME, NO_ACTIVITY_APK_PATH);
}
@Test
public void archiveApp_appOptedOut() throws NameNotFoundException {
installPackage(PACKAGE_NAME, APK_PATH);
setOptInStatus(PACKAGE_NAME, /* optIn= */ false);
PackageManager.NameNotFoundException e =
runWithShellPermissionIdentity(
() -> assertThrows(
PackageManager.NameNotFoundException.class,
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender))),
Manifest.permission.DELETE_PACKAGES);
assertThat(e).hasMessageThat().isEqualTo(
TextUtils.formatSimple("The app %s is opted out of archiving.", PACKAGE_NAME));
}
@Test
public void archiveApp_shellCommand() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
assertThat(
SystemUtil.runShellCommand(String.format("pm archive %s", PACKAGE_NAME))).isEqualTo(
"Success\n");
assertThat(mPackageManager.getPackageInfo(PACKAGE_NAME,
PackageInfoFlags.of(MATCH_ARCHIVED_PACKAGES)).applicationInfo.isArchived).isTrue();
}
@Test
public void unarchiveApp_shellCommand() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
assertThat(
SystemUtil.runShellCommand(String.format("pm archive %s", PACKAGE_NAME))).isEqualTo(
"Success\n");
assertThat(
SystemUtil.runShellCommand(String.format("pm request-unarchive %s", PACKAGE_NAME)))
.isEqualTo("Success\n");
assertThat(sUnarchiveReceiverPackageName.get(5, TimeUnit.SECONDS)).isEqualTo(PACKAGE_NAME);
assertThat(sUnarchiveReceiverAllUsers.get(1, TimeUnit.MILLISECONDS)).isFalse();
}
@Test
public void archiveApp_broadcasts() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
int currentUser = ActivityManager.getCurrentUser();
PackageBroadcastReceiver
addedBroadcastReceiver = new PackageBroadcastReceiver(
PACKAGE_NAME, currentUser, Intent.ACTION_PACKAGE_ADDED
);
PackageBroadcastReceiver removedBroadcastReceiver = new PackageBroadcastReceiver(
PACKAGE_NAME, currentUser, Intent.ACTION_PACKAGE_REMOVED
);
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
try {
mContext.registerReceiver(removedBroadcastReceiver, intentFilter);
runWithShellPermissionIdentity(
() -> mPackageInstaller.requestArchive(PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get(5, TimeUnit.SECONDS)).isEqualTo(
PackageInstaller.STATUS_SUCCESS);
removedBroadcastReceiver.assertBroadcastReceived();
Intent removedIntent = removedBroadcastReceiver.getBroadcastResult();
assertNotNull(removedIntent);
assertTrue(removedIntent.getExtras().getBoolean(Intent.EXTRA_ARCHIVAL, false));
assertTrue(removedIntent.getExtras().getBoolean(Intent.EXTRA_REPLACING, false));
mContext.registerReceiver(addedBroadcastReceiver, intentFilter);
installPackage(PACKAGE_NAME, APK_PATH);
addedBroadcastReceiver.assertBroadcastReceived();
Intent addedIntent = addedBroadcastReceiver.getBroadcastResult();
assertNotNull(addedIntent);
assertTrue(addedIntent.getExtras().getBoolean(Intent.EXTRA_REPLACING, false));
} finally {
try {
mContext.unregisterReceiver(removedBroadcastReceiver);
mContext.unregisterReceiver(addedBroadcastReceiver);
} catch (Exception e) {
// Already unregistered.
}
}
}
@Test
public void isAppArchivable_success() throws NameNotFoundException {
installPackage(PACKAGE_NAME, APK_PATH);
assertThat(mContext.getPackageManager().isAppArchivable(PACKAGE_NAME)).isTrue();
}
@Test
public void isAppArchivable_installerDoesntSupportUnarchival() throws NameNotFoundException {
installPackage(PACKAGE_NAME, APK_PATH);
mContext.getPackageManager().setComponentEnabledSetting(
new ComponentName(mContext, UnarchiveBroadcastReceiver.class),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
assertThat(mContext.getPackageManager().isAppArchivable(PACKAGE_NAME)).isFalse();
}
@Test
public void isAppArchivable_noMainActivity() throws NameNotFoundException {
// To ensure the installer is set.
uninstallPackage(NO_ACTIVITY_PACKAGE_NAME);
installPackage(NO_ACTIVITY_PACKAGE_NAME, NO_ACTIVITY_APK_PATH);
assertThat(
mContext.getPackageManager().isAppArchivable(NO_ACTIVITY_PACKAGE_NAME)).isFalse();
// Reset for other PM tests.
installPackageWithNoInstaller(NO_ACTIVITY_PACKAGE_NAME, NO_ACTIVITY_APK_PATH);
}
@Test
public void isAppArchivable_appOptedOut() throws NameNotFoundException {
installPackage(PACKAGE_NAME, APK_PATH);
setOptInStatus(PACKAGE_NAME, /* optIn= */ false);
assertThat(
mContext.getPackageManager().isAppArchivable(PACKAGE_NAME)).isFalse();
}
@Test
public void isAppArchivable_systemApp() throws NameNotFoundException {
assertThat(
mContext.getPackageManager().isAppArchivable("android")).isFalse();
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ARCHIVING)
public void startUnarchival_intentIsNotRelatedToArchivedApp()
throws NameNotFoundException, ExecutionException, InterruptedException {
installPackage(PACKAGE_NAME, APK_PATH);
runWithShellPermissionIdentity(
() ->
mPackageInstaller.requestArchive(
PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get()).isEqualTo(PackageInstaller.STATUS_SUCCESS);
ComponentName archiveComponentName =
new ComponentName(PACKAGE_NAME, "ClassRandom.MainActivity");
Intent callingIntent =
new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setClassName(PACKAGE_NAME, archiveComponentName.getClassName())
.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
ActivityNotFoundException e =
assertThrows(
ActivityNotFoundException.class,
() -> mContext.startActivity(callingIntent));
assertThat(e)
.hasMessageThat()
.contains(
TextUtils.formatSimple(
"Unable to find explicit activity class %s",
archiveComponentName.toShortString()));
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ARCHIVING)
public void startUnarchival_appIsNotDefaultLauncher_permissionDeniedForUnarchival()
throws NameNotFoundException, ExecutionException, InterruptedException {
installPackage(PACKAGE_NAME, APK_PATH);
runWithShellPermissionIdentity(
() ->
mPackageInstaller.requestArchive(
PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get()).isEqualTo(PackageInstaller.STATUS_SUCCESS);
ComponentName archiveComponentName = new ComponentName(PACKAGE_NAME, ACTIVITY_NAME);
Intent callingIntent =
new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setClassName(PACKAGE_NAME, archiveComponentName.getClassName())
.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
SecurityException e =
assertThrows(
SecurityException.class,
() -> mContext.startActivity(callingIntent));
assertThat(e).hasMessageThat().contains("Not allowed to start activity Intent");
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ARCHIVING)
public void startUnarchival_success() throws Exception {
installPackage(PACKAGE_NAME, APK_PATH);
runWithShellPermissionIdentity(
() ->
mPackageInstaller.requestArchive(
PACKAGE_NAME,
new IntentSender((IIntentSender) mArchiveIntentSender)),
Manifest.permission.DELETE_PACKAGES);
assertThat(mArchiveIntentSender.mStatus.get()).isEqualTo(PackageInstaller.STATUS_SUCCESS);
ComponentName archiveComponentName = new ComponentName(PACKAGE_NAME, ACTIVITY_NAME);
SystemUtil.runShellCommand(
TextUtils.formatSimple(
"am start -n %s", archiveComponentName.flattenToShortString()));
assertThat(sUnarchiveReceiverPackageName.get()).isEqualTo(PACKAGE_NAME);
assertThat(sUnarchiveReceiverAllUsers.get()).isFalse();
}
private void launchTestActivity() {
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
mContext.startActivity(createTestActivityIntent(),
options.toBundle());
mUiDevice.wait(Until.hasObject(By.clazz(PACKAGE_NAME, ACTIVITY_NAME)),
TimeUnit.SECONDS.toMillis(5));
}
private Intent createTestActivityIntent() {
final Intent intent = new Intent();
intent.setClassName(PACKAGE_NAME, ACTIVITY_NAME);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
return intent;
}
private void uninstallPackage(String packageName) {
SystemUtil.runShellCommand(
String.format("pm uninstall %s", packageName));
}
private void installPackage(String packageName, String path) throws NameNotFoundException {
assertEquals("Success\n", SystemUtil.runShellCommand(
String.format("pm install -r -i %s -t -g %s", mContext.getPackageName(),
path)));
setOptInStatus(packageName, /* optIn= */ true);
}
private void setOptInStatus(String packageName, boolean optIn) throws NameNotFoundException {
ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(
packageName, /* flags= */ 0);
runWithShellPermissionIdentity(
() -> mAppOpsManager.setUidMode(
AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
applicationInfo.uid,
optIn ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED),
Manifest.permission.MANAGE_APP_OPS_MODES);
}
private void installPackageWithNoInstaller(String packageName, String path)
throws NameNotFoundException {
assertEquals("Success\n",
SystemUtil.runShellCommand(String.format("pm install -r -t -g %s", path)));
setOptInStatus(packageName, /* optIn= */ true);
}
private PackageInfo getPackageInfo() {
try {
return mContext.getPackageManager().getPackageInfo(
PACKAGE_NAME, /* flags= */ 0);
} catch (NameNotFoundException e) {
fail("Package " + PACKAGE_NAME + " not installed for user "
+ mContext.getUser() + ": " + e);
}
return null;
}
private static void recycleBitmap(Drawable icon, Bitmap bitmap) {
bitmap.recycle();
if (icon instanceof BitmapDrawable) {
((BitmapDrawable) icon).getBitmap().recycle();
}
}
private static boolean isFormFactorSupported() {
return !FeatureUtil.isArc()
&& !FeatureUtil.isAutomotive()
&& !FeatureUtil.isTV()
&& !FeatureUtil.isWatch()
&& !FeatureUtil.isVrHeadset();
}
private static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(
drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
public static class UnarchiveBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(Intent.ACTION_UNARCHIVE_PACKAGE)) {
return;
}
if (sUnarchiveId == null) {
sUnarchiveId = new CompletableFuture<>();
}
sUnarchiveId.complete(intent.getIntExtra(PackageInstaller.EXTRA_UNARCHIVE_ID, -1));
if (sUnarchiveReceiverPackageName == null) {
sUnarchiveReceiverPackageName = new CompletableFuture<>();
}
sUnarchiveReceiverPackageName.complete(
intent.getStringExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME));
if (sUnarchiveReceiverAllUsers == null) {
sUnarchiveReceiverAllUsers = new CompletableFuture<>();
}
sUnarchiveReceiverAllUsers.complete(
intent.getBooleanExtra(PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS,
true /* defaultValue */));
}
}
static class ArchiveIntentSender extends IIntentSender.Stub {
final CompletableFuture<String> mPackage = new CompletableFuture<>();
final CompletableFuture<Integer> mStatus = new CompletableFuture<>();
final CompletableFuture<String> mMessage = new CompletableFuture<>();
@Override
public void send(int code, Intent intent, String resolvedType,
IBinder whitelistToken, IIntentReceiver finishedReceiver,
String requiredPermission, Bundle options) throws RemoteException {
mPackage.complete(intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME));
mStatus.complete(intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -100));
mMessage.complete(intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
}
}
static class UnarchiveIntentSender extends IIntentSender.Stub {
final CompletableFuture<String> mPackage = new CompletableFuture<>();
final CompletableFuture<Integer> mStatus = new CompletableFuture<>();
final CompletableFuture<Intent> mPermissionIntent = new CompletableFuture<>();
@Override
public void send(int code, Intent intent, String resolvedType,
IBinder whitelistToken, IIntentReceiver finishedReceiver,
String requiredPermission, Bundle options) throws RemoteException {
mPackage.complete(intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME));
int status = intent.getIntExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, -100);
mStatus.complete(status);
if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
mPermissionIntent.complete(
intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class));
}
}
}
static class SessionListener extends PackageInstaller.SessionCallback {
final CompletableFuture<Integer> mSessionIdCreated = new CompletableFuture<>();
final CompletableFuture<Integer> mSessionIdFinished = new CompletableFuture<>();
@Override
public void onCreated(int sessionId) {
mSessionIdCreated.complete(sessionId);
}
@Override
public void onBadgingChanged(int sessionId) {
}
@Override
public void onActiveChanged(int sessionId, boolean active) {
}
@Override
public void onProgressChanged(int sessionId, float progress) {
}
@Override
public void onFinished(int sessionId, boolean success) {
mSessionIdFinished.complete(sessionId);
}
}
}