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 onCreate(Bundle icicle)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 onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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 onResume()160 public void onResume() { 161 super.onResume(); 162 mStartTime = android.os.Process.getElapsedCpuTime(); 163 checkForceStop(); 164 } 165 166 @Override onPause()167 public void onPause() { 168 super.onPause(); 169 } 170 createDetails()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 onClick(View v)262 public void onClick(View v) { 263 doAction((Integer) v.getTag()); 264 } 265 266 // utility method used to start sub activity startApplicationDetailsActivity()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 doAction(int action)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 fillDetailsSection()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 fillControlsSection(int uid)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 addControl(int title, int summary, int action)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 removePackagesSection()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 killProcesses()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 checkForceStop()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 reportBatteryUse()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 fillPackagesSection(int uid)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 getDescriptionForDrainType()544 private String getDescriptionForDrainType() { 545 return getResources().getString(sDrainTypeDesciptions[mDrainType.ordinal()]); 546 } 547 } 548