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