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