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