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