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_HORIZONTAL;
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.VERTICAL;
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.Matrix;
35 import android.graphics.PointF;
36 import android.graphics.Rect;
37 import android.graphics.RectF;
38 import android.graphics.drawable.ShapeDrawable;
39 import android.util.FloatProperty;
40 import android.util.Pair;
41 import android.view.MotionEvent;
42 import android.view.Surface;
43 import android.view.VelocityTracker;
44 import android.view.View;
45 import android.view.accessibility.AccessibilityEvent;
46 import android.widget.FrameLayout;
47 import android.widget.LinearLayout;
48 
49 import com.android.launcher3.DeviceProfile;
50 import com.android.launcher3.R;
51 import com.android.launcher3.Utilities;
52 import com.android.launcher3.util.SplitConfigurationOptions;
53 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
54 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
55 import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
56 import com.android.launcher3.views.BaseDragLayer;
57 
58 import java.util.ArrayList;
59 import java.util.List;
60 
61 public class PortraitPagedViewHandler implements PagedOrientationHandler {
62 
63     private final Matrix mTmpMatrix = new Matrix();
64     private final RectF mTmpRectF = new RectF();
65 
66     @Override
getPrimaryValue(T x, T y)67     public <T> T getPrimaryValue(T x, T y) {
68         return x;
69     }
70 
71     @Override
getSecondaryValue(T x, T y)72     public <T> T getSecondaryValue(T x, T y) {
73         return y;
74     }
75 
76     @Override
getPrimaryValue(int x, int y)77     public int getPrimaryValue(int x, int y) {
78         return x;
79     }
80 
81     @Override
getSecondaryValue(int x, int y)82     public int getSecondaryValue(int x, int y) {
83         return y;
84     }
85 
86     @Override
getPrimaryValue(float x, float y)87     public float getPrimaryValue(float x, float y) {
88         return x;
89     }
90 
91     @Override
getSecondaryValue(float x, float y)92     public float getSecondaryValue(float x, float y) {
93         return y;
94     }
95 
96     @Override
isLayoutNaturalToLauncher()97     public boolean isLayoutNaturalToLauncher() {
98         return true;
99     }
100 
101     @Override
adjustFloatingIconStartVelocity(PointF velocity)102     public void adjustFloatingIconStartVelocity(PointF velocity) {
103         //no-op
104     }
105 
106     @Override
setPrimary(T target, Int2DAction<T> action, int param)107     public <T> void setPrimary(T target, Int2DAction<T> action, int param) {
108         action.call(target, param, 0);
109     }
110 
111     @Override
setPrimary(T target, Float2DAction<T> action, float param)112     public <T> void setPrimary(T target, Float2DAction<T> action, float param) {
113         action.call(target, param, 0);
114     }
115 
116     @Override
setSecondary(T target, Float2DAction<T> action, float param)117     public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
118         action.call(target, 0, param);
119     }
120 
121     @Override
set(T target, Int2DAction<T> action, int primaryParam, int secondaryParam)122     public <T> void set(T target, Int2DAction<T> action, int primaryParam,
123             int secondaryParam) {
124         action.call(target, primaryParam, secondaryParam);
125     }
126 
127     @Override
getPrimaryDirection(MotionEvent event, int pointerIndex)128     public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
129         return event.getX(pointerIndex);
130     }
131 
132     @Override
getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId)133     public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) {
134         return velocityTracker.getXVelocity(pointerId);
135     }
136 
137     @Override
getMeasuredSize(View view)138     public int getMeasuredSize(View view) {
139         return view.getMeasuredWidth();
140     }
141 
142     @Override
getPrimarySize(View view)143     public int getPrimarySize(View view) {
144         return view.getWidth();
145     }
146 
147     @Override
getPrimarySize(RectF rect)148     public float getPrimarySize(RectF rect) {
149         return rect.width();
150     }
151 
152     @Override
getStart(RectF rect)153     public float getStart(RectF rect) {
154         return rect.left;
155     }
156 
157     @Override
getEnd(RectF rect)158     public float getEnd(RectF rect) {
159         return rect.right;
160     }
161 
162     @Override
getClearAllSidePadding(View view, boolean isRtl)163     public int getClearAllSidePadding(View view, boolean isRtl) {
164         return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2;
165     }
166 
167     @Override
getSecondaryDimension(View view)168     public int getSecondaryDimension(View view) {
169         return view.getHeight();
170     }
171 
172     @Override
getPrimaryViewTranslate()173     public FloatProperty<View> getPrimaryViewTranslate() {
174         return VIEW_TRANSLATE_X;
175     }
176 
177     @Override
getSecondaryViewTranslate()178     public FloatProperty<View> getSecondaryViewTranslate() {
179         return VIEW_TRANSLATE_Y;
180     }
181 
182     @Override
getSplitTaskViewDismissDirection(@tagePosition int stagePosition, DeviceProfile dp)183     public int getSplitTaskViewDismissDirection(@StagePosition int stagePosition,
184             DeviceProfile dp) {
185         if (stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
186             if (dp.isLandscape) {
187                 // Left side
188                 return SPLIT_TRANSLATE_PRIMARY_NEGATIVE;
189             } else {
190                 // Top side
191                 return SPLIT_TRANSLATE_SECONDARY_NEGATIVE;
192             }
193         } else if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
194             // We don't have a bottom option, so should be right
195             return SPLIT_TRANSLATE_PRIMARY_POSITIVE;
196         }
197         throw new IllegalStateException("Invalid split stage position: " + stagePosition);
198     }
199 
200     @Override
getPrimaryScroll(View view)201     public int getPrimaryScroll(View view) {
202         return view.getScrollX();
203     }
204 
205     @Override
getPrimaryScale(View view)206     public float getPrimaryScale(View view) {
207         return view.getScaleX();
208     }
209 
210     @Override
setMaxScroll(AccessibilityEvent event, int maxScroll)211     public void setMaxScroll(AccessibilityEvent event, int maxScroll) {
212         event.setMaxScrollX(maxScroll);
213     }
214 
215     @Override
getRecentsRtlSetting(Resources resources)216     public boolean getRecentsRtlSetting(Resources resources) {
217         return !Utilities.isRtl(resources);
218     }
219 
220     @Override
getDegreesRotated()221     public float getDegreesRotated() {
222         return 0;
223     }
224 
225     @Override
getRotation()226     public int getRotation() {
227         return Surface.ROTATION_0;
228     }
229 
230     @Override
setPrimaryScale(View view, float scale)231     public void setPrimaryScale(View view, float scale) {
232         view.setScaleX(scale);
233     }
234 
235     @Override
setSecondaryScale(View view, float scale)236     public void setSecondaryScale(View view, float scale) {
237         view.setScaleY(scale);
238     }
239 
240     @Override
getChildStart(View view)241     public int getChildStart(View view) {
242         return view.getLeft();
243     }
244 
245     @Override
getCenterForPage(View view, Rect insets)246     public int getCenterForPage(View view, Rect insets) {
247         return (view.getPaddingTop() + view.getMeasuredHeight() + insets.top
248             - insets.bottom - view.getPaddingBottom()) / 2;
249     }
250 
251     @Override
getScrollOffsetStart(View view, Rect insets)252     public int getScrollOffsetStart(View view, Rect insets) {
253         return insets.left + view.getPaddingLeft();
254     }
255 
256     @Override
getScrollOffsetEnd(View view, Rect insets)257     public int getScrollOffsetEnd(View view, Rect insets) {
258         return view.getWidth() - view.getPaddingRight() - insets.right;
259     }
260 
getSecondaryTranslationDirectionFactor()261     public int getSecondaryTranslationDirectionFactor() {
262         return -1;
263     }
264 
265     @Override
getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile)266     public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
267         if (deviceProfile.isLandscape && stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
268             return -1;
269         } else {
270             return 1;
271         }
272     }
273 
274     @Override
getTaskMenuX(float x, View thumbnailView, int overScroll, DeviceProfile deviceProfile)275     public float getTaskMenuX(float x, View thumbnailView, int overScroll,
276             DeviceProfile deviceProfile) {
277         if (deviceProfile.isLandscape) {
278             return x + overScroll
279                     + (thumbnailView.getMeasuredWidth() - thumbnailView.getMeasuredHeight()) / 2f;
280         } else {
281             return x + overScroll;
282         }
283     }
284 
285     @Override
getTaskMenuY(float y, View thumbnailView, int overScroll)286     public float getTaskMenuY(float y, View thumbnailView, int overScroll) {
287         return y;
288     }
289 
290     @Override
getTaskMenuWidth(View view, DeviceProfile deviceProfile)291     public int getTaskMenuWidth(View view, DeviceProfile deviceProfile) {
292         return deviceProfile.isLandscape && !deviceProfile.overviewShowAsGrid ?
293                 view.getMeasuredHeight() :
294                 view.getMeasuredWidth();
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         viewGroup.setOrientation(LinearLayout.HORIZONTAL);
310         lp.width = LinearLayout.LayoutParams.MATCH_PARENT;
311         lp.height = WRAP_CONTENT;
312     }
313 
314     @Override
setTaskMenuAroundTaskView(LinearLayout taskView, float margin)315     public void setTaskMenuAroundTaskView(LinearLayout taskView, float margin) {
316         BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskView.getLayoutParams();
317         lp.topMargin += margin;
318         lp.leftMargin += margin;
319     }
320 
321     @Override
getAdditionalInsetForTaskMenu(float margin)322     public PointF getAdditionalInsetForTaskMenu(float margin) {
323         return new PointF(0, 0);
324     }
325 
326     @Override
setDwbLayoutParamsAndGetTranslations(int taskViewWidth, int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile, View[] thumbnailViews, int desiredTaskId, View banner)327     public Pair<Float, Float> setDwbLayoutParamsAndGetTranslations(int taskViewWidth,
328             int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
329             View[] thumbnailViews, int desiredTaskId, View banner) {
330         float translationX = 0;
331         float translationY = 0;
332         FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
333         banner.setPivotX(0);
334         banner.setPivotY(0);
335         banner.setRotation(getDegreesRotated());
336         if (splitBounds == null) {
337             // Single, fullscreen case
338             bannerParams.width = MATCH_PARENT;
339             bannerParams.gravity = BOTTOM | CENTER_HORIZONTAL;
340             return new Pair<>(translationX, translationY);
341         }
342 
343         bannerParams.gravity = BOTTOM | ((deviceProfile.isLandscape) ? START : CENTER_HORIZONTAL);
344 
345         // Set correct width
346         if (desiredTaskId == splitBounds.leftTopTaskId) {
347             bannerParams.width = thumbnailViews[0].getMeasuredWidth();
348         } else {
349             bannerParams.width = thumbnailViews[1].getMeasuredWidth();
350         }
351 
352         // Set translations
353         if (deviceProfile.isLandscape) {
354             if (desiredTaskId == splitBounds.rightBottomTaskId) {
355                 translationX = ((taskViewWidth * splitBounds.leftTaskPercent)
356                                 + (taskViewWidth * splitBounds.dividerWidthPercent));
357             }
358         } else {
359             if (desiredTaskId == splitBounds.leftTopTaskId) {
360                 FrameLayout.LayoutParams snapshotParams =
361                         (FrameLayout.LayoutParams) thumbnailViews[0]
362                                 .getLayoutParams();
363                 translationY = -((taskViewHeight - snapshotParams.topMargin)
364                         * (1f - splitBounds.topTaskPercent));
365             }
366         }
367         return new Pair<>(translationX, translationY);
368     }
369 
370     /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
371 
372     @Override
getUpDownSwipeDirection()373     public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
374         return VERTICAL;
375     }
376 
377     @Override
getUpDirection(boolean isRtl)378     public int getUpDirection(boolean isRtl) {
379         // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
380         return SingleAxisSwipeDetector.DIRECTION_POSITIVE;
381     }
382 
383     @Override
isGoingUp(float displacement, boolean isRtl)384     public boolean isGoingUp(float displacement, boolean isRtl) {
385         // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
386         return displacement < 0;
387     }
388 
389     @Override
getTaskDragDisplacementFactor(boolean isRtl)390     public int getTaskDragDisplacementFactor(boolean isRtl) {
391         // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
392         return 1;
393     }
394 
395     /* -------------------- */
396 
397     @Override
getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild)398     public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
399         boolean layoutChild) {
400         final int childWidth = child.getMeasuredWidth();
401         final int childRight = childStart + childWidth;
402         final int childHeight = child.getMeasuredHeight();
403         final int childTop = pageCenter - childHeight / 2;
404         if (layoutChild) {
405             child.layout(childStart, childTop, childRight, childTop + childHeight);
406         }
407         return new ChildBounds(childWidth, childHeight, childRight, childTop);
408     }
409 
410     @Override
getDistanceToBottomOfRect(DeviceProfile dp, Rect rect)411     public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
412         return dp.heightPx - rect.bottom;
413     }
414 
415     @Override
getSplitPositionOptions(DeviceProfile dp)416     public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
417         List<SplitPositionOption> options = new ArrayList<>(1);
418         // Add both left and right options if we're in tablet mode
419         if (dp.isTablet && dp.isLandscape) {
420             options.add(new SplitPositionOption(
421                     R.drawable.ic_split_right, R.string.split_screen_position_right,
422                     STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
423             options.add(new SplitPositionOption(
424                     R.drawable.ic_split_left, R.string.split_screen_position_left,
425                     STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
426         } else {
427             if (dp.isSeascape()) {
428                 // Add left/right options
429                 options.add(new SplitPositionOption(
430                         R.drawable.ic_split_right, R.string.split_screen_position_right,
431                         STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
432             } else if (dp.isLandscape) {
433                 options.add(new SplitPositionOption(
434                         R.drawable.ic_split_left, R.string.split_screen_position_left,
435                         STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
436             } else {
437                 // Only add top option
438                 options.add(new SplitPositionOption(
439                         R.drawable.ic_split_top, R.string.split_screen_position_top,
440                         STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
441             }
442         }
443         return options;
444     }
445 
446     @Override
getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp, @StagePosition int stagePosition, Rect out)447     public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp,
448             @StagePosition int stagePosition, Rect out) {
449         int width = dp.widthPx;
450         out.set(0, 0, width, placeholderHeight);
451         if (!dp.isLandscape) {
452             // portrait, phone or tablet - spans width of screen, nothing else to do
453             return;
454         }
455 
456         // Now we rotate the portrait rect depending on what side we want pinned
457         boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
458 
459         int screenHeight = dp.heightPx;
460         float postRotateScale = (float) screenHeight / width;
461         mTmpMatrix.reset();
462         mTmpMatrix.postRotate(pinToRight ? 90 : 270);
463         mTmpMatrix.postTranslate(pinToRight ? width : 0, pinToRight ? 0 : width);
464         // The placeholder height stays constant after rotation, so we don't change width scale
465         mTmpMatrix.postScale(1, postRotateScale);
466 
467         mTmpRectF.set(out);
468         mTmpMatrix.mapRect(mTmpRectF);
469         mTmpRectF.roundOut(out);
470     }
471 
472     @Override
getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp, @StagePosition int stagePosition, Rect out1, Rect out2)473     public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
474             @StagePosition int stagePosition, Rect out1, Rect out2) {
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         if (!dp.isLandscape) {
480             // Portrait - the window bounds are always top and bottom half
481             return;
482         }
483 
484         // Now we rotate the portrait rect depending on what side we want pinned
485         boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
486         float postRotateScale = (float) screenHeight / screenWidth;
487 
488         mTmpMatrix.reset();
489         mTmpMatrix.postRotate(pinToRight ? 90 : 270);
490         mTmpMatrix.postTranslate(pinToRight ? screenHeight : 0, pinToRight ? 0 : screenWidth);
491         mTmpMatrix.postScale(1 / postRotateScale, postRotateScale);
492 
493         mTmpRectF.set(out1);
494         mTmpMatrix.mapRect(mTmpRectF);
495         mTmpRectF.roundOut(out1);
496 
497         mTmpRectF.set(out2);
498         mTmpMatrix.mapRect(mTmpRectF);
499         mTmpRectF.roundOut(out2);
500     }
501 
502     @Override
setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, StagedSplitBounds splitInfo, int desiredStagePosition)503     public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
504             StagedSplitBounds splitInfo, int desiredStagePosition) {
505         boolean isLandscape = dp.isLandscape;
506         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
507             if (isLandscape) {
508                 outRect.right = outRect.left + (int) (outRect.width() * splitInfo.leftTaskPercent);
509             } else {
510                 outRect.bottom = outRect.top + (int) (outRect.height() * splitInfo.topTaskPercent);
511             }
512         } else {
513             if (isLandscape) {
514                 outRect.left += (int) (outRect.width() *
515                         (splitInfo.leftTaskPercent + splitInfo.dividerWidthPercent));
516             } else {
517                 outRect.top += (int) (outRect.height() *
518                         (splitInfo.topTaskPercent + splitInfo.dividerHeightPercent));
519             }
520         }
521     }
522 
523     @Override
measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot, int parentWidth, int parentHeight, StagedSplitBounds splitBoundsConfig, DeviceProfile dp)524     public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
525             int parentWidth, int parentHeight,
526             StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
527         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
528         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
529         int dividerBar = splitBoundsConfig.appsStackedVertically
530                 ? (int) (splitBoundsConfig.dividerHeightPercent * parentHeight)
531                 : (int) (splitBoundsConfig.dividerWidthPercent * parentWidth);
532         int primarySnapshotHeight;
533         int primarySnapshotWidth;
534         int secondarySnapshotHeight;
535         int secondarySnapshotWidth;
536         float taskPercent = splitBoundsConfig.appsStackedVertically ?
537                 splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
538         if (dp.isLandscape) {
539             primarySnapshotHeight = totalThumbnailHeight;
540             primarySnapshotWidth = (int) (parentWidth * taskPercent);
541 
542             secondarySnapshotHeight = totalThumbnailHeight;
543             secondarySnapshotWidth = parentWidth - primarySnapshotWidth - dividerBar;
544             int translationX = primarySnapshotWidth + dividerBar;
545             secondarySnapshot.setTranslationX(translationX);
546             secondarySnapshot.setTranslationY(spaceAboveSnapshot);
547         } else {
548             primarySnapshotWidth = parentWidth;
549             primarySnapshotHeight = (int) (totalThumbnailHeight * taskPercent);
550 
551             secondarySnapshotWidth = parentWidth;
552             secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
553             int translationY = primarySnapshotHeight + spaceAboveSnapshot + dividerBar;
554             secondarySnapshot.setTranslationY(translationY);
555             secondarySnapshot.setTranslationX(0);
556         }
557         primarySnapshot.measure(
558                 View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
559                 View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY));
560         secondarySnapshot.measure(
561                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
562                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
563                         View.MeasureSpec.EXACTLY));
564     }
565 
566     @Override
setIconAndSnapshotParams(View iconView, int taskIconMargin, int taskIconHeight, FrameLayout.LayoutParams snapshotParams, boolean isRtl)567     public void setIconAndSnapshotParams(View iconView, int taskIconMargin, int taskIconHeight,
568             FrameLayout.LayoutParams snapshotParams, boolean isRtl) {
569         FrameLayout.LayoutParams iconParams =
570                 (FrameLayout.LayoutParams) iconView.getLayoutParams();
571         iconParams.gravity = TOP | CENTER_HORIZONTAL;
572         iconParams.leftMargin = iconParams.rightMargin = 0;
573         iconParams.topMargin = taskIconMargin;
574     }
575 
576     @Override
setSplitIconParams(View primaryIconView, View secondaryIconView, int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight, boolean isRtl, DeviceProfile deviceProfile, StagedSplitBounds splitConfig)577     public void setSplitIconParams(View primaryIconView, View secondaryIconView,
578             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
579             boolean isRtl, DeviceProfile deviceProfile, StagedSplitBounds splitConfig) {
580         FrameLayout.LayoutParams primaryIconParams =
581                 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
582         FrameLayout.LayoutParams secondaryIconParams =
583                 new FrameLayout.LayoutParams(primaryIconParams);
584         int dividerBar = (splitConfig.appsStackedVertically ?
585                 splitConfig.visualDividerBounds.height() :
586                 splitConfig.visualDividerBounds.width());
587 
588         if (deviceProfile.isLandscape) {
589             primaryIconParams.gravity = TOP | START;
590             primaryIconView.setTranslationX(
591                     primarySnapshotWidth - primaryIconView.getMeasuredWidth());
592             primaryIconView.setTranslationY(0);
593             secondaryIconParams.gravity = TOP | START;
594             secondaryIconView.setTranslationX(primarySnapshotWidth + dividerBar);
595         } else {
596             primaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
597             primaryIconView.setTranslationX(-(primaryIconView.getMeasuredWidth()) / 2f);
598             primaryIconView.setTranslationY(0);
599 
600             secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
601             secondaryIconView.setTranslationX(secondaryIconView.getMeasuredWidth() / 2f);
602         }
603         secondaryIconView.setTranslationY(0);
604         primaryIconView.setLayoutParams(primaryIconParams);
605         secondaryIconView.setLayoutParams(secondaryIconParams);
606     }
607 
608     @Override
getDefaultSplitPosition(DeviceProfile deviceProfile)609     public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
610         if (!deviceProfile.isTablet) {
611             throw new IllegalStateException("Default position available only for large screens");
612         }
613         if (deviceProfile.isLandscape) {
614             return STAGE_POSITION_BOTTOM_OR_RIGHT;
615         } else {
616             return STAGE_POSITION_TOP_OR_LEFT;
617         }
618     }
619 
620     @Override
getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary, DeviceProfile deviceProfile)621     public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
622             FloatProperty secondary, DeviceProfile deviceProfile) {
623         if (deviceProfile.isLandscape) { // or seascape
624             return new Pair<>(primary, secondary);
625         } else {
626             return new Pair<>(secondary, primary);
627         }
628     }
629 }
630