diff options
Diffstat (limited to 'src/com/android/dialer/list/PhoneFavoritesTileAdapter.java')
-rw-r--r-- | src/com/android/dialer/list/PhoneFavoritesTileAdapter.java | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java new file mode 100644 index 000000000..36fc34608 --- /dev/null +++ b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java @@ -0,0 +1,515 @@ +/* + * 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.list; + +import android.content.ContentUris; +import android.content.Context; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Contacts; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; + +import com.android.contacts.common.ContactPhotoManager; +import com.android.contacts.common.ContactTileLoaderFactory; +import com.android.contacts.common.R; +import com.android.contacts.common.list.ContactEntry; +import com.android.contacts.common.list.ContactTileAdapter; +import com.android.contacts.common.list.ContactTileView; + +import java.util.ArrayList; + +/** + * Also allows for a configurable number of columns as well as a maximum row of tiled contacts. + * + * This adapter has been rewritten to only support a maximum of one row for favorites. + * + * TODO Krelease: Move to PhoneContactTileAdapter in Dialer package. + */ +public class PhoneFavoritesTileAdapter extends BaseAdapter { + private static final String TAG = ContactTileAdapter.class.getSimpleName(); + + public static final int NO_ROW_LIMIT = -1; + + private ContactTileView.Listener mListener; + private Context mContext; + private Resources mResources; + protected Cursor mContactCursor = null; + private ContactPhotoManager mPhotoManager; + protected int mNumFrequents; + + /** + * Index of the first NON starred contact in the {@link Cursor} + * Only valid when {@link DisplayType#STREQUENT} is true + */ + private int mDividerPosition; + protected int mColumnCount; + private int mMaxTiledRows = NO_ROW_LIMIT; + private int mStarredIndex; + + protected int mIdIndex; + protected int mLookupIndex; + protected int mPhotoUriIndex; + protected int mNameIndex; + protected int mPresenceIndex; + protected int mStatusIndex; + + /** + * Only valid when {@link DisplayType#STREQUENT_PHONE_ONLY} is true + */ + private int mPhoneNumberIndex; + private int mPhoneNumberTypeIndex; + private int mPhoneNumberLabelIndex; + + private boolean mIsQuickContactEnabled = false; + private final int mPaddingInPixels; + + public PhoneFavoritesTileAdapter(Context context, ContactTileView.Listener listener, int numCols) { + this(context, listener, numCols, NO_ROW_LIMIT); + } + + public PhoneFavoritesTileAdapter(Context context, ContactTileView.Listener listener, int numCols, + int maxTiledRows) { + mListener = listener; + mContext = context; + mResources = context.getResources(); + mColumnCount = numCols; + mNumFrequents = 0; + mMaxTiledRows = maxTiledRows; + + // Converting padding in dips to padding in pixels + mPaddingInPixels = mContext.getResources() + .getDimensionPixelSize(R.dimen.contact_tile_divider_padding); + bindColumnIndices(); + } + + public void setPhotoLoader(ContactPhotoManager photoLoader) { + mPhotoManager = photoLoader; + } + + public void setMaxRowCount(int maxRows) { + mMaxTiledRows = maxRows; + } + + public void setColumnCount(int columnCount) { + mColumnCount = columnCount; + } + + public void enableQuickContact(boolean enableQuickContact) { + mIsQuickContactEnabled = enableQuickContact; + } + + /** + * Sets the column indices for expected {@link Cursor} + * based on {@link DisplayType}. + */ + protected void bindColumnIndices() { + mIdIndex = ContactTileLoaderFactory.CONTACT_ID; + mLookupIndex = ContactTileLoaderFactory.LOOKUP_KEY; + mPhotoUriIndex = ContactTileLoaderFactory.PHOTO_URI; + mNameIndex = ContactTileLoaderFactory.DISPLAY_NAME; + mStarredIndex = ContactTileLoaderFactory.STARRED; + mPresenceIndex = ContactTileLoaderFactory.CONTACT_PRESENCE; + mStatusIndex = ContactTileLoaderFactory.CONTACT_STATUS; + + mPhoneNumberIndex = ContactTileLoaderFactory.PHONE_NUMBER; + mPhoneNumberTypeIndex = ContactTileLoaderFactory.PHONE_NUMBER_TYPE; + mPhoneNumberLabelIndex = ContactTileLoaderFactory.PHONE_NUMBER_LABEL; + } + + /** + * Gets the number of frequents from the passed in cursor. + * + * This methods is needed so the GroupMemberTileAdapter can override this. + * + * @param cursor The cursor to get number of frequents from. + */ + protected void saveNumFrequentsFromCursor(Cursor cursor) { + mNumFrequents = cursor.getCount() - mDividerPosition; + } + + /** + * Creates {@link ContactTileView}s for each item in {@link Cursor}. + * + * Else use {@link ContactTileLoaderFactory} + */ + public void setContactCursor(Cursor cursor) { + mContactCursor = cursor; + mDividerPosition = getDividerPosition(cursor); + + saveNumFrequentsFromCursor(cursor); + + // cause a refresh of any views that rely on this data + notifyDataSetChanged(); + } + + /** + * Iterates over the {@link Cursor} + * Returns position of the first NON Starred Contact + * Returns -1 if {@link DisplayType#STARRED_ONLY} + * Returns 0 if {@link DisplayType#FREQUENT_ONLY} + */ + protected int getDividerPosition(Cursor cursor) { + if (cursor == null || cursor.isClosed()) { + throw new IllegalStateException("Unable to access cursor"); + } + + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + if (cursor.getInt(mStarredIndex) == 0) { + return cursor.getPosition(); + } + } + + // There are not NON Starred contacts in cursor + // Set divider positon to end + return cursor.getCount(); + } + + protected ContactEntry createContactEntryFromCursor(Cursor cursor, int position) { + // If the loader was canceled we will be given a null cursor. + // In that case, show an empty list of contacts. + if (cursor == null || cursor.isClosed() || cursor.getCount() <= position) return null; + + cursor.moveToPosition(position); + long id = cursor.getLong(mIdIndex); + String photoUri = cursor.getString(mPhotoUriIndex); + String lookupKey = cursor.getString(mLookupIndex); + + ContactEntry contact = new ContactEntry(); + String name = cursor.getString(mNameIndex); + contact.name = (name != null) ? name : mResources.getString(R.string.missing_name); + contact.status = cursor.getString(mStatusIndex); + contact.photoUri = (photoUri != null ? Uri.parse(photoUri) : null); + contact.lookupKey = ContentUris.withAppendedId( + Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), id); + + // Set phone number and label + int phoneNumberType = cursor.getInt(mPhoneNumberTypeIndex); + String phoneNumberCustomLabel = cursor.getString(mPhoneNumberLabelIndex); + contact.phoneLabel = (String) Phone.getTypeLabel(mResources, phoneNumberType, + phoneNumberCustomLabel); + contact.phoneNumber = cursor.getString(mPhoneNumberIndex); + + return contact; + } + + /** + * Returns the number of frequents that will be displayed in the list. + */ + public int getNumFrequents() { + return mNumFrequents; + } + + @Override + public int getCount() { + if (mContactCursor == null || mContactCursor.isClosed()) { + return 0; + } + + // Takes numbers of rows the Starred Contacts Occupy + int starredRowCount = getRowCount(mDividerPosition) + + (mMaxTiledRows == NO_ROW_LIMIT ? 0 : Math.max(0, mDividerPosition - + mMaxTiledRows * mColumnCount)); + + // Compute the frequent row count which is 1 plus the number of frequents + // (to account for the divider) or 0 if there are no frequents. + int frequentRowCount = mNumFrequents == 0 ? 0 : mNumFrequents; + + // Return the number of starred plus frequent rows + return starredRowCount + frequentRowCount; + } + + /** + * Returns the number of rows required to show the provided number of entries + * with the current number of columns. + */ + protected int getRowCount(int entryCount) { + if (entryCount == 0) return 0; + final int nonLimitedRows = ((entryCount - 1) / mColumnCount) + 1; + return mMaxTiledRows == NO_ROW_LIMIT ? nonLimitedRows : Math.min(mMaxTiledRows, + nonLimitedRows); + } + + public int getColumnCount() { + return mColumnCount; + } + + /** + * Returns an ArrayList of the {@link ContactEntry}s that are to appear + * on the row for the given position. + */ + @Override + public ArrayList<ContactEntry> getItem(int position) { + ArrayList<ContactEntry> resultList = new ArrayList<ContactEntry>(mColumnCount); + int contactIndex = position * mColumnCount; + if (position < getRowCount(mDividerPosition)) { + for (int columnCounter = 0; columnCounter < mColumnCount && + contactIndex != mDividerPosition; columnCounter++) { + resultList.add(createContactEntryFromCursor(mContactCursor, contactIndex)); + contactIndex++; + } + } else { + /* + * Current position minus how many rows are before the divider and Minus 1 for the + * divider itself provides the relative index of the frequent contact being displayed. + * Then add the dividerPostion to give the offset into the contacts cursor to get the + * absolute index. + */ + final int rowCount = getRowCount(mDividerPosition); + contactIndex = position - rowCount + Math.min(mDividerPosition, + rowCount * mColumnCount); + resultList.add(createContactEntryFromCursor(mContactCursor, contactIndex)); + } + return resultList; + } + + @Override + public long getItemId(int position) { + // As we show several selectable items for each ListView row, + // we can not determine a stable id. But as we don't rely on ListView's selection, + // this should not be a problem. + return position; + } + + @Override + public boolean areAllItemsEnabled() { + // No dividers, so all items are enabled. + return true; + } + + @Override + public boolean isEnabled(int position) { + return position != getRowCount(mDividerPosition); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + int itemViewType = getItemViewType(position); + + ContactTileRow contactTileRowView = (ContactTileRow) convertView; + ArrayList<ContactEntry> contactList = getItem(position); + + if (contactTileRowView == null) { + // Creating new row if needed + contactTileRowView = new ContactTileRow(mContext, itemViewType); + } + + contactTileRowView.configureRow(contactList, position == getCount() - 1); + return contactTileRowView; + } + + private int getLayoutResourceId(int viewType) { + switch (viewType) { + case ViewTypes.FREQUENT: + return R.layout.phone_favorite_regular_row_view; + case ViewTypes.STARRED_PHONE: + return R.layout.phone_favorite_tile_view; + default: + throw new IllegalArgumentException("Unrecognized viewType " + viewType); + } + } + @Override + public int getViewTypeCount() { + return ViewTypes.COUNT; + } + + @Override + public int getItemViewType(int position) { + if (position < getRowCount(mDividerPosition)) { + return ViewTypes.STARRED_PHONE; + } else { + return ViewTypes.FREQUENT; + } + } + + /** + * Returns the "frequent header" position. Only available when STREQUENT or + * STREQUENT_PHONE_ONLY is used for its display type. + */ + public int getFrequentHeaderPosition() { + return getRowCount(mDividerPosition); + } + + /** + * Acts as a row item composed of {@link ContactTileView} + * + * TODO: FREQUENT doesn't really need it. Just let {@link #getView} return + */ + private class ContactTileRow extends FrameLayout { + private int mItemViewType; + private int mLayoutResId; + + public ContactTileRow(Context context, int itemViewType) { + super(context); + mItemViewType = itemViewType; + mLayoutResId = getLayoutResourceId(mItemViewType); + + // Remove row (but not children) from accessibility node tree. + setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + + /** + * Configures the row to add {@link ContactEntry}s information to the views + */ + public void configureRow(ArrayList<ContactEntry> list, boolean isLastRow) { + int columnCount = mItemViewType == ViewTypes.FREQUENT ? 1 : mColumnCount; + + // Adding tiles to row and filling in contact information + for (int columnCounter = 0; columnCounter < columnCount; columnCounter++) { + ContactEntry entry = + columnCounter < list.size() ? list.get(columnCounter) : null; + addTileFromEntry(entry, columnCounter, isLastRow); + } + } + + private void addTileFromEntry(ContactEntry entry, int childIndex, boolean isLastRow) { + final ContactTileView contactTile; + + if (getChildCount() <= childIndex) { + + contactTile = (ContactTileView) inflate(mContext, mLayoutResId, null); + // Note: the layoutparam set here is only actually used for FREQUENT. + // We override onMeasure() for STARRED and we don't care the layout param there. + Resources resources = mContext.getResources(); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + + params.setMargins( + resources.getDimensionPixelSize(R.dimen.detail_item_side_margin), + 0, + resources.getDimensionPixelSize(R.dimen.detail_item_side_margin), + 0); + contactTile.setLayoutParams(params); + contactTile.setPhotoManager(mPhotoManager); + contactTile.setListener(mListener); + addView(contactTile); + } else { + contactTile = (ContactTileView) getChildAt(childIndex); + } + contactTile.loadFromContact(entry); + switch (mItemViewType) { + case ViewTypes.STARRED_PHONE: + // Setting divider visibilities + contactTile.setPaddingRelative(0, 0, + childIndex >= mColumnCount - 1 ? 0 : mPaddingInPixels, + isLastRow ? 0 : mPaddingInPixels); + break; + case ViewTypes.FREQUENT: + contactTile.setHorizontalDividerVisibility( + isLastRow ? View.GONE : View.VISIBLE); + break; + default: + break; + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + switch (mItemViewType) { + case ViewTypes.STARRED_PHONE: + onLayoutForTiles(); + return; + default: + super.onLayout(changed, left, top, right, bottom); + return; + } + } + + private void onLayoutForTiles() { + final int count = getChildCount(); + + // Just line up children horizontally. + int childLeft = 0; + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + + // Note MeasuredWidth includes the padding. + final int childWidth = child.getMeasuredWidth(); + child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight()); + childLeft += childWidth; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + switch (mItemViewType) { + case ViewTypes.STARRED_PHONE: + case ViewTypes.STARRED: + onMeasureForTiles(widthMeasureSpec); + return; + default: + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + } + + private void onMeasureForTiles(int widthMeasureSpec) { + final int width = MeasureSpec.getSize(widthMeasureSpec); + + final int childCount = getChildCount(); + if (childCount == 0) { + // Just in case... + setMeasuredDimension(width, 0); + return; + } + + // 1. Calculate image size. + // = ([total width] - [total padding]) / [child count] + // + // 2. Set it to width/height of each children. + // If we have a remainder, some tiles will have 1 pixel larger width than its height. + // + // 3. Set the dimensions of itself. + // Let width = given width. + // Let height = image size + bottom paddding. + + final int totalPaddingsInPixels = (mColumnCount - 1) * mPaddingInPixels; + + // Preferred width / height for images (excluding the padding). + // The actual width may be 1 pixel larger than this if we have a remainder. + final int imageSize = (width - totalPaddingsInPixels) / mColumnCount; + final int remainder = width - (imageSize * mColumnCount) - totalPaddingsInPixels; + + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final int childWidth = imageSize + child.getPaddingRight() + // Compensate for the remainder + + (i < remainder ? 1 : 0); + final int childHeight = imageSize + child.getPaddingBottom(); + child.measure( + MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY) + ); + } + setMeasuredDimension(width, imageSize + getChildAt(0).getPaddingBottom()); + } + } + + protected static class ViewTypes { + public static final int COUNT = 3; + public static final int STARRED = 0; + public static final int FREQUENT = 1; + public static final int STARRED_PHONE = 2; + } +} |