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
17package com.android.dialer.app.list;
18
19import static com.android.dialer.app.list.DialtactsPagerAdapter.TAB_COUNT_WITH_VOICEMAIL;
20import static com.android.dialer.app.list.DialtactsPagerAdapter.TAB_INDEX_ALL_CONTACTS;
21import static com.android.dialer.app.list.DialtactsPagerAdapter.TAB_INDEX_HISTORY;
22import static com.android.dialer.app.list.DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL;
23import static com.android.dialer.app.list.DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL;
24
25import android.app.Fragment;
26import android.content.SharedPreferences;
27import android.database.ContentObserver;
28import android.database.Cursor;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.Trace;
32import android.preference.PreferenceManager;
33import android.provider.VoicemailContract;
34import android.support.v4.view.ViewPager;
35import android.support.v4.view.ViewPager.OnPageChangeListener;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39import com.android.contacts.common.list.ViewPagerTabs;
40import com.android.dialer.app.R;
41import com.android.dialer.app.calllog.CallLogFragment;
42import com.android.dialer.app.calllog.CallLogNotificationsService;
43import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler;
44import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler.Source;
45import com.android.dialer.common.LogUtil;
46import com.android.dialer.database.CallLogQueryHandler;
47import com.android.dialer.logging.DialerImpression;
48import com.android.dialer.logging.Logger;
49import com.android.dialer.logging.ScreenEvent;
50import com.android.dialer.speeddial.SpeedDialFragment;
51import com.android.dialer.util.PermissionsUtil;
52import com.android.dialer.voicemailstatus.VisualVoicemailEnabledChecker;
53import com.android.dialer.voicemailstatus.VoicemailStatusHelper;
54import 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 */
63public class ListsFragment extends Fragment
64    implements ViewPager.OnPageChangeListener, CallLogQueryHandler.Listener {
65
66  private static final String TAG = "ListsFragment";
67  private ViewPager mViewPager;
68  private ViewPagerTabs mViewPagerTabs;
69  private DialtactsPagerAdapter mAdapter;
70  private RemoveView mRemoveView;
71  private View mRemoveViewContent;
72  private Fragment mCurrentPage;
73  private SharedPreferences mPrefs;
74  private boolean mHasFetchedVoicemailStatus;
75  private boolean mShowVoicemailTabAfterVoicemailStatusIsFetched;
76  private VoicemailStatusHelper mVoicemailStatusHelper;
77  private final ArrayList<OnPageChangeListener> mOnPageChangeListeners = new ArrayList<>();
78  /** The position of the currently selected tab. */
79  private int mTabIndex = TAB_INDEX_SPEED_DIAL;
80  private boolean mPaused;
81
82  private CallLogQueryHandler mCallLogQueryHandler;
83
84  private final ContentObserver mVoicemailStatusObserver =
85      new ContentObserver(new Handler()) {
86        @Override
87        public void onChange(boolean selfChange) {
88          super.onChange(selfChange);
89          mCallLogQueryHandler.fetchVoicemailStatus();
90        }
91      };
92
93  @Override
94  public void onCreate(Bundle savedInstanceState) {
95    LogUtil.d("ListsFragment.onCreate", null);
96    Trace.beginSection(TAG + " onCreate");
97    super.onCreate(savedInstanceState);
98    mVoicemailStatusHelper = new VoicemailStatusHelper();
99    mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
100    Trace.endSection();
101  }
102
103  @Override
104  public void onResume() {
105    LogUtil.d("ListsFragment.onResume", null);
106    Trace.beginSection(TAG + " onResume");
107    super.onResume();
108
109    mPaused = false;
110
111    if (getUserVisibleHint()) {
112      sendScreenViewForCurrentPosition();
113    }
114
115    // Fetch voicemail status to determine if we should show the voicemail tab.
116    mCallLogQueryHandler =
117        new CallLogQueryHandler(getActivity(), getActivity().getContentResolver(), this);
118    mCallLogQueryHandler.fetchVoicemailStatus();
119    mCallLogQueryHandler.fetchMissedCallsUnreadCount();
120    Trace.endSection();
121    mCurrentPage = mAdapter.getItem(mViewPager.getCurrentItem());
122    if (mCurrentPage instanceof CallLogFragment) {
123      ((CallLogFragment) mCurrentPage).onVisible();
124    }
125  }
126
127  @Override
128  public void onPause() {
129    LogUtil.d("ListsFragment.onPause", null);
130    if (mCurrentPage instanceof CallLogFragment) {
131      ((CallLogFragment) mCurrentPage).onNotVisible();
132    }
133    super.onPause();
134
135    mPaused = true;
136  }
137
138  @Override
139  public void onDestroyView() {
140    super.onDestroyView();
141    mViewPager.removeOnPageChangeListener(this);
142  }
143
144  @Override
145  public View onCreateView(
146      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
147    LogUtil.d("ListsFragment.onCreateView", null);
148    Trace.beginSection(TAG + " onCreateView");
149    Trace.beginSection(TAG + " inflate view");
150    final View parentView = inflater.inflate(R.layout.lists_fragment, container, false);
151    Trace.endSection();
152    Trace.beginSection(TAG + " setup views");
153
154    String[] tabTitles = new String[TAB_COUNT_WITH_VOICEMAIL];
155    tabTitles[TAB_INDEX_SPEED_DIAL] = getResources().getString(R.string.tab_speed_dial);
156    tabTitles[TAB_INDEX_HISTORY] = getResources().getString(R.string.tab_history);
157    tabTitles[TAB_INDEX_ALL_CONTACTS] = getResources().getString(R.string.tab_all_contacts);
158    tabTitles[TAB_INDEX_VOICEMAIL] = getResources().getString(R.string.tab_voicemail);
159
160    int[] tabIcons = new int[TAB_COUNT_WITH_VOICEMAIL];
161    tabIcons[TAB_INDEX_SPEED_DIAL] = R.drawable.quantum_ic_grade_white_24;
162    tabIcons[TAB_INDEX_HISTORY] = R.drawable.quantum_ic_schedule_white_24;
163    tabIcons[TAB_INDEX_ALL_CONTACTS] = R.drawable.quantum_ic_people_white_24;
164    tabIcons[TAB_INDEX_VOICEMAIL] = R.drawable.quantum_ic_voicemail_white_24;
165
166    mViewPager = (ViewPager) parentView.findViewById(R.id.lists_pager);
167    mAdapter =
168        new DialtactsPagerAdapter(
169            getContext(),
170            getChildFragmentManager(),
171            tabTitles,
172            mPrefs.getBoolean(
173                VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, false));
174    mViewPager.setAdapter(mAdapter);
175    mViewPager.setOffscreenPageLimit(TAB_COUNT_WITH_VOICEMAIL - 1);
176    mViewPager.addOnPageChangeListener(this);
177    showTab(TAB_INDEX_SPEED_DIAL);
178
179    mViewPagerTabs = (ViewPagerTabs) parentView.findViewById(R.id.lists_pager_header);
180    mViewPagerTabs.configureTabIcons(tabIcons);
181    mViewPagerTabs.setViewPager(mViewPager);
182    addOnPageChangeListener(mViewPagerTabs);
183
184    mRemoveView = (RemoveView) parentView.findViewById(R.id.remove_view);
185    mRemoveViewContent = parentView.findViewById(R.id.remove_view_content);
186
187    if (PermissionsUtil.hasReadVoicemailPermissions(getContext())
188        && PermissionsUtil.hasAddVoicemailPermissions(getContext())) {
189      getActivity()
190          .getContentResolver()
191          .registerContentObserver(
192              VoicemailContract.Status.CONTENT_URI, true, mVoicemailStatusObserver);
193    } else {
194      LogUtil.w("ListsFragment.onCreateView", "no voicemail read/add permissions");
195    }
196
197    Trace.endSection();
198    Trace.endSection();
199    return parentView;
200  }
201
202  @Override
203  public void onDestroy() {
204    getActivity().getContentResolver().unregisterContentObserver(mVoicemailStatusObserver);
205    super.onDestroy();
206  }
207
208  public void addOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
209    if (!mOnPageChangeListeners.contains(onPageChangeListener)) {
210      mOnPageChangeListeners.add(onPageChangeListener);
211    }
212  }
213
214  /**
215   * Shows the tab with the specified index. If the voicemail tab index is specified, but the
216   * voicemail status hasn't been fetched, it will try to show the tab after the voicemail status
217   * has been fetched.
218   */
219  public void showTab(int index) {
220    if (index == TAB_INDEX_VOICEMAIL) {
221      if (mAdapter.hasActiveVoicemailProvider()) {
222        mViewPager.setCurrentItem(mAdapter.getRtlPosition(TAB_INDEX_VOICEMAIL));
223      } else if (!mHasFetchedVoicemailStatus) {
224        // Try to show the voicemail tab after the voicemail status returns.
225        mShowVoicemailTabAfterVoicemailStatusIsFetched = true;
226      }
227    } else if (index < getTabCount()) {
228      mViewPager.setCurrentItem(mAdapter.getRtlPosition(index));
229    }
230  }
231
232  @Override
233  public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
234    mTabIndex = mAdapter.getRtlPosition(position);
235
236    final int count = mOnPageChangeListeners.size();
237    for (int i = 0; i < count; i++) {
238      mOnPageChangeListeners.get(i).onPageScrolled(position, positionOffset, positionOffsetPixels);
239    }
240  }
241
242  @Override
243  public void onPageSelected(int position) {
244    LogUtil.i("ListsFragment.onPageSelected", "position: %d", position);
245    mTabIndex = mAdapter.getRtlPosition(position);
246
247    // Show the tab which has been selected instead.
248    mShowVoicemailTabAfterVoicemailStatusIsFetched = false;
249
250    final int count = mOnPageChangeListeners.size();
251    for (int i = 0; i < count; i++) {
252      mOnPageChangeListeners.get(i).onPageSelected(position);
253    }
254    sendScreenViewForCurrentPosition();
255
256    if (mCurrentPage instanceof CallLogFragment) {
257      ((CallLogFragment) mCurrentPage).onNotVisible();
258    }
259    mCurrentPage = mAdapter.getItem(position);
260    if (mCurrentPage instanceof CallLogFragment) {
261      ((CallLogFragment) mCurrentPage).onVisible();
262    }
263  }
264
265  @Override
266  public void onPageScrollStateChanged(int state) {
267    final int count = mOnPageChangeListeners.size();
268    for (int i = 0; i < count; i++) {
269      mOnPageChangeListeners.get(i).onPageScrollStateChanged(state);
270    }
271  }
272
273  @Override
274  public void onVoicemailStatusFetched(Cursor statusCursor) {
275    mHasFetchedVoicemailStatus = true;
276
277    if (getActivity() == null || mPaused) {
278      return;
279    }
280
281    VoicemailStatusCorruptionHandler.maybeFixVoicemailStatus(
282        getContext(), statusCursor, Source.Activity);
283
284    // Update hasActiveVoicemailProvider, which controls the number of tabs displayed.
285    boolean hasActiveVoicemailProvider =
286        mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0;
287    if (hasActiveVoicemailProvider != mAdapter.hasActiveVoicemailProvider()) {
288      mAdapter.setHasActiveVoicemailProvider(hasActiveVoicemailProvider);
289      mAdapter.notifyDataSetChanged();
290
291      if (hasActiveVoicemailProvider) {
292        Logger.get(getContext()).logImpression(DialerImpression.Type.VVM_TAB_VISIBLE);
293        mViewPagerTabs.updateTab(TAB_INDEX_VOICEMAIL);
294      } else {
295        mViewPagerTabs.removeTab(TAB_INDEX_VOICEMAIL);
296        mAdapter.removeVoicemailFragment(getChildFragmentManager());
297      }
298
299      mPrefs
300          .edit()
301          .putBoolean(
302              VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER,
303              hasActiveVoicemailProvider)
304          .apply();
305    }
306
307    if (hasActiveVoicemailProvider) {
308      mCallLogQueryHandler.fetchVoicemailUnreadCount();
309    }
310
311    if (mAdapter.hasActiveVoicemailProvider() && mShowVoicemailTabAfterVoicemailStatusIsFetched) {
312      mShowVoicemailTabAfterVoicemailStatusIsFetched = false;
313      showTab(TAB_INDEX_VOICEMAIL);
314    }
315  }
316
317  @Override
318  public void onVoicemailUnreadCountFetched(Cursor cursor) {
319    if (getActivity() == null || getActivity().isFinishing() || cursor == null) {
320      return;
321    }
322
323    int count = 0;
324    try {
325      count = cursor.getCount();
326    } finally {
327      cursor.close();
328    }
329
330    mViewPagerTabs.setUnreadCount(count, TAB_INDEX_VOICEMAIL);
331    mViewPagerTabs.updateTab(TAB_INDEX_VOICEMAIL);
332  }
333
334  @Override
335  public void onMissedCallsUnreadCountFetched(Cursor cursor) {
336    if (getActivity() == null || getActivity().isFinishing() || cursor == null) {
337      return;
338    }
339
340    int count = 0;
341    try {
342      count = cursor.getCount();
343    } finally {
344      cursor.close();
345    }
346
347    mViewPagerTabs.setUnreadCount(count, TAB_INDEX_HISTORY);
348    mViewPagerTabs.updateTab(TAB_INDEX_HISTORY);
349  }
350
351  @Override
352  public boolean onCallsFetched(Cursor statusCursor) {
353    // Return false; did not take ownership of cursor
354    return false;
355  }
356
357  public int getCurrentTabIndex() {
358    return mTabIndex;
359  }
360
361  /**
362   * External method to update unread count because the unread count changes when the user expands a
363   * voicemail in the call log or when the user expands an unread call in the call history tab.
364   */
365  public void updateTabUnreadCounts() {
366    if (mCallLogQueryHandler != null) {
367      mCallLogQueryHandler.fetchMissedCallsUnreadCount();
368      if (mAdapter.hasActiveVoicemailProvider()) {
369        mCallLogQueryHandler.fetchVoicemailUnreadCount();
370      }
371    }
372  }
373
374  /** External method to mark all missed calls as read. */
375  public void markMissedCallsAsReadAndRemoveNotifications() {
376    if (mCallLogQueryHandler != null) {
377      mCallLogQueryHandler.markMissedCallsAsRead();
378      CallLogNotificationsService.markNewMissedCallsAsOld(getContext(), null);
379    }
380  }
381
382  public void showRemoveView(boolean show) {
383    mRemoveViewContent.setVisibility(show ? View.VISIBLE : View.GONE);
384    mRemoveView.setAlpha(show ? 0 : 1);
385    mRemoveView.animate().alpha(show ? 1 : 0).start();
386  }
387
388  public boolean hasFrequents() {
389    Fragment page = mAdapter.getItem(mAdapter.getRtlPosition(TAB_INDEX_SPEED_DIAL));
390    return page instanceof OldSpeedDialFragment
391        ? ((OldSpeedDialFragment) page).hasFrequents()
392        : ((SpeedDialFragment) page).hasFrequents();
393  }
394
395  public RemoveView getRemoveView() {
396    return mRemoveView;
397  }
398
399  public int getTabCount() {
400    return mAdapter.getCount();
401  }
402
403  public void sendScreenViewForCurrentPosition() {
404    if (!isResumed()) {
405      return;
406    }
407
408    ScreenEvent.Type screenType;
409    switch (getCurrentTabIndex()) {
410      case TAB_INDEX_SPEED_DIAL:
411        screenType = ScreenEvent.Type.SPEED_DIAL;
412        break;
413      case TAB_INDEX_HISTORY:
414        screenType = ScreenEvent.Type.CALL_LOG;
415        break;
416      case TAB_INDEX_ALL_CONTACTS:
417        screenType = ScreenEvent.Type.ALL_CONTACTS;
418        break;
419      case TAB_INDEX_VOICEMAIL:
420        screenType = ScreenEvent.Type.VOICEMAIL_LOG;
421        break;
422      default:
423        return;
424    }
425    Logger.get(getActivity()).logScreenView(screenType, getActivity());
426  }
427}
428