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