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 { 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 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 120 public long getItemId(int position) { 121 return getRtlPosition(position); 122 } 123 124 @Override 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 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 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 179 public int getCount() { 180 return mHasActiveVoicemailProvider ? TAB_COUNT_WITH_VOICEMAIL : TAB_COUNT_DEFAULT; 181 } 182 183 @Override 184 public CharSequence getPageTitle(int position) { 185 return mTabTitles[position]; 186 } 187 } 188 189 @Override 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 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 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 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 */ 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 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 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 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 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 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 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 392 public boolean onCallsFetched(Cursor statusCursor) { 393 // Return false; did not take ownership of cursor 394 return false; 395 } 396 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 */ 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 */ 418 public void markMissedCallsAsReadAndRemoveNotifications() { 419 if (mCallLogQueryHandler != null) { 420 mCallLogQueryHandler.markMissedCallsAsRead(); 421 CallLogNotificationsHelper.removeMissedCallNotifications(getActivity()); 422 } 423 } 424 425 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 432 public boolean shouldShowActionBar() { 433 // TODO: Update this based on scroll state. 434 return mActionBar != null; 435 } 436 437 public SpeedDialFragment getSpeedDialFragment() { 438 return mSpeedDialFragment; 439 } 440 441 public RemoveView getRemoveView() { 442 return mRemoveView; 443 } 444 445 public int getTabCount() { 446 return mViewPagerAdapter.getCount(); 447 } 448 449 private int getRtlPosition(int position) { 450 if (DialerUtils.isRtl()) { 451 return mViewPagerAdapter.getCount() - 1 - position; 452 } 453 return position; 454 } 455 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 480 private void removeVoicemailFragment() { 481 if (mVoicemailFragment != null) { 482 getChildFragmentManager().beginTransaction().remove(mVoicemailFragment) 483 .commitAllowingStateLoss(); 484 mVoicemailFragment = null; 485 } 486 } 487 } 488