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