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