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