1 package com.android.server.location;
2 
3 import android.os.SystemClock;
4 import android.util.Log;
5 
6 import java.util.HashMap;
7 
8 /**
9  * Holds statistics for location requests (active requests by provider).
10  *
11  * <p>Must be externally synchronized.
12  */
13 public class LocationRequestStatistics {
14     private static final String TAG = "LocationStats";
15 
16     // Maps package name and provider to location request statistics.
17     public final HashMap<PackageProviderKey, PackageStatistics> statistics
18             = new HashMap<PackageProviderKey, PackageStatistics>();
19 
20     /**
21      * Signals that a package has started requesting locations.
22      *
23      * @param packageName Name of package that has requested locations.
24      * @param providerName Name of provider that is requested (e.g. "gps").
25      * @param intervalMs The interval that is requested in ms.
26      */
startRequesting(String packageName, String providerName, long intervalMs, boolean isForeground)27     public void startRequesting(String packageName, String providerName, long intervalMs,
28             boolean isForeground) {
29         PackageProviderKey key = new PackageProviderKey(packageName, providerName);
30         PackageStatistics stats = statistics.get(key);
31         if (stats == null) {
32             stats = new PackageStatistics();
33             statistics.put(key, stats);
34         }
35         stats.startRequesting(intervalMs);
36         stats.updateForeground(isForeground);
37     }
38 
39     /**
40      * Signals that a package has stopped requesting locations.
41      *
42      * @param packageName Name of package that has stopped requesting locations.
43      * @param providerName Provider that is no longer being requested.
44      */
stopRequesting(String packageName, String providerName)45     public void stopRequesting(String packageName, String providerName) {
46         PackageProviderKey key = new PackageProviderKey(packageName, providerName);
47         PackageStatistics stats = statistics.get(key);
48         if (stats != null) {
49             stats.stopRequesting();
50         }
51     }
52 
53     /**
54      * Signals that a package possibly switched background/foreground.
55      *
56      * @param packageName Name of package that has stopped requesting locations.
57      * @param providerName Provider that is no longer being requested.
58      */
updateForeground(String packageName, String providerName, boolean isForeground)59     public void updateForeground(String packageName, String providerName, boolean isForeground) {
60         PackageProviderKey key = new PackageProviderKey(packageName, providerName);
61         PackageStatistics stats = statistics.get(key);
62         if (stats != null) {
63             stats.updateForeground(isForeground);
64         }
65     }
66 
67     /**
68      * A key that holds both package and provider names.
69      */
70     public static class PackageProviderKey {
71         /**
72          * Name of package requesting location.
73          */
74         public final String packageName;
75         /**
76          * Name of provider being requested (e.g. "gps").
77          */
78         public final String providerName;
79 
PackageProviderKey(String packageName, String providerName)80         public PackageProviderKey(String packageName, String providerName) {
81             this.packageName = packageName;
82             this.providerName = providerName;
83         }
84 
85         @Override
equals(Object other)86         public boolean equals(Object other) {
87             if (!(other instanceof PackageProviderKey)) {
88                 return false;
89             }
90 
91             PackageProviderKey otherKey = (PackageProviderKey) other;
92             return packageName.equals(otherKey.packageName)
93                     && providerName.equals(otherKey.providerName);
94         }
95 
96         @Override
hashCode()97         public int hashCode() {
98             return packageName.hashCode() + 31 * providerName.hashCode();
99         }
100     }
101 
102     /**
103      * Usage statistics for a package/provider pair.
104      */
105     public static class PackageStatistics {
106         // Time when this package first requested location.
107         private final long mInitialElapsedTimeMs;
108         // Number of active location requests this package currently has.
109         private int mNumActiveRequests;
110         // Time when this package most recently went from not requesting location to requesting.
111         private long mLastActivitationElapsedTimeMs;
112         // The fastest interval this package has ever requested.
113         private long mFastestIntervalMs;
114         // The slowest interval this package has ever requested.
115         private long mSlowestIntervalMs;
116         // The total time this app has requested location (not including currently running requests).
117         private long mTotalDurationMs;
118 
119         // Time when this package most recently went to foreground, requesting location. 0 means
120         // not currently in foreground.
121         private long mLastForegroundElapsedTimeMs;
122         // The time this app has requested location (not including currently running requests), while
123         // in foreground.
124         private long mForegroundDurationMs;
125 
126         // Time when package last went dormant (stopped requesting location)
127         private long mLastStopElapsedTimeMs;
128 
PackageStatistics()129         private PackageStatistics() {
130             mInitialElapsedTimeMs = SystemClock.elapsedRealtime();
131             mNumActiveRequests = 0;
132             mTotalDurationMs = 0;
133             mFastestIntervalMs = Long.MAX_VALUE;
134             mSlowestIntervalMs = 0;
135             mForegroundDurationMs = 0;
136             mLastForegroundElapsedTimeMs = 0;
137             mLastStopElapsedTimeMs = 0;
138         }
139 
startRequesting(long intervalMs)140         private void startRequesting(long intervalMs) {
141             if (mNumActiveRequests == 0) {
142                 mLastActivitationElapsedTimeMs = SystemClock.elapsedRealtime();
143             }
144 
145             if (intervalMs < mFastestIntervalMs) {
146                 mFastestIntervalMs = intervalMs;
147             }
148 
149             if (intervalMs > mSlowestIntervalMs) {
150                 mSlowestIntervalMs = intervalMs;
151             }
152 
153             mNumActiveRequests++;
154         }
155 
updateForeground(boolean isForeground)156         private void updateForeground(boolean isForeground) {
157             long nowElapsedTimeMs = SystemClock.elapsedRealtime();
158             // if previous interval was foreground, accumulate before resetting start
159             if (mLastForegroundElapsedTimeMs != 0) {
160                 mForegroundDurationMs += (nowElapsedTimeMs - mLastForegroundElapsedTimeMs);
161             }
162             mLastForegroundElapsedTimeMs = isForeground ? nowElapsedTimeMs : 0;
163         }
164 
stopRequesting()165         private void stopRequesting() {
166             if (mNumActiveRequests <= 0) {
167                 // Shouldn't be a possible code path
168                 Log.e(TAG, "Reference counting corrupted in usage statistics.");
169                 return;
170             }
171 
172             mNumActiveRequests--;
173             if (mNumActiveRequests == 0) {
174                 mLastStopElapsedTimeMs = SystemClock.elapsedRealtime();
175                 long lastDurationMs = mLastStopElapsedTimeMs - mLastActivitationElapsedTimeMs;
176                 mTotalDurationMs += lastDurationMs;
177                 updateForeground(false);
178             }
179         }
180 
181         /**
182          * Returns the duration that this request has been active.
183          */
getDurationMs()184         public long getDurationMs() {
185             long currentDurationMs = mTotalDurationMs;
186             if (mNumActiveRequests > 0) {
187                 currentDurationMs
188                         += SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
189             }
190             return currentDurationMs;
191         }
192 
193         /**
194          * Returns the duration that this request has been active.
195          */
getForegroundDurationMs()196         public long getForegroundDurationMs() {
197             long currentDurationMs = mForegroundDurationMs;
198             if (mLastForegroundElapsedTimeMs != 0 ) {
199                 currentDurationMs
200                         += SystemClock.elapsedRealtime() - mLastForegroundElapsedTimeMs;
201             }
202             return currentDurationMs;
203         }
204 
205         /**
206          * Returns the time since the initial request in ms.
207          */
getTimeSinceFirstRequestMs()208         public long getTimeSinceFirstRequestMs() {
209             return SystemClock.elapsedRealtime() - mInitialElapsedTimeMs;
210         }
211 
212         /**
213          * Returns the time since the last request stopped in ms.
214          */
getTimeSinceLastRequestStoppedMs()215         public long getTimeSinceLastRequestStoppedMs() {
216             return SystemClock.elapsedRealtime() - mLastStopElapsedTimeMs;
217         }
218 
219         /**
220          * Returns the fastest interval that has been tracked.
221          */
getFastestIntervalMs()222         public long getFastestIntervalMs() {
223             return mFastestIntervalMs;
224         }
225 
226         /**
227          * Returns the slowest interval that has been tracked.
228          */
getSlowestIntervalMs()229         public long getSlowestIntervalMs() {
230             return mSlowestIntervalMs;
231         }
232 
233         /**
234          * Returns true if a request is active for these tracked statistics.
235          */
isActive()236         public boolean isActive() {
237             return mNumActiveRequests > 0;
238         }
239 
240         @Override
toString()241         public String toString() {
242             StringBuilder s = new StringBuilder();
243             if (mFastestIntervalMs == mSlowestIntervalMs) {
244                 s.append("Interval ").append(mFastestIntervalMs / 1000).append(" seconds");
245             } else {
246                 s.append("Min interval ").append(mFastestIntervalMs / 1000).append(" seconds");
247                 s.append(": Max interval ").append(mSlowestIntervalMs / 1000).append(" seconds");
248             }
249             s.append(": Duration requested ")
250                     .append((getDurationMs() / 1000) / 60)
251                     .append(" total, ")
252                     .append((getForegroundDurationMs() / 1000) / 60)
253                     .append(" foreground, out of the last ")
254                     .append((getTimeSinceFirstRequestMs() / 1000) / 60)
255                     .append(" minutes");
256             if (isActive()) {
257                 s.append(": Currently active");
258             } else {
259                 s.append(": Last active ")
260                         .append((getTimeSinceLastRequestStoppedMs() / 1000) / 60)
261                         .append(" minutes ago");
262             }
263             return s.toString();
264         }
265     }
266 }
267