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