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