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.internal.logging.MetricsLogger;
     37 import com.android.settings.InstrumentedFragment;
     38 import com.android.settings.R;
     39 import com.android.settings.Utils;
     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 InstrumentedFragment
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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(Context.LAYOUT_INFLATER_SERVICE);
    508 
    509         mState = RunningState.getInstance(getActivity());
    510     }
    511 
    512     @Override
    513     public View onCreateView(
    514             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    515         final View view = inflater.inflate(R.layout.running_service_details, container, false);
    516         Utils.prepareCustomPreferencesList(container, view, view, false);
    517 
    518         mRootView = view;
    519         mAllDetails = (ViewGroup)view.findViewById(R.id.all_details);
    520         mSnippet = (ViewGroup)view.findViewById(R.id.snippet);
    521         mSnippetViewHolder = new RunningProcessesView.ViewHolder(mSnippet);
    522 
    523         // We want to retrieve the data right now, so any active managed
    524         // dialog that gets created can find it.
    525         ensureData();
    526 
    527         return view;
    528     }
    529 
    530     @Override
    531     public void onPause() {
    532         super.onPause();
    533         mHaveData = false;
    534         mState.pause();
    535     }
    536 
    537     @Override
    538     protected int getMetricsCategory() {
    539         return MetricsLogger.RUNNING_SERVICE_DETAILS;
    540     }
    541 
    542     @Override
    543     public void onResume() {
    544         super.onResume();
    545         ensureData();
    546     }
    547 
    548     ActiveDetail activeDetailForService(ComponentName comp) {
    549         for (int i=0; i<mActiveDetails.size(); i++) {
    550             ActiveDetail ad = mActiveDetails.get(i);
    551             if (ad.mServiceItem != null && ad.mServiceItem.mRunningService != null
    552                     && comp.equals(ad.mServiceItem.mRunningService.service)) {
    553                 return ad;
    554             }
    555         }
    556         return null;
    557     }
    558 
    559     private void showConfirmStopDialog(ComponentName comp) {
    560         DialogFragment newFragment = MyAlertDialogFragment.newConfirmStop(
    561                 DIALOG_CONFIRM_STOP, comp);
    562         newFragment.setTargetFragment(this, 0);
    563         newFragment.show(getFragmentManager(), "confirmstop");
    564     }
    565 
    566     public static class MyAlertDialogFragment extends DialogFragment {
    567 
    568         public static MyAlertDialogFragment newConfirmStop(int id, ComponentName comp) {
    569             MyAlertDialogFragment frag = new MyAlertDialogFragment();
    570             Bundle args = new Bundle();
    571             args.putInt("id", id);
    572             args.putParcelable("comp", comp);
    573             frag.setArguments(args);
    574             return frag;
    575         }
    576 
    577         RunningServiceDetails getOwner() {
    578             return (RunningServiceDetails)getTargetFragment();
    579         }
    580 
    581         @Override
    582         public Dialog onCreateDialog(Bundle savedInstanceState) {
    583             int id = getArguments().getInt("id");
    584             switch (id) {
    585                 case DIALOG_CONFIRM_STOP: {
    586                     final ComponentName comp = (ComponentName)getArguments().getParcelable("comp");
    587                     if (getOwner().activeDetailForService(comp) == null) {
    588                         return null;
    589                     }
    590 
    591                     return new AlertDialog.Builder(getActivity())
    592                             .setTitle(getActivity().getString(R.string.runningservicedetails_stop_dlg_title))
    593                             .setMessage(getActivity().getString(R.string.runningservicedetails_stop_dlg_text))
    594                             .setPositiveButton(R.string.dlg_ok,
    595                                     new DialogInterface.OnClickListener() {
    596                                 public void onClick(DialogInterface dialog, int which) {
    597                                     ActiveDetail ad = getOwner().activeDetailForService(comp);
    598                                     if (ad != null) {
    599                                         ad.stopActiveService(true);
    600                                     }
    601                                 }
    602                             })
    603                             .setNegativeButton(R.string.dlg_cancel, null)
    604                             .create();
    605                 }
    606             }
    607             throw new IllegalArgumentException("unknown id " + id);
    608         }
    609     }
    610 
    611     void ensureData() {
    612         if (!mHaveData) {
    613             mHaveData = true;
    614             mState.resume(this);
    615 
    616             // We want to go away if the service being shown no longer exists,
    617             // so we need to ensure we have done the initial data retrieval before
    618             // showing our ui.
    619             mState.waitForData();
    620 
    621             // And since we know we have the data, let's show the UI right away
    622             // to avoid flicker.
    623             refreshUi(true);
    624         }
    625     }
    626 
    627     void updateTimes() {
    628         if (mSnippetActiveItem != null) {
    629             mSnippetActiveItem.updateTime(getActivity(), mBuilder);
    630         }
    631         for (int i=0; i<mActiveDetails.size(); i++) {
    632             mActiveDetails.get(i).mActiveItem.updateTime(getActivity(), mBuilder);
    633         }
    634     }
    635 
    636     @Override
    637     public void onRefreshUi(int what) {
    638         if (getActivity() == null) return;
    639         switch (what) {
    640             case REFRESH_TIME:
    641                 updateTimes();
    642                 break;
    643             case REFRESH_DATA:
    644                 refreshUi(false);
    645                 updateTimes();
    646                 break;
    647             case REFRESH_STRUCTURE:
    648                 refreshUi(true);
    649                 updateTimes();
    650                 break;
    651         }
    652     }
    653 }
    654