• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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