1 /* 2 * Copyright (C) 2019 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 package com.android.launcher3.touch; 18 19 import static android.view.Gravity.CENTER_VERTICAL; 20 import static android.view.Gravity.END; 21 import static android.view.Gravity.START; 22 import static android.view.Gravity.TOP; 23 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 24 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 25 26 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; 27 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; 28 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL; 29 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; 30 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; 31 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN; 32 33 import android.content.res.Resources; 34 import android.graphics.PointF; 35 import android.graphics.Rect; 36 import android.graphics.RectF; 37 import android.graphics.drawable.ShapeDrawable; 38 import android.util.FloatProperty; 39 import android.util.Pair; 40 import android.view.MotionEvent; 41 import android.view.Surface; 42 import android.view.VelocityTracker; 43 import android.view.View; 44 import android.view.accessibility.AccessibilityEvent; 45 import android.widget.FrameLayout; 46 import android.widget.LinearLayout; 47 48 import com.android.launcher3.DeviceProfile; 49 import com.android.launcher3.R; 50 import com.android.launcher3.Utilities; 51 import com.android.launcher3.util.SplitConfigurationOptions; 52 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; 53 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; 54 import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds; 55 import com.android.launcher3.views.BaseDragLayer; 56 57 import java.util.Collections; 58 import java.util.List; 59 60 public class LandscapePagedViewHandler implements PagedOrientationHandler { 61 62 @Override getPrimaryValue(T x, T y)63 public <T> T getPrimaryValue(T x, T y) { 64 return y; 65 } 66 67 @Override getSecondaryValue(T x, T y)68 public <T> T getSecondaryValue(T x, T y) { 69 return x; 70 } 71 72 @Override getPrimaryValue(int x, int y)73 public int getPrimaryValue(int x, int y) { 74 return y; 75 } 76 77 @Override getSecondaryValue(int x, int y)78 public int getSecondaryValue(int x, int y) { 79 return x; 80 } 81 82 @Override getPrimaryValue(float x, float y)83 public float getPrimaryValue(float x, float y) { 84 return y; 85 } 86 87 @Override getSecondaryValue(float x, float y)88 public float getSecondaryValue(float x, float y) { 89 return x; 90 } 91 92 @Override isLayoutNaturalToLauncher()93 public boolean isLayoutNaturalToLauncher() { 94 return false; 95 } 96 97 @Override adjustFloatingIconStartVelocity(PointF velocity)98 public void adjustFloatingIconStartVelocity(PointF velocity) { 99 float oldX = velocity.x; 100 float oldY = velocity.y; 101 velocity.set(-oldY, oldX); 102 } 103 104 @Override setPrimary(T target, Int2DAction<T> action, int param)105 public <T> void setPrimary(T target, Int2DAction<T> action, int param) { 106 action.call(target, 0, param); 107 } 108 109 @Override setPrimary(T target, Float2DAction<T> action, float param)110 public <T> void setPrimary(T target, Float2DAction<T> action, float param) { 111 action.call(target, 0, param); 112 } 113 114 @Override setSecondary(T target, Float2DAction<T> action, float param)115 public <T> void setSecondary(T target, Float2DAction<T> action, float param) { 116 action.call(target, param, 0); 117 } 118 119 @Override set(T target, Int2DAction<T> action, int primaryParam, int secondaryParam)120 public <T> void set(T target, Int2DAction<T> action, int primaryParam, 121 int secondaryParam) { 122 action.call(target, secondaryParam, primaryParam); 123 } 124 125 @Override getPrimaryDirection(MotionEvent event, int pointerIndex)126 public float getPrimaryDirection(MotionEvent event, int pointerIndex) { 127 return event.getY(pointerIndex); 128 } 129 130 @Override getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId)131 public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) { 132 return velocityTracker.getYVelocity(pointerId); 133 } 134 135 @Override getMeasuredSize(View view)136 public int getMeasuredSize(View view) { 137 return view.getMeasuredHeight(); 138 } 139 140 @Override getPrimarySize(View view)141 public int getPrimarySize(View view) { 142 return view.getHeight(); 143 } 144 145 @Override getPrimarySize(RectF rect)146 public float getPrimarySize(RectF rect) { 147 return rect.height(); 148 } 149 150 @Override getStart(RectF rect)151 public float getStart(RectF rect) { 152 return rect.top; 153 } 154 155 @Override getEnd(RectF rect)156 public float getEnd(RectF rect) { 157 return rect.bottom; 158 } 159 160 @Override getClearAllSidePadding(View view, boolean isRtl)161 public int getClearAllSidePadding(View view, boolean isRtl) { 162 return (isRtl ? view.getPaddingBottom() : - view.getPaddingTop()) / 2; 163 } 164 165 @Override getSecondaryDimension(View view)166 public int getSecondaryDimension(View view) { 167 return view.getWidth(); 168 } 169 170 @Override getPrimaryViewTranslate()171 public FloatProperty<View> getPrimaryViewTranslate() { 172 return VIEW_TRANSLATE_Y; 173 } 174 175 @Override getSecondaryViewTranslate()176 public FloatProperty<View> getSecondaryViewTranslate() { 177 return VIEW_TRANSLATE_X; 178 } 179 180 @Override getSplitTaskViewDismissDirection(@tagePosition int stagePosition, DeviceProfile dp)181 public int getSplitTaskViewDismissDirection(@StagePosition int stagePosition, 182 DeviceProfile dp) { 183 // Don't use device profile here because we know we're in fake landscape, only split option 184 // available is top/left 185 if (stagePosition == STAGE_POSITION_TOP_OR_LEFT) { 186 // Top (visually left) side 187 return SPLIT_TRANSLATE_PRIMARY_NEGATIVE; 188 } 189 throw new IllegalStateException("Invalid split stage position: " + stagePosition); 190 } 191 192 @Override getPrimaryScroll(View view)193 public int getPrimaryScroll(View view) { 194 return view.getScrollY(); 195 } 196 197 @Override getPrimaryScale(View view)198 public float getPrimaryScale(View view) { 199 return view.getScaleY(); 200 } 201 202 @Override setMaxScroll(AccessibilityEvent event, int maxScroll)203 public void setMaxScroll(AccessibilityEvent event, int maxScroll) { 204 event.setMaxScrollY(maxScroll); 205 } 206 207 @Override getRecentsRtlSetting(Resources resources)208 public boolean getRecentsRtlSetting(Resources resources) { 209 return !Utilities.isRtl(resources); 210 } 211 212 @Override getDegreesRotated()213 public float getDegreesRotated() { 214 return 90; 215 } 216 217 @Override getRotation()218 public int getRotation() { 219 return Surface.ROTATION_90; 220 } 221 222 @Override setPrimaryScale(View view, float scale)223 public void setPrimaryScale(View view, float scale) { 224 view.setScaleY(scale); 225 } 226 227 @Override setSecondaryScale(View view, float scale)228 public void setSecondaryScale(View view, float scale) { 229 view.setScaleX(scale); 230 } 231 232 @Override getChildStart(View view)233 public int getChildStart(View view) { 234 return view.getTop(); 235 } 236 237 @Override getCenterForPage(View view, Rect insets)238 public int getCenterForPage(View view, Rect insets) { 239 return (view.getPaddingLeft() + view.getMeasuredWidth() + insets.left 240 - insets.right - view.getPaddingRight()) / 2; 241 } 242 243 @Override getScrollOffsetStart(View view, Rect insets)244 public int getScrollOffsetStart(View view, Rect insets) { 245 return insets.top + view.getPaddingTop(); 246 } 247 248 @Override getScrollOffsetEnd(View view, Rect insets)249 public int getScrollOffsetEnd(View view, Rect insets) { 250 return view.getHeight() - view.getPaddingBottom() - insets.bottom; 251 } 252 getSecondaryTranslationDirectionFactor()253 public int getSecondaryTranslationDirectionFactor() { 254 return 1; 255 } 256 257 @Override getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile)258 public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) { 259 if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) { 260 return -1; 261 } else { 262 return 1; 263 } 264 } 265 266 @Override getTaskMenuX(float x, View thumbnailView, int overScroll, DeviceProfile deviceProfile)267 public float getTaskMenuX(float x, View thumbnailView, int overScroll, 268 DeviceProfile deviceProfile) { 269 return thumbnailView.getMeasuredWidth() + x; 270 } 271 272 @Override getTaskMenuY(float y, View thumbnailView, int overScroll)273 public float getTaskMenuY(float y, View thumbnailView, int overScroll) { 274 return y + overScroll + 275 (thumbnailView.getMeasuredHeight() - thumbnailView.getMeasuredWidth()) / 2f; 276 } 277 278 @Override getTaskMenuWidth(View view, DeviceProfile deviceProfile)279 public int getTaskMenuWidth(View view, DeviceProfile deviceProfile) { 280 return view.getMeasuredWidth(); 281 } 282 283 @Override setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile, LinearLayout taskMenuLayout, int dividerSpacing, ShapeDrawable dividerDrawable)284 public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile, 285 LinearLayout taskMenuLayout, int dividerSpacing, 286 ShapeDrawable dividerDrawable) { 287 taskMenuLayout.setOrientation(LinearLayout.VERTICAL); 288 dividerDrawable.setIntrinsicHeight(dividerSpacing); 289 taskMenuLayout.setDividerDrawable(dividerDrawable); 290 } 291 292 @Override setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp, LinearLayout viewGroup, DeviceProfile deviceProfile)293 public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp, 294 LinearLayout viewGroup, DeviceProfile deviceProfile) { 295 // Phone fake landscape 296 viewGroup.setOrientation(LinearLayout.HORIZONTAL); 297 lp.width = MATCH_PARENT; 298 lp.height = WRAP_CONTENT; 299 } 300 301 @Override setTaskMenuAroundTaskView(LinearLayout taskView, float margin)302 public void setTaskMenuAroundTaskView(LinearLayout taskView, float margin) { 303 BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskView.getLayoutParams(); 304 lp.topMargin += margin; 305 } 306 307 @Override getAdditionalInsetForTaskMenu(float margin)308 public PointF getAdditionalInsetForTaskMenu(float margin) { 309 return new PointF(margin, 0); 310 } 311 312 @Override setDwbLayoutParamsAndGetTranslations(int taskViewWidth, int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile, View[] thumbnailViews, int desiredTaskId, View banner)313 public Pair<Float, Float> setDwbLayoutParamsAndGetTranslations(int taskViewWidth, 314 int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile, 315 View[] thumbnailViews, int desiredTaskId, View banner) { 316 float translationX = 0; 317 float translationY = 0; 318 FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams(); 319 banner.setPivotX(0); 320 banner.setPivotY(0); 321 banner.setRotation(getDegreesRotated()); 322 translationX = banner.getHeight(); 323 FrameLayout.LayoutParams snapshotParams = 324 (FrameLayout.LayoutParams) thumbnailViews[0] 325 .getLayoutParams(); 326 bannerParams.gravity = TOP | START; 327 if (splitBounds == null) { 328 // Single, fullscreen case 329 bannerParams.width = taskViewHeight - snapshotParams.topMargin; 330 return new Pair<>(translationX, Integer.valueOf(snapshotParams.topMargin).floatValue()); 331 } 332 333 // Set correct width 334 if (desiredTaskId == splitBounds.leftTopTaskId) { 335 bannerParams.width = thumbnailViews[0].getMeasuredHeight(); 336 } else { 337 bannerParams.width = thumbnailViews[1].getMeasuredHeight(); 338 } 339 340 // Set translations 341 if (desiredTaskId == splitBounds.rightBottomTaskId) { 342 translationY = (snapshotParams.topMargin + taskViewHeight) 343 * (splitBounds.leftTaskPercent) + 344 (taskViewHeight * splitBounds.dividerWidthPercent); 345 } 346 if (desiredTaskId == splitBounds.leftTopTaskId) { 347 translationY = snapshotParams.topMargin; 348 } 349 return new Pair<>(translationX, translationY); 350 } 351 352 /* ---------- The following are only used by TaskViewTouchHandler. ---------- */ 353 354 @Override getUpDownSwipeDirection()355 public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() { 356 return HORIZONTAL; 357 } 358 359 @Override getUpDirection(boolean isRtl)360 public int getUpDirection(boolean isRtl) { 361 return isRtl ? SingleAxisSwipeDetector.DIRECTION_NEGATIVE 362 : SingleAxisSwipeDetector.DIRECTION_POSITIVE; 363 } 364 365 @Override isGoingUp(float displacement, boolean isRtl)366 public boolean isGoingUp(float displacement, boolean isRtl) { 367 return isRtl ? displacement < 0 : displacement > 0; 368 } 369 370 @Override getTaskDragDisplacementFactor(boolean isRtl)371 public int getTaskDragDisplacementFactor(boolean isRtl) { 372 return isRtl ? 1 : -1; 373 } 374 375 /* -------------------- */ 376 377 @Override getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild)378 public ChildBounds getChildBounds(View child, int childStart, int pageCenter, 379 boolean layoutChild) { 380 final int childHeight = child.getMeasuredHeight(); 381 final int childBottom = childStart + childHeight; 382 final int childWidth = child.getMeasuredWidth(); 383 final int childLeft = pageCenter - childWidth/ 2; 384 if (layoutChild) { 385 child.layout(childLeft, childStart, childLeft + childWidth, childBottom); 386 } 387 return new ChildBounds(childHeight, childWidth, childBottom, childLeft); 388 } 389 390 @SuppressWarnings("SuspiciousNameCombination") 391 @Override getDistanceToBottomOfRect(DeviceProfile dp, Rect rect)392 public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) { 393 return rect.left; 394 } 395 396 @Override getSplitPositionOptions(DeviceProfile dp)397 public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) { 398 // Add "left" side of phone which is actually the top 399 return Collections.singletonList(new SplitPositionOption( 400 R.drawable.ic_split_left, R.string.split_screen_position_left, 401 STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN)); 402 } 403 404 @Override getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp, @StagePosition int stagePosition, Rect out)405 public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp, 406 @StagePosition int stagePosition, Rect out) { 407 // In fake land/seascape, the placeholder always needs to go to the "top" of the device, 408 // which is the same bounds as 0 rotation. 409 int width = dp.widthPx; 410 out.set(0, 0, width, placeholderHeight); 411 } 412 413 @Override getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp, @StagePosition int stagePosition, Rect out1, Rect out2)414 public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp, 415 @StagePosition int stagePosition, Rect out1, Rect out2) { 416 // In fake land/seascape, the window bounds are always top and bottom half 417 int screenHeight = dp.heightPx; 418 int screenWidth = dp.widthPx; 419 out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize); 420 out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight); 421 } 422 423 @Override setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, StagedSplitBounds splitInfo, int desiredStagePosition)424 public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, 425 StagedSplitBounds splitInfo, int desiredStagePosition) { 426 float diff; 427 float horizontalDividerDiff = splitInfo.visualDividerBounds.width() / 2f; 428 if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) { 429 diff = outRect.height() * (1f - splitInfo.leftTaskPercent) + horizontalDividerDiff; 430 outRect.bottom -= diff; 431 } else { 432 diff = outRect.height() * splitInfo.leftTaskPercent + horizontalDividerDiff; 433 outRect.top += diff; 434 } 435 } 436 437 @Override measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot, int parentWidth, int parentHeight, StagedSplitBounds splitBoundsConfig, DeviceProfile dp)438 public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot, 439 int parentWidth, int parentHeight, 440 StagedSplitBounds splitBoundsConfig, DeviceProfile dp) { 441 int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx; 442 int totalThumbnailHeight = parentHeight - spaceAboveSnapshot; 443 int dividerBar = splitBoundsConfig.visualDividerBounds.width(); 444 int primarySnapshotHeight; 445 int primarySnapshotWidth; 446 int secondarySnapshotHeight; 447 int secondarySnapshotWidth; 448 449 float taskPercent = splitBoundsConfig.appsStackedVertically ? 450 splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent; 451 primarySnapshotWidth = parentWidth; 452 primarySnapshotHeight = (int) (totalThumbnailHeight * taskPercent); 453 454 secondarySnapshotWidth = parentWidth; 455 secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar; 456 secondarySnapshot.setTranslationY(primarySnapshotHeight + spaceAboveSnapshot + dividerBar); 457 primarySnapshot.measure( 458 View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY), 459 View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY)); 460 secondarySnapshot.measure( 461 View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY), 462 View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight, 463 View.MeasureSpec.EXACTLY)); 464 } 465 466 @Override setIconAndSnapshotParams(View iconView, int taskIconMargin, int taskIconHeight, FrameLayout.LayoutParams snapshotParams, boolean isRtl)467 public void setIconAndSnapshotParams(View iconView, int taskIconMargin, int taskIconHeight, 468 FrameLayout.LayoutParams snapshotParams, boolean isRtl) { 469 FrameLayout.LayoutParams iconParams = 470 (FrameLayout.LayoutParams) iconView.getLayoutParams(); 471 iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL; 472 iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2; 473 iconParams.leftMargin = 0; 474 iconParams.topMargin = snapshotParams.topMargin / 2; 475 } 476 477 @Override setSplitIconParams(View primaryIconView, View secondaryIconView, int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight, boolean isRtl, DeviceProfile deviceProfile, StagedSplitBounds splitConfig)478 public void setSplitIconParams(View primaryIconView, View secondaryIconView, 479 int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight, 480 boolean isRtl, DeviceProfile deviceProfile, StagedSplitBounds splitConfig) { 481 FrameLayout.LayoutParams primaryIconParams = 482 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams(); 483 FrameLayout.LayoutParams secondaryIconParams = 484 new FrameLayout.LayoutParams(primaryIconParams); 485 int dividerBar = (splitConfig.appsStackedVertically ? 486 splitConfig.visualDividerBounds.height() : 487 splitConfig.visualDividerBounds.width()); 488 489 primaryIconParams.gravity = (isRtl ? START : END) | TOP; 490 primaryIconView.setTranslationY(primarySnapshotHeight - primaryIconView.getHeight() / 2f); 491 primaryIconView.setTranslationX(0); 492 493 secondaryIconParams.gravity = (isRtl ? START : END) | TOP; 494 secondaryIconView.setTranslationY(primarySnapshotHeight + taskIconHeight + dividerBar); 495 secondaryIconView.setTranslationX(0); 496 primaryIconView.setLayoutParams(primaryIconParams); 497 secondaryIconView.setLayoutParams(secondaryIconParams); 498 } 499 500 @Override getDefaultSplitPosition(DeviceProfile deviceProfile)501 public int getDefaultSplitPosition(DeviceProfile deviceProfile) { 502 throw new IllegalStateException("Default position not available in fake landscape"); 503 } 504 505 @Override getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary, DeviceProfile deviceProfile)506 public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary, 507 FloatProperty secondary, DeviceProfile deviceProfile) { 508 return new Pair<>(primary, secondary); 509 } 510 } 511