• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.systemui.animation
18 
19 import android.os.Trace
20 import android.view.View
21 import java.lang.ref.WeakReference
22 
23 /**
24  * A registry to temporarily store the view being transitioned into a Dialog (using
25  * [DialogTransitionAnimator]) or an Activity (using [ActivityTransitionAnimator]).
26  */
27 class ViewTransitionRegistry : IViewTransitionRegistry {
28 
29     /**
30      * A map of a unique token to a WeakReference of the View being transitioned. WeakReference
31      * ensures that Views are garbage collected whenever they become eligible and avoid any
32      * memory leaks.
33      */
34     private val registry by lazy { mutableMapOf<ViewTransitionToken, ViewTransitionInfo>() }
35 
36     /**
37      * A [View.OnAttachStateChangeListener] to be attached to all views stored in the registry to
38      * ensure that views (and their corresponding entry) is automatically removed when the view is
39      * detached from the Window.
40      */
41     private val listener by lazy {
42         object : View.OnAttachStateChangeListener {
43             override fun onViewAttachedToWindow(view: View) {
44                 // empty
45             }
46 
47             override fun onViewDetachedFromWindow(view: View) {
48                 // if view is detached from window, remove it from registry irrespective of number
49                 // of reference held by clients/user of this registry
50                 getViewToken(view)?.let { token -> remove(token) }
51             }
52         }
53     }
54 
55     /**
56      * Creates an entry of a unique token mapped to transitioning [view] in the registry.
57      *
58      * @param view view undergoing transitions
59      * @return unique token mapped to the view being registered
60      */
61     override fun register(view: View): ViewTransitionToken {
62         // if view being registered is already present in the registry and has a unique token
63         // assigned to it, reuse that token
64         getViewToken(view)?.let { token ->
65             registry[token]?.let { info -> info.viewRefCount += 1 }
66             return token
67         }
68 
69         // token embedded as a view tag enables to use a single listener for all views
70         val token = ViewTransitionToken(view::class.java)
71         view.setTag(R.id.tag_view_transition_token, token)
72         view.addOnAttachStateChangeListener(listener)
73         registry[token] = ViewTransitionInfo(WeakReference(view))
74         onRegistryUpdate()
75 
76         return token
77     }
78 
79     /**
80      * Unregisters a view mapped to the unique [token] in the registry. This will either remove the
81      * entry entirely from registry (if the reference count of the associated view reached zero) or
82      * will decrement the reference count of the associated view in the registry.
83      *
84      * @param token unique token associated with the transitioning view
85      */
86     override fun unregister(token: ViewTransitionToken) {
87         registry[token]?.let { info ->
88             info.viewRefCount -= 1
89             if (info.viewRefCount == 0) {
90                 remove(token)
91             }
92         }
93     }
94 
95     /**
96      * Removes the entry associated with the unique [token] in the registry.
97      *
98      * @param token unique token associated with the transitioning view
99      */
100     private fun remove(token: ViewTransitionToken) {
101         registry.remove(token)?.let { removedInfo ->
102             removedInfo.viewRef.get()?.let { view ->
103                 view.removeOnAttachStateChangeListener(listener)
104                 view.setTag(R.id.tag_view_transition_token, null)
105             }
106             removedInfo.viewRef.clear()
107             onRegistryUpdate()
108         }
109     }
110 
111     /**
112      * Access a view from registry using unique [token] associated with it.
113      * WARNING - this returns a StrongReference to the View stored in the registry
114      */
115     override fun getView(token: ViewTransitionToken): View? {
116         return registry[token]?.viewRef?.get()
117     }
118 
119     /**
120      * Return token mapped to the [view], if it is present in the registry.
121      *
122      * @param view the transitioning view whose token we are requesting
123      * @return token associated with the [view] if present, else null
124      */
125     override fun getViewToken(view: View): ViewTransitionToken? {
126         // extract token from the view if it is embedded inside it as a tag
127         val token = view.getTag(R.id.tag_view_transition_token) as? ViewTransitionToken
128 
129         // this should never really happen, but if token embedded inside the view as tag, doesn't
130         // point to a valid view in the registry, remove that token (tag) from the view and registry
131         if (token != null && getView(token) == null) {
132             view.setTag(R.id.tag_view_transition_token, null)
133             remove(token)
134             return null
135         }
136 
137         return token
138     }
139 
140     /** Event call to run on registry update (on both [register] and [unregister]). */
141     override fun onRegistryUpdate() {
142         emitCountForTrace()
143     }
144 
145     /**
146      * Utility function to emit number of non-null views in the registry whenever the registry is
147      * updated (via [register] or [unregister]).
148      */
149     private fun emitCountForTrace() {
150         Trace.setCounter("transition_registry_view_count", registry.count().toLong())
151     }
152 
153     /** Information associated with each transitioning view in the registry. */
154     private data class ViewTransitionInfo(
155 
156         /** View being transitioned */
157         val viewRef: WeakReference<View>,
158 
159         /** Count of clients (users of this registry) referencing same transitioning view */
160         var viewRefCount: Int = 1
161     )
162 
163     companion object {
164         val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { ViewTransitionRegistry() }
165     }
166 }
167