Home | History | Annotate | Download | only in fuelgauge
      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 
     17 package com.android.settings.fuelgauge;
     18 
     19 import static com.android.settings.Utils.prepareCustomPreferencesList;
     20 
     21 import android.app.Activity;
     22 import android.app.ActivityManager;
     23 import android.app.ApplicationErrorReport;
     24 import android.app.Fragment;
     25 import android.app.admin.DevicePolicyManager;
     26 import android.content.BroadcastReceiver;
     27 import android.content.ComponentName;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.content.pm.ApplicationInfo;
     31 import android.content.pm.PackageInfo;
     32 import android.content.pm.PackageManager;
     33 import android.content.pm.PackageManager.NameNotFoundException;
     34 import android.content.res.Resources;
     35 import android.graphics.drawable.Drawable;
     36 import android.net.Uri;
     37 import android.os.Bundle;
     38 import android.os.Process;
     39 import android.os.UserHandle;
     40 import android.preference.PreferenceActivity;
     41 import android.provider.Settings;
     42 import android.text.TextUtils;
     43 import android.text.format.Formatter;
     44 import android.view.LayoutInflater;
     45 import android.view.View;
     46 import android.view.ViewGroup;
     47 import android.widget.Button;
     48 import android.widget.ImageView;
     49 import android.widget.ProgressBar;
     50 import android.widget.TextView;
     51 
     52 import com.android.settings.DisplaySettings;
     53 import com.android.settings.R;
     54 import com.android.settings.WirelessSettings;
     55 import com.android.settings.applications.InstalledAppDetails;
     56 import com.android.settings.bluetooth.BluetoothSettings;
     57 import com.android.settings.location.LocationSettings;
     58 import com.android.settings.wifi.WifiSettings;
     59 
     60 public 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