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