DividerView.java revision 069dfe67a27a068d3893f42d3fb54c4255503f73
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.stackdivider; 18 19import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW; 20import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW; 21 22import android.animation.Animator; 23import android.animation.AnimatorListenerAdapter; 24import android.animation.ValueAnimator; 25import android.animation.ValueAnimator.AnimatorUpdateListener; 26import android.annotation.Nullable; 27import android.app.ActivityManager.StackId; 28import android.content.Context; 29import android.content.res.Configuration; 30import android.graphics.Rect; 31import android.graphics.Region.Op; 32import android.hardware.display.DisplayManager; 33import android.os.Bundle; 34import android.util.AttributeSet; 35import android.view.Display; 36import android.view.DisplayInfo; 37import android.view.GestureDetector; 38import android.view.GestureDetector.SimpleOnGestureListener; 39import android.view.HapticFeedbackConstants; 40import android.view.MotionEvent; 41import android.view.PointerIcon; 42import android.view.VelocityTracker; 43import android.view.View; 44import android.view.View.OnTouchListener; 45import android.view.ViewConfiguration; 46import android.view.ViewTreeObserver.InternalInsetsInfo; 47import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; 48import android.view.WindowInsets; 49import android.view.WindowManager; 50import android.view.accessibility.AccessibilityNodeInfo; 51import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 52import android.view.animation.Interpolator; 53import android.view.animation.PathInterpolator; 54import android.widget.FrameLayout; 55 56import com.android.internal.logging.MetricsLogger; 57import com.android.internal.logging.MetricsProto.MetricsEvent; 58import com.android.internal.policy.DividerSnapAlgorithm; 59import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; 60import com.android.internal.policy.DockedDividerUtils; 61import com.android.systemui.Interpolators; 62import com.android.systemui.R; 63import com.android.systemui.recents.Recents; 64import com.android.systemui.recents.events.EventBus; 65import com.android.systemui.recents.events.activity.DockedTopTaskEvent; 66import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; 67import com.android.systemui.recents.events.activity.UndockingTaskEvent; 68import com.android.systemui.recents.events.ui.RecentsDrawnEvent; 69import com.android.systemui.recents.events.ui.RecentsGrowingEvent; 70import com.android.systemui.recents.misc.SystemServicesProxy; 71import com.android.systemui.stackdivider.events.StartedDragingEvent; 72import com.android.systemui.stackdivider.events.StoppedDragingEvent; 73import com.android.systemui.statusbar.FlingAnimationUtils; 74import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; 75 76/** 77 * Docked stack divider. 78 */ 79public class DividerView extends FrameLayout implements OnTouchListener, 80 OnComputeInternalInsetsListener { 81 82 static final long TOUCH_ANIMATION_DURATION = 150; 83 static final long TOUCH_RELEASE_ANIMATION_DURATION = 200; 84 85 public static final int INVALID_RECENTS_GROW_TARGET = -1; 86 87 private static final int LOG_VALUE_RESIZE_50_50 = 0; 88 private static final int LOG_VALUE_RESIZE_DOCKED_SMALLER = 1; 89 private static final int LOG_VALUE_RESIZE_DOCKED_LARGER = 2; 90 91 private static final int LOG_VALUE_UNDOCK_MAX_DOCKED = 0; 92 private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1; 93 94 private static final int TASK_POSITION_SAME = Integer.MAX_VALUE; 95 private static final boolean SWAPPING_ENABLED = false; 96 97 /** 98 * How much the background gets scaled when we are in the minimized dock state. 99 */ 100 private static final float MINIMIZE_DOCK_SCALE = 0f; 101 private static final float ADJUSTED_FOR_IME_SCALE = 0.5f; 102 103 private static final PathInterpolator SLOWDOWN_INTERPOLATOR = 104 new PathInterpolator(0.5f, 1f, 0.5f, 1f); 105 private static final PathInterpolator DIM_INTERPOLATOR = 106 new PathInterpolator(.23f, .87f, .52f, -0.11f); 107 private static final Interpolator IME_ADJUST_INTERPOLATOR = 108 new PathInterpolator(0.2f, 0f, 0.1f, 1f); 109 110 private DividerHandleView mHandle; 111 private View mBackground; 112 private MinimizedDockShadow mMinimizedShadow; 113 private int mStartX; 114 private int mStartY; 115 private int mStartPosition; 116 private int mDockSide; 117 private final int[] mTempInt2 = new int[2]; 118 private boolean mMoving; 119 private int mTouchSlop; 120 private boolean mBackgroundLifted; 121 122 private int mDividerInsets; 123 private int mDisplayWidth; 124 private int mDisplayHeight; 125 private int mDividerWindowWidth; 126 private int mDividerSize; 127 private int mTouchElevation; 128 private int mLongPressEntraceAnimDuration; 129 130 private final Rect mDockedRect = new Rect(); 131 private final Rect mDockedTaskRect = new Rect(); 132 private final Rect mOtherTaskRect = new Rect(); 133 private final Rect mOtherRect = new Rect(); 134 private final Rect mDockedInsetRect = new Rect(); 135 private final Rect mOtherInsetRect = new Rect(); 136 private final Rect mLastResizeRect = new Rect(); 137 private final Rect mDisplayRect = new Rect(); 138 private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance(); 139 private DividerWindowManager mWindowManager; 140 private VelocityTracker mVelocityTracker; 141 private FlingAnimationUtils mFlingAnimationUtils; 142 private DividerSnapAlgorithm mSnapAlgorithm; 143 private final Rect mStableInsets = new Rect(); 144 145 private boolean mAnimateAfterRecentsDrawn; 146 private boolean mGrowAfterRecentsDrawn; 147 private boolean mGrowRecents; 148 private ValueAnimator mCurrentAnimator; 149 private boolean mEntranceAnimationRunning; 150 private boolean mExitAnimationRunning; 151 private int mExitStartPosition; 152 private GestureDetector mGestureDetector; 153 private boolean mDockedStackMinimized; 154 private boolean mAdjustedForIme; 155 156 private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() { 157 @Override 158 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 159 super.onInitializeAccessibilityNodeInfo(host, info); 160 if (isHorizontalDivision()) { 161 info.addAction(new AccessibilityAction(R.id.action_move_tl_full, 162 mContext.getString(R.string.accessibility_action_divider_top_full))); 163 if (mSnapAlgorithm.isFirstSplitTargetAvailable()) { 164 info.addAction(new AccessibilityAction(R.id.action_move_tl_70, 165 mContext.getString(R.string.accessibility_action_divider_top_70))); 166 } 167 info.addAction(new AccessibilityAction(R.id.action_move_tl_50, 168 mContext.getString(R.string.accessibility_action_divider_top_50))); 169 if (mSnapAlgorithm.isLastSplitTargetAvailable()) { 170 info.addAction(new AccessibilityAction(R.id.action_move_tl_30, 171 mContext.getString(R.string.accessibility_action_divider_top_30))); 172 } 173 info.addAction(new AccessibilityAction(R.id.action_move_rb_full, 174 mContext.getString(R.string.accessibility_action_divider_bottom_full))); 175 } else { 176 info.addAction(new AccessibilityAction(R.id.action_move_tl_full, 177 mContext.getString(R.string.accessibility_action_divider_left_full))); 178 if (mSnapAlgorithm.isFirstSplitTargetAvailable()) { 179 info.addAction(new AccessibilityAction(R.id.action_move_tl_70, 180 mContext.getString(R.string.accessibility_action_divider_left_70))); 181 } 182 info.addAction(new AccessibilityAction(R.id.action_move_tl_50, 183 mContext.getString(R.string.accessibility_action_divider_left_50))); 184 if (mSnapAlgorithm.isLastSplitTargetAvailable()) { 185 info.addAction(new AccessibilityAction(R.id.action_move_tl_30, 186 mContext.getString(R.string.accessibility_action_divider_left_30))); 187 } 188 info.addAction(new AccessibilityAction(R.id.action_move_rb_full, 189 mContext.getString(R.string.accessibility_action_divider_right_full))); 190 } 191 } 192 193 @Override 194 public boolean performAccessibilityAction(View host, int action, Bundle args) { 195 int currentPosition = getCurrentPosition(); 196 SnapTarget nextTarget = null; 197 switch (action) { 198 case R.id.action_move_tl_full: 199 nextTarget = mSnapAlgorithm.getDismissEndTarget(); 200 break; 201 case R.id.action_move_tl_70: 202 nextTarget = mSnapAlgorithm.getLastSplitTarget(); 203 break; 204 case R.id.action_move_tl_50: 205 nextTarget = mSnapAlgorithm.getMiddleTarget(); 206 break; 207 case R.id.action_move_tl_30: 208 nextTarget = mSnapAlgorithm.getFirstSplitTarget(); 209 break; 210 case R.id.action_move_rb_full: 211 nextTarget = mSnapAlgorithm.getDismissStartTarget(); 212 break; 213 } 214 if (nextTarget != null) { 215 startDragging(true /* animate */, false /* touching */); 216 stopDragging(currentPosition, nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN); 217 return true; 218 } 219 return super.performAccessibilityAction(host, action, args); 220 } 221 }; 222 223 private final Runnable mResetBackgroundRunnable = new Runnable() { 224 @Override 225 public void run() { 226 resetBackground(); 227 } 228 }; 229 230 public DividerView(Context context) { 231 super(context); 232 } 233 234 public DividerView(Context context, @Nullable AttributeSet attrs) { 235 super(context, attrs); 236 } 237 238 public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 239 super(context, attrs, defStyleAttr); 240 } 241 242 public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, 243 int defStyleRes) { 244 super(context, attrs, defStyleAttr, defStyleRes); 245 } 246 247 @Override 248 protected void onFinishInflate() { 249 super.onFinishInflate(); 250 mHandle = (DividerHandleView) findViewById(R.id.docked_divider_handle); 251 mBackground = findViewById(R.id.docked_divider_background); 252 mMinimizedShadow = (MinimizedDockShadow) findViewById(R.id.minimized_dock_shadow); 253 mHandle.setOnTouchListener(this); 254 mDividerWindowWidth = getResources().getDimensionPixelSize( 255 com.android.internal.R.dimen.docked_stack_divider_thickness); 256 mDividerInsets = getResources().getDimensionPixelSize( 257 com.android.internal.R.dimen.docked_stack_divider_insets); 258 mDividerSize = mDividerWindowWidth - 2 * mDividerInsets; 259 mTouchElevation = getResources().getDimensionPixelSize( 260 R.dimen.docked_stack_divider_lift_elevation); 261 mLongPressEntraceAnimDuration = getResources().getInteger( 262 R.integer.long_press_dock_anim_duration); 263 mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow); 264 mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); 265 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f); 266 updateDisplayInfo(); 267 boolean landscape = getResources().getConfiguration().orientation 268 == Configuration.ORIENTATION_LANDSCAPE; 269 mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(), 270 landscape ? STYLE_HORIZONTAL_DOUBLE_ARROW : STYLE_VERTICAL_DOUBLE_ARROW)); 271 getViewTreeObserver().addOnComputeInternalInsetsListener(this); 272 mHandle.setAccessibilityDelegate(mHandleDelegate); 273 mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() { 274 @Override 275 public boolean onSingleTapUp(MotionEvent e) { 276 if (SWAPPING_ENABLED) { 277 updateDockSide(); 278 SystemServicesProxy ssp = Recents.getSystemServices(); 279 if (mDockSide != WindowManager.DOCKED_INVALID 280 && !ssp.isRecentsActivityVisible()) { 281 mWindowManagerProxy.swapTasks(); 282 return true; 283 } 284 } 285 return false; 286 } 287 }); 288 } 289 290 @Override 291 protected void onAttachedToWindow() { 292 super.onAttachedToWindow(); 293 EventBus.getDefault().register(this); 294 } 295 296 @Override 297 protected void onDetachedFromWindow() { 298 super.onDetachedFromWindow(); 299 EventBus.getDefault().unregister(this); 300 } 301 302 @Override 303 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 304 if (mStableInsets.left != insets.getStableInsetLeft() 305 || mStableInsets.top != insets.getStableInsetTop() 306 || mStableInsets.right != insets.getStableInsetRight() 307 || mStableInsets.bottom != insets.getStableInsetBottom()) { 308 mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(), 309 insets.getStableInsetRight(), insets.getStableInsetBottom()); 310 if (mSnapAlgorithm != null) { 311 mSnapAlgorithm = null; 312 initializeSnapAlgorithm(); 313 } 314 } 315 return super.onApplyWindowInsets(insets); 316 } 317 318 @Override 319 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 320 super.onLayout(changed, left, top, right, bottom); 321 int minimizeLeft = 0; 322 int minimizeTop = 0; 323 if (mDockSide == WindowManager.DOCKED_TOP) { 324 minimizeTop = mBackground.getTop(); 325 } else if (mDockSide == WindowManager.DOCKED_LEFT) { 326 minimizeLeft = mBackground.getLeft(); 327 } else if (mDockSide == WindowManager.DOCKED_RIGHT) { 328 minimizeLeft = mBackground.getRight() - mMinimizedShadow.getWidth(); 329 } 330 mMinimizedShadow.layout(minimizeLeft, minimizeTop, 331 minimizeLeft + mMinimizedShadow.getMeasuredWidth(), 332 minimizeTop + mMinimizedShadow.getMeasuredHeight()); 333 if (changed) { 334 mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(), 335 mHandle.getRight(), mHandle.getBottom())); 336 } 337 } 338 339 public void setWindowManager(DividerWindowManager windowManager) { 340 mWindowManager = windowManager; 341 } 342 343 public WindowManagerProxy getWindowManagerProxy() { 344 return mWindowManagerProxy; 345 } 346 347 public boolean startDragging(boolean animate, boolean touching) { 348 cancelFlingAnimation(); 349 if (touching) { 350 mHandle.setTouching(true, animate); 351 } 352 mDockSide = mWindowManagerProxy.getDockSide(); 353 initializeSnapAlgorithm(); 354 mWindowManagerProxy.setResizing(true); 355 if (touching) { 356 mWindowManager.setSlippery(false); 357 liftBackground(); 358 } 359 EventBus.getDefault().send(new StartedDragingEvent()); 360 return mDockSide != WindowManager.DOCKED_INVALID; 361 } 362 363 public void stopDragging(int position, float velocity, boolean avoidDismissStart, 364 boolean logMetrics) { 365 mHandle.setTouching(false, true /* animate */); 366 fling(position, velocity, avoidDismissStart, logMetrics); 367 mWindowManager.setSlippery(true); 368 releaseBackground(); 369 } 370 371 public void stopDragging(int position, SnapTarget target, long duration, 372 Interpolator interpolator) { 373 stopDragging(position, target, duration, 0 /* startDelay*/, 0 /* endDelay */, interpolator); 374 } 375 376 public void stopDragging(int position, SnapTarget target, long duration, 377 Interpolator interpolator, long endDelay) { 378 stopDragging(position, target, duration, 0 /* startDelay*/, endDelay, interpolator); 379 } 380 381 public void stopDragging(int position, SnapTarget target, long duration, long startDelay, 382 long endDelay, Interpolator interpolator) { 383 mHandle.setTouching(false, true /* animate */); 384 flingTo(position, target, duration, startDelay, endDelay, interpolator); 385 mWindowManager.setSlippery(true); 386 releaseBackground(); 387 } 388 389 private void stopDragging() { 390 mHandle.setTouching(false, true /* animate */); 391 mWindowManager.setSlippery(true); 392 releaseBackground(); 393 } 394 395 private void updateDockSide() { 396 mDockSide = mWindowManagerProxy.getDockSide(); 397 mMinimizedShadow.setDockSide(mDockSide); 398 } 399 400 private void initializeSnapAlgorithm() { 401 if (mSnapAlgorithm == null) { 402 mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(), mDisplayWidth, 403 mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets); 404 } 405 } 406 407 public DividerSnapAlgorithm getSnapAlgorithm() { 408 initializeSnapAlgorithm(); 409 return mSnapAlgorithm; 410 } 411 412 public int getCurrentPosition() { 413 getLocationOnScreen(mTempInt2); 414 if (isHorizontalDivision()) { 415 return mTempInt2[1] + mDividerInsets; 416 } else { 417 return mTempInt2[0] + mDividerInsets; 418 } 419 } 420 421 @Override 422 public boolean onTouch(View v, MotionEvent event) { 423 convertToScreenCoordinates(event); 424 mGestureDetector.onTouchEvent(event); 425 final int action = event.getAction() & MotionEvent.ACTION_MASK; 426 switch (action) { 427 case MotionEvent.ACTION_DOWN: 428 mVelocityTracker = VelocityTracker.obtain(); 429 mVelocityTracker.addMovement(event); 430 mStartX = (int) event.getX(); 431 mStartY = (int) event.getY(); 432 boolean result = startDragging(true /* animate */, true /* touching */); 433 if (!result) { 434 435 // Weren't able to start dragging successfully, so cancel it again. 436 stopDragging(); 437 } 438 mStartPosition = getCurrentPosition(); 439 mMoving = false; 440 return result; 441 case MotionEvent.ACTION_MOVE: 442 mVelocityTracker.addMovement(event); 443 int x = (int) event.getX(); 444 int y = (int) event.getY(); 445 boolean exceededTouchSlop = 446 isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop 447 || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop); 448 if (!mMoving && exceededTouchSlop) { 449 mStartX = x; 450 mStartY = y; 451 mMoving = true; 452 } 453 if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) { 454 SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget( 455 mStartPosition, 0 /* velocity */, false /* hardDismiss */); 456 resizeStack(calculatePosition(x, y), mStartPosition, snapTarget); 457 } 458 break; 459 case MotionEvent.ACTION_UP: 460 case MotionEvent.ACTION_CANCEL: 461 mVelocityTracker.addMovement(event); 462 463 x = (int) event.getRawX(); 464 y = (int) event.getRawY(); 465 466 mVelocityTracker.computeCurrentVelocity(1000); 467 int position = calculatePosition(x, y); 468 stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity() 469 : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */, 470 true /* log */); 471 mMoving = false; 472 break; 473 } 474 return true; 475 } 476 477 private void logResizeEvent(SnapTarget snapTarget) { 478 if (snapTarget == mSnapAlgorithm.getDismissStartTarget()) { 479 MetricsLogger.action( 480 mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide) 481 ? LOG_VALUE_UNDOCK_MAX_OTHER 482 : LOG_VALUE_UNDOCK_MAX_DOCKED); 483 } else if (snapTarget == mSnapAlgorithm.getDismissEndTarget()) { 484 MetricsLogger.action( 485 mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide) 486 ? LOG_VALUE_UNDOCK_MAX_OTHER 487 : LOG_VALUE_UNDOCK_MAX_DOCKED); 488 } else if (snapTarget == mSnapAlgorithm.getMiddleTarget()) { 489 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE, 490 LOG_VALUE_RESIZE_50_50); 491 } else if (snapTarget == mSnapAlgorithm.getFirstSplitTarget()) { 492 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE, 493 dockSideTopLeft(mDockSide) 494 ? LOG_VALUE_RESIZE_DOCKED_SMALLER 495 : LOG_VALUE_RESIZE_DOCKED_LARGER); 496 } else if (snapTarget == mSnapAlgorithm.getLastSplitTarget()) { 497 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE, 498 dockSideTopLeft(mDockSide) 499 ? LOG_VALUE_RESIZE_DOCKED_LARGER 500 : LOG_VALUE_RESIZE_DOCKED_SMALLER); 501 } 502 } 503 504 private void convertToScreenCoordinates(MotionEvent event) { 505 event.setLocation(event.getRawX(), event.getRawY()); 506 } 507 508 private void fling(int position, float velocity, boolean avoidDismissStart, 509 boolean logMetrics) { 510 SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity); 511 if (avoidDismissStart && snapTarget == mSnapAlgorithm.getDismissStartTarget()) { 512 snapTarget = mSnapAlgorithm.getFirstSplitTarget(); 513 } 514 if (logMetrics) { 515 logResizeEvent(snapTarget); 516 } 517 ValueAnimator anim = getFlingAnimator(position, snapTarget, 0 /* endDelay */); 518 mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity); 519 anim.start(); 520 } 521 522 private void flingTo(int position, SnapTarget target, long duration, long startDelay, 523 long endDelay, Interpolator interpolator) { 524 ValueAnimator anim = getFlingAnimator(position, target, endDelay); 525 anim.setDuration(duration); 526 anim.setStartDelay(startDelay); 527 anim.setInterpolator(interpolator); 528 anim.start(); 529 } 530 531 private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget, 532 final long endDelay) { 533 final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE; 534 ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position); 535 anim.addUpdateListener(animation -> resizeStack((Integer) animation.getAnimatedValue(), 536 taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f 537 ? TASK_POSITION_SAME 538 : snapTarget.position, snapTarget)); 539 Runnable endAction = () -> { 540 commitSnapFlags(snapTarget); 541 mWindowManagerProxy.setResizing(false); 542 mDockSide = WindowManager.DOCKED_INVALID; 543 mCurrentAnimator = null; 544 mEntranceAnimationRunning = false; 545 mExitAnimationRunning = false; 546 EventBus.getDefault().send(new StoppedDragingEvent()); 547 }; 548 anim.addListener(new AnimatorListenerAdapter() { 549 550 private boolean mCancelled; 551 552 @Override 553 public void onAnimationCancel(Animator animation) { 554 mCancelled = true; 555 } 556 557 @Override 558 public void onAnimationEnd(Animator animation) { 559 if (endDelay == 0 || mCancelled) { 560 endAction.run(); 561 } else { 562 postDelayed(endAction, endDelay); 563 } 564 } 565 }); 566 mCurrentAnimator = anim; 567 return anim; 568 } 569 570 private void cancelFlingAnimation() { 571 if (mCurrentAnimator != null) { 572 mCurrentAnimator.cancel(); 573 } 574 } 575 576 private void commitSnapFlags(SnapTarget target) { 577 if (target.flag == SnapTarget.FLAG_NONE) { 578 return; 579 } 580 boolean dismissOrMaximize; 581 if (target.flag == SnapTarget.FLAG_DISMISS_START) { 582 dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT 583 || mDockSide == WindowManager.DOCKED_TOP; 584 } else { 585 dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT 586 || mDockSide == WindowManager.DOCKED_BOTTOM; 587 } 588 if (dismissOrMaximize) { 589 mWindowManagerProxy.dismissDockedStack(); 590 } else { 591 mWindowManagerProxy.maximizeDockedStack(); 592 } 593 mWindowManagerProxy.setResizeDimLayer(false, -1, 0f); 594 } 595 596 private void liftBackground() { 597 if (mBackgroundLifted) { 598 return; 599 } 600 if (isHorizontalDivision()) { 601 mBackground.animate().scaleY(1.4f); 602 } else { 603 mBackground.animate().scaleX(1.4f); 604 } 605 mBackground.animate() 606 .setInterpolator(Interpolators.TOUCH_RESPONSE) 607 .setDuration(TOUCH_ANIMATION_DURATION) 608 .translationZ(mTouchElevation) 609 .start(); 610 611 // Lift handle as well so it doesn't get behind the background, even though it doesn't 612 // cast shadow. 613 mHandle.animate() 614 .setInterpolator(Interpolators.TOUCH_RESPONSE) 615 .setDuration(TOUCH_ANIMATION_DURATION) 616 .translationZ(mTouchElevation) 617 .start(); 618 mBackgroundLifted = true; 619 } 620 621 private void releaseBackground() { 622 if (!mBackgroundLifted) { 623 return; 624 } 625 mBackground.animate() 626 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 627 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION) 628 .translationZ(0) 629 .scaleX(1f) 630 .scaleY(1f) 631 .start(); 632 mHandle.animate() 633 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 634 .setDuration(TOUCH_RELEASE_ANIMATION_DURATION) 635 .translationZ(0) 636 .start(); 637 mBackgroundLifted = false; 638 } 639 640 641 public void setMinimizedDockStack(boolean minimized) { 642 updateDockSide(); 643 mHandle.setAlpha(minimized ? 0f : 1f); 644 if (!minimized) { 645 resetBackground(); 646 } else if (mDockSide == WindowManager.DOCKED_TOP) { 647 mBackground.setPivotY(0); 648 mBackground.setScaleY(MINIMIZE_DOCK_SCALE); 649 } else if (mDockSide == WindowManager.DOCKED_LEFT 650 || mDockSide == WindowManager.DOCKED_RIGHT) { 651 mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT 652 ? 0 653 : mBackground.getWidth()); 654 mBackground.setScaleX(MINIMIZE_DOCK_SCALE); 655 } 656 mMinimizedShadow.setAlpha(minimized ? 1f : 0f); 657 mDockedStackMinimized = minimized; 658 } 659 660 public void setMinimizedDockStack(boolean minimized, long animDuration) { 661 updateDockSide(); 662 mHandle.animate() 663 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 664 .setDuration(animDuration) 665 .alpha(minimized ? 0f : 1f) 666 .start(); 667 if (mDockSide == WindowManager.DOCKED_TOP) { 668 mBackground.setPivotY(0); 669 mBackground.animate() 670 .scaleY(minimized ? MINIMIZE_DOCK_SCALE : 1f); 671 } else if (mDockSide == WindowManager.DOCKED_LEFT 672 || mDockSide == WindowManager.DOCKED_RIGHT) { 673 mBackground.setPivotX(mDockSide == WindowManager.DOCKED_LEFT 674 ? 0 675 : mBackground.getWidth()); 676 mBackground.animate() 677 .scaleX(minimized ? MINIMIZE_DOCK_SCALE : 1f); 678 } 679 if (!minimized) { 680 mBackground.animate().withEndAction(mResetBackgroundRunnable); 681 } 682 mMinimizedShadow.animate() 683 .alpha(minimized ? 1f : 0f) 684 .setInterpolator(Interpolators.ALPHA_IN) 685 .setDuration(animDuration) 686 .start(); 687 mBackground.animate() 688 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 689 .setDuration(animDuration) 690 .start(); 691 mDockedStackMinimized = minimized; 692 } 693 694 public void setAdjustedForIme(boolean adjustedForIme) { 695 updateDockSide(); 696 mHandle.setAlpha(adjustedForIme ? 0f : 1f); 697 if (!adjustedForIme) { 698 resetBackground(); 699 } else if (mDockSide == WindowManager.DOCKED_TOP) { 700 mBackground.setPivotY(0); 701 mBackground.setScaleY(ADJUSTED_FOR_IME_SCALE); 702 } 703 mAdjustedForIme = adjustedForIme; 704 } 705 706 public void setAdjustedForIme(boolean adjustedForIme, long animDuration) { 707 updateDockSide(); 708 mHandle.animate() 709 .setInterpolator(IME_ADJUST_INTERPOLATOR) 710 .setDuration(animDuration) 711 .alpha(adjustedForIme ? 0f : 1f) 712 .start(); 713 if (mDockSide == WindowManager.DOCKED_TOP) { 714 mBackground.setPivotY(0); 715 mBackground.animate() 716 .scaleY(adjustedForIme ? ADJUSTED_FOR_IME_SCALE : 1f); 717 } 718 if (!adjustedForIme) { 719 mBackground.animate().withEndAction(mResetBackgroundRunnable); 720 } 721 mBackground.animate() 722 .setInterpolator(IME_ADJUST_INTERPOLATOR) 723 .setDuration(animDuration) 724 .start(); 725 mAdjustedForIme = adjustedForIme; 726 } 727 728 private void resetBackground() { 729 mBackground.setPivotX(mBackground.getWidth() / 2); 730 mBackground.setPivotY(mBackground.getHeight() / 2); 731 mBackground.setScaleX(1f); 732 mBackground.setScaleY(1f); 733 mMinimizedShadow.setAlpha(0f); 734 } 735 736 @Override 737 protected void onConfigurationChanged(Configuration newConfig) { 738 super.onConfigurationChanged(newConfig); 739 updateDisplayInfo(); 740 } 741 742 743 public void notifyDockSideChanged(int newDockSide) { 744 mDockSide = newDockSide; 745 mMinimizedShadow.setDockSide(mDockSide); 746 requestLayout(); 747 } 748 749 private void updateDisplayInfo() { 750 final DisplayManager displayManager = 751 (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); 752 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); 753 final DisplayInfo info = new DisplayInfo(); 754 display.getDisplayInfo(info); 755 mDisplayWidth = info.logicalWidth; 756 mDisplayHeight = info.logicalHeight; 757 mSnapAlgorithm = null; 758 initializeSnapAlgorithm(); 759 } 760 761 private int calculatePosition(int touchX, int touchY) { 762 return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX); 763 } 764 765 public boolean isHorizontalDivision() { 766 return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; 767 } 768 769 private int calculateXPosition(int touchX) { 770 return mStartPosition + touchX - mStartX; 771 } 772 773 private int calculateYPosition(int touchY) { 774 return mStartPosition + touchY - mStartY; 775 } 776 777 private void alignTopLeft(Rect containingRect, Rect rect) { 778 int width = rect.width(); 779 int height = rect.height(); 780 rect.set(containingRect.left, containingRect.top, 781 containingRect.left + width, containingRect.top + height); 782 } 783 784 private void alignBottomRight(Rect containingRect, Rect rect) { 785 int width = rect.width(); 786 int height = rect.height(); 787 rect.set(containingRect.right - width, containingRect.bottom - height, 788 containingRect.right, containingRect.bottom); 789 } 790 791 public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) { 792 DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth, 793 mDisplayHeight, mDividerSize); 794 } 795 796 public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) { 797 calculateBoundsForPosition(position, mDockSide, mDockedRect); 798 799 if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) { 800 return; 801 } 802 803 // Make sure shadows are updated 804 if (mBackground.getZ() > 0f) { 805 mBackground.invalidate(); 806 } 807 808 mLastResizeRect.set(mDockedRect); 809 if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) { 810 if (mCurrentAnimator != null) { 811 calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect); 812 } else { 813 calculateBoundsForPosition(isHorizontalDivision() ? mDisplayHeight : mDisplayWidth, 814 mDockSide, mDockedTaskRect); 815 } 816 calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide), 817 mOtherTaskRect); 818 mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null, 819 mOtherTaskRect, null); 820 } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) { 821 calculateBoundsForPosition(taskPosition, 822 mDockSide, mDockedTaskRect); 823 calculateBoundsForPosition(mExitStartPosition, 824 DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect); 825 mOtherInsetRect.set(mOtherTaskRect); 826 applyExitAnimationParallax(mOtherTaskRect, position); 827 mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, null, 828 mOtherTaskRect, mOtherInsetRect); 829 } else if (taskPosition != TASK_POSITION_SAME) { 830 calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide), 831 mOtherRect); 832 int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide); 833 int taskPositionDocked = 834 restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget); 835 int taskPositionOther = 836 restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget); 837 calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect); 838 calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect); 839 mDisplayRect.set(0, 0, mDisplayWidth, mDisplayHeight); 840 alignTopLeft(mDockedRect, mDockedTaskRect); 841 alignTopLeft(mOtherRect, mOtherTaskRect); 842 mDockedInsetRect.set(mDockedTaskRect); 843 mOtherInsetRect.set(mOtherTaskRect); 844 if (dockSideTopLeft(mDockSide)) { 845 alignTopLeft(mDisplayRect, mDockedInsetRect); 846 alignBottomRight(mDisplayRect, mOtherInsetRect); 847 } else { 848 alignBottomRight(mDisplayRect, mDockedInsetRect); 849 alignTopLeft(mDisplayRect, mOtherInsetRect); 850 } 851 applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position, 852 taskPositionDocked); 853 applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position, 854 taskPositionOther); 855 mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect, 856 mOtherTaskRect, mOtherInsetRect); 857 } else { 858 mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null); 859 } 860 SnapTarget closestDismissTarget = mSnapAlgorithm.getClosestDismissTarget(position); 861 float dimFraction = getDimFraction(position, closestDismissTarget); 862 mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f, 863 getStackIdForDismissTarget(closestDismissTarget), 864 dimFraction); 865 } 866 867 private void applyExitAnimationParallax(Rect taskRect, int position) { 868 if (mDockSide == WindowManager.DOCKED_TOP) { 869 taskRect.offset(0, (int) ((position - mExitStartPosition) * 0.25f)); 870 } else if (mDockSide == WindowManager.DOCKED_LEFT) { 871 taskRect.offset((int) ((position - mExitStartPosition) * 0.25f), 0); 872 } else if (mDockSide == WindowManager.DOCKED_RIGHT) { 873 taskRect.offset((int) ((mExitStartPosition - position) * 0.25f), 0); 874 } 875 } 876 877 private float getDimFraction(int position, SnapTarget dismissTarget) { 878 if (mEntranceAnimationRunning) { 879 return 0f; 880 } 881 float fraction = mSnapAlgorithm.calculateDismissingFraction(position); 882 fraction = Math.max(0, Math.min(fraction, 1f)); 883 fraction = DIM_INTERPOLATOR.getInterpolation(fraction); 884 if (hasInsetsAtDismissTarget(dismissTarget)) { 885 886 // Less darkening with system insets. 887 fraction *= 0.8f; 888 } 889 return fraction; 890 } 891 892 /** 893 * @return true if and only if there are system insets at the location of the dismiss target 894 */ 895 private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) { 896 if (isHorizontalDivision()) { 897 if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) { 898 return mStableInsets.top != 0; 899 } else { 900 return mStableInsets.bottom != 0; 901 } 902 } else { 903 if (dismissTarget == mSnapAlgorithm.getDismissStartTarget()) { 904 return mStableInsets.left != 0; 905 } else { 906 return mStableInsets.right != 0; 907 } 908 } 909 } 910 911 /** 912 * When the snap target is dismissing one side, make sure that the dismissing side doesn't get 913 * 0 size. 914 */ 915 private int restrictDismissingTaskPosition(int taskPosition, int dockSide, 916 SnapTarget snapTarget) { 917 if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) { 918 return Math.max(mSnapAlgorithm.getFirstSplitTarget().position, mStartPosition); 919 } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END 920 && dockSideBottomRight(dockSide)) { 921 return Math.min(mSnapAlgorithm.getLastSplitTarget().position, mStartPosition); 922 } else { 923 return taskPosition; 924 } 925 } 926 927 /** 928 * Applies a parallax to the task when dismissing. 929 */ 930 private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget, 931 int position, int taskPosition) { 932 float fraction = Math.min(1, Math.max(0, 933 mSnapAlgorithm.calculateDismissingFraction(position))); 934 SnapTarget dismissTarget = null; 935 SnapTarget splitTarget = null; 936 int start = 0; 937 if (position <= mSnapAlgorithm.getLastSplitTarget().position 938 && dockSideTopLeft(dockSide)) { 939 dismissTarget = mSnapAlgorithm.getDismissStartTarget(); 940 splitTarget = mSnapAlgorithm.getFirstSplitTarget(); 941 start = taskPosition; 942 } else if (position >= mSnapAlgorithm.getLastSplitTarget().position 943 && dockSideBottomRight(dockSide)) { 944 dismissTarget = mSnapAlgorithm.getDismissEndTarget(); 945 splitTarget = mSnapAlgorithm.getLastSplitTarget(); 946 start = splitTarget.position; 947 } 948 if (dismissTarget != null && fraction > 0f 949 && isDismissing(splitTarget, position, dockSide)) { 950 fraction = calculateParallaxDismissingFraction(fraction, dockSide); 951 int offsetPosition = (int) (start + 952 fraction * (dismissTarget.position - splitTarget.position)); 953 int width = taskRect.width(); 954 int height = taskRect.height(); 955 switch (dockSide) { 956 case WindowManager.DOCKED_LEFT: 957 taskRect.left = offsetPosition - width; 958 taskRect.right = offsetPosition; 959 break; 960 case WindowManager.DOCKED_RIGHT: 961 taskRect.left = offsetPosition + mDividerSize; 962 taskRect.right = offsetPosition + width + mDividerSize; 963 break; 964 case WindowManager.DOCKED_TOP: 965 taskRect.top = offsetPosition - height; 966 taskRect.bottom = offsetPosition; 967 break; 968 case WindowManager.DOCKED_BOTTOM: 969 taskRect.top = offsetPosition + mDividerSize; 970 taskRect.bottom = offsetPosition + height + mDividerSize; 971 break; 972 } 973 } 974 } 975 976 /** 977 * @return for a specified {@code fraction}, this returns an adjusted value that simulates a 978 * slowing down parallax effect 979 */ 980 private static float calculateParallaxDismissingFraction(float fraction, int dockSide) { 981 float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f; 982 983 // Less parallax at the top, just because. 984 if (dockSide == WindowManager.DOCKED_TOP) { 985 result /= 2f; 986 } 987 return result; 988 } 989 990 private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) { 991 if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) { 992 return position < snapTarget.position; 993 } else { 994 return position > snapTarget.position; 995 } 996 } 997 998 private int getStackIdForDismissTarget(SnapTarget dismissTarget) { 999 if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide)) 1000 || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END 1001 && dockSideBottomRight(mDockSide))) { 1002 return StackId.DOCKED_STACK_ID; 1003 } else { 1004 return StackId.HOME_STACK_ID; 1005 } 1006 } 1007 1008 /** 1009 * @return true if and only if {@code dockSide} is top or left 1010 */ 1011 private static boolean dockSideTopLeft(int dockSide) { 1012 return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT; 1013 } 1014 1015 /** 1016 * @return true if and only if {@code dockSide} is bottom or right 1017 */ 1018 private static boolean dockSideBottomRight(int dockSide) { 1019 return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT; 1020 } 1021 1022 @Override 1023 public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) { 1024 inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 1025 inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), 1026 mHandle.getBottom()); 1027 inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(), 1028 mBackground.getRight(), mBackground.getBottom(), Op.UNION); 1029 } 1030 1031 /** 1032 * Checks whether recents will grow when invoked. This happens in multi-window when recents is 1033 * very small. When invoking recents, we shrink the docked stack so recents has more space. 1034 * 1035 * @return the position of the divider when recents grows, or 1036 * {@link #INVALID_RECENTS_GROW_TARGET} if recents won't grow 1037 */ 1038 public int growsRecents() { 1039 boolean result = mGrowRecents 1040 && mWindowManagerProxy.getDockSide() == WindowManager.DOCKED_TOP 1041 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position; 1042 if (result) { 1043 return getSnapAlgorithm().getMiddleTarget().position; 1044 } else { 1045 return INVALID_RECENTS_GROW_TARGET; 1046 } 1047 } 1048 1049 public final void onBusEvent(RecentsActivityStartingEvent recentsActivityStartingEvent) { 1050 if (mGrowRecents && getWindowManagerProxy().getDockSide() == WindowManager.DOCKED_TOP 1051 && getCurrentPosition() == getSnapAlgorithm().getLastSplitTarget().position) { 1052 mGrowAfterRecentsDrawn = true; 1053 startDragging(false /* animate */, false /* touching */); 1054 } 1055 } 1056 1057 public final void onBusEvent(DockedTopTaskEvent event) { 1058 if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) { 1059 mGrowAfterRecentsDrawn = false; 1060 mAnimateAfterRecentsDrawn = true; 1061 startDragging(false /* animate */, false /* touching */); 1062 } 1063 updateDockSide(); 1064 int position = DockedDividerUtils.calculatePositionForBounds(event.initialRect, 1065 mDockSide, mDividerSize); 1066 mEntranceAnimationRunning = true; 1067 resizeStack(position, mSnapAlgorithm.getMiddleTarget().position, 1068 mSnapAlgorithm.getMiddleTarget()); 1069 1070 // Vibrate after docking 1071 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 1072 } 1073 1074 public final void onBusEvent(RecentsDrawnEvent drawnEvent) { 1075 if (mAnimateAfterRecentsDrawn) { 1076 mAnimateAfterRecentsDrawn = false; 1077 updateDockSide(); 1078 1079 // Delay switching resizing mode because this might cause jank in recents animation 1080 // that's long than this animation. 1081 stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 1082 mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN, 1083 200 /* endDelay */); 1084 } 1085 if (mGrowAfterRecentsDrawn) { 1086 mGrowAfterRecentsDrawn = false; 1087 updateDockSide(); 1088 EventBus.getDefault().send(new RecentsGrowingEvent()); 1089 stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 336, 1090 Interpolators.FAST_OUT_SLOW_IN); 1091 } 1092 } 1093 1094 public final void onBusEvent(UndockingTaskEvent undockingTaskEvent) { 1095 int dockSide = mWindowManagerProxy.getDockSide(); 1096 if (dockSide != WindowManager.DOCKED_INVALID && !mDockedStackMinimized) { 1097 startDragging(false /* animate */, false /* touching */); 1098 SnapTarget target = dockSideTopLeft(dockSide) 1099 ? mSnapAlgorithm.getDismissEndTarget() 1100 : mSnapAlgorithm.getDismissStartTarget(); 1101 1102 // Don't start immediately - give a little bit time to settle the drag resize change. 1103 mExitAnimationRunning = true; 1104 mExitStartPosition = getCurrentPosition(); 1105 stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */, 1106 0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN); 1107 1108 // Vibrate after undocking 1109 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 1110 } 1111 } 1112} 1113