• 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.settings.SettingsEnums;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.database.ContentObserver;
25 import android.net.Uri;
26 import android.os.BatteryStats;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.provider.SearchIndexableResource;
30 import android.provider.Settings;
31 import android.provider.Settings.Global;
32 import android.text.format.Formatter;
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 androidx.annotation.VisibleForTesting;
41 import androidx.loader.app.LoaderManager;
42 import androidx.loader.app.LoaderManager.LoaderCallbacks;
43 import androidx.loader.content.Loader;
44 
45 import com.android.settings.R;
46 import com.android.settings.SettingsActivity;
47 import com.android.settings.Utils;
48 import com.android.settings.core.SubSettingLauncher;
49 import com.android.settings.fuelgauge.batterytip.BatteryTipLoader;
50 import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController;
51 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
52 import com.android.settings.overlay.FeatureFactory;
53 import com.android.settings.search.BaseSearchIndexProvider;
54 import com.android.settingslib.fuelgauge.EstimateKt;
55 import com.android.settingslib.search.SearchIndexable;
56 import com.android.settingslib.utils.PowerUtil;
57 import com.android.settingslib.utils.StringUtil;
58 import com.android.settingslib.widget.LayoutPreference;
59 
60 import java.util.Collections;
61 import java.util.List;
62 
63 /**
64  * Displays a list of apps and subsystems that consume power, ordered by how much power was
65  * consumed since the last time it was unplugged.
66  */
67 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
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 
76     private static final String KEY_SCREEN_USAGE = "screen_usage";
77     private static final String KEY_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge";
78     private static final String KEY_BATTERY_SAVER_SUMMARY = "battery_saver_summary";
79 
80     @VisibleForTesting
81     static final int BATTERY_INFO_LOADER = 1;
82     @VisibleForTesting
83     static final int BATTERY_TIP_LOADER = 2;
84     @VisibleForTesting
85     static final int MENU_STATS_TYPE = Menu.FIRST;
86     @VisibleForTesting
87     static final int MENU_ADVANCED_BATTERY = Menu.FIRST + 1;
88     public static final int DEBUG_INFO_LOADER = 3;
89 
90     @VisibleForTesting
91     PowerGaugePreference mScreenUsagePref;
92     @VisibleForTesting
93     PowerGaugePreference mLastFullChargePref;
94     @VisibleForTesting
95     PowerUsageFeatureProvider mPowerFeatureProvider;
96     @VisibleForTesting
97     BatteryUtils mBatteryUtils;
98     @VisibleForTesting
99     LayoutPreference mBatteryLayoutPref;
100     @VisibleForTesting
101     BatteryInfo mBatteryInfo;
102 
103     @VisibleForTesting
104     BatteryHeaderPreferenceController mBatteryHeaderPreferenceController;
105     @VisibleForTesting
106     boolean mNeedUpdateBatteryTip;
107     @VisibleForTesting
108     BatteryTipPreferenceController mBatteryTipPreferenceController;
109     private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
110 
111     @VisibleForTesting
112     final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
113         @Override
114         public void onChange(boolean selfChange, Uri uri) {
115             restartBatteryInfoLoader();
116         }
117     };
118 
119     @VisibleForTesting
120     LoaderManager.LoaderCallbacks<BatteryInfo> mBatteryInfoLoaderCallbacks =
121             new LoaderManager.LoaderCallbacks<BatteryInfo>() {
122 
123                 @Override
124                 public Loader<BatteryInfo> onCreateLoader(int i, Bundle bundle) {
125                     return new BatteryInfoLoader(getContext(), mStatsHelper);
126                 }
127 
128                 @Override
129                 public void onLoadFinished(Loader<BatteryInfo> loader, BatteryInfo batteryInfo) {
130                     mBatteryHeaderPreferenceController.updateHeaderPreference(batteryInfo);
131                     mBatteryInfo = batteryInfo;
132                     updateLastFullChargePreference();
133                 }
134 
135                 @Override
136                 public void onLoaderReset(Loader<BatteryInfo> loader) {
137                     // do nothing
138                 }
139             };
140 
141     LoaderManager.LoaderCallbacks<List<BatteryInfo>> mBatteryInfoDebugLoaderCallbacks =
142             new LoaderCallbacks<List<BatteryInfo>>() {
143                 @Override
144                 public Loader<List<BatteryInfo>> onCreateLoader(int i, Bundle bundle) {
145                     return new DebugEstimatesLoader(getContext(), mStatsHelper);
146                 }
147 
148                 @Override
149                 public void onLoadFinished(Loader<List<BatteryInfo>> loader,
150                         List<BatteryInfo> batteryInfos) {
151                     updateViews(batteryInfos);
152                 }
153 
154                 @Override
155                 public void onLoaderReset(Loader<List<BatteryInfo>> loader) {
156                 }
157             };
158 
updateViews(List<BatteryInfo> batteryInfos)159     protected void updateViews(List<BatteryInfo> batteryInfos) {
160         final BatteryMeterView batteryView = mBatteryLayoutPref
161                 .findViewById(R.id.battery_header_icon);
162         final TextView percentRemaining =
163                 mBatteryLayoutPref.findViewById(R.id.battery_percent);
164         final TextView summary1 = mBatteryLayoutPref.findViewById(R.id.summary1);
165         final TextView summary2 = mBatteryLayoutPref.findViewById(R.id.summary2);
166         BatteryInfo oldInfo = batteryInfos.get(0);
167         BatteryInfo newInfo = batteryInfos.get(1);
168         percentRemaining.setText(Utils.formatPercentage(oldInfo.batteryLevel));
169 
170         // set the text to the old estimate (copied from battery info). Note that this
171         // can sometimes say 0 time remaining because battery stats requires the phone
172         // be unplugged for a period of time before being willing ot make an estimate.
173         summary1.setText(mPowerFeatureProvider.getOldEstimateDebugString(
174                 Formatter.formatShortElapsedTime(getContext(),
175                         PowerUtil.convertUsToMs(oldInfo.remainingTimeUs))));
176 
177         // for this one we can just set the string directly
178         summary2.setText(mPowerFeatureProvider.getEnhancedEstimateDebugString(
179                 Formatter.formatShortElapsedTime(getContext(),
180                         PowerUtil.convertUsToMs(newInfo.remainingTimeUs))));
181 
182         batteryView.setBatteryLevel(oldInfo.batteryLevel);
183         batteryView.setCharging(!oldInfo.discharging);
184     }
185 
186     private LoaderManager.LoaderCallbacks<List<BatteryTip>> mBatteryTipsCallbacks =
187             new LoaderManager.LoaderCallbacks<List<BatteryTip>>() {
188 
189                 @Override
190                 public Loader<List<BatteryTip>> onCreateLoader(int id, Bundle args) {
191                     return new BatteryTipLoader(getContext(), mStatsHelper);
192                 }
193 
194                 @Override
195                 public void onLoadFinished(Loader<List<BatteryTip>> loader,
196                         List<BatteryTip> data) {
197                     mBatteryTipPreferenceController.updateBatteryTips(data);
198                 }
199 
200                 @Override
201                 public void onLoaderReset(Loader<List<BatteryTip>> loader) {
202 
203                 }
204             };
205 
206     @Override
onAttach(Context context)207     public void onAttach(Context context) {
208         super.onAttach(context);
209         final SettingsActivity activity = (SettingsActivity) getActivity();
210 
211         mBatteryHeaderPreferenceController = use(BatteryHeaderPreferenceController.class);
212         mBatteryHeaderPreferenceController.setActivity(activity);
213         mBatteryHeaderPreferenceController.setFragment(this);
214         mBatteryHeaderPreferenceController.setLifecycle(getSettingsLifecycle());
215 
216         mBatteryTipPreferenceController = use(BatteryTipPreferenceController.class);
217         mBatteryTipPreferenceController.setActivity(activity);
218         mBatteryTipPreferenceController.setFragment(this);
219         mBatteryTipPreferenceController.setBatteryTipListener(this::onBatteryTipHandled);
220     }
221 
222     @Override
onCreate(Bundle icicle)223     public void onCreate(Bundle icicle) {
224         super.onCreate(icicle);
225         setAnimationAllowed(true);
226 
227         initFeatureProvider();
228         mBatteryLayoutPref = (LayoutPreference) findPreference(KEY_BATTERY_HEADER);
229 
230         mScreenUsagePref = (PowerGaugePreference) findPreference(KEY_SCREEN_USAGE);
231         mLastFullChargePref = (PowerGaugePreference) findPreference(
232                 KEY_TIME_SINCE_LAST_FULL_CHARGE);
233         mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary);
234         mBatteryUtils = BatteryUtils.getInstance(getContext());
235 
236         restartBatteryInfoLoader();
237         mBatteryTipPreferenceController.restoreInstanceState(icicle);
238         updateBatteryTipFlag(icicle);
239     }
240 
241     @Override
onResume()242     public void onResume() {
243         super.onResume();
244         getContentResolver().registerContentObserver(
245                 Global.getUriFor(Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME),
246                 false,
247                 mSettingsObserver);
248     }
249 
250     @Override
onPause()251     public void onPause() {
252         getContentResolver().unregisterContentObserver(mSettingsObserver);
253         super.onPause();
254     }
255 
256     @Override
getMetricsCategory()257     public int getMetricsCategory() {
258         return SettingsEnums.FUELGAUGE_POWER_USAGE_SUMMARY_V2;
259     }
260 
261     @Override
getLogTag()262     protected String getLogTag() {
263         return TAG;
264     }
265 
266     @Override
getPreferenceScreenResId()267     protected int getPreferenceScreenResId() {
268         return R.xml.power_usage_summary;
269     }
270 
271     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)272     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
273         if (DEBUG) {
274             menu.add(Menu.NONE, MENU_STATS_TYPE, Menu.NONE, R.string.menu_stats_total)
275                     .setIcon(com.android.internal.R.drawable.ic_menu_info_details)
276                     .setAlphabeticShortcut('t');
277         }
278 
279         menu.add(Menu.NONE, MENU_ADVANCED_BATTERY, Menu.NONE, R.string.advanced_battery_title);
280 
281         super.onCreateOptionsMenu(menu, inflater);
282     }
283 
284     @Override
getHelpResource()285     public int getHelpResource() {
286         return R.string.help_url_battery;
287     }
288 
289     @Override
onOptionsItemSelected(MenuItem item)290     public boolean onOptionsItemSelected(MenuItem item) {
291         switch (item.getItemId()) {
292             case MENU_STATS_TYPE:
293                 if (mStatsType == BatteryStats.STATS_SINCE_CHARGED) {
294                     mStatsType = BatteryStats.STATS_SINCE_UNPLUGGED;
295                 } else {
296                     mStatsType = BatteryStats.STATS_SINCE_CHARGED;
297                 }
298                 refreshUi(BatteryUpdateType.MANUAL);
299                 return true;
300             case MENU_ADVANCED_BATTERY:
301                 new SubSettingLauncher(getContext())
302                         .setDestination(PowerUsageAdvanced.class.getName())
303                         .setSourceMetricsCategory(getMetricsCategory())
304                         .setTitleRes(R.string.advanced_battery_title)
305                         .launch();
306                 return true;
307             default:
308                 return super.onOptionsItemSelected(item);
309         }
310     }
311 
refreshUi(@atteryUpdateType int refreshType)312     protected void refreshUi(@BatteryUpdateType int refreshType) {
313         final Context context = getContext();
314         if (context == null) {
315             return;
316         }
317 
318         // Skip BatteryTipLoader if device is rotated or only battery level change
319         if (mNeedUpdateBatteryTip
320                 && refreshType != BatteryUpdateType.BATTERY_LEVEL) {
321             restartBatteryTipLoader();
322         } else {
323             mNeedUpdateBatteryTip = true;
324         }
325 
326         // reload BatteryInfo and updateUI
327         restartBatteryInfoLoader();
328         updateLastFullChargePreference();
329         mScreenUsagePref.setSubtitle(StringUtil.formatElapsedTime(getContext(),
330                 mBatteryUtils.calculateScreenUsageTime(mStatsHelper), false));
331     }
332 
333     @VisibleForTesting
restartBatteryTipLoader()334     void restartBatteryTipLoader() {
335         getLoaderManager().restartLoader(BATTERY_TIP_LOADER, Bundle.EMPTY, mBatteryTipsCallbacks);
336     }
337 
338     @VisibleForTesting
setBatteryLayoutPreference(LayoutPreference layoutPreference)339     void setBatteryLayoutPreference(LayoutPreference layoutPreference) {
340         mBatteryLayoutPref = layoutPreference;
341     }
342 
343     @VisibleForTesting
updateLastFullChargePreference()344     void updateLastFullChargePreference() {
345         if (mBatteryInfo != null && mBatteryInfo.averageTimeToDischarge
346                 != EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN) {
347             mLastFullChargePref.setTitle(R.string.battery_full_charge_last);
348             mLastFullChargePref.setSubtitle(
349                     StringUtil.formatElapsedTime(getContext(), mBatteryInfo.averageTimeToDischarge,
350                             false /* withSeconds */));
351         } else {
352             final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime(mStatsHelper,
353                     System.currentTimeMillis());
354             mLastFullChargePref.setTitle(R.string.battery_last_full_charge);
355             mLastFullChargePref.setSubtitle(
356                     StringUtil.formatRelativeTime(getContext(), lastFullChargeTime,
357                             false /* withSeconds */));
358         }
359     }
360 
361     @VisibleForTesting
showBothEstimates()362     void showBothEstimates() {
363         final Context context = getContext();
364         if (context == null
365                 || !mPowerFeatureProvider.isEnhancedBatteryPredictionEnabled(context)) {
366             return;
367         }
368         getLoaderManager().restartLoader(DEBUG_INFO_LOADER, Bundle.EMPTY,
369                 mBatteryInfoDebugLoaderCallbacks);
370     }
371 
372     @VisibleForTesting
initFeatureProvider()373     void initFeatureProvider() {
374         final Context context = getContext();
375         mPowerFeatureProvider = FeatureFactory.getFactory(context)
376                 .getPowerUsageFeatureProvider(context);
377     }
378 
379     @VisibleForTesting
restartBatteryInfoLoader()380     void restartBatteryInfoLoader() {
381         if (getContext() == null) {
382             return;
383         }
384         getLoaderManager().restartLoader(BATTERY_INFO_LOADER, Bundle.EMPTY,
385                 mBatteryInfoLoaderCallbacks);
386         if (mPowerFeatureProvider.isEstimateDebugEnabled()) {
387             // Set long click action for summary to show debug info
388             View header = mBatteryLayoutPref.findViewById(R.id.summary1);
389             header.setOnLongClickListener(this);
390         }
391     }
392 
393     @VisibleForTesting
updateBatteryTipFlag(Bundle icicle)394     void updateBatteryTipFlag(Bundle icicle) {
395         mNeedUpdateBatteryTip = icicle == null || mBatteryTipPreferenceController.needUpdate();
396     }
397 
398     @Override
onLongClick(View view)399     public boolean onLongClick(View view) {
400         showBothEstimates();
401         view.setOnLongClickListener(null);
402         return true;
403     }
404 
405     @Override
restartBatteryStatsLoader(@atteryUpdateType int refreshType)406     protected void restartBatteryStatsLoader(@BatteryUpdateType int refreshType) {
407         super.restartBatteryStatsLoader(refreshType);
408         mBatteryHeaderPreferenceController.quickUpdateHeaderPreference();
409     }
410 
411     @Override
onSaveInstanceState(Bundle outState)412     public void onSaveInstanceState(Bundle outState) {
413         super.onSaveInstanceState(outState);
414         mBatteryTipPreferenceController.saveInstanceState(outState);
415     }
416 
417     @Override
onBatteryTipHandled(BatteryTip batteryTip)418     public void onBatteryTipHandled(BatteryTip batteryTip) {
419         restartBatteryTipLoader();
420     }
421 
422 
423     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
424             new BaseSearchIndexProvider() {
425                 @Override
426                 public List<SearchIndexableResource> getXmlResourcesToIndex(
427                         Context context, boolean enabled) {
428                     final SearchIndexableResource sir = new SearchIndexableResource(context);
429                     sir.xmlResId = R.xml.power_usage_summary;
430                     return Collections.singletonList(sir);
431                 }
432             };
433 }
434