| /* |
| * 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.animation.Animator; |
| import android.animation.AnimatorSet; |
| import android.animation.ObjectAnimator; |
| import android.app.Activity; |
| import android.app.Fragment; |
| import android.app.LoaderManager; |
| import android.content.Context; |
| import android.content.CursorLoader; |
| import android.content.Loader; |
| import android.content.SharedPreferences; |
| import android.database.Cursor; |
| import android.graphics.Rect; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.CallLog; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.view.ViewGroup; |
| import android.view.ViewTreeObserver; |
| import android.widget.AbsListView; |
| import android.widget.AdapterView; |
| import android.widget.AdapterView.OnItemClickListener; |
| import android.widget.Button; |
| import android.widget.ImageView; |
| import android.widget.ListView; |
| import android.widget.RelativeLayout; |
| import android.widget.RelativeLayout.LayoutParams; |
| |
| import com.android.contacts.common.ContactPhotoManager; |
| import com.android.contacts.common.ContactTileLoaderFactory; |
| import com.android.contacts.common.GeoUtil; |
| import com.android.contacts.common.list.ContactEntry; |
| import com.android.contacts.common.list.ContactListItemView; |
| import com.android.contacts.common.list.ContactTileView; |
| import com.android.dialer.DialtactsActivity; |
| import com.android.dialer.R; |
| import com.android.dialer.calllog.CallLogAdapter; |
| import com.android.dialer.calllog.CallLogQuery; |
| import com.android.dialer.calllog.CallLogQueryHandler; |
| import com.android.dialer.calllog.ContactInfoHelper; |
| import com.android.dialer.list.PhoneFavoritesTileAdapter.ContactTileRow; |
| import com.android.dialerbind.ObjectFactory; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| |
| /** |
| * Fragment for Phone UI's favorite screen. |
| * |
| * This fragment contains three kinds of contacts in one screen: "starred", "frequent", and "all" |
| * contacts. To show them at once, this merges results from {@link com.android.contacts.common.list.ContactTileAdapter} and |
| * {@link com.android.contacts.common.list.PhoneNumberListAdapter} into one unified list using {@link PhoneFavoriteMergedAdapter}. |
| * A contact filter header is also inserted between those adapters' results. |
| */ |
| public class PhoneFavoriteFragment extends Fragment implements OnItemClickListener, |
| CallLogQueryHandler.Listener, CallLogAdapter.CallFetcher, |
| PhoneFavoritesTileAdapter.OnDataSetChangedForAnimationListener { |
| |
| /** |
| * By default, the animation code assumes that all items in a list view are of the same height |
| * when animating new list items into view (e.g. from the bottom of the screen into view). |
| * This can cause incorrect translation offsets when a item that is larger or smaller than |
| * other list item is removed from the list. This key is used to provide the actual height |
| * of the removed object so that the actual translation appears correct to the user. |
| */ |
| private static final long KEY_REMOVED_ITEM_HEIGHT = Long.MAX_VALUE; |
| |
| private static final String TAG = PhoneFavoriteFragment.class.getSimpleName(); |
| private static final boolean DEBUG = false; |
| |
| private int mAnimationDuration; |
| |
| /** |
| * Used with LoaderManager. |
| */ |
| private static int LOADER_ID_CONTACT_TILE = 1; |
| private static int MISSED_CALL_LOADER = 2; |
| |
| private static final String KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE = |
| "key_last_dismissed_call_shortcut_date"; |
| |
| public interface OnShowAllContactsListener { |
| public void onShowAllContacts(); |
| } |
| |
| public interface Listener { |
| public void onContactSelected(Uri contactUri); |
| public void onCallNumberDirectly(String phoneNumber); |
| } |
| |
| public interface HostInterface { |
| public void setDragDropController(DragDropController controller); |
| } |
| |
| private class MissedCallLogLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> { |
| |
| @Override |
| public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
| final Uri uri = CallLog.Calls.CONTENT_URI; |
| final String[] projection = new String[] {CallLog.Calls.TYPE}; |
| final String selection = CallLog.Calls.TYPE + " = " + CallLog.Calls.MISSED_TYPE + |
| " AND " + CallLog.Calls.IS_READ + " = 0"; |
| return new CursorLoader(getActivity(), uri, projection, selection, null, null); |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor data) { |
| mCallLogAdapter.setMissedCalls(data); |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<Cursor> cursorLoader) { |
| } |
| } |
| |
| private class ContactTileLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> { |
| @Override |
| public CursorLoader onCreateLoader(int id, Bundle args) { |
| if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onCreateLoader."); |
| return ContactTileLoaderFactory.createStrequentPhoneOnlyLoader(getActivity()); |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<Cursor> loader, Cursor data) { |
| if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoadFinished"); |
| mContactTileAdapter.setContactCursor(data); |
| setEmptyViewVisibility(mContactTileAdapter.getCount() == 0); |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<Cursor> loader) { |
| if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoaderReset. "); |
| } |
| } |
| |
| private class ContactTileAdapterListener implements ContactTileView.Listener { |
| @Override |
| public void onContactSelected(Uri contactUri, Rect targetRect) { |
| if (mListener != null) { |
| mListener.onContactSelected(contactUri); |
| } |
| } |
| |
| @Override |
| public void onCallNumberDirectly(String phoneNumber) { |
| if (mListener != null) { |
| mListener.onCallNumberDirectly(phoneNumber); |
| } |
| } |
| |
| @Override |
| public int getApproximateTileWidth() { |
| return getView().getWidth() / mContactTileAdapter.getColumnCount(); |
| } |
| } |
| |
| private class ScrollListener implements ListView.OnScrollListener { |
| @Override |
| public void onScroll(AbsListView view, |
| int firstVisibleItem, int visibleItemCount, int totalItemCount) { |
| } |
| |
| @Override |
| public void onScrollStateChanged(AbsListView view, int scrollState) { |
| mActivityScrollListener.onListFragmentScrollStateChange(scrollState); |
| } |
| } |
| |
| private Listener mListener; |
| |
| private OnListFragmentScrolledListener mActivityScrollListener; |
| private OnShowAllContactsListener mShowAllContactsListener; |
| private PhoneFavoriteMergedAdapter mAdapter; |
| private PhoneFavoritesTileAdapter mContactTileAdapter; |
| |
| private CallLogAdapter mCallLogAdapter; |
| private CallLogQueryHandler mCallLogQueryHandler; |
| |
| private View mParentView; |
| |
| private PhoneFavoriteListView mListView; |
| |
| private View mPhoneFavoritesMenu; |
| private View mContactTileFrame; |
| |
| private TileInteractionTeaserView mTileInteractionTeaserView; |
| |
| private final HashMap<Long, Integer> mItemIdTopMap = new HashMap<Long, Integer>(); |
| private final HashMap<Long, Integer> mItemIdLeftMap = new HashMap<Long, Integer>(); |
| |
| /** |
| * Layout used when there are no favorites. |
| */ |
| private View mEmptyView; |
| |
| /** |
| * Call shortcuts older than this date (persisted in shared preferences) will not show up in |
| * at the top of the screen |
| */ |
| private long mLastCallShortcutDate = 0; |
| |
| /** |
| * The date of the current call shortcut that is showing on screen. |
| */ |
| private long mCurrentCallShortcutDate = 0; |
| |
| private final ContactTileView.Listener mContactTileAdapterListener = |
| new ContactTileAdapterListener(); |
| private final LoaderManager.LoaderCallbacks<Cursor> mContactTileLoaderListener = |
| new ContactTileLoaderListener(); |
| private final ScrollListener mScrollListener = new ScrollListener(); |
| |
| @Override |
| public void onAttach(Activity activity) { |
| if (DEBUG) Log.d(TAG, "onAttach()"); |
| super.onAttach(activity); |
| |
| // Construct two base adapters which will become part of PhoneFavoriteMergedAdapter. |
| // We don't construct the resultant adapter at this moment since it requires LayoutInflater |
| // that will be available on onCreateView(). |
| mContactTileAdapter = new PhoneFavoritesTileAdapter(activity, mContactTileAdapterListener, |
| this, |
| getResources().getInteger(R.integer.contact_tile_column_count_in_favorites), |
| PhoneFavoritesTileAdapter.NO_ROW_LIMIT); |
| mContactTileAdapter.setPhotoLoader(ContactPhotoManager.getInstance(activity)); |
| } |
| |
| @Override |
| public void onCreate(Bundle savedState) { |
| if (DEBUG) Log.d(TAG, "onCreate()"); |
| super.onCreate(savedState); |
| |
| mAnimationDuration = getResources().getInteger(R.integer.fade_duration); |
| mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), |
| this, 1); |
| final String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); |
| mCallLogAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this, |
| new ContactInfoHelper(getActivity(), currentCountryIso), false, false); |
| setHasOptionsMenu(true); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| final SharedPreferences prefs = getActivity().getSharedPreferences( |
| DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE); |
| |
| mLastCallShortcutDate = prefs.getLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, 0); |
| |
| fetchCalls(); |
| mCallLogAdapter.setLoading(true); |
| getLoaderManager().getLoader(LOADER_ID_CONTACT_TILE).forceLoad(); |
| } |
| |
| @Override |
| public View onCreateView(LayoutInflater inflater, ViewGroup container, |
| Bundle savedInstanceState) { |
| mParentView = inflater.inflate(R.layout.phone_favorites_fragment, container, false); |
| |
| mListView = (PhoneFavoriteListView) mParentView.findViewById(R.id.contact_tile_list); |
| mListView.setItemsCanFocus(true); |
| mListView.setOnItemClickListener(this); |
| mListView.setVerticalScrollBarEnabled(false); |
| mListView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT); |
| mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY); |
| mListView.setOnItemSwipeListener(mContactTileAdapter); |
| mListView.getDragDropController().addOnDragDropListener(mContactTileAdapter); |
| |
| final ImageView dragShadowOverlay = |
| (ImageView) mParentView.findViewById(R.id.contact_tile_drag_shadow_overlay); |
| mListView.setDragShadowOverlay(dragShadowOverlay); |
| |
| mEmptyView = mParentView.findViewById(R.id.phone_no_favorites_view); |
| |
| mPhoneFavoritesMenu = inflater.inflate(R.layout.phone_favorites_menu, mListView, false); |
| prepareFavoritesMenu(mPhoneFavoritesMenu); |
| |
| mContactTileFrame = mParentView.findViewById(R.id.contact_tile_frame); |
| |
| mTileInteractionTeaserView = (TileInteractionTeaserView) inflater.inflate( |
| R.layout.tile_interactions_teaser_view, mListView, false); |
| |
| mAdapter = new PhoneFavoriteMergedAdapter(getActivity(), this, mContactTileAdapter, |
| mCallLogAdapter, mPhoneFavoritesMenu, mTileInteractionTeaserView); |
| |
| mTileInteractionTeaserView.setAdapter(mAdapter); |
| |
| mListView.setAdapter(mAdapter); |
| |
| mListView.setOnScrollListener(mScrollListener); |
| mListView.setFastScrollEnabled(false); |
| mListView.setFastScrollAlwaysVisible(false); |
| |
| return mParentView; |
| } |
| |
| public boolean hasFrequents() { |
| if (mContactTileAdapter == null) return false; |
| return mContactTileAdapter.getNumFrequents() > 0; |
| } |
| |
| /* package */ void setEmptyViewVisibility(final boolean visible) { |
| final int previousVisibility = mEmptyView.getVisibility(); |
| final int newVisibility = visible ? View.VISIBLE : View.GONE; |
| |
| if (previousVisibility != newVisibility) { |
| final RelativeLayout.LayoutParams params = (LayoutParams) mContactTileFrame |
| .getLayoutParams(); |
| params.height = visible ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT; |
| mContactTileFrame.setLayoutParams(params); |
| mEmptyView.setVisibility(newVisibility); |
| } |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| |
| final Activity activity = getActivity(); |
| |
| try { |
| mActivityScrollListener = (OnListFragmentScrolledListener) activity; |
| } catch (ClassCastException e) { |
| throw new ClassCastException(activity.toString() |
| + " must implement OnListFragmentScrolledListener"); |
| } |
| |
| try { |
| mShowAllContactsListener = (OnShowAllContactsListener) activity; |
| } catch (ClassCastException e) { |
| throw new ClassCastException(activity.toString() |
| + " must implement OnShowAllContactsListener"); |
| } |
| |
| try { |
| OnDragDropListener listener = (OnDragDropListener) activity; |
| mListView.getDragDropController().addOnDragDropListener(listener); |
| ((HostInterface) activity).setDragDropController(mListView.getDragDropController()); |
| } catch (ClassCastException e) { |
| throw new ClassCastException(activity.toString() |
| + " must implement OnDragDropListener and HostInterface"); |
| } |
| |
| // Use initLoader() instead of restartLoader() to refraining unnecessary reload. |
| // This method call implicitly assures ContactTileLoaderListener's onLoadFinished() will |
| // be called, on which we'll check if "all" contacts should be reloaded again or not. |
| getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, mContactTileLoaderListener); |
| getLoaderManager().initLoader(MISSED_CALL_LOADER, null, new MissedCallLogLoaderListener()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * This is only effective for elements provided by {@link #mContactTileAdapter}. |
| * {@link #mContactTileAdapter} has its own logic for click events. |
| */ |
| @Override |
| public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| final int contactTileAdapterCount = mContactTileAdapter.getCount(); |
| if (position <= contactTileAdapterCount) { |
| Log.e(TAG, "onItemClick() event for unexpected position. " |
| + "The position " + position + " is before \"all\" section. Ignored."); |
| } |
| } |
| |
| /** |
| * Gets called when user click on the show all contacts button. |
| */ |
| private void showAllContacts() { |
| mShowAllContactsListener.onShowAllContacts(); |
| } |
| |
| public void setListener(Listener listener) { |
| mListener = listener; |
| } |
| |
| @Override |
| public void onVoicemailStatusFetched(Cursor statusCursor) { |
| // no-op |
| } |
| |
| @Override |
| public void onCallsFetched(Cursor cursor) { |
| animateListView(); |
| mCallLogAdapter.setLoading(false); |
| |
| // Save the date of the most recent call log item |
| if (cursor != null && cursor.moveToFirst()) { |
| mCurrentCallShortcutDate = cursor.getLong(CallLogQuery.DATE); |
| } |
| |
| mCallLogAdapter.changeCursor(cursor); |
| mAdapter.notifyDataSetChanged(); |
| } |
| |
| @Override |
| public void fetchCalls() { |
| mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL, mLastCallShortcutDate); |
| } |
| |
| @Override |
| public void onPause() { |
| // If there are any pending contact entries that are to be removed, remove them |
| mContactTileAdapter.removePendingContactEntry(); |
| // Wipe the cache to refresh the call shortcut item. This is not that expensive because |
| // it only contains one item. |
| mCallLogAdapter.invalidateCache(); |
| super.onPause(); |
| } |
| |
| /** |
| * Cache the current view offsets into memory. Once a relayout of views in the ListView |
| * has happened due to a dataset change, the cached offsets are used to create animations |
| * that slide views from their previous positions to their new ones, to give the appearance |
| * that the views are sliding into their new positions. |
| */ |
| @SuppressWarnings("unchecked") |
| private void saveOffsets(int removedItemHeight) { |
| final int firstVisiblePosition = mListView.getFirstVisiblePosition(); |
| if (DEBUG) { |
| Log.d(TAG, "Child count : " + mListView.getChildCount()); |
| } |
| for (int i = 0; i < mListView.getChildCount(); i++) { |
| final View child = mListView.getChildAt(i); |
| final int position = firstVisiblePosition + i; |
| final long itemId = mAdapter.getItemId(position); |
| final int itemViewType = mAdapter.getItemViewType(position); |
| if (itemViewType == PhoneFavoritesTileAdapter.ViewTypes.TOP) { |
| // This is a tiled row, so save horizontal offsets instead |
| saveHorizontalOffsets((ContactTileRow) child, (ArrayList<ContactEntry>) |
| mAdapter.getItem(position), position); |
| } |
| if (DEBUG) { |
| Log.d(TAG, "Saving itemId: " + itemId + " for listview child " + i + " Top: " |
| + child.getTop()); |
| } |
| mItemIdTopMap.put(itemId, child.getTop()); |
| } |
| |
| mItemIdTopMap.put(KEY_REMOVED_ITEM_HEIGHT, removedItemHeight); |
| } |
| |
| /** |
| * Saves the horizontal offsets for contacts that are displayed as tiles in a row. Saving |
| * these offsets allow us to animate tiles sliding left and right within the same row. |
| * See {@link #saveOffsets(int removedItemHeight)} |
| */ |
| private void saveHorizontalOffsets(ContactTileRow row, ArrayList<ContactEntry> list, |
| int currentRowIndex) { |
| for (int i = 0; i < list.size() && i < row.getChildCount(); i++) { |
| final View child = row.getChildAt(i); |
| if (child == null) { |
| continue; |
| } |
| final ContactEntry entry = list.get(i); |
| final long itemId = mContactTileAdapter.getAdjustedItemId(entry.id); |
| if (DEBUG) { |
| Log.d(TAG, "Saving itemId: " + itemId + " for tileview child " + i + " Left: " |
| + child.getTop()); |
| } |
| mItemIdTopMap.put(itemId, currentRowIndex); |
| mItemIdLeftMap.put(itemId, child.getLeft()); |
| } |
| } |
| |
| /* |
| * Performs a animations for a row of tiles |
| */ |
| private void performHorizontalAnimations(ContactTileRow row, ArrayList<ContactEntry> list, |
| long[] idsInPlace, int currentRow) { |
| if (mItemIdLeftMap.isEmpty()) { |
| return; |
| } |
| final AnimatorSet animSet = new AnimatorSet(); |
| final ArrayList<Animator> animators = new ArrayList<Animator>(); |
| for (int i = 0; i < list.size(); i++) { |
| final View child = row.getChildAt(i); |
| final ContactEntry entry = list.get(i); |
| final long itemId = mContactTileAdapter.getAdjustedItemId(entry.id); |
| |
| if (containsId(idsInPlace, itemId)) { |
| animators.add(ObjectAnimator.ofFloat( |
| child, "alpha", 0.0f, 1.0f)); |
| break; |
| } else { |
| Integer startLeft = mItemIdLeftMap.get(itemId); |
| int left = child.getLeft(); |
| |
| Integer startRow = mItemIdTopMap.get(itemId); |
| |
| if (startRow != null) { |
| if (startRow > currentRow) { |
| // Item has shifted upwards to the previous row. |
| // It should now animate in from right to left. |
| startLeft = left + child.getWidth(); |
| } else if (startRow < currentRow) { |
| // Item has shifted downwards to the next row. |
| // It should now animate in from left to right. |
| startLeft = left - child.getWidth(); |
| } |
| |
| // If the item hasn't shifted rows (startRow == currentRow), it either remains |
| // in the same position or has shifted left or right within its current row. |
| // Either way, startLeft has already been correctly saved and retrieved from |
| // mItemIdTopMap. |
| } |
| |
| if (startLeft != null) { |
| if (startLeft != left) { |
| int delta = startLeft - left; |
| if (DEBUG) { |
| Log.d(TAG, "Found itemId: " + itemId + " for tileview child " + i + |
| " Left: " + left + |
| " Delta: " + delta); |
| } |
| animators.add(ObjectAnimator.ofFloat( |
| child, "translationX", delta, 0.0f)); |
| } |
| } else { |
| // In case the last square row is pushed up from the non-square section. |
| animators.add(ObjectAnimator.ofFloat( |
| child, "translationX", left, 0.0f)); |
| } |
| } |
| } |
| if (animators.size() > 0) { |
| animSet.setDuration(mAnimationDuration).playTogether(animators); |
| animSet.start(); |
| } |
| } |
| |
| /* |
| * Performs animations for the list view. If the list item is a row of tiles, horizontal |
| * animations will be performed instead. |
| */ |
| private void animateListView(final long... idsInPlace) { |
| if (mItemIdTopMap.isEmpty()) { |
| // Don't do animations if the database is being queried for the first time and |
| // the previous item offsets have not been cached, or the user hasn't done anything |
| // (dragging, swiping etc) that requires an animation. |
| return; |
| } |
| |
| final int removedItemHeight = mItemIdTopMap.get(KEY_REMOVED_ITEM_HEIGHT); |
| |
| final ViewTreeObserver observer = mListView.getViewTreeObserver(); |
| observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { |
| @SuppressWarnings("unchecked") |
| @Override |
| public boolean onPreDraw() { |
| observer.removeOnPreDrawListener(this); |
| final int firstVisiblePosition = mListView.getFirstVisiblePosition(); |
| final AnimatorSet animSet = new AnimatorSet(); |
| final ArrayList<Animator> animators = new ArrayList<Animator>(); |
| for (int i = 0; i < mListView.getChildCount(); i++) { |
| final View child = mListView.getChildAt(i); |
| int position = firstVisiblePosition + i; |
| final int itemViewType = mAdapter.getItemViewType(position); |
| if (itemViewType == PhoneFavoritesTileAdapter.ViewTypes.TOP && |
| child instanceof ContactTileRow) { |
| // This is a tiled row, so perform horizontal animations instead |
| performHorizontalAnimations((ContactTileRow) child, ( |
| ArrayList<ContactEntry>) mAdapter.getItem(position), idsInPlace, |
| position); |
| } |
| |
| final long itemId = mAdapter.getItemId(position); |
| |
| if (containsId(idsInPlace, itemId)) { |
| animators.add(ObjectAnimator.ofFloat( |
| child, "alpha", 0.0f, 1.0f)); |
| break; |
| } else { |
| Integer startTop = mItemIdTopMap.get(itemId); |
| final int top = child.getTop(); |
| int delta = 0; |
| if (startTop != null) { |
| if (startTop != top) { |
| delta = startTop - top; |
| } |
| } else if (!mItemIdLeftMap.containsKey(itemId)) { |
| // Animate new views along with the others. The catch is that they did |
| // not exist in the start state, so we must calculate their starting |
| // position based on neighboring views. |
| |
| final int itemHeight; |
| if (removedItemHeight == 0) { |
| itemHeight = child.getHeight() + mListView.getDividerHeight(); |
| } else { |
| itemHeight = removedItemHeight; |
| } |
| startTop = top + (i > 0 ? itemHeight : -itemHeight); |
| delta = startTop - top; |
| } else { |
| // In case the first non-square row is pushed down |
| // from the square section. |
| animators.add(ObjectAnimator.ofFloat( |
| child, "alpha", 0.0f, 1.0f)); |
| } |
| if (DEBUG) { |
| Log.d(TAG, "Found itemId: " + itemId + " for listview child " + i + |
| " Top: " + top + |
| " Delta: " + delta); |
| } |
| |
| if (delta != 0) { |
| animators.add(ObjectAnimator.ofFloat( |
| child, "translationY", delta, 0.0f)); |
| } |
| } |
| } |
| |
| if (animators.size() > 0) { |
| animSet.setDuration(mAnimationDuration).playTogether(animators); |
| animSet.start(); |
| } |
| |
| mItemIdTopMap.clear(); |
| mItemIdLeftMap.clear(); |
| return true; |
| } |
| }); |
| } |
| |
| private boolean containsId(long[] ids, long target) { |
| // Linear search on array is fine because this is typically only 0-1 elements long |
| for (int i = 0; i < ids.length; i++) { |
| if (ids[i] == target) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public void onDataSetChangedForAnimation(long... idsInPlace) { |
| animateListView(idsInPlace); |
| } |
| |
| @Override |
| public void cacheOffsetsForDatasetChange() { |
| saveOffsets(0); |
| } |
| |
| public void dismissShortcut(int height) { |
| saveOffsets(height); |
| mLastCallShortcutDate = mCurrentCallShortcutDate; |
| final SharedPreferences prefs = getActivity().getSharedPreferences( |
| DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE); |
| prefs.edit().putLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, mLastCallShortcutDate) |
| .apply(); |
| fetchCalls(); |
| } |
| |
| /** |
| * Prepares the favorites menu which contains the static label "Speed Dial" and the |
| * "All Contacts" button. Taps anywhere in the view take the user to "All Contacts". |
| * This emulates how the headers in Play Store work. |
| */ |
| private void prepareFavoritesMenu(View favoritesMenu) { |
| // Set the onClick listener for the view to bring up the all contacts view. |
| favoritesMenu.setOnClickListener(new OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| showAllContacts(); |
| } |
| }); |
| } |
| } |