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 package com.android.intentresolver 17 18 import android.app.SharedElementCallback 19 import android.view.View 20 import androidx.activity.ComponentActivity 21 import androidx.lifecycle.lifecycleScope 22 import com.android.intentresolver.widget.ImagePreviewView.TransitionElementStatusCallback 23 import com.android.internal.annotations.VisibleForTesting 24 import kotlinx.coroutines.Job 25 import kotlinx.coroutines.delay 26 import kotlinx.coroutines.launch 27 import java.util.function.Supplier 28 29 /** 30 * A helper class to track app's readiness for the scene transition animation. 31 * The app is ready when both the image is laid out and the drawer offset is calculated. 32 */ 33 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 34 class EnterTransitionAnimationDelegate( 35 private val activity: ComponentActivity, 36 private val transitionTargetSupplier: Supplier<View?>, 37 ) : View.OnLayoutChangeListener, TransitionElementStatusCallback { 38 39 private val transitionElements = HashSet<String>() 40 private var previewReady = false 41 private var offsetCalculated = false 42 private var timeoutJob: Job? = null 43 44 init { 45 activity.setEnterSharedElementCallback( 46 object : SharedElementCallback() { onMapSharedElementsnull47 override fun onMapSharedElements( 48 names: MutableList<String>, sharedElements: MutableMap<String, View> 49 ) { 50 this@EnterTransitionAnimationDelegate.onMapSharedElements( 51 names, sharedElements 52 ) 53 } 54 }) 55 } 56 postponeTransitionnull57 fun postponeTransition() { 58 activity.postponeEnterTransition() 59 timeoutJob = activity.lifecycleScope.launch { 60 delay(activity.resources.getInteger(R.integer.config_shortAnimTime).toLong()) 61 onTimeout() 62 } 63 } 64 onTimeoutnull65 private fun onTimeout() { 66 // We only mark the preview readiness and not the offset readiness 67 // (see [#markOffsetCalculated()]) as this is what legacy logic, effectively, did. We might 68 // want to review that aspect separately. 69 onAllTransitionElementsReady() 70 } 71 onTransitionElementReadynull72 override fun onTransitionElementReady(name: String) { 73 transitionElements.add(name) 74 } 75 onAllTransitionElementsReadynull76 override fun onAllTransitionElementsReady() { 77 timeoutJob?.cancel() 78 if (!previewReady) { 79 previewReady = true 80 maybeStartListenForLayout() 81 } 82 } 83 markOffsetCalculatednull84 fun markOffsetCalculated() { 85 if (!offsetCalculated) { 86 offsetCalculated = true 87 maybeStartListenForLayout() 88 } 89 } 90 onMapSharedElementsnull91 private fun onMapSharedElements( 92 names: MutableList<String>, 93 sharedElements: MutableMap<String, View> 94 ) { 95 names.removeAll { !transitionElements.contains(it) } 96 sharedElements.entries.removeAll { !transitionElements.contains(it.key) } 97 } 98 maybeStartListenForLayoutnull99 private fun maybeStartListenForLayout() { 100 val drawer = transitionTargetSupplier.get() 101 if (previewReady && offsetCalculated && drawer != null) { 102 if (drawer.isInLayout) { 103 startPostponedEnterTransition() 104 } else { 105 drawer.addOnLayoutChangeListener(this) 106 drawer.requestLayout() 107 } 108 } 109 } 110 onLayoutChangenull111 override fun onLayoutChange( 112 v: View, 113 left: Int, top: Int, right: Int, bottom: Int, 114 oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int 115 ) { 116 v.removeOnLayoutChangeListener(this) 117 startPostponedEnterTransition() 118 } 119 startPostponedEnterTransitionnull120 private fun startPostponedEnterTransition() { 121 if (transitionElements.isNotEmpty() && activity.isActivityTransitionRunning) { 122 // Disable the window animations as it interferes with the transition animation. 123 activity.window.setWindowAnimations(0) 124 } 125 activity.startPostponedEnterTransition() 126 } 127 } 128