xref: /aosp_15_r20/cts/tests/app/src/android/app/cts/ActivityManagerApi29Test.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2019 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 package android.app.cts;
17 
18 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
19 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
20 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
21 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
22 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
23 import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
24 import static android.app.AppOpsManager.MODE_ALLOWED;
25 import static android.app.AppOpsManager.MODE_FOREGROUND;
26 import static android.app.AppOpsManager.MODE_IGNORED;
27 import static android.app.AppOpsManager.OPSTR_CAMERA;
28 import static android.app.AppOpsManager.OPSTR_COARSE_LOCATION;
29 import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
30 import static android.app.AppOpsManager.OP_FLAGS_ALL;
31 import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
32 import static android.app.AppOpsManager.UID_STATE_TOP;
33 
34 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
35 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
36 
37 import static org.junit.Assert.assertEquals;
38 
39 import android.app.AppOpsManager;
40 import android.app.AppOpsManager.HistoricalOp;
41 import android.app.AppOpsManager.HistoricalOps;
42 import android.app.AppOpsManager.HistoricalOpsRequest;
43 import android.app.Instrumentation;
44 import android.app.cts.android.app.cts.tools.WaitForBroadcast;
45 import android.app.cts.android.app.cts.tools.WatchUidRunner;
46 import android.content.Context;
47 import android.content.Intent;
48 import android.content.pm.PackageManager.NameNotFoundException;
49 import android.permission.cts.PermissionUtils;
50 import android.provider.Settings;
51 
52 import androidx.test.InstrumentationRegistry;
53 import androidx.test.runner.AndroidJUnit4;
54 import androidx.test.uiautomator.UiDevice;
55 
56 import org.junit.After;
57 import org.junit.Before;
58 import org.junit.Ignore;
59 import org.junit.Test;
60 import org.junit.runner.RunWith;
61 
62 import java.time.Instant;
63 import java.time.temporal.ChronoUnit;
64 import java.util.Arrays;
65 import java.util.concurrent.CompletableFuture;
66 import java.util.concurrent.TimeUnit;
67 
68 /**
69  * AppOpsManager.MODE_FOREGROUND is introduced in API level 29. This test class specifically tests
70  * ActivityManagerService's interaction with AppOpsService regarding MODE_FOREGROUND operation.
71  * If an operation's mode is MODE_FOREGROUND, this operation is allowed only when the process is in
72  * one of the foreground state (including foreground_service state), this operation will be denied
73  * when the process is in background state.
74  */
75 @RunWith(AndroidJUnit4.class)
76 public class ActivityManagerApi29Test {
77     private static final String PACKAGE_NAME = "android.app.cts.activitymanager.api29";
78     private static final String SIMPLE_ACTIVITY = ".SimpleActivity";
79     private static final String ACTION_SIMPLE_ACTIVITY_START_RESULT =
80             "android.app.cts.activitymanager.api29.SimpleActivity.RESULT";
81     private static final String ACTION_SERVICE_START_RESULT =
82             "android.app.cts.activitymanager.api29.LocationForegroundService.RESULT";
83     private static final String SERVICE_NAME = ".LocationForegroundService";
84     private static final int WAITFOR_MSEC = 10000;
85     private static final int NOTEOP_COUNT = 5;
86 
87     private static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION
88             | PROCESS_CAPABILITY_FOREGROUND_CAMERA
89             | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
90             | PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
91 
92     private static final Instrumentation sInstrumentation =
93             InstrumentationRegistry.getInstrumentation();
94     private static final Context sContext = sInstrumentation.getContext();
95     private static final Context sTargetContext = sInstrumentation.getTargetContext();
96     private static final AppOpsManager sAppOps =
97             (AppOpsManager) sContext.getSystemService(AppOpsManager.class);
98     private static Intent sActivityIntent;
99     private static Intent sServiceIntent = new Intent().setClassName(
100             PACKAGE_NAME, PACKAGE_NAME + SERVICE_NAME);
101     private static int sUid;
102     static {
103         try {
104             sUid = sContext.getPackageManager().getApplicationInfo(PACKAGE_NAME, 0).uid;
105         } catch (NameNotFoundException e) {
106             throw new RuntimeException("NameNotFoundException:" + e);
107         }
108     }
109 
110     private String mOldAppOpsSettings;
111     private WatchUidRunner mUidWatcher;
112 
113     @Before
setUp()114     public void setUp() throws Exception {
115         CtsAppTestUtils.turnScreenOn(sInstrumentation, sContext);
116         CtsAppTestUtils.makeUidIdle(sInstrumentation, PACKAGE_NAME);
117         // PACKAGE_NAME's targetSdkVersion is 29, when ACCESS_COARSE_LOCATION is granted, appOp is
118         // MODE_FOREGROUND (In API level lower than 29, appOp is MODE_ALLOWED).
119         assertEquals(MODE_FOREGROUND,
120                 PermissionUtils.getAppOp(PACKAGE_NAME, ACCESS_COARSE_LOCATION));
121         runWithShellPermissionIdentity(()-> {
122             mOldAppOpsSettings = Settings.Global.getString(sContext.getContentResolver(),
123                     Settings.Global.APP_OPS_CONSTANTS);
124             Settings.Global.putString(sContext.getContentResolver(),
125                     Settings.Global.APP_OPS_CONSTANTS,
126                     "top_state_settle_time=0,fg_service_state_settle_time=0,"
127                     + "bg_state_settle_time=0");
128             sAppOps.clearHistory();
129             sAppOps.resetHistoryParameters(); }
130         );
131         mUidWatcher = new WatchUidRunner(sInstrumentation, sUid, WAITFOR_MSEC);
132         // Press home key to ensure stopAppSwitches is called so the grace period of
133         // the background start will be ignored if there's any.
134         UiDevice.getInstance(sInstrumentation).pressHome();
135     }
136 
137     @After
tearDown()138     public void tearDown() throws Exception {
139         CtsAppTestUtils.makeUidIdle(sInstrumentation, PACKAGE_NAME);
140         runWithShellPermissionIdentity(() -> {
141             // restore old AppOps settings.
142             Settings.Global.putString(sContext.getContentResolver(),
143                     Settings.Global.APP_OPS_CONSTANTS, mOldAppOpsSettings);
144             sAppOps.clearHistory();
145             sAppOps.resetHistoryParameters(); }
146         );
147         mUidWatcher.finish();
148     }
149 
startSimpleActivity()150     private void startSimpleActivity() {
151         sActivityIntent = new Intent(Intent.ACTION_MAIN)
152                 .setClassName(PACKAGE_NAME, PACKAGE_NAME + SIMPLE_ACTIVITY)
153                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
154         sTargetContext.startActivity(sActivityIntent);
155     }
156 
stopSimpleActivity()157     private void stopSimpleActivity() {
158         sActivityIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
159                 .putExtra("finish", true);
160         sTargetContext.startActivity(sActivityIntent);
161     }
162 
startSimpleService()163     private void startSimpleService() {
164         sTargetContext.startForegroundService(sServiceIntent);
165     }
166 
stopSimpleService()167     private void stopSimpleService() {
168         sTargetContext.stopService(sServiceIntent);
169     }
170 
171     /**
172      * This tests app in PROCESS_STATE_TOP state can have location access.
173      * The app's permission is AppOpsManager.MODE_FOREGROUND. If the process is in PROCESS_STATE_TOP
174      * , even its capability is zero, it still has location access.
175      * @throws Exception
176      */
177     @Test
178     @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
179             + "restriction")
testTopActivityWithAppOps()180     public void testTopActivityWithAppOps() throws Exception {
181         startSimpleActivity();
182         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
183                 new Integer(PROCESS_CAPABILITY_ALL));
184 
185         // AppOps location access should be allowed.
186         assertEquals(MODE_ALLOWED, noteOp(OPSTR_COARSE_LOCATION));
187 
188         stopSimpleActivity();
189         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
190                 new Integer(PROCESS_CAPABILITY_NONE));
191 
192         // AppOps location access should be denied.
193         assertEquals(MODE_IGNORED, noteOp(OPSTR_COARSE_LOCATION));
194     }
195 
196     /**
197      * When ActivityManagerService process states and capability changes, it updates AppOpsService.
198      * This test starts a foreground service with location type, it updates AppOpsService with
199      * PROCESS_STATE_FOREGROUND_SERVICE and PROCESS_CAPABILITY_FOREGROUND_LOCATION, then check if
200      * AppOpsManager allow ACCESS_COARSE_LOCATION of MODE_FOREGROUND.
201      *
202      * The "android.app.cts.activitymanager.api29" package's targetSdkVersion is 29.
203      * @throws Exception
204      */
205     @Test
206     @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
207             + "restriction")
testFgsLocationWithAppOps()208     public void testFgsLocationWithAppOps() throws Exception {
209         // Start a foreground service with location
210         startSimpleService();
211         // Wait for state and capability change.
212         // BG started FGS does not have location capability.
213         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
214                 new Integer(PROCESS_CAPABILITY_NONE));
215 
216         startSimpleActivity();
217         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
218                 new Integer(PROCESS_CAPABILITY_ALL));
219 
220         // AppOps location access should be allowed.
221         assertEquals(MODE_ALLOWED, noteOp(OPSTR_COARSE_LOCATION));
222 
223         stopSimpleActivity();
224         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
225                 new Integer(PROCESS_CAPABILITY_NONE));
226 
227         // AppOps location access should be denied.
228         assertEquals(MODE_IGNORED, noteOp(OPSTR_COARSE_LOCATION));
229 
230         stopSimpleService();
231         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
232                 new Integer(PROCESS_CAPABILITY_NONE));
233 
234         // AppOps location access should be denied.
235         assertEquals(MODE_IGNORED, noteOp(OPSTR_COARSE_LOCATION));
236     }
237 
238     /**
239      * After calling AppOpsManager.noteOp() interface multiple times in different process states,
240      * this test calls AppOpsManager.getHistoricalOps() and check the access count and reject count
241      * in HistoricalOps.
242       *
243      * @throws Exception
244      */
245     @Test
246     @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
247             + "restriction")
testAppOpsHistoricalOps()248     public void testAppOpsHistoricalOps() throws Exception {
249         runWithShellPermissionIdentity(
250                 () ->  sAppOps.setHistoryParameters(AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
251                         1000, 10)
252         );
253         WaitForBroadcast waiter = new WaitForBroadcast(sInstrumentation.getTargetContext());
254         waiter.prepare(ACTION_SIMPLE_ACTIVITY_START_RESULT);
255         startSimpleActivity();
256         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
257                 new Integer(PROCESS_CAPABILITY_ALL));
258         waiter.doWait(WAITFOR_MSEC);
259 
260         waiter = new WaitForBroadcast(sInstrumentation.getTargetContext());
261         waiter.prepare(ACTION_SERVICE_START_RESULT);
262         startSimpleService();
263         waiter.doWait(WAITFOR_MSEC);
264 
265         for (int i = 0; i < NOTEOP_COUNT; i++) {
266             noteOp(OPSTR_COARSE_LOCATION);
267         }
268 
269         stopSimpleActivity();
270         // The callingPackage to start FGS is in background.
271         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
272                 new Integer(PROCESS_CAPABILITY_NONE));
273         for (int i = 0; i < NOTEOP_COUNT; i++) {
274             noteOp(OPSTR_COARSE_LOCATION);
275         }
276         stopSimpleService();
277         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
278                 new Integer(PROCESS_CAPABILITY_NONE));
279 
280         for (int i = 0; i < NOTEOP_COUNT; i++) {
281             noteOp(OPSTR_COARSE_LOCATION);
282         }
283         runWithShellPermissionIdentity(() -> {
284             CompletableFuture<HistoricalOps> ops = new CompletableFuture<>();
285             HistoricalOpsRequest histOpsRequest = new HistoricalOpsRequest.Builder(
286                     Instant.now().minus(1, ChronoUnit.HOURS).toEpochMilli(),
287                     Long.MAX_VALUE)
288                     .setUid(sUid)
289                     .setPackageName(PACKAGE_NAME)
290                     .setOpNames(Arrays.asList(OPSTR_COARSE_LOCATION))
291                     .setFlags(OP_FLAGS_ALL)
292                     .build();
293             sAppOps.getHistoricalOps(histOpsRequest, sContext.getMainExecutor(), ops::complete);
294             HistoricalOp hOp = ops.get(5000, TimeUnit.MILLISECONDS)
295                     .getUidOps(sUid).getPackageOps(PACKAGE_NAME)
296                     .getOp(OPSTR_COARSE_LOCATION);
297             assertEquals(NOTEOP_COUNT, hOp.getAccessCount(UID_STATE_TOP,
298                     UID_STATE_FOREGROUND_SERVICE, AppOpsManager.OP_FLAGS_ALL));
299             assertEquals(NOTEOP_COUNT, hOp.getForegroundAccessCount(OP_FLAGS_ALL));
300             assertEquals(NOTEOP_COUNT, hOp.getForegroundRejectCount(OP_FLAGS_ALL));
301             assertEquals(0, hOp.getBackgroundAccessCount(OP_FLAGS_ALL));
302             // denied access one time in background.
303             assertEquals(NOTEOP_COUNT, hOp.getBackgroundRejectCount(OP_FLAGS_ALL)); }
304         );
305     }
306 
307     /**
308      * Only FGS started by TOP app can have OP_CAMERA and OP_RECORD_AUDIO.
309      * @throws Exception
310      */
311     @Test
312     @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
313             + "restriction")
testCameraWithAppOps()314     public void testCameraWithAppOps() throws Exception {
315         startSimpleService();
316         // Wait for state and capability change.
317         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
318                 new Integer(PROCESS_CAPABILITY_NONE));
319 
320         // Non-Top started FGS do not have while-in-use permission, camera/microphone access is
321         // denied.
322         assertEquals(MODE_IGNORED, noteOp(OPSTR_CAMERA));
323         assertEquals(MODE_IGNORED, noteOp(OPSTR_RECORD_AUDIO));
324 
325         // Start an activity, put app in TOP.
326         startSimpleActivity();
327         // TOP process has all capabilities.
328         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
329                 new Integer(PROCESS_CAPABILITY_ALL));
330 
331         // Camera/microphone access is allowed because the app is TOP.
332         assertEquals(MODE_ALLOWED, noteOp(OPSTR_CAMERA));
333         assertEquals(MODE_ALLOWED, noteOp(OPSTR_RECORD_AUDIO));
334 
335         // Tell the activity to finalize.
336         stopSimpleActivity();
337         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
338                 new Integer(PROCESS_CAPABILITY_NONE));
339 
340         // App not in Top, camera/microphone access should be denied.
341         assertEquals(MODE_IGNORED, noteOp(OPSTR_CAMERA));
342         assertEquals(MODE_IGNORED, noteOp(OPSTR_RECORD_AUDIO));
343 
344         // Stop the foreground service.
345         stopSimpleService();
346         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
347                 new Integer(PROCESS_CAPABILITY_NONE));
348 
349         assertEquals(MODE_IGNORED, noteOp(OPSTR_CAMERA));
350         assertEquals(MODE_IGNORED, noteOp(OPSTR_RECORD_AUDIO));
351     }
352 
noteOp(String opStr)353     private int noteOp(String opStr) throws Exception {
354         return callWithShellPermissionIdentity(
355                 () -> sAppOps.noteOp(opStr, sUid, PACKAGE_NAME,
356                         opStr, ""));
357     }
358 }
359