1/* 2 * Copyright (C) 2009 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.settings.fuelgauge; 18 19import android.app.Activity; 20import android.app.LoaderManager; 21import android.app.LoaderManager.LoaderCallbacks; 22import android.content.Context; 23import android.content.Loader; 24import android.content.res.TypedArray; 25import android.graphics.drawable.Drawable; 26import android.os.BatteryStats; 27import android.os.Bundle; 28import android.os.Handler; 29import android.os.Message; 30import android.os.Process; 31import android.os.UserHandle; 32import android.provider.SearchIndexableResource; 33import android.support.annotation.VisibleForTesting; 34import android.support.v7.preference.Preference; 35import android.support.v7.preference.PreferenceGroup; 36import android.text.TextUtils; 37import android.text.format.DateUtils; 38import android.text.format.Formatter; 39import android.util.Log; 40import android.util.SparseArray; 41import android.view.Menu; 42import android.view.MenuInflater; 43import android.view.MenuItem; 44import android.view.View; 45import android.view.View.OnClickListener; 46import android.view.View.OnLongClickListener; 47import android.widget.TextView; 48 49import com.android.internal.hardware.AmbientDisplayConfiguration; 50import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 51import com.android.internal.os.BatterySipper; 52import com.android.internal.os.BatterySipper.DrainType; 53import com.android.internal.os.PowerProfile; 54import com.android.settings.R; 55import com.android.settings.Settings.HighPowerApplicationsActivity; 56import com.android.settings.SettingsActivity; 57import com.android.settings.Utils; 58import com.android.settings.applications.LayoutPreference; 59import com.android.settings.applications.ManageApplications; 60import com.android.settings.core.instrumentation.MetricsFeatureProvider; 61import com.android.settings.dashboard.SummaryLoader; 62import com.android.settings.display.AmbientDisplayPreferenceController; 63import com.android.settings.display.AutoBrightnessPreferenceController; 64import com.android.settings.display.BatteryPercentagePreferenceController; 65import com.android.settings.display.TimeoutPreferenceController; 66import com.android.settings.fuelgauge.anomaly.Anomaly; 67import com.android.settings.fuelgauge.anomaly.AnomalyDetectionPolicy; 68import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment.AnomalyDialogListener; 69import com.android.settings.fuelgauge.anomaly.AnomalyLoader; 70import com.android.settings.fuelgauge.anomaly.AnomalySummaryPreferenceController; 71import com.android.settings.fuelgauge.anomaly.AnomalyUtils; 72import com.android.settings.overlay.FeatureFactory; 73import com.android.settings.search.BaseSearchIndexProvider; 74import com.android.settingslib.core.AbstractPreferenceController; 75 76import java.util.ArrayList; 77import java.util.Arrays; 78import java.util.List; 79 80/** 81 * Displays a list of apps and subsystems that consume power, ordered by how much power was 82 * consumed since the last time it was unplugged. 83 */ 84public class PowerUsageSummary extends PowerUsageBase implements 85 AnomalyDialogListener, OnLongClickListener, OnClickListener { 86 87 static final String TAG = "PowerUsageSummary"; 88 89 private static final boolean DEBUG = false; 90 private static final boolean USE_FAKE_DATA = false; 91 private static final String KEY_APP_LIST = "app_list"; 92 private static final String KEY_BATTERY_HEADER = "battery_header"; 93 private static final String KEY_SHOW_ALL_APPS = "show_all_apps"; 94 private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 10; 95 private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10; 96 97 private static final String KEY_SCREEN_USAGE = "screen_usage"; 98 private static final String KEY_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge"; 99 100 private static final String KEY_AUTO_BRIGHTNESS = "auto_brightness_battery"; 101 private static final String KEY_SCREEN_TIMEOUT = "screen_timeout_battery"; 102 private static final String KEY_AMBIENT_DISPLAY = "ambient_display_battery"; 103 private static final String KEY_BATTERY_SAVER_SUMMARY = "battery_saver_summary"; 104 private static final String KEY_HIGH_USAGE = "high_usage"; 105 106 @VisibleForTesting 107 static final int ANOMALY_LOADER = 1; 108 @VisibleForTesting 109 static final int BATTERY_INFO_LOADER = 2; 110 private static final int MENU_STATS_TYPE = Menu.FIRST; 111 @VisibleForTesting 112 static final int MENU_HIGH_POWER_APPS = Menu.FIRST + 3; 113 @VisibleForTesting 114 static final int MENU_ADDITIONAL_BATTERY_INFO = Menu.FIRST + 4; 115 @VisibleForTesting 116 static final int MENU_TOGGLE_APPS = Menu.FIRST + 5; 117 private static final int MENU_HELP = Menu.FIRST + 6; 118 public static final int DEBUG_INFO_LOADER = 3; 119 120 @VisibleForTesting 121 boolean mShowAllApps = false; 122 @VisibleForTesting 123 PowerGaugePreference mScreenUsagePref; 124 @VisibleForTesting 125 PowerGaugePreference mLastFullChargePref; 126 @VisibleForTesting 127 PowerUsageFeatureProvider mPowerFeatureProvider; 128 @VisibleForTesting 129 BatteryUtils mBatteryUtils; 130 @VisibleForTesting 131 LayoutPreference mBatteryLayoutPref; 132 133 /** 134 * SparseArray that maps uid to {@link Anomaly}, so we could find {@link Anomaly} by uid 135 */ 136 @VisibleForTesting 137 SparseArray<List<Anomaly>> mAnomalySparseArray; 138 @VisibleForTesting 139 PreferenceGroup mAppListGroup; 140 @VisibleForTesting 141 BatteryHeaderPreferenceController mBatteryHeaderPreferenceController; 142 private AnomalySummaryPreferenceController mAnomalySummaryPreferenceController; 143 private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; 144 145 private LoaderManager.LoaderCallbacks<List<Anomaly>> mAnomalyLoaderCallbacks = 146 new LoaderManager.LoaderCallbacks<List<Anomaly>>() { 147 148 @Override 149 public Loader<List<Anomaly>> onCreateLoader(int id, Bundle args) { 150 return new AnomalyLoader(getContext(), mStatsHelper); 151 } 152 153 @Override 154 public void onLoadFinished(Loader<List<Anomaly>> loader, List<Anomaly> data) { 155 final AnomalyUtils anomalyUtils = AnomalyUtils.getInstance(getContext()); 156 anomalyUtils.logAnomalies(mMetricsFeatureProvider, data, 157 MetricsEvent.FUELGAUGE_POWER_USAGE_SUMMARY); 158 159 // show high usage preference if possible 160 mAnomalySummaryPreferenceController.updateAnomalySummaryPreference(data); 161 162 updateAnomalySparseArray(data); 163 refreshAnomalyIcon(); 164 } 165 166 @Override 167 public void onLoaderReset(Loader<List<Anomaly>> loader) { 168 169 } 170 }; 171 172 @VisibleForTesting 173 LoaderManager.LoaderCallbacks<BatteryInfo> mBatteryInfoLoaderCallbacks = 174 new LoaderManager.LoaderCallbacks<BatteryInfo>() { 175 176 @Override 177 public Loader<BatteryInfo> onCreateLoader(int i, Bundle bundle) { 178 return new BatteryInfoLoader(getContext(), mStatsHelper); 179 } 180 181 @Override 182 public void onLoadFinished(Loader<BatteryInfo> loader, BatteryInfo batteryInfo) { 183 mBatteryHeaderPreferenceController.updateHeaderPreference(batteryInfo); 184 } 185 186 @Override 187 public void onLoaderReset(Loader<BatteryInfo> loader) { 188 // do nothing 189 } 190 }; 191 192 LoaderManager.LoaderCallbacks<List<BatteryInfo>> mBatteryInfoDebugLoaderCallbacks = 193 new LoaderCallbacks<List<BatteryInfo>>() { 194 @Override 195 public Loader<List<BatteryInfo>> onCreateLoader(int i, Bundle bundle) { 196 return new DebugEstimatesLoader(getContext(), mStatsHelper); 197 } 198 199 @Override 200 public void onLoadFinished(Loader<List<BatteryInfo>> loader, 201 List<BatteryInfo> batteryInfos) { 202 final BatteryMeterView batteryView = (BatteryMeterView) mBatteryLayoutPref 203 .findViewById(R.id.battery_header_icon); 204 final TextView percentRemaining = 205 mBatteryLayoutPref.findViewById(R.id.battery_percent); 206 final TextView summary1 = mBatteryLayoutPref.findViewById(R.id.summary1); 207 final TextView summary2 = mBatteryLayoutPref.findViewById(R.id.summary2); 208 BatteryInfo oldInfo = batteryInfos.get(0); 209 BatteryInfo newInfo = batteryInfos.get(1); 210 percentRemaining.setText(Utils.formatPercentage(oldInfo.batteryLevel)); 211 212 // set the text to the old estimate (copied from battery info). Note that this 213 // can sometimes say 0 time remaining because battery stats requires the phone 214 // be unplugged for a period of time before being willing ot make an estimate. 215 summary1.setText(mPowerFeatureProvider.getOldEstimateDebugString( 216 Formatter.formatShortElapsedTime(getContext(), 217 BatteryUtils.convertUsToMs(oldInfo.remainingTimeUs)))); 218 219 // for this one we can just set the string directly 220 summary2.setText(mPowerFeatureProvider.getEnhancedEstimateDebugString( 221 Formatter.formatShortElapsedTime(getContext(), 222 BatteryUtils.convertUsToMs(newInfo.remainingTimeUs)))); 223 224 batteryView.setBatteryLevel(oldInfo.batteryLevel); 225 batteryView.setCharging(!oldInfo.discharging); 226 } 227 228 @Override 229 public void onLoaderReset(Loader<List<BatteryInfo>> loader) { 230 } 231 }; 232 233 @Override 234 public void onCreate(Bundle icicle) { 235 super.onCreate(icicle); 236 setAnimationAllowed(true); 237 238 initFeatureProvider(); 239 mBatteryLayoutPref = (LayoutPreference) findPreference(KEY_BATTERY_HEADER); 240 241 mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST); 242 mScreenUsagePref = (PowerGaugePreference) findPreference(KEY_SCREEN_USAGE); 243 mLastFullChargePref = (PowerGaugePreference) findPreference( 244 KEY_TIME_SINCE_LAST_FULL_CHARGE); 245 mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary); 246 mAnomalySummaryPreferenceController = new AnomalySummaryPreferenceController( 247 (SettingsActivity) getActivity(), this, MetricsEvent.FUELGAUGE_POWER_USAGE_SUMMARY); 248 mBatteryUtils = BatteryUtils.getInstance(getContext()); 249 mAnomalySparseArray = new SparseArray<>(); 250 251 restartBatteryInfoLoader(); 252 restoreSavedInstance(icicle); 253 } 254 255 @Override 256 public int getMetricsCategory() { 257 return MetricsEvent.FUELGAUGE_POWER_USAGE_SUMMARY; 258 } 259 260 @Override 261 public void onPause() { 262 BatteryEntry.stopRequestQueue(); 263 mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON); 264 super.onPause(); 265 } 266 267 @Override 268 public void onDestroy() { 269 super.onDestroy(); 270 if (getActivity().isChangingConfigurations()) { 271 BatteryEntry.clearUidCache(); 272 } 273 } 274 275 @Override 276 public void onSaveInstanceState(Bundle outState) { 277 super.onSaveInstanceState(outState); 278 outState.putBoolean(KEY_SHOW_ALL_APPS, mShowAllApps); 279 } 280 281 @Override 282 public boolean onPreferenceTreeClick(Preference preference) { 283 if (mAnomalySummaryPreferenceController.onPreferenceTreeClick(preference)) { 284 return true; 285 } 286 if (KEY_BATTERY_HEADER.equals(preference.getKey())) { 287 performBatteryHeaderClick(); 288 return true; 289 } else if (!(preference instanceof PowerGaugePreference)) { 290 return super.onPreferenceTreeClick(preference); 291 } 292 PowerGaugePreference pgp = (PowerGaugePreference) preference; 293 BatteryEntry entry = pgp.getInfo(); 294 AdvancedPowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), 295 this, mStatsHelper, mStatsType, entry, pgp.getPercent(), 296 mAnomalySparseArray.get(entry.sipper.getUid())); 297 return super.onPreferenceTreeClick(preference); 298 } 299 300 @Override 301 protected String getLogTag() { 302 return TAG; 303 } 304 305 @Override 306 protected int getPreferenceScreenResId() { 307 return R.xml.power_usage_summary; 308 } 309 310 @Override 311 protected List<AbstractPreferenceController> getPreferenceControllers(Context context) { 312 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 313 mBatteryHeaderPreferenceController = new BatteryHeaderPreferenceController( 314 context, getActivity(), this /* host */, getLifecycle()); 315 controllers.add(mBatteryHeaderPreferenceController); 316 controllers.add(new AutoBrightnessPreferenceController(context, KEY_AUTO_BRIGHTNESS)); 317 controllers.add(new TimeoutPreferenceController(context, KEY_SCREEN_TIMEOUT)); 318 controllers.add(new BatterySaverController(context, getLifecycle())); 319 controllers.add(new BatteryPercentagePreferenceController(context)); 320 controllers.add(new AmbientDisplayPreferenceController( 321 context, 322 new AmbientDisplayConfiguration(context), 323 KEY_AMBIENT_DISPLAY)); 324 return controllers; 325 } 326 327 @Override 328 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 329 if (DEBUG) { 330 menu.add(Menu.NONE, MENU_STATS_TYPE, Menu.NONE, R.string.menu_stats_total) 331 .setIcon(com.android.internal.R.drawable.ic_menu_info_details) 332 .setAlphabeticShortcut('t'); 333 } 334 335 menu.add(Menu.NONE, MENU_HIGH_POWER_APPS, Menu.NONE, R.string.high_power_apps); 336 337 if (mPowerFeatureProvider.isAdditionalBatteryInfoEnabled()) { 338 menu.add(Menu.NONE, MENU_ADDITIONAL_BATTERY_INFO, 339 Menu.NONE, R.string.additional_battery_info); 340 } 341 if (mPowerFeatureProvider.isPowerAccountingToggleEnabled()) { 342 menu.add(Menu.NONE, MENU_TOGGLE_APPS, Menu.NONE, 343 mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps); 344 } 345 346 super.onCreateOptionsMenu(menu, inflater); 347 } 348 349 @Override 350 protected int getHelpResource() { 351 return R.string.help_url_battery; 352 } 353 354 @Override 355 public boolean onOptionsItemSelected(MenuItem item) { 356 final SettingsActivity sa = (SettingsActivity) getActivity(); 357 final Context context = getContext(); 358 final MetricsFeatureProvider metricsFeatureProvider = 359 FeatureFactory.getFactory(context).getMetricsFeatureProvider(); 360 361 switch (item.getItemId()) { 362 case MENU_STATS_TYPE: 363 if (mStatsType == BatteryStats.STATS_SINCE_CHARGED) { 364 mStatsType = BatteryStats.STATS_SINCE_UNPLUGGED; 365 } else { 366 mStatsType = BatteryStats.STATS_SINCE_CHARGED; 367 } 368 refreshUi(); 369 return true; 370 case MENU_HIGH_POWER_APPS: 371 Bundle args = new Bundle(); 372 args.putString(ManageApplications.EXTRA_CLASSNAME, 373 HighPowerApplicationsActivity.class.getName()); 374 sa.startPreferencePanel(this, ManageApplications.class.getName(), args, 375 R.string.high_power_apps, null, null, 0); 376 metricsFeatureProvider.action(context, 377 MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_OPTIMIZATION); 378 return true; 379 case MENU_ADDITIONAL_BATTERY_INFO: 380 startActivity(mPowerFeatureProvider 381 .getAdditionalBatteryInfoIntent()); 382 metricsFeatureProvider.action(context, 383 MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_USAGE_ALERTS); 384 return true; 385 case MENU_TOGGLE_APPS: 386 mShowAllApps = !mShowAllApps; 387 item.setTitle(mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps); 388 metricsFeatureProvider.action(context, 389 MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE, mShowAllApps); 390 restartBatteryStatsLoader(false /* clearHeader */); 391 return true; 392 default: 393 return super.onOptionsItemSelected(item); 394 } 395 } 396 397 @VisibleForTesting 398 void restoreSavedInstance(Bundle savedInstance) { 399 if (savedInstance != null) { 400 mShowAllApps = savedInstance.getBoolean(KEY_SHOW_ALL_APPS, false); 401 } 402 } 403 404 private void addNotAvailableMessage() { 405 final String NOT_AVAILABLE = "not_available"; 406 Preference notAvailable = getCachedPreference(NOT_AVAILABLE); 407 if (notAvailable == null) { 408 notAvailable = new Preference(getPrefContext()); 409 notAvailable.setKey(NOT_AVAILABLE); 410 notAvailable.setTitle(R.string.power_usage_not_available); 411 mAppListGroup.addPreference(notAvailable); 412 } 413 } 414 415 private void performBatteryHeaderClick() { 416 if (mPowerFeatureProvider.isAdvancedUiEnabled()) { 417 Utils.startWithFragment(getContext(), PowerUsageAdvanced.class.getName(), null, 418 null, 0, R.string.advanced_battery_title, null, getMetricsCategory()); 419 } else { 420 mStatsHelper.storeStatsHistoryInFile(BatteryHistoryDetail.BATTERY_HISTORY_FILE); 421 Bundle args = new Bundle(2); 422 args.putString(BatteryHistoryDetail.EXTRA_STATS, 423 BatteryHistoryDetail.BATTERY_HISTORY_FILE); 424 args.putParcelable(BatteryHistoryDetail.EXTRA_BROADCAST, 425 mStatsHelper.getBatteryBroadcast()); 426 Utils.startWithFragment(getContext(), BatteryHistoryDetail.class.getName(), args, 427 null, 0, R.string.history_details_title, null, getMetricsCategory()); 428 } 429 } 430 431 private static boolean isSharedGid(int uid) { 432 return UserHandle.getAppIdFromSharedAppGid(uid) > 0; 433 } 434 435 private static boolean isSystemUid(int uid) { 436 return uid >= Process.SYSTEM_UID && uid < Process.FIRST_APPLICATION_UID; 437 } 438 439 /** 440 * We want to coalesce some UIDs. For example, dex2oat runs under a shared gid that 441 * exists for all users of the same app. We detect this case and merge the power use 442 * for dex2oat to the device OWNER's use of the app. 443 * 444 * @return A sorted list of apps using power. 445 */ 446 private List<BatterySipper> getCoalescedUsageList(final List<BatterySipper> sippers) { 447 final SparseArray<BatterySipper> uidList = new SparseArray<>(); 448 449 final ArrayList<BatterySipper> results = new ArrayList<>(); 450 final int numSippers = sippers.size(); 451 for (int i = 0; i < numSippers; i++) { 452 BatterySipper sipper = sippers.get(i); 453 if (sipper.getUid() > 0) { 454 int realUid = sipper.getUid(); 455 456 // Check if this UID is a shared GID. If so, we combine it with the OWNER's 457 // actual app UID. 458 if (isSharedGid(sipper.getUid())) { 459 realUid = UserHandle.getUid(UserHandle.USER_SYSTEM, 460 UserHandle.getAppIdFromSharedAppGid(sipper.getUid())); 461 } 462 463 // Check if this UID is a system UID (mediaserver, logd, nfc, drm, etc). 464 if (isSystemUid(realUid) 465 && !"mediaserver".equals(sipper.packageWithHighestDrain)) { 466 // Use the system UID for all UIDs running in their own sandbox that 467 // are not apps. We exclude mediaserver because we already are expected to 468 // report that as a separate item. 469 realUid = Process.SYSTEM_UID; 470 } 471 472 if (realUid != sipper.getUid()) { 473 // Replace the BatterySipper with a new one with the real UID set. 474 BatterySipper newSipper = new BatterySipper(sipper.drainType, 475 new FakeUid(realUid), 0.0); 476 newSipper.add(sipper); 477 newSipper.packageWithHighestDrain = sipper.packageWithHighestDrain; 478 newSipper.mPackages = sipper.mPackages; 479 sipper = newSipper; 480 } 481 482 int index = uidList.indexOfKey(realUid); 483 if (index < 0) { 484 // New entry. 485 uidList.put(realUid, sipper); 486 } else { 487 // Combine BatterySippers if we already have one with this UID. 488 final BatterySipper existingSipper = uidList.valueAt(index); 489 existingSipper.add(sipper); 490 if (existingSipper.packageWithHighestDrain == null 491 && sipper.packageWithHighestDrain != null) { 492 existingSipper.packageWithHighestDrain = sipper.packageWithHighestDrain; 493 } 494 495 final int existingPackageLen = existingSipper.mPackages != null ? 496 existingSipper.mPackages.length : 0; 497 final int newPackageLen = sipper.mPackages != null ? 498 sipper.mPackages.length : 0; 499 if (newPackageLen > 0) { 500 String[] newPackages = new String[existingPackageLen + newPackageLen]; 501 if (existingPackageLen > 0) { 502 System.arraycopy(existingSipper.mPackages, 0, newPackages, 0, 503 existingPackageLen); 504 } 505 System.arraycopy(sipper.mPackages, 0, newPackages, existingPackageLen, 506 newPackageLen); 507 existingSipper.mPackages = newPackages; 508 } 509 } 510 } else { 511 results.add(sipper); 512 } 513 } 514 515 final int numUidSippers = uidList.size(); 516 for (int i = 0; i < numUidSippers; i++) { 517 results.add(uidList.valueAt(i)); 518 } 519 520 // The sort order must have changed, so re-sort based on total power use. 521 mBatteryUtils.sortUsageList(results); 522 return results; 523 } 524 525 protected void refreshUi() { 526 final Context context = getContext(); 527 if (context == null) { 528 return; 529 } 530 531 restartAnomalyDetectionIfPossible(); 532 533 // reload BatteryInfo and updateUI 534 restartBatteryInfoLoader(); 535 final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime(mStatsHelper, 536 System.currentTimeMillis()); 537 updateScreenPreference(); 538 updateLastFullChargePreference(lastFullChargeTime); 539 540 final CharSequence timeSequence = Utils.formatElapsedTime(context, lastFullChargeTime, 541 false); 542 final int resId = mShowAllApps ? R.string.power_usage_list_summary_device 543 : R.string.power_usage_list_summary; 544 mAppListGroup.setTitle(TextUtils.expandTemplate(getText(resId), timeSequence)); 545 546 refreshAppListGroup(); 547 } 548 549 private void refreshAppListGroup() { 550 final Context context = getContext(); 551 final PowerProfile powerProfile = mStatsHelper.getPowerProfile(); 552 final BatteryStats stats = mStatsHelper.getStats(); 553 final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); 554 boolean addedSome = false; 555 556 TypedArray array = context.obtainStyledAttributes( 557 new int[]{android.R.attr.colorControlNormal}); 558 final int colorControl = array.getColor(0, 0); 559 array.recycle(); 560 561 final int dischargeAmount = USE_FAKE_DATA ? 5000 562 : stats != null ? stats.getDischargeAmount(mStatsType) : 0; 563 564 cacheRemoveAllPrefs(mAppListGroup); 565 mAppListGroup.setOrderingAsAdded(false); 566 567 if (averagePower >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP || USE_FAKE_DATA) { 568 final List<BatterySipper> usageList = getCoalescedUsageList( 569 USE_FAKE_DATA ? getFakeStats() : mStatsHelper.getUsageList()); 570 double hiddenPowerMah = mShowAllApps ? 0 : 571 mBatteryUtils.removeHiddenBatterySippers(usageList); 572 mBatteryUtils.sortUsageList(usageList); 573 574 final int numSippers = usageList.size(); 575 for (int i = 0; i < numSippers; i++) { 576 final BatterySipper sipper = usageList.get(i); 577 double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower(); 578 579 final double percentOfTotal = mBatteryUtils.calculateBatteryPercent( 580 sipper.totalPowerMah, totalPower, hiddenPowerMah, dischargeAmount); 581 582 if (((int) (percentOfTotal + .5)) < 1) { 583 continue; 584 } 585 if (shouldHideSipper(sipper)) { 586 continue; 587 } 588 final UserHandle userHandle = new UserHandle(UserHandle.getUserId(sipper.getUid())); 589 final BatteryEntry entry = new BatteryEntry(getActivity(), mHandler, mUm, sipper); 590 final Drawable badgedIcon = mUm.getBadgedIconForUser(entry.getIcon(), 591 userHandle); 592 final CharSequence contentDescription = mUm.getBadgedLabelForUser(entry.getLabel(), 593 userHandle); 594 595 final String key = extractKeyFromSipper(sipper); 596 PowerGaugePreference pref = (PowerGaugePreference) getCachedPreference(key); 597 if (pref == null) { 598 pref = new PowerGaugePreference(getPrefContext(), badgedIcon, 599 contentDescription, entry); 600 pref.setKey(key); 601 } 602 603 final double percentOfMax = (sipper.totalPowerMah * 100) 604 / mStatsHelper.getMaxPower(); 605 sipper.percent = percentOfTotal; 606 pref.setTitle(entry.getLabel()); 607 pref.setOrder(i + 1); 608 pref.setPercent(percentOfTotal); 609 pref.shouldShowAnomalyIcon(false); 610 if (sipper.usageTimeMs == 0 && sipper.drainType == DrainType.APP) { 611 sipper.usageTimeMs = mBatteryUtils.getProcessTimeMs( 612 BatteryUtils.StatusType.FOREGROUND, sipper.uidObj, mStatsType); 613 } 614 setUsageSummary(pref, sipper); 615 if ((sipper.drainType != DrainType.APP 616 || sipper.uidObj.getUid() == Process.ROOT_UID) 617 && sipper.drainType != DrainType.USER) { 618 pref.setTint(colorControl); 619 } 620 addedSome = true; 621 mAppListGroup.addPreference(pref); 622 if (mAppListGroup.getPreferenceCount() - getCachedCount() 623 > (MAX_ITEMS_TO_LIST + 1)) { 624 break; 625 } 626 } 627 } 628 if (!addedSome) { 629 addNotAvailableMessage(); 630 } 631 removeCachedPrefs(mAppListGroup); 632 633 BatteryEntry.startRequestQueue(); 634 } 635 636 @VisibleForTesting 637 boolean shouldHideSipper(BatterySipper sipper) { 638 // Don't show over-counted and unaccounted in any condition 639 return sipper.drainType == BatterySipper.DrainType.OVERCOUNTED 640 || sipper.drainType == BatterySipper.DrainType.UNACCOUNTED; 641 } 642 643 @VisibleForTesting 644 void refreshAnomalyIcon() { 645 for (int i = 0, size = mAnomalySparseArray.size(); i < size; i++) { 646 final String key = extractKeyFromUid(mAnomalySparseArray.keyAt(i)); 647 final PowerGaugePreference pref = (PowerGaugePreference) mAppListGroup.findPreference( 648 key); 649 if (pref != null) { 650 pref.shouldShowAnomalyIcon(true); 651 } 652 } 653 } 654 655 @VisibleForTesting 656 void restartAnomalyDetectionIfPossible() { 657 if (getAnomalyDetectionPolicy().isAnomalyDetectionEnabled()) { 658 getLoaderManager().restartLoader(ANOMALY_LOADER, Bundle.EMPTY, mAnomalyLoaderCallbacks); 659 } 660 } 661 662 @VisibleForTesting 663 AnomalyDetectionPolicy getAnomalyDetectionPolicy() { 664 return new AnomalyDetectionPolicy(getContext()); 665 } 666 667 @VisibleForTesting 668 BatterySipper findBatterySipperByType(List<BatterySipper> usageList, DrainType type) { 669 for (int i = 0, size = usageList.size(); i < size; i++) { 670 final BatterySipper sipper = usageList.get(i); 671 if (sipper.drainType == type) { 672 return sipper; 673 } 674 } 675 return null; 676 } 677 678 @VisibleForTesting 679 void updateScreenPreference() { 680 final BatterySipper sipper = findBatterySipperByType( 681 mStatsHelper.getUsageList(), DrainType.SCREEN); 682 final long usageTimeMs = sipper != null ? sipper.usageTimeMs : 0; 683 684 mScreenUsagePref.setSubtitle(Utils.formatElapsedTime(getContext(), usageTimeMs, false)); 685 } 686 687 @VisibleForTesting 688 void updateLastFullChargePreference(long timeMs) { 689 final CharSequence timeSequence = Utils.formatElapsedTime(getContext(), timeMs, false); 690 mLastFullChargePref.setSubtitle( 691 TextUtils.expandTemplate(getText(R.string.power_last_full_charge_summary), 692 timeSequence)); 693 } 694 695 @VisibleForTesting 696 void showBothEstimates() { 697 final Context context = getContext(); 698 if (context == null 699 || !mPowerFeatureProvider.isEnhancedBatteryPredictionEnabled(context)) { 700 return; 701 } 702 getLoaderManager().restartLoader(DEBUG_INFO_LOADER, Bundle.EMPTY, 703 mBatteryInfoDebugLoaderCallbacks); 704 } 705 706 @VisibleForTesting 707 double calculatePercentage(double powerUsage, double dischargeAmount) { 708 final double totalPower = mStatsHelper.getTotalPower(); 709 return totalPower == 0 ? 0 : 710 ((powerUsage / totalPower) * dischargeAmount); 711 } 712 713 @VisibleForTesting 714 void setUsageSummary(Preference preference, BatterySipper sipper) { 715 // Only show summary when usage time is longer than one minute 716 final long usageTimeMs = sipper.usageTimeMs; 717 if (usageTimeMs >= DateUtils.MINUTE_IN_MILLIS) { 718 final CharSequence timeSequence = Utils.formatElapsedTime(getContext(), usageTimeMs, 719 false); 720 preference.setSummary( 721 (sipper.drainType != DrainType.APP || mBatteryUtils.shouldHideSipper(sipper)) 722 ? timeSequence 723 : TextUtils.expandTemplate(getText(R.string.battery_used_for), 724 timeSequence)); 725 } 726 } 727 728 @VisibleForTesting 729 String extractKeyFromSipper(BatterySipper sipper) { 730 if (sipper.uidObj != null) { 731 return extractKeyFromUid(sipper.getUid()); 732 } else if (sipper.drainType != DrainType.APP) { 733 return sipper.drainType.toString(); 734 } else if (sipper.getPackages() != null) { 735 return TextUtils.concat(sipper.getPackages()).toString(); 736 } else { 737 Log.w(TAG, "Inappropriate BatterySipper without uid and package names: " + sipper); 738 return "-1"; 739 } 740 } 741 742 @VisibleForTesting 743 String extractKeyFromUid(int uid) { 744 return Integer.toString(uid); 745 } 746 747 @VisibleForTesting 748 void setBatteryLayoutPreference(LayoutPreference layoutPreference) { 749 mBatteryLayoutPref = layoutPreference; 750 } 751 752 @VisibleForTesting 753 void initFeatureProvider() { 754 final Context context = getContext(); 755 mPowerFeatureProvider = FeatureFactory.getFactory(context) 756 .getPowerUsageFeatureProvider(context); 757 } 758 759 @VisibleForTesting 760 void updateAnomalySparseArray(List<Anomaly> anomalies) { 761 mAnomalySparseArray.clear(); 762 for (int i = 0, size = anomalies.size(); i < size; i++) { 763 final Anomaly anomaly = anomalies.get(i); 764 if (mAnomalySparseArray.get(anomaly.uid) == null) { 765 mAnomalySparseArray.append(anomaly.uid, new ArrayList<>()); 766 } 767 mAnomalySparseArray.get(anomaly.uid).add(anomaly); 768 } 769 } 770 771 @VisibleForTesting 772 void restartBatteryInfoLoader() { 773 getLoaderManager().restartLoader(BATTERY_INFO_LOADER, Bundle.EMPTY, 774 mBatteryInfoLoaderCallbacks); 775 if (mPowerFeatureProvider.isEstimateDebugEnabled()) { 776 // Unfortunately setting a long click listener on a view means it will no 777 // longer pass the regular click event to the parent, so we have to register 778 // a regular click listener as well. 779 View header = mBatteryLayoutPref.findViewById(R.id.summary1); 780 header.setOnLongClickListener(this); 781 header.setOnClickListener(this); 782 } 783 } 784 785 private static List<BatterySipper> getFakeStats() { 786 ArrayList<BatterySipper> stats = new ArrayList<>(); 787 float use = 5; 788 for (DrainType type : DrainType.values()) { 789 if (type == DrainType.APP) { 790 continue; 791 } 792 stats.add(new BatterySipper(type, null, use)); 793 use += 5; 794 } 795 for (int i = 0; i < 100; i++) { 796 stats.add(new BatterySipper(DrainType.APP, 797 new FakeUid(Process.FIRST_APPLICATION_UID + i), use)); 798 } 799 stats.add(new BatterySipper(DrainType.APP, 800 new FakeUid(0), use)); 801 802 // Simulate dex2oat process. 803 BatterySipper sipper = new BatterySipper(DrainType.APP, 804 new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID)), 10.0f); 805 sipper.packageWithHighestDrain = "dex2oat"; 806 stats.add(sipper); 807 808 sipper = new BatterySipper(DrainType.APP, 809 new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID + 1)), 10.0f); 810 sipper.packageWithHighestDrain = "dex2oat"; 811 stats.add(sipper); 812 813 sipper = new BatterySipper(DrainType.APP, 814 new FakeUid(UserHandle.getSharedAppGid(Process.LOG_UID)), 9.0f); 815 stats.add(sipper); 816 817 return stats; 818 } 819 820 Handler mHandler = new Handler() { 821 822 @Override 823 public void handleMessage(Message msg) { 824 switch (msg.what) { 825 case BatteryEntry.MSG_UPDATE_NAME_ICON: 826 BatteryEntry entry = (BatteryEntry) msg.obj; 827 PowerGaugePreference pgp = 828 (PowerGaugePreference) findPreference( 829 Integer.toString(entry.sipper.uidObj.getUid())); 830 if (pgp != null) { 831 final int userId = UserHandle.getUserId(entry.sipper.getUid()); 832 final UserHandle userHandle = new UserHandle(userId); 833 pgp.setIcon(mUm.getBadgedIconForUser(entry.getIcon(), userHandle)); 834 pgp.setTitle(entry.name); 835 if (entry.sipper.drainType == DrainType.APP) { 836 pgp.setContentDescription(entry.name); 837 } 838 } 839 break; 840 case BatteryEntry.MSG_REPORT_FULLY_DRAWN: 841 Activity activity = getActivity(); 842 if (activity != null) { 843 activity.reportFullyDrawn(); 844 } 845 break; 846 } 847 super.handleMessage(msg); 848 } 849 }; 850 851 @Override 852 public void onAnomalyHandled(Anomaly anomaly) { 853 mAnomalySummaryPreferenceController.hideHighUsagePreference(); 854 } 855 856 @Override 857 public boolean onLongClick(View view) { 858 showBothEstimates(); 859 view.setOnLongClickListener(null); 860 return true; 861 } 862 863 @Override 864 public void onClick(View view) { 865 performBatteryHeaderClick(); 866 } 867 868 @Override 869 protected void restartBatteryStatsLoader() { 870 restartBatteryStatsLoader(true /* clearHeader */); 871 } 872 873 void restartBatteryStatsLoader(boolean clearHeader) { 874 super.restartBatteryStatsLoader(); 875 if (clearHeader) { 876 mBatteryHeaderPreferenceController.quickUpdateHeaderPreference(); 877 } 878 } 879 880 private static class SummaryProvider implements SummaryLoader.SummaryProvider { 881 private final Context mContext; 882 private final SummaryLoader mLoader; 883 private final BatteryBroadcastReceiver mBatteryBroadcastReceiver; 884 885 private SummaryProvider(Context context, SummaryLoader loader) { 886 mContext = context; 887 mLoader = loader; 888 mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext); 889 mBatteryBroadcastReceiver.setBatteryChangedListener(() -> { 890 BatteryInfo.getBatteryInfo(mContext, new BatteryInfo.Callback() { 891 @Override 892 public void onBatteryInfoLoaded(BatteryInfo info) { 893 mLoader.setSummary(SummaryProvider.this, info.chargeLabel); 894 } 895 }, true /* shortString */); 896 }); 897 } 898 899 @Override 900 public void setListening(boolean listening) { 901 if (listening) { 902 mBatteryBroadcastReceiver.register(); 903 } else { 904 mBatteryBroadcastReceiver.unRegister(); 905 } 906 } 907 } 908 909 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 910 new BaseSearchIndexProvider() { 911 @Override 912 public List<SearchIndexableResource> getXmlResourcesToIndex( 913 Context context, boolean enabled) { 914 final SearchIndexableResource sir = new SearchIndexableResource(context); 915 sir.xmlResId = R.xml.power_usage_summary; 916 return Arrays.asList(sir); 917 } 918 919 @Override 920 public List<String> getNonIndexableKeys(Context context) { 921 List<String> niks = super.getNonIndexableKeys(context); 922 niks.add(KEY_HIGH_USAGE); 923 niks.add(KEY_BATTERY_SAVER_SUMMARY); 924 // Duplicates in display 925 niks.add(KEY_AUTO_BRIGHTNESS); 926 niks.add(KEY_SCREEN_TIMEOUT); 927 niks.add(KEY_AMBIENT_DISPLAY); 928 return niks; 929 } 930 }; 931 932 public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY 933 = new SummaryLoader.SummaryProviderFactory() { 934 @Override 935 public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, 936 SummaryLoader summaryLoader) { 937 return new SummaryProvider(activity, summaryLoader); 938 } 939 }; 940} 941