2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.fuelgauge;
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.graphics.drawable.Drawable;
25 import android.os.BatteryStats;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.Message;
29 import android.os.Process;
30 import android.os.UserHandle;
31 import android.provider.SearchIndexableResource;
32 import android.support.annotation.VisibleForTesting;
33 import android.support.v7.preference.Preference;
34 import android.support.v7.preference.PreferenceGroup;
35 import android.text.TextUtils;
36 import android.text.format.DateUtils;
37 import android.text.format.Formatter;
38 import android.util.Log;
39 import android.util.SparseArray;
40 import android.view.Menu;
41 import android.view.MenuInflater;
42 import android.view.MenuItem;
43 import android.view.View;
44 import android.view.View.OnClickListener;
45 import android.view.View.OnLongClickListener;
46 import android.widget.TextView;
48 import com.android.internal.hardware.AmbientDisplayConfiguration;
49 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
50 import com.android.internal.os.BatterySipper;
51 import com.android.internal.os.BatterySipper.DrainType;
52 import com.android.internal.os.PowerProfile;
53 import com.android.settings.R;
54 import com.android.settings.Settings.HighPowerApplicationsActivity;
55 import com.android.settings.SettingsActivity;
56 import com.android.settings.Utils;
57 import com.android.settings.applications.LayoutPreference;
58 import com.android.settings.applications.manageapplications.ManageApplications;
59 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
60 import com.android.settings.dashboard.SummaryLoader;
61 import com.android.settings.display.AmbientDisplayPreferenceController;
62 import com.android.settings.display.AutoBrightnessPreferenceController;
63 import com.android.settings.display.BatteryPercentagePreferenceController;
64 import com.android.settings.display.TimeoutPreferenceController;
65 import com.android.settings.fuelgauge.anomaly.Anomaly;
66 import com.android.settings.fuelgauge.anomaly.AnomalyDetectionPolicy;
67 import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment.AnomalyDialogListener;
68 import com.android.settings.fuelgauge.anomaly.AnomalyLoader;
69 import com.android.settings.fuelgauge.anomaly.AnomalySummaryPreferenceController;
70 import com.android.settings.fuelgauge.anomaly.AnomalyUtils;
71 import com.android.settings.overlay.FeatureFactory;
72 import com.android.settings.search.BaseSearchIndexProvider;
73 import com.android.settingslib.core.AbstractPreferenceController;
75 import java.util.ArrayList;
76 import java.util.Arrays;
77 import java.util.List;
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.
83 public class PowerUsageSummary extends PowerUsageBase implements
84 AnomalyDialogListener, OnLongClickListener, OnClickListener {
86 static final String TAG = "PowerUsageSummary";
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;
96 private static final String KEY_SCREEN_USAGE = "screen_usage";
97 private static final String KEY_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge";
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";
106 static final int ANOMALY_LOADER = 1;
108 static final int BATTERY_INFO_LOADER = 2;
109 private static final int MENU_STATS_TYPE = Menu.FIRST;
111 static final int MENU_HIGH_POWER_APPS = Menu.FIRST + 3;
113 static final int MENU_TOGGLE_APPS = Menu.FIRST + 4;
114 private static final int MENU_HELP = Menu.FIRST + 5;
115 public static final int DEBUG_INFO_LOADER = 3;
118 boolean mShowAllApps = false;
120 PowerGaugePreference mScreenUsagePref;
122 PowerGaugePreference mLastFullChargePref;
124 PowerUsageFeatureProvider mPowerFeatureProvider;
126 BatteryUtils mBatteryUtils;
128 LayoutPreference mBatteryLayoutPref;
131 * SparseArray that maps uid to {@link Anomaly}, so we could find {@link Anomaly} by uid
134 SparseArray<List<Anomaly>> mAnomalySparseArray;
136 PreferenceGroup mAppListGroup;
138 BatteryHeaderPreferenceController mBatteryHeaderPreferenceController;
139 private AnomalySummaryPreferenceController mAnomalySummaryPreferenceController;
140 private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
142 private LoaderManager.LoaderCallbacks<List<Anomaly>> mAnomalyLoaderCallbacks =
143 new LoaderManager.LoaderCallbacks<List<Anomaly>>() {
146 public Loader<List<Anomaly>> onCreateLoader(int id, Bundle args) {
147 return new AnomalyLoader(getContext(), mStatsHelper);
151 public void onLoadFinished(Loader<List<Anomaly>> loader, List<Anomaly> data) {
152 final AnomalyUtils anomalyUtils = AnomalyUtils.getInstance(getContext());
153 anomalyUtils.logAnomalies(mMetricsFeatureProvider, data,
154 MetricsEvent.FUELGAUGE_POWER_USAGE_SUMMARY);
156 // show high usage preference if possible
157 mAnomalySummaryPreferenceController.updateAnomalySummaryPreference(data);
159 updateAnomalySparseArray(data);
160 refreshAnomalyIcon();
164 public void onLoaderReset(Loader<List<Anomaly>> loader) {
170 LoaderManager.LoaderCallbacks<BatteryInfo> mBatteryInfoLoaderCallbacks =
171 new LoaderManager.LoaderCallbacks<BatteryInfo>() {
174 public Loader<BatteryInfo> onCreateLoader(int i, Bundle bundle) {
175 return new BatteryInfoLoader(getContext(), mStatsHelper);
179 public void onLoadFinished(Loader<BatteryInfo> loader, BatteryInfo batteryInfo) {
180 mBatteryHeaderPreferenceController.updateHeaderPreference(batteryInfo);
184 public void onLoaderReset(Loader<BatteryInfo> loader) {
189 LoaderManager.LoaderCallbacks<List<BatteryInfo>> mBatteryInfoDebugLoaderCallbacks =
190 new LoaderCallbacks<List<BatteryInfo>>() {
192 public Loader<List<BatteryInfo>> onCreateLoader(int i, Bundle bundle) {
193 return new DebugEstimatesLoader(getContext(), mStatsHelper);
197 public void onLoadFinished(Loader<List<BatteryInfo>> loader,
198 List<BatteryInfo> batteryInfos) {
199 final BatteryMeterView batteryView = (BatteryMeterView) mBatteryLayoutPref
200 .findViewById(R.id.battery_header_icon);
201 final TextView percentRemaining =
202 mBatteryLayoutPref.findViewById(R.id.battery_percent);
203 final TextView summary1 = mBatteryLayoutPref.findViewById(R.id.summary1);
204 final TextView summary2 = mBatteryLayoutPref.findViewById(R.id.summary2);
205 BatteryInfo oldInfo = batteryInfos.get(0);
206 BatteryInfo newInfo = batteryInfos.get(1);
207 percentRemaining.setText(Utils.formatPercentage(oldInfo.batteryLevel));
209 // set the text to the old estimate (copied from battery info). Note that this
210 // can sometimes say 0 time remaining because battery stats requires the phone
211 // be unplugged for a period of time before being willing ot make an estimate.
212 summary1.setText(mPowerFeatureProvider.getOldEstimateDebugString(
213 Formatter.formatShortElapsedTime(getContext(),
214 BatteryUtils.convertUsToMs(oldInfo.remainingTimeUs))));
216 // for this one we can just set the string directly
217 summary2.setText(mPowerFeatureProvider.getEnhancedEstimateDebugString(
218 Formatter.formatShortElapsedTime(getContext(),
219 BatteryUtils.convertUsToMs(newInfo.remainingTimeUs))));
221 batteryView.setBatteryLevel(oldInfo.batteryLevel);
222 batteryView.setCharging(!oldInfo.discharging);
226 public void onLoaderReset(Loader<List<BatteryInfo>> loader) {
231 public void onCreate(Bundle icicle) {
232 super.onCreate(icicle);
233 setAnimationAllowed(true);
235 initFeatureProvider();
236 mBatteryLayoutPref = (LayoutPreference) findPreference(KEY_BATTERY_HEADER);
238 mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST);
239 mScreenUsagePref = (PowerGaugePreference) findPreference(KEY_SCREEN_USAGE);
240 mLastFullChargePref = (PowerGaugePreference) findPreference(
241 KEY_TIME_SINCE_LAST_FULL_CHARGE);
242 mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary);
243 mAnomalySummaryPreferenceController = new AnomalySummaryPreferenceController(
244 (SettingsActivity) getActivity(), this, MetricsEvent.FUELGAUGE_POWER_USAGE_SUMMARY);
245 mBatteryUtils = BatteryUtils.getInstance(getContext());
246 mAnomalySparseArray = new SparseArray<>();
248 restartBatteryInfoLoader();
249 restoreSavedInstance(icicle);
253 public int getMetricsCategory() {
254 return MetricsEvent.FUELGAUGE_POWER_USAGE_SUMMARY;
258 public void onPause() {
259 BatteryEntry.stopRequestQueue();
260 mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON);
265 public void onDestroy() {
267 if (getActivity().isChangingConfigurations()) {
268 BatteryEntry.clearUidCache();
273 public void onSaveInstanceState(Bundle outState) {
274 super.onSaveInstanceState(outState);
275 outState.putBoolean(KEY_SHOW_ALL_APPS, mShowAllApps);
279 public boolean onPreferenceTreeClick(Preference preference) {
280 if (mAnomalySummaryPreferenceController.onPreferenceTreeClick(preference)) {
283 if (KEY_BATTERY_HEADER.equals(preference.getKey())) {
284 performBatteryHeaderClick();
286 } else if (!(preference instanceof PowerGaugePreference)) {
287 return super.onPreferenceTreeClick(preference);
289 PowerGaugePreference pgp = (PowerGaugePreference) preference;
290 BatteryEntry entry = pgp.getInfo();
291 AdvancedPowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(),
292 this, mStatsHelper, mStatsType, entry, pgp.getPercent(),
293 mAnomalySparseArray.get(entry.sipper.getUid()));
294 return super.onPreferenceTreeClick(preference);
298 protected String getLogTag() {
303 protected int getPreferenceScreenResId() {
304 return R.xml.power_usage_summary;
308 protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
309 final List<AbstractPreferenceController> controllers = new ArrayList<>();
310 mBatteryHeaderPreferenceController = new BatteryHeaderPreferenceController(
311 context, getActivity(), this /* host */, getLifecycle());
312 controllers.add(mBatteryHeaderPreferenceController);
313 controllers.add(new AutoBrightnessPreferenceController(context, KEY_AUTO_BRIGHTNESS));
314 controllers.add(new TimeoutPreferenceController(context, KEY_SCREEN_TIMEOUT));
315 controllers.add(new BatterySaverController(context, getLifecycle()));
316 controllers.add(new BatteryPercentagePreferenceController(context));
317 controllers.add(new AmbientDisplayPreferenceController(
319 new AmbientDisplayConfiguration(context),
320 KEY_AMBIENT_DISPLAY));
325 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
327 menu.add(Menu.NONE, MENU_STATS_TYPE, Menu.NONE, R.string.menu_stats_total)
328 .setIcon(com.android.internal.R.drawable.ic_menu_info_details)
329 .setAlphabeticShortcut('t');
332 menu.add(Menu.NONE, MENU_HIGH_POWER_APPS, Menu.NONE, R.string.high_power_apps);
334 if (mPowerFeatureProvider.isPowerAccountingToggleEnabled()) {
335 menu.add(Menu.NONE, MENU_TOGGLE_APPS, Menu.NONE,
336 mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps);
339 super.onCreateOptionsMenu(menu, inflater);
343 public int getHelpResource() {
344 return R.string.help_url_battery;
348 public boolean onOptionsItemSelected(MenuItem item) {
349 final SettingsActivity sa = (SettingsActivity) getActivity();
350 final Context context = getContext();
351 final MetricsFeatureProvider metricsFeatureProvider =
352 FeatureFactory.getFactory(context).getMetricsFeatureProvider();
354 switch (item.getItemId()) {
355 case MENU_STATS_TYPE:
356 if (mStatsType == BatteryStats.STATS_SINCE_CHARGED) {
357 mStatsType = BatteryStats.STATS_SINCE_UNPLUGGED;
359 mStatsType = BatteryStats.STATS_SINCE_CHARGED;
363 case MENU_HIGH_POWER_APPS:
364 Bundle args = new Bundle();
365 args.putString(ManageApplications.EXTRA_CLASSNAME,
366 HighPowerApplicationsActivity.class.getName());
367 sa.startPreferencePanel(this, ManageApplications.class.getName(), args,
368 R.string.high_power_apps, null, null, 0);
369 metricsFeatureProvider.action(context,
370 MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_OPTIMIZATION);
372 case MENU_TOGGLE_APPS:
373 mShowAllApps = !mShowAllApps;
374 item.setTitle(mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps);
375 metricsFeatureProvider.action(context,
376 MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE, mShowAllApps);
377 restartBatteryStatsLoader(false /* clearHeader */);
380 return super.onOptionsItemSelected(item);
385 void restoreSavedInstance(Bundle savedInstance) {
386 if (savedInstance != null) {
387 mShowAllApps = savedInstance.getBoolean(KEY_SHOW_ALL_APPS, false);
391 private void addNotAvailableMessage() {
392 final String NOT_AVAILABLE = "not_available";
393 Preference notAvailable = getCachedPreference(NOT_AVAILABLE);
394 if (notAvailable == null) {
395 notAvailable = new Preference(getPrefContext());
396 notAvailable.setKey(NOT_AVAILABLE);
397 notAvailable.setTitle(R.string.power_usage_not_available);
398 mAppListGroup.addPreference(notAvailable);
402 private void performBatteryHeaderClick() {
403 if (mPowerFeatureProvider.isAdvancedUiEnabled()) {
404 Utils.startWithFragment(getContext(), PowerUsageAdvanced.class.getName(), null,
405 null, 0, R.string.advanced_battery_title, null, getMetricsCategory());
407 mStatsHelper.storeStatsHistoryInFile(BatteryHistoryDetail.BATTERY_HISTORY_FILE);
408 Bundle args = new Bundle(2);
409 args.putString(BatteryHistoryDetail.EXTRA_STATS,
410 BatteryHistoryDetail.BATTERY_HISTORY_FILE);
411 args.putParcelable(BatteryHistoryDetail.EXTRA_BROADCAST,
412 mStatsHelper.getBatteryBroadcast());
413 Utils.startWithFragment(getContext(), BatteryHistoryDetail.class.getName(), args,
414 null, 0, R.string.history_details_title, null, getMetricsCategory());
418 private static boolean isSharedGid(int uid) {
419 return UserHandle.getAppIdFromSharedAppGid(uid) > 0;
422 private static boolean isSystemUid(int uid) {
423 final int appUid = UserHandle.getAppId(uid);
424 return appUid >= Process.SYSTEM_UID && appUid < Process.FIRST_APPLICATION_UID;
428 * We want to coalesce some UIDs. For example, dex2oat runs under a shared gid that
429 * exists for all users of the same app. We detect this case and merge the power use
430 * for dex2oat to the device OWNER's use of the app.
432 * @return A sorted list of apps using power.
434 private List<BatterySipper> getCoalescedUsageList(final List<BatterySipper> sippers) {
435 final SparseArray<BatterySipper> uidList = new SparseArray<>();
437 final ArrayList<BatterySipper> results = new ArrayList<>();
438 final int numSippers = sippers.size();
439 for (int i = 0; i < numSippers; i++) {
440 BatterySipper sipper = sippers.get(i);
441 if (sipper.getUid() > 0) {
442 int realUid = sipper.getUid();
444 // Check if this UID is a shared GID. If so, we combine it with the OWNER's
446 if (isSharedGid(sipper.getUid())) {
447 realUid = UserHandle.getUid(UserHandle.USER_SYSTEM,
448 UserHandle.getAppIdFromSharedAppGid(sipper.getUid()));
451 // Check if this UID is a system UID (mediaserver, logd, nfc, drm, etc).
452 if (isSystemUid(realUid)
453 && !"mediaserver".equals(sipper.packageWithHighestDrain)) {
454 // Use the system UID for all UIDs running in their own sandbox that
455 // are not apps. We exclude mediaserver because we already are expected to
456 // report that as a separate item.
457 realUid = Process.SYSTEM_UID;
460 if (realUid != sipper.getUid()) {
461 // Replace the BatterySipper with a new one with the real UID set.
462 BatterySipper newSipper = new BatterySipper(sipper.drainType,
463 new FakeUid(realUid), 0.0);
464 newSipper.add(sipper);
465 newSipper.packageWithHighestDrain = sipper.packageWithHighestDrain;
466 newSipper.mPackages = sipper.mPackages;
470 int index = uidList.indexOfKey(realUid);
473 uidList.put(realUid, sipper);
475 // Combine BatterySippers if we already have one with this UID.
476 final BatterySipper existingSipper = uidList.valueAt(index);
477 existingSipper.add(sipper);
478 if (existingSipper.packageWithHighestDrain == null
479 && sipper.packageWithHighestDrain != null) {
480 existingSipper.packageWithHighestDrain = sipper.packageWithHighestDrain;
483 final int existingPackageLen = existingSipper.mPackages != null ?
484 existingSipper.mPackages.length : 0;
485 final int newPackageLen = sipper.mPackages != null ?
486 sipper.mPackages.length : 0;
487 if (newPackageLen > 0) {
488 String[] newPackages = new String[existingPackageLen + newPackageLen];
489 if (existingPackageLen > 0) {
490 System.arraycopy(existingSipper.mPackages, 0, newPackages, 0,
493 System.arraycopy(sipper.mPackages, 0, newPackages, existingPackageLen,
495 existingSipper.mPackages = newPackages;
503 final int numUidSippers = uidList.size();
504 for (int i = 0; i < numUidSippers; i++) {
505 results.add(uidList.valueAt(i));
508 // The sort order must have changed, so re-sort based on total power use.
509 mBatteryUtils.sortUsageList(results);
513 protected void refreshUi() {
514 final Context context = getContext();
515 if (context == null) {
519 restartAnomalyDetectionIfPossible();
521 // reload BatteryInfo and updateUI
522 restartBatteryInfoLoader();
523 final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime(mStatsHelper,
524 System.currentTimeMillis());
525 updateScreenPreference();
526 updateLastFullChargePreference(lastFullChargeTime);
528 final CharSequence timeSequence = Utils.formatRelativeTime(context, lastFullChargeTime,
530 final int resId = mShowAllApps ? R.string.power_usage_list_summary_device
531 : R.string.power_usage_list_summary;
532 mAppListGroup.setTitle(TextUtils.expandTemplate(getText(resId), timeSequence));
534 refreshAppListGroup();
537 private void refreshAppListGroup() {
538 final PowerProfile powerProfile = mStatsHelper.getPowerProfile();
539 final BatteryStats stats = mStatsHelper.getStats();
540 final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
541 boolean addedSome = false;
542 final int dischargeAmount = USE_FAKE_DATA ? 5000
543 : stats != null ? stats.getDischargeAmount(mStatsType) : 0;
545 cacheRemoveAllPrefs(mAppListGroup);
546 mAppListGroup.setOrderingAsAdded(false);
548 if (averagePower >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP || USE_FAKE_DATA) {
549 final List<BatterySipper> usageList = getCoalescedUsageList(
550 USE_FAKE_DATA ? getFakeStats() : mStatsHelper.getUsageList());
551 double hiddenPowerMah = mShowAllApps ? 0 :
552 mBatteryUtils.removeHiddenBatterySippers(usageList);
553 mBatteryUtils.sortUsageList(usageList);
555 final int numSippers = usageList.size();
556 for (int i = 0; i < numSippers; i++) {
557 final BatterySipper sipper = usageList.get(i);
558 double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower();
560 final double percentOfTotal = mBatteryUtils.calculateBatteryPercent(
561 sipper.totalPowerMah, totalPower, hiddenPowerMah, dischargeAmount);
563 if (((int) (percentOfTotal + .5)) < 1) {
566 if (shouldHideSipper(sipper)) {
569 final UserHandle userHandle = new UserHandle(UserHandle.getUserId(sipper.getUid()));
570 final BatteryEntry entry = new BatteryEntry(getActivity(), mHandler, mUm, sipper);
571 final Drawable badgedIcon = mUm.getBadgedIconForUser(entry.getIcon(),
573 final CharSequence contentDescription = mUm.getBadgedLabelForUser(entry.getLabel(),
576 final String key = extractKeyFromSipper(sipper);
577 PowerGaugePreference pref = (PowerGaugePreference) getCachedPreference(key);
579 pref = new PowerGaugePreference(getPrefContext(), badgedIcon,
580 contentDescription, entry);
583 sipper.percent = percentOfTotal;
584 pref.setTitle(entry.getLabel());
585 pref.setOrder(i + 1);
586 pref.setPercent(percentOfTotal);
587 pref.shouldShowAnomalyIcon(false);
588 if (sipper.usageTimeMs == 0 && sipper.drainType == DrainType.APP) {
589 sipper.usageTimeMs = mBatteryUtils.getProcessTimeMs(
590 BatteryUtils.StatusType.FOREGROUND, sipper.uidObj, mStatsType);
592 setUsageSummary(pref, sipper);
594 mAppListGroup.addPreference(pref);
595 if (mAppListGroup.getPreferenceCount() - getCachedCount()
596 > (MAX_ITEMS_TO_LIST + 1)) {
602 addNotAvailableMessage();
604 removeCachedPrefs(mAppListGroup);
606 BatteryEntry.startRequestQueue();
610 boolean shouldHideSipper(BatterySipper sipper) {
611 // Don't show over-counted and unaccounted in any condition
612 return sipper.drainType == BatterySipper.DrainType.OVERCOUNTED
613 || sipper.drainType == BatterySipper.DrainType.UNACCOUNTED;
617 void refreshAnomalyIcon() {
618 for (int i = 0, size = mAnomalySparseArray.size(); i < size; i++) {
619 final String key = extractKeyFromUid(mAnomalySparseArray.keyAt(i));
620 final PowerGaugePreference pref = (PowerGaugePreference) mAppListGroup.findPreference(
623 pref.shouldShowAnomalyIcon(true);
629 void restartAnomalyDetectionIfPossible() {
630 if (getAnomalyDetectionPolicy().isAnomalyDetectionEnabled()) {
631 getLoaderManager().restartLoader(ANOMALY_LOADER, Bundle.EMPTY, mAnomalyLoaderCallbacks);
636 AnomalyDetectionPolicy getAnomalyDetectionPolicy() {
637 return new AnomalyDetectionPolicy(getContext());
641 BatterySipper findBatterySipperByType(List<BatterySipper> usageList, DrainType type) {
642 for (int i = 0, size = usageList.size(); i < size; i++) {
643 final BatterySipper sipper = usageList.get(i);
644 if (sipper.drainType == type) {
652 void updateScreenPreference() {
653 final BatterySipper sipper = findBatterySipperByType(
654 mStatsHelper.getUsageList(), DrainType.SCREEN);
655 final long usageTimeMs = sipper != null ? sipper.usageTimeMs : 0;
657 mScreenUsagePref.setSubtitle(Utils.formatElapsedTime(getContext(), usageTimeMs, false));
661 void updateLastFullChargePreference(long timeMs) {
662 final CharSequence timeSequence = Utils.formatRelativeTime(getContext(), timeMs, false);
663 mLastFullChargePref.setSubtitle(timeSequence);
667 void showBothEstimates() {
668 final Context context = getContext();
670 || !mPowerFeatureProvider.isEnhancedBatteryPredictionEnabled(context)) {
673 getLoaderManager().restartLoader(DEBUG_INFO_LOADER, Bundle.EMPTY,
674 mBatteryInfoDebugLoaderCallbacks);
678 double calculatePercentage(double powerUsage, double dischargeAmount) {
679 final double totalPower = mStatsHelper.getTotalPower();
680 return totalPower == 0 ? 0 :
681 ((powerUsage / totalPower) * dischargeAmount);
685 void setUsageSummary(Preference preference, BatterySipper sipper) {
686 // Only show summary when usage time is longer than one minute
687 final long usageTimeMs = sipper.usageTimeMs;
688 if (usageTimeMs >= DateUtils.MINUTE_IN_MILLIS) {
689 final CharSequence timeSequence = Utils.formatElapsedTime(getContext(), usageTimeMs,
691 preference.setSummary(
692 (sipper.drainType != DrainType.APP || mBatteryUtils.shouldHideSipper(sipper))
694 : TextUtils.expandTemplate(getText(R.string.battery_used_for),
700 String extractKeyFromSipper(BatterySipper sipper) {
701 if (sipper.uidObj != null) {
702 return extractKeyFromUid(sipper.getUid());
703 } else if (sipper.drainType == DrainType.USER) {
704 return sipper.drainType.toString() + sipper.userId;
705 } else if (sipper.drainType != DrainType.APP) {
706 return sipper.drainType.toString();
707 } else if (sipper.getPackages() != null) {
708 return TextUtils.concat(sipper.getPackages()).toString();
710 Log.w(TAG, "Inappropriate BatterySipper without uid and package names: " + sipper);
716 String extractKeyFromUid(int uid) {
717 return Integer.toString(uid);
721 void setBatteryLayoutPreference(LayoutPreference layoutPreference) {
722 mBatteryLayoutPref = layoutPreference;
726 void initFeatureProvider() {
727 final Context context = getContext();
728 mPowerFeatureProvider = FeatureFactory.getFactory(context)
729 .getPowerUsageFeatureProvider(context);
733 void updateAnomalySparseArray(List<Anomaly> anomalies) {
734 mAnomalySparseArray.clear();
735 for (int i = 0, size = anomalies.size(); i < size; i++) {
736 final Anomaly anomaly = anomalies.get(i);
737 if (mAnomalySparseArray.get(anomaly.uid) == null) {
738 mAnomalySparseArray.append(anomaly.uid, new ArrayList<>());
740 mAnomalySparseArray.get(anomaly.uid).add(anomaly);
745 void restartBatteryInfoLoader() {
746 getLoaderManager().restartLoader(BATTERY_INFO_LOADER, Bundle.EMPTY,
747 mBatteryInfoLoaderCallbacks);
748 if (mPowerFeatureProvider.isEstimateDebugEnabled()) {
749 // Unfortunately setting a long click listener on a view means it will no
750 // longer pass the regular click event to the parent, so we have to register
751 // a regular click listener as well.
752 View header = mBatteryLayoutPref.findViewById(R.id.summary1);
753 header.setOnLongClickListener(this);
754 header.setOnClickListener(this);
758 private static List<BatterySipper> getFakeStats() {
759 ArrayList<BatterySipper> stats = new ArrayList<>();
761 for (DrainType type : DrainType.values()) {
762 if (type == DrainType.APP) {
765 stats.add(new BatterySipper(type, null, use));
768 for (int i = 0; i < 100; i++) {
769 stats.add(new BatterySipper(DrainType.APP,
770 new FakeUid(Process.FIRST_APPLICATION_UID + i), use));
772 stats.add(new BatterySipper(DrainType.APP,
773 new FakeUid(0), use));
775 // Simulate dex2oat process.
776 BatterySipper sipper = new BatterySipper(DrainType.APP,
777 new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID)), 10.0f);
778 sipper.packageWithHighestDrain = "dex2oat";
781 sipper = new BatterySipper(DrainType.APP,
782 new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID + 1)), 10.0f);
783 sipper.packageWithHighestDrain = "dex2oat";
786 sipper = new BatterySipper(DrainType.APP,
787 new FakeUid(UserHandle.getSharedAppGid(Process.LOG_UID)), 9.0f);
793 Handler mHandler = new Handler() {
796 public void handleMessage(Message msg) {
798 case BatteryEntry.MSG_UPDATE_NAME_ICON:
799 BatteryEntry entry = (BatteryEntry) msg.obj;
800 PowerGaugePreference pgp =
801 (PowerGaugePreference) findPreference(
802 Integer.toString(entry.sipper.uidObj.getUid()));
804 final int userId = UserHandle.getUserId(entry.sipper.getUid());
805 final UserHandle userHandle = new UserHandle(userId);
806 pgp.setIcon(mUm.getBadgedIconForUser(entry.getIcon(), userHandle));
807 pgp.setTitle(entry.name);
808 if (entry.sipper.drainType == DrainType.APP) {
809 pgp.setContentDescription(entry.name);
813 case BatteryEntry.MSG_REPORT_FULLY_DRAWN:
814 Activity activity = getActivity();
815 if (activity != null) {
816 activity.reportFullyDrawn();
820 super.handleMessage(msg);
825 public void onAnomalyHandled(Anomaly anomaly) {
826 mAnomalySummaryPreferenceController.hideHighUsagePreference();
830 public boolean onLongClick(View view) {
832 view.setOnLongClickListener(null);
837 public void onClick(View view) {
838 performBatteryHeaderClick();
842 protected void restartBatteryStatsLoader() {
843 restartBatteryStatsLoader(true /* clearHeader */);
846 void restartBatteryStatsLoader(boolean clearHeader) {
847 super.restartBatteryStatsLoader();
849 mBatteryHeaderPreferenceController.quickUpdateHeaderPreference();
853 private static class SummaryProvider implements SummaryLoader.SummaryProvider {
854 private final Context mContext;
855 private final SummaryLoader mLoader;
856 private final BatteryBroadcastReceiver mBatteryBroadcastReceiver;
858 private SummaryProvider(Context context, SummaryLoader loader) {
861 mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext);
862 mBatteryBroadcastReceiver.setBatteryChangedListener(() -> {
863 BatteryInfo.getBatteryInfo(mContext, new BatteryInfo.Callback() {
865 public void onBatteryInfoLoaded(BatteryInfo info) {
866 mLoader.setSummary(SummaryProvider.this, info.chargeLabel);
868 }, true /* shortString */);
873 public void setListening(boolean listening) {
875 mBatteryBroadcastReceiver.register();
877 mBatteryBroadcastReceiver.unRegister();
882 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
883 new BaseSearchIndexProvider() {
885 public List<SearchIndexableResource> getXmlResourcesToIndex(
886 Context context, boolean enabled) {
887 final SearchIndexableResource sir = new SearchIndexableResource(context);
888 sir.xmlResId = R.xml.power_usage_summary;
889 return Arrays.asList(sir);
893 public List<String> getNonIndexableKeys(Context context) {
894 List<String> niks = super.getNonIndexableKeys(context);
895 niks.add(KEY_HIGH_USAGE);
896 niks.add(KEY_BATTERY_SAVER_SUMMARY);
897 // Duplicates in display
898 niks.add(KEY_AUTO_BRIGHTNESS);
899 niks.add(KEY_SCREEN_TIMEOUT);
900 niks.add(KEY_AMBIENT_DISPLAY);
905 public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
906 = new SummaryLoader.SummaryProviderFactory() {
908 public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
909 SummaryLoader summaryLoader) {
910 return new SummaryProvider(activity, summaryLoader);