1 /* <lambda>null2 * Copyright (C) 2025 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.wm.shell.desktopmode 18 19 import android.animation.Animator 20 import android.animation.AnimatorListenerAdapter 21 import android.animation.AnimatorSet 22 import android.animation.RectEvaluator 23 import android.animation.ValueAnimator 24 import android.app.ActivityManager 25 import android.content.Context 26 import android.graphics.PixelFormat 27 import android.graphics.Rect 28 import android.graphics.drawable.LayerDrawable 29 import android.view.Display 30 import android.view.SurfaceControl 31 import android.view.SurfaceControlViewHost 32 import android.view.View 33 import android.view.WindowManager 34 import android.view.WindowlessWindowManager 35 import android.view.animation.DecelerateInterpolator 36 import android.widget.FrameLayout 37 import androidx.core.animation.doOnEnd 38 import com.android.internal.annotations.VisibleForTesting 39 import com.android.window.flags.Flags 40 import com.android.wm.shell.R 41 import com.android.wm.shell.common.DisplayController 42 import com.android.wm.shell.common.DisplayLayout 43 import com.android.wm.shell.common.ShellExecutor 44 import com.android.wm.shell.common.SyncTransactionQueue 45 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType 46 import com.android.wm.shell.shared.annotations.ShellDesktopThread 47 import com.android.wm.shell.shared.annotations.ShellMainThread 48 import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper 49 import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider 50 import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory 51 import com.android.wm.shell.windowdecor.tiling.SnapEventHandler 52 53 /** 54 * Container for the view / viewhost of the indicator, ensuring it is created / animated off the 55 * main thread. 56 */ 57 @VisibleForTesting 58 class VisualIndicatorViewContainer 59 @JvmOverloads 60 constructor( 61 @ShellDesktopThread private val desktopExecutor: ShellExecutor, 62 @ShellMainThread private val mainExecutor: ShellExecutor, 63 private val indicatorBuilder: SurfaceControl.Builder, 64 private val syncQueue: SyncTransactionQueue, 65 private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = 66 object : SurfaceControlViewHostFactory {}, 67 private val bubbleBoundsProvider: BubbleDropTargetBoundsProvider?, 68 private val snapEventHandler: SnapEventHandler, 69 ) { 70 @VisibleForTesting var indicatorView: View? = null 71 // Optional extra indicator showing the outline of the bubble bar 72 private var barIndicatorView: View? = null 73 private var indicatorViewHost: SurfaceControlViewHost? = null 74 // Below variables and the SyncTransactionQueue are the only variables that should 75 // be accessed from shell main thread. Everything else should be used exclusively 76 // from the desktop thread. 77 private var indicatorLeash: SurfaceControl? = null 78 private var isReleased = false 79 80 /** Create a fullscreen indicator with no animation */ 81 @ShellMainThread createViewnull82 fun createView( 83 context: Context, 84 display: Display, 85 layout: DisplayLayout, 86 taskInfo: ActivityManager.RunningTaskInfo, 87 taskSurface: SurfaceControl, 88 ) { 89 if (isReleased) return 90 desktopExecutor.execute { 91 val resources = context.resources 92 val metrics = resources.displayMetrics 93 val screenWidth: Int 94 val screenHeight: Int 95 if (Flags.enableBugFixesForSecondaryDisplay()) { 96 screenWidth = layout.width() 97 screenHeight = layout.height() 98 } else { 99 screenWidth = metrics.widthPixels 100 screenHeight = metrics.heightPixels 101 } 102 indicatorView = 103 if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) { 104 FrameLayout(context) 105 } else { 106 View(context) 107 } 108 val leash = 109 indicatorBuilder 110 .setName("Desktop Mode Visual Indicator") 111 .setContainerLayer() 112 .setCallsite("DesktopModeVisualIndicator.createView") 113 .build() 114 val lp = 115 WindowManager.LayoutParams( 116 screenWidth, 117 screenHeight, 118 WindowManager.LayoutParams.TYPE_APPLICATION, 119 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 120 PixelFormat.TRANSPARENT, 121 ) 122 lp.title = "Desktop Mode Visual Indicator" 123 lp.setTrustedOverlay() 124 lp.inputFeatures = 125 lp.inputFeatures or WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL 126 val windowManager = 127 WindowlessWindowManager( 128 taskInfo.configuration, 129 leash, 130 /* hostInputTransferToken= */ null, 131 ) 132 indicatorViewHost = 133 surfaceControlViewHostFactory.create( 134 context, 135 display, 136 windowManager, 137 "VisualIndicatorViewContainer", 138 ) 139 indicatorView?.let { indicatorViewHost?.setView(it, lp) } 140 showIndicator(taskSurface, leash) 141 } 142 } 143 144 /** Reparent the indicator to {@code newParent}. */ reparentLeashnull145 fun reparentLeash(t: SurfaceControl.Transaction, newParent: SurfaceControl) { 146 val leash = indicatorLeash ?: return 147 t.reparent(leash, newParent) 148 } 149 showIndicatornull150 private fun showIndicator(taskSurface: SurfaceControl, leash: SurfaceControl) { 151 mainExecutor.execute { 152 indicatorLeash = leash 153 val t = SurfaceControl.Transaction() 154 t.show(indicatorLeash) 155 // We want this indicator to be behind the dragged task, but in front of all others. 156 t.setRelativeLayer(indicatorLeash, taskSurface, -1) 157 syncQueue.runInSync { transaction: SurfaceControl.Transaction -> 158 transaction.merge(t) 159 t.close() 160 } 161 } 162 } 163 164 @VisibleForTesting getIndicatorBoundsnull165 fun getIndicatorBounds(): Rect { 166 return indicatorView?.background?.getBounds() ?: Rect() 167 } 168 169 /** 170 * Takes existing indicator and animates it to bounds reflecting a new indicator type. Should 171 * only be called from the main thread. 172 */ 173 @ShellMainThread transitionIndicatornull174 fun transitionIndicator( 175 taskInfo: ActivityManager.RunningTaskInfo, 176 displayController: DisplayController, 177 currentType: IndicatorType, 178 newType: IndicatorType, 179 ) { 180 if (currentType == newType || isReleased) return 181 desktopExecutor.execute { 182 val layout = 183 displayController.getDisplayLayout(taskInfo.displayId) 184 ?: error("Expected to find DisplayLayout for taskId${taskInfo.taskId}.") 185 if (currentType == IndicatorType.NO_INDICATOR) { 186 fadeInIndicatorInternal(layout, newType, taskInfo.displayId, snapEventHandler) 187 } else if (newType == IndicatorType.NO_INDICATOR) { 188 fadeOutIndicator( 189 layout, 190 currentType, 191 /* finishCallback= */ null, 192 taskInfo.displayId, 193 snapEventHandler, 194 ) 195 } else { 196 val animStartType = IndicatorType.valueOf(currentType.name) 197 val indicator = indicatorView ?: return@execute 198 var animator: Animator = 199 VisualIndicatorAnimator.animateIndicatorType( 200 indicator, 201 layout, 202 animStartType, 203 newType, 204 bubbleBoundsProvider, 205 taskInfo.displayId, 206 snapEventHandler, 207 ) 208 if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) { 209 if (currentType.isBubbleType() || newType.isBubbleType()) { 210 animator = addBarIndicatorAnimation(animator, currentType, newType) 211 } 212 } 213 animator.start() 214 } 215 } 216 } 217 addBarIndicatorAnimationnull218 private fun addBarIndicatorAnimation( 219 visualIndicatorAnimator: Animator, 220 currentType: IndicatorType, 221 newType: IndicatorType, 222 ): Animator { 223 if (newType.isBubbleType()) { 224 getOrCreateBubbleBarIndicator(newType)?.let { bar -> 225 return AnimatorSet().apply { 226 playTogether(visualIndicatorAnimator, fadeBarIndicatorIn(bar)) 227 } 228 } 229 } 230 if (currentType.isBubbleType()) { 231 barIndicatorView?.let { bar -> 232 barIndicatorView = null 233 return AnimatorSet().apply { 234 playTogether(visualIndicatorAnimator, fadeBarIndicatorOut(bar)) 235 } 236 } 237 } 238 return visualIndicatorAnimator 239 } 240 241 /** 242 * Fade indicator in as provided type. 243 * 244 * Animator fades the indicator in while expanding the bounds outwards. 245 */ fadeInIndicatornull246 fun fadeInIndicator(layout: DisplayLayout, type: IndicatorType, displayId: Int) { 247 if (isReleased) return 248 desktopExecutor.execute { 249 fadeInIndicatorInternal(layout, type, displayId, snapEventHandler) 250 } 251 } 252 253 /** 254 * Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards. 255 */ 256 @VisibleForTesting fadeInIndicatorInternalnull257 fun fadeInIndicatorInternal( 258 layout: DisplayLayout, 259 type: IndicatorType, 260 displayId: Int, 261 snapEventHandler: SnapEventHandler, 262 ) { 263 desktopExecutor.assertCurrentThread() 264 indicatorView?.let { indicator -> 265 indicator.setBackgroundResource(R.drawable.desktop_windowing_transition_background) 266 var animator: Animator = 267 VisualIndicatorAnimator.fadeBoundsIn( 268 indicator, 269 type, 270 layout, 271 bubbleBoundsProvider, 272 displayId, 273 snapEventHandler, 274 ) 275 if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) { 276 animator = addBarIndicatorAnimation(animator, IndicatorType.NO_INDICATOR, type) 277 } 278 animator.start() 279 } 280 } 281 282 /** 283 * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds. 284 * 285 * @param finishCallback called when animation ends or gets cancelled 286 */ fadeOutIndicatornull287 fun fadeOutIndicator( 288 layout: DisplayLayout, 289 currentType: IndicatorType, 290 finishCallback: Runnable?, 291 displayId: Int, 292 snapEventHandler: SnapEventHandler, 293 ) { 294 if (currentType == IndicatorType.NO_INDICATOR) { 295 // In rare cases, fade out can be requested before the indicator has determined its 296 // initial type and started animating in. In this case, no animator is needed. 297 finishCallback?.run() 298 return 299 } 300 desktopExecutor.execute { 301 indicatorView?.let { 302 val animStartType = IndicatorType.valueOf(currentType.name) 303 var animator: Animator = 304 VisualIndicatorAnimator.fadeBoundsOut( 305 it, 306 animStartType, 307 layout, 308 bubbleBoundsProvider, 309 displayId, 310 snapEventHandler, 311 ) 312 if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) { 313 animator = 314 addBarIndicatorAnimation(animator, currentType, IndicatorType.NO_INDICATOR) 315 } 316 animator.addListener( 317 object : AnimatorListenerAdapter() { 318 override fun onAnimationEnd(animation: Animator) { 319 if (finishCallback != null) { 320 mainExecutor.execute(finishCallback) 321 } 322 } 323 } 324 ) 325 animator.start() 326 } 327 } 328 } 329 330 /** Release the indicator and its components when it is no longer needed. */ 331 @ShellMainThread releaseVisualIndicatornull332 fun releaseVisualIndicator() { 333 if (isReleased) return 334 desktopExecutor.execute { 335 indicatorViewHost?.release() 336 indicatorViewHost = null 337 } 338 indicatorLeash?.let { 339 val tx = SurfaceControl.Transaction() 340 tx.remove(it) 341 indicatorLeash = null 342 syncQueue.runInSync { transaction: SurfaceControl.Transaction -> 343 transaction.merge(tx) 344 tx.close() 345 } 346 } 347 isReleased = true 348 } 349 getOrCreateBubbleBarIndicatornull350 private fun getOrCreateBubbleBarIndicator(type: IndicatorType): View? { 351 val container = indicatorView as? FrameLayout ?: return null 352 val onLeft = type == IndicatorType.TO_BUBBLE_LEFT_INDICATOR 353 val bounds = bubbleBoundsProvider?.getBarDropTargetBounds(onLeft) ?: return null 354 val lp = FrameLayout.LayoutParams(bounds.width(), bounds.height()) 355 lp.leftMargin = bounds.left 356 lp.topMargin = bounds.top 357 if (barIndicatorView == null) { 358 val indicator = View(container.context) 359 indicator.setBackgroundResource(R.drawable.desktop_windowing_transition_background) 360 container.addView(indicator, lp) 361 barIndicatorView = indicator 362 } else { 363 barIndicatorView?.layoutParams = lp 364 } 365 return barIndicatorView 366 } 367 fadeBarIndicatorInnull368 private fun fadeBarIndicatorIn(barIndicator: View): Animator { 369 // Use layout bounds as the end bounds in case the view has not been laid out yet 370 val lp = barIndicator.layoutParams 371 val endBounds = Rect(0, 0, lp.width, lp.height) 372 return VisualIndicatorAnimator.fadeBoundsIn(barIndicator, endBounds) 373 } 374 fadeBarIndicatorOutnull375 private fun fadeBarIndicatorOut(barIndicator: View): Animator { 376 val startBounds = Rect(0, 0, barIndicator.width, barIndicator.height) 377 val barAnimator = VisualIndicatorAnimator.fadeBoundsOut(barIndicator, startBounds) 378 barAnimator.doOnEnd { (indicatorView as? FrameLayout)?.removeView(barIndicator) } 379 return barAnimator 380 } 381 382 /** 383 * Animator for Desktop Mode transitions which supports bounds and alpha animation. Functions 384 * should only be called from the desktop executor. 385 */ 386 @VisibleForTesting 387 class VisualIndicatorAnimator(view: View, startBounds: Rect, endBounds: Rect) : 388 ValueAnimator() { 389 /** 390 * Determines how this animator will interact with the view's alpha: Fade in, fade out, or 391 * no change to alpha 392 */ 393 private enum class AlphaAnimType { 394 ALPHA_FADE_IN_ANIM, 395 ALPHA_FADE_OUT_ANIM, 396 ALPHA_NO_CHANGE_ANIM, 397 } 398 399 private val indicatorView: View = view 400 @VisibleForTesting val indicatorStartBounds = Rect(startBounds) 401 @VisibleForTesting val indicatorEndBounds = endBounds 402 private val mRectEvaluator: RectEvaluator 403 404 init { 405 setFloatValues(0f, 1f) 406 mRectEvaluator = RectEvaluator(Rect()) 407 } 408 409 /** 410 * Update bounds of view based on current animation fraction. Use of delta is to animate 411 * bounds independently, in case we need to run multiple animations simultaneously. 412 * 413 * @param fraction fraction to use, compared against previous fraction 414 * @param view the view to update 415 */ 416 @ShellDesktopThread updateBoundsnull417 private fun updateBounds(fraction: Float, view: View?) { 418 if (indicatorStartBounds == indicatorEndBounds) { 419 return 420 } 421 val currentBounds = 422 mRectEvaluator.evaluate(fraction, indicatorStartBounds, indicatorEndBounds) 423 view?.background?.bounds = currentBounds 424 } 425 426 /** 427 * Fade in the fullscreen indicator 428 * 429 * @param fraction current animation fraction 430 */ 431 @ShellDesktopThread updateIndicatorAlphanull432 private fun updateIndicatorAlpha(fraction: Float, view: View?) { 433 val drawable = view?.background as LayerDrawable 434 drawable.findDrawableByLayerId(R.id.indicator_stroke).alpha = 435 (MAXIMUM_OPACITY * fraction).toInt() 436 drawable.findDrawableByLayerId(R.id.indicator_solid).alpha = 437 (MAXIMUM_OPACITY * fraction * INDICATOR_FINAL_OPACITY).toInt() 438 } 439 440 companion object { 441 private const val FULLSCREEN_INDICATOR_DURATION = 200 442 private const val FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f 443 private const val INDICATOR_FINAL_OPACITY = 0.35f 444 private const val MAXIMUM_OPACITY = 255 445 446 @ShellDesktopThread fadeBoundsInnull447 fun fadeBoundsIn( 448 view: View, 449 type: IndicatorType, 450 displayLayout: DisplayLayout, 451 bubbleBoundsProvider: BubbleDropTargetBoundsProvider?, 452 displayId: Int, 453 snapEventHandler: SnapEventHandler, 454 ): VisualIndicatorAnimator { 455 val endBounds = 456 getIndicatorBounds( 457 displayLayout, 458 type, 459 bubbleBoundsProvider, 460 displayId, 461 snapEventHandler, 462 ) 463 return fadeBoundsIn(view, endBounds) 464 } 465 466 @ShellDesktopThread fadeBoundsInnull467 fun fadeBoundsIn(view: View, endBounds: Rect): VisualIndicatorAnimator { 468 val startBounds = getMinBounds(endBounds) 469 view.background.bounds = startBounds 470 val animator = VisualIndicatorAnimator(view, startBounds, endBounds) 471 animator.interpolator = DecelerateInterpolator() 472 setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_IN_ANIM) 473 return animator 474 } 475 476 @ShellDesktopThread fadeBoundsOutnull477 fun fadeBoundsOut( 478 view: View, 479 type: IndicatorType, 480 displayLayout: DisplayLayout, 481 bubbleBoundsProvider: BubbleDropTargetBoundsProvider?, 482 displayId: Int, 483 snapEventHandler: SnapEventHandler, 484 ): VisualIndicatorAnimator { 485 val startBounds = 486 getIndicatorBounds( 487 displayLayout, 488 type, 489 bubbleBoundsProvider, 490 displayId, 491 snapEventHandler, 492 ) 493 return fadeBoundsOut(view, startBounds) 494 } 495 496 @ShellDesktopThread fadeBoundsOutnull497 fun fadeBoundsOut(view: View, startBounds: Rect): VisualIndicatorAnimator { 498 val endBounds = getMinBounds(startBounds) 499 view.background.bounds = startBounds 500 val animator = VisualIndicatorAnimator(view, startBounds, endBounds) 501 animator.interpolator = DecelerateInterpolator() 502 setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_OUT_ANIM) 503 return animator 504 } 505 506 /** 507 * Create animator for visual indicator changing type (i.e., fullscreen to freeform, 508 * freeform to split, etc.) 509 * 510 * @param view the view for this indicator 511 * @param displayLayout information about the display the transitioning task is 512 * currently on 513 * @param origType the original indicator type 514 * @param newType the new indicator type 515 * @param desktopExecutor: the executor for the ShellDesktopThread; should be the only 516 * thread this function runs on 517 */ 518 @ShellDesktopThread animateIndicatorTypenull519 fun animateIndicatorType( 520 view: View, 521 displayLayout: DisplayLayout, 522 origType: IndicatorType, 523 newType: IndicatorType, 524 bubbleBoundsProvider: BubbleDropTargetBoundsProvider?, 525 displayId: Int, 526 snapEventHandler: SnapEventHandler, 527 ): VisualIndicatorAnimator { 528 val startBounds = 529 getIndicatorBounds( 530 displayLayout, 531 origType, 532 bubbleBoundsProvider, 533 displayId, 534 snapEventHandler, 535 ) 536 val endBounds = 537 getIndicatorBounds( 538 displayLayout, 539 newType, 540 bubbleBoundsProvider, 541 displayId, 542 snapEventHandler, 543 ) 544 val animator = VisualIndicatorAnimator(view, startBounds, endBounds) 545 animator.interpolator = DecelerateInterpolator() 546 setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_NO_CHANGE_ANIM) 547 return animator 548 } 549 550 /** Calculates the bounds the indicator should have when fully faded in. */ getIndicatorBoundsnull551 private fun getIndicatorBounds( 552 layout: DisplayLayout, 553 type: IndicatorType, 554 bubbleBoundsProvider: BubbleDropTargetBoundsProvider?, 555 displayId: Int, 556 snapEventHandler: SnapEventHandler, 557 ): Rect { 558 val desktopStableBounds = Rect() 559 layout.getStableBounds(desktopStableBounds) 560 val padding = desktopStableBounds.top 561 when (type) { 562 IndicatorType.TO_FULLSCREEN_INDICATOR -> { 563 desktopStableBounds.top += padding 564 desktopStableBounds.bottom -= padding 565 desktopStableBounds.left += padding 566 desktopStableBounds.right -= padding 567 return desktopStableBounds 568 } 569 570 IndicatorType.TO_DESKTOP_INDICATOR -> { 571 val adjustmentPercentage = 572 (1f - DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE) 573 return Rect( 574 (adjustmentPercentage * desktopStableBounds.width() / 2).toInt(), 575 (adjustmentPercentage * desktopStableBounds.height() / 2).toInt(), 576 (desktopStableBounds.width() - 577 (adjustmentPercentage * desktopStableBounds.width() / 2)) 578 .toInt(), 579 (desktopStableBounds.height() - 580 (adjustmentPercentage * desktopStableBounds.height() / 2)) 581 .toInt(), 582 ) 583 } 584 585 IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { 586 val currentLeftBounds = snapEventHandler.getLeftSnapBoundsIfTiled(displayId) 587 return Rect( 588 padding, 589 padding, 590 currentLeftBounds.right - padding, 591 desktopStableBounds.height(), 592 ) 593 } 594 IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { 595 val currentRightBounds = 596 snapEventHandler.getRightSnapBoundsIfTiled(displayId) 597 return Rect( 598 currentRightBounds.left + padding, 599 padding, 600 desktopStableBounds.width() - padding, 601 desktopStableBounds.height(), 602 ) 603 } 604 IndicatorType.TO_BUBBLE_LEFT_INDICATOR -> 605 return bubbleBoundsProvider?.getBubbleBarExpandedViewDropTargetBounds( 606 /* onLeft= */ true 607 ) ?: Rect() 608 IndicatorType.TO_BUBBLE_RIGHT_INDICATOR -> 609 return bubbleBoundsProvider?.getBubbleBarExpandedViewDropTargetBounds( 610 /* onLeft= */ false 611 ) ?: Rect() 612 else -> throw IllegalArgumentException("Invalid indicator type provided.") 613 } 614 } 615 616 /** Add necessary listener for animation of indicator */ setupIndicatorAnimationnull617 private fun setupIndicatorAnimation( 618 animator: VisualIndicatorAnimator, 619 animType: AlphaAnimType, 620 ) { 621 animator.addUpdateListener { a: ValueAnimator -> 622 animator.updateBounds(a.animatedFraction, animator.indicatorView) 623 if (animType == AlphaAnimType.ALPHA_FADE_IN_ANIM) { 624 animator.updateIndicatorAlpha(a.animatedFraction, animator.indicatorView) 625 } else if (animType == AlphaAnimType.ALPHA_FADE_OUT_ANIM) { 626 animator.updateIndicatorAlpha( 627 1 - a.animatedFraction, 628 animator.indicatorView, 629 ) 630 } 631 } 632 animator.addListener( 633 object : AnimatorListenerAdapter() { 634 override fun onAnimationEnd(animation: Animator) { 635 animator.indicatorView.background.bounds = animator.indicatorEndBounds 636 } 637 } 638 ) 639 animator.setDuration(FULLSCREEN_INDICATOR_DURATION.toLong()) 640 } 641 642 /** 643 * Return the minimum bounds of a visual indicator, to be used at the end of fading out 644 * and the start of fading in. 645 */ getMinBoundsnull646 private fun getMinBounds(maxBounds: Rect): Rect { 647 return Rect( 648 (maxBounds.left + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.width())) 649 .toInt(), 650 (maxBounds.top + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.height())) 651 .toInt(), 652 (maxBounds.right - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.width())) 653 .toInt(), 654 (maxBounds.bottom - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.height())) 655 .toInt(), 656 ) 657 } 658 } 659 } 660 IndicatorTypenull661 private fun IndicatorType.isBubbleType(): Boolean { 662 return this == IndicatorType.TO_BUBBLE_LEFT_INDICATOR || 663 this == IndicatorType.TO_BUBBLE_RIGHT_INDICATOR 664 } 665 } 666