PowerUsageDetail.java revision 59a48604b5a803fbec6857e07f9fa1adbc6b8bb4
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
17package com.android.settings.fuelgauge;
18
19import android.app.Activity;
20import android.app.ActivityManager;
21import android.app.ApplicationErrorReport;
22import android.app.Fragment;
23import android.content.BroadcastReceiver;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.ApplicationInfo;
28import android.content.pm.PackageInfo;
29import android.content.pm.PackageManager;
30import android.content.pm.PackageManager.NameNotFoundException;
31import android.content.res.Resources;
32import android.graphics.drawable.Drawable;
33import android.net.Uri;
34import android.os.Bundle;
35import android.os.Process;
36import android.preference.PreferenceActivity;
37import android.provider.Settings;
38import android.text.TextUtils;
39import android.view.LayoutInflater;
40import android.view.View;
41import android.view.ViewGroup;
42import android.widget.Button;
43import android.widget.ImageView;
44import android.widget.TextView;
45import com.android.settings.R;
46import com.android.settings.applications.InstalledAppDetails;
47
48public class PowerUsageDetail extends Fragment implements Button.OnClickListener {
49
50    enum DrainType {
51        IDLE,
52        CELL,
53        PHONE,
54        WIFI,
55        BLUETOOTH,
56        SCREEN,
57        APP
58    }
59
60    // Note: Must match the sequence of the DrainType
61    private static int[] sDrainTypeDesciptions = new int[] {
62        R.string.battery_desc_standby,
63        R.string.battery_desc_radio,
64        R.string.battery_desc_voice,
65        R.string.battery_desc_wifi,
66        R.string.battery_desc_bluetooth,
67        R.string.battery_desc_display,
68        R.string.battery_desc_apps
69    };
70
71    public static final int ACTION_DISPLAY_SETTINGS = 1;
72    public static final int ACTION_WIFI_SETTINGS = 2;
73    public static final int ACTION_BLUETOOTH_SETTINGS = 3;
74    public static final int ACTION_WIRELESS_SETTINGS = 4;
75    public static final int ACTION_APP_DETAILS = 5;
76    public static final int ACTION_SECURITY_SETTINGS = 6;
77    public static final int ACTION_FORCE_STOP = 7;
78    public static final int ACTION_REPORT = 8;
79
80    public static final int USAGE_SINCE_UNPLUGGED = 1;
81    public static final int USAGE_SINCE_RESET = 2;
82
83    public static final String EXTRA_TITLE = "title";
84    public static final String EXTRA_PERCENT = "percent";
85    public static final String EXTRA_GAUGE = "gauge";
86    public static final String EXTRA_UID = "uid";
87    public static final String EXTRA_USAGE_SINCE = "since";
88    public static final String EXTRA_USAGE_DURATION = "duration";
89    public static final String EXTRA_REPORT_DETAILS = "report_details";
90    public static final String EXTRA_REPORT_CHECKIN_DETAILS = "report_checkin_details";
91    public static final String EXTRA_DETAIL_TYPES = "types"; // Array of usage types (cpu, gps, etc)
92    public static final String EXTRA_DETAIL_VALUES = "values"; // Array of doubles
93    public static final String EXTRA_DRAIN_TYPE = "drainType"; // DrainType
94    public static final String EXTRA_ICON_PACKAGE = "iconPackage"; // String
95    public static final String EXTRA_NO_COVERAGE = "noCoverage";
96    public static final String EXTRA_ICON_ID = "iconId"; // Int
97
98    private static final boolean DEBUG = true;
99    private String mTitle;
100    private int mUsageSince;
101    private int[] mTypes;
102    private int mUid;
103    private double[] mValues;
104    private View mRootView;
105    private TextView mTitleView;
106    private ViewGroup mTwoButtonsPanel;
107    private Button mForceStopButton;
108    private Button mReportButton;
109    private ViewGroup mDetailsParent;
110    private ViewGroup mControlsParent;
111    private long mStartTime;
112    private DrainType mDrainType;
113    private PercentageBar mGauge;
114    private Drawable mAppIcon;
115    private double mNoCoverage; // Percentage of time that there was no coverage
116
117    private boolean mUsesGps;
118
119    private static final String TAG = "PowerUsageDetail";
120    private String[] mPackages;
121
122    ApplicationInfo mApp;
123    ComponentName mInstaller;
124
125    @Override
126    public void onCreate(Bundle icicle) {
127        super.onCreate(icicle);
128    }
129
130    @Override
131    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
132        View view = mRootView = inflater.inflate(R.layout.power_usage_details, null);
133        createDetails();
134        return view;
135    }
136
137    @Override
138    public void onResume() {
139        super.onResume();
140        mStartTime = android.os.Process.getElapsedCpuTime();
141        checkForceStop();
142    }
143
144    @Override
145    public void onPause() {
146        super.onPause();
147    }
148
149    private void createDetails() {
150        final Bundle args = getArguments();
151        mTitle = args.getString(EXTRA_TITLE);
152        final int percentage = args.getInt(EXTRA_PERCENT, 1);
153        final int gaugeValue = args.getInt(EXTRA_GAUGE, 1);
154        mUsageSince = args.getInt(EXTRA_USAGE_SINCE, USAGE_SINCE_UNPLUGGED);
155        mUid = args.getInt(EXTRA_UID, 0);
156        mDrainType = (DrainType) args.getSerializable(EXTRA_DRAIN_TYPE);
157        mNoCoverage = args.getDouble(EXTRA_NO_COVERAGE, 0);
158        String iconPackage = args.getString(EXTRA_ICON_PACKAGE);
159        int iconId = args.getInt(EXTRA_ICON_ID, 0);
160        if (!TextUtils.isEmpty(iconPackage)) {
161            try {
162                final PackageManager pm = getActivity().getPackageManager();
163                ApplicationInfo ai = pm.getPackageInfo(iconPackage, 0).applicationInfo;
164                if (ai != null) {
165                    mAppIcon = ai.loadIcon(pm);
166                }
167            } catch (NameNotFoundException nnfe) {
168                // Use default icon
169            }
170        } else if (iconId != 0) {
171            mAppIcon = getActivity().getResources().getDrawable(iconId);
172        }
173        if (mAppIcon == null) {
174            mAppIcon = getActivity().getPackageManager().getDefaultActivityIcon();
175        }
176
177        // Set the description
178        String summary = getDescriptionForDrainType();
179        ((TextView)mRootView.findViewById(R.id.summary)).setText(summary);
180
181        mTypes = args.getIntArray(EXTRA_DETAIL_TYPES);
182        mValues = args.getDoubleArray(EXTRA_DETAIL_VALUES);
183
184        mTitleView = (TextView)mRootView.findViewById(R.id.name);
185        mTitleView.setText(mTitle);
186        ((TextView)mRootView.findViewById(R.id.battery_percentage))
187            .setText(String.format("%d%%", percentage));
188
189        mTwoButtonsPanel = (ViewGroup)mRootView.findViewById(R.id.two_buttons_panel);
190        mForceStopButton = (Button)mRootView.findViewById(R.id.left_button);
191        mReportButton = (Button)mRootView.findViewById(R.id.right_button);
192        mForceStopButton.setEnabled(false);
193
194        ImageView gaugeImage = (ImageView)mRootView.findViewById(R.id.gauge);
195        mGauge = new PercentageBar();
196        mGauge.percent = gaugeValue;
197        mGauge.bar = getResources().getDrawable(R.drawable.app_gauge);
198        gaugeImage.setImageDrawable(mGauge);
199
200        ImageView iconImage = (ImageView)mRootView.findViewById(R.id.icon);
201        iconImage.setImageDrawable(mAppIcon);
202
203        mDetailsParent = (ViewGroup)mRootView.findViewById(R.id.details);
204        mControlsParent = (ViewGroup)mRootView.findViewById(R.id.controls);
205
206        fillDetailsSection();
207        fillPackagesSection(mUid);
208        fillControlsSection(mUid);
209
210        if (mUid >= Process.FIRST_APPLICATION_UID) {
211            mForceStopButton.setText(R.string.force_stop);
212            mForceStopButton.setTag(ACTION_FORCE_STOP);
213            mForceStopButton.setOnClickListener(this);
214            mReportButton.setText(com.android.internal.R.string.report);
215            mReportButton.setTag(ACTION_REPORT);
216            mReportButton.setOnClickListener(this);
217
218            // check if error reporting is enabled in secure settings
219            int enabled = Settings.Secure.getInt(getActivity().getContentResolver(),
220                    Settings.Secure.SEND_ACTION_APP_ERROR, 0);
221            if (enabled != 0) {
222                if (mPackages != null && mPackages.length > 0) {
223                    try {
224                        mApp = getActivity().getPackageManager().getApplicationInfo(
225                                mPackages[0], 0);
226                        mInstaller = ApplicationErrorReport.getErrorReportReceiver(
227                                getActivity(), mPackages[0], mApp.flags);
228                    } catch (NameNotFoundException e) {
229                    }
230                }
231                mReportButton.setEnabled(mInstaller != null);
232            } else {
233                mTwoButtonsPanel.setVisibility(View.GONE);
234            }
235        } else {
236            mTwoButtonsPanel.setVisibility(View.GONE);
237        }
238    }
239
240    public void onClick(View v) {
241        doAction((Integer) v.getTag());
242    }
243
244    // utility method used to start sub activity
245    private void startApplicationDetailsActivity() {
246        // start new fragment to display extended information
247        Bundle args = new Bundle();
248        args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackages[0]);
249
250        PreferenceActivity pa = (PreferenceActivity)getActivity();
251        pa.startPreferencePanel(InstalledAppDetails.class.getName(), args,
252                R.string.application_info_label, null, null, 0);
253    }
254
255    private void doAction(int action) {
256        switch (action) {
257            case ACTION_DISPLAY_SETTINGS:
258                startActivity(new Intent(Settings.ACTION_DISPLAY_SETTINGS));
259                break;
260            case ACTION_WIFI_SETTINGS:
261                startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
262                break;
263            case ACTION_BLUETOOTH_SETTINGS:
264                startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
265                break;
266            case ACTION_WIRELESS_SETTINGS:
267                startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
268                break;
269            case ACTION_APP_DETAILS:
270                startApplicationDetailsActivity();
271                break;
272            case ACTION_SECURITY_SETTINGS:
273                startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
274                break;
275            case ACTION_FORCE_STOP:
276                killProcesses();
277                break;
278            case ACTION_REPORT:
279                reportBatteryUse();
280                break;
281        }
282    }
283
284    private void fillDetailsSection() {
285        LayoutInflater inflater = getActivity().getLayoutInflater();
286        if (mTypes != null && mValues != null) {
287            for (int i = 0; i < mTypes.length; i++) {
288                // Only add an item if the time is greater than zero
289                if (mValues[i] <= 0) continue;
290                final String label = getString(mTypes[i]);
291                String value = null;
292                switch (mTypes[i]) {
293                    case R.string.usage_type_data_recv:
294                    case R.string.usage_type_data_send:
295                        value = Utils.formatBytes(getActivity(), mValues[i]);
296                        break;
297                    case R.string.usage_type_no_coverage:
298                        value = String.format("%d%%", (int) Math.floor(mValues[i]));
299                        break;
300                    case R.string.usage_type_gps:
301                        mUsesGps = true;
302                        // Fall through
303                    default:
304                        value = Utils.formatElapsedTime(getActivity(), mValues[i]);
305                }
306                ViewGroup item = (ViewGroup) inflater.inflate(R.layout.power_usage_detail_item_text,
307                        null);
308                mDetailsParent.addView(item);
309                TextView labelView = (TextView) item.findViewById(R.id.label);
310                TextView valueView = (TextView) item.findViewById(R.id.value);
311                labelView.setText(label);
312                valueView.setText(value);
313            }
314        }
315    }
316
317    private void fillControlsSection(int uid) {
318        PackageManager pm = getActivity().getPackageManager();
319        String[] packages = pm.getPackagesForUid(uid);
320        PackageInfo pi = null;
321        try {
322            pi = packages != null ? pm.getPackageInfo(packages[0], 0) : null;
323        } catch (NameNotFoundException nnfe) { /* Nothing */ }
324        ApplicationInfo ai = pi != null? pi.applicationInfo : null;
325        boolean isSystem = ai != null? (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0 : false;
326
327        boolean removeHeader = true;
328        switch (mDrainType) {
329            case APP:
330                // If it is a Java application and it's not a system application
331                if (packages != null && !isSystem) {
332                    addControl(R.string.battery_action_app_details,
333                            R.string.battery_sugg_apps_info, ACTION_APP_DETAILS);
334                    removeHeader = false;
335                    // If the application has a settings screen, jump to  that
336                    // TODO:
337                }
338                if (mUsesGps) {
339                    addControl(R.string.security_settings_title,
340                            R.string.battery_sugg_apps_gps, ACTION_SECURITY_SETTINGS);
341                    removeHeader = false;
342                }
343                break;
344            case SCREEN:
345                addControl(R.string.display_settings,
346                        R.string.battery_sugg_display,
347                        ACTION_DISPLAY_SETTINGS);
348                removeHeader = false;
349                break;
350            case WIFI:
351                addControl(R.string.wifi_settings,
352                        R.string.battery_sugg_wifi,
353                        ACTION_WIFI_SETTINGS);
354                removeHeader = false;
355                break;
356            case BLUETOOTH:
357                addControl(R.string.bluetooth_settings,
358                        R.string.battery_sugg_bluetooth_basic,
359                        ACTION_BLUETOOTH_SETTINGS);
360                removeHeader = false;
361                break;
362            case CELL:
363                if (mNoCoverage > 10) {
364                    addControl(R.string.radio_controls_title,
365                            R.string.battery_sugg_radio,
366                            ACTION_WIRELESS_SETTINGS);
367                    removeHeader = false;
368                }
369                break;
370        }
371        if (removeHeader) {
372            mControlsParent.setVisibility(View.GONE);
373        }
374    }
375
376    private void addControl(int title, int summary, int action) {
377        final Resources res = getResources();
378        LayoutInflater inflater = getActivity().getLayoutInflater();
379        ViewGroup item = (ViewGroup) inflater.inflate(R.layout.power_usage_action_item,null);
380        mControlsParent.addView(item);
381        Button actionButton = (Button) item.findViewById(R.id.action_button);
382        TextView summaryView = (TextView) item.findViewById(R.id.summary);
383        actionButton.setText(res.getString(title));
384        summaryView.setText(res.getString(summary));
385        actionButton.setOnClickListener(this);
386        actionButton.setTag(new Integer(action));
387    }
388
389    private void removePackagesSection() {
390        View view;
391        if ((view = mRootView.findViewById(R.id.packages_section_title)) != null) {
392            view.setVisibility(View.GONE);
393        }
394        if ((view = mRootView.findViewById(R.id.packages_section)) != null) {
395            view.setVisibility(View.GONE);
396        }
397    }
398
399    private void killProcesses() {
400        if (mPackages == null) return;
401        ActivityManager am = (ActivityManager)getActivity().getSystemService(
402                Context.ACTIVITY_SERVICE);
403        for (int i = 0; i < mPackages.length; i++) {
404            am.forceStopPackage(mPackages[i]);
405        }
406        checkForceStop();
407    }
408
409    private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
410        @Override
411        public void onReceive(Context context, Intent intent) {
412            mForceStopButton.setEnabled(getResultCode() != Activity.RESULT_CANCELED);
413        }
414    };
415
416    private void checkForceStop() {
417        if (mPackages == null || mUid < Process.FIRST_APPLICATION_UID) {
418            mForceStopButton.setEnabled(false);
419            return;
420        }
421        Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
422                Uri.fromParts("package", mPackages[0], null));
423        intent.putExtra(Intent.EXTRA_PACKAGES, mPackages);
424        intent.putExtra(Intent.EXTRA_UID, mUid);
425        getActivity().sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null,
426                Activity.RESULT_CANCELED, null, null);
427    }
428
429    private void reportBatteryUse() {
430        if (mPackages == null) return;
431
432        ApplicationErrorReport report = new ApplicationErrorReport();
433        report.type = ApplicationErrorReport.TYPE_BATTERY;
434        report.packageName = mPackages[0];
435        report.installerPackageName = mInstaller.getPackageName();
436        report.processName = mPackages[0];
437        report.time = System.currentTimeMillis();
438        report.systemApp = (mApp.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
439
440        final Bundle args = getArguments();
441        ApplicationErrorReport.BatteryInfo batteryInfo = new ApplicationErrorReport.BatteryInfo();
442        batteryInfo.usagePercent = args.getInt(EXTRA_PERCENT, 1);
443        batteryInfo.durationMicros = args.getLong(EXTRA_USAGE_DURATION, 0);
444        batteryInfo.usageDetails = args.getString(EXTRA_REPORT_DETAILS);
445        batteryInfo.checkinDetails = args.getString(EXTRA_REPORT_CHECKIN_DETAILS);
446        report.batteryInfo = batteryInfo;
447
448        Intent result = new Intent(Intent.ACTION_APP_ERROR);
449        result.setComponent(mInstaller);
450        result.putExtra(Intent.EXTRA_BUG_REPORT, report);
451        result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
452        startActivity(result);
453    }
454
455    private void fillPackagesSection(int uid) {
456        if (uid < 1) {
457            removePackagesSection();
458            return;
459        }
460        ViewGroup packagesParent = (ViewGroup)mRootView.findViewById(R.id.packages_section);
461        if (packagesParent == null) return;
462        LayoutInflater inflater = getActivity().getLayoutInflater();
463
464        PackageManager pm = getActivity().getPackageManager();
465        //final Drawable defaultActivityIcon = pm.getDefaultActivityIcon();
466        mPackages = pm.getPackagesForUid(uid);
467        if (mPackages == null || mPackages.length < 2) {
468            removePackagesSection();
469            return;
470        }
471
472        // Convert package names to user-facing labels where possible
473        for (int i = 0; i < mPackages.length; i++) {
474            try {
475                ApplicationInfo ai = pm.getApplicationInfo(mPackages[i], 0);
476                CharSequence label = ai.loadLabel(pm);
477                //Drawable icon = defaultActivityIcon;
478                if (label != null) {
479                    mPackages[i] = label.toString();
480                }
481                //if (ai.icon != 0) {
482                //    icon = ai.loadIcon(pm);
483                //}
484                ViewGroup item = (ViewGroup) inflater.inflate(R.layout.power_usage_package_item,
485                        null);
486                packagesParent.addView(item);
487                TextView labelView = (TextView) item.findViewById(R.id.label);
488                labelView.setText(mPackages[i]);
489            } catch (NameNotFoundException e) {
490            }
491        }
492    }
493
494    private String getDescriptionForDrainType() {
495        return getResources().getString(sDrainTypeDesciptions[mDrainType.ordinal()]);
496    }
497}
498