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