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