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