BatteryStatsService.java revision 94326cb56aa0c7cee110d6781fb8b8f16fb09663
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 android.bluetooth.BluetoothActivityEnergyInfo; 20import android.content.Context; 21import android.content.pm.ApplicationInfo; 22import android.content.pm.PackageManager; 23import android.net.wifi.WifiActivityEnergyInfo; 24import android.os.PowerSaveState; 25import android.os.BatteryStats; 26import android.os.Binder; 27import android.os.Handler; 28import android.os.IBinder; 29import android.os.Parcel; 30import android.os.ParcelFileDescriptor; 31import android.os.ParcelFormatException; 32import android.os.PowerManagerInternal; 33import android.os.Process; 34import android.os.ServiceManager; 35import android.os.SystemClock; 36import android.os.UserHandle; 37import android.os.WorkSource; 38import android.os.health.HealthStatsParceler; 39import android.os.health.HealthStatsWriter; 40import android.os.health.UidHealthStats; 41import android.telephony.DataConnectionRealTimeInfo; 42import android.telephony.ModemActivityInfo; 43import android.telephony.SignalStrength; 44import android.telephony.TelephonyManager; 45import android.util.Slog; 46 47import com.android.internal.app.IBatteryStats; 48import com.android.internal.os.BatteryStatsHelper; 49import com.android.internal.os.BatteryStatsImpl; 50import com.android.internal.os.PowerProfile; 51import com.android.internal.util.DumpUtils; 52import com.android.server.LocalServices; 53import com.android.server.power.BatterySaverPolicy.ServiceType; 54 55import java.io.File; 56import java.io.FileDescriptor; 57import java.io.IOException; 58import java.io.PrintWriter; 59import java.nio.ByteBuffer; 60import java.nio.CharBuffer; 61import java.nio.charset.CharsetDecoder; 62import java.nio.charset.CodingErrorAction; 63import java.nio.charset.StandardCharsets; 64import java.util.Arrays; 65import java.util.List; 66import java.util.concurrent.ExecutionException; 67import java.util.concurrent.Future; 68 69/** 70 * All information we are collecting about things that can happen that impact 71 * battery life. 72 */ 73public final class BatteryStatsService extends IBatteryStats.Stub 74 implements PowerManagerInternal.LowPowerModeListener, 75 BatteryStatsImpl.PlatformIdleStateCallback { 76 static final String TAG = "BatteryStatsService"; 77 static final boolean DBG = false; 78 79 private static IBatteryStats sService; 80 81 final BatteryStatsImpl mStats; 82 private final Context mContext; 83 private final BatteryExternalStatsWorker mWorker; 84 85 private native int getPlatformLowPowerStats(ByteBuffer outBuffer); 86 private native int getSubsystemLowPowerStats(ByteBuffer outBuffer); 87 private CharsetDecoder mDecoderStat = StandardCharsets.UTF_8 88 .newDecoder() 89 .onMalformedInput(CodingErrorAction.REPLACE) 90 .onUnmappableCharacter(CodingErrorAction.REPLACE) 91 .replaceWith("?"); 92 private ByteBuffer mUtf8BufferStat = ByteBuffer.allocateDirect(MAX_LOW_POWER_STATS_SIZE); 93 private CharBuffer mUtf16BufferStat = CharBuffer.allocate(MAX_LOW_POWER_STATS_SIZE); 94 private static final int MAX_LOW_POWER_STATS_SIZE = 512; 95 96 @Override 97 public String getPlatformLowPowerStats() { 98 if (DBG) Slog.d(TAG, "begin getPlatformLowPowerStats"); 99 try { 100 mUtf8BufferStat.clear(); 101 mUtf16BufferStat.clear(); 102 mDecoderStat.reset(); 103 int bytesWritten = getPlatformLowPowerStats(mUtf8BufferStat); 104 if (bytesWritten < 0) { 105 return null; 106 } else if (bytesWritten == 0) { 107 return "Empty"; 108 } 109 mUtf8BufferStat.limit(bytesWritten); 110 mDecoderStat.decode(mUtf8BufferStat, mUtf16BufferStat, true); 111 mUtf16BufferStat.flip(); 112 return mUtf16BufferStat.toString(); 113 } finally { 114 if (DBG) Slog.d(TAG, "end getPlatformLowPowerStats"); 115 } 116 } 117 118 @Override 119 public String getSubsystemLowPowerStats() { 120 Slog.d(TAG, "begin getSubsystemLowPowerStats"); 121 try { 122 mUtf8BufferStat.clear(); 123 mUtf16BufferStat.clear(); 124 mDecoderStat.reset(); 125 int bytesWritten = getSubsystemLowPowerStats(mUtf8BufferStat); 126 if (bytesWritten < 0) { 127 return null; 128 } else if (bytesWritten == 0) { 129 return "Empty"; 130 } 131 mUtf8BufferStat.limit(bytesWritten); 132 mDecoderStat.decode(mUtf8BufferStat, mUtf16BufferStat, true); 133 mUtf16BufferStat.flip(); 134 return mUtf16BufferStat.toString(); 135 } finally { 136 Slog.d(TAG, "end getSubsystemLowPowerStats"); 137 } 138 } 139 140 BatteryStatsService(Context context, File systemDir, Handler handler) { 141 // BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through. 142 mContext = context; 143 mStats = new BatteryStatsImpl(systemDir, handler, this); 144 mWorker = new BatteryExternalStatsWorker(context, mStats); 145 mStats.setExternalStatsSyncLocked(mWorker); 146 mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger( 147 com.android.internal.R.integer.config_radioScanningTimeout) * 1000L); 148 mStats.setPowerProfileLocked(new PowerProfile(context)); 149 } 150 151 public void publish() { 152 ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder()); 153 } 154 155 private static void awaitUninterruptibly(Future<?> future) { 156 while (true) { 157 try { 158 future.get(); 159 return; 160 } catch (ExecutionException e) { 161 return; 162 } catch (InterruptedException e) { 163 // Keep looping 164 } 165 } 166 } 167 168 /** 169 * At the time when the constructor runs, the power manager has not yet been 170 * initialized. So we initialize the low power observer later. 171 */ 172 public void initPowerManagement() { 173 final PowerManagerInternal powerMgr = LocalServices.getService(PowerManagerInternal.class); 174 powerMgr.registerLowPowerModeObserver(this); 175 synchronized (mStats) { 176 mStats.notePowerSaveModeLocked( 177 powerMgr.getLowPowerState(ServiceType.BATTERY_STATS) 178 .batterySaverEnabled); 179 } 180 (new WakeupReasonThread()).start(); 181 } 182 183 public void shutdown() { 184 Slog.w("BatteryStats", "Writing battery stats before shutdown..."); 185 186 awaitUninterruptibly(mWorker.scheduleSync("shutdown", BatteryExternalStatsWorker.UPDATE_ALL)); 187 188 synchronized (mStats) { 189 mStats.shutdownLocked(); 190 } 191 192 // Shutdown the thread we made. 193 mWorker.shutdown(); 194 } 195 196 public static IBatteryStats getService() { 197 if (sService != null) { 198 return sService; 199 } 200 IBinder b = ServiceManager.getService(BatteryStats.SERVICE_NAME); 201 sService = asInterface(b); 202 return sService; 203 } 204 205 @Override 206 public int getServiceType() { 207 return ServiceType.BATTERY_STATS; 208 } 209 210 @Override 211 public void onLowPowerModeChanged(PowerSaveState result) { 212 synchronized (mStats) { 213 mStats.notePowerSaveModeLocked(result.batterySaverEnabled); 214 } 215 } 216 217 /** 218 * @return the current statistics object, which may be modified 219 * to reflect events that affect battery usage. You must lock the 220 * stats object before doing anything with it. 221 */ 222 public BatteryStatsImpl getActiveStatistics() { 223 return mStats; 224 } 225 226 /** 227 * Schedules a write to disk to occur. This will cause the BatteryStatsImpl 228 * object to update with the latest info, then write to disk. 229 */ 230 public void scheduleWriteToDisk() { 231 mWorker.scheduleWrite(); 232 } 233 234 // These are for direct use by the activity manager... 235 236 /** 237 * Remove a UID from the BatteryStats and BatteryStats' external dependencies. 238 */ 239 void removeUid(int uid) { 240 synchronized (mStats) { 241 mStats.removeUidStatsLocked(uid); 242 } 243 } 244 245 void addIsolatedUid(int isolatedUid, int appUid) { 246 synchronized (mStats) { 247 mStats.addIsolatedUidLocked(isolatedUid, appUid); 248 } 249 } 250 251 void removeIsolatedUid(int isolatedUid, int appUid) { 252 synchronized (mStats) { 253 mStats.scheduleRemoveIsolatedUidLocked(isolatedUid, appUid); 254 } 255 } 256 257 void noteProcessStart(String name, int uid) { 258 synchronized (mStats) { 259 mStats.noteProcessStartLocked(name, uid); 260 } 261 } 262 263 void noteProcessCrash(String name, int uid) { 264 synchronized (mStats) { 265 mStats.noteProcessCrashLocked(name, uid); 266 } 267 } 268 269 void noteProcessAnr(String name, int uid) { 270 synchronized (mStats) { 271 mStats.noteProcessAnrLocked(name, uid); 272 } 273 } 274 275 void noteProcessFinish(String name, int uid) { 276 synchronized (mStats) { 277 mStats.noteProcessFinishLocked(name, uid); 278 } 279 } 280 281 void noteUidProcessState(int uid, int state) { 282 synchronized (mStats) { 283 mStats.noteUidProcessStateLocked(uid, state); 284 } 285 } 286 287 // Public interface... 288 289 public byte[] getStatistics() { 290 mContext.enforceCallingPermission( 291 android.Manifest.permission.BATTERY_STATS, null); 292 //Slog.i("foo", "SENDING BATTERY INFO:"); 293 //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM)); 294 Parcel out = Parcel.obtain(); 295 awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL)); 296 synchronized (mStats) { 297 mStats.writeToParcel(out, 0); 298 } 299 byte[] data = out.marshall(); 300 out.recycle(); 301 return data; 302 } 303 304 public ParcelFileDescriptor getStatisticsStream() { 305 mContext.enforceCallingPermission( 306 android.Manifest.permission.BATTERY_STATS, null); 307 //Slog.i("foo", "SENDING BATTERY INFO:"); 308 //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM)); 309 Parcel out = Parcel.obtain(); 310 awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL)); 311 synchronized (mStats) { 312 mStats.writeToParcel(out, 0); 313 } 314 byte[] data = out.marshall(); 315 out.recycle(); 316 try { 317 return ParcelFileDescriptor.fromData(data, "battery-stats"); 318 } catch (IOException e) { 319 Slog.w(TAG, "Unable to create shared memory", e); 320 return null; 321 } 322 } 323 324 public boolean isCharging() { 325 synchronized (mStats) { 326 return mStats.isCharging(); 327 } 328 } 329 330 public long computeBatteryTimeRemaining() { 331 synchronized (mStats) { 332 long time = mStats.computeBatteryTimeRemaining(SystemClock.elapsedRealtime()); 333 return time >= 0 ? (time/1000) : time; 334 } 335 } 336 337 public long computeChargeTimeRemaining() { 338 synchronized (mStats) { 339 long time = mStats.computeChargeTimeRemaining(SystemClock.elapsedRealtime()); 340 return time >= 0 ? (time/1000) : time; 341 } 342 } 343 344 public void noteEvent(int code, String name, int uid) { 345 enforceCallingPermission(); 346 synchronized (mStats) { 347 mStats.noteEventLocked(code, name, uid); 348 } 349 } 350 351 public void noteSyncStart(String name, int uid) { 352 enforceCallingPermission(); 353 synchronized (mStats) { 354 mStats.noteSyncStartLocked(name, uid); 355 } 356 } 357 358 public void noteSyncFinish(String name, int uid) { 359 enforceCallingPermission(); 360 synchronized (mStats) { 361 mStats.noteSyncFinishLocked(name, uid); 362 } 363 } 364 365 public void noteJobStart(String name, int uid) { 366 enforceCallingPermission(); 367 synchronized (mStats) { 368 mStats.noteJobStartLocked(name, uid); 369 } 370 } 371 372 public void noteJobFinish(String name, int uid, int stopReason) { 373 enforceCallingPermission(); 374 synchronized (mStats) { 375 mStats.noteJobFinishLocked(name, uid, stopReason); 376 } 377 } 378 379 public void noteAlarmStart(String name, int uid) { 380 enforceCallingPermission(); 381 synchronized (mStats) { 382 mStats.noteAlarmStartLocked(name, uid); 383 } 384 } 385 386 public void noteAlarmFinish(String name, int uid) { 387 enforceCallingPermission(); 388 synchronized (mStats) { 389 mStats.noteAlarmFinishLocked(name, uid); 390 } 391 } 392 393 public void noteStartWakelock(int uid, int pid, String name, String historyName, int type, 394 boolean unimportantForLogging) { 395 enforceCallingPermission(); 396 synchronized (mStats) { 397 mStats.noteStartWakeLocked(uid, pid, name, historyName, type, unimportantForLogging, 398 SystemClock.elapsedRealtime(), SystemClock.uptimeMillis()); 399 } 400 } 401 402 public void noteStopWakelock(int uid, int pid, String name, String historyName, int type) { 403 enforceCallingPermission(); 404 synchronized (mStats) { 405 mStats.noteStopWakeLocked(uid, pid, name, historyName, type, 406 SystemClock.elapsedRealtime(), SystemClock.uptimeMillis()); 407 } 408 } 409 410 public void noteStartWakelockFromSource(WorkSource ws, int pid, String name, 411 String historyName, int type, boolean unimportantForLogging) { 412 enforceCallingPermission(); 413 synchronized (mStats) { 414 mStats.noteStartWakeFromSourceLocked(ws, pid, name, historyName, 415 type, unimportantForLogging); 416 } 417 } 418 419 public void noteChangeWakelockFromSource(WorkSource ws, int pid, String name, 420 String historyName, int type, WorkSource newWs, int newPid, String newName, 421 String newHistoryName, int newType, boolean newUnimportantForLogging) { 422 enforceCallingPermission(); 423 synchronized (mStats) { 424 mStats.noteChangeWakelockFromSourceLocked(ws, pid, name, historyName, type, 425 newWs, newPid, newName, newHistoryName, newType, newUnimportantForLogging); 426 } 427 } 428 429 public void noteStopWakelockFromSource(WorkSource ws, int pid, String name, String historyName, 430 int type) { 431 enforceCallingPermission(); 432 synchronized (mStats) { 433 mStats.noteStopWakeFromSourceLocked(ws, pid, name, historyName, type); 434 } 435 } 436 437 public void noteLongPartialWakelockStart(String name, String historyName, int uid) { 438 enforceCallingPermission(); 439 synchronized (mStats) { 440 mStats.noteLongPartialWakelockStart(name, historyName, uid); 441 } 442 } 443 444 public void noteLongPartialWakelockFinish(String name, String historyName, int uid) { 445 enforceCallingPermission(); 446 synchronized (mStats) { 447 mStats.noteLongPartialWakelockFinish(name, historyName, uid); 448 } 449 } 450 451 public void noteStartSensor(int uid, int sensor) { 452 enforceCallingPermission(); 453 synchronized (mStats) { 454 mStats.noteStartSensorLocked(uid, sensor); 455 } 456 } 457 458 public void noteStopSensor(int uid, int sensor) { 459 enforceCallingPermission(); 460 synchronized (mStats) { 461 mStats.noteStopSensorLocked(uid, sensor); 462 } 463 } 464 465 public void noteVibratorOn(int uid, long durationMillis) { 466 enforceCallingPermission(); 467 synchronized (mStats) { 468 mStats.noteVibratorOnLocked(uid, durationMillis); 469 } 470 } 471 472 public void noteVibratorOff(int uid) { 473 enforceCallingPermission(); 474 synchronized (mStats) { 475 mStats.noteVibratorOffLocked(uid); 476 } 477 } 478 479 public void noteStartGps(int uid) { 480 enforceCallingPermission(); 481 synchronized (mStats) { 482 mStats.noteStartGpsLocked(uid); 483 } 484 } 485 486 public void noteStopGps(int uid) { 487 enforceCallingPermission(); 488 synchronized (mStats) { 489 mStats.noteStopGpsLocked(uid); 490 } 491 } 492 493 public void noteScreenState(int state) { 494 enforceCallingPermission(); 495 if (DBG) Slog.d(TAG, "begin noteScreenState"); 496 synchronized (mStats) { 497 mStats.noteScreenStateLocked(state); 498 } 499 if (DBG) Slog.d(TAG, "end noteScreenState"); 500 } 501 502 public void noteScreenBrightness(int brightness) { 503 enforceCallingPermission(); 504 synchronized (mStats) { 505 mStats.noteScreenBrightnessLocked(brightness); 506 } 507 } 508 509 public void noteUserActivity(int uid, int event) { 510 enforceCallingPermission(); 511 synchronized (mStats) { 512 mStats.noteUserActivityLocked(uid, event); 513 } 514 } 515 516 public void noteWakeUp(String reason, int reasonUid) { 517 enforceCallingPermission(); 518 synchronized (mStats) { 519 mStats.noteWakeUpLocked(reason, reasonUid); 520 } 521 } 522 523 public void noteInteractive(boolean interactive) { 524 enforceCallingPermission(); 525 synchronized (mStats) { 526 mStats.noteInteractiveLocked(interactive); 527 } 528 } 529 530 public void noteConnectivityChanged(int type, String extra) { 531 enforceCallingPermission(); 532 synchronized (mStats) { 533 mStats.noteConnectivityChangedLocked(type, extra); 534 } 535 } 536 537 public void noteMobileRadioPowerState(int powerState, long timestampNs, int uid) { 538 enforceCallingPermission(); 539 final boolean update; 540 synchronized (mStats) { 541 update = mStats.noteMobileRadioPowerStateLocked(powerState, timestampNs, uid); 542 } 543 544 if (update) { 545 mWorker.scheduleSync("modem-data", BatteryExternalStatsWorker.UPDATE_RADIO); 546 } 547 } 548 549 public void notePhoneOn() { 550 enforceCallingPermission(); 551 synchronized (mStats) { 552 mStats.notePhoneOnLocked(); 553 } 554 } 555 556 public void notePhoneOff() { 557 enforceCallingPermission(); 558 synchronized (mStats) { 559 mStats.notePhoneOffLocked(); 560 } 561 } 562 563 public void notePhoneSignalStrength(SignalStrength signalStrength) { 564 enforceCallingPermission(); 565 synchronized (mStats) { 566 mStats.notePhoneSignalStrengthLocked(signalStrength); 567 } 568 } 569 570 public void notePhoneDataConnectionState(int dataType, boolean hasData) { 571 enforceCallingPermission(); 572 synchronized (mStats) { 573 mStats.notePhoneDataConnectionStateLocked(dataType, hasData); 574 } 575 } 576 577 public void notePhoneState(int state) { 578 enforceCallingPermission(); 579 int simState = TelephonyManager.getDefault().getSimState(); 580 synchronized (mStats) { 581 mStats.notePhoneStateLocked(state, simState); 582 } 583 } 584 585 public void noteWifiOn() { 586 enforceCallingPermission(); 587 synchronized (mStats) { 588 mStats.noteWifiOnLocked(); 589 } 590 } 591 592 public void noteWifiOff() { 593 enforceCallingPermission(); 594 synchronized (mStats) { 595 mStats.noteWifiOffLocked(); 596 } 597 } 598 599 public void noteStartAudio(int uid) { 600 enforceCallingPermission(); 601 synchronized (mStats) { 602 mStats.noteAudioOnLocked(uid); 603 } 604 } 605 606 public void noteStopAudio(int uid) { 607 enforceCallingPermission(); 608 synchronized (mStats) { 609 mStats.noteAudioOffLocked(uid); 610 } 611 } 612 613 public void noteStartVideo(int uid) { 614 enforceCallingPermission(); 615 synchronized (mStats) { 616 mStats.noteVideoOnLocked(uid); 617 } 618 } 619 620 public void noteStopVideo(int uid) { 621 enforceCallingPermission(); 622 synchronized (mStats) { 623 mStats.noteVideoOffLocked(uid); 624 } 625 } 626 627 public void noteResetAudio() { 628 enforceCallingPermission(); 629 synchronized (mStats) { 630 mStats.noteResetAudioLocked(); 631 } 632 } 633 634 public void noteResetVideo() { 635 enforceCallingPermission(); 636 synchronized (mStats) { 637 mStats.noteResetVideoLocked(); 638 } 639 } 640 641 public void noteFlashlightOn(int uid) { 642 enforceCallingPermission(); 643 synchronized (mStats) { 644 mStats.noteFlashlightOnLocked(uid); 645 } 646 } 647 648 public void noteFlashlightOff(int uid) { 649 enforceCallingPermission(); 650 synchronized (mStats) { 651 mStats.noteFlashlightOffLocked(uid); 652 } 653 } 654 655 public void noteStartCamera(int uid) { 656 enforceCallingPermission(); 657 if (DBG) Slog.d(TAG, "begin noteStartCamera"); 658 synchronized (mStats) { 659 mStats.noteCameraOnLocked(uid); 660 } 661 if (DBG) Slog.d(TAG, "end noteStartCamera"); 662 } 663 664 public void noteStopCamera(int uid) { 665 enforceCallingPermission(); 666 synchronized (mStats) { 667 mStats.noteCameraOffLocked(uid); 668 } 669 } 670 671 public void noteResetCamera() { 672 enforceCallingPermission(); 673 synchronized (mStats) { 674 mStats.noteResetCameraLocked(); 675 } 676 } 677 678 public void noteResetFlashlight() { 679 enforceCallingPermission(); 680 synchronized (mStats) { 681 mStats.noteResetFlashlightLocked(); 682 } 683 } 684 685 @Override 686 public void noteWifiRadioPowerState(int powerState, long tsNanos, int uid) { 687 enforceCallingPermission(); 688 689 // There was a change in WiFi power state. 690 // Collect data now for the past activity. 691 synchronized (mStats) { 692 if (mStats.isOnBattery()) { 693 final String type = (powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH || 694 powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM) ? "active" 695 : "inactive"; 696 mWorker.scheduleSync("wifi-data: " + type, BatteryExternalStatsWorker.UPDATE_WIFI); 697 } 698 mStats.noteWifiRadioPowerState(powerState, tsNanos, uid); 699 } 700 } 701 702 public void noteWifiRunning(WorkSource ws) { 703 enforceCallingPermission(); 704 synchronized (mStats) { 705 mStats.noteWifiRunningLocked(ws); 706 } 707 } 708 709 public void noteWifiRunningChanged(WorkSource oldWs, WorkSource newWs) { 710 enforceCallingPermission(); 711 synchronized (mStats) { 712 mStats.noteWifiRunningChangedLocked(oldWs, newWs); 713 } 714 } 715 716 public void noteWifiStopped(WorkSource ws) { 717 enforceCallingPermission(); 718 synchronized (mStats) { 719 mStats.noteWifiStoppedLocked(ws); 720 } 721 } 722 723 public void noteWifiState(int wifiState, String accessPoint) { 724 enforceCallingPermission(); 725 synchronized (mStats) { 726 mStats.noteWifiStateLocked(wifiState, accessPoint); 727 } 728 } 729 730 public void noteWifiSupplicantStateChanged(int supplState, boolean failedAuth) { 731 enforceCallingPermission(); 732 synchronized (mStats) { 733 mStats.noteWifiSupplicantStateChangedLocked(supplState, failedAuth); 734 } 735 } 736 737 public void noteWifiRssiChanged(int newRssi) { 738 enforceCallingPermission(); 739 synchronized (mStats) { 740 mStats.noteWifiRssiChangedLocked(newRssi); 741 } 742 } 743 744 public void noteFullWifiLockAcquired(int uid) { 745 enforceCallingPermission(); 746 synchronized (mStats) { 747 mStats.noteFullWifiLockAcquiredLocked(uid); 748 } 749 } 750 751 public void noteFullWifiLockReleased(int uid) { 752 enforceCallingPermission(); 753 synchronized (mStats) { 754 mStats.noteFullWifiLockReleasedLocked(uid); 755 } 756 } 757 758 public void noteWifiScanStarted(int uid) { 759 enforceCallingPermission(); 760 synchronized (mStats) { 761 mStats.noteWifiScanStartedLocked(uid); 762 } 763 } 764 765 public void noteWifiScanStopped(int uid) { 766 enforceCallingPermission(); 767 synchronized (mStats) { 768 mStats.noteWifiScanStoppedLocked(uid); 769 } 770 } 771 772 public void noteWifiMulticastEnabled(int uid) { 773 enforceCallingPermission(); 774 synchronized (mStats) { 775 mStats.noteWifiMulticastEnabledLocked(uid); 776 } 777 } 778 779 public void noteWifiMulticastDisabled(int uid) { 780 enforceCallingPermission(); 781 synchronized (mStats) { 782 mStats.noteWifiMulticastDisabledLocked(uid); 783 } 784 } 785 786 public void noteFullWifiLockAcquiredFromSource(WorkSource ws) { 787 enforceCallingPermission(); 788 synchronized (mStats) { 789 mStats.noteFullWifiLockAcquiredFromSourceLocked(ws); 790 } 791 } 792 793 public void noteFullWifiLockReleasedFromSource(WorkSource ws) { 794 enforceCallingPermission(); 795 synchronized (mStats) { 796 mStats.noteFullWifiLockReleasedFromSourceLocked(ws); 797 } 798 } 799 800 public void noteWifiScanStartedFromSource(WorkSource ws) { 801 enforceCallingPermission(); 802 synchronized (mStats) { 803 mStats.noteWifiScanStartedFromSourceLocked(ws); 804 } 805 } 806 807 public void noteWifiScanStoppedFromSource(WorkSource ws) { 808 enforceCallingPermission(); 809 synchronized (mStats) { 810 mStats.noteWifiScanStoppedFromSourceLocked(ws); 811 } 812 } 813 814 public void noteWifiBatchedScanStartedFromSource(WorkSource ws, int csph) { 815 enforceCallingPermission(); 816 synchronized (mStats) { 817 mStats.noteWifiBatchedScanStartedFromSourceLocked(ws, csph); 818 } 819 } 820 821 public void noteWifiBatchedScanStoppedFromSource(WorkSource ws) { 822 enforceCallingPermission(); 823 synchronized (mStats) { 824 mStats.noteWifiBatchedScanStoppedFromSourceLocked(ws); 825 } 826 } 827 828 public void noteWifiMulticastEnabledFromSource(WorkSource ws) { 829 enforceCallingPermission(); 830 synchronized (mStats) { 831 mStats.noteWifiMulticastEnabledFromSourceLocked(ws); 832 } 833 } 834 835 @Override 836 public void noteWifiMulticastDisabledFromSource(WorkSource ws) { 837 enforceCallingPermission(); 838 synchronized (mStats) { 839 mStats.noteWifiMulticastDisabledFromSourceLocked(ws); 840 } 841 } 842 843 @Override 844 public void noteNetworkInterfaceType(String iface, int networkType) { 845 enforceCallingPermission(); 846 synchronized (mStats) { 847 mStats.noteNetworkInterfaceTypeLocked(iface, networkType); 848 } 849 } 850 851 @Override 852 public void noteNetworkStatsEnabled() { 853 enforceCallingPermission(); 854 // During device boot, qtaguid isn't enabled until after the inital 855 // loading of battery stats. Now that they're enabled, take our initial 856 // snapshot for future delta calculation. 857 mWorker.scheduleSync("network-stats-enabled", 858 BatteryExternalStatsWorker.UPDATE_RADIO | BatteryExternalStatsWorker.UPDATE_WIFI); 859 } 860 861 @Override 862 public void noteDeviceIdleMode(int mode, String activeReason, int activeUid) { 863 enforceCallingPermission(); 864 synchronized (mStats) { 865 mStats.noteDeviceIdleModeLocked(mode, activeReason, activeUid); 866 } 867 } 868 869 public void notePackageInstalled(String pkgName, int versionCode) { 870 enforceCallingPermission(); 871 synchronized (mStats) { 872 mStats.notePackageInstalledLocked(pkgName, versionCode); 873 } 874 } 875 876 public void notePackageUninstalled(String pkgName) { 877 enforceCallingPermission(); 878 synchronized (mStats) { 879 mStats.notePackageUninstalledLocked(pkgName); 880 } 881 } 882 883 @Override 884 public void noteBleScanStarted(WorkSource ws, boolean isUnoptimized) { 885 enforceCallingPermission(); 886 synchronized (mStats) { 887 mStats.noteBluetoothScanStartedFromSourceLocked(ws, isUnoptimized); 888 } 889 } 890 891 @Override 892 public void noteBleScanStopped(WorkSource ws) { 893 enforceCallingPermission(); 894 synchronized (mStats) { 895 mStats.noteBluetoothScanStoppedFromSourceLocked(ws); 896 } 897 } 898 899 @Override 900 public void noteResetBleScan() { 901 enforceCallingPermission(); 902 synchronized (mStats) { 903 mStats.noteResetBluetoothScanLocked(); 904 } 905 } 906 907 @Override 908 public void noteBleScanResults(WorkSource ws, int numNewResults) { 909 enforceCallingPermission(); 910 synchronized (mStats) { 911 mStats.noteBluetoothScanResultsFromSourceLocked(ws, numNewResults); 912 } 913 } 914 915 @Override 916 public void noteWifiControllerActivity(WifiActivityEnergyInfo info) { 917 enforceCallingPermission(); 918 919 if (info == null || !info.isValid()) { 920 Slog.e(TAG, "invalid wifi data given: " + info); 921 return; 922 } 923 924 mStats.updateWifiState(info); 925 } 926 927 @Override 928 public void noteBluetoothControllerActivity(BluetoothActivityEnergyInfo info) { 929 enforceCallingPermission(); 930 if (info == null || !info.isValid()) { 931 Slog.e(TAG, "invalid bluetooth data given: " + info); 932 return; 933 } 934 935 mStats.updateBluetoothStateLocked(info); 936 } 937 938 @Override 939 public void noteModemControllerActivity(ModemActivityInfo info) { 940 enforceCallingPermission(); 941 942 if (info == null || !info.isValid()) { 943 Slog.e(TAG, "invalid modem data given: " + info); 944 return; 945 } 946 947 mStats.updateMobileRadioState(info); 948 } 949 950 public boolean isOnBattery() { 951 return mStats.isOnBattery(); 952 } 953 954 @Override 955 public void setBatteryState(final int status, final int health, final int plugType, 956 final int level, final int temp, final int volt, final int chargeUAh, 957 final int chargeFullUAh) { 958 enforceCallingPermission(); 959 960 // BatteryService calls us here and we may update external state. It would be wrong 961 // to block such a low level service like BatteryService on external stats like WiFi. 962 mWorker.scheduleRunnable(() -> { 963 synchronized (mStats) { 964 final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE; 965 if (mStats.isOnBattery() == onBattery) { 966 // The battery state has not changed, so we don't need to sync external 967 // stats immediately. 968 mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt, 969 chargeUAh, chargeFullUAh); 970 return; 971 } 972 } 973 974 // Sync external stats first as the battery has changed states. If we don't sync 975 // before changing the state, we may not collect the relevant data later. 976 // Order here is guaranteed since we're scheduling from the same thread and we are 977 // using a single threaded executor. 978 mWorker.scheduleSync("battery-state", BatteryExternalStatsWorker.UPDATE_ALL); 979 mWorker.scheduleRunnable(() -> { 980 synchronized (mStats) { 981 mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt, 982 chargeUAh, chargeFullUAh); 983 } 984 }); 985 }); 986 } 987 988 public long getAwakeTimeBattery() { 989 mContext.enforceCallingOrSelfPermission( 990 android.Manifest.permission.BATTERY_STATS, null); 991 return mStats.getAwakeTimeBattery(); 992 } 993 994 public long getAwakeTimePlugged() { 995 mContext.enforceCallingOrSelfPermission( 996 android.Manifest.permission.BATTERY_STATS, null); 997 return mStats.getAwakeTimePlugged(); 998 } 999 1000 public void enforceCallingPermission() { 1001 if (Binder.getCallingPid() == Process.myPid()) { 1002 return; 1003 } 1004 mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS, 1005 Binder.getCallingPid(), Binder.getCallingUid(), null); 1006 } 1007 1008 final class WakeupReasonThread extends Thread { 1009 private static final int MAX_REASON_SIZE = 512; 1010 private CharsetDecoder mDecoder; 1011 private ByteBuffer mUtf8Buffer; 1012 private CharBuffer mUtf16Buffer; 1013 1014 WakeupReasonThread() { 1015 super("BatteryStats_wakeupReason"); 1016 } 1017 1018 public void run() { 1019 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); 1020 1021 mDecoder = StandardCharsets.UTF_8 1022 .newDecoder() 1023 .onMalformedInput(CodingErrorAction.REPLACE) 1024 .onUnmappableCharacter(CodingErrorAction.REPLACE) 1025 .replaceWith("?"); 1026 1027 mUtf8Buffer = ByteBuffer.allocateDirect(MAX_REASON_SIZE); 1028 mUtf16Buffer = CharBuffer.allocate(MAX_REASON_SIZE); 1029 1030 try { 1031 String reason; 1032 while ((reason = waitWakeup()) != null) { 1033 synchronized (mStats) { 1034 mStats.noteWakeupReasonLocked(reason); 1035 } 1036 } 1037 } catch (RuntimeException e) { 1038 Slog.e(TAG, "Failure reading wakeup reasons", e); 1039 } 1040 } 1041 1042 private String waitWakeup() { 1043 mUtf8Buffer.clear(); 1044 mUtf16Buffer.clear(); 1045 mDecoder.reset(); 1046 1047 int bytesWritten = nativeWaitWakeup(mUtf8Buffer); 1048 if (bytesWritten < 0) { 1049 return null; 1050 } else if (bytesWritten == 0) { 1051 return "unknown"; 1052 } 1053 1054 // Set the buffer's limit to the number of bytes written. 1055 mUtf8Buffer.limit(bytesWritten); 1056 1057 // Decode the buffer from UTF-8 to UTF-16. 1058 // Unmappable characters will be replaced. 1059 mDecoder.decode(mUtf8Buffer, mUtf16Buffer, true); 1060 mUtf16Buffer.flip(); 1061 1062 // Create a String from the UTF-16 buffer. 1063 return mUtf16Buffer.toString(); 1064 } 1065 } 1066 1067 private static native int nativeWaitWakeup(ByteBuffer outBuffer); 1068 1069 private void dumpHelp(PrintWriter pw) { 1070 pw.println("Battery stats (batterystats) dump options:"); 1071 pw.println(" [--checkin] [--history] [--history-start] [--charged] [-c]"); 1072 pw.println(" [--daily] [--reset] [--write] [--new-daily] [--read-daily] [-h] [<package.name>]"); 1073 pw.println(" --checkin: generate output for a checkin report; will write (and clear) the"); 1074 pw.println(" last old completed stats when they had been reset."); 1075 pw.println(" -c: write the current stats in checkin format."); 1076 pw.println(" --history: show only history data."); 1077 pw.println(" --history-start <num>: show only history data starting at given time offset."); 1078 pw.println(" --charged: only output data since last charged."); 1079 pw.println(" --daily: only output full daily data."); 1080 pw.println(" --reset: reset the stats, clearing all current data."); 1081 pw.println(" --write: force write current collected stats to disk."); 1082 pw.println(" --new-daily: immediately create and write new daily stats record."); 1083 pw.println(" --read-daily: read-load last written daily stats."); 1084 pw.println(" <package.name>: optional name of package to filter output by."); 1085 pw.println(" -h: print this help text."); 1086 pw.println("Battery stats (batterystats) commands:"); 1087 pw.println(" enable|disable <option>"); 1088 pw.println(" Enable or disable a running option. Option state is not saved across boots."); 1089 pw.println(" Options are:"); 1090 pw.println(" full-history: include additional detailed events in battery history:"); 1091 pw.println(" wake_lock_in, alarms and proc events"); 1092 pw.println(" no-auto-reset: don't automatically reset stats when unplugged"); 1093 pw.println(" pretend-screen-off: pretend the screen is off, even if screen state changes"); 1094 } 1095 1096 private int doEnableOrDisable(PrintWriter pw, int i, String[] args, boolean enable) { 1097 i++; 1098 if (i >= args.length) { 1099 pw.println("Missing option argument for " + (enable ? "--enable" : "--disable")); 1100 dumpHelp(pw); 1101 return -1; 1102 } 1103 if ("full-wake-history".equals(args[i]) || "full-history".equals(args[i])) { 1104 synchronized (mStats) { 1105 mStats.setRecordAllHistoryLocked(enable); 1106 } 1107 } else if ("no-auto-reset".equals(args[i])) { 1108 synchronized (mStats) { 1109 mStats.setNoAutoReset(enable); 1110 } 1111 } else if ("pretend-screen-off".equals(args[i])) { 1112 synchronized (mStats) { 1113 mStats.setPretendScreenOff(enable); 1114 } 1115 } else { 1116 pw.println("Unknown enable/disable option: " + args[i]); 1117 dumpHelp(pw); 1118 return -1; 1119 } 1120 return i; 1121 } 1122 1123 1124 @Override 1125 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1126 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; 1127 1128 int flags = 0; 1129 boolean useCheckinFormat = false; 1130 boolean isRealCheckin = false; 1131 boolean noOutput = false; 1132 boolean writeData = false; 1133 long historyStart = -1; 1134 int reqUid = -1; 1135 if (args != null) { 1136 for (int i=0; i<args.length; i++) { 1137 String arg = args[i]; 1138 if ("--checkin".equals(arg)) { 1139 useCheckinFormat = true; 1140 isRealCheckin = true; 1141 } else if ("--history".equals(arg)) { 1142 flags |= BatteryStats.DUMP_HISTORY_ONLY; 1143 } else if ("--history-start".equals(arg)) { 1144 flags |= BatteryStats.DUMP_HISTORY_ONLY; 1145 i++; 1146 if (i >= args.length) { 1147 pw.println("Missing time argument for --history-since"); 1148 dumpHelp(pw); 1149 return; 1150 } 1151 historyStart = Long.parseLong(args[i]); 1152 writeData = true; 1153 } else if ("-c".equals(arg)) { 1154 useCheckinFormat = true; 1155 flags |= BatteryStats.DUMP_INCLUDE_HISTORY; 1156 } else if ("--charged".equals(arg)) { 1157 flags |= BatteryStats.DUMP_CHARGED_ONLY; 1158 } else if ("--daily".equals(arg)) { 1159 flags |= BatteryStats.DUMP_DAILY_ONLY; 1160 } else if ("--reset".equals(arg)) { 1161 synchronized (mStats) { 1162 mStats.resetAllStatsCmdLocked(); 1163 pw.println("Battery stats reset."); 1164 noOutput = true; 1165 } 1166 mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL); 1167 } else if ("--write".equals(arg)) { 1168 awaitUninterruptibly(mWorker.scheduleSync("dump", 1169 BatteryExternalStatsWorker.UPDATE_ALL)); 1170 synchronized (mStats) { 1171 mStats.writeSyncLocked(); 1172 pw.println("Battery stats written."); 1173 noOutput = true; 1174 } 1175 } else if ("--new-daily".equals(arg)) { 1176 synchronized (mStats) { 1177 mStats.recordDailyStatsLocked(); 1178 pw.println("New daily stats written."); 1179 noOutput = true; 1180 } 1181 } else if ("--read-daily".equals(arg)) { 1182 synchronized (mStats) { 1183 mStats.readDailyStatsLocked(); 1184 pw.println("Last daily stats read."); 1185 noOutput = true; 1186 } 1187 } else if ("--enable".equals(arg) || "enable".equals(arg)) { 1188 i = doEnableOrDisable(pw, i, args, true); 1189 if (i < 0) { 1190 return; 1191 } 1192 pw.println("Enabled: " + args[i]); 1193 return; 1194 } else if ("--disable".equals(arg) || "disable".equals(arg)) { 1195 i = doEnableOrDisable(pw, i, args, false); 1196 if (i < 0) { 1197 return; 1198 } 1199 pw.println("Disabled: " + args[i]); 1200 return; 1201 } else if ("-h".equals(arg)) { 1202 dumpHelp(pw); 1203 return; 1204 } else if ("-a".equals(arg)) { 1205 flags |= BatteryStats.DUMP_VERBOSE; 1206 } else if (arg.length() > 0 && arg.charAt(0) == '-'){ 1207 pw.println("Unknown option: " + arg); 1208 dumpHelp(pw); 1209 return; 1210 } else { 1211 // Not an option, last argument must be a package name. 1212 try { 1213 reqUid = mContext.getPackageManager().getPackageUidAsUser(arg, 1214 UserHandle.getCallingUserId()); 1215 } catch (PackageManager.NameNotFoundException e) { 1216 pw.println("Unknown package: " + arg); 1217 dumpHelp(pw); 1218 return; 1219 } 1220 } 1221 } 1222 } 1223 if (noOutput) { 1224 return; 1225 } 1226 1227 long ident = Binder.clearCallingIdentity(); 1228 try { 1229 if (BatteryStatsHelper.checkWifiOnly(mContext)) { 1230 flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY; 1231 } 1232 // Fetch data from external sources and update the BatteryStatsImpl object with them. 1233 awaitUninterruptibly(mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL)); 1234 } finally { 1235 Binder.restoreCallingIdentity(ident); 1236 } 1237 1238 if (reqUid >= 0) { 1239 // By default, if the caller is only interested in a specific package, then 1240 // we only dump the aggregated data since charged. 1241 if ((flags&(BatteryStats.DUMP_HISTORY_ONLY|BatteryStats.DUMP_CHARGED_ONLY)) == 0) { 1242 flags |= BatteryStats.DUMP_CHARGED_ONLY; 1243 // Also if they are doing -c, we don't want history. 1244 flags &= ~BatteryStats.DUMP_INCLUDE_HISTORY; 1245 } 1246 } 1247 1248 if (useCheckinFormat) { 1249 List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications( 1250 PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL); 1251 if (isRealCheckin) { 1252 // For a real checkin, first we want to prefer to use the last complete checkin 1253 // file if there is one. 1254 synchronized (mStats.mCheckinFile) { 1255 if (mStats.mCheckinFile.exists()) { 1256 try { 1257 byte[] raw = mStats.mCheckinFile.readFully(); 1258 if (raw != null) { 1259 Parcel in = Parcel.obtain(); 1260 in.unmarshall(raw, 0, raw.length); 1261 in.setDataPosition(0); 1262 BatteryStatsImpl checkinStats = new BatteryStatsImpl( 1263 null, mStats.mHandler, null); 1264 checkinStats.readSummaryFromParcel(in); 1265 in.recycle(); 1266 checkinStats.dumpCheckinLocked(mContext, pw, apps, flags, 1267 historyStart); 1268 mStats.mCheckinFile.delete(); 1269 return; 1270 } 1271 } catch (IOException | ParcelFormatException e) { 1272 Slog.w(TAG, "Failure reading checkin file " 1273 + mStats.mCheckinFile.getBaseFile(), e); 1274 } 1275 } 1276 } 1277 } 1278 if (DBG) Slog.d(TAG, "begin dumpCheckinLocked from UID " + Binder.getCallingUid()); 1279 synchronized (mStats) { 1280 mStats.dumpCheckinLocked(mContext, pw, apps, flags, historyStart); 1281 if (writeData) { 1282 mStats.writeAsyncLocked(); 1283 } 1284 } 1285 if (DBG) Slog.d(TAG, "end dumpCheckinLocked"); 1286 } else { 1287 if (DBG) Slog.d(TAG, "begin dumpLocked from UID " + Binder.getCallingUid()); 1288 synchronized (mStats) { 1289 mStats.dumpLocked(mContext, pw, flags, reqUid, historyStart); 1290 if (writeData) { 1291 mStats.writeAsyncLocked(); 1292 } 1293 } 1294 if (DBG) Slog.d(TAG, "end dumpLocked"); 1295 } 1296 } 1297 1298 /** 1299 * Gets a snapshot of the system health for a particular uid. 1300 */ 1301 @Override 1302 public HealthStatsParceler takeUidSnapshot(int requestUid) { 1303 if (requestUid != Binder.getCallingUid()) { 1304 mContext.enforceCallingOrSelfPermission( 1305 android.Manifest.permission.BATTERY_STATS, null); 1306 } 1307 long ident = Binder.clearCallingIdentity(); 1308 try { 1309 awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids", 1310 BatteryExternalStatsWorker.UPDATE_ALL)); 1311 synchronized (mStats) { 1312 return getHealthStatsForUidLocked(requestUid); 1313 } 1314 } catch (Exception ex) { 1315 Slog.w(TAG, "Crashed while writing for takeUidSnapshot(" + requestUid + ")", ex); 1316 throw ex; 1317 } finally { 1318 Binder.restoreCallingIdentity(ident); 1319 } 1320 } 1321 1322 /** 1323 * Gets a snapshot of the system health for a number of uids. 1324 */ 1325 @Override 1326 public HealthStatsParceler[] takeUidSnapshots(int[] requestUids) { 1327 if (!onlyCaller(requestUids)) { 1328 mContext.enforceCallingOrSelfPermission( 1329 android.Manifest.permission.BATTERY_STATS, null); 1330 } 1331 long ident = Binder.clearCallingIdentity(); 1332 int i=-1; 1333 try { 1334 awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids", 1335 BatteryExternalStatsWorker.UPDATE_ALL)); 1336 synchronized (mStats) { 1337 final int N = requestUids.length; 1338 final HealthStatsParceler[] results = new HealthStatsParceler[N]; 1339 for (i=0; i<N; i++) { 1340 results[i] = getHealthStatsForUidLocked(requestUids[i]); 1341 } 1342 return results; 1343 } 1344 } catch (Exception ex) { 1345 if (DBG) Slog.d(TAG, "Crashed while writing for takeUidSnapshots(" 1346 + Arrays.toString(requestUids) + ") i=" + i, ex); 1347 throw ex; 1348 } finally { 1349 Binder.restoreCallingIdentity(ident); 1350 } 1351 } 1352 1353 /** 1354 * Returns whether the Binder.getCallingUid is the only thing in requestUids. 1355 */ 1356 private static boolean onlyCaller(int[] requestUids) { 1357 final int caller = Binder.getCallingUid(); 1358 final int N = requestUids.length; 1359 for (int i=0; i<N; i++) { 1360 if (requestUids[i] != caller) { 1361 return false; 1362 } 1363 } 1364 return true; 1365 } 1366 1367 /** 1368 * Gets a HealthStatsParceler for the given uid. You should probably call 1369 * updateExternalStatsSync first. 1370 */ 1371 HealthStatsParceler getHealthStatsForUidLocked(int requestUid) { 1372 final HealthStatsBatteryStatsWriter writer = new HealthStatsBatteryStatsWriter(); 1373 final HealthStatsWriter uidWriter = new HealthStatsWriter(UidHealthStats.CONSTANTS); 1374 final BatteryStats.Uid uid = mStats.getUidStats().get(requestUid); 1375 if (uid != null) { 1376 writer.writeUid(uidWriter, mStats, uid); 1377 } 1378 return new HealthStatsParceler(uidWriter); 1379 } 1380 1381} 1382