• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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