Home | History | Annotate | Download | only in location
      1 package com.android.server.location;
      2 
      3 import android.os.SystemClock;
      4 import android.util.Log;
      5 
      6 import java.util.HashMap;
      7 
      8 /**
      9  * Holds statistics for location requests (active requests by provider).
     10  *
     11  * <p>Must be externally synchronized.
     12  */
     13 public class LocationRequestStatistics {
     14     private static final String TAG = "LocationStats";
     15 
     16     // Maps package name and provider to location request statistics.
     17     public final HashMap<PackageProviderKey, PackageStatistics> statistics
     18             = new HashMap<PackageProviderKey, PackageStatistics>();
     19 
     20     /**
     21      * Signals that a package has started requesting locations.
     22      *
     23      * @param packageName Name of package that has requested locations.
     24      * @param providerName Name of provider that is requested (e.g. "gps").
     25      * @param intervalMs The interval that is requested in ms.
     26      */
     27     public void startRequesting(String packageName, String providerName, long intervalMs,
     28             boolean isForeground) {
     29         PackageProviderKey key = new PackageProviderKey(packageName, providerName);
     30         PackageStatistics stats = statistics.get(key);
     31         if (stats == null) {
     32             stats = new PackageStatistics();
     33             statistics.put(key, stats);
     34         }
     35         stats.startRequesting(intervalMs);
     36         stats.updateForeground(isForeground);
     37     }
     38 
     39     /**
     40      * Signals that a package has stopped requesting locations.
     41      *
     42      * @param packageName Name of package that has stopped requesting locations.
     43      * @param providerName Provider that is no longer being requested.
     44      */
     45     public void stopRequesting(String packageName, String providerName) {
     46         PackageProviderKey key = new PackageProviderKey(packageName, providerName);
     47         PackageStatistics stats = statistics.get(key);
     48         if (stats != null) {
     49             stats.stopRequesting();
     50         }
     51     }
     52 
     53     /**
     54      * Signals that a package possibly switched background/foreground.
     55      *
     56      * @param packageName Name of package that has stopped requesting locations.
     57      * @param providerName Provider that is no longer being requested.
     58      */
     59     public void updateForeground(String packageName, String providerName, boolean isForeground) {
     60         PackageProviderKey key = new PackageProviderKey(packageName, providerName);
     61         PackageStatistics stats = statistics.get(key);
     62         if (stats != null) {
     63             stats.updateForeground(isForeground);
     64         }
     65     }
     66 
     67     /**
     68      * A key that holds both package and provider names.
     69      */
     70     public static class PackageProviderKey {
     71         /**
     72          * Name of package requesting location.
     73          */
     74         public final String packageName;
     75         /**
     76          * Name of provider being requested (e.g. "gps").
     77          */
     78         public final String providerName;
     79 
     80         public PackageProviderKey(String packageName, String providerName) {
     81             this.packageName = packageName;
     82             this.providerName = providerName;
     83         }
     84 
     85         @Override
     86         public boolean equals(Object other) {
     87             if (!(other instanceof PackageProviderKey)) {
     88                 return false;
     89             }
     90 
     91             PackageProviderKey otherKey = (PackageProviderKey) other;
     92             return packageName.equals(otherKey.packageName)
     93                     && providerName.equals(otherKey.providerName);
     94         }
     95 
     96         @Override
     97         public int hashCode() {
     98             return packageName.hashCode() + 31 * providerName.hashCode();
     99         }
    100     }
    101 
    102     /**
    103      * Usage statistics for a package/provider pair.
    104      */
    105     public static class PackageStatistics {
    106         // Time when this package first requested location.
    107         private final long mInitialElapsedTimeMs;
    108         // Number of active location requests this package currently has.
    109         private int mNumActiveRequests;
    110         // Time when this package most recently went from not requesting location to requesting.
    111         private long mLastActivitationElapsedTimeMs;
    112         // The fastest interval this package has ever requested.
    113         private long mFastestIntervalMs;
    114         // The slowest interval this package has ever requested.
    115         private long mSlowestIntervalMs;
    116         // The total time this app has requested location (not including currently running requests).
    117         private long mTotalDurationMs;
    118 
    119         // Time when this package most recently went to foreground, requesting location. 0 means
    120         // not currently in foreground.
    121         private long mLastForegroundElapsedTimeMs;
    122         // The time this app has requested location (not including currently running requests), while
    123         // in foreground.
    124         private long mForegroundDurationMs;
    125 
    126         private PackageStatistics() {
    127             mInitialElapsedTimeMs = SystemClock.elapsedRealtime();
    128             mNumActiveRequests = 0;
    129             mTotalDurationMs = 0;
    130             mFastestIntervalMs = Long.MAX_VALUE;
    131             mSlowestIntervalMs = 0;
    132             mForegroundDurationMs = 0;
    133             mLastForegroundElapsedTimeMs = 0;
    134         }
    135 
    136         private void startRequesting(long intervalMs) {
    137             if (mNumActiveRequests == 0) {
    138                 mLastActivitationElapsedTimeMs = SystemClock.elapsedRealtime();
    139             }
    140 
    141             if (intervalMs < mFastestIntervalMs) {
    142                 mFastestIntervalMs = intervalMs;
    143             }
    144 
    145             if (intervalMs > mSlowestIntervalMs) {
    146                 mSlowestIntervalMs = intervalMs;
    147             }
    148 
    149             mNumActiveRequests++;
    150         }
    151 
    152         private void updateForeground(boolean isForeground) {
    153             long nowElapsedTimeMs = SystemClock.elapsedRealtime();
    154             // if previous interval was foreground, accumulate before resetting start
    155             if (mLastForegroundElapsedTimeMs != 0) {
    156                 mForegroundDurationMs += (nowElapsedTimeMs - mLastForegroundElapsedTimeMs);
    157             }
    158             mLastForegroundElapsedTimeMs = isForeground ? nowElapsedTimeMs : 0;
    159         }
    160 
    161         private void stopRequesting() {
    162             if (mNumActiveRequests <= 0) {
    163                 // Shouldn't be a possible code path
    164                 Log.e(TAG, "Reference counting corrupted in usage statistics.");
    165                 return;
    166             }
    167 
    168             mNumActiveRequests--;
    169             if (mNumActiveRequests == 0) {
    170                 long lastDurationMs
    171                         = SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
    172                 mTotalDurationMs += lastDurationMs;
    173                 updateForeground(false);
    174             }
    175         }
    176 
    177         /**
    178          * Returns the duration that this request has been active.
    179          */
    180         public long getDurationMs() {
    181             long currentDurationMs = mTotalDurationMs;
    182             if (mNumActiveRequests > 0) {
    183                 currentDurationMs
    184                         += SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
    185             }
    186             return currentDurationMs;
    187         }
    188 
    189         /**
    190          * Returns the duration that this request has been active.
    191          */
    192         public long getForegroundDurationMs() {
    193             long currentDurationMs = mForegroundDurationMs;
    194             if (mLastForegroundElapsedTimeMs != 0 ) {
    195                 currentDurationMs
    196                         += SystemClock.elapsedRealtime() - mLastForegroundElapsedTimeMs;
    197             }
    198             return currentDurationMs;
    199         }
    200 
    201         /**
    202          * Returns the time since the initial request in ms.
    203          */
    204         public long getTimeSinceFirstRequestMs() {
    205             return SystemClock.elapsedRealtime() - mInitialElapsedTimeMs;
    206         }
    207 
    208         /**
    209          * Returns the fastest interval that has been tracked.
    210          */
    211         public long getFastestIntervalMs() {
    212             return mFastestIntervalMs;
    213         }
    214 
    215         /**
    216          * Returns the slowest interval that has been tracked.
    217          */
    218         public long getSlowestIntervalMs() {
    219             return mSlowestIntervalMs;
    220         }
    221 
    222         /**
    223          * Returns true if a request is active for these tracked statistics.
    224          */
    225         public boolean isActive() {
    226             return mNumActiveRequests > 0;
    227         }
    228 
    229         @Override
    230         public String toString() {
    231             StringBuilder s = new StringBuilder();
    232             if (mFastestIntervalMs == mSlowestIntervalMs) {
    233                 s.append("Interval ").append(mFastestIntervalMs / 1000).append(" seconds");
    234             } else {
    235                 s.append("Min interval ").append(mFastestIntervalMs / 1000).append(" seconds");
    236                 s.append(": Max interval ").append(mSlowestIntervalMs / 1000).append(" seconds");
    237             }
    238             s.append(": Duration requested ")
    239                     .append((getDurationMs() / 1000) / 60)
    240                     .append(" total, ")
    241                     .append((getForegroundDurationMs() / 1000) / 60)
    242                     .append(" foreground, out of the last ")
    243                     .append((getTimeSinceFirstRequestMs() / 1000) / 60)
    244                     .append(" minutes");
    245             if (isActive()) {
    246                 s.append(": Currently active");
    247             }
    248             return s.toString();
    249         }
    250     }
    251 }
    252