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