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