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