Home | History | Annotate | Download | only in list
      1 /*
      2  * Copyright (C) 2011 Google Inc.
      3  * Licensed to The Android Open Source Project.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 package com.android.dialer.list;
     18 
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.database.Cursor;
     22 import android.database.DataSetObserver;
     23 import android.view.MotionEvent;
     24 import android.view.View;
     25 import android.view.ViewConfiguration;
     26 import android.view.ViewGroup;
     27 import android.widget.BaseAdapter;
     28 import android.widget.FrameLayout;
     29 
     30 import com.android.dialer.R;
     31 import com.android.dialer.calllog.CallLogAdapter;
     32 import com.android.dialer.calllog.CallLogNotificationsHelper;
     33 import com.android.dialer.calllog.CallLogQueryHandler;
     34 import com.android.dialer.list.SwipeHelper.OnItemGestureListener;
     35 import com.android.dialer.list.SwipeHelper.SwipeHelperCallback;
     36 
     37 /**
     38  * An adapter that combines items from {@link com.android.contacts.common.list.ContactTileAdapter}
     39  * and {@link com.android.dialer.calllog.CallLogAdapter} into a single list.
     40  */
     41 public class PhoneFavoriteMergedAdapter extends BaseAdapter {
     42 
     43     private class CustomDataSetObserver extends DataSetObserver {
     44         @Override
     45         public void onChanged() {
     46             notifyDataSetChanged();
     47         }
     48     }
     49 
     50     private static final String TAG = PhoneFavoriteMergedAdapter.class.getSimpleName();
     51 
     52     private static final int TILE_INTERACTION_TEASER_VIEW_POSITION = 2;
     53     private static final int TILE_INTERACTION_TEASER_VIEW_ID = -2;
     54     private static final int ALL_CONTACTS_BUTTON_ITEM_ID = -1;
     55     private final PhoneFavoritesTileAdapter mContactTileAdapter;
     56     private final CallLogAdapter mCallLogAdapter;
     57     private final View mShowAllContactsButton;
     58     private final PhoneFavoriteFragment mFragment;
     59     private final TileInteractionTeaserView mTileInteractionTeaserView;
     60 
     61     private final int mCallLogPadding;
     62 
     63     private final Context mContext;
     64 
     65     private final DataSetObserver mObserver;
     66 
     67     private final CallLogQueryHandler mCallLogQueryHandler;
     68 
     69     private final OnItemGestureListener mCallLogOnItemSwipeListener =
     70             new OnItemGestureListener() {
     71         @Override
     72         public void onSwipe(View view) {
     73             mCallLogQueryHandler.markNewCallsAsOld();
     74             mCallLogQueryHandler.markNewVoicemailsAsOld();
     75             CallLogNotificationsHelper.removeMissedCallNotifications();
     76             CallLogNotificationsHelper.updateVoicemailNotifications(mContext);
     77             mFragment.dismissShortcut(((View) view.getParent()).getHeight());
     78         }
     79 
     80         @Override
     81         public void onTouch() {}
     82 
     83         @Override
     84         public boolean isSwipeEnabled() {
     85             return true;
     86         }
     87     };
     88 
     89     private final CallLogQueryHandler.Listener mCallLogQueryHandlerListener =
     90             new CallLogQueryHandler.Listener() {
     91         @Override
     92         public void onVoicemailStatusFetched(Cursor statusCursor) {}
     93 
     94         @Override
     95         public void onCallsFetched(Cursor combinedCursor) {
     96             mCallLogAdapter.invalidateCache();
     97             mCallLogAdapter.changeCursor(combinedCursor);
     98             mCallLogAdapter.notifyDataSetChanged();
     99         }
    100     };
    101 
    102     public PhoneFavoriteMergedAdapter(Context context,
    103             PhoneFavoriteFragment fragment,
    104             PhoneFavoritesTileAdapter contactTileAdapter,
    105             CallLogAdapter callLogAdapter,
    106             View showAllContactsButton,
    107             TileInteractionTeaserView tileInteractionTeaserView) {
    108         final Resources resources = context.getResources();
    109         mContext = context;
    110         mFragment = fragment;
    111         mCallLogPadding = resources.getDimensionPixelSize(R.dimen.recent_call_log_item_padding);
    112         mContactTileAdapter = contactTileAdapter;
    113         mCallLogAdapter = callLogAdapter;
    114         mObserver = new CustomDataSetObserver();
    115         mCallLogAdapter.registerDataSetObserver(mObserver);
    116         mContactTileAdapter.registerDataSetObserver(mObserver);
    117         mShowAllContactsButton = showAllContactsButton;
    118         mTileInteractionTeaserView = tileInteractionTeaserView;
    119         mCallLogQueryHandler = new CallLogQueryHandler(mContext.getContentResolver(),
    120                 mCallLogQueryHandlerListener);
    121     }
    122 
    123     @Override
    124     public int getCount() {
    125         if (mContactTileAdapter.getCount() > 0) {
    126             return mContactTileAdapter.getCount() + mCallLogAdapter.getCount() + 1 +
    127                     getTeaserViewCount();
    128         } else {
    129             return mCallLogAdapter.getCount();
    130         }
    131     }
    132 
    133     @Override
    134     public Object getItem(int position) {
    135         final int callLogAdapterCount = mCallLogAdapter.getCount();
    136 
    137         if (callLogAdapterCount > 0) {
    138             if (position < callLogAdapterCount) {
    139                 return mCallLogAdapter.getItem(position);
    140             }
    141         }
    142         // Set position to the position of the actual favorite contact in the favorites adapter
    143         position = getAdjustedFavoritePosition(position, callLogAdapterCount);
    144 
    145         return mContactTileAdapter.getItem(position);
    146     }
    147 
    148     /**
    149      * In order to ensure that items have stable ids (for animation purposes), we need to
    150      * guarantee that every single item has a unique ID, even across data set changes.
    151      *
    152      * These are the ranges of IDs reserved for each item type.
    153      *
    154      * -(N + 1) to -3: CallLogAdapterItems, where N is equal to the number of call log items
    155      * -2: Teaser
    156      * -1: All contacts button
    157      * 0 to (N -1): Rows of tiled contacts, where N is equal to the max rows of tiled contacts
    158      * N to infinity: Rows of regular contacts. Their item id is calculated by N + contact_id,
    159      * where contact_id is guaranteed to never be negative.
    160      */
    161     @Override
    162     public long getItemId(int position) {
    163         final int callLogAdapterCount = mCallLogAdapter.getCount();
    164         if (position < callLogAdapterCount) {
    165             // Call log items are not animated, so reusing their position for IDs is fine.
    166             return ALL_CONTACTS_BUTTON_ITEM_ID - 2 - position;
    167         } else if (position == TILE_INTERACTION_TEASER_VIEW_POSITION + callLogAdapterCount &&
    168                 mTileInteractionTeaserView.getShouldDisplayInList()){
    169             return TILE_INTERACTION_TEASER_VIEW_ID;
    170         } else if (position < (callLogAdapterCount + mContactTileAdapter.getCount() +
    171                 getTeaserViewCount())) {
    172             return mContactTileAdapter.getItemId(
    173                     getAdjustedFavoritePosition(position, callLogAdapterCount));
    174         } else {
    175             // All contacts button
    176             return ALL_CONTACTS_BUTTON_ITEM_ID;
    177         }
    178     }
    179 
    180     @Override
    181     public boolean hasStableIds() {
    182         return true;
    183     }
    184 
    185     @Override
    186     public int getViewTypeCount() {
    187         return (mContactTileAdapter.getViewTypeCount() +            /* Favorite and frequent */
    188                 mCallLogAdapter.getViewTypeCount() +                /* Recent call log */
    189                 getTeaserViewCount() +                              /* Teaser */
    190                 1);                                                 /* Show all contacts button. */
    191     }
    192 
    193     @Override
    194     public int getItemViewType(int position) {
    195         final int callLogAdapterCount = mCallLogAdapter.getCount();
    196 
    197         if (position < callLogAdapterCount) {
    198             // View type of the call log adapter is the last view type of the contact tile adapter
    199             // + 1
    200             return mContactTileAdapter.getViewTypeCount();
    201         } else if (position == TILE_INTERACTION_TEASER_VIEW_POSITION + callLogAdapterCount &&
    202                 mTileInteractionTeaserView.getShouldDisplayInList()) {
    203             // View type of the teaser row is the last view type of the contact tile adapter + 3
    204             return mContactTileAdapter.getViewTypeCount() + 2;
    205         } else if (position < getCount() - 1) {
    206             return mContactTileAdapter.getItemViewType(
    207                     getAdjustedFavoritePosition(position, callLogAdapterCount));
    208         } else {
    209             // View type of the show all contact button is the last view type of the contact tile
    210             // adapter + 2
    211             return mContactTileAdapter.getViewTypeCount() + 1;
    212         }
    213     }
    214 
    215     @Override
    216     public View getView(int position, View convertView, ViewGroup parent) {
    217         final int callLogAdapterCount = mCallLogAdapter.getCount();
    218 
    219         if ((position == getCount() - 1) && (mContactTileAdapter.getCount() > 0)) {
    220             return mShowAllContactsButton;
    221         }
    222 
    223         if (mTileInteractionTeaserView.getShouldDisplayInList())  {
    224             if (position == TILE_INTERACTION_TEASER_VIEW_POSITION + callLogAdapterCount) {
    225                 return mTileInteractionTeaserView;
    226             }
    227         }
    228 
    229         if (callLogAdapterCount > 0) {
    230             if (position == 0) {
    231                 final SwipeableCallLogRow wrapper;
    232                 if (convertView == null) {
    233                     wrapper = new SwipeableCallLogRow(mContext);
    234                     wrapper.setOnItemSwipeListener(mCallLogOnItemSwipeListener);
    235                 } else {
    236                     wrapper = (SwipeableCallLogRow) convertView;
    237                 }
    238 
    239                 // Special case wrapper view for the most recent call log item. This allows
    240                 // us to create a card-like effect for the more recent call log item in
    241                 // the PhoneFavoriteMergedAdapter, but keep the original look of the item in
    242                 // the CallLogAdapter.
    243                 final View view = mCallLogAdapter.getView(position, convertView == null ?
    244                         null : wrapper.getChildAt(0), parent);
    245                 wrapper.removeAllViews();
    246                 final View callLogItem = view.findViewById(R.id.call_log_list_item);
    247                 // Reset the internal call log item view if it is being recycled
    248                 callLogItem.setTranslationX(0);
    249                 callLogItem.setAlpha(1);
    250                 wrapper.addView(view);
    251                 return wrapper;
    252             }
    253         }
    254 
    255         // Set position to the position of the actual favorite contact in the
    256         // favorites adapter
    257         position = getAdjustedFavoritePosition(position, callLogAdapterCount);
    258 
    259         // Favorites section
    260         final View view = mContactTileAdapter.getView(position, convertView, parent);
    261         if (position >= mContactTileAdapter.getMaxTiledRows()) {
    262             final FrameLayout frameLayout = (FrameLayout) view;
    263             final View child = frameLayout.getChildAt(0);
    264             FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
    265                     FrameLayout.LayoutParams.WRAP_CONTENT,
    266                     FrameLayout.LayoutParams.WRAP_CONTENT);
    267             child.setLayoutParams(params);
    268         }
    269         return view;
    270     }
    271 
    272     @Override
    273     public boolean areAllItemsEnabled() {
    274         return mCallLogAdapter.areAllItemsEnabled() && mContactTileAdapter.areAllItemsEnabled();
    275     }
    276 
    277     @Override
    278     public boolean isEnabled(int position) {
    279         final int callLogAdapterCount = mCallLogAdapter.getCount();
    280         if (position < callLogAdapterCount) {
    281             return mCallLogAdapter.isEnabled(position);
    282         } else { // For favorites section
    283             return mContactTileAdapter.isEnabled(
    284                     getAdjustedFavoritePosition(position, callLogAdapterCount));
    285         }
    286     }
    287 
    288     private int getAdjustedFavoritePosition(int position, int callLogAdapterCount) {
    289         if (position - callLogAdapterCount > TILE_INTERACTION_TEASER_VIEW_POSITION &&
    290                 mTileInteractionTeaserView.getShouldDisplayInList()) {
    291             return position - callLogAdapterCount - 1;
    292         } else {
    293             return position - callLogAdapterCount;
    294         }
    295     }
    296 
    297     private int getTeaserViewCount() {
    298         return (mContactTileAdapter.getCount() > TILE_INTERACTION_TEASER_VIEW_POSITION &&
    299                 mTileInteractionTeaserView.getShouldDisplayInList() ? 1 : 0);
    300     }
    301 
    302     /**
    303      * The swipeable call log row.
    304      * See also {@link PhoneFavoritesTileAdapter.ContactTileRow}.
    305      */
    306     private class SwipeableCallLogRow extends FrameLayout implements SwipeHelperCallback {
    307         private SwipeHelper mSwipeHelper;
    308         private OnItemGestureListener mOnItemSwipeListener;
    309 
    310         public SwipeableCallLogRow(Context context) {
    311             super(context);
    312             final float densityScale = getResources().getDisplayMetrics().density;
    313             final float pagingTouchSlop = ViewConfiguration.get(context)
    314                     .getScaledPagingTouchSlop();
    315             mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this,
    316                     densityScale, pagingTouchSlop);
    317         }
    318 
    319         @Override
    320         public void addView(View view) {
    321             view.setBackgroundResource(R.drawable.dialer_recent_card_bg);
    322 
    323             final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
    324                     FrameLayout.LayoutParams.MATCH_PARENT,
    325                     FrameLayout.LayoutParams.WRAP_CONTENT);
    326             params.setMarginsRelative(mCallLogPadding, mCallLogPadding, mCallLogPadding,
    327                     mCallLogPadding);
    328             view.setLayoutParams(params);
    329 
    330             super.addView(view);
    331         }
    332 
    333         @Override
    334         public View getChildAtPosition(MotionEvent ev) {
    335             return getChildCount() > 0 ? getChildAt(0) : null;
    336         }
    337 
    338         @Override
    339         public View getChildContentView(View v) {
    340             return v.findViewById(R.id.call_log_list_item);
    341         }
    342 
    343         @Override
    344         public void onScroll() {}
    345 
    346         @Override
    347         public boolean canChildBeDismissed(View v) {
    348             return true;
    349         }
    350 
    351         @Override
    352         public void onBeginDrag(View v) {
    353             // We do this so the underlying ScrollView knows that it won't get
    354             // the chance to intercept events anymore
    355             requestDisallowInterceptTouchEvent(true);
    356         }
    357 
    358         @Override
    359         public void onChildDismissed(View v) {
    360             if (v != null && mOnItemSwipeListener != null) {
    361                 mOnItemSwipeListener.onSwipe(v);
    362             }
    363         }
    364 
    365         @Override
    366         public void onDragCancelled(View v) {}
    367 
    368         @Override
    369         public boolean onInterceptTouchEvent(MotionEvent ev) {
    370             if (mSwipeHelper != null) {
    371                 return mSwipeHelper.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev);
    372             } else {
    373                 return super.onInterceptTouchEvent(ev);
    374             }
    375         }
    376 
    377         @Override
    378         public boolean onTouchEvent(MotionEvent ev) {
    379             if (mSwipeHelper != null) {
    380                 return mSwipeHelper.onTouchEvent(ev) || super.onTouchEvent(ev);
    381             } else {
    382                 return super.onTouchEvent(ev);
    383             }
    384         }
    385 
    386         public void setOnItemSwipeListener(OnItemGestureListener listener) {
    387             mOnItemSwipeListener = listener;
    388         }
    389     }
    390 }
    391