Home | History | Annotate | Download | only in keyguard
      1 /*
      2  * Copyright (C) 2012 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.keyguard;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.content.Context;
     23 import android.content.res.Resources;
     24 import android.content.res.TypedArray;
     25 import android.graphics.Rect;
     26 import android.util.AttributeSet;
     27 import android.util.DisplayMetrics;
     28 import android.view.Gravity;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 import android.widget.LinearLayout;
     32 
     33 public class MultiPaneChallengeLayout extends ViewGroup implements ChallengeLayout {
     34     private static final String TAG = "MultiPaneChallengeLayout";
     35 
     36     final int mOrientation;
     37     private boolean mIsBouncing;
     38 
     39     public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
     40     public static final int VERTICAL = LinearLayout.VERTICAL;
     41     public static final int ANIMATE_BOUNCE_DURATION = 350;
     42 
     43     private KeyguardSecurityContainer mChallengeView;
     44     private View mUserSwitcherView;
     45     private View mScrimView;
     46     private OnBouncerStateChangedListener mBouncerListener;
     47 
     48     private final Rect mTempRect = new Rect();
     49     private final Rect mZeroPadding = new Rect();
     50     private final Rect mInsets = new Rect();
     51 
     52     private final DisplayMetrics mDisplayMetrics;
     53 
     54     private final OnClickListener mScrimClickListener = new OnClickListener() {
     55         @Override
     56         public void onClick(View v) {
     57             hideBouncer();
     58         }
     59     };
     60 
     61     public MultiPaneChallengeLayout(Context context) {
     62         this(context, null);
     63     }
     64 
     65     public MultiPaneChallengeLayout(Context context, AttributeSet attrs) {
     66         this(context, attrs, 0);
     67     }
     68 
     69     public MultiPaneChallengeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
     70         super(context, attrs, defStyleAttr);
     71 
     72         final TypedArray a = context.obtainStyledAttributes(attrs,
     73                 R.styleable.MultiPaneChallengeLayout, defStyleAttr, 0);
     74         mOrientation = a.getInt(R.styleable.MultiPaneChallengeLayout_android_orientation,
     75                 HORIZONTAL);
     76         a.recycle();
     77 
     78         final Resources res = getResources();
     79         mDisplayMetrics = res.getDisplayMetrics();
     80 
     81         setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
     82     }
     83 
     84     public void setInsets(Rect insets) {
     85         mInsets.set(insets);
     86     }
     87 
     88     @Override
     89     public boolean isChallengeShowing() {
     90         return true;
     91     }
     92 
     93     @Override
     94     public boolean isChallengeOverlapping() {
     95         return false;
     96     }
     97 
     98     @Override
     99     public void showChallenge(boolean b) {
    100     }
    101 
    102     @Override
    103     public int getBouncerAnimationDuration() {
    104         return ANIMATE_BOUNCE_DURATION;
    105     }
    106 
    107     @Override
    108     public void showBouncer() {
    109         if (mIsBouncing) return;
    110         mIsBouncing = true;
    111         if (mScrimView != null) {
    112             if (mChallengeView != null) {
    113                 mChallengeView.showBouncer(ANIMATE_BOUNCE_DURATION);
    114             }
    115 
    116             Animator anim = ObjectAnimator.ofFloat(mScrimView, "alpha", 1f);
    117             anim.setDuration(ANIMATE_BOUNCE_DURATION);
    118             anim.addListener(new AnimatorListenerAdapter() {
    119                 @Override
    120                 public void onAnimationStart(Animator animation) {
    121                     mScrimView.setVisibility(VISIBLE);
    122                 }
    123             });
    124             anim.start();
    125         }
    126         if (mBouncerListener != null) {
    127             mBouncerListener.onBouncerStateChanged(true);
    128         }
    129     }
    130 
    131     @Override
    132     public void hideBouncer() {
    133         if (!mIsBouncing) return;
    134         mIsBouncing = false;
    135         if (mScrimView != null) {
    136             if (mChallengeView != null) {
    137                 mChallengeView.hideBouncer(ANIMATE_BOUNCE_DURATION);
    138             }
    139 
    140             Animator anim = ObjectAnimator.ofFloat(mScrimView, "alpha", 0f);
    141             anim.setDuration(ANIMATE_BOUNCE_DURATION);
    142             anim.addListener(new AnimatorListenerAdapter() {
    143                 @Override
    144                 public void onAnimationEnd(Animator animation) {
    145                     mScrimView.setVisibility(INVISIBLE);
    146                 }
    147             });
    148             anim.start();
    149         }
    150         if (mBouncerListener != null) {
    151             mBouncerListener.onBouncerStateChanged(false);
    152         }
    153     }
    154 
    155     @Override
    156     public boolean isBouncing() {
    157         return mIsBouncing;
    158     }
    159 
    160     @Override
    161     public void setOnBouncerStateChangedListener(OnBouncerStateChangedListener listener) {
    162         mBouncerListener = listener;
    163     }
    164 
    165     @Override
    166     public void requestChildFocus(View child, View focused) {
    167         if (mIsBouncing && child != mChallengeView) {
    168             // Clear out of the bouncer if the user tries to move focus outside of
    169             // the security challenge view.
    170             hideBouncer();
    171         }
    172         super.requestChildFocus(child, focused);
    173     }
    174 
    175     void setScrimView(View scrim) {
    176         if (mScrimView != null) {
    177             mScrimView.setOnClickListener(null);
    178         }
    179         mScrimView = scrim;
    180         if (mScrimView != null) {
    181             mScrimView.setAlpha(mIsBouncing ? 1.0f : 0.0f);
    182             mScrimView.setVisibility(mIsBouncing ? VISIBLE : INVISIBLE);
    183             mScrimView.setFocusable(true);
    184             mScrimView.setOnClickListener(mScrimClickListener);
    185         }
    186     }
    187 
    188     private int getVirtualHeight(LayoutParams lp, int height, int heightUsed) {
    189         int virtualHeight = height;
    190         final View root = getRootView();
    191         if (root != null) {
    192             // This calculation is super dodgy and relies on several assumptions.
    193             // Specifically that the root of the window will be padded in for insets
    194             // and that the window is LAYOUT_IN_SCREEN.
    195             virtualHeight = mDisplayMetrics.heightPixels - root.getPaddingTop() - mInsets.top;
    196         }
    197         if (lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER) {
    198             // Always measure the user switcher as if there were no IME insets
    199             // on the window.
    200             return virtualHeight - heightUsed;
    201         } else if (lp.childType == LayoutParams.CHILD_TYPE_PAGE_DELETE_DROP_TARGET) {
    202             return height;
    203         }
    204         return Math.min(virtualHeight - heightUsed, height);
    205     }
    206 
    207     @Override
    208     protected void onMeasure(final int widthSpec, final int heightSpec) {
    209         if (MeasureSpec.getMode(widthSpec) != MeasureSpec.EXACTLY ||
    210                 MeasureSpec.getMode(heightSpec) != MeasureSpec.EXACTLY) {
    211             throw new IllegalArgumentException(
    212                     "MultiPaneChallengeLayout must be measured with an exact size");
    213         }
    214 
    215         final int width = MeasureSpec.getSize(widthSpec);
    216         final int height = MeasureSpec.getSize(heightSpec);
    217         setMeasuredDimension(width, height);
    218 
    219         final int insetHeight = height - mInsets.top - mInsets.bottom;
    220         final int insetHeightSpec = MeasureSpec.makeMeasureSpec(insetHeight, MeasureSpec.EXACTLY);
    221 
    222         int widthUsed = 0;
    223         int heightUsed = 0;
    224 
    225         // First pass. Find the challenge view and measure the user switcher,
    226         // which consumes space in the layout.
    227         mChallengeView = null;
    228         mUserSwitcherView = null;
    229         final int count = getChildCount();
    230         for (int i = 0; i < count; i++) {
    231             final View child = getChildAt(i);
    232             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    233 
    234             if (lp.childType == LayoutParams.CHILD_TYPE_CHALLENGE) {
    235                 if (mChallengeView != null) {
    236                     throw new IllegalStateException(
    237                             "There may only be one child of type challenge");
    238                 }
    239                 if (!(child instanceof KeyguardSecurityContainer)) {
    240                     throw new IllegalArgumentException(
    241                             "Challenge must be a KeyguardSecurityContainer");
    242                 }
    243                 mChallengeView = (KeyguardSecurityContainer) child;
    244             } else if (lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER) {
    245                 if (mUserSwitcherView != null) {
    246                     throw new IllegalStateException(
    247                             "There may only be one child of type userSwitcher");
    248                 }
    249                 mUserSwitcherView = child;
    250 
    251                 if (child.getVisibility() == GONE) continue;
    252 
    253                 int adjustedWidthSpec = widthSpec;
    254                 int adjustedHeightSpec = insetHeightSpec;
    255                 if (lp.maxWidth >= 0) {
    256                     adjustedWidthSpec = MeasureSpec.makeMeasureSpec(
    257                             Math.min(lp.maxWidth, width), MeasureSpec.EXACTLY);
    258                 }
    259                 if (lp.maxHeight >= 0) {
    260                     adjustedHeightSpec = MeasureSpec.makeMeasureSpec(
    261                             Math.min(lp.maxHeight, insetHeight), MeasureSpec.EXACTLY);
    262                 }
    263                 // measureChildWithMargins will resolve layout direction for the LayoutParams
    264                 measureChildWithMargins(child, adjustedWidthSpec, 0, adjustedHeightSpec, 0);
    265 
    266                 // Only subtract out space from one dimension. Favor vertical.
    267                 // Offset by 1.5x to add some balance along the other edge.
    268                 if (Gravity.isVertical(lp.gravity)) {
    269                     heightUsed += child.getMeasuredHeight() * 1.5f;
    270                 } else if (Gravity.isHorizontal(lp.gravity)) {
    271                     widthUsed += child.getMeasuredWidth() * 1.5f;
    272                 }
    273             } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) {
    274                 setScrimView(child);
    275                 child.measure(widthSpec, heightSpec);
    276             }
    277         }
    278 
    279         // Second pass. Measure everything that's left.
    280         for (int i = 0; i < count; i++) {
    281             final View child = getChildAt(i);
    282             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    283 
    284             if (lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER ||
    285                     lp.childType == LayoutParams.CHILD_TYPE_SCRIM ||
    286                     child.getVisibility() == GONE) {
    287                 // Don't need to measure GONE children, and the user switcher was already measured.
    288                 continue;
    289             }
    290 
    291             final int virtualHeight = getVirtualHeight(lp, insetHeight, heightUsed);
    292 
    293             int adjustedWidthSpec;
    294             int adjustedHeightSpec;
    295             if (lp.centerWithinArea > 0) {
    296                 if (mOrientation == HORIZONTAL) {
    297                     adjustedWidthSpec = MeasureSpec.makeMeasureSpec(
    298                             (int) ((width - widthUsed) * lp.centerWithinArea + 0.5f),
    299                             MeasureSpec.EXACTLY);
    300                     adjustedHeightSpec = MeasureSpec.makeMeasureSpec(
    301                             virtualHeight, MeasureSpec.EXACTLY);
    302                 } else {
    303                     adjustedWidthSpec = MeasureSpec.makeMeasureSpec(
    304                             width - widthUsed, MeasureSpec.EXACTLY);
    305                     adjustedHeightSpec = MeasureSpec.makeMeasureSpec(
    306                             (int) (virtualHeight * lp.centerWithinArea + 0.5f),
    307                             MeasureSpec.EXACTLY);
    308                 }
    309             } else {
    310                 adjustedWidthSpec = MeasureSpec.makeMeasureSpec(
    311                         width - widthUsed, MeasureSpec.EXACTLY);
    312                 adjustedHeightSpec = MeasureSpec.makeMeasureSpec(
    313                         virtualHeight, MeasureSpec.EXACTLY);
    314             }
    315             if (lp.maxWidth >= 0) {
    316                 adjustedWidthSpec = MeasureSpec.makeMeasureSpec(
    317                         Math.min(lp.maxWidth, MeasureSpec.getSize(adjustedWidthSpec)),
    318                         MeasureSpec.EXACTLY);
    319             }
    320             if (lp.maxHeight >= 0) {
    321                 adjustedHeightSpec = MeasureSpec.makeMeasureSpec(
    322                         Math.min(lp.maxHeight, MeasureSpec.getSize(adjustedHeightSpec)),
    323                         MeasureSpec.EXACTLY);
    324             }
    325 
    326             measureChildWithMargins(child, adjustedWidthSpec, 0, adjustedHeightSpec, 0);
    327         }
    328     }
    329 
    330     @Override
    331     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    332         final Rect padding = mTempRect;
    333         padding.left = getPaddingLeft();
    334         padding.top = getPaddingTop();
    335         padding.right = getPaddingRight();
    336         padding.bottom = getPaddingBottom();
    337         final int width = r - l;
    338         final int height = b - t;
    339         final int insetHeight = height - mInsets.top - mInsets.bottom;
    340 
    341         // Reserve extra space in layout for the user switcher by modifying
    342         // local padding during this layout pass
    343         if (mUserSwitcherView != null && mUserSwitcherView.getVisibility() != GONE) {
    344             layoutWithGravity(width, insetHeight, mUserSwitcherView, padding, true);
    345         }
    346 
    347         final int count = getChildCount();
    348         for (int i = 0; i < count; i++) {
    349             final View child = getChildAt(i);
    350             LayoutParams lp = (LayoutParams) child.getLayoutParams();
    351 
    352             // We did the user switcher above if we have one.
    353             if (child == mUserSwitcherView || child.getVisibility() == GONE) continue;
    354 
    355             if (child == mScrimView) {
    356                 child.layout(0, 0, width, height);
    357                 continue;
    358             } else if (lp.childType == LayoutParams.CHILD_TYPE_PAGE_DELETE_DROP_TARGET) {
    359                 layoutWithGravity(width, insetHeight, child, mZeroPadding, false);
    360                 continue;
    361             }
    362 
    363             layoutWithGravity(width, insetHeight, child, padding, false);
    364         }
    365     }
    366 
    367     private void layoutWithGravity(int width, int height, View child, Rect padding,
    368             boolean adjustPadding) {
    369         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    370 
    371         final int heightUsed = padding.top + padding.bottom - getPaddingTop() - getPaddingBottom();
    372         height = getVirtualHeight(lp, height, heightUsed);
    373 
    374         final int gravity = Gravity.getAbsoluteGravity(lp.gravity, getLayoutDirection());
    375 
    376         final boolean fixedLayoutSize = lp.centerWithinArea > 0;
    377         final boolean fixedLayoutHorizontal = fixedLayoutSize && mOrientation == HORIZONTAL;
    378         final boolean fixedLayoutVertical = fixedLayoutSize && mOrientation == VERTICAL;
    379 
    380         final int adjustedWidth;
    381         final int adjustedHeight;
    382         if (fixedLayoutHorizontal) {
    383             final int paddedWidth = width - padding.left - padding.right;
    384             adjustedWidth = (int) (paddedWidth * lp.centerWithinArea + 0.5f);
    385             adjustedHeight = height;
    386         } else if (fixedLayoutVertical) {
    387             final int paddedHeight = height - getPaddingTop() - getPaddingBottom();
    388             adjustedWidth = width;
    389             adjustedHeight = (int) (paddedHeight * lp.centerWithinArea + 0.5f);
    390         } else {
    391             adjustedWidth = width;
    392             adjustedHeight = height;
    393         }
    394 
    395         final boolean isVertical = Gravity.isVertical(gravity);
    396         final boolean isHorizontal = Gravity.isHorizontal(gravity);
    397         final int childWidth = child.getMeasuredWidth();
    398         final int childHeight = child.getMeasuredHeight();
    399 
    400         int left = padding.left;
    401         int top = padding.top;
    402         int right = left + childWidth;
    403         int bottom = top + childHeight;
    404         switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
    405             case Gravity.TOP:
    406                 top = fixedLayoutVertical ?
    407                         padding.top + (adjustedHeight - childHeight) / 2 : padding.top;
    408                 bottom = top + childHeight;
    409                 if (adjustPadding && isVertical) {
    410                     padding.top = bottom;
    411                     padding.bottom += childHeight / 2;
    412                 }
    413                 break;
    414             case Gravity.BOTTOM:
    415                 bottom = fixedLayoutVertical
    416                         ? padding.top + height - (adjustedHeight - childHeight) / 2
    417                         : padding.top + height;
    418                 top = bottom - childHeight;
    419                 if (adjustPadding && isVertical) {
    420                     padding.bottom = height - top;
    421                     padding.top += childHeight / 2;
    422                 }
    423                 break;
    424             case Gravity.CENTER_VERTICAL:
    425                 top = padding.top + (height - childHeight) / 2;
    426                 bottom = top + childHeight;
    427                 break;
    428         }
    429         switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
    430             case Gravity.LEFT:
    431                 left = fixedLayoutHorizontal ?
    432                         padding.left + (adjustedWidth - childWidth) / 2 : padding.left;
    433                 right = left + childWidth;
    434                 if (adjustPadding && isHorizontal && !isVertical) {
    435                     padding.left = right;
    436                     padding.right += childWidth / 2;
    437                 }
    438                 break;
    439             case Gravity.RIGHT:
    440                 right = fixedLayoutHorizontal
    441                         ? width - padding.right - (adjustedWidth - childWidth) / 2
    442                         : width - padding.right;
    443                 left = right - childWidth;
    444                 if (adjustPadding && isHorizontal && !isVertical) {
    445                     padding.right = width - left;
    446                     padding.left += childWidth / 2;
    447                 }
    448                 break;
    449             case Gravity.CENTER_HORIZONTAL:
    450                 final int paddedWidth = width - padding.left - padding.right;
    451                 left = (paddedWidth - childWidth) / 2;
    452                 right = left + childWidth;
    453                 break;
    454         }
    455         top += mInsets.top;
    456         bottom += mInsets.top;
    457         child.layout(left, top, right, bottom);
    458     }
    459 
    460     @Override
    461     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
    462         return new LayoutParams(getContext(), attrs, this);
    463     }
    464 
    465     @Override
    466     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    467         return p instanceof LayoutParams ? new LayoutParams((LayoutParams) p) :
    468                 p instanceof MarginLayoutParams ? new LayoutParams((MarginLayoutParams) p) :
    469                 new LayoutParams(p);
    470     }
    471 
    472     @Override
    473     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
    474         return new LayoutParams();
    475     }
    476 
    477     @Override
    478     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    479         return p instanceof LayoutParams;
    480     }
    481 
    482     public static class LayoutParams extends MarginLayoutParams {
    483 
    484         public float centerWithinArea = 0;
    485 
    486         public int childType = 0;
    487 
    488         public static final int CHILD_TYPE_NONE = 0;
    489         public static final int CHILD_TYPE_WIDGET = 1;
    490         public static final int CHILD_TYPE_CHALLENGE = 2;
    491         public static final int CHILD_TYPE_USER_SWITCHER = 3;
    492         public static final int CHILD_TYPE_SCRIM = 4;
    493         public static final int CHILD_TYPE_PAGE_DELETE_DROP_TARGET = 7;
    494 
    495         public int gravity = Gravity.NO_GRAVITY;
    496 
    497         public int maxWidth = -1;
    498         public int maxHeight = -1;
    499 
    500         public LayoutParams() {
    501             this(WRAP_CONTENT, WRAP_CONTENT);
    502         }
    503 
    504         LayoutParams(Context c, AttributeSet attrs, MultiPaneChallengeLayout parent) {
    505             super(c, attrs);
    506 
    507             final TypedArray a = c.obtainStyledAttributes(attrs,
    508                     R.styleable.MultiPaneChallengeLayout_Layout);
    509 
    510             centerWithinArea = a.getFloat(
    511                     R.styleable.MultiPaneChallengeLayout_Layout_layout_centerWithinArea, 0);
    512             childType = a.getInt(R.styleable.MultiPaneChallengeLayout_Layout_layout_childType,
    513                     CHILD_TYPE_NONE);
    514             gravity = a.getInt(R.styleable.MultiPaneChallengeLayout_Layout_layout_gravity,
    515                     Gravity.NO_GRAVITY);
    516             maxWidth = a.getDimensionPixelSize(
    517                     R.styleable.MultiPaneChallengeLayout_Layout_layout_maxWidth, -1);
    518             maxHeight = a.getDimensionPixelSize(
    519                     R.styleable.MultiPaneChallengeLayout_Layout_layout_maxHeight, -1);
    520 
    521             // Default gravity settings based on type and parent orientation
    522             if (gravity == Gravity.NO_GRAVITY) {
    523                 if (parent.mOrientation == HORIZONTAL) {
    524                     switch (childType) {
    525                         case CHILD_TYPE_WIDGET:
    526                             gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
    527                             break;
    528                         case CHILD_TYPE_CHALLENGE:
    529                             gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
    530                             break;
    531                         case CHILD_TYPE_USER_SWITCHER:
    532                             gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
    533                             break;
    534                     }
    535                 } else {
    536                     switch (childType) {
    537                         case CHILD_TYPE_WIDGET:
    538                             gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
    539                             break;
    540                         case CHILD_TYPE_CHALLENGE:
    541                             gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
    542                             break;
    543                         case CHILD_TYPE_USER_SWITCHER:
    544                             gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
    545                             break;
    546                     }
    547                 }
    548             }
    549 
    550             a.recycle();
    551         }
    552 
    553         public LayoutParams(int width, int height) {
    554             super(width, height);
    555         }
    556 
    557         public LayoutParams(ViewGroup.LayoutParams source) {
    558             super(source);
    559         }
    560 
    561         public LayoutParams(MarginLayoutParams source) {
    562             super(source);
    563         }
    564 
    565         public LayoutParams(LayoutParams source) {
    566             this((MarginLayoutParams) source);
    567 
    568             centerWithinArea = source.centerWithinArea;
    569             childType = source.childType;
    570             gravity = source.gravity;
    571             maxWidth = source.maxWidth;
    572             maxHeight = source.maxHeight;
    573         }
    574     }
    575 }
    576