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 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 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 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 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 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 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.setBackgroundResource(com.android.internal.R.drawable.title_bar_medium); 443 mSnippet.setPadding(0, mSnippet.getPaddingTop(), 0, mSnippet.getPaddingBottom()); 444 mSnippetViewHolder = new RunningProcessesView.ViewHolder(mSnippet); 445 446 // We want to retrieve the data right now, so any active managed 447 // dialog that gets created can find it. 448 ensureData(); 449 450 return view; 451 } 452 453 @Override 454 public void onPause() { 455 super.onPause(); 456 mHaveData = false; 457 mState.pause(); 458 } 459 460 @Override 461 public void onResume() { 462 super.onResume(); 463 ensureData(); 464 } 465 466 ActiveDetail activeDetailForService(ComponentName comp) { 467 for (int i=0; i<mActiveDetails.size(); i++) { 468 ActiveDetail ad = mActiveDetails.get(i); 469 if (ad.mServiceItem != null && ad.mServiceItem.mRunningService != null 470 && comp.equals(ad.mServiceItem.mRunningService.service)) { 471 return ad; 472 } 473 } 474 return null; 475 } 476 477 private void showConfirmStopDialog(ComponentName comp) { 478 DialogFragment newFragment = MyAlertDialogFragment.newConfirmStop( 479 DIALOG_CONFIRM_STOP, comp); 480 newFragment.setTargetFragment(this, 0); 481 newFragment.show(getFragmentManager(), "confirmstop"); 482 } 483 484 public static class MyAlertDialogFragment extends DialogFragment { 485 486 public static MyAlertDialogFragment newConfirmStop(int id, ComponentName comp) { 487 MyAlertDialogFragment frag = new MyAlertDialogFragment(); 488 Bundle args = new Bundle(); 489 args.putInt("id", id); 490 args.putParcelable("comp", comp); 491 frag.setArguments(args); 492 return frag; 493 } 494 495 RunningServiceDetails getOwner() { 496 return (RunningServiceDetails)getTargetFragment(); 497 } 498 499 @Override 500 public Dialog onCreateDialog(Bundle savedInstanceState) { 501 int id = getArguments().getInt("id"); 502 switch (id) { 503 case DIALOG_CONFIRM_STOP: { 504 final ComponentName comp = (ComponentName)getArguments().getParcelable("comp"); 505 if (getOwner().activeDetailForService(comp) == null) { 506 return null; 507 } 508 509 return new AlertDialog.Builder(getActivity()) 510 .setTitle(getActivity().getString(R.string.runningservicedetails_stop_dlg_title)) 511 .setIcon(android.R.drawable.ic_dialog_alert) 512 .setMessage(getActivity().getString(R.string.runningservicedetails_stop_dlg_text)) 513 .setPositiveButton(R.string.dlg_ok, 514 new DialogInterface.OnClickListener() { 515 public void onClick(DialogInterface dialog, int which) { 516 ActiveDetail ad = getOwner().activeDetailForService(comp); 517 if (ad != null) { 518 ad.stopActiveService(true); 519 } 520 } 521 }) 522 .setNegativeButton(R.string.dlg_cancel, null) 523 .create(); 524 } 525 } 526 throw new IllegalArgumentException("unknown id " + id); 527 } 528 } 529 530 void ensureData() { 531 if (!mHaveData) { 532 mHaveData = true; 533 mState.resume(this); 534 535 // We want to go away if the service being shown no longer exists, 536 // so we need to ensure we have done the initial data retrieval before 537 // showing our ui. 538 mState.waitForData(); 539 540 // And since we know we have the data, let's show the UI right away 541 // to avoid flicker. 542 refreshUi(true); 543 } 544 } 545 546 void updateTimes() { 547 if (mSnippetActiveItem != null) { 548 mSnippetActiveItem.updateTime(getActivity(), mBuilder); 549 } 550 for (int i=0; i<mActiveDetails.size(); i++) { 551 mActiveDetails.get(i).mActiveItem.updateTime(getActivity(), mBuilder); 552 } 553 } 554 555 @Override 556 public void onRefreshUi(int what) { 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