• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.MetricsProto.MetricsEvent;
36 import com.android.settings.InstrumentedFragment;
37 import com.android.settings.R;
38 import com.android.settings.Utils;
39 
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.util.ArrayList;
45 import java.util.Collections;
46 
47 public 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 
stopActiveService(boolean confirmed)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 
onClick(View v)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 
findMergedItem()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 
addServicesHeader()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 
addProcessesHeader()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 
addServiceDetailsView(RunningState.ServiceItem si, RunningState.MergedItem mi, boolean isService, boolean inclDetails)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 
addProcessDetailsView(RunningState.ProcessItem pi, boolean isMain)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 
addDetailsViews(RunningState.MergedItem item, boolean inclServices, boolean inclProcesses)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