Home | History | Annotate | Download | only in fuelgauge
      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