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