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