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