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