/* * Copyright (C) 2011 Google Inc. * Licensed to 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.list; import android.content.Context; import android.content.res.Resources; import android.database.Cursor; import android.database.DataSetObserver; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.FrameLayout; import com.android.dialer.R; import com.android.dialer.calllog.CallLogAdapter; import com.android.dialer.calllog.CallLogNotificationsHelper; import com.android.dialer.calllog.CallLogQueryHandler; import com.android.dialer.list.SwipeHelper.OnItemGestureListener; import com.android.dialer.list.SwipeHelper.SwipeHelperCallback; /** * An adapter that combines items from {@link com.android.contacts.common.list.ContactTileAdapter} * and {@link com.android.dialer.calllog.CallLogAdapter} into a single list. */ public class PhoneFavoriteMergedAdapter extends BaseAdapter { private class CustomDataSetObserver extends DataSetObserver { @Override public void onChanged() { notifyDataSetChanged(); } } private static final String TAG = PhoneFavoriteMergedAdapter.class.getSimpleName(); private static final int TILE_INTERACTION_TEASER_VIEW_POSITION = 2; private static final int TILE_INTERACTION_TEASER_VIEW_ID = -2; private static final int ALL_CONTACTS_BUTTON_ITEM_ID = -1; private final PhoneFavoritesTileAdapter mContactTileAdapter; private final CallLogAdapter mCallLogAdapter; private final View mShowAllContactsButton; private final PhoneFavoriteFragment mFragment; private final TileInteractionTeaserView mTileInteractionTeaserView; private final int mCallLogPadding; private final Context mContext; private final DataSetObserver mObserver; private final CallLogQueryHandler mCallLogQueryHandler; private final OnItemGestureListener mCallLogOnItemSwipeListener = new OnItemGestureListener() { @Override public void onSwipe(View view) { mCallLogQueryHandler.markNewCallsAsOld(); mCallLogQueryHandler.markNewVoicemailsAsOld(); CallLogNotificationsHelper.removeMissedCallNotifications(); CallLogNotificationsHelper.updateVoicemailNotifications(mContext); mFragment.dismissShortcut(((View) view.getParent()).getHeight()); } @Override public void onTouch() {} @Override public boolean isSwipeEnabled() { return true; } }; private final CallLogQueryHandler.Listener mCallLogQueryHandlerListener = new CallLogQueryHandler.Listener() { @Override public void onVoicemailStatusFetched(Cursor statusCursor) {} @Override public void onCallsFetched(Cursor combinedCursor) { mCallLogAdapter.invalidateCache(); mCallLogAdapter.changeCursor(combinedCursor); mCallLogAdapter.notifyDataSetChanged(); } }; public PhoneFavoriteMergedAdapter(Context context, PhoneFavoriteFragment fragment, PhoneFavoritesTileAdapter contactTileAdapter, CallLogAdapter callLogAdapter, View showAllContactsButton, TileInteractionTeaserView tileInteractionTeaserView) { final Resources resources = context.getResources(); mContext = context; mFragment = fragment; mCallLogPadding = resources.getDimensionPixelSize(R.dimen.recent_call_log_item_padding); mContactTileAdapter = contactTileAdapter; mCallLogAdapter = callLogAdapter; mObserver = new CustomDataSetObserver(); mCallLogAdapter.registerDataSetObserver(mObserver); mContactTileAdapter.registerDataSetObserver(mObserver); mShowAllContactsButton = showAllContactsButton; mTileInteractionTeaserView = tileInteractionTeaserView; mCallLogQueryHandler = new CallLogQueryHandler(mContext.getContentResolver(), mCallLogQueryHandlerListener); } @Override public int getCount() { if (mContactTileAdapter.getCount() > 0) { return mContactTileAdapter.getCount() + mCallLogAdapter.getCount() + 1 + getTeaserViewCount(); } else { return mCallLogAdapter.getCount(); } } @Override public Object getItem(int position) { final int callLogAdapterCount = mCallLogAdapter.getCount(); if (callLogAdapterCount > 0) { if (position < callLogAdapterCount) { return mCallLogAdapter.getItem(position); } } // Set position to the position of the actual favorite contact in the favorites adapter position = getAdjustedFavoritePosition(position, callLogAdapterCount); return mContactTileAdapter.getItem(position); } /** * In order to ensure that items have stable ids (for animation purposes), we need to * guarantee that every single item has a unique ID, even across data set changes. * * These are the ranges of IDs reserved for each item type. * * -(N + 1) to -3: CallLogAdapterItems, where N is equal to the number of call log items * -2: Teaser * -1: All contacts button * 0 to (N -1): Rows of tiled contacts, where N is equal to the max rows of tiled contacts * N to infinity: Rows of regular contacts. Their item id is calculated by N + contact_id, * where contact_id is guaranteed to never be negative. */ @Override public long getItemId(int position) { final int callLogAdapterCount = mCallLogAdapter.getCount(); if (position < callLogAdapterCount) { // Call log items are not animated, so reusing their position for IDs is fine. return ALL_CONTACTS_BUTTON_ITEM_ID - 2 - position; } else if (position == TILE_INTERACTION_TEASER_VIEW_POSITION + callLogAdapterCount && mTileInteractionTeaserView.getShouldDisplayInList()){ return TILE_INTERACTION_TEASER_VIEW_ID; } else if (position < (callLogAdapterCount + mContactTileAdapter.getCount() + getTeaserViewCount())) { return mContactTileAdapter.getItemId( getAdjustedFavoritePosition(position, callLogAdapterCount)); } else { // All contacts button return ALL_CONTACTS_BUTTON_ITEM_ID; } } @Override public boolean hasStableIds() { return true; } @Override public int getViewTypeCount() { return (mContactTileAdapter.getViewTypeCount() + /* Favorite and frequent */ mCallLogAdapter.getViewTypeCount() + /* Recent call log */ getTeaserViewCount() + /* Teaser */ 1); /* Show all contacts button. */ } @Override public int getItemViewType(int position) { final int callLogAdapterCount = mCallLogAdapter.getCount(); if (position < callLogAdapterCount) { // View type of the call log adapter is the last view type of the contact tile adapter // + 1 return mContactTileAdapter.getViewTypeCount(); } else if (position == TILE_INTERACTION_TEASER_VIEW_POSITION + callLogAdapterCount && mTileInteractionTeaserView.getShouldDisplayInList()) { // View type of the teaser row is the last view type of the contact tile adapter + 3 return mContactTileAdapter.getViewTypeCount() + 2; } else if (position < getCount() - 1) { return mContactTileAdapter.getItemViewType( getAdjustedFavoritePosition(position, callLogAdapterCount)); } else { // View type of the show all contact button is the last view type of the contact tile // adapter + 2 return mContactTileAdapter.getViewTypeCount() + 1; } } @Override public View getView(int position, View convertView, ViewGroup parent) { final int callLogAdapterCount = mCallLogAdapter.getCount(); if ((position == getCount() - 1) && (mContactTileAdapter.getCount() > 0)) { return mShowAllContactsButton; } if (mTileInteractionTeaserView.getShouldDisplayInList()) { if (position == TILE_INTERACTION_TEASER_VIEW_POSITION + callLogAdapterCount) { return mTileInteractionTeaserView; } } if (callLogAdapterCount > 0) { if (position == 0) { final SwipeableCallLogRow wrapper; if (convertView == null) { wrapper = new SwipeableCallLogRow(mContext); wrapper.setOnItemSwipeListener(mCallLogOnItemSwipeListener); } else { wrapper = (SwipeableCallLogRow) convertView; } // Special case wrapper view for the most recent call log item. This allows // us to create a card-like effect for the more recent call log item in // the PhoneFavoriteMergedAdapter, but keep the original look of the item in // the CallLogAdapter. final View view = mCallLogAdapter.getView(position, convertView == null ? null : wrapper.getChildAt(0), parent); wrapper.removeAllViews(); final View callLogItem = view.findViewById(R.id.call_log_list_item); // Reset the internal call log item view if it is being recycled callLogItem.setTranslationX(0); callLogItem.setAlpha(1); wrapper.addView(view); return wrapper; } } // Set position to the position of the actual favorite contact in the // favorites adapter position = getAdjustedFavoritePosition(position, callLogAdapterCount); // Favorites section final View view = mContactTileAdapter.getView(position, convertView, parent); if (position >= mContactTileAdapter.getMaxTiledRows()) { final FrameLayout frameLayout = (FrameLayout) view; final View child = frameLayout.getChildAt(0); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); child.setLayoutParams(params); } return view; } @Override public boolean areAllItemsEnabled() { return mCallLogAdapter.areAllItemsEnabled() && mContactTileAdapter.areAllItemsEnabled(); } @Override public boolean isEnabled(int position) { final int callLogAdapterCount = mCallLogAdapter.getCount(); if (position < callLogAdapterCount) { return mCallLogAdapter.isEnabled(position); } else { // For favorites section return mContactTileAdapter.isEnabled( getAdjustedFavoritePosition(position, callLogAdapterCount)); } } private int getAdjustedFavoritePosition(int position, int callLogAdapterCount) { if (position - callLogAdapterCount > TILE_INTERACTION_TEASER_VIEW_POSITION && mTileInteractionTeaserView.getShouldDisplayInList()) { return position - callLogAdapterCount - 1; } else { return position - callLogAdapterCount; } } private int getTeaserViewCount() { return (mContactTileAdapter.getCount() > TILE_INTERACTION_TEASER_VIEW_POSITION && mTileInteractionTeaserView.getShouldDisplayInList() ? 1 : 0); } /** * The swipeable call log row. * See also {@link PhoneFavoritesTileAdapter.ContactTileRow}. */ private class SwipeableCallLogRow extends FrameLayout implements SwipeHelperCallback { private SwipeHelper mSwipeHelper; private OnItemGestureListener mOnItemSwipeListener; public SwipeableCallLogRow(Context context) { super(context); final float densityScale = getResources().getDisplayMetrics().density; final float pagingTouchSlop = ViewConfiguration.get(context) .getScaledPagingTouchSlop(); mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale, pagingTouchSlop); } @Override public void addView(View view) { view.setBackgroundResource(R.drawable.dialer_recent_card_bg); final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); params.setMarginsRelative(mCallLogPadding, mCallLogPadding, mCallLogPadding, mCallLogPadding); view.setLayoutParams(params); super.addView(view); } @Override public View getChildAtPosition(MotionEvent ev) { return getChildCount() > 0 ? getChildAt(0) : null; } @Override public View getChildContentView(View v) { return v.findViewById(R.id.call_log_list_item); } @Override public void onScroll() {} @Override public boolean canChildBeDismissed(View v) { return true; } @Override public void onBeginDrag(View v) { // We do this so the underlying ScrollView knows that it won't get // the chance to intercept events anymore requestDisallowInterceptTouchEvent(true); } @Override public void onChildDismissed(View v) { if (v != null && mOnItemSwipeListener != null) { mOnItemSwipeListener.onSwipe(v); } } @Override public void onDragCancelled(View v) {} @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (mSwipeHelper != null) { return mSwipeHelper.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev); } else { return super.onInterceptTouchEvent(ev); } } @Override public boolean onTouchEvent(MotionEvent ev) { if (mSwipeHelper != null) { return mSwipeHelper.onTouchEvent(ev) || super.onTouchEvent(ev); } else { return super.onTouchEvent(ev); } } public void setOnItemSwipeListener(OnItemGestureListener listener) { mOnItemSwipeListener = listener; } } }