| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.dialer.app.list; |
| |
| import android.app.Activity; |
| import android.app.Fragment; |
| import android.app.FragmentManager; |
| import android.content.SharedPreferences; |
| import android.database.ContentObserver; |
| import android.database.Cursor; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Trace; |
| import android.preference.PreferenceManager; |
| import android.provider.VoicemailContract; |
| import android.support.annotation.Nullable; |
| import android.support.v13.app.FragmentPagerAdapter; |
| import android.support.v4.view.ViewPager; |
| import android.support.v4.view.ViewPager.OnPageChangeListener; |
| import android.support.v7.app.ActionBar; |
| import android.support.v7.app.AppCompatActivity; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import com.android.contacts.common.list.ViewPagerTabs; |
| import com.android.dialer.app.R; |
| import com.android.dialer.app.calllog.CallLogFragment; |
| import com.android.dialer.app.calllog.CallLogNotificationsHelper; |
| import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment; |
| import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler; |
| import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler.Source; |
| import com.android.dialer.app.widget.ActionBarController; |
| import com.android.dialer.common.LogUtil; |
| import com.android.dialer.database.CallLogQueryHandler; |
| import com.android.dialer.logging.Logger; |
| import com.android.dialer.logging.nano.DialerImpression; |
| import com.android.dialer.logging.nano.ScreenEvent; |
| import com.android.dialer.util.ViewUtil; |
| import com.android.dialer.voicemailstatus.VisualVoicemailEnabledChecker; |
| import com.android.dialer.voicemailstatus.VoicemailStatusHelper; |
| import com.android.dialer.voicemailstatus.VoicemailStatusHelperImpl; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Fragment that is used as the main screen of the Dialer. |
| * |
| * <p>Contains a ViewPager that contains various contact lists like the Speed Dial list and the All |
| * Contacts list. This will also eventually contain the logic that allows sliding the ViewPager |
| * containing the lists up above the search bar and pin it against the top of the screen. |
| */ |
| public class ListsFragment extends Fragment |
| implements ViewPager.OnPageChangeListener, CallLogQueryHandler.Listener { |
| |
| /** Every fragment in the list show implement this interface. */ |
| public interface ListsPage { |
| |
| /** |
| * Called when the page is resumed, including selecting the page or activity resume. Note: This |
| * is called before the page fragment is attached to a activity. |
| * |
| * @param activity the activity hosting the ListFragment |
| */ |
| void onPageResume(@Nullable Activity activity); |
| |
| /** |
| * Called when the page is paused, including selecting another page or activity pause. Note: |
| * This is called after the page fragment is detached from a activity. |
| * |
| * @param activity the activity hosting the ListFragment |
| */ |
| void onPagePause(@Nullable Activity activity); |
| } |
| |
| public static final int TAB_INDEX_SPEED_DIAL = 0; |
| public static final int TAB_INDEX_HISTORY = 1; |
| public static final int TAB_INDEX_ALL_CONTACTS = 2; |
| public static final int TAB_INDEX_VOICEMAIL = 3; |
| public static final int TAB_COUNT_DEFAULT = 3; |
| public static final int TAB_COUNT_WITH_VOICEMAIL = 4; |
| private static final String TAG = "ListsFragment"; |
| private ActionBar mActionBar; |
| private ViewPager mViewPager; |
| private ViewPagerTabs mViewPagerTabs; |
| private ViewPagerAdapter mViewPagerAdapter; |
| private RemoveView mRemoveView; |
| private View mRemoveViewContent; |
| private SpeedDialFragment mSpeedDialFragment; |
| private CallLogFragment mHistoryFragment; |
| private AllContactsFragment mAllContactsFragment; |
| private CallLogFragment mVoicemailFragment; |
| private ListsPage mCurrentPage; |
| private SharedPreferences mPrefs; |
| private boolean mHasActiveVoicemailProvider; |
| private boolean mHasFetchedVoicemailStatus; |
| private boolean mShowVoicemailTabAfterVoicemailStatusIsFetched; |
| private VoicemailStatusHelper mVoicemailStatusHelper; |
| private ArrayList<OnPageChangeListener> mOnPageChangeListeners = |
| new ArrayList<OnPageChangeListener>(); |
| private String[] mTabTitles; |
| private int[] mTabIcons; |
| /** The position of the currently selected tab. */ |
| private int mTabIndex = TAB_INDEX_SPEED_DIAL; |
| |
| private CallLogQueryHandler mCallLogQueryHandler; |
| |
| private final ContentObserver mVoicemailStatusObserver = |
| new ContentObserver(new Handler()) { |
| @Override |
| public void onChange(boolean selfChange) { |
| super.onChange(selfChange); |
| mCallLogQueryHandler.fetchVoicemailStatus(); |
| } |
| }; |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| LogUtil.d("ListsFragment.onCreate", null); |
| Trace.beginSection(TAG + " onCreate"); |
| super.onCreate(savedInstanceState); |
| |
| mVoicemailStatusHelper = new VoicemailStatusHelperImpl(); |
| mHasFetchedVoicemailStatus = false; |
| |
| mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); |
| mHasActiveVoicemailProvider = |
| mPrefs.getBoolean( |
| VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, false); |
| |
| Trace.endSection(); |
| } |
| |
| @Override |
| public void onResume() { |
| LogUtil.d("ListsFragment.onResume", null); |
| Trace.beginSection(TAG + " onResume"); |
| super.onResume(); |
| |
| mActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); |
| if (getUserVisibleHint()) { |
| sendScreenViewForCurrentPosition(); |
| } |
| |
| // Fetch voicemail status to determine if we should show the voicemail tab. |
| mCallLogQueryHandler = |
| new CallLogQueryHandler(getActivity(), getActivity().getContentResolver(), this); |
| mCallLogQueryHandler.fetchVoicemailStatus(); |
| mCallLogQueryHandler.fetchMissedCallsUnreadCount(); |
| Trace.endSection(); |
| mCurrentPage = getListsPage(mViewPager.getCurrentItem()); |
| if (mCurrentPage != null) { |
| mCurrentPage.onPageResume(getActivity()); |
| } |
| } |
| |
| @Override |
| public void onPause() { |
| LogUtil.d("ListsFragment.onPause", null); |
| if (mCurrentPage != null) { |
| mCurrentPage.onPagePause(getActivity()); |
| } |
| super.onPause(); |
| } |
| |
| @Override |
| public void onDestroyView() { |
| super.onDestroyView(); |
| mViewPager.removeOnPageChangeListener(this); |
| } |
| |
| @Override |
| public View onCreateView( |
| LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
| LogUtil.d("ListsFragment.onCreateView", null); |
| Trace.beginSection(TAG + " onCreateView"); |
| Trace.beginSection(TAG + " inflate view"); |
| final View parentView = inflater.inflate(R.layout.lists_fragment, container, false); |
| Trace.endSection(); |
| Trace.beginSection(TAG + " setup views"); |
| mViewPager = (ViewPager) parentView.findViewById(R.id.lists_pager); |
| mViewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager()); |
| mViewPager.setAdapter(mViewPagerAdapter); |
| mViewPager.setOffscreenPageLimit(TAB_COUNT_WITH_VOICEMAIL - 1); |
| mViewPager.addOnPageChangeListener(this); |
| showTab(TAB_INDEX_SPEED_DIAL); |
| |
| mTabTitles = new String[TAB_COUNT_WITH_VOICEMAIL]; |
| mTabTitles[TAB_INDEX_SPEED_DIAL] = getResources().getString(R.string.tab_speed_dial); |
| mTabTitles[TAB_INDEX_HISTORY] = getResources().getString(R.string.tab_history); |
| mTabTitles[TAB_INDEX_ALL_CONTACTS] = getResources().getString(R.string.tab_all_contacts); |
| mTabTitles[TAB_INDEX_VOICEMAIL] = getResources().getString(R.string.tab_voicemail); |
| |
| mTabIcons = new int[TAB_COUNT_WITH_VOICEMAIL]; |
| mTabIcons[TAB_INDEX_SPEED_DIAL] = R.drawable.ic_grade_24dp; |
| mTabIcons[TAB_INDEX_HISTORY] = R.drawable.ic_schedule_24dp; |
| mTabIcons[TAB_INDEX_ALL_CONTACTS] = R.drawable.ic_people_24dp; |
| mTabIcons[TAB_INDEX_VOICEMAIL] = R.drawable.ic_voicemail_24dp; |
| |
| mViewPagerTabs = (ViewPagerTabs) parentView.findViewById(R.id.lists_pager_header); |
| mViewPagerTabs.configureTabIcons(mTabIcons); |
| mViewPagerTabs.setViewPager(mViewPager); |
| addOnPageChangeListener(mViewPagerTabs); |
| |
| mRemoveView = (RemoveView) parentView.findViewById(R.id.remove_view); |
| mRemoveViewContent = parentView.findViewById(R.id.remove_view_content); |
| |
| getActivity() |
| .getContentResolver() |
| .registerContentObserver( |
| VoicemailContract.Status.CONTENT_URI, true, mVoicemailStatusObserver); |
| |
| Trace.endSection(); |
| Trace.endSection(); |
| return parentView; |
| } |
| |
| @Override |
| public void onDestroy() { |
| getActivity().getContentResolver().unregisterContentObserver(mVoicemailStatusObserver); |
| super.onDestroy(); |
| } |
| |
| public void addOnPageChangeListener(OnPageChangeListener onPageChangeListener) { |
| if (!mOnPageChangeListeners.contains(onPageChangeListener)) { |
| mOnPageChangeListeners.add(onPageChangeListener); |
| } |
| } |
| |
| /** |
| * Shows the tab with the specified index. If the voicemail tab index is specified, but the |
| * voicemail status hasn't been fetched, it will try to show the tab after the voicemail status |
| * has been fetched. |
| */ |
| public void showTab(int index) { |
| if (index == TAB_INDEX_VOICEMAIL) { |
| if (mHasActiveVoicemailProvider) { |
| Logger.get(getContext()).logImpression(DialerImpression.Type.VVM_TAB_VISIBLE); |
| mViewPager.setCurrentItem(getRtlPosition(TAB_INDEX_VOICEMAIL)); |
| } else if (!mHasFetchedVoicemailStatus) { |
| // Try to show the voicemail tab after the voicemail status returns. |
| mShowVoicemailTabAfterVoicemailStatusIsFetched = true; |
| } |
| } else if (index < getTabCount()) { |
| mViewPager.setCurrentItem(getRtlPosition(index)); |
| } |
| } |
| |
| @Override |
| public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { |
| mTabIndex = getRtlPosition(position); |
| |
| final int count = mOnPageChangeListeners.size(); |
| for (int i = 0; i < count; i++) { |
| mOnPageChangeListeners.get(i).onPageScrolled(position, positionOffset, positionOffsetPixels); |
| } |
| } |
| |
| @Override |
| public void onPageSelected(int position) { |
| LogUtil.i("ListsFragment.onPageSelected", "position: %d", position); |
| mTabIndex = getRtlPosition(position); |
| |
| // Show the tab which has been selected instead. |
| mShowVoicemailTabAfterVoicemailStatusIsFetched = false; |
| |
| final int count = mOnPageChangeListeners.size(); |
| for (int i = 0; i < count; i++) { |
| mOnPageChangeListeners.get(i).onPageSelected(position); |
| } |
| sendScreenViewForCurrentPosition(); |
| |
| if (mCurrentPage != null) { |
| mCurrentPage.onPagePause(getActivity()); |
| } |
| mCurrentPage = getListsPage(position); |
| if (mCurrentPage != null) { |
| mCurrentPage.onPageResume(getActivity()); |
| } |
| } |
| |
| @Override |
| public void onPageScrollStateChanged(int state) { |
| final int count = mOnPageChangeListeners.size(); |
| for (int i = 0; i < count; i++) { |
| mOnPageChangeListeners.get(i).onPageScrollStateChanged(state); |
| } |
| } |
| |
| @Override |
| public void onVoicemailStatusFetched(Cursor statusCursor) { |
| mHasFetchedVoicemailStatus = true; |
| |
| if (getActivity() == null || getActivity().isFinishing()) { |
| return; |
| } |
| |
| VoicemailStatusCorruptionHandler.maybeFixVoicemailStatus( |
| getContext(), statusCursor, Source.Activity); |
| |
| // Update mHasActiveVoicemailProvider, which controls the number of tabs displayed. |
| boolean hasActiveVoicemailProvider = |
| mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0; |
| if (hasActiveVoicemailProvider != mHasActiveVoicemailProvider) { |
| mHasActiveVoicemailProvider = hasActiveVoicemailProvider; |
| mViewPagerAdapter.notifyDataSetChanged(); |
| |
| if (hasActiveVoicemailProvider) { |
| mViewPagerTabs.updateTab(TAB_INDEX_VOICEMAIL); |
| } else { |
| mViewPagerTabs.removeTab(TAB_INDEX_VOICEMAIL); |
| removeVoicemailFragment(); |
| } |
| |
| mPrefs |
| .edit() |
| .putBoolean( |
| VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, |
| hasActiveVoicemailProvider) |
| .commit(); |
| } |
| |
| if (hasActiveVoicemailProvider) { |
| mCallLogQueryHandler.fetchVoicemailUnreadCount(); |
| } |
| |
| if (mHasActiveVoicemailProvider && mShowVoicemailTabAfterVoicemailStatusIsFetched) { |
| mShowVoicemailTabAfterVoicemailStatusIsFetched = false; |
| showTab(TAB_INDEX_VOICEMAIL); |
| } |
| } |
| |
| @Override |
| public void onVoicemailUnreadCountFetched(Cursor cursor) { |
| if (getActivity() == null || getActivity().isFinishing() || cursor == null) { |
| return; |
| } |
| |
| int count = 0; |
| try { |
| count = cursor.getCount(); |
| } finally { |
| cursor.close(); |
| } |
| |
| mViewPagerTabs.setUnreadCount(count, TAB_INDEX_VOICEMAIL); |
| mViewPagerTabs.updateTab(TAB_INDEX_VOICEMAIL); |
| } |
| |
| @Override |
| public void onMissedCallsUnreadCountFetched(Cursor cursor) { |
| if (getActivity() == null || getActivity().isFinishing() || cursor == null) { |
| return; |
| } |
| |
| int count = 0; |
| try { |
| count = cursor.getCount(); |
| } finally { |
| cursor.close(); |
| } |
| |
| mViewPagerTabs.setUnreadCount(count, TAB_INDEX_HISTORY); |
| mViewPagerTabs.updateTab(TAB_INDEX_HISTORY); |
| } |
| |
| @Override |
| public boolean onCallsFetched(Cursor statusCursor) { |
| // Return false; did not take ownership of cursor |
| return false; |
| } |
| |
| public int getCurrentTabIndex() { |
| return mTabIndex; |
| } |
| |
| /** |
| * External method to update unread count because the unread count changes when the user expands a |
| * voicemail in the call log or when the user expands an unread call in the call history tab. |
| */ |
| public void updateTabUnreadCounts() { |
| if (mCallLogQueryHandler != null) { |
| mCallLogQueryHandler.fetchMissedCallsUnreadCount(); |
| if (mHasActiveVoicemailProvider) { |
| mCallLogQueryHandler.fetchVoicemailUnreadCount(); |
| } |
| } |
| } |
| |
| /** External method to mark all missed calls as read. */ |
| public void markMissedCallsAsReadAndRemoveNotifications() { |
| if (mCallLogQueryHandler != null) { |
| mCallLogQueryHandler.markMissedCallsAsRead(); |
| CallLogNotificationsHelper.removeMissedCallNotifications(getActivity()); |
| } |
| } |
| |
| public void showRemoveView(boolean show) { |
| mRemoveViewContent.setVisibility(show ? View.VISIBLE : View.GONE); |
| mRemoveView.setAlpha(show ? 0 : 1); |
| mRemoveView.animate().alpha(show ? 1 : 0).start(); |
| } |
| |
| public boolean shouldShowActionBar() { |
| // TODO: Update this based on scroll state. |
| return mActionBar != null; |
| } |
| |
| public SpeedDialFragment getSpeedDialFragment() { |
| return mSpeedDialFragment; |
| } |
| |
| public RemoveView getRemoveView() { |
| return mRemoveView; |
| } |
| |
| public int getTabCount() { |
| return mViewPagerAdapter.getCount(); |
| } |
| |
| private int getRtlPosition(int position) { |
| if (ViewUtil.isRtl()) { |
| return mViewPagerAdapter.getCount() - 1 - position; |
| } |
| return position; |
| } |
| |
| public void sendScreenViewForCurrentPosition() { |
| if (!isResumed()) { |
| return; |
| } |
| |
| int screenType; |
| switch (getCurrentTabIndex()) { |
| case TAB_INDEX_SPEED_DIAL: |
| screenType = ScreenEvent.Type.SPEED_DIAL; |
| break; |
| case TAB_INDEX_HISTORY: |
| screenType = ScreenEvent.Type.CALL_LOG; |
| break; |
| case TAB_INDEX_ALL_CONTACTS: |
| screenType = ScreenEvent.Type.ALL_CONTACTS; |
| break; |
| case TAB_INDEX_VOICEMAIL: |
| screenType = ScreenEvent.Type.VOICEMAIL_LOG; |
| break; |
| default: |
| return; |
| } |
| Logger.get(getActivity()).logScreenView(screenType, getActivity()); |
| } |
| |
| private void removeVoicemailFragment() { |
| if (mVoicemailFragment != null) { |
| getChildFragmentManager() |
| .beginTransaction() |
| .remove(mVoicemailFragment) |
| .commitAllowingStateLoss(); |
| mVoicemailFragment = null; |
| } |
| } |
| |
| private ListsPage getListsPage(int position) { |
| switch (getRtlPosition(position)) { |
| case TAB_INDEX_SPEED_DIAL: |
| return mSpeedDialFragment; |
| case TAB_INDEX_HISTORY: |
| return mHistoryFragment; |
| case TAB_INDEX_ALL_CONTACTS: |
| return mAllContactsFragment; |
| case TAB_INDEX_VOICEMAIL: |
| return mVoicemailFragment; |
| } |
| throw new IllegalStateException("No fragment at position " + position); |
| } |
| |
| public interface HostInterface { |
| |
| ActionBarController getActionBarController(); |
| } |
| |
| public class ViewPagerAdapter extends FragmentPagerAdapter { |
| |
| private final List<Fragment> mFragments = new ArrayList<>(); |
| |
| public ViewPagerAdapter(FragmentManager fm) { |
| super(fm); |
| for (int i = 0; i < TAB_COUNT_WITH_VOICEMAIL; i++) { |
| mFragments.add(null); |
| } |
| } |
| |
| @Override |
| public long getItemId(int position) { |
| return getRtlPosition(position); |
| } |
| |
| @Override |
| public Fragment getItem(int position) { |
| LogUtil.d("ViewPagerAdapter.getItem", "position: %d", position); |
| switch (getRtlPosition(position)) { |
| case TAB_INDEX_SPEED_DIAL: |
| if (mSpeedDialFragment == null) { |
| mSpeedDialFragment = new SpeedDialFragment(); |
| } |
| return mSpeedDialFragment; |
| case TAB_INDEX_HISTORY: |
| if (mHistoryFragment == null) { |
| mHistoryFragment = new CallLogFragment(); |
| } |
| return mHistoryFragment; |
| case TAB_INDEX_ALL_CONTACTS: |
| if (mAllContactsFragment == null) { |
| mAllContactsFragment = new AllContactsFragment(); |
| } |
| return mAllContactsFragment; |
| case TAB_INDEX_VOICEMAIL: |
| if (mVoicemailFragment == null) { |
| mVoicemailFragment = new VisualVoicemailCallLogFragment(); |
| LogUtil.v( |
| "ViewPagerAdapter.getItem", |
| "new VisualVoicemailCallLogFragment: %s", |
| mVoicemailFragment); |
| } |
| return mVoicemailFragment; |
| } |
| throw new IllegalStateException("No fragment at position " + position); |
| } |
| |
| @Override |
| public Fragment instantiateItem(ViewGroup container, int position) { |
| LogUtil.d("ViewPagerAdapter.instantiateItem", "position: %d", position); |
| // On rotation the FragmentManager handles rotation. Therefore getItem() isn't called. |
| // Copy the fragments that the FragmentManager finds so that we can store them in |
| // instance variables for later. |
| final Fragment fragment = (Fragment) super.instantiateItem(container, position); |
| if (fragment instanceof SpeedDialFragment) { |
| mSpeedDialFragment = (SpeedDialFragment) fragment; |
| } else if (fragment instanceof CallLogFragment && position == TAB_INDEX_HISTORY) { |
| mHistoryFragment = (CallLogFragment) fragment; |
| } else if (fragment instanceof AllContactsFragment) { |
| mAllContactsFragment = (AllContactsFragment) fragment; |
| } else if (fragment instanceof CallLogFragment && position == TAB_INDEX_VOICEMAIL) { |
| mVoicemailFragment = (CallLogFragment) fragment; |
| LogUtil.v("ViewPagerAdapter.instantiateItem", mVoicemailFragment.toString()); |
| } |
| mFragments.set(position, fragment); |
| return fragment; |
| } |
| |
| /** |
| * When {@link android.support.v4.view.PagerAdapter#notifyDataSetChanged} is called, this method |
| * is called on all pages to determine whether they need to be recreated. When the voicemail tab |
| * is removed, the view needs to be recreated by returning POSITION_NONE. If |
| * notifyDataSetChanged is called for some other reason, the voicemail tab is recreated only if |
| * it is active. All other tabs do not need to be recreated and POSITION_UNCHANGED is returned. |
| */ |
| @Override |
| public int getItemPosition(Object object) { |
| return !mHasActiveVoicemailProvider && mFragments.indexOf(object) == TAB_INDEX_VOICEMAIL |
| ? POSITION_NONE |
| : POSITION_UNCHANGED; |
| } |
| |
| @Override |
| public int getCount() { |
| return mHasActiveVoicemailProvider ? TAB_COUNT_WITH_VOICEMAIL : TAB_COUNT_DEFAULT; |
| } |
| |
| @Override |
| public CharSequence getPageTitle(int position) { |
| return mTabTitles[position]; |
| } |
| } |
| } |