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