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