1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm; 18 19 import static android.app.AppOpsManager.MODE_ALLOWED; 20 import static android.app.AppOpsManager.MODE_IGNORED; 21 import static android.app.AppOpsManager.OP_CAMERA; 22 import static android.app.AppOpsManager.OP_PLAY_AUDIO; 23 import static android.app.AppOpsManager.OP_RECORD_AUDIO; 24 import static android.app.AppOpsManager.opToName; 25 26 import static org.junit.Assert.assertEquals; 27 import static org.junit.Assert.assertFalse; 28 import static org.junit.Assert.assertNull; 29 import static org.junit.Assert.assertTrue; 30 import static org.junit.Assert.fail; 31 32 import android.app.AppGlobals; 33 import android.content.Context; 34 import android.content.pm.IPackageManager; 35 import android.content.pm.LauncherApps; 36 import android.content.pm.PackageManager; 37 import android.content.pm.SuspendDialogInfo; 38 import android.os.BaseBundle; 39 import android.os.Bundle; 40 import android.os.Handler; 41 import android.os.Looper; 42 import android.os.PersistableBundle; 43 import android.os.RemoteException; 44 import android.os.ServiceManager; 45 import android.os.UserHandle; 46 47 import androidx.test.InstrumentationRegistry; 48 import androidx.test.filters.FlakyTest; 49 import androidx.test.filters.LargeTest; 50 import androidx.test.runner.AndroidJUnit4; 51 52 import com.android.internal.app.IAppOpsCallback; 53 import com.android.internal.app.IAppOpsService; 54 import com.android.servicestests.apps.suspendtestapp.SuspendTestReceiver; 55 56 import org.junit.After; 57 import org.junit.Before; 58 import org.junit.Test; 59 import org.junit.runner.RunWith; 60 61 import java.io.IOException; 62 import java.util.Arrays; 63 import java.util.concurrent.CountDownLatch; 64 import java.util.concurrent.TimeUnit; 65 import java.util.concurrent.atomic.AtomicReference; 66 67 @RunWith(AndroidJUnit4.class) 68 @LargeTest 69 @FlakyTest 70 public class SuspendPackagesTest { 71 private static final String TEST_APP_PACKAGE_NAME = SuspendTestReceiver.PACKAGE_NAME; 72 private static final String[] PACKAGES_TO_SUSPEND = new String[]{TEST_APP_PACKAGE_NAME}; 73 74 public static final String INSTRUMENTATION_PACKAGE = "com.android.frameworks.servicestests"; 75 public static final String ACTION_REPORT_MY_PACKAGE_SUSPENDED = 76 INSTRUMENTATION_PACKAGE + ".action.REPORT_MY_PACKAGE_SUSPENDED"; 77 public static final String ACTION_REPORT_MY_PACKAGE_UNSUSPENDED = 78 INSTRUMENTATION_PACKAGE + ".action.REPORT_MY_PACKAGE_UNSUSPENDED"; 79 public static final String ACTION_REPORT_TEST_ACTIVITY_STARTED = 80 INSTRUMENTATION_PACKAGE + ".action.REPORT_TEST_ACTIVITY_STARTED"; 81 public static final String ACTION_REPORT_TEST_ACTIVITY_STOPPED = 82 INSTRUMENTATION_PACKAGE + ".action.REPORT_TEST_ACTIVITY_STOPPED"; 83 public static final String ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED = 84 INSTRUMENTATION_PACKAGE + ".action.REPORT_MORE_DETAILS_ACTIVITY_STARTED"; 85 public static final String ACTION_FINISH_TEST_ACTIVITY = 86 INSTRUMENTATION_PACKAGE + ".action.FINISH_TEST_ACTIVITY"; 87 public static final String EXTRA_RECEIVED_PACKAGE_NAME = 88 SuspendPackagesTest.INSTRUMENTATION_PACKAGE + ".extra.RECEIVED_PACKAGE_NAME"; 89 90 private Context mContext; 91 private PackageManager mPackageManager; 92 private LauncherApps mLauncherApps; 93 private Handler mReceiverHandler; 94 private StubbedCallback mTestCallback; 95 96 @Before setUp()97 public void setUp() { 98 mContext = InstrumentationRegistry.getTargetContext(); 99 mPackageManager = mContext.getPackageManager(); 100 mLauncherApps = (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE); 101 mReceiverHandler = new Handler(Looper.getMainLooper()); 102 IPackageManager ipm = AppGlobals.getPackageManager(); 103 try { 104 // Otherwise implicit broadcasts will not be delivered. 105 ipm.setPackageStoppedState(TEST_APP_PACKAGE_NAME, false, mContext.getUserId()); 106 } catch (RemoteException e) { 107 e.rethrowAsRuntimeException(); 108 } 109 unsuspendTestPackage(); 110 } 111 getExtras(String keyPrefix, long lval, String sval, double dval)112 private PersistableBundle getExtras(String keyPrefix, long lval, String sval, double dval) { 113 final PersistableBundle extras = new PersistableBundle(3); 114 extras.putLong(keyPrefix + ".LONG_VALUE", lval); 115 extras.putDouble(keyPrefix + ".DOUBLE_VALUE", dval); 116 extras.putString(keyPrefix + ".STRING_VALUE", sval); 117 return extras; 118 } 119 suspendTestPackage(PersistableBundle appExtras, PersistableBundle launcherExtras, SuspendDialogInfo dialogInfo)120 private void suspendTestPackage(PersistableBundle appExtras, PersistableBundle launcherExtras, 121 SuspendDialogInfo dialogInfo) { 122 final String[] unchangedPackages = mPackageManager.setPackagesSuspended( 123 PACKAGES_TO_SUSPEND, true, appExtras, launcherExtras, dialogInfo); 124 assertTrue("setPackagesSuspended returned non-empty list", unchangedPackages.length == 0); 125 } 126 unsuspendTestPackage()127 private void unsuspendTestPackage() { 128 final String[] unchangedPackages = mPackageManager.setPackagesSuspended( 129 PACKAGES_TO_SUSPEND, false, null, null, (SuspendDialogInfo) null); 130 assertTrue("setPackagesSuspended returned non-empty list", unchangedPackages.length == 0); 131 } 132 areSameExtras(BaseBundle expected, BaseBundle received)133 private static boolean areSameExtras(BaseBundle expected, BaseBundle received) { 134 if (expected != null) { 135 expected.get(""); // hack to unparcel the bundles. 136 } 137 if (received != null) { 138 received.get(""); 139 } 140 return BaseBundle.kindofEquals(expected, received); 141 } 142 assertSameExtras(String message, BaseBundle expected, BaseBundle received)143 private static void assertSameExtras(String message, BaseBundle expected, BaseBundle received) { 144 if (!areSameExtras(expected, received)) { 145 fail(message + ": [expected: " + expected + "; received: " + received + "]"); 146 } 147 } 148 149 @Test testGetLauncherExtrasNonNull()150 public void testGetLauncherExtrasNonNull() { 151 final Bundle extrasWhenUnsuspended = mLauncherApps.getSuspendedPackageLauncherExtras( 152 TEST_APP_PACKAGE_NAME, mContext.getUser()); 153 assertNull("Non null extras when package unsuspended:", extrasWhenUnsuspended); 154 final PersistableBundle launcherExtras = getExtras("testGetLauncherExtras", 1, "1", 0.1); 155 suspendTestPackage(null, launcherExtras, null); 156 final Bundle receivedExtras = mLauncherApps.getSuspendedPackageLauncherExtras( 157 TEST_APP_PACKAGE_NAME, mContext.getUser()); 158 assertSameExtras("Received launcher extras different to the ones supplied", launcherExtras, 159 receivedExtras); 160 } 161 162 @Test testGetLauncherExtrasNull()163 public void testGetLauncherExtrasNull() { 164 suspendTestPackage(null, null, null); 165 final Bundle extrasWhenNoneGiven = mLauncherApps.getSuspendedPackageLauncherExtras( 166 TEST_APP_PACKAGE_NAME, mContext.getUser()); 167 assertNull("Non null extras when null extras provided:", extrasWhenNoneGiven); 168 } 169 170 @Test testGetLauncherExtrasInvalidPackage()171 public void testGetLauncherExtrasInvalidPackage() { 172 final Bundle extrasForInvalidPackage = mLauncherApps.getSuspendedPackageLauncherExtras( 173 "test.nonexistent.packagename", mContext.getUser()); 174 assertNull("Non null extras for an invalid package:", extrasForInvalidPackage); 175 } 176 177 @Test testOnPackagesSuspendedNewAndOld()178 public void testOnPackagesSuspendedNewAndOld() throws InterruptedException { 179 final PersistableBundle suppliedExtras = getExtras( 180 "testOnPackagesSuspendedNewAndOld", 2, "2", 0.2); 181 final AtomicReference<String> error = new AtomicReference<>(""); 182 final CountDownLatch rightCallbackLatch = new CountDownLatch(1); 183 final CountDownLatch wrongCallbackLatch = new CountDownLatch(1); 184 mTestCallback = new StubbedCallback() { 185 @Override 186 public void onPackagesSuspended(String[] packageNames, UserHandle user) { 187 error.set(error.get() 188 + "Old callback called even when the new one is overriden. "); 189 wrongCallbackLatch.countDown(); 190 } 191 192 @Override 193 public void onPackagesSuspended(String[] packageNames, UserHandle user, 194 Bundle launcherExtras) { 195 final StringBuilder errorString = new StringBuilder(); 196 if (!Arrays.equals(packageNames, PACKAGES_TO_SUSPEND)) { 197 errorString.append("Received unexpected packageNames in onPackagesSuspended:"); 198 for (String packageName : packageNames) { 199 errorString.append(" " + packageName); 200 } 201 errorString.append(". "); 202 } 203 if (user.getIdentifier() != mContext.getUserId()) { 204 errorString.append("Received wrong user " + user.getIdentifier() + ". "); 205 } 206 if (!areSameExtras(launcherExtras, suppliedExtras)) { 207 errorString.append("Unexpected launcherExtras, supplied: " + suppliedExtras 208 + ", received: " + launcherExtras + ". "); 209 } 210 error.set(error.get() 211 + errorString.toString()); 212 rightCallbackLatch.countDown(); 213 } 214 }; 215 mLauncherApps.registerCallback(mTestCallback, mReceiverHandler); 216 suspendTestPackage(null, suppliedExtras, null); 217 assertFalse("Wrong callback was invoked", wrongCallbackLatch.await(5, TimeUnit.SECONDS)); 218 assertTrue("Right callback wasn't invoked", rightCallbackLatch.await(2, TimeUnit.SECONDS)); 219 final String result = error.get(); 220 assertTrue("Callbacks did not complete as expected: " + result, result.isEmpty()); 221 } 222 223 @Test testOnPackagesSuspendedOld()224 public void testOnPackagesSuspendedOld() throws InterruptedException { 225 final PersistableBundle suppliedExtras = getExtras( 226 "testOnPackagesSuspendedOld", 2, "2", 0.2); 227 final AtomicReference<String> overridingOneCallbackResult = new AtomicReference<>(""); 228 final CountDownLatch oneCallbackLatch = new CountDownLatch(1); 229 mTestCallback = new StubbedCallback() { 230 @Override 231 public void onPackagesSuspended(String[] packageNames, UserHandle user) { 232 final StringBuilder errorString = new StringBuilder(); 233 if (!Arrays.equals(packageNames, PACKAGES_TO_SUSPEND)) { 234 errorString.append("Received unexpected packageNames in onPackagesSuspended:"); 235 for (String packageName : packageNames) { 236 errorString.append(" " + packageName); 237 } 238 errorString.append(". "); 239 } 240 if (user.getIdentifier() != mContext.getUserId()) { 241 errorString.append("Received wrong user " + user.getIdentifier() + ". "); 242 } 243 overridingOneCallbackResult.set(overridingOneCallbackResult.get() 244 + errorString.toString()); 245 oneCallbackLatch.countDown(); 246 } 247 }; 248 mLauncherApps.registerCallback(mTestCallback, mReceiverHandler); 249 suspendTestPackage(null, suppliedExtras, null); 250 assertTrue("Callback not invoked", oneCallbackLatch.await(5, TimeUnit.SECONDS)); 251 final String result = overridingOneCallbackResult.get(); 252 assertTrue("Callback did not complete as expected: " + result, result.isEmpty()); 253 } 254 255 @Test testCameraBlockedOnSuspend()256 public void testCameraBlockedOnSuspend() throws Exception { 257 assertOpBlockedOnSuspend(OP_CAMERA); 258 } 259 260 @Test testPlayAudioBlockedOnSuspend()261 public void testPlayAudioBlockedOnSuspend() throws Exception { 262 assertOpBlockedOnSuspend(OP_PLAY_AUDIO); 263 } 264 265 @Test testRecordAudioBlockedOnSuspend()266 public void testRecordAudioBlockedOnSuspend() throws Exception { 267 assertOpBlockedOnSuspend(OP_RECORD_AUDIO); 268 } 269 assertOpBlockedOnSuspend(int code)270 private void assertOpBlockedOnSuspend(int code) throws Exception { 271 final IAppOpsService iAppOps = IAppOpsService.Stub.asInterface( 272 ServiceManager.getService(Context.APP_OPS_SERVICE)); 273 final CountDownLatch latch = new CountDownLatch(1); 274 final IAppOpsCallback watcher = new IAppOpsCallback.Stub() { 275 @Override 276 public void opChanged(int op, int uid, String packageName, String persistentDeviceId) { 277 if (op == code && packageName.equals(TEST_APP_PACKAGE_NAME)) { 278 latch.countDown(); 279 } 280 } 281 }; 282 iAppOps.startWatchingMode(code, TEST_APP_PACKAGE_NAME, watcher); 283 final int testPackageUid = mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0); 284 int opMode = iAppOps.checkOperation(code, testPackageUid, TEST_APP_PACKAGE_NAME); 285 assertEquals("Op " + opToName(code) + " disallowed for unsuspended package", MODE_ALLOWED, 286 opMode); 287 suspendTestPackage(null, null, null); 288 assertTrue("AppOpsWatcher did not callback", latch.await(5, TimeUnit.SECONDS)); 289 opMode = iAppOps.checkOperation(code, testPackageUid, TEST_APP_PACKAGE_NAME); 290 assertEquals("Op " + opToName(code) + " allowed for suspended package", MODE_IGNORED, 291 opMode); 292 iAppOps.stopWatchingMode(watcher); 293 } 294 295 @After tearDown()296 public void tearDown() throws IOException { 297 if (mTestCallback != null) { 298 mLauncherApps.unregisterCallback(mTestCallback); 299 } 300 } 301 302 private static abstract class StubbedCallback extends LauncherApps.Callback { 303 304 @Override onPackageRemoved(String packageName, UserHandle user)305 public void onPackageRemoved(String packageName, UserHandle user) { 306 } 307 308 @Override onPackageAdded(String packageName, UserHandle user)309 public void onPackageAdded(String packageName, UserHandle user) { 310 } 311 312 @Override onPackageChanged(String packageName, UserHandle user)313 public void onPackageChanged(String packageName, UserHandle user) { 314 } 315 316 @Override onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing)317 public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) { 318 319 } 320 321 @Override onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing)322 public void onPackagesUnavailable(String[] packageNames, UserHandle user, 323 boolean replacing) { 324 } 325 } 326 } 327