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.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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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