1 package com.android.settings.applications;
2 
3 import android.app.Activity;
4 import android.app.ActivityManager;
5 import android.app.AlertDialog;
6 import android.app.ApplicationErrorReport;
7 import android.app.Dialog;
8 import android.app.DialogFragment;
9 import android.app.PendingIntent;
10 import android.content.ActivityNotFoundException;
11 import android.content.ComponentName;
12 import android.content.Context;
13 import android.content.DialogInterface;
14 import android.content.Intent;
15 import android.content.IntentSender;
16 import android.content.pm.ApplicationInfo;
17 import android.content.pm.PackageManager;
18 import android.content.pm.PackageManager.NameNotFoundException;
19 import android.content.pm.ProviderInfo;
20 import android.content.pm.ServiceInfo;
21 import android.content.res.Resources;
22 import android.os.Bundle;
23 import android.os.Debug;
24 import android.os.Handler;
25 import android.os.SystemClock;
26 import android.os.UserHandle;
27 import android.provider.Settings;
28 import android.util.Log;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.Button;
33 import android.widget.TextView;
34 
35 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
36 import com.android.settings.R;
37 import com.android.settings.Utils;
38 import com.android.settings.core.InstrumentedPreferenceFragment;
39 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
40 
41 import java.io.File;
42 import java.io.FileInputStream;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 
48 public class RunningServiceDetails extends InstrumentedPreferenceFragment
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 
stopActiveService(boolean confirmed)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 
onClick(View v)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 
findMergedItem()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 
addServicesHeader()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 
addProcessesHeader()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 
addServiceDetailsView(RunningState.ServiceItem si, RunningState.MergedItem mi, boolean isService, boolean inclDetails)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 
addProcessDetailsView(RunningState.ProcessItem pi, boolean isMain)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 
addDetailsViews(RunningState.MergedItem item, boolean inclServices, boolean inclProcesses)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         setHasOptionsMenu(true);
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(
508                 Context.LAYOUT_INFLATER_SERVICE);
509 
510         mState = RunningState.getInstance(getActivity());
511     }
512 
513     @Override
514     public View onCreateView(
515             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
516         final View view = inflater.inflate(R.layout.running_service_details, container, false);
517         Utils.prepareCustomPreferencesList(container, view, view, false);
518 
519         mRootView = view;
520         mAllDetails = (ViewGroup)view.findViewById(R.id.all_details);
521         mSnippet = (ViewGroup)view.findViewById(R.id.snippet);
522         mSnippetViewHolder = new RunningProcessesView.ViewHolder(mSnippet);
523 
524         // We want to retrieve the data right now, so any active managed
525         // dialog that gets created can find it.
526         ensureData();
527 
528         return view;
529     }
530 
531     @Override
532     public void onPause() {
533         super.onPause();
534         mHaveData = false;
535         mState.pause();
536     }
537 
538     @Override
539     public int getMetricsCategory() {
540         return MetricsEvent.RUNNING_SERVICE_DETAILS;
541     }
542 
543     @Override
544     public void onResume() {
545         super.onResume();
546         ensureData();
547     }
548 
549     ActiveDetail activeDetailForService(ComponentName comp) {
550         for (int i=0; i<mActiveDetails.size(); i++) {
551             ActiveDetail ad = mActiveDetails.get(i);
552             if (ad.mServiceItem != null && ad.mServiceItem.mRunningService != null
553                     && comp.equals(ad.mServiceItem.mRunningService.service)) {
554                 return ad;
555             }
556         }
557         return null;
558     }
559 
560     private void showConfirmStopDialog(ComponentName comp) {
561         DialogFragment newFragment = MyAlertDialogFragment.newConfirmStop(
562                 DIALOG_CONFIRM_STOP, comp);
563         newFragment.setTargetFragment(this, 0);
564         newFragment.show(getFragmentManager(), "confirmstop");
565     }
566 
567     public static class MyAlertDialogFragment extends InstrumentedDialogFragment {
568 
569         public static MyAlertDialogFragment newConfirmStop(int id, ComponentName comp) {
570             MyAlertDialogFragment frag = new MyAlertDialogFragment();
571             Bundle args = new Bundle();
572             args.putInt("id", id);
573             args.putParcelable("comp", comp);
574             frag.setArguments(args);
575             return frag;
576         }
577 
578         RunningServiceDetails getOwner() {
579             return (RunningServiceDetails)getTargetFragment();
580         }
581 
582         @Override
583         public Dialog onCreateDialog(Bundle savedInstanceState) {
584             int id = getArguments().getInt("id");
585             switch (id) {
586                 case DIALOG_CONFIRM_STOP: {
587                     final ComponentName comp = (ComponentName)getArguments().getParcelable("comp");
588                     if (getOwner().activeDetailForService(comp) == null) {
589                         return null;
590                     }
591 
592                     return new AlertDialog.Builder(getActivity())
593                             .setTitle(getActivity().getString(R.string.runningservicedetails_stop_dlg_title))
594                             .setMessage(getActivity().getString(R.string.runningservicedetails_stop_dlg_text))
595                             .setPositiveButton(R.string.dlg_ok,
596                                     new DialogInterface.OnClickListener() {
597                                 public void onClick(DialogInterface dialog, int which) {
598                                     ActiveDetail ad = getOwner().activeDetailForService(comp);
599                                     if (ad != null) {
600                                         ad.stopActiveService(true);
601                                     }
602                                 }
603                             })
604                             .setNegativeButton(R.string.dlg_cancel, null)
605                             .create();
606                 }
607             }
608             throw new IllegalArgumentException("unknown id " + id);
609         }
610 
611         @Override
612         public int getMetricsCategory() {
613             return MetricsEvent.DIALOG_RUNNIGN_SERVICE;
614         }
615     }
616 
617     void ensureData() {
618         if (!mHaveData) {
619             mHaveData = true;
620             mState.resume(this);
621 
622             // We want to go away if the service being shown no longer exists,
623             // so we need to ensure we have done the initial data retrieval before
624             // showing our ui.
625             mState.waitForData();
626 
627             // And since we know we have the data, let's show the UI right away
628             // to avoid flicker.
629             refreshUi(true);
630         }
631     }
632 
633     void updateTimes() {
634         if (mSnippetActiveItem != null) {
635             mSnippetActiveItem.updateTime(getActivity(), mBuilder);
636         }
637         for (int i=0; i<mActiveDetails.size(); i++) {
638             mActiveDetails.get(i).mActiveItem.updateTime(getActivity(), mBuilder);
639         }
640     }
641 
642     @Override
643     public void onRefreshUi(int what) {
644         if (getActivity() == null) return;
645         switch (what) {
646             case REFRESH_TIME:
647                 updateTimes();
648                 break;
649             case REFRESH_DATA:
650                 refreshUi(false);
651                 updateTimes();
652                 break;
653             case REFRESH_STRUCTURE:
654                 refreshUi(true);
655                 updateTimes();
656                 break;
657         }
658     }
659 }
660