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 com.android.server.devicelock;
18 
19 import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION;
20 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
21 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
22 import static android.devicelock.DeviceId.DEVICE_ID_TYPE_IMEI;
23 import static android.devicelock.DeviceId.DEVICE_ID_TYPE_MEID;
24 import static android.devicelock.IDeviceLockService.KEY_REMOTE_CALLBACK_RESULT;
25 import static android.os.UserHandle.USER_SYSTEM;
26 
27 import static com.android.server.devicelock.DeviceLockControllerPackageUtils.SERVICE_ACTION;
28 import static com.android.server.devicelock.DeviceLockServiceImpl.MANAGE_DEVICE_LOCK_SERVICE_FROM_CONTROLLER;
29 import static com.android.server.devicelock.DeviceLockServiceImpl.OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
30 import static com.android.server.devicelock.DeviceLockServiceImpl.OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
31 import static com.android.server.devicelock.DeviceLockServiceImpl.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
32 import static com.android.server.devicelock.TestUtils.eventually;
33 
34 import static com.google.common.truth.Truth.assertThat;
35 
36 import static org.mockito.ArgumentMatchers.any;
37 import static org.mockito.ArgumentMatchers.eq;
38 import static org.mockito.Mockito.doAnswer;
39 import static org.mockito.Mockito.mock;
40 import static org.mockito.Mockito.timeout;
41 import static org.mockito.Mockito.verify;
42 import static org.robolectric.Shadows.shadowOf;
43 
44 import android.app.AppOpsManager;
45 import android.app.Application;
46 import android.content.ComponentName;
47 import android.content.Context;
48 import android.content.Intent;
49 import android.content.ServiceConnection;
50 import android.content.pm.ApplicationInfo;
51 import android.content.pm.PackageInfo;
52 import android.content.pm.PackageManager;
53 import android.content.pm.ResolveInfo;
54 import android.content.pm.ServiceInfo;
55 import android.devicelock.IGetDeviceIdCallback;
56 import android.os.Binder;
57 import android.os.Bundle;
58 import android.os.Looper;
59 import android.os.PowerExemptionManager;
60 import android.os.Process;
61 import android.os.RemoteCallback;
62 import android.os.UserHandle;
63 import android.os.UserManager;
64 import android.telephony.TelephonyManager;
65 
66 import androidx.test.core.app.ApplicationProvider;
67 
68 import com.android.devicelockcontroller.IDeviceLockControllerService;
69 
70 import org.junit.Before;
71 import org.junit.Rule;
72 import org.junit.Test;
73 import org.junit.runner.RunWith;
74 import org.mockito.Mock;
75 import org.mockito.junit.MockitoJUnit;
76 import org.mockito.junit.MockitoRule;
77 import org.mockito.stubbing.Answer;
78 import org.robolectric.RobolectricTestRunner;
79 import org.robolectric.shadows.ShadowAppOpsManager;
80 import org.robolectric.shadows.ShadowApplication;
81 import org.robolectric.shadows.ShadowBinder;
82 import org.robolectric.shadows.ShadowPackageManager;
83 import org.robolectric.shadows.ShadowTelephonyManager;
84 import org.robolectric.shadows.ShadowUserManager;
85 
86 import java.util.concurrent.ExecutionException;
87 import java.util.concurrent.ExecutorService;
88 import java.util.concurrent.Executors;
89 import java.util.concurrent.atomic.AtomicBoolean;
90 
91 /**
92  * Tests for {@link com.android.server.devicelock.DeviceLockServiceImpl}.
93  *
94  * TODO(b/329330992): Add tests for multi-user scenarios where users have different finalization
95  * states. Robolectric does not support creating contexts as other users, so the package manager
96  * infos are the same for all users. This makes it infeasible to unit test scenarios where the
97  * package states are different for different users.
98  *
99  */
100 @RunWith(RobolectricTestRunner.class)
101 public final class DeviceLockServiceImplTest {
102     private static final String DLC_PACKAGE_NAME = "test.package";
103 
104     private static final String DLC_SERVICE_NAME = "test.service";
105 
106     private static final String SYSTEM_USER_NAME = "system";
107     private static final int USER_SECONDARY = 10;
108     private static final String SECONDARY_USER_NAME = "secondary";
109 
110     private static final long ONE_SEC_MILLIS = 1000;
111 
112     @Rule
113     public MockitoRule mMockitoRule = MockitoJUnit.rule();
114 
115     private Context mContext;
116     private ShadowTelephonyManager mShadowTelephonyManager;
117     private ShadowAppOpsManager mShadowAppOpsManager;
118     private ShadowPackageManager mShadowPackageManager;
119     private PackageManager mPackageManager;
120     private ShadowUserManager mShadowUserManager;
121     private UserHandle mSystemUser;
122     private UserHandle mSecondaryUser;
123 
124     @Mock
125     private IDeviceLockControllerService mDeviceLockControllerService;
126     @Mock
127     private PowerExemptionManager mPowerExemptionManager;
128 
129     private ShadowApplication mShadowApplication;
130 
131     private DeviceLockServiceImpl mService;
132     private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
133 
134     @Before
setup()135     public void setup() throws Exception {
136         mContext = ApplicationProvider.getApplicationContext();
137         mShadowApplication = shadowOf((Application) mContext);
138         mShadowApplication.grantPermissions(MANAGE_DEVICE_LOCK_SERVICE_FROM_CONTROLLER);
139         mShadowApplication.setSystemService(
140                 mContext.getSystemServiceName(PowerExemptionManager.class),
141                 mPowerExemptionManager);
142 
143         mPackageManager = mContext.getPackageManager();
144         mShadowPackageManager = shadowOf(mPackageManager);
145         mShadowPackageManager.setPackagesForUid(Process.myUid(),
146                 new String[]{mContext.getPackageName()});
147 
148         PackageInfo dlcPackageInfo = new PackageInfo();
149         dlcPackageInfo.packageName = DLC_PACKAGE_NAME;
150         mShadowPackageManager.installPackage(dlcPackageInfo);
151 
152         Intent intent = new Intent(SERVICE_ACTION);
153         ResolveInfo resolveInfo = makeDlcResolveInfo();
154         mShadowPackageManager.addResolveInfoForIntent(intent, resolveInfo);
155 
156         TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
157         mShadowTelephonyManager = shadowOf(telephonyManager);
158 
159         mShadowAppOpsManager = shadowOf(mContext.getSystemService(AppOpsManager.class));
160 
161         mShadowUserManager = shadowOf(mContext.getSystemService(UserManager.class));
162         mSystemUser = mShadowUserManager.addUser(USER_SYSTEM, SYSTEM_USER_NAME, /* flags= */ 0);
163         mSecondaryUser = mShadowUserManager.addUser(USER_SECONDARY, SECONDARY_USER_NAME,
164                 /* flags= */ 0);
165 
166         mService = new DeviceLockServiceImpl(mContext, telephonyManager, mExecutorService,
167                 mContext.getFilesDir());
168         waitUntilBgExecutorIdle();
169         shadowOf(Looper.getMainLooper()).idle();
170     }
171 
172     @Test
getDeviceId_withIMEIType_shouldReturnIMEI()173     public void getDeviceId_withIMEIType_shouldReturnIMEI() throws Exception {
174         // GIVEN an IMEI registered in telephony manager
175         final String testImei = "983402979622353";
176         mShadowTelephonyManager.setActiveModemCount(1);
177         mShadowTelephonyManager.setImei(/* slotIndex= */ 0, testImei);
178         mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_TELEPHONY_GSM,
179                 /* supported= */ true);
180 
181         // GIVEN a successful service call to DLC app
182         doAnswer((Answer<Void>) invocation -> {
183             RemoteCallback callback = invocation.getArgument(0);
184             Bundle bundle = new Bundle();
185             bundle.putString(IDeviceLockControllerService.KEY_RESULT, testImei);
186             callback.sendResult(bundle);
187             return null;
188         }).when(mDeviceLockControllerService).getDeviceIdentifier(any(RemoteCallback.class));
189 
190         IGetDeviceIdCallback mockCallback = mock(IGetDeviceIdCallback.class);
191 
192         // WHEN the device id is requested with the IMEI device type
193         mService.getDeviceId(mockCallback, 1 << DEVICE_ID_TYPE_IMEI);
194         waitUntilConnected();
195 
196         // THEN the IMEI id is received
197         verify(mockCallback, timeout(ONE_SEC_MILLIS)).onDeviceIdReceived(
198                 eq(DEVICE_ID_TYPE_IMEI), eq(testImei));
199     }
200 
201     @Test
getDeviceId_withMEIDType_shouldReturnMEID()202     public void getDeviceId_withMEIDType_shouldReturnMEID() throws Exception {
203         // GIVEN an MEID registered in telephony manager
204         final String testMeid = "354403064522046";
205         mShadowTelephonyManager.setActiveModemCount(1);
206         mShadowTelephonyManager.setMeid(/* slotIndex= */ 0, testMeid);
207         mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA,
208                 /* supported= */ true);
209 
210         // GIVEN a successful service call to DLC app
211         doAnswer((Answer<Void>) invocation -> {
212             RemoteCallback callback = invocation.getArgument(0);
213             Bundle bundle = new Bundle();
214             bundle.putString(IDeviceLockControllerService.KEY_RESULT, testMeid);
215             callback.sendResult(bundle);
216             return null;
217         }).when(mDeviceLockControllerService).getDeviceIdentifier(any(RemoteCallback.class));
218 
219         IGetDeviceIdCallback mockCallback = mock(IGetDeviceIdCallback.class);
220 
221         // WHEN the device id is requested with the MEID device type
222         mService.getDeviceId(mockCallback, 1 << DEVICE_ID_TYPE_MEID);
223         waitUntilConnected();
224 
225         // THEN the MEID id is received
226         verify(mockCallback, timeout(ONE_SEC_MILLIS)).onDeviceIdReceived(
227                 eq(DEVICE_ID_TYPE_MEID), eq(testMeid));
228     }
229 
230     @Test
setCallerAllowedToSendUndismissibleNotifications_trueAllowsAppOp()231     public void setCallerAllowedToSendUndismissibleNotifications_trueAllowsAppOp() {
232         final AtomicBoolean succeeded = new AtomicBoolean(false);
233         RemoteCallback callback = new RemoteCallback(result -> {
234             succeeded.set(result.getBoolean(KEY_REMOTE_CALLBACK_RESULT));
235         });
236         mService.setCallerAllowedToSendUndismissibleNotifications(true, callback);
237 
238         assertThat(succeeded.get()).isTrue();
239         final int opMode = mShadowAppOpsManager.unsafeCheckOpNoThrow(
240                 OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS,
241                 Process.myUid(),
242                 DLC_PACKAGE_NAME);
243         assertThat(opMode).isEqualTo(AppOpsManager.MODE_ALLOWED);
244     }
245 
246     @Test
setCallerAllowedToSendUndismissibleNotifications_falseDisallowsAppOp()247     public void setCallerAllowedToSendUndismissibleNotifications_falseDisallowsAppOp() {
248         final AtomicBoolean succeeded = new AtomicBoolean(false);
249         RemoteCallback callback = new RemoteCallback(result -> {
250             succeeded.set(result.getBoolean(KEY_REMOTE_CALLBACK_RESULT));
251         });
252         mService.setCallerAllowedToSendUndismissibleNotifications(false, callback);
253 
254         assertThat(succeeded.get()).isTrue();
255         final int opMode = mShadowAppOpsManager.unsafeCheckOpNoThrow(
256                 OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS,
257                 Process.myUid(),
258                 DLC_PACKAGE_NAME);
259         assertThat(opMode).isEqualTo(AppOpsManager.MODE_DEFAULT);
260     }
261 
262     @Test
setCallerExemptFromActivityBgStartRestrictionState_trueAllowsAppOp()263     public void setCallerExemptFromActivityBgStartRestrictionState_trueAllowsAppOp() {
264         final AtomicBoolean succeeded = new AtomicBoolean(false);
265         RemoteCallback callback = new RemoteCallback(result -> {
266             succeeded.set(result.getBoolean(KEY_REMOTE_CALLBACK_RESULT));
267         });
268         mService.setCallerExemptFromActivityBgStartRestrictionState(true, callback);
269 
270         assertThat(succeeded.get()).isTrue();
271         final int opMode = mShadowAppOpsManager.unsafeCheckOpNoThrow(
272                 OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
273                 Process.myUid(),
274                 DLC_PACKAGE_NAME);
275         assertThat(opMode).isEqualTo(AppOpsManager.MODE_ALLOWED);
276     }
277 
278     @Test
setCallerExemptFromActivityBgStartRestrictionState_falseDisallowsAppOp()279     public void setCallerExemptFromActivityBgStartRestrictionState_falseDisallowsAppOp() {
280         final AtomicBoolean succeeded = new AtomicBoolean(false);
281         RemoteCallback callback = new RemoteCallback(result -> {
282             succeeded.set(result.getBoolean(KEY_REMOTE_CALLBACK_RESULT));
283         });
284         mService.setCallerExemptFromActivityBgStartRestrictionState(false, callback);
285 
286         assertThat(succeeded.get()).isTrue();
287         final int opMode = mShadowAppOpsManager.unsafeCheckOpNoThrow(
288                 OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
289                 Process.myUid(),
290                 DLC_PACKAGE_NAME);
291         assertThat(opMode).isEqualTo(AppOpsManager.MODE_DEFAULT);
292     }
293 
294     @Test
setUidExemptFromRestrictionsState_trueAllowsAppOps()295     public void setUidExemptFromRestrictionsState_trueAllowsAppOps() {
296         final AtomicBoolean succeeded = new AtomicBoolean(false);
297         RemoteCallback callback = new RemoteCallback(result -> {
298             succeeded.set(result.getBoolean(KEY_REMOTE_CALLBACK_RESULT));
299         });
300         mService.setUidExemptFromRestrictionsState(Process.myUid(), true, callback);
301 
302         assertThat(succeeded.get()).isTrue();
303         final int hibernationOpMode = mShadowAppOpsManager.unsafeCheckOpNoThrow(
304                 OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION,
305                 Process.myUid(),
306                 mContext.getPackageName());
307         assertThat(hibernationOpMode).isEqualTo(AppOpsManager.MODE_ALLOWED);
308         final int powerOpMode = mShadowAppOpsManager.unsafeCheckOpNoThrow(
309                 OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS,
310                 Process.myUid(),
311                 mContext.getPackageName());
312         assertThat(powerOpMode).isEqualTo(AppOpsManager.MODE_ALLOWED);
313     }
314 
315     @Test
setUidExemptFromRestrictionsState_falseDisallowsAppOps()316     public void setUidExemptFromRestrictionsState_falseDisallowsAppOps() {
317         final AtomicBoolean succeeded = new AtomicBoolean(false);
318         RemoteCallback callback = new RemoteCallback(result -> {
319             succeeded.set(result.getBoolean(KEY_REMOTE_CALLBACK_RESULT));
320         });
321         mService.setUidExemptFromRestrictionsState(Process.myUid(), false, callback);
322 
323         assertThat(succeeded.get()).isTrue();
324         final int hibernationOpMode = mShadowAppOpsManager.unsafeCheckOpNoThrow(
325                 OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION,
326                 Process.myUid(),
327                 mContext.getPackageName());
328         assertThat(hibernationOpMode).isEqualTo(AppOpsManager.MODE_DEFAULT);
329         final int powerOpMode = mShadowAppOpsManager.unsafeCheckOpNoThrow(
330                 OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS,
331                 Process.myUid(),
332                 mContext.getPackageName());
333         assertThat(powerOpMode).isEqualTo(AppOpsManager.MODE_DEFAULT);
334     }
335 
336     @Test
setDeviceFinalized_nonSystemUser_disablesPackage()337     public void setDeviceFinalized_nonSystemUser_disablesPackage() throws Exception {
338         ShadowBinder.setCallingUserHandle(mSecondaryUser);
339 
340         AtomicBoolean succeeded = new AtomicBoolean(false);
341         mService.setDeviceFinalized(true, new RemoteCallback(result -> succeeded.set(true)));
342         waitUntilBgExecutorIdle();
343 
344         assertThat(succeeded.get()).isTrue();
345         assertThat(mPackageManager.getApplicationEnabledSetting(DLC_PACKAGE_NAME))
346                 .isEqualTo(COMPONENT_ENABLED_STATE_DISABLED);
347     }
348 
349     @Test
setDeviceFinalized_systemUser_butOtherUserUnfinalized_doesNotDisablePackage()350     public void setDeviceFinalized_systemUser_butOtherUserUnfinalized_doesNotDisablePackage()
351             throws Exception {
352         ShadowBinder.setCallingUserHandle(mSystemUser);
353 
354         AtomicBoolean succeeded = new AtomicBoolean(false);
355         mService.setDeviceFinalized(true, new RemoteCallback(result -> succeeded.set(true)));
356         waitUntilBgExecutorIdle();
357 
358         assertThat(succeeded.get()).isTrue();
359         assertThat(mPackageManager.getApplicationEnabledSetting(DLC_PACKAGE_NAME))
360                 .isEqualTo(COMPONENT_ENABLED_STATE_DEFAULT);
361     }
362 
363     @Test
onUserSwitching_ifNotFinalizedAndDlcDisabled_enables()364     public void onUserSwitching_ifNotFinalizedAndDlcDisabled_enables() throws Exception {
365         // GIVEN device is not finalized and DLC is disabled
366         ShadowBinder.setCallingUserHandle(mSecondaryUser);
367         mPackageManager.setApplicationEnabledSetting(
368                 DLC_PACKAGE_NAME, COMPONENT_ENABLED_STATE_DISABLED, /* flags= */ 0);
369 
370         // WHEN the service checks finalization
371         mService.onUserSwitching(mSecondaryUser);
372 
373         waitUntilBgExecutorIdle();
374         shadowOf(Looper.getMainLooper()).idle();
375 
376         // THEN DLC is enabled
377         assertThat(mPackageManager.getApplicationEnabledSetting(DLC_PACKAGE_NAME))
378                 .isEqualTo(COMPONENT_ENABLED_STATE_DEFAULT);
379     }
380 
381     @Test
onUserSwitching_ifFinalizedAndDisabledOnSecondary_doesNothing()382     public void onUserSwitching_ifFinalizedAndDisabledOnSecondary_doesNothing() throws Exception {
383         // GIVEN device is finalized and DLC is disabled on a secondary user
384         ShadowBinder.setCallingUserHandle(mSecondaryUser);
385         mService.setDeviceFinalized(true, new RemoteCallback(result -> {}));
386         waitUntilBgExecutorIdle();
387         assertThat(mPackageManager.getApplicationEnabledSetting(DLC_PACKAGE_NAME))
388                 .isEqualTo(COMPONENT_ENABLED_STATE_DISABLED);
389 
390         // WHEN there is a user switch to a secondary user
391         mService.onUserSwitching(mSecondaryUser);
392 
393         waitUntilBgExecutorIdle();
394         shadowOf(Looper.getMainLooper()).idle();
395 
396         // THEN DLC stays disabled
397         assertThat(mPackageManager.getApplicationEnabledSetting(DLC_PACKAGE_NAME))
398                 .isEqualTo(COMPONENT_ENABLED_STATE_DISABLED);
399     }
400 
401     /**
402      * Make the resolve info for the DLC package.
403      */
makeDlcResolveInfo()404     private ResolveInfo makeDlcResolveInfo() {
405         ApplicationInfo appInfo = new ApplicationInfo();
406         appInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
407         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
408         ServiceInfo serviceInfo = new ServiceInfo();
409         serviceInfo.name = DLC_SERVICE_NAME;
410         serviceInfo.packageName = DLC_PACKAGE_NAME;
411         serviceInfo.applicationInfo = appInfo;
412         ResolveInfo resolveInfo = new ResolveInfo();
413         resolveInfo.serviceInfo = serviceInfo;
414 
415         return resolveInfo;
416     }
417 
418     /**
419      * Set-up calls to mock the service being connected.
420      */
waitUntilConnected()421     private void waitUntilConnected() {
422         eventually(() -> {
423             shadowOf(Looper.getMainLooper()).idle();
424             ServiceConnection connection = mShadowApplication.getBoundServiceConnections().get(0);
425             Binder binder = new Binder();
426             binder.attachInterface(mDeviceLockControllerService,
427                     IDeviceLockControllerService.class.getName());
428             connection.onServiceConnected(new ComponentName(DLC_PACKAGE_NAME, DLC_SERVICE_NAME),
429                     binder);
430         }, ONE_SEC_MILLIS);
431     }
432 
waitUntilBgExecutorIdle()433     private void waitUntilBgExecutorIdle() throws InterruptedException, ExecutionException {
434         mExecutorService.submit(() -> {}).get();
435     }
436 }
437