1 /* 2 * Copyright (C) 2020 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.location.injector; 18 19 import static com.android.server.location.LocationManagerService.TAG; 20 21 import android.location.Geofence; 22 import android.location.LocationManager; 23 import android.location.LocationRequest; 24 import android.stats.location.LocationStatsEnums; 25 import android.util.Log; 26 27 import com.android.internal.annotations.GuardedBy; 28 import com.android.internal.util.FrameworkStatsLog; 29 30 import java.time.Instant; 31 32 /** 33 * Logger for Location API usage logging. 34 */ 35 public class LocationUsageLogger { 36 37 private static final int ONE_SEC_IN_MILLIS = 1000; 38 private static final int ONE_MINUTE_IN_MILLIS = 60000; 39 private static final int ONE_HOUR_IN_MILLIS = 3600000; 40 41 private static final int API_USAGE_LOG_HOURLY_CAP = 60; 42 43 @GuardedBy("this") 44 private long mLastApiUsageLogHour = 0; 45 @GuardedBy("this") 46 private int mApiUsageLogHourlyCount = 0; 47 48 /** 49 * Log a location API usage event. 50 */ logLocationApiUsage(int usageType, int apiInUse, String packageName, String attributionTag, String provider, LocationRequest locationRequest, boolean hasListener, boolean hasIntent, Geofence geofence, boolean foreground)51 public void logLocationApiUsage(int usageType, int apiInUse, 52 String packageName, String attributionTag, String provider, 53 LocationRequest locationRequest, boolean hasListener, 54 boolean hasIntent, Geofence geofence, boolean foreground) { 55 try { 56 if (hitApiUsageLogCap()) { 57 return; 58 } 59 60 boolean isLocationRequestNull = locationRequest == null; 61 boolean isGeofenceNull = geofence == null; 62 63 FrameworkStatsLog.write(FrameworkStatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, 64 usageType, apiInUse, packageName, 65 isLocationRequestNull 66 ? LocationStatsEnums.PROVIDER_UNKNOWN 67 : bucketizeProvider(provider), 68 isLocationRequestNull 69 ? LocationStatsEnums.QUALITY_UNKNOWN 70 : locationRequest.getQuality(), 71 isLocationRequestNull 72 ? LocationStatsEnums.INTERVAL_UNKNOWN 73 : bucketizeInterval(locationRequest.getIntervalMillis()), 74 isLocationRequestNull 75 ? LocationStatsEnums.DISTANCE_UNKNOWN 76 : bucketizeDistance( 77 locationRequest.getMinUpdateDistanceMeters()), 78 isLocationRequestNull ? 0 : locationRequest.getMaxUpdates(), 79 // only log expireIn for USAGE_STARTED 80 isLocationRequestNull || usageType == LocationStatsEnums.USAGE_ENDED 81 ? LocationStatsEnums.EXPIRATION_UNKNOWN 82 : bucketizeExpireIn(locationRequest.getDurationMillis()), 83 getCallbackType(apiInUse, hasListener, hasIntent), 84 isGeofenceNull 85 ? LocationStatsEnums.RADIUS_UNKNOWN 86 : bucketizeRadius(geofence.getRadius()), 87 categorizeActivityImportance(foreground), 88 attributionTag); 89 } catch (Exception e) { 90 // Swallow exceptions to avoid crashing LMS. 91 Log.w(TAG, "Failed to log API usage to statsd.", e); 92 } 93 } 94 95 /** 96 * Log a location API usage event. 97 */ logLocationApiUsage(int usageType, int apiInUse, String providerName)98 public void logLocationApiUsage(int usageType, int apiInUse, String providerName) { 99 try { 100 if (hitApiUsageLogCap()) { 101 return; 102 } 103 104 FrameworkStatsLog.write(FrameworkStatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, 105 usageType, apiInUse, 106 /* package_name= */ null, 107 bucketizeProvider(providerName), 108 LocationStatsEnums.QUALITY_UNKNOWN, 109 LocationStatsEnums.INTERVAL_UNKNOWN, 110 LocationStatsEnums.DISTANCE_UNKNOWN, 111 /* numUpdates= */ 0, 112 LocationStatsEnums.EXPIRATION_UNKNOWN, 113 getCallbackType( 114 apiInUse, 115 /* isListenerNull= */ true, 116 /* isIntentNull= */ true), 117 /* bucketizedRadius= */ 0, 118 LocationStatsEnums.IMPORTANCE_UNKNOWN, 119 /* attribution_tag */ null); 120 } catch (Exception e) { 121 Log.w(TAG, "Failed to log API usage to statsd.", e); 122 } 123 } 124 bucketizeProvider(String provider)125 private static int bucketizeProvider(String provider) { 126 if (LocationManager.NETWORK_PROVIDER.equals(provider)) { 127 return LocationStatsEnums.PROVIDER_NETWORK; 128 } else if (LocationManager.GPS_PROVIDER.equals(provider)) { 129 return LocationStatsEnums.PROVIDER_GPS; 130 } else if (LocationManager.PASSIVE_PROVIDER.equals(provider)) { 131 return LocationStatsEnums.PROVIDER_PASSIVE; 132 } else if (LocationManager.FUSED_PROVIDER.equals(provider)) { 133 return LocationStatsEnums.PROVIDER_FUSED; 134 } else { 135 return LocationStatsEnums.PROVIDER_UNKNOWN; 136 } 137 } 138 bucketizeInterval(long interval)139 private static int bucketizeInterval(long interval) { 140 if (interval < ONE_SEC_IN_MILLIS) { 141 return LocationStatsEnums.INTERVAL_BETWEEN_0_SEC_AND_1_SEC; 142 } else if (interval < ONE_SEC_IN_MILLIS * 5) { 143 return LocationStatsEnums.INTERVAL_BETWEEN_1_SEC_AND_5_SEC; 144 } else if (interval < ONE_MINUTE_IN_MILLIS) { 145 return LocationStatsEnums.INTERVAL_BETWEEN_5_SEC_AND_1_MIN; 146 } else if (interval < ONE_MINUTE_IN_MILLIS * 10) { 147 return LocationStatsEnums.INTERVAL_BETWEEN_1_MIN_AND_10_MIN; 148 } else if (interval < ONE_HOUR_IN_MILLIS) { 149 return LocationStatsEnums.INTERVAL_BETWEEN_10_MIN_AND_1_HOUR; 150 } else { 151 return LocationStatsEnums.INTERVAL_LARGER_THAN_1_HOUR; 152 } 153 } 154 bucketizeDistance(float smallestDisplacement)155 private static int bucketizeDistance(float smallestDisplacement) { 156 if (smallestDisplacement <= 0) { 157 return LocationStatsEnums.DISTANCE_ZERO; 158 } else if (smallestDisplacement > 0 && smallestDisplacement <= 100) { 159 return LocationStatsEnums.DISTANCE_BETWEEN_0_AND_100; 160 } else { 161 return LocationStatsEnums.DISTANCE_LARGER_THAN_100; 162 } 163 } 164 bucketizeRadius(float radius)165 private static int bucketizeRadius(float radius) { 166 if (radius < 0) { 167 return LocationStatsEnums.RADIUS_NEGATIVE; 168 } else if (radius < 100) { 169 return LocationStatsEnums.RADIUS_BETWEEN_0_AND_100; 170 } else if (radius < 200) { 171 return LocationStatsEnums.RADIUS_BETWEEN_100_AND_200; 172 } else if (radius < 300) { 173 return LocationStatsEnums.RADIUS_BETWEEN_200_AND_300; 174 } else if (radius < 1000) { 175 return LocationStatsEnums.RADIUS_BETWEEN_300_AND_1000; 176 } else if (radius < 10000) { 177 return LocationStatsEnums.RADIUS_BETWEEN_1000_AND_10000; 178 } else { 179 return LocationStatsEnums.RADIUS_LARGER_THAN_100000; 180 } 181 } 182 bucketizeExpireIn(long expireIn)183 private static int bucketizeExpireIn(long expireIn) { 184 if (expireIn == Long.MAX_VALUE) { 185 return LocationStatsEnums.EXPIRATION_NO_EXPIRY; 186 } 187 188 if (expireIn < 20 * ONE_SEC_IN_MILLIS) { 189 return LocationStatsEnums.EXPIRATION_BETWEEN_0_AND_20_SEC; 190 } else if (expireIn < ONE_MINUTE_IN_MILLIS) { 191 return LocationStatsEnums.EXPIRATION_BETWEEN_20_SEC_AND_1_MIN; 192 } else if (expireIn < ONE_MINUTE_IN_MILLIS * 10) { 193 return LocationStatsEnums.EXPIRATION_BETWEEN_1_MIN_AND_10_MIN; 194 } else if (expireIn < ONE_HOUR_IN_MILLIS) { 195 return LocationStatsEnums.EXPIRATION_BETWEEN_10_MIN_AND_1_HOUR; 196 } else { 197 return LocationStatsEnums.EXPIRATION_LARGER_THAN_1_HOUR; 198 } 199 } 200 categorizeActivityImportance(boolean foreground)201 private static int categorizeActivityImportance(boolean foreground) { 202 if (foreground) { 203 return LocationStatsEnums.IMPORTANCE_TOP; 204 } else { 205 return LocationStatsEnums.IMPORTANCE_BACKGROUND; 206 } 207 } 208 getCallbackType( int apiType, boolean hasListener, boolean hasIntent)209 private static int getCallbackType( 210 int apiType, boolean hasListener, boolean hasIntent) { 211 if (apiType == LocationStatsEnums.API_SEND_EXTRA_COMMAND) { 212 return LocationStatsEnums.CALLBACK_NOT_APPLICABLE; 213 } 214 215 // Listener and PendingIntent will not be set at 216 // the same time. 217 if (hasIntent) { 218 return LocationStatsEnums.CALLBACK_PENDING_INTENT; 219 } else if (hasListener) { 220 return LocationStatsEnums.CALLBACK_LISTENER; 221 } else { 222 return LocationStatsEnums.CALLBACK_UNKNOWN; 223 } 224 } 225 hitApiUsageLogCap()226 private synchronized boolean hitApiUsageLogCap() { 227 long currentHour = Instant.now().toEpochMilli() / ONE_HOUR_IN_MILLIS; 228 if (currentHour > mLastApiUsageLogHour) { 229 mLastApiUsageLogHour = currentHour; 230 mApiUsageLogHourlyCount = 0; 231 return false; 232 } else { 233 mApiUsageLogHourlyCount = Math.min( 234 mApiUsageLogHourlyCount + 1, API_USAGE_LOG_HOURLY_CAP); 235 return mApiUsageLogHourlyCount >= API_USAGE_LOG_HOURLY_CAP; 236 } 237 } 238 } 239