• 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 static com.android.settings.fuelgauge.BatteryBroadcastReceiver.BatteryUpdateType;
20 
21 import android.app.Activity;
22 import android.app.LoaderManager;
23 import android.app.LoaderManager.LoaderCallbacks;
24 import android.content.Context;
25 import android.content.Loader;
26 import android.os.BatteryStats;
27 import android.os.Bundle;
28 import android.provider.SearchIndexableResource;
29 import android.support.annotation.VisibleForTesting;
30 import android.text.BidiFormatter;
31 import android.text.format.Formatter;
32 import android.util.SparseArray;
33 import android.view.Menu;
34 import android.view.MenuInflater;
35 import android.view.MenuItem;
36 import android.view.View;
37 import android.view.View.OnLongClickListener;
38 import android.widget.TextView;
39 
40 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
41 import com.android.settings.R;
42 import com.android.settings.SettingsActivity;
43 import com.android.settings.Utils;
44 import com.android.settings.applications.LayoutPreference;
45 import com.android.settings.core.SubSettingLauncher;
46 import com.android.settings.dashboard.SummaryLoader;
47 import com.android.settings.display.BatteryPercentagePreferenceController;
48 import com.android.settings.fuelgauge.anomaly.Anomaly;
49 import com.android.settings.fuelgauge.anomaly.AnomalyDetectionPolicy;
50 import com.android.settings.fuelgauge.batterytip.BatteryTipLoader;
51 import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController;
52 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
53 import com.android.settings.overlay.FeatureFactory;
54 import com.android.settings.search.BaseSearchIndexProvider;
55 import com.android.settingslib.core.AbstractPreferenceController;
56 import com.android.settingslib.core.lifecycle.Lifecycle;
57 import com.android.settingslib.utils.PowerUtil;
58 import com.android.settingslib.utils.StringUtil;
59 
60 import java.util.ArrayList;
61 import java.util.Collections;
62 import java.util.List;
63 
64 /**
65  * Displays a list of apps and subsystems that consume power, ordered by how much power was
66  * consumed since the last time it was unplugged.
67  */
68 public class PowerUsageSummary extends PowerUsageBase implements OnLongClickListener,
69         BatteryTipPreferenceController.BatteryTipListener {
70 
71     static final String TAG = "PowerUsageSummary";
72 
73     private static final boolean DEBUG = false;
74     private static final String KEY_BATTERY_HEADER = "battery_header";
75     private static final String KEY_BATTERY_TIP = "battery_tip";
76 
77     private static final String KEY_SCREEN_USAGE = "screen_usage";
78     private static final String KEY_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge";
79     private static final String KEY_BATTERY_SAVER_SUMMARY = "battery_saver_summary";
80 
81     @VisibleForTesting
82     static final int BATTERY_INFO_LOADER = 1;
83     @VisibleForTesting
84     static final int BATTERY_TIP_LOADER = 2;
85     @VisibleForTesting
86     static final int MENU_STATS_TYPE = Menu.FIRST;
87     @VisibleForTesting
88     static final int MENU_ADVANCED_BATTERY = Menu.FIRST + 1;
89     public static final int DEBUG_INFO_LOADER = 3;
90 
91     @VisibleForTesting
92     PowerGaugePreference mScreenUsagePref;
93     @VisibleForTesting
94     PowerGaugePreference mLastFullChargePref;
95     @VisibleForTesting
96     PowerUsageFeatureProvider mPowerFeatureProvider;
97     @VisibleForTesting
98     BatteryUtils mBatteryUtils;
99     @VisibleForTesting
100     LayoutPreference mBatteryLayoutPref;
101     @VisibleForTesting
102     BatteryInfo mBatteryInfo;
103 
104     /**
105      * SparseArray that maps uid to {@link Anomaly}, so we could find {@link Anomaly} by uid
106      */
107     @VisibleForTesting
108     SparseArray<List<Anomaly>> mAnomalySparseArray;
109     @VisibleForTesting
110     BatteryHeaderPreferenceController mBatteryHeaderPreferenceController;
111     @VisibleForTesting
112     boolean mNeedUpdateBatteryTip;
113     @VisibleForTesting
114     BatteryTipPreferenceController mBatteryTipPreferenceController;
115     private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
116 
117     @VisibleForTesting
118     LoaderManager.LoaderCallbacks<BatteryInfo> mBatteryInfoLoaderCallbacks =
119             new LoaderManager.LoaderCallbacks<BatteryInfo>() {
120 
121                 @Override
122                 public Loader<BatteryInfo> onCreateLoader(int i, Bundle bundle) {
123                     return new BatteryInfoLoader(getContext(), mStatsHelper);
124                 }
125 
126                 @Override
127                 public void onLoadFinished(Loader<BatteryInfo> loader, BatteryInfo batteryInfo) {
128                     mBatteryHeaderPreferenceController.updateHeaderPreference(batteryInfo);
129                     mBatteryInfo = batteryInfo;
130                     updateLastFullChargePreference();
131                 }
132 
133                 @Override
134                 public void onLoaderReset(Loader<BatteryInfo> loader) {
135                     // do nothing
136                 }
137             };
138 
139     LoaderManager.LoaderCallbacks<List<BatteryInfo>> mBatteryInfoDebugLoaderCallbacks =
140             new LoaderCallbacks<List<BatteryInfo>>() {
141                 @Override
142                 public Loader<List<BatteryInfo>> onCreateLoader(int i, Bundle bundle) {
143                     return new DebugEstimatesLoader(getContext(), mStatsHelper);
144                 }
145 
146                 @Override
147                 public void onLoadFinished(Loader<List<BatteryInfo>> loader,
148                         List<BatteryInfo> batteryInfos) {
149                     updateViews(batteryInfos);
150                 }
151 
152                 @Override
153                 public void onLoaderReset(Loader<List<BatteryInfo>> loader) {
154                 }
155             };
156 
updateViews(List<BatteryInfo> batteryInfos)157     protected void updateViews(List<BatteryInfo> batteryInfos) {
158         final BatteryMeterView batteryView = mBatteryLayoutPref
159             .findViewById(R.id.battery_header_icon);
160         final TextView percentRemaining =
161             mBatteryLayoutPref.findViewById(R.id.battery_percent);
162         final TextView summary1 = mBatteryLayoutPref.findViewById(R.id.summary1);
163         final TextView summary2 = mBatteryLayoutPref.findViewById(R.id.summary2);
164         BatteryInfo oldInfo = batteryInfos.get(0);
165         BatteryInfo newInfo = batteryInfos.get(1);
166         percentRemaining.setText(Utils.formatPercentage(oldInfo.batteryLevel));
167 
168         // set the text to the old estimate (copied from battery info). Note that this
169         // can sometimes say 0 time remaining because battery stats requires the phone
170         // be unplugged for a period of time before being willing ot make an estimate.
171         summary1.setText(mPowerFeatureProvider.getOldEstimateDebugString(
172             Formatter.formatShortElapsedTime(getContext(),
173                 PowerUtil.convertUsToMs(oldInfo.remainingTimeUs))));
174 
175         // for this one we can just set the string directly
176         summary2.setText(mPowerFeatureProvider.getEnhancedEstimateDebugString(
177             Formatter.formatShortElapsedTime(getContext(),
178                 PowerUtil.convertUsToMs(newInfo.remainingTimeUs))));
179 
180         batteryView.setBatteryLevel(oldInfo.batteryLevel);
181         batteryView.setCharging(!oldInfo.discharging);
182     }
183 
184     private LoaderManager.LoaderCallbacks<List<BatteryTip>> mBatteryTipsCallbacks =
185             new LoaderManager.LoaderCallbacks<List<BatteryTip>>() {
186 
187                 @Override
188                 public Loader<List<BatteryTip>> onCreateLoader(int id, Bundle args) {
189                     return new BatteryTipLoader(getContext(), mStatsHelper);
190                 }
191 
192                 @Override
193                 public void onLoadFinished(Loader<List<BatteryTip>> loader,
194                         List<BatteryTip> data) {
195                     mBatteryTipPreferenceController.updateBatteryTips(data);
196                 }
197 
198                 @Override
199                 public void onLoaderReset(Loader<List<BatteryTip>> loader) {
200 
201                 }
202             };
203 
204     @Override
onCreate(Bundle icicle)205     public void onCreate(Bundle icicle) {
206         super.onCreate(icicle);
207         setAnimationAllowed(true);
208 
209         initFeatureProvider();
210         mBatteryLayoutPref = (LayoutPreference) findPreference(KEY_BATTERY_HEADER);
211 
212         mScreenUsagePref = (PowerGaugePreference) findPreference(KEY_SCREEN_USAGE);
213         mLastFullChargePref = (PowerGaugePreference) findPreference(
214                 KEY_TIME_SINCE_LAST_FULL_CHARGE);
215         mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary);
216         mBatteryUtils = BatteryUtils.getInstance(getContext());
217         mAnomalySparseArray = new SparseArray<>();
218 
219         restartBatteryInfoLoader();
220         mBatteryTipPreferenceController.restoreInstanceState(icicle);
221         updateBatteryTipFlag(icicle);
222     }
223 
224     @Override
getMetricsCategory()225     public int getMetricsCategory() {
226         return MetricsEvent.FUELGAUGE_POWER_USAGE_SUMMARY_V2;
227     }
228 
229     @Override
getLogTag()230     protected String getLogTag() {
231         return TAG;
232     }
233 
234     @Override
getPreferenceScreenResId()235     protected int getPreferenceScreenResId() {
236         return R.xml.power_usage_summary;
237     }
238 
239     @Override
createPreferenceControllers(Context context)240     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
241         final Lifecycle lifecycle = getLifecycle();
242         final SettingsActivity activity = (SettingsActivity) getActivity();
243         final List<AbstractPreferenceController> controllers = new ArrayList<>();
244         mBatteryHeaderPreferenceController = new BatteryHeaderPreferenceController(
245                 context, activity, this /* host */, lifecycle);
246         controllers.add(mBatteryHeaderPreferenceController);
247         mBatteryTipPreferenceController = new BatteryTipPreferenceController(context,
248                 KEY_BATTERY_TIP, (SettingsActivity) getActivity(), this /* fragment */, this /*
249                 BatteryTipListener */);
250         controllers.add(mBatteryTipPreferenceController);
251         controllers.add(new BatteryPercentagePreferenceController(context));
252         return controllers;
253     }
254 
255     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)256     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
257         if (DEBUG) {
258             menu.add(Menu.NONE, MENU_STATS_TYPE, Menu.NONE, R.string.menu_stats_total)
259                     .setIcon(com.android.internal.R.drawable.ic_menu_info_details)
260                     .setAlphabeticShortcut('t');
261         }
262 
263         menu.add(Menu.NONE, MENU_ADVANCED_BATTERY, Menu.NONE, R.string.advanced_battery_title);
264 
265         super.onCreateOptionsMenu(menu, inflater);
266     }
267 
268     @Override
getHelpResource()269     public int getHelpResource() {
270         return R.string.help_url_battery;
271     }
272 
273     @Override
onOptionsItemSelected(MenuItem item)274     public boolean onOptionsItemSelected(MenuItem item) {
275         switch (item.getItemId()) {
276             case MENU_STATS_TYPE:
277                 if (mStatsType == BatteryStats.STATS_SINCE_CHARGED) {
278                     mStatsType = BatteryStats.STATS_SINCE_UNPLUGGED;
279                 } else {
280                     mStatsType = BatteryStats.STATS_SINCE_CHARGED;
281                 }
282                 refreshUi(BatteryUpdateType.MANUAL);
283                 return true;
284             case MENU_ADVANCED_BATTERY:
285                 new SubSettingLauncher(getContext())
286                         .setDestination(PowerUsageAdvanced.class.getName())
287                         .setSourceMetricsCategory(getMetricsCategory())
288                         .setTitle(R.string.advanced_battery_title)
289                         .launch();
290                 return true;
291             default:
292                 return super.onOptionsItemSelected(item);
293         }
294     }
295 
refreshUi(@atteryUpdateType int refreshType)296     protected void refreshUi(@BatteryUpdateType int refreshType) {
297         final Context context = getContext();
298         if (context == null) {
299             return;
300         }
301 
302         // Skip BatteryTipLoader if device is rotated or only battery level change
303         if (mNeedUpdateBatteryTip
304                 && refreshType != BatteryUpdateType.BATTERY_LEVEL) {
305             restartBatteryTipLoader();
306         } else {
307             mNeedUpdateBatteryTip = true;
308         }
309 
310         // reload BatteryInfo and updateUI
311         restartBatteryInfoLoader();
312         updateLastFullChargePreference();
313         mScreenUsagePref.setSubtitle(StringUtil.formatElapsedTime(getContext(),
314                 mBatteryUtils.calculateScreenUsageTime(mStatsHelper), false));
315     }
316 
317     @VisibleForTesting
restartBatteryTipLoader()318     void restartBatteryTipLoader() {
319         getLoaderManager().restartLoader(BATTERY_TIP_LOADER, Bundle.EMPTY, mBatteryTipsCallbacks);
320     }
321 
322     @VisibleForTesting
setBatteryLayoutPreference(LayoutPreference layoutPreference)323     void setBatteryLayoutPreference(LayoutPreference layoutPreference) {
324         mBatteryLayoutPref = layoutPreference;
325     }
326 
327     @VisibleForTesting
getAnomalyDetectionPolicy()328     AnomalyDetectionPolicy getAnomalyDetectionPolicy() {
329         return new AnomalyDetectionPolicy(getContext());
330     }
331 
332     @VisibleForTesting
updateLastFullChargePreference()333     void updateLastFullChargePreference() {
334         if (mBatteryInfo != null && mBatteryInfo.averageTimeToDischarge
335                 != Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN) {
336             mLastFullChargePref.setTitle(R.string.battery_full_charge_last);
337             mLastFullChargePref.setSubtitle(
338                     StringUtil.formatElapsedTime(getContext(), mBatteryInfo.averageTimeToDischarge,
339                             false /* withSeconds */));
340         } else {
341             final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime(mStatsHelper,
342                     System.currentTimeMillis());
343             mLastFullChargePref.setTitle(R.string.battery_last_full_charge);
344             mLastFullChargePref.setSubtitle(
345                     StringUtil.formatRelativeTime(getContext(), lastFullChargeTime,
346                             false /* withSeconds */));
347         }
348     }
349 
350     @VisibleForTesting
showBothEstimates()351     void showBothEstimates() {
352         final Context context = getContext();
353         if (context == null
354                 || !mPowerFeatureProvider.isEnhancedBatteryPredictionEnabled(context)) {
355             return;
356         }
357         getLoaderManager().restartLoader(DEBUG_INFO_LOADER, Bundle.EMPTY,
358                 mBatteryInfoDebugLoaderCallbacks);
359     }
360 
361     @VisibleForTesting
initFeatureProvider()362     void initFeatureProvider() {
363         final Context context = getContext();
364         mPowerFeatureProvider = FeatureFactory.getFactory(context)
365                 .getPowerUsageFeatureProvider(context);
366     }
367 
368     @VisibleForTesting
updateAnomalySparseArray(List<Anomaly> anomalies)369     void updateAnomalySparseArray(List<Anomaly> anomalies) {
370         mAnomalySparseArray.clear();
371         for (final Anomaly anomaly : anomalies) {
372             if (mAnomalySparseArray.get(anomaly.uid) == null) {
373                 mAnomalySparseArray.append(anomaly.uid, new ArrayList<>());
374             }
375             mAnomalySparseArray.get(anomaly.uid).add(anomaly);
376         }
377     }
378 
379     @VisibleForTesting
restartBatteryInfoLoader()380     void restartBatteryInfoLoader() {
381         getLoaderManager().restartLoader(BATTERY_INFO_LOADER, Bundle.EMPTY,
382                 mBatteryInfoLoaderCallbacks);
383         if (mPowerFeatureProvider.isEstimateDebugEnabled()) {
384             // Set long click action for summary to show debug info
385             View header = mBatteryLayoutPref.findViewById(R.id.summary1);
386             header.setOnLongClickListener(this);
387         }
388     }
389 
390     @VisibleForTesting
updateBatteryTipFlag(Bundle icicle)391     void updateBatteryTipFlag(Bundle icicle) {
392         mNeedUpdateBatteryTip = icicle == null || mBatteryTipPreferenceController.needUpdate();
393     }
394 
395     @Override
onLongClick(View view)396     public boolean onLongClick(View view) {
397         showBothEstimates();
398         view.setOnLongClickListener(null);
399         return true;
400     }
401 
402     @Override
restartBatteryStatsLoader(@atteryUpdateType int refreshType)403     protected void restartBatteryStatsLoader(@BatteryUpdateType int refreshType) {
404         super.restartBatteryStatsLoader(refreshType);
405         mBatteryHeaderPreferenceController.quickUpdateHeaderPreference();
406     }
407 
408     @Override
onSaveInstanceState(Bundle outState)409     public void onSaveInstanceState(Bundle outState) {
410         super.onSaveInstanceState(outState);
411         mBatteryTipPreferenceController.saveInstanceState(outState);
412     }
413 
414     @Override
onBatteryTipHandled(BatteryTip batteryTip)415     public void onBatteryTipHandled(BatteryTip batteryTip) {
416         restartBatteryTipLoader();
417     }
418 
419     private static class SummaryProvider implements SummaryLoader.SummaryProvider {
420         private final Context mContext;
421         private final SummaryLoader mLoader;
422         private final BatteryBroadcastReceiver mBatteryBroadcastReceiver;
423 
SummaryProvider(Context context, SummaryLoader loader)424         private SummaryProvider(Context context, SummaryLoader loader) {
425             mContext = context;
426             mLoader = loader;
427             mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext);
428             mBatteryBroadcastReceiver.setBatteryChangedListener(type -> {
429                 BatteryInfo.getBatteryInfo(mContext, new BatteryInfo.Callback() {
430                     @Override
431                     public void onBatteryInfoLoaded(BatteryInfo info) {
432                         mLoader.setSummary(SummaryProvider.this, getDashboardLabel(mContext, info));
433                     }
434                 }, true /* shortString */);
435             });
436         }
437 
438         @Override
setListening(boolean listening)439         public void setListening(boolean listening) {
440             if (listening) {
441                 mBatteryBroadcastReceiver.register();
442             } else {
443                 mBatteryBroadcastReceiver.unRegister();
444             }
445         }
446     }
447 
448     @VisibleForTesting
getDashboardLabel(Context context, BatteryInfo info)449     static CharSequence getDashboardLabel(Context context, BatteryInfo info) {
450         CharSequence label;
451         final BidiFormatter formatter = BidiFormatter.getInstance();
452         if (info.remainingLabel == null) {
453             label = info.batteryPercentString;
454         } else {
455             label = context.getString(R.string.power_remaining_settings_home_page,
456                     formatter.unicodeWrap(info.batteryPercentString),
457                     formatter.unicodeWrap(info.remainingLabel));
458         }
459         return label;
460     }
461 
462     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
463             new BaseSearchIndexProvider() {
464                 @Override
465                 public List<SearchIndexableResource> getXmlResourcesToIndex(
466                         Context context, boolean enabled) {
467                     final SearchIndexableResource sir = new SearchIndexableResource(context);
468                     sir.xmlResId = R.xml.power_usage_summary;
469                     return Collections.singletonList(sir);
470                 }
471 
472                 @Override
473                 public List<String> getNonIndexableKeys(Context context) {
474                     List<String> niks = super.getNonIndexableKeys(context);
475                     niks.add(KEY_BATTERY_SAVER_SUMMARY);
476                     return niks;
477                 }
478             };
479 
480     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
481             = new SummaryLoader.SummaryProviderFactory() {
482         @Override
483         public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
484                 SummaryLoader summaryLoader) {
485             return new SummaryProvider(activity, summaryLoader);
486         }
487     };
488 }
489