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