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 android.app.usage; 17 18 import android.annotation.IntDef; 19 import android.annotation.SystemApi; 20 import android.content.res.Configuration; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 24 import java.lang.annotation.Retention; 25 import java.lang.annotation.RetentionPolicy; 26 import java.util.Arrays; 27 import 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 */ 33 public 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 #getAppStandbyBucket()}. 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 /** 143 * An event type denoting that the screen has gone in to an interactive state (turned 144 * on for full user interaction, not ambient display or other non-interactive state). 145 */ 146 public static final int SCREEN_INTERACTIVE = 15; 147 148 /** 149 * An event type denoting that the screen has gone in to a non-interactive state 150 * (completely turned off or turned on only in a non-interactive state like ambient 151 * display). 152 */ 153 public static final int SCREEN_NON_INTERACTIVE = 16; 154 155 /** 156 * An event type denoting that the screen's keyguard has been shown, whether or not 157 * the screen is off. 158 */ 159 public static final int KEYGUARD_SHOWN = 17; 160 161 /** 162 * An event type denoting that the screen's keyguard has been hidden. This typically 163 * happens when the user unlocks their phone after turning it on. 164 */ 165 public static final int KEYGUARD_HIDDEN = 18; 166 167 /** @hide */ 168 public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; 169 170 /** @hide */ 171 @IntDef(flag = true, prefix = { "FLAG_" }, value = { 172 FLAG_IS_PACKAGE_INSTANT_APP, 173 }) 174 @Retention(RetentionPolicy.SOURCE) 175 public @interface EventFlags {} 176 177 /** 178 * {@hide} 179 */ 180 public String mPackage; 181 182 /** 183 * {@hide} 184 */ 185 public String mClass; 186 187 /** 188 * {@hide} 189 */ 190 public long mTimeStamp; 191 192 /** 193 * {@hide} 194 */ 195 public int mEventType; 196 197 /** 198 * Only present for {@link #CONFIGURATION_CHANGE} event types. 199 * {@hide} 200 */ 201 public Configuration mConfiguration; 202 203 /** 204 * ID of the shortcut. 205 * Only present for {@link #SHORTCUT_INVOCATION} event types. 206 * {@hide} 207 */ 208 public String mShortcutId; 209 210 /** 211 * Action type passed to ChooserActivity 212 * Only present for {@link #CHOOSER_ACTION} event types. 213 * {@hide} 214 */ 215 public String mAction; 216 217 /** 218 * Content type passed to ChooserActivity. 219 * Only present for {@link #CHOOSER_ACTION} event types. 220 * {@hide} 221 */ 222 public String mContentType; 223 224 /** 225 * Content annotations passed to ChooserActivity. 226 * Only present for {@link #CHOOSER_ACTION} event types. 227 * {@hide} 228 */ 229 public String[] mContentAnnotations; 230 231 /** 232 * The app standby bucket assigned and reason. Bucket is the high order 16 bits, reason 233 * is the low order 16 bits. 234 * Only present for {@link #STANDBY_BUCKET_CHANGED} event types 235 * {@hide} 236 */ 237 public int mBucketAndReason; 238 239 /** 240 * The id of the {@link android.app.NotificationChannel} to which an interruptive 241 * notification was posted. 242 * Only present for {@link #NOTIFICATION_INTERRUPTION} event types. 243 * {@hide} 244 */ 245 public String mNotificationChannelId; 246 247 /** @hide */ 248 @EventFlags 249 public int mFlags; 250 Event()251 public Event() { 252 } 253 254 /** @hide */ Event(Event orig)255 public Event(Event orig) { 256 mPackage = orig.mPackage; 257 mClass = orig.mClass; 258 mTimeStamp = orig.mTimeStamp; 259 mEventType = orig.mEventType; 260 mConfiguration = orig.mConfiguration; 261 mShortcutId = orig.mShortcutId; 262 mAction = orig.mAction; 263 mContentType = orig.mContentType; 264 mContentAnnotations = orig.mContentAnnotations; 265 mFlags = orig.mFlags; 266 mBucketAndReason = orig.mBucketAndReason; 267 mNotificationChannelId = orig.mNotificationChannelId; 268 } 269 270 /** 271 * The package name of the source of this event. 272 */ getPackageName()273 public String getPackageName() { 274 return mPackage; 275 } 276 277 /** 278 * The class name of the source of this event. This may be null for 279 * certain events. 280 */ getClassName()281 public String getClassName() { 282 return mClass; 283 } 284 285 /** 286 * The time at which this event occurred, measured in milliseconds since the epoch. 287 * <p/> 288 * See {@link System#currentTimeMillis()}. 289 */ getTimeStamp()290 public long getTimeStamp() { 291 return mTimeStamp; 292 } 293 294 /** 295 * The event type. 296 * 297 * @see #MOVE_TO_BACKGROUND 298 * @see #MOVE_TO_FOREGROUND 299 * @see #CONFIGURATION_CHANGE 300 * @see #USER_INTERACTION 301 * @see #STANDBY_BUCKET_CHANGED 302 */ getEventType()303 public int getEventType() { 304 return mEventType; 305 } 306 307 /** 308 * Returns a {@link Configuration} for this event if the event is of type 309 * {@link #CONFIGURATION_CHANGE}, otherwise it returns null. 310 */ getConfiguration()311 public Configuration getConfiguration() { 312 return mConfiguration; 313 } 314 315 /** 316 * Returns the ID of a {@link android.content.pm.ShortcutInfo} for this event 317 * if the event is of type {@link #SHORTCUT_INVOCATION}, otherwise it returns null. 318 * 319 * @see android.content.pm.ShortcutManager#reportShortcutUsed(String) 320 */ getShortcutId()321 public String getShortcutId() { 322 return mShortcutId; 323 } 324 325 /** 326 * Returns the standby bucket of the app, if the event is of type 327 * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. 328 * @return the standby bucket associated with the event. 329 * @hide 330 */ getStandbyBucket()331 public int getStandbyBucket() { 332 return (mBucketAndReason & 0xFFFF0000) >>> 16; 333 } 334 335 /** 336 * Returns the standby bucket of the app, if the event is of type 337 * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. 338 * @return the standby bucket associated with the event. 339 * 340 */ getAppStandbyBucket()341 public int getAppStandbyBucket() { 342 return (mBucketAndReason & 0xFFFF0000) >>> 16; 343 } 344 345 /** 346 * Returns the reason for the bucketing, if the event is of type 347 * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. Reason values include 348 * the main reason which is one of REASON_MAIN_*, OR'ed with REASON_SUB_*, if there 349 * are sub-reasons for the main reason, such as REASON_SUB_USAGE_* when the main reason 350 * is REASON_MAIN_USAGE. 351 * @hide 352 */ getStandbyReason()353 public int getStandbyReason() { 354 return mBucketAndReason & 0x0000FFFF; 355 } 356 357 /** 358 * Returns the ID of the {@link android.app.NotificationChannel} for this event if the 359 * event is of type {@link #NOTIFICATION_INTERRUPTION}, otherwise it returns null; 360 * @hide 361 */ 362 @SystemApi getNotificationChannelId()363 public String getNotificationChannelId() { 364 return mNotificationChannelId; 365 } 366 367 /** @hide */ getObfuscatedIfInstantApp()368 public Event getObfuscatedIfInstantApp() { 369 if ((mFlags & FLAG_IS_PACKAGE_INSTANT_APP) == 0) { 370 return this; 371 } 372 final Event ret = new Event(this); 373 ret.mPackage = INSTANT_APP_PACKAGE_NAME; 374 ret.mClass = INSTANT_APP_CLASS_NAME; 375 376 // Note there are other string fields too, but they're for app shortcuts and choosers, 377 // which instant apps can't use anyway, so there's no need to hide them. 378 return ret; 379 } 380 } 381 382 // Only used when creating the resulting events. Not used for reading/unparceling. 383 private List<Event> mEventsToWrite = null; 384 385 // Only used for reading/unparceling events. 386 private Parcel mParcel = null; 387 private final int mEventCount; 388 389 private int mIndex = 0; 390 391 /* 392 * In order to save space, since ComponentNames will be duplicated everywhere, 393 * we use a map and index into it. 394 */ 395 private String[] mStringPool; 396 397 /** 398 * Construct the iterator from a parcel. 399 * {@hide} 400 */ UsageEvents(Parcel in)401 public UsageEvents(Parcel in) { 402 byte[] bytes = in.readBlob(); 403 Parcel data = Parcel.obtain(); 404 data.unmarshall(bytes, 0, bytes.length); 405 data.setDataPosition(0); 406 mEventCount = data.readInt(); 407 mIndex = data.readInt(); 408 if (mEventCount > 0) { 409 mStringPool = data.createStringArray(); 410 411 final int listByteLength = data.readInt(); 412 final int positionInParcel = data.readInt(); 413 mParcel = Parcel.obtain(); 414 mParcel.setDataPosition(0); 415 mParcel.appendFrom(data, data.dataPosition(), listByteLength); 416 mParcel.setDataSize(mParcel.dataPosition()); 417 mParcel.setDataPosition(positionInParcel); 418 } 419 } 420 421 /** 422 * Create an empty iterator. 423 * {@hide} 424 */ UsageEvents()425 UsageEvents() { 426 mEventCount = 0; 427 } 428 429 /** 430 * Construct the iterator in preparation for writing it to a parcel. 431 * {@hide} 432 */ UsageEvents(List<Event> events, String[] stringPool)433 public UsageEvents(List<Event> events, String[] stringPool) { 434 mStringPool = stringPool; 435 mEventCount = events.size(); 436 mEventsToWrite = events; 437 } 438 439 /** 440 * Returns whether or not there are more events to read using 441 * {@link #getNextEvent(android.app.usage.UsageEvents.Event)}. 442 * 443 * @return true if there are more events, false otherwise. 444 */ hasNextEvent()445 public boolean hasNextEvent() { 446 return mIndex < mEventCount; 447 } 448 449 /** 450 * Retrieve the next {@link android.app.usage.UsageEvents.Event} from the collection and put the 451 * resulting data into {@code eventOut}. 452 * 453 * @param eventOut The {@link android.app.usage.UsageEvents.Event} object that will receive the 454 * next event data. 455 * @return true if an event was available, false if there are no more events. 456 */ getNextEvent(Event eventOut)457 public boolean getNextEvent(Event eventOut) { 458 if (mIndex >= mEventCount) { 459 return false; 460 } 461 462 readEventFromParcel(mParcel, eventOut); 463 464 mIndex++; 465 if (mIndex >= mEventCount) { 466 mParcel.recycle(); 467 mParcel = null; 468 } 469 return true; 470 } 471 472 /** 473 * Resets the collection so that it can be iterated over from the beginning. 474 * 475 * @hide When this object is iterated to completion, the parcel is destroyed and 476 * so resetToStart doesn't work. 477 */ resetToStart()478 public void resetToStart() { 479 mIndex = 0; 480 if (mParcel != null) { 481 mParcel.setDataPosition(0); 482 } 483 } 484 findStringIndex(String str)485 private int findStringIndex(String str) { 486 final int index = Arrays.binarySearch(mStringPool, str); 487 if (index < 0) { 488 throw new IllegalStateException("String '" + str + "' is not in the string pool"); 489 } 490 return index; 491 } 492 493 /** 494 * Writes a single event to the parcel. Modify this when updating {@link Event}. 495 */ writeEventToParcel(Event event, Parcel p, int flags)496 private void writeEventToParcel(Event event, Parcel p, int flags) { 497 final int packageIndex; 498 if (event.mPackage != null) { 499 packageIndex = findStringIndex(event.mPackage); 500 } else { 501 packageIndex = -1; 502 } 503 504 final int classIndex; 505 if (event.mClass != null) { 506 classIndex = findStringIndex(event.mClass); 507 } else { 508 classIndex = -1; 509 } 510 p.writeInt(packageIndex); 511 p.writeInt(classIndex); 512 p.writeInt(event.mEventType); 513 p.writeLong(event.mTimeStamp); 514 515 switch (event.mEventType) { 516 case Event.CONFIGURATION_CHANGE: 517 event.mConfiguration.writeToParcel(p, flags); 518 break; 519 case Event.SHORTCUT_INVOCATION: 520 p.writeString(event.mShortcutId); 521 break; 522 case Event.CHOOSER_ACTION: 523 p.writeString(event.mAction); 524 p.writeString(event.mContentType); 525 p.writeStringArray(event.mContentAnnotations); 526 break; 527 case Event.STANDBY_BUCKET_CHANGED: 528 p.writeInt(event.mBucketAndReason); 529 break; 530 case Event.NOTIFICATION_INTERRUPTION: 531 p.writeString(event.mNotificationChannelId); 532 break; 533 } 534 } 535 536 /** 537 * Reads a single event from the parcel. Modify this when updating {@link Event}. 538 */ readEventFromParcel(Parcel p, Event eventOut)539 private void readEventFromParcel(Parcel p, Event eventOut) { 540 final int packageIndex = p.readInt(); 541 if (packageIndex >= 0) { 542 eventOut.mPackage = mStringPool[packageIndex]; 543 } else { 544 eventOut.mPackage = null; 545 } 546 547 final int classIndex = p.readInt(); 548 if (classIndex >= 0) { 549 eventOut.mClass = mStringPool[classIndex]; 550 } else { 551 eventOut.mClass = null; 552 } 553 eventOut.mEventType = p.readInt(); 554 eventOut.mTimeStamp = p.readLong(); 555 556 // Fill out the event-dependant fields. 557 eventOut.mConfiguration = null; 558 eventOut.mShortcutId = null; 559 eventOut.mAction = null; 560 eventOut.mContentType = null; 561 eventOut.mContentAnnotations = null; 562 eventOut.mNotificationChannelId = null; 563 564 switch (eventOut.mEventType) { 565 case Event.CONFIGURATION_CHANGE: 566 // Extract the configuration for configuration change events. 567 eventOut.mConfiguration = Configuration.CREATOR.createFromParcel(p); 568 break; 569 case Event.SHORTCUT_INVOCATION: 570 eventOut.mShortcutId = p.readString(); 571 break; 572 case Event.CHOOSER_ACTION: 573 eventOut.mAction = p.readString(); 574 eventOut.mContentType = p.readString(); 575 eventOut.mContentAnnotations = p.createStringArray(); 576 break; 577 case Event.STANDBY_BUCKET_CHANGED: 578 eventOut.mBucketAndReason = p.readInt(); 579 break; 580 case Event.NOTIFICATION_INTERRUPTION: 581 eventOut.mNotificationChannelId = p.readString(); 582 break; 583 } 584 } 585 586 @Override describeContents()587 public int describeContents() { 588 return 0; 589 } 590 591 @Override writeToParcel(Parcel dest, int flags)592 public void writeToParcel(Parcel dest, int flags) { 593 Parcel data = Parcel.obtain(); 594 data.writeInt(mEventCount); 595 data.writeInt(mIndex); 596 if (mEventCount > 0) { 597 data.writeStringArray(mStringPool); 598 599 if (mEventsToWrite != null) { 600 // Write out the events 601 Parcel p = Parcel.obtain(); 602 try { 603 p.setDataPosition(0); 604 for (int i = 0; i < mEventCount; i++) { 605 final Event event = mEventsToWrite.get(i); 606 writeEventToParcel(event, p, flags); 607 } 608 609 final int listByteLength = p.dataPosition(); 610 611 // Write the total length of the data. 612 data.writeInt(listByteLength); 613 614 // Write our current position into the data. 615 data.writeInt(0); 616 617 // Write the data. 618 data.appendFrom(p, 0, listByteLength); 619 } finally { 620 p.recycle(); 621 } 622 623 } else if (mParcel != null) { 624 // Write the total length of the data. 625 data.writeInt(mParcel.dataSize()); 626 627 // Write out current position into the data. 628 data.writeInt(mParcel.dataPosition()); 629 630 // Write the data. 631 data.appendFrom(mParcel, 0, mParcel.dataSize()); 632 } else { 633 throw new IllegalStateException( 634 "Either mParcel or mEventsToWrite must not be null"); 635 } 636 } 637 // Data can be too large for a transact. Write the data as a Blob, which will be written to 638 // ashmem if too large. 639 dest.writeBlob(data.marshall()); 640 } 641 642 public static final Creator<UsageEvents> CREATOR = new Creator<UsageEvents>() { 643 @Override 644 public UsageEvents createFromParcel(Parcel source) { 645 return new UsageEvents(source); 646 } 647 648 @Override 649 public UsageEvents[] newArray(int size) { 650 return new UsageEvents[size]; 651 } 652 }; 653 } 654