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