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 17 package android.app.usage; 18 19 import android.annotation.SystemApi; 20 import android.os.Bundle; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.util.ArrayMap; 24 25 /** 26 * Contains usage statistics for an app package for a specific 27 * time range. 28 */ 29 public final class UsageStats implements Parcelable { 30 31 /** 32 * {@hide} 33 */ 34 public String mPackageName; 35 36 /** 37 * {@hide} 38 */ 39 public long mBeginTimeStamp; 40 41 /** 42 * {@hide} 43 */ 44 public long mEndTimeStamp; 45 46 /** 47 * Last time used by the user with an explicit action (notification, activity launch). 48 * {@hide} 49 */ 50 public long mLastTimeUsed; 51 52 /** 53 * {@hide} 54 */ 55 public long mTotalTimeInForeground; 56 57 /** 58 * {@hide} 59 */ 60 public int mLaunchCount; 61 62 /** 63 * {@hide} 64 */ 65 public int mAppLaunchCount; 66 67 /** 68 * {@hide} 69 */ 70 public int mLastEvent; 71 72 /** 73 * {@hide} 74 */ 75 public ArrayMap<String, ArrayMap<String, Integer>> mChooserCounts; 76 77 /** 78 * {@hide} 79 */ UsageStats()80 public UsageStats() { 81 } 82 UsageStats(UsageStats stats)83 public UsageStats(UsageStats stats) { 84 mPackageName = stats.mPackageName; 85 mBeginTimeStamp = stats.mBeginTimeStamp; 86 mEndTimeStamp = stats.mEndTimeStamp; 87 mLastTimeUsed = stats.mLastTimeUsed; 88 mTotalTimeInForeground = stats.mTotalTimeInForeground; 89 mLaunchCount = stats.mLaunchCount; 90 mAppLaunchCount = stats.mAppLaunchCount; 91 mLastEvent = stats.mLastEvent; 92 mChooserCounts = stats.mChooserCounts; 93 } 94 95 /** 96 * {@hide} 97 */ getObfuscatedForInstantApp()98 public UsageStats getObfuscatedForInstantApp() { 99 final UsageStats ret = new UsageStats(this); 100 101 ret.mPackageName = UsageEvents.INSTANT_APP_PACKAGE_NAME; 102 103 return ret; 104 } 105 getPackageName()106 public String getPackageName() { 107 return mPackageName; 108 } 109 110 /** 111 * Get the beginning of the time range this {@link android.app.usage.UsageStats} represents, 112 * measured in milliseconds since the epoch. 113 * <p/> 114 * See {@link System#currentTimeMillis()}. 115 */ getFirstTimeStamp()116 public long getFirstTimeStamp() { 117 return mBeginTimeStamp; 118 } 119 120 /** 121 * Get the end of the time range this {@link android.app.usage.UsageStats} represents, 122 * measured in milliseconds since the epoch. 123 * <p/> 124 * See {@link System#currentTimeMillis()}. 125 */ getLastTimeStamp()126 public long getLastTimeStamp() { 127 return mEndTimeStamp; 128 } 129 130 /** 131 * Get the last time this package was used, measured in milliseconds since the epoch. 132 * <p/> 133 * See {@link System#currentTimeMillis()}. 134 */ getLastTimeUsed()135 public long getLastTimeUsed() { 136 return mLastTimeUsed; 137 } 138 139 /** 140 * Get the total time this package spent in the foreground, measured in milliseconds. 141 */ getTotalTimeInForeground()142 public long getTotalTimeInForeground() { 143 return mTotalTimeInForeground; 144 } 145 146 /** 147 * Returns the number of times the app was launched as an activity from outside of the app. 148 * Excludes intra-app activity transitions. 149 * @hide 150 */ 151 @SystemApi getAppLaunchCount()152 public int getAppLaunchCount() { 153 return mAppLaunchCount; 154 } 155 156 /** 157 * Add the statistics from the right {@link UsageStats} to the left. The package name for 158 * both {@link UsageStats} objects must be the same. 159 * @param right The {@link UsageStats} object to merge into this one. 160 * @throws java.lang.IllegalArgumentException if the package names of the two 161 * {@link UsageStats} objects are different. 162 */ add(UsageStats right)163 public void add(UsageStats right) { 164 if (!mPackageName.equals(right.mPackageName)) { 165 throw new IllegalArgumentException("Can't merge UsageStats for package '" + 166 mPackageName + "' with UsageStats for package '" + right.mPackageName + "'."); 167 } 168 169 // We use the mBeginTimeStamp due to a bug where UsageStats files can overlap with 170 // regards to their mEndTimeStamp. 171 if (right.mBeginTimeStamp > mBeginTimeStamp) { 172 // Even though incoming UsageStat begins after this one, its last time used fields 173 // may somehow be empty or chronologically preceding the older UsageStat. 174 mLastEvent = Math.max(mLastEvent, right.mLastEvent); 175 mLastTimeUsed = Math.max(mLastTimeUsed, right.mLastTimeUsed); 176 } 177 mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp); 178 mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp); 179 mTotalTimeInForeground += right.mTotalTimeInForeground; 180 mLaunchCount += right.mLaunchCount; 181 mAppLaunchCount += right.mAppLaunchCount; 182 if (mChooserCounts == null) { 183 mChooserCounts = right.mChooserCounts; 184 } else if (right.mChooserCounts != null) { 185 final int chooserCountsSize = right.mChooserCounts.size(); 186 for (int i = 0; i < chooserCountsSize; i++) { 187 String action = right.mChooserCounts.keyAt(i); 188 ArrayMap<String, Integer> counts = right.mChooserCounts.valueAt(i); 189 if (!mChooserCounts.containsKey(action) || mChooserCounts.get(action) == null) { 190 mChooserCounts.put(action, counts); 191 continue; 192 } 193 final int annotationSize = counts.size(); 194 for (int j = 0; j < annotationSize; j++) { 195 String key = counts.keyAt(j); 196 int rightValue = counts.valueAt(j); 197 int leftValue = mChooserCounts.get(action).getOrDefault(key, 0); 198 mChooserCounts.get(action).put(key, leftValue + rightValue); 199 } 200 } 201 } 202 } 203 204 @Override describeContents()205 public int describeContents() { 206 return 0; 207 } 208 209 @Override writeToParcel(Parcel dest, int flags)210 public void writeToParcel(Parcel dest, int flags) { 211 dest.writeString(mPackageName); 212 dest.writeLong(mBeginTimeStamp); 213 dest.writeLong(mEndTimeStamp); 214 dest.writeLong(mLastTimeUsed); 215 dest.writeLong(mTotalTimeInForeground); 216 dest.writeInt(mLaunchCount); 217 dest.writeInt(mAppLaunchCount); 218 dest.writeInt(mLastEvent); 219 Bundle allCounts = new Bundle(); 220 if (mChooserCounts != null) { 221 final int chooserCountSize = mChooserCounts.size(); 222 for (int i = 0; i < chooserCountSize; i++) { 223 String action = mChooserCounts.keyAt(i); 224 ArrayMap<String, Integer> counts = mChooserCounts.valueAt(i); 225 Bundle currentCounts = new Bundle(); 226 final int annotationSize = counts.size(); 227 for (int j = 0; j < annotationSize; j++) { 228 currentCounts.putInt(counts.keyAt(j), counts.valueAt(j)); 229 } 230 allCounts.putBundle(action, currentCounts); 231 } 232 } 233 dest.writeBundle(allCounts); 234 } 235 236 public static final Creator<UsageStats> CREATOR = new Creator<UsageStats>() { 237 @Override 238 public UsageStats createFromParcel(Parcel in) { 239 UsageStats stats = new UsageStats(); 240 stats.mPackageName = in.readString(); 241 stats.mBeginTimeStamp = in.readLong(); 242 stats.mEndTimeStamp = in.readLong(); 243 stats.mLastTimeUsed = in.readLong(); 244 stats.mTotalTimeInForeground = in.readLong(); 245 stats.mLaunchCount = in.readInt(); 246 stats.mAppLaunchCount = in.readInt(); 247 stats.mLastEvent = in.readInt(); 248 Bundle allCounts = in.readBundle(); 249 if (allCounts != null) { 250 stats.mChooserCounts = new ArrayMap<>(); 251 for (String action : allCounts.keySet()) { 252 if (!stats.mChooserCounts.containsKey(action)) { 253 ArrayMap<String, Integer> newCounts = new ArrayMap<>(); 254 stats.mChooserCounts.put(action, newCounts); 255 } 256 Bundle currentCounts = allCounts.getBundle(action); 257 if (currentCounts != null) { 258 for (String key : currentCounts.keySet()) { 259 int value = currentCounts.getInt(key); 260 if (value > 0) { 261 stats.mChooserCounts.get(action).put(key, value); 262 } 263 } 264 } 265 } 266 } 267 return stats; 268 } 269 270 @Override 271 public UsageStats[] newArray(int size) { 272 return new UsageStats[size]; 273 } 274 }; 275 } 276