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 android.view;
  
  import android.annotation.Nullable;
  import android.annotation.UnsupportedAppUsage;
  import android.app.AppOpsManager;
  import android.app.Notification;
  import android.content.Context;
  import android.content.res.Resources;
  import android.content.res.TypedArray;
  import android.graphics.Canvas;
  import android.graphics.Outline;
  import android.graphics.Rect;
  import android.graphics.drawable.Drawable;
  import android.util.ArraySet;
  import android.util.AttributeSet;
  import android.widget.ImageView;
  import android.widget.RemoteViews;
  
  import com.android.internal.R;
  import com.android.internal.widget.CachingIconView;
  
  import java.util.ArrayList;
  
  /**
   * A header of a notification view
   *
   * @hide
   */
  @RemoteViews.RemoteView
  public class NotificationHeaderView extends ViewGroup {
      public static final int NO_COLOR = Notification.COLOR_INVALID;
      private final int mChildMinWidth;
      private final int mContentEndMargin;
      private final int mGravity;
      private View mAppName;
      private View mHeaderText;
      private View mSecondaryHeaderText;
      private OnClickListener mExpandClickListener;
      private OnClickListener mAppOpsListener;
      private HeaderTouchListener mTouchListener = new HeaderTouchListener();
      private ImageView mExpandButton;
      private CachingIconView mIcon;
      private View mProfileBadge;
      private View mOverlayIcon;
      private View mCameraIcon;
      private View mMicIcon;
      private View mAppOps;
      private View mAudiblyAlertedIcon;
      private int mIconColor;
      private int mOriginalNotificationColor;
      private boolean mExpanded;
      private boolean mShowExpandButtonAtEnd;
      private boolean mShowWorkBadgeAtEnd;
      private int mHeaderTextMarginEnd;
      private Drawable mBackground;
      private boolean mEntireHeaderClickable;
      private boolean mExpandOnlyOnButton;
      private boolean mAcceptAllTouches;
      private int mTotalWidth;
  
      ViewOutlineProvider mProvider = new ViewOutlineProvider() {
          @Override
          public void getOutline(View view, Outline outline) {
              if (mBackground != null) {
                  outline.setRect(0, 0, getWidth(), getHeight());
                  outline.setAlpha(1f);
              }
          }
      };
  
      public NotificationHeaderView(Context context) {
          this(context, null);
      }
  
      @UnsupportedAppUsage
      public NotificationHeaderView(Context context, @Nullable AttributeSet attrs) {
          this(context, attrs, 0);
      }
  
      public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
          this(context, attrs, defStyleAttr, 0);
      }
  
      public NotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
          Resources res = getResources();
          mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
          mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end);
          mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);
  
          int[] attrIds = { android.R.attr.gravity };
          TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes);
          mGravity = ta.getInt(0, 0);
          ta.recycle();
      }
  
      @Override
      protected void onFinishInflate() {
          super.onFinishInflate();
          mAppName = findViewById(com.android.internal.R.id.app_name_text);
          mHeaderText = findViewById(com.android.internal.R.id.header_text);
          mSecondaryHeaderText = findViewById(com.android.internal.R.id.header_text_secondary);
          mExpandButton = findViewById(com.android.internal.R.id.expand_button);
          mIcon = findViewById(com.android.internal.R.id.icon);
          mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
          mCameraIcon = findViewById(com.android.internal.R.id.camera);
          mMicIcon = findViewById(com.android.internal.R.id.mic);
          mOverlayIcon = findViewById(com.android.internal.R.id.overlay);
          mAppOps = findViewById(com.android.internal.R.id.app_ops);
          mAudiblyAlertedIcon = findViewById(com.android.internal.R.id.alerted_icon);
      }
  
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
          final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
          int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth,
                  MeasureSpec.AT_MOST);
          int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight,
                  MeasureSpec.AT_MOST);
          int totalWidth = getPaddingStart();
          int iconWidth = getPaddingEnd();
          for (int i = 0; i < getChildCount(); i++) {
              final View child = getChildAt(i);
              if (child.getVisibility() == GONE) {
                  // We'll give it the rest of the space in the end
                  continue;
              }
              final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
              int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec,
                      lp.leftMargin + lp.rightMargin, lp.width);
              int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec,
                      lp.topMargin + lp.bottomMargin, lp.height);
              child.measure(childWidthSpec, childHeightSpec);
              if ((child == mExpandButton && mShowExpandButtonAtEnd)
                      || child == mProfileBadge
                      || child == mAppOps) {
                  iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
              } else {
                  totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
              }
          }
  
          // Ensure that there is at least enough space for the icons
          int endMargin = Math.max(mHeaderTextMarginEnd, iconWidth);
          if (totalWidth > givenWidth - endMargin) {
              int overFlow = totalWidth - givenWidth + endMargin;
              // We are overflowing, lets shrink the app name first
              overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName,
                      mChildMinWidth);
  
              // still overflowing, we shrink the header text
              overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0);
  
              // still overflowing, finally we shrink the secondary header text
              shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText,
                      0);
          }
          totalWidth += getPaddingEnd();
          mTotalWidth = Math.min(totalWidth, givenWidth);
          setMeasuredDimension(givenWidth, givenHeight);
      }
  
      private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView,
              int minimumWidth) {
          final int oldWidth = targetView.getMeasuredWidth();
          if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
              // we're still too big
              int newSize = Math.max(minimumWidth, oldWidth - overFlow);
              int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
              targetView.measure(childWidthSpec, heightSpec);
              overFlow -= oldWidth - newSize;
          }
          return overFlow;
      }
  
      @Override
      protected void onLayout(boolean changed, int l, int t, int r, int b) {
          int left = getPaddingStart();
          int end = getMeasuredWidth();
          final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0;
          if (centerAligned) {
              left += getMeasuredWidth() / 2 - mTotalWidth / 2;
          }
          int childCount = getChildCount();
          int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
          for (int i = 0; i < childCount; i++) {
              View child = getChildAt(i);
              if (child.getVisibility() == GONE) {
                  continue;
              }
              int childHeight = child.getMeasuredHeight();
              MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
              int layoutLeft;
              int layoutRight;
              int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f);
              int bottom = top + childHeight;
              if ((child == mExpandButton && mShowExpandButtonAtEnd)
                      || child == mProfileBadge
                      || child == mAppOps) {
                  if (end == getMeasuredWidth()) {
                      layoutRight = end - mContentEndMargin;
                  } else {
                      layoutRight = end - params.getMarginEnd();
                  }
                  layoutLeft = layoutRight - child.getMeasuredWidth();
                  end = layoutLeft - params.getMarginStart();
              } else {
                  left += params.getMarginStart();
                  int right = left + child.getMeasuredWidth();
                  layoutLeft = left;
                  layoutRight = right;
                  left = right + params.getMarginEnd();
              }
              if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
                  int ltrLeft = layoutLeft;
                  layoutLeft = getWidth() - layoutRight;
                  layoutRight = getWidth() - ltrLeft;
              }
              child.layout(layoutLeft, top, layoutRight, bottom);
          }
          updateTouchListener();
      }
  
      @Override
      public LayoutParams generateLayoutParams(AttributeSet attrs) {
          return new ViewGroup.MarginLayoutParams(getContext(), attrs);
      }
  
      /**
       * Set a {@link Drawable} to be displayed as a background on the header.
       */
      public void setHeaderBackgroundDrawable(Drawable drawable) {
          if (drawable != null) {
              setWillNotDraw(false);
              mBackground = drawable;
              mBackground.setCallback(this);
              setOutlineProvider(mProvider);
          } else {
              setWillNotDraw(true);
              mBackground = null;
              setOutlineProvider(null);
          }
          invalidate();
      }
  
      @Override
      protected void onDraw(Canvas canvas) {
          if (mBackground != null) {
              mBackground.setBounds(0, 0, getWidth(), getHeight());
              mBackground.draw(canvas);
          }
      }
  
      @Override
      protected boolean verifyDrawable(Drawable who) {
          return super.verifyDrawable(who) || who == mBackground;
      }
  
      @Override
      protected void drawableStateChanged() {
          if (mBackground != null && mBackground.isStateful()) {
              mBackground.setState(getDrawableState());
          }
      }
  
      private void updateTouchListener() {
          if (mExpandClickListener == null && mAppOpsListener == null) {
              setOnTouchListener(null);
              return;
          }
          setOnTouchListener(mTouchListener);
          mTouchListener.bindTouchRects();
      }
  
      /**
       * Sets onclick listener for app ops icons.
       */
      public void setAppOpsOnClickListener(OnClickListener l) {
          mAppOpsListener = l;
          mAppOps.setOnClickListener(mAppOpsListener);
          mCameraIcon.setOnClickListener(mAppOpsListener);
          mMicIcon.setOnClickListener(mAppOpsListener);
          mOverlayIcon.setOnClickListener(mAppOpsListener);
          updateTouchListener();
      }
  
      @Override
      public void setOnClickListener(@Nullable OnClickListener l) {
          mExpandClickListener = l;
          mExpandButton.setOnClickListener(mExpandClickListener);
          updateTouchListener();
      }
  
      @RemotableViewMethod
      public void setOriginalIconColor(int color) {
          mIconColor = color;
      }
  
      public int getOriginalIconColor() {
          return mIconColor;
      }
  
      @RemotableViewMethod
      public void setOriginalNotificationColor(int color) {
          mOriginalNotificationColor = color;
      }
  
      public int getOriginalNotificationColor() {
          return mOriginalNotificationColor;
      }
  
      @RemotableViewMethod
      public void setExpanded(boolean expanded) {
          mExpanded = expanded;
          updateExpandButton();
      }
  
      /**
       * Shows or hides 'app op in use' icons based on app usage.
       */
      public void showAppOpsIcons(ArraySet<Integer> appOps) {
          if (mOverlayIcon == null || mCameraIcon == null || mMicIcon == null || appOps == null) {
              return;
          }
  
          mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
                  ? View.VISIBLE : View.GONE);
          mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA)
                  ? View.VISIBLE : View.GONE);
          mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO)
                  ? View.VISIBLE : View.GONE);
      }
  
      /** Updates icon visibility based on the noisiness of the notification. */
      public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
          mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
      }
  
      private void updateExpandButton() {
          int drawableId;
          int contentDescriptionId;
          if (mExpanded) {
              drawableId = R.drawable.ic_collapse_notification;
              contentDescriptionId = R.string.expand_button_content_description_expanded;
          } else {
              drawableId = R.drawable.ic_expand_notification;
              contentDescriptionId = R.string.expand_button_content_description_collapsed;
          }
          mExpandButton.setImageDrawable(getContext().getDrawable(drawableId));
          mExpandButton.setColorFilter(mOriginalNotificationColor);
          mExpandButton.setContentDescription(mContext.getText(contentDescriptionId));
      }
  
      public void setShowWorkBadgeAtEnd(boolean showWorkBadgeAtEnd) {
          if (showWorkBadgeAtEnd != mShowWorkBadgeAtEnd) {
              setClipToPadding(!showWorkBadgeAtEnd);
              mShowWorkBadgeAtEnd = showWorkBadgeAtEnd;
          }
      }
  
      /**
       * Sets whether or not the expand button appears at the end of the NotificationHeaderView. If
       * both this and {@link #setShowWorkBadgeAtEnd(boolean)} have been set to true, then the
       * expand button will appear closer to the end than the work badge.
       */
      public void setShowExpandButtonAtEnd(boolean showExpandButtonAtEnd) {
          if (showExpandButtonAtEnd != mShowExpandButtonAtEnd) {
              setClipToPadding(!showExpandButtonAtEnd);
              mShowExpandButtonAtEnd = showExpandButtonAtEnd;
          }
      }
  
      public View getWorkProfileIcon() {
          return mProfileBadge;
      }
  
      public CachingIconView getIcon() {
          return mIcon;
      }
  
      /**
       * Sets the margin end for the text portion of the header, excluding right-aligned elements
       * @param headerTextMarginEnd margin size
       */
      @RemotableViewMethod
      public void setHeaderTextMarginEnd(int headerTextMarginEnd) {
          if (mHeaderTextMarginEnd != headerTextMarginEnd) {
              mHeaderTextMarginEnd = headerTextMarginEnd;
              requestLayout();
          }
      }
  
      /**
       * Get the current margin end value for the header text
       * @return margin size
       */
      public int getHeaderTextMarginEnd() {
          return mHeaderTextMarginEnd;
      }
  
      public class HeaderTouchListener implements View.OnTouchListener {
  
          private final ArrayList<Rect> mTouchRects = new ArrayList<>();
          private Rect mExpandButtonRect;
          private Rect mAppOpsRect;
          private int mTouchSlop;
          private boolean mTrackGesture;
          private float mDownX;
          private float mDownY;
  
          public HeaderTouchListener() {
          }
  
          public void bindTouchRects() {
              mTouchRects.clear();
              addRectAroundView(mIcon);
              mExpandButtonRect = addRectAroundView(mExpandButton);
              mAppOpsRect = addRectAroundView(mAppOps);
              addWidthRect();
              mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
          }
  
          private void addWidthRect() {
              Rect r = new Rect();
              r.top = 0;
              r.bottom = (int) (32 * getResources().getDisplayMetrics().density);
              r.left = 0;
              r.right = getWidth();
              mTouchRects.add(r);
          }
  
          private Rect addRectAroundView(View view) {
              final Rect r = getRectAroundView(view);
              mTouchRects.add(r);
              return r;
          }
  
          private Rect getRectAroundView(View view) {
              float size = 48 * getResources().getDisplayMetrics().density;
              float width = Math.max(size, view.getWidth());
              float height = Math.max(size, view.getHeight());
              final Rect r = new Rect();
              if (view.getVisibility() == GONE) {
                  view = getFirstChildNotGone();
                  r.left = (int) (view.getLeft() - width / 2.0f);
              } else {
                  r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - width / 2.0f);
              }
              r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - height / 2.0f);
              r.bottom = (int) (r.top + height);
              r.right = (int) (r.left + width);
              return r;
          }
  
          @Override
          public boolean onTouch(View v, MotionEvent event) {
              float x = event.getX();
              float y = event.getY();
              switch (event.getActionMasked() & MotionEvent.ACTION_MASK) {
                  case MotionEvent.ACTION_DOWN:
                      mTrackGesture = false;
                      if (isInside(x, y)) {
                          mDownX = x;
                          mDownY = y;
                          mTrackGesture = true;
                          return true;
                      }
                      break;
                  case MotionEvent.ACTION_MOVE:
                      if (mTrackGesture) {
                          if (Math.abs(mDownX - x) > mTouchSlop
                                  || Math.abs(mDownY - y) > mTouchSlop) {
                              mTrackGesture = false;
                          }
                      }
                      break;
                  case MotionEvent.ACTION_UP:
                      if (mTrackGesture) {
                          if (mAppOps.isVisibleToUser() && (mAppOpsRect.contains((int) x, (int) y)
                                  || mAppOpsRect.contains((int) mDownX, (int) mDownY))) {
                              mAppOps.performClick();
                              return true;
                          }
                          mExpandButton.performClick();
                      }
                      break;
              }
              return mTrackGesture;
          }
  
          private boolean isInside(float x, float y) {
              if (mAcceptAllTouches) {
                  return true;
              }
              if (mExpandOnlyOnButton) {
                  return mExpandButtonRect.contains((int) x, (int) y);
              }
              for (int i = 0; i < mTouchRects.size(); i++) {
                  Rect r = mTouchRects.get(i);
                  if (r.contains((int) x, (int) y)) {
                      return true;
                  }
              }
              return false;
          }
      }
  
      private View getFirstChildNotGone() {
          for (int i = 0; i < getChildCount(); i++) {
              final View child = getChildAt(i);
              if (child.getVisibility() != GONE) {
                  return child;
              }
          }
          return this;
      }
  
      public ImageView getExpandButton() {
          return mExpandButton;
      }
  
      @Override
      public boolean hasOverlappingRendering() {
          return false;
      }
  
      public boolean isInTouchRect(float x, float y) {
          if (mExpandClickListener == null) {
              return false;
          }
          return mTouchListener.isInside(x, y);
      }
  
      /**
       * Sets whether or not all touches to this header view will register as a click. Note that
       * if the config value for {@code config_notificationHeaderClickableForExpand} is {@code true},
       * then calling this method with {@code false} will not override that configuration.
       */
      @RemotableViewMethod
      public void setAcceptAllTouches(boolean acceptAllTouches) {
          mAcceptAllTouches = mEntireHeaderClickable || acceptAllTouches;
      }
  
      /**
       * Sets whether only the expand icon itself should serve as the expand target.
       */
      @RemotableViewMethod
      public void setExpandOnlyOnButton(boolean expandOnlyOnButton) {
          mExpandOnlyOnButton = expandOnlyOnButton;
      }
  }
  