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 17package com.android.server.am; 18 19import com.android.internal.app.IUsageStats; 20 21import android.content.ComponentName; 22import android.content.Context; 23import android.os.Binder; 24import android.os.IBinder; 25import com.android.internal.os.PkgUsageStats; 26import android.os.Parcel; 27import android.os.Process; 28import android.os.ServiceManager; 29import android.os.SystemClock; 30import android.util.Slog; 31import java.io.File; 32import java.io.FileDescriptor; 33import java.io.FileInputStream; 34import java.io.FileNotFoundException; 35import java.io.FileOutputStream; 36import java.io.IOException; 37import java.io.PrintWriter; 38import java.util.ArrayList; 39import java.util.Calendar; 40import java.util.Collections; 41import java.util.HashMap; 42import java.util.HashSet; 43import java.util.List; 44import java.util.Map; 45import java.util.Set; 46import java.util.TimeZone; 47 48/** 49 * This service collects the statistics associated with usage 50 * of various components, like when a particular package is launched or 51 * paused and aggregates events like number of time a component is launched 52 * total duration of a component launch. 53 */ 54public final class UsageStatsService extends IUsageStats.Stub { 55 public static final String SERVICE_NAME = "usagestats"; 56 private static final boolean localLOGV = false; 57 private static final String TAG = "UsageStats"; 58 59 // Current on-disk Parcel version 60 private static final int VERSION = 1005; 61 62 private static final int CHECKIN_VERSION = 4; 63 64 private static final String FILE_PREFIX = "usage-"; 65 66 private static final int FILE_WRITE_INTERVAL = 30*60*1000; //ms 67 68 private static final int MAX_NUM_FILES = 5; 69 70 private static final int NUM_LAUNCH_TIME_BINS = 10; 71 private static final int[] LAUNCH_TIME_BINS = { 72 250, 500, 750, 1000, 1500, 2000, 3000, 4000, 5000 73 }; 74 75 static IUsageStats sService; 76 private Context mContext; 77 // structure used to maintain statistics since the last checkin. 78 final private Map<String, PkgUsageStatsExtended> mStats; 79 // Lock to update package stats. Methods suffixed by SLOCK should invoked with 80 // this lock held 81 final Object mStatsLock; 82 // Lock to write to file. Methods suffixed by FLOCK should invoked with 83 // this lock held. 84 final Object mFileLock; 85 // Order of locks is mFileLock followed by mStatsLock to avoid deadlocks 86 private String mLastResumedPkg; 87 private String mLastResumedComp; 88 private boolean mIsResumed; 89 private File mFile; 90 private String mFileLeaf; 91 //private File mBackupFile; 92 private long mLastWriteElapsedTime; 93 private File mDir; 94 private Calendar mCal; 95 private int mLastWriteDay; 96 97 static class TimeStats { 98 int count; 99 int[] times = new int[NUM_LAUNCH_TIME_BINS]; 100 101 TimeStats() { 102 } 103 104 void incCount() { 105 count++; 106 } 107 108 void add(int val) { 109 final int[] bins = LAUNCH_TIME_BINS; 110 for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) { 111 if (val < bins[i]) { 112 times[i]++; 113 return; 114 } 115 } 116 times[NUM_LAUNCH_TIME_BINS-1]++; 117 } 118 119 TimeStats(Parcel in) { 120 count = in.readInt(); 121 final int[] localTimes = times; 122 for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) { 123 localTimes[i] = in.readInt(); 124 } 125 } 126 127 void writeToParcel(Parcel out) { 128 out.writeInt(count); 129 final int[] localTimes = times; 130 for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) { 131 out.writeInt(localTimes[i]); 132 } 133 } 134 } 135 136 private class PkgUsageStatsExtended { 137 final HashMap<String, TimeStats> mLaunchTimes 138 = new HashMap<String, TimeStats>(); 139 int mLaunchCount; 140 long mUsageTime; 141 long mPausedTime; 142 long mResumedTime; 143 144 PkgUsageStatsExtended() { 145 mLaunchCount = 0; 146 mUsageTime = 0; 147 } 148 149 PkgUsageStatsExtended(Parcel in) { 150 mLaunchCount = in.readInt(); 151 mUsageTime = in.readLong(); 152 if (localLOGV) Slog.v(TAG, "Launch count: " + mLaunchCount 153 + ", Usage time:" + mUsageTime); 154 155 final int N = in.readInt(); 156 if (localLOGV) Slog.v(TAG, "Reading comps: " + N); 157 for (int i=0; i<N; i++) { 158 String comp = in.readString(); 159 if (localLOGV) Slog.v(TAG, "Component: " + comp); 160 TimeStats times = new TimeStats(in); 161 mLaunchTimes.put(comp, times); 162 } 163 } 164 165 void updateResume(boolean launched) { 166 if (launched) { 167 mLaunchCount ++; 168 } 169 mResumedTime = SystemClock.elapsedRealtime(); 170 } 171 172 void updatePause() { 173 mPausedTime = SystemClock.elapsedRealtime(); 174 mUsageTime += (mPausedTime - mResumedTime); 175 } 176 177 void addLaunchCount(String comp) { 178 TimeStats times = mLaunchTimes.get(comp); 179 if (times == null) { 180 times = new TimeStats(); 181 mLaunchTimes.put(comp, times); 182 } 183 times.incCount(); 184 } 185 186 void addLaunchTime(String comp, int millis) { 187 TimeStats times = mLaunchTimes.get(comp); 188 if (times == null) { 189 times = new TimeStats(); 190 mLaunchTimes.put(comp, times); 191 } 192 times.add(millis); 193 } 194 195 void writeToParcel(Parcel out) { 196 out.writeInt(mLaunchCount); 197 out.writeLong(mUsageTime); 198 final int N = mLaunchTimes.size(); 199 out.writeInt(N); 200 if (N > 0) { 201 for (Map.Entry<String, TimeStats> ent : mLaunchTimes.entrySet()) { 202 out.writeString(ent.getKey()); 203 TimeStats times = ent.getValue(); 204 times.writeToParcel(out); 205 } 206 } 207 } 208 209 void clear() { 210 mLaunchTimes.clear(); 211 mLaunchCount = 0; 212 mUsageTime = 0; 213 } 214 } 215 216 UsageStatsService(String dir) { 217 mStats = new HashMap<String, PkgUsageStatsExtended>(); 218 mStatsLock = new Object(); 219 mFileLock = new Object(); 220 mDir = new File(dir); 221 mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0")); 222 223 mDir.mkdir(); 224 225 // Remove any old usage files from previous versions. 226 File parentDir = mDir.getParentFile(); 227 String fList[] = parentDir.list(); 228 if (fList != null) { 229 String prefix = mDir.getName() + "."; 230 int i = fList.length; 231 while (i > 0) { 232 i--; 233 if (fList[i].startsWith(prefix)) { 234 Slog.i(TAG, "Deleting old usage file: " + fList[i]); 235 (new File(parentDir, fList[i])).delete(); 236 } 237 } 238 } 239 240 // Update current stats which are binned by date 241 mFileLeaf = getCurrentDateStr(FILE_PREFIX); 242 mFile = new File(mDir, mFileLeaf); 243 readStatsFromFile(); 244 mLastWriteElapsedTime = SystemClock.elapsedRealtime(); 245 // mCal was set by getCurrentDateStr(), want to use that same time. 246 mLastWriteDay = mCal.get(Calendar.DAY_OF_YEAR); 247 } 248 249 /* 250 * Utility method to convert date into string. 251 */ 252 private String getCurrentDateStr(String prefix) { 253 mCal.setTimeInMillis(System.currentTimeMillis()); 254 StringBuilder sb = new StringBuilder(); 255 if (prefix != null) { 256 sb.append(prefix); 257 } 258 sb.append(mCal.get(Calendar.YEAR)); 259 int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1; 260 if (mm < 10) { 261 sb.append("0"); 262 } 263 sb.append(mm); 264 int dd = mCal.get(Calendar.DAY_OF_MONTH); 265 if (dd < 10) { 266 sb.append("0"); 267 } 268 sb.append(dd); 269 return sb.toString(); 270 } 271 272 private Parcel getParcelForFile(File file) throws IOException { 273 FileInputStream stream = new FileInputStream(file); 274 byte[] raw = readFully(stream); 275 Parcel in = Parcel.obtain(); 276 in.unmarshall(raw, 0, raw.length); 277 in.setDataPosition(0); 278 stream.close(); 279 return in; 280 } 281 282 private void readStatsFromFile() { 283 File newFile = mFile; 284 synchronized (mFileLock) { 285 try { 286 if (newFile.exists()) { 287 readStatsFLOCK(newFile); 288 } else { 289 // Check for file limit before creating a new file 290 checkFileLimitFLOCK(); 291 newFile.createNewFile(); 292 } 293 } catch (IOException e) { 294 Slog.w(TAG,"Error : " + e + " reading data from file:" + newFile); 295 } 296 } 297 } 298 299 private void readStatsFLOCK(File file) throws IOException { 300 Parcel in = getParcelForFile(file); 301 int vers = in.readInt(); 302 if (vers != VERSION) { 303 Slog.w(TAG, "Usage stats version changed; dropping"); 304 return; 305 } 306 int N = in.readInt(); 307 while (N > 0) { 308 N--; 309 String pkgName = in.readString(); 310 if (pkgName == null) { 311 break; 312 } 313 if (localLOGV) Slog.v(TAG, "Reading package #" + N + ": " + pkgName); 314 PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in); 315 synchronized (mStatsLock) { 316 mStats.put(pkgName, pus); 317 } 318 } 319 } 320 321 private ArrayList<String> getUsageStatsFileListFLOCK() { 322 // Check if there are too many files in the system and delete older files 323 String fList[] = mDir.list(); 324 if (fList == null) { 325 return null; 326 } 327 ArrayList<String> fileList = new ArrayList<String>(); 328 for (String file : fList) { 329 if (!file.startsWith(FILE_PREFIX)) { 330 continue; 331 } 332 if (file.endsWith(".bak")) { 333 (new File(mDir, file)).delete(); 334 continue; 335 } 336 fileList.add(file); 337 } 338 return fileList; 339 } 340 341 private void checkFileLimitFLOCK() { 342 // Get all usage stats output files 343 ArrayList<String> fileList = getUsageStatsFileListFLOCK(); 344 if (fileList == null) { 345 // Strange but we dont have to delete any thing 346 return; 347 } 348 int count = fileList.size(); 349 if (count <= MAX_NUM_FILES) { 350 return; 351 } 352 // Sort files 353 Collections.sort(fileList); 354 count -= MAX_NUM_FILES; 355 // Delete older files 356 for (int i = 0; i < count; i++) { 357 String fileName = fileList.get(i); 358 File file = new File(mDir, fileName); 359 Slog.i(TAG, "Deleting usage file : " + fileName); 360 file.delete(); 361 } 362 } 363 364 private void writeStatsToFile(boolean force) { 365 synchronized (mFileLock) { 366 mCal.setTimeInMillis(System.currentTimeMillis()); 367 final int curDay = mCal.get(Calendar.DAY_OF_YEAR); 368 // Determine if the day changed... note that this will be wrong 369 // if the year has changed but we are in the same day of year... 370 // we can probably live with this. 371 final boolean dayChanged = curDay != mLastWriteDay; 372 long currElapsedTime = SystemClock.elapsedRealtime(); 373 if (!force) { 374 if (((currElapsedTime-mLastWriteElapsedTime) < FILE_WRITE_INTERVAL) && 375 (!dayChanged)) { 376 // wait till the next update 377 return; 378 } 379 } 380 // Get the most recent file 381 mFileLeaf = getCurrentDateStr(FILE_PREFIX); 382 // Copy current file to back up 383 File backupFile = null; 384 if (mFile != null && mFile.exists()) { 385 backupFile = new File(mFile.getPath() + ".bak"); 386 if (!backupFile.exists()) { 387 if (!mFile.renameTo(backupFile)) { 388 Slog.w(TAG, "Failed to persist new stats"); 389 return; 390 } 391 } else { 392 mFile.delete(); 393 } 394 } 395 396 try { 397 // Write mStats to file 398 writeStatsFLOCK(); 399 mLastWriteElapsedTime = currElapsedTime; 400 if (dayChanged) { 401 mLastWriteDay = curDay; 402 // clear stats 403 synchronized (mStats) { 404 mStats.clear(); 405 } 406 mFile = new File(mDir, mFileLeaf); 407 checkFileLimitFLOCK(); 408 } 409 // Delete the backup file 410 if (backupFile != null) { 411 backupFile.delete(); 412 } 413 } catch (IOException e) { 414 Slog.w(TAG, "Failed writing stats to file:" + mFile); 415 if (backupFile != null) { 416 mFile.delete(); 417 backupFile.renameTo(mFile); 418 } 419 } 420 } 421 } 422 423 private void writeStatsFLOCK() throws IOException { 424 FileOutputStream stream = new FileOutputStream(mFile); 425 try { 426 Parcel out = Parcel.obtain(); 427 writeStatsToParcelFLOCK(out); 428 stream.write(out.marshall()); 429 out.recycle(); 430 stream.flush(); 431 } finally { 432 stream.close(); 433 } 434 } 435 436 private void writeStatsToParcelFLOCK(Parcel out) { 437 synchronized (mStatsLock) { 438 out.writeInt(VERSION); 439 Set<String> keys = mStats.keySet(); 440 out.writeInt(keys.size()); 441 for (String key : keys) { 442 PkgUsageStatsExtended pus = mStats.get(key); 443 out.writeString(key); 444 pus.writeToParcel(out); 445 } 446 } 447 } 448 449 public void publish(Context context) { 450 mContext = context; 451 ServiceManager.addService(SERVICE_NAME, asBinder()); 452 } 453 454 public void shutdown() { 455 Slog.w(TAG, "Writing usage stats before shutdown..."); 456 writeStatsToFile(true); 457 } 458 459 public static IUsageStats getService() { 460 if (sService != null) { 461 return sService; 462 } 463 IBinder b = ServiceManager.getService(SERVICE_NAME); 464 sService = asInterface(b); 465 return sService; 466 } 467 468 public void noteResumeComponent(ComponentName componentName) { 469 enforceCallingPermission(); 470 String pkgName; 471 synchronized (mStatsLock) { 472 if ((componentName == null) || 473 ((pkgName = componentName.getPackageName()) == null)) { 474 return; 475 } 476 477 final boolean samePackage = pkgName.equals(mLastResumedPkg); 478 if (mIsResumed) { 479 if (mLastResumedPkg != null) { 480 // We last resumed some other package... just pause it now 481 // to recover. 482 Slog.i(TAG, "Unexpected resume of " + pkgName 483 + " while already resumed in " + mLastResumedPkg); 484 PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg); 485 if (pus != null) { 486 pus.updatePause(); 487 } 488 } 489 } 490 491 final boolean sameComp = samePackage 492 && componentName.getClassName().equals(mLastResumedComp); 493 494 mIsResumed = true; 495 mLastResumedPkg = pkgName; 496 mLastResumedComp = componentName.getClassName(); 497 498 if (localLOGV) Slog.i(TAG, "started component:" + pkgName); 499 PkgUsageStatsExtended pus = mStats.get(pkgName); 500 if (pus == null) { 501 pus = new PkgUsageStatsExtended(); 502 mStats.put(pkgName, pus); 503 } 504 pus.updateResume(!samePackage); 505 if (!sameComp) { 506 pus.addLaunchCount(mLastResumedComp); 507 } 508 } 509 } 510 511 public void notePauseComponent(ComponentName componentName) { 512 enforceCallingPermission(); 513 514 synchronized (mStatsLock) { 515 String pkgName; 516 if ((componentName == null) || 517 ((pkgName = componentName.getPackageName()) == null)) { 518 return; 519 } 520 if (!mIsResumed) { 521 Slog.i(TAG, "Something wrong here, didn't expect " 522 + pkgName + " to be paused"); 523 return; 524 } 525 mIsResumed = false; 526 527 if (localLOGV) Slog.i(TAG, "paused component:"+pkgName); 528 529 PkgUsageStatsExtended pus = mStats.get(pkgName); 530 if (pus == null) { 531 // Weird some error here 532 Slog.i(TAG, "No package stats for pkg:"+pkgName); 533 return; 534 } 535 pus.updatePause(); 536 } 537 538 // Persist current data to file if needed. 539 writeStatsToFile(false); 540 } 541 542 public void noteLaunchTime(ComponentName componentName, int millis) { 543 enforceCallingPermission(); 544 String pkgName; 545 if ((componentName == null) || 546 ((pkgName = componentName.getPackageName()) == null)) { 547 return; 548 } 549 550 // Persist current data to file if needed. 551 writeStatsToFile(false); 552 553 synchronized (mStatsLock) { 554 PkgUsageStatsExtended pus = mStats.get(pkgName); 555 if (pus != null) { 556 pus.addLaunchTime(componentName.getClassName(), millis); 557 } 558 } 559 } 560 561 public void enforceCallingPermission() { 562 if (Binder.getCallingPid() == Process.myPid()) { 563 return; 564 } 565 mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS, 566 Binder.getCallingPid(), Binder.getCallingUid(), null); 567 } 568 569 public PkgUsageStats getPkgUsageStats(ComponentName componentName) { 570 mContext.enforceCallingOrSelfPermission( 571 android.Manifest.permission.PACKAGE_USAGE_STATS, null); 572 String pkgName; 573 if ((componentName == null) || 574 ((pkgName = componentName.getPackageName()) == null)) { 575 return null; 576 } 577 synchronized (mStatsLock) { 578 PkgUsageStatsExtended pus = mStats.get(pkgName); 579 if (pus == null) { 580 return null; 581 } 582 return new PkgUsageStats(pkgName, pus.mLaunchCount, pus.mUsageTime); 583 } 584 } 585 586 public PkgUsageStats[] getAllPkgUsageStats() { 587 mContext.enforceCallingOrSelfPermission( 588 android.Manifest.permission.PACKAGE_USAGE_STATS, null); 589 synchronized (mStatsLock) { 590 Set<String> keys = mStats.keySet(); 591 int size = keys.size(); 592 if (size <= 0) { 593 return null; 594 } 595 PkgUsageStats retArr[] = new PkgUsageStats[size]; 596 int i = 0; 597 for (String key: keys) { 598 PkgUsageStatsExtended pus = mStats.get(key); 599 retArr[i] = new PkgUsageStats(key, pus.mLaunchCount, pus.mUsageTime); 600 i++; 601 } 602 return retArr; 603 } 604 } 605 606 static byte[] readFully(FileInputStream stream) throws java.io.IOException { 607 int pos = 0; 608 int avail = stream.available(); 609 byte[] data = new byte[avail]; 610 while (true) { 611 int amt = stream.read(data, pos, data.length-pos); 612 if (amt <= 0) { 613 return data; 614 } 615 pos += amt; 616 avail = stream.available(); 617 if (avail > data.length-pos) { 618 byte[] newData = new byte[pos+avail]; 619 System.arraycopy(data, 0, newData, 0, pos); 620 data = newData; 621 } 622 } 623 } 624 625 private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput, 626 boolean deleteAfterPrint, HashSet<String> packages) { 627 List<String> fileList = getUsageStatsFileListFLOCK(); 628 if (fileList == null) { 629 return; 630 } 631 Collections.sort(fileList); 632 for (String file : fileList) { 633 if (deleteAfterPrint && file.equalsIgnoreCase(mFileLeaf)) { 634 // In this mode we don't print the current day's stats, since 635 // they are incomplete. 636 continue; 637 } 638 File dFile = new File(mDir, file); 639 String dateStr = file.substring(FILE_PREFIX.length()); 640 try { 641 Parcel in = getParcelForFile(dFile); 642 collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput, 643 packages); 644 if (deleteAfterPrint) { 645 // Delete old file after collecting info only for checkin requests 646 dFile.delete(); 647 } 648 } catch (FileNotFoundException e) { 649 Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : " + file); 650 return; 651 } catch (IOException e) { 652 Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file); 653 } 654 } 655 } 656 657 private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw, 658 String date, boolean isCompactOutput, HashSet<String> packages) { 659 StringBuilder sb = new StringBuilder(512); 660 if (isCompactOutput) { 661 sb.append("D:"); 662 sb.append(CHECKIN_VERSION); 663 sb.append(','); 664 } else { 665 sb.append("Date: "); 666 } 667 668 sb.append(date); 669 670 int vers = in.readInt(); 671 if (vers != VERSION) { 672 sb.append(" (old data version)"); 673 pw.println(sb.toString()); 674 return; 675 } 676 677 pw.println(sb.toString()); 678 int N = in.readInt(); 679 680 while (N > 0) { 681 N--; 682 String pkgName = in.readString(); 683 if (pkgName == null) { 684 break; 685 } 686 sb.setLength(0); 687 PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in); 688 if (packages != null && !packages.contains(pkgName)) { 689 // This package has not been requested -- don't print 690 // anything for it. 691 } else if (isCompactOutput) { 692 sb.append("P:"); 693 sb.append(pkgName); 694 sb.append(','); 695 sb.append(pus.mLaunchCount); 696 sb.append(','); 697 sb.append(pus.mUsageTime); 698 sb.append('\n'); 699 final int NC = pus.mLaunchTimes.size(); 700 if (NC > 0) { 701 for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) { 702 sb.append("A:"); 703 String activity = ent.getKey(); 704 if (activity.startsWith(pkgName)) { 705 sb.append('*'); 706 sb.append(activity.substring( 707 pkgName.length(), activity.length())); 708 } else { 709 sb.append(activity); 710 } 711 TimeStats times = ent.getValue(); 712 sb.append(','); 713 sb.append(times.count); 714 for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) { 715 sb.append(","); 716 sb.append(times.times[i]); 717 } 718 sb.append('\n'); 719 } 720 } 721 722 } else { 723 sb.append(" "); 724 sb.append(pkgName); 725 sb.append(": "); 726 sb.append(pus.mLaunchCount); 727 sb.append(" times, "); 728 sb.append(pus.mUsageTime); 729 sb.append(" ms"); 730 sb.append('\n'); 731 final int NC = pus.mLaunchTimes.size(); 732 if (NC > 0) { 733 for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) { 734 sb.append(" "); 735 sb.append(ent.getKey()); 736 TimeStats times = ent.getValue(); 737 sb.append(": "); 738 sb.append(times.count); 739 sb.append(" starts"); 740 int lastBin = 0; 741 for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) { 742 if (times.times[i] != 0) { 743 sb.append(", "); 744 sb.append(lastBin); 745 sb.append('-'); 746 sb.append(LAUNCH_TIME_BINS[i]); 747 sb.append("ms="); 748 sb.append(times.times[i]); 749 } 750 lastBin = LAUNCH_TIME_BINS[i]; 751 } 752 if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) { 753 sb.append(", "); 754 sb.append(">="); 755 sb.append(lastBin); 756 sb.append("ms="); 757 sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]); 758 } 759 sb.append('\n'); 760 } 761 } 762 } 763 764 pw.write(sb.toString()); 765 } 766 } 767 768 /** 769 * Searches array of arguments for the specified string 770 * @param args array of argument strings 771 * @param value value to search for 772 * @return true if the value is contained in the array 773 */ 774 private static boolean scanArgs(String[] args, String value) { 775 if (args != null) { 776 for (String arg : args) { 777 if (value.equals(arg)) { 778 return true; 779 } 780 } 781 } 782 return false; 783 } 784 785 /** 786 * Searches array of arguments for the specified string's data 787 * @param args array of argument strings 788 * @param value value to search for 789 * @return the string of data after the arg, or null if there is none 790 */ 791 private static String scanArgsData(String[] args, String value) { 792 if (args != null) { 793 final int N = args.length; 794 for (int i=0; i<N; i++) { 795 if (value.equals(args[i])) { 796 i++; 797 return i < N ? args[i] : null; 798 } 799 } 800 } 801 return null; 802 } 803 804 @Override 805 /* 806 * The data persisted to file is parsed and the stats are computed. 807 */ 808 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 809 final boolean isCheckinRequest = scanArgs(args, "--checkin"); 810 final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c"); 811 final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d"); 812 final String rawPackages = scanArgsData(args, "--packages"); 813 814 // Make sure the current stats are written to the file. This 815 // doesn't need to be done if we are deleting files after printing, 816 // since it that case we won't print the current stats. 817 if (!deleteAfterPrint) { 818 writeStatsToFile(true); 819 } 820 821 HashSet<String> packages = null; 822 if (rawPackages != null) { 823 if (!"*".equals(rawPackages)) { 824 // A * is a wildcard to show all packages. 825 String[] names = rawPackages.split(","); 826 for (String n : names) { 827 if (packages == null) { 828 packages = new HashSet<String>(); 829 } 830 packages.add(n); 831 } 832 } 833 } else if (isCheckinRequest) { 834 // If checkin doesn't specify any packages, then we simply won't 835 // show anything. 836 Slog.w(TAG, "Checkin without packages"); 837 return; 838 } 839 840 synchronized (mFileLock) { 841 collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint, packages); 842 } 843 } 844 845} 846