AnimatedAdapter.java revision 6a6deb20dbbea543ccf093487d26dea4ac4f9cd4
1/* 2 * Copyright (C) 2012 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 18package com.android.mail.ui; 19 20import android.animation.Animator; 21import android.animation.AnimatorSet; 22import android.animation.ObjectAnimator; 23import android.content.Context; 24import android.database.Cursor; 25import android.os.Bundle; 26import android.os.Handler; 27import android.view.LayoutInflater; 28import android.view.View; 29import android.view.ViewGroup; 30import android.widget.SimpleCursorAdapter; 31 32import com.android.mail.R; 33import com.android.mail.browse.ConversationCursor; 34import com.android.mail.browse.ConversationItemView; 35import com.android.mail.browse.ConversationItemViewCoordinates; 36import com.android.mail.browse.SwipeableConversationItemView; 37import com.android.mail.providers.Account; 38import com.android.mail.providers.AccountObserver; 39import com.android.mail.providers.Conversation; 40import com.android.mail.providers.Folder; 41import com.android.mail.providers.UIProvider; 42import com.android.mail.ui.SwipeableListView.ListItemsRemovedListener; 43import com.android.mail.utils.LogTag; 44import com.android.mail.utils.LogUtils; 45import com.google.common.collect.Maps; 46 47import java.util.ArrayList; 48import java.util.Collection; 49import java.util.HashMap; 50import java.util.HashSet; 51import java.util.Iterator; 52import java.util.Map.Entry; 53 54public class AnimatedAdapter extends SimpleCursorAdapter implements 55 android.animation.Animator.AnimatorListener { 56 private static int sDismissAllDelay = -1; 57 private static final String LAST_DELETING_ITEMS = "last_deleting_items"; 58 private static final String LEAVE_BEHIND_ITEM_DATA = "leave_behind_item_data"; 59 private static final String LEAVE_BEHIND_ITEM_ID = "leave_behind_item_id"; 60 private final static int TYPE_VIEW_CONVERSATION = 0; 61 private final static int TYPE_VIEW_FOOTER = 1; 62 private final static int TYPE_VIEW_DONT_RECYCLE = -1; 63 private final HashSet<Long> mDeletingItems = new HashSet<Long>(); 64 private final ArrayList<Long> mLastDeletingItems = new ArrayList<Long>(); 65 private final HashSet<Long> mUndoingItems = new HashSet<Long>(); 66 private final HashSet<Long> mSwipeDeletingItems = new HashSet<Long>(); 67 private final HashSet<Long> mSwipeUndoingItems = new HashSet<Long>(); 68 private final HashMap<Long, SwipeableConversationItemView> mAnimatingViews = 69 new HashMap<Long, SwipeableConversationItemView>(); 70 private final HashMap<Long, LeaveBehindItem> mFadeLeaveBehindItems = 71 new HashMap<Long, LeaveBehindItem>(); 72 /** The current account */ 73 private Account mAccount; 74 private final Context mContext; 75 private final ConversationSelectionSet mBatchConversations; 76 private Runnable mCountDown; 77 private Handler mHandler; 78 protected long mLastLeaveBehind = -1; 79 80 /** 81 * The next action to perform. Do not read or write this. All accesses should 82 * be in {@link #performAndSetNextAction(DestructiveAction)} which commits the 83 * previous action, if any. 84 */ 85 private ListItemsRemovedListener mPendingDestruction; 86 /** 87 * A destructive action that refreshes the list and performs no other action. 88 */ 89 private final ListItemsRemovedListener mRefreshAction = new ListItemsRemovedListener() { 90 @Override 91 public void onListItemsRemoved() { 92 notifyDataSetChanged(); 93 } 94 }; 95 96 public interface Listener { 97 void onAnimationEnd(AnimatedAdapter adapter); 98 } 99 100 private View mFooter; 101 private boolean mShowFooter; 102 private Folder mFolder; 103 private final SwipeableListView mListView; 104 private boolean mSwipeEnabled; 105 private final HashMap<Long, LeaveBehindItem> mLeaveBehindItems = Maps.newHashMap(); 106 /** True if priority inbox markers are enabled, false otherwise. */ 107 private boolean mPriorityMarkersEnabled; 108 private ControllableActivity mActivity; 109 private final AccountObserver mAccountListener = new AccountObserver() { 110 @Override 111 public void onChanged(Account newAccount) { 112 setAccount(newAccount); 113 notifyDataSetChanged(); 114 } 115 }; 116 117 private final void setAccount(Account newAccount) { 118 mAccount = newAccount; 119 mPriorityMarkersEnabled = mAccount.settings.priorityArrowsEnabled; 120 mSwipeEnabled = mAccount.supportsCapability(UIProvider.AccountCapabilities.UNDO); 121 } 122 123 /** 124 * Used only for debugging. 125 */ 126 private static final String LOG_TAG = LogTag.getLogTag(); 127 128 public AnimatedAdapter(Context context, int textViewResourceId, ConversationCursor cursor, 129 ConversationSelectionSet batch, 130 ControllableActivity activity, SwipeableListView listView) { 131 super(context, textViewResourceId, cursor, UIProvider.CONVERSATION_PROJECTION, null, 0); 132 mContext = context; 133 mBatchConversations = batch; 134 setAccount(mAccountListener.initialize(activity.getAccountController())); 135 mActivity = activity; 136 mShowFooter = false; 137 mListView = listView; 138 mHandler = new Handler(); 139 if (sDismissAllDelay == -1) { 140 sDismissAllDelay = 141 context.getResources().getInteger(R.integer.dismiss_all_leavebehinds_delay); 142 } 143 } 144 145 public final void destroy() { 146 // Set a null cursor in the adapter 147 swapCursor(null); 148 mAccountListener.unregisterAndDestroy(); 149 } 150 151 @Override 152 public int getCount() { 153 final int count = super.getCount(); 154 return mShowFooter ? count + 1 : count; 155 } 156 157 public void setUndo(boolean undo) { 158 if (undo && !mLastDeletingItems.isEmpty()) { 159 mUndoingItems.addAll(mLastDeletingItems); 160 mLastDeletingItems.clear(); 161 // Start animation 162 notifyDataSetChanged(); 163 performAndSetNextAction(mRefreshAction); 164 } 165 } 166 167 public void setSwipeUndo(boolean undo) { 168 if (undo && !mLastDeletingItems.isEmpty()) { 169 mSwipeUndoingItems.addAll(mLastDeletingItems); 170 mLastDeletingItems.clear(); 171 // Start animation 172 notifyDataSetChanged(); 173 performAndSetNextAction(mRefreshAction); 174 } 175 } 176 177 public View createConversationItemView(SwipeableConversationItemView view, Context context, 178 Conversation conv) { 179 if (view == null) { 180 view = new SwipeableConversationItemView(context, mAccount.name); 181 } 182 view.bind(conv, mActivity, mBatchConversations, mFolder, 183 mAccount != null ? mAccount.settings.hideCheckboxes : false, mSwipeEnabled, 184 mPriorityMarkersEnabled, this); 185 return view; 186 } 187 188 @Override 189 public boolean hasStableIds() { 190 return true; 191 } 192 193 @Override 194 public int getViewTypeCount() { 195 // TYPE_VIEW_CONVERSATION, TYPE_VIEW_DELETING, TYPE_VIEW_UNDOING, and 196 // TYPE_VIEW_FOOTER, TYPE_VIEW_LEAVEBEHIND. 197 return 5; 198 } 199 200 @Override 201 public int getItemViewType(int position) { 202 // Try to recycle views. 203 if (mShowFooter && position == super.getCount()) { 204 return TYPE_VIEW_FOOTER; 205 } else if (hasLeaveBehinds() || isAnimating()) { 206 // Setting as type -1 means the recycler won't take this view and 207 // return it in get view. This is a bit of a "hammer" in that it 208 // won't let even safe views be recycled here, 209 // but its safer and cheaper than trying to determine individual 210 // types. In a future release, use position/id map to try to make 211 // this cleaner / faster to determine if the view is animating. 212 return TYPE_VIEW_DONT_RECYCLE; 213 } 214 return TYPE_VIEW_CONVERSATION; 215 } 216 217 /** 218 * Deletes the selected conversations from the conversation list view with a 219 * translation and then a shrink. These conversations <b>must</b> have their 220 * {@link Conversation#position} set to the position of these conversations 221 * among the list. This will only remove the element from the list. The job 222 * of deleting the actual element is left to the the listener. This listener 223 * will be called when the animations are complete and is required to delete 224 * the conversation. 225 * @param conversations 226 * @param listener 227 */ 228 public void swipeDelete(Collection<Conversation> conversations, 229 ListItemsRemovedListener listener) { 230 delete(conversations, listener, mSwipeDeletingItems); 231 } 232 233 234 /** 235 * Deletes the selected conversations from the conversation list view by 236 * shrinking them away. These conversations <b>must</b> have their 237 * {@link Conversation#position} set to the position of these conversations 238 * among the list. This will only remove the element from the list. The job 239 * of deleting the actual element is left to the the listener. This listener 240 * will be called when the animations are complete and is required to delete 241 * the conversation. 242 * @param conversations 243 * @param listener 244 */ 245 public void delete(Collection<Conversation> conversations, ListItemsRemovedListener listener) { 246 delete(conversations, listener, mDeletingItems); 247 } 248 249 private void delete(Collection<Conversation> conversations, ListItemsRemovedListener listener, 250 HashSet<Long> list) { 251 // Clear out any remaining items and add the new ones 252 mLastDeletingItems.clear(); 253 // Since we are deleting new items, clear any remaining undo items 254 mUndoingItems.clear(); 255 256 final int startPosition = mListView.getFirstVisiblePosition(); 257 final int endPosition = mListView.getLastVisiblePosition(); 258 259 // Only animate visible items 260 for (Conversation c: conversations) { 261 if (c.position >= startPosition && c.position <= endPosition) { 262 mLastDeletingItems.add(c.id); 263 list.add(c.id); 264 } 265 } 266 267 if (list.isEmpty()) { 268 // If we have no deleted items on screen, skip the animation 269 listener.onListItemsRemoved(); 270 } else { 271 performAndSetNextAction(listener); 272 } 273 notifyDataSetChanged(); 274 } 275 276 @Override 277 public View getView(int position, View convertView, ViewGroup parent) { 278 if (mShowFooter && position == super.getCount()) { 279 return mFooter; 280 } 281 ConversationCursor cursor = (ConversationCursor) getItem(position); 282 Conversation conv = new Conversation(cursor); 283 if (isPositionUndoing(conv.id)) { 284 return getUndoingView(position, conv, parent, false /* don't show swipe background */); 285 } if (isPositionUndoingSwipe(conv.id)) { 286 return getUndoingView(position, conv, parent, true /* show swipe background */); 287 } else if (isPositionDeleting(conv.id)) { 288 return getDeletingView(position, conv, parent, false); 289 } else if (isPositionSwipeDeleting(conv.id)) { 290 return getDeletingView(position, conv, parent, true); 291 } 292 if (hasFadeLeaveBehinds()) { 293 if(isPositionFadeLeaveBehind(conv)) { 294 LeaveBehindItem fade = getFadeLeaveBehindItem(position, conv); 295 fade.startAnimation(mActivity.getViewMode(), this); 296 return fade; 297 } 298 } 299 if (hasLeaveBehinds()) { 300 if (isPositionLeaveBehind(conv)) { 301 LeaveBehindItem fadeIn = getLeaveBehindItem(conv); 302 fadeIn.startFadeInAnimation(hasFadeLeaveBehinds()); 303 return fadeIn; 304 } 305 } 306 if (convertView != null && !(convertView instanceof SwipeableConversationItemView)) { 307 LogUtils.w(LOG_TAG, "Incorrect convert view received; nulling it out"); 308 convertView = newView(mContext, cursor, parent); 309 } else if (convertView != null) { 310 ((SwipeableConversationItemView) convertView).reset(); 311 } 312 return createConversationItemView((SwipeableConversationItemView) convertView, mContext, 313 conv); 314 } 315 316 private boolean hasLeaveBehinds() { 317 return !mLeaveBehindItems.isEmpty(); 318 } 319 320 private boolean hasFadeLeaveBehinds() { 321 return !mFadeLeaveBehindItems.isEmpty(); 322 } 323 324 public LeaveBehindItem setupLeaveBehind(Conversation target, ToastBarOperation undoOp, 325 int deletedRow) { 326 mLastLeaveBehind = target.id; 327 fadeOutLeaveBehindItems(); 328 boolean isWide = ConversationItemViewCoordinates.isWideMode(ConversationItemViewCoordinates 329 .getMode(mContext, mActivity.getViewMode())); 330 LeaveBehindItem leaveBehind = (LeaveBehindItem) LayoutInflater.from(mContext).inflate( 331 isWide? R.layout.swipe_leavebehind_wide : R.layout.swipe_leavebehind, null); 332 leaveBehind.bindOperations(deletedRow, mAccount, this, undoOp, target, mFolder); 333 mLeaveBehindItems.put(target.id, leaveBehind); 334 mLastDeletingItems.add(target.id); 335 return leaveBehind; 336 } 337 338 public void fadeOutSpecificLeaveBehindItem(long id) { 339 if (mLastLeaveBehind == id) { 340 mLastLeaveBehind = -1; 341 } 342 startFadeOutLeaveBehindItemsAnimations(); 343 } 344 345 // This should kick off a timer such that there is a minimum time each item 346 // shows up before being dismissed. That way if the user is swiping away 347 // items in rapid succession, their finger position is maintained. 348 public void fadeOutLeaveBehindItems() { 349 if (mCountDown == null) { 350 mCountDown = new Runnable() { 351 @Override 352 public void run() { 353 startFadeOutLeaveBehindItemsAnimations(); 354 } 355 }; 356 } else { 357 mHandler.removeCallbacks(mCountDown); 358 } 359 // Clear all the text since these are no longer clickable 360 Iterator<Entry<Long, LeaveBehindItem>> i = mLeaveBehindItems.entrySet().iterator(); 361 LeaveBehindItem item; 362 while (i.hasNext()) { 363 item = i.next().getValue(); 364 Conversation conv = item.getData(); 365 if (mLastLeaveBehind == -1 || conv.id != mLastLeaveBehind) { 366 item.makeInert(); 367 } 368 } 369 mHandler.postDelayed(mCountDown, sDismissAllDelay); 370 } 371 372 protected void startFadeOutLeaveBehindItemsAnimations() { 373 final int startPosition = mListView.getFirstVisiblePosition(); 374 final int endPosition = mListView.getLastVisiblePosition(); 375 376 if (hasLeaveBehinds()) { 377 // If the item is visible, fade it out. Otherwise, just remove 378 // it. 379 Iterator<Entry<Long, LeaveBehindItem>> i = mLeaveBehindItems.entrySet().iterator(); 380 LeaveBehindItem item; 381 while (i.hasNext()) { 382 item = i.next().getValue(); 383 Conversation conv = item.getData(); 384 if (mLastLeaveBehind == -1 || conv.id != mLastLeaveBehind) { 385 if (conv.position >= startPosition && conv.position <= endPosition) { 386 mFadeLeaveBehindItems.put(conv.id, item); 387 } else { 388 item.commit(); 389 } 390 i.remove(); 391 } 392 } 393 } 394 if (!mLastDeletingItems.isEmpty()) { 395 mLastDeletingItems.clear(); 396 } 397 notifyDataSetChanged(); 398 } 399 400 public SwipeableListView getListView() { 401 return mListView; 402 } 403 404 public void commitLeaveBehindItems(boolean animate) { 405 // Remove any previously existing leave behinds. 406 boolean changed = false; 407 if (hasLeaveBehinds()) { 408 for (LeaveBehindItem item : mLeaveBehindItems.values()) { 409 if (animate) { 410 item.dismiss(); 411 } else { 412 item.commit(); 413 } 414 } 415 changed = true; 416 mLastLeaveBehind = -1; 417 mLeaveBehindItems.clear(); 418 } 419 if (hasFadeLeaveBehinds() && !animate) { 420 // Find any fading leave behind items and commit them all, too. 421 for (LeaveBehindItem item : mFadeLeaveBehindItems.values()) { 422 item.commit(); 423 } 424 mFadeLeaveBehindItems.clear(); 425 changed = true; 426 } 427 if (!mLastDeletingItems.isEmpty()) { 428 mLastDeletingItems.clear(); 429 changed = true; 430 } 431 if (changed) { 432 notifyDataSetChanged(); 433 } 434 } 435 436 private LeaveBehindItem getLeaveBehindItem(Conversation target) { 437 return mLeaveBehindItems.get(target.id); 438 } 439 440 private LeaveBehindItem getFadeLeaveBehindItem(int position, Conversation target) { 441 return mFadeLeaveBehindItems.get(target.id); 442 } 443 444 @Override 445 public long getItemId(int position) { 446 if (mShowFooter && position == super.getCount()) { 447 return -1; 448 } 449 return super.getItemId(position); 450 } 451 452 private View getDeletingView(int position, Conversation conversation, ViewGroup parent, 453 boolean swipe) { 454 conversation.position = position; 455 SwipeableConversationItemView deletingView = mAnimatingViews.get(conversation.id); 456 if (deletingView == null) { 457 // The undo animation consists of fading in the conversation that 458 // had been destroyed. 459 deletingView = newConversationItemView(position, parent, conversation); 460 deletingView.startDeleteAnimation(this, swipe); 461 } 462 return deletingView; 463 } 464 465 private View getUndoingView(int position, Conversation conv, ViewGroup parent, boolean swipe) { 466 conv.position = position; 467 SwipeableConversationItemView undoView = mAnimatingViews.get(conv.id); 468 if (undoView == null) { 469 // The undo animation consists of fading in the conversation that 470 // had been destroyed. 471 undoView = newConversationItemView(position, parent, conv); 472 undoView.startUndoAnimation(mActivity.getViewMode(), this, swipe); 473 } 474 return undoView; 475 } 476 477 @Override 478 public View newView(Context context, Cursor cursor, ViewGroup parent) { 479 SwipeableConversationItemView view = new SwipeableConversationItemView(context, 480 mAccount.name); 481 return view; 482 } 483 484 @Override 485 public void bindView(View view, Context context, Cursor cursor) { 486 if (! (view instanceof SwipeableConversationItemView)) { 487 return; 488 } 489 ((SwipeableConversationItemView) view).bind(cursor, mActivity, mBatchConversations, mFolder, 490 mAccount != null ? mAccount.settings.hideCheckboxes : false, 491 mSwipeEnabled, mPriorityMarkersEnabled, this); 492 } 493 494 private SwipeableConversationItemView newConversationItemView(int position, ViewGroup parent, 495 Conversation conversation) { 496 SwipeableConversationItemView view = (SwipeableConversationItemView) super.getView( 497 position, null, parent); 498 view.reset(); 499 view.bind(conversation, mActivity, mBatchConversations, mFolder, 500 mAccount != null ? mAccount.settings.hideCheckboxes : false, mSwipeEnabled, 501 mPriorityMarkersEnabled, this); 502 mAnimatingViews.put(conversation.id, view); 503 return view; 504 } 505 506 @Override 507 public Object getItem(int position) { 508 if (mShowFooter && position == super.getCount()) { 509 return mFooter; 510 } 511 return super.getItem(position); 512 } 513 514 private boolean isPositionDeleting(long id) { 515 return mDeletingItems.contains(id); 516 } 517 518 private boolean isPositionSwipeDeleting(long id) { 519 return mSwipeDeletingItems.contains(id); 520 } 521 522 private boolean isPositionUndoing(long id) { 523 return mUndoingItems.contains(id); 524 } 525 526 private boolean isPositionUndoingSwipe(long id) { 527 return mSwipeUndoingItems.contains(id); 528 } 529 530 private boolean isPositionLeaveBehind(Conversation conv) { 531 return hasLeaveBehinds() 532 && mLeaveBehindItems.containsKey(conv.id) 533 && conv.isMostlyDead(); 534 } 535 536 private boolean isPositionFadeLeaveBehind(Conversation conv) { 537 return hasFadeLeaveBehinds() 538 && mFadeLeaveBehindItems.containsKey(conv.id) 539 && conv.isMostlyDead(); 540 } 541 542 @Override 543 public void onAnimationStart(Animator animation) { 544 if (!mUndoingItems.isEmpty()) { 545 mDeletingItems.clear(); 546 mLastDeletingItems.clear(); 547 mSwipeDeletingItems.clear(); 548 } else { 549 mUndoingItems.clear(); 550 } 551 } 552 553 /** 554 * Performs the pending destruction, if any and assigns the next pending action. 555 * @param next The next action that is to be performed, possibly null (if no next action is 556 * needed). 557 */ 558 private final void performAndSetNextAction(ListItemsRemovedListener next) { 559 if (mPendingDestruction != null) { 560 mPendingDestruction.onListItemsRemoved(); 561 } 562 mPendingDestruction = next; 563 } 564 565 @Override 566 public void onAnimationEnd(Animator animation) { 567 Object obj; 568 if (animation instanceof AnimatorSet) { 569 AnimatorSet set = (AnimatorSet) animation; 570 obj = ((ObjectAnimator) set.getChildAnimations().get(0)).getTarget(); 571 } else { 572 obj = ((ObjectAnimator) animation).getTarget(); 573 } 574 updateAnimatingConversationItems(obj, mSwipeDeletingItems); 575 updateAnimatingConversationItems(obj, mDeletingItems); 576 updateAnimatingConversationItems(obj, mSwipeUndoingItems); 577 updateAnimatingConversationItems(obj, mUndoingItems); 578 if (hasFadeLeaveBehinds() && obj instanceof LeaveBehindItem) { 579 LeaveBehindItem objItem = (LeaveBehindItem) obj; 580 clearLeaveBehind(objItem.getConversationId()); 581 objItem.commit(); 582 // The view types have changed, since the animating views are gone. 583 notifyDataSetChanged(); 584 } 585 586 if (!isAnimating()) { 587 mActivity.onAnimationEnd(this); 588 } 589 } 590 591 private void updateAnimatingConversationItems(Object obj, HashSet<Long> items) { 592 if (!items.isEmpty()) { 593 if (obj instanceof ConversationItemView) { 594 final ConversationItemView target = (ConversationItemView) obj; 595 final long id = target.getConversation().id; 596 items.remove(id); 597 mAnimatingViews.remove(id); 598 if (items.isEmpty()) { 599 performAndSetNextAction(null); 600 notifyDataSetChanged(); 601 } 602 } 603 } 604 } 605 606 @Override 607 public boolean areAllItemsEnabled() { 608 // The animating positions are not enabled. 609 return false; 610 } 611 612 @Override 613 public boolean isEnabled(int position) { 614 return !isPositionDeleting(position) && !isPositionUndoing(position); 615 } 616 617 @Override 618 public void onAnimationCancel(Animator animation) { 619 onAnimationEnd(animation); 620 } 621 622 @Override 623 public void onAnimationRepeat(Animator animation) { 624 } 625 626 public void showFooter() { 627 setFooterVisibility(true); 628 } 629 630 public void hideFooter() { 631 setFooterVisibility(false); 632 } 633 634 public void setFooterVisibility(boolean show) { 635 if (mShowFooter != show) { 636 mShowFooter = show; 637 notifyDataSetChanged(); 638 } 639 } 640 641 public void addFooter(View footerView) { 642 mFooter = footerView; 643 } 644 645 public void setFolder(Folder folder) { 646 mFolder = folder; 647 } 648 649 public void clearLeaveBehind(long itemId) { 650 if (hasLeaveBehinds() && mLeaveBehindItems.containsKey(itemId)) { 651 mLeaveBehindItems.remove(itemId); 652 } else if (hasFadeLeaveBehinds()) { 653 mFadeLeaveBehindItems.remove(itemId); 654 } else { 655 LogUtils.d(LOG_TAG, "Trying to clear a non-existant leave behind"); 656 } 657 if (mLastLeaveBehind == itemId) { 658 mLastLeaveBehind = -1; 659 } 660 } 661 662 public void onSaveInstanceState(Bundle outState) { 663 long[] lastDeleting = new long[mLastDeletingItems.size()]; 664 for (int i = 0; i < lastDeleting.length; i++) { 665 lastDeleting[i] = mLastDeletingItems.get(i); 666 } 667 outState.putLongArray(LAST_DELETING_ITEMS, lastDeleting); 668 if (hasLeaveBehinds()) { 669 if (mLastLeaveBehind != -1) { 670 outState.putParcelable(LEAVE_BEHIND_ITEM_DATA, 671 mLeaveBehindItems.get(mLastLeaveBehind).getLeaveBehindData()); 672 outState.putLong(LEAVE_BEHIND_ITEM_ID, mLastLeaveBehind); 673 } 674 for (LeaveBehindItem item : mLeaveBehindItems.values()) { 675 if (mLastLeaveBehind == -1 || item.getData().id != mLastLeaveBehind) { 676 item.commit(); 677 } 678 } 679 } 680 } 681 682 public void onRestoreInstanceState(Bundle outState) { 683 if (outState.containsKey(LAST_DELETING_ITEMS)) { 684 final long[] lastDeleting = outState.getLongArray(LAST_DELETING_ITEMS); 685 for (int i = 0; i < lastDeleting.length; i++) { 686 mLastDeletingItems.add(lastDeleting[i]); 687 } 688 } 689 if (outState.containsKey(LEAVE_BEHIND_ITEM_DATA)) { 690 LeaveBehindData left = 691 (LeaveBehindData) outState.getParcelable(LEAVE_BEHIND_ITEM_DATA); 692 mLeaveBehindItems.put(outState.getLong(LEAVE_BEHIND_ITEM_ID), 693 setupLeaveBehind(left.data, left.op, left.data.position)); 694 } 695 } 696 697 /** 698 * Return if the adapter is in the process of animating anything. 699 */ 700 public boolean isAnimating() { 701 return !mUndoingItems.isEmpty() 702 || !mSwipeUndoingItems.isEmpty() 703 || hasFadeLeaveBehinds() 704 || !mDeletingItems.isEmpty() 705 || !mSwipeDeletingItems.isEmpty(); 706 } 707 708 /** 709 * Get the ConversationCursor associated with this adapter. 710 */ 711 public ConversationCursor getConversationCursor() { 712 return (ConversationCursor) getCursor(); 713 } 714} 715