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