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