blob: b7934d978bac4b8d46ed8bd8a7885f794635a676 [file] [log] [blame]
David Christie2ff96af2014-01-30 16:09:37 -08001package com.android.server.location;
2
3import android.os.SystemClock;
4import android.util.Log;
5
6import java.util.HashMap;
7
8/**
9 * Holds statistics for location requests (active requests by provider).
10 *
11 * <p>Must be externally synchronized.
12 */
13public class LocationRequestStatistics {
14 private static final String TAG = "LocationStats";
15
David Christie00dc0562014-05-14 14:28:54 -070016 // Maps package name and provider to location request statistics.
David Christie2ff96af2014-01-30 16:09:37 -080017 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 */
Wyatt Rileyf7075e02018-04-12 17:54:26 -070027 public void startRequesting(String packageName, String providerName, long intervalMs,
28 boolean isForeground) {
David Christie2ff96af2014-01-30 16:09:37 -080029 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);
Wyatt Rileyf7075e02018-04-12 17:54:26 -070036 stats.updateForeground(isForeground);
David Christie2ff96af2014-01-30 16:09:37 -080037 }
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 */
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();
Wyatt Rileyf7075e02018-04-12 17:54:26 -070050 }
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 */
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);
David Christie2ff96af2014-01-30 16:09:37 -080064 }
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
80 public PackageProviderKey(String packageName, String providerName) {
81 this.packageName = packageName;
82 this.providerName = providerName;
83 }
84
85 @Override
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
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
Wyatt Rileyf7075e02018-04-12 17:54:26 -0700119 // 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
David Christie2ff96af2014-01-30 16:09:37 -0800126 private PackageStatistics() {
127 mInitialElapsedTimeMs = SystemClock.elapsedRealtime();
128 mNumActiveRequests = 0;
129 mTotalDurationMs = 0;
130 mFastestIntervalMs = Long.MAX_VALUE;
131 mSlowestIntervalMs = 0;
Wyatt Rileyf7075e02018-04-12 17:54:26 -0700132 mForegroundDurationMs = 0;
133 mLastForegroundElapsedTimeMs = 0;
David Christie2ff96af2014-01-30 16:09:37 -0800134 }
135
136 private void startRequesting(long intervalMs) {
137 if (mNumActiveRequests == 0) {
138 mLastActivitationElapsedTimeMs = SystemClock.elapsedRealtime();
139 }
140
141 if (intervalMs < mFastestIntervalMs) {
142 mFastestIntervalMs = intervalMs;
143 }
144
145 if (intervalMs > mSlowestIntervalMs) {
146 mSlowestIntervalMs = intervalMs;
147 }
148
149 mNumActiveRequests++;
150 }
151
Wyatt Rileyf7075e02018-04-12 17:54:26 -0700152 private void updateForeground(boolean isForeground) {
153 long nowElapsedTimeMs = SystemClock.elapsedRealtime();
154 // if previous interval was foreground, accumulate before resetting start
155 if (mLastForegroundElapsedTimeMs != 0) {
156 mForegroundDurationMs += (nowElapsedTimeMs - mLastForegroundElapsedTimeMs);
157 }
158 mLastForegroundElapsedTimeMs = isForeground ? nowElapsedTimeMs : 0;
159 }
160
David Christie2ff96af2014-01-30 16:09:37 -0800161 private void stopRequesting() {
162 if (mNumActiveRequests <= 0) {
163 // Shouldn't be a possible code path
164 Log.e(TAG, "Reference counting corrupted in usage statistics.");
165 return;
166 }
167
168 mNumActiveRequests--;
169 if (mNumActiveRequests == 0) {
170 long lastDurationMs
171 = SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
172 mTotalDurationMs += lastDurationMs;
Wyatt Rileyf7075e02018-04-12 17:54:26 -0700173 updateForeground(false);
David Christie2ff96af2014-01-30 16:09:37 -0800174 }
175 }
176
177 /**
178 * Returns the duration that this request has been active.
179 */
180 public long getDurationMs() {
181 long currentDurationMs = mTotalDurationMs;
182 if (mNumActiveRequests > 0) {
183 currentDurationMs
184 += SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
185 }
186 return currentDurationMs;
187 }
188
189 /**
Wyatt Rileyf7075e02018-04-12 17:54:26 -0700190 * Returns the duration that this request has been active.
191 */
192 public long getForegroundDurationMs() {
193 long currentDurationMs = mForegroundDurationMs;
194 if (mLastForegroundElapsedTimeMs != 0 ) {
195 currentDurationMs
196 += SystemClock.elapsedRealtime() - mLastForegroundElapsedTimeMs;
197 }
198 return currentDurationMs;
199 }
200
201 /**
David Christie2ff96af2014-01-30 16:09:37 -0800202 * Returns the time since the initial request in ms.
203 */
204 public long getTimeSinceFirstRequestMs() {
205 return SystemClock.elapsedRealtime() - mInitialElapsedTimeMs;
206 }
207
208 /**
209 * Returns the fastest interval that has been tracked.
210 */
211 public long getFastestIntervalMs() {
212 return mFastestIntervalMs;
213 }
214
215 /**
216 * Returns the slowest interval that has been tracked.
217 */
218 public long getSlowestIntervalMs() {
219 return mSlowestIntervalMs;
220 }
221
222 /**
223 * Returns true if a request is active for these tracked statistics.
224 */
225 public boolean isActive() {
226 return mNumActiveRequests > 0;
227 }
228
229 @Override
230 public String toString() {
231 StringBuilder s = new StringBuilder();
232 if (mFastestIntervalMs == mSlowestIntervalMs) {
233 s.append("Interval ").append(mFastestIntervalMs / 1000).append(" seconds");
234 } else {
235 s.append("Min interval ").append(mFastestIntervalMs / 1000).append(" seconds");
236 s.append(": Max interval ").append(mSlowestIntervalMs / 1000).append(" seconds");
237 }
238 s.append(": Duration requested ")
239 .append((getDurationMs() / 1000) / 60)
Wyatt Rileyf7075e02018-04-12 17:54:26 -0700240 .append(" total, ")
241 .append((getForegroundDurationMs() / 1000) / 60)
242 .append(" foreground, out of the last ")
David Christie2ff96af2014-01-30 16:09:37 -0800243 .append((getTimeSinceFirstRequestMs() / 1000) / 60)
244 .append(" minutes");
245 if (isActive()) {
246 s.append(": Currently active");
247 }
248 return s.toString();
249 }
250 }
251}