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