1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.dialer.list; 17 18 import android.animation.Animator; 19 import android.animation.AnimatorSet; 20 import android.animation.ObjectAnimator; 21 import android.app.Activity; 22 import android.app.Fragment; 23 import android.app.LoaderManager; 24 import android.content.Context; 25 import android.content.CursorLoader; 26 import android.content.Loader; 27 import android.content.SharedPreferences; 28 import android.database.Cursor; 29 import android.graphics.Rect; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.provider.CallLog; 33 import android.util.Log; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.View.OnClickListener; 37 import android.view.ViewGroup; 38 import android.view.ViewTreeObserver; 39 import android.widget.AbsListView; 40 import android.widget.AdapterView; 41 import android.widget.AdapterView.OnItemClickListener; 42 import android.widget.Button; 43 import android.widget.ImageView; 44 import android.widget.ListView; 45 import android.widget.RelativeLayout; 46 import android.widget.RelativeLayout.LayoutParams; 47 48 import com.android.contacts.common.ContactPhotoManager; 49 import com.android.contacts.common.ContactTileLoaderFactory; 50 import com.android.contacts.common.GeoUtil; 51 import com.android.contacts.common.list.ContactEntry; 52 import com.android.contacts.common.list.ContactListItemView; 53 import com.android.contacts.common.list.ContactTileView; 54 import com.android.dialer.DialtactsActivity; 55 import com.android.dialer.R; 56 import com.android.dialer.calllog.CallLogAdapter; 57 import com.android.dialer.calllog.CallLogQuery; 58 import com.android.dialer.calllog.CallLogQueryHandler; 59 import com.android.dialer.calllog.ContactInfoHelper; 60 import com.android.dialer.list.PhoneFavoritesTileAdapter.ContactTileRow; 61 import com.android.dialerbind.ObjectFactory; 62 63 import java.util.ArrayList; 64 import java.util.HashMap; 65 66 /** 67 * Fragment for Phone UI's favorite screen. 68 * 69 * This fragment contains three kinds of contacts in one screen: "starred", "frequent", and "all" 70 * contacts. To show them at once, this merges results from {@link com.android.contacts.common.list.ContactTileAdapter} and 71 * {@link com.android.contacts.common.list.PhoneNumberListAdapter} into one unified list using {@link PhoneFavoriteMergedAdapter}. 72 * A contact filter header is also inserted between those adapters' results. 73 */ 74 public class PhoneFavoriteFragment extends Fragment implements OnItemClickListener, 75 CallLogQueryHandler.Listener, CallLogAdapter.CallFetcher, 76 PhoneFavoritesTileAdapter.OnDataSetChangedForAnimationListener { 77 78 /** 79 * By default, the animation code assumes that all items in a list view are of the same height 80 * when animating new list items into view (e.g. from the bottom of the screen into view). 81 * This can cause incorrect translation offsets when a item that is larger or smaller than 82 * other list item is removed from the list. This key is used to provide the actual height 83 * of the removed object so that the actual translation appears correct to the user. 84 */ 85 private static final long KEY_REMOVED_ITEM_HEIGHT = Long.MAX_VALUE; 86 87 private static final String TAG = PhoneFavoriteFragment.class.getSimpleName(); 88 private static final boolean DEBUG = false; 89 90 private int mAnimationDuration; 91 92 /** 93 * Used with LoaderManager. 94 */ 95 private static int LOADER_ID_CONTACT_TILE = 1; 96 private static int MISSED_CALL_LOADER = 2; 97 98 private static final String KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE = 99 "key_last_dismissed_call_shortcut_date"; 100 101 public interface OnShowAllContactsListener { 102 public void onShowAllContacts(); 103 } 104 105 public interface Listener { 106 public void onContactSelected(Uri contactUri); 107 public void onCallNumberDirectly(String phoneNumber); 108 } 109 110 public interface HostInterface { 111 public void setDragDropController(DragDropController controller); 112 } 113 114 private class MissedCallLogLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> { 115 116 @Override 117 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 118 final Uri uri = CallLog.Calls.CONTENT_URI; 119 final String[] projection = new String[] {CallLog.Calls.TYPE}; 120 final String selection = CallLog.Calls.TYPE + " = " + CallLog.Calls.MISSED_TYPE + 121 " AND " + CallLog.Calls.IS_READ + " = 0"; 122 return new CursorLoader(getActivity(), uri, projection, selection, null, null); 123 } 124 125 @Override 126 public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor data) { 127 mCallLogAdapter.setMissedCalls(data); 128 } 129 130 @Override 131 public void onLoaderReset(Loader<Cursor> cursorLoader) { 132 } 133 } 134 135 private class ContactTileLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> { 136 @Override 137 public CursorLoader onCreateLoader(int id, Bundle args) { 138 if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onCreateLoader."); 139 return ContactTileLoaderFactory.createStrequentPhoneOnlyLoader(getActivity()); 140 } 141 142 @Override 143 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 144 if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoadFinished"); 145 mContactTileAdapter.setContactCursor(data); 146 setEmptyViewVisibility(mContactTileAdapter.getCount() == 0); 147 } 148 149 @Override 150 public void onLoaderReset(Loader<Cursor> loader) { 151 if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoaderReset. "); 152 } 153 } 154 155 private class ContactTileAdapterListener implements ContactTileView.Listener { 156 @Override 157 public void onContactSelected(Uri contactUri, Rect targetRect) { 158 if (mListener != null) { 159 mListener.onContactSelected(contactUri); 160 } 161 } 162 163 @Override 164 public void onCallNumberDirectly(String phoneNumber) { 165 if (mListener != null) { 166 mListener.onCallNumberDirectly(phoneNumber); 167 } 168 } 169 170 @Override 171 public int getApproximateTileWidth() { 172 return getView().getWidth() / mContactTileAdapter.getColumnCount(); 173 } 174 } 175 176 private class ScrollListener implements ListView.OnScrollListener { 177 @Override 178 public void onScroll(AbsListView view, 179 int firstVisibleItem, int visibleItemCount, int totalItemCount) { 180 } 181 182 @Override 183 public void onScrollStateChanged(AbsListView view, int scrollState) { 184 mActivityScrollListener.onListFragmentScrollStateChange(scrollState); 185 } 186 } 187 188 private Listener mListener; 189 190 private OnListFragmentScrolledListener mActivityScrollListener; 191 private OnShowAllContactsListener mShowAllContactsListener; 192 private PhoneFavoriteMergedAdapter mAdapter; 193 private PhoneFavoritesTileAdapter mContactTileAdapter; 194 195 private CallLogAdapter mCallLogAdapter; 196 private CallLogQueryHandler mCallLogQueryHandler; 197 198 private View mParentView; 199 200 private PhoneFavoriteListView mListView; 201 202 private View mPhoneFavoritesMenu; 203 private View mContactTileFrame; 204 205 private TileInteractionTeaserView mTileInteractionTeaserView; 206 207 private final HashMap<Long, Integer> mItemIdTopMap = new HashMap<Long, Integer>(); 208 private final HashMap<Long, Integer> mItemIdLeftMap = new HashMap<Long, Integer>(); 209 210 /** 211 * Layout used when there are no favorites. 212 */ 213 private View mEmptyView; 214 215 /** 216 * Call shortcuts older than this date (persisted in shared preferences) will not show up in 217 * at the top of the screen 218 */ 219 private long mLastCallShortcutDate = 0; 220 221 /** 222 * The date of the current call shortcut that is showing on screen. 223 */ 224 private long mCurrentCallShortcutDate = 0; 225 226 private final ContactTileView.Listener mContactTileAdapterListener = 227 new ContactTileAdapterListener(); 228 private final LoaderManager.LoaderCallbacks<Cursor> mContactTileLoaderListener = 229 new ContactTileLoaderListener(); 230 private final ScrollListener mScrollListener = new ScrollListener(); 231 232 @Override 233 public void onAttach(Activity activity) { 234 if (DEBUG) Log.d(TAG, "onAttach()"); 235 super.onAttach(activity); 236 237 // Construct two base adapters which will become part of PhoneFavoriteMergedAdapter. 238 // We don't construct the resultant adapter at this moment since it requires LayoutInflater 239 // that will be available on onCreateView(). 240 mContactTileAdapter = new PhoneFavoritesTileAdapter(activity, mContactTileAdapterListener, 241 this, 242 getResources().getInteger(R.integer.contact_tile_column_count_in_favorites), 243 PhoneFavoritesTileAdapter.NO_ROW_LIMIT); 244 mContactTileAdapter.setPhotoLoader(ContactPhotoManager.getInstance(activity)); 245 } 246 247 @Override 248 public void onCreate(Bundle savedState) { 249 if (DEBUG) Log.d(TAG, "onCreate()"); 250 super.onCreate(savedState); 251 252 mAnimationDuration = getResources().getInteger(R.integer.fade_duration); 253 mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), 254 this, 1); 255 final String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); 256 mCallLogAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this, 257 new ContactInfoHelper(getActivity(), currentCountryIso), false, false); 258 setHasOptionsMenu(true); 259 } 260 261 @Override 262 public void onResume() { 263 super.onResume(); 264 final SharedPreferences prefs = getActivity().getSharedPreferences( 265 DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE); 266 267 mLastCallShortcutDate = prefs.getLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, 0); 268 269 fetchCalls(); 270 mCallLogAdapter.setLoading(true); 271 getLoaderManager().getLoader(LOADER_ID_CONTACT_TILE).forceLoad(); 272 } 273 274 @Override 275 public View onCreateView(LayoutInflater inflater, ViewGroup container, 276 Bundle savedInstanceState) { 277 mParentView = inflater.inflate(R.layout.phone_favorites_fragment, container, false); 278 279 mListView = (PhoneFavoriteListView) mParentView.findViewById(R.id.contact_tile_list); 280 mListView.setItemsCanFocus(true); 281 mListView.setOnItemClickListener(this); 282 mListView.setVerticalScrollBarEnabled(false); 283 mListView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT); 284 mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY); 285 mListView.setOnItemSwipeListener(mContactTileAdapter); 286 mListView.getDragDropController().addOnDragDropListener(mContactTileAdapter); 287 288 final ImageView dragShadowOverlay = 289 (ImageView) mParentView.findViewById(R.id.contact_tile_drag_shadow_overlay); 290 mListView.setDragShadowOverlay(dragShadowOverlay); 291 292 mEmptyView = mParentView.findViewById(R.id.phone_no_favorites_view); 293 294 mPhoneFavoritesMenu = inflater.inflate(R.layout.phone_favorites_menu, mListView, false); 295 prepareFavoritesMenu(mPhoneFavoritesMenu); 296 297 mContactTileFrame = mParentView.findViewById(R.id.contact_tile_frame); 298 299 mTileInteractionTeaserView = (TileInteractionTeaserView) inflater.inflate( 300 R.layout.tile_interactions_teaser_view, mListView, false); 301 302 mAdapter = new PhoneFavoriteMergedAdapter(getActivity(), this, mContactTileAdapter, 303 mCallLogAdapter, mPhoneFavoritesMenu, mTileInteractionTeaserView); 304 305 mTileInteractionTeaserView.setAdapter(mAdapter); 306 307 mListView.setAdapter(mAdapter); 308 309 mListView.setOnScrollListener(mScrollListener); 310 mListView.setFastScrollEnabled(false); 311 mListView.setFastScrollAlwaysVisible(false); 312 313 return mParentView; 314 } 315 316 public boolean hasFrequents() { 317 if (mContactTileAdapter == null) return false; 318 return mContactTileAdapter.getNumFrequents() > 0; 319 } 320 321 /* package */ void setEmptyViewVisibility(final boolean visible) { 322 final int previousVisibility = mEmptyView.getVisibility(); 323 final int newVisibility = visible ? View.VISIBLE : View.GONE; 324 325 if (previousVisibility != newVisibility) { 326 final RelativeLayout.LayoutParams params = (LayoutParams) mContactTileFrame 327 .getLayoutParams(); 328 params.height = visible ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT; 329 mContactTileFrame.setLayoutParams(params); 330 mEmptyView.setVisibility(newVisibility); 331 } 332 } 333 334 @Override 335 public void onStart() { 336 super.onStart(); 337 338 final Activity activity = getActivity(); 339 340 try { 341 mActivityScrollListener = (OnListFragmentScrolledListener) activity; 342 } catch (ClassCastException e) { 343 throw new ClassCastException(activity.toString() 344 + " must implement OnListFragmentScrolledListener"); 345 } 346 347 try { 348 mShowAllContactsListener = (OnShowAllContactsListener) activity; 349 } catch (ClassCastException e) { 350 throw new ClassCastException(activity.toString() 351 + " must implement OnShowAllContactsListener"); 352 } 353 354 try { 355 OnDragDropListener listener = (OnDragDropListener) activity; 356 mListView.getDragDropController().addOnDragDropListener(listener); 357 ((HostInterface) activity).setDragDropController(mListView.getDragDropController()); 358 } catch (ClassCastException e) { 359 throw new ClassCastException(activity.toString() 360 + " must implement OnDragDropListener and HostInterface"); 361 } 362 363 // Use initLoader() instead of restartLoader() to refraining unnecessary reload. 364 // This method call implicitly assures ContactTileLoaderListener's onLoadFinished() will 365 // be called, on which we'll check if "all" contacts should be reloaded again or not. 366 getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, mContactTileLoaderListener); 367 getLoaderManager().initLoader(MISSED_CALL_LOADER, null, new MissedCallLogLoaderListener()); 368 } 369 370 /** 371 * {@inheritDoc} 372 * 373 * This is only effective for elements provided by {@link #mContactTileAdapter}. 374 * {@link #mContactTileAdapter} has its own logic for click events. 375 */ 376 @Override 377 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 378 final int contactTileAdapterCount = mContactTileAdapter.getCount(); 379 if (position <= contactTileAdapterCount) { 380 Log.e(TAG, "onItemClick() event for unexpected position. " 381 + "The position " + position + " is before \"all\" section. Ignored."); 382 } 383 } 384 385 /** 386 * Gets called when user click on the show all contacts button. 387 */ 388 private void showAllContacts() { 389 mShowAllContactsListener.onShowAllContacts(); 390 } 391 392 public void setListener(Listener listener) { 393 mListener = listener; 394 } 395 396 @Override 397 public void onVoicemailStatusFetched(Cursor statusCursor) { 398 // no-op 399 } 400 401 @Override 402 public void onCallsFetched(Cursor cursor) { 403 animateListView(); 404 mCallLogAdapter.setLoading(false); 405 406 // Save the date of the most recent call log item 407 if (cursor != null && cursor.moveToFirst()) { 408 mCurrentCallShortcutDate = cursor.getLong(CallLogQuery.DATE); 409 } 410 411 mCallLogAdapter.changeCursor(cursor); 412 mAdapter.notifyDataSetChanged(); 413 } 414 415 @Override 416 public void fetchCalls() { 417 mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL, mLastCallShortcutDate); 418 } 419 420 @Override 421 public void onPause() { 422 // If there are any pending contact entries that are to be removed, remove them 423 mContactTileAdapter.removePendingContactEntry(); 424 // Wipe the cache to refresh the call shortcut item. This is not that expensive because 425 // it only contains one item. 426 mCallLogAdapter.invalidateCache(); 427 super.onPause(); 428 } 429 430 /** 431 * Cache the current view offsets into memory. Once a relayout of views in the ListView 432 * has happened due to a dataset change, the cached offsets are used to create animations 433 * that slide views from their previous positions to their new ones, to give the appearance 434 * that the views are sliding into their new positions. 435 */ 436 @SuppressWarnings("unchecked") 437 private void saveOffsets(int removedItemHeight) { 438 final int firstVisiblePosition = mListView.getFirstVisiblePosition(); 439 if (DEBUG) { 440 Log.d(TAG, "Child count : " + mListView.getChildCount()); 441 } 442 for (int i = 0; i < mListView.getChildCount(); i++) { 443 final View child = mListView.getChildAt(i); 444 final int position = firstVisiblePosition + i; 445 final long itemId = mAdapter.getItemId(position); 446 final int itemViewType = mAdapter.getItemViewType(position); 447 if (itemViewType == PhoneFavoritesTileAdapter.ViewTypes.TOP && 448 child instanceof ContactTileRow) { 449 // This is a tiled row, so save horizontal offsets instead 450 saveHorizontalOffsets((ContactTileRow) child, (ArrayList<ContactEntry>) 451 mAdapter.getItem(position), 452 mAdapter.getAdjustedPositionInContactTileAdapter(position)); 453 } 454 if (DEBUG) { 455 Log.d(TAG, "Saving itemId: " + itemId + " for listview child " + i + " Top: " 456 + child.getTop()); 457 } 458 mItemIdTopMap.put(itemId, child.getTop()); 459 } 460 461 mItemIdTopMap.put(KEY_REMOVED_ITEM_HEIGHT, removedItemHeight); 462 } 463 464 /** 465 * Saves the horizontal offsets for contacts that are displayed as tiles in a row. Saving 466 * these offsets allow us to animate tiles sliding left and right within the same row. 467 * See {@link #saveOffsets(int removedItemHeight)} 468 */ 469 private void saveHorizontalOffsets(ContactTileRow row, ArrayList<ContactEntry> list, 470 int currentRowIndex) { 471 for (int i = 0; i < list.size() && i < row.getChildCount(); i++) { 472 final View child = row.getChildAt(i); 473 if (child == null) { 474 continue; 475 } 476 final ContactEntry entry = list.get(i); 477 final long itemId = mContactTileAdapter.getAdjustedItemId(entry.id); 478 if (DEBUG) { 479 Log.d(TAG, "Saving itemId: " + itemId + " for tileview child " + i + " Left: " 480 + child.getTop()); 481 } 482 mItemIdTopMap.put(itemId, currentRowIndex); 483 mItemIdLeftMap.put(itemId, child.getLeft()); 484 } 485 } 486 487 /* 488 * Performs a animations for a row of tiles 489 */ 490 private void performHorizontalAnimations(ContactTileRow row, ArrayList<ContactEntry> list, 491 long[] idsInPlace, int currentRow) { 492 if (mItemIdLeftMap.isEmpty()) { 493 return; 494 } 495 final AnimatorSet animSet = new AnimatorSet(); 496 final ArrayList<Animator> animators = new ArrayList<Animator>(); 497 for (int i = 0; i < list.size(); i++) { 498 final View child = row.getChildAt(i); 499 final ContactEntry entry = list.get(i); 500 final long itemId = mContactTileAdapter.getAdjustedItemId(entry.id); 501 502 if (containsId(idsInPlace, itemId)) { 503 animators.add(ObjectAnimator.ofFloat( 504 child, "alpha", 0.0f, 1.0f)); 505 break; 506 } else { 507 Integer startLeft = mItemIdLeftMap.get(itemId); 508 int left = child.getLeft(); 509 510 Integer startRow = mItemIdTopMap.get(itemId); 511 if (startRow != null) { 512 if (startRow > currentRow) { 513 // Item has shifted upwards to the previous row. 514 // It should now animate in from right to left. 515 startLeft = left + child.getWidth(); 516 } else if (startRow < currentRow) { 517 // Item has shifted downwards to the next row. 518 // It should now animate in from left to right. 519 startLeft = left - child.getWidth(); 520 } 521 522 // If the item hasn't shifted rows (startRow == currentRow), it either remains 523 // in the same position or has shifted left or right within its current row. 524 // Either way, startLeft has already been correctly saved and retrieved from 525 // mItemIdTopMap. 526 } 527 528 if (startLeft != null) { 529 if (startLeft != left) { 530 int delta = startLeft - left; 531 if (DEBUG) { 532 Log.d(TAG, "Found itemId: " + itemId + " for tileview child " + i + 533 " Left: " + left + 534 " Delta: " + delta); 535 } 536 animators.add(ObjectAnimator.ofFloat( 537 child, "translationX", delta, 0.0f)); 538 } 539 } 540 } 541 } 542 if (animators.size() > 0) { 543 animSet.setDuration(mAnimationDuration).playTogether(animators); 544 animSet.start(); 545 } 546 } 547 548 /* 549 * Performs animations for the list view. If the list item is a row of tiles, horizontal 550 * animations will be performed instead. 551 */ 552 private void animateListView(final long... idsInPlace) { 553 if (mItemIdTopMap.isEmpty()) { 554 // Don't do animations if the database is being queried for the first time and 555 // the previous item offsets have not been cached, or the user hasn't done anything 556 // (dragging, swiping etc) that requires an animation. 557 return; 558 } 559 560 final int removedItemHeight = mItemIdTopMap.get(KEY_REMOVED_ITEM_HEIGHT); 561 562 final ViewTreeObserver observer = mListView.getViewTreeObserver(); 563 observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 564 @SuppressWarnings("unchecked") 565 @Override 566 public boolean onPreDraw() { 567 observer.removeOnPreDrawListener(this); 568 final int firstVisiblePosition = mListView.getFirstVisiblePosition(); 569 final AnimatorSet animSet = new AnimatorSet(); 570 final ArrayList<Animator> animators = new ArrayList<Animator>(); 571 for (int i = 0; i < mListView.getChildCount(); i++) { 572 final View child = mListView.getChildAt(i); 573 int position = firstVisiblePosition + i; 574 final int itemViewType = mAdapter.getItemViewType(position); 575 if (itemViewType == PhoneFavoritesTileAdapter.ViewTypes.TOP && 576 child instanceof ContactTileRow) { 577 // This is a tiled row, so perform horizontal animations instead 578 performHorizontalAnimations((ContactTileRow) child, ( 579 ArrayList<ContactEntry>) mAdapter.getItem(position), idsInPlace, 580 mAdapter.getAdjustedPositionInContactTileAdapter(position)); 581 } 582 583 final long itemId = mAdapter.getItemId(position); 584 585 if (containsId(idsInPlace, itemId)) { 586 animators.add(ObjectAnimator.ofFloat( 587 child, "alpha", 0.0f, 1.0f)); 588 break; 589 } else { 590 Integer startTop = mItemIdTopMap.get(itemId); 591 final int top = child.getTop(); 592 int delta = 0; 593 if (startTop != null) { 594 if (startTop != top) { 595 delta = startTop - top; 596 } 597 } else if (!mItemIdLeftMap.containsKey(itemId)) { 598 // Animate new views along with the others. The catch is that they did 599 // not exist in the start state, so we must calculate their starting 600 // position based on neighboring views. 601 602 final int itemHeight; 603 if (removedItemHeight == 0) { 604 itemHeight = child.getHeight() + mListView.getDividerHeight(); 605 } else { 606 itemHeight = removedItemHeight; 607 } 608 startTop = top + (i > 0 ? itemHeight : -itemHeight); 609 delta = startTop - top; 610 } 611 if (DEBUG) { 612 Log.d(TAG, "Found itemId: " + itemId + " for listview child " + i + 613 " Top: " + top + 614 " Delta: " + delta); 615 } 616 617 if (delta != 0) { 618 animators.add(ObjectAnimator.ofFloat( 619 child, "translationY", delta, 0.0f)); 620 } 621 } 622 } 623 624 if (animators.size() > 0) { 625 animSet.setDuration(mAnimationDuration).playTogether(animators); 626 animSet.start(); 627 } 628 629 mItemIdTopMap.clear(); 630 mItemIdLeftMap.clear(); 631 return true; 632 } 633 }); 634 } 635 636 private boolean containsId(long[] ids, long target) { 637 // Linear search on array is fine because this is typically only 0-1 elements long 638 for (int i = 0; i < ids.length; i++) { 639 if (ids[i] == target) { 640 return true; 641 } 642 } 643 return false; 644 } 645 646 @Override 647 public void onDataSetChangedForAnimation(long... idsInPlace) { 648 animateListView(idsInPlace); 649 } 650 651 @Override 652 public void cacheOffsetsForDatasetChange() { 653 saveOffsets(0); 654 } 655 656 public void dismissShortcut(int height) { 657 saveOffsets(height); 658 mLastCallShortcutDate = mCurrentCallShortcutDate; 659 final SharedPreferences prefs = getActivity().getSharedPreferences( 660 DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE); 661 prefs.edit().putLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, mLastCallShortcutDate) 662 .apply(); 663 fetchCalls(); 664 } 665 666 /** 667 * Prepares the favorites menu which contains the static label "Speed Dial" and the 668 * "All Contacts" button. Sets the onClickListener for the "All Contacts" button. 669 */ 670 private void prepareFavoritesMenu(View favoritesMenu) { 671 Button allContactsButton = (Button) favoritesMenu.findViewById(R.id.all_contacts_button); 672 // Set the onClick listener for the button to bring up the all contacts view. 673 allContactsButton.setOnClickListener(new OnClickListener() { 674 @Override 675 public void onClick(View view) { 676 showAllContacts(); 677 } 678 }); 679 } 680 } 681