• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 package com.android.settings.fuelgauge;
15 
16 import android.app.Activity;
17 import android.content.Context;
18 import android.content.pm.PackageManager;
19 import android.os.BatteryStats;
20 import android.os.Bundle;
21 import android.os.Handler;
22 import android.os.Message;
23 import android.os.UserManager;
24 import android.provider.SearchIndexableResource;
25 import android.support.annotation.ColorInt;
26 import android.support.annotation.IntDef;
27 import android.support.annotation.NonNull;
28 import android.support.annotation.StringRes;
29 import android.support.annotation.VisibleForTesting;
30 import android.support.v7.preference.Preference;
31 import android.support.v7.preference.PreferenceGroup;
32 import android.text.TextUtils;
33 
34 import com.android.internal.logging.nano.MetricsProto;
35 import com.android.internal.os.BatterySipper;
36 import com.android.internal.os.BatterySipper.DrainType;
37 import com.android.internal.os.BatteryStatsHelper;
38 import com.android.settings.R;
39 import com.android.settings.Utils;
40 import com.android.settings.core.PreferenceController;
41 import com.android.settings.fuelgauge.PowerUsageAdvanced.PowerUsageData.UsageType;
42 import com.android.settings.overlay.FeatureFactory;
43 import com.android.settings.search.BaseSearchIndexProvider;
44 
45 import java.lang.annotation.Retention;
46 import java.lang.annotation.RetentionPolicy;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Collections;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.Map;
53 
54 public class PowerUsageAdvanced extends PowerUsageBase {
55     private static final String TAG = "AdvancedBatteryUsage";
56     private static final String KEY_BATTERY_GRAPH = "battery_graph";
57     private static final String KEY_BATTERY_USAGE_LIST = "battery_usage_list";
58     private static final int STATUS_TYPE = BatteryStats.STATS_SINCE_CHARGED;
59 
60     @VisibleForTesting
61     final int[] mUsageTypes = {
62             UsageType.WIFI,
63             UsageType.CELL,
64             UsageType.SYSTEM,
65             UsageType.BLUETOOTH,
66             UsageType.USER,
67             UsageType.IDLE,
68             UsageType.APP,
69             UsageType.UNACCOUNTED,
70             UsageType.OVERCOUNTED};
71 
72     private BatteryUtils mBatteryUtils;
73     private BatteryHistoryPreference mHistPref;
74     private PreferenceGroup mUsageListGroup;
75     private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
76     private PackageManager mPackageManager;
77     private UserManager mUserManager;
78     private Map<Integer, PowerUsageData> mBatteryDataMap;
79 
80     Handler mHandler = new Handler() {
81 
82         @Override
83         public void handleMessage(Message msg) {
84             switch (msg.what) {
85                 case BatteryEntry.MSG_UPDATE_NAME_ICON:
86                     final int dischargeAmount = mStatsHelper.getStats().getDischargeAmount(
87                             STATUS_TYPE);
88                     final double totalPower = mStatsHelper.getTotalPower();
89                     final BatteryEntry entry = (BatteryEntry) msg.obj;
90                     final int usageType = extractUsageType(entry.sipper);
91 
92                     PowerUsageData usageData = mBatteryDataMap.get(usageType);
93                     Preference pref = findPreference(String.valueOf(usageType));
94                     if (pref != null && usageData != null) {
95                         updateUsageDataSummary(usageData, totalPower, dischargeAmount);
96                         pref.setSummary(usageData.summary);
97                     }
98                     break;
99                 case BatteryEntry.MSG_REPORT_FULLY_DRAWN:
100                     Activity activity = getActivity();
101                     if (activity != null) {
102                         activity.reportFullyDrawn();
103                     }
104                     break;
105             }
106             super.handleMessage(msg);
107         }
108     };
109 
110     @Override
onCreate(Bundle icicle)111     public void onCreate(Bundle icicle) {
112         super.onCreate(icicle);
113 
114         mHistPref = (BatteryHistoryPreference) findPreference(KEY_BATTERY_GRAPH);
115         mUsageListGroup = (PreferenceGroup) findPreference(KEY_BATTERY_USAGE_LIST);
116 
117         final Context context = getContext();
118         mPowerUsageFeatureProvider = FeatureFactory.getFactory(context)
119                 .getPowerUsageFeatureProvider(context);
120         mPackageManager = context.getPackageManager();
121         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
122         mBatteryUtils = BatteryUtils.getInstance(context);
123     }
124 
125     @Override
onResume()126     public void onResume() {
127         super.onResume();
128     }
129 
130     @Override
onPause()131     public void onPause() {
132         BatteryEntry.stopRequestQueue();
133         mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON);
134         super.onPause();
135     }
136 
137     @Override
onDestroy()138     public void onDestroy() {
139         super.onDestroy();
140         if (getActivity().isChangingConfigurations()) {
141             BatteryEntry.clearUidCache();
142         }
143     }
144 
145     @Override
getMetricsCategory()146     public int getMetricsCategory() {
147         return MetricsProto.MetricsEvent.FUELGAUGE_BATTERY_HISTORY_DETAIL;
148     }
149 
150     @Override
getLogTag()151     protected String getLogTag() {
152         return TAG;
153     }
154 
155     @Override
getPreferenceScreenResId()156     protected int getPreferenceScreenResId() {
157         return R.xml.power_usage_advanced;
158     }
159 
160     @Override
getPreferenceControllers(Context context)161     protected List<PreferenceController> getPreferenceControllers(Context context) {
162         return null;
163     }
164 
165     @Override
refreshUi()166     protected void refreshUi() {
167         final Context context = getContext();
168         if (context == null) {
169             return;
170         }
171         updatePreference(mHistPref);
172         refreshPowerUsageDataList(mStatsHelper, mUsageListGroup);
173         BatteryEntry.startRequestQueue();
174     }
175 
176     @VisibleForTesting
refreshPowerUsageDataList(BatteryStatsHelper statsHelper, PreferenceGroup preferenceGroup)177     void refreshPowerUsageDataList(BatteryStatsHelper statsHelper,
178             PreferenceGroup preferenceGroup) {
179         List<PowerUsageData> dataList = parsePowerUsageData(statsHelper);
180         preferenceGroup.removeAll();
181         for (int i = 0, size = dataList.size(); i < size; i++) {
182             final PowerUsageData batteryData = dataList.get(i);
183             if (shouldHideCategory(batteryData)) {
184                 continue;
185             }
186             final PowerGaugePreference pref = new PowerGaugePreference(getPrefContext());
187 
188             pref.setKey(String.valueOf(batteryData.usageType));
189             pref.setTitle(batteryData.titleResId);
190             pref.setSummary(batteryData.summary);
191             pref.setPercent(batteryData.percentage);
192             pref.setSelectable(false);
193             preferenceGroup.addPreference(pref);
194         }
195     }
196 
197     @VisibleForTesting
198     @UsageType
extractUsageType(BatterySipper sipper)199     int extractUsageType(BatterySipper sipper) {
200         final DrainType drainType = sipper.drainType;
201         final int uid = sipper.getUid();
202 
203         if (drainType == DrainType.WIFI) {
204             return UsageType.WIFI;
205         } else if (drainType == DrainType.BLUETOOTH) {
206             return UsageType.BLUETOOTH;
207         } else if (drainType == DrainType.IDLE) {
208             return UsageType.IDLE;
209         } else if (drainType == DrainType.USER) {
210             return UsageType.USER;
211         } else if (drainType == DrainType.CELL) {
212             return UsageType.CELL;
213         } else if (drainType == DrainType.UNACCOUNTED) {
214             return UsageType.UNACCOUNTED;
215         } else if (drainType == DrainType.OVERCOUNTED) {
216             return UsageType.OVERCOUNTED;
217         } else if (mPowerUsageFeatureProvider.isTypeSystem(sipper)
218                 || mPowerUsageFeatureProvider.isTypeService(sipper)) {
219             return UsageType.SYSTEM;
220         } else {
221             return UsageType.APP;
222         }
223     }
224 
225     @VisibleForTesting
shouldHideCategory(PowerUsageData powerUsageData)226     boolean shouldHideCategory(PowerUsageData powerUsageData) {
227         return powerUsageData.usageType == UsageType.UNACCOUNTED
228                 || powerUsageData.usageType == UsageType.OVERCOUNTED
229                 || (powerUsageData.usageType == UsageType.USER && mUserManager.getUserCount() == 1);
230     }
231 
232     @VisibleForTesting
shouldShowBatterySipper(BatterySipper batterySipper)233     boolean shouldShowBatterySipper(BatterySipper batterySipper) {
234         return batterySipper.drainType != DrainType.SCREEN;
235     }
236 
237     @VisibleForTesting
parsePowerUsageData(BatteryStatsHelper statusHelper)238     List<PowerUsageData> parsePowerUsageData(BatteryStatsHelper statusHelper) {
239         final List<BatterySipper> batterySippers = statusHelper.getUsageList();
240         final Map<Integer, PowerUsageData> batteryDataMap = new HashMap<>();
241 
242         for (final @UsageType Integer type : mUsageTypes) {
243             batteryDataMap.put(type, new PowerUsageData(type));
244         }
245 
246         // Accumulate power usage based on usage type
247         for (final BatterySipper sipper : batterySippers) {
248             sipper.mPackages = mPackageManager.getPackagesForUid(sipper.getUid());
249             final PowerUsageData usageData = batteryDataMap.get(extractUsageType(sipper));
250             usageData.totalPowerMah += sipper.totalPowerMah;
251             if (sipper.drainType == DrainType.APP && sipper.usageTimeMs != 0) {
252                 sipper.usageTimeMs = mBatteryUtils.getProcessTimeMs(
253                         BatteryUtils.StatusType.FOREGROUND, sipper.uidObj, STATUS_TYPE);
254             }
255             usageData.totalUsageTimeMs += sipper.usageTimeMs;
256             if (shouldShowBatterySipper(sipper)) {
257                 usageData.usageList.add(sipper);
258             }
259         }
260 
261         final List<PowerUsageData> batteryDataList = new ArrayList<>(batteryDataMap.values());
262         final int dischargeAmount = statusHelper.getStats().getDischargeAmount(STATUS_TYPE);
263         final double totalPower = statusHelper.getTotalPower();
264         final double hiddenPower = calculateHiddenPower(batteryDataList);
265         for (final PowerUsageData usageData : batteryDataList) {
266             usageData.percentage = mBatteryUtils.calculateBatteryPercent(usageData.totalPowerMah,
267                     totalPower, hiddenPower, dischargeAmount);
268             updateUsageDataSummary(usageData, totalPower, dischargeAmount);
269         }
270 
271         Collections.sort(batteryDataList);
272 
273         mBatteryDataMap = batteryDataMap;
274         return batteryDataList;
275     }
276 
277     @VisibleForTesting
calculateHiddenPower(List<PowerUsageData> batteryDataList)278     double calculateHiddenPower(List<PowerUsageData> batteryDataList) {
279         for (final PowerUsageData usageData : batteryDataList) {
280             if (usageData.usageType == UsageType.UNACCOUNTED) {
281                 return usageData.totalPowerMah;
282             }
283         }
284 
285         return 0;
286     }
287 
288     @VisibleForTesting
updateUsageDataSummary(PowerUsageData usageData, double totalPower, int dischargeAmount)289     void updateUsageDataSummary(PowerUsageData usageData, double totalPower, int dischargeAmount) {
290         if (shouldHideSummary(usageData)) {
291             return;
292         }
293         if (usageData.usageList.size() <= 1) {
294             CharSequence timeSequence = Utils.formatElapsedTime(getContext(),
295                     usageData.totalUsageTimeMs, false);
296             usageData.summary = usageData.usageType == UsageType.IDLE ? timeSequence
297                     : TextUtils.expandTemplate(getText(R.string.battery_used_for), timeSequence);
298         } else {
299             BatterySipper sipper = findBatterySipperWithMaxBatteryUsage(usageData.usageList);
300             BatteryEntry batteryEntry = new BatteryEntry(getContext(), mHandler, mUserManager,
301                     sipper);
302             final double percentage = (sipper.totalPowerMah / totalPower) * dischargeAmount;
303             usageData.summary = getString(R.string.battery_used_by,
304                     Utils.formatPercentage(percentage, true), batteryEntry.name);
305         }
306     }
307 
308     @VisibleForTesting
shouldHideSummary(PowerUsageData powerUsageData)309     boolean shouldHideSummary(PowerUsageData powerUsageData) {
310         @UsageType final int usageType = powerUsageData.usageType;
311 
312         return usageType == UsageType.CELL
313                 || usageType == UsageType.BLUETOOTH
314                 || usageType == UsageType.WIFI;
315     }
316 
317     @VisibleForTesting
findBatterySipperWithMaxBatteryUsage(List<BatterySipper> usageList)318     BatterySipper findBatterySipperWithMaxBatteryUsage(List<BatterySipper> usageList) {
319         BatterySipper sipper = usageList.get(0);
320         for (int i = 1, size = usageList.size(); i < size; i++) {
321             final BatterySipper comparedSipper = usageList.get(i);
322             if (comparedSipper.totalPowerMah > sipper.totalPowerMah) {
323                 sipper = comparedSipper;
324             }
325         }
326 
327         return sipper;
328     }
329 
330     @VisibleForTesting
setPackageManager(PackageManager packageManager)331     void setPackageManager(PackageManager packageManager) {
332         mPackageManager = packageManager;
333     }
334 
335     @VisibleForTesting
setPowerUsageFeatureProvider(PowerUsageFeatureProvider provider)336     void setPowerUsageFeatureProvider(PowerUsageFeatureProvider provider) {
337         mPowerUsageFeatureProvider = provider;
338     }
339     @VisibleForTesting
setUserManager(UserManager userManager)340     void setUserManager(UserManager userManager) {
341         mUserManager = userManager;
342     }
343     @VisibleForTesting
setBatteryUtils(BatteryUtils batteryUtils)344     void setBatteryUtils(BatteryUtils batteryUtils) {
345         mBatteryUtils = batteryUtils;
346     }
347 
348     /**
349      * Class that contains data used in {@link PowerGaugePreference}.
350      */
351     @VisibleForTesting
352     static class PowerUsageData implements Comparable<PowerUsageData> {
353 
354         @Retention(RetentionPolicy.SOURCE)
355         @IntDef({UsageType.APP,
356                 UsageType.WIFI,
357                 UsageType.CELL,
358                 UsageType.SYSTEM,
359                 UsageType.BLUETOOTH,
360                 UsageType.USER,
361                 UsageType.IDLE,
362                 UsageType.UNACCOUNTED,
363                 UsageType.OVERCOUNTED})
364         public @interface UsageType {
365             int APP = 0;
366             int WIFI = 1;
367             int CELL = 2;
368             int SYSTEM = 3;
369             int BLUETOOTH = 4;
370             int USER = 5;
371             int IDLE = 6;
372             int UNACCOUNTED = 7;
373             int OVERCOUNTED = 8;
374         }
375 
376         @StringRes
377         public int titleResId;
378         public CharSequence summary;
379         public double percentage;
380         public double totalPowerMah;
381         public long totalUsageTimeMs;
382         @ColorInt
383         public int iconColor;
384         @UsageType
385         public int usageType;
386         public List<BatterySipper> usageList;
387 
PowerUsageData(@sageType int usageType)388         public PowerUsageData(@UsageType int usageType) {
389             this(usageType, 0);
390         }
391 
PowerUsageData(@sageType int usageType, double totalPower)392         public PowerUsageData(@UsageType int usageType, double totalPower) {
393             this.usageType = usageType;
394             totalPowerMah = 0;
395             totalUsageTimeMs = 0;
396             titleResId = getTitleResId(usageType);
397             totalPowerMah = totalPower;
398             usageList = new ArrayList<>();
399         }
400 
getTitleResId(@sageType int usageType)401         private int getTitleResId(@UsageType int usageType) {
402             switch (usageType) {
403                 case UsageType.WIFI:
404                     return R.string.power_wifi;
405                 case UsageType.CELL:
406                     return R.string.power_cell;
407                 case UsageType.SYSTEM:
408                     return R.string.power_system;
409                 case UsageType.BLUETOOTH:
410                     return R.string.power_bluetooth;
411                 case UsageType.USER:
412                     return R.string.power_user;
413                 case UsageType.IDLE:
414                     return R.string.power_idle;
415                 case UsageType.UNACCOUNTED:
416                     return R.string.power_unaccounted;
417                 case UsageType.OVERCOUNTED:
418                     return R.string.power_overcounted;
419                 case UsageType.APP:
420                 default:
421                     return R.string.power_apps;
422             }
423         }
424 
425         @Override
compareTo(@onNull PowerUsageData powerUsageData)426         public int compareTo(@NonNull PowerUsageData powerUsageData) {
427             final int diff = Double.compare(powerUsageData.totalPowerMah, totalPowerMah);
428             return diff != 0 ? diff : usageType - powerUsageData.usageType;
429         }
430     }
431 
432     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
433             new BaseSearchIndexProvider() {
434                 @Override
435                 public List<SearchIndexableResource> getXmlResourcesToIndex(
436                         Context context, boolean enabled) {
437                     final SearchIndexableResource sir = new SearchIndexableResource(context);
438                     sir.xmlResId = R.xml.power_usage_advanced;
439                     return Arrays.asList(sir);
440                 }
441             };
442 
443 }
444