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