• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.graphics.drawable.Drawable;
21 import android.os.BatteryStats;
22 import android.os.Build;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Message;
26 import android.os.Process;
27 import android.os.UserHandle;
28 import android.preference.Preference;
29 import android.preference.PreferenceGroup;
30 import android.preference.PreferenceScreen;
31 import android.text.TextUtils;
32 import android.util.SparseArray;
33 import android.util.TypedValue;
34 import android.view.Menu;
35 import android.view.MenuInflater;
36 import android.view.MenuItem;
37 
38 import com.android.internal.logging.MetricsLogger;
39 import com.android.internal.os.BatterySipper;
40 import com.android.internal.os.BatterySipper.DrainType;
41 import com.android.internal.os.PowerProfile;
42 import com.android.settings.HelpUtils;
43 import com.android.settings.R;
44 import com.android.settings.Settings.HighPowerApplicationsActivity;
45 import com.android.settings.SettingsActivity;
46 import com.android.settings.applications.ManageApplications;
47 
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.Comparator;
51 import java.util.List;
52 
53 /**
54  * Displays a list of apps and subsystems that consume power, ordered by how much power was
55  * consumed since the last time it was unplugged.
56  */
57 public class PowerUsageSummary extends PowerUsageBase {
58 
59     private static final boolean DEBUG = false;
60 
61     private static final boolean USE_FAKE_DATA = false;
62 
63     static final String TAG = "PowerUsageSummary";
64 
65     private static final String KEY_APP_LIST = "app_list";
66     private static final String KEY_BATTERY_HISTORY = "battery_history";
67 
68     private static final int MENU_STATS_TYPE = Menu.FIRST;
69     private static final int MENU_BATTERY_SAVER = Menu.FIRST + 2;
70     private static final int MENU_HIGH_POWER_APPS = Menu.FIRST + 3;
71     private static final int MENU_HELP = Menu.FIRST + 4;
72 
73     private BatteryHistoryPreference mHistPref;
74     private PreferenceGroup mAppListGroup;
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 = USE_FAKE_DATA ? 30 : 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     @Override
onCreate(Bundle icicle)84     public void onCreate(Bundle icicle) {
85         super.onCreate(icicle);
86 
87         addPreferencesFromResource(R.xml.power_usage_summary);
88         mHistPref = (BatteryHistoryPreference) findPreference(KEY_BATTERY_HISTORY);
89         mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST);
90     }
91 
92     @Override
getMetricsCategory()93     protected int getMetricsCategory() {
94         return MetricsLogger.FUELGAUGE_POWER_USAGE_SUMMARY;
95     }
96 
97     @Override
onResume()98     public void onResume() {
99         super.onResume();
100         refreshStats();
101     }
102 
103     @Override
onPause()104     public void onPause() {
105         BatteryEntry.stopRequestQueue();
106         mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON);
107         super.onPause();
108     }
109 
110     @Override
onDestroy()111     public void onDestroy() {
112         super.onDestroy();
113         if (getActivity().isChangingConfigurations()) {
114             BatteryEntry.clearUidCache();
115         }
116     }
117 
118     @Override
onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)119     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
120         if (!(preference instanceof PowerGaugePreference)) {
121             return false;
122         }
123         PowerGaugePreference pgp = (PowerGaugePreference) preference;
124         BatteryEntry entry = pgp.getInfo();
125         PowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), mStatsHelper,
126                 mStatsType, entry, true);
127         return super.onPreferenceTreeClick(preferenceScreen, preference);
128     }
129 
130     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)131     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
132         if (DEBUG) {
133             menu.add(0, MENU_STATS_TYPE, 0, R.string.menu_stats_total)
134                     .setIcon(com.android.internal.R.drawable.ic_menu_info_details)
135                     .setAlphabeticShortcut('t');
136         }
137 
138         MenuItem batterySaver = menu.add(0, MENU_BATTERY_SAVER, 0, R.string.battery_saver);
139         batterySaver.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
140 
141         menu.add(0, MENU_HIGH_POWER_APPS, 0, R.string.high_power_apps);
142         super.onCreateOptionsMenu(menu, inflater);
143     }
144 
145     @Override
getHelpResource()146     protected int getHelpResource() {
147         return R.string.help_url_battery;
148     }
149 
150     @Override
onOptionsItemSelected(MenuItem item)151     public boolean onOptionsItemSelected(MenuItem item) {
152         final SettingsActivity sa = (SettingsActivity) getActivity();
153         switch (item.getItemId()) {
154             case MENU_STATS_TYPE:
155                 if (mStatsType == BatteryStats.STATS_SINCE_CHARGED) {
156                     mStatsType = BatteryStats.STATS_SINCE_UNPLUGGED;
157                 } else {
158                     mStatsType = BatteryStats.STATS_SINCE_CHARGED;
159                 }
160                 refreshStats();
161                 return true;
162             case MENU_BATTERY_SAVER:
163                 sa.startPreferencePanel(BatterySaverSettings.class.getName(), null,
164                         R.string.battery_saver, null, null, 0);
165                 return true;
166             case MENU_HIGH_POWER_APPS:
167                 Bundle args = new Bundle();
168                 args.putString(ManageApplications.EXTRA_CLASSNAME,
169                         HighPowerApplicationsActivity.class.getName());
170                 sa.startPreferencePanel(ManageApplications.class.getName(), args,
171                         R.string.high_power_apps, null, null, 0);
172                 return true;
173             default:
174                 return super.onOptionsItemSelected(item);
175         }
176     }
177 
addNotAvailableMessage()178     private void addNotAvailableMessage() {
179         Preference notAvailable = new Preference(getActivity());
180         notAvailable.setTitle(R.string.power_usage_not_available);
181         mAppListGroup.addPreference(notAvailable);
182     }
183 
isSharedGid(int uid)184     private static boolean isSharedGid(int uid) {
185         return UserHandle.getAppIdFromSharedAppGid(uid) > 0;
186     }
187 
isSystemUid(int uid)188     private static boolean isSystemUid(int uid) {
189         return uid >= Process.SYSTEM_UID && uid < Process.FIRST_APPLICATION_UID;
190     }
191 
192     /**
193      * We want to coalesce some UIDs. For example, dex2oat runs under a shared gid that
194      * exists for all users of the same app. We detect this case and merge the power use
195      * for dex2oat to the device OWNER's use of the app.
196      * @return A sorted list of apps using power.
197      */
getCoalescedUsageList(final List<BatterySipper> sippers)198     private static List<BatterySipper> getCoalescedUsageList(final List<BatterySipper> sippers) {
199         final SparseArray<BatterySipper> uidList = new SparseArray<>();
200 
201         final ArrayList<BatterySipper> results = new ArrayList<>();
202         final int numSippers = sippers.size();
203         for (int i = 0; i < numSippers; i++) {
204             BatterySipper sipper = sippers.get(i);
205             if (sipper.getUid() > 0) {
206                 int realUid = sipper.getUid();
207 
208                 // Check if this UID is a shared GID. If so, we combine it with the OWNER's
209                 // actual app UID.
210                 if (isSharedGid(sipper.getUid())) {
211                     realUid = UserHandle.getUid(UserHandle.USER_OWNER,
212                             UserHandle.getAppIdFromSharedAppGid(sipper.getUid()));
213                 }
214 
215                 // Check if this UID is a system UID (mediaserver, logd, nfc, drm, etc).
216                 if (isSystemUid(realUid)
217                         && !"mediaserver".equals(sipper.packageWithHighestDrain)) {
218                     // Use the system UID for all UIDs running in their own sandbox that
219                     // are not apps. We exclude mediaserver because we already are expected to
220                     // report that as a separate item.
221                     realUid = Process.SYSTEM_UID;
222                 }
223 
224                 if (realUid != sipper.getUid()) {
225                     // Replace the BatterySipper with a new one with the real UID set.
226                     BatterySipper newSipper = new BatterySipper(sipper.drainType,
227                             new FakeUid(realUid), 0.0);
228                     newSipper.add(sipper);
229                     newSipper.packageWithHighestDrain = sipper.packageWithHighestDrain;
230                     newSipper.mPackages = sipper.mPackages;
231                     sipper = newSipper;
232                 }
233 
234                 int index = uidList.indexOfKey(realUid);
235                 if (index < 0) {
236                     // New entry.
237                     uidList.put(realUid, sipper);
238                 } else {
239                     // Combine BatterySippers if we already have one with this UID.
240                     final BatterySipper existingSipper = uidList.valueAt(index);
241                     existingSipper.add(sipper);
242                     if (existingSipper.packageWithHighestDrain == null
243                             && sipper.packageWithHighestDrain != null) {
244                         existingSipper.packageWithHighestDrain = sipper.packageWithHighestDrain;
245                     }
246 
247                     final int existingPackageLen = existingSipper.mPackages != null ?
248                             existingSipper.mPackages.length : 0;
249                     final int newPackageLen = sipper.mPackages != null ?
250                             sipper.mPackages.length : 0;
251                     if (newPackageLen > 0) {
252                         String[] newPackages = new String[existingPackageLen + newPackageLen];
253                         if (existingPackageLen > 0) {
254                             System.arraycopy(existingSipper.mPackages, 0, newPackages, 0,
255                                     existingPackageLen);
256                         }
257                         System.arraycopy(sipper.mPackages, 0, newPackages, existingPackageLen,
258                                 newPackageLen);
259                         existingSipper.mPackages = newPackages;
260                     }
261                 }
262             } else {
263                 results.add(sipper);
264             }
265         }
266 
267         final int numUidSippers = uidList.size();
268         for (int i = 0; i < numUidSippers; i++) {
269             results.add(uidList.valueAt(i));
270         }
271 
272         // The sort order must have changed, so re-sort based on total power use.
273         Collections.sort(results, new Comparator<BatterySipper>() {
274             @Override
275             public int compare(BatterySipper a, BatterySipper b) {
276                 return Double.compare(b.totalPowerMah, a.totalPowerMah);
277             }
278         });
279         return results;
280     }
281 
refreshStats()282     protected void refreshStats() {
283         super.refreshStats();
284         updatePreference(mHistPref);
285         mAppListGroup.removeAll();
286         mAppListGroup.setOrderingAsAdded(false);
287         boolean addedSome = false;
288 
289         final PowerProfile powerProfile = mStatsHelper.getPowerProfile();
290         final BatteryStats stats = mStatsHelper.getStats();
291         final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
292 
293         TypedValue value = new TypedValue();
294         getContext().getTheme().resolveAttribute(android.R.attr.colorControlNormal, value, true);
295         int colorControl = getContext().getColor(value.resourceId);
296 
297         if (averagePower >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP || USE_FAKE_DATA) {
298             final List<BatterySipper> usageList = getCoalescedUsageList(
299                     USE_FAKE_DATA ? getFakeStats() : mStatsHelper.getUsageList());
300 
301             final int dischargeAmount = USE_FAKE_DATA ? 5000
302                     : stats != null ? stats.getDischargeAmount(mStatsType) : 0;
303             final int numSippers = usageList.size();
304             for (int i = 0; i < numSippers; i++) {
305                 final BatterySipper sipper = usageList.get(i);
306                 if ((sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP) {
307                     continue;
308                 }
309                 double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower();
310                 final double percentOfTotal =
311                         ((sipper.totalPowerMah / totalPower) * dischargeAmount);
312                 if (((int) (percentOfTotal + .5)) < 1) {
313                     continue;
314                 }
315                 if (sipper.drainType == BatterySipper.DrainType.OVERCOUNTED) {
316                     // Don't show over-counted unless it is at least 2/3 the size of
317                     // the largest real entry, and its percent of total is more significant
318                     if (sipper.totalPowerMah < ((mStatsHelper.getMaxRealPower()*2)/3)) {
319                         continue;
320                     }
321                     if (percentOfTotal < 10) {
322                         continue;
323                     }
324                     if ("user".equals(Build.TYPE)) {
325                         continue;
326                     }
327                 }
328                 if (sipper.drainType == BatterySipper.DrainType.UNACCOUNTED) {
329                     // Don't show over-counted unless it is at least 1/2 the size of
330                     // the largest real entry, and its percent of total is more significant
331                     if (sipper.totalPowerMah < (mStatsHelper.getMaxRealPower()/2)) {
332                         continue;
333                     }
334                     if (percentOfTotal < 5) {
335                         continue;
336                     }
337                     if ("user".equals(Build.TYPE)) {
338                         continue;
339                     }
340                 }
341                 final UserHandle userHandle = new UserHandle(UserHandle.getUserId(sipper.getUid()));
342                 final BatteryEntry entry = new BatteryEntry(getActivity(), mHandler, mUm, sipper);
343                 final Drawable badgedIcon = mUm.getBadgedIconForUser(entry.getIcon(),
344                         userHandle);
345                 final CharSequence contentDescription = mUm.getBadgedLabelForUser(entry.getLabel(),
346                         userHandle);
347                 final PowerGaugePreference pref = new PowerGaugePreference(getActivity(),
348                         badgedIcon, contentDescription, entry);
349 
350                 final double percentOfMax = (sipper.totalPowerMah * 100)
351                         / mStatsHelper.getMaxPower();
352                 sipper.percent = percentOfTotal;
353                 pref.setTitle(entry.getLabel());
354                 pref.setOrder(i + 1);
355                 pref.setPercent(percentOfMax, percentOfTotal);
356                 if (sipper.uidObj != null) {
357                     pref.setKey(Integer.toString(sipper.uidObj.getUid()));
358                 }
359                 if ((sipper.drainType != DrainType.APP || sipper.uidObj.getUid() == 0)
360                          && sipper.drainType != DrainType.USER) {
361                     pref.setTint(colorControl);
362                 }
363                 addedSome = true;
364                 mAppListGroup.addPreference(pref);
365                 if (mAppListGroup.getPreferenceCount() > (MAX_ITEMS_TO_LIST + 1)) {
366                     break;
367                 }
368             }
369         }
370         if (!addedSome) {
371             addNotAvailableMessage();
372         }
373 
374         BatteryEntry.startRequestQueue();
375     }
376 
getFakeStats()377     private static List<BatterySipper> getFakeStats() {
378         ArrayList<BatterySipper> stats = new ArrayList<>();
379         float use = 5;
380         for (DrainType type : DrainType.values()) {
381             if (type == DrainType.APP) {
382                 continue;
383             }
384             stats.add(new BatterySipper(type, null, use));
385             use += 5;
386         }
387         stats.add(new BatterySipper(DrainType.APP,
388                 new FakeUid(Process.FIRST_APPLICATION_UID), use));
389         stats.add(new BatterySipper(DrainType.APP,
390                 new FakeUid(0), use));
391 
392         // Simulate dex2oat process.
393         BatterySipper sipper = new BatterySipper(DrainType.APP,
394                 new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID)), 10.0f);
395         sipper.packageWithHighestDrain = "dex2oat";
396         stats.add(sipper);
397 
398         sipper = new BatterySipper(DrainType.APP,
399                 new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID + 1)), 10.0f);
400         sipper.packageWithHighestDrain = "dex2oat";
401         stats.add(sipper);
402 
403         sipper = new BatterySipper(DrainType.APP,
404                 new FakeUid(UserHandle.getSharedAppGid(Process.LOG_UID)), 9.0f);
405         stats.add(sipper);
406 
407         return stats;
408     }
409 
410     Handler mHandler = new Handler() {
411 
412         @Override
413         public void handleMessage(Message msg) {
414             switch (msg.what) {
415                 case BatteryEntry.MSG_UPDATE_NAME_ICON:
416                     BatteryEntry entry = (BatteryEntry) msg.obj;
417                     PowerGaugePreference pgp =
418                             (PowerGaugePreference) findPreference(
419                                     Integer.toString(entry.sipper.uidObj.getUid()));
420                     if (pgp != null) {
421                         final int userId = UserHandle.getUserId(entry.sipper.getUid());
422                         final UserHandle userHandle = new UserHandle(userId);
423                         pgp.setIcon(mUm.getBadgedIconForUser(entry.getIcon(), userHandle));
424                         pgp.setTitle(entry.name);
425                     }
426                     break;
427                 case BatteryEntry.MSG_REPORT_FULLY_DRAWN:
428                     Activity activity = getActivity();
429                     if (activity != null) {
430                         activity.reportFullyDrawn();
431                     }
432                     break;
433             }
434             super.handleMessage(msg);
435         }
436     };
437 }
438