• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onCreate(Bundle savedInstanceState)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
onResume()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
onPause()133   public void onPause() {
134     LogUtil.enterBlock("ListsFragment.onPause");
135     super.onPause();
136 
137     paused = true;
138   }
139 
140   @Override
onDestroyView()141   public void onDestroyView() {
142     super.onDestroyView();
143     viewPager.removeOnPageChangeListener(this);
144   }
145 
146   @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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
onDestroy()239   public void onDestroy() {
240     getActivity().getContentResolver().unregisterContentObserver(voicemailStatusObserver);
241     super.onDestroy();
242   }
243 
addOnPageChangeListener(OnPageChangeListener onPageChangeListener)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    */
showTab(int index)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
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)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
onPageSelected(int position)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
onPageScrollStateChanged(int state)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
onVoicemailStatusFetched(Cursor statusCursor)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
onVoicemailUnreadCountFetched(Cursor cursor)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
onMissedCallsUnreadCountFetched(Cursor cursor)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
onCallsFetched(Cursor statusCursor)409   public boolean onCallsFetched(Cursor statusCursor) {
410     // Return false; did not take ownership of cursor
411     return false;
412   }
413 
getCurrentTabIndex()414   public int getCurrentTabIndex() {
415     return tabIndex;
416   }
417 
shouldShowFab()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
updateTabUnreadCounts()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. */
markMissedCallsAsReadAndRemoveNotifications()439   public void markMissedCallsAsReadAndRemoveNotifications() {
440     if (callLogQueryHandler != null) {
441       callLogQueryHandler.markMissedCallsAsRead();
442       CallLogNotificationsService.cancelAllMissedCalls(getContext());
443     }
444   }
445 
showRemoveView(boolean show)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
showMultiSelectRemoveView(boolean show)453   public void showMultiSelectRemoveView(boolean show) {
454     viewPagerTabs.setVisibility(show ? View.GONE : View.VISIBLE);
455     viewPager.setEnableSwipingPages(!show);
456   }
457 
hasFrequents()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 
getRemoveView()466   public RemoveView getRemoveView() {
467     return removeView;
468   }
469 
getTabCount()470   public int getTabCount() {
471     return adapter.getCount();
472   }
473 
sendScreenViewForCurrentPosition()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