blob: 70596748d66c9d55d94510d7b25e9546db94ead5 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006-2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.am;
18
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080019import android.content.ComponentName;
20import android.content.Context;
Mark Brophyc6350272011-08-05 16:16:39 +010021import android.content.pm.PackageInfo;
Kenny Root3abd75b2011-09-29 11:00:41 -070022import android.content.pm.PackageManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.os.Binder;
24import android.os.IBinder;
Dianne Hackborn8bdf5932010-10-15 12:54:40 -070025import android.os.FileUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.os.Parcel;
27import android.os.Process;
28import android.os.ServiceManager;
29import android.os.SystemClock;
Dianne Hackborn39606a02012-07-31 17:54:35 -070030import android.util.AtomicFile;
Joe Onorato8a9b2202010-02-26 18:56:32 -080031import android.util.Slog;
Mark Brophyc6350272011-08-05 16:16:39 +010032import android.util.Xml;
33
34import com.android.internal.app.IUsageStats;
35import com.android.internal.content.PackageMonitor;
Mark Brophyc6350272011-08-05 16:16:39 +010036import com.android.internal.os.PkgUsageStats;
37import com.android.internal.util.FastXmlSerializer;
38
39import org.xmlpull.v1.XmlPullParser;
40import org.xmlpull.v1.XmlPullParserException;
41import org.xmlpull.v1.XmlSerializer;
42
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import java.io.File;
44import java.io.FileDescriptor;
45import java.io.FileInputStream;
46import java.io.FileNotFoundException;
47import java.io.FileOutputStream;
48import java.io.IOException;
49import java.io.PrintWriter;
50import java.util.ArrayList;
51import java.util.Calendar;
52import java.util.Collections;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053import java.util.HashMap;
Dianne Hackborn9fdbf6a2009-07-19 14:18:51 -070054import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055import java.util.List;
56import java.util.Map;
57import java.util.Set;
Dianne Hackborn6447ca32009-04-07 19:50:08 -070058import java.util.TimeZone;
Brad Fitzpatrick389a9162010-08-03 15:41:05 -070059import java.util.concurrent.atomic.AtomicBoolean;
60import java.util.concurrent.atomic.AtomicInteger;
61import java.util.concurrent.atomic.AtomicLong;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062
63/**
64 * This service collects the statistics associated with usage
65 * of various components, like when a particular package is launched or
66 * paused and aggregates events like number of time a component is launched
67 * total duration of a component launch.
68 */
69public final class UsageStatsService extends IUsageStats.Stub {
70 public static final String SERVICE_NAME = "usagestats";
71 private static final boolean localLOGV = false;
Dianne Hackborncef65ee2010-09-30 18:27:22 -070072 private static final boolean REPORT_UNEXPECTED = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 private static final String TAG = "UsageStats";
Dianne Hackborn6447ca32009-04-07 19:50:08 -070074
75 // Current on-disk Parcel version
Mark Brophyc6350272011-08-05 16:16:39 +010076 private static final int VERSION = 1007;
Dianne Hackborn6447ca32009-04-07 19:50:08 -070077
Dianne Hackborn760ec4a2009-06-17 20:05:26 -070078 private static final int CHECKIN_VERSION = 4;
Dianne Hackborn6447ca32009-04-07 19:50:08 -070079
80 private static final String FILE_PREFIX = "usage-";
Mark Brophyc6350272011-08-05 16:16:39 +010081
82 private static final String FILE_HISTORY = FILE_PREFIX + "history.xml";
83
Dianne Hackborn6447ca32009-04-07 19:50:08 -070084 private static final int FILE_WRITE_INTERVAL = 30*60*1000; //ms
85
86 private static final int MAX_NUM_FILES = 5;
87
Dianne Hackbornf210d6b2009-04-13 18:42:49 -070088 private static final int NUM_LAUNCH_TIME_BINS = 10;
89 private static final int[] LAUNCH_TIME_BINS = {
90 250, 500, 750, 1000, 1500, 2000, 3000, 4000, 5000
91 };
Dianne Hackborn6447ca32009-04-07 19:50:08 -070092
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093 static IUsageStats sService;
94 private Context mContext;
95 // structure used to maintain statistics since the last checkin.
96 final private Map<String, PkgUsageStatsExtended> mStats;
Mark Brophyc6350272011-08-05 16:16:39 +010097
98 // Maintains the last time any component was resumed, for all time.
99 final private Map<String, Map<String, Long>> mLastResumeTimes;
100
101 // To remove last-resume time stats when a pacakge is removed.
102 private PackageMonitor mPackageMonitor;
103
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 // Lock to update package stats. Methods suffixed by SLOCK should invoked with
105 // this lock held
106 final Object mStatsLock;
107 // Lock to write to file. Methods suffixed by FLOCK should invoked with
108 // this lock held.
109 final Object mFileLock;
110 // Order of locks is mFileLock followed by mStatsLock to avoid deadlocks
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700111 private String mLastResumedPkg;
112 private String mLastResumedComp;
113 private boolean mIsResumed;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 private File mFile;
Mark Brophyc6350272011-08-05 16:16:39 +0100115 private AtomicFile mHistoryFile;
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700116 private String mFileLeaf;
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700117 private File mDir;
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700118
119 private Calendar mCal; // guarded by itself
120
121 private final AtomicInteger mLastWriteDay = new AtomicInteger(-1);
122 private final AtomicLong mLastWriteElapsedTime = new AtomicLong(0);
123 private final AtomicBoolean mUnforcedDiskWriteRunning = new AtomicBoolean(false);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700124
125 static class TimeStats {
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700126 int count;
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700127 int[] times = new int[NUM_LAUNCH_TIME_BINS];
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700128
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700129 TimeStats() {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700130 }
131
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700132 void incCount() {
133 count++;
134 }
135
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700136 void add(int val) {
137 final int[] bins = LAUNCH_TIME_BINS;
138 for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) {
139 if (val < bins[i]) {
140 times[i]++;
141 return;
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700142 }
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700143 }
144 times[NUM_LAUNCH_TIME_BINS-1]++;
145 }
146
147 TimeStats(Parcel in) {
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700148 count = in.readInt();
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700149 final int[] localTimes = times;
150 for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
151 localTimes[i] = in.readInt();
152 }
153 }
154
155 void writeToParcel(Parcel out) {
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700156 out.writeInt(count);
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700157 final int[] localTimes = times;
158 for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
159 out.writeInt(localTimes[i]);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700160 }
161 }
162 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800163
164 private class PkgUsageStatsExtended {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700165 final HashMap<String, TimeStats> mLaunchTimes
166 = new HashMap<String, TimeStats>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 int mLaunchCount;
168 long mUsageTime;
169 long mPausedTime;
170 long mResumedTime;
171
172 PkgUsageStatsExtended() {
173 mLaunchCount = 0;
174 mUsageTime = 0;
175 }
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700176
177 PkgUsageStatsExtended(Parcel in) {
178 mLaunchCount = in.readInt();
179 mUsageTime = in.readLong();
Joe Onorato8a9b2202010-02-26 18:56:32 -0800180 if (localLOGV) Slog.v(TAG, "Launch count: " + mLaunchCount
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700181 + ", Usage time:" + mUsageTime);
182
Mark Brophy9fc03302011-07-01 16:56:24 +0100183 final int numTimeStats = in.readInt();
184 if (localLOGV) Slog.v(TAG, "Reading comps: " + numTimeStats);
185 for (int i=0; i<numTimeStats; i++) {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700186 String comp = in.readString();
Joe Onorato8a9b2202010-02-26 18:56:32 -0800187 if (localLOGV) Slog.v(TAG, "Component: " + comp);
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700188 TimeStats times = new TimeStats(in);
189 mLaunchTimes.put(comp, times);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700190 }
191 }
Mark Brophy9fc03302011-07-01 16:56:24 +0100192
193 void updateResume(String comp, boolean launched) {
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700194 if (launched) {
195 mLaunchCount ++;
196 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 mResumedTime = SystemClock.elapsedRealtime();
198 }
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700199
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200 void updatePause() {
201 mPausedTime = SystemClock.elapsedRealtime();
202 mUsageTime += (mPausedTime - mResumedTime);
203 }
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700204
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700205 void addLaunchCount(String comp) {
206 TimeStats times = mLaunchTimes.get(comp);
207 if (times == null) {
208 times = new TimeStats();
209 mLaunchTimes.put(comp, times);
210 }
211 times.incCount();
212 }
213
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700214 void addLaunchTime(String comp, int millis) {
215 TimeStats times = mLaunchTimes.get(comp);
216 if (times == null) {
217 times = new TimeStats();
218 mLaunchTimes.put(comp, times);
219 }
220 times.add(millis);
221 }
222
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700223 void writeToParcel(Parcel out) {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700224 out.writeInt(mLaunchCount);
225 out.writeLong(mUsageTime);
Mark Brophy9fc03302011-07-01 16:56:24 +0100226 final int numTimeStats = mLaunchTimes.size();
227 out.writeInt(numTimeStats);
228 if (numTimeStats > 0) {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700229 for (Map.Entry<String, TimeStats> ent : mLaunchTimes.entrySet()) {
230 out.writeString(ent.getKey());
231 TimeStats times = ent.getValue();
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700232 times.writeToParcel(out);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700233 }
234 }
235 }
236
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 void clear() {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700238 mLaunchTimes.clear();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 mLaunchCount = 0;
240 mUsageTime = 0;
241 }
242 }
243
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700244 UsageStatsService(String dir) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 mStats = new HashMap<String, PkgUsageStatsExtended>();
Mark Brophyc6350272011-08-05 16:16:39 +0100246 mLastResumeTimes = new HashMap<String, Map<String, Long>>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247 mStatsLock = new Object();
248 mFileLock = new Object();
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700249 mDir = new File(dir);
250 mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
251
252 mDir.mkdir();
253
254 // Remove any old usage files from previous versions.
255 File parentDir = mDir.getParentFile();
256 String fList[] = parentDir.list();
257 if (fList != null) {
258 String prefix = mDir.getName() + ".";
259 int i = fList.length;
260 while (i > 0) {
261 i--;
262 if (fList[i].startsWith(prefix)) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800263 Slog.i(TAG, "Deleting old usage file: " + fList[i]);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700264 (new File(parentDir, fList[i])).delete();
265 }
266 }
267 }
268
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 // Update current stats which are binned by date
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700270 mFileLeaf = getCurrentDateStr(FILE_PREFIX);
271 mFile = new File(mDir, mFileLeaf);
Mark Brophyc6350272011-08-05 16:16:39 +0100272 mHistoryFile = new AtomicFile(new File(mDir, FILE_HISTORY));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273 readStatsFromFile();
Mark Brophyc6350272011-08-05 16:16:39 +0100274 readHistoryStatsFromFile();
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700275 mLastWriteElapsedTime.set(SystemClock.elapsedRealtime());
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700276 // mCal was set by getCurrentDateStr(), want to use that same time.
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700277 mLastWriteDay.set(mCal.get(Calendar.DAY_OF_YEAR));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278 }
279
280 /*
281 * Utility method to convert date into string.
282 */
283 private String getCurrentDateStr(String prefix) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800284 StringBuilder sb = new StringBuilder();
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700285 synchronized (mCal) {
286 mCal.setTimeInMillis(System.currentTimeMillis());
287 if (prefix != null) {
288 sb.append(prefix);
289 }
290 sb.append(mCal.get(Calendar.YEAR));
291 int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1;
292 if (mm < 10) {
293 sb.append("0");
294 }
295 sb.append(mm);
296 int dd = mCal.get(Calendar.DAY_OF_MONTH);
297 if (dd < 10) {
298 sb.append("0");
299 }
300 sb.append(dd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800301 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 return sb.toString();
303 }
304
305 private Parcel getParcelForFile(File file) throws IOException {
306 FileInputStream stream = new FileInputStream(file);
307 byte[] raw = readFully(stream);
308 Parcel in = Parcel.obtain();
309 in.unmarshall(raw, 0, raw.length);
310 in.setDataPosition(0);
311 stream.close();
312 return in;
313 }
314
315 private void readStatsFromFile() {
316 File newFile = mFile;
317 synchronized (mFileLock) {
318 try {
319 if (newFile.exists()) {
320 readStatsFLOCK(newFile);
321 } else {
322 // Check for file limit before creating a new file
323 checkFileLimitFLOCK();
324 newFile.createNewFile();
325 }
326 } catch (IOException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800327 Slog.w(TAG,"Error : " + e + " reading data from file:" + newFile);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 }
329 }
330 }
331
332 private void readStatsFLOCK(File file) throws IOException {
333 Parcel in = getParcelForFile(file);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700334 int vers = in.readInt();
335 if (vers != VERSION) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800336 Slog.w(TAG, "Usage stats version changed; dropping");
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700337 return;
338 }
339 int N = in.readInt();
340 while (N > 0) {
341 N--;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 String pkgName = in.readString();
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700343 if (pkgName == null) {
344 break;
345 }
Joe Onorato8a9b2202010-02-26 18:56:32 -0800346 if (localLOGV) Slog.v(TAG, "Reading package #" + N + ": " + pkgName);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700347 PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 synchronized (mStatsLock) {
349 mStats.put(pkgName, pus);
350 }
351 }
352 }
353
Mark Brophyc6350272011-08-05 16:16:39 +0100354 private void readHistoryStatsFromFile() {
355 synchronized (mFileLock) {
356 if (mHistoryFile.getBaseFile().exists()) {
357 readHistoryStatsFLOCK(mHistoryFile);
358 }
359 }
360 }
361
362 private void readHistoryStatsFLOCK(AtomicFile file) {
363 FileInputStream fis = null;
364 try {
365 fis = mHistoryFile.openRead();
366 XmlPullParser parser = Xml.newPullParser();
367 parser.setInput(fis, null);
368 int eventType = parser.getEventType();
369 while (eventType != XmlPullParser.START_TAG) {
370 eventType = parser.next();
371 }
372 String tagName = parser.getName();
373 if ("usage-history".equals(tagName)) {
374 String pkg = null;
375 do {
376 eventType = parser.next();
377 if (eventType == XmlPullParser.START_TAG) {
378 tagName = parser.getName();
379 int depth = parser.getDepth();
380 if ("pkg".equals(tagName) && depth == 2) {
381 pkg = parser.getAttributeValue(null, "name");
382 } else if ("comp".equals(tagName) && depth == 3 && pkg != null) {
383 String comp = parser.getAttributeValue(null, "name");
384 String lastResumeTimeStr = parser.getAttributeValue(null, "lrt");
385 if (comp != null && lastResumeTimeStr != null) {
386 try {
387 long lastResumeTime = Long.parseLong(lastResumeTimeStr);
388 synchronized (mStatsLock) {
389 Map<String, Long> lrt = mLastResumeTimes.get(pkg);
390 if (lrt == null) {
391 lrt = new HashMap<String, Long>();
392 mLastResumeTimes.put(pkg, lrt);
393 }
394 lrt.put(comp, lastResumeTime);
395 }
396 } catch (NumberFormatException e) {
397 }
398 }
399 }
400 } else if (eventType == XmlPullParser.END_TAG) {
401 if ("pkg".equals(parser.getName())) {
402 pkg = null;
403 }
404 }
405 } while (eventType != XmlPullParser.END_DOCUMENT);
406 }
407 } catch (XmlPullParserException e) {
408 Slog.w(TAG,"Error reading history stats: " + e);
409 } catch (IOException e) {
410 Slog.w(TAG,"Error reading history stats: " + e);
411 } finally {
412 if (fis != null) {
413 try {
414 fis.close();
415 } catch (IOException e) {
416 }
417 }
418 }
419 }
420
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800421 private ArrayList<String> getUsageStatsFileListFLOCK() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800422 // Check if there are too many files in the system and delete older files
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700423 String fList[] = mDir.list();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424 if (fList == null) {
425 return null;
426 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800427 ArrayList<String> fileList = new ArrayList<String>();
428 for (String file : fList) {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700429 if (!file.startsWith(FILE_PREFIX)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 continue;
431 }
432 if (file.endsWith(".bak")) {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700433 (new File(mDir, file)).delete();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800434 continue;
435 }
436 fileList.add(file);
437 }
438 return fileList;
439 }
440
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 private void checkFileLimitFLOCK() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 // Get all usage stats output files
443 ArrayList<String> fileList = getUsageStatsFileListFLOCK();
444 if (fileList == null) {
445 // Strange but we dont have to delete any thing
446 return;
447 }
448 int count = fileList.size();
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700449 if (count <= MAX_NUM_FILES) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 return;
451 }
452 // Sort files
453 Collections.sort(fileList);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700454 count -= MAX_NUM_FILES;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 // Delete older files
456 for (int i = 0; i < count; i++) {
457 String fileName = fileList.get(i);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700458 File file = new File(mDir, fileName);
Joe Onorato8a9b2202010-02-26 18:56:32 -0800459 Slog.i(TAG, "Deleting usage file : " + fileName);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800460 file.delete();
461 }
462 }
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700463
464 /**
465 * Conditionally start up a disk write if it's been awhile, or the
466 * day has rolled over.
467 *
468 * This is called indirectly from user-facing actions (when
469 * 'force' is false) so it tries to be quick, without writing to
470 * disk directly or acquiring heavy locks.
471 *
472 * @params force do an unconditional, synchronous stats flush
473 * to disk on the current thread.
Mark Brophyc6350272011-08-05 16:16:39 +0100474 * @params forceWriteHistoryStats Force writing of historical stats.
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700475 */
Mark Brophyc6350272011-08-05 16:16:39 +0100476 private void writeStatsToFile(final boolean force, final boolean forceWriteHistoryStats) {
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700477 int curDay;
478 synchronized (mCal) {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700479 mCal.setTimeInMillis(System.currentTimeMillis());
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700480 curDay = mCal.get(Calendar.DAY_OF_YEAR);
481 }
482 final boolean dayChanged = curDay != mLastWriteDay.get();
483
484 // Determine if the day changed... note that this will be wrong
485 // if the year has changed but we are in the same day of year...
486 // we can probably live with this.
487 final long currElapsedTime = SystemClock.elapsedRealtime();
488
489 // Fast common path, without taking the often-contentious
490 // mFileLock.
491 if (!force) {
492 if (!dayChanged &&
493 (currElapsedTime - mLastWriteElapsedTime.get()) < FILE_WRITE_INTERVAL) {
494 // wait till the next update
495 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496 }
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700497 if (mUnforcedDiskWriteRunning.compareAndSet(false, true)) {
498 new Thread("UsageStatsService_DiskWriter") {
499 public void run() {
500 try {
Dianne Hackborncef65ee2010-09-30 18:27:22 -0700501 if (localLOGV) Slog.d(TAG, "Disk writer thread starting.");
Mark Brophyc6350272011-08-05 16:16:39 +0100502 writeStatsToFile(true, false);
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700503 } finally {
504 mUnforcedDiskWriteRunning.set(false);
Dianne Hackborncef65ee2010-09-30 18:27:22 -0700505 if (localLOGV) Slog.d(TAG, "Disk writer thread ending.");
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700506 }
507 }
508 }.start();
509 }
510 return;
511 }
512
513 synchronized (mFileLock) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 // Get the most recent file
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700515 mFileLeaf = getCurrentDateStr(FILE_PREFIX);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 // Copy current file to back up
Suchi Amalapurapube1c4222009-12-04 12:31:13 -0800517 File backupFile = null;
518 if (mFile != null && mFile.exists()) {
519 backupFile = new File(mFile.getPath() + ".bak");
Dianne Hackborn1afd1c92010-03-18 22:47:17 -0700520 if (!backupFile.exists()) {
521 if (!mFile.renameTo(backupFile)) {
522 Slog.w(TAG, "Failed to persist new stats");
523 return;
524 }
525 } else {
526 mFile.delete();
Suchi Amalapurapube1c4222009-12-04 12:31:13 -0800527 }
Suchi Amalapurapu8550f252009-09-29 15:20:32 -0700528 }
Suchi Amalapurapube1c4222009-12-04 12:31:13 -0800529
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800530 try {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800531 // Write mStats to file
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700532 writeStatsFLOCK(mFile);
533 mLastWriteElapsedTime.set(currElapsedTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800534 if (dayChanged) {
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700535 mLastWriteDay.set(curDay);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 // clear stats
537 synchronized (mStats) {
538 mStats.clear();
539 }
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700540 mFile = new File(mDir, mFileLeaf);
541 checkFileLimitFLOCK();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 }
Mark Brophyc6350272011-08-05 16:16:39 +0100543
544 if (dayChanged || forceWriteHistoryStats) {
545 // Write history stats daily, or when forced (due to shutdown).
546 writeHistoryStatsFLOCK(mHistoryFile);
547 }
548
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549 // Delete the backup file
550 if (backupFile != null) {
551 backupFile.delete();
552 }
553 } catch (IOException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800554 Slog.w(TAG, "Failed writing stats to file:" + mFile);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 if (backupFile != null) {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700556 mFile.delete();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 backupFile.renameTo(mFile);
558 }
559 }
560 }
Dianne Hackborncef65ee2010-09-30 18:27:22 -0700561 if (localLOGV) Slog.d(TAG, "Dumped usage stats.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800562 }
563
Brad Fitzpatrick389a9162010-08-03 15:41:05 -0700564 private void writeStatsFLOCK(File file) throws IOException {
565 FileOutputStream stream = new FileOutputStream(file);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700566 try {
567 Parcel out = Parcel.obtain();
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700568 writeStatsToParcelFLOCK(out);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700569 stream.write(out.marshall());
570 out.recycle();
571 stream.flush();
572 } finally {
Dianne Hackborn8bdf5932010-10-15 12:54:40 -0700573 FileUtils.sync(stream);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700574 stream.close();
575 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800576 }
577
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700578 private void writeStatsToParcelFLOCK(Parcel out) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 synchronized (mStatsLock) {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700580 out.writeInt(VERSION);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800581 Set<String> keys = mStats.keySet();
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700582 out.writeInt(keys.size());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 for (String key : keys) {
584 PkgUsageStatsExtended pus = mStats.get(key);
585 out.writeString(key);
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700586 pus.writeToParcel(out);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800587 }
588 }
589 }
590
Mark Brophyc6350272011-08-05 16:16:39 +0100591 /** Filter out stats for any packages which aren't present anymore. */
592 private void filterHistoryStats() {
593 synchronized (mStatsLock) {
594 // Copy and clear the last resume times map, then copy back stats
595 // for all installed packages.
596 Map<String, Map<String, Long>> tmpLastResumeTimes =
597 new HashMap<String, Map<String, Long>>(mLastResumeTimes);
598 mLastResumeTimes.clear();
599 for (PackageInfo info : mContext.getPackageManager().getInstalledPackages(0)) {
600 if (tmpLastResumeTimes.containsKey(info.packageName)) {
601 mLastResumeTimes.put(info.packageName, tmpLastResumeTimes.get(info.packageName));
602 }
603 }
604 }
605 }
606
607 private void writeHistoryStatsFLOCK(AtomicFile historyFile) {
608 FileOutputStream fos = null;
609 try {
610 fos = historyFile.startWrite();
611 XmlSerializer out = new FastXmlSerializer();
612 out.setOutput(fos, "utf-8");
613 out.startDocument(null, true);
614 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
615 out.startTag(null, "usage-history");
616 synchronized (mStatsLock) {
617 for (Map.Entry<String, Map<String, Long>> pkgEntry : mLastResumeTimes.entrySet()) {
618 out.startTag(null, "pkg");
619 out.attribute(null, "name", pkgEntry.getKey());
620 for (Map.Entry<String, Long> compEntry : pkgEntry.getValue().entrySet()) {
621 out.startTag(null, "comp");
622 out.attribute(null, "name", compEntry.getKey());
623 out.attribute(null, "lrt", compEntry.getValue().toString());
624 out.endTag(null, "comp");
625 }
626 out.endTag(null, "pkg");
627 }
628 }
629 out.endTag(null, "usage-history");
630 out.endDocument();
631
632 historyFile.finishWrite(fos);
633 } catch (IOException e) {
634 Slog.w(TAG,"Error writing history stats" + e);
635 if (fos != null) {
636 historyFile.failWrite(fos);
637 }
638 }
639 }
640
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800641 public void publish(Context context) {
642 mContext = context;
643 ServiceManager.addService(SERVICE_NAME, asBinder());
644 }
Mark Brophyc6350272011-08-05 16:16:39 +0100645
646 /**
647 * Start watching packages to remove stats when a package is uninstalled.
648 * May only be called when the package manager is ready.
649 */
650 public void monitorPackages() {
651 mPackageMonitor = new PackageMonitor() {
652 @Override
653 public void onPackageRemoved(String packageName, int uid) {
654 synchronized (mStatsLock) {
655 mLastResumeTimes.remove(packageName);
656 }
657 }
658 };
Dianne Hackbornd0d75032012-04-19 23:12:09 -0700659 mPackageMonitor.register(mContext, null, true);
Mark Brophyc6350272011-08-05 16:16:39 +0100660 filterHistoryStats();
Dianne Hackborn55280a92009-05-07 15:53:46 -0700661 }
Mark Brophyc6350272011-08-05 16:16:39 +0100662
663 public void shutdown() {
664 if (mPackageMonitor != null) {
665 mPackageMonitor.unregister();
666 }
667 Slog.i(TAG, "Writing usage stats before shutdown...");
668 writeStatsToFile(true, true);
669 }
670
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800671 public static IUsageStats getService() {
672 if (sService != null) {
673 return sService;
674 }
675 IBinder b = ServiceManager.getService(SERVICE_NAME);
676 sService = asInterface(b);
677 return sService;
678 }
679
680 public void noteResumeComponent(ComponentName componentName) {
681 enforceCallingPermission();
682 String pkgName;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800683 synchronized (mStatsLock) {
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700684 if ((componentName == null) ||
685 ((pkgName = componentName.getPackageName()) == null)) {
686 return;
687 }
688
689 final boolean samePackage = pkgName.equals(mLastResumedPkg);
690 if (mIsResumed) {
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700691 if (mLastResumedPkg != null) {
692 // We last resumed some other package... just pause it now
693 // to recover.
Dianne Hackborncef65ee2010-09-30 18:27:22 -0700694 if (REPORT_UNEXPECTED) Slog.i(TAG, "Unexpected resume of " + pkgName
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700695 + " while already resumed in " + mLastResumedPkg);
696 PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg);
697 if (pus != null) {
698 pus.updatePause();
699 }
700 }
701 }
702
703 final boolean sameComp = samePackage
704 && componentName.getClassName().equals(mLastResumedComp);
705
706 mIsResumed = true;
707 mLastResumedPkg = pkgName;
708 mLastResumedComp = componentName.getClassName();
709
Joe Onorato8a9b2202010-02-26 18:56:32 -0800710 if (localLOGV) Slog.i(TAG, "started component:" + pkgName);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800711 PkgUsageStatsExtended pus = mStats.get(pkgName);
712 if (pus == null) {
713 pus = new PkgUsageStatsExtended();
714 mStats.put(pkgName, pus);
715 }
Mark Brophy9fc03302011-07-01 16:56:24 +0100716 pus.updateResume(mLastResumedComp, !samePackage);
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700717 if (!sameComp) {
718 pus.addLaunchCount(mLastResumedComp);
719 }
Mark Brophyc6350272011-08-05 16:16:39 +0100720
721 Map<String, Long> componentResumeTimes = mLastResumeTimes.get(pkgName);
722 if (componentResumeTimes == null) {
723 componentResumeTimes = new HashMap<String, Long>();
724 mLastResumeTimes.put(pkgName, componentResumeTimes);
725 }
726 componentResumeTimes.put(mLastResumedComp, System.currentTimeMillis());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800727 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800728 }
729
730 public void notePauseComponent(ComponentName componentName) {
731 enforceCallingPermission();
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700732
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 synchronized (mStatsLock) {
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700734 String pkgName;
735 if ((componentName == null) ||
736 ((pkgName = componentName.getPackageName()) == null)) {
737 return;
738 }
739 if (!mIsResumed) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -0700740 if (REPORT_UNEXPECTED) Slog.i(TAG, "Something wrong here, didn't expect "
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700741 + pkgName + " to be paused");
742 return;
743 }
744 mIsResumed = false;
745
Joe Onorato8a9b2202010-02-26 18:56:32 -0800746 if (localLOGV) Slog.i(TAG, "paused component:"+pkgName);
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700747
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800748 PkgUsageStatsExtended pus = mStats.get(pkgName);
749 if (pus == null) {
750 // Weird some error here
Joe Onorato8a9b2202010-02-26 18:56:32 -0800751 Slog.i(TAG, "No package stats for pkg:"+pkgName);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800752 return;
753 }
754 pus.updatePause();
755 }
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700756
757 // Persist current data to file if needed.
Mark Brophyc6350272011-08-05 16:16:39 +0100758 writeStatsToFile(false, false);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700759 }
760
761 public void noteLaunchTime(ComponentName componentName, int millis) {
762 enforceCallingPermission();
763 String pkgName;
764 if ((componentName == null) ||
765 ((pkgName = componentName.getPackageName()) == null)) {
766 return;
767 }
768
769 // Persist current data to file if needed.
Mark Brophyc6350272011-08-05 16:16:39 +0100770 writeStatsToFile(false, false);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700771
772 synchronized (mStatsLock) {
773 PkgUsageStatsExtended pus = mStats.get(pkgName);
774 if (pus != null) {
775 pus.addLaunchTime(componentName.getClassName(), millis);
776 }
777 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778 }
779
780 public void enforceCallingPermission() {
781 if (Binder.getCallingPid() == Process.myPid()) {
782 return;
783 }
784 mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
785 Binder.getCallingPid(), Binder.getCallingUid(), null);
786 }
787
788 public PkgUsageStats getPkgUsageStats(ComponentName componentName) {
789 mContext.enforceCallingOrSelfPermission(
790 android.Manifest.permission.PACKAGE_USAGE_STATS, null);
791 String pkgName;
792 if ((componentName == null) ||
793 ((pkgName = componentName.getPackageName()) == null)) {
794 return null;
795 }
796 synchronized (mStatsLock) {
797 PkgUsageStatsExtended pus = mStats.get(pkgName);
Mark Brophyc6350272011-08-05 16:16:39 +0100798 Map<String, Long> lastResumeTimes = mLastResumeTimes.get(pkgName);
799 if (pus == null && lastResumeTimes == null) {
800 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801 }
Mark Brophyc6350272011-08-05 16:16:39 +0100802 int launchCount = pus != null ? pus.mLaunchCount : 0;
803 long usageTime = pus != null ? pus.mUsageTime : 0;
804 return new PkgUsageStats(pkgName, launchCount, usageTime, lastResumeTimes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800805 }
806 }
807
808 public PkgUsageStats[] getAllPkgUsageStats() {
809 mContext.enforceCallingOrSelfPermission(
810 android.Manifest.permission.PACKAGE_USAGE_STATS, null);
811 synchronized (mStatsLock) {
Mark Brophyc6350272011-08-05 16:16:39 +0100812 int size = mLastResumeTimes.size();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800813 if (size <= 0) {
814 return null;
815 }
816 PkgUsageStats retArr[] = new PkgUsageStats[size];
817 int i = 0;
Mark Brophyc6350272011-08-05 16:16:39 +0100818 for (Map.Entry<String, Map<String, Long>> entry : mLastResumeTimes.entrySet()) {
819 String pkg = entry.getKey();
820 long usageTime = 0;
821 int launchCount = 0;
822
823 PkgUsageStatsExtended pus = mStats.get(pkg);
824 if (pus != null) {
825 usageTime = pus.mUsageTime;
826 launchCount = pus.mLaunchCount;
827 }
828 retArr[i] = new PkgUsageStats(pkg, launchCount, usageTime, entry.getValue());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800829 i++;
830 }
831 return retArr;
832 }
833 }
834
835 static byte[] readFully(FileInputStream stream) throws java.io.IOException {
836 int pos = 0;
837 int avail = stream.available();
838 byte[] data = new byte[avail];
839 while (true) {
840 int amt = stream.read(data, pos, data.length-pos);
841 if (amt <= 0) {
842 return data;
843 }
844 pos += amt;
845 avail = stream.available();
846 if (avail > data.length-pos) {
847 byte[] newData = new byte[pos+avail];
848 System.arraycopy(data, 0, newData, 0, pos);
849 data = newData;
850 }
851 }
852 }
853
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700854 private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput,
Dianne Hackborn9fdbf6a2009-07-19 14:18:51 -0700855 boolean deleteAfterPrint, HashSet<String> packages) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800856 List<String> fileList = getUsageStatsFileListFLOCK();
857 if (fileList == null) {
858 return;
859 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800860 Collections.sort(fileList);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800861 for (String file : fileList) {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700862 if (deleteAfterPrint && file.equalsIgnoreCase(mFileLeaf)) {
863 // In this mode we don't print the current day's stats, since
864 // they are incomplete.
865 continue;
866 }
867 File dFile = new File(mDir, file);
868 String dateStr = file.substring(FILE_PREFIX.length());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800869 try {
870 Parcel in = getParcelForFile(dFile);
Dianne Hackborn9fdbf6a2009-07-19 14:18:51 -0700871 collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput,
872 packages);
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700873 if (deleteAfterPrint) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800874 // Delete old file after collecting info only for checkin requests
875 dFile.delete();
876 }
877 } catch (FileNotFoundException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800878 Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : " + file);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800879 return;
880 } catch (IOException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800881 Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800882 }
883 }
884 }
885
886 private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw,
Dianne Hackborn9fdbf6a2009-07-19 14:18:51 -0700887 String date, boolean isCompactOutput, HashSet<String> packages) {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700888 StringBuilder sb = new StringBuilder(512);
889 if (isCompactOutput) {
890 sb.append("D:");
891 sb.append(CHECKIN_VERSION);
892 sb.append(',');
893 } else {
894 sb.append("Date: ");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800895 }
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700896
897 sb.append(date);
898
899 int vers = in.readInt();
900 if (vers != VERSION) {
901 sb.append(" (old data version)");
902 pw.println(sb.toString());
903 return;
904 }
905
906 pw.println(sb.toString());
907 int N = in.readInt();
908
909 while (N > 0) {
910 N--;
911 String pkgName = in.readString();
912 if (pkgName == null) {
913 break;
914 }
915 sb.setLength(0);
916 PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
Dianne Hackborn9fdbf6a2009-07-19 14:18:51 -0700917 if (packages != null && !packages.contains(pkgName)) {
918 // This package has not been requested -- don't print
919 // anything for it.
920 } else if (isCompactOutput) {
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700921 sb.append("P:");
922 sb.append(pkgName);
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700923 sb.append(',');
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700924 sb.append(pus.mLaunchCount);
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700925 sb.append(',');
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700926 sb.append(pus.mUsageTime);
927 sb.append('\n');
928 final int NC = pus.mLaunchTimes.size();
929 if (NC > 0) {
930 for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) {
931 sb.append("A:");
Christian Sonntag6639bb62009-08-13 11:51:13 -0700932 String activity = ent.getKey();
933 if (activity.startsWith(pkgName)) {
934 sb.append('*');
935 sb.append(activity.substring(
936 pkgName.length(), activity.length()));
937 } else {
938 sb.append(activity);
939 }
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700940 TimeStats times = ent.getValue();
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700941 sb.append(',');
942 sb.append(times.count);
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700943 for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
944 sb.append(",");
945 sb.append(times.times[i]);
946 }
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700947 sb.append('\n');
948 }
949 }
950
951 } else {
952 sb.append(" ");
953 sb.append(pkgName);
954 sb.append(": ");
955 sb.append(pus.mLaunchCount);
956 sb.append(" times, ");
957 sb.append(pus.mUsageTime);
958 sb.append(" ms");
959 sb.append('\n');
960 final int NC = pus.mLaunchTimes.size();
961 if (NC > 0) {
962 for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) {
963 sb.append(" ");
964 sb.append(ent.getKey());
965 TimeStats times = ent.getValue();
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700966 sb.append(": ");
967 sb.append(times.count);
968 sb.append(" starts");
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700969 int lastBin = 0;
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700970 for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) {
971 if (times.times[i] != 0) {
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700972 sb.append(", ");
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700973 sb.append(lastBin);
974 sb.append('-');
975 sb.append(LAUNCH_TIME_BINS[i]);
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700976 sb.append("ms=");
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700977 sb.append(times.times[i]);
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700978 }
979 lastBin = LAUNCH_TIME_BINS[i];
980 }
981 if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) {
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700982 sb.append(", ");
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700983 sb.append(">=");
984 sb.append(lastBin);
Dianne Hackborn760ec4a2009-06-17 20:05:26 -0700985 sb.append("ms=");
Dianne Hackbornf210d6b2009-04-13 18:42:49 -0700986 sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]);
987 }
Dianne Hackborn6447ca32009-04-07 19:50:08 -0700988 sb.append('\n');
989 }
990 }
991 }
992
993 pw.write(sb.toString());
994 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800995 }
996
997 /**
998 * Searches array of arguments for the specified string
999 * @param args array of argument strings
1000 * @param value value to search for
1001 * @return true if the value is contained in the array
1002 */
1003 private static boolean scanArgs(String[] args, String value) {
1004 if (args != null) {
1005 for (String arg : args) {
1006 if (value.equals(arg)) {
1007 return true;
1008 }
1009 }
1010 }
1011 return false;
1012 }
1013
Dianne Hackborn9fdbf6a2009-07-19 14:18:51 -07001014 /**
1015 * Searches array of arguments for the specified string's data
1016 * @param args array of argument strings
1017 * @param value value to search for
1018 * @return the string of data after the arg, or null if there is none
1019 */
1020 private static String scanArgsData(String[] args, String value) {
1021 if (args != null) {
1022 final int N = args.length;
1023 for (int i=0; i<N; i++) {
1024 if (value.equals(args[i])) {
1025 i++;
1026 return i < N ? args[i] : null;
1027 }
1028 }
1029 }
1030 return null;
1031 }
1032
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001033 @Override
1034 /*
1035 * The data persisted to file is parsed and the stats are computed.
1036 */
1037 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Kenny Root3abd75b2011-09-29 11:00:41 -07001038 if (mContext.checkCallingPermission(android.Manifest.permission.DUMP)
1039 != PackageManager.PERMISSION_GRANTED) {
1040 pw.println("Permission Denial: can't dump UsageStats from from pid="
1041 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
1042 + " without permission " + android.Manifest.permission.DUMP);
1043 return;
1044 }
1045
Dianne Hackborn6447ca32009-04-07 19:50:08 -07001046 final boolean isCheckinRequest = scanArgs(args, "--checkin");
1047 final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c");
1048 final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d");
Dianne Hackborn9fdbf6a2009-07-19 14:18:51 -07001049 final String rawPackages = scanArgsData(args, "--packages");
Dianne Hackborn6447ca32009-04-07 19:50:08 -07001050
1051 // Make sure the current stats are written to the file. This
1052 // doesn't need to be done if we are deleting files after printing,
1053 // since it that case we won't print the current stats.
1054 if (!deleteAfterPrint) {
Mark Brophyc6350272011-08-05 16:16:39 +01001055 writeStatsToFile(true, false);
Dianne Hackborn6447ca32009-04-07 19:50:08 -07001056 }
1057
Dianne Hackborn9fdbf6a2009-07-19 14:18:51 -07001058 HashSet<String> packages = null;
1059 if (rawPackages != null) {
1060 if (!"*".equals(rawPackages)) {
1061 // A * is a wildcard to show all packages.
1062 String[] names = rawPackages.split(",");
1063 for (String n : names) {
1064 if (packages == null) {
1065 packages = new HashSet<String>();
1066 }
1067 packages.add(n);
1068 }
1069 }
1070 } else if (isCheckinRequest) {
1071 // If checkin doesn't specify any packages, then we simply won't
1072 // show anything.
Joe Onorato8a9b2202010-02-26 18:56:32 -08001073 Slog.w(TAG, "Checkin without packages");
Dianne Hackborn9fdbf6a2009-07-19 14:18:51 -07001074 return;
1075 }
1076
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077 synchronized (mFileLock) {
Dianne Hackborn9fdbf6a2009-07-19 14:18:51 -07001078 collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint, packages);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001079 }
1080 }
1081
1082}