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     )
189 
190     /**
191      * Creates two Points representing the dimensions of the two tasks in a GroupedTaskView
192      *
193      * @return first -> primary task snapshot, second -> secondary task snapshot. x -> width, y ->
194      *   height
195      */
196     fun getGroupedTaskViewSizes(
197         dp: DeviceProfile,
198         splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
199         parentWidth: Int,
200         parentHeight: Int
201     ): Pair<Point, Point>
202     // Overview TaskMenuView methods
203     /** Sets layout params on a task's app icon. Only use this when app chip is disabled. */
204     fun setTaskIconParams(
205         iconParams: FrameLayout.LayoutParams,
206         taskIconMargin: Int,
207         taskIconHeight: Int,
208         thumbnailTopMargin: Int,
209         isRtl: Boolean
210     )
211 
212     /**
213      * Sets layout params on the children of an app chip. Only use this when app chip is enabled.
214      */
215     fun setIconAppChipChildrenParams(
216         iconParams: FrameLayout.LayoutParams,
217         chipChildMarginStart: Int
218     )
219 
220     fun setIconAppChipMenuParams(
221         iconAppChipView: IconAppChipView,
222         iconMenuParams: FrameLayout.LayoutParams,
223         iconMenuMargin: Int,
224         thumbnailTopMargin: Int
225     )
226 
227     fun setSplitIconParams(
228         primaryIconView: View,
229         secondaryIconView: View,
230         taskIconHeight: Int,
231         primarySnapshotWidth: Int,
232         primarySnapshotHeight: Int,
233         groupedTaskViewHeight: Int,
234         groupedTaskViewWidth: Int,
235         isRtl: Boolean,
236         deviceProfile: DeviceProfile,
237         splitConfig: SplitConfigurationOptions.SplitBounds
238     )
239 
240     /*
241      * The following two methods try to center the TaskMenuView in landscape by finding the center
242      * of the thumbnail view and then subtracting half of the taskMenu width. In this case, the
243      * taskMenu width is the same size as the thumbnail width (what got set below in
244      * getTaskMenuWidth()), so we directly use that in the calculations.
245      */
246     fun getTaskMenuX(
247         x: Float,
248         thumbnailView: View,
249         deviceProfile: DeviceProfile,
250         taskInsetMargin: Float,
251         taskViewIcon: View
252     ): Float
253 
254     fun getTaskMenuY(
255         y: Float,
256         thumbnailView: View,
257         stagePosition: Int,
258         taskMenuView: View,
259         taskInsetMargin: Float,
260         taskViewIcon: View
261     ): Float
262 
263     fun getTaskMenuWidth(
264         thumbnailView: View,
265         deviceProfile: DeviceProfile,
266         @StagePosition stagePosition: Int
267     ): Int
268 
269     fun getTaskMenuHeight(
270         taskInsetMargin: Float,
271         deviceProfile: DeviceProfile,
272         taskMenuX: Float,
273         taskMenuY: Float
274     ): Int
275 
276     /**
277      * Sets linear layout orientation for [com.android.launcher3.popup.SystemShortcut] items inside
278      * task menu view.
279      */
280     fun setTaskOptionsMenuLayoutOrientation(
281         deviceProfile: DeviceProfile,
282         taskMenuLayout: LinearLayout,
283         dividerSpacing: Int,
284         dividerDrawable: ShapeDrawable
285     )
286 
287     /**
288      * Sets layout param attributes for [com.android.launcher3.popup.SystemShortcut] child views
289      * inside task menu view.
290      */
291     fun setLayoutParamsForTaskMenuOptionItem(
292         lp: LinearLayout.LayoutParams,
293         viewGroup: LinearLayout,
294         deviceProfile: DeviceProfile
295     )
296 
297     /**
298      * Calculates the position where a Digital Wellbeing Banner should be placed on its parent
299      * TaskView.
300      *
301      * @return A Pair of Floats representing the proper x and y translations.
302      */
303     fun getDwbLayoutTranslations(
304         taskViewWidth: Int,
305         taskViewHeight: Int,
306         splitBounds: SplitConfigurationOptions.SplitBounds?,
307         deviceProfile: DeviceProfile,
308         thumbnailViews: Array<View>,
309         desiredTaskId: Int,
310         banner: View
311     ): Pair<Float, Float>
312     // The following are only used by TaskViewTouchHandler.
313 
314     /** @return Either VERTICAL or HORIZONTAL. */
315     val upDownSwipeDirection: SingleAxisSwipeDetector.Direction
316 
317     /** @return Given [.getUpDownSwipeDirection], whether POSITIVE or NEGATIVE is up. */
318     fun getUpDirection(isRtl: Boolean): Int
319 
320     /** @return Whether the displacement is going towards the top of the screen. */
321     fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean
322 
323     /** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
324     fun getTaskDragDisplacementFactor(isRtl: Boolean): Int
325 
326     /**
327      * Maps the velocity from the coordinate plane of the foreground app to that of Launcher's
328      * (which now will always be portrait)
329      */
330     fun adjustFloatingIconStartVelocity(velocity: PointF)
331 
332     /**
333      * Ensures that outStartRect left bound is within the DeviceProfile's visual boundaries
334      *
335      * @param outStartRect The start rect that will directly be modified
336      */
337     fun fixBoundsForHomeAnimStartRect(outStartRect: RectF, deviceProfile: DeviceProfile)
338 
339     /**
340      * Determine the target translation for animating the FloatingTaskView out. This value could
341      * either be an x-coordinate or a y-coordinate, depending on which way the FloatingTaskView was
342      * docked.
343      *
344      * @param floatingTask The FloatingTaskView.
345      * @param onScreenRect The current on-screen dimensions of the FloatingTaskView.
346      * @param stagePosition STAGE_POSITION_TOP_OR_LEFT or STAGE_POSITION_BOTTOM_OR_RIGHT.
347      * @param dp The device profile.
348      * @return A float. When an animation translates the FloatingTaskView to this position, it will
349      *   appear to tuck away off the edge of the screen.
350      */
351     fun getFloatingTaskOffscreenTranslationTarget(
352         floatingTask: View,
353         onScreenRect: RectF,
354         @StagePosition stagePosition: Int,
355         dp: DeviceProfile
356     ): Float
357 
358     /**
359      * Sets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
360      * either x or y), depending on how the view is oriented.
361      *
362      * @param floatingTask The FloatingTaskView to be translated.
363      * @param translation The target translation value.
364      * @param dp The current device profile.
365      */
366     fun setFloatingTaskPrimaryTranslation(floatingTask: View, translation: Float, dp: DeviceProfile)
367 
368     /**
369      * Gets the translation of a FloatingTaskView along its "slide-in/slide-out" axis (could be
370      * either x or y), depending on how the view is oriented.
371      *
372      * @param floatingTask The FloatingTaskView in question.
373      * @param dp The current device profile.
374      * @return The current translation value.
375      */
376     fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float
377 
378     fun getHandlerTypeForLogging(): LauncherAtom.TaskSwitcherContainer.OrientationHandler
379 
380     companion object {
381         @JvmField val PORTRAIT: RecentsPagedOrientationHandler = PortraitPagedViewHandler()
382         @JvmField val LANDSCAPE: RecentsPagedOrientationHandler = LandscapePagedViewHandler()
383         @JvmField val SEASCAPE: RecentsPagedOrientationHandler = SeascapePagedViewHandler()
384     }
385 }
386