1 /*
2  * Copyright (C) 2024 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 package com.android.quickstep.orientation
17 
18 import android.graphics.Point
19 import android.graphics.PointF
20 import android.graphics.Rect
21 import android.graphics.RectF
22 import android.graphics.drawable.ShapeDrawable
23 import android.util.FloatProperty
24 import android.util.Pair
25 import android.view.View
26 import android.widget.FrameLayout
27 import android.widget.LinearLayout
28 import com.android.launcher3.DeviceProfile
29 import com.android.launcher3.logger.LauncherAtom
30 import com.android.launcher3.touch.PagedOrientationHandler
31 import com.android.launcher3.touch.PagedOrientationHandler.Float2DAction
32 import com.android.launcher3.touch.PagedOrientationHandler.Int2DAction
33 import com.android.launcher3.touch.SingleAxisSwipeDetector
34 import com.android.launcher3.util.SplitConfigurationOptions
35 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
36 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
37 import com.android.quickstep.views.IconAppChipView
38 
39 /**
40  * Abstraction layer to separate horizontal and vertical specific implementations for
41  * [com.android.quickstep.views.RecentsView]. Majority of these implementations are (should be) as
42  * simple as choosing the correct X and Y analogous methods.
43  */
44 interface RecentsPagedOrientationHandler : PagedOrientationHandler {
setSecondarynull45     fun <T> setSecondary(target: T, action: Float2DAction<T>, param: Float)
46 
47     operator fun <T> set(target: T, action: Int2DAction<T>, primaryParam: Int, secondaryParam: Int)
48 
49     fun getPrimarySize(view: View): Int
50 
51     fun getPrimarySize(rect: RectF): Float
52 
53     val secondaryTranslationDirectionFactor: Int
54 
55     val degreesRotated: Float
56 
57     val rotation: Int
58 
59     val isLayoutNaturalToLauncher: Boolean
60 
61     fun <T> getPrimaryValue(x: T, y: T): T
62 
63     fun <T> getSecondaryValue(x: T, y: T): T
64 
65     fun setPrimaryScale(view: View, scale: Float)
66 
67     fun setSecondaryScale(view: View, scale: Float)
68 
69     fun getStart(rect: RectF): Float
70 
71     fun getEnd(rect: RectF): Float
72 
73     /** Rotate the provided insets to portrait perspective. */
74     fun rotateInsets(insets: Rect, outInsets: Rect)
75 
76     fun getClearAllSidePadding(view: View, isRtl: Boolean): Int
77 
78     fun getSecondaryDimension(view: View): Int
79 
80     val primaryViewTranslate: FloatProperty<View>
81     val secondaryViewTranslate: FloatProperty<View>
82 
83     fun getSplitTranslationDirectionFactor(
84         @StagePosition stagePosition: Int,
85         deviceProfile: DeviceProfile
86     ): Int
87 
88     fun <T> getSplitSelectTaskOffset(
89         primary: FloatProperty<T>,
90         secondary: FloatProperty<T>,
91         deviceProfile: DeviceProfile
92     ): Pair<FloatProperty<T>, FloatProperty<T>>
93 
94     fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int
95 
96     fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption>
97 
98     /** @param placeholderHeight height of placeholder view in portrait, width in landscape */
99     fun getInitialSplitPlaceholderBounds(
100         placeholderHeight: Int,
101         placeholderInset: Int,
102         dp: DeviceProfile,
103         @StagePosition stagePosition: Int,
104         out: Rect
105     )
106 
107     /**
108      * Centers an icon in the split staging area, accounting for insets.
109      *
110      * @param out The icon that needs to be centered.
111      * @param onScreenRectCenterX The x-center of the on-screen staging area (most of the Rect is
112      *   offscreen).
113      * @param onScreenRectCenterY The y-center of the on-screen staging area (most of the Rect is
114      *   offscreen).
115      * @param fullscreenScaleX A x-scaling factor used to convert coordinates back into pixels.
116      * @param fullscreenScaleY A y-scaling factor used to convert coordinates back into pixels.
117      * @param drawableWidth The icon's drawable (final) width.
118      * @param drawableHeight The icon's drawable (final) height.
119      * @param dp The device profile, used to report rotation and hardware insets.
120      * @param stagePosition 0 if the staging area is pinned to top/left, 1 for bottom/right.
121      */
122     fun updateSplitIconParams(
123         out: View,
124         onScreenRectCenterX: Float,
125         onScreenRectCenterY: Float,
126         fullscreenScaleX: Float,
127         fullscreenScaleY: Float,
128         drawableWidth: Int,
129         drawableHeight: Int,
130         dp: DeviceProfile,
131         @StagePosition stagePosition: Int
132     )
133 
134     /**
135      * Sets positioning and rotation for a SplitInstructionsView.
136      *
137      * @param out The SplitInstructionsView that needs to be positioned.
138      * @param dp The device profile, used to report rotation and device type.
139      * @param splitInstructionsHeight The SplitInstructionView's height.
140      * @param splitInstructionsWidth The SplitInstructionView's width.
141      */
142     fun setSplitInstructionsParams(
143         out: View,
144         dp: DeviceProfile,
145         splitInstructionsHeight: Int,
146         splitInstructionsWidth: Int
147     )
148 
149     /**
150      * @param splitDividerSize height of split screen drag handle in portrait, width in landscape
151      * @param stagePosition the split position option (top/left, bottom/right) of the first task
152      *   selected for entering split
153      * @param out1 the bounds for where the first selected app will be
154      * @param out2 the bounds for where the second selected app will be, complimentary to {@param
155      *   out1} based on {@param initialSplitOption}
156      */
157     fun getFinalSplitPlaceholderBounds(
158         splitDividerSize: Int,
159         dp: DeviceProfile,
160         @StagePosition stagePosition: Int,
161         out1: Rect,
162         out2: Rect
163     )
164 
165     fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int
166 
167     /**
168      * @param outRect This is expected to be the rect that has the dimensions for a non-split,
169      *   fullscreen task in overview. This will directly be modified.
170      * @param desiredStagePosition Which stage position (topLeft/rightBottom) we want to resize
171      *   outRect for
172      */
173     fun setSplitTaskSwipeRect(
174         dp: DeviceProfile,
175         outRect: Rect,
176         splitInfo: SplitConfigurationOptions.SplitBounds,
177         @StagePosition desiredStagePosition: Int
178     )
179 
180     fun measureGroupedTaskViewThumbnailBounds(
181         primarySnapshot: View,
182         secondarySnapshot: View,
183         parentWidth: Int,
184         parentHeight: Int,
185         splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
186         dp: DeviceProfile,
187         isRtl: Boolean,
188         inSplitSelection: Boolean
189     )
190 
191     /**
192      * Creates two Points representing the dimensions of the two tasks in a GroupedTaskView
193      *
194      * @return first -> primary task snapshot, second -> secondary task snapshot. x -> width, y ->
195      *   height
196      */
197     fun getGroupedTaskViewSizes(
198         dp: DeviceProfile,
199         splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
200         parentWidth: Int,
201         parentHeight: Int
202     ): Pair<Point, Point>
203 
204     // Overview TaskMenuView methods
205     /** Sets layout params on a task's app icon. Only use this when app chip is disabled. */
206     fun setTaskIconParams(
207         iconParams: FrameLayout.LayoutParams,
208         taskIconMargin: Int,
209         taskIconHeight: Int,
210         thumbnailTopMargin: Int,
211         isRtl: Boolean
212     )
213 
214     /**
215      * Sets layout params on the children of an app chip. Only use this when app chip is enabled.
216      */
217     fun setIconAppChipChildrenParams(
218         iconParams: FrameLayout.LayoutParams,
219         chipChildMarginStart: Int
220     )
221 
222     fun setIconAppChipMenuParams(
223         iconAppChipView: IconAppChipView,
224         iconMenuParams: FrameLayout.LayoutParams,
225         iconMenuMargin: Int,
226         thumbnailTopMargin: Int
227     )
228 
229     fun setSplitIconParams(
230         primaryIconView: View,
231         secondaryIconView: View,
232         taskIconHeight: Int,
233         primarySnapshotWidth: Int,
234         primarySnapshotHeight: Int,
235         groupedTaskViewHeight: Int,
236         groupedTaskViewWidth: Int,
237         isRtl: Boolean,
238         deviceProfile: DeviceProfile,
239         splitConfig: SplitConfigurationOptions.SplitBounds,
240         inSplitSelection: Boolean
241     )
242 
243     /*
244      * The following two methods try to center the TaskMenuView in landscape by finding the center
245      * of the thumbnail view and then subtracting half of the taskMenu width. In this case, the
246      * taskMenu width is the same size as the thumbnail width (what got set below in
247      * getTaskMenuWidth()), so we directly use that in the calculations.
248      */
249     fun getTaskMenuX(
250         x: Float,
251         thumbnailView: View,
252         deviceProfile: DeviceProfile,
253         taskInsetMargin: Float,
254         taskViewIcon: View
255     ): Float
256 
257     fun getTaskMenuY(
258         y: Float,
259         thumbnailView: View,
260         stagePosition: Int,
261         taskMenuView: View,
262         taskInsetMargin: Float,
263         taskViewIcon: View
264     ): Float
265 
266     fun getTaskMenuWidth(
267         thumbnailView: View,
268         deviceProfile: DeviceProfile,
269         @StagePosition stagePosition: Int
270     ): Int
271 
272     fun getTaskMenuHeight(
273         taskInsetMargin: Float,
274         deviceProfile: DeviceProfile,
275         taskMenuX: Float,
276         taskMenuY: Float
277     ): Int
278 
279     /**
280      * Sets linear layout orientation for [com.android.launcher3.popup.SystemShortcut] items inside
281      * task menu view.
282      */
283     fun setTaskOptionsMenuLayoutOrientation(
284         deviceProfile: DeviceProfile,
285         taskMenuLayout: LinearLayout,
286         dividerSpacing: Int,
287         dividerDrawable: ShapeDrawable
288     )
289 
290     /**
291      * Sets layout param attributes for [com.android.launcher3.popup.SystemShortcut] child views
292      * inside task menu view.
293      */
294     fun setLayoutParamsForTaskMenuOptionItem(
295         lp: LinearLayout.LayoutParams,
296         viewGroup: LinearLayout,
297         deviceProfile: DeviceProfile
298     )
299 
300     /** Layout a Digital Wellbeing Banner on its parent. TaskView. */
301     fun updateDwbBannerLayout(
302         taskViewWidth: Int,
303         taskViewHeight: Int,
304         isGroupedTaskView: Boolean,
305         deviceProfile: DeviceProfile,
306         snapshotViewWidth: Int,
307         snapshotViewHeight: Int,
308         banner: View
309     )
310 
311     /**
312      * Calculates the translations where a Digital Wellbeing Banner should be apply on its parent
313      * TaskView.
314      *
315      * @return A Pair of Floats representing the proper x and y translations.
316      */
317     fun getDwbBannerTranslations(
318         taskViewWidth: Int,
319         taskViewHeight: Int,
320         splitBounds: SplitConfigurationOptions.SplitBounds?,
321         deviceProfile: DeviceProfile,
322         thumbnailViews: Array<View>,
323         desiredTaskId: Int,
324         banner: View
325     ): Pair<Float, Float>
326 
327     // The following are only used by TaskViewTouchHandler.
328 
329     /** @return Either VERTICAL or HORIZONTAL. */
330     val upDownSwipeDirection: SingleAxisSwipeDetector.Direction
331 
332     /** @return Given [.getUpDownSwipeDirection], whether POSITIVE or NEGATIVE is up. */
333     fun getUpDirection(isRtl: Boolean): Int
334 
335     /** @return Whether the displacement is going towards the top of the screen. */
336     fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean
337 
338     /** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
339     fun getTaskDragDisplacementFactor(isRtl: Boolean): Int
340 
341     /**
342      * Maps the velocity from the coordinate plane of the foreground app to that of Launcher's
343      * (which now will always be portrait)
344      */
345     fun adjustFloatingIconStartVelocity(velocity: PointF)
346 
347     /**
348      * Ensures that outStartRect left bound is within the DeviceProfile's visual boundaries
349      *
350      * @param outStartRect The start rect that will directly be modified
351      */
352     fun fixBoundsForHomeAnimStartRect(outStartRect: RectF, deviceProfile: DeviceProfile)
353 
354     /**
355      * Determine the target translation for animating the FloatingTaskView out. This value could
356      * either be an x-coordinate or a y-coordinate, depending on which way the FloatingTaskView was
357      * docked.
358      *
359      * @param floatingTask The FloatingTaskView.
360      * @param onScreenRect The current on-screen dimensions of the FloatingTaskView.
361      * @param stagePosition STAGE_POSITION_TOP_OR_LEFT or STAGE_POSITION_BOTTOM_OR_RIGHT.
362      * @param dp The device profile.
363      * @return A float. When an animation translates the FloatingTaskView to this position, it will
364      *   appear to tuck away off the edge of the screen.
365      */
366     fun getFloatingTaskOffscreenTranslationTarget(
367         floatingTask: View,
368         onScreenRect: RectF,
369         @StagePosition stagePosition: Int,
370         dp: DeviceProfile
371     ): Float
372 
373     /**
374      * Sets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
375      * either x or y), depending on how the view is oriented.
376      *
377      * @param floatingTask The FloatingTaskView to be translated.
378      * @param translation The target translation value.
379      * @param dp The current device profile.
380      */
381     fun setFloatingTaskPrimaryTranslation(floatingTask: View, translation: Float, dp: DeviceProfile)
382 
383     /**
384      * Gets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
385      * either x or y), depending on how the view is oriented.
386      *
387      * @param floatingTask The FloatingTaskView in question.
388      * @param dp The current device profile.
389      * @return The current translation value.
390      */
391     fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float
392 
393     fun getHandlerTypeForLogging(): LauncherAtom.TaskSwitcherContainer.OrientationHandler
394 
395     companion object {
396         @JvmField val PORTRAIT: RecentsPagedOrientationHandler = PortraitPagedViewHandler()
397         @JvmField val LANDSCAPE: RecentsPagedOrientationHandler = LandscapePagedViewHandler()
398         @JvmField val SEASCAPE: RecentsPagedOrientationHandler = SeascapePagedViewHandler()
399     }
400 }
401