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