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