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 java.util.function.Supplier 25 import kotlinx.coroutines.Job 26 import kotlinx.coroutines.delay 27 import kotlinx.coroutines.launch 28 29 /** 30 * A helper class to track app's readiness for the scene transition animation. The app is ready when 31 * 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>, 49 sharedElements: MutableMap<String, View> 50 ) { 51 this@EnterTransitionAnimationDelegate.onMapSharedElements(names, sharedElements) 52 } 53 } 54 ) 55 } 56 postponeTransitionnull57 fun postponeTransition() { 58 activity.postponeEnterTransition() 59 timeoutJob = 60 activity.lifecycleScope.launch { 61 delay(activity.resources.getInteger(R.integer.config_shortAnimTime).toLong()) 62 onTimeout() 63 } 64 } 65 onTimeoutnull66 private fun onTimeout() { 67 // We only mark the preview readiness and not the offset readiness 68 // (see [#markOffsetCalculated()]) as this is what legacy logic, effectively, did. We might 69 // want to review that aspect separately. 70 onAllTransitionElementsReady() 71 } 72 onTransitionElementReadynull73 override fun onTransitionElementReady(name: String) { 74 transitionElements.add(name) 75 } 76 onAllTransitionElementsReadynull77 override fun onAllTransitionElementsReady() { 78 timeoutJob?.cancel() 79 if (!previewReady) { 80 previewReady = true 81 maybeStartListenForLayout() 82 } 83 } 84 markOffsetCalculatednull85 fun markOffsetCalculated() { 86 if (!offsetCalculated) { 87 offsetCalculated = true 88 maybeStartListenForLayout() 89 } 90 } 91 onMapSharedElementsnull92 private fun onMapSharedElements( 93 names: MutableList<String>, 94 sharedElements: MutableMap<String, View> 95 ) { 96 names.removeAll { !transitionElements.contains(it) } 97 sharedElements.entries.removeAll { !transitionElements.contains(it.key) } 98 } 99 maybeStartListenForLayoutnull100 private fun maybeStartListenForLayout() { 101 val drawer = transitionTargetSupplier.get() 102 if (previewReady && offsetCalculated && drawer != null) { 103 if (drawer.isInLayout) { 104 startPostponedEnterTransition() 105 } else { 106 drawer.addOnLayoutChangeListener(this) 107 drawer.requestLayout() 108 } 109 } 110 } 111 onLayoutChangenull112 override fun onLayoutChange( 113 v: View, 114 left: Int, 115 top: Int, 116 right: Int, 117 bottom: Int, 118 oldLeft: Int, 119 oldTop: Int, 120 oldRight: Int, 121 oldBottom: Int 122 ) { 123 v.removeOnLayoutChangeListener(this) 124 startPostponedEnterTransition() 125 } 126 startPostponedEnterTransitionnull127 private fun startPostponedEnterTransition() { 128 if (transitionElements.isNotEmpty() && activity.isActivityTransitionRunning) { 129 // Disable the window animations as it interferes with the transition animation. 130 activity.window.setWindowAnimations(0) 131 } 132 activity.startPostponedEnterTransition() 133 } 134 } 135