SlidingChallengeLayout.java revision 8a7785c7aa2df74203276299e10b0d9056cd0560
1d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller/*
2d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller * Copyright (C) 2012 The Android Open Source Project
3d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller *
4d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller * Licensed under the Apache License, Version 2.0 (the "License");
5d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller * you may not use this file except in compliance with the License.
6d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller * You may obtain a copy of the License at
7d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller *
8d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller *      http://www.apache.org/licenses/LICENSE-2.0
9d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller *
10d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller * Unless required by applicable law or agreed to in writing, software
11d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller * distributed under the License is distributed on an "AS IS" BASIS,
12d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller * See the License for the specific language governing permissions and
14d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller * limitations under the License.
15d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller */
16d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
17d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerpackage com.android.internal.policy.impl.keyguard;
18d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
196f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powellimport android.animation.ObjectAnimator;
20d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.content.Context;
21d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.content.res.TypedArray;
22d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.graphics.Canvas;
231bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandlerimport android.graphics.Paint;
24d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.graphics.drawable.Drawable;
25d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.util.AttributeSet;
266f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powellimport android.util.FloatProperty;
27d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.util.Log;
286f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powellimport android.util.Property;
29d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.view.MotionEvent;
30d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.view.VelocityTracker;
31d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.view.View;
32d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.view.ViewConfiguration;
33d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.view.ViewGroup;
34d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.view.animation.Interpolator;
35d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport android.widget.Scroller;
36d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
37d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Millerimport com.android.internal.R;
38d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
39d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller/**
40d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller * This layout handles interaction with the sliding security challenge views
41d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller * that overlay/resize other keyguard contents.
42d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller */
4319a5267003e7dc70100a4bd4f1f449523b2ff38bJim Millerpublic class SlidingChallengeLayout extends ViewGroup implements ChallengeLayout {
44d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private static final String TAG = "SlidingChallengeLayout";
451bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler    private static final boolean DEBUG = false;
46d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
47c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    // The drag handle is measured in dp above & below the top edge of the
48c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    // challenge view; these parameters change based on whether the challenge
49c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    // is open or closed.
50cfc3086b18396df03f982fb98112649dc5b5a6f2Adam Powell    private static final int DRAG_HANDLE_CLOSED_ABOVE = 64; // dp
51cfc3086b18396df03f982fb98112649dc5b5a6f2Adam Powell    private static final int DRAG_HANDLE_CLOSED_BELOW = 0; // dp
52c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    private static final int DRAG_HANDLE_OPEN_ABOVE = 8; // dp
53c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    private static final int DRAG_HANDLE_OPEN_BELOW = 0; // dp
54c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler
55c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    private static final boolean OPEN_ON_CLICK = true;
56c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler
57c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    private static final int HANDLE_ANIMATE_DURATION = 200; // ms
58c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler
59d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    // Drawn to show the drag handle in closed state; crossfades to the challenge view
60d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    // when challenge is fully visible
61d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private Drawable mHandleDrawable;
626f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    private Drawable mFrameDrawable;
636f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    private Drawable mDragIconDrawable;
64d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
65d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    // Initialized during measurement from child layoutparams
66d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private View mChallengeView;
67eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    private View mScrimView;
68d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
69d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    // Range: 0 (fully hidden) to 1 (fully visible)
70d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private float mChallengeOffset = 1.f;
71d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private boolean mChallengeShowing = true;
72eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    private boolean mIsBouncing = false;
73d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
74d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private final Scroller mScroller;
75d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private int mScrollState;
760b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell    private OnChallengeScrolledListener mScrollListener;
770b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell    private OnBouncerStateChangedListener mBouncerListener;
78d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
79d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public static final int SCROLL_STATE_IDLE = 0;
80d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public static final int SCROLL_STATE_DRAGGING = 1;
81d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public static final int SCROLL_STATE_SETTLING = 2;
82d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
83d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private static final int MAX_SETTLE_DURATION = 600; // ms
84d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
85d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    // ID of the pointer in charge of a current drag
86d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private int mActivePointerId = INVALID_POINTER;
87d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private static final int INVALID_POINTER = -1;
88d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
89d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    // True if the user is currently dragging the slider
90d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private boolean mDragging;
91d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    // True if the user may not drag until a new gesture begins
92d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private boolean mBlockDrag;
93d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
94d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private VelocityTracker mVelocityTracker;
95d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private int mMinVelocity;
96d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private int mMaxVelocity;
97c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    private float mGestureStartX, mGestureStartY; // where did you first touch the screen?
98b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler    private int mGestureStartChallengeBottom; // where was the challenge at that time?
99c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler
100c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    private int mDragHandleClosedBelow; // handle hitrect extension into the challenge view
101c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    private int mDragHandleClosedAbove; // extend the handle's hitrect this far above the line
102c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    private int mDragHandleOpenBelow; // handle hitrect extension into the challenge view
103c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    private int mDragHandleOpenAbove; // extend the handle's hitrect this far above the line
104c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler
1050b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell    private int mDragHandleEdgeSlop;
10667625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell    private int mChallengeBottomBound; // Number of pixels from the top of the challenge view
10767625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell                                       // that should remain on-screen
108c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler
109c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    private int mTouchSlop;
110c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler
1116f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    float mHandleAlpha;
1126f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    float mFrameAlpha;
1136f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    private ObjectAnimator mHandleAnimation;
1146f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    private ObjectAnimator mFrameAnimation;
1156f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell
1166f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    static final Property<SlidingChallengeLayout, Float> HANDLE_ALPHA =
1176f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            new FloatProperty<SlidingChallengeLayout>("handleAlpha") {
1186f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        @Override
1196f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        public void setValue(SlidingChallengeLayout view, float value) {
1206f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            view.mHandleAlpha = value;
1216f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            view.invalidate();
1226f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        }
1236f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell
1246f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        @Override
1256f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        public Float get(SlidingChallengeLayout view) {
1266f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            return view.mHandleAlpha;
1276f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        }
1286f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    };
1296f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell
1306f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    static final Property<SlidingChallengeLayout, Float> FRAME_ALPHA =
1316f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            new FloatProperty<SlidingChallengeLayout>("frameAlpha") {
1326f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        @Override
1336f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        public void setValue(SlidingChallengeLayout view, float value) {
1346f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            if (view.mFrameDrawable != null) {
1356f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                view.mFrameAlpha = value;
1366f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                view.mFrameDrawable.setAlpha((int) (value * 0xFF));
1376f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                view.mFrameDrawable.invalidateSelf();
1386f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            }
1396f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        }
1406f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell
1416f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        @Override
1426f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        public Float get(SlidingChallengeLayout view) {
1436f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            return view.mFrameAlpha;
1446f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        }
1456f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    };
146d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
147d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    // True if at least one layout pass has happened since the view was attached.
148d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private boolean mHasLayout;
149d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
150d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private static final Interpolator sMotionInterpolator = new Interpolator() {
151d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        public float getInterpolation(float t) {
152d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            t -= 1.0f;
153d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            return t * t * t * t * t + 1.0f;
154d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
155d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    };
156d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
157d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private static final Interpolator sHandleFadeInterpolator = new Interpolator() {
158d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        public float getInterpolation(float t) {
159d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            return t * t;
160d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
161d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    };
162d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
163d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private final Runnable mEndScrollRunnable = new Runnable () {
164d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        public void run() {
165d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            completeChallengeScroll();
166d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
167d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    };
168d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
169eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    private final OnClickListener mScrimClickListener = new OnClickListener() {
170eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        @Override
171eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        public void onClick(View v) {
172eee209313d564be37b8fc068f785b3844cd6597eAdam Powell            hideBouncer();
173eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        }
174eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    };
175eee209313d564be37b8fc068f785b3844cd6597eAdam Powell
176d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    /**
177d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * Listener interface that reports changes in scroll state of the challenge area.
178d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     */
179d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public interface OnChallengeScrolledListener {
180d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        /**
181d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * The scroll state itself changed.
182d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         *
183d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * <p>scrollState will be one of the following:</p>
184d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         *
185d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * <ul>
186d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * <li><code>SCROLL_STATE_IDLE</code> - The challenge area is stationary.</li>
187d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * <li><code>SCROLL_STATE_DRAGGING</code> - The user is actively dragging
188d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * the challenge area.</li>
189d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * <li><code>SCROLL_STATE_SETTLING</code> - The challenge area is animating
190d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * into place.</li>
191d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * </ul>
192d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         *
193d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * <p>Do not perform expensive operations (e.g. layout)
194d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * while the scroll state is not <code>SCROLL_STATE_IDLE</code>.</p>
195d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         *
196d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * @param scrollState The new scroll state of the challenge area.
197d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         */
198d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        public void onScrollStateChanged(int scrollState);
199d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
200d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        /**
201d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * The precise position of the challenge area has changed.
202d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         *
203d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * <p>NOTE: It is NOT safe to modify layout or call any View methods that may
204d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * result in a requestLayout anywhere in your view hierarchy as a result of this call.
205d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * It may be called during drawing.</p>
206d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         *
207d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * @param scrollPosition New relative position of the challenge area.
208d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         *                       1.f = fully visible/ready to be interacted with.
209d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         *                       0.f = fully invisible/inaccessible to the user.
210d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         * @param challengeTop Position of the top edge of the challenge view in px in the
211d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         *                     SlidingChallengeLayout's coordinate system.
212d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller         */
213d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        public void onScrollPositionChanged(float scrollPosition, int challengeTop);
214d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
215d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
216d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public SlidingChallengeLayout(Context context) {
217d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        this(context, null);
218d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
219d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
220d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public SlidingChallengeLayout(Context context, AttributeSet attrs) {
221d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        this(context, attrs, 0);
222d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
223d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
224d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public SlidingChallengeLayout(Context context, AttributeSet attrs, int defStyle) {
225d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        super(context, attrs, defStyle);
226d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
227d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final TypedArray a = context.obtainStyledAttributes(attrs,
228d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                R.styleable.SlidingChallengeLayout, defStyle, 0);
2296f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        setDragDrawables(a.getDrawable(R.styleable.SlidingChallengeLayout_dragHandle),
2306f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                a.getDrawable(R.styleable.SlidingChallengeLayout_dragIcon));
231d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
232d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        a.recycle();
233d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
234d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mScroller = new Scroller(context, sMotionInterpolator);
235d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
236d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final ViewConfiguration vc = ViewConfiguration.get(context);
237d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mMinVelocity = vc.getScaledMinimumFlingVelocity();
238d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mMaxVelocity = vc.getScaledMaximumFlingVelocity();
239d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
2400b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        mDragHandleEdgeSlop = getResources().getDimensionPixelSize(
2410b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell                R.dimen.kg_edge_swipe_region_size);
2420b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell
243c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
244c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler
2456f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        final float density = getResources().getDisplayMetrics().density;
2461bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler
2471bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler        // top half of the lock icon, plus another 25% to be sure
248c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler        mDragHandleClosedAbove = (int) (DRAG_HANDLE_CLOSED_ABOVE * density + 0.5f);
249c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler        mDragHandleClosedBelow = (int) (DRAG_HANDLE_CLOSED_BELOW * density + 0.5f);
250c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler        mDragHandleOpenAbove = (int) (DRAG_HANDLE_OPEN_ABOVE * density + 0.5f);
251c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler        mDragHandleOpenBelow = (int) (DRAG_HANDLE_OPEN_BELOW * density + 0.5f);
252c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler
253c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler        // how much space to account for in the handle when closed
254cfc3086b18396df03f982fb98112649dc5b5a6f2Adam Powell        mChallengeBottomBound = mDragHandleClosedBelow;
2551bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler
256cfc3086b18396df03f982fb98112649dc5b5a6f2Adam Powell        setWillNotDraw(false);
257cfc3086b18396df03f982fb98112649dc5b5a6f2Adam Powell    }
258cfc3086b18396df03f982fb98112649dc5b5a6f2Adam Powell
259cfc3086b18396df03f982fb98112649dc5b5a6f2Adam Powell    public void setDragDrawables(Drawable handle, Drawable icon) {
2606f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        mHandleDrawable = handle;
2616f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        mDragIconDrawable = icon;
2626f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    }
2636f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell
2646f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    public void setDragIconDrawable(Drawable d) {
2656f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        mDragIconDrawable = d;
2666f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    }
2676f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell
2686f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    public void showHandle(boolean visible) {
2696f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        if (visible) {
2706f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            if (mHandleAnimation != null) {
2716f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                mHandleAnimation.cancel();
2726f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                mHandleAnimation = null;
2736f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            }
2746f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            mHandleAlpha = 1.f;
2756f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            invalidate();
2766f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        } else {
2776f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            animateHandle(false);
2786f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        }
2796f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    }
2806f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell
2816f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    void animateHandle(boolean visible) {
2826f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        if (mHandleAnimation != null) {
2836f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            mHandleAnimation.cancel();
28467625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell            mHandleAnimation = null;
285d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
28667625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        final float targetAlpha = visible ? 1.f : 0.f;
28767625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        if (targetAlpha == mHandleAlpha) {
28867625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell            return;
28967625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        }
29067625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        mHandleAnimation = ObjectAnimator.ofFloat(this, HANDLE_ALPHA, targetAlpha);
2916f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        mHandleAnimation.setInterpolator(sHandleFadeInterpolator);
2926f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        mHandleAnimation.setDuration(HANDLE_ANIMATE_DURATION);
2936f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        mHandleAnimation.start();
2946f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    }
2956f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell
2966f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell    void animateFrame(boolean visible, boolean full) {
2976f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        if (mFrameDrawable == null) return;
2986f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell
2996f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        if (mFrameAnimation != null) {
3006f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            mFrameAnimation.cancel();
30167625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell            mFrameAnimation = null;
30267625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        }
30367625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        final float targetAlpha = visible ? (full ? 1.f : 0.5f) : 0.f;
30467625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        if (targetAlpha == mFrameAlpha) {
30567625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell            return;
306d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
30767625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell
30867625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        mFrameAnimation = ObjectAnimator.ofFloat(this, FRAME_ALPHA, targetAlpha);
3096f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        mFrameAnimation.setInterpolator(sHandleFadeInterpolator);
3106f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        mFrameAnimation.setDuration(HANDLE_ANIMATE_DURATION);
3116f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        mFrameAnimation.start();
312d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
313d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
314d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private void sendInitialListenerUpdates() {
3150b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        if (mScrollListener != null) {
316d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            int challengeTop = mChallengeView != null ? mChallengeView.getTop() : 0;
3170b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell            mScrollListener.onScrollPositionChanged(mChallengeOffset, challengeTop);
3180b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell            mScrollListener.onScrollStateChanged(mScrollState);
319d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
320d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
321d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
322d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public void setOnChallengeScrolledListener(OnChallengeScrolledListener listener) {
3230b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        mScrollListener = listener;
324d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (mHasLayout) {
325d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            sendInitialListenerUpdates();
326d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
327d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
328d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
3290b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell    public void setOnBouncerStateChangedListener(OnBouncerStateChangedListener listener) {
3300b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        mBouncerListener = listener;
3310b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell    }
3320b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell
333d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    @Override
334d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public void onAttachedToWindow() {
335d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        super.onAttachedToWindow();
336d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
337d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mHasLayout = false;
338d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
339d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
340d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    @Override
341d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public void onDetachedFromWindow() {
342d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        super.onDetachedFromWindow();
343d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
344d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        removeCallbacks(mEndScrollRunnable);
345d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mHasLayout = false;
346d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
347d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
3480b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell    @Override
3490b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell    public void requestChildFocus(View child, View focused) {
3500b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        if (mIsBouncing && child != mChallengeView) {
3510b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell            // Clear out of the bouncer if the user tries to move focus outside of
3520b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell            // the security challenge view.
3530b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell            hideBouncer();
3540b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        }
3550b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        super.requestChildFocus(child, focused);
3560b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell    }
3570b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell
358d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    // We want the duration of the page snap animation to be influenced by the distance that
359d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    // the screen has to travel, however, we don't want this duration to be effected in a
360d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
361d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    // of travel has on the overall snap duration.
362d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    float distanceInfluenceForSnapDuration(float f) {
363d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        f -= 0.5f; // center the values about 0.
364d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        f *= 0.3f * Math.PI / 2.0f;
365d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        return (float) Math.sin(f);
366d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
367d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
368d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    void setScrollState(int state) {
369d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (mScrollState != state) {
370d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            mScrollState = state;
371d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
3726f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            animateHandle(state == SCROLL_STATE_IDLE && !mChallengeShowing);
373e3643138c89d49a01ba6a622ffaf71c9a95d5cdcAdam Cohen            animateFrame(false , false);
3740b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell            if (mScrollListener != null) {
3750b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell                mScrollListener.onScrollStateChanged(state);
376d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            }
377d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
378d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
379d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
380d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    void completeChallengeScroll() {
381d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        setChallengeShowing(mChallengeOffset != 0);
382d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        setScrollState(SCROLL_STATE_IDLE);
383d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
384d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
385eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    void setScrimView(View scrim) {
386eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        if (mScrimView != null) {
387eee209313d564be37b8fc068f785b3844cd6597eAdam Powell            mScrimView.setOnClickListener(null);
388eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        }
389eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        mScrimView = scrim;
390eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        mScrimView.setVisibility(mIsBouncing ? VISIBLE : GONE);
391eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        mScrimView.setFocusable(true);
392eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        mScrimView.setOnClickListener(mScrimClickListener);
393eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    }
394eee209313d564be37b8fc068f785b3844cd6597eAdam Powell
395d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    /**
396d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * Animate the bottom edge of the challenge view to the given position.
397d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     *
398d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * @param y desired final position for the bottom edge of the challenge view in px
399d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * @param velocity velocity in
400d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     */
401d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    void animateChallengeTo(int y, int velocity) {
402d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (mChallengeView == null) {
403d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            // Nothing to do.
404d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            return;
405d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
40667625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        final int sy = mChallengeView.getBottom();
40767625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        final int dy = y - sy;
408d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (dy == 0) {
409d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            completeChallengeScroll();
410d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            return;
411d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
412d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
413d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        setScrollState(SCROLL_STATE_SETTLING);
414d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
415d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int childHeight = mChallengeView.getHeight();
416d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int halfHeight = childHeight / 2;
417d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dy) / childHeight);
418d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final float distance = halfHeight + halfHeight *
419d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                distanceInfluenceForSnapDuration(distanceRatio);
420d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
421d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        int duration = 0;
422d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        velocity = Math.abs(velocity);
423d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (velocity > 0) {
424d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
425d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        } else {
426d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            final float childDelta = (float) Math.abs(dy) / childHeight;
427d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            duration = (int) ((childDelta + 1) * 100);
428d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
429d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        duration = Math.min(duration, MAX_SETTLE_DURATION);
430d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
431d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mScroller.startScroll(0, sy, 0, dy, duration);
432d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        postInvalidateOnAnimation();
433d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
434d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
435d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private void setChallengeShowing(boolean showChallenge) {
43667625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        mChallengeShowing = showChallenge;
437d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
438d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
439d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    /**
440d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * @return true if the challenge is at all visible.
441d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     */
442d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public boolean isChallengeShowing() {
443d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        return mChallengeShowing;
444d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
445d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
446d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    @Override
44719a5267003e7dc70100a4bd4f1f449523b2ff38bJim Miller    public boolean isChallengeOverlapping() {
44819a5267003e7dc70100a4bd4f1f449523b2ff38bJim Miller        return mChallengeShowing;
44919a5267003e7dc70100a4bd4f1f449523b2ff38bJim Miller    }
45019a5267003e7dc70100a4bd4f1f449523b2ff38bJim Miller
45119a5267003e7dc70100a4bd4f1f449523b2ff38bJim Miller    @Override
452eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    public boolean isBouncing() {
453eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        return mIsBouncing;
454eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    }
455eee209313d564be37b8fc068f785b3844cd6597eAdam Powell
456eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    @Override
457eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    public void showBouncer() {
458eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        if (mIsBouncing) return;
459eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        showChallenge(true);
460eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        mIsBouncing = true;
461eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        if (mScrimView != null) {
4626f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            mScrimView.setVisibility(VISIBLE);
463eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        }
4646f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        animateFrame(true, true);
4650b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        if (mBouncerListener != null) {
4660b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell            mBouncerListener.onBouncerStateChanged(true);
4670b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        }
468eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    }
469eee209313d564be37b8fc068f785b3844cd6597eAdam Powell
470eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    @Override
471eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    public void hideBouncer() {
472eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        if (!mIsBouncing) return;
473eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        setChallengeShowing(false);
474eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        mIsBouncing = false;
475eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        if (mScrimView != null) {
476eee209313d564be37b8fc068f785b3844cd6597eAdam Powell            mScrimView.setVisibility(GONE);
477eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        }
4786f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell        animateFrame(false, false);
4790b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        if (mBouncerListener != null) {
4800b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell            mBouncerListener.onBouncerStateChanged(false);
4810b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        }
482eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    }
483eee209313d564be37b8fc068f785b3844cd6597eAdam Powell
484eee209313d564be37b8fc068f785b3844cd6597eAdam Powell    @Override
485d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public void requestDisallowInterceptTouchEvent(boolean allowIntercept) {
486d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        // We'll intercept whoever we feel like! ...as long as it isn't a challenge view.
487d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        // If there are one or more pointers in the challenge view before we take over
488d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        // touch events, onInterceptTouchEvent will set mBlockDrag.
489d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
490d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
491d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    @Override
492d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public boolean onInterceptTouchEvent(MotionEvent ev) {
493d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (mVelocityTracker == null) {
494d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            mVelocityTracker = VelocityTracker.obtain();
495d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
496d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mVelocityTracker.addMovement(ev);
497d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
498c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler        //Log.v(TAG, "onIntercept: " + ev);
499c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler
500d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int action = ev.getActionMasked();
501d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        switch (action) {
502022554e3da6e496524387a5d3ecc67ef9749b587Jim Miller            case MotionEvent.ACTION_DOWN:
503c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                mGestureStartX = ev.getX();
504b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler                mGestureStartY = ev.getY();
505995270f01175234d0615a0e1900576f142a3b4adAdam Powell                mBlockDrag = false;
506022554e3da6e496524387a5d3ecc67ef9749b587Jim Miller                break;
507022554e3da6e496524387a5d3ecc67ef9749b587Jim Miller
508d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            case MotionEvent.ACTION_CANCEL:
509d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            case MotionEvent.ACTION_UP:
510d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                resetTouch();
511d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                break;
512d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
513d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            case MotionEvent.ACTION_MOVE:
514d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                final int count = ev.getPointerCount();
515d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                for (int i = 0; i < count; i++) {
516d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    final float x = ev.getX(i);
517d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    final float y = ev.getY(i);
518d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
5196f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                    if (!mIsBouncing &&
520b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler                            (isInDragHandle(x, y) || crossedDragHandle(x, y, mGestureStartY) ||
521d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                            (isInChallengeView(x, y) && mScrollState == SCROLL_STATE_SETTLING)) &&
522d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                            mActivePointerId == INVALID_POINTER) {
523d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        mActivePointerId = ev.getPointerId(i);
524c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                        mGestureStartX = x;
525c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                        mGestureStartY = y;
526b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler                        mGestureStartChallengeBottom = getChallengeBottom();
527d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        mDragging = true;
528d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    } else if (isInChallengeView(x, y)) {
529d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        mBlockDrag = true;
530d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    }
531d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                }
532d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                break;
533d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
534d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
535d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (mBlockDrag) {
536d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            mActivePointerId = INVALID_POINTER;
537d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            mDragging = false;
538d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
539d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
540d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        return mDragging;
541d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
542d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
543d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private void resetTouch() {
544d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mVelocityTracker.recycle();
545d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mVelocityTracker = null;
546d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mActivePointerId = INVALID_POINTER;
547d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mDragging = mBlockDrag = false;
548d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
549d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
550d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    @Override
551d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public boolean onTouchEvent(MotionEvent ev) {
552d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (mVelocityTracker == null) {
553d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            mVelocityTracker = VelocityTracker.obtain();
554d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
555d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mVelocityTracker.addMovement(ev);
556d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
557d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int action = ev.getActionMasked();
558d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        switch (action) {
559995270f01175234d0615a0e1900576f142a3b4adAdam Powell            case MotionEvent.ACTION_DOWN:
560995270f01175234d0615a0e1900576f142a3b4adAdam Powell                mBlockDrag = false;
561c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                mGestureStartX = ev.getX();
562995270f01175234d0615a0e1900576f142a3b4adAdam Powell                mGestureStartY = ev.getY();
563995270f01175234d0615a0e1900576f142a3b4adAdam Powell                break;
564995270f01175234d0615a0e1900576f142a3b4adAdam Powell
565d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            case MotionEvent.ACTION_CANCEL:
566d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                if (mDragging) {
567d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    showChallenge(0);
568d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                }
569d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                resetTouch();
570d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                break;
571d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
572d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            case MotionEvent.ACTION_POINTER_UP:
573d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                if (mActivePointerId != ev.getPointerId(ev.getActionIndex())) {
574d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    break;
575d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                }
576d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            case MotionEvent.ACTION_UP:
577c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                if (OPEN_ON_CLICK
578c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                        && isInDragHandle(mGestureStartX, mGestureStartY)
579c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                        && Math.abs(ev.getX() - mGestureStartX) <= mTouchSlop
580c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                        && Math.abs(ev.getY() - mGestureStartY) <= mTouchSlop) {
581c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                    showChallenge(true);
582c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                } else if (mDragging) {
583d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
584d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    showChallenge((int) mVelocityTracker.getYVelocity(mActivePointerId));
585d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                }
586d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                resetTouch();
587d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                break;
588d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
589d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            case MotionEvent.ACTION_MOVE:
5906f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                if (!mDragging && !mBlockDrag && !mIsBouncing) {
591d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    final int count = ev.getPointerCount();
592d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    for (int i = 0; i < count; i++) {
593d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        final float x = ev.getX(i);
594d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        final float y = ev.getY(i);
595d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
596b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler                        if ((isInDragHandle(x, y) || crossedDragHandle(x, y, mGestureStartY) ||
597d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                                (isInChallengeView(x, y) && mScrollState == SCROLL_STATE_SETTLING))
598d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                                && mActivePointerId == INVALID_POINTER) {
599c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                            mGestureStartX = x;
600b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler                            mGestureStartY = y;
601d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                            mActivePointerId = ev.getPointerId(i);
602b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler                            mGestureStartChallengeBottom = getChallengeBottom();
603d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                            mDragging = true;
604d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                            break;
605d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        }
606d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    }
607d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                }
608d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                // Not an else; this can be set above.
609d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                if (mDragging) {
610d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    // No-op if already in this state, but set it here in case we arrived
611d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    // at this point from either intercept or the above.
612d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    setScrollState(SCROLL_STATE_DRAGGING);
613d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
614d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    final int index = ev.findPointerIndex(mActivePointerId);
615d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    if (index < 0) {
616d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        // Oops, bogus state. We lost some touch events somewhere.
617d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        // Just drop it with no velocity and let things settle.
618d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        resetTouch();
619d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        showChallenge(0);
620d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        return true;
621d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    }
622b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler                    final float y = ev.getY(index);
623b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler                    final float pos = Math.min(y - mGestureStartY,
62467625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell                            getLayoutBottom() - mChallengeBottomBound);
625b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler
626b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler                    moveChallengeTo(mGestureStartChallengeBottom + (int) pos);
627d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                }
628d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                break;
629d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
630d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        return true;
631d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
632d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
633b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler    /**
634b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler     * We only want to add additional vertical space to the drag handle when the panel is fully
635b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler     * closed.
636b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler     */
637c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    private int getDragHandleSizeAbove() {
638c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler        return isChallengeShowing() ? mDragHandleOpenAbove : mDragHandleClosedAbove;
639c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    }
640c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler    private int getDragHandleSizeBelow() {
641c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler        return isChallengeShowing() ? mDragHandleOpenBelow : mDragHandleClosedBelow;
642b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler    }
643b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler
644d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private boolean isInChallengeView(float x, float y) {
645d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (mChallengeView == null) return false;
646d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
647d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        return x >= mChallengeView.getLeft() && y >= mChallengeView.getTop() &&
648d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                x < mChallengeView.getRight() && y < mChallengeView.getBottom();
649d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
650d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
651d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private boolean isInDragHandle(float x, float y) {
652d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (mChallengeView == null) return false;
653d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
6540b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        return x >= mDragHandleEdgeSlop &&
655c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                y >= mChallengeView.getTop() - getDragHandleSizeAbove() &&
6560b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell                x < getWidth() - mDragHandleEdgeSlop &&
657c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                y < mChallengeView.getTop() + getDragHandleSizeBelow();
658d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
659d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
660b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler    private boolean crossedDragHandle(float x, float y, float initialY) {
661022554e3da6e496524387a5d3ecc67ef9749b587Jim Miller        final int challengeTop = mChallengeView.getTop();
6621bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler        return  x >= 0 &&
6631bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                x < getWidth() &&
664c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                initialY < (challengeTop - getDragHandleSizeAbove()) &&
665c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                y > challengeTop + getDragHandleSizeBelow();
666022554e3da6e496524387a5d3ecc67ef9749b587Jim Miller    }
667022554e3da6e496524387a5d3ecc67ef9749b587Jim Miller
668d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    @Override
669d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    protected void onMeasure(int widthSpec, int heightSpec) {
670d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (MeasureSpec.getMode(widthSpec) != MeasureSpec.EXACTLY ||
671d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                MeasureSpec.getMode(heightSpec) != MeasureSpec.EXACTLY) {
672d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            throw new IllegalArgumentException(
673d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    "SlidingChallengeLayout must be measured with an exact size");
674d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
675d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
676d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int width = MeasureSpec.getSize(widthSpec);
677d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int height = MeasureSpec.getSize(heightSpec);
678d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        setMeasuredDimension(width, height);
679d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
680d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        // Find one and only one challenge view.
681d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final View oldChallengeView = mChallengeView;
682d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mChallengeView = null;
683d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int count = getChildCount();
6848a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen
6858a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen        // First iteration through the children finds special children and sets any associated
6868a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen        // state.
687d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        for (int i = 0; i < count; i++) {
688d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            final View child = getChildAt(i);
689d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
690d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
691eee209313d564be37b8fc068f785b3844cd6597eAdam Powell            if (lp.childType == LayoutParams.CHILD_TYPE_CHALLENGE) {
692d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                if (mChallengeView != null) {
693d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    throw new IllegalStateException(
694d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                            "There may only be one child with layout_isChallenge=\"true\"");
695d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                }
696d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                mChallengeView = child;
697d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                if (mChallengeView != oldChallengeView) {
698d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    mChallengeView.setVisibility(mChallengeShowing ? VISIBLE : INVISIBLE);
699d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                }
7006f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                // We're going to play silly games with the frame's background drawable later.
7016f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                mFrameDrawable = mChallengeView.getBackground();
702eee209313d564be37b8fc068f785b3844cd6597eAdam Powell            } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) {
703eee209313d564be37b8fc068f785b3844cd6597eAdam Powell                setScrimView(child);
704d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            }
705d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            if (child.getVisibility() == GONE) continue;
7068a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen        }
7078a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen
7088a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen        // We want to measure the challenge view first, for various reasons that I'd rather
7098a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen        // not get into here.
7108a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen        if (mChallengeView != null) {
7118a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen            measureChildWithMargins(mChallengeView, widthSpec, 0, heightSpec, 0);
7128a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen        }
713d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
7148a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen        // Measure the rest of the children
7158a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen        for (int i = 0; i < count; i++) {
7168a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen            final View child = getChildAt(i);
7178a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen            // Don't measure the challenge view twice!
7188a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen            if (child != mChallengeView) {
7198a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen                measureChildWithMargins(child, widthSpec, 0, heightSpec, 0);
7208a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen            }
721d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
722d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
723d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
724d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    @Override
725d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    protected void onLayout(boolean changed, int l, int t, int r, int b) {
726d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int paddingLeft = getPaddingLeft();
727d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int paddingTop = getPaddingTop();
728d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int paddingRight = getPaddingRight();
729d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int paddingBottom = getPaddingBottom();
730d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int width = r - l;
731d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int height = b - t;
732d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
733d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int count = getChildCount();
734d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        for (int i = 0; i < count; i++) {
735d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            final View child = getChildAt(i);
736d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
737d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            if (child.getVisibility() == GONE) continue;
738d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
739d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
740d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
741eee209313d564be37b8fc068f785b3844cd6597eAdam Powell            if (lp.childType == LayoutParams.CHILD_TYPE_CHALLENGE) {
742d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                // Challenge views pin to the bottom, offset by a portion of their height,
743d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                // and center horizontally.
744d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                final int center = (paddingLeft + width - paddingRight) / 2;
745d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                final int childWidth = child.getMeasuredWidth();
746d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                final int childHeight = child.getMeasuredHeight();
747d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                final int left = center - childWidth / 2;
748d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                final int layoutBottom = height - paddingBottom - lp.bottomMargin;
749d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                // We use the top of the challenge view to position the handle, so
750d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                // we never want less than the handle size showing at the bottom.
75167625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell                final int bottom = layoutBottom + (int) ((childHeight - mChallengeBottomBound)
752d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        * (1 - mChallengeOffset));
75367625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell                child.setAlpha(mChallengeOffset / 2 + 0.5f);
754d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                child.layout(left, bottom - childHeight, left + childWidth, bottom);
755d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            } else {
756d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                // Non-challenge views lay out from the upper left, layered.
757d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                child.layout(paddingLeft + lp.leftMargin,
758d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        paddingTop + lp.topMargin,
759d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        paddingLeft + child.getMeasuredWidth(),
760d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                        paddingTop + child.getMeasuredHeight());
761d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            }
762d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
763d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
764d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (!mHasLayout) {
76567625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell            if (mFrameDrawable != null) {
76667625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell                mFrameDrawable.setAlpha(0);
76767625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell            }
7686f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            mHasLayout = true;
769d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
770d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
771d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
772d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public void computeScroll() {
773d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        super.computeScroll();
774d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
775d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (!mScroller.isFinished()) {
776d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            if (mChallengeView == null) {
777d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                // Can't scroll if the view is missing.
778d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                Log.e(TAG, "Challenge view missing in computeScroll");
779d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                mScroller.abortAnimation();
780d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                return;
781d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            }
782d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
783d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            mScroller.computeScrollOffset();
784d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            moveChallengeTo(mScroller.getCurrY());
785d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
786d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            if (mScroller.isFinished()) {
787d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                post(mEndScrollRunnable);
788d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            }
789d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
790d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
791d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
792d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    @Override
793d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public void draw(Canvas c) {
794d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        super.draw(c);
7951bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler
7961bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler        final Paint debugPaint;
7971bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler        if (DEBUG) {
7981bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler            debugPaint = new Paint();
7991bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler            debugPaint.setColor(0x40FF00CC);
8001bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler            // show the isInDragHandle() rect
8011bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler            c.drawRect(mDragHandleEdgeSlop,
802c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                    mChallengeView.getTop() - getDragHandleSizeAbove(),
8031bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                    getWidth() - mDragHandleEdgeSlop,
804c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                    mChallengeView.getTop() + getDragHandleSizeBelow(),
8051bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                    debugPaint);
8061bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler        }
8071bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler
808c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler        if (mChallengeView != null && mHandleAlpha > 0) {
809d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            final int top = mChallengeView.getTop();
810c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler            final int handleHeight;
8116f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            final int challengeLeft = mChallengeView.getLeft();
8126f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            final int challengeRight = mChallengeView.getRight();
813c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler            if (mHandleDrawable != null) {
814c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                handleHeight = mHandleDrawable.getIntrinsicHeight();
815c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                mHandleDrawable.setBounds(challengeLeft, top, challengeRight, top + handleHeight);
816c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                mHandleDrawable.setAlpha((int) (mHandleAlpha * 0xFF));
817c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                mHandleDrawable.draw(c);
818c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler            } else {
819c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler                handleHeight = 0;
820c606bf1af953bb8a682589a29809e714c648c291Daniel Sandler            }
8216f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell
8221bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler            if (DEBUG) {
8231bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                // now show the actual drag handle
8241bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                debugPaint.setStyle(Paint.Style.STROKE);
8251bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                debugPaint.setStrokeWidth(1);
8261bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                debugPaint.setColor(0xFF80FF00);
8271bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                c.drawRect(challengeLeft, top, challengeRight, top + handleHeight, debugPaint);
8281bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler            }
8291bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler
8306f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            if (mDragIconDrawable != null) {
831cfc3086b18396df03f982fb98112649dc5b5a6f2Adam Powell                final int closedTop = getLayoutBottom() - mChallengeBottomBound;
8326f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                final int iconWidth = mDragIconDrawable.getIntrinsicWidth();
8336f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                final int iconHeight = mDragIconDrawable.getIntrinsicHeight();
8346f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                final int iconLeft = (challengeLeft + challengeRight - iconWidth) / 2;
835cfc3086b18396df03f982fb98112649dc5b5a6f2Adam Powell                final int iconTop = closedTop +
836cfc3086b18396df03f982fb98112649dc5b5a6f2Adam Powell                        (mDragHandleClosedBelow - mDragHandleClosedAbove - iconHeight) / 2;
8376f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                mDragIconDrawable.setBounds(iconLeft, iconTop, iconLeft + iconWidth,
8386f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                        iconTop + iconHeight);
8396f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                mDragIconDrawable.setAlpha((int) (mHandleAlpha * 0xFF));
8406f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell                mDragIconDrawable.draw(c);
8411bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler
8421bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                if (DEBUG) {
8431bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                    debugPaint.setColor(0xFF00FF00);
8441bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                    c.drawRect(iconLeft, iconTop, iconLeft + iconWidth,
8451bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                        iconTop + iconHeight, debugPaint);
8461bfcc82184824c8dc648a0948303f4b3611ac087Daniel Sandler                }
8476f35209e728a7a9a56afbbdefd3d7bfa1e05ea73Adam Powell            }
848d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
849d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
850d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
851e3643138c89d49a01ba6a622ffaf71c9a95d5cdcAdam Cohen    public int getMaxChallengeTop() {
852e3643138c89d49a01ba6a622ffaf71c9a95d5cdcAdam Cohen        if (mChallengeView == null) return 0;
853e3643138c89d49a01ba6a622ffaf71c9a95d5cdcAdam Cohen
854e3643138c89d49a01ba6a622ffaf71c9a95d5cdcAdam Cohen        final int layoutBottom = getLayoutBottom();
8558a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen        final int challengeHeight = mChallengeView.getMeasuredHeight();
856e3643138c89d49a01ba6a622ffaf71c9a95d5cdcAdam Cohen        return layoutBottom - challengeHeight;
857e3643138c89d49a01ba6a622ffaf71c9a95d5cdcAdam Cohen    }
858e3643138c89d49a01ba6a622ffaf71c9a95d5cdcAdam Cohen
859d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    /**
860d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * Move the bottom edge of mChallengeView to a new position and notify the listener
861d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * if it represents a change in position. Changes made through this method will
862d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * be stable across layout passes. If this method is called before first layout of
863d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * this SlidingChallengeLayout it will have no effect.
864d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     *
865d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * @param bottom New bottom edge in px in this SlidingChallengeLayout's coordinate system.
866d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * @return true if the challenge view was moved
867d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     */
868d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private boolean moveChallengeTo(int bottom) {
869d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (mChallengeView == null || !mHasLayout) {
870d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            return false;
871d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
872d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
873b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler        final int layoutBottom = getLayoutBottom();
874d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        final int challengeHeight = mChallengeView.getHeight();
875d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
876d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        bottom = Math.max(layoutBottom,
87767625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell                Math.min(bottom, layoutBottom + challengeHeight - mChallengeBottomBound));
878d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
87967625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        float offset = 1.f - (float) (bottom - layoutBottom) /
88067625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell                (challengeHeight - mChallengeBottomBound);
881d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mChallengeOffset = offset;
882d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (offset > 0 && !mChallengeShowing) {
883d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            setChallengeShowing(true);
884d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
885d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
886d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        mChallengeView.layout(mChallengeView.getLeft(),
887d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                bottom - mChallengeView.getHeight(), mChallengeView.getRight(), bottom);
888d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
88967625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell        mChallengeView.setAlpha(offset / 2 + 0.5f);
8900b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell        if (mScrollListener != null) {
8910b1b552268bc6641cc5d01fb80c12258da9c6985Adam Powell            mScrollListener.onScrollPositionChanged(offset, mChallengeView.getTop());
892d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
893d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        postInvalidateOnAnimation();
894d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        return true;
895d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
896d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
897b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler    /**
898b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler     * The bottom edge of this SlidingChallengeLayout's coordinate system; will coincide with
899b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler     * the bottom edge of mChallengeView when the challenge is fully opened.
900b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler     */
901b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler    private int getLayoutBottom() {
902b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler        final int bottomMargin = (mChallengeView == null)
903b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler                ? 0
904b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler                : ((LayoutParams) mChallengeView.getLayoutParams()).bottomMargin;
9058a7785c7aa2df74203276299e10b0d9056cd0560Adam Cohen        final int layoutBottom = getMeasuredHeight() - getPaddingBottom() - bottomMargin;
906b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler        return layoutBottom;
907b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler    }
908b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler
909b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler    /**
910b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler     * The bottom edge of mChallengeView; essentially, where the sliding challenge 'is'.
911b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler     */
912d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private int getChallengeBottom() {
913d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (mChallengeView == null) return 0;
914d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
915d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        return mChallengeView.getBottom();
916d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
917d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
918b42b842ee873160acf317fc3ff6339243514b008Daniel Sandler    /**
919d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * Show or hide the challenge view, animating it if necessary.
920d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     * @param show true to show, false to hide
921d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller     */
922d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public void showChallenge(boolean show) {
923d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        showChallenge(show, 0);
924995270f01175234d0615a0e1900576f142a3b4adAdam Powell        if (!show) {
925995270f01175234d0615a0e1900576f142a3b4adAdam Powell            // Block any drags in progress so that callers can use this to disable dragging
926995270f01175234d0615a0e1900576f142a3b4adAdam Powell            // for other touch interactions.
927995270f01175234d0615a0e1900576f142a3b4adAdam Powell            mBlockDrag = true;
928995270f01175234d0615a0e1900576f142a3b4adAdam Powell        }
929d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
930d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
931d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private void showChallenge(int velocity) {
932d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        boolean show = false;
933d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (Math.abs(velocity) > mMinVelocity) {
934d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            show = velocity < 0;
935d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        } else {
936d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            show = mChallengeOffset >= 0.5f;
937d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
938d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        showChallenge(show, velocity);
939d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
940d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
941d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    private void showChallenge(boolean show, int velocity) {
942d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (mChallengeView == null) {
943d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            setChallengeShowing(false);
944d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            return;
945d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
946d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
947d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        if (mHasLayout) {
94867625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell            final int layoutBottom = getLayoutBottom();
949d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            animateChallengeTo(show ? layoutBottom :
95067625e29208a97afeb8dca22a73c52ad7b4619baAdam Powell                    layoutBottom + mChallengeView.getHeight() - mChallengeBottomBound, velocity);
951d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
952d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
953d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
95419a5267003e7dc70100a4bd4f1f449523b2ff38bJim Miller    @Override
955d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
956d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        return new LayoutParams(getContext(), attrs);
957d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
958d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
959d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    @Override
960d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
961d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        return p instanceof LayoutParams ? new LayoutParams((LayoutParams) p) :
962d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                p instanceof MarginLayoutParams ? new LayoutParams((MarginLayoutParams) p) :
963d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                new LayoutParams(p);
964d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
965d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
966d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    @Override
967d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
968d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        return new LayoutParams();
969d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
970d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
971d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    @Override
972d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
973d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        return p instanceof LayoutParams;
974d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
975d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
976d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    public static class LayoutParams extends MarginLayoutParams {
977eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        public int childType = CHILD_TYPE_NONE;
978eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        public static final int CHILD_TYPE_NONE = 0;
979eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        public static final int CHILD_TYPE_CHALLENGE = 2;
980eee209313d564be37b8fc068f785b3844cd6597eAdam Powell        public static final int CHILD_TYPE_SCRIM = 4;
981d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
982d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        public LayoutParams() {
983d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            this(MATCH_PARENT, WRAP_CONTENT);
984d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
985d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
986d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        public LayoutParams(int width, int height) {
987d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            super(width, height);
988d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
989d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
990d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        public LayoutParams(android.view.ViewGroup.LayoutParams source) {
991d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            super(source);
992d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
993d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
994d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        public LayoutParams(MarginLayoutParams source) {
995d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            super(source);
996d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
997d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
998d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        public LayoutParams(LayoutParams source) {
999d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            super(source);
1000d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
1001eee209313d564be37b8fc068f785b3844cd6597eAdam Powell            childType = source.childType;
1002d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
1003d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
1004d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        public LayoutParams(Context c, AttributeSet attrs) {
1005d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            super(c, attrs);
1006d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller
1007d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            final TypedArray a = c.obtainStyledAttributes(attrs,
1008d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller                    R.styleable.SlidingChallengeLayout_Layout);
1009eee209313d564be37b8fc068f785b3844cd6597eAdam Powell            childType = a.getInt(R.styleable.SlidingChallengeLayout_Layout_layout_childType,
1010eee209313d564be37b8fc068f785b3844cd6597eAdam Powell                    CHILD_TYPE_NONE);
1011d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller            a.recycle();
1012d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller        }
1013d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller    }
1014d6523da1ad7bae805a3dbea8d7b398d86cd3c181Jim Miller}
1015