blob: 45c833498ac75df4fe66ced31a6ebc203dca356c [file] [log] [blame]
WyattRileyfa1ef8d32019-12-30 17:19:55 -08001/*
2 * Copyright 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
David Christie2ff96af2014-01-30 16:09:37 -080017package com.android.server.location;
18
19import android.os.SystemClock;
20import android.util.Log;
WyattRileyfa1ef8d32019-12-30 17:19:55 -080021import android.util.TimeUtils;
David Christie2ff96af2014-01-30 16:09:37 -080022
WyattRileyfa1ef8d32019-12-30 17:19:55 -080023import com.android.internal.annotations.VisibleForTesting;
24import com.android.internal.util.IndentingPrintWriter;
25
26import java.util.ArrayList;
David Christie2ff96af2014-01-30 16:09:37 -080027import java.util.HashMap;
28
29/**
30 * Holds statistics for location requests (active requests by provider).
31 *
32 * <p>Must be externally synchronized.
33 */
34public class LocationRequestStatistics {
35 private static final String TAG = "LocationStats";
36
David Christie00dc0562014-05-14 14:28:54 -070037 // Maps package name and provider to location request statistics.
David Christie2ff96af2014-01-30 16:09:37 -080038 public final HashMap<PackageProviderKey, PackageStatistics> statistics
39 = new HashMap<PackageProviderKey, PackageStatistics>();
40
WyattRileyfa1ef8d32019-12-30 17:19:55 -080041 public final RequestSummaryLimitedHistory history = new RequestSummaryLimitedHistory();
42
David Christie2ff96af2014-01-30 16:09:37 -080043 /**
44 * Signals that a package has started requesting locations.
45 *
46 * @param packageName Name of package that has requested locations.
47 * @param providerName Name of provider that is requested (e.g. "gps").
48 * @param intervalMs The interval that is requested in ms.
49 */
Wyatt Rileyf7075e02018-04-12 17:54:26 -070050 public void startRequesting(String packageName, String providerName, long intervalMs,
51 boolean isForeground) {
David Christie2ff96af2014-01-30 16:09:37 -080052 PackageProviderKey key = new PackageProviderKey(packageName, providerName);
53 PackageStatistics stats = statistics.get(key);
54 if (stats == null) {
55 stats = new PackageStatistics();
56 statistics.put(key, stats);
57 }
58 stats.startRequesting(intervalMs);
Wyatt Rileyf7075e02018-04-12 17:54:26 -070059 stats.updateForeground(isForeground);
WyattRileyfa1ef8d32019-12-30 17:19:55 -080060 history.addRequest(packageName, providerName, intervalMs);
David Christie2ff96af2014-01-30 16:09:37 -080061 }
62
63 /**
64 * Signals that a package has stopped requesting locations.
65 *
66 * @param packageName Name of package that has stopped requesting locations.
67 * @param providerName Provider that is no longer being requested.
68 */
69 public void stopRequesting(String packageName, String providerName) {
70 PackageProviderKey key = new PackageProviderKey(packageName, providerName);
71 PackageStatistics stats = statistics.get(key);
72 if (stats != null) {
73 stats.stopRequesting();
Wyatt Rileyf7075e02018-04-12 17:54:26 -070074 }
WyattRileyfa1ef8d32019-12-30 17:19:55 -080075 history.removeRequest(packageName, providerName);
Wyatt Rileyf7075e02018-04-12 17:54:26 -070076 }
77
78 /**
79 * Signals that a package possibly switched background/foreground.
80 *
81 * @param packageName Name of package that has stopped requesting locations.
82 * @param providerName Provider that is no longer being requested.
83 */
84 public void updateForeground(String packageName, String providerName, boolean isForeground) {
85 PackageProviderKey key = new PackageProviderKey(packageName, providerName);
86 PackageStatistics stats = statistics.get(key);
87 if (stats != null) {
88 stats.updateForeground(isForeground);
David Christie2ff96af2014-01-30 16:09:37 -080089 }
90 }
91
92 /**
93 * A key that holds both package and provider names.
94 */
95 public static class PackageProviderKey {
96 /**
97 * Name of package requesting location.
98 */
99 public final String packageName;
100 /**
101 * Name of provider being requested (e.g. "gps").
102 */
103 public final String providerName;
104
WyattRileyfa1ef8d32019-12-30 17:19:55 -0800105 PackageProviderKey(String packageName, String providerName) {
David Christie2ff96af2014-01-30 16:09:37 -0800106 this.packageName = packageName;
107 this.providerName = providerName;
108 }
109
110 @Override
111 public boolean equals(Object other) {
112 if (!(other instanceof PackageProviderKey)) {
113 return false;
114 }
115
116 PackageProviderKey otherKey = (PackageProviderKey) other;
117 return packageName.equals(otherKey.packageName)
118 && providerName.equals(otherKey.providerName);
119 }
120
121 @Override
122 public int hashCode() {
123 return packageName.hashCode() + 31 * providerName.hashCode();
124 }
125 }
126
127 /**
WyattRileyfa1ef8d32019-12-30 17:19:55 -0800128 * A data structure to hold past requests
129 */
130 public static class RequestSummaryLimitedHistory {
131 @VisibleForTesting
132 static final int MAX_SIZE = 100;
133
134 final ArrayList<RequestSummary> mList = new ArrayList<>(MAX_SIZE);
135
136 /**
137 * Append an added location request to the history
138 */
139 @VisibleForTesting
140 void addRequest(String packageName, String providerName, long intervalMs) {
141 addRequestSummary(new RequestSummary(packageName, providerName, intervalMs));
142 }
143
144 /**
145 * Append a removed location request to the history
146 */
147 @VisibleForTesting
148 void removeRequest(String packageName, String providerName) {
149 addRequestSummary(new RequestSummary(
150 packageName, providerName, RequestSummary.REQUEST_ENDED_INTERVAL));
151 }
152
153 private void addRequestSummary(RequestSummary summary) {
154 while (mList.size() >= MAX_SIZE) {
155 mList.remove(0);
156 }
157 mList.add(summary);
158 }
159
160 /**
161 * Dump history to a printwriter (for dumpsys location)
162 */
163 public void dump(IndentingPrintWriter ipw) {
164 long systemElapsedOffsetMillis = System.currentTimeMillis()
165 - SystemClock.elapsedRealtime();
166
167 ipw.println("Last Several Location Requests:");
168 ipw.increaseIndent();
169
170 for (RequestSummary requestSummary : mList) {
171 requestSummary.dump(ipw, systemElapsedOffsetMillis);
172 }
173
174 ipw.decreaseIndent();
175 }
176 }
177
178 /**
179 * A data structure to hold a single request
180 */
181 static class RequestSummary {
182 /**
183 * Name of package requesting location.
184 */
185 private final String mPackageName;
186 /**
187 * Name of provider being requested (e.g. "gps").
188 */
189 private final String mProviderName;
190 /**
191 * Interval Requested, or REQUEST_ENDED_INTERVAL indicating request has ended
192 */
193 private final long mIntervalMillis;
194 /**
195 * Elapsed time of request
196 */
197 private final long mElapsedRealtimeMillis;
198
199 /**
200 * Placeholder for requested ending (other values indicate request started/changed)
201 */
202 static final long REQUEST_ENDED_INTERVAL = -1;
203
204 RequestSummary(String packageName, String providerName, long intervalMillis) {
205 this.mPackageName = packageName;
206 this.mProviderName = providerName;
207 this.mIntervalMillis = intervalMillis;
208 this.mElapsedRealtimeMillis = SystemClock.elapsedRealtime();
209 }
210
211 void dump(IndentingPrintWriter ipw, long systemElapsedOffsetMillis) {
212 StringBuilder s = new StringBuilder();
213 long systemTimeMillis = systemElapsedOffsetMillis + mElapsedRealtimeMillis;
214 s.append("At ").append(TimeUtils.formatForLogging(systemTimeMillis)).append(": ")
215 .append(mIntervalMillis == REQUEST_ENDED_INTERVAL ? "- " : "+ ")
216 .append(String.format("%7s", mProviderName)).append(" request from ")
217 .append(mPackageName);
218 if (mIntervalMillis != REQUEST_ENDED_INTERVAL) {
219 s.append(" at interval ").append(mIntervalMillis / 1000).append(" seconds");
220 }
221 ipw.println(s);
222 }
223 }
224
225 /**
David Christie2ff96af2014-01-30 16:09:37 -0800226 * Usage statistics for a package/provider pair.
227 */
228 public static class PackageStatistics {
229 // Time when this package first requested location.
230 private final long mInitialElapsedTimeMs;
231 // Number of active location requests this package currently has.
232 private int mNumActiveRequests;
233 // Time when this package most recently went from not requesting location to requesting.
234 private long mLastActivitationElapsedTimeMs;
235 // The fastest interval this package has ever requested.
236 private long mFastestIntervalMs;
237 // The slowest interval this package has ever requested.
238 private long mSlowestIntervalMs;
239 // The total time this app has requested location (not including currently running requests).
240 private long mTotalDurationMs;
241
Wyatt Rileyf7075e02018-04-12 17:54:26 -0700242 // Time when this package most recently went to foreground, requesting location. 0 means
243 // not currently in foreground.
244 private long mLastForegroundElapsedTimeMs;
245 // The time this app has requested location (not including currently running requests), while
246 // in foreground.
247 private long mForegroundDurationMs;
248
WyattRiley07d2bfc2018-12-21 08:05:27 -0800249 // Time when package last went dormant (stopped requesting location)
250 private long mLastStopElapsedTimeMs;
251
David Christie2ff96af2014-01-30 16:09:37 -0800252 private PackageStatistics() {
253 mInitialElapsedTimeMs = SystemClock.elapsedRealtime();
254 mNumActiveRequests = 0;
255 mTotalDurationMs = 0;
256 mFastestIntervalMs = Long.MAX_VALUE;
257 mSlowestIntervalMs = 0;
Wyatt Rileyf7075e02018-04-12 17:54:26 -0700258 mForegroundDurationMs = 0;
259 mLastForegroundElapsedTimeMs = 0;
WyattRiley07d2bfc2018-12-21 08:05:27 -0800260 mLastStopElapsedTimeMs = 0;
David Christie2ff96af2014-01-30 16:09:37 -0800261 }
262
263 private void startRequesting(long intervalMs) {
264 if (mNumActiveRequests == 0) {
265 mLastActivitationElapsedTimeMs = SystemClock.elapsedRealtime();
266 }
267
268 if (intervalMs < mFastestIntervalMs) {
269 mFastestIntervalMs = intervalMs;
270 }
271
272 if (intervalMs > mSlowestIntervalMs) {
273 mSlowestIntervalMs = intervalMs;
274 }
275
276 mNumActiveRequests++;
277 }
278
Wyatt Rileyf7075e02018-04-12 17:54:26 -0700279 private void updateForeground(boolean isForeground) {
280 long nowElapsedTimeMs = SystemClock.elapsedRealtime();
281 // if previous interval was foreground, accumulate before resetting start
282 if (mLastForegroundElapsedTimeMs != 0) {
283 mForegroundDurationMs += (nowElapsedTimeMs - mLastForegroundElapsedTimeMs);
284 }
285 mLastForegroundElapsedTimeMs = isForeground ? nowElapsedTimeMs : 0;
286 }
287
David Christie2ff96af2014-01-30 16:09:37 -0800288 private void stopRequesting() {
289 if (mNumActiveRequests <= 0) {
290 // Shouldn't be a possible code path
291 Log.e(TAG, "Reference counting corrupted in usage statistics.");
292 return;
293 }
294
295 mNumActiveRequests--;
296 if (mNumActiveRequests == 0) {
WyattRiley07d2bfc2018-12-21 08:05:27 -0800297 mLastStopElapsedTimeMs = SystemClock.elapsedRealtime();
298 long lastDurationMs = mLastStopElapsedTimeMs - mLastActivitationElapsedTimeMs;
David Christie2ff96af2014-01-30 16:09:37 -0800299 mTotalDurationMs += lastDurationMs;
Wyatt Rileyf7075e02018-04-12 17:54:26 -0700300 updateForeground(false);
David Christie2ff96af2014-01-30 16:09:37 -0800301 }
302 }
303
304 /**
305 * Returns the duration that this request has been active.
306 */
307 public long getDurationMs() {
308 long currentDurationMs = mTotalDurationMs;
309 if (mNumActiveRequests > 0) {
310 currentDurationMs
311 += SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
312 }
313 return currentDurationMs;
314 }
315
316 /**
Wyatt Rileyf7075e02018-04-12 17:54:26 -0700317 * Returns the duration that this request has been active.
318 */
319 public long getForegroundDurationMs() {
320 long currentDurationMs = mForegroundDurationMs;
321 if (mLastForegroundElapsedTimeMs != 0 ) {
322 currentDurationMs
323 += SystemClock.elapsedRealtime() - mLastForegroundElapsedTimeMs;
324 }
325 return currentDurationMs;
326 }
327
328 /**
David Christie2ff96af2014-01-30 16:09:37 -0800329 * Returns the time since the initial request in ms.
330 */
331 public long getTimeSinceFirstRequestMs() {
332 return SystemClock.elapsedRealtime() - mInitialElapsedTimeMs;
333 }
334
335 /**
WyattRiley07d2bfc2018-12-21 08:05:27 -0800336 * Returns the time since the last request stopped in ms.
337 */
338 public long getTimeSinceLastRequestStoppedMs() {
339 return SystemClock.elapsedRealtime() - mLastStopElapsedTimeMs;
340 }
341
342 /**
David Christie2ff96af2014-01-30 16:09:37 -0800343 * Returns the fastest interval that has been tracked.
344 */
345 public long getFastestIntervalMs() {
346 return mFastestIntervalMs;
347 }
348
349 /**
350 * Returns the slowest interval that has been tracked.
351 */
352 public long getSlowestIntervalMs() {
353 return mSlowestIntervalMs;
354 }
355
356 /**
357 * Returns true if a request is active for these tracked statistics.
358 */
359 public boolean isActive() {
360 return mNumActiveRequests > 0;
361 }
362
363 @Override
364 public String toString() {
365 StringBuilder s = new StringBuilder();
366 if (mFastestIntervalMs == mSlowestIntervalMs) {
367 s.append("Interval ").append(mFastestIntervalMs / 1000).append(" seconds");
368 } else {
369 s.append("Min interval ").append(mFastestIntervalMs / 1000).append(" seconds");
370 s.append(": Max interval ").append(mSlowestIntervalMs / 1000).append(" seconds");
371 }
372 s.append(": Duration requested ")
373 .append((getDurationMs() / 1000) / 60)
Wyatt Rileyf7075e02018-04-12 17:54:26 -0700374 .append(" total, ")
375 .append((getForegroundDurationMs() / 1000) / 60)
376 .append(" foreground, out of the last ")
David Christie2ff96af2014-01-30 16:09:37 -0800377 .append((getTimeSinceFirstRequestMs() / 1000) / 60)
378 .append(" minutes");
379 if (isActive()) {
380 s.append(": Currently active");
WyattRiley07d2bfc2018-12-21 08:05:27 -0800381 } else {
382 s.append(": Last active ")
383 .append((getTimeSinceLastRequestStoppedMs() / 1000) / 60)
384 .append(" minutes ago");
David Christie2ff96af2014-01-30 16:09:37 -0800385 }
386 return s.toString();
387 }
388 }
389}