• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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