OSDN Git Service

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