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