1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.fuelgauge; 18 19 import android.app.Activity; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.graphics.drawable.Drawable; 25 import android.os.BatteryStats; 26 import android.os.Build; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.Message; 30 import android.os.UserHandle; 31 import android.os.UserManager; 32 import android.preference.Preference; 33 import android.preference.PreferenceFragment; 34 import android.preference.PreferenceGroup; 35 import android.preference.PreferenceScreen; 36 import android.text.TextUtils; 37 import android.view.Menu; 38 import android.view.MenuInflater; 39 import android.view.MenuItem; 40 41 import com.android.internal.os.BatterySipper; 42 import com.android.internal.os.BatteryStatsHelper; 43 import com.android.internal.os.PowerProfile; 44 import com.android.settings.HelpUtils; 45 import com.android.settings.R; 46 import com.android.settings.SettingsActivity; 47 48 import java.util.List; 49 50 /** 51 * Displays a list of apps and subsystems that consume power, ordered by how much power was 52 * consumed since the last time it was unplugged. 53 */ 54 public class PowerUsageSummary extends PreferenceFragment { 55 56 private static final boolean DEBUG = false; 57 58 static final String TAG = "PowerUsageSummary"; 59 60 private static final String KEY_APP_LIST = "app_list"; 61 62 private static final String BATTERY_HISTORY_FILE = "tmp_bat_history.bin"; 63 64 private static final int MENU_STATS_TYPE = Menu.FIRST; 65 private static final int MENU_STATS_REFRESH = Menu.FIRST + 1; 66 private static final int MENU_BATTERY_SAVER = Menu.FIRST + 2; 67 private static final int MENU_HELP = Menu.FIRST + 3; 68 69 private UserManager mUm; 70 71 private BatteryHistoryPreference mHistPref; 72 private PreferenceGroup mAppListGroup; 73 private String mBatteryLevel; 74 private String mBatteryStatus; 75 76 private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; 77 78 private static final int MIN_POWER_THRESHOLD_MILLI_AMP = 5; 79 private static final int MAX_ITEMS_TO_LIST = 10; 80 private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10; 81 private static final int SECONDS_IN_HOUR = 60 * 60; 82 83 private BatteryStatsHelper mStatsHelper; 84 85 private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() { 86 87 @Override 88 public void onReceive(Context context, Intent intent) { 89 String action = intent.getAction(); 90 if (Intent.ACTION_BATTERY_CHANGED.equals(action) 91 && updateBatteryStatus(intent)) { 92 if (!mHandler.hasMessages(MSG_REFRESH_STATS)) { 93 mHandler.sendEmptyMessageDelayed(MSG_REFRESH_STATS, 500); 94 } 95 } 96 } 97 }; 98 99 @Override 100 public void onAttach(Activity activity) { 101 super.onAttach(activity); 102 mUm = (UserManager) activity.getSystemService(Context.USER_SERVICE); 103 mStatsHelper = new BatteryStatsHelper(activity, true); 104 } 105 106 @Override 107 public void onCreate(Bundle icicle) { 108 super.onCreate(icicle); 109 mStatsHelper.create(icicle); 110 111 addPreferencesFromResource(R.xml.power_usage_summary); 112 mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST); 113 setHasOptionsMenu(true); 114 } 115 116 @Override 117 public void onStart() { 118 super.onStart(); 119 mStatsHelper.clearStats(); 120 } 121 122 @Override 123 public void onResume() { 124 super.onResume(); 125 BatteryStatsHelper.dropFile(getActivity(), BATTERY_HISTORY_FILE); 126 updateBatteryStatus(getActivity().registerReceiver(mBatteryInfoReceiver, 127 new IntentFilter(Intent.ACTION_BATTERY_CHANGED))); 128 if (mHandler.hasMessages(MSG_REFRESH_STATS)) { 129 mHandler.removeMessages(MSG_REFRESH_STATS); 130 mStatsHelper.clearStats(); 131 } 132 refreshStats(); 133 } 134 135 @Override 136 public void onPause() { 137 BatteryEntry.stopRequestQueue(); 138 mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON); 139 getActivity().unregisterReceiver(mBatteryInfoReceiver); 140 super.onPause(); 141 } 142 143 @Override 144 public void onStop() { 145 super.onStop(); 146 mHandler.removeMessages(MSG_REFRESH_STATS); 147 } 148 149 @Override 150 public void onDestroy() { 151 super.onDestroy(); 152 if (getActivity().isChangingConfigurations()) { 153 mStatsHelper.storeState(); 154 BatteryEntry.clearUidCache(); 155 } 156 } 157 158 @Override 159 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { 160 if (preference instanceof BatteryHistoryPreference) { 161 mStatsHelper.storeStatsHistoryInFile(BATTERY_HISTORY_FILE); 162 Bundle args = new Bundle(); 163 args.putString(BatteryHistoryDetail.EXTRA_STATS, BATTERY_HISTORY_FILE); 164 args.putParcelable(BatteryHistoryDetail.EXTRA_BROADCAST, 165 mStatsHelper.getBatteryBroadcast()); 166 SettingsActivity sa = (SettingsActivity) getActivity(); 167 sa.startPreferencePanel(BatteryHistoryDetail.class.getName(), args, 168 R.string.history_details_title, null, null, 0); 169 return super.onPreferenceTreeClick(preferenceScreen, preference); 170 } 171 if (!(preference instanceof PowerGaugePreference)) { 172 return false; 173 } 174 PowerGaugePreference pgp = (PowerGaugePreference) preference; 175 BatteryEntry entry = pgp.getInfo(); 176 PowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), mStatsHelper, 177 mStatsType, entry, true); 178 return super.onPreferenceTreeClick(preferenceScreen, preference); 179 } 180 181 @Override 182 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 183 if (DEBUG) { 184 menu.add(0, MENU_STATS_TYPE, 0, R.string.menu_stats_total) 185 .setIcon(com.android.internal.R.drawable.ic_menu_info_details) 186 .setAlphabeticShortcut('t'); 187 } 188 MenuItem refresh = menu.add(0, MENU_STATS_REFRESH, 0, R.string.menu_stats_refresh) 189 .setIcon(com.android.internal.R.drawable.ic_menu_refresh) 190 .setAlphabeticShortcut('r'); 191 refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | 192 MenuItem.SHOW_AS_ACTION_WITH_TEXT); 193 194 MenuItem batterySaver = menu.add(0, MENU_BATTERY_SAVER, 0, R.string.battery_saver); 195 batterySaver.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 196 197 String helpUrl; 198 if (!TextUtils.isEmpty(helpUrl = getResources().getString(R.string.help_url_battery))) { 199 final MenuItem help = menu.add(0, MENU_HELP, 0, R.string.help_label); 200 HelpUtils.prepareHelpMenuItem(getActivity(), help, helpUrl); 201 } 202 } 203 204 @Override 205 public boolean onOptionsItemSelected(MenuItem item) { 206 switch (item.getItemId()) { 207 case MENU_STATS_TYPE: 208 if (mStatsType == BatteryStats.STATS_SINCE_CHARGED) { 209 mStatsType = BatteryStats.STATS_SINCE_UNPLUGGED; 210 } else { 211 mStatsType = BatteryStats.STATS_SINCE_CHARGED; 212 } 213 refreshStats(); 214 return true; 215 case MENU_STATS_REFRESH: 216 mStatsHelper.clearStats(); 217 refreshStats(); 218 mHandler.removeMessages(MSG_REFRESH_STATS); 219 return true; 220 case MENU_BATTERY_SAVER: 221 final SettingsActivity sa = (SettingsActivity) getActivity(); 222 sa.startPreferencePanel(BatterySaverSettings.class.getName(), null, 223 R.string.battery_saver, null, null, 0); 224 return true; 225 default: 226 return false; 227 } 228 } 229 230 private void addNotAvailableMessage() { 231 Preference notAvailable = new Preference(getActivity()); 232 notAvailable.setTitle(R.string.power_usage_not_available); 233 mHistPref.setHideLabels(true); 234 mAppListGroup.addPreference(notAvailable); 235 } 236 237 private boolean updateBatteryStatus(Intent intent) { 238 if (intent != null) { 239 String batteryLevel = com.android.settings.Utils.getBatteryPercentage(intent); 240 String batteryStatus = com.android.settings.Utils.getBatteryStatus(getResources(), 241 intent); 242 if (!batteryLevel.equals(mBatteryLevel) || !batteryStatus.equals(mBatteryStatus)) { 243 mBatteryLevel = batteryLevel; 244 mBatteryStatus = batteryStatus; 245 return true; 246 } 247 } 248 return false; 249 } 250 251 private void refreshStats() { 252 mAppListGroup.removeAll(); 253 mAppListGroup.setOrderingAsAdded(false); 254 mHistPref = new BatteryHistoryPreference(getActivity(), mStatsHelper.getStats(), 255 mStatsHelper.getBatteryBroadcast()); 256 mHistPref.setOrder(-1); 257 mAppListGroup.addPreference(mHistPref); 258 boolean addedSome = false; 259 260 final PowerProfile powerProfile = mStatsHelper.getPowerProfile(); 261 final BatteryStats stats = mStatsHelper.getStats(); 262 final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); 263 if (averagePower >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP) { 264 final List<UserHandle> profiles = mUm.getUserProfiles(); 265 266 mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, profiles); 267 268 final List<BatterySipper> usageList = mStatsHelper.getUsageList(); 269 270 final int dischargeAmount = stats != null ? stats.getDischargeAmount(mStatsType) : 0; 271 final int numSippers = usageList.size(); 272 for (int i = 0; i < numSippers; i++) { 273 final BatterySipper sipper = usageList.get(i); 274 if ((sipper.value * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP) { 275 continue; 276 } 277 final double percentOfTotal = 278 ((sipper.value / mStatsHelper.getTotalPower()) * dischargeAmount); 279 if (((int) (percentOfTotal + .5)) < 1) { 280 continue; 281 } 282 if (sipper.drainType == BatterySipper.DrainType.OVERCOUNTED) { 283 // Don't show over-counted unless it is at least 2/3 the size of 284 // the largest real entry, and its percent of total is more significant 285 if (sipper.value < ((mStatsHelper.getMaxRealPower()*2)/3)) { 286 continue; 287 } 288 if (percentOfTotal < 10) { 289 continue; 290 } 291 if ("user".equals(Build.TYPE)) { 292 continue; 293 } 294 } 295 if (sipper.drainType == BatterySipper.DrainType.UNACCOUNTED) { 296 // Don't show over-counted unless it is at least 1/2 the size of 297 // the largest real entry, and its percent of total is more significant 298 if (sipper.value < (mStatsHelper.getMaxRealPower()/2)) { 299 continue; 300 } 301 if (percentOfTotal < 5) { 302 continue; 303 } 304 if ("user".equals(Build.TYPE)) { 305 continue; 306 } 307 } 308 final UserHandle userHandle = new UserHandle(UserHandle.getUserId(sipper.getUid())); 309 final BatteryEntry entry = new BatteryEntry(getActivity(), mHandler, mUm, sipper); 310 final Drawable badgedIcon = mUm.getBadgedIconForUser(entry.getIcon(), 311 userHandle); 312 final CharSequence contentDescription = mUm.getBadgedLabelForUser(entry.getLabel(), 313 userHandle); 314 final PowerGaugePreference pref = new PowerGaugePreference(getActivity(), 315 badgedIcon, contentDescription, entry); 316 317 final double percentOfMax = (sipper.value * 100) / mStatsHelper.getMaxPower(); 318 sipper.percent = percentOfTotal; 319 pref.setTitle(entry.getLabel()); 320 pref.setOrder(i + 1); 321 pref.setPercent(percentOfMax, percentOfTotal); 322 if (sipper.uidObj != null) { 323 pref.setKey(Integer.toString(sipper.uidObj.getUid())); 324 } 325 addedSome = true; 326 mAppListGroup.addPreference(pref); 327 if (mAppListGroup.getPreferenceCount() > (MAX_ITEMS_TO_LIST + 1)) { 328 break; 329 } 330 } 331 } 332 if (!addedSome) { 333 addNotAvailableMessage(); 334 } 335 336 BatteryEntry.startRequestQueue(); 337 } 338 339 static final int MSG_REFRESH_STATS = 100; 340 341 Handler mHandler = new Handler() { 342 343 @Override 344 public void handleMessage(Message msg) { 345 switch (msg.what) { 346 case BatteryEntry.MSG_UPDATE_NAME_ICON: 347 BatteryEntry entry = (BatteryEntry) msg.obj; 348 PowerGaugePreference pgp = 349 (PowerGaugePreference) findPreference( 350 Integer.toString(entry.sipper.uidObj.getUid())); 351 if (pgp != null) { 352 final int userId = UserHandle.getUserId(entry.sipper.getUid()); 353 final UserHandle userHandle = new UserHandle(userId); 354 pgp.setIcon(mUm.getBadgedIconForUser(entry.getIcon(), userHandle)); 355 pgp.setTitle(entry.name); 356 } 357 break; 358 case BatteryEntry.MSG_REPORT_FULLY_DRAWN: 359 Activity activity = getActivity(); 360 if (activity != null) { 361 activity.reportFullyDrawn(); 362 } 363 break; 364 case MSG_REFRESH_STATS: 365 mStatsHelper.clearStats(); 366 refreshStats(); 367 } 368 super.handleMessage(msg); 369 } 370 }; 371 } 372