Home | History | Annotate | Download | only in list
      1 package com.android.dialer.list;
      2 
      3 import android.animation.LayoutTransition;
      4 import android.app.ActionBar;
      5 import android.app.Fragment;
      6 import android.app.FragmentManager;
      7 import android.content.Context;
      8 import android.content.SharedPreferences;
      9 import android.database.Cursor;
     10 import android.os.Bundle;
     11 import android.support.v13.app.FragmentPagerAdapter;
     12 import android.support.v4.view.ViewPager;
     13 import android.support.v4.view.ViewPager.OnPageChangeListener;
     14 import android.util.Log;
     15 import android.view.LayoutInflater;
     16 import android.view.View;
     17 import android.view.ViewGroup;
     18 import android.widget.AbsListView;
     19 import android.widget.ListView;
     20 
     21 import com.android.contacts.common.GeoUtil;
     22 import com.android.contacts.common.list.ViewPagerTabs;
     23 import com.android.contacts.commonbind.analytics.AnalyticsUtil;
     24 import com.android.dialer.DialtactsActivity;
     25 import com.android.dialer.R;
     26 import com.android.dialer.calllog.CallLogAdapter;
     27 import com.android.dialer.calllog.CallLogFragment;
     28 import com.android.dialer.calllog.CallLogQuery;
     29 import com.android.dialer.calllog.CallLogQueryHandler;
     30 import com.android.dialer.calllog.ContactInfoHelper;
     31 import com.android.dialer.list.ShortcutCardsAdapter.SwipeableShortcutCard;
     32 import com.android.dialer.util.DialerUtils;
     33 import com.android.dialer.widget.ActionBarController;
     34 import com.android.dialer.widget.OverlappingPaneLayout;
     35 import com.android.dialer.widget.OverlappingPaneLayout.PanelSlideCallbacks;
     36 import com.android.dialerbind.ObjectFactory;
     37 
     38 import java.util.ArrayList;
     39 
     40 /**
     41  * Fragment that is used as the main screen of the Dialer.
     42  *
     43  * Contains a ViewPager that contains various contact lists like the Speed Dial list and the
     44  * All Contacts list. This will also eventually contain the logic that allows sliding the
     45  * ViewPager containing the lists up above the shortcut cards and pin it against the top of the
     46  * screen.
     47  */
     48 public class ListsFragment extends Fragment implements CallLogQueryHandler.Listener,
     49         CallLogAdapter.CallFetcher, ViewPager.OnPageChangeListener {
     50 
     51     private static final boolean DEBUG = DialtactsActivity.DEBUG;
     52     private static final String TAG = "ListsFragment";
     53 
     54     public static final int TAB_INDEX_SPEED_DIAL = 0;
     55     public static final int TAB_INDEX_RECENTS = 1;
     56     public static final int TAB_INDEX_ALL_CONTACTS = 2;
     57 
     58     public static final int TAB_INDEX_COUNT = 3;
     59 
     60     private static final int MAX_RECENTS_ENTRIES = 20;
     61     // Oldest recents entry to display is 2 weeks old.
     62     private static final long OLDEST_RECENTS_DATE = 1000L * 60 * 60 * 24 * 14;
     63 
     64     private static final String KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE =
     65             "key_last_dismissed_call_shortcut_date";
     66 
     67     public static final float REMOVE_VIEW_SHOWN_ALPHA = 0.5f;
     68     public static final float REMOVE_VIEW_HIDDEN_ALPHA = 1;
     69 
     70     public interface HostInterface {
     71         public void showCallHistory();
     72         public ActionBarController getActionBarController();
     73     }
     74 
     75     private ActionBar mActionBar;
     76     private ViewPager mViewPager;
     77     private ViewPagerTabs mViewPagerTabs;
     78     private ViewPagerAdapter mViewPagerAdapter;
     79     private ListView mShortcutCardsListView;
     80     private RemoveView mRemoveView;
     81     private View mRemoveViewContent;
     82     private SpeedDialFragment mSpeedDialFragment;
     83     private CallLogFragment mRecentsFragment;
     84     private AllContactsFragment mAllContactsFragment;
     85     private ArrayList<OnPageChangeListener> mOnPageChangeListeners =
     86             new ArrayList<OnPageChangeListener>();
     87 
     88     private String[] mTabTitles;
     89 
     90     private ShortcutCardsAdapter mMergedAdapter;
     91     private CallLogAdapter mCallLogAdapter;
     92     private CallLogQueryHandler mCallLogQueryHandler;
     93     private OverlappingPaneLayout mOverlappingPaneLayout;
     94 
     95     private boolean mIsPanelOpen = true;
     96 
     97     /**
     98      * Call shortcuts older than this date (persisted in shared preferences) will not show up in
     99      * at the top of the screen
    100      */
    101     private long mLastCallShortcutDate = 0;
    102 
    103     /**
    104      * The date of the current call shortcut that is showing on screen.
    105      */
    106     private long mCurrentCallShortcutDate = 0;
    107 
    108     private PanelSlideCallbacks mPanelSlideCallbacks = new PanelSlideCallbacks() {
    109         @Override
    110         public void onPanelSlide(View panel, float slideOffset) {
    111             // For every 1 percent that the panel is slid upwards, clip 1 percent off the top
    112             // edge of the shortcut card, to achieve the animated effect of the shortcut card
    113             // being pushed out of view when the panel is slid upwards. slideOffset is 1 when
    114             // the shortcut card is fully exposed, and 0 when completely hidden.
    115             float ratioCardHidden = (1 - slideOffset);
    116             if (mShortcutCardsListView.getChildCount() > 0) {
    117                 final SwipeableShortcutCard v =
    118                         (SwipeableShortcutCard) mShortcutCardsListView.getChildAt(0);
    119                 v.clipCard(ratioCardHidden);
    120             }
    121 
    122             if (mActionBar != null) {
    123                 // Amount of available space that is not being hidden by the bottom pane
    124                 final int topPaneHeight = (int) (slideOffset * mShortcutCardsListView.getHeight());
    125 
    126                 final int availableActionBarHeight =
    127                         Math.min(mActionBar.getHeight(), topPaneHeight);
    128                 final ActionBarController controller =
    129                         ((HostInterface) getActivity()).getActionBarController();
    130                 controller.setHideOffset(mActionBar.getHeight() - availableActionBarHeight);
    131 
    132                 if (!mActionBar.isShowing()) {
    133                     mActionBar.show();
    134                 }
    135             }
    136         }
    137 
    138         @Override
    139         public void onPanelOpened(View panel) {
    140             if (DEBUG) {
    141                 Log.d(TAG, "onPanelOpened");
    142             }
    143             mIsPanelOpen = true;
    144         }
    145 
    146         @Override
    147         public void onPanelClosed(View panel) {
    148             if (DEBUG) {
    149                 Log.d(TAG, "onPanelClosed");
    150             }
    151             mIsPanelOpen = false;
    152         }
    153 
    154         @Override
    155         public void onPanelFlingReachesEdge(int velocityY) {
    156             if (getCurrentListView() != null) {
    157                 getCurrentListView().fling(velocityY);
    158             }
    159         }
    160 
    161         @Override
    162         public boolean isScrollableChildUnscrolled() {
    163             final AbsListView listView = getCurrentListView();
    164             return listView != null && (listView.getChildCount() == 0
    165                     || listView.getChildAt(0).getTop() == listView.getPaddingTop());
    166         }
    167     };
    168 
    169     private AbsListView getCurrentListView() {
    170         final int position = mViewPager.getCurrentItem();
    171         switch (getRtlPosition(position)) {
    172             case TAB_INDEX_SPEED_DIAL:
    173                 return mSpeedDialFragment == null ? null : mSpeedDialFragment.getListView();
    174             case TAB_INDEX_RECENTS:
    175                 return mRecentsFragment == null ? null : mRecentsFragment.getListView();
    176             case TAB_INDEX_ALL_CONTACTS:
    177                 return mAllContactsFragment == null ? null : mAllContactsFragment.getListView();
    178         }
    179         throw new IllegalStateException("No fragment at position " + position);
    180     }
    181 
    182     public class ViewPagerAdapter extends FragmentPagerAdapter {
    183         public ViewPagerAdapter(FragmentManager fm) {
    184             super(fm);
    185         }
    186 
    187         @Override
    188         public long getItemId(int position) {
    189             return getRtlPosition(position);
    190         }
    191 
    192         @Override
    193         public Fragment getItem(int position) {
    194             switch (getRtlPosition(position)) {
    195                 case TAB_INDEX_SPEED_DIAL:
    196                     mSpeedDialFragment = new SpeedDialFragment();
    197                     return mSpeedDialFragment;
    198                 case TAB_INDEX_RECENTS:
    199                     mRecentsFragment = new CallLogFragment(CallLogQueryHandler.CALL_TYPE_ALL,
    200                             MAX_RECENTS_ENTRIES, System.currentTimeMillis() - OLDEST_RECENTS_DATE);
    201                     mRecentsFragment.setHasFooterView(true);
    202                     return mRecentsFragment;
    203                 case TAB_INDEX_ALL_CONTACTS:
    204                     mAllContactsFragment = new AllContactsFragment();
    205                     return mAllContactsFragment;
    206             }
    207             throw new IllegalStateException("No fragment at position " + position);
    208         }
    209 
    210         @Override
    211         public Object instantiateItem(ViewGroup container, int position) {
    212             // On rotation the FragmentManager handles rotation. Therefore getItem() isn't called.
    213             // Copy the fragments that the FragmentManager finds so that we can store them in
    214             // instance variables for later.
    215             final Fragment fragment =
    216                     (Fragment) super.instantiateItem(container, position);
    217             if (fragment instanceof SpeedDialFragment) {
    218                 mSpeedDialFragment = (SpeedDialFragment) fragment;
    219             } else if (fragment instanceof CallLogFragment) {
    220                 mRecentsFragment = (CallLogFragment) fragment;
    221             } else if (fragment instanceof AllContactsFragment) {
    222                 mAllContactsFragment = (AllContactsFragment) fragment;
    223             }
    224             return fragment;
    225         }
    226 
    227         @Override
    228         public int getCount() {
    229             return TAB_INDEX_COUNT;
    230         }
    231 
    232         @Override
    233         public CharSequence getPageTitle(int position) {
    234             return mTabTitles[position];
    235         }
    236     }
    237 
    238     @Override
    239     public void onCreate(Bundle savedInstanceState) {
    240         super.onCreate(savedInstanceState);
    241 
    242         mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(),
    243                 this, 1);
    244         final String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
    245         mCallLogAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this,
    246                 new ContactInfoHelper(getActivity(), currentCountryIso), null, null, false);
    247 
    248         mMergedAdapter = new ShortcutCardsAdapter(getActivity(), this, mCallLogAdapter);
    249     }
    250 
    251     @Override
    252     public void onResume() {
    253         super.onResume();
    254         final SharedPreferences prefs = getActivity().getSharedPreferences(
    255                 DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
    256         mLastCallShortcutDate = prefs.getLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, 0);
    257         mActionBar = getActivity().getActionBar();
    258         fetchCalls();
    259         mCallLogAdapter.setLoading(true);
    260         if (getUserVisibleHint()) {
    261             sendScreenViewForPosition(mViewPager.getCurrentItem());
    262         }
    263     }
    264 
    265     @Override
    266     public void onPause() {
    267         // Wipe the cache to refresh the call shortcut item. This is not that expensive because
    268         // it only contains one item.
    269         mCallLogAdapter.invalidateCache();
    270         super.onPause();
    271     }
    272 
    273     @Override
    274     public void onDestroy() {
    275         mCallLogAdapter.stopRequestProcessing();
    276         super.onDestroy();
    277     }
    278 
    279     @Override
    280     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    281             Bundle savedInstanceState) {
    282         final View parentView = inflater.inflate(R.layout.lists_fragment, container, false);
    283         mViewPager = (ViewPager) parentView.findViewById(R.id.lists_pager);
    284         mViewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
    285         mViewPager.setAdapter(mViewPagerAdapter);
    286         mViewPager.setOffscreenPageLimit(2);
    287         mViewPager.setOnPageChangeListener(this);
    288         mViewPager.setCurrentItem(getRtlPosition(TAB_INDEX_SPEED_DIAL));
    289 
    290         mTabTitles = new String[TAB_INDEX_COUNT];
    291         mTabTitles[TAB_INDEX_SPEED_DIAL] = getResources().getString(R.string.tab_speed_dial);
    292         mTabTitles[TAB_INDEX_RECENTS] = getResources().getString(R.string.tab_recents);
    293         mTabTitles[TAB_INDEX_ALL_CONTACTS] = getResources().getString(R.string.tab_all_contacts);
    294 
    295         mViewPagerTabs = (ViewPagerTabs) parentView.findViewById(R.id.lists_pager_header);
    296         mViewPagerTabs.setViewPager(mViewPager);
    297         addOnPageChangeListener(mViewPagerTabs);
    298 
    299         mShortcutCardsListView = (ListView) parentView.findViewById(R.id.shortcut_card_list);
    300         mShortcutCardsListView.setAdapter(mMergedAdapter);
    301 
    302         mRemoveView = (RemoveView) parentView.findViewById(R.id.remove_view);
    303         mRemoveViewContent = parentView.findViewById(R.id.remove_view_content);
    304 
    305         setupPaneLayout((OverlappingPaneLayout) parentView);
    306         mOverlappingPaneLayout = (OverlappingPaneLayout) parentView;
    307 
    308         return parentView;
    309     }
    310 
    311     @Override
    312     public void onVoicemailStatusFetched(Cursor statusCursor) {
    313         // no-op
    314     }
    315 
    316     @Override
    317     public boolean onCallsFetched(Cursor cursor) {
    318         mCallLogAdapter.setLoading(false);
    319 
    320         // Save the date of the most recent call log item
    321         if (cursor != null && cursor.moveToFirst()) {
    322             mCurrentCallShortcutDate = cursor.getLong(CallLogQuery.DATE);
    323         }
    324 
    325         mCallLogAdapter.changeCursor(cursor);
    326         mMergedAdapter.notifyDataSetChanged();
    327 
    328         // Refresh the overlapping pane to ensure that any changes in the shortcut card height
    329         // are appropriately reflected in the overlap position.
    330         mOverlappingPaneLayout.refresh();
    331 
    332         // Return true; took ownership of cursor
    333         return true;
    334     }
    335 
    336     @Override
    337     public void fetchCalls() {
    338         mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL, mLastCallShortcutDate);
    339     }
    340 
    341     public void dismissShortcut(View view) {
    342         mLastCallShortcutDate = mCurrentCallShortcutDate;
    343         final SharedPreferences prefs = view.getContext().getSharedPreferences(
    344                 DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
    345         prefs.edit().putLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, mLastCallShortcutDate)
    346                 .apply();
    347         fetchCalls();
    348     }
    349 
    350     public void addOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
    351         if (!mOnPageChangeListeners.contains(onPageChangeListener)) {
    352             mOnPageChangeListeners.add(onPageChangeListener);
    353         }
    354     }
    355 
    356     @Override
    357     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    358         final int count = mOnPageChangeListeners.size();
    359         for (int i = 0; i < count; i++) {
    360             mOnPageChangeListeners.get(i).onPageScrolled(position, positionOffset,
    361                     positionOffsetPixels);
    362         }
    363     }
    364 
    365     @Override
    366     public void onPageSelected(int position) {
    367         final int count = mOnPageChangeListeners.size();
    368         for (int i = 0; i < count; i++) {
    369             mOnPageChangeListeners.get(i).onPageSelected(position);
    370         }
    371         sendScreenViewForPosition(position);
    372     }
    373 
    374     @Override
    375     public void onPageScrollStateChanged(int state) {
    376         final int count = mOnPageChangeListeners.size();
    377         for (int i = 0; i < count; i++) {
    378             mOnPageChangeListeners.get(i).onPageScrollStateChanged(state);
    379         }
    380     }
    381 
    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         if (mShortcutCardsListView.getChildCount() > 0) {
    388             View v = mShortcutCardsListView.getChildAt(0);
    389             v.animate().withLayer()
    390                     .alpha(show ? REMOVE_VIEW_SHOWN_ALPHA : REMOVE_VIEW_HIDDEN_ALPHA)
    391                     .start();
    392         }
    393     }
    394 
    395     public boolean shouldShowActionBar() {
    396         return mIsPanelOpen && mActionBar != null;
    397     }
    398 
    399     public boolean isPaneOpen() {
    400         return mIsPanelOpen;
    401     }
    402 
    403     private void setupPaneLayout(OverlappingPaneLayout paneLayout) {
    404         // TODO: Remove the notion of a capturable view. The entire view be slideable, once
    405         // the framework better supports nested scrolling.
    406         paneLayout.setCapturableView(mViewPagerTabs);
    407         paneLayout.openPane();
    408         paneLayout.setPanelSlideCallbacks(mPanelSlideCallbacks);
    409         paneLayout.setIntermediatePinnedOffset(
    410                 ((HostInterface) getActivity()).getActionBarController().getActionBarHeight());
    411 
    412         LayoutTransition transition = paneLayout.getLayoutTransition();
    413         // Turns on animations for all types of layout changes so that they occur for
    414         // height changes.
    415         transition.enableTransitionType(LayoutTransition.CHANGING);
    416     }
    417 
    418     public SpeedDialFragment getSpeedDialFragment() {
    419         return mSpeedDialFragment;
    420     }
    421 
    422     public RemoveView getRemoveView() {
    423         return mRemoveView;
    424     }
    425 
    426     public int getRtlPosition(int position) {
    427         if (DialerUtils.isRtl()) {
    428             return TAB_INDEX_COUNT - 1 - position;
    429         }
    430         return position;
    431     }
    432 
    433     public void sendScreenViewForCurrentPosition() {
    434         sendScreenViewForPosition(mViewPager.getCurrentItem());
    435     }
    436 
    437     private void sendScreenViewForPosition(int position) {
    438         if (!isResumed()) {
    439             return;
    440         }
    441         String fragmentName;
    442         switch (getRtlPosition(position)) {
    443             case TAB_INDEX_SPEED_DIAL:
    444                 fragmentName = SpeedDialFragment.class.getSimpleName();
    445                 break;
    446             case TAB_INDEX_RECENTS:
    447                 fragmentName = CallLogFragment.class.getSimpleName();
    448                 break;
    449             case TAB_INDEX_ALL_CONTACTS:
    450                 fragmentName = AllContactsFragment.class.getSimpleName();
    451                 break;
    452             default:
    453                 return;
    454         }
    455         AnalyticsUtil.sendScreenView(fragmentName, getActivity(), null);
    456     }
    457 }
    458