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.dialer.DialtactsActivity;
     24 import com.android.dialer.R;
     25 import com.android.dialer.calllog.CallLogAdapter;
     26 import com.android.dialer.calllog.CallLogFragment;
     27 import com.android.dialer.calllog.CallLogQuery;
     28 import com.android.dialer.calllog.CallLogQueryHandler;
     29 import com.android.dialer.calllog.ContactInfoHelper;
     30 import com.android.dialer.list.ShortcutCardsAdapter.SwipeableShortcutCard;
     31 import com.android.dialer.util.DialerUtils;
     32 import com.android.dialer.widget.OverlappingPaneLayout;
     33 import com.android.dialer.widget.OverlappingPaneLayout.PanelSlideCallbacks;
     34 import com.android.dialerbind.analytics.AnalyticsFragment;
     35 import com.android.dialerbind.ObjectFactory;
     36 
     37 import java.util.ArrayList;
     38 
     39 /**
     40  * Fragment that is used as the main screen of the Dialer.
     41  *
     42  * Contains a ViewPager that contains various contact lists like the Speed Dial list and the
     43  * All Contacts list. This will also eventually contain the logic that allows sliding the
     44  * ViewPager containing the lists up above the shortcut cards and pin it against the top of the
     45  * screen.
     46  */
     47 public class ListsFragment extends AnalyticsFragment implements CallLogQueryHandler.Listener,
     48         CallLogAdapter.CallFetcher, ViewPager.OnPageChangeListener {
     49 
     50     private static final boolean DEBUG = DialtactsActivity.DEBUG;
     51     private static final String TAG = "ListsFragment";
     52 
     53     public static final int TAB_INDEX_SPEED_DIAL = 0;
     54     public static final int TAB_INDEX_RECENTS = 1;
     55     public static final int TAB_INDEX_ALL_CONTACTS = 2;
     56 
     57     public static final int TAB_INDEX_COUNT = 3;
     58 
     59     private static final int MAX_RECENTS_ENTRIES = 20;
     60     // Oldest recents entry to display is 2 weeks old.
     61     private static final long OLDEST_RECENTS_DATE = 1000L * 60 * 60 * 24 * 14;
     62 
     63     private static final String KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE =
     64             "key_last_dismissed_call_shortcut_date";
     65 
     66     public static final float REMOVE_VIEW_SHOWN_ALPHA = 0.5f;
     67     public static final float REMOVE_VIEW_HIDDEN_ALPHA = 1;
     68 
     69     public interface HostInterface {
     70         public void showCallHistory();
     71         public int getActionBarHeight();
     72         public void setActionBarHideOffset(int offset);
     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 
     94     private boolean mIsPanelOpen = true;
     95 
     96     /**
     97      * Call shortcuts older than this date (persisted in shared preferences) will not show up in
     98      * at the top of the screen
     99      */
    100     private long mLastCallShortcutDate = 0;
    101 
    102     /**
    103      * The date of the current call shortcut that is showing on screen.
    104      */
    105     private long mCurrentCallShortcutDate = 0;
    106 
    107     private PanelSlideCallbacks mPanelSlideCallbacks = new PanelSlideCallbacks() {
    108         @Override
    109         public void onPanelSlide(View panel, float slideOffset) {
    110             // For every 1 percent that the panel is slid upwards, clip 1 percent off the top
    111             // edge of the shortcut card, to achieve the animated effect of the shortcut card
    112             // being pushed out of view when the panel is slid upwards. slideOffset is 1 when
    113             // the shortcut card is fully exposed, and 0 when completely hidden.
    114             float ratioCardHidden = (1 - slideOffset);
    115             if (mShortcutCardsListView.getChildCount() > 0) {
    116                 final SwipeableShortcutCard v =
    117                         (SwipeableShortcutCard) mShortcutCardsListView.getChildAt(0);
    118                 v.clipCard(ratioCardHidden);
    119             }
    120 
    121             if (mActionBar != null) {
    122                 // Amount of available space that is not being hidden by the bottom pane
    123                 final int topPaneHeight = (int) (slideOffset * mShortcutCardsListView.getHeight());
    124 
    125                 final int availableActionBarHeight =
    126                         Math.min(mActionBar.getHeight(), topPaneHeight);
    127                 ((HostInterface) getActivity()).setActionBarHideOffset(
    128                         mActionBar.getHeight() - availableActionBarHeight);
    129 
    130                 if (!mActionBar.isShowing()) {
    131                     mActionBar.show();
    132                 }
    133             }
    134         }
    135 
    136         @Override
    137         public void onPanelOpened(View panel) {
    138             if (DEBUG) {
    139                 Log.d(TAG, "onPanelOpened");
    140             }
    141             mIsPanelOpen = true;
    142         }
    143 
    144         @Override
    145         public void onPanelClosed(View panel) {
    146             if (DEBUG) {
    147                 Log.d(TAG, "onPanelClosed");
    148             }
    149             mIsPanelOpen = false;
    150         }
    151 
    152         @Override
    153         public void onPanelFlingReachesEdge(int velocityY) {
    154             if (getCurrentListView() != null) {
    155                 getCurrentListView().fling(velocityY);
    156             }
    157         }
    158 
    159         @Override
    160         public boolean isScrollableChildUnscrolled() {
    161             final AbsListView listView = getCurrentListView();
    162             return listView != null && (listView.getChildCount() == 0
    163                     || listView.getChildAt(0).getTop() == listView.getPaddingTop());
    164         }
    165     };
    166 
    167     private AbsListView getCurrentListView() {
    168         final int position = mViewPager.getCurrentItem();
    169         switch (getRtlPosition(position)) {
    170             case TAB_INDEX_SPEED_DIAL:
    171                 return mSpeedDialFragment == null ? null : mSpeedDialFragment.getListView();
    172             case TAB_INDEX_RECENTS:
    173                 return mRecentsFragment == null ? null : mRecentsFragment.getListView();
    174             case TAB_INDEX_ALL_CONTACTS:
    175                 return mAllContactsFragment == null ? null : mAllContactsFragment.getListView();
    176         }
    177         throw new IllegalStateException("No fragment at position " + position);
    178     }
    179 
    180     public class ViewPagerAdapter extends FragmentPagerAdapter {
    181         public ViewPagerAdapter(FragmentManager fm) {
    182             super(fm);
    183         }
    184 
    185         @Override
    186         public long getItemId(int position) {
    187             return getRtlPosition(position);
    188         }
    189 
    190         @Override
    191         public Fragment getItem(int position) {
    192             switch (getRtlPosition(position)) {
    193                 case TAB_INDEX_SPEED_DIAL:
    194                     mSpeedDialFragment = new SpeedDialFragment();
    195                     return mSpeedDialFragment;
    196                 case TAB_INDEX_RECENTS:
    197                     mRecentsFragment = new CallLogFragment(CallLogQueryHandler.CALL_TYPE_ALL,
    198                             MAX_RECENTS_ENTRIES, System.currentTimeMillis() - OLDEST_RECENTS_DATE);
    199                     mRecentsFragment.setHasFooterView(true);
    200                     return mRecentsFragment;
    201                 case TAB_INDEX_ALL_CONTACTS:
    202                     mAllContactsFragment = new AllContactsFragment();
    203                     return mAllContactsFragment;
    204             }
    205             throw new IllegalStateException("No fragment at position " + position);
    206         }
    207 
    208         @Override
    209         public Object instantiateItem(ViewGroup container, int position) {
    210             // On rotation the FragmentManager handles rotation. Therefore getItem() isn't called.
    211             // Copy the fragments that the FragmentManager finds so that we can store them in
    212             // instance variables for later.
    213             final Fragment fragment =
    214                     (Fragment) super.instantiateItem(container, position);
    215             if (fragment instanceof SpeedDialFragment) {
    216                 mSpeedDialFragment = (SpeedDialFragment) fragment;
    217             } else if (fragment instanceof CallLogFragment) {
    218                 mRecentsFragment = (CallLogFragment) fragment;
    219             } else if (fragment instanceof AllContactsFragment) {
    220                 mAllContactsFragment = (AllContactsFragment) fragment;
    221             }
    222             return fragment;
    223         }
    224 
    225         @Override
    226         public int getCount() {
    227             return TAB_INDEX_COUNT;
    228         }
    229 
    230         @Override
    231         public CharSequence getPageTitle(int position) {
    232             return mTabTitles[position];
    233         }
    234     }
    235 
    236     @Override
    237     public void onCreate(Bundle savedInstanceState) {
    238         super.onCreate(savedInstanceState);
    239 
    240         mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(),
    241                 this, 1);
    242         final String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
    243         mCallLogAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this,
    244                 new ContactInfoHelper(getActivity(), currentCountryIso), null, null, false);
    245 
    246         mMergedAdapter = new ShortcutCardsAdapter(getActivity(), this, mCallLogAdapter);
    247     }
    248 
    249     @Override
    250     public void onStart() {
    251         super.onStart();
    252     }
    253 
    254     @Override
    255     public void onResume() {
    256         super.onResume();
    257         final SharedPreferences prefs = getActivity().getSharedPreferences(
    258                 DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
    259         mLastCallShortcutDate = prefs.getLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, 0);
    260         mActionBar = getActivity().getActionBar();
    261         fetchCalls();
    262         mCallLogAdapter.setLoading(true);
    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 
    307         return parentView;
    308     }
    309 
    310     @Override
    311     public void onVoicemailStatusFetched(Cursor statusCursor) {
    312         // no-op
    313     }
    314 
    315     @Override
    316     public boolean onCallsFetched(Cursor cursor) {
    317         mCallLogAdapter.setLoading(false);
    318 
    319         // Save the date of the most recent call log item
    320         if (cursor != null && cursor.moveToFirst()) {
    321             mCurrentCallShortcutDate = cursor.getLong(CallLogQuery.DATE);
    322         }
    323 
    324         mCallLogAdapter.changeCursor(cursor);
    325         mMergedAdapter.notifyDataSetChanged();
    326         // Return true; took ownership of cursor
    327         return true;
    328     }
    329 
    330     @Override
    331     public void fetchCalls() {
    332         mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL, mLastCallShortcutDate);
    333     }
    334 
    335     public void dismissShortcut(View view) {
    336         mLastCallShortcutDate = mCurrentCallShortcutDate;
    337         final SharedPreferences prefs = view.getContext().getSharedPreferences(
    338                 DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
    339         prefs.edit().putLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, mLastCallShortcutDate)
    340                 .apply();
    341         fetchCalls();
    342     }
    343 
    344     public void addOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
    345         if (!mOnPageChangeListeners.contains(onPageChangeListener)) {
    346             mOnPageChangeListeners.add(onPageChangeListener);
    347         }
    348     }
    349 
    350     @Override
    351     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    352         final int count = mOnPageChangeListeners.size();
    353         for (int i = 0; i < count; i++) {
    354             mOnPageChangeListeners.get(i).onPageScrolled(position, positionOffset,
    355                     positionOffsetPixels);
    356         }
    357     }
    358 
    359     @Override
    360     public void onPageSelected(int position) {
    361         if (position == TAB_INDEX_SPEED_DIAL && mSpeedDialFragment != null) {
    362             mSpeedDialFragment.sendScreenView();
    363         } else if (position == TAB_INDEX_RECENTS && mRecentsFragment != null) {
    364             mRecentsFragment.sendScreenView();
    365         } else if (position == TAB_INDEX_ALL_CONTACTS && mAllContactsFragment != null) {
    366             mAllContactsFragment.sendScreenView();
    367         }
    368         final int count = mOnPageChangeListeners.size();
    369         for (int i = 0; i < count; i++) {
    370             mOnPageChangeListeners.get(i).onPageSelected(position);
    371         }
    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()).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