• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.server.am;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.pm.PackageInfo;
22 import android.content.pm.PackageManager;
23 import android.os.Binder;
24 import android.os.IBinder;
25 import android.os.FileUtils;
26 import android.os.Parcel;
27 import android.os.Process;
28 import android.os.ServiceManager;
29 import android.os.SystemClock;
30 import android.util.Slog;
31 import android.util.Xml;
32 
33 import com.android.internal.app.IUsageStats;
34 import com.android.internal.content.PackageMonitor;
35 import com.android.internal.os.AtomicFile;
36 import com.android.internal.os.PkgUsageStats;
37 import com.android.internal.util.FastXmlSerializer;
38 
39 import org.xmlpull.v1.XmlPullParser;
40 import org.xmlpull.v1.XmlPullParserException;
41 import org.xmlpull.v1.XmlSerializer;
42 
43 import java.io.File;
44 import java.io.FileDescriptor;
45 import java.io.FileInputStream;
46 import java.io.FileNotFoundException;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.io.PrintWriter;
50 import java.util.ArrayList;
51 import java.util.Calendar;
52 import java.util.Collections;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Set;
58 import java.util.TimeZone;
59 import java.util.concurrent.atomic.AtomicBoolean;
60 import java.util.concurrent.atomic.AtomicInteger;
61 import java.util.concurrent.atomic.AtomicLong;
62 
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  */
69 public final class UsageStatsService extends IUsageStats.Stub {
70     public static final String SERVICE_NAME = "usagestats";
71     private static final boolean localLOGV = false;
72     private static final boolean REPORT_UNEXPECTED = false;
73     private static final String TAG = "UsageStats";
74 
75     // Current on-disk Parcel version
76     private static final int VERSION = 1007;
77 
78     private static final int CHECKIN_VERSION = 4;
79 
80     private static final String FILE_PREFIX = "usage-";
81 
82     private static final String FILE_HISTORY = FILE_PREFIX + "history.xml";
83 
84     private static final int FILE_WRITE_INTERVAL = 30*60*1000; //ms
85 
86     private static final int MAX_NUM_FILES = 5;
87 
88     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     };
92 
93     static IUsageStats sService;
94     private Context mContext;
95     // structure used to maintain statistics since the last checkin.
96     final private Map<String, PkgUsageStatsExtended> mStats;
97 
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 
104     // 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
111     private String mLastResumedPkg;
112     private String mLastResumedComp;
113     private boolean mIsResumed;
114     private File mFile;
115     private AtomicFile mHistoryFile;
116     private String mFileLeaf;
117     private File mDir;
118 
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);
124 
125     static class TimeStats {
126         int count;
127         int[] times = new int[NUM_LAUNCH_TIME_BINS];
128 
TimeStats()129         TimeStats() {
130         }
131 
incCount()132         void incCount() {
133             count++;
134         }
135 
add(int val)136         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;
142                 }
143             }
144             times[NUM_LAUNCH_TIME_BINS-1]++;
145         }
146 
TimeStats(Parcel in)147         TimeStats(Parcel in) {
148             count = in.readInt();
149             final int[] localTimes = times;
150             for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
151                 localTimes[i] = in.readInt();
152             }
153         }
154 
writeToParcel(Parcel out)155         void writeToParcel(Parcel out) {
156             out.writeInt(count);
157             final int[] localTimes = times;
158             for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
159                 out.writeInt(localTimes[i]);
160             }
161         }
162     }
163 
164     private class PkgUsageStatsExtended {
165         final HashMap<String, TimeStats> mLaunchTimes
166                 = new HashMap<String, TimeStats>();
167         int mLaunchCount;
168         long mUsageTime;
169         long mPausedTime;
170         long mResumedTime;
171 
PkgUsageStatsExtended()172         PkgUsageStatsExtended() {
173             mLaunchCount = 0;
174             mUsageTime = 0;
175         }
176 
PkgUsageStatsExtended(Parcel in)177         PkgUsageStatsExtended(Parcel in) {
178             mLaunchCount = in.readInt();
179             mUsageTime = in.readLong();
180             if (localLOGV) Slog.v(TAG, "Launch count: " + mLaunchCount
181                     + ", Usage time:" + mUsageTime);
182 
183             final int numTimeStats = in.readInt();
184             if (localLOGV) Slog.v(TAG, "Reading comps: " + numTimeStats);
185             for (int i=0; i<numTimeStats; i++) {
186                 String comp = in.readString();
187                 if (localLOGV) Slog.v(TAG, "Component: " + comp);
188                 TimeStats times = new TimeStats(in);
189                 mLaunchTimes.put(comp, times);
190             }
191         }
192 
updateResume(String comp, boolean launched)193         void updateResume(String comp, boolean launched) {
194             if (launched) {
195                 mLaunchCount ++;
196             }
197             mResumedTime = SystemClock.elapsedRealtime();
198         }
199 
updatePause()200         void updatePause() {
201             mPausedTime =  SystemClock.elapsedRealtime();
202             mUsageTime += (mPausedTime - mResumedTime);
203         }
204 
addLaunchCount(String comp)205         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 
addLaunchTime(String comp, int millis)214         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 
writeToParcel(Parcel out)223         void writeToParcel(Parcel out) {
224             out.writeInt(mLaunchCount);
225             out.writeLong(mUsageTime);
226             final int numTimeStats = mLaunchTimes.size();
227             out.writeInt(numTimeStats);
228             if (numTimeStats > 0) {
229                 for (Map.Entry<String, TimeStats> ent : mLaunchTimes.entrySet()) {
230                     out.writeString(ent.getKey());
231                     TimeStats times = ent.getValue();
232                     times.writeToParcel(out);
233                 }
234             }
235         }
236 
clear()237         void clear() {
238             mLaunchTimes.clear();
239             mLaunchCount = 0;
240             mUsageTime = 0;
241         }
242     }
243 
UsageStatsService(String dir)244     UsageStatsService(String dir) {
245         mStats = new HashMap<String, PkgUsageStatsExtended>();
246         mLastResumeTimes = new HashMap<String, Map<String, Long>>();
247         mStatsLock = new Object();
248         mFileLock = new Object();
249         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)) {
263                     Slog.i(TAG, "Deleting old usage file: " + fList[i]);
264                     (new File(parentDir, fList[i])).delete();
265                 }
266             }
267         }
268 
269         // Update current stats which are binned by date
270         mFileLeaf = getCurrentDateStr(FILE_PREFIX);
271         mFile = new File(mDir, mFileLeaf);
272         mHistoryFile = new AtomicFile(new File(mDir, FILE_HISTORY));
273         readStatsFromFile();
274         readHistoryStatsFromFile();
275         mLastWriteElapsedTime.set(SystemClock.elapsedRealtime());
276         // mCal was set by getCurrentDateStr(), want to use that same time.
277         mLastWriteDay.set(mCal.get(Calendar.DAY_OF_YEAR));
278     }
279 
280     /*
281      * Utility method to convert date into string.
282      */
getCurrentDateStr(String prefix)283     private String getCurrentDateStr(String prefix) {
284         StringBuilder sb = new StringBuilder();
285         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);
301         }
302         return sb.toString();
303     }
304 
getParcelForFile(File file)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 
readStatsFromFile()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) {
327                 Slog.w(TAG,"Error : " + e + " reading data from file:" + newFile);
328             }
329         }
330     }
331 
readStatsFLOCK(File file)332     private void readStatsFLOCK(File file) throws IOException {
333         Parcel in = getParcelForFile(file);
334         int vers = in.readInt();
335         if (vers != VERSION) {
336             Slog.w(TAG, "Usage stats version changed; dropping");
337             return;
338         }
339         int N = in.readInt();
340         while (N > 0) {
341             N--;
342             String pkgName = in.readString();
343             if (pkgName == null) {
344                 break;
345             }
346             if (localLOGV) Slog.v(TAG, "Reading package #" + N + ": " + pkgName);
347             PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
348             synchronized (mStatsLock) {
349                 mStats.put(pkgName, pus);
350             }
351         }
352     }
353 
readHistoryStatsFromFile()354     private void readHistoryStatsFromFile() {
355         synchronized (mFileLock) {
356             if (mHistoryFile.getBaseFile().exists()) {
357                 readHistoryStatsFLOCK(mHistoryFile);
358             }
359         }
360     }
361 
readHistoryStatsFLOCK(AtomicFile file)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 
getUsageStatsFileListFLOCK()421     private ArrayList<String> getUsageStatsFileListFLOCK() {
422         // Check if there are too many files in the system and delete older files
423         String fList[] = mDir.list();
424         if (fList == null) {
425             return null;
426         }
427         ArrayList<String> fileList = new ArrayList<String>();
428         for (String file : fList) {
429             if (!file.startsWith(FILE_PREFIX)) {
430                 continue;
431             }
432             if (file.endsWith(".bak")) {
433                 (new File(mDir, file)).delete();
434                 continue;
435             }
436             fileList.add(file);
437         }
438         return fileList;
439     }
440 
checkFileLimitFLOCK()441     private void checkFileLimitFLOCK() {
442         // 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();
449         if (count <= MAX_NUM_FILES) {
450             return;
451         }
452         // Sort files
453         Collections.sort(fileList);
454         count -= MAX_NUM_FILES;
455         // Delete older files
456         for (int i = 0; i < count; i++) {
457             String fileName = fileList.get(i);
458             File file = new File(mDir, fileName);
459             Slog.i(TAG, "Deleting usage file : " + fileName);
460             file.delete();
461         }
462     }
463 
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.
474      * @params forceWriteHistoryStats Force writing of historical stats.
475      */
writeStatsToFile(final boolean force, final boolean forceWriteHistoryStats)476     private void writeStatsToFile(final boolean force, final boolean forceWriteHistoryStats) {
477         int curDay;
478         synchronized (mCal) {
479             mCal.setTimeInMillis(System.currentTimeMillis());
480             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;
496             }
497             if (mUnforcedDiskWriteRunning.compareAndSet(false, true)) {
498                 new Thread("UsageStatsService_DiskWriter") {
499                     public void run() {
500                         try {
501                             if (localLOGV) Slog.d(TAG, "Disk writer thread starting.");
502                             writeStatsToFile(true, false);
503                         } finally {
504                             mUnforcedDiskWriteRunning.set(false);
505                             if (localLOGV) Slog.d(TAG, "Disk writer thread ending.");
506                         }
507                     }
508                 }.start();
509             }
510             return;
511         }
512 
513         synchronized (mFileLock) {
514             // Get the most recent file
515             mFileLeaf = getCurrentDateStr(FILE_PREFIX);
516             // Copy current file to back up
517             File backupFile = null;
518             if (mFile != null && mFile.exists()) {
519                 backupFile = new File(mFile.getPath() + ".bak");
520                 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();
527                 }
528             }
529 
530             try {
531                 // Write mStats to file
532                 writeStatsFLOCK(mFile);
533                 mLastWriteElapsedTime.set(currElapsedTime);
534                 if (dayChanged) {
535                     mLastWriteDay.set(curDay);
536                     // clear stats
537                     synchronized (mStats) {
538                         mStats.clear();
539                     }
540                     mFile = new File(mDir, mFileLeaf);
541                     checkFileLimitFLOCK();
542                 }
543 
544                 if (dayChanged || forceWriteHistoryStats) {
545                     // Write history stats daily, or when forced (due to shutdown).
546                     writeHistoryStatsFLOCK(mHistoryFile);
547                 }
548 
549                 // Delete the backup file
550                 if (backupFile != null) {
551                     backupFile.delete();
552                 }
553             } catch (IOException e) {
554                 Slog.w(TAG, "Failed writing stats to file:" + mFile);
555                 if (backupFile != null) {
556                     mFile.delete();
557                     backupFile.renameTo(mFile);
558                 }
559             }
560         }
561         if (localLOGV) Slog.d(TAG, "Dumped usage stats.");
562     }
563 
writeStatsFLOCK(File file)564     private void writeStatsFLOCK(File file) throws IOException {
565         FileOutputStream stream = new FileOutputStream(file);
566         try {
567             Parcel out = Parcel.obtain();
568             writeStatsToParcelFLOCK(out);
569             stream.write(out.marshall());
570             out.recycle();
571             stream.flush();
572         } finally {
573             FileUtils.sync(stream);
574             stream.close();
575         }
576     }
577 
writeStatsToParcelFLOCK(Parcel out)578     private void writeStatsToParcelFLOCK(Parcel out) {
579         synchronized (mStatsLock) {
580             out.writeInt(VERSION);
581             Set<String> keys = mStats.keySet();
582             out.writeInt(keys.size());
583             for (String key : keys) {
584                 PkgUsageStatsExtended pus = mStats.get(key);
585                 out.writeString(key);
586                 pus.writeToParcel(out);
587             }
588         }
589     }
590 
591     /** Filter out stats for any packages which aren't present anymore. */
filterHistoryStats()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 
writeHistoryStatsFLOCK(AtomicFile historyFile)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 
publish(Context context)641     public void publish(Context context) {
642         mContext = context;
643         ServiceManager.addService(SERVICE_NAME, asBinder());
644     }
645 
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      */
monitorPackages()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         };
659         mPackageMonitor.register(mContext, true);
660         filterHistoryStats();
661     }
662 
shutdown()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 
getService()671     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 
noteResumeComponent(ComponentName componentName)680     public void noteResumeComponent(ComponentName componentName) {
681         enforceCallingPermission();
682         String pkgName;
683         synchronized (mStatsLock) {
684             if ((componentName == null) ||
685                     ((pkgName = componentName.getPackageName()) == null)) {
686                 return;
687             }
688 
689             final boolean samePackage = pkgName.equals(mLastResumedPkg);
690             if (mIsResumed) {
691                 if (mLastResumedPkg != null) {
692                     // We last resumed some other package...  just pause it now
693                     // to recover.
694                     if (REPORT_UNEXPECTED) Slog.i(TAG, "Unexpected resume of " + pkgName
695                             + " 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 
710             if (localLOGV) Slog.i(TAG, "started component:" + pkgName);
711             PkgUsageStatsExtended pus = mStats.get(pkgName);
712             if (pus == null) {
713                 pus = new PkgUsageStatsExtended();
714                 mStats.put(pkgName, pus);
715             }
716             pus.updateResume(mLastResumedComp, !samePackage);
717             if (!sameComp) {
718                 pus.addLaunchCount(mLastResumedComp);
719             }
720 
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());
727         }
728     }
729 
notePauseComponent(ComponentName componentName)730     public void notePauseComponent(ComponentName componentName) {
731         enforceCallingPermission();
732 
733         synchronized (mStatsLock) {
734             String pkgName;
735             if ((componentName == null) ||
736                     ((pkgName = componentName.getPackageName()) == null)) {
737                 return;
738             }
739             if (!mIsResumed) {
740                 if (REPORT_UNEXPECTED) Slog.i(TAG, "Something wrong here, didn't expect "
741                         + pkgName + " to be paused");
742                 return;
743             }
744             mIsResumed = false;
745 
746             if (localLOGV) Slog.i(TAG, "paused component:"+pkgName);
747 
748             PkgUsageStatsExtended pus = mStats.get(pkgName);
749             if (pus == null) {
750                 // Weird some error here
751                 Slog.i(TAG, "No package stats for pkg:"+pkgName);
752                 return;
753             }
754             pus.updatePause();
755         }
756 
757         // Persist current data to file if needed.
758         writeStatsToFile(false, false);
759     }
760 
noteLaunchTime(ComponentName componentName, int millis)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.
770         writeStatsToFile(false, false);
771 
772         synchronized (mStatsLock) {
773             PkgUsageStatsExtended pus = mStats.get(pkgName);
774             if (pus != null) {
775                 pus.addLaunchTime(componentName.getClassName(), millis);
776             }
777         }
778     }
779 
enforceCallingPermission()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 
getPkgUsageStats(ComponentName componentName)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);
798             Map<String, Long> lastResumeTimes = mLastResumeTimes.get(pkgName);
799             if (pus == null && lastResumeTimes == null) {
800                 return null;
801             }
802             int launchCount = pus != null ? pus.mLaunchCount : 0;
803             long usageTime = pus != null ? pus.mUsageTime : 0;
804             return new PkgUsageStats(pkgName, launchCount, usageTime, lastResumeTimes);
805         }
806     }
807 
getAllPkgUsageStats()808     public PkgUsageStats[] getAllPkgUsageStats() {
809         mContext.enforceCallingOrSelfPermission(
810                 android.Manifest.permission.PACKAGE_USAGE_STATS, null);
811         synchronized (mStatsLock) {
812             int size = mLastResumeTimes.size();
813             if (size <= 0) {
814                 return null;
815             }
816             PkgUsageStats retArr[] = new PkgUsageStats[size];
817             int i = 0;
818             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());
829                 i++;
830             }
831             return retArr;
832         }
833     }
834 
readFully(FileInputStream stream)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 
collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput, boolean deleteAfterPrint, HashSet<String> packages)854     private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput,
855             boolean deleteAfterPrint, HashSet<String> packages) {
856         List<String> fileList = getUsageStatsFileListFLOCK();
857         if (fileList == null) {
858             return;
859         }
860         Collections.sort(fileList);
861         for (String file : fileList) {
862             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());
869             try {
870                 Parcel in = getParcelForFile(dFile);
871                 collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput,
872                         packages);
873                 if (deleteAfterPrint) {
874                     // Delete old file after collecting info only for checkin requests
875                     dFile.delete();
876                 }
877             } catch (FileNotFoundException e) {
878                 Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : " + file);
879                 return;
880             } catch (IOException e) {
881                 Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file);
882             }
883         }
884     }
885 
collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw, String date, boolean isCompactOutput, HashSet<String> packages)886     private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw,
887             String date, boolean isCompactOutput, HashSet<String> packages) {
888         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: ");
895         }
896 
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);
917             if (packages != null && !packages.contains(pkgName)) {
918                 // This package has not been requested -- don't print
919                 // anything for it.
920             } else if (isCompactOutput) {
921                 sb.append("P:");
922                 sb.append(pkgName);
923                 sb.append(',');
924                 sb.append(pus.mLaunchCount);
925                 sb.append(',');
926                 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:");
932                         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                         }
940                         TimeStats times = ent.getValue();
941                         sb.append(',');
942                         sb.append(times.count);
943                         for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
944                             sb.append(",");
945                             sb.append(times.times[i]);
946                         }
947                         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();
966                         sb.append(": ");
967                         sb.append(times.count);
968                         sb.append(" starts");
969                         int lastBin = 0;
970                         for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) {
971                             if (times.times[i] != 0) {
972                                 sb.append(", ");
973                                 sb.append(lastBin);
974                                 sb.append('-');
975                                 sb.append(LAUNCH_TIME_BINS[i]);
976                                 sb.append("ms=");
977                                 sb.append(times.times[i]);
978                             }
979                             lastBin = LAUNCH_TIME_BINS[i];
980                         }
981                         if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) {
982                             sb.append(", ");
983                             sb.append(">=");
984                             sb.append(lastBin);
985                             sb.append("ms=");
986                             sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]);
987                         }
988                         sb.append('\n');
989                     }
990                 }
991             }
992 
993             pw.write(sb.toString());
994         }
995     }
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      */
scanArgs(String[] args, String value)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 
1014     /**
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      */
scanArgsData(String[] args, String value)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 
1033     @Override
1034     /*
1035      * The data persisted to file is parsed and the stats are computed.
1036      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)1037     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1038         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 
1046         final boolean isCheckinRequest = scanArgs(args, "--checkin");
1047         final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c");
1048         final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d");
1049         final String rawPackages = scanArgsData(args, "--packages");
1050 
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) {
1055             writeStatsToFile(true, false);
1056         }
1057 
1058         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.
1073             Slog.w(TAG, "Checkin without packages");
1074             return;
1075         }
1076 
1077         synchronized (mFileLock) {
1078             collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint, packages);
1079         }
1080     }
1081 
1082 }
1083