DividerView.java revision 81fe2d1f0adc9e752d7f1a410d66af6a326fd6e2
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 android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ValueAnimator; 22import android.animation.ValueAnimator.AnimatorUpdateListener; 23import android.annotation.Nullable; 24import android.content.Context; 25import android.content.res.Configuration; 26import android.graphics.Rect; 27import android.graphics.Region.Op; 28import android.hardware.display.DisplayManager; 29import android.util.AttributeSet; 30import android.view.Display; 31import android.view.DisplayInfo; 32import android.view.MotionEvent; 33import android.view.PointerIcon; 34import android.view.VelocityTracker; 35import android.view.View; 36import android.view.View.OnTouchListener; 37import android.view.ViewTreeObserver.InternalInsetsInfo; 38import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; 39import android.view.WindowInsets; 40import android.view.WindowManager; 41import android.view.animation.AnimationUtils; 42import android.view.animation.Interpolator; 43import android.view.animation.PathInterpolator; 44import android.widget.FrameLayout; 45import android.widget.ImageButton; 46 47import com.android.systemui.R; 48import com.android.systemui.stackdivider.DividerSnapAlgorithm.SnapTarget; 49import com.android.systemui.statusbar.FlingAnimationUtils; 50 51import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW; 52import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW; 53 54/** 55 * Docked stack divider. 56 */ 57public class DividerView extends FrameLayout implements OnTouchListener, 58 OnComputeInternalInsetsListener { 59 60 private static final String TAG = "DividerView"; 61 62 private static final int TASK_POSITION_SAME = Integer.MAX_VALUE; 63 64 private ImageButton mHandle; 65 private View mBackground; 66 private int mStartX; 67 private int mStartY; 68 private int mStartPosition; 69 private int mDockSide; 70 private final int[] mTempInt2 = new int[2]; 71 72 private int mDividerInsets; 73 private int mDisplayWidth; 74 private int mDisplayHeight; 75 private int mDividerWindowWidth; 76 private int mDividerSize; 77 private int mTouchElevation; 78 79 private final Rect mDockedRect = new Rect(); 80 private final Rect mDockedTaskRect = new Rect(); 81 private final Rect mOtherTaskRect = new Rect(); 82 private final Rect mOtherRect = new Rect(); 83 private final Rect mDockedInsetRect = new Rect(); 84 private final Rect mOtherInsetRect = new Rect(); 85 private final Rect mLastResizeRect = new Rect(); 86 private final WindowManagerProxy mWindowManagerProxy = WindowManagerProxy.getInstance(); 87 private Interpolator mFastOutSlowInInterpolator; 88 private final Interpolator mTouchResponseInterpolator = 89 new PathInterpolator(0.3f, 0f, 0.1f, 1f); 90 private DividerWindowManager mWindowManager; 91 private VelocityTracker mVelocityTracker; 92 private FlingAnimationUtils mFlingAnimationUtils; 93 private DividerSnapAlgorithm mSnapAlgorithm; 94 private final Rect mStableInsets = new Rect(); 95 96 public DividerView(Context context) { 97 super(context); 98 } 99 100 public DividerView(Context context, @Nullable AttributeSet attrs) { 101 super(context, attrs); 102 } 103 104 public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 105 super(context, attrs, defStyleAttr); 106 } 107 108 public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, 109 int defStyleRes) { 110 super(context, attrs, defStyleAttr, defStyleRes); 111 } 112 113 @Override 114 protected void onFinishInflate() { 115 super.onFinishInflate(); 116 mHandle = (ImageButton) findViewById(R.id.docked_divider_handle); 117 mBackground = findViewById(R.id.docked_divider_background); 118 mHandle.setOnTouchListener(this); 119 mDividerWindowWidth = getResources().getDimensionPixelSize( 120 com.android.internal.R.dimen.docked_stack_divider_thickness); 121 mDividerInsets = getResources().getDimensionPixelSize( 122 com.android.internal.R.dimen.docked_stack_divider_insets); 123 mDividerSize = mDividerWindowWidth - 2 * mDividerInsets; 124 mTouchElevation = getResources().getDimensionPixelSize( 125 R.dimen.docked_stack_divider_lift_elevation); 126 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), 127 android.R.interpolator.fast_out_slow_in); 128 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f); 129 updateDisplayInfo(); 130 boolean landscape = getResources().getConfiguration().orientation 131 == Configuration.ORIENTATION_LANDSCAPE; 132 mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(), 133 landscape ? STYLE_HORIZONTAL_DOUBLE_ARROW : STYLE_VERTICAL_DOUBLE_ARROW)); 134 getViewTreeObserver().addOnComputeInternalInsetsListener(this); 135 } 136 137 @Override 138 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 139 mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(), 140 insets.getStableInsetRight(), insets.getStableInsetBottom()); 141 return super.onApplyWindowInsets(insets); 142 } 143 144 public void setWindowManager(DividerWindowManager windowManager) { 145 mWindowManager = windowManager; 146 } 147 148 public WindowManagerProxy getWindowManagerProxy() { 149 return mWindowManagerProxy; 150 } 151 152 public boolean startDragging() { 153 mDockSide = mWindowManagerProxy.getDockSide(); 154 mSnapAlgorithm = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils, mDisplayWidth, 155 mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets); 156 if (mDockSide != WindowManager.DOCKED_INVALID) { 157 mWindowManagerProxy.setResizing(true); 158 mWindowManager.setSlippery(false); 159 liftBackground(); 160 return true; 161 } else { 162 return false; 163 } 164 } 165 166 public void stopDragging(int position, float velocity) { 167 fling(position, velocity); 168 mWindowManager.setSlippery(true); 169 releaseBackground(); 170 } 171 172 public DividerSnapAlgorithm getSnapAlgorithm() { 173 return mSnapAlgorithm; 174 } 175 176 @Override 177 public boolean onTouch(View v, MotionEvent event) { 178 convertToScreenCoordinates(event); 179 final int action = event.getAction() & MotionEvent.ACTION_MASK; 180 switch (action) { 181 case MotionEvent.ACTION_DOWN: 182 mVelocityTracker = VelocityTracker.obtain(); 183 mVelocityTracker.addMovement(event); 184 mStartX = (int) event.getX(); 185 mStartY = (int) event.getY(); 186 getLocationOnScreen(mTempInt2); 187 boolean result = startDragging(); 188 if (isHorizontalDivision()) { 189 mStartPosition = mTempInt2[1] + mDividerInsets; 190 } else { 191 mStartPosition = mTempInt2[0] + mDividerInsets; 192 } 193 return result; 194 case MotionEvent.ACTION_MOVE: 195 mVelocityTracker.addMovement(event); 196 int x = (int) event.getX(); 197 int y = (int) event.getY(); 198 if (mDockSide != WindowManager.DOCKED_INVALID) { 199 int position = calculatePosition(x, y); 200 SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, 201 0 /* velocity */); 202 resizeStack(calculatePosition(x, y), snapTarget.position); 203 } 204 break; 205 case MotionEvent.ACTION_UP: 206 case MotionEvent.ACTION_CANCEL: 207 mVelocityTracker.addMovement(event); 208 209 x = (int) event.getRawX(); 210 y = (int) event.getRawY(); 211 212 mVelocityTracker.computeCurrentVelocity(1000); 213 int position = calculatePosition(x, y); 214 stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity() 215 : mVelocityTracker.getXVelocity()); 216 break; 217 } 218 return true; 219 } 220 221 private void convertToScreenCoordinates(MotionEvent event) { 222 event.setLocation(event.getRawX(), event.getRawY()); 223 } 224 225 private void fling(int position, float velocity) { 226 final SnapTarget snapTarget = mSnapAlgorithm.calculateSnapTarget(position, velocity); 227 228 ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position); 229 anim.addUpdateListener(new AnimatorUpdateListener() { 230 @Override 231 public void onAnimationUpdate(ValueAnimator animation) { 232 resizeStack((Integer) animation.getAnimatedValue(), 233 animation.getAnimatedFraction() == 1f 234 ? TASK_POSITION_SAME 235 : snapTarget.position); 236 } 237 }); 238 anim.addListener(new AnimatorListenerAdapter() { 239 @Override 240 public void onAnimationEnd(Animator animation) { 241 commitSnapFlags(snapTarget); 242 mWindowManagerProxy.setResizing(false); 243 mDockSide = WindowManager.DOCKED_INVALID; 244 } 245 }); 246 mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity); 247 anim.start(); 248 } 249 250 private void commitSnapFlags(SnapTarget target) { 251 if (target.flag == SnapTarget.FLAG_NONE) { 252 return; 253 } 254 boolean dismissOrMaximize; 255 if (target.flag == SnapTarget.FLAG_DISMISS_START) { 256 dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT 257 || mDockSide == WindowManager.DOCKED_TOP; 258 } else { 259 dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT 260 || mDockSide == WindowManager.DOCKED_BOTTOM; 261 } 262 if (dismissOrMaximize) { 263 mWindowManagerProxy.dismissDockedStack(); 264 } else { 265 mWindowManagerProxy.maximizeDockedStack(); 266 } 267 } 268 269 private void liftBackground() { 270 if (isHorizontalDivision()) { 271 mBackground.animate().scaleY(1.4f); 272 } else { 273 mBackground.animate().scaleX(1.4f); 274 } 275 mBackground.animate() 276 .setInterpolator(mTouchResponseInterpolator) 277 .setDuration(150) 278 .translationZ(mTouchElevation); 279 280 // Lift handle as well so it doesn't get behind the background, even though it doesn't 281 // cast shadow. 282 mHandle.animate() 283 .setInterpolator(mTouchResponseInterpolator) 284 .setDuration(150) 285 .translationZ(mTouchElevation); 286 } 287 288 private void releaseBackground() { 289 mBackground.animate() 290 .setInterpolator(mFastOutSlowInInterpolator) 291 .setDuration(200) 292 .translationZ(0) 293 .scaleX(1f) 294 .scaleY(1f); 295 mHandle.animate() 296 .setInterpolator(mFastOutSlowInInterpolator) 297 .setDuration(200) 298 .translationZ(0); 299 } 300 301 @Override 302 protected void onConfigurationChanged(Configuration newConfig) { 303 super.onConfigurationChanged(newConfig); 304 updateDisplayInfo(); 305 } 306 307 private void updateDisplayInfo() { 308 final DisplayManager displayManager = 309 (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); 310 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); 311 final DisplayInfo info = new DisplayInfo(); 312 display.getDisplayInfo(info); 313 mDisplayWidth = info.logicalWidth; 314 mDisplayHeight = info.logicalHeight; 315 } 316 317 private int calculatePosition(int touchX, int touchY) { 318 return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX); 319 } 320 321 public boolean isHorizontalDivision() { 322 return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; 323 } 324 325 private int calculateXPosition(int touchX) { 326 return mStartPosition + touchX - mStartX; 327 } 328 329 private int calculateYPosition(int touchY) { 330 return mStartPosition + touchY - mStartY; 331 } 332 333 public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) { 334 outRect.set(0, 0, mDisplayWidth, mDisplayHeight); 335 switch (dockSide) { 336 case WindowManager.DOCKED_LEFT: 337 outRect.right = position; 338 break; 339 case WindowManager.DOCKED_TOP: 340 outRect.bottom = position; 341 break; 342 case WindowManager.DOCKED_RIGHT: 343 outRect.left = position + mDividerWindowWidth - 2 * mDividerInsets; 344 break; 345 case WindowManager.DOCKED_BOTTOM: 346 outRect.top = position + mDividerWindowWidth - 2 * mDividerInsets; 347 break; 348 } 349 if (outRect.left > outRect.right) { 350 outRect.left = outRect.right; 351 } 352 if (outRect.top > outRect.bottom) { 353 outRect.top = outRect.bottom; 354 } 355 if (outRect.right < outRect.left) { 356 outRect.right = outRect.left; 357 } 358 if (outRect.bottom < outRect.top) { 359 outRect.bottom = outRect.top; 360 } 361 } 362 363 private int invertDockSide(int dockSide) { 364 switch (dockSide) { 365 case WindowManager.DOCKED_LEFT: 366 return WindowManager.DOCKED_RIGHT; 367 case WindowManager.DOCKED_TOP: 368 return WindowManager.DOCKED_BOTTOM; 369 case WindowManager.DOCKED_RIGHT: 370 return WindowManager.DOCKED_LEFT; 371 case WindowManager.DOCKED_BOTTOM: 372 return WindowManager.DOCKED_TOP; 373 default: 374 return WindowManager.DOCKED_INVALID; 375 } 376 } 377 378 private void alignTopLeft(Rect containingRect, Rect rect) { 379 int width = rect.width(); 380 int height = rect.height(); 381 rect.set(containingRect.left, containingRect.top, 382 containingRect.left + width, containingRect.top + height); 383 } 384 385 private void alignBottomRight(Rect containingRect, Rect rect) { 386 int width = rect.width(); 387 int height = rect.height(); 388 rect.set(containingRect.right - width, containingRect.bottom - height, 389 containingRect.right, containingRect.bottom); 390 } 391 392 public void resizeStack(int position, int taskPosition) { 393 calculateBoundsForPosition(position, mDockSide, mDockedRect); 394 395 if (mDockedRect.equals(mLastResizeRect)) { 396 return; 397 } 398 399 // Make sure shadows are updated 400 mBackground.invalidate(); 401 402 mLastResizeRect.set(mDockedRect); 403 if (taskPosition != TASK_POSITION_SAME) { 404 calculateBoundsForPosition(position, invertDockSide(mDockSide), mOtherRect); 405 calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect); 406 calculateBoundsForPosition(taskPosition, invertDockSide(mDockSide), mOtherTaskRect); 407 alignTopLeft(mDockedRect, mDockedTaskRect); 408 alignTopLeft(mOtherRect, mOtherTaskRect); 409 mDockedInsetRect.set(mDockedTaskRect); 410 mOtherInsetRect.set(mOtherTaskRect); 411 if (mDockSide == WindowManager.DOCKED_LEFT || mDockSide == WindowManager.DOCKED_TOP) { 412 alignTopLeft(mDockedRect, mDockedInsetRect); 413 alignBottomRight(mOtherRect, mOtherInsetRect); 414 } else { 415 alignBottomRight(mDockedRect, mDockedInsetRect); 416 alignTopLeft(mOtherRect, mOtherInsetRect); 417 } 418 mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedInsetRect, 419 mOtherTaskRect, mOtherInsetRect); 420 } else { 421 mWindowManagerProxy.resizeDockedStack(mDockedRect, null, null, null, null); 422 } 423 } 424 425 @Override 426 public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) { 427 inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); 428 inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), 429 mHandle.getBottom()); 430 inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(), 431 mBackground.getRight(), mBackground.getBottom(), Op.UNION); 432 } 433} 434