• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.eventlog;
18 
19 import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
20 import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
21 import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
22 import static android.os.PowerManager.LOCATION_MODE_NO_CHANGE;
23 import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
24 import static android.util.TimeUtils.formatDuration;
25 
26 import static com.android.server.location.LocationManagerService.D;
27 
28 import static java.lang.Math.max;
29 import static java.lang.Math.min;
30 import static java.util.concurrent.TimeUnit.MILLISECONDS;
31 
32 import android.annotation.Nullable;
33 import android.location.LocationRequest;
34 import android.location.provider.ProviderRequest;
35 import android.location.util.identity.CallerIdentity;
36 import android.os.PowerManager.LocationPowerSaveMode;
37 import android.os.SystemClock;
38 import android.util.ArrayMap;
39 import android.util.TimeUtils;
40 
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.internal.util.Preconditions;
43 
44 import java.util.function.Consumer;
45 
46 /** In memory event log for location events. */
47 public class LocationEventLog extends LocalEventLog<Object> {
48 
49     public static final LocationEventLog EVENT_LOG = new LocationEventLog();
50 
getLogSize()51     private static int getLogSize() {
52         if (D) {
53             return 600;
54         } else {
55             return 300;
56         }
57     }
58 
getLocationsLogSize()59     private static int getLocationsLogSize() {
60         if (D) {
61             return 200;
62         } else {
63             return 100;
64         }
65     }
66 
67     @GuardedBy("mAggregateStats")
68     private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats;
69 
70     @GuardedBy("this")
71     private final LocationsEventLog mLocationsLog;
72 
LocationEventLog()73     private LocationEventLog() {
74         super(getLogSize(), Object.class);
75         mAggregateStats = new ArrayMap<>(4);
76         mLocationsLog = new LocationsEventLog(getLocationsLogSize());
77     }
78 
79     /** Copies out all aggregated stats. */
copyAggregateStats()80     public ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> copyAggregateStats() {
81         synchronized (mAggregateStats) {
82             ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> copy = new ArrayMap<>(
83                     mAggregateStats);
84             for (int i = 0; i < copy.size(); i++) {
85                 copy.setValueAt(i, new ArrayMap<>(copy.valueAt(i)));
86             }
87             return copy;
88         }
89     }
90 
getAggregateStats(String provider, CallerIdentity identity)91     private AggregateStats getAggregateStats(String provider, CallerIdentity identity) {
92         synchronized (mAggregateStats) {
93             ArrayMap<CallerIdentity, AggregateStats> packageMap = mAggregateStats.get(provider);
94             if (packageMap == null) {
95                 packageMap = new ArrayMap<>(2);
96                 mAggregateStats.put(provider, packageMap);
97             }
98             CallerIdentity aggregate = CallerIdentity.forAggregation(identity);
99             AggregateStats stats = packageMap.get(aggregate);
100             if (stats == null) {
101                 stats = new AggregateStats();
102                 packageMap.put(aggregate, stats);
103             }
104             return stats;
105         }
106     }
107 
108     /** Logs a user switched event. */
logUserSwitched(int userIdFrom, int userIdTo)109     public void logUserSwitched(int userIdFrom, int userIdTo) {
110         addLog(new UserSwitchedEvent(userIdFrom, userIdTo));
111     }
112 
113     /** Logs a location enabled/disabled event. */
logLocationEnabled(int userId, boolean enabled)114     public void logLocationEnabled(int userId, boolean enabled) {
115         addLog(new LocationEnabledEvent(userId, enabled));
116     }
117 
118     /** Logs a location enabled/disabled event. */
logAdasLocationEnabled(int userId, boolean enabled)119     public void logAdasLocationEnabled(int userId, boolean enabled) {
120         addLog(new LocationAdasEnabledEvent(userId, enabled));
121     }
122 
123     /** Logs a location provider enabled/disabled event. */
logProviderEnabled(String provider, int userId, boolean enabled)124     public void logProviderEnabled(String provider, int userId, boolean enabled) {
125         addLog(new ProviderEnabledEvent(provider, userId, enabled));
126     }
127 
128     /** Logs a location provider being replaced/unreplaced by a mock provider. */
logProviderMocked(String provider, boolean mocked)129     public void logProviderMocked(String provider, boolean mocked) {
130         addLog(new ProviderMockedEvent(provider, mocked));
131     }
132 
133     /** Logs a new client registration for a location provider. */
logProviderClientRegistered(String provider, CallerIdentity identity, LocationRequest request)134     public void logProviderClientRegistered(String provider, CallerIdentity identity,
135             LocationRequest request) {
136         addLog(new ProviderClientRegisterEvent(provider, true, identity, request));
137         getAggregateStats(provider, identity).markRequestAdded(request.getIntervalMillis());
138     }
139 
140     /** Logs a client unregistration for a location provider. */
logProviderClientUnregistered(String provider, CallerIdentity identity)141     public void logProviderClientUnregistered(String provider, CallerIdentity identity) {
142         addLog(new ProviderClientRegisterEvent(provider, false, identity, null));
143         getAggregateStats(provider, identity).markRequestRemoved();
144     }
145 
146     /** Logs a client for a location provider entering the active state. */
logProviderClientActive(String provider, CallerIdentity identity)147     public void logProviderClientActive(String provider, CallerIdentity identity) {
148         getAggregateStats(provider, identity).markRequestActive();
149     }
150 
151     /** Logs a client for a location provider leaving the active state. */
logProviderClientInactive(String provider, CallerIdentity identity)152     public void logProviderClientInactive(String provider, CallerIdentity identity) {
153         getAggregateStats(provider, identity).markRequestInactive();
154     }
155 
156     /** Logs a client for a location provider entering the foreground state. */
logProviderClientForeground(String provider, CallerIdentity identity)157     public void logProviderClientForeground(String provider, CallerIdentity identity) {
158         if (D) {
159             addLog(new ProviderClientForegroundEvent(provider, true, identity));
160         }
161         getAggregateStats(provider, identity).markRequestForeground();
162     }
163 
164     /** Logs a client for a location provider leaving the foreground state. */
logProviderClientBackground(String provider, CallerIdentity identity)165     public void logProviderClientBackground(String provider, CallerIdentity identity) {
166         if (D) {
167             addLog(new ProviderClientForegroundEvent(provider, false, identity));
168         }
169         getAggregateStats(provider, identity).markRequestBackground();
170     }
171 
172     /** Logs a client for a location provider entering the permitted state. */
logProviderClientPermitted(String provider, CallerIdentity identity)173     public void logProviderClientPermitted(String provider, CallerIdentity identity) {
174         if (D) {
175             addLog(new ProviderClientPermittedEvent(provider, true, identity));
176         }
177     }
178 
179     /** Logs a client for a location provider leaving the permitted state. */
logProviderClientUnpermitted(String provider, CallerIdentity identity)180     public void logProviderClientUnpermitted(String provider, CallerIdentity identity) {
181         if (D) {
182             addLog(new ProviderClientPermittedEvent(provider, false, identity));
183         }
184     }
185 
186     /** Logs a change to the provider request for a location provider. */
logProviderUpdateRequest(String provider, ProviderRequest request)187     public void logProviderUpdateRequest(String provider, ProviderRequest request) {
188         addLog(new ProviderUpdateEvent(provider, request));
189     }
190 
191     /** Logs a new incoming location for a location provider. */
logProviderReceivedLocations(String provider, int numLocations)192     public void logProviderReceivedLocations(String provider, int numLocations) {
193         synchronized (this) {
194             mLocationsLog.logProviderReceivedLocations(provider, numLocations);
195         }
196     }
197 
198     /** Logs a location deliver for a client of a location provider. */
logProviderDeliveredLocations(String provider, int numLocations, CallerIdentity identity)199     public void logProviderDeliveredLocations(String provider, int numLocations,
200             CallerIdentity identity) {
201         synchronized (this) {
202             mLocationsLog.logProviderDeliveredLocations(provider, numLocations, identity);
203         }
204         getAggregateStats(provider, identity).markLocationDelivered();
205     }
206 
207     /** Logs that a provider has entered or exited stationary throttling. */
logProviderStationaryThrottled(String provider, boolean throttled, ProviderRequest request)208     public void logProviderStationaryThrottled(String provider, boolean throttled,
209             ProviderRequest request) {
210         addLog(new ProviderStationaryThrottledEvent(provider, throttled, request));
211     }
212 
213     /** Logs that the location power save mode has changed. */
logLocationPowerSaveMode( @ocationPowerSaveMode int locationPowerSaveMode)214     public void logLocationPowerSaveMode(
215             @LocationPowerSaveMode int locationPowerSaveMode) {
216         addLog(new LocationPowerSaveModeEvent(locationPowerSaveMode));
217     }
218 
addLog(Object logEvent)219     private void addLog(Object logEvent) {
220         addLog(SystemClock.elapsedRealtime(), logEvent);
221     }
222 
223     @Override
iterate(LogConsumer<? super Object> consumer)224     public synchronized void iterate(LogConsumer<? super Object> consumer) {
225         iterate(consumer, this, mLocationsLog);
226     }
227 
iterate(Consumer<String> consumer)228     public void iterate(Consumer<String> consumer) {
229         iterate(consumer, null);
230     }
231 
iterate(Consumer<String> consumer, @Nullable String providerFilter)232     public void iterate(Consumer<String> consumer, @Nullable String providerFilter) {
233         long systemTimeDeltaMs = System.currentTimeMillis() - SystemClock.elapsedRealtime();
234         StringBuilder builder = new StringBuilder();
235         iterate(
236                 (time, logEvent) -> {
237                     boolean match = providerFilter == null || (logEvent instanceof ProviderEvent
238                             && providerFilter.equals(((ProviderEvent) logEvent).mProvider));
239                     if (match) {
240                         builder.setLength(0);
241                         builder.append(TimeUtils.logTimeOfDay(time + systemTimeDeltaMs));
242                         builder.append(": ");
243                         builder.append(logEvent);
244                         consumer.accept(builder.toString());
245                     }
246                 });
247     }
248 
249     private abstract static class ProviderEvent {
250 
251         protected final String mProvider;
252 
ProviderEvent(String provider)253         ProviderEvent(String provider) {
254             mProvider = provider;
255         }
256     }
257 
258     private static final class ProviderEnabledEvent extends ProviderEvent {
259 
260         private final int mUserId;
261         private final boolean mEnabled;
262 
ProviderEnabledEvent(String provider, int userId, boolean enabled)263         ProviderEnabledEvent(String provider, int userId,
264                 boolean enabled) {
265             super(provider);
266             mUserId = userId;
267             mEnabled = enabled;
268         }
269 
270         @Override
toString()271         public String toString() {
272             return mProvider + " provider [u" + mUserId + "] " + (mEnabled ? "enabled"
273                     : "disabled");
274         }
275     }
276 
277     private static final class ProviderMockedEvent extends ProviderEvent {
278 
279         private final boolean mMocked;
280 
ProviderMockedEvent(String provider, boolean mocked)281         ProviderMockedEvent(String provider, boolean mocked) {
282             super(provider);
283             mMocked = mocked;
284         }
285 
286         @Override
toString()287         public String toString() {
288             if (mMocked) {
289                 return mProvider + " provider added mock provider override";
290             } else {
291                 return mProvider + " provider removed mock provider override";
292             }
293         }
294     }
295 
296     private static final class ProviderClientRegisterEvent extends ProviderEvent {
297 
298         private final boolean mRegistered;
299         private final CallerIdentity mIdentity;
300         @Nullable private final LocationRequest mLocationRequest;
301 
ProviderClientRegisterEvent(String provider, boolean registered, CallerIdentity identity, @Nullable LocationRequest locationRequest)302         ProviderClientRegisterEvent(String provider, boolean registered,
303                 CallerIdentity identity, @Nullable LocationRequest locationRequest) {
304             super(provider);
305             mRegistered = registered;
306             mIdentity = identity;
307             mLocationRequest = locationRequest;
308         }
309 
310         @Override
toString()311         public String toString() {
312             if (mRegistered) {
313                 return mProvider + " provider +registration " + mIdentity + " -> "
314                         + mLocationRequest;
315             } else {
316                 return mProvider + " provider -registration " + mIdentity;
317             }
318         }
319     }
320 
321     private static final class ProviderClientForegroundEvent extends ProviderEvent {
322 
323         private final boolean mForeground;
324         private final CallerIdentity mIdentity;
325 
ProviderClientForegroundEvent(String provider, boolean foreground, CallerIdentity identity)326         ProviderClientForegroundEvent(String provider, boolean foreground,
327                 CallerIdentity identity) {
328             super(provider);
329             mForeground = foreground;
330             mIdentity = identity;
331         }
332 
333         @Override
toString()334         public String toString() {
335             return mProvider + " provider client " + mIdentity + " -> "
336                     + (mForeground ? "foreground" : "background");
337         }
338     }
339 
340     private static final class ProviderClientPermittedEvent extends ProviderEvent {
341 
342         private final boolean mPermitted;
343         private final CallerIdentity mIdentity;
344 
ProviderClientPermittedEvent(String provider, boolean permitted, CallerIdentity identity)345         ProviderClientPermittedEvent(String provider, boolean permitted, CallerIdentity identity) {
346             super(provider);
347             mPermitted = permitted;
348             mIdentity = identity;
349         }
350 
351         @Override
toString()352         public String toString() {
353             return mProvider + " provider client " + mIdentity + " -> "
354                     + (mPermitted ? "permitted" : "unpermitted");
355         }
356     }
357 
358     private static final class ProviderUpdateEvent extends ProviderEvent {
359 
360         private final ProviderRequest mRequest;
361 
ProviderUpdateEvent(String provider, ProviderRequest request)362         ProviderUpdateEvent(String provider, ProviderRequest request) {
363             super(provider);
364             mRequest = request;
365         }
366 
367         @Override
toString()368         public String toString() {
369             return mProvider + " provider request = " + mRequest;
370         }
371     }
372 
373     private static final class ProviderReceiveLocationEvent extends ProviderEvent {
374 
375         private final int mNumLocations;
376 
ProviderReceiveLocationEvent(String provider, int numLocations)377         ProviderReceiveLocationEvent(String provider, int numLocations) {
378             super(provider);
379             mNumLocations = numLocations;
380         }
381 
382         @Override
toString()383         public String toString() {
384             return mProvider + " provider received location[" + mNumLocations + "]";
385         }
386     }
387 
388     private static final class ProviderDeliverLocationEvent extends ProviderEvent {
389 
390         private final int mNumLocations;
391         @Nullable private final CallerIdentity mIdentity;
392 
ProviderDeliverLocationEvent(String provider, int numLocations, @Nullable CallerIdentity identity)393         ProviderDeliverLocationEvent(String provider, int numLocations,
394                 @Nullable CallerIdentity identity) {
395             super(provider);
396             mNumLocations = numLocations;
397             mIdentity = identity;
398         }
399 
400         @Override
toString()401         public String toString() {
402             return mProvider + " provider delivered location[" + mNumLocations + "] to "
403                     + mIdentity;
404         }
405     }
406 
407     private static final class ProviderStationaryThrottledEvent extends ProviderEvent {
408 
409         private final boolean mStationaryThrottled;
410         private final ProviderRequest mRequest;
411 
ProviderStationaryThrottledEvent(String provider, boolean stationaryThrottled, ProviderRequest request)412         ProviderStationaryThrottledEvent(String provider, boolean stationaryThrottled,
413                 ProviderRequest request) {
414             super(provider);
415             mStationaryThrottled = stationaryThrottled;
416             mRequest = request;
417         }
418 
419         @Override
toString()420         public String toString() {
421             return mProvider + " provider stationary/idle " + (mStationaryThrottled ? "throttled"
422                     : "unthrottled") + ", request = " + mRequest;
423         }
424     }
425 
426     private static final class LocationPowerSaveModeEvent {
427 
428         @LocationPowerSaveMode
429         private final int mLocationPowerSaveMode;
430 
LocationPowerSaveModeEvent(@ocationPowerSaveMode int locationPowerSaveMode)431         LocationPowerSaveModeEvent(@LocationPowerSaveMode int locationPowerSaveMode) {
432             mLocationPowerSaveMode = locationPowerSaveMode;
433         }
434 
435         @Override
toString()436         public String toString() {
437             String mode;
438             switch (mLocationPowerSaveMode) {
439                 case LOCATION_MODE_NO_CHANGE:
440                     mode = "NO_CHANGE";
441                     break;
442                 case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
443                     mode = "GPS_DISABLED_WHEN_SCREEN_OFF";
444                     break;
445                 case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
446                     mode = "ALL_DISABLED_WHEN_SCREEN_OFF";
447                     break;
448                 case LOCATION_MODE_FOREGROUND_ONLY:
449                     mode = "FOREGROUND_ONLY";
450                     break;
451                 case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
452                     mode = "THROTTLE_REQUESTS_WHEN_SCREEN_OFF";
453                     break;
454                 default:
455                     mode = "UNKNOWN";
456                     break;
457             }
458             return "location power save mode changed to " + mode;
459         }
460     }
461 
462     private static final class UserSwitchedEvent {
463 
464         private final int mUserIdFrom;
465         private final int mUserIdTo;
466 
UserSwitchedEvent(int userIdFrom, int userIdTo)467         UserSwitchedEvent(int userIdFrom, int userIdTo) {
468             mUserIdFrom = userIdFrom;
469             mUserIdTo = userIdTo;
470         }
471 
472         @Override
toString()473         public String toString() {
474             return "current user switched from u" + mUserIdFrom + " to u" + mUserIdTo;
475         }
476     }
477 
478     private static final class LocationEnabledEvent {
479 
480         private final int mUserId;
481         private final boolean mEnabled;
482 
LocationEnabledEvent(int userId, boolean enabled)483         LocationEnabledEvent(int userId, boolean enabled) {
484             mUserId = userId;
485             mEnabled = enabled;
486         }
487 
488         @Override
toString()489         public String toString() {
490             return "location [u" + mUserId + "] " + (mEnabled ? "enabled" : "disabled");
491         }
492     }
493 
494     private static final class LocationAdasEnabledEvent {
495 
496         private final int mUserId;
497         private final boolean mEnabled;
498 
LocationAdasEnabledEvent(int userId, boolean enabled)499         LocationAdasEnabledEvent(int userId, boolean enabled) {
500             mUserId = userId;
501             mEnabled = enabled;
502         }
503 
504         @Override
toString()505         public String toString() {
506             return "adas location [u" + mUserId + "] " + (mEnabled ? "enabled" : "disabled");
507         }
508     }
509 
510     private static final class LocationsEventLog extends LocalEventLog<Object> {
511 
LocationsEventLog(int size)512         LocationsEventLog(int size) {
513             super(size, Object.class);
514         }
515 
logProviderReceivedLocations(String provider, int numLocations)516         public void logProviderReceivedLocations(String provider, int numLocations) {
517             addLog(new ProviderReceiveLocationEvent(provider, numLocations));
518         }
519 
logProviderDeliveredLocations(String provider, int numLocations, CallerIdentity identity)520         public void logProviderDeliveredLocations(String provider, int numLocations,
521                 CallerIdentity identity) {
522             addLog(new ProviderDeliverLocationEvent(provider, numLocations, identity));
523         }
524 
addLog(Object logEvent)525         private void addLog(Object logEvent) {
526             this.addLog(SystemClock.elapsedRealtime(), logEvent);
527         }
528     }
529 
530     /**
531      * Aggregate statistics for a single package under a single provider.
532      */
533     public static final class AggregateStats {
534 
535         @GuardedBy("this")
536         private int mAddedRequestCount;
537         @GuardedBy("this")
538         private int mActiveRequestCount;
539         @GuardedBy("this")
540         private int mForegroundRequestCount;
541         @GuardedBy("this")
542         private int mDeliveredLocationCount;
543 
544         @GuardedBy("this")
545         private long mFastestIntervalMs = Long.MAX_VALUE;
546         @GuardedBy("this")
547         private long mSlowestIntervalMs = 0;
548 
549         @GuardedBy("this")
550         private long mAddedTimeTotalMs;
551         @GuardedBy("this")
552         private long mAddedTimeLastUpdateRealtimeMs;
553 
554         @GuardedBy("this")
555         private long mActiveTimeTotalMs;
556         @GuardedBy("this")
557         private long mActiveTimeLastUpdateRealtimeMs;
558 
559         @GuardedBy("this")
560         private long mForegroundTimeTotalMs;
561         @GuardedBy("this")
562         private long mForegroundTimeLastUpdateRealtimeMs;
563 
AggregateStats()564         AggregateStats() {}
565 
markRequestAdded(long intervalMillis)566         synchronized void markRequestAdded(long intervalMillis) {
567             if (mAddedRequestCount++ == 0) {
568                 mAddedTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
569             }
570 
571             mFastestIntervalMs = min(intervalMillis, mFastestIntervalMs);
572             mSlowestIntervalMs = max(intervalMillis, mSlowestIntervalMs);
573         }
574 
markRequestRemoved()575         synchronized void markRequestRemoved() {
576             updateTotals();
577             --mAddedRequestCount;
578             Preconditions.checkState(mAddedRequestCount >= 0);
579 
580             mActiveRequestCount = min(mAddedRequestCount, mActiveRequestCount);
581             mForegroundRequestCount = min(mAddedRequestCount, mForegroundRequestCount);
582         }
583 
markRequestActive()584         synchronized void markRequestActive() {
585             Preconditions.checkState(mAddedRequestCount > 0);
586             if (mActiveRequestCount++ == 0) {
587                 mActiveTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
588             }
589         }
590 
markRequestInactive()591         synchronized void markRequestInactive() {
592             updateTotals();
593             --mActiveRequestCount;
594             Preconditions.checkState(mActiveRequestCount >= 0);
595         }
596 
markRequestForeground()597         synchronized void markRequestForeground() {
598             Preconditions.checkState(mAddedRequestCount > 0);
599             if (mForegroundRequestCount++ == 0) {
600                 mForegroundTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
601             }
602         }
603 
markRequestBackground()604         synchronized void markRequestBackground() {
605             updateTotals();
606             --mForegroundRequestCount;
607             Preconditions.checkState(mForegroundRequestCount >= 0);
608         }
609 
markLocationDelivered()610         synchronized void markLocationDelivered() {
611             mDeliveredLocationCount++;
612         }
613 
updateTotals()614         public synchronized void updateTotals() {
615             if (mAddedRequestCount > 0) {
616                 long realtimeMs = SystemClock.elapsedRealtime();
617                 mAddedTimeTotalMs += realtimeMs - mAddedTimeLastUpdateRealtimeMs;
618                 mAddedTimeLastUpdateRealtimeMs = realtimeMs;
619             }
620             if (mActiveRequestCount > 0) {
621                 long realtimeMs = SystemClock.elapsedRealtime();
622                 mActiveTimeTotalMs += realtimeMs - mActiveTimeLastUpdateRealtimeMs;
623                 mActiveTimeLastUpdateRealtimeMs = realtimeMs;
624             }
625             if (mForegroundRequestCount > 0) {
626                 long realtimeMs = SystemClock.elapsedRealtime();
627                 mForegroundTimeTotalMs += realtimeMs - mForegroundTimeLastUpdateRealtimeMs;
628                 mForegroundTimeLastUpdateRealtimeMs = realtimeMs;
629             }
630         }
631 
632         @Override
toString()633         public synchronized String toString() {
634             return "min/max interval = " + intervalToString(mFastestIntervalMs) + "/"
635                     + intervalToString(mSlowestIntervalMs)
636                     + ", total/active/foreground duration = " + formatDuration(mAddedTimeTotalMs)
637                     + "/" + formatDuration(mActiveTimeTotalMs) + "/"
638                     + formatDuration(mForegroundTimeTotalMs) + ", locations = "
639                     + mDeliveredLocationCount;
640         }
641 
intervalToString(long intervalMs)642         private static String intervalToString(long intervalMs) {
643             if (intervalMs == LocationRequest.PASSIVE_INTERVAL) {
644                 return "passive";
645             } else {
646                 return MILLISECONDS.toSeconds(intervalMs) + "s";
647             }
648         }
649     }
650 }
651