• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.healthconnect.permission;
18 
19 import static com.android.server.healthconnect.permission.FirstGrantTimeDatastore.DATA_TYPE_CURRENT;
20 import static com.android.server.healthconnect.permission.FirstGrantTimeDatastore.DATA_TYPE_STAGED;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.ArgumentMatchers.anyInt;
26 import static org.mockito.ArgumentMatchers.anyString;
27 import static org.mockito.ArgumentMatchers.eq;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31 
32 import android.content.Context;
33 import android.content.pm.PackageInfo;
34 import android.content.pm.PackageManager;
35 import android.health.connect.HealthPermissions;
36 import android.os.Process;
37 import android.os.UserHandle;
38 import android.os.UserManager;
39 import android.util.Pair;
40 
41 import androidx.test.ext.junit.runners.AndroidJUnit4;
42 
43 import com.android.server.healthconnect.HealthConnectThreadScheduler;
44 import com.android.server.healthconnect.injector.HealthConnectInjectorImpl;
45 import com.android.server.healthconnect.migration.MigrationStateManager;
46 import com.android.server.healthconnect.storage.datatypehelpers.HealthDataCategoryPriorityHelper;
47 import com.android.server.healthconnect.testing.HealthPermissionsMocker;
48 
49 import org.junit.Before;
50 import org.junit.Rule;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 import org.mockito.ArgumentCaptor;
54 import org.mockito.ArgumentMatchers;
55 import org.mockito.Mock;
56 import org.mockito.junit.MockitoJUnit;
57 import org.mockito.junit.MockitoRule;
58 
59 import java.time.Instant;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.List;
63 import java.util.Optional;
64 import java.util.concurrent.TimeoutException;
65 
66 // TODO(b/261432978): add test for sharedUser backup
67 @RunWith(AndroidJUnit4.class)
68 public class FirstGrantTimeUnitTest {
69 
70     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
71 
72     private static final String SELF_PACKAGE_NAME = "com.android.healthconnect.unittests";
73     private static final UserHandle CURRENT_USER = Process.myUserHandle();
74     private static final int SELF_APP_ID = 123;
75     // Under the hood package uid is a combination of app id and user id.
76     // Create a fake packageId for use in this test that reverses properly.
77     private static final int SELF_PACKAGE_UID = CURRENT_USER.getUid(SELF_APP_ID);
78 
79     private static final int DEFAULT_VERSION = 1;
80 
81     @Mock private HealthPermissionIntentAppsTracker mTracker;
82     @Mock private MigrationStateManager mMigrationStateManager;
83     @Mock private HealthDataCategoryPriorityHelper mHealthDataCategoryPriorityHelper;
84     @Mock private PackageManager mPackageManager;
85     @Mock private UserManager mUserManager;
86     @Mock private Context mContext;
87     @Mock private FirstGrantTimeDatastore mDatastore;
88     @Mock private HealthConnectThreadScheduler mThreadScheduler;
89 
90     private FirstGrantTimeManager mFirstGrantTimeManager;
91 
92     @Before
setUp()93     public void setUp() throws PackageManager.NameNotFoundException {
94         when(mMigrationStateManager.isMigrationInProgress()).thenReturn(false);
95         when(mTracker.supportsPermissionUsageIntent(anyString(), any())).thenReturn(true);
96 
97         when(mDatastore.readForUser(CURRENT_USER, DATA_TYPE_CURRENT))
98                 .thenReturn(new UserGrantTimeState(DEFAULT_VERSION));
99         when(mDatastore.readForUser(CURRENT_USER, DATA_TYPE_STAGED))
100                 .thenReturn(new UserGrantTimeState(DEFAULT_VERSION));
101 
102         when(mContext.createContextAsUser(any(), anyInt())).thenReturn(mContext);
103         when(mContext.getApplicationContext()).thenReturn(mContext);
104         when(mContext.getPackageManager()).thenReturn(mPackageManager);
105         when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
106         when(mContext.getUser()).thenReturn(CURRENT_USER);
107 
108         when(mUserManager.isUserUnlocked(any())).thenReturn(true);
109 
110         when(mPackageManager.getPackageUid(eq(SELF_PACKAGE_NAME), any()))
111                 .thenReturn(SELF_PACKAGE_UID);
112         when(mPackageManager.getPackagesForUid(SELF_PACKAGE_UID))
113                 .thenReturn(new String[] {SELF_PACKAGE_NAME});
114 
115         PackageInfo packageInfo = new PackageInfo();
116         packageInfo.packageName = SELF_PACKAGE_NAME;
117         packageInfo.requestedPermissions = new String[] {HealthPermissions.WRITE_STEPS};
118         packageInfo.requestedPermissionsFlags =
119                 new int[] {PackageInfo.REQUESTED_PERMISSION_GRANTED};
120         when(mPackageManager.getPackageInfo(eq(SELF_PACKAGE_NAME), any())).thenReturn(packageInfo);
121         when(mPackageManager.getInstalledPackages(any())).thenReturn(List.of(packageInfo));
122 
123         HealthPermissionsMocker.mockPackageManagerPermissions(mPackageManager);
124 
125         mFirstGrantTimeManager =
126                 HealthConnectInjectorImpl.newBuilderForTest(mContext)
127                         .setMigrationStateManager(mMigrationStateManager)
128                         .setFirstGrantTimeDatastore(mDatastore)
129                         .setHealthPermissionIntentAppsTracker(mTracker)
130                         .setHealthDataCategoryPriorityHelper(mHealthDataCategoryPriorityHelper)
131                         .setThreadScheduler(mThreadScheduler)
132                         .build()
133                         .getFirstGrantTimeManager();
134     }
135 
136     @Test
testSetFirstGrantTimeForAnApp_expectOtherAppsGrantTimesRemained()137     public void testSetFirstGrantTimeForAnApp_expectOtherAppsGrantTimesRemained()
138             throws PackageManager.NameNotFoundException {
139         Instant instant1 = Instant.parse("2023-02-11T10:00:00Z");
140         Instant instant2 = Instant.parse("2023-02-12T10:00:00Z");
141         Instant instant3 = Instant.parse("2023-02-13T10:00:00Z");
142         String anotherPackage = "another.package";
143 
144         // mock packageManager
145         List<Pair<String, Integer>> packageNameAndAppIdPairs =
146                 Arrays.asList(new Pair<>(SELF_PACKAGE_NAME, 123), new Pair<>(anotherPackage, 456));
147         List<PackageInfo> packageInfos = new ArrayList<>();
148         for (Pair<String, Integer> pair : packageNameAndAppIdPairs) {
149             String packageName = pair.first;
150             int appId = pair.second;
151             int uid = CURRENT_USER.getUid(appId);
152             PackageInfo packageInfo = new PackageInfo();
153             packageInfo.packageName = packageName;
154             packageInfo.requestedPermissions = new String[] {HealthPermissions.WRITE_STEPS};
155             packageInfo.requestedPermissionsFlags =
156                     new int[] {PackageInfo.REQUESTED_PERMISSION_GRANTED};
157             packageInfos.add(packageInfo);
158             when(mPackageManager.getPackageUid(eq(packageName), any())).thenReturn(uid);
159             when(mPackageManager.getPackagesForUid(uid)).thenReturn(new String[] {packageName});
160             when(mPackageManager.getPackageInfo(eq(packageName), any())).thenReturn(packageInfo);
161         }
162         when(mPackageManager.getInstalledPackages(any())).thenReturn(packageInfos);
163 
164         // Mock Datastore.
165         UserGrantTimeState currentGrantTimeState = new UserGrantTimeState(DEFAULT_VERSION);
166         currentGrantTimeState.setPackageGrantTime(SELF_PACKAGE_NAME, instant1);
167         currentGrantTimeState.setPackageGrantTime(anotherPackage, instant2);
168         when(mDatastore.readForUser(CURRENT_USER, DATA_TYPE_CURRENT))
169                 .thenReturn(currentGrantTimeState);
170 
171         assertThat(mFirstGrantTimeManager.getFirstGrantTime(SELF_PACKAGE_NAME, CURRENT_USER))
172                 .hasValue(instant1);
173         assertThat(mFirstGrantTimeManager.getFirstGrantTime(anotherPackage, CURRENT_USER))
174                 .hasValue(instant2);
175 
176         ArgumentCaptor<UserGrantTimeState> captor =
177                 ArgumentCaptor.forClass(UserGrantTimeState.class);
178         mFirstGrantTimeManager.setFirstGrantTime(SELF_PACKAGE_NAME, instant3, CURRENT_USER);
179         verify(mDatastore).writeForUser(captor.capture(), eq(CURRENT_USER), eq(DATA_TYPE_CURRENT));
180 
181         UserGrantTimeState newUserGrantTimeState = captor.getValue();
182         assertThat(newUserGrantTimeState.getPackageGrantTimes().keySet()).hasSize(2);
183         assertThat(newUserGrantTimeState.getPackageGrantTimes().get(SELF_PACKAGE_NAME))
184                 .isEqualTo(instant3);
185         assertThat(newUserGrantTimeState.getPackageGrantTimes().get(anotherPackage))
186                 .isEqualTo(instant2);
187     }
188 
189     @Test(expected = IllegalArgumentException.class)
testUnknownPackage_throwsException()190     public void testUnknownPackage_throwsException() throws PackageManager.NameNotFoundException {
191         String unknownPackage = "android.unknown_package";
192         when(mPackageManager.getPackageUid(eq(unknownPackage), any()))
193                 .thenThrow(new PackageManager.NameNotFoundException());
194         mFirstGrantTimeManager.getFirstGrantTime(unknownPackage, CURRENT_USER);
195     }
196 
197     @Test
testCurrentPackage_intentNotSupported_grantTimeIsNull()198     public void testCurrentPackage_intentNotSupported_grantTimeIsNull() {
199         when(mTracker.supportsPermissionUsageIntent(SELF_PACKAGE_NAME, CURRENT_USER))
200                 .thenReturn(false);
201         assertThat(mFirstGrantTimeManager.getFirstGrantTime(SELF_PACKAGE_NAME, CURRENT_USER))
202                 .isEmpty();
203     }
204 
205     @Test
testOnPermissionsChangedCalledWhileDeviceIsLocked_getGrantTimeNotNullAfterUnlock()206     public void testOnPermissionsChangedCalledWhileDeviceIsLocked_getGrantTimeNotNullAfterUnlock()
207             throws TimeoutException {
208         // before device is unlocked
209         when(mUserManager.isUserUnlocked(any())).thenReturn(false);
210         when(mDatastore.readForUser(CURRENT_USER, DATA_TYPE_CURRENT)).thenReturn(null);
211         when(mDatastore.readForUser(CURRENT_USER, DATA_TYPE_STAGED)).thenReturn(null);
212         mFirstGrantTimeManager.onPermissionsChanged(SELF_PACKAGE_UID);
213         // after device is unlocked
214         when(mUserManager.isUserUnlocked(any())).thenReturn(true);
215         UserGrantTimeState currentGrantTimeState = new UserGrantTimeState(DEFAULT_VERSION);
216         Instant now = Instant.parse("2023-02-14T10:00:00Z");
217         currentGrantTimeState.setPackageGrantTime(SELF_PACKAGE_NAME, now);
218         when(mDatastore.readForUser(CURRENT_USER, DATA_TYPE_CURRENT))
219                 .thenReturn(currentGrantTimeState);
220 
221         assertThat(mFirstGrantTimeManager.getFirstGrantTime(SELF_PACKAGE_NAME, CURRENT_USER))
222                 .hasValue(now);
223     }
224 
225     @Test
testOnPermissionsChanged_withHealthPermissionsUid_expectBackgroundTaskAdded()226     public void testOnPermissionsChanged_withHealthPermissionsUid_expectBackgroundTaskAdded() {
227         mFirstGrantTimeManager.onPermissionsChanged(SELF_PACKAGE_UID);
228 
229         verify(mThreadScheduler, times(1)).scheduleInternalTask(any());
230     }
231 
232     @Test
testOnPermissionsChanged_withNoHealthPermissionsUid_expectNoBackgroundTaskAdded()233     public void testOnPermissionsChanged_withNoHealthPermissionsUid_expectNoBackgroundTaskAdded() {
234         when(mTracker.supportsPermissionUsageIntent(SELF_PACKAGE_NAME, CURRENT_USER))
235                 .thenReturn(false);
236 
237         mFirstGrantTimeManager.onPermissionsChanged(SELF_PACKAGE_UID);
238 
239         verify(mThreadScheduler, times(0)).scheduleInternalTask(any());
240     }
241 
242     @Test
testCurrentPackage_intentSupported_grantTimeIsNotNull()243     public void testCurrentPackage_intentSupported_grantTimeIsNotNull() {
244         // Calling getFirstGrantTime will set grant time for the package
245         Optional<Instant> firstGrantTime =
246                 mFirstGrantTimeManager.getFirstGrantTime(SELF_PACKAGE_NAME, CURRENT_USER);
247         assertThat(firstGrantTime).isPresent();
248 
249         assertThat(firstGrantTime.get()).isGreaterThan(Instant.now().minusSeconds((long) 1e3));
250         assertThat(firstGrantTime.get()).isLessThan(Instant.now().plusSeconds((long) 1e3));
251         firstGrantTime.ifPresent(
252                 grantTime -> {
253                     assertThat(grantTime).isGreaterThan(Instant.now().minusSeconds((long) 1e3));
254                     assertThat(grantTime).isLessThan(Instant.now().plusSeconds((long) 1e3));
255                 });
256         verify(mDatastore)
257                 .writeForUser(
258                         ArgumentMatchers.any(),
259                         ArgumentMatchers.eq(CURRENT_USER),
260                         ArgumentMatchers.eq(DATA_TYPE_CURRENT));
261         verify(mDatastore)
262                 .readForUser(
263                         ArgumentMatchers.eq(CURRENT_USER), ArgumentMatchers.eq(DATA_TYPE_CURRENT));
264     }
265 
266     @Test
testCurrentPackage_noGrantTimeBackupBecameAvailable_grantTimeEqualToStaged()267     public void testCurrentPackage_noGrantTimeBackupBecameAvailable_grantTimeEqualToStaged() {
268         assertThat(mFirstGrantTimeManager.getFirstGrantTime(SELF_PACKAGE_NAME, CURRENT_USER))
269                 .isPresent();
270         Instant backupTime = Instant.now().minusSeconds((long) 1e5);
271         UserGrantTimeState stagedState = setupGrantTimeState(null, backupTime);
272         mFirstGrantTimeManager.applyAndStageGrantTimeStateForUser(CURRENT_USER, stagedState);
273         assertThat(mFirstGrantTimeManager.getFirstGrantTime(SELF_PACKAGE_NAME, CURRENT_USER))
274                 .hasValue(backupTime);
275     }
276 
277     @Test
testCurrentPackage_noBackup_useRecordedTime()278     public void testCurrentPackage_noBackup_useRecordedTime() {
279         Instant stateTime = Instant.now().minusSeconds((long) 1e5);
280         UserGrantTimeState stagedState = setupGrantTimeState(stateTime, null);
281 
282         assertThat(mFirstGrantTimeManager.getFirstGrantTime(SELF_PACKAGE_NAME, CURRENT_USER))
283                 .hasValue(stateTime);
284         mFirstGrantTimeManager.applyAndStageGrantTimeStateForUser(CURRENT_USER, stagedState);
285         assertThat(mFirstGrantTimeManager.getFirstGrantTime(SELF_PACKAGE_NAME, CURRENT_USER))
286                 .hasValue(stateTime);
287     }
288 
289     @Test
testCurrentPackage_noBackup_grantTimeEqualToStaged()290     public void testCurrentPackage_noBackup_grantTimeEqualToStaged() {
291         Instant backupTime = Instant.now().minusSeconds((long) 1e5);
292         Instant stateTime = backupTime.plusSeconds(10);
293         UserGrantTimeState stagedState = setupGrantTimeState(stateTime, backupTime);
294 
295         mFirstGrantTimeManager.applyAndStageGrantTimeStateForUser(CURRENT_USER, stagedState);
296         assertThat(mFirstGrantTimeManager.getFirstGrantTime(SELF_PACKAGE_NAME, CURRENT_USER))
297                 .hasValue(backupTime);
298     }
299 
300     @Test
testCurrentPackage_backupDataLater_stagedDataSkipped()301     public void testCurrentPackage_backupDataLater_stagedDataSkipped() {
302         Instant stateTime = Instant.now().minusSeconds((long) 1e5);
303         UserGrantTimeState stagedState = setupGrantTimeState(stateTime, stateTime.plusSeconds(1));
304 
305         mFirstGrantTimeManager.applyAndStageGrantTimeStateForUser(CURRENT_USER, stagedState);
306         assertThat(mFirstGrantTimeManager.getFirstGrantTime(SELF_PACKAGE_NAME, CURRENT_USER))
307                 .hasValue(stateTime);
308     }
309 
310     @Test
testWriteStagedData_getStagedStateForCurrentPackage_returnsCorrectState()311     public void testWriteStagedData_getStagedStateForCurrentPackage_returnsCorrectState() {
312         Instant stateTime = Instant.now().minusSeconds((long) 1e5);
313         setupGrantTimeState(stateTime, null);
314 
315         UserGrantTimeState state = mFirstGrantTimeManager.getGrantTimeStateForUser(CURRENT_USER);
316         assertThat(state.getSharedUserGrantTimes()).isEmpty();
317         assertThat(state.getPackageGrantTimes().containsKey(SELF_PACKAGE_NAME)).isTrue();
318         assertThat(state.getPackageGrantTimes().get(SELF_PACKAGE_NAME)).isEqualTo(stateTime);
319     }
320 
setupGrantTimeState(Instant currentTime, Instant stagedTime)321     private UserGrantTimeState setupGrantTimeState(Instant currentTime, Instant stagedTime) {
322         if (currentTime != null) {
323             UserGrantTimeState state = new UserGrantTimeState(DEFAULT_VERSION);
324             state.setPackageGrantTime(SELF_PACKAGE_NAME, currentTime);
325             when(mDatastore.readForUser(CURRENT_USER, DATA_TYPE_CURRENT)).thenReturn(state);
326         }
327 
328         UserGrantTimeState backupState = new UserGrantTimeState(DEFAULT_VERSION);
329         if (stagedTime != null) {
330             backupState.setPackageGrantTime(SELF_PACKAGE_NAME, stagedTime);
331         }
332         when(mDatastore.readForUser(CURRENT_USER, DATA_TYPE_STAGED)).thenReturn(backupState);
333         return backupState;
334     }
335 }
336