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