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