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