Home | History | Annotate | Download | only in fuelgauge
      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
    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
    251     public int getMetricsCategory() {
    252         return MetricsEvent.FUELGAUGE_POWER_USAGE_SUMMARY;
    253     }
    254 
    255     @Override
    256     public void onPause() {
    257         BatteryEntry.stopRequestQueue();
    258         mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON);
    259         super.onPause();
    260     }
    261 
    262     @Override
    263     public void onDestroy() {
    264         super.onDestroy();
    265         if (getActivity().isChangingConfigurations()) {
    266             BatteryEntry.clearUidCache();
    267         }
    268     }
    269 
    270     @Override
    271     public void onSaveInstanceState(Bundle outState) {
    272         super.onSaveInstanceState(outState);
    273         outState.putBoolean(KEY_SHOW_ALL_APPS, mShowAllApps);
    274     }
    275 
    276     @Override
    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
    296     protected String getLogTag() {
    297         return TAG;
    298     }
    299 
    300     @Override
    301     protected int getPreferenceScreenResId() {
    302         return R.xml.power_usage_summary;
    303     }
    304 
    305     @Override
    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
    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
    345     protected int getHelpResource() {
    346         return R.string.help_url_battery;
    347     }
    348 
    349     @Override
    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
    393     void restoreSavedInstance(Bundle savedInstance) {
    394         if (savedInstance != null) {
    395             mShowAllApps = savedInstance.getBoolean(KEY_SHOW_ALL_APPS, false);
    396         }
    397     }
    398 
    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 
    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 
    426     private static boolean isSharedGid(int uid) {
    427         return UserHandle.getAppIdFromSharedAppGid(uid) > 0;
    428     }
    429 
    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      */
    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 
    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 
    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
    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
    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
    651     void restartAnomalyDetectionIfPossible() {
    652         if (getAnomalyDetectionPolicy().isAnomalyDetectionEnabled()) {
    653             getLoaderManager().restartLoader(ANOMALY_LOADER, Bundle.EMPTY, mAnomalyLoaderCallbacks);
    654         }
    655     }
    656 
    657     @VisibleForTesting
    658     AnomalyDetectionPolicy getAnomalyDetectionPolicy() {
    659         return new AnomalyDetectionPolicy(getContext());
    660     }
    661 
    662     @VisibleForTesting
    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
    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
    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
    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
    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
    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
    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
    738     String extractKeyFromUid(int uid) {
    739         return Integer.toString(uid);
    740     }
    741 
    742     @VisibleForTesting
    743     void setBatteryLayoutPreference(LayoutPreference layoutPreference) {
    744         mBatteryLayoutPref = layoutPreference;
    745     }
    746 
    747     @VisibleForTesting
    748     void initFeatureProvider() {
    749         final Context context = getContext();
    750         mPowerFeatureProvider = FeatureFactory.getFactory(context)
    751                 .getPowerUsageFeatureProvider(context);
    752     }
    753 
    754     @VisibleForTesting
    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
    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 
    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
    847     public void onAnomalyHandled(Anomaly anomaly) {
    848         mAnomalySummaryPreferenceController.hideHighUsagePreference();
    849     }
    850 
    851     @Override
    852     public boolean onLongClick(View view) {
    853         showBothEstimates();
    854         view.setOnLongClickListener(null);
    855         return true;
    856     }
    857 
    858     @Override
    859     public void onClick(View view) {
    860         performBatteryHeaderClick();
    861     }
    862 
    863     @Override
    864     protected void restartBatteryStatsLoader() {
    865         restartBatteryStatsLoader(true /* clearHeader */);
    866     }
    867 
    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 
    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
    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