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