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 com.android.dialer.app.list.DialtactsPagerAdapter.TAB_COUNT_WITH_VOICEMAIL;
20 import static com.android.dialer.app.list.DialtactsPagerAdapter.TAB_INDEX_ALL_CONTACTS;
21 import static com.android.dialer.app.list.DialtactsPagerAdapter.TAB_INDEX_HISTORY;
22 import static com.android.dialer.app.list.DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL;
23 import static com.android.dialer.app.list.DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL;
24 
25 import android.app.Fragment;
26 import android.content.SharedPreferences;
27 import android.database.ContentObserver;
28 import android.database.Cursor;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.Trace;
32 import android.preference.PreferenceManager;
33 import android.provider.VoicemailContract;
34 import android.support.v4.view.ViewPager;
35 import android.support.v4.view.ViewPager.OnPageChangeListener;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import com.android.contacts.common.list.ViewPagerTabs;
40 import com.android.dialer.app.R;
41 import com.android.dialer.app.calllog.CallLogFragment;
42 import com.android.dialer.app.calllog.CallLogNotificationsService;
43 import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler;
44 import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler.Source;
45 import com.android.dialer.common.LogUtil;
46 import com.android.dialer.database.CallLogQueryHandler;
47 import com.android.dialer.logging.DialerImpression;
48 import com.android.dialer.logging.Logger;
49 import com.android.dialer.logging.ScreenEvent;
50 import com.android.dialer.speeddial.SpeedDialFragment;
51 import com.android.dialer.util.PermissionsUtil;
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 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
onCreate(Bundle savedInstanceState)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
onResume()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
onPause()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
onDestroyView()139   public void onDestroyView() {
140     super.onDestroyView();
141     mViewPager.removeOnPageChangeListener(this);
142   }
143 
144   @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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
onDestroy()203   public void onDestroy() {
204     getActivity().getContentResolver().unregisterContentObserver(mVoicemailStatusObserver);
205     super.onDestroy();
206   }
207 
addOnPageChangeListener(OnPageChangeListener onPageChangeListener)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    */
showTab(int index)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
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)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
onPageSelected(int position)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
onPageScrollStateChanged(int state)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
onVoicemailStatusFetched(Cursor statusCursor)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
onVoicemailUnreadCountFetched(Cursor cursor)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
onMissedCallsUnreadCountFetched(Cursor cursor)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
onCallsFetched(Cursor statusCursor)352   public boolean onCallsFetched(Cursor statusCursor) {
353     // Return false; did not take ownership of cursor
354     return false;
355   }
356 
getCurrentTabIndex()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    */
updateTabUnreadCounts()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. */
markMissedCallsAsReadAndRemoveNotifications()375   public void markMissedCallsAsReadAndRemoveNotifications() {
376     if (mCallLogQueryHandler != null) {
377       mCallLogQueryHandler.markMissedCallsAsRead();
378       CallLogNotificationsService.markNewMissedCallsAsOld(getContext(), null);
379     }
380   }
381 
showRemoveView(boolean show)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 
hasFrequents()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 
getRemoveView()395   public RemoveView getRemoveView() {
396     return mRemoveView;
397   }
398 
getTabCount()399   public int getTabCount() {
400     return mAdapter.getCount();
401   }
402 
sendScreenViewForCurrentPosition()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