• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 android.app.cts.fgstest;
18 
19 import static android.app.fgstesthelper.LocalForegroundServiceBase.RESULT_INVALID_TYPE_EXCEPTION;
20 import static android.app.fgstesthelper.LocalForegroundServiceBase.RESULT_MISSING_TYPE_EXCEPTION;
21 import static android.app.fgstesthelper.LocalForegroundServiceBase.RESULT_OK;
22 import static android.app.fgstesthelper.LocalForegroundServiceBase.RESULT_SECURITY_EXCEPTION;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.fail;
26 
27 import android.app.ActivityManager;
28 import android.app.AppOpsManager;
29 import android.app.ForegroundServiceTypePolicy;
30 import android.app.ForegroundServiceTypePolicy.ForegroundServiceTypePolicyInfo;
31 import android.app.Instrumentation;
32 import android.app.cts.android.app.cts.tools.WatchUidRunner;
33 import android.app.fgstesthelper.LocalForegroundServiceBase;
34 import android.app.role.RoleManager;
35 import android.content.BroadcastReceiver;
36 import android.content.ComponentName;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.IntentFilter;
40 import android.content.pm.ApplicationInfo;
41 import android.content.pm.PackageManager;
42 import android.content.pm.PermissionInfo;
43 import android.content.pm.ServiceInfo;
44 import android.location.LocationManager;
45 import android.os.Process;
46 import android.os.UserHandle;
47 import android.platform.test.annotations.Presubmit;
48 import android.util.ArrayMap;
49 
50 import androidx.test.InstrumentationRegistry;
51 import androidx.test.runner.AndroidJUnit4;
52 import androidx.test.uiautomator.UiDevice;
53 
54 import com.android.compatibility.common.util.ApiTest;
55 import com.android.compatibility.common.util.DeviceConfigStateHelper;
56 import com.android.compatibility.common.util.SystemUtil;
57 import com.android.internal.util.ArrayUtils;
58 
59 import org.junit.After;
60 import org.junit.Before;
61 import org.junit.Ignore;
62 import org.junit.Test;
63 import org.junit.runner.RunWith;
64 
65 import java.util.ArrayList;
66 import java.util.Arrays;
67 import java.util.concurrent.CountDownLatch;
68 import java.util.concurrent.TimeUnit;
69 
70 @RunWith(AndroidJUnit4.class)
71 @Presubmit
72 public final class ActivityManagerForegroundServiceTypeTest {
73     private static final String TAG = ActivityManagerForegroundServiceTypeTest.class.getName();
74 
75     private static final String TEST_PKG_NAME_TARGET = "android.app.fgstesthelper";
76     private static final String TEST_PKG_NAME_CURRENT = "android.app.fgstesthelpercurrent";
77     private static final String TEST_PKG_NAME_API33 = "android.app.fgstesthelper33";
78     private static final String SHELL_PKG_NAME = "com.android.shell";
79 
80     private static final String TEST_CLS_NAME_NO_TYPE =
81             "android.app.fgstesthelper.LocalForegroundServiceNoType";
82     private static final String TEST_CLS_NAME_ALL_TYPE =
83             "android.app.fgstesthelper.LocalForegroundServiceAllTypes";
84     private static final String FGS_TYPE_PERMISSION_CHANGE_ID = "FGS_TYPE_PERMISSION_CHANGE_ID";
85 
86     private static final long WAITFOR_MSEC = 5000;
87 
88     private static final ComponentName TEST_COMP_TARGET_FGS_NO_TYPE = new ComponentName(
89             TEST_PKG_NAME_TARGET, TEST_CLS_NAME_NO_TYPE);
90     private static final ComponentName TEST_COMP_TARGET_FGS_ALL_TYPE = new ComponentName(
91             TEST_PKG_NAME_TARGET, TEST_CLS_NAME_ALL_TYPE);
92     private static final ComponentName TEST_COMP_CURRENT_FGS_NO_TYPE = new ComponentName(
93             TEST_PKG_NAME_CURRENT, TEST_CLS_NAME_NO_TYPE);
94     private static final ComponentName TEST_COMP_CURRENT_FGS_ALL_TYPE = new ComponentName(
95             TEST_PKG_NAME_CURRENT, TEST_CLS_NAME_ALL_TYPE);
96     private static final ComponentName TEST_COMP_API33_FGS_NO_TYPE = new ComponentName(
97             TEST_PKG_NAME_API33, TEST_CLS_NAME_NO_TYPE);
98     private static final ComponentName TEST_COMP_API33_FGS_ALL_TYPE = new ComponentName(
99             TEST_PKG_NAME_API33, TEST_CLS_NAME_ALL_TYPE);
100 
101     private static final String SPECIAL_PERMISSION_OP_ALLOWLISTED = "SPECIAL_PERM_ALLOWLISTED";
102     private static final ArrayMap<String, SpecialPermissionOp> sSpecialPermissionOps =
103             new ArrayMap<>();
104 
105     private Context mContext;
106     private Context mTargetContext;
107     private Instrumentation mInstrumentation;
108     private ActivityManager mActivityManager;
109     private PackageManager mPackageManager;
110 
111     @Before
setUp()112     public void setUp() {
113         mInstrumentation = InstrumentationRegistry.getInstrumentation();
114         mContext = mInstrumentation.getContext();
115         mTargetContext = mInstrumentation.getTargetContext();
116         mActivityManager = mInstrumentation.getContext().getSystemService(ActivityManager.class);
117         mPackageManager = mInstrumentation.getContext().getPackageManager();
118         if (sSpecialPermissionOps.isEmpty()) {
119             sSpecialPermissionOps.put(SPECIAL_PERMISSION_OP_ALLOWLISTED,
120                     new DeviceAllowlistPermissionOp());
121         }
122     }
123 
124     @After
tearDown()125     public void tearDown() throws Exception {
126         SystemUtil.runWithShellPermissionIdentity(() -> {
127             mActivityManager.forceStopPackage(TEST_PKG_NAME_CURRENT);
128             mActivityManager.forceStopPackage(TEST_PKG_NAME_API33);
129         });
130     }
131 
132     @ApiTest(apis = {"android.app.Service#startForeground"})
133     @Test
testForegroundServiceTypeMissing()134     public void testForegroundServiceTypeMissing() throws Exception {
135         try {
136             enablePermissionEnforcement(false, TEST_PKG_NAME_CURRENT);
137             enablePermissionEnforcement(false, TEST_PKG_NAME_API33);
138             testForegroundServiceTypeDisabledCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST,
139                     RESULT_MISSING_TYPE_EXCEPTION,
140                     TEST_COMP_API33_FGS_NO_TYPE, TEST_COMP_CURRENT_FGS_NO_TYPE);
141         } finally {
142             clearPermissionEnforcement(TEST_PKG_NAME_CURRENT);
143             clearPermissionEnforcement(TEST_PKG_NAME_API33);
144         }
145     }
146 
147     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_NONE"})
148     @Test
testForegroundServiceTypeNone()149     public void testForegroundServiceTypeNone() throws Exception {
150         try {
151             enablePermissionEnforcement(false, TEST_PKG_NAME_CURRENT);
152             enablePermissionEnforcement(false, TEST_PKG_NAME_API33);
153             testForegroundServiceTypeDisabledCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE,
154                     TEST_COMP_API33_FGS_NO_TYPE, TEST_COMP_CURRENT_FGS_NO_TYPE);
155         } finally {
156             clearPermissionEnforcement(TEST_PKG_NAME_CURRENT);
157             clearPermissionEnforcement(TEST_PKG_NAME_API33);
158         }
159     }
160 
161     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC"})
162     @Test
testForegroundServiceTypeDataSync()163     public void testForegroundServiceTypeDataSync() throws Exception {
164         try {
165             enablePermissionEnforcement(false, TEST_PKG_NAME_CURRENT);
166             enablePermissionEnforcement(false, TEST_PKG_NAME_API33);
167             testForegroundServiceTypeDisabledCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
168                     TEST_COMP_API33_FGS_ALL_TYPE, TEST_COMP_CURRENT_FGS_ALL_TYPE);
169         } finally {
170             clearPermissionEnforcement(TEST_PKG_NAME_CURRENT);
171             clearPermissionEnforcement(TEST_PKG_NAME_API33);
172         }
173     }
174 
175     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC"})
176     @Test
testForegroundServiceTypeDataSyncPermission()177     public void testForegroundServiceTypeDataSyncPermission() throws Exception {
178         testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
179     }
180 
181     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK"})
182     @Test
testForegroundServiceTypeMediaPlaybackPermission()183     public void testForegroundServiceTypeMediaPlaybackPermission() throws Exception {
184         testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
185     }
186 
187     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_PHONE_CALL"})
188     @Test
testForegroundServiceTypePhoneCallPermission()189     public void testForegroundServiceTypePhoneCallPermission() throws Exception {
190         testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL);
191     }
192 
193     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION"})
194     @Test
testForegroundServiceTypeLocationPermission()195     public void testForegroundServiceTypeLocationPermission() throws Exception {
196         final LocationManager lm = mContext.getSystemService(LocationManager.class);
197         final UserHandle user = Process.myUserHandle();
198         final boolean wasEnabled = lm.isLocationEnabledForUser(user);
199         try {
200             SystemUtil.runWithShellPermissionIdentity(() -> {
201                 lm.setLocationEnabledForUser(true, user);
202             });
203             testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
204         } finally {
205             SystemUtil.runWithShellPermissionIdentity(() -> {
206                 lm.setLocationEnabledForUser(wasEnabled, user);
207             });
208         }
209     }
210 
211     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE"})
212     @Test
testForegroundServiceTypeConnectedDevicePermission()213     public void testForegroundServiceTypeConnectedDevicePermission() throws Exception {
214         testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
215     }
216 
217     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION"})
218     @Test
testForegroundServiceTypeMediaProjectionPermission()219     public void testForegroundServiceTypeMediaProjectionPermission() throws Exception {
220         testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
221     }
222 
223     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_CAMERA"})
224     @Test
testForegroundServiceTypeCameraPermission()225     public void testForegroundServiceTypeCameraPermission() throws Exception {
226         testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA);
227     }
228 
229     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MICROPHONE"})
230     @Test
testForegroundServiceTypeMicrophonePermission()231     public void testForegroundServiceTypeMicrophonePermission() throws Exception {
232         testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
233     }
234 
235     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH"})
236     @Test
testForegroundServiceTypeHealthPermission()237     public void testForegroundServiceTypeHealthPermission() throws Exception {
238         testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH);
239     }
240 
241     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING"})
242     @Test
testForegroundServiceTypeRemoteMessagingPermission()243     public void testForegroundServiceTypeRemoteMessagingPermission() throws Exception {
244         testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING);
245     }
246 
247     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED"})
248     @Test
testForegroundServiceTypeSystemExemptedPermission()249     public void testForegroundServiceTypeSystemExemptedPermission() throws Exception {
250         testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
251                 new String[] {SPECIAL_PERMISSION_OP_ALLOWLISTED});
252     }
253 
254     @Ignore("b/265347862")
255     @Test
testForegroundServiceTypeFileManagementPermission()256     public void testForegroundServiceTypeFileManagementPermission() throws Exception {
257         testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT);
258     }
259 
260     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE"})
261     @Test
testForegroundServiceTypeSpecialUsePermission()262     public void testForegroundServiceTypeSpecialUsePermission() throws Exception {
263         testPermissionEnforcementCommon(ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
264     }
265 
266     @ApiTest(apis = {"android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE"})
267     @Test
testForegroundServiceTypeSpecialUseProperty()268     public void testForegroundServiceTypeSpecialUseProperty() throws Exception {
269         final String expectedPropertyValue = "foo";
270         try {
271             final PackageManager.Property prop = mTargetContext.getPackageManager()
272                     .getProperty(PackageManager.PROPERTY_SPECIAL_USE_FGS_SUBTYPE,
273                             TEST_COMP_TARGET_FGS_NO_TYPE);
274             fail("Property " + PackageManager.PROPERTY_SPECIAL_USE_FGS_SUBTYPE + " not expected.");
275         } catch (PackageManager.NameNotFoundException e) {
276             // expected.
277         }
278         final PackageManager.Property prop = mTargetContext.getPackageManager()
279                 .getProperty(PackageManager.PROPERTY_SPECIAL_USE_FGS_SUBTYPE,
280                         TEST_COMP_TARGET_FGS_ALL_TYPE);
281         assertEquals(expectedPropertyValue, prop.getString());
282     }
283 
testForegroundServiceTypeDisabledCommon(int type, ComponentName api33Comp, ComponentName apiCurComp)284     private void testForegroundServiceTypeDisabledCommon(int type,
285             ComponentName api33Comp, ComponentName apiCurComp) throws Exception {
286         testForegroundServiceTypeDisabledCommon(type, RESULT_INVALID_TYPE_EXCEPTION,
287                 api33Comp, apiCurComp);
288     }
289 
testForegroundServiceTypeDisabledCommon(int type, int exceptionType, ComponentName api33Comp, ComponentName apiCurComp)290     private void testForegroundServiceTypeDisabledCommon(int type, int exceptionType,
291             ComponentName api33Comp, ComponentName apiCurComp) throws Exception {
292         final ApplicationInfo appCurInfo = mTargetContext.getPackageManager().getApplicationInfo(
293                 TEST_PKG_NAME_CURRENT, 0);
294         final ApplicationInfo app33Info = mTargetContext.getPackageManager().getApplicationInfo(
295                 TEST_PKG_NAME_API33, 0);
296         final WatchUidRunner uidCurWatcher = new WatchUidRunner(mInstrumentation, appCurInfo.uid,
297                 WAITFOR_MSEC);
298         final WatchUidRunner uid33Watcher = new WatchUidRunner(mInstrumentation, app33Info.uid,
299                 WAITFOR_MSEC);
300 
301         final ForegroundServiceTypePolicy policy = ForegroundServiceTypePolicy.getDefaultPolicy();
302         final ForegroundServiceTypePolicyInfo info = policy.getForegroundServiceTypePolicyInfo(
303                 type, ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
304         try {
305             SystemUtil.runWithShellPermissionIdentity(() -> {
306                 info.setTypeDisabledForTest(false, TEST_PKG_NAME_CURRENT);
307                 info.setTypeDisabledForTest(false, TEST_PKG_NAME_API33);
308             });
309             startAndStopFgsType(api33Comp, type, uid33Watcher);
310             startAndStopFgsType(apiCurComp, type, uidCurWatcher);
311 
312             SystemUtil.runWithShellPermissionIdentity(() -> {
313                 info.setTypeDisabledForTest(true, TEST_PKG_NAME_CURRENT);
314             });
315 
316             assertEquals(exceptionType, startForegroundServiceWithType(apiCurComp, type));
317 
318             stopService(apiCurComp, null);
319             startAndStopFgsType(api33Comp, type, uid33Watcher);
320         } finally {
321             SystemUtil.runWithShellPermissionIdentity(() -> {
322                 info.clearTypeDisabledForTest(TEST_PKG_NAME_CURRENT);
323                 info.clearTypeDisabledForTest(TEST_PKG_NAME_API33);
324             });
325         }
326     }
327 
startAndStopFgsType(ComponentName compName, int type, WatchUidRunner uidWatcher)328     private void startAndStopFgsType(ComponentName compName, int type, WatchUidRunner uidWatcher)
329             throws Exception {
330         assertEquals(RESULT_OK, startForegroundServiceWithType(compName, type));
331         if (uidWatcher != null) {
332             uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE, null);
333         }
334         stopService(compName, uidWatcher);
335     }
336 
startForegroundServiceWithType(ComponentName compName, int type)337     private int startForegroundServiceWithType(ComponentName compName, int type) throws Exception {
338         final CountDownLatch latch = new CountDownLatch(1);
339         final int[] result = new int[1];
340         final BroadcastReceiver receiver = new BroadcastReceiver() {
341             @Override
342             public void onReceive(Context context, Intent intent) {
343                 result[0] = intent.getIntExtra(LocalForegroundServiceBase.EXTRA_RESULT_CODE,
344                         RESULT_OK);
345                 latch.countDown();
346             }
347         };
348         final Intent intent = new Intent();
349         intent.setComponent(compName);
350         intent.putExtra(LocalForegroundServiceBase.EXTRA_COMMAND,
351                 LocalForegroundServiceBase.COMMAND_START_FOREGROUND);
352         intent.putExtra(LocalForegroundServiceBase.EXTRA_FGS_TYPE, type);
353         final IntentFilter filter =
354                 new IntentFilter(LocalForegroundServiceBase.ACTION_START_FGS_RESULT);
355 
356         try {
357             mTargetContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
358             mTargetContext.startForegroundService(intent);
359             latch.await(WAITFOR_MSEC, TimeUnit.MILLISECONDS);
360             return result[0];
361         } finally {
362             mTargetContext.unregisterReceiver(receiver);
363         }
364     }
365 
stopService(ComponentName compName, WatchUidRunner uidWatcher)366     private void stopService(ComponentName compName, WatchUidRunner uidWatcher) throws Exception {
367         final Intent intent = new Intent();
368         intent.setComponent(compName);
369         intent.putExtra(LocalForegroundServiceBase.EXTRA_COMMAND,
370                 LocalForegroundServiceBase.COMMAND_STOP_SELF);
371 
372         mTargetContext.startService(intent);
373 
374         if (uidWatcher != null) {
375             uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
376                     null);
377         }
378     }
379 
testPermissionEnforcementCommon(int type)380     private void testPermissionEnforcementCommon(int type) throws Exception {
381         testPermissionEnforcementCommon(type, null);
382     }
383 
testPermissionEnforcementCommon(int type, String[] specialOps)384     private void testPermissionEnforcementCommon(int type, String[] specialOps) throws Exception {
385         final String testPackageName = TEST_PKG_NAME_TARGET;
386         TestPermissionInfo[] allOfPermissions = null;
387         TestPermissionInfo[] anyOfPermissions = null;
388         final ForegroundServiceTypePolicy policy =
389                 ForegroundServiceTypePolicy.getDefaultPolicy();
390         final ForegroundServiceTypePolicyInfo info = policy.getForegroundServiceTypePolicyInfo(
391                 type, ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);
392         final String permFlag = info.getPermissionEnforcementFlagForTest();
393         try (DeviceConfigStateHelper helper = new DeviceConfigStateHelper("activity_manager")) {
394             // Enable the permission check.
395             enablePermissionEnforcement(true, testPackageName);
396             if (permFlag != null) {
397                 helper.set(permFlag, "true");
398             }
399 
400             assertEquals(type, info.getForegroundServiceType());
401             allOfPermissions = triagePermissions(
402                     info.getRequiredAllOfPermissionsForTest(mTargetContext).orElse(null));
403             anyOfPermissions = ArrayUtils.concat(TestPermissionInfo.class,
404                     triagePermissions(info.getRequiredAnyOfPermissionsForTest(
405                             mTargetContext).orElse(null)),
406                     triagePermissions(specialOps));
407 
408             // If we grant all of the permissions, the foreground service start will succeed.
409             grantPermissions(ArrayUtils.concat(TestPermissionInfo.class,
410                     allOfPermissions, anyOfPermissions), testPackageName);
411 
412             startAndStopFgsType(TEST_COMP_TARGET_FGS_ALL_TYPE, type, null);
413 
414             resetPermissions(anyOfPermissions, testPackageName);
415 
416             // If we grant all of the "allOf" permission, but none of the "anyOf" permission, it
417             // should fail to start a foreground service.
418             if (!ArrayUtils.isEmpty(anyOfPermissions)) {
419                 grantPermissions(allOfPermissions, testPackageName);
420                 assertEquals(RESULT_SECURITY_EXCEPTION,
421                         startForegroundServiceWithType(TEST_COMP_TARGET_FGS_ALL_TYPE, type));
422                 stopService(TEST_COMP_TARGET_FGS_ALL_TYPE, null);
423                 resetPermissions(anyOfPermissions, testPackageName);
424 
425                 // If there is a feature flag to turn the permission check off, it should succeed.
426                 if (permFlag != null) {
427                     helper.set(permFlag, "false");
428                     grantPermissions(allOfPermissions, testPackageName);
429                     startAndStopFgsType(TEST_COMP_TARGET_FGS_ALL_TYPE, type, null);
430                     resetPermissions(anyOfPermissions, testPackageName);
431                     helper.set(permFlag, "true");
432                 }
433 
434                 // If we grant any of them, it should succeed.
435                 for (TestPermissionInfo perm: anyOfPermissions) {
436                     grantPermissions(ArrayUtils.concat(TestPermissionInfo.class,
437                             allOfPermissions, new TestPermissionInfo[] {perm}),
438                             testPackageName);
439                     startAndStopFgsType(TEST_COMP_TARGET_FGS_ALL_TYPE, type, null);
440                     resetPermissions(anyOfPermissions, testPackageName);
441                 }
442             }
443 
444             // If we skip one of the "allOf" permissions, it should fail.
445             if (!ArrayUtils.isEmpty(allOfPermissions)) {
446                 for (int i = 0; i < allOfPermissions.length; i++) {
447                     final TestPermissionInfo[] perms = getListExceptIndex(allOfPermissions, i);
448                     grantPermissions(ArrayUtils.concat(TestPermissionInfo.class,
449                                 perms, anyOfPermissions), testPackageName);
450                     assertEquals(RESULT_SECURITY_EXCEPTION,
451                             startForegroundServiceWithType(TEST_COMP_TARGET_FGS_ALL_TYPE, type));
452                     stopService(TEST_COMP_TARGET_FGS_ALL_TYPE, null);
453                     resetPermissions(anyOfPermissions, testPackageName);
454                 }
455             }
456         } finally {
457             resetPermissions(anyOfPermissions, testPackageName);
458             enablePermissionEnforcement(false, testPackageName);
459         }
460     }
461 
regularPermissionToAppOpIfPossible(TestPermissionInfo perm)462     private static int regularPermissionToAppOpIfPossible(TestPermissionInfo perm) {
463         return !perm.mIsAppOps && perm.mSpecialOp == null
464                 ? AppOpsManager.permissionToOpCode(perm.mName)
465                 : AppOpsManager.OP_NONE;
466     }
467 
getListExceptIndex(TestPermissionInfo[] list, int exceptIndex)468     private TestPermissionInfo[] getListExceptIndex(TestPermissionInfo[] list, int exceptIndex) {
469         final ArrayList<TestPermissionInfo> ret = new ArrayList<>();
470         for (int i = 0; i < list.length; i++) {
471             if (i == exceptIndex) {
472                 continue;
473             }
474             ret.add(list[i]);
475         }
476         if (ret.size() > 0) {
477             return ret.toArray(new TestPermissionInfo[ret.size()]);
478         } else {
479             return null;
480         }
481     }
482 
enablePermissionEnforcement(boolean enable, String packageName)483     private void enablePermissionEnforcement(boolean enable, String packageName) throws Exception {
484         if (enable) {
485             executeShellCommand("am compat enable --no-kill FGS_TYPE_PERMISSION_CHANGE_ID "
486                     + packageName);
487         } else {
488             executeShellCommand("am compat disable --no-kill FGS_TYPE_PERMISSION_CHANGE_ID "
489                     + packageName);
490         }
491     }
492 
clearPermissionEnforcement(String packageName)493     private void clearPermissionEnforcement(String packageName) throws Exception {
494         executeShellCommand("am compat reset --no-kill FGS_TYPE_PERMISSION_CHANGE_ID "
495                 + packageName);
496     }
497 
executeShellCommand(String cmd)498     private String executeShellCommand(String cmd) throws Exception {
499         final UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
500         return uiDevice.executeShellCommand(cmd).trim();
501     }
502 
503     private class TestPermissionInfo {
504         final String mName;
505         final boolean mIsAppOps;
506         final SpecialPermissionOp mSpecialOp;
507         final boolean mIsRole;
508 
TestPermissionInfo(String name, boolean isAppOps, SpecialPermissionOp specialOp, boolean isRole)509         TestPermissionInfo(String name, boolean isAppOps, SpecialPermissionOp specialOp,
510                 boolean isRole) {
511             mName = name;
512             mIsAppOps = isAppOps;
513             mSpecialOp = specialOp;
514             mIsRole = isRole;
515         }
516     }
517 
518     private interface SpecialPermissionOp {
grantPermission(String packageName)519         void grantPermission(String packageName) throws Exception;
revokePermission(String packageName)520         void revokePermission(String packageName) throws Exception;
521     }
522 
523     private class DeviceAllowlistPermissionOp implements SpecialPermissionOp {
524         @Override
grantPermission(String packageName)525         public void grantPermission(String packageName) throws Exception {
526             executeShellCommand("cmd deviceidle whitelist +" + packageName);
527         }
528 
529         @Override
revokePermission(String packageName)530         public void revokePermission(String packageName) throws Exception {
531             executeShellCommand("cmd deviceidle whitelist -" + packageName);
532         }
533     }
534 
triagePermissions(String[] permissions)535     private TestPermissionInfo[] triagePermissions(String[] permissions) {
536         final ArrayList<TestPermissionInfo> perms = new ArrayList<>();
537         if (permissions != null) {
538             final RoleManager rm = mTargetContext.getSystemService(RoleManager.class);
539             for (String perm : permissions) {
540                 PermissionInfo pi = null;
541                 try {
542                     pi = mPackageManager.getPermissionInfo(perm, 0);
543                 } catch (PackageManager.NameNotFoundException e) {
544                     // It could be an appop.
545                 }
546                 if (pi != null) {
547                     perms.add(new TestPermissionInfo(perm, false, null, false));
548                 } else if (sSpecialPermissionOps.containsKey(perm)) {
549                     perms.add(new TestPermissionInfo(perm, false, sSpecialPermissionOps.get(perm),
550                             true));
551                 } else if (rm.isRoleAvailable(perm)) {
552                     perms.add(new TestPermissionInfo(perm, false, null, true));
553                 } else {
554                     try {
555                         AppOpsManager.strOpToOp(perm);
556                         perms.add(new TestPermissionInfo(perm, true, null, false));
557                     } catch (IllegalArgumentException e) {
558                         // We don't support other type of permissions in CTS tests here.
559                     }
560                 }
561             }
562         }
563         return perms.toArray(new TestPermissionInfo[perms.size()]);
564     }
565 
grantPermissions(TestPermissionInfo[] permissions, String packageName)566     private void grantPermissions(TestPermissionInfo[] permissions, String packageName)
567             throws Exception {
568         if (ArrayUtils.isEmpty(permissions)) {
569             return;
570         }
571         final String[] regularPermissions = Arrays.stream(permissions)
572                 .filter(p -> !p.mIsAppOps && p.mSpecialOp == null && !p.mIsRole)
573                 .map(p -> p.mName)
574                 .toArray(String[]::new);
575         final String[] appops = ArrayUtils.concat(String.class, Arrays.stream(permissions)
576                 .filter(p -> p.mIsAppOps && p.mSpecialOp == null && !p.mIsRole)
577                 .map(p -> p.mName)
578                 .toArray(String[]::new),
579                 Arrays.stream(permissions)
580                 .filter(p -> regularPermissionToAppOpIfPossible(p) != AppOpsManager.OP_NONE)
581                 .map(p-> AppOpsManager.opToPublicName(regularPermissionToAppOpIfPossible(p)))
582                 .toArray(String[]::new));
583         final SpecialPermissionOp[] specialOps = Arrays.stream(permissions)
584                 .filter(p-> p.mSpecialOp != null)
585                 .map(p -> p.mSpecialOp)
586                 .toArray(SpecialPermissionOp[]::new);
587         final String[] roles = Arrays.stream(permissions)
588                 .filter(p-> p.mIsRole && p.mSpecialOp == null && !p.mIsAppOps)
589                 .map(p -> p.mName)
590                 .toArray(String[]::new);
591         if (!ArrayUtils.isEmpty(regularPermissions)) {
592             mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(regularPermissions);
593         }
594         if (!ArrayUtils.isEmpty(appops)) {
595             for (String appop : appops) {
596                 // Because we're adopting the shell identity, we have to set the appop to shell here
597                 executeShellCommand("appops set --user " + UserHandle.myUserId()
598                         + " --uid " + SHELL_PKG_NAME + " " + appop + " allow");
599             }
600         }
601         if (!ArrayUtils.isEmpty(specialOps)) {
602             for (SpecialPermissionOp op : specialOps) {
603                 op.grantPermission(packageName);
604             }
605         }
606         if (!ArrayUtils.isEmpty(roles)) {
607             for (String role: roles) {
608                 executeShellCommand("cmd role add-role-holder --user " + UserHandle.myUserId()
609                         + " " + role + " " + packageName);
610             }
611         }
612     }
613 
resetPermissions(TestPermissionInfo[] permissions, String packageName)614     private void resetPermissions(TestPermissionInfo[] permissions, String packageName)
615             throws Exception {
616         mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
617         executeShellCommand("appops reset --user " + UserHandle.myUserId()
618                 + " " + SHELL_PKG_NAME);
619         if (permissions != null) {
620             final SpecialPermissionOp[] specialOps = Arrays.stream(permissions)
621                     .filter(p-> p.mSpecialOp != null)
622                     .map(p -> p.mSpecialOp)
623                     .toArray(SpecialPermissionOp[]::new);
624             final String[] roles = Arrays.stream(permissions)
625                     .filter(p-> p.mIsRole && p.mSpecialOp == null && !p.mIsAppOps)
626                     .map(p -> p.mName)
627                     .toArray(String[]::new);
628             if (!ArrayUtils.isEmpty(specialOps)) {
629                 for (SpecialPermissionOp op : specialOps) {
630                     op.revokePermission(packageName);
631                 }
632             }
633             if (!ArrayUtils.isEmpty(roles)) {
634                 for (String role: roles) {
635                     executeShellCommand("cmd role remove-role-holder --user "
636                             + UserHandle.myUserId() + " " + role + " " + packageName);
637                 }
638             }
639         }
640     }
641 }
642