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 
CallLogFragment()143   public CallLogFragment() {
144     this(CallLogQueryHandler.CALL_TYPE_ALL, NO_LOG_LIMIT);
145   }
146 
CallLogFragment(int filterType)147   public CallLogFragment(int filterType) {
148     this(filterType, NO_LOG_LIMIT);
149   }
150 
CallLogFragment(int filterType, boolean isCallLogActivity)151   public CallLogFragment(int filterType, boolean isCallLogActivity) {
152     this(filterType, NO_LOG_LIMIT);
153     mIsCallLogActivity = isCallLogActivity;
154   }
155 
CallLogFragment(int filterType, int logLimit)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    */
CallLogFragment(int filterType, long dateLimit)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    */
CallLogFragment(int filterType, int logLimit, long dateLimit)179   public CallLogFragment(int filterType, int logLimit, long dateLimit) {
180     mCallTypeFilter = filterType;
181     mLogLimit = logLimit;
182     mDateLimit = dateLimit;
183   }
184 
185   @Override
onCreate(Bundle state)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
onCallsFetched(Cursor cursor)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
onVoicemailStatusFetched(Cursor statusCursor)273   public void onVoicemailStatusFetched(Cursor statusCursor) {}
274 
275   @Override
onVoicemailUnreadCountFetched(Cursor cursor)276   public void onVoicemailUnreadCountFetched(Cursor cursor) {}
277 
278   @Override
onMissedCallsUnreadCountFetched(Cursor cursor)279   public void onMissedCallsUnreadCountFetched(Cursor cursor) {}
280 
281   @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState)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 
setupView(View view)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 
setupData()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
getVoicemailPlaybackPresenter()330   protected VoicemailPlaybackPresenter getVoicemailPlaybackPresenter() {
331     return null;
332   }
333 
334   @Override
onActivityCreated(Bundle savedInstanceState)335   public void onActivityCreated(Bundle savedInstanceState) {
336     super.onActivityCreated(savedInstanceState);
337     setupData();
338     mAdapter.onRestoreInstanceState(savedInstanceState);
339   }
340 
341   @Override
onViewCreated(View view, Bundle savedInstanceState)342   public void onViewCreated(View view, Bundle savedInstanceState) {
343     super.onViewCreated(view, savedInstanceState);
344     updateEmptyMessage(mCallTypeFilter);
345   }
346 
347   @Override
onResume()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
onPause()375   public void onPause() {
376     LogUtil.d("CallLogFragment.onPause", toString());
377     cancelDisplayUpdate();
378     mAdapter.onPause();
379     super.onPause();
380   }
381 
382   @Override
onStop()383   public void onStop() {
384     updateOnTransition();
385 
386     super.onStop();
387     mAdapter.onStop();
388     mContactInfoCache.stop();
389   }
390 
391   @Override
onDestroy()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
onSaveInstanceState(Bundle outState)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
fetchCalls()415   public void fetchCalls() {
416     mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);
417     if (!mIsCallLogActivity) {
418       ((ListsFragment) getParentFragment()).updateTabUnreadCounts();
419     }
420   }
421 
updateEmptyMessage(int filterType)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 
getAdapter()457   public CallLogAdapter getAdapter() {
458     return mAdapter;
459   }
460 
461   @Override
setMenuVisibility(boolean menuVisible)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. */
refreshData()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    */
updateOnTransition()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
onEmptyViewActionButtonClicked()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
onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults)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). */
rescheduleDisplayUpdate()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). */
cancelDisplayUpdate()549   private void cancelDisplayUpdate() {
550     mDisplayUpdateHandler.removeMessages(EVENT_UPDATE_DISPLAY);
551   }
552 
553   @CallSuper
onVisible()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
onNotVisible()563   public void onNotVisible() {
564     LogUtil.enterBlock("CallLogFragment.onPageUnselected");
565   }
566 
567   @Override
onShowModalAlert(boolean show)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 
showDialpad()594     void showDialpad();
595 
enableFloatingButton(boolean enabled)596     void enableFloatingButton(boolean enabled);
597   }
598 
599   protected class CustomContentObserver extends ContentObserver {
600 
CustomContentObserver()601     public CustomContentObserver() {
602       super(mHandler);
603     }
604 
605     @Override
onChange(boolean selfChange)606     public void onChange(boolean selfChange) {
607       mRefreshDataRequired = true;
608     }
609   }
610 }
611