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.healthconnect.logging;
18 
19 import static android.content.pm.PackageManager.GET_PERMISSIONS;
20 
21 import android.annotation.NonNull;
22 import android.content.Context;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.health.connect.HealthConnectManager;
26 import android.os.UserHandle;
27 
28 import com.android.server.healthconnect.permission.PackageInfoUtils;
29 import com.android.server.healthconnect.storage.datatypehelpers.AccessLogsHelper;
30 import com.android.server.healthconnect.storage.datatypehelpers.PreferenceHelper;
31 
32 import java.time.Instant;
33 import java.time.temporal.ChronoUnit;
34 import java.util.List;
35 import java.util.Objects;
36 
37 /**
38  * Collects Health Connect usage stats.
39  *
40  * @hide
41  */
42 final class UsageStatsCollector {
43     private static final String USER_MOST_RECENT_ACCESS_LOG_TIME =
44             "USER_MOST_RECENT_ACCESS_LOG_TIME";
45     private static final int NUMBER_OF_DAYS_FOR_USER_TO_BE_MONTHLY_ACTIVE = 30;
46     private final Context mContext;
47     private final List<PackageInfo> mAllPackagesInstalledForUser;
48 
UsageStatsCollector(@onNull Context context, @NonNull UserHandle userHandle)49     UsageStatsCollector(@NonNull Context context, @NonNull UserHandle userHandle) {
50         Objects.requireNonNull(userHandle);
51         Objects.requireNonNull(context);
52 
53         mContext = context;
54         mAllPackagesInstalledForUser =
55                 context.createContextAsUser(userHandle, /* flag= */ 0)
56                         .getPackageManager()
57                         .getInstalledPackages(PackageManager.PackageInfoFlags.of(GET_PERMISSIONS));
58     }
59 
60     /**
61      * Returns the number of apps that can be connected to Health Connect.
62      *
63      * <p>The apps not necessarily have permissions to read/write data. It just mentions permission
64      * in the manifest i.e. if not connected yet, it can be connected to Health Connect.
65      *
66      * @return Number of apps that can be connected (not necessarily connected) to Health Connect
67      */
getNumberOfAppsCompatibleWithHealthConnect()68     int getNumberOfAppsCompatibleWithHealthConnect() {
69         int numberOfAppsGrantedHealthPermissions = 0;
70         for (PackageInfo info : mAllPackagesInstalledForUser) {
71             if (hasRequestedHealthPermission(info)) {
72                 numberOfAppsGrantedHealthPermissions++;
73             }
74         }
75         return numberOfAppsGrantedHealthPermissions;
76     }
77 
78     /**
79      * Returns the number of apps that are connected to Health Connect.
80      *
81      * @return Number of apps that are connected (have read/write) to Health Connect
82      */
getPackagesHoldingHealthPermissions()83     int getPackagesHoldingHealthPermissions() {
84         // TODO(b/260707328): replace with getPackagesHoldingPermissions
85         int count = 0;
86 
87         for (PackageInfo info : mAllPackagesInstalledForUser) {
88             if (PackageInfoUtils.anyRequestedHealthPermissionGranted(mContext, info)) {
89                 count++;
90             }
91         }
92         return count;
93     }
94 
isUserMonthlyActive()95     boolean isUserMonthlyActive() {
96         PreferenceHelper preferenceHelper = PreferenceHelper.getInstance();
97 
98         String latestAccessLogTimeStampString =
99                 preferenceHelper.getPreference(USER_MOST_RECENT_ACCESS_LOG_TIME);
100 
101         // Return false if preference is empty and make sure latest access was within past
102         // 30 days.
103         return latestAccessLogTimeStampString != null
104                 && Instant.now()
105                                 .minus(
106                                         NUMBER_OF_DAYS_FOR_USER_TO_BE_MONTHLY_ACTIVE,
107                                         ChronoUnit.DAYS)
108                                 .toEpochMilli()
109                         <= Long.parseLong(latestAccessLogTimeStampString);
110     }
111 
upsertLastAccessLogTimeStamp()112     void upsertLastAccessLogTimeStamp() {
113         PreferenceHelper preferenceHelper = PreferenceHelper.getInstance();
114 
115         long latestAccessLogTimeStamp = AccessLogsHelper.getLatestAccessLogTimeStamp();
116 
117         // Access logs are only stored for 7 days, therefore only update this value if there is an
118         // access log. Last access timestamp can be before 7 days and might already exist in
119         // preference and in that case we should not overwrite the existing value.
120         if (latestAccessLogTimeStamp != Long.MIN_VALUE) {
121             preferenceHelper.insertOrReplacePreference(
122                     USER_MOST_RECENT_ACCESS_LOG_TIME, String.valueOf(latestAccessLogTimeStamp));
123         }
124     }
125 
hasRequestedHealthPermission(@onNull PackageInfo packageInfo)126     private boolean hasRequestedHealthPermission(@NonNull PackageInfo packageInfo) {
127         if (packageInfo == null || packageInfo.requestedPermissions == null) {
128             return false;
129         }
130 
131         for (int i = 0; i < packageInfo.requestedPermissions.length; i++) {
132             if (HealthConnectManager.isHealthPermission(
133                     mContext, packageInfo.requestedPermissions[i])) {
134                 return true;
135             }
136         }
137         return false;
138     }
139 }
140