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