package com.android.server.location;
|
|
import android.os.SystemClock;
|
import android.util.Log;
|
|
import java.util.HashMap;
|
|
/**
|
* Holds statistics for location requests (active requests by provider).
|
*
|
* <p>Must be externally synchronized.
|
*/
|
public class LocationRequestStatistics {
|
private static final String TAG = "LocationStats";
|
|
// Maps package name and provider to location request statistics.
|
public final HashMap<PackageProviderKey, PackageStatistics> statistics
|
= new HashMap<PackageProviderKey, PackageStatistics>();
|
|
/**
|
* Signals that a package has started requesting locations.
|
*
|
* @param packageName Name of package that has requested locations.
|
* @param providerName Name of provider that is requested (e.g. "gps").
|
* @param intervalMs The interval that is requested in ms.
|
*/
|
public void startRequesting(String packageName, String providerName, long intervalMs,
|
boolean isForeground) {
|
PackageProviderKey key = new PackageProviderKey(packageName, providerName);
|
PackageStatistics stats = statistics.get(key);
|
if (stats == null) {
|
stats = new PackageStatistics();
|
statistics.put(key, stats);
|
}
|
stats.startRequesting(intervalMs);
|
stats.updateForeground(isForeground);
|
}
|
|
/**
|
* Signals that a package has stopped requesting locations.
|
*
|
* @param packageName Name of package that has stopped requesting locations.
|
* @param providerName Provider that is no longer being requested.
|
*/
|
public void stopRequesting(String packageName, String providerName) {
|
PackageProviderKey key = new PackageProviderKey(packageName, providerName);
|
PackageStatistics stats = statistics.get(key);
|
if (stats != null) {
|
stats.stopRequesting();
|
}
|
}
|
|
/**
|
* Signals that a package possibly switched background/foreground.
|
*
|
* @param packageName Name of package that has stopped requesting locations.
|
* @param providerName Provider that is no longer being requested.
|
*/
|
public void updateForeground(String packageName, String providerName, boolean isForeground) {
|
PackageProviderKey key = new PackageProviderKey(packageName, providerName);
|
PackageStatistics stats = statistics.get(key);
|
if (stats != null) {
|
stats.updateForeground(isForeground);
|
}
|
}
|
|
/**
|
* A key that holds both package and provider names.
|
*/
|
public static class PackageProviderKey {
|
/**
|
* Name of package requesting location.
|
*/
|
public final String packageName;
|
/**
|
* Name of provider being requested (e.g. "gps").
|
*/
|
public final String providerName;
|
|
public PackageProviderKey(String packageName, String providerName) {
|
this.packageName = packageName;
|
this.providerName = providerName;
|
}
|
|
@Override
|
public boolean equals(Object other) {
|
if (!(other instanceof PackageProviderKey)) {
|
return false;
|
}
|
|
PackageProviderKey otherKey = (PackageProviderKey) other;
|
return packageName.equals(otherKey.packageName)
|
&& providerName.equals(otherKey.providerName);
|
}
|
|
@Override
|
public int hashCode() {
|
return packageName.hashCode() + 31 * providerName.hashCode();
|
}
|
}
|
|
/**
|
* Usage statistics for a package/provider pair.
|
*/
|
public static class PackageStatistics {
|
// Time when this package first requested location.
|
private final long mInitialElapsedTimeMs;
|
// Number of active location requests this package currently has.
|
private int mNumActiveRequests;
|
// Time when this package most recently went from not requesting location to requesting.
|
private long mLastActivitationElapsedTimeMs;
|
// The fastest interval this package has ever requested.
|
private long mFastestIntervalMs;
|
// The slowest interval this package has ever requested.
|
private long mSlowestIntervalMs;
|
// The total time this app has requested location (not including currently running requests).
|
private long mTotalDurationMs;
|
|
// Time when this package most recently went to foreground, requesting location. 0 means
|
// not currently in foreground.
|
private long mLastForegroundElapsedTimeMs;
|
// The time this app has requested location (not including currently running requests), while
|
// in foreground.
|
private long mForegroundDurationMs;
|
|
// Time when package last went dormant (stopped requesting location)
|
private long mLastStopElapsedTimeMs;
|
|
private PackageStatistics() {
|
mInitialElapsedTimeMs = SystemClock.elapsedRealtime();
|
mNumActiveRequests = 0;
|
mTotalDurationMs = 0;
|
mFastestIntervalMs = Long.MAX_VALUE;
|
mSlowestIntervalMs = 0;
|
mForegroundDurationMs = 0;
|
mLastForegroundElapsedTimeMs = 0;
|
mLastStopElapsedTimeMs = 0;
|
}
|
|
private void startRequesting(long intervalMs) {
|
if (mNumActiveRequests == 0) {
|
mLastActivitationElapsedTimeMs = SystemClock.elapsedRealtime();
|
}
|
|
if (intervalMs < mFastestIntervalMs) {
|
mFastestIntervalMs = intervalMs;
|
}
|
|
if (intervalMs > mSlowestIntervalMs) {
|
mSlowestIntervalMs = intervalMs;
|
}
|
|
mNumActiveRequests++;
|
}
|
|
private void updateForeground(boolean isForeground) {
|
long nowElapsedTimeMs = SystemClock.elapsedRealtime();
|
// if previous interval was foreground, accumulate before resetting start
|
if (mLastForegroundElapsedTimeMs != 0) {
|
mForegroundDurationMs += (nowElapsedTimeMs - mLastForegroundElapsedTimeMs);
|
}
|
mLastForegroundElapsedTimeMs = isForeground ? nowElapsedTimeMs : 0;
|
}
|
|
private void stopRequesting() {
|
if (mNumActiveRequests <= 0) {
|
// Shouldn't be a possible code path
|
Log.e(TAG, "Reference counting corrupted in usage statistics.");
|
return;
|
}
|
|
mNumActiveRequests--;
|
if (mNumActiveRequests == 0) {
|
mLastStopElapsedTimeMs = SystemClock.elapsedRealtime();
|
long lastDurationMs = mLastStopElapsedTimeMs - mLastActivitationElapsedTimeMs;
|
mTotalDurationMs += lastDurationMs;
|
updateForeground(false);
|
}
|
}
|
|
/**
|
* Returns the duration that this request has been active.
|
*/
|
public long getDurationMs() {
|
long currentDurationMs = mTotalDurationMs;
|
if (mNumActiveRequests > 0) {
|
currentDurationMs
|
+= SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
|
}
|
return currentDurationMs;
|
}
|
|
/**
|
* Returns the duration that this request has been active.
|
*/
|
public long getForegroundDurationMs() {
|
long currentDurationMs = mForegroundDurationMs;
|
if (mLastForegroundElapsedTimeMs != 0 ) {
|
currentDurationMs
|
+= SystemClock.elapsedRealtime() - mLastForegroundElapsedTimeMs;
|
}
|
return currentDurationMs;
|
}
|
|
/**
|
* Returns the time since the initial request in ms.
|
*/
|
public long getTimeSinceFirstRequestMs() {
|
return SystemClock.elapsedRealtime() - mInitialElapsedTimeMs;
|
}
|
|
/**
|
* Returns the time since the last request stopped in ms.
|
*/
|
public long getTimeSinceLastRequestStoppedMs() {
|
return SystemClock.elapsedRealtime() - mLastStopElapsedTimeMs;
|
}
|
|
/**
|
* Returns the fastest interval that has been tracked.
|
*/
|
public long getFastestIntervalMs() {
|
return mFastestIntervalMs;
|
}
|
|
/**
|
* Returns the slowest interval that has been tracked.
|
*/
|
public long getSlowestIntervalMs() {
|
return mSlowestIntervalMs;
|
}
|
|
/**
|
* Returns true if a request is active for these tracked statistics.
|
*/
|
public boolean isActive() {
|
return mNumActiveRequests > 0;
|
}
|
|
@Override
|
public String toString() {
|
StringBuilder s = new StringBuilder();
|
if (mFastestIntervalMs == mSlowestIntervalMs) {
|
s.append("Interval ").append(mFastestIntervalMs / 1000).append(" seconds");
|
} else {
|
s.append("Min interval ").append(mFastestIntervalMs / 1000).append(" seconds");
|
s.append(": Max interval ").append(mSlowestIntervalMs / 1000).append(" seconds");
|
}
|
s.append(": Duration requested ")
|
.append((getDurationMs() / 1000) / 60)
|
.append(" total, ")
|
.append((getForegroundDurationMs() / 1000) / 60)
|
.append(" foreground, out of the last ")
|
.append((getTimeSinceFirstRequestMs() / 1000) / 60)
|
.append(" minutes");
|
if (isActive()) {
|
s.append(": Currently active");
|
} else {
|
s.append(": Last active ")
|
.append((getTimeSinceLastRequestStoppedMs() / 1000) / 60)
|
.append(" minutes ago");
|
}
|
return s.toString();
|
}
|
}
|
}
|