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
60  * consumed 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         restartBatteryInfoLoader();
224         mBatteryTipPreferenceController.restoreInstanceState(icicle);
225         updateBatteryTipFlag(icicle);
226     }
227 
228     @Override
onResume()229     public void onResume() {
230         super.onResume();
231         getContentResolver().registerContentObserver(
232                 Global.getUriFor(Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME),
233                 false,
234                 mSettingsObserver);
235     }
236 
237     @Override
onPause()238     public void onPause() {
239         getContentResolver().unregisterContentObserver(mSettingsObserver);
240         super.onPause();
241     }
242 
243     @Override
getMetricsCategory()244     public int getMetricsCategory() {
245         return SettingsEnums.FUELGAUGE_POWER_USAGE_SUMMARY_V2;
246     }
247 
248     @Override
getLogTag()249     protected String getLogTag() {
250         return TAG;
251     }
252 
253     @Override
getPreferenceScreenResId()254     protected int getPreferenceScreenResId() {
255         return R.xml.power_usage_summary;
256     }
257 
258     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)259     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
260         menu.add(Menu.NONE, MENU_ADVANCED_BATTERY, Menu.NONE, R.string.advanced_battery_title);
261 
262         super.onCreateOptionsMenu(menu, inflater);
263     }
264 
265     @Override
getHelpResource()266     public int getHelpResource() {
267         return R.string.help_url_battery;
268     }
269 
270     @Override
onOptionsItemSelected(MenuItem item)271     public boolean onOptionsItemSelected(MenuItem item) {
272         switch (item.getItemId()) {
273             case MENU_ADVANCED_BATTERY:
274                 new SubSettingLauncher(getContext())
275                         .setDestination(PowerUsageAdvanced.class.getName())
276                         .setSourceMetricsCategory(getMetricsCategory())
277                         .setTitleRes(R.string.advanced_battery_title)
278                         .launch();
279                 return true;
280             default:
281                 return super.onOptionsItemSelected(item);
282         }
283     }
284 
refreshUi(@atteryUpdateType int refreshType)285     protected void refreshUi(@BatteryUpdateType int refreshType) {
286         final Context context = getContext();
287         if (context == null) {
288             return;
289         }
290 
291         // Skip BatteryTipLoader if device is rotated or only battery level change
292         if (mNeedUpdateBatteryTip
293                 && refreshType != BatteryUpdateType.BATTERY_LEVEL) {
294             restartBatteryTipLoader();
295         } else {
296             mNeedUpdateBatteryTip = true;
297         }
298 
299         // reload BatteryInfo and updateUI
300         restartBatteryInfoLoader();
301         updateLastFullChargePreference();
302         mScreenUsagePref.setSubtitle(StringUtil.formatElapsedTime(getContext(),
303                 mBatteryUtils.calculateScreenUsageTime(mStatsHelper), false));
304     }
305 
306     @VisibleForTesting
restartBatteryTipLoader()307     void restartBatteryTipLoader() {
308         getLoaderManager().restartLoader(BATTERY_TIP_LOADER, Bundle.EMPTY, mBatteryTipsCallbacks);
309     }
310 
311     @VisibleForTesting
setBatteryLayoutPreference(LayoutPreference layoutPreference)312     void setBatteryLayoutPreference(LayoutPreference layoutPreference) {
313         mBatteryLayoutPref = layoutPreference;
314     }
315 
316     @VisibleForTesting
updateLastFullChargePreference()317     void updateLastFullChargePreference() {
318         if (mBatteryInfo != null && mBatteryInfo.averageTimeToDischarge
319                 != EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN) {
320             mLastFullChargePref.setTitle(R.string.battery_full_charge_last);
321             mLastFullChargePref.setSubtitle(
322                     StringUtil.formatElapsedTime(getContext(), mBatteryInfo.averageTimeToDischarge,
323                             false /* withSeconds */));
324         } else {
325             final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime(mStatsHelper,
326                     System.currentTimeMillis());
327             mLastFullChargePref.setTitle(R.string.battery_last_full_charge);
328             mLastFullChargePref.setSubtitle(
329                     StringUtil.formatRelativeTime(getContext(), lastFullChargeTime,
330                             false /* withSeconds */));
331         }
332     }
333 
334     @VisibleForTesting
showBothEstimates()335     void showBothEstimates() {
336         final Context context = getContext();
337         if (context == null
338                 || !mPowerFeatureProvider.isEnhancedBatteryPredictionEnabled(context)) {
339             return;
340         }
341         getLoaderManager().restartLoader(DEBUG_INFO_LOADER, Bundle.EMPTY,
342                 mBatteryInfoDebugLoaderCallbacks);
343     }
344 
345     @VisibleForTesting
initFeatureProvider()346     void initFeatureProvider() {
347         final Context context = getContext();
348         mPowerFeatureProvider = FeatureFactory.getFactory(context)
349                 .getPowerUsageFeatureProvider(context);
350     }
351 
352     @VisibleForTesting
restartBatteryInfoLoader()353     void restartBatteryInfoLoader() {
354         if (getContext() == null) {
355             return;
356         }
357         getLoaderManager().restartLoader(BATTERY_INFO_LOADER, Bundle.EMPTY,
358                 mBatteryInfoLoaderCallbacks);
359         if (mPowerFeatureProvider.isEstimateDebugEnabled()) {
360             // Set long click action for summary to show debug info
361             View header = mBatteryLayoutPref.findViewById(R.id.summary1);
362             header.setOnLongClickListener(this);
363         }
364     }
365 
366     @VisibleForTesting
updateBatteryTipFlag(Bundle icicle)367     void updateBatteryTipFlag(Bundle icicle) {
368         mNeedUpdateBatteryTip = icicle == null || mBatteryTipPreferenceController.needUpdate();
369     }
370 
371     @Override
onLongClick(View view)372     public boolean onLongClick(View view) {
373         showBothEstimates();
374         view.setOnLongClickListener(null);
375         return true;
376     }
377 
378     @Override
restartBatteryStatsLoader(@atteryUpdateType int refreshType)379     protected void restartBatteryStatsLoader(@BatteryUpdateType int refreshType) {
380         super.restartBatteryStatsLoader(refreshType);
381         mBatteryHeaderPreferenceController.quickUpdateHeaderPreference();
382     }
383 
384     @Override
onSaveInstanceState(Bundle outState)385     public void onSaveInstanceState(Bundle outState) {
386         super.onSaveInstanceState(outState);
387         mBatteryTipPreferenceController.saveInstanceState(outState);
388     }
389 
390     @Override
onBatteryTipHandled(BatteryTip batteryTip)391     public void onBatteryTipHandled(BatteryTip batteryTip) {
392         restartBatteryTipLoader();
393     }
394 
395 
396     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
397             new BaseSearchIndexProvider(R.xml.power_usage_summary);
398 }
399