blob: edfaf493ce4e01ec1d3bdc0cf442313f9d4d83f4 [file] [log] [blame]
Daniel Sandler6a858c32012-03-12 14:38:58 -04001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package com.android.systemui;
19
Romain Guy8900e632012-05-25 12:08:39 -070020import android.animation.Animator;
21import android.animation.AnimatorListenerAdapter;
Chris Wrenba925e82012-04-20 16:46:43 -040022import android.animation.AnimatorSet;
Daniel Sandler6a858c32012-03-12 14:38:58 -040023import android.animation.ObjectAnimator;
24import android.content.Context;
Chris Wrenb4e2c48b2012-06-15 16:51:54 -040025import android.os.Vibrator;
Daniel Sandler7a1a4062012-06-18 11:26:43 -040026import android.util.Slog;
Chris Wren9b2cd152012-06-11 10:39:36 -040027import android.view.Gravity;
Daniel Sandler6a858c32012-03-12 14:38:58 -040028import android.view.MotionEvent;
29import android.view.ScaleGestureDetector;
Daniel Sandlerac47ff72012-10-23 10:41:44 -040030import android.view.ScaleGestureDetector.OnScaleGestureListener;
Daniel Sandler6a858c32012-03-12 14:38:58 -040031import android.view.View;
Chris Wrenb4e2c48b2012-06-15 16:51:54 -040032import android.view.ViewConfiguration;
Daniel Sandler6a858c32012-03-12 14:38:58 -040033import android.view.ViewGroup;
34import android.view.View.OnClickListener;
Daniel Sandler6a858c32012-03-12 14:38:58 -040035
36public class ExpandHelper implements Gefingerpoken, OnClickListener {
37 public interface Callback {
Chris Wren5de6e942012-05-16 14:22:21 -040038 View getChildAtRawPosition(float x, float y);
Daniel Sandler6a858c32012-03-12 14:38:58 -040039 View getChildAtPosition(float x, float y);
Chris Wren80a76272012-04-18 10:52:18 -040040 boolean canChildBeExpanded(View v);
Chris Wren3ddab0d2012-08-02 16:52:21 -040041 boolean setUserExpandedChild(View v, boolean userExpanded);
42 boolean setUserLockedChild(View v, boolean userLocked);
Daniel Sandler6a858c32012-03-12 14:38:58 -040043 }
44
45 private static final String TAG = "ExpandHelper";
Chris Wrene46647d2012-06-20 16:18:10 -040046 protected static final boolean DEBUG = false;
Chris Wren3c148f12012-06-19 13:10:25 -040047 protected static final boolean DEBUG_SCALE = false;
Chris Wrene46647d2012-06-20 16:18:10 -040048 protected static final boolean DEBUG_GLOW = false;
Daniel Sandler6a858c32012-03-12 14:38:58 -040049 private static final long EXPAND_DURATION = 250;
Chris Wrenba925e82012-04-20 16:46:43 -040050 private static final long GLOW_DURATION = 150;
51
Daniel Sandler4377d142012-09-11 15:18:47 -040052 // Set to false to disable focus-based gestures (spread-finger vertical pull).
Chris Wren89139d72012-05-07 17:00:06 -040053 private static final boolean USE_DRAG = true;
54 // Set to false to disable scale-based gestures (both horizontal and vertical).
55 private static final boolean USE_SPAN = true;
56 // Both gestures types may be active at the same time.
57 // At least one gesture type should be active.
58 // A variant of the screwdriver gesture will emerge from either gesture type.
Daniel Sandler6a858c32012-03-12 14:38:58 -040059
Chris Wren80a76272012-04-18 10:52:18 -040060 // amount of overstretch for maximum brightness expressed in U
61 // 2f: maximum brightness is stretching a 1U to 3U, or a 4U to 6U
62 private static final float STRETCH_INTERVAL = 2f;
63
64 // level of glow for a touch, without overstretch
65 // overstretch fills the range (GLOW_BASE, 1.0]
66 private static final float GLOW_BASE = 0.5f;
67
Daniel Sandler6a858c32012-03-12 14:38:58 -040068 @SuppressWarnings("unused")
69 private Context mContext;
70
Daniel Sandler4377d142012-09-11 15:18:47 -040071 private boolean mExpanding;
72 private static final int NONE = 0;
73 private static final int BLINDS = 1<<0;
74 private static final int PULL = 1<<1;
75 private static final int STRETCH = 1<<2;
76 private int mExpansionStyle = NONE;
Chris Wrenb4e2c48b2012-06-15 16:51:54 -040077 private boolean mWatchingForPull;
78 private boolean mHasPopped;
Chris Wren5de6e942012-05-16 14:22:21 -040079 private View mEventSource;
Daniel Sandler6a858c32012-03-12 14:38:58 -040080 private View mCurrView;
Chris Wren80a76272012-04-18 10:52:18 -040081 private View mCurrViewTopGlow;
82 private View mCurrViewBottomGlow;
Daniel Sandler6a858c32012-03-12 14:38:58 -040083 private float mOldHeight;
84 private float mNaturalHeight;
Chris Wren89139d72012-05-07 17:00:06 -040085 private float mInitialTouchFocusY;
Chris Wrenb4e2c48b2012-06-15 16:51:54 -040086 private float mInitialTouchY;
Daniel Sandler6a858c32012-03-12 14:38:58 -040087 private float mInitialTouchSpan;
Chris Wrencea52072012-10-10 21:02:56 -040088 private float mLastFocusY;
89 private float mLastSpanY;
Chris Wrenb4e2c48b2012-06-15 16:51:54 -040090 private int mTouchSlop;
91 private int mLastMotionY;
92 private float mPopLimit;
93 private int mPopDuration;
Daniel Sandler4377d142012-09-11 15:18:47 -040094 private float mPullGestureMinXSpan;
Daniel Sandler6a858c32012-03-12 14:38:58 -040095 private Callback mCallback;
Daniel Sandler4377d142012-09-11 15:18:47 -040096 private ScaleGestureDetector mSGD;
Daniel Sandler6a858c32012-03-12 14:38:58 -040097 private ViewScaler mScaler;
Chris Wrenba925e82012-04-20 16:46:43 -040098 private ObjectAnimator mScaleAnimation;
99 private AnimatorSet mGlowAnimationSet;
100 private ObjectAnimator mGlowTopAnimation;
101 private ObjectAnimator mGlowBottomAnimation;
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400102 private Vibrator mVibrator;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400103
104 private int mSmallSize;
105 private int mLargeSize;
Chris Wren80a76272012-04-18 10:52:18 -0400106 private float mMaximumStretch;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400107
Chris Wren9b2cd152012-06-11 10:39:36 -0400108 private int mGravity;
109
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400110 private View mScrollView;
111
Daniel Sandlerac47ff72012-10-23 10:41:44 -0400112 private OnScaleGestureListener mScaleGestureListener
113 = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
114 @Override
115 public boolean onScaleBegin(ScaleGestureDetector detector) {
116 if (DEBUG_SCALE) Slog.v(TAG, "onscalebegin()");
117 float focusX = detector.getFocusX();
118 float focusY = detector.getFocusY();
119
120 final View underFocus = findView(focusX, focusY);
121 if (underFocus != null) {
122 startExpanding(underFocus, STRETCH);
123 }
124 return mExpanding;
125 }
126
127 @Override
128 public boolean onScale(ScaleGestureDetector detector) {
129 if (DEBUG_SCALE) Slog.v(TAG, "onscale() on " + mCurrView);
130 return true;
131 }
132
133 @Override
134 public void onScaleEnd(ScaleGestureDetector detector) {
135 }
136 };
137
Daniel Sandler6a858c32012-03-12 14:38:58 -0400138 private class ViewScaler {
139 View mView;
Chris Wren9b2cd152012-06-11 10:39:36 -0400140
Daniel Sandler6a858c32012-03-12 14:38:58 -0400141 public ViewScaler() {}
142 public void setView(View v) {
143 mView = v;
144 }
145 public void setHeight(float h) {
Chris Wren3c148f12012-06-19 13:10:25 -0400146 if (DEBUG_SCALE) Slog.v(TAG, "SetHeight: setting to " + h);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400147 ViewGroup.LayoutParams lp = mView.getLayoutParams();
148 lp.height = (int)h;
149 mView.setLayoutParams(lp);
Chris Wren89139d72012-05-07 17:00:06 -0400150 mView.requestLayout();
Daniel Sandler6a858c32012-03-12 14:38:58 -0400151 }
152 public float getHeight() {
153 int height = mView.getLayoutParams().height;
154 if (height < 0) {
155 height = mView.getMeasuredHeight();
156 }
Daniel Sandler4377d142012-09-11 15:18:47 -0400157 return height;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400158 }
159 public int getNaturalHeight(int maximum) {
160 ViewGroup.LayoutParams lp = mView.getLayoutParams();
Chris Wren3c148f12012-06-19 13:10:25 -0400161 if (DEBUG_SCALE) Slog.v(TAG, "Inspecting a child of type: " +
162 mView.getClass().getName());
Daniel Sandler6a858c32012-03-12 14:38:58 -0400163 int oldHeight = lp.height;
164 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
165 mView.setLayoutParams(lp);
166 mView.measure(
167 View.MeasureSpec.makeMeasureSpec(mView.getMeasuredWidth(),
Chris Wren89139d72012-05-07 17:00:06 -0400168 View.MeasureSpec.EXACTLY),
Daniel Sandler6a858c32012-03-12 14:38:58 -0400169 View.MeasureSpec.makeMeasureSpec(maximum,
Chris Wren89139d72012-05-07 17:00:06 -0400170 View.MeasureSpec.AT_MOST));
Daniel Sandler6a858c32012-03-12 14:38:58 -0400171 lp.height = oldHeight;
172 mView.setLayoutParams(lp);
173 return mView.getMeasuredHeight();
174 }
175 }
176
Chris Wren9b2cd152012-06-11 10:39:36 -0400177 /**
178 * Handle expansion gestures to expand and contract children of the callback.
179 *
180 * @param context application context
181 * @param callback the container that holds the items to be manipulated
182 * @param small the smallest allowable size for the manuipulated items.
183 * @param large the largest allowable size for the manuipulated items.
184 * @param scoller if non-null also manipulate the scroll position to obey the gravity.
185 */
Daniel Sandler6a858c32012-03-12 14:38:58 -0400186 public ExpandHelper(Context context, Callback callback, int small, int large) {
187 mSmallSize = small;
Chris Wren80a76272012-04-18 10:52:18 -0400188 mMaximumStretch = mSmallSize * STRETCH_INTERVAL;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400189 mLargeSize = large;
190 mContext = context;
191 mCallback = callback;
192 mScaler = new ViewScaler();
Chris Wren9b2cd152012-06-11 10:39:36 -0400193 mGravity = Gravity.TOP;
Chris Wrenba925e82012-04-20 16:46:43 -0400194 mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
195 mScaleAnimation.setDuration(EXPAND_DURATION);
Daniel Sandler4377d142012-09-11 15:18:47 -0400196 mPopLimit = mContext.getResources().getDimension(R.dimen.blinds_pop_threshold);
197 mPopDuration = mContext.getResources().getInteger(R.integer.blinds_pop_duration_ms);
198 mPullGestureMinXSpan = mContext.getResources().getDimension(R.dimen.pull_span_min);
Chris Wrenba925e82012-04-20 16:46:43 -0400199
Romain Guy8900e632012-05-25 12:08:39 -0700200 AnimatorListenerAdapter glowVisibilityController = new AnimatorListenerAdapter() {
201 @Override
202 public void onAnimationStart(Animator animation) {
203 View target = (View) ((ObjectAnimator) animation).getTarget();
204 if (target.getAlpha() <= 0.0f) {
205 target.setVisibility(View.VISIBLE);
206 }
207 }
208
209 @Override
210 public void onAnimationEnd(Animator animation) {
211 View target = (View) ((ObjectAnimator) animation).getTarget();
212 if (target.getAlpha() <= 0.0f) {
213 target.setVisibility(View.INVISIBLE);
214 }
215 }
216 };
217
Chris Wrenba925e82012-04-20 16:46:43 -0400218 mGlowTopAnimation = ObjectAnimator.ofFloat(null, "alpha", 0f);
Romain Guy8900e632012-05-25 12:08:39 -0700219 mGlowTopAnimation.addListener(glowVisibilityController);
Chris Wrenba925e82012-04-20 16:46:43 -0400220 mGlowBottomAnimation = ObjectAnimator.ofFloat(null, "alpha", 0f);
Romain Guy8900e632012-05-25 12:08:39 -0700221 mGlowBottomAnimation.addListener(glowVisibilityController);
Chris Wrenba925e82012-04-20 16:46:43 -0400222 mGlowAnimationSet = new AnimatorSet();
223 mGlowAnimationSet.play(mGlowTopAnimation).with(mGlowBottomAnimation);
224 mGlowAnimationSet.setDuration(GLOW_DURATION);
225
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400226 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
227 mTouchSlop = configuration.getScaledTouchSlop();
228
Daniel Sandlerac47ff72012-10-23 10:41:44 -0400229 mSGD = new ScaleGestureDetector(context, mScaleGestureListener);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400230 }
Chris Wren5de6e942012-05-16 14:22:21 -0400231
Daniel Sandler4377d142012-09-11 15:18:47 -0400232 private void updateExpansion() {
Chris Wrencea52072012-10-10 21:02:56 -0400233 if (DEBUG_SCALE) Slog.v(TAG, "updateExpansion()");
Daniel Sandler4377d142012-09-11 15:18:47 -0400234 // are we scaling or dragging?
Chris Wrencea52072012-10-10 21:02:56 -0400235 float span = mSGD.getCurrentSpan() - mInitialTouchSpan;
Daniel Sandler4377d142012-09-11 15:18:47 -0400236 span *= USE_SPAN ? 1f : 0f;
237 float drag = mSGD.getFocusY() - mInitialTouchFocusY;
238 drag *= USE_DRAG ? 1f : 0f;
239 drag *= mGravity == Gravity.BOTTOM ? -1f : 1f;
240 float pull = Math.abs(drag) + Math.abs(span) + 1f;
241 float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull;
242 float target = hand + mOldHeight;
243 float newHeight = clamp(target);
244 mScaler.setHeight(newHeight);
245
246 setGlow(calculateGlow(target, newHeight));
Chris Wrencea52072012-10-10 21:02:56 -0400247 mLastFocusY = mSGD.getFocusY();
248 mLastSpanY = mSGD.getCurrentSpan();
Daniel Sandler4377d142012-09-11 15:18:47 -0400249 }
250
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400251 private float clamp(float target) {
252 float out = target;
253 out = out < mSmallSize ? mSmallSize : (out > mLargeSize ? mLargeSize : out);
254 out = out > mNaturalHeight ? mNaturalHeight : out;
255 return out;
256 }
257
258 private View findView(float x, float y) {
259 View v = null;
260 if (mEventSource != null) {
261 int[] location = new int[2];
262 mEventSource.getLocationOnScreen(location);
Daniel Sandler4377d142012-09-11 15:18:47 -0400263 x += location[0];
264 y += location[1];
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400265 v = mCallback.getChildAtRawPosition(x, y);
266 } else {
267 v = mCallback.getChildAtPosition(x, y);
268 }
269 return v;
270 }
271
272 private boolean isInside(View v, float x, float y) {
273 if (DEBUG) Slog.d(TAG, "isinside (" + x + ", " + y + ")");
274
275 if (v == null) {
276 if (DEBUG) Slog.d(TAG, "isinside null subject");
277 return false;
278 }
279 if (mEventSource != null) {
280 int[] location = new int[2];
281 mEventSource.getLocationOnScreen(location);
Daniel Sandler4377d142012-09-11 15:18:47 -0400282 x += location[0];
283 y += location[1];
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400284 if (DEBUG) Slog.d(TAG, " to global (" + x + ", " + y + ")");
285 }
286 int[] location = new int[2];
287 v.getLocationOnScreen(location);
Daniel Sandler4377d142012-09-11 15:18:47 -0400288 x -= location[0];
289 y -= location[1];
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400290 if (DEBUG) Slog.d(TAG, " to local (" + x + ", " + y + ")");
291 if (DEBUG) Slog.d(TAG, " inside (" + v.getWidth() + ", " + v.getHeight() + ")");
292 boolean inside = (x > 0f && y > 0f && x < v.getWidth() & y < v.getHeight());
293 return inside;
294 }
295
Chris Wren5de6e942012-05-16 14:22:21 -0400296 public void setEventSource(View eventSource) {
297 mEventSource = eventSource;
298 }
299
Chris Wren9b2cd152012-06-11 10:39:36 -0400300 public void setGravity(int gravity) {
301 mGravity = gravity;
302 }
303
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400304 public void setScrollView(View scrollView) {
305 mScrollView = scrollView;
306 }
307
308 private float calculateGlow(float target, float actual) {
309 // glow if overscale
Chris Wren3c148f12012-06-19 13:10:25 -0400310 if (DEBUG_GLOW) Slog.d(TAG, "target: " + target + " actual: " + actual);
Daniel Sandler4377d142012-09-11 15:18:47 -0400311 float stretch = Math.abs((target - actual) / mMaximumStretch);
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400312 float strength = 1f / (1f + (float) Math.pow(Math.E, -1 * ((8f * stretch) - 5f)));
Chris Wren3c148f12012-06-19 13:10:25 -0400313 if (DEBUG_GLOW) Slog.d(TAG, "stretch: " + stretch + " strength: " + strength);
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400314 return (GLOW_BASE + strength * (1f - GLOW_BASE));
315 }
316
Chris Wren80a76272012-04-18 10:52:18 -0400317 public void setGlow(float glow) {
Chris Wren89139d72012-05-07 17:00:06 -0400318 if (!mGlowAnimationSet.isRunning() || glow == 0f) {
319 if (mGlowAnimationSet.isRunning()) {
Chris Wren3c148f12012-06-19 13:10:25 -0400320 mGlowAnimationSet.end();
Chris Wren89139d72012-05-07 17:00:06 -0400321 }
Chris Wrenba925e82012-04-20 16:46:43 -0400322 if (mCurrViewTopGlow != null && mCurrViewBottomGlow != null) {
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400323 if (glow == 0f || mCurrViewTopGlow.getAlpha() == 0f) {
Chris Wrenba925e82012-04-20 16:46:43 -0400324 // animate glow in and out
325 mGlowTopAnimation.setTarget(mCurrViewTopGlow);
326 mGlowBottomAnimation.setTarget(mCurrViewBottomGlow);
327 mGlowTopAnimation.setFloatValues(glow);
328 mGlowBottomAnimation.setFloatValues(glow);
329 mGlowAnimationSet.setupStartValues();
330 mGlowAnimationSet.start();
331 } else {
332 // set it explicitly in reponse to touches.
333 mCurrViewTopGlow.setAlpha(glow);
334 mCurrViewBottomGlow.setAlpha(glow);
Romain Guy8900e632012-05-25 12:08:39 -0700335 handleGlowVisibility();
Chris Wrenba925e82012-04-20 16:46:43 -0400336 }
337 }
Chris Wren80a76272012-04-18 10:52:18 -0400338 }
339 }
Romain Guy8900e632012-05-25 12:08:39 -0700340
341 private void handleGlowVisibility() {
342 mCurrViewTopGlow.setVisibility(mCurrViewTopGlow.getAlpha() <= 0.0f ?
343 View.INVISIBLE : View.VISIBLE);
344 mCurrViewBottomGlow.setVisibility(mCurrViewBottomGlow.getAlpha() <= 0.0f ?
345 View.INVISIBLE : View.VISIBLE);
346 }
347
Daniel Sandler4377d142012-09-11 15:18:47 -0400348 @Override
Daniel Sandler6a858c32012-03-12 14:38:58 -0400349 public boolean onInterceptTouchEvent(MotionEvent ev) {
Daniel Sandler4377d142012-09-11 15:18:47 -0400350 final int action = ev.getAction();
351 if (DEBUG_SCALE) Slog.d(TAG, "intercept: act=" + MotionEvent.actionToString(action) +
352 " expanding=" + mExpanding +
353 (0 != (mExpansionStyle & BLINDS) ? " (blinds)" : "") +
354 (0 != (mExpansionStyle & PULL) ? " (pull)" : "") +
355 (0 != (mExpansionStyle & STRETCH) ? " (stretch)" : ""));
356 // check for a spread-finger vertical pull gesture
357 mSGD.onTouchEvent(ev);
358 final int x = (int) mSGD.getFocusX();
359 final int y = (int) mSGD.getFocusY();
Chris Wrencea52072012-10-10 21:02:56 -0400360
361 mInitialTouchFocusY = y;
362 mInitialTouchSpan = mSGD.getCurrentSpan();
363 mLastFocusY = mInitialTouchFocusY;
364 mLastSpanY = mInitialTouchSpan;
365 if (DEBUG_SCALE) Slog.d(TAG, "set initial span: " + mInitialTouchSpan);
366
Daniel Sandler4377d142012-09-11 15:18:47 -0400367 if (mExpanding) {
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400368 return true;
369 } else {
Daniel Sandler4377d142012-09-11 15:18:47 -0400370 if ((action == MotionEvent.ACTION_MOVE) && 0 != (mExpansionStyle & BLINDS)) {
371 // we've begun Venetian blinds style expansion
372 return true;
373 }
374 final float xspan = mSGD.getCurrentSpanX();
375 if ((action == MotionEvent.ACTION_MOVE &&
376 xspan > mPullGestureMinXSpan &&
377 xspan > mSGD.getCurrentSpanY())) {
378 // detect a vertical pulling gesture with fingers somewhat separated
379 if (DEBUG_SCALE) Slog.v(TAG, "got pull gesture (xspan=" + xspan + "px)");
380
Daniel Sandler4377d142012-09-11 15:18:47 -0400381 final View underFocus = findView(x, y);
382 if (underFocus != null) {
383 startExpanding(underFocus, PULL);
384 }
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400385 return true;
386 }
387 if (mScrollView != null && mScrollView.getScrollY() > 0) {
388 return false;
389 }
Daniel Sandler4377d142012-09-11 15:18:47 -0400390 // Now look for other gestures
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400391 switch (action & MotionEvent.ACTION_MASK) {
392 case MotionEvent.ACTION_MOVE: {
393 if (mWatchingForPull) {
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400394 final int yDiff = y - mLastMotionY;
395 if (yDiff > mTouchSlop) {
Daniel Sandler4377d142012-09-11 15:18:47 -0400396 if (DEBUG) Slog.v(TAG, "got venetian gesture (dy=" + yDiff + "px)");
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400397 mLastMotionY = y;
Daniel Sandler4377d142012-09-11 15:18:47 -0400398 final View underFocus = findView(x, y);
399 if (underFocus != null) {
400 startExpanding(underFocus, BLINDS);
Chris Wren3c148f12012-06-19 13:10:25 -0400401 mInitialTouchY = mLastMotionY;
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400402 mHasPopped = false;
403 }
404 }
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400405 }
406 break;
407 }
408
409 case MotionEvent.ACTION_DOWN:
Daniel Sandler4377d142012-09-11 15:18:47 -0400410 mWatchingForPull = isInside(mScrollView, x, y);
411 mLastMotionY = y;
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400412 break;
413
414 case MotionEvent.ACTION_CANCEL:
415 case MotionEvent.ACTION_UP:
Daniel Sandler4377d142012-09-11 15:18:47 -0400416 if (DEBUG) Slog.d(TAG, "up/cancel");
417 finishExpanding(false);
418 clearView();
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400419 break;
420 }
Daniel Sandler4377d142012-09-11 15:18:47 -0400421 return mExpanding;
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400422 }
Daniel Sandler6a858c32012-03-12 14:38:58 -0400423 }
424
Daniel Sandler4377d142012-09-11 15:18:47 -0400425 @Override
Daniel Sandler6a858c32012-03-12 14:38:58 -0400426 public boolean onTouchEvent(MotionEvent ev) {
Chris Wrencea52072012-10-10 21:02:56 -0400427 final int action = ev.getActionMasked();
Daniel Sandler4377d142012-09-11 15:18:47 -0400428 if (DEBUG_SCALE) Slog.d(TAG, "touch: act=" + MotionEvent.actionToString(action) +
429 " expanding=" + mExpanding +
430 (0 != (mExpansionStyle & BLINDS) ? " (blinds)" : "") +
431 (0 != (mExpansionStyle & PULL) ? " (pull)" : "") +
432 (0 != (mExpansionStyle & STRETCH) ? " (stretch)" : ""));
433
434 mSGD.onTouchEvent(ev);
435
Daniel Sandler6a858c32012-03-12 14:38:58 -0400436 switch (action) {
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400437 case MotionEvent.ACTION_MOVE: {
Daniel Sandler4377d142012-09-11 15:18:47 -0400438 if (0 != (mExpansionStyle & BLINDS)) {
Chris Wren86d00fb2012-08-01 17:03:07 -0400439 final float rawHeight = ev.getY() - mInitialTouchY + mOldHeight;
440 final float newHeight = clamp(rawHeight);
441 final boolean wasClosed = (mOldHeight == mSmallSize);
442 boolean isFinished = false;
443 if (rawHeight > mNaturalHeight) {
444 isFinished = true;
445 }
446 if (rawHeight < mSmallSize) {
447 isFinished = true;
448 }
449
450 final float pull = Math.abs(ev.getY() - mInitialTouchY);
451 if (mHasPopped || pull > mPopLimit) {
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400452 if (!mHasPopped) {
453 vibrate(mPopDuration);
454 mHasPopped = true;
455 }
Chris Wren86d00fb2012-08-01 17:03:07 -0400456 }
457
458 if (mHasPopped) {
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400459 mScaler.setHeight(newHeight);
Chris Wren86d00fb2012-08-01 17:03:07 -0400460 setGlow(GLOW_BASE);
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400461 } else {
Chris Wren86d00fb2012-08-01 17:03:07 -0400462 setGlow(calculateGlow(4f * pull, 0f));
463 }
464
Daniel Sandler4377d142012-09-11 15:18:47 -0400465 final int x = (int) mSGD.getFocusX();
466 final int y = (int) mSGD.getFocusY();
467 View underFocus = findView(x, y);
468 if (isFinished && underFocus != null && underFocus != mCurrView) {
469 finishExpanding(false); // @@@ needed?
470 startExpanding(underFocus, BLINDS);
471 mInitialTouchY = y;
Chris Wren86d00fb2012-08-01 17:03:07 -0400472 mHasPopped = false;
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400473 }
474 return true;
475 }
Daniel Sandler4377d142012-09-11 15:18:47 -0400476
477 if (mExpanding) {
478 updateExpansion();
479 return true;
480 }
481
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400482 break;
483 }
Chris Wrencea52072012-10-10 21:02:56 -0400484
485 case MotionEvent.ACTION_POINTER_UP:
486 case MotionEvent.ACTION_POINTER_DOWN:
487 if (DEBUG) Slog.d(TAG, "pointer change");
488 mInitialTouchY += mSGD.getFocusY() - mLastFocusY;
489 mInitialTouchSpan += mSGD.getCurrentSpan() - mLastSpanY;
490 break;
491
Daniel Sandler6a858c32012-03-12 14:38:58 -0400492 case MotionEvent.ACTION_UP:
493 case MotionEvent.ACTION_CANCEL:
Daniel Sandler4377d142012-09-11 15:18:47 -0400494 if (DEBUG) Slog.d(TAG, "up/cancel");
495 finishExpanding(false);
Chris Wren80a76272012-04-18 10:52:18 -0400496 clearView();
Daniel Sandler6a858c32012-03-12 14:38:58 -0400497 break;
498 }
499 return true;
500 }
Daniel Sandler4377d142012-09-11 15:18:47 -0400501
502 private void startExpanding(View v, int expandType) {
Chris Wrencea52072012-10-10 21:02:56 -0400503 mExpansionStyle = expandType;
504 if (mExpanding && v == mCurrView) {
505 return;
506 }
Daniel Sandler4377d142012-09-11 15:18:47 -0400507 mExpanding = true;
Daniel Sandler4377d142012-09-11 15:18:47 -0400508 if (DEBUG) Slog.d(TAG, "scale type " + expandType + " beginning on view: " + v);
509 mCallback.setUserLockedChild(v, true);
510 setView(v);
511 setGlow(GLOW_BASE);
512 mScaler.setView(v);
513 mOldHeight = mScaler.getHeight();
514 if (mCallback.canChildBeExpanded(v)) {
515 if (DEBUG) Slog.d(TAG, "working on an expandable child");
516 mNaturalHeight = mScaler.getNaturalHeight(mLargeSize);
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400517 } else {
Daniel Sandler4377d142012-09-11 15:18:47 -0400518 if (DEBUG) Slog.d(TAG, "working on a non-expandable child");
519 mNaturalHeight = mOldHeight;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400520 }
Daniel Sandler4377d142012-09-11 15:18:47 -0400521 if (DEBUG) Slog.d(TAG, "got mOldHeight: " + mOldHeight +
522 " mNaturalHeight: " + mNaturalHeight);
523 v.getParent().requestDisallowInterceptTouchEvent(true);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400524 }
525
Daniel Sandler4377d142012-09-11 15:18:47 -0400526 private void finishExpanding(boolean force) {
527 if (!mExpanding) return;
528
Chris Wrencea52072012-10-10 21:02:56 -0400529 if (DEBUG) Slog.d(TAG, "scale in finishing on view: " + mCurrView);
530
Chris Wren3ddab0d2012-08-02 16:52:21 -0400531 float currentHeight = mScaler.getHeight();
532 float targetHeight = mSmallSize;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400533 float h = mScaler.getHeight();
534 final boolean wasClosed = (mOldHeight == mSmallSize);
535 if (wasClosed) {
Chris Wren3ddab0d2012-08-02 16:52:21 -0400536 targetHeight = (force || currentHeight > mSmallSize) ? mNaturalHeight : mSmallSize;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400537 } else {
Chris Wren3ddab0d2012-08-02 16:52:21 -0400538 targetHeight = (force || currentHeight < mNaturalHeight) ? mSmallSize : mNaturalHeight;
Daniel Sandler6a858c32012-03-12 14:38:58 -0400539 }
Chris Wrenba925e82012-04-20 16:46:43 -0400540 if (mScaleAnimation.isRunning()) {
541 mScaleAnimation.cancel();
542 }
Chris Wren80a76272012-04-18 10:52:18 -0400543 setGlow(0f);
Chris Wren8fd12652012-05-09 21:25:57 -0400544 mCallback.setUserExpandedChild(mCurrView, h == mNaturalHeight);
Chris Wren3ddab0d2012-08-02 16:52:21 -0400545 if (targetHeight != currentHeight) {
546 mScaleAnimation.setFloatValues(targetHeight);
547 mScaleAnimation.setupStartValues();
548 mScaleAnimation.start();
549 }
550 mCallback.setUserLockedChild(mCurrView, false);
Daniel Sandler4377d142012-09-11 15:18:47 -0400551
552 mExpanding = false;
553 mExpansionStyle = NONE;
554
Chris Wrencea52072012-10-10 21:02:56 -0400555 if (DEBUG) Slog.d(TAG, "wasClosed is: " + wasClosed);
556 if (DEBUG) Slog.d(TAG, "currentHeight is: " + currentHeight);
557 if (DEBUG) Slog.d(TAG, "mSmallSize is: " + mSmallSize);
558 if (DEBUG) Slog.d(TAG, "targetHeight is: " + targetHeight);
Daniel Sandler7a1a4062012-06-18 11:26:43 -0400559 if (DEBUG) Slog.d(TAG, "scale was finished on view: " + mCurrView);
Chris Wren80a76272012-04-18 10:52:18 -0400560 }
561
562 private void clearView() {
Daniel Sandler6a858c32012-03-12 14:38:58 -0400563 mCurrView = null;
Chris Wren80a76272012-04-18 10:52:18 -0400564 mCurrViewTopGlow = null;
565 mCurrViewBottomGlow = null;
566 }
567
568 private void setView(View v) {
Chris Wren89139d72012-05-07 17:00:06 -0400569 mCurrView = v;
Chris Wren80a76272012-04-18 10:52:18 -0400570 if (v instanceof ViewGroup) {
571 ViewGroup g = (ViewGroup) v;
572 mCurrViewTopGlow = g.findViewById(R.id.top_glow);
573 mCurrViewBottomGlow = g.findViewById(R.id.bottom_glow);
Chris Wren89139d72012-05-07 17:00:06 -0400574 if (DEBUG) {
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400575 String debugLog = "Looking for glows: " +
Chris Wren80a76272012-04-18 10:52:18 -0400576 (mCurrViewTopGlow != null ? "found top " : "didn't find top") +
577 (mCurrViewBottomGlow != null ? "found bottom " : "didn't find bottom");
Daniel Sandler7a1a4062012-06-18 11:26:43 -0400578 Slog.v(TAG, debugLog);
Chris Wren80a76272012-04-18 10:52:18 -0400579 }
580 }
Daniel Sandler6a858c32012-03-12 14:38:58 -0400581 }
582
583 @Override
584 public void onClick(View v) {
Daniel Sandler4377d142012-09-11 15:18:47 -0400585 startExpanding(v, STRETCH);
586 finishExpanding(true);
Chris Wren3c148f12012-06-19 13:10:25 -0400587 clearView();
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400588 }
Daniel Sandler6a858c32012-03-12 14:38:58 -0400589
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400590 /**
Daniel Sandlerac47ff72012-10-23 10:41:44 -0400591 * Use this to abort any pending expansions in progress.
592 */
593 public void cancel() {
594 finishExpanding(true);
595 clearView();
596
597 // reset the gesture detector
598 mSGD = new ScaleGestureDetector(mContext, mScaleGestureListener);
599 }
600
601 /**
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400602 * Triggers haptic feedback.
603 */
604 private synchronized void vibrate(long duration) {
605 if (mVibrator == null) {
606 mVibrator = (android.os.Vibrator)
607 mContext.getSystemService(Context.VIBRATOR_SERVICE);
608 }
609 mVibrator.vibrate(duration);
Daniel Sandler6a858c32012-03-12 14:38:58 -0400610 }
611}
Chris Wrenb4e2c48b2012-06-15 16:51:54 -0400612