content: /*
   * Copyright (C) 2016 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.content.Context;
  import android.graphics.drawable.Drawable;
  import android.graphics.Rect;
  import android.util.AttributeSet;
  import android.view.Gravity;
  import android.view.View;
  import android.view.ViewTreeObserver;
  import android.widget.ListView;
  import android.widget.FrameLayout;
  
  import java.util.ArrayList;
  
  
  /**
   * Layout for the decor for ListViews on watch-type devices with small screens.
   * <p>
   * Supports one panel with the gravity set to top, and one panel with gravity set to bottom.
   * <p>
   * Use with one ListView child. The top and bottom panels will track the ListView's scrolling.
   * If there is no ListView child, it will act like a normal FrameLayout.
   */
  public class WatchListDecorLayout extends FrameLayout
          implements ViewTreeObserver.OnScrollChangedListener {
  
      private int mForegroundPaddingLeft = 0;
      private int mForegroundPaddingTop = 0;
      private int mForegroundPaddingRight = 0;
      private int mForegroundPaddingBottom = 0;
  
      private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1);
  
      /** Track the amount the ListView has to scroll up to account for padding change difference. */
      private int mPendingScroll;
      private View mBottomPanel;
      private View mTopPanel;
      private ListView mListView;
      private ViewTreeObserver mObserver;
  
  
      public WatchListDecorLayout(Context context, AttributeSet attrs) {
          super(context, attrs);
      }
  
      public WatchListDecorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
      }
  
      public WatchListDecorLayout(
              Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
      }
  
      @Override
      protected void onAttachedToWindow() {
          super.onAttachedToWindow();
  
          mPendingScroll = 0;
  
          for (int i = 0; i < getChildCount(); ++i) {
              View child = getChildAt(i);
              if (child instanceof ListView) {
                  if (mListView != null) {
                      throw new IllegalArgumentException("only one ListView child allowed");
                  }
                  mListView = (ListView) child;
  
                  mListView.setNestedScrollingEnabled(true);
                  mObserver = mListView.getViewTreeObserver();
                  mObserver.addOnScrollChangedListener(this);
              } else {
                  int gravity = (((LayoutParams) child.getLayoutParams()).gravity
                          & Gravity.VERTICAL_GRAVITY_MASK);
                  if (gravity == Gravity.TOP && mTopPanel == null) {
                      mTopPanel = child;
                  } else if (gravity == Gravity.BOTTOM && mBottomPanel == null) {
                      mBottomPanel = child;
                  }
              }
          }
      }
  
      @Override
      public void onDetachedFromWindow() {
          mListView = null;
          mBottomPanel = null;
          mTopPanel = null;
          if (mObserver != null) {
              if (mObserver.isAlive()) {
                  mObserver.removeOnScrollChangedListener(this);
              }
              mObserver = null;
          }
      }
  
      private void applyMeasureToChild(View child, int widthMeasureSpec, int heightMeasureSpec) {
          final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
  
          final int childWidthMeasureSpec;
          if (lp.width == LayoutParams.MATCH_PARENT) {
              final int width = Math.max(0, getMeasuredWidth()
                      - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                      - lp.leftMargin - lp.rightMargin);
              childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                      width, MeasureSpec.EXACTLY);
          } else {
              childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                      getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                      lp.leftMargin + lp.rightMargin,
                      lp.width);
          }
  
          final int childHeightMeasureSpec;
          if (lp.height == LayoutParams.MATCH_PARENT) {
              final int height = Math.max(0, getMeasuredHeight()
                      - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                      - lp.topMargin - lp.bottomMargin);
              childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                      height, MeasureSpec.EXACTLY);
          } else {
              childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                      getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                      lp.topMargin + lp.bottomMargin,
                      lp.height);
          }
  
          child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
      }
  
      private int measureAndGetHeight(View child, int widthMeasureSpec, int heightMeasureSpec) {
          if (child != null) {
              if (child.getVisibility() != GONE) {
                  applyMeasureToChild(mBottomPanel, widthMeasureSpec, heightMeasureSpec);
                  return child.getMeasuredHeight();
              } else if (getMeasureAllChildren()) {
                  applyMeasureToChild(mBottomPanel, widthMeasureSpec, heightMeasureSpec);
              }
          }
          return 0;
      }
  
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          int count = getChildCount();
  
          final boolean measureMatchParentChildren =
                  MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                  MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
          mMatchParentChildren.clear();
  
          int maxHeight = 0;
          int maxWidth = 0;
          int childState = 0;
  
          for (int i = 0; i < count; i++) {
              final View child = getChildAt(i);
              if (getMeasureAllChildren() || child.getVisibility() != GONE) {
                  measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                  final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                  maxWidth = Math.max(maxWidth,
                          child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                  maxHeight = Math.max(maxHeight,
                          child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                  childState = combineMeasuredStates(childState, child.getMeasuredState());
                  if (measureMatchParentChildren) {
                      if (lp.width == LayoutParams.MATCH_PARENT ||
                              lp.height == LayoutParams.MATCH_PARENT) {
                          mMatchParentChildren.add(child);
                      }
                  }
              }
          }
  
          // Account for padding too
          maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
          maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
  
          // Check against our minimum height and width
          maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
          maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
  
          // Check against our foreground's minimum height and width
          final Drawable drawable = getForeground();
          if (drawable != null) {
              maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
              maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
          }
  
          setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                  resolveSizeAndState(maxHeight, heightMeasureSpec,
                          childState << MEASURED_HEIGHT_STATE_SHIFT));
  
          if (mListView != null) {
              if (mPendingScroll != 0) {
                  mListView.scrollListBy(mPendingScroll);
                  mPendingScroll = 0;
              }
  
              int paddingTop = Math.max(mListView.getPaddingTop(),
                      measureAndGetHeight(mTopPanel, widthMeasureSpec, heightMeasureSpec));
              int paddingBottom = Math.max(mListView.getPaddingBottom(),
                      measureAndGetHeight(mBottomPanel, widthMeasureSpec, heightMeasureSpec));
  
              if (paddingTop != mListView.getPaddingTop()
                      || paddingBottom != mListView.getPaddingBottom()) {
                  mPendingScroll += mListView.getPaddingTop() - paddingTop;
                  mListView.setPadding(
                          mListView.getPaddingLeft(), paddingTop,
                          mListView.getPaddingRight(), paddingBottom);
              }
          }
  
          count = mMatchParentChildren.size();
          if (count > 1) {
              for (int i = 0; i < count; i++) {
                  final View child = mMatchParentChildren.get(i);
                  if (mListView == null || (child != mTopPanel && child != mBottomPanel)) {
                      applyMeasureToChild(child, widthMeasureSpec, heightMeasureSpec);
                  }
              }
          }
      }
  
      @Override
      public void setForegroundGravity(int foregroundGravity) {
          if (getForegroundGravity() != foregroundGravity) {
              super.setForegroundGravity(foregroundGravity);
  
              // calling get* again here because the set above may apply default constraints
              final Drawable foreground = getForeground();
              if (getForegroundGravity() == Gravity.FILL && foreground != null) {
                  Rect padding = new Rect();
                  if (foreground.getPadding(padding)) {
                      mForegroundPaddingLeft = padding.left;
                      mForegroundPaddingTop = padding.top;
                      mForegroundPaddingRight = padding.right;
                      mForegroundPaddingBottom = padding.bottom;
                  }
              } else {
                  mForegroundPaddingLeft = 0;
                  mForegroundPaddingTop = 0;
                  mForegroundPaddingRight = 0;
                  mForegroundPaddingBottom = 0;
              }
          }
      }
  
      private int getPaddingLeftWithForeground() {
          return isForegroundInsidePadding() ? Math.max(mPaddingLeft, mForegroundPaddingLeft) :
              mPaddingLeft + mForegroundPaddingLeft;
      }
  
      private int getPaddingRightWithForeground() {
          return isForegroundInsidePadding() ? Math.max(mPaddingRight, mForegroundPaddingRight) :
              mPaddingRight + mForegroundPaddingRight;
      }
  
      private int getPaddingTopWithForeground() {
          return isForegroundInsidePadding() ? Math.max(mPaddingTop, mForegroundPaddingTop) :
              mPaddingTop + mForegroundPaddingTop;
      }
  
      private int getPaddingBottomWithForeground() {
          return isForegroundInsidePadding() ? Math.max(mPaddingBottom, mForegroundPaddingBottom) :
              mPaddingBottom + mForegroundPaddingBottom;
      }
  
      @Override
      public void onScrollChanged() {
          if (mListView == null) {
              return;
          }
  
          if (mTopPanel != null) {
              if (mListView.getChildCount() > 0) {
                  if (mListView.getFirstVisiblePosition() == 0) {
                      View firstChild = mListView.getChildAt(0);
                      setScrolling(mTopPanel,
                              firstChild.getY() - mTopPanel.getHeight() - mTopPanel.getTop());
                  } else {
                      // shift to hide the frame, last child is not the last position
                      setScrolling(mTopPanel, -mTopPanel.getHeight());
                  }
              } else {
                  setScrolling(mTopPanel, 0); // no visible child, fallback to default behaviour
              }
          }
  
          if (mBottomPanel != null) {
              if (mListView.getChildCount() > 0) {
                  if (mListView.getLastVisiblePosition() >= mListView.getCount() - 1) {
                      View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
                      setScrolling(mBottomPanel, Math.max(
                              0,
                              lastChild.getY() + lastChild.getHeight() - mBottomPanel.getTop()));
                  } else {
                      // shift to hide the frame, last child is not the last position
                      setScrolling(mBottomPanel, mBottomPanel.getHeight());
                  }
              } else {
                  setScrolling(mBottomPanel, 0); // no visible child, fallback to default behaviour
              }
          }
      }
  
      /** Only set scrolling for the panel if there is a change in its translationY. */
      private void setScrolling(View panel, float translationY) {
          if (panel.getTranslationY() != translationY) {
              panel.setTranslationY(translationY);
          }
      }
  }
  