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