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