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.Fragment; 10 import android.app.PendingIntent; 11 import android.content.ActivityNotFoundException; 12 import android.content.ComponentName; 13 import android.content.Context; 14 import android.content.DialogInterface; 15 import android.content.Intent; 16 import android.content.IntentSender; 17 import android.content.pm.ApplicationInfo; 18 import android.content.pm.PackageManager; 19 import android.content.pm.PackageManager.NameNotFoundException; 20 import android.content.pm.ProviderInfo; 21 import android.content.pm.ServiceInfo; 22 import android.content.res.Resources; 23 import android.os.Bundle; 24 import android.os.Debug; 25 import android.os.Handler; 26 import android.os.SystemClock; 27 import android.os.UserHandle; 28 import android.provider.Settings; 29 import android.util.Log; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.widget.Button; 34 import android.widget.TextView; 35 36 import com.android.internal.logging.MetricsLogger; 37 import com.android.settings.InstrumentedFragment; 38 import com.android.settings.R; 39 import com.android.settings.Utils; 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 InstrumentedFragment 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 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 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 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 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 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 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 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 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 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(Context.LAYOUT_INFLATER_SERVICE); 508 509 mState = RunningState.getInstance(getActivity()); 510 } 511 512 @Override 513 public View onCreateView( 514 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 515 final View view = inflater.inflate(R.layout.running_service_details, container, false); 516 Utils.prepareCustomPreferencesList(container, view, view, false); 517 518 mRootView = view; 519 mAllDetails = (ViewGroup)view.findViewById(R.id.all_details); 520 mSnippet = (ViewGroup)view.findViewById(R.id.snippet); 521 mSnippetViewHolder = new RunningProcessesView.ViewHolder(mSnippet); 522 523 // We want to retrieve the data right now, so any active managed 524 // dialog that gets created can find it. 525 ensureData(); 526 527 return view; 528 } 529 530 @Override 531 public void onPause() { 532 super.onPause(); 533 mHaveData = false; 534 mState.pause(); 535 } 536 537 @Override 538 protected int getMetricsCategory() { 539 return MetricsLogger.RUNNING_SERVICE_DETAILS; 540 } 541 542 @Override 543 public void onResume() { 544 super.onResume(); 545 ensureData(); 546 } 547 548 ActiveDetail activeDetailForService(ComponentName comp) { 549 for (int i=0; i<mActiveDetails.size(); i++) { 550 ActiveDetail ad = mActiveDetails.get(i); 551 if (ad.mServiceItem != null && ad.mServiceItem.mRunningService != null 552 && comp.equals(ad.mServiceItem.mRunningService.service)) { 553 return ad; 554 } 555 } 556 return null; 557 } 558 559 private void showConfirmStopDialog(ComponentName comp) { 560 DialogFragment newFragment = MyAlertDialogFragment.newConfirmStop( 561 DIALOG_CONFIRM_STOP, comp); 562 newFragment.setTargetFragment(this, 0); 563 newFragment.show(getFragmentManager(), "confirmstop"); 564 } 565 566 public static class MyAlertDialogFragment extends DialogFragment { 567 568 public static MyAlertDialogFragment newConfirmStop(int id, ComponentName comp) { 569 MyAlertDialogFragment frag = new MyAlertDialogFragment(); 570 Bundle args = new Bundle(); 571 args.putInt("id", id); 572 args.putParcelable("comp", comp); 573 frag.setArguments(args); 574 return frag; 575 } 576 577 RunningServiceDetails getOwner() { 578 return (RunningServiceDetails)getTargetFragment(); 579 } 580 581 @Override 582 public Dialog onCreateDialog(Bundle savedInstanceState) { 583 int id = getArguments().getInt("id"); 584 switch (id) { 585 case DIALOG_CONFIRM_STOP: { 586 final ComponentName comp = (ComponentName)getArguments().getParcelable("comp"); 587 if (getOwner().activeDetailForService(comp) == null) { 588 return null; 589 } 590 591 return new AlertDialog.Builder(getActivity()) 592 .setTitle(getActivity().getString(R.string.runningservicedetails_stop_dlg_title)) 593 .setMessage(getActivity().getString(R.string.runningservicedetails_stop_dlg_text)) 594 .setPositiveButton(R.string.dlg_ok, 595 new DialogInterface.OnClickListener() { 596 public void onClick(DialogInterface dialog, int which) { 597 ActiveDetail ad = getOwner().activeDetailForService(comp); 598 if (ad != null) { 599 ad.stopActiveService(true); 600 } 601 } 602 }) 603 .setNegativeButton(R.string.dlg_cancel, null) 604 .create(); 605 } 606 } 607 throw new IllegalArgumentException("unknown id " + id); 608 } 609 } 610 611 void ensureData() { 612 if (!mHaveData) { 613 mHaveData = true; 614 mState.resume(this); 615 616 // We want to go away if the service being shown no longer exists, 617 // so we need to ensure we have done the initial data retrieval before 618 // showing our ui. 619 mState.waitForData(); 620 621 // And since we know we have the data, let's show the UI right away 622 // to avoid flicker. 623 refreshUi(true); 624 } 625 } 626 627 void updateTimes() { 628 if (mSnippetActiveItem != null) { 629 mSnippetActiveItem.updateTime(getActivity(), mBuilder); 630 } 631 for (int i=0; i<mActiveDetails.size(); i++) { 632 mActiveDetails.get(i).mActiveItem.updateTime(getActivity(), mBuilder); 633 } 634 } 635 636 @Override 637 public void onRefreshUi(int what) { 638 if (getActivity() == null) return; 639 switch (what) { 640 case REFRESH_TIME: 641 updateTimes(); 642 break; 643 case REFRESH_DATA: 644 refreshUi(false); 645 updateTimes(); 646 break; 647 case REFRESH_STRUCTURE: 648 refreshUi(true); 649 updateTimes(); 650 break; 651 } 652 } 653 } 654