OSDN Git Service

Merge "Remove dynamic summaries for "On the lockscreen"" into oc-dev
[android-x86/packages-apps-Settings.git] / src / com / android / settings / fuelgauge / PowerUsageSummary.java
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.animation.Animator;
20 import android.animation.ValueAnimator;
21 import android.app.Activity;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.graphics.drawable.Drawable;
26 import android.os.BatteryStats;
27 import android.os.Build;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.os.Process;
32 import android.os.SystemClock;
33 import android.os.UserHandle;
34 import android.provider.SearchIndexableResource;
35 import android.support.annotation.VisibleForTesting;
36 import android.support.v7.preference.Preference;
37 import android.support.v7.preference.PreferenceGroup;
38 import android.text.TextUtils;
39 import android.text.format.DateUtils;
40 import android.util.Log;
41 import android.util.SparseArray;
42 import android.util.TypedValue;
43 import android.view.Menu;
44 import android.view.MenuInflater;
45 import android.view.MenuItem;
46 import android.view.animation.AnimationUtils;
47 import android.widget.TextView;
48
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;
59 import com.android.settings.core.PreferenceController;
60 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
61 import com.android.settings.dashboard.SummaryLoader;
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.overlay.FeatureFactory;
66 import com.android.settings.search.BaseSearchIndexProvider;
67 import com.android.settings.widget.FooterPreferenceMixin;
68 import com.android.settingslib.BatteryInfo;
69
70 import java.util.ArrayList;
71 import java.util.Arrays;
72 import java.util.Collections;
73 import java.util.Comparator;
74 import java.util.List;
75
76 /**
77  * Displays a list of apps and subsystems that consume power, ordered by how much power was
78  * consumed since the last time it was unplugged.
79  */
80 public class PowerUsageSummary extends PowerUsageBase {
81
82     static final String TAG = "PowerUsageSummary";
83
84     private static final boolean DEBUG = false;
85     private static final boolean USE_FAKE_DATA = false;
86     private static final String KEY_APP_LIST = "app_list";
87     private static final String KEY_BATTERY_HEADER = "battery_header";
88     private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 10;
89     private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10;
90     private static final int BATTERY_ANIMATION_DURATION_MS_PER_LEVEL = 30;
91
92     @VisibleForTesting
93     static final String ARG_BATTERY_LEVEL = "key_battery_level";
94
95     private static final String KEY_SCREEN_USAGE = "screen_usage";
96     private static final String KEY_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge";
97
98     private static final String KEY_AUTO_BRIGHTNESS = "auto_brightness_battery";
99     private static final String KEY_SCREEN_TIMEOUT = "screen_timeout_battery";
100     private static final String KEY_BATTERY_SAVER_SUMMARY = "battery_saver_summary";
101
102     private static final int MENU_STATS_TYPE = Menu.FIRST;
103     @VisibleForTesting
104     static final int MENU_HIGH_POWER_APPS = Menu.FIRST + 3;
105     @VisibleForTesting
106     static final int MENU_ADDITIONAL_BATTERY_INFO = Menu.FIRST + 4;
107     @VisibleForTesting
108     static final int MENU_TOGGLE_APPS = Menu.FIRST + 5;
109     private static final int MENU_HELP = Menu.FIRST + 6;
110
111     private final FooterPreferenceMixin mFooterPreferenceMixin =
112             new FooterPreferenceMixin(this, getLifecycle());
113
114     @VisibleForTesting
115     int mBatteryLevel;
116     @VisibleForTesting
117     boolean mShowAllApps = false;
118     @VisibleForTesting
119     PowerGaugePreference mScreenUsagePref;
120     @VisibleForTesting
121     PowerGaugePreference mLastFullChargePref;
122     @VisibleForTesting
123     PowerUsageFeatureProvider mPowerFeatureProvider;
124     @VisibleForTesting
125     BatteryUtils mBatteryUtils;
126
127     private LayoutPreference mBatteryLayoutPref;
128     private PreferenceGroup mAppListGroup;
129     private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
130
131     @Override
132     public void onCreate(Bundle icicle) {
133         super.onCreate(icicle);
134         setAnimationAllowed(true);
135
136         mBatteryLevel = getContext().getResources().getInteger(
137                 com.android.internal.R.integer.config_criticalBatteryWarningLevel) + 1;
138         mBatteryLayoutPref = (LayoutPreference) findPreference(KEY_BATTERY_HEADER);
139         mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST);
140         mScreenUsagePref = (PowerGaugePreference) findPreference(KEY_SCREEN_USAGE);
141         mLastFullChargePref = (PowerGaugePreference) findPreference(
142                 KEY_TIME_SINCE_LAST_FULL_CHARGE);
143         mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary);
144
145         mBatteryUtils = BatteryUtils.getInstance(getContext());
146
147         initFeatureProvider();
148     }
149
150     @Override
151     public void onActivityCreated(Bundle savedInstanceState) {
152         super.onActivityCreated(savedInstanceState);
153         if (savedInstanceState != null) {
154             mBatteryLevel = savedInstanceState.getInt(ARG_BATTERY_LEVEL);
155         }
156     }
157
158     @Override
159     public int getMetricsCategory() {
160         return MetricsEvent.FUELGAUGE_POWER_USAGE_SUMMARY;
161     }
162
163     @Override
164     public void onResume() {
165         super.onResume();
166
167         initHeaderPreference();
168     }
169
170     @Override
171     public void onPause() {
172         BatteryEntry.stopRequestQueue();
173         mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON);
174         super.onPause();
175     }
176
177     @Override
178     public void onDestroy() {
179         super.onDestroy();
180         if (getActivity().isChangingConfigurations()) {
181             BatteryEntry.clearUidCache();
182         }
183     }
184
185     @Override
186     public void onSaveInstanceState(Bundle outState) {
187         super.onSaveInstanceState(outState);
188         outState.putInt(ARG_BATTERY_LEVEL, mBatteryLevel);
189     }
190
191     @Override
192     public boolean onPreferenceTreeClick(Preference preference) {
193         if (KEY_BATTERY_HEADER.equals(preference.getKey())) {
194             performBatteryHeaderClick();
195             return true;
196         } else if (!(preference instanceof PowerGaugePreference)) {
197             return super.onPreferenceTreeClick(preference);
198         }
199         PowerGaugePreference pgp = (PowerGaugePreference) preference;
200         BatteryEntry entry = pgp.getInfo();
201         AdvancedPowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(),
202                 this, mStatsHelper, mStatsType, entry, pgp.getPercent());
203         return super.onPreferenceTreeClick(preference);
204     }
205
206     @Override
207     protected String getLogTag() {
208         return TAG;
209     }
210
211     @Override
212     protected int getPreferenceScreenResId() {
213         return R.xml.power_usage_summary;
214     }
215
216     @Override
217     protected List<PreferenceController> getPreferenceControllers(Context context) {
218         final List<PreferenceController> controllers = new ArrayList<>();
219         controllers.add(new AutoBrightnessPreferenceController(context, KEY_AUTO_BRIGHTNESS));
220         controllers.add(new TimeoutPreferenceController(context, KEY_SCREEN_TIMEOUT));
221         controllers.add(new BatterySaverController(context, getLifecycle()));
222         controllers.add(new BatteryPercentagePreferenceController(context));
223         return controllers;
224     }
225
226     @Override
227     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
228         if (DEBUG) {
229             menu.add(Menu.NONE, MENU_STATS_TYPE, Menu.NONE, R.string.menu_stats_total)
230                     .setIcon(com.android.internal.R.drawable.ic_menu_info_details)
231                     .setAlphabeticShortcut('t');
232         }
233
234         menu.add(Menu.NONE, MENU_HIGH_POWER_APPS, Menu.NONE, R.string.high_power_apps);
235
236         if (mPowerFeatureProvider.isAdditionalBatteryInfoEnabled()) {
237             menu.add(Menu.NONE, MENU_ADDITIONAL_BATTERY_INFO,
238                     Menu.NONE, R.string.additional_battery_info);
239         }
240         if (mPowerFeatureProvider.isPowerAccountingToggleEnabled()) {
241             menu.add(Menu.NONE, MENU_TOGGLE_APPS, Menu.NONE,
242                     mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps);
243         }
244
245         super.onCreateOptionsMenu(menu, inflater);
246     }
247
248     @Override
249     protected int getHelpResource() {
250         return R.string.help_url_battery;
251     }
252
253     @Override
254     public boolean onOptionsItemSelected(MenuItem item) {
255         final SettingsActivity sa = (SettingsActivity) getActivity();
256         final Context context = getContext();
257         final MetricsFeatureProvider metricsFeatureProvider =
258                 FeatureFactory.getFactory(context).getMetricsFeatureProvider();
259
260         switch (item.getItemId()) {
261             case MENU_STATS_TYPE:
262                 if (mStatsType == BatteryStats.STATS_SINCE_CHARGED) {
263                     mStatsType = BatteryStats.STATS_SINCE_UNPLUGGED;
264                 } else {
265                     mStatsType = BatteryStats.STATS_SINCE_CHARGED;
266                 }
267                 refreshUi();
268                 return true;
269             case MENU_HIGH_POWER_APPS:
270                 Bundle args = new Bundle();
271                 args.putString(ManageApplications.EXTRA_CLASSNAME,
272                         HighPowerApplicationsActivity.class.getName());
273                 sa.startPreferencePanel(this, ManageApplications.class.getName(), args,
274                         R.string.high_power_apps, null, null, 0);
275                 metricsFeatureProvider.action(context,
276                         MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_OPTIMIZATION);
277                 return true;
278             case MENU_ADDITIONAL_BATTERY_INFO:
279                 startActivity(FeatureFactory.getFactory(getContext())
280                         .getPowerUsageFeatureProvider(getContext())
281                         .getAdditionalBatteryInfoIntent());
282                 metricsFeatureProvider.action(context,
283                         MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_USAGE_ALERTS);
284                 return true;
285             case MENU_TOGGLE_APPS:
286                 mShowAllApps = !mShowAllApps;
287                 item.setTitle(mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps);
288                 metricsFeatureProvider.action(context,
289                         MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE, mShowAllApps);
290                 refreshUi();
291                 return true;
292             default:
293                 return super.onOptionsItemSelected(item);
294         }
295     }
296
297     private void addNotAvailableMessage() {
298         final String NOT_AVAILABLE = "not_available";
299         Preference notAvailable = getCachedPreference(NOT_AVAILABLE);
300         if (notAvailable == null) {
301             notAvailable = new Preference(getPrefContext());
302             notAvailable.setKey(NOT_AVAILABLE);
303             notAvailable.setTitle(R.string.power_usage_not_available);
304             mAppListGroup.addPreference(notAvailable);
305         }
306     }
307
308     private void performBatteryHeaderClick() {
309         final Context context = getContext();
310         final PowerUsageFeatureProvider featureProvider = FeatureFactory.getFactory(context)
311                 .getPowerUsageFeatureProvider(context);
312
313         if (featureProvider.isAdvancedUiEnabled()) {
314             Utils.startWithFragment(getContext(), PowerUsageAdvanced.class.getName(), null,
315                     null, 0, R.string.advanced_battery_title, null, getMetricsCategory());
316         } else {
317             mStatsHelper.storeStatsHistoryInFile(BatteryHistoryDetail.BATTERY_HISTORY_FILE);
318             Bundle args = new Bundle(2);
319             args.putString(BatteryHistoryDetail.EXTRA_STATS,
320                     BatteryHistoryDetail.BATTERY_HISTORY_FILE);
321             args.putParcelable(BatteryHistoryDetail.EXTRA_BROADCAST,
322                     mStatsHelper.getBatteryBroadcast());
323             Utils.startWithFragment(getContext(), BatteryHistoryDetail.class.getName(), args,
324                     null, 0, R.string.history_details_title, null, getMetricsCategory());
325         }
326     }
327
328     private static boolean isSharedGid(int uid) {
329         return UserHandle.getAppIdFromSharedAppGid(uid) > 0;
330     }
331
332     private static boolean isSystemUid(int uid) {
333         return uid >= Process.SYSTEM_UID && uid < Process.FIRST_APPLICATION_UID;
334     }
335
336     /**
337      * We want to coalesce some UIDs. For example, dex2oat runs under a shared gid that
338      * exists for all users of the same app. We detect this case and merge the power use
339      * for dex2oat to the device OWNER's use of the app.
340      *
341      * @return A sorted list of apps using power.
342      */
343     private static List<BatterySipper> getCoalescedUsageList(final List<BatterySipper> sippers) {
344         final SparseArray<BatterySipper> uidList = new SparseArray<>();
345
346         final ArrayList<BatterySipper> results = new ArrayList<>();
347         final int numSippers = sippers.size();
348         for (int i = 0; i < numSippers; i++) {
349             BatterySipper sipper = sippers.get(i);
350             if (sipper.getUid() > 0) {
351                 int realUid = sipper.getUid();
352
353                 // Check if this UID is a shared GID. If so, we combine it with the OWNER's
354                 // actual app UID.
355                 if (isSharedGid(sipper.getUid())) {
356                     realUid = UserHandle.getUid(UserHandle.USER_SYSTEM,
357                             UserHandle.getAppIdFromSharedAppGid(sipper.getUid()));
358                 }
359
360                 // Check if this UID is a system UID (mediaserver, logd, nfc, drm, etc).
361                 if (isSystemUid(realUid)
362                         && !"mediaserver".equals(sipper.packageWithHighestDrain)) {
363                     // Use the system UID for all UIDs running in their own sandbox that
364                     // are not apps. We exclude mediaserver because we already are expected to
365                     // report that as a separate item.
366                     realUid = Process.SYSTEM_UID;
367                 }
368
369                 if (realUid != sipper.getUid()) {
370                     // Replace the BatterySipper with a new one with the real UID set.
371                     BatterySipper newSipper = new BatterySipper(sipper.drainType,
372                             new FakeUid(realUid), 0.0);
373                     newSipper.add(sipper);
374                     newSipper.packageWithHighestDrain = sipper.packageWithHighestDrain;
375                     newSipper.mPackages = sipper.mPackages;
376                     sipper = newSipper;
377                 }
378
379                 int index = uidList.indexOfKey(realUid);
380                 if (index < 0) {
381                     // New entry.
382                     uidList.put(realUid, sipper);
383                 } else {
384                     // Combine BatterySippers if we already have one with this UID.
385                     final BatterySipper existingSipper = uidList.valueAt(index);
386                     existingSipper.add(sipper);
387                     if (existingSipper.packageWithHighestDrain == null
388                             && sipper.packageWithHighestDrain != null) {
389                         existingSipper.packageWithHighestDrain = sipper.packageWithHighestDrain;
390                     }
391
392                     final int existingPackageLen = existingSipper.mPackages != null ?
393                             existingSipper.mPackages.length : 0;
394                     final int newPackageLen = sipper.mPackages != null ?
395                             sipper.mPackages.length : 0;
396                     if (newPackageLen > 0) {
397                         String[] newPackages = new String[existingPackageLen + newPackageLen];
398                         if (existingPackageLen > 0) {
399                             System.arraycopy(existingSipper.mPackages, 0, newPackages, 0,
400                                     existingPackageLen);
401                         }
402                         System.arraycopy(sipper.mPackages, 0, newPackages, existingPackageLen,
403                                 newPackageLen);
404                         existingSipper.mPackages = newPackages;
405                     }
406                 }
407             } else {
408                 results.add(sipper);
409             }
410         }
411
412         final int numUidSippers = uidList.size();
413         for (int i = 0; i < numUidSippers; i++) {
414             results.add(uidList.valueAt(i));
415         }
416
417         // The sort order must have changed, so re-sort based on total power use.
418         Collections.sort(results, new Comparator<BatterySipper>() {
419             @Override
420             public int compare(BatterySipper a, BatterySipper b) {
421                 return Double.compare(b.totalPowerMah, a.totalPowerMah);
422             }
423         });
424         return results;
425     }
426
427     protected void refreshUi() {
428         final Context context = getContext();
429         if (context == null) {
430             return;
431         }
432
433         cacheRemoveAllPrefs(mAppListGroup);
434         mAppListGroup.setOrderingAsAdded(false);
435         boolean addedSome = false;
436
437         final PowerProfile powerProfile = mStatsHelper.getPowerProfile();
438         final BatteryStats stats = mStatsHelper.getStats();
439         final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
440
441         final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
442         Intent batteryBroadcast = context.registerReceiver(null,
443                 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
444         BatteryInfo batteryInfo = BatteryInfo.getBatteryInfo(context, batteryBroadcast,
445                 mStatsHelper.getStats(), elapsedRealtimeUs, false);
446         updateHeaderPreference(batteryInfo);
447
448         final TypedValue value = new TypedValue();
449         context.getTheme().resolveAttribute(android.R.attr.colorControlNormal, value, true);
450         final int colorControl = context.getColor(value.resourceId);
451         final int dischargeAmount = USE_FAKE_DATA ? 5000
452                 : stats != null ? stats.getDischargeAmount(mStatsType) : 0;
453
454         final long runningTime = calculateRunningTimeBasedOnStatsType();
455         updateScreenPreference();
456         updateLastFullChargePreference(runningTime);
457
458         final CharSequence timeSequence = Utils.formatElapsedTime(context, runningTime, false);
459         mAppListGroup.setTitle(
460                 TextUtils.expandTemplate(getText(R.string.power_usage_list_summary), timeSequence));
461
462         if (averagePower >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP || USE_FAKE_DATA) {
463             final List<BatterySipper> usageList = getCoalescedUsageList(
464                     USE_FAKE_DATA ? getFakeStats() : mStatsHelper.getUsageList());
465
466             double hiddenPowerMah = mShowAllApps ? 0 :
467                     mBatteryUtils.removeHiddenBatterySippers(usageList);
468
469             final int numSippers = usageList.size();
470             for (int i = 0; i < numSippers; i++) {
471                 final BatterySipper sipper = usageList.get(i);
472                 double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower();
473
474                 final double percentOfTotal = mBatteryUtils.calculateBatteryPercent(
475                         sipper.totalPowerMah, totalPower, hiddenPowerMah, dischargeAmount);
476
477                 if (((int) (percentOfTotal + .5)) < 1) {
478                     continue;
479                 }
480                 if (sipper.drainType == BatterySipper.DrainType.OVERCOUNTED) {
481                     // Don't show over-counted unless it is at least 2/3 the size of
482                     // the largest real entry, and its percent of total is more significant
483                     if (sipper.totalPowerMah < ((mStatsHelper.getMaxRealPower() * 2) / 3)) {
484                         continue;
485                     }
486                     if (percentOfTotal < 10) {
487                         continue;
488                     }
489                     if ("user".equals(Build.TYPE)) {
490                         continue;
491                     }
492                 }
493                 if (sipper.drainType == BatterySipper.DrainType.UNACCOUNTED) {
494                     // Don't show over-counted unless it is at least 1/2 the size of
495                     // the largest real entry, and its percent of total is more significant
496                     if (sipper.totalPowerMah < (mStatsHelper.getMaxRealPower() / 2)) {
497                         continue;
498                     }
499                     if (percentOfTotal < 5) {
500                         continue;
501                     }
502                     if ("user".equals(Build.TYPE)) {
503                         continue;
504                     }
505                 }
506                 final UserHandle userHandle = new UserHandle(UserHandle.getUserId(sipper.getUid()));
507                 final BatteryEntry entry = new BatteryEntry(getActivity(), mHandler, mUm, sipper);
508                 final Drawable badgedIcon = mUm.getBadgedIconForUser(entry.getIcon(),
509                         userHandle);
510                 final CharSequence contentDescription = mUm.getBadgedLabelForUser(entry.getLabel(),
511                         userHandle);
512
513                 final String key = extractKeyFromSipper(sipper);
514                 PowerGaugePreference pref = (PowerGaugePreference) getCachedPreference(key);
515                 if (pref == null) {
516                     pref = new PowerGaugePreference(getPrefContext(), badgedIcon,
517                             contentDescription, entry);
518                     pref.setKey(key);
519                 }
520
521                 final double percentOfMax = (sipper.totalPowerMah * 100)
522                         / mStatsHelper.getMaxPower();
523                 sipper.percent = percentOfTotal;
524                 pref.setTitle(entry.getLabel());
525                 pref.setOrder(i + 1);
526                 pref.setPercent(percentOfTotal);
527                 if (sipper.usageTimeMs == 0 && sipper.drainType == DrainType.APP) {
528                     sipper.usageTimeMs = mBatteryUtils.getProcessTimeMs(
529                             BatteryUtils.StatusType.FOREGROUND, sipper.uidObj, mStatsType);
530                 }
531                 setUsageSummary(pref, sipper.usageTimeMs);
532                 if ((sipper.drainType != DrainType.APP
533                         || sipper.uidObj.getUid() == Process.ROOT_UID)
534                         && sipper.drainType != DrainType.USER) {
535                     pref.setTint(colorControl);
536                 }
537                 addedSome = true;
538                 mAppListGroup.addPreference(pref);
539                 if (mAppListGroup.getPreferenceCount() - getCachedCount()
540                         > (MAX_ITEMS_TO_LIST + 1)) {
541                     break;
542                 }
543             }
544         }
545         if (!addedSome) {
546             addNotAvailableMessage();
547         }
548         removeCachedPrefs(mAppListGroup);
549
550         BatteryEntry.startRequestQueue();
551     }
552
553     @VisibleForTesting
554     BatterySipper findBatterySipperByType(List<BatterySipper> usageList, DrainType type) {
555         for (int i = 0, size = usageList.size(); i < size; i++) {
556             final BatterySipper sipper = usageList.get(i);
557             if (sipper.drainType == type) {
558                 return sipper;
559             }
560         }
561         return null;
562     }
563
564     @VisibleForTesting
565     void updateScreenPreference() {
566         final BatterySipper sipper = findBatterySipperByType(
567                 mStatsHelper.getUsageList(), DrainType.SCREEN);
568         final long usageTimeMs = sipper != null ? sipper.usageTimeMs : 0;
569
570         mScreenUsagePref.setSubtitle(Utils.formatElapsedTime(getContext(), usageTimeMs, false));
571     }
572
573     @VisibleForTesting
574     void updateLastFullChargePreference(long timeMs) {
575         final CharSequence timeSequence = Utils.formatElapsedTime(getContext(), timeMs, false);
576         mLastFullChargePref.setSubtitle(
577                 TextUtils.expandTemplate(getText(R.string.power_last_full_charge_summary),
578                         timeSequence));
579     }
580
581     @VisibleForTesting
582     long calculateRunningTimeBasedOnStatsType() {
583         final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
584         // Return the battery time (millisecond) on status mStatsType
585         return mStatsHelper.getStats().computeBatteryRealtime(elapsedRealtimeUs,
586                 mStatsType /* STATS_SINCE_CHARGED */) / 1000;
587     }
588
589     @VisibleForTesting
590     void updateHeaderPreference(BatteryInfo info) {
591         final Context context = getContext();
592         if (context == null) {
593             return;
594         }
595         final BatteryMeterView batteryView = (BatteryMeterView) mBatteryLayoutPref
596                 .findViewById(R.id.battery_header_icon);
597         final TextView timeText = (TextView) mBatteryLayoutPref.findViewById(R.id.battery_percent);
598         final TextView summary1 = (TextView) mBatteryLayoutPref.findViewById(R.id.summary1);
599         if (info.remainingLabel == null ) {
600             summary1.setText(info.statusLabel);
601         } else {
602             summary1.setText(info.remainingLabel);
603         }
604         batteryView.setCharging(!info.discharging);
605         startBatteryHeaderAnimationIfNecessary(batteryView, timeText, mBatteryLevel,
606                 info.batteryLevel);
607     }
608
609     @VisibleForTesting
610     void initHeaderPreference() {
611         final BatteryMeterView batteryView = (BatteryMeterView) mBatteryLayoutPref
612                 .findViewById(R.id.battery_header_icon);
613         final TextView timeText = (TextView) mBatteryLayoutPref.findViewById(R.id.battery_percent);
614
615         batteryView.setBatteryLevel(mBatteryLevel);
616         timeText.setText(Utils.formatPercentage(mBatteryLevel));
617     }
618
619     @VisibleForTesting
620     void startBatteryHeaderAnimationIfNecessary(BatteryMeterView batteryView, TextView timeTextView,
621             int prevLevel, int currentLevel) {
622         mBatteryLevel = currentLevel;
623         final int diff = Math.abs(prevLevel - currentLevel);
624         if (diff != 0) {
625             final ValueAnimator animator = ValueAnimator.ofInt(prevLevel, currentLevel);
626             animator.setDuration(BATTERY_ANIMATION_DURATION_MS_PER_LEVEL * diff);
627             animator.setInterpolator(AnimationUtils.loadInterpolator(getContext(),
628                     android.R.interpolator.fast_out_slow_in));
629             animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
630                 @Override
631                 public void onAnimationUpdate(ValueAnimator animation) {
632                     final Integer level = (Integer) animation.getAnimatedValue();
633                     batteryView.setBatteryLevel(level);
634                     timeTextView.setText(Utils.formatPercentage(level));
635                 }
636             });
637             animator.start();
638         }
639     }
640
641     @VisibleForTesting
642     double calculatePercentage(double powerUsage, double dischargeAmount) {
643         final double totalPower = mStatsHelper.getTotalPower();
644         return totalPower == 0 ? 0 :
645                 ((powerUsage / totalPower) * dischargeAmount);
646     }
647
648     @VisibleForTesting
649     void setUsageSummary(Preference preference, long usageTimeMs) {
650         // Only show summary when usage time is longer than one minute
651         if (usageTimeMs >= DateUtils.MINUTE_IN_MILLIS) {
652             final CharSequence timeSequence = Utils.formatElapsedTime(getContext(), usageTimeMs,
653                     false);
654             preference.setSummary(
655                     TextUtils.expandTemplate(getText(R.string.battery_used_for), timeSequence));
656         }
657     }
658
659     @VisibleForTesting
660     String extractKeyFromSipper(BatterySipper sipper) {
661         if (sipper.uidObj != null) {
662             return Integer.toString(sipper.getUid());
663         } else if (sipper.drainType != DrainType.APP) {
664             return sipper.drainType.toString();
665         } else if (sipper.getPackages() != null) {
666             return TextUtils.concat(sipper.getPackages()).toString();
667         } else {
668             Log.w(TAG, "Inappropriate BatterySipper without uid and package names: " + sipper);
669             return "-1";
670         }
671     }
672
673     @VisibleForTesting
674     void setBatteryLayoutPreference(LayoutPreference layoutPreference) {
675         mBatteryLayoutPref = layoutPreference;
676     }
677
678     @VisibleForTesting
679     void initFeatureProvider() {
680         final Context context = getContext();
681         mPowerFeatureProvider = FeatureFactory.getFactory(context)
682                 .getPowerUsageFeatureProvider(context);
683     }
684
685     private static List<BatterySipper> getFakeStats() {
686         ArrayList<BatterySipper> stats = new ArrayList<>();
687         float use = 5;
688         for (DrainType type : DrainType.values()) {
689             if (type == DrainType.APP) {
690                 continue;
691             }
692             stats.add(new BatterySipper(type, null, use));
693             use += 5;
694         }
695         for (int i = 0; i < 100; i++) {
696             stats.add(new BatterySipper(DrainType.APP,
697                     new FakeUid(Process.FIRST_APPLICATION_UID + i), use));
698         }
699         stats.add(new BatterySipper(DrainType.APP,
700                 new FakeUid(0), use));
701
702         // Simulate dex2oat process.
703         BatterySipper sipper = new BatterySipper(DrainType.APP,
704                 new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID)), 10.0f);
705         sipper.packageWithHighestDrain = "dex2oat";
706         stats.add(sipper);
707
708         sipper = new BatterySipper(DrainType.APP,
709                 new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID + 1)), 10.0f);
710         sipper.packageWithHighestDrain = "dex2oat";
711         stats.add(sipper);
712
713         sipper = new BatterySipper(DrainType.APP,
714                 new FakeUid(UserHandle.getSharedAppGid(Process.LOG_UID)), 9.0f);
715         stats.add(sipper);
716
717         return stats;
718     }
719
720     Handler mHandler = new Handler() {
721
722         @Override
723         public void handleMessage(Message msg) {
724             switch (msg.what) {
725                 case BatteryEntry.MSG_UPDATE_NAME_ICON:
726                     BatteryEntry entry = (BatteryEntry) msg.obj;
727                     PowerGaugePreference pgp =
728                             (PowerGaugePreference) findPreference(
729                                     Integer.toString(entry.sipper.uidObj.getUid()));
730                     if (pgp != null) {
731                         final int userId = UserHandle.getUserId(entry.sipper.getUid());
732                         final UserHandle userHandle = new UserHandle(userId);
733                         pgp.setIcon(mUm.getBadgedIconForUser(entry.getIcon(), userHandle));
734                         pgp.setTitle(entry.name);
735                         if (entry.sipper.drainType == DrainType.APP) {
736                             pgp.setContentDescription(entry.name);
737                         }
738                     }
739                     break;
740                 case BatteryEntry.MSG_REPORT_FULLY_DRAWN:
741                     Activity activity = getActivity();
742                     if (activity != null) {
743                         activity.reportFullyDrawn();
744                     }
745                     break;
746             }
747             super.handleMessage(msg);
748         }
749     };
750
751     private static class SummaryProvider implements SummaryLoader.SummaryProvider {
752         private final Context mContext;
753         private final SummaryLoader mLoader;
754         private final BatteryBroadcastReceiver mBatteryBroadcastReceiver;
755
756         private SummaryProvider(Context context, SummaryLoader loader) {
757             mContext = context;
758             mLoader = loader;
759             mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext);
760             mBatteryBroadcastReceiver.setBatteryChangedListener(() -> {
761                 BatteryInfo.getBatteryInfo(mContext, new BatteryInfo.Callback() {
762                     @Override
763                     public void onBatteryInfoLoaded(BatteryInfo info) {
764                         mLoader.setSummary(SummaryProvider.this, info.chargeLabelString);
765                     }
766                 });
767             });
768         }
769
770         @Override
771         public void setListening(boolean listening) {
772             if (listening) {
773                 mBatteryBroadcastReceiver.register();
774             } else {
775                 mBatteryBroadcastReceiver.unRegister();
776             }
777         }
778     }
779
780     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
781             new BaseSearchIndexProvider() {
782                 @Override
783                 public List<SearchIndexableResource> getXmlResourcesToIndex(
784                         Context context, boolean enabled) {
785                     final SearchIndexableResource sir = new SearchIndexableResource(context);
786                     sir.xmlResId = R.xml.power_usage_summary;
787                     return Arrays.asList(sir);
788                 }
789
790                 @Override
791                 public List<String> getNonIndexableKeys(Context context) {
792                     List<String> niks = new ArrayList<>();
793                     // Duplicates in display
794                     niks.add(KEY_AUTO_BRIGHTNESS);
795                     niks.add(KEY_SCREEN_TIMEOUT);
796                     niks.add(KEY_BATTERY_SAVER_SUMMARY);
797                     return niks;
798                 }
799             };
800
801     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
802             = new SummaryLoader.SummaryProviderFactory() {
803         @Override
804         public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
805                 SummaryLoader summaryLoader) {
806             return new SummaryProvider(activity, summaryLoader);
807         }
808     };
809 }