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) { PackageProviderKey key = new PackageProviderKey(packageName, providerName); PackageStatistics stats = statistics.get(key); if (stats == null) { stats = new PackageStatistics(); statistics.put(key, stats); } stats.startRequesting(intervalMs); } /** * 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(); } else { // This shouldn't be a possible code path. Log.e(TAG, "Couldn't find package statistics when removing location request."); } } /** * 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; private PackageStatistics() { mInitialElapsedTimeMs = SystemClock.elapsedRealtime(); mNumActiveRequests = 0; mTotalDurationMs = 0; mFastestIntervalMs = Long.MAX_VALUE; mSlowestIntervalMs = 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 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) { long lastDurationMs = SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs; mTotalDurationMs += lastDurationMs; } } /** * 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 time since the initial request in ms. */ public long getTimeSinceFirstRequestMs() { return SystemClock.elapsedRealtime() - mInitialElapsedTimeMs; } /** * 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(" out of the last ") .append((getTimeSinceFirstRequestMs() / 1000) / 60) .append(" minutes"); if (isActive()) { s.append(": Currently active"); } return s.toString(); } } }