RunningServiceDetails.java revision 1337d0f70b18dd732c6a617be83e5be0bb8f2f87
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    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