Home | History | Annotate | Download | only in list
      1 /*
      2  * Copyright (C) 2013 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.list;
     18 
     19 import static android.support.v4.view.ViewPager.SCROLL_STATE_SETTLING;
     20 
     21 import android.app.Fragment;
     22 import android.content.SharedPreferences;
     23 import android.database.ContentObserver;
     24 import android.database.Cursor;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.os.Trace;
     28 import android.preference.PreferenceManager;
     29 import android.provider.VoicemailContract;
     30 import android.support.v4.view.ViewPager.OnPageChangeListener;
     31 import android.view.LayoutInflater;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import com.android.contacts.common.list.ViewPagerTabs;
     35 import com.android.dialer.app.R;
     36 import com.android.dialer.app.calllog.CallLogFragment;
     37 import com.android.dialer.app.calllog.CallLogFragment.CallLogFragmentListener;
     38 import com.android.dialer.app.calllog.CallLogNotificationsService;
     39 import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment;
     40 import com.android.dialer.common.LogUtil;
     41 import com.android.dialer.database.CallLogQueryHandler;
     42 import com.android.dialer.database.CallLogQueryHandler.Listener;
     43 import com.android.dialer.logging.DialerImpression;
     44 import com.android.dialer.logging.Logger;
     45 import com.android.dialer.logging.ScreenEvent;
     46 import com.android.dialer.logging.UiAction;
     47 import com.android.dialer.performancereport.PerformanceReport;
     48 import com.android.dialer.speeddial.SpeedDialFragment;
     49 import com.android.dialer.util.PermissionsUtil;
     50 import com.android.dialer.voicemail.listui.error.VoicemailStatusCorruptionHandler;
     51 import com.android.dialer.voicemail.listui.error.VoicemailStatusCorruptionHandler.Source;
     52 import com.android.dialer.voicemailstatus.VisualVoicemailEnabledChecker;
     53 import com.android.dialer.voicemailstatus.VoicemailStatusHelper;
     54 import java.util.ArrayList;
     55 
     56 /**
     57  * Fragment that is used as the main screen of the Dialer.
     58  *
     59  * <p>Contains a ViewPager that contains various contact lists like the Speed Dial list and the All
     60  * Contacts list. This will also eventually contain the logic that allows sliding the ViewPager
     61  * containing the lists up above the search bar and pin it against the top of the screen.
     62  */
     63 public class ListsFragment extends Fragment
     64     implements OnPageChangeListener, Listener, CallLogFragmentListener {
     65 
     66   private static final String TAG = "ListsFragment";
     67 
     68   private DialerViewPager viewPager;
     69   private ViewPagerTabs viewPagerTabs;
     70   private DialtactsPagerAdapter adapter;
     71   private RemoveView removeView;
     72   private View removeViewContent;
     73   private Fragment currentPage;
     74   private SharedPreferences prefs;
     75   private boolean hasFetchedVoicemailStatus;
     76   private boolean showVoicemailTabAfterVoicemailStatusIsFetched;
     77   private final ArrayList<OnPageChangeListener> onPageChangeListeners = new ArrayList<>();
     78   /** The position of the currently selected tab. */
     79   private int tabIndex = DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL;
     80 
     81   private boolean paused;
     82   private CallLogQueryHandler callLogQueryHandler;
     83 
     84   private UiAction.Type[] actionTypeList;
     85   private final DialerImpression.Type[] swipeImpressionList =
     86       new DialerImpression.Type[DialtactsPagerAdapter.TAB_COUNT_WITH_VOICEMAIL];
     87   private final DialerImpression.Type[] clickImpressionList =
     88       new DialerImpression.Type[DialtactsPagerAdapter.TAB_COUNT_WITH_VOICEMAIL];
     89 
     90   // Only for detecting page selected by swiping or clicking.
     91   private boolean onPageScrolledBeforeScrollStateSettling;
     92 
     93   private final ContentObserver voicemailStatusObserver =
     94       new ContentObserver(new Handler()) {
     95         @Override
     96         public void onChange(boolean selfChange) {
     97           super.onChange(selfChange);
     98           callLogQueryHandler.fetchVoicemailStatus();
     99         }
    100       };
    101 
    102   @Override
    103   public void onCreate(Bundle savedInstanceState) {
    104     LogUtil.d("ListsFragment.onCreate", null);
    105     Trace.beginSection(TAG + " onCreate");
    106     super.onCreate(savedInstanceState);
    107     prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
    108     Trace.endSection();
    109   }
    110 
    111   @Override
    112   public void onResume() {
    113     LogUtil.enterBlock("ListsFragment.onResume");
    114     Trace.beginSection(TAG + " onResume");
    115     super.onResume();
    116 
    117     paused = false;
    118 
    119     if (getUserVisibleHint()) {
    120       sendScreenViewForCurrentPosition();
    121     }
    122 
    123     // Fetch voicemail status to determine if we should show the voicemail tab.
    124     callLogQueryHandler =
    125         new CallLogQueryHandler(getActivity(), getActivity().getContentResolver(), this);
    126     callLogQueryHandler.fetchVoicemailStatus();
    127     callLogQueryHandler.fetchMissedCallsUnreadCount();
    128     Trace.endSection();
    129     currentPage = adapter.getItem(viewPager.getCurrentItem());
    130   }
    131 
    132   @Override
    133   public void onPause() {
    134     LogUtil.enterBlock("ListsFragment.onPause");
    135     super.onPause();
    136 
    137     paused = true;
    138   }
    139 
    140   @Override
    141   public void onDestroyView() {
    142     super.onDestroyView();
    143     viewPager.removeOnPageChangeListener(this);
    144   }
    145 
    146   @Override
    147   public View onCreateView(
    148       LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    149     LogUtil.enterBlock("ListsFragment.onCreateView");
    150     Trace.beginSection(TAG + " onCreateView");
    151     Trace.beginSection(TAG + " inflate view");
    152     final View parentView = inflater.inflate(R.layout.lists_fragment, container, false);
    153     Trace.endSection();
    154     Trace.beginSection(TAG + " setup views");
    155 
    156     actionTypeList = new UiAction.Type[DialtactsPagerAdapter.TAB_COUNT_WITH_VOICEMAIL];
    157     actionTypeList[DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL] =
    158         UiAction.Type.CHANGE_TAB_TO_FAVORITE;
    159     actionTypeList[DialtactsPagerAdapter.TAB_INDEX_HISTORY] = UiAction.Type.CHANGE_TAB_TO_CALL_LOG;
    160     actionTypeList[DialtactsPagerAdapter.TAB_INDEX_ALL_CONTACTS] =
    161         UiAction.Type.CHANGE_TAB_TO_CONTACTS;
    162     actionTypeList[DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL] =
    163         UiAction.Type.CHANGE_TAB_TO_VOICEMAIL;
    164 
    165     swipeImpressionList[DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL] =
    166         DialerImpression.Type.SWITCH_TAB_TO_FAVORITE_BY_SWIPE;
    167     swipeImpressionList[DialtactsPagerAdapter.TAB_INDEX_HISTORY] =
    168         DialerImpression.Type.SWITCH_TAB_TO_CALL_LOG_BY_SWIPE;
    169     swipeImpressionList[DialtactsPagerAdapter.TAB_INDEX_ALL_CONTACTS] =
    170         DialerImpression.Type.SWITCH_TAB_TO_CONTACTS_BY_SWIPE;
    171     swipeImpressionList[DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL] =
    172         DialerImpression.Type.SWITCH_TAB_TO_VOICEMAIL_BY_SWIPE;
    173 
    174     clickImpressionList[DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL] =
    175         DialerImpression.Type.SWITCH_TAB_TO_FAVORITE_BY_CLICK;
    176     clickImpressionList[DialtactsPagerAdapter.TAB_INDEX_HISTORY] =
    177         DialerImpression.Type.SWITCH_TAB_TO_CALL_LOG_BY_CLICK;
    178     clickImpressionList[DialtactsPagerAdapter.TAB_INDEX_ALL_CONTACTS] =
    179         DialerImpression.Type.SWITCH_TAB_TO_CONTACTS_BY_CLICK;
    180     clickImpressionList[DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL] =
    181         DialerImpression.Type.SWITCH_TAB_TO_VOICEMAIL_BY_CLICK;
    182 
    183     String[] tabTitles = new String[DialtactsPagerAdapter.TAB_COUNT_WITH_VOICEMAIL];
    184     tabTitles[DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL] =
    185         getResources().getString(R.string.tab_speed_dial);
    186     tabTitles[DialtactsPagerAdapter.TAB_INDEX_HISTORY] =
    187         getResources().getString(R.string.tab_history);
    188     tabTitles[DialtactsPagerAdapter.TAB_INDEX_ALL_CONTACTS] =
    189         getResources().getString(R.string.tab_all_contacts);
    190     tabTitles[DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL] =
    191         getResources().getString(R.string.tab_voicemail);
    192 
    193     int[] tabIcons = new int[DialtactsPagerAdapter.TAB_COUNT_WITH_VOICEMAIL];
    194     tabIcons[DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL] = R.drawable.quantum_ic_grade_white_24;
    195     tabIcons[DialtactsPagerAdapter.TAB_INDEX_HISTORY] = R.drawable.quantum_ic_schedule_white_24;
    196     tabIcons[DialtactsPagerAdapter.TAB_INDEX_ALL_CONTACTS] = R.drawable.quantum_ic_people_white_24;
    197     tabIcons[DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL] = R.drawable.quantum_ic_voicemail_white_24;
    198 
    199     viewPager = (DialerViewPager) parentView.findViewById(R.id.lists_pager);
    200     adapter =
    201         new DialtactsPagerAdapter(
    202             getContext(),
    203             getChildFragmentManager(),
    204             tabTitles,
    205             prefs.getBoolean(
    206                 VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, false));
    207     viewPager.setAdapter(adapter);
    208 
    209     // This is deliberate. See cl/172018946 for the app startup implications of using 1 here
    210     // versus loading more fragments upfront.
    211     viewPager.setOffscreenPageLimit(1);
    212 
    213     viewPager.addOnPageChangeListener(this);
    214     showTab(DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL);
    215 
    216     viewPagerTabs = (ViewPagerTabs) parentView.findViewById(R.id.lists_pager_header);
    217     viewPagerTabs.configureTabIcons(tabIcons);
    218     viewPagerTabs.setViewPager(viewPager);
    219     addOnPageChangeListener(viewPagerTabs);
    220     removeView = (RemoveView) parentView.findViewById(R.id.remove_view);
    221     removeViewContent = parentView.findViewById(R.id.remove_view_content);
    222 
    223     if (PermissionsUtil.hasReadVoicemailPermissions(getContext())
    224         && PermissionsUtil.hasAddVoicemailPermissions(getContext())) {
    225       getActivity()
    226           .getContentResolver()
    227           .registerContentObserver(
    228               VoicemailContract.Status.CONTENT_URI, true, voicemailStatusObserver);
    229     } else {
    230       LogUtil.w("ListsFragment.onCreateView", "no voicemail read permissions");
    231     }
    232 
    233     Trace.endSection();
    234     Trace.endSection();
    235     return parentView;
    236   }
    237 
    238   @Override
    239   public void onDestroy() {
    240     getActivity().getContentResolver().unregisterContentObserver(voicemailStatusObserver);
    241     super.onDestroy();
    242   }
    243 
    244   public void addOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
    245     if (!onPageChangeListeners.contains(onPageChangeListener)) {
    246       onPageChangeListeners.add(onPageChangeListener);
    247     }
    248   }
    249 
    250   /**
    251    * Shows the tab with the specified index. If the voicemail tab index is specified, but the
    252    * voicemail status hasn't been fetched, it will show the speed dial tab and try to show the
    253    * voicemail tab after the voicemail status has been fetched.
    254    */
    255   public void showTab(int index) {
    256     if (index == DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL) {
    257       if (adapter.hasActiveVoicemailProvider()) {
    258         viewPager.setCurrentItem(adapter.getRtlPosition(DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL));
    259       } else if (!hasFetchedVoicemailStatus) {
    260         // Try to show the voicemail tab after the voicemail status returns.
    261         showVoicemailTabAfterVoicemailStatusIsFetched = true;
    262       }
    263     } else if (index < getTabCount()) {
    264       viewPager.setCurrentItem(adapter.getRtlPosition(index));
    265     }
    266   }
    267 
    268   @Override
    269   public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    270     // onPageScrolled(0, 0, 0) is called when app launch. And we should ignore it.
    271     // It's also called when swipe right from first tab, but we don't care.
    272     if (positionOffsetPixels != 0) {
    273       onPageScrolledBeforeScrollStateSettling = true;
    274     }
    275     tabIndex = adapter.getRtlPosition(position);
    276 
    277     final int count = onPageChangeListeners.size();
    278     for (int i = 0; i < count; i++) {
    279       onPageChangeListeners.get(i).onPageScrolled(position, positionOffset, positionOffsetPixels);
    280     }
    281   }
    282 
    283   @Override
    284   public void onPageSelected(int position) {
    285     // onPageScrollStateChanged(SCROLL_STATE_SETTLING) must be called before this.
    286     // If onPageScrolled() is called before that, the page is selected by swiping;
    287     // otherwise the page is selected by clicking.
    288     if (onPageScrolledBeforeScrollStateSettling) {
    289       Logger.get(getContext()).logImpression(swipeImpressionList[position]);
    290       onPageScrolledBeforeScrollStateSettling = false;
    291     } else {
    292       Logger.get(getContext()).logImpression(clickImpressionList[position]);
    293     }
    294 
    295     PerformanceReport.recordClick(actionTypeList[position]);
    296 
    297     LogUtil.i("ListsFragment.onPageSelected", "position: %d", position);
    298     tabIndex = adapter.getRtlPosition(position);
    299 
    300     // Show the tab which has been selected instead.
    301     showVoicemailTabAfterVoicemailStatusIsFetched = false;
    302 
    303     final int count = onPageChangeListeners.size();
    304     for (int i = 0; i < count; i++) {
    305       onPageChangeListeners.get(i).onPageSelected(position);
    306     }
    307     sendScreenViewForCurrentPosition();
    308 
    309     if (currentPage instanceof CallLogFragment) {
    310       ((CallLogFragment) currentPage).onNotVisible();
    311     }
    312     currentPage = adapter.getItem(position);
    313     if (currentPage instanceof CallLogFragment) {
    314       ((CallLogFragment) currentPage).onVisible();
    315     }
    316   }
    317 
    318   @Override
    319   public void onPageScrollStateChanged(int state) {
    320     if (state != SCROLL_STATE_SETTLING) {
    321       onPageScrolledBeforeScrollStateSettling = false;
    322     }
    323 
    324     final int count = onPageChangeListeners.size();
    325     for (int i = 0; i < count; i++) {
    326       onPageChangeListeners.get(i).onPageScrollStateChanged(state);
    327     }
    328   }
    329 
    330   @Override
    331   public void onVoicemailStatusFetched(Cursor statusCursor) {
    332     hasFetchedVoicemailStatus = true;
    333 
    334     if (getActivity() == null || paused) {
    335       return;
    336     }
    337 
    338     VoicemailStatusCorruptionHandler.maybeFixVoicemailStatus(
    339         getContext(), statusCursor, Source.Activity);
    340 
    341     // Update hasActiveVoicemailProvider, which controls the number of tabs displayed.
    342     boolean hasActiveVoicemailProvider =
    343         VoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0;
    344     if (hasActiveVoicemailProvider != adapter.hasActiveVoicemailProvider()) {
    345       adapter.setHasActiveVoicemailProvider(hasActiveVoicemailProvider);
    346       adapter.notifyDataSetChanged();
    347 
    348       if (hasActiveVoicemailProvider) {
    349         Logger.get(getContext()).logImpression(DialerImpression.Type.VVM_TAB_VISIBLE);
    350         viewPagerTabs.updateTab(DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL);
    351       } else {
    352         viewPagerTabs.removeTab(DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL);
    353         adapter.removeVoicemailFragment(getChildFragmentManager());
    354       }
    355 
    356       prefs
    357           .edit()
    358           .putBoolean(
    359               VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER,
    360               hasActiveVoicemailProvider)
    361           .apply();
    362     }
    363 
    364     if (hasActiveVoicemailProvider) {
    365       callLogQueryHandler.fetchVoicemailUnreadCount();
    366     }
    367 
    368     if (adapter.hasActiveVoicemailProvider() && showVoicemailTabAfterVoicemailStatusIsFetched) {
    369       showVoicemailTabAfterVoicemailStatusIsFetched = false;
    370       showTab(DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL);
    371     }
    372   }
    373 
    374   @Override
    375   public void onVoicemailUnreadCountFetched(Cursor cursor) {
    376     if (getActivity() == null || getActivity().isFinishing() || cursor == null) {
    377       return;
    378     }
    379 
    380     int count = 0;
    381     try {
    382       count = cursor.getCount();
    383     } finally {
    384       cursor.close();
    385     }
    386 
    387     viewPagerTabs.setUnreadCount(count, DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL);
    388     viewPagerTabs.updateTab(DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL);
    389   }
    390 
    391   @Override
    392   public void onMissedCallsUnreadCountFetched(Cursor cursor) {
    393     if (getActivity() == null || getActivity().isFinishing() || cursor == null) {
    394       return;
    395     }
    396 
    397     int count = 0;
    398     try {
    399       count = cursor.getCount();
    400     } finally {
    401       cursor.close();
    402     }
    403 
    404     viewPagerTabs.setUnreadCount(count, DialtactsPagerAdapter.TAB_INDEX_HISTORY);
    405     viewPagerTabs.updateTab(DialtactsPagerAdapter.TAB_INDEX_HISTORY);
    406   }
    407 
    408   @Override
    409   public boolean onCallsFetched(Cursor statusCursor) {
    410     // Return false; did not take ownership of cursor
    411     return false;
    412   }
    413 
    414   public int getCurrentTabIndex() {
    415     return tabIndex;
    416   }
    417 
    418   public boolean shouldShowFab() {
    419     // If the VVM TOS is visible, don't show the fab
    420     if (currentPage instanceof VisualVoicemailCallLogFragment
    421         && ((VisualVoicemailCallLogFragment) currentPage).isModalAlertVisible()) {
    422       return false;
    423     }
    424 
    425     return true;
    426   }
    427 
    428   @Override
    429   public void updateTabUnreadCounts() {
    430     if (callLogQueryHandler != null) {
    431       callLogQueryHandler.fetchMissedCallsUnreadCount();
    432       if (adapter.hasActiveVoicemailProvider()) {
    433         callLogQueryHandler.fetchVoicemailUnreadCount();
    434       }
    435     }
    436   }
    437 
    438   /** External method to mark all missed calls as read. */
    439   public void markMissedCallsAsReadAndRemoveNotifications() {
    440     if (callLogQueryHandler != null) {
    441       callLogQueryHandler.markMissedCallsAsRead();
    442       CallLogNotificationsService.cancelAllMissedCalls(getContext());
    443     }
    444   }
    445 
    446   public void showRemoveView(boolean show) {
    447     removeViewContent.setVisibility(show ? View.VISIBLE : View.GONE);
    448     removeView.setAlpha(show ? 0 : 1);
    449     removeView.animate().alpha(show ? 1 : 0).start();
    450   }
    451 
    452   @Override
    453   public void showMultiSelectRemoveView(boolean show) {
    454     viewPagerTabs.setVisibility(show ? View.GONE : View.VISIBLE);
    455     viewPager.setEnableSwipingPages(!show);
    456   }
    457 
    458   public boolean hasFrequents() {
    459     Fragment page =
    460         adapter.getItem(adapter.getRtlPosition(DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL));
    461     return page instanceof OldSpeedDialFragment
    462         ? ((OldSpeedDialFragment) page).hasFrequents()
    463         : ((SpeedDialFragment) page).hasFrequents();
    464   }
    465 
    466   public RemoveView getRemoveView() {
    467     return removeView;
    468   }
    469 
    470   public int getTabCount() {
    471     return adapter.getCount();
    472   }
    473 
    474   public void sendScreenViewForCurrentPosition() {
    475     if (!isResumed()) {
    476       return;
    477     }
    478 
    479     ScreenEvent.Type screenType;
    480     switch (getCurrentTabIndex()) {
    481       case DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL:
    482         screenType = ScreenEvent.Type.SPEED_DIAL;
    483         break;
    484       case DialtactsPagerAdapter.TAB_INDEX_HISTORY:
    485         screenType = ScreenEvent.Type.CALL_LOG;
    486         break;
    487       case DialtactsPagerAdapter.TAB_INDEX_ALL_CONTACTS:
    488         screenType = ScreenEvent.Type.ALL_CONTACTS;
    489         break;
    490       case DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL:
    491         screenType = ScreenEvent.Type.VOICEMAIL_LOG;
    492         break;
    493       default:
    494         return;
    495     }
    496     Logger.get(getActivity()).logScreenView(screenType, getActivity());
    497   }
    498 }
    499