blob: 6efa5b91162157484fdd618bda8a9278a8800697 [file] [log] [blame]
/*
* Copyright (C) 2020 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.suspendapps.cts;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.suspendapps.cts.Constants.ALL_TEST_PACKAGES;
import static android.suspendapps.cts.Constants.DEVICE_ADMIN_COMPONENT;
import static android.suspendapps.cts.Constants.DEVICE_ADMIN_PACKAGE;
import static android.suspendapps.cts.Constants.TEST_APP_PACKAGE_NAME;
import static android.suspendapps.cts.Constants.TEST_PACKAGE_ARRAY;
import static android.suspendapps.cts.SuspendTestUtils.areSameExtras;
import static android.suspendapps.cts.SuspendTestUtils.assertSameExtras;
import static android.suspendapps.cts.SuspendTestUtils.createExtras;
import static android.suspendapps.cts.SuspendTestUtils.removeDeviceAdmin;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.Manifest;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.UserHandle;
import android.platform.test.annotations.SystemUserOnly;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
import com.android.bedstead.nene.TestApis;
import com.android.compatibility.common.util.FeatureUtil;
import com.android.compatibility.common.util.SystemUtil;
import com.android.suspendapps.testdeviceadmin.TestDeviceAdmin;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@RunWith(BedsteadJUnit4.class)
@LargeTest
public class SuspendPackagesTest {
private static final String TAG = SuspendPackagesTest.class.getSimpleName();
private Context mContext;
private PackageManager mPackageManager;
private AppOpsManager mAppOpsManager;
private TestAppInterface mTestAppInterface;
@ClassRule
@Rule
public static final DeviceState sDeviceState = new DeviceState();
private boolean addDeviceOwner() {
try {
TestApis.devicePolicy().setDeviceOwner(new ComponentName(DEVICE_ADMIN_PACKAGE,
TestDeviceAdmin.class.getName()));
return true;
} catch (Exception e) {
Log.e(TAG, "Could not add device owner", e);
}
return false;
}
private void addAndAssertDeviceAdmin() {
SystemUtil.runShellCommand("dpm set-active-admin --user cur " + DEVICE_ADMIN_COMPONENT,
output -> output.startsWith("Success"));
}
@Before
public void setUp() {
Log.d(TAG, "Running as user " + UserHandle.myUserId());
mContext = InstrumentationRegistry.getTargetContext();
mPackageManager = mContext.getPackageManager();
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
}
@Test
public void testIsPackageSuspended() throws Exception {
SuspendTestUtils.suspend(null, null, null);
assertTrue("isPackageSuspended is false",
mPackageManager.isPackageSuspended(TEST_APP_PACKAGE_NAME));
}
@Test
public void testSuspendedStateFromApp() throws Exception {
mTestAppInterface = new TestAppInterface(mContext);
assertFalse(mTestAppInterface.isTestAppSuspended());
assertNull(mTestAppInterface.getTestAppSuspendedExtras());
final PersistableBundle appExtras = createExtras("testSuspendedStateFromApp", 20, "20",
0.2);
SuspendTestUtils.suspend(appExtras, null, null);
final Bundle extrasFromApp = mTestAppInterface.getTestAppSuspendedExtras();
final boolean suspendedFromApp = mTestAppInterface.isTestAppSuspended();
assertTrue("isPackageSuspended from suspended app is false", suspendedFromApp);
assertSameExtras("getSuspendedPackageAppExtras different to the ones supplied",
appExtras, extrasFromApp);
}
@Test
public void testMyPackageSuspendedUnsuspended() throws Exception {
mTestAppInterface = new TestAppInterface(mContext);
mTestAppInterface.startListeningForBroadcast(Intent.ACTION_MY_PACKAGE_SUSPENDED);
final PersistableBundle appExtras = createExtras("testMyPackageSuspendBroadcasts", 1, "1",
.1);
SuspendTestUtils.suspend(appExtras, null, null);
assertNotNull(Intent.ACTION_MY_PACKAGE_SUSPENDED + " not reported",
mTestAppInterface.awaitBroadcast());
mTestAppInterface.startListeningForBroadcast(Intent.ACTION_MY_PACKAGE_UNSUSPENDED);
SuspendTestUtils.unsuspendAll();
assertNotNull(Intent.ACTION_MY_PACKAGE_UNSUSPENDED + " not reported",
mTestAppInterface.awaitBroadcast());
}
@Test
public void testUpdatingAppExtras() throws Exception {
mTestAppInterface = new TestAppInterface(mContext);
mTestAppInterface.startListeningForBroadcast(Intent.ACTION_MY_PACKAGE_SUSPENDED);
final PersistableBundle extras1 = createExtras("testUpdatingAppExtras", 1,
"1", 0.1);
SuspendTestUtils.suspend(extras1, null, null);
Intent intentFromApp = mTestAppInterface.awaitBroadcast();
assertNotNull(Intent.ACTION_MY_PACKAGE_SUSPENDED + " not reported", intentFromApp);
Bundle receivedExtras = intentFromApp.getBundleExtra(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS);
assertSameExtras("Received app extras different to the ones supplied", extras1,
receivedExtras);
mTestAppInterface.startListeningForBroadcast(Intent.ACTION_MY_PACKAGE_SUSPENDED);
final PersistableBundle extras2 = createExtras("testUpdatingAppExtras", 2,
"2", 0.2);
SuspendTestUtils.suspend(extras2, null, null);
intentFromApp = mTestAppInterface.awaitBroadcast();
assertNotNull(Intent.ACTION_MY_PACKAGE_SUSPENDED + " not reported", intentFromApp);
receivedExtras = intentFromApp.getBundleExtra(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS);
assertSameExtras("Received app extras different to the ones supplied", extras2,
receivedExtras);
}
@Test
public void testCannotSuspendSelf() throws Exception {
final String[] self = new String[]{mContext.getPackageName()};
SuspendTestUtils.suspendAndAssertResult(self, null, null, null, self);
}
@Test
public void testActivityStoppedOnSuspend() throws Exception {
mTestAppInterface = new TestAppInterface(mContext);
SuspendTestUtils.startTestAppActivity(null);
assertNotNull("Test app's activity not started",
mTestAppInterface.awaitTestActivityStart());
SuspendTestUtils.suspend(null, null, null);
assertTrue("Test app's activity not stopped on suspending the app",
mTestAppInterface.awaitTestActivityStop());
}
@SystemUserOnly(reason = "Device owner can be set up only on system user")
@Test
public void testCanSuspendWhenDeviceOwner() throws Exception {
assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
assumeTrue(addDeviceOwner());
SuspendTestUtils.suspend(null, null, null);
}
private int getCurrentUserId() {
final String result = SystemUtil.runShellCommand("am get-current-user");
return Integer.parseInt(result.trim());
}
@Test
public void testLauncherCallback() throws Exception {
final PersistableBundle suppliedExtras = createExtras("testLauncherCallback", 2, "2", 0.2);
final int currentUserId = getCurrentUserId();
final AtomicReference<String> callBackErrors = new AtomicReference<>("");
final CountDownLatch oldCallbackLatch = new CountDownLatch(1);
final CountDownLatch newCallbackLatch = new CountDownLatch(1);
LauncherApps.Callback testCallback = new StubbedCallback() {
@Override
public void onPackagesSuspended(String[] packageNames, UserHandle user) {
oldCallbackLatch.countDown();
}
@Override
public void onPackagesSuspended(String[] packageNames, UserHandle user,
Bundle launcherExtras) {
final StringBuilder errorString = new StringBuilder();
if (!Arrays.equals(packageNames, TEST_PACKAGE_ARRAY)) {
errorString.append("Received unexpected packageNames in onPackagesSuspended: ");
errorString.append(Arrays.toString(packageNames));
errorString.append(". ");
}
if (user.getIdentifier() != currentUserId) {
errorString.append("Received wrong user " + user.getIdentifier()
+ ", current user: " + currentUserId + ". ");
}
if (!areSameExtras(launcherExtras, suppliedExtras)) {
errorString.append("Unexpected launcherExtras, supplied: " + suppliedExtras
+ ", received: " + launcherExtras + ". ");
}
callBackErrors.set(errorString.toString());
newCallbackLatch.countDown();
}
};
final LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
launcherApps.registerCallback(testCallback, new Handler(Looper.getMainLooper()));
SuspendTestUtils.suspend(null, suppliedExtras, null);
assertFalse("Old callback invoked", oldCallbackLatch.await(2, TimeUnit.SECONDS));
assertTrue("New callback not invoked", newCallbackLatch.await(2, TimeUnit.SECONDS));
final String errors = callBackErrors.get();
assertTrue("Callbacks did not complete as expected: " + errors, errors.isEmpty());
launcherApps.unregisterCallback(testCallback);
}
@Test
public void testTestAppsSuspendable() throws Exception {
final String[] unsuspendablePkgs = SystemUtil.callWithShellPermissionIdentity(() ->
mPackageManager.getUnsuspendablePackages(ALL_TEST_PACKAGES));
assertTrue("Normal test apps unsuspendable: " + Arrays.toString(unsuspendablePkgs),
unsuspendablePkgs.length == 0);
}
@Test
public void testDeviceAdminUnsuspendable() throws Exception {
assumeTrue(FeatureUtil.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN));
addAndAssertDeviceAdmin();
final String[] unsuspendablePkgs = SystemUtil.callWithShellPermissionIdentity(() ->
mPackageManager.getUnsuspendablePackages(new String[]{DEVICE_ADMIN_PACKAGE}));
assertTrue("Device admin suspendable", (unsuspendablePkgs.length == 1)
&& DEVICE_ADMIN_PACKAGE.equals(unsuspendablePkgs[0]));
}
private void assertOpDisallowed(int op) throws Exception {
final int testUid = mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0);
final int mode = SystemUtil.callWithShellPermissionIdentity(
() -> mAppOpsManager.checkOpNoThrow(op, testUid, TEST_APP_PACKAGE_NAME));
assertNotEquals("Op " + AppOpsManager.opToName(op) + " allowed while package is suspended",
MODE_ALLOWED, mode);
}
@Test
public void testOpRecordAudioOnSuspend() throws Exception {
final int recordAudioOp = AppOpsManager.permissionToOpCode(
Manifest.permission.RECORD_AUDIO);
SuspendTestUtils.suspend(null, null, null);
assertOpDisallowed(recordAudioOp);
}
@Test
public void testOpCameraOnSuspend() throws Exception {
final int cameraOp = AppOpsManager.permissionToOpCode(Manifest.permission.CAMERA);
SuspendTestUtils.suspend(null, null, null);
assertOpDisallowed(cameraOp);
}
@Test
public void testOpVibrateOnSuspend() throws Exception {
final int vibrateOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_VIBRATE);
SuspendTestUtils.suspend(null, null, null);
assertOpDisallowed(vibrateOp);
}
@After
public void tearDown() throws Exception {
if (mTestAppInterface != null) {
mTestAppInterface.disconnect();
}
removeDeviceAdmin();
SuspendTestUtils.unsuspendAll();
}
private abstract static class StubbedCallback extends LauncherApps.Callback {
@Override
public void onPackageRemoved(String packageName, UserHandle user) {
}
@Override
public void onPackageAdded(String packageName, UserHandle user) {
}
@Override
public void onPackageChanged(String packageName, UserHandle user) {
}
@Override
public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
}
@Override
public void onPackagesUnavailable(String[] packageNames, UserHandle user,
boolean replacing) {
}
}
}