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.app.AppGlobals; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.pm.IPackageManager; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.os.Binder; 26 import android.os.IBinder; 27 import android.os.FileUtils; 28 import android.os.Parcel; 29 import android.os.Process; 30 import android.os.RemoteException; 31 import android.os.ServiceManager; 32 import android.os.SystemClock; 33 import android.util.ArrayMap; 34 import android.util.AtomicFile; 35 import android.util.Slog; 36 import android.util.Xml; 37 38 import com.android.internal.app.IUsageStats; 39 import com.android.internal.content.PackageMonitor; 40 import com.android.internal.os.PkgUsageStats; 41 import com.android.internal.util.FastXmlSerializer; 42 43 import org.xmlpull.v1.XmlPullParser; 44 import org.xmlpull.v1.XmlPullParserException; 45 import org.xmlpull.v1.XmlSerializer; 46 47 import java.io.File; 48 import java.io.FileDescriptor; 49 import java.io.FileInputStream; 50 import java.io.FileNotFoundException; 51 import java.io.FileOutputStream; 52 import java.io.IOException; 53 import java.io.PrintWriter; 54 import java.util.ArrayList; 55 import java.util.Calendar; 56 import java.util.Collections; 57 import java.util.HashMap; 58 import java.util.HashSet; 59 import java.util.List; 60 import java.util.Map; 61 import java.util.Set; 62 import java.util.TimeZone; 63 import java.util.concurrent.atomic.AtomicBoolean; 64 import java.util.concurrent.atomic.AtomicInteger; 65 import java.util.concurrent.atomic.AtomicLong; 66 67 /** 68 * This service collects the statistics associated with usage 69 * of various components, like when a particular package is launched or 70 * paused and aggregates events like number of time a component is launched 71 * total duration of a component launch. 72 */ 73 public final class UsageStatsService extends IUsageStats.Stub { 74 public static final String SERVICE_NAME = "usagestats"; 75 private static final boolean localLOGV = false; 76 private static final boolean REPORT_UNEXPECTED = false; 77 private static final String TAG = "UsageStats"; 78 79 // Current on-disk Parcel version 80 private static final int VERSION = 1008; 81 82 private static final int CHECKIN_VERSION = 4; 83 84 private static final String FILE_PREFIX = "usage-"; 85 86 private static final String FILE_HISTORY = FILE_PREFIX + "history.xml"; 87 88 private static final int FILE_WRITE_INTERVAL = 30*60*1000; //ms 89 90 private static final int MAX_NUM_FILES = 5; 91 92 private static final int NUM_LAUNCH_TIME_BINS = 10; 93 private static final int[] LAUNCH_TIME_BINS = { 94 250, 500, 750, 1000, 1500, 2000, 3000, 4000, 5000 95 }; 96 97 static IUsageStats sService; 98 private Context mContext; 99 // structure used to maintain statistics since the last checkin. 100 final private ArrayMap<String, PkgUsageStatsExtended> mStats; 101 102 // Maintains the last time any component was resumed, for all time. 103 final private ArrayMap<String, ArrayMap<String, Long>> mLastResumeTimes; 104 105 // To remove last-resume time stats when a pacakge is removed. 106 private PackageMonitor mPackageMonitor; 107 108 // Lock to update package stats. Methods suffixed by SLOCK should invoked with 109 // this lock held 110 final Object mStatsLock; 111 // Lock to write to file. Methods suffixed by FLOCK should invoked with 112 // this lock held. 113 final Object mFileLock; 114 // Order of locks is mFileLock followed by mStatsLock to avoid deadlocks 115 private String mLastResumedPkg; 116 private String mLastResumedComp; 117 private boolean mIsResumed; 118 private File mFile; 119 private AtomicFile mHistoryFile; 120 private String mFileLeaf; 121 private File mDir; 122 123 private Calendar mCal; // guarded by itself 124 125 private final AtomicInteger mLastWriteDay = new AtomicInteger(-1); 126 private final AtomicLong mLastWriteElapsedTime = new AtomicLong(0); 127 private final AtomicBoolean mUnforcedDiskWriteRunning = new AtomicBoolean(false); 128 129 static class TimeStats { 130 int count; 131 int[] times = new int[NUM_LAUNCH_TIME_BINS]; 132 133 TimeStats() { 134 } 135 136 void incCount() { 137 count++; 138 } 139 140 void add(int val) { 141 final int[] bins = LAUNCH_TIME_BINS; 142 for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) { 143 if (val < bins[i]) { 144 times[i]++; 145 return; 146 } 147 } 148 times[NUM_LAUNCH_TIME_BINS-1]++; 149 } 150 151 TimeStats(Parcel in) { 152 count = in.readInt(); 153 final int[] localTimes = times; 154 for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) { 155 localTimes[i] = in.readInt(); 156 } 157 } 158 159 void writeToParcel(Parcel out) { 160 out.writeInt(count); 161 final int[] localTimes = times; 162 for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) { 163 out.writeInt(localTimes[i]); 164 } 165 } 166 } 167 168 private class PkgUsageStatsExtended { 169 final ArrayMap<String, TimeStats> mLaunchTimes 170 = new ArrayMap<String, TimeStats>(); 171 final ArrayMap<String, TimeStats> mFullyDrawnTimes 172 = new ArrayMap<String, TimeStats>(); 173 int mLaunchCount; 174 long mUsageTime; 175 long mPausedTime; 176 long mResumedTime; 177 178 PkgUsageStatsExtended() { 179 mLaunchCount = 0; 180 mUsageTime = 0; 181 } 182 183 PkgUsageStatsExtended(Parcel in) { 184 mLaunchCount = in.readInt(); 185 mUsageTime = in.readLong(); 186 if (localLOGV) Slog.v(TAG, "Launch count: " + mLaunchCount 187 + ", Usage time:" + mUsageTime); 188 189 final int numLaunchTimeStats = in.readInt(); 190 if (localLOGV) Slog.v(TAG, "Reading launch times: " + numLaunchTimeStats); 191 mLaunchTimes.ensureCapacity(numLaunchTimeStats); 192 for (int i=0; i<numLaunchTimeStats; i++) { 193 String comp = in.readString(); 194 if (localLOGV) Slog.v(TAG, "Component: " + comp); 195 TimeStats times = new TimeStats(in); 196 mLaunchTimes.put(comp, times); 197 } 198 199 final int numFullyDrawnTimeStats = in.readInt(); 200 if (localLOGV) Slog.v(TAG, "Reading fully drawn times: " + numFullyDrawnTimeStats); 201 mFullyDrawnTimes.ensureCapacity(numFullyDrawnTimeStats); 202 for (int i=0; i<numFullyDrawnTimeStats; i++) { 203 String comp = in.readString(); 204 if (localLOGV) Slog.v(TAG, "Component: " + comp); 205 TimeStats times = new TimeStats(in); 206 mFullyDrawnTimes.put(comp, times); 207 } 208 } 209 210 void updateResume(String comp, boolean launched) { 211 if (launched) { 212 mLaunchCount ++; 213 } 214 mResumedTime = SystemClock.elapsedRealtime(); 215 } 216 217 void updatePause() { 218 mPausedTime = SystemClock.elapsedRealtime(); 219 mUsageTime += (mPausedTime - mResumedTime); 220 } 221 222 void addLaunchCount(String comp) { 223 TimeStats times = mLaunchTimes.get(comp); 224 if (times == null) { 225 times = new TimeStats(); 226 mLaunchTimes.put(comp, times); 227 } 228 times.incCount(); 229 } 230 231 void addLaunchTime(String comp, int millis) { 232 TimeStats times = mLaunchTimes.get(comp); 233 if (times == null) { 234 times = new TimeStats(); 235 mLaunchTimes.put(comp, times); 236 } 237 times.add(millis); 238 } 239 240 void addFullyDrawnTime(String comp, int millis) { 241 TimeStats times = mFullyDrawnTimes.get(comp); 242 if (times == null) { 243 times = new TimeStats(); 244 mFullyDrawnTimes.put(comp, times); 245 } 246 times.add(millis); 247 } 248 249 void writeToParcel(Parcel out) { 250 out.writeInt(mLaunchCount); 251 out.writeLong(mUsageTime); 252 final int numLaunchTimeStats = mLaunchTimes.size(); 253 out.writeInt(numLaunchTimeStats); 254 for (int i=0; i<numLaunchTimeStats; i++) { 255 out.writeString(mLaunchTimes.keyAt(i)); 256 mLaunchTimes.valueAt(i).writeToParcel(out); 257 } 258 final int numFullyDrawnTimeStats = mFullyDrawnTimes.size(); 259 out.writeInt(numFullyDrawnTimeStats); 260 for (int i=0; i<numFullyDrawnTimeStats; i++) { 261 out.writeString(mFullyDrawnTimes.keyAt(i)); 262 mFullyDrawnTimes.valueAt(i).writeToParcel(out); 263 } 264 } 265 266 void clear() { 267 mLaunchTimes.clear(); 268 mFullyDrawnTimes.clear(); 269 mLaunchCount = 0; 270 mUsageTime = 0; 271 } 272 } 273 274 UsageStatsService(String dir) { 275 mStats = new ArrayMap<String, PkgUsageStatsExtended>(); 276 mLastResumeTimes = new ArrayMap<String, ArrayMap<String, Long>>(); 277 mStatsLock = new Object(); 278 mFileLock = new Object(); 279 mDir = new File(dir); 280 mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0")); 281 282 mDir.mkdir(); 283 284 // Remove any old usage files from previous versions. 285 File parentDir = mDir.getParentFile(); 286 String fList[] = parentDir.list(); 287 if (fList != null) { 288 String prefix = mDir.getName() + "."; 289 int i = fList.length; 290 while (i > 0) { 291 i--; 292 if (fList[i].startsWith(prefix)) { 293 Slog.i(TAG, "Deleting old usage file: " + fList[i]); 294 (new File(parentDir, fList[i])).delete(); 295 } 296 } 297 } 298 299 // Update current stats which are binned by date 300 mFileLeaf = getCurrentDateStr(FILE_PREFIX); 301 mFile = new File(mDir, mFileLeaf); 302 mHistoryFile = new AtomicFile(new File(mDir, FILE_HISTORY)); 303 readStatsFromFile(); 304 readHistoryStatsFromFile(); 305 mLastWriteElapsedTime.set(SystemClock.elapsedRealtime()); 306 // mCal was set by getCurrentDateStr(), want to use that same time. 307 mLastWriteDay.set(mCal.get(Calendar.DAY_OF_YEAR)); 308 } 309 310 /* 311 * Utility method to convert date into string. 312 */ 313 private String getCurrentDateStr(String prefix) { 314 StringBuilder sb = new StringBuilder(); 315 synchronized (mCal) { 316 mCal.setTimeInMillis(System.currentTimeMillis()); 317 if (prefix != null) { 318 sb.append(prefix); 319 } 320 sb.append(mCal.get(Calendar.YEAR)); 321 int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1; 322 if (mm < 10) { 323 sb.append("0"); 324 } 325 sb.append(mm); 326 int dd = mCal.get(Calendar.DAY_OF_MONTH); 327 if (dd < 10) { 328 sb.append("0"); 329 } 330 sb.append(dd); 331 } 332 return sb.toString(); 333 } 334 335 private Parcel getParcelForFile(File file) throws IOException { 336 FileInputStream stream = new FileInputStream(file); 337 byte[] raw = readFully(stream); 338 Parcel in = Parcel.obtain(); 339 in.unmarshall(raw, 0, raw.length); 340 in.setDataPosition(0); 341 stream.close(); 342 return in; 343 } 344 345 private void readStatsFromFile() { 346 File newFile = mFile; 347 synchronized (mFileLock) { 348 try { 349 if (newFile.exists()) { 350 readStatsFLOCK(newFile); 351 } else { 352 // Check for file limit before creating a new file 353 checkFileLimitFLOCK(); 354 newFile.createNewFile(); 355 } 356 } catch (IOException e) { 357 Slog.w(TAG,"Error : " + e + " reading data from file:" + newFile); 358 } 359 } 360 } 361 362 private void readStatsFLOCK(File file) throws IOException { 363 Parcel in = getParcelForFile(file); 364 int vers = in.readInt(); 365 if (vers != VERSION) { 366 Slog.w(TAG, "Usage stats version changed; dropping"); 367 return; 368 } 369 int N = in.readInt(); 370 while (N > 0) { 371 N--; 372 String pkgName = in.readString(); 373 if (pkgName == null) { 374 break; 375 } 376 if (localLOGV) Slog.v(TAG, "Reading package #" + N + ": " + pkgName); 377 PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in); 378 synchronized (mStatsLock) { 379 mStats.put(pkgName, pus); 380 } 381 } 382 } 383 384 private void readHistoryStatsFromFile() { 385 synchronized (mFileLock) { 386 if (mHistoryFile.getBaseFile().exists()) { 387 readHistoryStatsFLOCK(mHistoryFile); 388 } 389 } 390 } 391 392 private void readHistoryStatsFLOCK(AtomicFile file) { 393 FileInputStream fis = null; 394 try { 395 fis = mHistoryFile.openRead(); 396 XmlPullParser parser = Xml.newPullParser(); 397 parser.setInput(fis, null); 398 int eventType = parser.getEventType(); 399 while (eventType != XmlPullParser.START_TAG) { 400 eventType = parser.next(); 401 } 402 String tagName = parser.getName(); 403 if ("usage-history".equals(tagName)) { 404 String pkg = null; 405 do { 406 eventType = parser.next(); 407 if (eventType == XmlPullParser.START_TAG) { 408 tagName = parser.getName(); 409 int depth = parser.getDepth(); 410 if ("pkg".equals(tagName) && depth == 2) { 411 pkg = parser.getAttributeValue(null, "name"); 412 } else if ("comp".equals(tagName) && depth == 3 && pkg != null) { 413 String comp = parser.getAttributeValue(null, "name"); 414 String lastResumeTimeStr = parser.getAttributeValue(null, "lrt"); 415 if (comp != null && lastResumeTimeStr != null) { 416 try { 417 long lastResumeTime = Long.parseLong(lastResumeTimeStr); 418 synchronized (mStatsLock) { 419 ArrayMap<String, Long> lrt = mLastResumeTimes.get(pkg); 420 if (lrt == null) { 421 lrt = new ArrayMap<String, Long>(); 422 mLastResumeTimes.put(pkg, lrt); 423 } 424 lrt.put(comp, lastResumeTime); 425 } 426 } catch (NumberFormatException e) { 427 } 428 } 429 } 430 } else if (eventType == XmlPullParser.END_TAG) { 431 if ("pkg".equals(parser.getName())) { 432 pkg = null; 433 } 434 } 435 } while (eventType != XmlPullParser.END_DOCUMENT); 436 } 437 } catch (XmlPullParserException e) { 438 Slog.w(TAG,"Error reading history stats: " + e); 439 } catch (IOException e) { 440 Slog.w(TAG,"Error reading history stats: " + e); 441 } finally { 442 if (fis != null) { 443 try { 444 fis.close(); 445 } catch (IOException e) { 446 } 447 } 448 } 449 } 450 451 private ArrayList<String> getUsageStatsFileListFLOCK() { 452 // Check if there are too many files in the system and delete older files 453 String fList[] = mDir.list(); 454 if (fList == null) { 455 return null; 456 } 457 ArrayList<String> fileList = new ArrayList<String>(); 458 for (String file : fList) { 459 if (!file.startsWith(FILE_PREFIX)) { 460 continue; 461 } 462 if (file.endsWith(".bak")) { 463 (new File(mDir, file)).delete(); 464 continue; 465 } 466 fileList.add(file); 467 } 468 return fileList; 469 } 470 471 private void checkFileLimitFLOCK() { 472 // Get all usage stats output files 473 ArrayList<String> fileList = getUsageStatsFileListFLOCK(); 474 if (fileList == null) { 475 // Strange but we dont have to delete any thing 476 return; 477 } 478 int count = fileList.size(); 479 if (count <= MAX_NUM_FILES) { 480 return; 481 } 482 // Sort files 483 Collections.sort(fileList); 484 count -= MAX_NUM_FILES; 485 // Delete older files 486 for (int i = 0; i < count; i++) { 487 String fileName = fileList.get(i); 488 File file = new File(mDir, fileName); 489 Slog.i(TAG, "Deleting usage file : " + fileName); 490 file.delete(); 491 } 492 } 493 494 /** 495 * Conditionally start up a disk write if it's been awhile, or the 496 * day has rolled over. 497 * 498 * This is called indirectly from user-facing actions (when 499 * 'force' is false) so it tries to be quick, without writing to 500 * disk directly or acquiring heavy locks. 501 * 502 * @params force do an unconditional, synchronous stats flush 503 * to disk on the current thread. 504 * @params forceWriteHistoryStats Force writing of historical stats. 505 */ 506 private void writeStatsToFile(final boolean force, final boolean forceWriteHistoryStats) { 507 int curDay; 508 synchronized (mCal) { 509 mCal.setTimeInMillis(System.currentTimeMillis()); 510 curDay = mCal.get(Calendar.DAY_OF_YEAR); 511 } 512 final boolean dayChanged = curDay != mLastWriteDay.get(); 513 514 // Determine if the day changed... note that this will be wrong 515 // if the year has changed but we are in the same day of year... 516 // we can probably live with this. 517 final long currElapsedTime = SystemClock.elapsedRealtime(); 518 519 // Fast common path, without taking the often-contentious 520 // mFileLock. 521 if (!force) { 522 if (!dayChanged && 523 (currElapsedTime - mLastWriteElapsedTime.get()) < FILE_WRITE_INTERVAL) { 524 // wait till the next update 525 return; 526 } 527 if (mUnforcedDiskWriteRunning.compareAndSet(false, true)) { 528 new Thread("UsageStatsService_DiskWriter") { 529 public void run() { 530 try { 531 if (localLOGV) Slog.d(TAG, "Disk writer thread starting."); 532 writeStatsToFile(true, false); 533 } finally { 534 mUnforcedDiskWriteRunning.set(false); 535 if (localLOGV) Slog.d(TAG, "Disk writer thread ending."); 536 } 537 } 538 }.start(); 539 } 540 return; 541 } 542 543 synchronized (mFileLock) { 544 // Get the most recent file 545 mFileLeaf = getCurrentDateStr(FILE_PREFIX); 546 // Copy current file to back up 547 File backupFile = null; 548 if (mFile != null && mFile.exists()) { 549 backupFile = new File(mFile.getPath() + ".bak"); 550 if (!backupFile.exists()) { 551 if (!mFile.renameTo(backupFile)) { 552 Slog.w(TAG, "Failed to persist new stats"); 553 return; 554 } 555 } else { 556 mFile.delete(); 557 } 558 } 559 560 try { 561 // Write mStats to file 562 writeStatsFLOCK(mFile); 563 mLastWriteElapsedTime.set(currElapsedTime); 564 if (dayChanged) { 565 mLastWriteDay.set(curDay); 566 // clear stats 567 synchronized (mStats) { 568 mStats.clear(); 569 } 570 mFile = new File(mDir, mFileLeaf); 571 checkFileLimitFLOCK(); 572 } 573 574 if (dayChanged || forceWriteHistoryStats) { 575 // Write history stats daily, or when forced (due to shutdown). 576 writeHistoryStatsFLOCK(mHistoryFile); 577 } 578 579 // Delete the backup file 580 if (backupFile != null) { 581 backupFile.delete(); 582 } 583 } catch (IOException e) { 584 Slog.w(TAG, "Failed writing stats to file:" + mFile); 585 if (backupFile != null) { 586 mFile.delete(); 587 backupFile.renameTo(mFile); 588 } 589 } 590 } 591 if (localLOGV) Slog.d(TAG, "Dumped usage stats."); 592 } 593 594 private void writeStatsFLOCK(File file) throws IOException { 595 FileOutputStream stream = new FileOutputStream(file); 596 try { 597 Parcel out = Parcel.obtain(); 598 writeStatsToParcelFLOCK(out); 599 stream.write(out.marshall()); 600 out.recycle(); 601 stream.flush(); 602 } finally { 603 FileUtils.sync(stream); 604 stream.close(); 605 } 606 } 607 608 private void writeStatsToParcelFLOCK(Parcel out) { 609 synchronized (mStatsLock) { 610 out.writeInt(VERSION); 611 Set<String> keys = mStats.keySet(); 612 out.writeInt(keys.size()); 613 for (String key : keys) { 614 PkgUsageStatsExtended pus = mStats.get(key); 615 out.writeString(key); 616 pus.writeToParcel(out); 617 } 618 } 619 } 620 621 /** Filter out stats for any packages which aren't present anymore. */ 622 private void filterHistoryStats() { 623 synchronized (mStatsLock) { 624 IPackageManager pm = AppGlobals.getPackageManager(); 625 for (int i=0; i<mLastResumeTimes.size(); i++) { 626 String pkg = mLastResumeTimes.keyAt(i); 627 try { 628 if (pm.getPackageUid(pkg, 0) < 0) { 629 mLastResumeTimes.removeAt(i); 630 i--; 631 } 632 } catch (RemoteException e) { 633 } 634 } 635 } 636 } 637 638 private void writeHistoryStatsFLOCK(AtomicFile historyFile) { 639 FileOutputStream fos = null; 640 try { 641 fos = historyFile.startWrite(); 642 XmlSerializer out = new FastXmlSerializer(); 643 out.setOutput(fos, "utf-8"); 644 out.startDocument(null, true); 645 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 646 out.startTag(null, "usage-history"); 647 synchronized (mStatsLock) { 648 for (int i=0; i<mLastResumeTimes.size(); i++) { 649 out.startTag(null, "pkg"); 650 out.attribute(null, "name", mLastResumeTimes.keyAt(i)); 651 ArrayMap<String, Long> comp = mLastResumeTimes.valueAt(i); 652 for (int j=0; j<comp.size(); j++) { 653 out.startTag(null, "comp"); 654 out.attribute(null, "name", comp.keyAt(j)); 655 out.attribute(null, "lrt", comp.valueAt(j).toString()); 656 out.endTag(null, "comp"); 657 } 658 out.endTag(null, "pkg"); 659 } 660 } 661 out.endTag(null, "usage-history"); 662 out.endDocument(); 663 664 historyFile.finishWrite(fos); 665 } catch (IOException e) { 666 Slog.w(TAG,"Error writing history stats" + e); 667 if (fos != null) { 668 historyFile.failWrite(fos); 669 } 670 } 671 } 672 673 public void publish(Context context) { 674 mContext = context; 675 ServiceManager.addService(SERVICE_NAME, asBinder()); 676 } 677 678 /** 679 * Start watching packages to remove stats when a package is uninstalled. 680 * May only be called when the package manager is ready. 681 */ 682 public void monitorPackages() { 683 mPackageMonitor = new PackageMonitor() { 684 @Override 685 public void onPackageRemovedAllUsers(String packageName, int uid) { 686 synchronized (mStatsLock) { 687 mLastResumeTimes.remove(packageName); 688 } 689 } 690 }; 691 mPackageMonitor.register(mContext, null, true); 692 filterHistoryStats(); 693 } 694 695 public void shutdown() { 696 if (mPackageMonitor != null) { 697 mPackageMonitor.unregister(); 698 } 699 Slog.i(TAG, "Writing usage stats before shutdown..."); 700 writeStatsToFile(true, true); 701 } 702 703 public static IUsageStats getService() { 704 if (sService != null) { 705 return sService; 706 } 707 IBinder b = ServiceManager.getService(SERVICE_NAME); 708 sService = asInterface(b); 709 return sService; 710 } 711 712 public void noteResumeComponent(ComponentName componentName) { 713 enforceCallingPermission(); 714 String pkgName; 715 synchronized (mStatsLock) { 716 if ((componentName == null) || 717 ((pkgName = componentName.getPackageName()) == null)) { 718 return; 719 } 720 721 final boolean samePackage = pkgName.equals(mLastResumedPkg); 722 if (mIsResumed) { 723 if (mLastResumedPkg != null) { 724 // We last resumed some other package... just pause it now 725 // to recover. 726 if (REPORT_UNEXPECTED) Slog.i(TAG, "Unexpected resume of " + pkgName 727 + " while already resumed in " + mLastResumedPkg); 728 PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg); 729 if (pus != null) { 730 pus.updatePause(); 731 } 732 } 733 } 734 735 final boolean sameComp = samePackage 736 && componentName.getClassName().equals(mLastResumedComp); 737 738 mIsResumed = true; 739 mLastResumedPkg = pkgName; 740 mLastResumedComp = componentName.getClassName(); 741 742 if (localLOGV) Slog.i(TAG, "started component:" + pkgName); 743 PkgUsageStatsExtended pus = mStats.get(pkgName); 744 if (pus == null) { 745 pus = new PkgUsageStatsExtended(); 746 mStats.put(pkgName, pus); 747 } 748 pus.updateResume(mLastResumedComp, !samePackage); 749 if (!sameComp) { 750 pus.addLaunchCount(mLastResumedComp); 751 } 752 753 ArrayMap<String, Long> componentResumeTimes = mLastResumeTimes.get(pkgName); 754 if (componentResumeTimes == null) { 755 componentResumeTimes = new ArrayMap<String, Long>(); 756 mLastResumeTimes.put(pkgName, componentResumeTimes); 757 } 758 componentResumeTimes.put(mLastResumedComp, System.currentTimeMillis()); 759 } 760 } 761 762 public void notePauseComponent(ComponentName componentName) { 763 enforceCallingPermission(); 764 765 synchronized (mStatsLock) { 766 String pkgName; 767 if ((componentName == null) || 768 ((pkgName = componentName.getPackageName()) == null)) { 769 return; 770 } 771 if (!mIsResumed) { 772 if (REPORT_UNEXPECTED) Slog.i(TAG, "Something wrong here, didn't expect " 773 + pkgName + " to be paused"); 774 return; 775 } 776 mIsResumed = false; 777 778 if (localLOGV) Slog.i(TAG, "paused component:"+pkgName); 779 780 PkgUsageStatsExtended pus = mStats.get(pkgName); 781 if (pus == null) { 782 // Weird some error here 783 Slog.i(TAG, "No package stats for pkg:"+pkgName); 784 return; 785 } 786 pus.updatePause(); 787 } 788 789 // Persist current data to file if needed. 790 writeStatsToFile(false, false); 791 } 792 793 public void noteLaunchTime(ComponentName componentName, int millis) { 794 enforceCallingPermission(); 795 String pkgName; 796 if ((componentName == null) || 797 ((pkgName = componentName.getPackageName()) == null)) { 798 return; 799 } 800 801 // Persist current data to file if needed. 802 writeStatsToFile(false, false); 803 804 synchronized (mStatsLock) { 805 PkgUsageStatsExtended pus = mStats.get(pkgName); 806 if (pus != null) { 807 pus.addLaunchTime(componentName.getClassName(), millis); 808 } 809 } 810 } 811 812 public void noteFullyDrawnTime(ComponentName componentName, int millis) { 813 enforceCallingPermission(); 814 String pkgName; 815 if ((componentName == null) || 816 ((pkgName = componentName.getPackageName()) == null)) { 817 return; 818 } 819 820 // Persist current data to file if needed. 821 writeStatsToFile(false, false); 822 823 synchronized (mStatsLock) { 824 PkgUsageStatsExtended pus = mStats.get(pkgName); 825 if (pus != null) { 826 pus.addFullyDrawnTime(componentName.getClassName(), millis); 827 } 828 } 829 } 830 831 public void enforceCallingPermission() { 832 if (Binder.getCallingPid() == Process.myPid()) { 833 return; 834 } 835 mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS, 836 Binder.getCallingPid(), Binder.getCallingUid(), null); 837 } 838 839 public PkgUsageStats getPkgUsageStats(ComponentName componentName) { 840 mContext.enforceCallingOrSelfPermission( 841 android.Manifest.permission.PACKAGE_USAGE_STATS, null); 842 String pkgName; 843 if ((componentName == null) || 844 ((pkgName = componentName.getPackageName()) == null)) { 845 return null; 846 } 847 synchronized (mStatsLock) { 848 PkgUsageStatsExtended pus = mStats.get(pkgName); 849 Map<String, Long> lastResumeTimes = mLastResumeTimes.get(pkgName); 850 if (pus == null && lastResumeTimes == null) { 851 return null; 852 } 853 int launchCount = pus != null ? pus.mLaunchCount : 0; 854 long usageTime = pus != null ? pus.mUsageTime : 0; 855 return new PkgUsageStats(pkgName, launchCount, usageTime, lastResumeTimes); 856 } 857 } 858 859 public PkgUsageStats[] getAllPkgUsageStats() { 860 mContext.enforceCallingOrSelfPermission( 861 android.Manifest.permission.PACKAGE_USAGE_STATS, null); 862 synchronized (mStatsLock) { 863 int size = mLastResumeTimes.size(); 864 if (size <= 0) { 865 return null; 866 } 867 PkgUsageStats retArr[] = new PkgUsageStats[size]; 868 for (int i=0; i<size; i++) { 869 String pkg = mLastResumeTimes.keyAt(i); 870 long usageTime = 0; 871 int launchCount = 0; 872 873 PkgUsageStatsExtended pus = mStats.get(pkg); 874 if (pus != null) { 875 usageTime = pus.mUsageTime; 876 launchCount = pus.mLaunchCount; 877 } 878 retArr[i] = new PkgUsageStats(pkg, launchCount, usageTime, 879 mLastResumeTimes.valueAt(i)); 880 } 881 return retArr; 882 } 883 } 884 885 static byte[] readFully(FileInputStream stream) throws java.io.IOException { 886 int pos = 0; 887 int avail = stream.available(); 888 byte[] data = new byte[avail]; 889 while (true) { 890 int amt = stream.read(data, pos, data.length-pos); 891 if (amt <= 0) { 892 return data; 893 } 894 pos += amt; 895 avail = stream.available(); 896 if (avail > data.length-pos) { 897 byte[] newData = new byte[pos+avail]; 898 System.arraycopy(data, 0, newData, 0, pos); 899 data = newData; 900 } 901 } 902 } 903 904 private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput, 905 boolean deleteAfterPrint, HashSet<String> packages) { 906 List<String> fileList = getUsageStatsFileListFLOCK(); 907 if (fileList == null) { 908 return; 909 } 910 Collections.sort(fileList); 911 for (String file : fileList) { 912 if (deleteAfterPrint && file.equalsIgnoreCase(mFileLeaf)) { 913 // In this mode we don't print the current day's stats, since 914 // they are incomplete. 915 continue; 916 } 917 File dFile = new File(mDir, file); 918 String dateStr = file.substring(FILE_PREFIX.length()); 919 if (dateStr.length() > 0 && (dateStr.charAt(0) <= '0' || dateStr.charAt(0) >= '9')) { 920 // If the remainder does not start with a number, it is not a date, 921 // so we should ignore it for purposes here. 922 continue; 923 } 924 try { 925 Parcel in = getParcelForFile(dFile); 926 collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput, 927 packages); 928 if (deleteAfterPrint) { 929 // Delete old file after collecting info only for checkin requests 930 dFile.delete(); 931 } 932 } catch (FileNotFoundException e) { 933 Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : " + file); 934 return; 935 } catch (IOException e) { 936 Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file); 937 } 938 } 939 } 940 941 private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw, 942 String date, boolean isCompactOutput, HashSet<String> packages) { 943 StringBuilder sb = new StringBuilder(512); 944 if (isCompactOutput) { 945 sb.append("D:"); 946 sb.append(CHECKIN_VERSION); 947 sb.append(','); 948 } else { 949 sb.append("Date: "); 950 } 951 952 sb.append(date); 953 954 int vers = in.readInt(); 955 if (vers != VERSION) { 956 sb.append(" (old data version)"); 957 pw.println(sb.toString()); 958 return; 959 } 960 961 pw.println(sb.toString()); 962 int N = in.readInt(); 963 964 while (N > 0) { 965 N--; 966 String pkgName = in.readString(); 967 if (pkgName == null) { 968 break; 969 } 970 sb.setLength(0); 971 PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in); 972 if (packages != null && !packages.contains(pkgName)) { 973 // This package has not been requested -- don't print 974 // anything for it. 975 } else if (isCompactOutput) { 976 sb.append("P:"); 977 sb.append(pkgName); 978 sb.append(','); 979 sb.append(pus.mLaunchCount); 980 sb.append(','); 981 sb.append(pus.mUsageTime); 982 sb.append('\n'); 983 final int NLT = pus.mLaunchTimes.size(); 984 for (int i=0; i<NLT; i++) { 985 sb.append("A:"); 986 String activity = pus.mLaunchTimes.keyAt(i); 987 sb.append(activity); 988 TimeStats times = pus.mLaunchTimes.valueAt(i); 989 sb.append(','); 990 sb.append(times.count); 991 for (int j=0; j<NUM_LAUNCH_TIME_BINS; j++) { 992 sb.append(","); 993 sb.append(times.times[j]); 994 } 995 sb.append('\n'); 996 } 997 final int NFDT = pus.mFullyDrawnTimes.size(); 998 for (int i=0; i<NFDT; i++) { 999 sb.append("A:"); 1000 String activity = pus.mFullyDrawnTimes.keyAt(i); 1001 sb.append(activity); 1002 TimeStats times = pus.mFullyDrawnTimes.valueAt(i); 1003 for (int j=0; j<NUM_LAUNCH_TIME_BINS; j++) { 1004 sb.append(","); 1005 sb.append(times.times[j]); 1006 } 1007 sb.append('\n'); 1008 } 1009 1010 } else { 1011 sb.append(" "); 1012 sb.append(pkgName); 1013 sb.append(": "); 1014 sb.append(pus.mLaunchCount); 1015 sb.append(" times, "); 1016 sb.append(pus.mUsageTime); 1017 sb.append(" ms"); 1018 sb.append('\n'); 1019 final int NLT = pus.mLaunchTimes.size(); 1020 for (int i=0; i<NLT; i++) { 1021 sb.append(" "); 1022 sb.append(pus.mLaunchTimes.keyAt(i)); 1023 TimeStats times = pus.mLaunchTimes.valueAt(i); 1024 sb.append(": "); 1025 sb.append(times.count); 1026 sb.append(" starts"); 1027 int lastBin = 0; 1028 for (int j=0; j<NUM_LAUNCH_TIME_BINS-1; j++) { 1029 if (times.times[j] != 0) { 1030 sb.append(", "); 1031 sb.append(lastBin); 1032 sb.append('-'); 1033 sb.append(LAUNCH_TIME_BINS[j]); 1034 sb.append("ms="); 1035 sb.append(times.times[j]); 1036 } 1037 lastBin = LAUNCH_TIME_BINS[j]; 1038 } 1039 if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) { 1040 sb.append(", "); 1041 sb.append(">="); 1042 sb.append(lastBin); 1043 sb.append("ms="); 1044 sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]); 1045 } 1046 sb.append('\n'); 1047 } 1048 final int NFDT = pus.mFullyDrawnTimes.size(); 1049 for (int i=0; i<NFDT; i++) { 1050 sb.append(" "); 1051 sb.append(pus.mFullyDrawnTimes.keyAt(i)); 1052 TimeStats times = pus.mFullyDrawnTimes.valueAt(i); 1053 sb.append(": fully drawn "); 1054 boolean needComma = false; 1055 int lastBin = 0; 1056 for (int j=0; j<NUM_LAUNCH_TIME_BINS-1; j++) { 1057 if (times.times[j] != 0) { 1058 if (needComma) { 1059 sb.append(", "); 1060 } else { 1061 needComma = true; 1062 } 1063 sb.append(lastBin); 1064 sb.append('-'); 1065 sb.append(LAUNCH_TIME_BINS[j]); 1066 sb.append("ms="); 1067 sb.append(times.times[j]); 1068 } 1069 lastBin = LAUNCH_TIME_BINS[j]; 1070 } 1071 if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) { 1072 if (needComma) { 1073 sb.append(", "); 1074 } 1075 sb.append(">="); 1076 sb.append(lastBin); 1077 sb.append("ms="); 1078 sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]); 1079 } 1080 sb.append('\n'); 1081 } 1082 } 1083 1084 pw.write(sb.toString()); 1085 } 1086 } 1087 1088 /** 1089 * Searches array of arguments for the specified string 1090 * @param args array of argument strings 1091 * @param value value to search for 1092 * @return true if the value is contained in the array 1093 */ 1094 private static boolean scanArgs(String[] args, String value) { 1095 if (args != null) { 1096 for (String arg : args) { 1097 if (value.equals(arg)) { 1098 return true; 1099 } 1100 } 1101 } 1102 return false; 1103 } 1104 1105 /** 1106 * Searches array of arguments for the specified string's data 1107 * @param args array of argument strings 1108 * @param value value to search for 1109 * @return the string of data after the arg, or null if there is none 1110 */ 1111 private static String scanArgsData(String[] args, String value) { 1112 if (args != null) { 1113 final int N = args.length; 1114 for (int i=0; i<N; i++) { 1115 if (value.equals(args[i])) { 1116 i++; 1117 return i < N ? args[i] : null; 1118 } 1119 } 1120 } 1121 return null; 1122 } 1123 1124 @Override 1125 /* 1126 * The data persisted to file is parsed and the stats are computed. 1127 */ 1128 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1129 if (mContext.checkCallingPermission(android.Manifest.permission.DUMP) 1130 != PackageManager.PERMISSION_GRANTED) { 1131 pw.println("Permission Denial: can't dump UsageStats from from pid=" 1132 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 1133 + " without permission " + android.Manifest.permission.DUMP); 1134 return; 1135 } 1136 1137 final boolean isCheckinRequest = scanArgs(args, "--checkin"); 1138 final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c"); 1139 final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d"); 1140 final String rawPackages = scanArgsData(args, "--packages"); 1141 1142 // Make sure the current stats are written to the file. This 1143 // doesn't need to be done if we are deleting files after printing, 1144 // since it that case we won't print the current stats. 1145 if (!deleteAfterPrint) { 1146 writeStatsToFile(true, false); 1147 } 1148 1149 HashSet<String> packages = null; 1150 if (rawPackages != null) { 1151 if (!"*".equals(rawPackages)) { 1152 // A * is a wildcard to show all packages. 1153 String[] names = rawPackages.split(","); 1154 for (String n : names) { 1155 if (packages == null) { 1156 packages = new HashSet<String>(); 1157 } 1158 packages.add(n); 1159 } 1160 } 1161 } else if (isCheckinRequest) { 1162 // If checkin doesn't specify any packages, then we simply won't 1163 // show anything. 1164 Slog.w(TAG, "Checkin without packages"); 1165 return; 1166 } 1167 1168 synchronized (mFileLock) { 1169 collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint, packages); 1170 } 1171 } 1172 1173 } 1174