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, boolean isForeground)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 */ stopRequesting(String packageName, String providerName)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 */ updateForeground(String packageName, String providerName, boolean isForeground)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 PackageProviderKey(String packageName, String providerName)80 public PackageProviderKey(String packageName, String providerName) { 81 this.packageName = packageName; 82 this.providerName = providerName; 83 } 84 85 @Override equals(Object other)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 hashCode()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 // Time when package last went dormant (stopped requesting location) 127 private long mLastStopElapsedTimeMs; 128 PackageStatistics()129 private PackageStatistics() { 130 mInitialElapsedTimeMs = SystemClock.elapsedRealtime(); 131 mNumActiveRequests = 0; 132 mTotalDurationMs = 0; 133 mFastestIntervalMs = Long.MAX_VALUE; 134 mSlowestIntervalMs = 0; 135 mForegroundDurationMs = 0; 136 mLastForegroundElapsedTimeMs = 0; 137 mLastStopElapsedTimeMs = 0; 138 } 139 startRequesting(long intervalMs)140 private void startRequesting(long intervalMs) { 141 if (mNumActiveRequests == 0) { 142 mLastActivitationElapsedTimeMs = SystemClock.elapsedRealtime(); 143 } 144 145 if (intervalMs < mFastestIntervalMs) { 146 mFastestIntervalMs = intervalMs; 147 } 148 149 if (intervalMs > mSlowestIntervalMs) { 150 mSlowestIntervalMs = intervalMs; 151 } 152 153 mNumActiveRequests++; 154 } 155 updateForeground(boolean isForeground)156 private void updateForeground(boolean isForeground) { 157 long nowElapsedTimeMs = SystemClock.elapsedRealtime(); 158 // if previous interval was foreground, accumulate before resetting start 159 if (mLastForegroundElapsedTimeMs != 0) { 160 mForegroundDurationMs += (nowElapsedTimeMs - mLastForegroundElapsedTimeMs); 161 } 162 mLastForegroundElapsedTimeMs = isForeground ? nowElapsedTimeMs : 0; 163 } 164 stopRequesting()165 private void stopRequesting() { 166 if (mNumActiveRequests <= 0) { 167 // Shouldn't be a possible code path 168 Log.e(TAG, "Reference counting corrupted in usage statistics."); 169 return; 170 } 171 172 mNumActiveRequests--; 173 if (mNumActiveRequests == 0) { 174 mLastStopElapsedTimeMs = SystemClock.elapsedRealtime(); 175 long lastDurationMs = mLastStopElapsedTimeMs - mLastActivitationElapsedTimeMs; 176 mTotalDurationMs += lastDurationMs; 177 updateForeground(false); 178 } 179 } 180 181 /** 182 * Returns the duration that this request has been active. 183 */ getDurationMs()184 public long getDurationMs() { 185 long currentDurationMs = mTotalDurationMs; 186 if (mNumActiveRequests > 0) { 187 currentDurationMs 188 += SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs; 189 } 190 return currentDurationMs; 191 } 192 193 /** 194 * Returns the duration that this request has been active. 195 */ getForegroundDurationMs()196 public long getForegroundDurationMs() { 197 long currentDurationMs = mForegroundDurationMs; 198 if (mLastForegroundElapsedTimeMs != 0 ) { 199 currentDurationMs 200 += SystemClock.elapsedRealtime() - mLastForegroundElapsedTimeMs; 201 } 202 return currentDurationMs; 203 } 204 205 /** 206 * Returns the time since the initial request in ms. 207 */ getTimeSinceFirstRequestMs()208 public long getTimeSinceFirstRequestMs() { 209 return SystemClock.elapsedRealtime() - mInitialElapsedTimeMs; 210 } 211 212 /** 213 * Returns the time since the last request stopped in ms. 214 */ getTimeSinceLastRequestStoppedMs()215 public long getTimeSinceLastRequestStoppedMs() { 216 return SystemClock.elapsedRealtime() - mLastStopElapsedTimeMs; 217 } 218 219 /** 220 * Returns the fastest interval that has been tracked. 221 */ getFastestIntervalMs()222 public long getFastestIntervalMs() { 223 return mFastestIntervalMs; 224 } 225 226 /** 227 * Returns the slowest interval that has been tracked. 228 */ getSlowestIntervalMs()229 public long getSlowestIntervalMs() { 230 return mSlowestIntervalMs; 231 } 232 233 /** 234 * Returns true if a request is active for these tracked statistics. 235 */ isActive()236 public boolean isActive() { 237 return mNumActiveRequests > 0; 238 } 239 240 @Override toString()241 public String toString() { 242 StringBuilder s = new StringBuilder(); 243 if (mFastestIntervalMs == mSlowestIntervalMs) { 244 s.append("Interval ").append(mFastestIntervalMs / 1000).append(" seconds"); 245 } else { 246 s.append("Min interval ").append(mFastestIntervalMs / 1000).append(" seconds"); 247 s.append(": Max interval ").append(mSlowestIntervalMs / 1000).append(" seconds"); 248 } 249 s.append(": Duration requested ") 250 .append((getDurationMs() / 1000) / 60) 251 .append(" total, ") 252 .append((getForegroundDurationMs() / 1000) / 60) 253 .append(" foreground, out of the last ") 254 .append((getTimeSinceFirstRequestMs() / 1000) / 60) 255 .append(" minutes"); 256 if (isActive()) { 257 s.append(": Currently active"); 258 } else { 259 s.append(": Last active ") 260 .append((getTimeSinceLastRequestStoppedMs() / 1000) / 60) 261 .append(" minutes ago"); 262 } 263 return s.toString(); 264 } 265 } 266 } 267