1 /* 2 * Copyright (C) 2025 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.testing; 18 19 import static org.mockito.ArgumentMatchers.any; 20 import static org.mockito.ArgumentMatchers.anyInt; 21 import static org.mockito.ArgumentMatchers.eq; 22 import static org.mockito.Mockito.when; 23 24 import android.content.Context; 25 import android.content.pm.PackageInfo; 26 import android.content.pm.PackageManager; 27 import android.content.pm.PermissionGroupInfo; 28 import android.content.pm.PermissionInfo; 29 import android.health.connect.HealthConnectManager; 30 import android.health.connect.HealthPermissions; 31 32 import java.lang.reflect.Field; 33 import java.lang.reflect.Modifier; 34 import java.util.ArrayList; 35 import java.util.List; 36 37 /** 38 * Helper class for tests which use mock package managers to get health connect permissions handled 39 * correctly. 40 */ 41 public class HealthPermissionsMocker { 42 43 /** 44 * Add mocking to the given mock package manager to make {@link 45 * HealthConnectManager#getHealthPermissions(Context)} and {@link 46 * HealthConnectManager#isHealthPermission(Context, String)} handle the current set of Health 47 * Connect permissions. 48 * 49 * <p>This method is designed to help tests that use mock package managers. In particular, 50 * because the static method {@link HealthConnectManager#getHealthPermissions(Context)} relies 51 * on the context package manager to populate its cache of permissions, if a mock is not set up 52 * correctly then the cache may be poisoned with incorrect values. Using this method in the 53 * setup for the test can prevent this from happening. 54 * 55 * @param mockPackageManager a mock package manager to populate 56 */ mockPackageManagerPermissions(PackageManager mockPackageManager)57 public static void mockPackageManagerPermissions(PackageManager mockPackageManager) 58 throws PackageManager.NameNotFoundException { 59 // For on device tests we can get permissions from the real package manager. 60 // But for Robolectric tests this will not be set up. So we need to have the values hard 61 // coded. 62 // See implementation for HealthConnectManager.getHealthPermissions() 63 // Here we are setting up fake package and permission information for the health connect 64 // module as this is the source of truth for the health connect permissions. 65 String modulePackageName = "android.health.connect"; 66 67 // Use deprecated constructor as we are simulating construction by the System. 68 PermissionGroupInfo permissionGroupInfo = new PermissionGroupInfo(); 69 permissionGroupInfo.packageName = modulePackageName; 70 when(mockPackageManager.getPermissionGroupInfo( 71 eq(android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP), 72 anyInt())) 73 .thenReturn(permissionGroupInfo); 74 75 PackageInfo modulePackageInfo = new PackageInfo(); 76 modulePackageInfo.packageName = modulePackageName; 77 78 // See implementation for HealthConnectManager.isValidHealthPermission() 79 List<String> allPermissions = getAllHealthPermissionsByReflection(); 80 PermissionInfo[] allModulePermissions = new PermissionInfo[allPermissions.size()]; 81 int index = 0; 82 for (String perm : allPermissions) { 83 // Use deprecated constructor as we are simulating construction by the system 84 PermissionInfo permissionInfo = new PermissionInfo(); 85 permissionInfo.packageName = modulePackageName; 86 permissionInfo.name = perm; 87 permissionInfo.group = android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP; 88 89 when(mockPackageManager.getPermissionInfo(eq(perm), anyInt())) 90 .thenReturn(permissionInfo); 91 allModulePermissions[index++] = permissionInfo; 92 } 93 modulePackageInfo.permissions = allModulePermissions; 94 95 when(mockPackageManager.getPackageInfo(eq(modulePackageName), any())) 96 .thenReturn(modulePackageInfo); 97 // This is necessary in case the cache was earlier initialized when this was not mocked. 98 HealthConnectManager.resetHealthPermissionsCache(); 99 } 100 getAllHealthPermissionsByReflection()101 private static List<String> getAllHealthPermissionsByReflection() { 102 ArrayList<String> result = new ArrayList<>(); 103 for (Field field : HealthPermissions.class.getFields()) { 104 int modifiers = field.getModifiers(); 105 if (Modifier.isPublic(modifiers) 106 && Modifier.isStatic(modifiers) 107 // We don't check for final here, because under robolectric they aren't final 108 && field.getType() == String.class) { 109 try { 110 String value = (String) field.get(null); 111 if (value.startsWith("android.permission.")) { 112 result.add(value); 113 } 114 } catch (IllegalAccessException e) { 115 // skip any errors 116 } 117 } 118 } 119 return result; 120 } 121 } 122