Home | History | Annotate | Download | only in verifier
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.cts.verifier;
     18 
     19 import android.annotation.TargetApi;
     20 import android.os.Build;
     21 import android.content.Context;
     22 import android.content.res.TypedArray;
     23 import android.graphics.Rect;
     24 import android.graphics.drawable.Drawable;
     25 import android.util.AttributeSet;
     26 import android.view.Gravity;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 import android.view.WindowInsets;
     30 import android.widget.FrameLayout;
     31 
     32 /**
     33  * BoxInsetLayout is a screen shape-aware FrameLayout that can box its children
     34  * in the center square of a round screen by using the
     35  * {@code ctsv_layout_box} attribute. The values for this attribute specify the
     36  * child's edges to be boxed in:
     37  * {@code left|top|right|bottom} or {@code all}.
     38  * The {@code ctsv_layout_box} attribute is ignored on a device with a rectangular
     39  * screen.
     40  */
     41 @TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
     42 public class BoxInsetLayout extends FrameLayout {
     43 
     44     private static float FACTOR = 0.146467f; //(1 - sqrt(2)/2)/2
     45     private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
     46 
     47     private Rect mForegroundPadding;
     48     private boolean mLastKnownRound;
     49     private Rect mInsets;
     50 
     51     public BoxInsetLayout(Context context) {
     52         this(context, null);
     53     }
     54 
     55     public BoxInsetLayout(Context context, AttributeSet attrs) {
     56         this(context, attrs, 0);
     57     }
     58 
     59     public BoxInsetLayout(Context context, AttributeSet attrs, int defStyle) {
     60         super(context, attrs, defStyle);
     61         // make sure we have foreground padding object
     62         if (mForegroundPadding == null) {
     63             mForegroundPadding = new Rect();
     64         }
     65         if (mInsets == null) {
     66             mInsets = new Rect();
     67         }
     68     }
     69 
     70     @Override
     71     protected void onAttachedToWindow() {
     72         super.onAttachedToWindow();
     73         requestApplyInsets();
     74     }
     75 
     76     @Override
     77     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
     78         insets = super.onApplyWindowInsets(insets);
     79         final boolean round = insets.isRound();
     80         if (round != mLastKnownRound) {
     81             mLastKnownRound = round;
     82             requestLayout();
     83         }
     84         mInsets.set(
     85             insets.getSystemWindowInsetLeft(),
     86             insets.getSystemWindowInsetTop(),
     87             insets.getSystemWindowInsetRight(),
     88             insets.getSystemWindowInsetBottom());
     89         return insets;
     90     }
     91 
     92     /**
     93      * determine screen shape
     94      * @return true if on a round screen
     95      */
     96     public boolean isRound() {
     97         return mLastKnownRound;
     98     }
     99 
    100     /**
    101      * @return the system window insets Rect
    102      */
    103     public Rect getInsets() {
    104         return mInsets;
    105     }
    106 
    107     @Override
    108     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    109         int count = getChildCount();
    110         // find max size
    111         int maxWidth = 0;
    112         int maxHeight = 0;
    113         int childState = 0;
    114         for (int i = 0; i < count; i++) {
    115             final View child = getChildAt(i);
    116             if (child.getVisibility() != GONE) {
    117                 LayoutParams lp = (BoxInsetLayout.LayoutParams) child.getLayoutParams();
    118                 int marginLeft = 0;
    119                 int marginRight = 0;
    120                 int marginTop = 0;
    121                 int marginBottom = 0;
    122                 if (mLastKnownRound) {
    123                     // round screen, check boxed, don't use margins on boxed
    124                     if ((lp.boxedEdges & LayoutParams.BOX_LEFT) == 0) {
    125                         marginLeft = lp.leftMargin;
    126                     }
    127                     if ((lp.boxedEdges & LayoutParams.BOX_RIGHT) == 0) {
    128                         marginRight = lp.rightMargin;
    129                     }
    130                     if ((lp.boxedEdges & LayoutParams.BOX_TOP) == 0) {
    131                         marginTop = lp.topMargin;
    132                     }
    133                     if ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) == 0) {
    134                         marginBottom = lp.bottomMargin;
    135                     }
    136                 } else {
    137                     // rectangular, ignore boxed, use margins
    138                     marginLeft = lp.leftMargin;
    139                     marginTop = lp.topMargin;
    140                     marginRight = lp.rightMargin;
    141                     marginBottom = lp.bottomMargin;
    142                 }
    143                 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
    144                 maxWidth = Math.max(maxWidth,
    145                         child.getMeasuredWidth() + marginLeft + marginRight);
    146                 maxHeight = Math.max(maxHeight,
    147                         child.getMeasuredHeight() + marginTop + marginBottom);
    148                 childState = combineMeasuredStates(childState, child.getMeasuredState());
    149             }
    150         }
    151         // Account for padding too
    152         maxWidth += getPaddingLeft() + mForegroundPadding.left
    153                 + getPaddingRight() + mForegroundPadding.right;
    154         maxHeight += getPaddingTop() + mForegroundPadding.top
    155                 + getPaddingBottom() + mForegroundPadding.bottom;
    156 
    157         // Check against our minimum height and width
    158         maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    159         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
    160 
    161         // Check against our foreground's minimum height and width
    162         final Drawable drawable = getForeground();
    163         if (drawable != null) {
    164             maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
    165             maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    166         }
    167 
    168         setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
    169                 resolveSizeAndState(maxHeight, heightMeasureSpec,
    170                         childState << MEASURED_HEIGHT_STATE_SHIFT));
    171 
    172         // determine boxed inset
    173         int boxInset = (int) (FACTOR * Math.max(getMeasuredWidth(), getMeasuredHeight()));
    174         // adjust the match parent children
    175         for (int i = 0; i < count; i++) {
    176             final View child = getChildAt(i);
    177 
    178             final LayoutParams lp = (BoxInsetLayout.LayoutParams) child.getLayoutParams();
    179             int childWidthMeasureSpec;
    180             int childHeightMeasureSpec;
    181             int plwf = getPaddingLeft() + mForegroundPadding.left;
    182             int prwf = getPaddingRight() + mForegroundPadding.right;
    183             int ptwf = getPaddingTop() + mForegroundPadding.top;
    184             int pbwf = getPaddingBottom() + mForegroundPadding.bottom;
    185 
    186             // adjust width
    187             int totalPadding = 0;
    188             int totalMargin = 0;
    189             // BoxInset is a padding. Ignore margin when we want to do BoxInset.
    190             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
    191                 totalPadding += boxInset;
    192             } else {
    193                 totalMargin += plwf + lp.leftMargin;
    194             }
    195             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
    196                 totalPadding += boxInset;
    197             } else {
    198                 totalMargin += prwf + lp.rightMargin;
    199             }
    200             if (lp.width == LayoutParams.MATCH_PARENT) {
    201                 //  Only subtract margin from the actual width, leave the padding in.
    202                 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
    203                         getMeasuredWidth() - totalMargin, MeasureSpec.EXACTLY);
    204             } else {
    205                 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
    206                         totalPadding + totalMargin, lp.width);
    207             }
    208 
    209             // adjust height
    210             totalPadding = 0;
    211             totalMargin = 0;
    212             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
    213                 totalPadding += boxInset;
    214             } else {
    215                 totalMargin += ptwf + lp.topMargin;
    216             }
    217             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
    218                 totalPadding += boxInset;
    219             } else {
    220                 totalMargin += pbwf + lp.bottomMargin;
    221             }
    222 
    223             if (lp.height == LayoutParams.MATCH_PARENT) {
    224                 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
    225                         getMeasuredHeight() - totalMargin, MeasureSpec.EXACTLY);
    226             } else {
    227                 childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
    228                         totalPadding + totalMargin, lp.height);
    229             }
    230 
    231             child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    232         }
    233     }
    234 
    235 
    236     @Override
    237     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    238         layoutBoxChildren(left, top, right, bottom, false /* no force left gravity */);
    239     }
    240 
    241     private void layoutBoxChildren(int left, int top, int right, int bottom,
    242                                   boolean forceLeftGravity) {
    243         final int count = getChildCount();
    244         int boxInset = (int)(FACTOR * Math.max(right - left, bottom - top));
    245 
    246         final int parentLeft = getPaddingLeft() + mForegroundPadding.left;
    247         final int parentRight = right - left - getPaddingRight() - mForegroundPadding.right;
    248 
    249         final int parentTop = getPaddingTop() + mForegroundPadding.top;
    250         final int parentBottom = bottom - top - getPaddingBottom() - mForegroundPadding.bottom;
    251 
    252         for (int i = 0; i < count; i++) {
    253             final View child = getChildAt(i);
    254             if (child.getVisibility() != GONE) {
    255                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    256 
    257                 final int width = child.getMeasuredWidth();
    258                 final int height = child.getMeasuredHeight();
    259 
    260                 int childLeft;
    261                 int childTop;
    262 
    263                 int gravity = lp.gravity;
    264                 if (gravity == -1) {
    265                     gravity = DEFAULT_CHILD_GRAVITY;
    266                 }
    267 
    268                 final int layoutDirection = getLayoutDirection();
    269                 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
    270                 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
    271 
    272                 // These values are replaced with boxInset below as necessary.
    273                 int paddingLeft = child.getPaddingLeft();
    274                 int paddingRight = child.getPaddingRight();
    275                 int paddingTop = child.getPaddingTop();
    276                 int paddingBottom = child.getPaddingBottom();
    277 
    278                 // If the child's width is match_parent, we ignore gravity and set boxInset padding
    279                 // on both sides, with a left position of parentLeft + the child's left margin.
    280                 if (lp.width == LayoutParams.MATCH_PARENT) {
    281                     if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
    282                         paddingLeft = boxInset;
    283                     }
    284                     if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
    285                         paddingRight = boxInset;
    286                     }
    287                     childLeft = parentLeft + lp.leftMargin;
    288                 } else {
    289                     switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
    290                         case Gravity.CENTER_HORIZONTAL:
    291                             childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
    292                                     lp.leftMargin - lp.rightMargin;
    293                             break;
    294                         case Gravity.RIGHT:
    295                             if (!forceLeftGravity) {
    296                                 if (mLastKnownRound
    297                                         && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
    298                                     paddingRight = boxInset;
    299                                     childLeft = right - left - width;
    300                                 } else {
    301                                     childLeft = parentRight - width - lp.rightMargin;
    302                                 }
    303                                 break;
    304                             }
    305                         case Gravity.LEFT:
    306                         default:
    307                             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
    308                                 paddingLeft = boxInset;
    309                                 childLeft = 0;
    310                             } else {
    311                                 childLeft = parentLeft + lp.leftMargin;
    312                             }
    313                     }
    314                 }
    315 
    316                 // If the child's height is match_parent, we ignore gravity and set boxInset padding
    317                 // on both top and bottom, with a top position of parentTop + the child's top
    318                 // margin.
    319                 if (lp.height == LayoutParams.MATCH_PARENT) {
    320                     if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
    321                         paddingTop = boxInset;
    322                     }
    323                     if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
    324                         paddingBottom = boxInset;
    325                     }
    326                     childTop = parentTop + lp.topMargin;
    327                 } else {
    328                     switch (verticalGravity) {
    329                         case Gravity.TOP:
    330                             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
    331                                 paddingTop = boxInset;
    332                                 childTop = 0;
    333                             } else {
    334                                 childTop = parentTop + lp.topMargin;
    335                             }
    336                             break;
    337                         case Gravity.CENTER_VERTICAL:
    338                             childTop = parentTop + (parentBottom - parentTop - height) / 2 +
    339                                     lp.topMargin - lp.bottomMargin;
    340                             break;
    341                         case Gravity.BOTTOM:
    342                             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
    343                                 paddingBottom = boxInset;
    344                                 childTop = bottom - top - height;
    345                             } else {
    346                                 childTop = parentBottom - height - lp.bottomMargin;
    347                             }
    348                             break;
    349                         default:
    350                             childTop = parentTop + lp.topMargin;
    351                     }
    352                 }
    353 
    354                 child.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
    355                 child.layout(childLeft, childTop, childLeft + width, childTop + height);
    356             }
    357         }
    358     }
    359 
    360     public void setForeground(Drawable drawable) {
    361         super.setForeground(drawable);
    362         if (mForegroundPadding == null) {
    363             mForegroundPadding = new Rect();
    364         }
    365         drawable.getPadding(mForegroundPadding);
    366     }
    367 
    368     @Override
    369     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    370         return p instanceof LayoutParams;
    371     }
    372 
    373     @Override
    374     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    375         return new LayoutParams(p);
    376     }
    377 
    378     @Override
    379     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    380         return new BoxInsetLayout.LayoutParams(getContext(), attrs);
    381     }
    382 
    383     /**
    384      * adds {@code ctsv_layout_box} attribute to layout parameters
    385      */
    386     public static class LayoutParams extends FrameLayout.LayoutParams {
    387 
    388         public static final int BOX_NONE = 0x0;
    389         public static final int BOX_LEFT = 0x01;
    390         public static final int BOX_TOP = 0x02;
    391         public static final int BOX_RIGHT = 0x04;
    392         public static final int BOX_BOTTOM = 0x08;
    393         public static final int BOX_ALL = 0x0F;
    394 
    395         public int boxedEdges = BOX_NONE;
    396 
    397         public LayoutParams(Context context, AttributeSet attrs) {
    398             super(context, attrs);
    399             TypedArray a = context.obtainStyledAttributes(attrs,  R.styleable.BoxInsetLayout_Layout, 0, 0);
    400             boxedEdges = a.getInt(R.styleable.BoxInsetLayout_Layout_ctsv_layout_box, BOX_NONE);
    401             a.recycle();
    402         }
    403 
    404         public LayoutParams(int width, int height) {
    405             super(width, height);
    406         }
    407 
    408         public LayoutParams(int width, int height, int gravity) {
    409             super(width, height, gravity);
    410         }
    411 
    412         public LayoutParams(int width, int height, int gravity, int boxed) {
    413             super(width, height, gravity);
    414             boxedEdges = boxed;
    415         }
    416 
    417         public LayoutParams(ViewGroup.LayoutParams source) {
    418             super(source);
    419         }
    420 
    421         public LayoutParams(ViewGroup.MarginLayoutParams source) {
    422             super(source);
    423         }
    424 
    425         public LayoutParams(FrameLayout.LayoutParams source) {
    426             super(source);
    427         }
    428 
    429         public LayoutParams(LayoutParams source) {
    430             super(source);
    431             this.boxedEdges = source.boxedEdges;
    432             this.gravity = source.gravity;
    433         }
    434 
    435     }
    436 }
    437