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 
125     /**
126      * Log a location enabled state change event.
127      */
logLocationEnabledStateChanged(boolean enabled)128     public synchronized void logLocationEnabledStateChanged(boolean enabled) {
129         FrameworkStatsLog.write(FrameworkStatsLog.LOCATION_ENABLED_STATE_CHANGED, enabled);
130     }
131 
132     /**
133      * Log emergency location state change event
134      */
logEmergencyStateChanged(boolean isInEmergency)135     public synchronized void logEmergencyStateChanged(boolean isInEmergency) {
136         FrameworkStatsLog.write(FrameworkStatsLog.EMERGENCY_STATE_CHANGED, isInEmergency);
137     }
138 
bucketizeProvider(String provider)139     private static int bucketizeProvider(String provider) {
140         if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
141             return LocationStatsEnums.PROVIDER_NETWORK;
142         } else if (LocationManager.GPS_PROVIDER.equals(provider)) {
143             return LocationStatsEnums.PROVIDER_GPS;
144         } else if (LocationManager.PASSIVE_PROVIDER.equals(provider)) {
145             return LocationStatsEnums.PROVIDER_PASSIVE;
146         } else if (LocationManager.FUSED_PROVIDER.equals(provider)) {
147             return LocationStatsEnums.PROVIDER_FUSED;
148         } else {
149             return LocationStatsEnums.PROVIDER_UNKNOWN;
150         }
151     }
152 
bucketizeInterval(long interval)153     private static int bucketizeInterval(long interval) {
154         if (interval < ONE_SEC_IN_MILLIS) {
155             return LocationStatsEnums.INTERVAL_BETWEEN_0_SEC_AND_1_SEC;
156         } else if (interval < ONE_SEC_IN_MILLIS * 5) {
157             return LocationStatsEnums.INTERVAL_BETWEEN_1_SEC_AND_5_SEC;
158         } else if (interval < ONE_MINUTE_IN_MILLIS) {
159             return LocationStatsEnums.INTERVAL_BETWEEN_5_SEC_AND_1_MIN;
160         } else if (interval < ONE_MINUTE_IN_MILLIS * 10) {
161             return LocationStatsEnums.INTERVAL_BETWEEN_1_MIN_AND_10_MIN;
162         } else if (interval < ONE_HOUR_IN_MILLIS) {
163             return LocationStatsEnums.INTERVAL_BETWEEN_10_MIN_AND_1_HOUR;
164         } else {
165             return LocationStatsEnums.INTERVAL_LARGER_THAN_1_HOUR;
166         }
167     }
168 
bucketizeDistance(float smallestDisplacement)169     private static int bucketizeDistance(float smallestDisplacement) {
170         if (smallestDisplacement <= 0) {
171             return LocationStatsEnums.DISTANCE_ZERO;
172         } else if (smallestDisplacement > 0 && smallestDisplacement <= 100) {
173             return LocationStatsEnums.DISTANCE_BETWEEN_0_AND_100;
174         } else {
175             return LocationStatsEnums.DISTANCE_LARGER_THAN_100;
176         }
177     }
178 
bucketizeRadius(float radius)179     private static int bucketizeRadius(float radius) {
180         if (radius < 0) {
181             return LocationStatsEnums.RADIUS_NEGATIVE;
182         } else if (radius < 100) {
183             return LocationStatsEnums.RADIUS_BETWEEN_0_AND_100;
184         } else if (radius < 200) {
185             return LocationStatsEnums.RADIUS_BETWEEN_100_AND_200;
186         } else if (radius < 300) {
187             return LocationStatsEnums.RADIUS_BETWEEN_200_AND_300;
188         } else if (radius < 1000) {
189             return LocationStatsEnums.RADIUS_BETWEEN_300_AND_1000;
190         } else if (radius < 10000) {
191             return LocationStatsEnums.RADIUS_BETWEEN_1000_AND_10000;
192         } else {
193             return LocationStatsEnums.RADIUS_LARGER_THAN_100000;
194         }
195     }
196 
bucketizeExpireIn(long expireIn)197     private static int bucketizeExpireIn(long expireIn) {
198         if (expireIn == Long.MAX_VALUE) {
199             return LocationStatsEnums.EXPIRATION_NO_EXPIRY;
200         }
201 
202         if (expireIn < 20 * ONE_SEC_IN_MILLIS) {
203             return LocationStatsEnums.EXPIRATION_BETWEEN_0_AND_20_SEC;
204         } else if (expireIn < ONE_MINUTE_IN_MILLIS) {
205             return LocationStatsEnums.EXPIRATION_BETWEEN_20_SEC_AND_1_MIN;
206         } else if (expireIn < ONE_MINUTE_IN_MILLIS * 10) {
207             return LocationStatsEnums.EXPIRATION_BETWEEN_1_MIN_AND_10_MIN;
208         } else if (expireIn < ONE_HOUR_IN_MILLIS) {
209             return LocationStatsEnums.EXPIRATION_BETWEEN_10_MIN_AND_1_HOUR;
210         } else {
211             return LocationStatsEnums.EXPIRATION_LARGER_THAN_1_HOUR;
212         }
213     }
214 
categorizeActivityImportance(boolean foreground)215     private static int categorizeActivityImportance(boolean foreground) {
216         if (foreground) {
217             return LocationStatsEnums.IMPORTANCE_TOP;
218         } else {
219             return LocationStatsEnums.IMPORTANCE_BACKGROUND;
220         }
221     }
222 
getCallbackType( int apiType, boolean hasListener, boolean hasIntent)223     private static int getCallbackType(
224             int apiType, boolean hasListener, boolean hasIntent) {
225         if (apiType == LocationStatsEnums.API_SEND_EXTRA_COMMAND) {
226             return LocationStatsEnums.CALLBACK_NOT_APPLICABLE;
227         }
228 
229         // Listener and PendingIntent will not be set at
230         // the same time.
231         if (hasIntent) {
232             return LocationStatsEnums.CALLBACK_PENDING_INTENT;
233         } else if (hasListener) {
234             return LocationStatsEnums.CALLBACK_LISTENER;
235         } else {
236             return LocationStatsEnums.CALLBACK_UNKNOWN;
237         }
238     }
239 
hitApiUsageLogCap()240     private synchronized boolean hitApiUsageLogCap() {
241         long currentHour = Instant.now().toEpochMilli() / ONE_HOUR_IN_MILLIS;
242         if (currentHour > mLastApiUsageLogHour) {
243             mLastApiUsageLogHour = currentHour;
244             mApiUsageLogHourlyCount = 0;
245             return false;
246         } else {
247             mApiUsageLogHourlyCount = Math.min(
248                     mApiUsageLogHourlyCount + 1, API_USAGE_LOG_HOURLY_CAP);
249             return mApiUsageLogHourlyCount >= API_USAGE_LOG_HOURLY_CAP;
250         }
251     }
252 }
253