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