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