content: /*
   * Copyright (C) 2017 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.internal.widget;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.util.ArrayMap;
  import android.util.LongSparseArray;
  import android.util.Pools;
  
  import static com.android.internal.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
  import static com.android.internal.widget.RecyclerView.ViewHolder;
  import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR;
  import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_AND_DISAPPEAR;
  import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR_PRE_AND_POST;
  import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED;
  import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_POST;
  import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_PRE;
  import static com.android.internal.widget.ViewInfoStore.InfoRecord.FLAG_PRE_AND_POST;
  
  import com.android.internal.annotations.VisibleForTesting;
  
  /**
   * This class abstracts all tracking for Views to run animations.
   */
  class ViewInfoStore {
  
      private static final boolean DEBUG = false;
  
      /**
       * View data records for pre-layout
       */
      @VisibleForTesting
      final ArrayMap<ViewHolder, InfoRecord> mLayoutHolderMap = new ArrayMap<>();
  
      @VisibleForTesting
      final LongSparseArray<ViewHolder> mOldChangedHolders = new LongSparseArray<>();
  
      /**
       * Clears the state and all existing tracking data
       */
      void clear() {
          mLayoutHolderMap.clear();
          mOldChangedHolders.clear();
      }
  
      /**
       * Adds the item information to the prelayout tracking
       * @param holder The ViewHolder whose information is being saved
       * @param info The information to save
       */
      void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
          InfoRecord record = mLayoutHolderMap.get(holder);
          if (record == null) {
              record = InfoRecord.obtain();
              mLayoutHolderMap.put(holder, record);
          }
          record.preInfo = info;
          record.flags |= FLAG_PRE;
      }
  
      boolean isDisappearing(ViewHolder holder) {
          final InfoRecord record = mLayoutHolderMap.get(holder);
          return record != null && ((record.flags & FLAG_DISAPPEARED) != 0);
      }
  
      /**
       * Finds the ItemHolderInfo for the given ViewHolder in preLayout list and removes it.
       *
       * @param vh The ViewHolder whose information is being queried
       * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
       */
      @Nullable
      ItemHolderInfo popFromPreLayout(ViewHolder vh) {
          return popFromLayoutStep(vh, FLAG_PRE);
      }
  
      /**
       * Finds the ItemHolderInfo for the given ViewHolder in postLayout list and removes it.
       *
       * @param vh The ViewHolder whose information is being queried
       * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
       */
      @Nullable
      ItemHolderInfo popFromPostLayout(ViewHolder vh) {
          return popFromLayoutStep(vh, FLAG_POST);
      }
  
      private ItemHolderInfo popFromLayoutStep(ViewHolder vh, int flag) {
          int index = mLayoutHolderMap.indexOfKey(vh);
          if (index < 0) {
              return null;
          }
          final InfoRecord record = mLayoutHolderMap.valueAt(index);
          if (record != null && (record.flags & flag) != 0) {
              record.flags &= ~flag;
              final ItemHolderInfo info;
              if (flag == FLAG_PRE) {
                  info = record.preInfo;
              } else if (flag == FLAG_POST) {
                  info = record.postInfo;
              } else {
                  throw new IllegalArgumentException("Must provide flag PRE or POST");
              }
              // if not pre-post flag is left, clear.
              if ((record.flags & (FLAG_PRE | FLAG_POST)) == 0) {
                  mLayoutHolderMap.removeAt(index);
                  InfoRecord.recycle(record);
              }
              return info;
          }
          return null;
      }
  
      /**
       * Adds the given ViewHolder to the oldChangeHolders list
       * @param key The key to identify the ViewHolder.
       * @param holder The ViewHolder to store
       */
      void addToOldChangeHolders(long key, ViewHolder holder) {
          mOldChangedHolders.put(key, holder);
      }
  
      /**
       * Adds the given ViewHolder to the appeared in pre layout list. These are Views added by the
       * LayoutManager during a pre-layout pass. We distinguish them from other views that were
       * already in the pre-layout so that ItemAnimator can choose to run a different animation for
       * them.
       *
       * @param holder The ViewHolder to store
       * @param info The information to save
       */
      void addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info) {
          InfoRecord record = mLayoutHolderMap.get(holder);
          if (record == null) {
              record = InfoRecord.obtain();
              mLayoutHolderMap.put(holder, record);
          }
          record.flags |= FLAG_APPEAR;
          record.preInfo = info;
      }
  
      /**
       * Checks whether the given ViewHolder is in preLayout list
       * @param viewHolder The ViewHolder to query
       *
       * @return True if the ViewHolder is present in preLayout, false otherwise
       */
      boolean isInPreLayout(ViewHolder viewHolder) {
          final InfoRecord record = mLayoutHolderMap.get(viewHolder);
          return record != null && (record.flags & FLAG_PRE) != 0;
      }
  
      /**
       * Queries the oldChangeHolder list for the given key. If they are not tracked, simply returns
       * null.
       * @param key The key to be used to find the ViewHolder.
       *
       * @return A ViewHolder if exists or null if it does not exist.
       */
      ViewHolder getFromOldChangeHolders(long key) {
          return mOldChangedHolders.get(key);
      }
  
      /**
       * Adds the item information to the post layout list
       * @param holder The ViewHolder whose information is being saved
       * @param info The information to save
       */
      void addToPostLayout(ViewHolder holder, ItemHolderInfo info) {
          InfoRecord record = mLayoutHolderMap.get(holder);
          if (record == null) {
              record = InfoRecord.obtain();
              mLayoutHolderMap.put(holder, record);
          }
          record.postInfo = info;
          record.flags |= FLAG_POST;
      }
  
      /**
       * A ViewHolder might be added by the LayoutManager just to animate its disappearance.
       * This list holds such items so that we can animate / recycle these ViewHolders properly.
       *
       * @param holder The ViewHolder which disappeared during a layout.
       */
      void addToDisappearedInLayout(ViewHolder holder) {
          InfoRecord record = mLayoutHolderMap.get(holder);
          if (record == null) {
              record = InfoRecord.obtain();
              mLayoutHolderMap.put(holder, record);
          }
          record.flags |= FLAG_DISAPPEARED;
      }
  
      /**
       * Removes a ViewHolder from disappearing list.
       * @param holder The ViewHolder to be removed from the disappearing list.
       */
      void removeFromDisappearedInLayout(ViewHolder holder) {
          InfoRecord record = mLayoutHolderMap.get(holder);
          if (record == null) {
              return;
          }
          record.flags &= ~FLAG_DISAPPEARED;
      }
  
      void process(ProcessCallback callback) {
          for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
              final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
              final InfoRecord record = mLayoutHolderMap.removeAt(index);
              if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
                  // Appeared then disappeared. Not useful for animations.
                  callback.unused(viewHolder);
              } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
                  // Set as "disappeared" by the LayoutManager (addDisappearingView)
                  if (record.preInfo == null) {
                      // similar to appear disappear but happened between different layout passes.
                      // this can happen when the layout manager is using auto-measure
                      callback.unused(viewHolder);
                  } else {
                      callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
                  }
              } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
                  // Appeared in the layout but not in the adapter (e.g. entered the viewport)
                  callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
              } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
                  // Persistent in both passes. Animate persistence
                  callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
              } else if ((record.flags & FLAG_PRE) != 0) {
                  // Was in pre-layout, never been added to post layout
                  callback.processDisappeared(viewHolder, record.preInfo, null);
              } else if ((record.flags & FLAG_POST) != 0) {
                  // Was not in pre-layout, been added to post layout
                  callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
              } else if ((record.flags & FLAG_APPEAR) != 0) {
                  // Scrap view. RecyclerView will handle removing/recycling this.
              } else if (DEBUG) {
                  throw new IllegalStateException("record without any reasonable flag combination:/");
              }
              InfoRecord.recycle(record);
          }
      }
  
      /**
       * Removes the ViewHolder from all list
       * @param holder The ViewHolder which we should stop tracking
       */
      void removeViewHolder(ViewHolder holder) {
          for (int i = mOldChangedHolders.size() - 1; i >= 0; i--) {
              if (holder == mOldChangedHolders.valueAt(i)) {
                  mOldChangedHolders.removeAt(i);
                  break;
              }
          }
          final InfoRecord info = mLayoutHolderMap.remove(holder);
          if (info != null) {
              InfoRecord.recycle(info);
          }
      }
  
      void onDetach() {
          InfoRecord.drainCache();
      }
  
      public void onViewDetached(ViewHolder viewHolder) {
          removeFromDisappearedInLayout(viewHolder);
      }
  
      interface ProcessCallback {
          void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
                  @Nullable ItemHolderInfo postInfo);
          void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
                  ItemHolderInfo postInfo);
          void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
                  @NonNull ItemHolderInfo postInfo);
          void unused(ViewHolder holder);
      }
  
      static class InfoRecord {
          // disappearing list
          static final int FLAG_DISAPPEARED = 1;
          // appear in pre layout list
          static final int FLAG_APPEAR = 1 << 1;
          // pre layout, this is necessary to distinguish null item info
          static final int FLAG_PRE = 1 << 2;
          // post layout, this is necessary to distinguish null item info
          static final int FLAG_POST = 1 << 3;
          static final int FLAG_APPEAR_AND_DISAPPEAR = FLAG_APPEAR | FLAG_DISAPPEARED;
          static final int FLAG_PRE_AND_POST = FLAG_PRE | FLAG_POST;
          static final int FLAG_APPEAR_PRE_AND_POST = FLAG_APPEAR | FLAG_PRE | FLAG_POST;
          int flags;
          @Nullable ItemHolderInfo preInfo;
          @Nullable ItemHolderInfo postInfo;
          static Pools.Pool<InfoRecord> sPool = new Pools.SimplePool<>(20);
  
          private InfoRecord() {
          }
  
          static InfoRecord obtain() {
              InfoRecord record = sPool.acquire();
              return record == null ? new InfoRecord() : record;
          }
  
          static void recycle(InfoRecord record) {
              record.flags = 0;
              record.preInfo = null;
              record.postInfo = null;
              sPool.release(record);
          }
  
          static void drainCache() {
              //noinspection StatementWithEmptyBody
              while (sPool.acquire() != null);
          }
      }
  }
  