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