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