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