ListsFragment.java revision ccca31529c07970e89419fb85a9e8153a5396838
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 */
16package com.android.dialer.app.list;
17
18import android.app.Activity;
19import android.app.Fragment;
20import android.app.FragmentManager;
21import android.content.SharedPreferences;
22import android.database.ContentObserver;
23import android.database.Cursor;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.Trace;
27import android.preference.PreferenceManager;
28import android.provider.VoicemailContract;
29import android.support.annotation.Nullable;
30import android.support.v13.app.FragmentPagerAdapter;
31import android.support.v4.view.ViewPager;
32import android.support.v4.view.ViewPager.OnPageChangeListener;
33import android.support.v7.app.ActionBar;
34import android.support.v7.app.AppCompatActivity;
35import android.view.LayoutInflater;
36import android.view.View;
37import android.view.ViewGroup;
38import com.android.contacts.common.list.ViewPagerTabs;
39import com.android.dialer.app.R;
40import com.android.dialer.app.calllog.CallLogFragment;
41import com.android.dialer.app.calllog.CallLogNotificationsHelper;
42import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment;
43import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler;
44import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler.Source;
45import com.android.dialer.app.widget.ActionBarController;
46import com.android.dialer.common.LogUtil;
47import com.android.dialer.database.CallLogQueryHandler;
48import com.android.dialer.logging.Logger;
49import com.android.dialer.logging.nano.DialerImpression;
50import com.android.dialer.logging.nano.ScreenEvent;
51import com.android.dialer.util.ViewUtil;
52import com.android.dialer.voicemailstatus.VisualVoicemailEnabledChecker;
53import com.android.dialer.voicemailstatus.VoicemailStatusHelper;
54import com.android.dialer.voicemailstatus.VoicemailStatusHelperImpl;
55import java.util.ArrayList;
56import java.util.List;
57
58/**
59 * Fragment that is used as the main screen of the Dialer.
60 *
61 * <p>Contains a ViewPager that contains various contact lists like the Speed Dial list and the All
62 * Contacts list. This will also eventually contain the logic that allows sliding the ViewPager
63 * containing the lists up above the search bar and pin it against the top of the screen.
64 */
65public class ListsFragment extends Fragment
66    implements ViewPager.OnPageChangeListener, CallLogQueryHandler.Listener {
67
68  /** Every fragment in the list show implement this interface. */
69  public interface ListsPage {
70
71    /**
72     * Called when the page is resumed, including selecting the page or activity resume. Note: This
73     * is called before the page fragment is attached to a activity.
74     *
75     * @param activity the activity hosting the ListFragment
76     */
77    void onPageResume(@Nullable Activity activity);
78
79    /**
80     * Called when the page is paused, including selecting another page or activity pause. Note:
81     * This is called after the page fragment is detached from a activity.
82     *
83     * @param activity the activity hosting the ListFragment
84     */
85    void onPagePause(@Nullable Activity activity);
86  }
87
88  public static final int TAB_INDEX_SPEED_DIAL = 0;
89  public static final int TAB_INDEX_HISTORY = 1;
90  public static final int TAB_INDEX_ALL_CONTACTS = 2;
91  public static final int TAB_INDEX_VOICEMAIL = 3;
92  public static final int TAB_COUNT_DEFAULT = 3;
93  public static final int TAB_COUNT_WITH_VOICEMAIL = 4;
94  private static final String TAG = "ListsFragment";
95  private ActionBar mActionBar;
96  private ViewPager mViewPager;
97  private ViewPagerTabs mViewPagerTabs;
98  private ViewPagerAdapter mViewPagerAdapter;
99  private RemoveView mRemoveView;
100  private View mRemoveViewContent;
101  private SpeedDialFragment mSpeedDialFragment;
102  private CallLogFragment mHistoryFragment;
103  private AllContactsFragment mAllContactsFragment;
104  private CallLogFragment mVoicemailFragment;
105  private ListsPage mCurrentPage;
106  private SharedPreferences mPrefs;
107  private boolean mHasActiveVoicemailProvider;
108  private boolean mHasFetchedVoicemailStatus;
109  private boolean mShowVoicemailTabAfterVoicemailStatusIsFetched;
110  private VoicemailStatusHelper mVoicemailStatusHelper;
111  private ArrayList<OnPageChangeListener> mOnPageChangeListeners =
112      new ArrayList<OnPageChangeListener>();
113  private String[] mTabTitles;
114  private int[] mTabIcons;
115  /** The position of the currently selected tab. */
116  private int mTabIndex = TAB_INDEX_SPEED_DIAL;
117
118  private CallLogQueryHandler mCallLogQueryHandler;
119
120  private final ContentObserver mVoicemailStatusObserver =
121      new ContentObserver(new Handler()) {
122        @Override
123        public void onChange(boolean selfChange) {
124          super.onChange(selfChange);
125          mCallLogQueryHandler.fetchVoicemailStatus();
126        }
127      };
128
129  @Override
130  public void onCreate(Bundle savedInstanceState) {
131    LogUtil.d("ListsFragment.onCreate", null);
132    Trace.beginSection(TAG + " onCreate");
133    super.onCreate(savedInstanceState);
134
135    mVoicemailStatusHelper = new VoicemailStatusHelperImpl();
136    mHasFetchedVoicemailStatus = false;
137
138    mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
139    mHasActiveVoicemailProvider =
140        mPrefs.getBoolean(
141            VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, false);
142
143    Trace.endSection();
144  }
145
146  @Override
147  public void onResume() {
148    LogUtil.d("ListsFragment.onResume", null);
149    Trace.beginSection(TAG + " onResume");
150    super.onResume();
151
152    mActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
153    if (getUserVisibleHint()) {
154      sendScreenViewForCurrentPosition();
155    }
156
157    // Fetch voicemail status to determine if we should show the voicemail tab.
158    mCallLogQueryHandler =
159        new CallLogQueryHandler(getActivity(), getActivity().getContentResolver(), this);
160    mCallLogQueryHandler.fetchVoicemailStatus();
161    mCallLogQueryHandler.fetchMissedCallsUnreadCount();
162    Trace.endSection();
163    mCurrentPage = getListsPage(mViewPager.getCurrentItem());
164    if (mCurrentPage != null) {
165      mCurrentPage.onPageResume(getActivity());
166    }
167  }
168
169  @Override
170  public void onPause() {
171    LogUtil.d("ListsFragment.onPause", null);
172    if (mCurrentPage != null) {
173      mCurrentPage.onPagePause(getActivity());
174    }
175    super.onPause();
176  }
177
178  @Override
179  public void onDestroyView() {
180    super.onDestroyView();
181    mViewPager.removeOnPageChangeListener(this);
182  }
183
184  @Override
185  public View onCreateView(
186      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
187    LogUtil.d("ListsFragment.onCreateView", null);
188    Trace.beginSection(TAG + " onCreateView");
189    Trace.beginSection(TAG + " inflate view");
190    final View parentView = inflater.inflate(R.layout.lists_fragment, container, false);
191    Trace.endSection();
192    Trace.beginSection(TAG + " setup views");
193    mViewPager = (ViewPager) parentView.findViewById(R.id.lists_pager);
194    mViewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
195    mViewPager.setAdapter(mViewPagerAdapter);
196    mViewPager.setOffscreenPageLimit(TAB_COUNT_WITH_VOICEMAIL - 1);
197    mViewPager.addOnPageChangeListener(this);
198    showTab(TAB_INDEX_SPEED_DIAL);
199
200    mTabTitles = new String[TAB_COUNT_WITH_VOICEMAIL];
201    mTabTitles[TAB_INDEX_SPEED_DIAL] = getResources().getString(R.string.tab_speed_dial);
202    mTabTitles[TAB_INDEX_HISTORY] = getResources().getString(R.string.tab_history);
203    mTabTitles[TAB_INDEX_ALL_CONTACTS] = getResources().getString(R.string.tab_all_contacts);
204    mTabTitles[TAB_INDEX_VOICEMAIL] = getResources().getString(R.string.tab_voicemail);
205
206    mTabIcons = new int[TAB_COUNT_WITH_VOICEMAIL];
207    mTabIcons[TAB_INDEX_SPEED_DIAL] = R.drawable.ic_grade_24dp;
208    mTabIcons[TAB_INDEX_HISTORY] = R.drawable.ic_schedule_24dp;
209    mTabIcons[TAB_INDEX_ALL_CONTACTS] = R.drawable.ic_people_24dp;
210    mTabIcons[TAB_INDEX_VOICEMAIL] = R.drawable.ic_voicemail_24dp;
211
212    mViewPagerTabs = (ViewPagerTabs) parentView.findViewById(R.id.lists_pager_header);
213    mViewPagerTabs.configureTabIcons(mTabIcons);
214    mViewPagerTabs.setViewPager(mViewPager);
215    addOnPageChangeListener(mViewPagerTabs);
216
217    mRemoveView = (RemoveView) parentView.findViewById(R.id.remove_view);
218    mRemoveViewContent = parentView.findViewById(R.id.remove_view_content);
219
220    getActivity()
221        .getContentResolver()
222        .registerContentObserver(
223            VoicemailContract.Status.CONTENT_URI, true, mVoicemailStatusObserver);
224
225    Trace.endSection();
226    Trace.endSection();
227    return parentView;
228  }
229
230  @Override
231  public void onDestroy() {
232    getActivity().getContentResolver().unregisterContentObserver(mVoicemailStatusObserver);
233    super.onDestroy();
234  }
235
236  public void addOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
237    if (!mOnPageChangeListeners.contains(onPageChangeListener)) {
238      mOnPageChangeListeners.add(onPageChangeListener);
239    }
240  }
241
242  /**
243   * Shows the tab with the specified index. If the voicemail tab index is specified, but the
244   * voicemail status hasn't been fetched, it will try to show the tab after the voicemail status
245   * has been fetched.
246   */
247  public void showTab(int index) {
248    if (index == TAB_INDEX_VOICEMAIL) {
249      if (mHasActiveVoicemailProvider) {
250        Logger.get(getContext()).logImpression(DialerImpression.Type.VVM_TAB_VISIBLE);
251        mViewPager.setCurrentItem(getRtlPosition(TAB_INDEX_VOICEMAIL));
252      } else if (!mHasFetchedVoicemailStatus) {
253        // Try to show the voicemail tab after the voicemail status returns.
254        mShowVoicemailTabAfterVoicemailStatusIsFetched = true;
255      }
256    } else if (index < getTabCount()) {
257      mViewPager.setCurrentItem(getRtlPosition(index));
258    }
259  }
260
261  @Override
262  public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
263    mTabIndex = getRtlPosition(position);
264
265    final int count = mOnPageChangeListeners.size();
266    for (int i = 0; i < count; i++) {
267      mOnPageChangeListeners.get(i).onPageScrolled(position, positionOffset, positionOffsetPixels);
268    }
269  }
270
271  @Override
272  public void onPageSelected(int position) {
273    LogUtil.i("ListsFragment.onPageSelected", "position: %d", position);
274    mTabIndex = getRtlPosition(position);
275
276    // Show the tab which has been selected instead.
277    mShowVoicemailTabAfterVoicemailStatusIsFetched = false;
278
279    final int count = mOnPageChangeListeners.size();
280    for (int i = 0; i < count; i++) {
281      mOnPageChangeListeners.get(i).onPageSelected(position);
282    }
283    sendScreenViewForCurrentPosition();
284
285    if (mCurrentPage != null) {
286      mCurrentPage.onPagePause(getActivity());
287    }
288    mCurrentPage = getListsPage(position);
289    if (mCurrentPage != null) {
290      mCurrentPage.onPageResume(getActivity());
291    }
292  }
293
294  @Override
295  public void onPageScrollStateChanged(int state) {
296    final int count = mOnPageChangeListeners.size();
297    for (int i = 0; i < count; i++) {
298      mOnPageChangeListeners.get(i).onPageScrollStateChanged(state);
299    }
300  }
301
302  @Override
303  public void onVoicemailStatusFetched(Cursor statusCursor) {
304    mHasFetchedVoicemailStatus = true;
305
306    if (getActivity() == null || getActivity().isFinishing()) {
307      return;
308    }
309
310    VoicemailStatusCorruptionHandler.maybeFixVoicemailStatus(
311        getContext(), statusCursor, Source.Activity);
312
313    // Update mHasActiveVoicemailProvider, which controls the number of tabs displayed.
314    boolean hasActiveVoicemailProvider =
315        mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0;
316    if (hasActiveVoicemailProvider != mHasActiveVoicemailProvider) {
317      mHasActiveVoicemailProvider = hasActiveVoicemailProvider;
318      mViewPagerAdapter.notifyDataSetChanged();
319
320      if (hasActiveVoicemailProvider) {
321        mViewPagerTabs.updateTab(TAB_INDEX_VOICEMAIL);
322      } else {
323        mViewPagerTabs.removeTab(TAB_INDEX_VOICEMAIL);
324        removeVoicemailFragment();
325      }
326
327      mPrefs
328          .edit()
329          .putBoolean(
330              VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER,
331              hasActiveVoicemailProvider)
332          .commit();
333    }
334
335    if (hasActiveVoicemailProvider) {
336      mCallLogQueryHandler.fetchVoicemailUnreadCount();
337    }
338
339    if (mHasActiveVoicemailProvider && mShowVoicemailTabAfterVoicemailStatusIsFetched) {
340      mShowVoicemailTabAfterVoicemailStatusIsFetched = false;
341      showTab(TAB_INDEX_VOICEMAIL);
342    }
343  }
344
345  @Override
346  public void onVoicemailUnreadCountFetched(Cursor cursor) {
347    if (getActivity() == null || getActivity().isFinishing() || cursor == null) {
348      return;
349    }
350
351    int count = 0;
352    try {
353      count = cursor.getCount();
354    } finally {
355      cursor.close();
356    }
357
358    mViewPagerTabs.setUnreadCount(count, TAB_INDEX_VOICEMAIL);
359    mViewPagerTabs.updateTab(TAB_INDEX_VOICEMAIL);
360  }
361
362  @Override
363  public void onMissedCallsUnreadCountFetched(Cursor cursor) {
364    if (getActivity() == null || getActivity().isFinishing() || cursor == null) {
365      return;
366    }
367
368    int count = 0;
369    try {
370      count = cursor.getCount();
371    } finally {
372      cursor.close();
373    }
374
375    mViewPagerTabs.setUnreadCount(count, TAB_INDEX_HISTORY);
376    mViewPagerTabs.updateTab(TAB_INDEX_HISTORY);
377  }
378
379  @Override
380  public boolean onCallsFetched(Cursor statusCursor) {
381    // Return false; did not take ownership of cursor
382    return false;
383  }
384
385  public int getCurrentTabIndex() {
386    return mTabIndex;
387  }
388
389  /**
390   * External method to update unread count because the unread count changes when the user expands a
391   * voicemail in the call log or when the user expands an unread call in the call history tab.
392   */
393  public void updateTabUnreadCounts() {
394    if (mCallLogQueryHandler != null) {
395      mCallLogQueryHandler.fetchMissedCallsUnreadCount();
396      if (mHasActiveVoicemailProvider) {
397        mCallLogQueryHandler.fetchVoicemailUnreadCount();
398      }
399    }
400  }
401
402  /** External method to mark all missed calls as read. */
403  public void markMissedCallsAsReadAndRemoveNotifications() {
404    if (mCallLogQueryHandler != null) {
405      mCallLogQueryHandler.markMissedCallsAsRead();
406      CallLogNotificationsHelper.removeMissedCallNotifications(getActivity());
407    }
408  }
409
410  public void showRemoveView(boolean show) {
411    mRemoveViewContent.setVisibility(show ? View.VISIBLE : View.GONE);
412    mRemoveView.setAlpha(show ? 0 : 1);
413    mRemoveView.animate().alpha(show ? 1 : 0).start();
414  }
415
416  public boolean shouldShowActionBar() {
417    // TODO: Update this based on scroll state.
418    return mActionBar != null;
419  }
420
421  public SpeedDialFragment getSpeedDialFragment() {
422    return mSpeedDialFragment;
423  }
424
425  public RemoveView getRemoveView() {
426    return mRemoveView;
427  }
428
429  public int getTabCount() {
430    return mViewPagerAdapter.getCount();
431  }
432
433  private int getRtlPosition(int position) {
434    if (ViewUtil.isRtl()) {
435      return mViewPagerAdapter.getCount() - 1 - position;
436    }
437    return position;
438  }
439
440  public void sendScreenViewForCurrentPosition() {
441    if (!isResumed()) {
442      return;
443    }
444
445    int screenType;
446    switch (getCurrentTabIndex()) {
447      case TAB_INDEX_SPEED_DIAL:
448        screenType = ScreenEvent.Type.SPEED_DIAL;
449        break;
450      case TAB_INDEX_HISTORY:
451        screenType = ScreenEvent.Type.CALL_LOG;
452        break;
453      case TAB_INDEX_ALL_CONTACTS:
454        screenType = ScreenEvent.Type.ALL_CONTACTS;
455        break;
456      case TAB_INDEX_VOICEMAIL:
457        screenType = ScreenEvent.Type.VOICEMAIL_LOG;
458        break;
459      default:
460        return;
461    }
462    Logger.get(getActivity()).logScreenView(screenType, getActivity());
463  }
464
465  private void removeVoicemailFragment() {
466    if (mVoicemailFragment != null) {
467      getChildFragmentManager()
468          .beginTransaction()
469          .remove(mVoicemailFragment)
470          .commitAllowingStateLoss();
471      mVoicemailFragment = null;
472    }
473  }
474
475  private ListsPage getListsPage(int position) {
476    switch (getRtlPosition(position)) {
477      case TAB_INDEX_SPEED_DIAL:
478        return mSpeedDialFragment;
479      case TAB_INDEX_HISTORY:
480        return mHistoryFragment;
481      case TAB_INDEX_ALL_CONTACTS:
482        return mAllContactsFragment;
483      case TAB_INDEX_VOICEMAIL:
484        return mVoicemailFragment;
485    }
486    throw new IllegalStateException("No fragment at position " + position);
487  }
488
489  public interface HostInterface {
490
491    ActionBarController getActionBarController();
492  }
493
494  public class ViewPagerAdapter extends FragmentPagerAdapter {
495
496    private final List<Fragment> mFragments = new ArrayList<>();
497
498    public ViewPagerAdapter(FragmentManager fm) {
499      super(fm);
500      for (int i = 0; i < TAB_COUNT_WITH_VOICEMAIL; i++) {
501        mFragments.add(null);
502      }
503    }
504
505    @Override
506    public long getItemId(int position) {
507      return getRtlPosition(position);
508    }
509
510    @Override
511    public Fragment getItem(int position) {
512      LogUtil.d("ViewPagerAdapter.getItem", "position: %d", position);
513      switch (getRtlPosition(position)) {
514        case TAB_INDEX_SPEED_DIAL:
515          if (mSpeedDialFragment == null) {
516            mSpeedDialFragment = new SpeedDialFragment();
517          }
518          return mSpeedDialFragment;
519        case TAB_INDEX_HISTORY:
520          if (mHistoryFragment == null) {
521            mHistoryFragment = new CallLogFragment();
522          }
523          return mHistoryFragment;
524        case TAB_INDEX_ALL_CONTACTS:
525          if (mAllContactsFragment == null) {
526            mAllContactsFragment = new AllContactsFragment();
527          }
528          return mAllContactsFragment;
529        case TAB_INDEX_VOICEMAIL:
530          if (mVoicemailFragment == null) {
531            mVoicemailFragment = new VisualVoicemailCallLogFragment();
532            LogUtil.v(
533                "ViewPagerAdapter.getItem",
534                "new VisualVoicemailCallLogFragment: %s",
535                mVoicemailFragment);
536          }
537          return mVoicemailFragment;
538      }
539      throw new IllegalStateException("No fragment at position " + position);
540    }
541
542    @Override
543    public Fragment instantiateItem(ViewGroup container, int position) {
544      LogUtil.d("ViewPagerAdapter.instantiateItem", "position: %d", position);
545      // On rotation the FragmentManager handles rotation. Therefore getItem() isn't called.
546      // Copy the fragments that the FragmentManager finds so that we can store them in
547      // instance variables for later.
548      final Fragment fragment = (Fragment) super.instantiateItem(container, position);
549      if (fragment instanceof SpeedDialFragment) {
550        mSpeedDialFragment = (SpeedDialFragment) fragment;
551      } else if (fragment instanceof CallLogFragment && position == TAB_INDEX_HISTORY) {
552        mHistoryFragment = (CallLogFragment) fragment;
553      } else if (fragment instanceof AllContactsFragment) {
554        mAllContactsFragment = (AllContactsFragment) fragment;
555      } else if (fragment instanceof CallLogFragment && position == TAB_INDEX_VOICEMAIL) {
556        mVoicemailFragment = (CallLogFragment) fragment;
557        LogUtil.v("ViewPagerAdapter.instantiateItem", mVoicemailFragment.toString());
558      }
559      mFragments.set(position, fragment);
560      return fragment;
561    }
562
563    /**
564     * When {@link android.support.v4.view.PagerAdapter#notifyDataSetChanged} is called, this method
565     * is called on all pages to determine whether they need to be recreated. When the voicemail tab
566     * is removed, the view needs to be recreated by returning POSITION_NONE. If
567     * notifyDataSetChanged is called for some other reason, the voicemail tab is recreated only if
568     * it is active. All other tabs do not need to be recreated and POSITION_UNCHANGED is returned.
569     */
570    @Override
571    public int getItemPosition(Object object) {
572      return !mHasActiveVoicemailProvider && mFragments.indexOf(object) == TAB_INDEX_VOICEMAIL
573          ? POSITION_NONE
574          : POSITION_UNCHANGED;
575    }
576
577    @Override
578    public int getCount() {
579      return mHasActiveVoicemailProvider ? TAB_COUNT_WITH_VOICEMAIL : TAB_COUNT_DEFAULT;
580    }
581
582    @Override
583    public CharSequence getPageTitle(int position) {
584      return mTabTitles[position];
585    }
586  }
587}
588