1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18 package com.android.systemui; 19 20 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_EXPAND; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorListenerAdapter; 24 import android.animation.ObjectAnimator; 25 import android.content.Context; 26 import android.util.FloatProperty; 27 import android.util.Log; 28 import android.view.Gravity; 29 import android.view.HapticFeedbackConstants; 30 import android.view.MotionEvent; 31 import android.view.ScaleGestureDetector; 32 import android.view.ScaleGestureDetector.OnScaleGestureListener; 33 import android.view.VelocityTracker; 34 import android.view.View; 35 import android.view.ViewConfiguration; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.internal.jank.InteractionJankMonitor; 39 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 40 import com.android.systemui.statusbar.notification.row.ExpandableView; 41 import com.android.systemui.statusbar.policy.ScrollAdapter; 42 import com.android.wm.shell.animation.FlingAnimationUtils; 43 44 public class ExpandHelper implements Gefingerpoken { 45 public interface Callback { getChildAtRawPosition(float x, float y)46 ExpandableView getChildAtRawPosition(float x, float y); getChildAtPosition(float x, float y)47 ExpandableView getChildAtPosition(float x, float y); canChildBeExpanded(View v)48 boolean canChildBeExpanded(View v); setUserExpandedChild(View v, boolean userExpanded)49 void setUserExpandedChild(View v, boolean userExpanded); setUserLockedChild(View v, boolean userLocked)50 void setUserLockedChild(View v, boolean userLocked); expansionStateChanged(boolean isExpanding)51 void expansionStateChanged(boolean isExpanding); getMaxExpandHeight(ExpandableView view)52 int getMaxExpandHeight(ExpandableView view); setExpansionCancelled(View view)53 void setExpansionCancelled(View view); 54 } 55 56 private static final String TAG = "ExpandHelper"; 57 protected static final boolean DEBUG = false; 58 protected static final boolean DEBUG_SCALE = false; 59 private static final float EXPAND_DURATION = 0.3f; 60 61 // Set to false to disable focus-based gestures (spread-finger vertical pull). 62 private static final boolean USE_DRAG = true; 63 // Set to false to disable scale-based gestures (both horizontal and vertical). 64 private static final boolean USE_SPAN = true; 65 // Both gestures types may be active at the same time. 66 // At least one gesture type should be active. 67 // A variant of the screwdriver gesture will emerge from either gesture type. 68 69 // amount of overstretch for maximum brightness expressed in U 70 // 2f: maximum brightness is stretching a 1U to 3U, or a 4U to 6U 71 private static final float STRETCH_INTERVAL = 2f; 72 73 private static final FloatProperty<ViewScaler> VIEW_SCALER_HEIGHT_PROPERTY = 74 new FloatProperty<ViewScaler>("ViewScalerHeight") { 75 @Override 76 public void setValue(ViewScaler object, float value) { 77 object.setHeight(value); 78 } 79 80 @Override 81 public Float get(ViewScaler object) { 82 return object.getHeight(); 83 } 84 }; 85 86 @SuppressWarnings("unused") 87 private Context mContext; 88 89 private boolean mExpanding; 90 private static final int NONE = 0; 91 private static final int BLINDS = 1<<0; 92 private static final int PULL = 1<<1; 93 private static final int STRETCH = 1<<2; 94 private int mExpansionStyle = NONE; 95 private boolean mWatchingForPull; 96 private boolean mHasPopped; 97 private View mEventSource; 98 private float mOldHeight; 99 private float mNaturalHeight; 100 private float mInitialTouchFocusY; 101 private float mInitialTouchX; 102 private float mInitialTouchY; 103 private float mInitialTouchSpan; 104 private float mLastFocusY; 105 private float mLastSpanY; 106 private final int mTouchSlop; 107 private final float mSlopMultiplier; 108 private float mLastMotionY; 109 private float mPullGestureMinXSpan; 110 private Callback mCallback; 111 private ScaleGestureDetector mSGD; 112 private ViewScaler mScaler; 113 private ObjectAnimator mScaleAnimation; 114 private boolean mEnabled = true; 115 private ExpandableView mResizedView; 116 private float mCurrentHeight; 117 118 private int mSmallSize; 119 private int mLargeSize; 120 private float mMaximumStretch; 121 private boolean mOnlyMovements; 122 123 private int mGravity; 124 125 private ScrollAdapter mScrollAdapter; 126 private FlingAnimationUtils mFlingAnimationUtils; 127 private VelocityTracker mVelocityTracker; 128 129 private OnScaleGestureListener mScaleGestureListener 130 = new ScaleGestureDetector.SimpleOnScaleGestureListener() { 131 @Override 132 public boolean onScaleBegin(ScaleGestureDetector detector) { 133 if (DEBUG_SCALE) Log.v(TAG, "onscalebegin()"); 134 135 if (!mOnlyMovements) { 136 startExpanding(mResizedView, STRETCH); 137 } 138 return mExpanding; 139 } 140 141 @Override 142 public boolean onScale(ScaleGestureDetector detector) { 143 if (DEBUG_SCALE) Log.v(TAG, "onscale() on " + mResizedView); 144 return true; 145 } 146 147 @Override 148 public void onScaleEnd(ScaleGestureDetector detector) { 149 } 150 }; 151 152 @VisibleForTesting getScaleAnimation()153 ObjectAnimator getScaleAnimation() { 154 return mScaleAnimation; 155 } 156 157 private class ViewScaler { 158 ExpandableView mView; 159 ViewScaler()160 public ViewScaler() {} setView(ExpandableView v)161 public void setView(ExpandableView v) { 162 mView = v; 163 } 164 setHeight(float h)165 public void setHeight(float h) { 166 if (DEBUG_SCALE) Log.v(TAG, "SetHeight: setting to " + h); 167 mView.setActualHeight((int) h); 168 mCurrentHeight = h; 169 } getHeight()170 public float getHeight() { 171 return mView.getActualHeight(); 172 } getNaturalHeight()173 public int getNaturalHeight() { 174 return mCallback.getMaxExpandHeight(mView); 175 } 176 } 177 178 /** 179 * Handle expansion gestures to expand and contract children of the callback. 180 * 181 * @param context application context 182 * @param callback the container that holds the items to be manipulated 183 * @param small the smallest allowable size for the manipulated items. 184 * @param large the largest allowable size for the manipulated items. 185 */ ExpandHelper(Context context, Callback callback, int small, int large)186 public ExpandHelper(Context context, Callback callback, int small, int large) { 187 mSmallSize = small; 188 mMaximumStretch = mSmallSize * STRETCH_INTERVAL; 189 mLargeSize = large; 190 mContext = context; 191 mCallback = callback; 192 mScaler = new ViewScaler(); 193 mGravity = Gravity.TOP; 194 mScaleAnimation = ObjectAnimator.ofFloat(mScaler, VIEW_SCALER_HEIGHT_PROPERTY, 0f); 195 mPullGestureMinXSpan = mContext.getResources().getDimension(R.dimen.pull_span_min); 196 197 final ViewConfiguration configuration = ViewConfiguration.get(mContext); 198 mTouchSlop = configuration.getScaledTouchSlop(); 199 mSlopMultiplier = configuration.getAmbiguousGestureMultiplier(); 200 201 mSGD = new ScaleGestureDetector(context, mScaleGestureListener); 202 mFlingAnimationUtils = new FlingAnimationUtils(mContext.getResources().getDisplayMetrics(), 203 EXPAND_DURATION); 204 } 205 206 @VisibleForTesting updateExpansion()207 void updateExpansion() { 208 if (DEBUG_SCALE) Log.v(TAG, "updateExpansion()"); 209 // are we scaling or dragging? 210 float span = mSGD.getCurrentSpan() - mInitialTouchSpan; 211 span *= USE_SPAN ? 1f : 0f; 212 float drag = mSGD.getFocusY() - mInitialTouchFocusY; 213 drag *= USE_DRAG ? 1f : 0f; 214 drag *= mGravity == Gravity.BOTTOM ? -1f : 1f; 215 float pull = Math.abs(drag) + Math.abs(span) + 1f; 216 float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull; 217 float target = hand + mOldHeight; 218 float newHeight = clamp(target); 219 mScaler.setHeight(newHeight); 220 mLastFocusY = mSGD.getFocusY(); 221 mLastSpanY = mSGD.getCurrentSpan(); 222 } 223 clamp(float target)224 private float clamp(float target) { 225 float out = target; 226 out = out < mSmallSize ? mSmallSize : out; 227 out = out > mNaturalHeight ? mNaturalHeight : out; 228 return out; 229 } 230 findView(float x, float y)231 private ExpandableView findView(float x, float y) { 232 ExpandableView v; 233 if (mEventSource != null) { 234 int[] location = new int[2]; 235 mEventSource.getLocationOnScreen(location); 236 x += location[0]; 237 y += location[1]; 238 v = mCallback.getChildAtRawPosition(x, y); 239 } else { 240 v = mCallback.getChildAtPosition(x, y); 241 } 242 return v; 243 } 244 isInside(View v, float x, float y)245 private boolean isInside(View v, float x, float y) { 246 if (DEBUG) Log.d(TAG, "isinside (" + x + ", " + y + ")"); 247 248 if (v == null) { 249 if (DEBUG) Log.d(TAG, "isinside null subject"); 250 return false; 251 } 252 if (mEventSource != null) { 253 int[] location = new int[2]; 254 mEventSource.getLocationOnScreen(location); 255 x += location[0]; 256 y += location[1]; 257 if (DEBUG) Log.d(TAG, " to global (" + x + ", " + y + ")"); 258 } 259 int[] location = new int[2]; 260 v.getLocationOnScreen(location); 261 x -= location[0]; 262 y -= location[1]; 263 if (DEBUG) Log.d(TAG, " to local (" + x + ", " + y + ")"); 264 if (DEBUG) Log.d(TAG, " inside (" + v.getWidth() + ", " + v.getHeight() + ")"); 265 boolean inside = (x > 0f && y > 0f && x < v.getWidth() & y < v.getHeight()); 266 return inside; 267 } 268 setEventSource(View eventSource)269 public void setEventSource(View eventSource) { 270 mEventSource = eventSource; 271 } 272 setGravity(int gravity)273 public void setGravity(int gravity) { 274 mGravity = gravity; 275 } 276 setScrollAdapter(ScrollAdapter adapter)277 public void setScrollAdapter(ScrollAdapter adapter) { 278 mScrollAdapter = adapter; 279 } 280 getTouchSlop(MotionEvent event)281 private float getTouchSlop(MotionEvent event) { 282 // Adjust the touch slop if another gesture may be being performed. 283 return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE 284 ? mTouchSlop * mSlopMultiplier 285 : mTouchSlop; 286 } 287 288 @Override onInterceptTouchEvent(MotionEvent ev)289 public boolean onInterceptTouchEvent(MotionEvent ev) { 290 if (!isEnabled()) { 291 return false; 292 } 293 trackVelocity(ev); 294 final int action = ev.getAction(); 295 if (DEBUG_SCALE) Log.d(TAG, "intercept: act=" + MotionEvent.actionToString(action) + 296 " expanding=" + mExpanding + 297 (0 != (mExpansionStyle & BLINDS) ? " (blinds)" : "") + 298 (0 != (mExpansionStyle & PULL) ? " (pull)" : "") + 299 (0 != (mExpansionStyle & STRETCH) ? " (stretch)" : "")); 300 // check for a spread-finger vertical pull gesture 301 mSGD.onTouchEvent(ev); 302 final int x = (int) mSGD.getFocusX(); 303 final int y = (int) mSGD.getFocusY(); 304 305 mInitialTouchFocusY = y; 306 mInitialTouchSpan = mSGD.getCurrentSpan(); 307 mLastFocusY = mInitialTouchFocusY; 308 mLastSpanY = mInitialTouchSpan; 309 if (DEBUG_SCALE) Log.d(TAG, "set initial span: " + mInitialTouchSpan); 310 311 if (mExpanding) { 312 mLastMotionY = ev.getRawY(); 313 maybeRecycleVelocityTracker(ev); 314 return true; 315 } else { 316 if ((action == MotionEvent.ACTION_MOVE) && 0 != (mExpansionStyle & BLINDS)) { 317 // we've begun Venetian blinds style expansion 318 return true; 319 } 320 switch (action & MotionEvent.ACTION_MASK) { 321 case MotionEvent.ACTION_MOVE: { 322 final float xspan = mSGD.getCurrentSpanX(); 323 if (xspan > mPullGestureMinXSpan && 324 xspan > mSGD.getCurrentSpanY() && !mExpanding) { 325 // detect a vertical pulling gesture with fingers somewhat separated 326 if (DEBUG_SCALE) Log.v(TAG, "got pull gesture (xspan=" + xspan + "px)"); 327 startExpanding(mResizedView, PULL); 328 mWatchingForPull = false; 329 } 330 if (mWatchingForPull) { 331 final float yDiff = ev.getRawY() - mInitialTouchY; 332 final float xDiff = ev.getRawX() - mInitialTouchX; 333 if (yDiff > getTouchSlop(ev) && yDiff > Math.abs(xDiff)) { 334 if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)"); 335 mWatchingForPull = false; 336 if (mResizedView != null && !isFullyExpanded(mResizedView)) { 337 if (startExpanding(mResizedView, BLINDS)) { 338 mLastMotionY = ev.getRawY(); 339 mInitialTouchY = ev.getRawY(); 340 mHasPopped = false; 341 } 342 } 343 } 344 } 345 break; 346 } 347 348 case MotionEvent.ACTION_DOWN: 349 mWatchingForPull = mScrollAdapter != null && 350 isInside(mScrollAdapter.getHostView(), x, y) 351 && mScrollAdapter.isScrolledToTop(); 352 mResizedView = findView(x, y); 353 if (mResizedView != null && !mCallback.canChildBeExpanded(mResizedView)) { 354 mResizedView = null; 355 mWatchingForPull = false; 356 } 357 mInitialTouchY = ev.getRawY(); 358 mInitialTouchX = ev.getRawX(); 359 break; 360 361 case MotionEvent.ACTION_CANCEL: 362 case MotionEvent.ACTION_UP: 363 if (DEBUG) Log.d(TAG, "up/cancel"); 364 finishExpanding(ev.getActionMasked() == MotionEvent.ACTION_CANCEL /* forceAbort */, 365 getCurrentVelocity()); 366 clearView(); 367 break; 368 } 369 mLastMotionY = ev.getRawY(); 370 maybeRecycleVelocityTracker(ev); 371 return mExpanding; 372 } 373 } 374 trackVelocity(MotionEvent event)375 private void trackVelocity(MotionEvent event) { 376 int action = event.getActionMasked(); 377 switch(action) { 378 case MotionEvent.ACTION_DOWN: 379 if (mVelocityTracker == null) { 380 mVelocityTracker = VelocityTracker.obtain(); 381 } else { 382 mVelocityTracker.clear(); 383 } 384 mVelocityTracker.addMovement(event); 385 break; 386 case MotionEvent.ACTION_MOVE: 387 if (mVelocityTracker == null) { 388 mVelocityTracker = VelocityTracker.obtain(); 389 } 390 mVelocityTracker.addMovement(event); 391 break; 392 default: 393 break; 394 } 395 } 396 maybeRecycleVelocityTracker(MotionEvent event)397 private void maybeRecycleVelocityTracker(MotionEvent event) { 398 if (mVelocityTracker != null && (event.getActionMasked() == MotionEvent.ACTION_CANCEL 399 || event.getActionMasked() == MotionEvent.ACTION_UP)) { 400 mVelocityTracker.recycle(); 401 mVelocityTracker = null; 402 } 403 } 404 getCurrentVelocity()405 private float getCurrentVelocity() { 406 if (mVelocityTracker != null) { 407 mVelocityTracker.computeCurrentVelocity(1000); 408 return mVelocityTracker.getYVelocity(); 409 } else { 410 return 0f; 411 } 412 } 413 setEnabled(boolean enable)414 public void setEnabled(boolean enable) { 415 mEnabled = enable; 416 } 417 isEnabled()418 private boolean isEnabled() { 419 return mEnabled; 420 } 421 isFullyExpanded(ExpandableView underFocus)422 private boolean isFullyExpanded(ExpandableView underFocus) { 423 return underFocus.getIntrinsicHeight() == underFocus.getMaxContentHeight() 424 && (!underFocus.isSummaryWithChildren() || underFocus.areChildrenExpanded()); 425 } 426 427 @Override onTouchEvent(MotionEvent ev)428 public boolean onTouchEvent(MotionEvent ev) { 429 if (!isEnabled() && !mExpanding) { 430 // In case we're expanding we still want to finish the current motion. 431 return false; 432 } 433 trackVelocity(ev); 434 final int action = ev.getActionMasked(); 435 if (DEBUG_SCALE) Log.d(TAG, "touch: act=" + MotionEvent.actionToString(action) + 436 " expanding=" + mExpanding + 437 (0 != (mExpansionStyle & BLINDS) ? " (blinds)" : "") + 438 (0 != (mExpansionStyle & PULL) ? " (pull)" : "") + 439 (0 != (mExpansionStyle & STRETCH) ? " (stretch)" : "")); 440 441 mSGD.onTouchEvent(ev); 442 final int x = (int) mSGD.getFocusX(); 443 final int y = (int) mSGD.getFocusY(); 444 445 if (mOnlyMovements) { 446 mLastMotionY = ev.getRawY(); 447 return false; 448 } 449 switch (action) { 450 case MotionEvent.ACTION_DOWN: 451 mWatchingForPull = mScrollAdapter != null && 452 isInside(mScrollAdapter.getHostView(), x, y); 453 mResizedView = findView(x, y); 454 mInitialTouchX = ev.getRawX(); 455 mInitialTouchY = ev.getRawY(); 456 break; 457 case MotionEvent.ACTION_MOVE: { 458 if (mWatchingForPull) { 459 final float yDiff = ev.getRawY() - mInitialTouchY; 460 final float xDiff = ev.getRawX() - mInitialTouchX; 461 if (yDiff > getTouchSlop(ev) && yDiff > Math.abs(xDiff)) { 462 if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)"); 463 mWatchingForPull = false; 464 if (mResizedView != null && !isFullyExpanded(mResizedView)) { 465 if (startExpanding(mResizedView, BLINDS)) { 466 mInitialTouchY = ev.getRawY(); 467 mLastMotionY = ev.getRawY(); 468 mHasPopped = false; 469 } 470 } 471 } 472 } 473 if (mExpanding && 0 != (mExpansionStyle & BLINDS)) { 474 final float rawHeight = ev.getRawY() - mLastMotionY + mCurrentHeight; 475 final float newHeight = clamp(rawHeight); 476 boolean isFinished = false; 477 boolean expanded = false; 478 if (rawHeight > mNaturalHeight) { 479 isFinished = true; 480 expanded = true; 481 } 482 if (rawHeight < mSmallSize) { 483 isFinished = true; 484 expanded = false; 485 } 486 487 if (!mHasPopped) { 488 if (mEventSource != null) { 489 mEventSource.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 490 } 491 mHasPopped = true; 492 } 493 494 mScaler.setHeight(newHeight); 495 mLastMotionY = ev.getRawY(); 496 if (isFinished) { 497 mCallback.expansionStateChanged(false); 498 } else { 499 mCallback.expansionStateChanged(true); 500 } 501 return true; 502 } 503 504 if (mExpanding) { 505 506 // Gestural expansion is running 507 updateExpansion(); 508 mLastMotionY = ev.getRawY(); 509 return true; 510 } 511 512 break; 513 } 514 515 case MotionEvent.ACTION_POINTER_UP: 516 case MotionEvent.ACTION_POINTER_DOWN: 517 if (DEBUG) Log.d(TAG, "pointer change"); 518 mInitialTouchY += mSGD.getFocusY() - mLastFocusY; 519 mInitialTouchSpan += mSGD.getCurrentSpan() - mLastSpanY; 520 break; 521 522 case MotionEvent.ACTION_UP: 523 case MotionEvent.ACTION_CANCEL: 524 if (DEBUG) Log.d(TAG, "up/cancel"); 525 finishExpanding(!isEnabled() || ev.getActionMasked() == MotionEvent.ACTION_CANCEL, 526 getCurrentVelocity()); 527 clearView(); 528 break; 529 } 530 mLastMotionY = ev.getRawY(); 531 maybeRecycleVelocityTracker(ev); 532 return mResizedView != null; 533 } 534 535 /** 536 * @return True if the view is expandable, false otherwise. 537 */ 538 @VisibleForTesting startExpanding(ExpandableView v, int expandType)539 boolean startExpanding(ExpandableView v, int expandType) { 540 if (!(v instanceof ExpandableNotificationRow)) { 541 return false; 542 } 543 mExpansionStyle = expandType; 544 if (mExpanding && v == mResizedView) { 545 return true; 546 } 547 mExpanding = true; 548 mCallback.expansionStateChanged(true); 549 if (DEBUG) Log.d(TAG, "scale type " + expandType + " beginning on view: " + v); 550 mCallback.setUserLockedChild(v, true); 551 mScaler.setView(v); 552 mOldHeight = mScaler.getHeight(); 553 mCurrentHeight = mOldHeight; 554 boolean canBeExpanded = mCallback.canChildBeExpanded(v); 555 if (canBeExpanded) { 556 if (DEBUG) Log.d(TAG, "working on an expandable child"); 557 mNaturalHeight = mScaler.getNaturalHeight(); 558 mSmallSize = v.getCollapsedHeight(); 559 } else { 560 if (DEBUG) Log.d(TAG, "working on a non-expandable child"); 561 mNaturalHeight = mOldHeight; 562 } 563 if (DEBUG) Log.d(TAG, "got mOldHeight: " + mOldHeight + 564 " mNaturalHeight: " + mNaturalHeight); 565 InteractionJankMonitor.getInstance().begin(v, CUJ_NOTIFICATION_SHADE_ROW_EXPAND); 566 return true; 567 } 568 569 /** 570 * Finish the current expand motion 571 * @param forceAbort whether the expansion should be forcefully aborted and returned to the old 572 * state 573 * @param velocity the velocity this was expanded/ collapsed with 574 */ 575 @VisibleForTesting finishExpanding(boolean forceAbort, float velocity)576 void finishExpanding(boolean forceAbort, float velocity) { 577 finishExpanding(forceAbort, velocity, true /* allowAnimation */); 578 } 579 580 /** 581 * Finish the current expand motion 582 * @param forceAbort whether the expansion should be forcefully aborted and returned to the old 583 * state 584 * @param velocity the velocity this was expanded/ collapsed with 585 */ finishExpanding(boolean forceAbort, float velocity, boolean allowAnimation)586 private void finishExpanding(boolean forceAbort, float velocity, boolean allowAnimation) { 587 if (!mExpanding) return; 588 589 if (DEBUG) Log.d(TAG, "scale in finishing on view: " + mResizedView); 590 591 float currentHeight = mScaler.getHeight(); 592 final boolean wasClosed = (mOldHeight == mSmallSize); 593 boolean nowExpanded; 594 if (!forceAbort) { 595 if (wasClosed) { 596 nowExpanded = currentHeight > mOldHeight && velocity >= 0; 597 } else { 598 nowExpanded = currentHeight >= mOldHeight || velocity > 0; 599 } 600 nowExpanded |= mNaturalHeight == mSmallSize; 601 } else { 602 nowExpanded = !wasClosed; 603 } 604 if (mScaleAnimation.isRunning()) { 605 mScaleAnimation.cancel(); 606 } 607 mCallback.expansionStateChanged(false); 608 int naturalHeight = mScaler.getNaturalHeight(); 609 float targetHeight = nowExpanded ? naturalHeight : mSmallSize; 610 if (targetHeight != currentHeight && mEnabled && allowAnimation) { 611 mScaleAnimation.setFloatValues(targetHeight); 612 mScaleAnimation.setupStartValues(); 613 final View scaledView = mResizedView; 614 final boolean expand = nowExpanded; 615 mScaleAnimation.addListener(new AnimatorListenerAdapter() { 616 public boolean mCancelled; 617 618 @Override 619 public void onAnimationEnd(Animator animation) { 620 if (!mCancelled) { 621 mCallback.setUserExpandedChild(scaledView, expand); 622 if (!mExpanding) { 623 mScaler.setView(null); 624 } 625 } else { 626 mCallback.setExpansionCancelled(scaledView); 627 } 628 mCallback.setUserLockedChild(scaledView, false); 629 mScaleAnimation.removeListener(this); 630 if (wasClosed) { 631 InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_ROW_EXPAND); 632 } 633 } 634 635 @Override 636 public void onAnimationCancel(Animator animation) { 637 mCancelled = true; 638 } 639 }); 640 velocity = nowExpanded == velocity >= 0 ? velocity : 0; 641 mFlingAnimationUtils.apply(mScaleAnimation, currentHeight, targetHeight, velocity); 642 mScaleAnimation.start(); 643 } else { 644 if (targetHeight != currentHeight) { 645 mScaler.setHeight(targetHeight); 646 } 647 mCallback.setUserExpandedChild(mResizedView, nowExpanded); 648 mCallback.setUserLockedChild(mResizedView, false); 649 mScaler.setView(null); 650 if (wasClosed) { 651 InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_ROW_EXPAND); 652 } 653 } 654 655 mExpanding = false; 656 mExpansionStyle = NONE; 657 658 if (DEBUG) Log.d(TAG, "wasClosed is: " + wasClosed); 659 if (DEBUG) Log.d(TAG, "currentHeight is: " + currentHeight); 660 if (DEBUG) Log.d(TAG, "mSmallSize is: " + mSmallSize); 661 if (DEBUG) Log.d(TAG, "targetHeight is: " + targetHeight); 662 if (DEBUG) Log.d(TAG, "scale was finished on view: " + mResizedView); 663 } 664 clearView()665 private void clearView() { 666 mResizedView = null; 667 } 668 669 /** 670 * Use this to abort any pending expansions in progress and force that there will be no 671 * animations. 672 */ cancelImmediately()673 public void cancelImmediately() { 674 cancel(false /* allowAnimation */); 675 } 676 677 /** 678 * Use this to abort any pending expansions in progress. 679 */ cancel()680 public void cancel() { 681 cancel(true /* allowAnimation */); 682 } 683 cancel(boolean allowAnimation)684 private void cancel(boolean allowAnimation) { 685 finishExpanding(true /* forceAbort */, 0f /* velocity */, allowAnimation); 686 clearView(); 687 688 // reset the gesture detector 689 mSGD = new ScaleGestureDetector(mContext, mScaleGestureListener); 690 } 691 692 /** 693 * Change the expansion mode to only observe movements and don't perform any resizing. 694 * This is needed when the expanding is finished and the scroller kicks in, 695 * performing an overscroll motion. We only want to shrink it again when we are not 696 * overscrolled. 697 * 698 * @param onlyMovements Should only movements be observed? 699 */ onlyObserveMovements(boolean onlyMovements)700 public void onlyObserveMovements(boolean onlyMovements) { 701 mOnlyMovements = onlyMovements; 702 } 703 } 704 705