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