UsageStatsService.java revision be1c422a73cf70e1478a13463de5ab929991c619
19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/* 29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2006-2007 The Android Open Source Project 39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License. 69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at 79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and 149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License. 159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage com.android.server.am; 189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport com.android.internal.app.IUsageStats; 209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 213e7b3c0f270d2bf86fb701a94e55cb135480d571Yu Shan Emily Lauimport android.content.ComponentName; 229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.content.Context; 239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.Binder; 249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.IBinder; 259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport com.android.internal.os.PkgUsageStats; 269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.Parcel; 279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.Process; 289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.ServiceManager; 299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.SystemClock; 309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.util.Log; 319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.File; 329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileDescriptor; 339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileInputStream; 349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileNotFoundException; 359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileOutputStream; 369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.IOException; 379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.PrintWriter; 389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.ArrayList; 399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Calendar; 409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Collections; 419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.HashMap; 429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.HashSet; 439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.List; 449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Map; 459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Set; 469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.TimeZone; 479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/** 499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * This service collects the statistics associated with usage 509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * of various components, like when a particular package is launched or 519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * paused and aggregates events like number of time a component is launched 529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * total duration of a component launch. 539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpublic final class UsageStatsService extends IUsageStats.Stub { 559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final String SERVICE_NAME = "usagestats"; 569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static final boolean localLOGV = false; 579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static final String TAG = "UsageStats"; 589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Current on-disk Parcel version 609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static final int VERSION = 1005; 619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 623e7b3c0f270d2bf86fb701a94e55cb135480d571Yu Shan Emily Lau private static final int CHECKIN_VERSION = 4; 639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static final String FILE_PREFIX = "usage-"; 659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static final int FILE_WRITE_INTERVAL = 30*60*1000; //ms 679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static final int MAX_NUM_FILES = 5; 699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static final int NUM_LAUNCH_TIME_BINS = 10; 719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 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) Log.v(TAG, "Launch count: " + mLaunchCount 153 + ", Usage time:" + mUsageTime); 154 155 final int N = in.readInt(); 156 if (localLOGV) Log.v(TAG, "Reading comps: " + N); 157 for (int i=0; i<N; i++) { 158 String comp = in.readString(); 159 if (localLOGV) Log.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 Log.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 Log.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 Log.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) Log.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 Log.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 (!mFile.renameTo(backupFile)) { 387 Log.w(TAG, "Failed to persist new stats"); 388 return; 389 } 390 } 391 392 try { 393 // Write mStats to file 394 writeStatsFLOCK(); 395 mLastWriteElapsedTime = currElapsedTime; 396 if (dayChanged) { 397 mLastWriteDay = curDay; 398 // clear stats 399 synchronized (mStats) { 400 mStats.clear(); 401 } 402 mFile = new File(mDir, mFileLeaf); 403 checkFileLimitFLOCK(); 404 } 405 // Delete the backup file 406 if (backupFile != null) { 407 backupFile.delete(); 408 } 409 } catch (IOException e) { 410 Log.w(TAG, "Failed writing stats to file:" + mFile); 411 if (backupFile != null) { 412 mFile.delete(); 413 backupFile.renameTo(mFile); 414 } 415 } 416 } 417 } 418 419 private void writeStatsFLOCK() throws IOException { 420 FileOutputStream stream = new FileOutputStream(mFile); 421 try { 422 Parcel out = Parcel.obtain(); 423 writeStatsToParcelFLOCK(out); 424 stream.write(out.marshall()); 425 out.recycle(); 426 stream.flush(); 427 } finally { 428 stream.close(); 429 } 430 } 431 432 private void writeStatsToParcelFLOCK(Parcel out) { 433 synchronized (mStatsLock) { 434 out.writeInt(VERSION); 435 Set<String> keys = mStats.keySet(); 436 out.writeInt(keys.size()); 437 for (String key : keys) { 438 PkgUsageStatsExtended pus = mStats.get(key); 439 out.writeString(key); 440 pus.writeToParcel(out); 441 } 442 } 443 } 444 445 public void publish(Context context) { 446 mContext = context; 447 ServiceManager.addService(SERVICE_NAME, asBinder()); 448 } 449 450 public void shutdown() { 451 Log.w(TAG, "Writing usage stats before shutdown..."); 452 writeStatsToFile(true); 453 } 454 455 public static IUsageStats getService() { 456 if (sService != null) { 457 return sService; 458 } 459 IBinder b = ServiceManager.getService(SERVICE_NAME); 460 sService = asInterface(b); 461 return sService; 462 } 463 464 public void noteResumeComponent(ComponentName componentName) { 465 enforceCallingPermission(); 466 String pkgName; 467 synchronized (mStatsLock) { 468 if ((componentName == null) || 469 ((pkgName = componentName.getPackageName()) == null)) { 470 return; 471 } 472 473 final boolean samePackage = pkgName.equals(mLastResumedPkg); 474 if (mIsResumed) { 475 if (mLastResumedPkg != null) { 476 // We last resumed some other package... just pause it now 477 // to recover. 478 Log.i(TAG, "Unexpected resume of " + pkgName 479 + " while already resumed in " + mLastResumedPkg); 480 PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg); 481 if (pus != null) { 482 pus.updatePause(); 483 } 484 } 485 } 486 487 final boolean sameComp = samePackage 488 && componentName.getClassName().equals(mLastResumedComp); 489 490 mIsResumed = true; 491 mLastResumedPkg = pkgName; 492 mLastResumedComp = componentName.getClassName(); 493 494 if (localLOGV) Log.i(TAG, "started component:" + pkgName); 495 PkgUsageStatsExtended pus = mStats.get(pkgName); 496 if (pus == null) { 497 pus = new PkgUsageStatsExtended(); 498 mStats.put(pkgName, pus); 499 } 500 pus.updateResume(!samePackage); 501 if (!sameComp) { 502 pus.addLaunchCount(mLastResumedComp); 503 } 504 } 505 } 506 507 public void notePauseComponent(ComponentName componentName) { 508 enforceCallingPermission(); 509 510 synchronized (mStatsLock) { 511 String pkgName; 512 if ((componentName == null) || 513 ((pkgName = componentName.getPackageName()) == null)) { 514 return; 515 } 516 if (!mIsResumed) { 517 Log.i(TAG, "Something wrong here, didn't expect " 518 + pkgName + " to be paused"); 519 return; 520 } 521 mIsResumed = false; 522 523 if (localLOGV) Log.i(TAG, "paused component:"+pkgName); 524 525 PkgUsageStatsExtended pus = mStats.get(pkgName); 526 if (pus == null) { 527 // Weird some error here 528 Log.i(TAG, "No package stats for pkg:"+pkgName); 529 return; 530 } 531 pus.updatePause(); 532 } 533 534 // Persist current data to file if needed. 535 writeStatsToFile(false); 536 } 537 538 public void noteLaunchTime(ComponentName componentName, int millis) { 539 enforceCallingPermission(); 540 String pkgName; 541 if ((componentName == null) || 542 ((pkgName = componentName.getPackageName()) == null)) { 543 return; 544 } 545 546 // Persist current data to file if needed. 547 writeStatsToFile(false); 548 549 synchronized (mStatsLock) { 550 PkgUsageStatsExtended pus = mStats.get(pkgName); 551 if (pus != null) { 552 pus.addLaunchTime(componentName.getClassName(), millis); 553 } 554 } 555 } 556 557 public void enforceCallingPermission() { 558 if (Binder.getCallingPid() == Process.myPid()) { 559 return; 560 } 561 mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS, 562 Binder.getCallingPid(), Binder.getCallingUid(), null); 563 } 564 565 public PkgUsageStats getPkgUsageStats(ComponentName componentName) { 566 mContext.enforceCallingOrSelfPermission( 567 android.Manifest.permission.PACKAGE_USAGE_STATS, null); 568 String pkgName; 569 if ((componentName == null) || 570 ((pkgName = componentName.getPackageName()) == null)) { 571 return null; 572 } 573 synchronized (mStatsLock) { 574 PkgUsageStatsExtended pus = mStats.get(pkgName); 575 if (pus == null) { 576 return null; 577 } 578 return new PkgUsageStats(pkgName, pus.mLaunchCount, pus.mUsageTime); 579 } 580 } 581 582 public PkgUsageStats[] getAllPkgUsageStats() { 583 mContext.enforceCallingOrSelfPermission( 584 android.Manifest.permission.PACKAGE_USAGE_STATS, null); 585 synchronized (mStatsLock) { 586 Set<String> keys = mStats.keySet(); 587 int size = keys.size(); 588 if (size <= 0) { 589 return null; 590 } 591 PkgUsageStats retArr[] = new PkgUsageStats[size]; 592 int i = 0; 593 for (String key: keys) { 594 PkgUsageStatsExtended pus = mStats.get(key); 595 retArr[i] = new PkgUsageStats(key, pus.mLaunchCount, pus.mUsageTime); 596 i++; 597 } 598 return retArr; 599 } 600 } 601 602 static byte[] readFully(FileInputStream stream) throws java.io.IOException { 603 int pos = 0; 604 int avail = stream.available(); 605 byte[] data = new byte[avail]; 606 while (true) { 607 int amt = stream.read(data, pos, data.length-pos); 608 if (amt <= 0) { 609 return data; 610 } 611 pos += amt; 612 avail = stream.available(); 613 if (avail > data.length-pos) { 614 byte[] newData = new byte[pos+avail]; 615 System.arraycopy(data, 0, newData, 0, pos); 616 data = newData; 617 } 618 } 619 } 620 621 private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput, 622 boolean deleteAfterPrint, HashSet<String> packages) { 623 List<String> fileList = getUsageStatsFileListFLOCK(); 624 if (fileList == null) { 625 return; 626 } 627 Collections.sort(fileList); 628 for (String file : fileList) { 629 if (deleteAfterPrint && file.equalsIgnoreCase(mFileLeaf)) { 630 // In this mode we don't print the current day's stats, since 631 // they are incomplete. 632 continue; 633 } 634 File dFile = new File(mDir, file); 635 String dateStr = file.substring(FILE_PREFIX.length()); 636 try { 637 Parcel in = getParcelForFile(dFile); 638 collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput, 639 packages); 640 if (deleteAfterPrint) { 641 // Delete old file after collecting info only for checkin requests 642 dFile.delete(); 643 } 644 } catch (FileNotFoundException e) { 645 Log.w(TAG, "Failed with "+e+" when collecting dump info from file : " + file); 646 return; 647 } catch (IOException e) { 648 Log.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file); 649 } 650 } 651 } 652 653 private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw, 654 String date, boolean isCompactOutput, HashSet<String> packages) { 655 StringBuilder sb = new StringBuilder(512); 656 if (isCompactOutput) { 657 sb.append("D:"); 658 sb.append(CHECKIN_VERSION); 659 sb.append(','); 660 } else { 661 sb.append("Date: "); 662 } 663 664 sb.append(date); 665 666 int vers = in.readInt(); 667 if (vers != VERSION) { 668 sb.append(" (old data version)"); 669 pw.println(sb.toString()); 670 return; 671 } 672 673 pw.println(sb.toString()); 674 int N = in.readInt(); 675 676 while (N > 0) { 677 N--; 678 String pkgName = in.readString(); 679 if (pkgName == null) { 680 break; 681 } 682 sb.setLength(0); 683 PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in); 684 if (packages != null && !packages.contains(pkgName)) { 685 // This package has not been requested -- don't print 686 // anything for it. 687 } else if (isCompactOutput) { 688 sb.append("P:"); 689 sb.append(pkgName); 690 sb.append(','); 691 sb.append(pus.mLaunchCount); 692 sb.append(','); 693 sb.append(pus.mUsageTime); 694 sb.append('\n'); 695 final int NC = pus.mLaunchTimes.size(); 696 if (NC > 0) { 697 for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) { 698 sb.append("A:"); 699 String activity = ent.getKey(); 700 if (activity.startsWith(pkgName)) { 701 sb.append('*'); 702 sb.append(activity.substring( 703 pkgName.length(), activity.length())); 704 } else { 705 sb.append(activity); 706 } 707 TimeStats times = ent.getValue(); 708 sb.append(','); 709 sb.append(times.count); 710 for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) { 711 sb.append(","); 712 sb.append(times.times[i]); 713 } 714 sb.append('\n'); 715 } 716 } 717 718 } else { 719 sb.append(" "); 720 sb.append(pkgName); 721 sb.append(": "); 722 sb.append(pus.mLaunchCount); 723 sb.append(" times, "); 724 sb.append(pus.mUsageTime); 725 sb.append(" ms"); 726 sb.append('\n'); 727 final int NC = pus.mLaunchTimes.size(); 728 if (NC > 0) { 729 for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) { 730 sb.append(" "); 731 sb.append(ent.getKey()); 732 TimeStats times = ent.getValue(); 733 sb.append(": "); 734 sb.append(times.count); 735 sb.append(" starts"); 736 int lastBin = 0; 737 for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) { 738 if (times.times[i] != 0) { 739 sb.append(", "); 740 sb.append(lastBin); 741 sb.append('-'); 742 sb.append(LAUNCH_TIME_BINS[i]); 743 sb.append("ms="); 744 sb.append(times.times[i]); 745 } 746 lastBin = LAUNCH_TIME_BINS[i]; 747 } 748 if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) { 749 sb.append(", "); 750 sb.append(">="); 751 sb.append(lastBin); 752 sb.append("ms="); 753 sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]); 754 } 755 sb.append('\n'); 756 } 757 } 758 } 759 760 pw.write(sb.toString()); 761 } 762 } 763 764 /** 765 * Searches array of arguments for the specified string 766 * @param args array of argument strings 767 * @param value value to search for 768 * @return true if the value is contained in the array 769 */ 770 private static boolean scanArgs(String[] args, String value) { 771 if (args != null) { 772 for (String arg : args) { 773 if (value.equals(arg)) { 774 return true; 775 } 776 } 777 } 778 return false; 779 } 780 781 /** 782 * Searches array of arguments for the specified string's data 783 * @param args array of argument strings 784 * @param value value to search for 785 * @return the string of data after the arg, or null if there is none 786 */ 787 private static String scanArgsData(String[] args, String value) { 788 if (args != null) { 789 final int N = args.length; 790 for (int i=0; i<N; i++) { 791 if (value.equals(args[i])) { 792 i++; 793 return i < N ? args[i] : null; 794 } 795 } 796 } 797 return null; 798 } 799 800 @Override 801 /* 802 * The data persisted to file is parsed and the stats are computed. 803 */ 804 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 805 final boolean isCheckinRequest = scanArgs(args, "--checkin"); 806 final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c"); 807 final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d"); 808 final String rawPackages = scanArgsData(args, "--packages"); 809 810 // Make sure the current stats are written to the file. This 811 // doesn't need to be done if we are deleting files after printing, 812 // since it that case we won't print the current stats. 813 if (!deleteAfterPrint) { 814 writeStatsToFile(true); 815 } 816 817 HashSet<String> packages = null; 818 if (rawPackages != null) { 819 if (!"*".equals(rawPackages)) { 820 // A * is a wildcard to show all packages. 821 String[] names = rawPackages.split(","); 822 for (String n : names) { 823 if (packages == null) { 824 packages = new HashSet<String>(); 825 } 826 packages.add(n); 827 } 828 } 829 } else if (isCheckinRequest) { 830 // If checkin doesn't specify any packages, then we simply won't 831 // show anything. 832 Log.w(TAG, "Checkin without packages"); 833 return; 834 } 835 836 synchronized (mFileLock) { 837 collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint, packages); 838 } 839 } 840 841} 842