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