• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.RIGHT;
23 import static android.view.Gravity.START;
24 
25 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
26 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
27 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
28 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
29 
30 import android.content.res.Resources;
31 import android.graphics.PointF;
32 import android.graphics.Rect;
33 import android.util.Pair;
34 import android.view.Surface;
35 import android.view.View;
36 import android.widget.FrameLayout;
37 
38 import com.android.launcher3.DeviceProfile;
39 import com.android.launcher3.R;
40 import com.android.launcher3.Utilities;
41 import com.android.launcher3.util.SplitConfigurationOptions;
42 import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
43 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
44 import com.android.launcher3.views.BaseDragLayer;
45 
46 import java.util.Collections;
47 import java.util.List;
48 
49 public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
50 
51     @Override
getSecondaryTranslationDirectionFactor()52     public int getSecondaryTranslationDirectionFactor() {
53         return -1;
54     }
55 
56     @Override
getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile)57     public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
58         if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
59             return -1;
60         } else {
61             return 1;
62         }
63     }
64 
65     @Override
getRecentsRtlSetting(Resources resources)66     public boolean getRecentsRtlSetting(Resources resources) {
67         return Utilities.isRtl(resources);
68     }
69 
70     @Override
getDegreesRotated()71     public float getDegreesRotated() {
72         return 270;
73     }
74 
75     @Override
getRotation()76     public int getRotation() {
77         return Surface.ROTATION_270;
78     }
79 
80     @Override
adjustFloatingIconStartVelocity(PointF velocity)81     public void adjustFloatingIconStartVelocity(PointF velocity) {
82         float oldX = velocity.x;
83         float oldY = velocity.y;
84         velocity.set(oldY, -oldX);
85     }
86 
87     @Override
getTaskMenuX(float x, View thumbnailView, DeviceProfile deviceProfile, float taskInsetMargin)88     public float getTaskMenuX(float x, View thumbnailView,
89             DeviceProfile deviceProfile, float taskInsetMargin) {
90         return x + taskInsetMargin;
91     }
92 
93     @Override
getTaskMenuY(float y, View thumbnailView, int stagePosition, View taskMenuView, float taskInsetMargin)94     public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
95             View taskMenuView, float taskInsetMargin) {
96         BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskMenuView.getLayoutParams();
97         int taskMenuWidth = lp.width;
98         if (stagePosition == STAGE_POSITION_UNDEFINED) {
99             return y + taskInsetMargin
100                     + (thumbnailView.getMeasuredHeight() + taskMenuWidth) / 2f;
101         } else {
102             return y + taskMenuWidth + taskInsetMargin;
103         }
104     }
105 
106     @Override
setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo, int desiredStagePosition)107     public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo,
108             int desiredStagePosition) {
109         float topLeftTaskPercent = splitInfo.appsStackedVertically
110                 ? splitInfo.topTaskPercent
111                 : splitInfo.leftTaskPercent;
112         float dividerBarPercent = splitInfo.appsStackedVertically
113                 ? splitInfo.dividerHeightPercent
114                 : splitInfo.dividerWidthPercent;
115 
116         // In seascape, the primary thumbnail is counterintuitively placed at the physical bottom of
117         // the screen. This is to preserve consistency when the user rotates: From the user's POV,
118         // the primary should always be on the left.
119         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
120             outRect.top += (int) (outRect.height() * ((1 - topLeftTaskPercent)));
121         } else {
122             outRect.bottom -= (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
123         }
124     }
125 
126     @Override
getDwbLayoutTranslations(int taskViewWidth, int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile, View[] thumbnailViews, int desiredTaskId, View banner)127     public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
128             int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
129             View[] thumbnailViews, int desiredTaskId, View banner) {
130         boolean isRtl = banner.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
131         float translationX = 0;
132         float translationY = 0;
133         FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
134         banner.setPivotX(0);
135         banner.setPivotY(0);
136         banner.setRotation(getDegreesRotated());
137         translationX = taskViewWidth - banner.getHeight();
138         FrameLayout.LayoutParams snapshotParams =
139                 (FrameLayout.LayoutParams) thumbnailViews[0]
140                         .getLayoutParams();
141         bannerParams.gravity = BOTTOM | (isRtl ? END : START);
142 
143         if (splitBounds == null) {
144             // Single, fullscreen case
145             bannerParams.width = taskViewHeight - snapshotParams.topMargin;
146             translationY = banner.getHeight();
147             return new Pair<>(translationX, translationY);
148         }
149 
150         // Set correct width
151         if (desiredTaskId == splitBounds.leftTopTaskId) {
152             bannerParams.width = thumbnailViews[0].getMeasuredHeight();
153         } else {
154             bannerParams.width = thumbnailViews[1].getMeasuredHeight();
155         }
156 
157         // Set translations
158         if (desiredTaskId == splitBounds.rightBottomTaskId) {
159             translationY = banner.getHeight();
160         }
161         if (desiredTaskId == splitBounds.leftTopTaskId) {
162             float bottomRightTaskPlusDividerPercent = splitBounds.appsStackedVertically
163                     ? (1f - splitBounds.topTaskPercent)
164                     : (1f - splitBounds.leftTaskPercent);
165             translationY = banner.getHeight()
166                     - ((taskViewHeight - snapshotParams.topMargin)
167                     * bottomRightTaskPlusDividerPercent);
168         }
169         return new Pair<>(translationX, translationY);
170     }
171 
172     @Override
getDistanceToBottomOfRect(DeviceProfile dp, Rect rect)173     public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
174         return dp.widthPx - rect.right;
175     }
176 
177     @Override
getSplitPositionOptions(DeviceProfile dp)178     public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
179         // Add "right" option which is actually the top
180         return Collections.singletonList(new SplitPositionOption(
181                 R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
182                 STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
183     }
184 
185     @Override
setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight, int splitInstructionsWidth)186     public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
187             int splitInstructionsWidth) {
188         out.setPivotX(0);
189         out.setPivotY(splitInstructionsHeight);
190         out.setRotation(getDegreesRotated());
191         int distanceToEdge = out.getResources().getDimensionPixelSize(
192                 R.dimen.split_instructions_bottom_margin_phone_landscape);
193         // Adjust for any insets on the right edge
194         int insetCorrectionX = dp.getInsets().right;
195         // Center the view in case of unbalanced insets on top or bottom of screen
196         int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
197         out.setTranslationX(splitInstructionsWidth - distanceToEdge + insetCorrectionX);
198         out.setTranslationY(((-splitInstructionsHeight + splitInstructionsWidth) / 2f)
199                 + insetCorrectionY);
200         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
201         // Setting gravity to RIGHT instead of the lint-recommended END because we always want this
202         // view to be screen-right when phone is in seascape, regardless of the RtL setting.
203         lp.gravity = RIGHT | CENTER_VERTICAL;
204         out.setLayoutParams(lp);
205     }
206 
207     @Override
setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl)208     public void setTaskIconParams(FrameLayout.LayoutParams iconParams,
209             int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
210         iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
211         iconParams.leftMargin = -taskIconHeight - taskIconMargin / 2;
212         iconParams.rightMargin = 0;
213         iconParams.topMargin = thumbnailTopMargin / 2;
214         iconParams.bottomMargin = 0;
215     }
216 
217     @Override
setSplitIconParams(View primaryIconView, View secondaryIconView, int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight, int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl, DeviceProfile deviceProfile, SplitBounds splitConfig)218     public void setSplitIconParams(View primaryIconView, View secondaryIconView,
219             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
220             int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
221             DeviceProfile deviceProfile, SplitBounds splitConfig) {
222         super.setSplitIconParams(primaryIconView, secondaryIconView, taskIconHeight,
223                 primarySnapshotWidth, primarySnapshotHeight, groupedTaskViewHeight,
224                 groupedTaskViewWidth, isRtl, deviceProfile, splitConfig);
225         FrameLayout.LayoutParams primaryIconParams =
226                 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
227         FrameLayout.LayoutParams secondaryIconParams =
228                 (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams();
229 
230         // We calculate the "midpoint" of the thumbnail area, and place the icons there.
231         // This is the place where the thumbnail area splits by default, in a near-50/50 split.
232         // It is usually not exactly 50/50, due to insets/screen cutouts.
233         int fullscreenInsetThickness = deviceProfile.getInsets().top
234                 - deviceProfile.getInsets().bottom;
235         int fullscreenMidpointFromBottom = ((deviceProfile.heightPx
236                 - fullscreenInsetThickness) / 2);
237         float midpointFromBottomPct = (float) fullscreenMidpointFromBottom / deviceProfile.heightPx;
238         float insetPct = (float) fullscreenInsetThickness / deviceProfile.heightPx;
239         int spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx;
240         int overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots;
241         int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness * midpointFromBottomPct);
242         int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
243 
244         primaryIconParams.gravity = BOTTOM | (isRtl ? END : START);
245         secondaryIconParams.gravity = BOTTOM | (isRtl ? END : START);
246         primaryIconView.setTranslationX(0);
247         secondaryIconView.setTranslationX(0);
248         if (splitConfig.initiatedFromSeascape) {
249             // if the split was initiated from seascape,
250             // the task on the right (secondary) is slightly larger
251             primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
252                     + taskIconHeight);
253             secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
254         } else {
255             // if not,
256             // the task on the left (primary) is slightly larger
257             primaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
258             secondaryIconView.setTranslationY(-bottomToMidpointOffset);
259         }
260 
261         primaryIconView.setLayoutParams(primaryIconParams);
262         secondaryIconView.setLayoutParams(secondaryIconParams);
263     }
264 
265     @Override
measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot, int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl)266     public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
267             int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, DeviceProfile dp,
268             boolean isRtl) {
269         FrameLayout.LayoutParams primaryParams =
270                 (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
271         FrameLayout.LayoutParams secondaryParams =
272                 (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
273 
274         // Swap the margins that are set in TaskView#setRecentsOrientedState()
275         secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx;
276         primaryParams.topMargin = 0;
277 
278         // Measure and layout the thumbnails bottom up, since the primary is on the visual left
279         // (portrait bottom) and secondary is on the right (portrait top)
280         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
281         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
282         int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
283                 ? splitBoundsConfig.dividerHeightPercent
284                 : splitBoundsConfig.dividerWidthPercent));
285         int primarySnapshotHeight;
286         int primarySnapshotWidth;
287         int secondarySnapshotHeight;
288         int secondarySnapshotWidth;
289 
290         float taskPercent = splitBoundsConfig.appsStackedVertically ?
291                 splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
292         primarySnapshotWidth = parentWidth;
293         primarySnapshotHeight = (int) (totalThumbnailHeight * (taskPercent));
294 
295         secondarySnapshotWidth = parentWidth;
296         secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
297         secondarySnapshot.setTranslationY(0);
298         primarySnapshot.setTranslationY(secondarySnapshotHeight + spaceAboveSnapshot + dividerBar);
299         primarySnapshot.measure(
300                 View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
301                 View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY));
302         secondarySnapshot.measure(
303                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
304                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
305                         View.MeasureSpec.EXACTLY));
306     }
307 
308     /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
309 
310     @Override
getUpDownSwipeDirection()311     public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
312         return HORIZONTAL;
313     }
314 
315     @Override
getUpDirection(boolean isRtl)316     public int getUpDirection(boolean isRtl) {
317         return isRtl ? SingleAxisSwipeDetector.DIRECTION_POSITIVE
318                 : SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
319     }
320 
321     @Override
isGoingUp(float displacement, boolean isRtl)322     public boolean isGoingUp(float displacement, boolean isRtl) {
323         return isRtl ? displacement > 0 : displacement < 0;
324     }
325 
326     @Override
getTaskDragDisplacementFactor(boolean isRtl)327     public int getTaskDragDisplacementFactor(boolean isRtl) {
328         return isRtl ? -1 : 1;
329     }
330 
331     /* -------------------- */
332 }
333