RunningServiceDetails.java revision 9fae74f6c37b60d10a2bddb214c28e7f9f7484f3
1package com.android.settings.applications; 2 3import com.android.settings.R; 4 5import android.app.Activity; 6import android.app.ActivityManager; 7import android.app.AlertDialog; 8import android.app.ApplicationErrorReport; 9import android.app.Dialog; 10import android.app.DialogFragment; 11import android.app.Fragment; 12import android.app.PendingIntent; 13import android.content.ActivityNotFoundException; 14import android.content.ComponentName; 15import android.content.Context; 16import android.content.DialogInterface; 17import android.content.Intent; 18import android.content.IntentSender; 19import android.content.pm.ApplicationInfo; 20import android.content.pm.PackageManager; 21import android.content.pm.ProviderInfo; 22import android.content.pm.ServiceInfo; 23import android.content.pm.PackageManager.NameNotFoundException; 24import android.content.res.Resources; 25import android.os.Bundle; 26import android.os.Debug; 27import android.os.Handler; 28import android.os.SystemClock; 29import android.os.UserHandle; 30import android.provider.Settings; 31import android.util.Log; 32import android.view.LayoutInflater; 33import android.view.View; 34import android.view.ViewGroup; 35import android.widget.Button; 36import android.widget.TextView; 37 38import java.io.File; 39import java.io.FileInputStream; 40import java.io.FileOutputStream; 41import java.io.IOException; 42import java.util.ArrayList; 43import java.util.Collections; 44 45public class RunningServiceDetails extends Fragment 46 implements RunningState.OnRefreshUiListener { 47 static final String TAG = "RunningServicesDetails"; 48 49 static final String KEY_UID = "uid"; 50 static final String KEY_USER_ID = "user_id"; 51 static final String KEY_PROCESS = "process"; 52 static final String KEY_BACKGROUND = "background"; 53 54 static final int DIALOG_CONFIRM_STOP = 1; 55 56 ActivityManager mAm; 57 LayoutInflater mInflater; 58 59 RunningState mState; 60 boolean mHaveData; 61 62 int mUid; 63 int mUserId; 64 String mProcessName; 65 boolean mShowBackground; 66 67 RunningState.MergedItem mMergedItem; 68 69 View mRootView; 70 ViewGroup mAllDetails; 71 ViewGroup mSnippet; 72 RunningProcessesView.ActiveItem mSnippetActiveItem; 73 RunningProcessesView.ViewHolder mSnippetViewHolder; 74 75 int mNumServices, mNumProcesses; 76 77 TextView mServicesHeader; 78 TextView mProcessesHeader; 79 final ArrayList<ActiveDetail> mActiveDetails = new ArrayList<ActiveDetail>(); 80 81 class ActiveDetail implements View.OnClickListener { 82 View mRootView; 83 Button mStopButton; 84 Button mReportButton; 85 RunningState.ServiceItem mServiceItem; 86 RunningProcessesView.ActiveItem mActiveItem; 87 RunningProcessesView.ViewHolder mViewHolder; 88 PendingIntent mManageIntent; 89 ComponentName mInstaller; 90 91 void stopActiveService(boolean confirmed) { 92 RunningState.ServiceItem si = mServiceItem; 93 if (!confirmed) { 94 if ((si.mServiceInfo.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) { 95 showConfirmStopDialog(si.mRunningService.service); 96 return; 97 } 98 } 99 getActivity().stopService(new Intent().setComponent(si.mRunningService.service)); 100 if (mMergedItem == null) { 101 // If this is gone, we are gone. 102 mState.updateNow(); 103 finish(); 104 } else if (!mShowBackground && mMergedItem.mServices.size() <= 1) { 105 // If there was only one service, we are finishing it, 106 // so no reason for the UI to stick around. 107 mState.updateNow(); 108 finish(); 109 } else { 110 mState.updateNow(); 111 } 112 } 113 114 public void onClick(View v) { 115 if (v == mReportButton) { 116 ApplicationErrorReport report = new ApplicationErrorReport(); 117 report.type = ApplicationErrorReport.TYPE_RUNNING_SERVICE; 118 report.packageName = mServiceItem.mServiceInfo.packageName; 119 report.installerPackageName = mInstaller.getPackageName(); 120 report.processName = mServiceItem.mRunningService.process; 121 report.time = System.currentTimeMillis(); 122 report.systemApp = (mServiceItem.mServiceInfo.applicationInfo.flags 123 & ApplicationInfo.FLAG_SYSTEM) != 0; 124 ApplicationErrorReport.RunningServiceInfo info 125 = new ApplicationErrorReport.RunningServiceInfo(); 126 if (mActiveItem.mFirstRunTime >= 0) { 127 info.durationMillis = SystemClock.elapsedRealtime()-mActiveItem.mFirstRunTime; 128 } else { 129 info.durationMillis = -1; 130 } 131 ComponentName comp = new ComponentName(mServiceItem.mServiceInfo.packageName, 132 mServiceItem.mServiceInfo.name); 133 File filename = getActivity().getFileStreamPath("service_dump.txt"); 134 FileOutputStream output = null; 135 try { 136 output = new FileOutputStream(filename); 137 Debug.dumpService("activity", output.getFD(), 138 new String[] { "-a", "service", comp.flattenToString() }); 139 } catch (IOException e) { 140 Log.w(TAG, "Can't dump service: " + comp, e); 141 } finally { 142 if (output != null) try { output.close(); } catch (IOException e) {} 143 } 144 FileInputStream input = null; 145 try { 146 input = new FileInputStream(filename); 147 byte[] buffer = new byte[(int) filename.length()]; 148 input.read(buffer); 149 info.serviceDetails = new String(buffer); 150 } catch (IOException e) { 151 Log.w(TAG, "Can't read service dump: " + comp, e); 152 } finally { 153 if (input != null) try { input.close(); } catch (IOException e) {} 154 } 155 filename.delete(); 156 Log.i(TAG, "Details: " + info.serviceDetails); 157 report.runningServiceInfo = info; 158 Intent result = new Intent(Intent.ACTION_APP_ERROR); 159 result.setComponent(mInstaller); 160 result.putExtra(Intent.EXTRA_BUG_REPORT, report); 161 result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 162 startActivity(result); 163 return; 164 } 165 166 if (mManageIntent != null) { 167 try { 168 getActivity().startIntentSender(mManageIntent.getIntentSender(), null, 169 Intent.FLAG_ACTIVITY_NEW_TASK 170 | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET, 171 Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET, 0); 172 } catch (IntentSender.SendIntentException e) { 173 Log.w(TAG, e); 174 } catch (IllegalArgumentException e) { 175 Log.w(TAG, e); 176 } catch (ActivityNotFoundException e) { 177 Log.w(TAG, e); 178 } 179 } else if (mServiceItem != null) { 180 stopActiveService(false); 181 } else if (mActiveItem.mItem.mBackground) { 182 // Background process. Just kill it. 183 mAm.killBackgroundProcesses(mActiveItem.mItem.mPackageInfo.packageName); 184 finish(); 185 } else { 186 // Heavy-weight process. We'll do a force-stop on it. 187 mAm.forceStopPackage(mActiveItem.mItem.mPackageInfo.packageName); 188 finish(); 189 } 190 } 191 } 192 193 StringBuilder mBuilder = new StringBuilder(128); 194 195 boolean findMergedItem() { 196 RunningState.MergedItem item = null; 197 ArrayList<RunningState.MergedItem> newItems = mShowBackground 198 ? mState.getCurrentBackgroundItems() : mState.getCurrentMergedItems(); 199 if (newItems != null) { 200 for (int i=0; i<newItems.size(); i++) { 201 RunningState.MergedItem mi = newItems.get(i); 202 if (mi.mUserId != mUserId) { 203 continue; 204 } 205 if (mUid >= 0 && mi.mProcess != null && mi.mProcess.mUid != mUid) { 206 continue; 207 } 208 if (mProcessName == null || (mi.mProcess != null 209 && mProcessName.equals(mi.mProcess.mProcessName))) { 210 item = mi; 211 break; 212 } 213 } 214 } 215 216 if (mMergedItem != item) { 217 mMergedItem = item; 218 return true; 219 } 220 return false; 221 } 222 223 void addServicesHeader() { 224 if (mNumServices == 0) { 225 mServicesHeader = (TextView)mInflater.inflate(R.layout.separator_label, 226 mAllDetails, false); 227 mServicesHeader.setText(R.string.runningservicedetails_services_title); 228 mAllDetails.addView(mServicesHeader); 229 } 230 mNumServices++; 231 } 232 233 void addProcessesHeader() { 234 if (mNumProcesses == 0) { 235 mProcessesHeader = (TextView)mInflater.inflate(R.layout.separator_label, 236 mAllDetails, false); 237 mProcessesHeader.setText(R.string.runningservicedetails_processes_title); 238 mAllDetails.addView(mProcessesHeader); 239 } 240 mNumProcesses++; 241 } 242 243 void addServiceDetailsView(RunningState.ServiceItem si, RunningState.MergedItem mi, 244 boolean isService, boolean inclDetails) { 245 if (isService) { 246 addServicesHeader(); 247 } else if (mi.mUserId != UserHandle.myUserId()) { 248 // This is being called for another user, and is not a service... 249 // That is, it is a background processes, being added for the 250 // details of a user. In this case we want a header for processes, 251 // since the top subject line is for the user. 252 addProcessesHeader(); 253 } 254 255 RunningState.BaseItem bi = si != null ? si : mi; 256 257 ActiveDetail detail = new ActiveDetail(); 258 View root = mInflater.inflate(R.layout.running_service_details_service, 259 mAllDetails, false); 260 mAllDetails.addView(root); 261 detail.mRootView = root; 262 detail.mServiceItem = si; 263 detail.mViewHolder = new RunningProcessesView.ViewHolder(root); 264 detail.mActiveItem = detail.mViewHolder.bind(mState, bi, mBuilder); 265 266 if (!inclDetails) { 267 root.findViewById(R.id.service).setVisibility(View.GONE); 268 } 269 270 if (si != null && si.mRunningService.clientLabel != 0) { 271 detail.mManageIntent = mAm.getRunningServiceControlPanel( 272 si.mRunningService.service); 273 } 274 275 TextView description = (TextView)root.findViewById(R.id.comp_description); 276 detail.mStopButton = (Button)root.findViewById(R.id.left_button); 277 detail.mReportButton = (Button)root.findViewById(R.id.right_button); 278 279 if (isService && mi.mUserId != UserHandle.myUserId()) { 280 // For services from other users, we don't show any description or 281 // controls, because the current user can not perform 282 // actions on them. 283 description.setVisibility(View.GONE); 284 root.findViewById(R.id.control_buttons_panel).setVisibility(View.GONE); 285 } else { 286 if (si != null && si.mServiceInfo.descriptionRes != 0) { 287 description.setText(getActivity().getPackageManager().getText( 288 si.mServiceInfo.packageName, si.mServiceInfo.descriptionRes, 289 si.mServiceInfo.applicationInfo)); 290 } else { 291 if (mi.mBackground) { 292 description.setText(R.string.background_process_stop_description); 293 } else if (detail.mManageIntent != null) { 294 try { 295 Resources clientr = getActivity().getPackageManager().getResourcesForApplication( 296 si.mRunningService.clientPackage); 297 String label = clientr.getString(si.mRunningService.clientLabel); 298 description.setText(getActivity().getString(R.string.service_manage_description, 299 label)); 300 } catch (PackageManager.NameNotFoundException e) { 301 } 302 } else { 303 description.setText(getActivity().getText(si != null 304 ? R.string.service_stop_description 305 : R.string.heavy_weight_stop_description)); 306 } 307 } 308 309 detail.mStopButton.setOnClickListener(detail); 310 detail.mStopButton.setText(getActivity().getText(detail.mManageIntent != null 311 ? R.string.service_manage : R.string.service_stop)); 312 detail.mReportButton.setOnClickListener(detail); 313 detail.mReportButton.setText(com.android.internal.R.string.report); 314 // check if error reporting is enabled in secure settings 315 int enabled = Settings.Secure.getInt(getActivity().getContentResolver(), 316 Settings.Secure.SEND_ACTION_APP_ERROR, 0); 317 if (enabled != 0 && si != null) { 318 detail.mInstaller = ApplicationErrorReport.getErrorReportReceiver( 319 getActivity(), si.mServiceInfo.packageName, 320 si.mServiceInfo.applicationInfo.flags); 321 detail.mReportButton.setEnabled(detail.mInstaller != null); 322 } else { 323 detail.mReportButton.setEnabled(false); 324 } 325 } 326 327 mActiveDetails.add(detail); 328 } 329 330 void addProcessDetailsView(RunningState.ProcessItem pi, boolean isMain) { 331 addProcessesHeader(); 332 333 ActiveDetail detail = new ActiveDetail(); 334 View root = mInflater.inflate(R.layout.running_service_details_process, 335 mAllDetails, false); 336 mAllDetails.addView(root); 337 detail.mRootView = root; 338 detail.mViewHolder = new RunningProcessesView.ViewHolder(root); 339 detail.mActiveItem = detail.mViewHolder.bind(mState, pi, mBuilder); 340 341 TextView description = (TextView)root.findViewById(R.id.comp_description); 342 if (pi.mUserId != UserHandle.myUserId()) { 343 // Processes for another user are all shown batched together; there is 344 // no reason to have a description. 345 description.setVisibility(View.GONE); 346 } else if (isMain) { 347 description.setText(R.string.main_running_process_description); 348 } else { 349 int textid = 0; 350 CharSequence label = null; 351 ActivityManager.RunningAppProcessInfo rpi = pi.mRunningProcessInfo; 352 final ComponentName comp = rpi.importanceReasonComponent; 353 //Log.i(TAG, "Secondary proc: code=" + rpi.importanceReasonCode 354 // + " pid=" + rpi.importanceReasonPid + " comp=" + comp); 355 switch (rpi.importanceReasonCode) { 356 case ActivityManager.RunningAppProcessInfo.REASON_PROVIDER_IN_USE: 357 textid = R.string.process_provider_in_use_description; 358 if (rpi.importanceReasonComponent != null) { 359 try { 360 ProviderInfo prov = getActivity().getPackageManager().getProviderInfo( 361 rpi.importanceReasonComponent, 0); 362 label = RunningState.makeLabel(getActivity().getPackageManager(), 363 prov.name, prov); 364 } catch (NameNotFoundException e) { 365 } 366 } 367 break; 368 case ActivityManager.RunningAppProcessInfo.REASON_SERVICE_IN_USE: 369 textid = R.string.process_service_in_use_description; 370 if (rpi.importanceReasonComponent != null) { 371 try { 372 ServiceInfo serv = getActivity().getPackageManager().getServiceInfo( 373 rpi.importanceReasonComponent, 0); 374 label = RunningState.makeLabel(getActivity().getPackageManager(), 375 serv.name, serv); 376 } catch (NameNotFoundException e) { 377 } 378 } 379 break; 380 } 381 if (textid != 0 && label != null) { 382 description.setText(getActivity().getString(textid, label)); 383 } 384 } 385 386 mActiveDetails.add(detail); 387 } 388 389 void addDetailsViews(RunningState.MergedItem item, boolean inclServices, 390 boolean inclProcesses) { 391 if (item != null) { 392 if (inclServices) { 393 for (int i=0; i<item.mServices.size(); i++) { 394 addServiceDetailsView(item.mServices.get(i), item, true, true); 395 } 396 } 397 398 if (inclProcesses) { 399 if (item.mServices.size() <= 0) { 400 // This item does not have any services, so it must be 401 // another interesting process... we will put a fake service 402 // entry for it, to allow the user to "stop" it. 403 addServiceDetailsView(null, item, false, item.mUserId != UserHandle.myUserId()); 404 } else { 405 // This screen is actually showing services, so also show 406 // the process details. 407 for (int i=-1; i<item.mOtherProcesses.size(); i++) { 408 RunningState.ProcessItem pi = i < 0 ? item.mProcess 409 : item.mOtherProcesses.get(i); 410 if (pi != null && pi.mPid <= 0) { 411 continue; 412 } 413 414 addProcessDetailsView(pi, i < 0); 415 } 416 } 417 } 418 } 419 } 420 421 void addDetailViews() { 422 for (int i=mActiveDetails.size()-1; i>=0; i--) { 423 mAllDetails.removeView(mActiveDetails.get(i).mRootView); 424 } 425 mActiveDetails.clear(); 426 427 if (mServicesHeader != null) { 428 mAllDetails.removeView(mServicesHeader); 429 mServicesHeader = null; 430 } 431 432 if (mProcessesHeader != null) { 433 mAllDetails.removeView(mProcessesHeader); 434 mProcessesHeader = null; 435 } 436 437 mNumServices = mNumProcesses = 0; 438 439 if (mMergedItem != null) { 440 if (mMergedItem.mUser != null) { 441 ArrayList<RunningState.MergedItem> items; 442 if (mShowBackground) { 443 items = new ArrayList<RunningState.MergedItem>(mMergedItem.mChildren); 444 Collections.sort(items, mState.mBackgroundComparator); 445 } else { 446 items = mMergedItem.mChildren; 447 } 448 for (int i=0; i<items.size(); i++) { 449 addDetailsViews(items.get(i), true, false); 450 } 451 for (int i=0; i<items.size(); i++) { 452 addDetailsViews(items.get(i), false, true); 453 } 454 } else { 455 addDetailsViews(mMergedItem, true, true); 456 } 457 } 458 } 459 460 void refreshUi(boolean dataChanged) { 461 if (findMergedItem()) { 462 dataChanged = true; 463 } 464 if (dataChanged) { 465 if (mMergedItem != null) { 466 mSnippetActiveItem = mSnippetViewHolder.bind(mState, 467 mMergedItem, mBuilder); 468 } else if (mSnippetActiveItem != null) { 469 // Clear whatever is currently being shown. 470 mSnippetActiveItem.mHolder.size.setText(""); 471 mSnippetActiveItem.mHolder.uptime.setText(""); 472 mSnippetActiveItem.mHolder.description.setText(R.string.no_services); 473 } else { 474 // No merged item, never had one. Nothing to do. 475 finish(); 476 return; 477 } 478 addDetailViews(); 479 } 480 } 481 482 private void finish() { 483 (new Handler()).post(new Runnable() { 484 @Override 485 public void run() { 486 Activity a = getActivity(); 487 if (a != null) { 488 a.onBackPressed(); 489 } 490 } 491 }); 492 } 493 494 @Override 495 public void onCreate(Bundle savedInstanceState) { 496 super.onCreate(savedInstanceState); 497 498 mUid = getArguments().getInt(KEY_UID, -1); 499 mUserId = getArguments().getInt(KEY_USER_ID, 0); 500 mProcessName = getArguments().getString(KEY_PROCESS, null); 501 mShowBackground = getArguments().getBoolean(KEY_BACKGROUND, false); 502 503 mAm = (ActivityManager)getActivity().getSystemService(Context.ACTIVITY_SERVICE); 504 mInflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 505 506 mState = RunningState.getInstance(getActivity()); 507 } 508 509 @Override 510 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 511 View view = mRootView = inflater.inflate(R.layout.running_service_details, null); 512 513 mAllDetails = (ViewGroup)view.findViewById(R.id.all_details); 514 mSnippet = (ViewGroup)view.findViewById(R.id.snippet); 515 mSnippet.setPadding(0, mSnippet.getPaddingTop(), 0, mSnippet.getPaddingBottom()); 516 mSnippetViewHolder = new RunningProcessesView.ViewHolder(mSnippet); 517 518 // We want to retrieve the data right now, so any active managed 519 // dialog that gets created can find it. 520 ensureData(); 521 522 return view; 523 } 524 525 @Override 526 public void onPause() { 527 super.onPause(); 528 mHaveData = false; 529 mState.pause(); 530 } 531 532 @Override 533 public void onResume() { 534 super.onResume(); 535 ensureData(); 536 } 537 538 ActiveDetail activeDetailForService(ComponentName comp) { 539 for (int i=0; i<mActiveDetails.size(); i++) { 540 ActiveDetail ad = mActiveDetails.get(i); 541 if (ad.mServiceItem != null && ad.mServiceItem.mRunningService != null 542 && comp.equals(ad.mServiceItem.mRunningService.service)) { 543 return ad; 544 } 545 } 546 return null; 547 } 548 549 private void showConfirmStopDialog(ComponentName comp) { 550 DialogFragment newFragment = MyAlertDialogFragment.newConfirmStop( 551 DIALOG_CONFIRM_STOP, comp); 552 newFragment.setTargetFragment(this, 0); 553 newFragment.show(getFragmentManager(), "confirmstop"); 554 } 555 556 public static class MyAlertDialogFragment extends DialogFragment { 557 558 public static MyAlertDialogFragment newConfirmStop(int id, ComponentName comp) { 559 MyAlertDialogFragment frag = new MyAlertDialogFragment(); 560 Bundle args = new Bundle(); 561 args.putInt("id", id); 562 args.putParcelable("comp", comp); 563 frag.setArguments(args); 564 return frag; 565 } 566 567 RunningServiceDetails getOwner() { 568 return (RunningServiceDetails)getTargetFragment(); 569 } 570 571 @Override 572 public Dialog onCreateDialog(Bundle savedInstanceState) { 573 int id = getArguments().getInt("id"); 574 switch (id) { 575 case DIALOG_CONFIRM_STOP: { 576 final ComponentName comp = (ComponentName)getArguments().getParcelable("comp"); 577 if (getOwner().activeDetailForService(comp) == null) { 578 return null; 579 } 580 581 return new AlertDialog.Builder(getActivity()) 582 .setTitle(getActivity().getString(R.string.runningservicedetails_stop_dlg_title)) 583 .setIconAttribute(android.R.attr.alertDialogIcon) 584 .setMessage(getActivity().getString(R.string.runningservicedetails_stop_dlg_text)) 585 .setPositiveButton(R.string.dlg_ok, 586 new DialogInterface.OnClickListener() { 587 public void onClick(DialogInterface dialog, int which) { 588 ActiveDetail ad = getOwner().activeDetailForService(comp); 589 if (ad != null) { 590 ad.stopActiveService(true); 591 } 592 } 593 }) 594 .setNegativeButton(R.string.dlg_cancel, null) 595 .create(); 596 } 597 } 598 throw new IllegalArgumentException("unknown id " + id); 599 } 600 } 601 602 void ensureData() { 603 if (!mHaveData) { 604 mHaveData = true; 605 mState.resume(this); 606 607 // We want to go away if the service being shown no longer exists, 608 // so we need to ensure we have done the initial data retrieval before 609 // showing our ui. 610 mState.waitForData(); 611 612 // And since we know we have the data, let's show the UI right away 613 // to avoid flicker. 614 refreshUi(true); 615 } 616 } 617 618 void updateTimes() { 619 if (mSnippetActiveItem != null) { 620 mSnippetActiveItem.updateTime(getActivity(), mBuilder); 621 } 622 for (int i=0; i<mActiveDetails.size(); i++) { 623 mActiveDetails.get(i).mActiveItem.updateTime(getActivity(), mBuilder); 624 } 625 } 626 627 @Override 628 public void onRefreshUi(int what) { 629 if (getActivity() == null) return; 630 switch (what) { 631 case REFRESH_TIME: 632 updateTimes(); 633 break; 634 case REFRESH_DATA: 635 refreshUi(false); 636 updateTimes(); 637 break; 638 case REFRESH_STRUCTURE: 639 refreshUi(true); 640 updateTimes(); 641 break; 642 } 643 } 644} 645