RunningServiceDetails.java revision 25bda17a57321c8cf599673248d2e35d692ad51e
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.DialogFragment;
11import android.app.Fragment;
12import android.app.PendingIntent;
13import android.content.ActivityNotFoundException;
14import android.content.ComponentName;
15import android.content.Context;
16import android.content.DialogInterface;
17import android.content.Intent;
18import android.content.IntentSender;
19import android.content.pm.ApplicationInfo;
20import android.content.pm.PackageManager;
21import android.content.pm.ProviderInfo;
22import android.content.pm.ServiceInfo;
23import android.content.pm.PackageManager.NameNotFoundException;
24import android.content.res.Resources;
25import android.os.Bundle;
26import android.os.Debug;
27import android.os.Handler;
28import android.os.SystemClock;
29import android.provider.Settings;
30import android.util.Log;
31import android.view.LayoutInflater;
32import android.view.View;
33import android.view.ViewGroup;
34import android.widget.Button;
35import android.widget.TextView;
36
37import java.io.File;
38import java.io.FileInputStream;
39import java.io.FileOutputStream;
40import java.io.IOException;
41import java.util.ArrayList;
42
43public 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