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