content: /*
   * Copyright (C) 2015 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.setupwizardlib.items;
  
  import android.content.res.TypedArray;
  import android.graphics.Rect;
  import android.graphics.drawable.Drawable;
  import android.graphics.drawable.LayerDrawable;
  import androidx.annotation.VisibleForTesting;
  import androidx.recyclerview.widget.RecyclerView;
  import android.util.Log;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.ViewGroup;
  import com.android.setupwizardlib.R;
  
  /**
   * An adapter used with RecyclerView to display an {@link ItemHierarchy}. The item hierarchy used to
   * create this adapter can be inflated by {@link com.android.setupwizardlib.items.ItemInflater} from
   * XML.
   */
  public class RecyclerItemAdapter extends RecyclerView.Adapter<ItemViewHolder>
      implements ItemHierarchy.Observer {
  
    private static final String TAG = "RecyclerItemAdapter";
  
    /**
     * A view tag set by {@link View#setTag(Object)}. If set on the root view of a layout, it will not
     * create the default background for the list item. This means the item will not have ripple touch
     * feedback by default.
     */
    public static final String TAG_NO_BACKGROUND = "noBackground";
  
    /** Listener for item selection in this adapter. */
    public interface OnItemSelectedListener {
  
      /**
       * Called when an item in this adapter is clicked.
       *
       * @param item The Item corresponding to the position being clicked.
       */
      void onItemSelected(IItem item);
    }
  
    private final ItemHierarchy itemHierarchy;
    private OnItemSelectedListener listener;
  
    public RecyclerItemAdapter(ItemHierarchy hierarchy) {
      itemHierarchy = hierarchy;
      itemHierarchy.registerObserver(this);
    }
  
    /**
     * Gets the item at the given position.
     *
     * @see ItemHierarchy#getItemAt(int)
     */
    public IItem getItem(int position) {
      return itemHierarchy.getItemAt(position);
    }
  
    @Override
    public long getItemId(int position) {
      IItem mItem = getItem(position);
      if (mItem instanceof AbstractItem) {
        final int id = ((AbstractItem) mItem).getId();
        return id > 0 ? id : RecyclerView.NO_ID;
      } else {
        return RecyclerView.NO_ID;
      }
    }
  
    @Override
    public int getItemCount() {
      return itemHierarchy.getCount();
    }
  
    @Override
    public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
      final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
      final View view = inflater.inflate(viewType, parent, false);
      final ItemViewHolder viewHolder = new ItemViewHolder(view);
  
      final Object viewTag = view.getTag();
      if (!TAG_NO_BACKGROUND.equals(viewTag)) {
        final TypedArray typedArray =
            parent.getContext().obtainStyledAttributes(R.styleable.SuwRecyclerItemAdapter);
        Drawable selectableItemBackground =
            typedArray.getDrawable(
                R.styleable.SuwRecyclerItemAdapter_android_selectableItemBackground);
        if (selectableItemBackground == null) {
          selectableItemBackground =
              typedArray.getDrawable(R.styleable.SuwRecyclerItemAdapter_selectableItemBackground);
        }
  
        Drawable background = view.getBackground();
        if (background == null) {
          background =
              typedArray.getDrawable(R.styleable.SuwRecyclerItemAdapter_android_colorBackground);
        }
  
        if (selectableItemBackground == null || background == null) {
          Log.e(
              TAG,
              "Cannot resolve required attributes."
                  + " selectableItemBackground="
                  + selectableItemBackground
                  + " background="
                  + background);
        } else {
          final Drawable[] layers = {background, selectableItemBackground};
          view.setBackgroundDrawable(new PatchedLayerDrawable(layers));
        }
  
        typedArray.recycle();
      }
  
      view.setOnClickListener(
          new View.OnClickListener() {
            @Override
            public void onClick(View view) {
              final IItem item = viewHolder.getItem();
              if (listener != null && item != null && item.isEnabled()) {
                listener.onItemSelected(item);
              }
            }
          });
  
      return viewHolder;
    }
  
    @Override
    public void onBindViewHolder(ItemViewHolder holder, int position) {
      final IItem item = getItem(position);
      holder.setEnabled(item.isEnabled());
      holder.setItem(item);
      item.onBindView(holder.itemView);
    }
  
    @Override
    public int getItemViewType(int position) {
      // Use layout resource as item view type. RecyclerView item type does not have to be
      // contiguous.
      IItem item = getItem(position);
      return item.getLayoutResource();
    }
  
    @Override
    public void onChanged(ItemHierarchy hierarchy) {
      notifyDataSetChanged();
    }
  
    @Override
    public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) {
      notifyItemRangeChanged(positionStart, itemCount);
    }
  
    @Override
    public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) {
      notifyItemRangeInserted(positionStart, itemCount);
    }
  
    @Override
    public void onItemRangeMoved(
        ItemHierarchy itemHierarchy, int fromPosition, int toPosition, int itemCount) {
      // There is no notifyItemRangeMoved
      // https://code.google.com/p/android/issues/detail?id=125984
      if (itemCount == 1) {
        notifyItemMoved(fromPosition, toPosition);
      } else {
        // If more than one, degenerate into the catch-all data set changed callback, since I'm
        // not sure how recycler view handles multiple calls to notifyItemMoved (if the result
        // is committed after every notification then naively calling
        // notifyItemMoved(from + i, to + i) is wrong).
        // Logging this in case this is a more common occurrence than expected.
        Log.i(TAG, "onItemRangeMoved with more than one item");
        notifyDataSetChanged();
      }
    }
  
    @Override
    public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) {
      notifyItemRangeRemoved(positionStart, itemCount);
    }
  
    /**
     * Find an item hierarchy within the root hierarchy.
     *
     * @see ItemHierarchy#findItemById(int)
     */
    public ItemHierarchy findItemById(int id) {
      return itemHierarchy.findItemById(id);
    }
  
    /** Gets the root item hierarchy in this adapter. */
    public ItemHierarchy getRootItemHierarchy() {
      return itemHierarchy;
    }
  
    /**
     * Sets the listener to listen for when user clicks on a item.
     *
     * @see OnItemSelectedListener
     */
    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
      this.listener = listener;
    }
  
    /**
     * Before Lollipop, LayerDrawable always return true in getPadding, even if the children layers do
     * not have any padding. Patch the implementation so that getPadding returns false if the padding
     * is empty.
     *
     * <p>When getPadding is true, the padding of the view will be replaced by the padding of the
     * drawable when {@link View#setBackgroundDrawable(Drawable)} is called. This patched class makes
     * sure layer drawables without padding does not clear out original padding on the view.
     */
    @VisibleForTesting
    static class PatchedLayerDrawable extends LayerDrawable {
  
      /** {@inheritDoc} */
      PatchedLayerDrawable(Drawable[] layers) {
        super(layers);
      }
  
      @Override
      public boolean getPadding(Rect padding) {
        final boolean superHasPadding = super.getPadding(padding);
        return superHasPadding
            && !(padding.left == 0 && padding.top == 0 && padding.right == 0 && padding.bottom == 0);
      }
    }
  }
  