blob: a8a3cc47cfb0ea1149b555d72d810d696310c805 [file] [log] [blame]
Hongyi Zhang700137e2019-05-23 21:19:36 -07001/*
2 * Copyright (C) 2019 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
17package com.android.server;
18
19import android.app.ActivityManager;
Hongyi Zhang0e9ea752019-06-09 22:49:25 -070020import android.location.Geofence;
Hongyi Zhang700137e2019-05-23 21:19:36 -070021import android.location.LocationManager;
22import android.location.LocationRequest;
23import android.os.SystemClock;
24import android.stats.location.LocationStatsEnums;
25import android.util.Log;
26import android.util.StatsLog;
27
28import java.time.Instant;
29
30/**
31 * Logger for Location API usage logging.
32 */
Sasha Kuznetsovb9f26b42019-10-03 17:30:46 -070033public class LocationUsageLogger {
Hongyi Zhang700137e2019-05-23 21:19:36 -070034 private static final String TAG = "LocationUsageLogger";
35 private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
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 long mLastApiUsageLogHour = 0;
42
43 private int mApiUsageLogHourlyCount = 0;
44
45 private static final int API_USAGE_LOG_HOURLY_CAP = 60;
46
47 private static int providerNameToStatsdEnum(String provider) {
48 if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
49 return LocationStatsEnums.PROVIDER_NETWORK;
50 } else if (LocationManager.GPS_PROVIDER.equals(provider)) {
51 return LocationStatsEnums.PROVIDER_GPS;
52 } else if (LocationManager.PASSIVE_PROVIDER.equals(provider)) {
53 return LocationStatsEnums.PROVIDER_PASSIVE;
54 } else if (LocationManager.FUSED_PROVIDER.equals(provider)) {
55 return LocationStatsEnums.PROVIDER_FUSED;
56 } else {
57 return LocationStatsEnums.PROVIDER_UNKNOWN;
58 }
59 }
60
61 private static int bucketizeIntervalToStatsdEnum(long interval) {
62 // LocationManager already converts negative values to 0.
63 if (interval < ONE_SEC_IN_MILLIS) {
64 return LocationStatsEnums.INTERVAL_BETWEEN_0_SEC_AND_1_SEC;
65 } else if (interval < ONE_SEC_IN_MILLIS * 5) {
66 return LocationStatsEnums.INTERVAL_BETWEEN_1_SEC_AND_5_SEC;
67 } else if (interval < ONE_MINUTE_IN_MILLIS) {
68 return LocationStatsEnums.INTERVAL_BETWEEN_5_SEC_AND_1_MIN;
69 } else if (interval < ONE_MINUTE_IN_MILLIS * 10) {
70 return LocationStatsEnums.INTERVAL_BETWEEN_1_MIN_AND_10_MIN;
71 } else if (interval < ONE_HOUR_IN_MILLIS) {
72 return LocationStatsEnums.INTERVAL_BETWEEN_10_MIN_AND_1_HOUR;
73 } else {
74 return LocationStatsEnums.INTERVAL_LARGER_THAN_1_HOUR;
75 }
76 }
77
78 private static int bucketizeSmallestDisplacementToStatsdEnum(float smallestDisplacement) {
79 // LocationManager already converts negative values to 0.
80 if (smallestDisplacement == 0) {
81 return LocationStatsEnums.DISTANCE_ZERO;
82 } else if (smallestDisplacement > 0 && smallestDisplacement <= 100) {
83 return LocationStatsEnums.DISTANCE_BETWEEN_0_AND_100;
84 } else {
85 return LocationStatsEnums.DISTANCE_LARGER_THAN_100;
86 }
87 }
88
89 private static int bucketizeRadiusToStatsdEnum(float radius) {
90 if (radius < 0) {
91 return LocationStatsEnums.RADIUS_NEGATIVE;
92 } else if (radius < 100) {
93 return LocationStatsEnums.RADIUS_BETWEEN_0_AND_100;
94 } else if (radius < 200) {
95 return LocationStatsEnums.RADIUS_BETWEEN_100_AND_200;
96 } else if (radius < 300) {
97 return LocationStatsEnums.RADIUS_BETWEEN_200_AND_300;
98 } else if (radius < 1000) {
99 return LocationStatsEnums.RADIUS_BETWEEN_300_AND_1000;
100 } else if (radius < 10000) {
101 return LocationStatsEnums.RADIUS_BETWEEN_1000_AND_10000;
102 } else {
103 return LocationStatsEnums.RADIUS_LARGER_THAN_100000;
104 }
105 }
106
107 private static int getBucketizedExpireIn(long expireAt) {
108 if (expireAt == Long.MAX_VALUE) {
109 return LocationStatsEnums.EXPIRATION_NO_EXPIRY;
110 }
111
112 long elapsedRealtime = SystemClock.elapsedRealtime();
113 long expireIn = Math.max(0, expireAt - elapsedRealtime);
114
115 if (expireIn < 20 * ONE_SEC_IN_MILLIS) {
116 return LocationStatsEnums.EXPIRATION_BETWEEN_0_AND_20_SEC;
117 } else if (expireIn < ONE_MINUTE_IN_MILLIS) {
118 return LocationStatsEnums.EXPIRATION_BETWEEN_20_SEC_AND_1_MIN;
119 } else if (expireIn < ONE_MINUTE_IN_MILLIS * 10) {
120 return LocationStatsEnums.EXPIRATION_BETWEEN_1_MIN_AND_10_MIN;
121 } else if (expireIn < ONE_HOUR_IN_MILLIS) {
122 return LocationStatsEnums.EXPIRATION_BETWEEN_10_MIN_AND_1_HOUR;
123 } else {
124 return LocationStatsEnums.EXPIRATION_LARGER_THAN_1_HOUR;
125 }
126 }
127
128 private static int categorizeActivityImportance(int importance) {
129 if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
130 return LocationStatsEnums.IMPORTANCE_TOP;
131 } else if (importance == ActivityManager
132 .RunningAppProcessInfo
133 .IMPORTANCE_FOREGROUND_SERVICE) {
134 return LocationStatsEnums.IMPORTANCE_FORGROUND_SERVICE;
135 } else {
136 return LocationStatsEnums.IMPORTANCE_BACKGROUND;
137 }
138 }
139
140 private static int getCallbackType(
141 int apiType, boolean hasListener, boolean hasIntent) {
142 if (apiType == LocationStatsEnums.API_SEND_EXTRA_COMMAND) {
143 return LocationStatsEnums.CALLBACK_NOT_APPLICABLE;
144 }
145
146 // Listener and PendingIntent will not be set at
147 // the same time.
148 if (hasIntent) {
149 return LocationStatsEnums.CALLBACK_PENDING_INTENT;
150 } else if (hasListener) {
151 return LocationStatsEnums.CALLBACK_LISTENER;
152 } else {
153 return LocationStatsEnums.CALLBACK_UNKNOWN;
154 }
155 }
156
157 // Update the hourly count of APIUsage log event.
158 // Returns false if hit the hourly log cap.
159 private boolean checkApiUsageLogCap() {
160 if (D) {
161 Log.d(TAG, "checking APIUsage log cap.");
162 }
163
164 long currentHour = Instant.now().toEpochMilli() / ONE_HOUR_IN_MILLIS;
165 if (currentHour > mLastApiUsageLogHour) {
166 mLastApiUsageLogHour = currentHour;
167 mApiUsageLogHourlyCount = 0;
168 return true;
169 } else {
170 mApiUsageLogHourlyCount = Math.min(
171 mApiUsageLogHourlyCount + 1, API_USAGE_LOG_HOURLY_CAP);
172 return mApiUsageLogHourlyCount < API_USAGE_LOG_HOURLY_CAP;
173 }
174 }
175
176 /**
177 * Log a Location API usage event to Statsd.
178 * Logging event is capped at 60 per hour. Usage events exceeding
179 * the cap will be dropped by LocationUsageLogger.
180 */
181 public void logLocationApiUsage(int usageType, int apiInUse,
182 String packageName, LocationRequest locationRequest,
183 boolean hasListener, boolean hasIntent,
Hongyi Zhang0e9ea752019-06-09 22:49:25 -0700184 Geofence geofence, int activityImportance) {
Hongyi Zhang700137e2019-05-23 21:19:36 -0700185 try {
186 if (!checkApiUsageLogCap()) {
187 return;
188 }
189
190 boolean isLocationRequestNull = locationRequest == null;
Hongyi Zhang0e9ea752019-06-09 22:49:25 -0700191 boolean isGeofenceNull = geofence == null;
Hongyi Zhang700137e2019-05-23 21:19:36 -0700192 if (D) {
193 Log.d(TAG, "log API Usage to statsd. usageType: " + usageType + ", apiInUse: "
194 + apiInUse + ", packageName: " + (packageName == null ? "" : packageName)
195 + ", locationRequest: "
196 + (isLocationRequestNull ? "" : locationRequest.toString())
197 + ", hasListener: " + hasListener
198 + ", hasIntent: " + hasIntent
Hongyi Zhang0e9ea752019-06-09 22:49:25 -0700199 + ", geofence: "
200 + (isGeofenceNull ? "" : geofence.toString())
Hongyi Zhang700137e2019-05-23 21:19:36 -0700201 + ", importance: " + activityImportance);
202 }
203
204 StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType,
205 apiInUse, packageName,
206 isLocationRequestNull
207 ? LocationStatsEnums.PROVIDER_UNKNOWN
208 : providerNameToStatsdEnum(locationRequest.getProvider()),
209 isLocationRequestNull
210 ? LocationStatsEnums.QUALITY_UNKNOWN
211 : locationRequest.getQuality(),
212 isLocationRequestNull
213 ? LocationStatsEnums.INTERVAL_UNKNOWN
214 : bucketizeIntervalToStatsdEnum(locationRequest.getInterval()),
215 isLocationRequestNull
216 ? LocationStatsEnums.DISTANCE_UNKNOWN
217 : bucketizeSmallestDisplacementToStatsdEnum(
218 locationRequest.getSmallestDisplacement()),
219 isLocationRequestNull ? 0 : locationRequest.getNumUpdates(),
220 // only log expireIn for USAGE_STARTED
221 isLocationRequestNull || usageType == LocationStatsEnums.USAGE_ENDED
222 ? LocationStatsEnums.EXPIRATION_UNKNOWN
223 : getBucketizedExpireIn(locationRequest.getExpireAt()),
224 getCallbackType(apiInUse, hasListener, hasIntent),
Hongyi Zhang0e9ea752019-06-09 22:49:25 -0700225 isGeofenceNull
226 ? LocationStatsEnums.RADIUS_UNKNOWN
227 : bucketizeRadiusToStatsdEnum(geofence.getRadius()),
Hongyi Zhang700137e2019-05-23 21:19:36 -0700228 categorizeActivityImportance(activityImportance));
229 } catch (Exception e) {
230 // Swallow exceptions to avoid crashing LMS.
231 Log.w(TAG, "Failed to log API usage to statsd.", e);
232 }
233 }
234
235 /**
236 * Log a Location API usage event to Statsd.
237 * Logging event is capped at 60 per hour. Usage events exceeding
238 * the cap will be dropped by LocationUsageLogger.
239 */
240 public void logLocationApiUsage(int usageType, int apiInUse, String providerName) {
241 try {
242 if (!checkApiUsageLogCap()) {
243 return;
244 }
245
246 if (D) {
247 Log.d(TAG, "log API Usage to statsd. usageType: " + usageType + ", apiInUse: "
248 + apiInUse + ", providerName: " + providerName);
249 }
250
251 StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType, apiInUse,
252 /* package_name= */ null,
253 providerNameToStatsdEnum(providerName),
254 LocationStatsEnums.QUALITY_UNKNOWN,
255 LocationStatsEnums.INTERVAL_UNKNOWN,
256 LocationStatsEnums.DISTANCE_UNKNOWN,
257 /* numUpdates= */ 0,
258 LocationStatsEnums.EXPIRATION_UNKNOWN,
259 getCallbackType(
260 apiInUse,
261 /* isListenerNull= */ true,
262 /* isIntentNull= */ true),
263 /* bucketizedRadius= */ 0,
264 LocationStatsEnums.IMPORTANCE_UNKNOWN);
265 } catch (Exception e) {
266 // Swallow exceptions to avoid crashing LMS.
267 Log.w(TAG, "Failed to log API usage to statsd.", e);
268 }
269 }
270}