UsageEvents.java revision 1918ef7569e90c70246e535478b26732b82d92d3
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 */ 16package android.app.usage; 17 18import android.annotation.IntDef; 19import android.annotation.SystemApi; 20import android.content.res.Configuration; 21import android.os.Parcel; 22import android.os.Parcelable; 23 24import java.lang.annotation.Retention; 25import java.lang.annotation.RetentionPolicy; 26import java.util.Arrays; 27import java.util.List; 28 29/** 30 * A result returned from {@link android.app.usage.UsageStatsManager#queryEvents(long, long)} 31 * from which to read {@link android.app.usage.UsageEvents.Event} objects. 32 */ 33public final class UsageEvents implements Parcelable { 34 35 /** @hide */ 36 public static final String INSTANT_APP_PACKAGE_NAME = "android.instant_app"; 37 38 /** @hide */ 39 public static final String INSTANT_APP_CLASS_NAME = "android.instant_class"; 40 41 /** 42 * An event representing a state change for a component. 43 */ 44 public static final class Event { 45 46 /** 47 * No event type. 48 */ 49 public static final int NONE = 0; 50 51 /** 52 * An event type denoting that a component moved to the foreground. 53 */ 54 public static final int MOVE_TO_FOREGROUND = 1; 55 56 /** 57 * An event type denoting that a component moved to the background. 58 */ 59 public static final int MOVE_TO_BACKGROUND = 2; 60 61 /** 62 * An event type denoting that a component was in the foreground when the stats 63 * rolled-over. This is effectively treated as a {@link #MOVE_TO_BACKGROUND}. 64 * {@hide} 65 */ 66 public static final int END_OF_DAY = 3; 67 68 /** 69 * An event type denoting that a component was in the foreground the previous day. 70 * This is effectively treated as a {@link #MOVE_TO_FOREGROUND}. 71 * {@hide} 72 */ 73 public static final int CONTINUE_PREVIOUS_DAY = 4; 74 75 /** 76 * An event type denoting that the device configuration has changed. 77 */ 78 public static final int CONFIGURATION_CHANGE = 5; 79 80 /** 81 * An event type denoting that a package was interacted with in some way by the system. 82 * @hide 83 */ 84 @SystemApi 85 public static final int SYSTEM_INTERACTION = 6; 86 87 /** 88 * An event type denoting that a package was interacted with in some way by the user. 89 */ 90 public static final int USER_INTERACTION = 7; 91 92 /** 93 * An event type denoting that an action equivalent to a ShortcutInfo is taken by the user. 94 * 95 * @see android.content.pm.ShortcutManager#reportShortcutUsed(String) 96 */ 97 public static final int SHORTCUT_INVOCATION = 8; 98 99 /** 100 * An event type denoting that a package was selected by the user for ChooserActivity. 101 * @hide 102 */ 103 public static final int CHOOSER_ACTION = 9; 104 105 /** 106 * An event type denoting that a notification was viewed by the user. 107 * @hide 108 */ 109 @SystemApi 110 public static final int NOTIFICATION_SEEN = 10; 111 112 /** 113 * An event type denoting a change in App Standby Bucket. The new bucket can be 114 * retrieved by calling {@link #getStandbyBucket()}. 115 * 116 * @see UsageStatsManager#getAppStandbyBucket() 117 */ 118 public static final int STANDBY_BUCKET_CHANGED = 11; 119 120 /** 121 * An event type denoting that an app posted an interruptive notification. Visual and 122 * audible interruptions are included. 123 * @hide 124 */ 125 @SystemApi 126 public static final int NOTIFICATION_INTERRUPTION = 12; 127 128 /** 129 * A Slice was pinned by the default launcher or the default assistant. 130 * @hide 131 */ 132 @SystemApi 133 public static final int SLICE_PINNED_PRIV = 13; 134 135 /** 136 * A Slice was pinned by an app. 137 * @hide 138 */ 139 @SystemApi 140 public static final int SLICE_PINNED = 14; 141 142 /** @hide */ 143 public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; 144 145 /** @hide */ 146 @IntDef(flag = true, prefix = { "FLAG_" }, value = { 147 FLAG_IS_PACKAGE_INSTANT_APP, 148 }) 149 @Retention(RetentionPolicy.SOURCE) 150 public @interface EventFlags {} 151 152 /** 153 * {@hide} 154 */ 155 public String mPackage; 156 157 /** 158 * {@hide} 159 */ 160 public String mClass; 161 162 /** 163 * {@hide} 164 */ 165 public long mTimeStamp; 166 167 /** 168 * {@hide} 169 */ 170 public int mEventType; 171 172 /** 173 * Only present for {@link #CONFIGURATION_CHANGE} event types. 174 * {@hide} 175 */ 176 public Configuration mConfiguration; 177 178 /** 179 * ID of the shortcut. 180 * Only present for {@link #SHORTCUT_INVOCATION} event types. 181 * {@hide} 182 */ 183 public String mShortcutId; 184 185 /** 186 * Action type passed to ChooserActivity 187 * Only present for {@link #CHOOSER_ACTION} event types. 188 * {@hide} 189 */ 190 public String mAction; 191 192 /** 193 * Content type passed to ChooserActivity. 194 * Only present for {@link #CHOOSER_ACTION} event types. 195 * {@hide} 196 */ 197 public String mContentType; 198 199 /** 200 * Content annotations passed to ChooserActivity. 201 * Only present for {@link #CHOOSER_ACTION} event types. 202 * {@hide} 203 */ 204 public String[] mContentAnnotations; 205 206 /** 207 * The app standby bucket assigned and reason. Bucket is the high order 16 bits, reason 208 * is the low order 16 bits. 209 * Only present for {@link #STANDBY_BUCKET_CHANGED} event types 210 * {@hide} 211 */ 212 public int mBucketAndReason; 213 214 /** 215 * The id of the {@link android.app.NotificationChannel} to which an interruptive 216 * notification was posted. 217 * Only present for {@link #NOTIFICATION_INTERRUPTION} event types. 218 * {@hide} 219 */ 220 public String mNotificationChannelId; 221 222 /** @hide */ 223 @EventFlags 224 public int mFlags; 225 226 public Event() { 227 } 228 229 /** @hide */ 230 public Event(Event orig) { 231 mPackage = orig.mPackage; 232 mClass = orig.mClass; 233 mTimeStamp = orig.mTimeStamp; 234 mEventType = orig.mEventType; 235 mConfiguration = orig.mConfiguration; 236 mShortcutId = orig.mShortcutId; 237 mAction = orig.mAction; 238 mContentType = orig.mContentType; 239 mContentAnnotations = orig.mContentAnnotations; 240 mFlags = orig.mFlags; 241 mBucketAndReason = orig.mBucketAndReason; 242 mNotificationChannelId = orig.mNotificationChannelId; 243 } 244 245 /** 246 * The package name of the source of this event. 247 */ 248 public String getPackageName() { 249 return mPackage; 250 } 251 252 /** 253 * The class name of the source of this event. This may be null for 254 * certain events. 255 */ 256 public String getClassName() { 257 return mClass; 258 } 259 260 /** 261 * The time at which this event occurred, measured in milliseconds since the epoch. 262 * <p/> 263 * See {@link System#currentTimeMillis()}. 264 */ 265 public long getTimeStamp() { 266 return mTimeStamp; 267 } 268 269 /** 270 * The event type. 271 * 272 * @see #MOVE_TO_BACKGROUND 273 * @see #MOVE_TO_FOREGROUND 274 * @see #CONFIGURATION_CHANGE 275 * @see #USER_INTERACTION 276 * @see #STANDBY_BUCKET_CHANGED 277 */ 278 public int getEventType() { 279 return mEventType; 280 } 281 282 /** 283 * Returns a {@link Configuration} for this event if the event is of type 284 * {@link #CONFIGURATION_CHANGE}, otherwise it returns null. 285 */ 286 public Configuration getConfiguration() { 287 return mConfiguration; 288 } 289 290 /** 291 * Returns the ID of a {@link android.content.pm.ShortcutInfo} for this event 292 * if the event is of type {@link #SHORTCUT_INVOCATION}, otherwise it returns null. 293 * 294 * @see android.content.pm.ShortcutManager#reportShortcutUsed(String) 295 */ 296 public String getShortcutId() { 297 return mShortcutId; 298 } 299 300 /** 301 * Returns the standby bucket of the app, if the event is of type 302 * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. 303 * @return the standby bucket associated with the event. 304 * 305 */ 306 public int getStandbyBucket() { 307 return (mBucketAndReason & 0xFFFF0000) >>> 16; 308 } 309 310 /** 311 * Returns the reason for the bucketing, if the event is of type 312 * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. Reason values include 313 * the main reason which is one of REASON_MAIN_*, OR'ed with REASON_SUB_*, if there 314 * are sub-reasons for the main reason, such as REASON_SUB_USAGE_* when the main reason 315 * is REASON_MAIN_USAGE. 316 * @hide 317 */ 318 public int getStandbyReason() { 319 return mBucketAndReason & 0x0000FFFF; 320 } 321 322 /** 323 * Returns the ID of the {@link android.app.NotificationChannel} for this event if the 324 * event is of type {@link #NOTIFICATION_INTERRUPTION}, otherwise it returns null; 325 * @hide 326 */ 327 @SystemApi 328 public String getNotificationChannelId() { 329 return mNotificationChannelId; 330 } 331 332 /** @hide */ 333 public Event getObfuscatedIfInstantApp() { 334 if ((mFlags & FLAG_IS_PACKAGE_INSTANT_APP) == 0) { 335 return this; 336 } 337 final Event ret = new Event(this); 338 ret.mPackage = INSTANT_APP_PACKAGE_NAME; 339 ret.mClass = INSTANT_APP_CLASS_NAME; 340 341 // Note there are other string fields too, but they're for app shortcuts and choosers, 342 // which instant apps can't use anyway, so there's no need to hide them. 343 return ret; 344 } 345 } 346 347 // Only used when creating the resulting events. Not used for reading/unparceling. 348 private List<Event> mEventsToWrite = null; 349 350 // Only used for reading/unparceling events. 351 private Parcel mParcel = null; 352 private final int mEventCount; 353 354 private int mIndex = 0; 355 356 /* 357 * In order to save space, since ComponentNames will be duplicated everywhere, 358 * we use a map and index into it. 359 */ 360 private String[] mStringPool; 361 362 /** 363 * Construct the iterator from a parcel. 364 * {@hide} 365 */ 366 public UsageEvents(Parcel in) { 367 mEventCount = in.readInt(); 368 mIndex = in.readInt(); 369 if (mEventCount > 0) { 370 mStringPool = in.createStringArray(); 371 372 final int listByteLength = in.readInt(); 373 final int positionInParcel = in.readInt(); 374 mParcel = Parcel.obtain(); 375 mParcel.setDataPosition(0); 376 mParcel.appendFrom(in, in.dataPosition(), listByteLength); 377 mParcel.setDataSize(mParcel.dataPosition()); 378 mParcel.setDataPosition(positionInParcel); 379 } 380 } 381 382 /** 383 * Create an empty iterator. 384 * {@hide} 385 */ 386 UsageEvents() { 387 mEventCount = 0; 388 } 389 390 /** 391 * Construct the iterator in preparation for writing it to a parcel. 392 * {@hide} 393 */ 394 public UsageEvents(List<Event> events, String[] stringPool) { 395 mStringPool = stringPool; 396 mEventCount = events.size(); 397 mEventsToWrite = events; 398 } 399 400 /** 401 * Returns whether or not there are more events to read using 402 * {@link #getNextEvent(android.app.usage.UsageEvents.Event)}. 403 * 404 * @return true if there are more events, false otherwise. 405 */ 406 public boolean hasNextEvent() { 407 return mIndex < mEventCount; 408 } 409 410 /** 411 * Retrieve the next {@link android.app.usage.UsageEvents.Event} from the collection and put the 412 * resulting data into {@code eventOut}. 413 * 414 * @param eventOut The {@link android.app.usage.UsageEvents.Event} object that will receive the 415 * next event data. 416 * @return true if an event was available, false if there are no more events. 417 */ 418 public boolean getNextEvent(Event eventOut) { 419 if (mIndex >= mEventCount) { 420 return false; 421 } 422 423 readEventFromParcel(mParcel, eventOut); 424 425 mIndex++; 426 if (mIndex >= mEventCount) { 427 mParcel.recycle(); 428 mParcel = null; 429 } 430 return true; 431 } 432 433 /** 434 * Resets the collection so that it can be iterated over from the beginning. 435 * 436 * @hide When this object is iterated to completion, the parcel is destroyed and 437 * so resetToStart doesn't work. 438 */ 439 public void resetToStart() { 440 mIndex = 0; 441 if (mParcel != null) { 442 mParcel.setDataPosition(0); 443 } 444 } 445 446 private int findStringIndex(String str) { 447 final int index = Arrays.binarySearch(mStringPool, str); 448 if (index < 0) { 449 throw new IllegalStateException("String '" + str + "' is not in the string pool"); 450 } 451 return index; 452 } 453 454 /** 455 * Writes a single event to the parcel. Modify this when updating {@link Event}. 456 */ 457 private void writeEventToParcel(Event event, Parcel p, int flags) { 458 final int packageIndex; 459 if (event.mPackage != null) { 460 packageIndex = findStringIndex(event.mPackage); 461 } else { 462 packageIndex = -1; 463 } 464 465 final int classIndex; 466 if (event.mClass != null) { 467 classIndex = findStringIndex(event.mClass); 468 } else { 469 classIndex = -1; 470 } 471 p.writeInt(packageIndex); 472 p.writeInt(classIndex); 473 p.writeInt(event.mEventType); 474 p.writeLong(event.mTimeStamp); 475 476 switch (event.mEventType) { 477 case Event.CONFIGURATION_CHANGE: 478 event.mConfiguration.writeToParcel(p, flags); 479 break; 480 case Event.SHORTCUT_INVOCATION: 481 p.writeString(event.mShortcutId); 482 break; 483 case Event.CHOOSER_ACTION: 484 p.writeString(event.mAction); 485 p.writeString(event.mContentType); 486 p.writeStringArray(event.mContentAnnotations); 487 break; 488 case Event.STANDBY_BUCKET_CHANGED: 489 p.writeInt(event.mBucketAndReason); 490 break; 491 case Event.NOTIFICATION_INTERRUPTION: 492 p.writeString(event.mNotificationChannelId); 493 break; 494 } 495 } 496 497 /** 498 * Reads a single event from the parcel. Modify this when updating {@link Event}. 499 */ 500 private void readEventFromParcel(Parcel p, Event eventOut) { 501 final int packageIndex = p.readInt(); 502 if (packageIndex >= 0) { 503 eventOut.mPackage = mStringPool[packageIndex]; 504 } else { 505 eventOut.mPackage = null; 506 } 507 508 final int classIndex = p.readInt(); 509 if (classIndex >= 0) { 510 eventOut.mClass = mStringPool[classIndex]; 511 } else { 512 eventOut.mClass = null; 513 } 514 eventOut.mEventType = p.readInt(); 515 eventOut.mTimeStamp = p.readLong(); 516 517 // Fill out the event-dependant fields. 518 eventOut.mConfiguration = null; 519 eventOut.mShortcutId = null; 520 eventOut.mAction = null; 521 eventOut.mContentType = null; 522 eventOut.mContentAnnotations = null; 523 eventOut.mNotificationChannelId = null; 524 525 switch (eventOut.mEventType) { 526 case Event.CONFIGURATION_CHANGE: 527 // Extract the configuration for configuration change events. 528 eventOut.mConfiguration = Configuration.CREATOR.createFromParcel(p); 529 break; 530 case Event.SHORTCUT_INVOCATION: 531 eventOut.mShortcutId = p.readString(); 532 break; 533 case Event.CHOOSER_ACTION: 534 eventOut.mAction = p.readString(); 535 eventOut.mContentType = p.readString(); 536 eventOut.mContentAnnotations = p.createStringArray(); 537 break; 538 case Event.STANDBY_BUCKET_CHANGED: 539 eventOut.mBucketAndReason = p.readInt(); 540 break; 541 case Event.NOTIFICATION_INTERRUPTION: 542 eventOut.mNotificationChannelId = p.readString(); 543 break; 544 } 545 } 546 547 @Override 548 public int describeContents() { 549 return 0; 550 } 551 552 @Override 553 public void writeToParcel(Parcel dest, int flags) { 554 dest.writeInt(mEventCount); 555 dest.writeInt(mIndex); 556 if (mEventCount > 0) { 557 dest.writeStringArray(mStringPool); 558 559 if (mEventsToWrite != null) { 560 // Write out the events 561 Parcel p = Parcel.obtain(); 562 try { 563 p.setDataPosition(0); 564 for (int i = 0; i < mEventCount; i++) { 565 final Event event = mEventsToWrite.get(i); 566 writeEventToParcel(event, p, flags); 567 } 568 569 final int listByteLength = p.dataPosition(); 570 571 // Write the total length of the data. 572 dest.writeInt(listByteLength); 573 574 // Write our current position into the data. 575 dest.writeInt(0); 576 577 // Write the data. 578 dest.appendFrom(p, 0, listByteLength); 579 } finally { 580 p.recycle(); 581 } 582 583 } else if (mParcel != null) { 584 // Write the total length of the data. 585 dest.writeInt(mParcel.dataSize()); 586 587 // Write out current position into the data. 588 dest.writeInt(mParcel.dataPosition()); 589 590 // Write the data. 591 dest.appendFrom(mParcel, 0, mParcel.dataSize()); 592 } else { 593 throw new IllegalStateException( 594 "Either mParcel or mEventsToWrite must not be null"); 595 } 596 } 597 } 598 599 public static final Creator<UsageEvents> CREATOR = new Creator<UsageEvents>() { 600 @Override 601 public UsageEvents createFromParcel(Parcel source) { 602 return new UsageEvents(source); 603 } 604 605 @Override 606 public UsageEvents[] newArray(int size) { 607 return new UsageEvents[size]; 608 } 609 }; 610} 611