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.launcher3.notification;
  
  import android.animation.Animator;
  import android.animation.AnimatorListenerAdapter;
  import android.animation.AnimatorSet;
  import android.animation.ObjectAnimator;
  import android.content.Context;
  import android.content.res.Resources;
  import android.graphics.Rect;
  import android.util.AttributeSet;
  import android.view.Gravity;
  import android.view.View;
  import android.widget.FrameLayout;
  import android.widget.LinearLayout;
  
  import com.android.launcher3.R;
  import com.android.launcher3.Utilities;
  import com.android.launcher3.anim.PropertyListBuilder;
  import com.android.launcher3.anim.PropertyResetListener;
  import com.android.launcher3.util.Themes;
  
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  
  /**
   * A {@link FrameLayout} that contains only icons of notifications.
   * If there are more than {@link #MAX_FOOTER_NOTIFICATIONS} icons, we add a "..." overflow.
   */
  public class NotificationFooterLayout extends FrameLayout {
  
      public interface IconAnimationEndListener {
          void onIconAnimationEnd(NotificationInfo animatedNotification);
      }
  
      private static final int MAX_FOOTER_NOTIFICATIONS = 5;
  
      private static final Rect sTempRect = new Rect();
  
      private final List<NotificationInfo> mNotifications = new ArrayList<>();
      private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
      private final boolean mRtl;
      private final int mBackgroundColor;
  
      FrameLayout.LayoutParams mIconLayoutParams;
      private View mOverflowEllipsis;
      private LinearLayout mIconRow;
      private NotificationItemView mContainer;
  
      public NotificationFooterLayout(Context context) {
          this(context, null, 0);
      }
  
      public NotificationFooterLayout(Context context, AttributeSet attrs) {
          this(context, attrs, 0);
      }
  
      public NotificationFooterLayout(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
  
          Resources res = getResources();
          mRtl = Utilities.isRtl(res);
  
          int iconSize = res.getDimensionPixelSize(R.dimen.notification_footer_icon_size);
          mIconLayoutParams = new LayoutParams(iconSize, iconSize);
          mIconLayoutParams.gravity = Gravity.CENTER_VERTICAL;
          // Compute margin start for each icon such that the icons between the first one
          // and the ellipsis are evenly spaced out.
          int paddingEnd = res.getDimensionPixelSize(R.dimen.notification_footer_icon_row_padding);
          int ellipsisSpace = res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_offset)
                  + res.getDimensionPixelSize(R.dimen.horizontal_ellipsis_size);
          int footerWidth = res.getDimensionPixelSize(R.dimen.bg_popup_item_width);
          int availableIconRowSpace = footerWidth - paddingEnd - ellipsisSpace
                  - iconSize * MAX_FOOTER_NOTIFICATIONS;
          mIconLayoutParams.setMarginStart(availableIconRowSpace / MAX_FOOTER_NOTIFICATIONS);
  
          mBackgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
      }
  
      @Override
      protected void onFinishInflate() {
          super.onFinishInflate();
          mOverflowEllipsis = findViewById(R.id.overflow);
          mIconRow = findViewById(R.id.icon_row);
      }
  
      void setContainer(NotificationItemView container) {
          mContainer = container;
      }
  
      /**
       * Keep track of the NotificationInfo, and then update the UI when
       * {@link #commitNotificationInfos()} is called.
       */
      public void addNotificationInfo(final NotificationInfo notificationInfo) {
          if (mNotifications.size() < MAX_FOOTER_NOTIFICATIONS) {
              mNotifications.add(notificationInfo);
          } else {
              mOverflowNotifications.add(notificationInfo);
          }
      }
  
      /**
       * Adds icons and potentially overflow text for all of the NotificationInfo's
       * added using {@link #addNotificationInfo(NotificationInfo)}.
       */
      public void commitNotificationInfos() {
          mIconRow.removeAllViews();
  
          for (int i = 0; i < mNotifications.size(); i++) {
              NotificationInfo info = mNotifications.get(i);
              addNotificationIconForInfo(info);
          }
          updateOverflowEllipsisVisibility();
      }
  
      private void updateOverflowEllipsisVisibility() {
          mOverflowEllipsis.setVisibility(mOverflowNotifications.isEmpty() ? GONE : VISIBLE);
      }
  
      /**
       * Creates an icon for the given NotificationInfo, and adds it to the icon row.
       * @return the icon view that was added
       */
      private View addNotificationIconForInfo(NotificationInfo info) {
          View icon = new View(getContext());
          icon.setBackground(info.getIconForBackground(getContext(), mBackgroundColor));
          icon.setOnClickListener(info);
          icon.setTag(info);
          icon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
          mIconRow.addView(icon, 0, mIconLayoutParams);
          return icon;
      }
  
      public void animateFirstNotificationTo(Rect toBounds,
              final IconAnimationEndListener callback) {
          AnimatorSet animation = new AnimatorSet();
          final View firstNotification = mIconRow.getChildAt(mIconRow.getChildCount() - 1);
  
          Rect fromBounds = sTempRect;
          firstNotification.getGlobalVisibleRect(fromBounds);
          float scale = (float) toBounds.height() / fromBounds.height();
          Animator moveAndScaleIcon = new PropertyListBuilder().scale(scale)
                  .translationY(toBounds.top - fromBounds.top
                          + (fromBounds.height() * scale - fromBounds.height()) / 2)
                  .build(firstNotification);
          moveAndScaleIcon.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationEnd(Animator animation) {
                  callback.onIconAnimationEnd((NotificationInfo) firstNotification.getTag());
                  removeViewFromIconRow(firstNotification);
              }
          });
          animation.play(moveAndScaleIcon);
  
          // Shift all notifications (not the overflow) over to fill the gap.
          int gapWidth = mIconLayoutParams.width + mIconLayoutParams.getMarginStart();
          if (mRtl) {
              gapWidth = -gapWidth;
          }
          if (!mOverflowNotifications.isEmpty()) {
              NotificationInfo notification = mOverflowNotifications.remove(0);
              mNotifications.add(notification);
              View iconFromOverflow = addNotificationIconForInfo(notification);
              animation.play(ObjectAnimator.ofFloat(iconFromOverflow, ALPHA, 0, 1));
          }
          int numIcons = mIconRow.getChildCount() - 1; // All children besides the one leaving.
          // We have to reset the translation X to 0 when the new main notification
          // is removed from the footer.
          PropertyResetListener<View, Float> propertyResetListener
                  = new PropertyResetListener<>(TRANSLATION_X, 0f);
          for (int i = 0; i < numIcons; i++) {
              final View child = mIconRow.getChildAt(i);
              Animator shiftChild = ObjectAnimator.ofFloat(child, TRANSLATION_X, gapWidth);
              shiftChild.addListener(propertyResetListener);
              animation.play(shiftChild);
          }
          animation.start();
      }
  
      private void removeViewFromIconRow(View child) {
          mIconRow.removeView(child);
          mNotifications.remove(child.getTag());
          updateOverflowEllipsisVisibility();
          if (mIconRow.getChildCount() == 0) {
              // There are no more icons in the footer, so hide it.
              if (mContainer != null) {
                  mContainer.removeFooter();
              }
          }
      }
  
      public void trimNotifications(List<String> notifications) {
          if (!isAttachedToWindow() || mIconRow.getChildCount() == 0) {
              return;
          }
          Iterator<NotificationInfo> overflowIterator = mOverflowNotifications.iterator();
          while (overflowIterator.hasNext()) {
              if (!notifications.contains(overflowIterator.next().notificationKey)) {
                  overflowIterator.remove();
              }
          }
          for (int i = mIconRow.getChildCount() - 1; i >= 0; i--) {
              View child = mIconRow.getChildAt(i);
              NotificationInfo childInfo = (NotificationInfo) child.getTag();
              if (!notifications.contains(childInfo.notificationKey)) {
                  removeViewFromIconRow(child);
              }
          }
      }
  }
  