Home | History | Annotate | Download | only in calllog
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.dialer.app.calllog;
     18 
     19 import static android.Manifest.permission.READ_CALL_LOG;
     20 
     21 import android.app.Activity;
     22 import android.app.Fragment;
     23 import android.app.KeyguardManager;
     24 import android.content.ContentResolver;
     25 import android.content.Context;
     26 import android.content.pm.PackageManager;
     27 import android.database.ContentObserver;
     28 import android.database.Cursor;
     29 import android.os.Bundle;
     30 import android.os.Handler;
     31 import android.os.Message;
     32 import android.provider.CallLog;
     33 import android.provider.CallLog.Calls;
     34 import android.provider.ContactsContract;
     35 import android.support.annotation.CallSuper;
     36 import android.support.annotation.Nullable;
     37 import android.support.v13.app.FragmentCompat;
     38 import android.support.v7.app.AppCompatActivity;
     39 import android.support.v7.widget.LinearLayoutManager;
     40 import android.support.v7.widget.RecyclerView;
     41 import android.view.LayoutInflater;
     42 import android.view.View;
     43 import android.view.ViewGroup;
     44 import com.android.dialer.app.Bindings;
     45 import com.android.dialer.app.R;
     46 import com.android.dialer.app.calllog.calllogcache.CallLogCache;
     47 import com.android.dialer.app.contactinfo.ContactInfoCache;
     48 import com.android.dialer.app.contactinfo.ContactInfoCache.OnContactInfoChangedListener;
     49 import com.android.dialer.app.contactinfo.ExpirableCacheHeadlessFragment;
     50 import com.android.dialer.app.list.ListsFragment;
     51 import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter;
     52 import com.android.dialer.app.widget.EmptyContentView;
     53 import com.android.dialer.app.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener;
     54 import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
     55 import com.android.dialer.common.Assert;
     56 import com.android.dialer.common.LogUtil;
     57 import com.android.dialer.database.CallLogQueryHandler;
     58 import com.android.dialer.location.GeoUtil;
     59 import com.android.dialer.phonenumbercache.ContactInfoHelper;
     60 import com.android.dialer.util.PermissionsUtil;
     61 
     62 /**
     63  * Displays a list of call log entries. To filter for a particular kind of call (all, missed or
     64  * voicemails), specify it in the constructor.
     65  */
     66 public class CallLogFragment extends Fragment
     67     implements CallLogQueryHandler.Listener,
     68         CallLogAdapter.CallFetcher,
     69         OnEmptyViewActionButtonClickedListener,
     70         FragmentCompat.OnRequestPermissionsResultCallback,
     71         CallLogModalAlertManager.Listener {
     72   private static final String KEY_FILTER_TYPE = "filter_type";
     73   private static final String KEY_LOG_LIMIT = "log_limit";
     74   private static final String KEY_DATE_LIMIT = "date_limit";
     75   private static final String KEY_IS_CALL_LOG_ACTIVITY = "is_call_log_activity";
     76   private static final String KEY_HAS_READ_CALL_LOG_PERMISSION = "has_read_call_log_permission";
     77   private static final String KEY_REFRESH_DATA_REQUIRED = "refresh_data_required";
     78 
     79   // No limit specified for the number of logs to show; use the CallLogQueryHandler's default.
     80   private static final int NO_LOG_LIMIT = -1;
     81   // No date-based filtering.
     82   private static final int NO_DATE_LIMIT = 0;
     83 
     84   private static final int READ_CALL_LOG_PERMISSION_REQUEST_CODE = 1;
     85 
     86   private static final int EVENT_UPDATE_DISPLAY = 1;
     87 
     88   private static final long MILLIS_IN_MINUTE = 60 * 1000;
     89   private final Handler mHandler = new Handler();
     90   // See issue 6363009
     91   private final ContentObserver mCallLogObserver = new CustomContentObserver();
     92   private final ContentObserver mContactsObserver = new CustomContentObserver();
     93   private RecyclerView mRecyclerView;
     94   private LinearLayoutManager mLayoutManager;
     95   private CallLogAdapter mAdapter;
     96   private CallLogQueryHandler mCallLogQueryHandler;
     97   private boolean mScrollToTop;
     98   private EmptyContentView mEmptyListView;
     99   private KeyguardManager mKeyguardManager;
    100   private ContactInfoCache mContactInfoCache;
    101   private final OnContactInfoChangedListener mOnContactInfoChangedListener =
    102       new OnContactInfoChangedListener() {
    103         @Override
    104         public void onContactInfoChanged() {
    105           if (mAdapter != null) {
    106             mAdapter.notifyDataSetChanged();
    107           }
    108         }
    109       };
    110   private boolean mRefreshDataRequired;
    111   private boolean mHasReadCallLogPermission;
    112   // Exactly same variable is in Fragment as a package private.
    113   private boolean mMenuVisible = true;
    114   // Default to all calls.
    115   private int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL;
    116   // Log limit - if no limit is specified, then the default in {@link CallLogQueryHandler}
    117   // will be used.
    118   private int mLogLimit = NO_LOG_LIMIT;
    119   // Date limit (in millis since epoch) - when non-zero, only calls which occurred on or after
    120   // the date filter are included.  If zero, no date-based filtering occurs.
    121   private long mDateLimit = NO_DATE_LIMIT;
    122   /*
    123    * True if this instance of the CallLogFragment shown in the CallLogActivity.
    124    */
    125   private boolean mIsCallLogActivity = false;
    126   private final Handler mDisplayUpdateHandler =
    127       new Handler() {
    128         @Override
    129         public void handleMessage(Message msg) {
    130           switch (msg.what) {
    131             case EVENT_UPDATE_DISPLAY:
    132               refreshData();
    133               rescheduleDisplayUpdate();
    134               break;
    135             default:
    136               throw Assert.createAssertionFailException("Invalid message: " + msg);
    137           }
    138         }
    139       };
    140   protected CallLogModalAlertManager mModalAlertManager;
    141   private ViewGroup mModalAlertView;
    142 
    143   public CallLogFragment() {
    144     this(CallLogQueryHandler.CALL_TYPE_ALL, NO_LOG_LIMIT);
    145   }
    146 
    147   public CallLogFragment(int filterType) {
    148     this(filterType, NO_LOG_LIMIT);
    149   }
    150 
    151   public CallLogFragment(int filterType, boolean isCallLogActivity) {
    152     this(filterType, NO_LOG_LIMIT);
    153     mIsCallLogActivity = isCallLogActivity;
    154   }
    155 
    156   public CallLogFragment(int filterType, int logLimit) {
    157     this(filterType, logLimit, NO_DATE_LIMIT);
    158   }
    159 
    160   /**
    161    * Creates a call log fragment, filtering to include only calls of the desired type, occurring
    162    * after the specified date.
    163    *
    164    * @param filterType type of calls to include.
    165    * @param dateLimit limits results to calls occurring on or after the specified date.
    166    */
    167   public CallLogFragment(int filterType, long dateLimit) {
    168     this(filterType, NO_LOG_LIMIT, dateLimit);
    169   }
    170 
    171   /**
    172    * Creates a call log fragment, filtering to include only calls of the desired type, occurring
    173    * after the specified date. Also provides a means to limit the number of results returned.
    174    *
    175    * @param filterType type of calls to include.
    176    * @param logLimit limits the number of results to return.
    177    * @param dateLimit limits results to calls occurring on or after the specified date.
    178    */
    179   public CallLogFragment(int filterType, int logLimit, long dateLimit) {
    180     mCallTypeFilter = filterType;
    181     mLogLimit = logLimit;
    182     mDateLimit = dateLimit;
    183   }
    184 
    185   @Override
    186   public void onCreate(Bundle state) {
    187     LogUtil.d("CallLogFragment.onCreate", toString());
    188     super.onCreate(state);
    189     mRefreshDataRequired = true;
    190     if (state != null) {
    191       mCallTypeFilter = state.getInt(KEY_FILTER_TYPE, mCallTypeFilter);
    192       mLogLimit = state.getInt(KEY_LOG_LIMIT, mLogLimit);
    193       mDateLimit = state.getLong(KEY_DATE_LIMIT, mDateLimit);
    194       mIsCallLogActivity = state.getBoolean(KEY_IS_CALL_LOG_ACTIVITY, mIsCallLogActivity);
    195       mHasReadCallLogPermission = state.getBoolean(KEY_HAS_READ_CALL_LOG_PERMISSION, false);
    196       mRefreshDataRequired = state.getBoolean(KEY_REFRESH_DATA_REQUIRED, mRefreshDataRequired);
    197     }
    198 
    199     final Activity activity = getActivity();
    200     final ContentResolver resolver = activity.getContentResolver();
    201     mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this, mLogLimit);
    202     mKeyguardManager = (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE);
    203 
    204     if (PermissionsUtil.hasCallLogReadPermissions(getContext())) {
    205       resolver.registerContentObserver(CallLog.CONTENT_URI, true, mCallLogObserver);
    206     } else {
    207       LogUtil.w("CallLogFragment.onCreate", "call log permission not available");
    208     }
    209     if (PermissionsUtil.hasContactsReadPermissions(getContext())) {
    210       resolver.registerContentObserver(
    211           ContactsContract.Contacts.CONTENT_URI, true, mContactsObserver);
    212     } else {
    213       LogUtil.w("CallLogFragment.onCreate", "contacts permission not available.");
    214     }
    215     setHasOptionsMenu(true);
    216   }
    217 
    218   /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
    219   @Override
    220   public boolean onCallsFetched(Cursor cursor) {
    221     if (getActivity() == null || getActivity().isFinishing()) {
    222       // Return false; we did not take ownership of the cursor
    223       return false;
    224     }
    225     mAdapter.invalidatePositions();
    226     mAdapter.setLoading(false);
    227     mAdapter.changeCursor(cursor);
    228     // This will update the state of the "Clear call log" menu item.
    229     getActivity().invalidateOptionsMenu();
    230 
    231     if (cursor != null && cursor.getCount() > 0) {
    232       mRecyclerView.setPaddingRelative(
    233           mRecyclerView.getPaddingStart(),
    234           0,
    235           mRecyclerView.getPaddingEnd(),
    236           getResources().getDimensionPixelSize(R.dimen.floating_action_button_list_bottom_padding));
    237       mEmptyListView.setVisibility(View.GONE);
    238     } else {
    239       mRecyclerView.setPaddingRelative(
    240           mRecyclerView.getPaddingStart(), 0, mRecyclerView.getPaddingEnd(), 0);
    241       mEmptyListView.setVisibility(View.VISIBLE);
    242     }
    243     if (mScrollToTop) {
    244       // The smooth-scroll animation happens over a fixed time period.
    245       // As a result, if it scrolls through a large portion of the list,
    246       // each frame will jump so far from the previous one that the user
    247       // will not experience the illusion of downward motion.  Instead,
    248       // if we're not already near the top of the list, we instantly jump
    249       // near the top, and animate from there.
    250       if (mLayoutManager.findFirstVisibleItemPosition() > 5) {
    251         // TODO: Jump to near the top, then begin smooth scroll.
    252         mRecyclerView.smoothScrollToPosition(0);
    253       }
    254       // Workaround for framework issue: the smooth-scroll doesn't
    255       // occur if setSelection() is called immediately before.
    256       mHandler.post(
    257           new Runnable() {
    258             @Override
    259             public void run() {
    260               if (getActivity() == null || getActivity().isFinishing()) {
    261                 return;
    262               }
    263               mRecyclerView.smoothScrollToPosition(0);
    264             }
    265           });
    266 
    267       mScrollToTop = false;
    268     }
    269     return true;
    270   }
    271 
    272   @Override
    273   public void onVoicemailStatusFetched(Cursor statusCursor) {}
    274 
    275   @Override
    276   public void onVoicemailUnreadCountFetched(Cursor cursor) {}
    277 
    278   @Override
    279   public void onMissedCallsUnreadCountFetched(Cursor cursor) {}
    280 
    281   @Override
    282   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
    283     View view = inflater.inflate(R.layout.call_log_fragment, container, false);
    284     setupView(view);
    285     return view;
    286   }
    287 
    288   protected void setupView(View view) {
    289     mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
    290     mRecyclerView.setHasFixedSize(true);
    291     mLayoutManager = new LinearLayoutManager(getActivity());
    292     mRecyclerView.setLayoutManager(mLayoutManager);
    293     mEmptyListView = (EmptyContentView) view.findViewById(R.id.empty_list_view);
    294     mEmptyListView.setImage(R.drawable.empty_call_log);
    295     mEmptyListView.setActionClickedListener(this);
    296     mModalAlertView = (ViewGroup) view.findViewById(R.id.modal_message_container);
    297     mModalAlertManager =
    298         new CallLogModalAlertManager(LayoutInflater.from(getContext()), mModalAlertView, this);
    299   }
    300 
    301   protected void setupData() {
    302     int activityType =
    303         mIsCallLogActivity
    304             ? CallLogAdapter.ACTIVITY_TYPE_CALL_LOG
    305             : CallLogAdapter.ACTIVITY_TYPE_DIALTACTS;
    306     String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
    307 
    308     mContactInfoCache =
    309         new ContactInfoCache(
    310             ExpirableCacheHeadlessFragment.attach((AppCompatActivity) getActivity())
    311                 .getRetainedCache(),
    312             new ContactInfoHelper(getActivity(), currentCountryIso),
    313             mOnContactInfoChangedListener);
    314     mAdapter =
    315         Bindings.getLegacy(getActivity())
    316             .newCallLogAdapter(
    317                 getActivity(),
    318                 mRecyclerView,
    319                 this,
    320                 CallLogCache.getCallLogCache(getActivity()),
    321                 mContactInfoCache,
    322                 getVoicemailPlaybackPresenter(),
    323                 new FilteredNumberAsyncQueryHandler(getActivity()),
    324                 activityType);
    325     mRecyclerView.setAdapter(mAdapter);
    326     fetchCalls();
    327   }
    328 
    329   @Nullable
    330   protected VoicemailPlaybackPresenter getVoicemailPlaybackPresenter() {
    331     return null;
    332   }
    333 
    334   @Override
    335   public void onActivityCreated(Bundle savedInstanceState) {
    336     super.onActivityCreated(savedInstanceState);
    337     setupData();
    338     mAdapter.onRestoreInstanceState(savedInstanceState);
    339   }
    340 
    341   @Override
    342   public void onViewCreated(View view, Bundle savedInstanceState) {
    343     super.onViewCreated(view, savedInstanceState);
    344     updateEmptyMessage(mCallTypeFilter);
    345   }
    346 
    347   @Override
    348   public void onResume() {
    349     LogUtil.d("CallLogFragment.onResume", toString());
    350     super.onResume();
    351     final boolean hasReadCallLogPermission =
    352         PermissionsUtil.hasPermission(getActivity(), READ_CALL_LOG);
    353     if (!mHasReadCallLogPermission && hasReadCallLogPermission) {
    354       // We didn't have the permission before, and now we do. Force a refresh of the call log.
    355       // Note that this code path always happens on a fresh start, but mRefreshDataRequired
    356       // is already true in that case anyway.
    357       mRefreshDataRequired = true;
    358       updateEmptyMessage(mCallTypeFilter);
    359     }
    360 
    361     mHasReadCallLogPermission = hasReadCallLogPermission;
    362 
    363     /*
    364      * Always clear the filtered numbers cache since users could have blocked/unblocked numbers
    365      * from the settings page
    366      */
    367     mAdapter.clearFilteredNumbersCache();
    368     refreshData();
    369     mAdapter.onResume();
    370 
    371     rescheduleDisplayUpdate();
    372   }
    373 
    374   @Override
    375   public void onPause() {
    376     LogUtil.d("CallLogFragment.onPause", toString());
    377     cancelDisplayUpdate();
    378     mAdapter.onPause();
    379     super.onPause();
    380   }
    381 
    382   @Override
    383   public void onStop() {
    384     updateOnTransition();
    385 
    386     super.onStop();
    387     mAdapter.onStop();
    388     mContactInfoCache.stop();
    389   }
    390 
    391   @Override
    392   public void onDestroy() {
    393     LogUtil.d("CallLogFragment.onDestroy", toString());
    394     mAdapter.changeCursor(null);
    395 
    396     getActivity().getContentResolver().unregisterContentObserver(mCallLogObserver);
    397     getActivity().getContentResolver().unregisterContentObserver(mContactsObserver);
    398     super.onDestroy();
    399   }
    400 
    401   @Override
    402   public void onSaveInstanceState(Bundle outState) {
    403     super.onSaveInstanceState(outState);
    404     outState.putInt(KEY_FILTER_TYPE, mCallTypeFilter);
    405     outState.putInt(KEY_LOG_LIMIT, mLogLimit);
    406     outState.putLong(KEY_DATE_LIMIT, mDateLimit);
    407     outState.putBoolean(KEY_IS_CALL_LOG_ACTIVITY, mIsCallLogActivity);
    408     outState.putBoolean(KEY_HAS_READ_CALL_LOG_PERMISSION, mHasReadCallLogPermission);
    409     outState.putBoolean(KEY_REFRESH_DATA_REQUIRED, mRefreshDataRequired);
    410 
    411     mAdapter.onSaveInstanceState(outState);
    412   }
    413 
    414   @Override
    415   public void fetchCalls() {
    416     mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);
    417     if (!mIsCallLogActivity) {
    418       ((ListsFragment) getParentFragment()).updateTabUnreadCounts();
    419     }
    420   }
    421 
    422   private void updateEmptyMessage(int filterType) {
    423     final Context context = getActivity();
    424     if (context == null) {
    425       return;
    426     }
    427 
    428     if (!PermissionsUtil.hasPermission(context, READ_CALL_LOG)) {
    429       mEmptyListView.setDescription(R.string.permission_no_calllog);
    430       mEmptyListView.setActionLabel(R.string.permission_single_turn_on);
    431       return;
    432     }
    433 
    434     final int messageId;
    435     switch (filterType) {
    436       case Calls.MISSED_TYPE:
    437         messageId = R.string.call_log_missed_empty;
    438         break;
    439       case Calls.VOICEMAIL_TYPE:
    440         messageId = R.string.call_log_voicemail_empty;
    441         break;
    442       case CallLogQueryHandler.CALL_TYPE_ALL:
    443         messageId = R.string.call_log_all_empty;
    444         break;
    445       default:
    446         throw new IllegalArgumentException(
    447             "Unexpected filter type in CallLogFragment: " + filterType);
    448     }
    449     mEmptyListView.setDescription(messageId);
    450     if (mIsCallLogActivity) {
    451       mEmptyListView.setActionLabel(EmptyContentView.NO_LABEL);
    452     } else if (filterType == CallLogQueryHandler.CALL_TYPE_ALL) {
    453       mEmptyListView.setActionLabel(R.string.call_log_all_empty_action);
    454     }
    455   }
    456 
    457   public CallLogAdapter getAdapter() {
    458     return mAdapter;
    459   }
    460 
    461   @Override
    462   public void setMenuVisibility(boolean menuVisible) {
    463     super.setMenuVisibility(menuVisible);
    464     if (mMenuVisible != menuVisible) {
    465       mMenuVisible = menuVisible;
    466       if (!menuVisible) {
    467         updateOnTransition();
    468       } else if (isResumed()) {
    469         refreshData();
    470       }
    471     }
    472   }
    473 
    474   /** Requests updates to the data to be shown. */
    475   private void refreshData() {
    476     // Prevent unnecessary refresh.
    477     if (mRefreshDataRequired) {
    478       // Mark all entries in the contact info cache as out of date, so they will be looked up
    479       // again once being shown.
    480       mContactInfoCache.invalidate();
    481       mAdapter.setLoading(true);
    482 
    483       fetchCalls();
    484       mCallLogQueryHandler.fetchVoicemailStatus();
    485       mCallLogQueryHandler.fetchMissedCallsUnreadCount();
    486       updateOnTransition();
    487       mRefreshDataRequired = false;
    488     } else {
    489       // Refresh the display of the existing data to update the timestamp text descriptions.
    490       mAdapter.notifyDataSetChanged();
    491     }
    492   }
    493 
    494   /**
    495    * Updates the voicemail notification state.
    496    *
    497    * <p>TODO: Move to CallLogActivity
    498    */
    499   private void updateOnTransition() {
    500     // We don't want to update any call data when keyguard is on because the user has likely not
    501     // seen the new calls yet.
    502     // This might be called before onCreate() and thus we need to check null explicitly.
    503     if (mKeyguardManager != null
    504         && !mKeyguardManager.inKeyguardRestrictedInputMode()
    505         && mCallTypeFilter == Calls.VOICEMAIL_TYPE) {
    506       CallLogNotificationsService.markNewVoicemailsAsOld(getActivity(), null);
    507     }
    508   }
    509 
    510   @Override
    511   public void onEmptyViewActionButtonClicked() {
    512     final Activity activity = getActivity();
    513     if (activity == null) {
    514       return;
    515     }
    516 
    517     if (!PermissionsUtil.hasPermission(activity, READ_CALL_LOG)) {
    518       FragmentCompat.requestPermissions(
    519           this, new String[] {READ_CALL_LOG}, READ_CALL_LOG_PERMISSION_REQUEST_CODE);
    520     } else if (!mIsCallLogActivity) {
    521       // Show dialpad if we are not in the call log activity.
    522       ((HostInterface) activity).showDialpad();
    523     }
    524   }
    525 
    526   @Override
    527   public void onRequestPermissionsResult(
    528       int requestCode, String[] permissions, int[] grantResults) {
    529     if (requestCode == READ_CALL_LOG_PERMISSION_REQUEST_CODE) {
    530       if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
    531         // Force a refresh of the data since we were missing the permission before this.
    532         mRefreshDataRequired = true;
    533       }
    534     }
    535   }
    536 
    537   /** Schedules an update to the relative call times (X mins ago). */
    538   private void rescheduleDisplayUpdate() {
    539     if (!mDisplayUpdateHandler.hasMessages(EVENT_UPDATE_DISPLAY)) {
    540       long time = System.currentTimeMillis();
    541       // This value allows us to change the display relatively close to when the time changes
    542       // from one minute to the next.
    543       long millisUtilNextMinute = MILLIS_IN_MINUTE - (time % MILLIS_IN_MINUTE);
    544       mDisplayUpdateHandler.sendEmptyMessageDelayed(EVENT_UPDATE_DISPLAY, millisUtilNextMinute);
    545     }
    546   }
    547 
    548   /** Cancels any pending update requests to update the relative call times (X mins ago). */
    549   private void cancelDisplayUpdate() {
    550     mDisplayUpdateHandler.removeMessages(EVENT_UPDATE_DISPLAY);
    551   }
    552 
    553   @CallSuper
    554   public void onVisible() {
    555     LogUtil.enterBlock("CallLogFragment.onPageSelected");
    556     if (getActivity() != null) {
    557       ((HostInterface) getActivity())
    558           .enableFloatingButton(mModalAlertManager == null || mModalAlertManager.isEmpty());
    559     }
    560   }
    561 
    562   @CallSuper
    563   public void onNotVisible() {
    564     LogUtil.enterBlock("CallLogFragment.onPageUnselected");
    565   }
    566 
    567   @Override
    568   public void onShowModalAlert(boolean show) {
    569     LogUtil.d(
    570         "CallLogFragment.onShowModalAlert",
    571         "show: %b, fragment: %s, isVisible: %b",
    572         show,
    573         this,
    574         getUserVisibleHint());
    575     getAdapter().notifyDataSetChanged();
    576     HostInterface hostInterface = (HostInterface) getActivity();
    577     if (show) {
    578       mRecyclerView.setVisibility(View.GONE);
    579       mModalAlertView.setVisibility(View.VISIBLE);
    580       if (hostInterface != null && getUserVisibleHint()) {
    581         hostInterface.enableFloatingButton(false);
    582       }
    583     } else {
    584       mRecyclerView.setVisibility(View.VISIBLE);
    585       mModalAlertView.setVisibility(View.GONE);
    586       if (hostInterface != null && getUserVisibleHint()) {
    587         hostInterface.enableFloatingButton(true);
    588       }
    589     }
    590   }
    591 
    592   public interface HostInterface {
    593 
    594     void showDialpad();
    595 
    596     void enableFloatingButton(boolean enabled);
    597   }
    598 
    599   protected class CustomContentObserver extends ContentObserver {
    600 
    601     public CustomContentObserver() {
    602       super(mHandler);
    603     }
    604 
    605     @Override
    606     public void onChange(boolean selfChange) {
    607       mRefreshDataRequired = true;
    608     }
    609   }
    610 }
    611