1 /* 2 * Copyright (C) 2022 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.systemui.animation 18 19 import android.view.GhostView 20 import android.view.View 21 import android.view.ViewGroup 22 import android.view.ViewRootImpl 23 import com.android.internal.jank.InteractionJankMonitor 24 25 /** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */ 26 class ViewDialogLaunchAnimatorController 27 internal constructor( 28 private val source: View, 29 override val cuj: DialogCuj?, 30 ) : DialogLaunchAnimator.Controller { 31 override val viewRoot: ViewRootImpl? 32 get() = source.viewRootImpl 33 34 override val sourceIdentity: Any = source 35 36 override fun startDrawingInOverlayOf(viewGroup: ViewGroup) { 37 // Delay the calls to `source.setVisibility()` during the animation. This must be called 38 // before `GhostView.addGhost()` is called because the latter will change the *transition* 39 // visibility, which won't be blocked and will affect the normal View visibility that is 40 // saved by `setShouldBlockVisibilityChanges()` for a later restoration. 41 (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true) 42 43 // Create a temporary ghost of the source (which will make it invisible) and add it 44 // to the host dialog. 45 GhostView.addGhost(source, viewGroup) 46 } 47 48 override fun stopDrawingInOverlay() { 49 // Note: here we should remove the ghost from the overlay, but in practice this is 50 // already done by the launch controller created below. 51 52 if (source is LaunchableView) { 53 // Make sure we allow the source to change its visibility again and restore its previous 54 // value. 55 source.setShouldBlockVisibilityChanges(false) 56 } else { 57 // We made the source invisible earlier, so let's make it visible again. 58 source.visibility = View.VISIBLE 59 } 60 } 61 62 override fun createLaunchController(): LaunchAnimator.Controller { 63 val delegate = GhostedViewLaunchAnimatorController(source) 64 return object : LaunchAnimator.Controller by delegate { 65 override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { 66 // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another 67 // ghost (that ghosts only the source content, and not its background) will 68 // be added right after this by the delegate and will be animated. 69 GhostView.removeGhost(source) 70 delegate.onLaunchAnimationStart(isExpandingFullyAbove) 71 } 72 73 override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { 74 delegate.onLaunchAnimationEnd(isExpandingFullyAbove) 75 76 // At this point the view visibility is restored by the delegate, so we delay the 77 // visibility changes again and make it invisible while the dialog is shown. 78 if (source is LaunchableView) { 79 source.setShouldBlockVisibilityChanges(true) 80 source.setTransitionVisibility(View.INVISIBLE) 81 } else { 82 source.visibility = View.INVISIBLE 83 } 84 } 85 } 86 } 87 88 override fun createExitController(): LaunchAnimator.Controller { 89 return GhostedViewLaunchAnimatorController(source) 90 } 91 92 override fun shouldAnimateExit(): Boolean { 93 // The source should be invisible by now, if it's not then something else changed 94 // its visibility and we probably don't want to run the animation. 95 if (source.visibility != View.INVISIBLE) { 96 return false 97 } 98 99 return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true) 100 } 101 102 override fun onExitAnimationCancelled() { 103 if (source is LaunchableView) { 104 // Make sure we allow the source to change its visibility again. 105 source.setShouldBlockVisibilityChanges(false) 106 } else { 107 // If the view is invisible it's probably because of us, so we make it visible 108 // again. 109 if (source.visibility == View.INVISIBLE) { 110 source.visibility = View.VISIBLE 111 } 112 } 113 } 114 115 override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? { 116 val type = cuj?.cujType ?: return null 117 return InteractionJankMonitor.Configuration.Builder.withView(type, source) 118 } 119 } 120