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 */ startRequesting(String packageName, String providerName, long intervalMs)27 public void startRequesting(String packageName, String providerName, long intervalMs) { 28 PackageProviderKey key = new PackageProviderKey(packageName, providerName); 29 PackageStatistics stats = statistics.get(key); 30 if (stats == null) { 31 stats = new PackageStatistics(); 32 statistics.put(key, stats); 33 } 34 stats.startRequesting(intervalMs); 35 } 36 37 /** 38 * Signals that a package has stopped requesting locations. 39 * 40 * @param packageName Name of package that has stopped requesting locations. 41 * @param providerName Provider that is no longer being requested. 42 */ stopRequesting(String packageName, String providerName)43 public void stopRequesting(String packageName, String providerName) { 44 PackageProviderKey key = new PackageProviderKey(packageName, providerName); 45 PackageStatistics stats = statistics.get(key); 46 if (stats != null) { 47 stats.stopRequesting(); 48 } else { 49 // This shouldn't be a possible code path. 50 Log.e(TAG, "Couldn't find package statistics when removing location request."); 51 } 52 } 53 54 /** 55 * A key that holds both package and provider names. 56 */ 57 public static class PackageProviderKey { 58 /** 59 * Name of package requesting location. 60 */ 61 public final String packageName; 62 /** 63 * Name of provider being requested (e.g. "gps"). 64 */ 65 public final String providerName; 66 PackageProviderKey(String packageName, String providerName)67 public PackageProviderKey(String packageName, String providerName) { 68 this.packageName = packageName; 69 this.providerName = providerName; 70 } 71 72 @Override equals(Object other)73 public boolean equals(Object other) { 74 if (!(other instanceof PackageProviderKey)) { 75 return false; 76 } 77 78 PackageProviderKey otherKey = (PackageProviderKey) other; 79 return packageName.equals(otherKey.packageName) 80 && providerName.equals(otherKey.providerName); 81 } 82 83 @Override hashCode()84 public int hashCode() { 85 return packageName.hashCode() + 31 * providerName.hashCode(); 86 } 87 } 88 89 /** 90 * Usage statistics for a package/provider pair. 91 */ 92 public static class PackageStatistics { 93 // Time when this package first requested location. 94 private final long mInitialElapsedTimeMs; 95 // Number of active location requests this package currently has. 96 private int mNumActiveRequests; 97 // Time when this package most recently went from not requesting location to requesting. 98 private long mLastActivitationElapsedTimeMs; 99 // The fastest interval this package has ever requested. 100 private long mFastestIntervalMs; 101 // The slowest interval this package has ever requested. 102 private long mSlowestIntervalMs; 103 // The total time this app has requested location (not including currently running requests). 104 private long mTotalDurationMs; 105 PackageStatistics()106 private PackageStatistics() { 107 mInitialElapsedTimeMs = SystemClock.elapsedRealtime(); 108 mNumActiveRequests = 0; 109 mTotalDurationMs = 0; 110 mFastestIntervalMs = Long.MAX_VALUE; 111 mSlowestIntervalMs = 0; 112 } 113 startRequesting(long intervalMs)114 private void startRequesting(long intervalMs) { 115 if (mNumActiveRequests == 0) { 116 mLastActivitationElapsedTimeMs = SystemClock.elapsedRealtime(); 117 } 118 119 if (intervalMs < mFastestIntervalMs) { 120 mFastestIntervalMs = intervalMs; 121 } 122 123 if (intervalMs > mSlowestIntervalMs) { 124 mSlowestIntervalMs = intervalMs; 125 } 126 127 mNumActiveRequests++; 128 } 129 stopRequesting()130 private void stopRequesting() { 131 if (mNumActiveRequests <= 0) { 132 // Shouldn't be a possible code path 133 Log.e(TAG, "Reference counting corrupted in usage statistics."); 134 return; 135 } 136 137 mNumActiveRequests--; 138 if (mNumActiveRequests == 0) { 139 long lastDurationMs 140 = SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs; 141 mTotalDurationMs += lastDurationMs; 142 } 143 } 144 145 /** 146 * Returns the duration that this request has been active. 147 */ getDurationMs()148 public long getDurationMs() { 149 long currentDurationMs = mTotalDurationMs; 150 if (mNumActiveRequests > 0) { 151 currentDurationMs 152 += SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs; 153 } 154 return currentDurationMs; 155 } 156 157 /** 158 * Returns the time since the initial request in ms. 159 */ getTimeSinceFirstRequestMs()160 public long getTimeSinceFirstRequestMs() { 161 return SystemClock.elapsedRealtime() - mInitialElapsedTimeMs; 162 } 163 164 /** 165 * Returns the fastest interval that has been tracked. 166 */ getFastestIntervalMs()167 public long getFastestIntervalMs() { 168 return mFastestIntervalMs; 169 } 170 171 /** 172 * Returns the slowest interval that has been tracked. 173 */ getSlowestIntervalMs()174 public long getSlowestIntervalMs() { 175 return mSlowestIntervalMs; 176 } 177 178 /** 179 * Returns true if a request is active for these tracked statistics. 180 */ isActive()181 public boolean isActive() { 182 return mNumActiveRequests > 0; 183 } 184 185 @Override toString()186 public String toString() { 187 StringBuilder s = new StringBuilder(); 188 if (mFastestIntervalMs == mSlowestIntervalMs) { 189 s.append("Interval ").append(mFastestIntervalMs / 1000).append(" seconds"); 190 } else { 191 s.append("Min interval ").append(mFastestIntervalMs / 1000).append(" seconds"); 192 s.append(": Max interval ").append(mSlowestIntervalMs / 1000).append(" seconds"); 193 } 194 s.append(": Duration requested ") 195 .append((getDurationMs() / 1000) / 60) 196 .append(" out of the last ") 197 .append((getTimeSinceFirstRequestMs() / 1000) / 60) 198 .append(" minutes"); 199 if (isActive()) { 200 s.append(": Currently active"); 201 } 202 return s.toString(); 203 } 204 } 205 } 206