content: package com.android.ex.chips;
  
  import android.content.Context;
  import android.content.res.Resources;
  import android.graphics.Bitmap;
  import android.graphics.BitmapFactory;
  import android.graphics.Color;
  import android.graphics.PorterDuff;
  import android.graphics.drawable.Drawable;
  import android.graphics.drawable.StateListDrawable;
  import android.net.Uri;
  import androidx.annotation.DrawableRes;
  import androidx.annotation.IdRes;
  import androidx.annotation.LayoutRes;
  import androidx.annotation.Nullable;
  import androidx.core.view.MarginLayoutParamsCompat;
  import android.text.SpannableStringBuilder;
  import android.text.Spanned;
  import android.text.TextUtils;
  import android.text.style.ForegroundColorSpan;
  import android.text.util.Rfc822Tokenizer;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.View.OnClickListener;
  import android.view.ViewGroup;
  import android.view.ViewGroup.MarginLayoutParams;
  import android.widget.ImageView;
  import android.widget.TextView;
  
  import com.android.ex.chips.Queries.Query;
  
  /**
   * A class that inflates and binds the views in the dropdown list from
   * RecipientEditTextView.
   */
  public class DropdownChipLayouter {
      /**
       * The type of adapter that is requesting a chip layout.
       */
      public enum AdapterType {
          BASE_RECIPIENT,
          RECIPIENT_ALTERNATES,
          SINGLE_RECIPIENT
      }
  
      public interface ChipDeleteListener {
          void onChipDelete();
      }
  
      /**
       * Listener that handles the dismisses of the entries of the
       * {@link RecipientEntry#ENTRY_TYPE_PERMISSION_REQUEST} type.
       */
      public interface PermissionRequestDismissedListener {
  
          /**
           * Callback that occurs when user dismisses the item that asks user to grant permissions to
           * the app.
           */
          void onPermissionRequestDismissed();
      }
  
      private final LayoutInflater mInflater;
      private final Context mContext;
      private ChipDeleteListener mDeleteListener;
      private PermissionRequestDismissedListener mPermissionRequestDismissedListener;
      private Query mQuery;
      private int mAutocompleteDividerMarginStart;
  
      public DropdownChipLayouter(LayoutInflater inflater, Context context) {
          mInflater = inflater;
          mContext = context;
          mAutocompleteDividerMarginStart =
                  context.getResources().getDimensionPixelOffset(R.dimen.chip_wrapper_start_padding);
      }
  
      public void setQuery(Query query) {
          mQuery = query;
      }
  
      public void setDeleteListener(ChipDeleteListener listener) {
          mDeleteListener = listener;
      }
  
      public void setPermissionRequestDismissedListener(PermissionRequestDismissedListener listener) {
          mPermissionRequestDismissedListener = listener;
      }
  
      public void setAutocompleteDividerMarginStart(int autocompleteDividerMarginStart) {
          mAutocompleteDividerMarginStart = autocompleteDividerMarginStart;
      }
  
      /**
       * Layouts and binds recipient information to the view. If convertView is null, inflates a new
       * view with getItemLaytout().
       *
       * @param convertView The view to bind information to.
       * @param parent The parent to bind the view to if we inflate a new view.
       * @param entry The recipient entry to get information from.
       * @param position The position in the list.
       * @param type The adapter type that is requesting the bind.
       * @param constraint The constraint typed in the auto complete view.
       *
       * @return A view ready to be shown in the drop down list.
       */
      public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position,
          AdapterType type, String constraint) {
          return bindView(convertView, parent, entry, position, type, constraint, null);
      }
  
      /**
       * See {@link #bindView(View, ViewGroup, RecipientEntry, int, AdapterType, String)}
       * @param deleteDrawable a {@link android.graphics.drawable.StateListDrawable} representing
       *     the delete icon. android.R.attr.state_activated should map to the delete icon, and the
       *     default state can map to a drawable of your choice (or null for no drawable).
       */
      public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position,
              AdapterType type, String constraint, StateListDrawable deleteDrawable) {
          // Default to show all the information
          CharSequence[] styledResults = getStyledResults(constraint, entry);
          CharSequence displayName = styledResults[0];
          CharSequence destination = styledResults[1];
          boolean showImage = true;
          CharSequence destinationType = getDestinationType(entry);
  
          final View itemView = reuseOrInflateView(convertView, parent, type);
  
          final ViewHolder viewHolder = new ViewHolder(itemView);
  
          // Hide some information depending on the adapter type.
          switch (type) {
              case BASE_RECIPIENT:
                  if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) {
                      displayName = destination;
  
                      // We only show the destination for secondary entries, so clear it only for the
                      // first level.
                      if (entry.isFirstLevel()) {
                          destination = null;
                      }
                  }
  
                  if (!entry.isFirstLevel()) {
                      displayName = null;
                      showImage = false;
                  }
  
                  // For BASE_RECIPIENT set all top dividers except for the first one to be GONE.
                  if (viewHolder.topDivider != null) {
                      viewHolder.topDivider.setVisibility(position == 0 ? View.VISIBLE : View.GONE);
                      MarginLayoutParamsCompat.setMarginStart(
                              (MarginLayoutParams) viewHolder.topDivider.getLayoutParams(),
                              mAutocompleteDividerMarginStart);
                  }
                  if (viewHolder.bottomDivider != null) {
                      MarginLayoutParamsCompat.setMarginStart(
                              (MarginLayoutParams) viewHolder.bottomDivider.getLayoutParams(),
                              mAutocompleteDividerMarginStart);
                  }
                  break;
              case RECIPIENT_ALTERNATES:
                  if (position != 0) {
                      displayName = null;
                      showImage = false;
                  }
                  break;
              case SINGLE_RECIPIENT:
                  if (!PhoneUtil.isPhoneNumber(entry.getDestination())) {
                      destination = Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress();
                  }
                  destinationType = null;
          }
  
          // Bind the information to the view
          bindTextToView(displayName, viewHolder.displayNameView);
          bindTextToView(destination, viewHolder.destinationView);
          bindTextToView(destinationType, viewHolder.destinationTypeView);
          bindIconToView(showImage, entry, viewHolder.imageView, type);
          bindDrawableToDeleteView(deleteDrawable, entry.getDisplayName(), viewHolder.deleteView);
          bindIndicatorToView(
                  entry.getIndicatorIconId(), entry.getIndicatorText(), viewHolder.indicatorView);
          bindPermissionRequestDismissView(viewHolder.permissionRequestDismissView);
  
          // Hide some view groups depending on the entry type
          final int entryType = entry.getEntryType();
          if (entryType == RecipientEntry.ENTRY_TYPE_PERSON) {
              setViewVisibility(viewHolder.personViewGroup, View.VISIBLE);
              setViewVisibility(viewHolder.permissionViewGroup, View.GONE);
              setViewVisibility(viewHolder.permissionBottomDivider, View.GONE);
          } else if (entryType == RecipientEntry.ENTRY_TYPE_PERMISSION_REQUEST) {
              setViewVisibility(viewHolder.personViewGroup, View.GONE);
              setViewVisibility(viewHolder.permissionViewGroup, View.VISIBLE);
              setViewVisibility(viewHolder.permissionBottomDivider, View.VISIBLE);
          }
  
          return itemView;
      }
  
      /**
       * Returns a new view with {@link #getItemLayoutResId(AdapterType)}.
       */
      public View newView(AdapterType type) {
          return mInflater.inflate(getItemLayoutResId(type), null);
      }
  
      /**
       * Returns the same view, or inflates a new one if the given view was null.
       */
      protected View reuseOrInflateView(View convertView, ViewGroup parent, AdapterType type) {
          int itemLayout = getItemLayoutResId(type);
          switch (type) {
              case BASE_RECIPIENT:
              case RECIPIENT_ALTERNATES:
                  break;
              case SINGLE_RECIPIENT:
                  itemLayout = getAlternateItemLayoutResId(type);
                  break;
          }
          return convertView != null ? convertView : mInflater.inflate(itemLayout, parent, false);
      }
  
      /**
       * Binds the text to the given text view. If the text was null, hides the text view.
       */
      protected void bindTextToView(CharSequence text, TextView view) {
          if (view == null) {
              return;
          }
  
          if (text != null) {
              view.setText(text);
              view.setVisibility(View.VISIBLE);
          } else {
              view.setVisibility(View.GONE);
          }
      }
  
      /**
       * Binds the avatar icon to the image view. If we don't want to show the image, hides the
       * image view.
       */
      protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view,
          AdapterType type) {
          if (view == null) {
              return;
          }
  
          if (showImage) {
              switch (type) {
                  case BASE_RECIPIENT:
                      byte[] photoBytes = entry.getPhotoBytes();
                      if (photoBytes != null && photoBytes.length > 0) {
                          final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
                              photoBytes.length);
                          view.setImageBitmap(photo);
                      } else {
                          view.setImageResource(getDefaultPhotoResId());
                      }
                      break;
                  case RECIPIENT_ALTERNATES:
                      Uri thumbnailUri = entry.getPhotoThumbnailUri();
                      if (thumbnailUri != null) {
                          // TODO: see if this needs to be done outside the main thread
                          // as it may be too slow to get immediately.
                          view.setImageURI(thumbnailUri);
                      } else {
                          view.setImageResource(getDefaultPhotoResId());
                      }
                      break;
                  case SINGLE_RECIPIENT:
                  default:
                      break;
              }
              view.setVisibility(View.VISIBLE);
          } else {
              view.setVisibility(View.GONE);
          }
      }
  
      protected void bindDrawableToDeleteView(final StateListDrawable drawable, String recipient,
              ImageView view) {
          if (view == null) {
              return;
          }
          if (drawable == null) {
              view.setVisibility(View.GONE);
          } else {
              final Resources res = mContext.getResources();
              view.setImageDrawable(drawable);
              view.setContentDescription(
                      res.getString(R.string.dropdown_delete_button_desc, recipient));
              if (mDeleteListener != null) {
                  view.setOnClickListener(new View.OnClickListener() {
                      @Override
                      public void onClick(View view) {
                          if (drawable.getCurrent() != null) {
                              mDeleteListener.onChipDelete();
                          }
                      }
                  });
              }
          }
      }
  
      protected void bindIndicatorToView(
              @DrawableRes int indicatorIconId, String indicatorText, TextView view) {
          if (view != null) {
              if (indicatorText != null || indicatorIconId != 0) {
                  view.setText(indicatorText);
                  view.setVisibility(View.VISIBLE);
                  final Drawable indicatorIcon;
                  if (indicatorIconId != 0) {
                      indicatorIcon = mContext.getDrawable(indicatorIconId).mutate();
                      indicatorIcon.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);
                  } else {
                      indicatorIcon = null;
                  }
                  view.setCompoundDrawablesRelativeWithIntrinsicBounds(
                          indicatorIcon, null, null, null);
              } else {
                  view.setVisibility(View.GONE);
              }
          }
      }
  
      protected void bindPermissionRequestDismissView(ImageView view) {
          if (view == null) {
              return;
          }
          view.setOnClickListener(new OnClickListener() {
              @Override
              public void onClick(View v) {
                  if (mPermissionRequestDismissedListener != null) {
                      mPermissionRequestDismissedListener.onPermissionRequestDismissed();
                  }
              }
          });
      }
  
      protected void setViewVisibility(View view, int visibility) {
          if (view != null) {
              view.setVisibility(visibility);
          }
      }
  
      protected CharSequence getDestinationType(RecipientEntry entry) {
          return mQuery.getTypeLabel(mContext.getResources(), entry.getDestinationType(),
              entry.getDestinationLabel()).toString().toUpperCase();
      }
  
      /**
       * Returns a layout id for each item inside auto-complete list.
       *
       * Each View must contain two TextViews (for display name and destination) and one ImageView
       * (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
       * {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
       */
      protected @LayoutRes int getItemLayoutResId(AdapterType type) {
          switch (type) {
              case BASE_RECIPIENT:
                  return R.layout.chips_autocomplete_recipient_dropdown_item;
              case RECIPIENT_ALTERNATES:
                  return R.layout.chips_recipient_dropdown_item;
              default:
                  return R.layout.chips_recipient_dropdown_item;
          }
      }
  
      /**
       * Returns a layout id for each item inside alternate auto-complete list.
       *
       * Each View must contain two TextViews (for display name and destination) and one ImageView
       * (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
       * {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
       */
      protected @LayoutRes int getAlternateItemLayoutResId(AdapterType type) {
          switch (type) {
              case BASE_RECIPIENT:
                  return R.layout.chips_autocomplete_recipient_dropdown_item;
              case RECIPIENT_ALTERNATES:
                  return R.layout.chips_recipient_dropdown_item;
              default:
                  return R.layout.chips_recipient_dropdown_item;
          }
      }
  
      /**
       * Returns a resource ID representing an image which should be shown when ther's no relevant
       * photo is available.
       */
      protected @DrawableRes int getDefaultPhotoResId() {
          return R.drawable.ic_contact_picture;
      }
  
      /**
       * Returns an id for the ViewGroup in an item View that contains the person ui elements.
       */
      protected @IdRes int getPersonGroupResId() {
          return R.id.chip_person_wrapper;
      }
  
      /**
       * Returns an id for TextView in an item View for showing a display name. By default
       * {@link android.R.id#title} is returned.
       */
      protected @IdRes int getDisplayNameResId() {
          return android.R.id.title;
      }
  
      /**
       * Returns an id for TextView in an item View for showing a destination
       * (an email address or a phone number).
       * By default {@link android.R.id#text1} is returned.
       */
      protected @IdRes int getDestinationResId() {
          return android.R.id.text1;
      }
  
      /**
       * Returns an id for TextView in an item View for showing the type of the destination.
       * By default {@link android.R.id#text2} is returned.
       */
      protected @IdRes int getDestinationTypeResId() {
          return android.R.id.text2;
      }
  
      /**
       * Returns an id for ImageView in an item View for showing photo image for a person. In default
       * {@link android.R.id#icon} is returned.
       */
      protected @IdRes int getPhotoResId() {
          return android.R.id.icon;
      }
  
      /**
       * Returns an id for ImageView in an item View for showing the delete button. In default
       * {@link android.R.id#icon1} is returned.
       */
      protected @IdRes int getDeleteResId() { return android.R.id.icon1; }
  
      /**
       * Returns an id for the ViewGroup in an item View that contains the request permission ui
       * elements.
       */
      protected @IdRes int getPermissionGroupResId() {
          return R.id.chip_permission_wrapper;
      }
  
      /**
       * Returns an id for ImageView in an item View for dismissing the permission request. In default
       * {@link android.R.id#icon2} is returned.
       */
      protected @IdRes int getPermissionRequestDismissResId() {
          return android.R.id.icon2;
      }
  
      /**
       * Given a constraint and a recipient entry, tries to find the constraint in the name and
       * destination in the recipient entry. A foreground font color style will be applied to the
       * section that matches the constraint. As soon as a match has been found, no further matches
       * are attempted.
       *
       * @param constraint A string that we will attempt to find within the results.
       * @param entry The recipient entry to style results for.
       *
       * @return An array of CharSequences, the length determined by the length of results. Each
       *     CharSequence will either be a styled SpannableString or just the input String.
       */
      protected CharSequence[] getStyledResults(@Nullable String constraint, RecipientEntry entry) {
        return getStyledResults(constraint, entry.getDisplayName(), entry.getDestination());
      }
  
      /**
       * Given a constraint and results, tries to find the constraint in those results, one at a time.
       * A foreground font color style will be applied to the section that matches the constraint. As
       * soon as a match has been found, no further matches are attempted.
       *
       * @param constraint A string that we will attempt to find within the results.
       * @param results Strings that may contain the constraint. The order given is the order used to
       *     search for the constraint.
       *
       * @return An array of CharSequences, the length determined by the length of results. Each
       *     CharSequence will either be a styled SpannableString or just the input String.
       */
      protected CharSequence[] getStyledResults(@Nullable String constraint, String... results) {
          if (isAllWhitespace(constraint)) {
              return results;
          }
  
          CharSequence[] styledResults = new CharSequence[results.length];
          boolean foundMatch = false;
          for (int i = 0; i < results.length; i++) {
              String result = results[i];
              if (result == null) {
                  continue;
              }
  
              if (!foundMatch) {
                  int index = result.toLowerCase().indexOf(constraint.toLowerCase());
                  if (index != -1) {
                      SpannableStringBuilder styled = SpannableStringBuilder.valueOf(result);
                      ForegroundColorSpan highlightSpan =
                              new ForegroundColorSpan(mContext.getResources().getColor(
                                      R.color.chips_dropdown_text_highlighted));
                      styled.setSpan(highlightSpan,
                              index, index + constraint.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                      styledResults[i] = styled;
                      foundMatch = true;
                      continue;
                  }
              }
              styledResults[i] = result;
          }
          return styledResults;
      }
  
      private static boolean isAllWhitespace(@Nullable String string) {
          if (TextUtils.isEmpty(string)) {
              return true;
          }
  
          for (int i = 0; i < string.length(); ++i) {
              if (!Character.isWhitespace(string.charAt(i))) {
                  return false;
              }
          }
  
          return true;
      }
  
      /**
       * A holder class the view. Uses the getters in DropdownChipLayouter to find the id of the
       * corresponding views.
       */
      protected class ViewHolder {
          public final ViewGroup personViewGroup;
          public final TextView displayNameView;
          public final TextView destinationView;
          public final TextView destinationTypeView;
          public final TextView indicatorView;
          public final ImageView imageView;
          public final ImageView deleteView;
          public final View topDivider;
          public final View bottomDivider;
          public final View permissionBottomDivider;
  
          public final ViewGroup permissionViewGroup;
          public final ImageView permissionRequestDismissView;
  
          public ViewHolder(View view) {
              personViewGroup = (ViewGroup) view.findViewById(getPersonGroupResId());
              displayNameView = (TextView) view.findViewById(getDisplayNameResId());
              destinationView = (TextView) view.findViewById(getDestinationResId());
              destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId());
              imageView = (ImageView) view.findViewById(getPhotoResId());
              deleteView = (ImageView) view.findViewById(getDeleteResId());
              topDivider = view.findViewById(R.id.chip_autocomplete_top_divider);
  
              bottomDivider = view.findViewById(R.id.chip_autocomplete_bottom_divider);
              permissionBottomDivider = view.findViewById(R.id.chip_permission_bottom_divider);
  
              indicatorView = (TextView) view.findViewById(R.id.chip_indicator_text);
  
              permissionViewGroup = (ViewGroup) view.findViewById(getPermissionGroupResId());
              permissionRequestDismissView =
                      (ImageView) view.findViewById(getPermissionRequestDismissResId());
          }
      }
  }
  