1 /** 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 package com.android.server.usage; 17 18 import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED; 19 import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED; 20 import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED; 21 import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE; 22 import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY; 23 import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE; 24 import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN; 25 import static android.app.usage.UsageEvents.Event.END_OF_DAY; 26 import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK; 27 import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START; 28 import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP; 29 import static android.app.usage.UsageEvents.Event.KEYGUARD_HIDDEN; 30 import static android.app.usage.UsageEvents.Event.KEYGUARD_SHOWN; 31 import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION; 32 import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE; 33 import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE; 34 import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE; 35 import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; 36 import static android.app.usage.UsageEvents.Event.STANDBY_BUCKET_CHANGED; 37 import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION; 38 39 import android.app.usage.ConfigurationStats; 40 import android.app.usage.EventList; 41 import android.app.usage.EventStats; 42 import android.app.usage.UsageEvents.Event; 43 import android.app.usage.UsageStats; 44 import android.content.res.Configuration; 45 import android.util.ArrayMap; 46 import android.util.ArraySet; 47 import android.util.proto.ProtoInputStream; 48 49 import com.android.internal.annotations.VisibleForTesting; 50 51 import java.io.IOException; 52 import java.util.List; 53 54 public class IntervalStats { 55 public static final int CURRENT_MAJOR_VERSION = 1; 56 public static final int CURRENT_MINOR_VERSION = 1; 57 public int majorVersion = CURRENT_MAJOR_VERSION; 58 public int minorVersion = CURRENT_MINOR_VERSION; 59 public long beginTime; 60 public long endTime; 61 public long lastTimeSaved; 62 public final EventTracker interactiveTracker = new EventTracker(); 63 public final EventTracker nonInteractiveTracker = new EventTracker(); 64 public final EventTracker keyguardShownTracker = new EventTracker(); 65 public final EventTracker keyguardHiddenTracker = new EventTracker(); 66 public final ArrayMap<String, UsageStats> packageStats = new ArrayMap<>(); 67 public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>(); 68 public Configuration activeConfiguration; 69 public final EventList events = new EventList(); 70 71 // A string cache. This is important as when we're parsing XML files, we don't want to 72 // keep hundreds of strings that have the same contents. We will read the string 73 // and only keep it if it's not in the cache. The GC will take care of the 74 // strings that had identical copies in the cache. 75 public final ArraySet<String> mStringCache = new ArraySet<>(); 76 77 public static final class EventTracker { 78 public long curStartTime; 79 public long lastEventTime; 80 public long duration; 81 public int count; 82 commitTime(long timeStamp)83 public void commitTime(long timeStamp) { 84 if (curStartTime != 0) { 85 duration += timeStamp - curStartTime; 86 curStartTime = 0; 87 } 88 } 89 update(long timeStamp)90 public void update(long timeStamp) { 91 if (curStartTime == 0) { 92 // If we aren't already running, time to bump the count. 93 count++; 94 } 95 commitTime(timeStamp); 96 curStartTime = timeStamp; 97 lastEventTime = timeStamp; 98 } 99 addToEventStats(List<EventStats> out, int event, long beginTime, long endTime)100 void addToEventStats(List<EventStats> out, int event, long beginTime, long endTime) { 101 if (count != 0 || duration != 0) { 102 EventStats ev = new EventStats(); 103 ev.mEventType = event; 104 ev.mCount = count; 105 ev.mTotalTime = duration; 106 ev.mLastEventTime = lastEventTime; 107 ev.mBeginTimeStamp = beginTime; 108 ev.mEndTimeStamp = endTime; 109 out.add(ev); 110 } 111 } 112 113 } 114 IntervalStats()115 public IntervalStats() { 116 } 117 118 /** 119 * Gets the UsageStats object for the given package, or creates one and adds it internally. 120 */ getOrCreateUsageStats(String packageName)121 UsageStats getOrCreateUsageStats(String packageName) { 122 UsageStats usageStats = packageStats.get(packageName); 123 if (usageStats == null) { 124 usageStats = new UsageStats(); 125 usageStats.mPackageName = getCachedStringRef(packageName); 126 usageStats.mBeginTimeStamp = beginTime; 127 usageStats.mEndTimeStamp = endTime; 128 packageStats.put(usageStats.mPackageName, usageStats); 129 } 130 return usageStats; 131 } 132 133 /** 134 * Gets the ConfigurationStats object for the given configuration, or creates one and adds it 135 * internally. 136 */ getOrCreateConfigurationStats(Configuration config)137 ConfigurationStats getOrCreateConfigurationStats(Configuration config) { 138 ConfigurationStats configStats = configurations.get(config); 139 if (configStats == null) { 140 configStats = new ConfigurationStats(); 141 configStats.mBeginTimeStamp = beginTime; 142 configStats.mEndTimeStamp = endTime; 143 configStats.mConfiguration = config; 144 configurations.put(config, configStats); 145 } 146 return configStats; 147 } 148 149 /** 150 * Builds a UsageEvents.Event, but does not add it internally. 151 */ buildEvent(String packageName, String className)152 Event buildEvent(String packageName, String className) { 153 Event event = new Event(); 154 event.mPackage = getCachedStringRef(packageName); 155 if (className != null) { 156 event.mClass = getCachedStringRef(className); 157 } 158 return event; 159 } 160 161 /** 162 * Builds a UsageEvents.Event from a proto, but does not add it internally. 163 * Built here to take advantage of the cached String Refs 164 */ buildEvent(ProtoInputStream parser, List<String> stringPool)165 Event buildEvent(ProtoInputStream parser, List<String> stringPool) 166 throws IOException { 167 final Event event = new Event(); 168 while (true) { 169 switch (parser.nextField()) { 170 case (int) IntervalStatsProto.Event.PACKAGE: 171 event.mPackage = getCachedStringRef( 172 parser.readString(IntervalStatsProto.Event.PACKAGE)); 173 break; 174 case (int) IntervalStatsProto.Event.PACKAGE_INDEX: 175 event.mPackage = getCachedStringRef(stringPool.get( 176 parser.readInt(IntervalStatsProto.Event.PACKAGE_INDEX) - 1)); 177 break; 178 case (int) IntervalStatsProto.Event.CLASS: 179 event.mClass = getCachedStringRef( 180 parser.readString(IntervalStatsProto.Event.CLASS)); 181 break; 182 case (int) IntervalStatsProto.Event.CLASS_INDEX: 183 event.mClass = getCachedStringRef(stringPool.get( 184 parser.readInt(IntervalStatsProto.Event.CLASS_INDEX) - 1)); 185 break; 186 case (int) IntervalStatsProto.Event.TIME_MS: 187 event.mTimeStamp = beginTime + parser.readLong( 188 IntervalStatsProto.Event.TIME_MS); 189 break; 190 case (int) IntervalStatsProto.Event.FLAGS: 191 event.mFlags = parser.readInt(IntervalStatsProto.Event.FLAGS); 192 break; 193 case (int) IntervalStatsProto.Event.TYPE: 194 event.mEventType = parser.readInt(IntervalStatsProto.Event.TYPE); 195 break; 196 case (int) IntervalStatsProto.Event.CONFIG: 197 event.mConfiguration = new Configuration(); 198 event.mConfiguration.readFromProto(parser, IntervalStatsProto.Event.CONFIG); 199 break; 200 case (int) IntervalStatsProto.Event.SHORTCUT_ID: 201 event.mShortcutId = parser.readString( 202 IntervalStatsProto.Event.SHORTCUT_ID).intern(); 203 break; 204 case (int) IntervalStatsProto.Event.STANDBY_BUCKET: 205 event.mBucketAndReason = parser.readInt( 206 IntervalStatsProto.Event.STANDBY_BUCKET); 207 break; 208 case (int) IntervalStatsProto.Event.NOTIFICATION_CHANNEL: 209 event.mNotificationChannelId = parser.readString( 210 IntervalStatsProto.Event.NOTIFICATION_CHANNEL); 211 break; 212 case (int) IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX: 213 event.mNotificationChannelId = getCachedStringRef(stringPool.get( 214 parser.readInt(IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX) 215 - 1)); 216 break; 217 case (int) IntervalStatsProto.Event.INSTANCE_ID: 218 event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID); 219 break; 220 case (int) IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX: 221 event.mTaskRootPackage = getCachedStringRef(stringPool.get( 222 parser.readInt(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX) - 1)); 223 break; 224 case (int) IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX: 225 event.mTaskRootClass = getCachedStringRef(stringPool.get( 226 parser.readInt(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX) - 1)); 227 break; 228 case ProtoInputStream.NO_MORE_FIELDS: 229 // Handle default values for certain events types 230 switch (event.mEventType) { 231 case CONFIGURATION_CHANGE: 232 if (event.mConfiguration == null) { 233 event.mConfiguration = new Configuration(); 234 } 235 break; 236 case SHORTCUT_INVOCATION: 237 if (event.mShortcutId == null) { 238 event.mShortcutId = ""; 239 } 240 break; 241 case NOTIFICATION_INTERRUPTION: 242 if (event.mNotificationChannelId == null) { 243 event.mNotificationChannelId = ""; 244 } 245 break; 246 } 247 if (event.mTimeStamp == 0) { 248 //mTimestamp not set, assume default value 0 plus beginTime 249 event.mTimeStamp = beginTime; 250 } 251 return event; 252 } 253 } 254 } 255 isStatefulEvent(int eventType)256 private boolean isStatefulEvent(int eventType) { 257 switch (eventType) { 258 case ACTIVITY_RESUMED: 259 case ACTIVITY_PAUSED: 260 case ACTIVITY_STOPPED: 261 case FOREGROUND_SERVICE_START: 262 case FOREGROUND_SERVICE_STOP: 263 case END_OF_DAY: 264 case ROLLOVER_FOREGROUND_SERVICE: 265 case CONTINUE_PREVIOUS_DAY: 266 case CONTINUING_FOREGROUND_SERVICE: 267 case DEVICE_SHUTDOWN: 268 return true; 269 } 270 return false; 271 } 272 273 /** 274 * Returns whether the event type is one caused by user visible 275 * interaction. Excludes those that are internally generated. 276 */ isUserVisibleEvent(int eventType)277 private boolean isUserVisibleEvent(int eventType) { 278 return eventType != SYSTEM_INTERACTION 279 && eventType != STANDBY_BUCKET_CHANGED; 280 } 281 282 /** 283 * Update the IntervalStats by a activity or foreground service event. 284 * @param packageName package name of this event. Is null if event targets to all packages. 285 * @param className class name of a activity or foreground service, could be null to if this 286 * is sent to all activities/services in this package. 287 * @param timeStamp Epoch timestamp in milliseconds. 288 * @param eventType event type as in {@link Event} 289 * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken. 290 * if className is not an activity, instanceId is not used. 291 * @hide 292 */ 293 @VisibleForTesting update(String packageName, String className, long timeStamp, int eventType, int instanceId)294 public void update(String packageName, String className, long timeStamp, int eventType, 295 int instanceId) { 296 if (eventType == DEVICE_SHUTDOWN 297 || eventType == FLUSH_TO_DISK) { 298 // DEVICE_SHUTDOWN and FLUSH_TO_DISK are sent to all packages. 299 final int size = packageStats.size(); 300 for (int i = 0; i < size; i++) { 301 UsageStats usageStats = packageStats.valueAt(i); 302 usageStats.update(null, timeStamp, eventType, instanceId); 303 } 304 } else { 305 UsageStats usageStats = getOrCreateUsageStats(packageName); 306 usageStats.update(className, timeStamp, eventType, instanceId); 307 } 308 if (timeStamp > endTime) { 309 endTime = timeStamp; 310 } 311 } 312 313 /** 314 * @hide 315 */ 316 @VisibleForTesting addEvent(Event event)317 public void addEvent(Event event) { 318 // Cache common use strings 319 event.mPackage = getCachedStringRef(event.mPackage); 320 if (event.mClass != null) { 321 event.mClass = getCachedStringRef(event.mClass); 322 } 323 if (event.mTaskRootPackage != null) { 324 event.mTaskRootPackage = getCachedStringRef(event.mTaskRootPackage); 325 } 326 if (event.mTaskRootClass != null) { 327 event.mTaskRootClass = getCachedStringRef(event.mTaskRootClass); 328 } 329 if (event.mEventType == NOTIFICATION_INTERRUPTION) { 330 event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId); 331 } 332 events.insert(event); 333 if (event.mTimeStamp > endTime) { 334 endTime = event.mTimeStamp; 335 } 336 } 337 updateChooserCounts(String packageName, String category, String action)338 void updateChooserCounts(String packageName, String category, String action) { 339 UsageStats usageStats = getOrCreateUsageStats(packageName); 340 if (usageStats.mChooserCounts == null) { 341 usageStats.mChooserCounts = new ArrayMap<>(); 342 } 343 ArrayMap<String, Integer> chooserCounts; 344 final int idx = usageStats.mChooserCounts.indexOfKey(action); 345 if (idx < 0) { 346 chooserCounts = new ArrayMap<>(); 347 usageStats.mChooserCounts.put(action, chooserCounts); 348 } else { 349 chooserCounts = usageStats.mChooserCounts.valueAt(idx); 350 } 351 int currentCount = chooserCounts.getOrDefault(category, 0); 352 chooserCounts.put(category, currentCount + 1); 353 } 354 updateConfigurationStats(Configuration config, long timeStamp)355 void updateConfigurationStats(Configuration config, long timeStamp) { 356 if (activeConfiguration != null) { 357 ConfigurationStats activeStats = configurations.get(activeConfiguration); 358 activeStats.mTotalTimeActive += timeStamp - activeStats.mLastTimeActive; 359 activeStats.mLastTimeActive = timeStamp - 1; 360 } 361 362 if (config != null) { 363 ConfigurationStats configStats = getOrCreateConfigurationStats(config); 364 configStats.mLastTimeActive = timeStamp; 365 configStats.mActivationCount += 1; 366 activeConfiguration = configStats.mConfiguration; 367 } 368 if (timeStamp > endTime) { 369 endTime = timeStamp; 370 } 371 } 372 incrementAppLaunchCount(String packageName)373 void incrementAppLaunchCount(String packageName) { 374 UsageStats usageStats = getOrCreateUsageStats(packageName); 375 usageStats.mAppLaunchCount += 1; 376 } 377 commitTime(long timeStamp)378 void commitTime(long timeStamp) { 379 interactiveTracker.commitTime(timeStamp); 380 nonInteractiveTracker.commitTime(timeStamp); 381 keyguardShownTracker.commitTime(timeStamp); 382 keyguardHiddenTracker.commitTime(timeStamp); 383 } 384 updateScreenInteractive(long timeStamp)385 void updateScreenInteractive(long timeStamp) { 386 interactiveTracker.update(timeStamp); 387 nonInteractiveTracker.commitTime(timeStamp); 388 } 389 updateScreenNonInteractive(long timeStamp)390 void updateScreenNonInteractive(long timeStamp) { 391 nonInteractiveTracker.update(timeStamp); 392 interactiveTracker.commitTime(timeStamp); 393 } 394 updateKeyguardShown(long timeStamp)395 void updateKeyguardShown(long timeStamp) { 396 keyguardShownTracker.update(timeStamp); 397 keyguardHiddenTracker.commitTime(timeStamp); 398 } 399 updateKeyguardHidden(long timeStamp)400 void updateKeyguardHidden(long timeStamp) { 401 keyguardHiddenTracker.update(timeStamp); 402 keyguardShownTracker.commitTime(timeStamp); 403 } 404 addEventStatsTo(List<EventStats> out)405 void addEventStatsTo(List<EventStats> out) { 406 interactiveTracker.addToEventStats(out, SCREEN_INTERACTIVE, 407 beginTime, endTime); 408 nonInteractiveTracker.addToEventStats(out, SCREEN_NON_INTERACTIVE, 409 beginTime, endTime); 410 keyguardShownTracker.addToEventStats(out, KEYGUARD_SHOWN, 411 beginTime, endTime); 412 keyguardHiddenTracker.addToEventStats(out, KEYGUARD_HIDDEN, 413 beginTime, endTime); 414 } 415 getCachedStringRef(String str)416 private String getCachedStringRef(String str) { 417 final int index = mStringCache.indexOf(str); 418 if (index < 0) { 419 mStringCache.add(str); 420 return str; 421 } 422 return mStringCache.valueAt(index); 423 } 424 425 /** 426 * When an IntervalStats object is deserialized, if the object's version number 427 * is lower than current version number, optionally perform a upgrade. 428 */ upgradeIfNeeded()429 void upgradeIfNeeded() { 430 // We only uprade on majorVersion change, no need to upgrade on minorVersion change. 431 if (!(majorVersion < CURRENT_MAJOR_VERSION)) { 432 return; 433 } 434 /* 435 Optional upgrade code here. 436 */ 437 majorVersion = CURRENT_MAJOR_VERSION; 438 } 439 } 440