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