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.BOTTOM; 20 import static android.view.Gravity.CENTER_VERTICAL; 21 import static android.view.Gravity.END; 22 import static android.view.Gravity.LEFT; 23 import static android.view.Gravity.START; 24 import static android.view.Gravity.TOP; 25 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 26 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 27 28 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; 29 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; 30 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL; 31 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; 32 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; 33 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; 34 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN; 35 36 import android.content.res.Resources; 37 import android.graphics.PointF; 38 import android.graphics.Rect; 39 import android.graphics.RectF; 40 import android.graphics.drawable.ShapeDrawable; 41 import android.util.FloatProperty; 42 import android.util.Pair; 43 import android.view.MotionEvent; 44 import android.view.Surface; 45 import android.view.VelocityTracker; 46 import android.view.View; 47 import android.view.accessibility.AccessibilityEvent; 48 import android.widget.FrameLayout; 49 import android.widget.LinearLayout; 50 51 import com.android.launcher3.DeviceProfile; 52 import com.android.launcher3.R; 53 import com.android.launcher3.Utilities; 54 import com.android.launcher3.util.SplitConfigurationOptions; 55 import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds; 56 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; 57 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; 58 import com.android.launcher3.views.BaseDragLayer; 59 60 import java.util.Collections; 61 import java.util.List; 62 63 public class LandscapePagedViewHandler implements PagedOrientationHandler { 64 65 @Override getPrimaryValue(T x, T y)66 public <T> T getPrimaryValue(T x, T y) { 67 return y; 68 } 69 70 @Override getSecondaryValue(T x, T y)71 public <T> T getSecondaryValue(T x, T y) { 72 return x; 73 } 74 75 @Override getPrimaryValue(int x, int y)76 public int getPrimaryValue(int x, int y) { 77 return y; 78 } 79 80 @Override getSecondaryValue(int x, int y)81 public int getSecondaryValue(int x, int y) { 82 return x; 83 } 84 85 @Override getPrimaryValue(float x, float y)86 public float getPrimaryValue(float x, float y) { 87 return y; 88 } 89 90 @Override getSecondaryValue(float x, float y)91 public float getSecondaryValue(float x, float y) { 92 return x; 93 } 94 95 @Override isLayoutNaturalToLauncher()96 public boolean isLayoutNaturalToLauncher() { 97 return false; 98 } 99 100 @Override adjustFloatingIconStartVelocity(PointF velocity)101 public void adjustFloatingIconStartVelocity(PointF velocity) { 102 float oldX = velocity.x; 103 float oldY = velocity.y; 104 velocity.set(-oldY, oldX); 105 } 106 107 @Override fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile)108 public void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile) { 109 // We don't need to check the "top" value here because the startRect is in the orientation 110 // of the app, not of the fixed portrait launcher. 111 if (outStartRect.left > deviceProfile.heightPx) { 112 outStartRect.offsetTo(0, outStartRect.top); 113 } else if (outStartRect.left < -deviceProfile.heightPx) { 114 outStartRect.offsetTo(0, outStartRect.top); 115 } 116 } 117 118 @Override setPrimary(T target, Int2DAction<T> action, int param)119 public <T> void setPrimary(T target, Int2DAction<T> action, int param) { 120 action.call(target, 0, param); 121 } 122 123 @Override setPrimary(T target, Float2DAction<T> action, float param)124 public <T> void setPrimary(T target, Float2DAction<T> action, float param) { 125 action.call(target, 0, param); 126 } 127 128 @Override setSecondary(T target, Float2DAction<T> action, float param)129 public <T> void setSecondary(T target, Float2DAction<T> action, float param) { 130 action.call(target, param, 0); 131 } 132 133 @Override set(T target, Int2DAction<T> action, int primaryParam, int secondaryParam)134 public <T> void set(T target, Int2DAction<T> action, int primaryParam, 135 int secondaryParam) { 136 action.call(target, secondaryParam, primaryParam); 137 } 138 139 @Override getPrimaryDirection(MotionEvent event, int pointerIndex)140 public float getPrimaryDirection(MotionEvent event, int pointerIndex) { 141 return event.getY(pointerIndex); 142 } 143 144 @Override getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId)145 public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) { 146 return velocityTracker.getYVelocity(pointerId); 147 } 148 149 @Override getMeasuredSize(View view)150 public int getMeasuredSize(View view) { 151 return view.getMeasuredHeight(); 152 } 153 154 @Override getPrimarySize(View view)155 public int getPrimarySize(View view) { 156 return view.getHeight(); 157 } 158 159 @Override getPrimarySize(RectF rect)160 public float getPrimarySize(RectF rect) { 161 return rect.height(); 162 } 163 164 @Override getStart(RectF rect)165 public float getStart(RectF rect) { 166 return rect.top; 167 } 168 169 @Override getEnd(RectF rect)170 public float getEnd(RectF rect) { 171 return rect.bottom; 172 } 173 174 @Override getClearAllSidePadding(View view, boolean isRtl)175 public int getClearAllSidePadding(View view, boolean isRtl) { 176 return (isRtl ? view.getPaddingBottom() : - view.getPaddingTop()) / 2; 177 } 178 179 @Override getSecondaryDimension(View view)180 public int getSecondaryDimension(View view) { 181 return view.getWidth(); 182 } 183 184 @Override getPrimaryViewTranslate()185 public FloatProperty<View> getPrimaryViewTranslate() { 186 return VIEW_TRANSLATE_Y; 187 } 188 189 @Override getSecondaryViewTranslate()190 public FloatProperty<View> getSecondaryViewTranslate() { 191 return VIEW_TRANSLATE_X; 192 } 193 194 @Override getPrimaryScroll(View view)195 public int getPrimaryScroll(View view) { 196 return view.getScrollY(); 197 } 198 199 @Override getPrimaryScale(View view)200 public float getPrimaryScale(View view) { 201 return view.getScaleY(); 202 } 203 204 @Override setMaxScroll(AccessibilityEvent event, int maxScroll)205 public void setMaxScroll(AccessibilityEvent event, int maxScroll) { 206 event.setMaxScrollY(maxScroll); 207 } 208 209 @Override getRecentsRtlSetting(Resources resources)210 public boolean getRecentsRtlSetting(Resources resources) { 211 return !Utilities.isRtl(resources); 212 } 213 214 @Override getDegreesRotated()215 public float getDegreesRotated() { 216 return 90; 217 } 218 219 @Override getRotation()220 public int getRotation() { 221 return Surface.ROTATION_90; 222 } 223 224 @Override setPrimaryScale(View view, float scale)225 public void setPrimaryScale(View view, float scale) { 226 view.setScaleY(scale); 227 } 228 229 @Override setSecondaryScale(View view, float scale)230 public void setSecondaryScale(View view, float scale) { 231 view.setScaleX(scale); 232 } 233 234 @Override getChildStart(View view)235 public int getChildStart(View view) { 236 return view.getTop(); 237 } 238 239 @Override getCenterForPage(View view, Rect insets)240 public int getCenterForPage(View view, Rect insets) { 241 return (view.getPaddingLeft() + view.getMeasuredWidth() + insets.left 242 - insets.right - view.getPaddingRight()) / 2; 243 } 244 245 @Override getScrollOffsetStart(View view, Rect insets)246 public int getScrollOffsetStart(View view, Rect insets) { 247 return insets.top + view.getPaddingTop(); 248 } 249 250 @Override getScrollOffsetEnd(View view, Rect insets)251 public int getScrollOffsetEnd(View view, Rect insets) { 252 return view.getHeight() - view.getPaddingBottom() - insets.bottom; 253 } 254 getSecondaryTranslationDirectionFactor()255 public int getSecondaryTranslationDirectionFactor() { 256 return 1; 257 } 258 259 @Override getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile)260 public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) { 261 if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) { 262 return -1; 263 } else { 264 return 1; 265 } 266 } 267 268 @Override getTaskMenuX(float x, View thumbnailView, DeviceProfile deviceProfile, float taskInsetMargin)269 public float getTaskMenuX(float x, View thumbnailView, 270 DeviceProfile deviceProfile, float taskInsetMargin) { 271 return thumbnailView.getMeasuredWidth() + x - taskInsetMargin; 272 } 273 274 @Override getTaskMenuY(float y, View thumbnailView, int stagePosition, View taskMenuView, float taskInsetMargin)275 public float getTaskMenuY(float y, View thumbnailView, int stagePosition, 276 View taskMenuView, float taskInsetMargin) { 277 BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskMenuView.getLayoutParams(); 278 int taskMenuWidth = lp.width; 279 if (stagePosition == STAGE_POSITION_UNDEFINED) { 280 return y + taskInsetMargin 281 + (thumbnailView.getMeasuredHeight() - taskMenuWidth) / 2f; 282 } else { 283 return y + taskInsetMargin; 284 } 285 } 286 287 @Override getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile, @StagePosition int stagePosition)288 public int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile, 289 @StagePosition int stagePosition) { 290 if (stagePosition == SplitConfigurationOptions.STAGE_POSITION_UNDEFINED) { 291 return thumbnailView.getMeasuredWidth(); 292 } else { 293 return thumbnailView.getMeasuredHeight(); 294 } 295 } 296 297 @Override setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile, LinearLayout taskMenuLayout, int dividerSpacing, ShapeDrawable dividerDrawable)298 public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile, 299 LinearLayout taskMenuLayout, int dividerSpacing, 300 ShapeDrawable dividerDrawable) { 301 taskMenuLayout.setOrientation(LinearLayout.VERTICAL); 302 dividerDrawable.setIntrinsicHeight(dividerSpacing); 303 taskMenuLayout.setDividerDrawable(dividerDrawable); 304 } 305 306 @Override setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp, LinearLayout viewGroup, DeviceProfile deviceProfile)307 public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp, 308 LinearLayout viewGroup, DeviceProfile deviceProfile) { 309 // Phone fake landscape 310 viewGroup.setOrientation(LinearLayout.HORIZONTAL); 311 lp.width = MATCH_PARENT; 312 lp.height = WRAP_CONTENT; 313 } 314 315 @Override getDwbLayoutTranslations(int taskViewWidth, int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile, View[] thumbnailViews, int desiredTaskId, View banner)316 public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth, 317 int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile, 318 View[] thumbnailViews, int desiredTaskId, View banner) { 319 boolean isRtl = banner.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 320 float translationX = 0; 321 float translationY = 0; 322 FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams(); 323 banner.setPivotX(0); 324 banner.setPivotY(0); 325 banner.setRotation(getDegreesRotated()); 326 translationX = banner.getHeight(); 327 FrameLayout.LayoutParams snapshotParams = 328 (FrameLayout.LayoutParams) thumbnailViews[0] 329 .getLayoutParams(); 330 bannerParams.gravity = TOP | (isRtl ? END : START); 331 if (splitBounds == null) { 332 // Single, fullscreen case 333 bannerParams.width = taskViewHeight - snapshotParams.topMargin; 334 return new Pair<>(translationX, Integer.valueOf(snapshotParams.topMargin).floatValue()); 335 } 336 337 // Set correct width 338 if (desiredTaskId == splitBounds.leftTopTaskId) { 339 bannerParams.width = thumbnailViews[0].getMeasuredHeight(); 340 } else { 341 bannerParams.width = thumbnailViews[1].getMeasuredHeight(); 342 } 343 344 // Set translations 345 if (desiredTaskId == splitBounds.rightBottomTaskId) { 346 float topLeftTaskPlusDividerPercent = splitBounds.appsStackedVertically 347 ? (splitBounds.topTaskPercent + splitBounds.dividerHeightPercent) 348 : (splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent); 349 translationY = snapshotParams.topMargin 350 + ((taskViewHeight - snapshotParams.topMargin) * topLeftTaskPlusDividerPercent); 351 } 352 if (desiredTaskId == splitBounds.leftTopTaskId) { 353 translationY = snapshotParams.topMargin; 354 } 355 return new Pair<>(translationX, translationY); 356 } 357 358 /* ---------- The following are only used by TaskViewTouchHandler. ---------- */ 359 360 @Override getUpDownSwipeDirection()361 public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() { 362 return HORIZONTAL; 363 } 364 365 @Override getUpDirection(boolean isRtl)366 public int getUpDirection(boolean isRtl) { 367 return isRtl ? SingleAxisSwipeDetector.DIRECTION_NEGATIVE 368 : SingleAxisSwipeDetector.DIRECTION_POSITIVE; 369 } 370 371 @Override isGoingUp(float displacement, boolean isRtl)372 public boolean isGoingUp(float displacement, boolean isRtl) { 373 return isRtl ? displacement < 0 : displacement > 0; 374 } 375 376 @Override getTaskDragDisplacementFactor(boolean isRtl)377 public int getTaskDragDisplacementFactor(boolean isRtl) { 378 return isRtl ? 1 : -1; 379 } 380 381 /* -------------------- */ 382 383 @Override getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild)384 public ChildBounds getChildBounds(View child, int childStart, int pageCenter, 385 boolean layoutChild) { 386 final int childHeight = child.getMeasuredHeight(); 387 final int childBottom = childStart + childHeight; 388 final int childWidth = child.getMeasuredWidth(); 389 final int childLeft = pageCenter - childWidth/ 2; 390 if (layoutChild) { 391 child.layout(childLeft, childStart, childLeft + childWidth, childBottom); 392 } 393 return new ChildBounds(childHeight, childWidth, childBottom, childLeft); 394 } 395 396 @SuppressWarnings("SuspiciousNameCombination") 397 @Override getDistanceToBottomOfRect(DeviceProfile dp, Rect rect)398 public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) { 399 return rect.left; 400 } 401 402 @Override getSplitPositionOptions(DeviceProfile dp)403 public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) { 404 // Add "left" side of phone which is actually the top 405 return Collections.singletonList(new SplitPositionOption( 406 R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen, 407 STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN)); 408 } 409 410 @Override getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset, DeviceProfile dp, @StagePosition int stagePosition, Rect out)411 public void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset, 412 DeviceProfile dp, @StagePosition int stagePosition, Rect out) { 413 // In fake land/seascape, the placeholder always needs to go to the "top" of the device, 414 // which is the same bounds as 0 rotation. 415 int width = dp.widthPx; 416 int insetSizeAdjustment = getPlaceholderSizeAdjustment(dp); 417 out.set(0, 0, width, placeholderHeight + insetSizeAdjustment); 418 out.inset(placeholderInset, 0); 419 420 // Adjust the top to account for content off screen. This will help to animate the view in 421 // with rounded corners. 422 int screenWidth = dp.widthPx; 423 int screenHeight = dp.heightPx; 424 int totalHeight = (int) (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset) 425 / screenWidth); 426 out.top -= (totalHeight - placeholderHeight); 427 } 428 429 @Override updateSplitIconParams(View out, float onScreenRectCenterX, float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY, int drawableWidth, int drawableHeight, DeviceProfile dp, @StagePosition int stagePosition)430 public void updateSplitIconParams(View out, float onScreenRectCenterX, 431 float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY, 432 int drawableWidth, int drawableHeight, DeviceProfile dp, 433 @StagePosition int stagePosition) { 434 float insetAdjustment = getPlaceholderSizeAdjustment(dp) / 2f; 435 out.setX(onScreenRectCenterX / fullscreenScaleX 436 - 1.0f * drawableWidth / 2); 437 out.setY((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY 438 - 1.0f * drawableHeight / 2); 439 } 440 441 /** 442 * The split placeholder comes with a default inset to buffer the icon from the top of the 443 * screen. But if the device already has a large inset (from cutouts etc), use that instead. 444 */ getPlaceholderSizeAdjustment(DeviceProfile dp)445 private int getPlaceholderSizeAdjustment(DeviceProfile dp) { 446 return Math.max(dp.getInsets().top - dp.splitPlaceholderInset, 0); 447 } 448 449 @Override setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight, int splitInstructionsWidth)450 public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight, 451 int splitInstructionsWidth) { 452 out.setPivotX(0); 453 out.setPivotY(splitInstructionsHeight); 454 out.setRotation(getDegreesRotated()); 455 int distanceToEdge = out.getResources().getDimensionPixelSize( 456 R.dimen.split_instructions_bottom_margin_phone_landscape); 457 // Adjust for any insets on the left edge 458 int insetCorrectionX = dp.getInsets().left; 459 // Center the view in case of unbalanced insets on top or bottom of screen 460 int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2; 461 out.setTranslationX(distanceToEdge - insetCorrectionX); 462 out.setTranslationY(((-splitInstructionsHeight - splitInstructionsWidth) / 2f) 463 + insetCorrectionY); 464 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams(); 465 // Setting gravity to LEFT instead of the lint-recommended START because we always want this 466 // view to be screen-left when phone is in landscape, regardless of the RtL setting. 467 lp.gravity = LEFT | CENTER_VERTICAL; 468 out.setLayoutParams(lp); 469 } 470 471 @Override getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp, @StagePosition int stagePosition, Rect out1, Rect out2)472 public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp, 473 @StagePosition int stagePosition, Rect out1, Rect out2) { 474 // In fake land/seascape, the window bounds are always top and bottom half 475 int screenHeight = dp.heightPx; 476 int screenWidth = dp.widthPx; 477 out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize); 478 out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight); 479 } 480 481 @Override setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo, int desiredStagePosition)482 public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, 483 SplitBounds splitInfo, int desiredStagePosition) { 484 float topLeftTaskPercent = splitInfo.appsStackedVertically 485 ? splitInfo.topTaskPercent 486 : splitInfo.leftTaskPercent; 487 float dividerBarPercent = splitInfo.appsStackedVertically 488 ? splitInfo.dividerHeightPercent 489 : splitInfo.dividerWidthPercent; 490 491 if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) { 492 outRect.bottom = outRect.top + (int) (outRect.height() * topLeftTaskPercent); 493 } else { 494 outRect.top += (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent)); 495 } 496 } 497 498 @Override measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot, int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl)499 public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot, 500 int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, 501 DeviceProfile dp, boolean isRtl) { 502 int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx; 503 int totalThumbnailHeight = parentHeight - spaceAboveSnapshot; 504 int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically 505 ? splitBoundsConfig.dividerHeightPercent 506 : splitBoundsConfig.dividerWidthPercent)); 507 int primarySnapshotHeight; 508 int primarySnapshotWidth; 509 int secondarySnapshotHeight; 510 int secondarySnapshotWidth; 511 512 float taskPercent = splitBoundsConfig.appsStackedVertically ? 513 splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent; 514 primarySnapshotWidth = parentWidth; 515 primarySnapshotHeight = (int) (totalThumbnailHeight * taskPercent); 516 517 secondarySnapshotWidth = parentWidth; 518 secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar; 519 secondarySnapshot.setTranslationY(primarySnapshotHeight + spaceAboveSnapshot + dividerBar); 520 primarySnapshot.measure( 521 View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY), 522 View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY)); 523 secondarySnapshot.measure( 524 View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY), 525 View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight, 526 View.MeasureSpec.EXACTLY)); 527 } 528 529 @Override setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl)530 public void setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin, 531 int taskIconHeight, int thumbnailTopMargin, boolean isRtl) { 532 iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL; 533 iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2; 534 iconParams.leftMargin = 0; 535 iconParams.topMargin = thumbnailTopMargin / 2; 536 iconParams.bottomMargin = 0; 537 } 538 539 @Override setSplitIconParams(View primaryIconView, View secondaryIconView, int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight, int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl, DeviceProfile deviceProfile, SplitBounds splitConfig)540 public void setSplitIconParams(View primaryIconView, View secondaryIconView, 541 int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight, 542 int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl, 543 DeviceProfile deviceProfile, SplitBounds splitConfig) { 544 FrameLayout.LayoutParams primaryIconParams = 545 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams(); 546 FrameLayout.LayoutParams secondaryIconParams = 547 new FrameLayout.LayoutParams(primaryIconParams); 548 549 // We calculate the "midpoint" of the thumbnail area, and place the icons there. 550 // This is the place where the thumbnail area splits by default, in a near-50/50 split. 551 // It is usually not exactly 50/50, due to insets/screen cutouts. 552 int fullscreenInsetThickness = deviceProfile.getInsets().top 553 - deviceProfile.getInsets().bottom; 554 int fullscreenMidpointFromBottom = ((deviceProfile.heightPx - fullscreenInsetThickness) 555 / 2); 556 float midpointFromBottomPct = (float) fullscreenMidpointFromBottom / deviceProfile.heightPx; 557 float insetPct = (float) fullscreenInsetThickness / deviceProfile.heightPx; 558 int spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx; 559 int overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots; 560 int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness * midpointFromBottomPct); 561 int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct); 562 563 primaryIconParams.gravity = BOTTOM | (isRtl ? START : END); 564 secondaryIconParams.gravity = BOTTOM | (isRtl ? START : END); 565 primaryIconView.setTranslationX(0); 566 secondaryIconView.setTranslationX(0); 567 if (splitConfig.initiatedFromSeascape) { 568 // if the split was initiated from seascape, 569 // the task on the right (secondary) is slightly larger 570 primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset); 571 secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset 572 + taskIconHeight); 573 } else { 574 // if not, 575 // the task on the left (primary) is slightly larger 576 primaryIconView.setTranslationY(-bottomToMidpointOffset); 577 secondaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight); 578 } 579 580 primaryIconView.setLayoutParams(primaryIconParams); 581 secondaryIconView.setLayoutParams(secondaryIconParams); 582 } 583 584 @Override getDefaultSplitPosition(DeviceProfile deviceProfile)585 public int getDefaultSplitPosition(DeviceProfile deviceProfile) { 586 throw new IllegalStateException("Default position not available in fake landscape"); 587 } 588 589 @Override getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary, DeviceProfile deviceProfile)590 public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary, 591 FloatProperty secondary, DeviceProfile deviceProfile) { 592 return new Pair<>(primary, secondary); 593 } 594 595 @Override getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect, @StagePosition int stagePosition, DeviceProfile dp)596 public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect, 597 @StagePosition int stagePosition, DeviceProfile dp) { 598 float currentTranslationY = floatingTask.getTranslationY(); 599 return currentTranslationY - onScreenRect.height(); 600 } 601 602 @Override setFloatingTaskPrimaryTranslation(View floatingTask, float translation, DeviceProfile dp)603 public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation, 604 DeviceProfile dp) { 605 floatingTask.setTranslationY(translation); 606 } 607 608 @Override getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp)609 public Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) { 610 return floatingTask.getTranslationY(); 611 } 612 } 613