Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit eeab7d36 authored by Bharat Singh's avatar Bharat Singh Committed by Android (Google) Code Review
Browse files

Merge "[AnimLib] Rename interfaces related to view transition registry" into main

parents 0655140d 7aac54ee
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -77,10 +77,10 @@ constructor(
    private var interactionJankMonitor: InteractionJankMonitor =
        InteractionJankMonitor.getInstance(),

    /** [ViewTransitionRegistry] to store the mapping of transitioning view and its token */
    private val transitionRegistry: IViewTransitionRegistry? =
    /** [ViewTransitionRegistryImpl] to store the mapping of transitioning view and its token */
    private val transitionRegistry: ViewTransitionRegistry? =
        if (Flags.decoupleViewControllerInAnimlib()) {
            ViewTransitionRegistry.instance
            ViewTransitionRegistryImpl.instance
        } else {
            null
        },
+0 −56
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.animation

import android.view.View

/** Represents a Registry for holding a transitioning view mapped to a token */
interface IViewTransitionRegistry {

    /**
     * Registers the transitioning [view] mapped to returned token
     *
     * @param view The view undergoing transition
     * @return token mapped to the transitioning view
     */
    fun register(view: View): ViewTransitionToken

    /**
     * Unregisters the transitioned view from its corresponding [token]
     *
     * @param token The token corresponding to the transitioning view
     */
    fun unregister(token: ViewTransitionToken)

    /**
     * Extracts a transitioning view from registry using its corresponding [token]
     *
     * @param token The token corresponding to the transitioning view
     */
    fun getView(token: ViewTransitionToken): View?

    /**
     * Return token mapped to the [view], if it is present in the registry
     *
     * @param view the transitioning view whose token we are requesting
     * @return token associated with the [view] if present, else null
     */
    fun getViewToken(view: View): ViewTransitionToken?

    /** Event call to run on registry update (on both [register] and [unregister]) */
    fun onRegistryUpdate()
}
+16 −126
Original line number Diff line number Diff line
@@ -16,151 +16,41 @@

package com.android.systemui.animation

import android.os.Trace
import android.view.View
import java.lang.ref.WeakReference

/**
 * A registry to temporarily store the view being transitioned into a Dialog (using
 * [DialogTransitionAnimator]) or an Activity (using [ActivityTransitionAnimator]).
 */
class ViewTransitionRegistry : IViewTransitionRegistry {

    /**
     * A map of a unique token to a WeakReference of the View being transitioned. WeakReference
     * ensures that Views are garbage collected whenever they become eligible and avoid any
     * memory leaks.
     */
    private val registry by lazy { mutableMapOf<ViewTransitionToken, ViewTransitionInfo>() }
/** Represents a Registry for holding a transitioning view mapped to a token */
interface ViewTransitionRegistry {

    /**
     * A [View.OnAttachStateChangeListener] to be attached to all views stored in the registry to
     * ensure that views (and their corresponding entry) is automatically removed when the view is
     * detached from the Window.
     */
    private val listener by lazy {
        object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(view: View) {
                // empty
            }

            override fun onViewDetachedFromWindow(view: View) {
                // if view is detached from window, remove it from registry irrespective of number
                // of reference held by clients/user of this registry
                getViewToken(view)?.let { token -> remove(token) }
            }
        }
    }

    /**
     * Creates an entry of a unique token mapped to transitioning [view] in the registry.
     * Registers the transitioning [view] mapped to returned token
     *
     * @param view view undergoing transitions
     * @return unique token mapped to the view being registered
     * @param view The view undergoing transition
     * @return token mapped to the transitioning view
     */
    override fun register(view: View): ViewTransitionToken {
        // if view being registered is already present in the registry and has a unique token
        // assigned to it, reuse that token
        getViewToken(view)?.let { token ->
            registry[token]?.let { info -> info.viewRefCount += 1 }
            return token
        }

        // token embedded as a view tag enables to use a single listener for all views
        val token = ViewTransitionToken(view::class.java)
        view.setTag(R.id.tag_view_transition_token, token)
        view.addOnAttachStateChangeListener(listener)
        registry[token] = ViewTransitionInfo(WeakReference(view))
        onRegistryUpdate()

        return token
    }
    fun register(view: View): ViewTransitionToken

    /**
     * Unregisters a view mapped to the unique [token] in the registry. This will either remove the
     * entry entirely from registry (if the reference count of the associated view reached zero) or
     * will decrement the reference count of the associated view in the registry.
     * Unregisters the transitioned view from its corresponding [token]
     *
     * @param token unique token associated with the transitioning view
     * @param token The token corresponding to the transitioning view
     */
    override fun unregister(token: ViewTransitionToken) {
        registry[token]?.let { info ->
            info.viewRefCount -= 1
            if (info.viewRefCount == 0) {
                remove(token)
            }
        }
    }
    fun unregister(token: ViewTransitionToken)

    /**
     * Removes the entry associated with the unique [token] in the registry.
     * Extracts a transitioning view from registry using its corresponding [token]
     *
     * @param token unique token associated with the transitioning view
     * @param token The token corresponding to the transitioning view
     */
    private fun remove(token: ViewTransitionToken) {
        registry.remove(token)?.let { removedInfo ->
            removedInfo.viewRef.get()?.let { view ->
                view.removeOnAttachStateChangeListener(listener)
                view.setTag(R.id.tag_view_transition_token, null)
            }
            removedInfo.viewRef.clear()
            onRegistryUpdate()
        }
    }

    /**
     * Access a view from registry using unique [token] associated with it.
     * WARNING - this returns a StrongReference to the View stored in the registry
     */
    override fun getView(token: ViewTransitionToken): View? {
        return registry[token]?.viewRef?.get()
    }
    fun getView(token: ViewTransitionToken): View?

    /**
     * Return token mapped to the [view], if it is present in the registry.
     * Return token mapped to the [view], if it is present in the registry
     *
     * @param view the transitioning view whose token we are requesting
     * @return token associated with the [view] if present, else null
     */
    override fun getViewToken(view: View): ViewTransitionToken? {
        // extract token from the view if it is embedded inside it as a tag
        val token = view.getTag(R.id.tag_view_transition_token) as? ViewTransitionToken

        // this should never really happen, but if token embedded inside the view as tag, doesn't
        // point to a valid view in the registry, remove that token (tag) from the view and registry
        if (token != null && getView(token) == null) {
            view.setTag(R.id.tag_view_transition_token, null)
            remove(token)
            return null
        }

        return token
    }

    /** Event call to run on registry update (on both [register] and [unregister]). */
    override fun onRegistryUpdate() {
        emitCountForTrace()
    }

    /**
     * Utility function to emit number of non-null views in the registry whenever the registry is
     * updated (via [register] or [unregister]).
     */
    private fun emitCountForTrace() {
        Trace.setCounter("transition_registry_view_count", registry.count().toLong())
    }

    /** Information associated with each transitioning view in the registry. */
    private data class ViewTransitionInfo(

        /** View being transitioned */
        val viewRef: WeakReference<View>,
    fun getViewToken(view: View): ViewTransitionToken?

        /** Count of clients (users of this registry) referencing same transitioning view */
        var viewRefCount: Int = 1
    )

    companion object {
        val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { ViewTransitionRegistry() }
    }
    /** Event call to run on registry update (on both [register] and [unregister]) */
    fun onRegistryUpdate()
}
+166 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.animation

import android.os.Trace
import android.view.View
import java.lang.ref.WeakReference

/**
 * A registry to temporarily store the view being transitioned into a Dialog (using
 * [DialogTransitionAnimator]) or an Activity (using [ActivityTransitionAnimator]).
 */
class ViewTransitionRegistryImpl : ViewTransitionRegistry {

    /**
     * A map of a unique token to a WeakReference of the View being transitioned. WeakReference
     * ensures that Views are garbage collected whenever they become eligible and avoid any memory
     * leaks.
     */
    private val registry by lazy { mutableMapOf<ViewTransitionToken, ViewTransitionInfo>() }

    /**
     * A [View.OnAttachStateChangeListener] to be attached to all views stored in the registry to
     * ensure that views (and their corresponding entry) is automatically removed when the view is
     * detached from the Window.
     */
    private val listener by lazy {
        object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(view: View) {
                // empty
            }

            override fun onViewDetachedFromWindow(view: View) {
                // if view is detached from window, remove it from registry irrespective of number
                // of reference held by clients/user of this registry
                getViewToken(view)?.let { token -> remove(token) }
            }
        }
    }

    /**
     * Creates an entry of a unique token mapped to transitioning [view] in the registry.
     *
     * @param view view undergoing transitions
     * @return unique token mapped to the view being registered
     */
    override fun register(view: View): ViewTransitionToken {
        // if view being registered is already present in the registry and has a unique token
        // assigned to it, reuse that token
        getViewToken(view)?.let { token ->
            registry[token]?.let { info -> info.viewRefCount += 1 }
            return token
        }

        // token embedded as a view tag enables to use a single listener for all views
        val token = ViewTransitionToken(view::class.java)
        view.setTag(R.id.tag_view_transition_token, token)
        view.addOnAttachStateChangeListener(listener)
        registry[token] = ViewTransitionInfo(WeakReference(view))
        onRegistryUpdate()

        return token
    }

    /**
     * Unregisters a view mapped to the unique [token] in the registry. This will either remove the
     * entry entirely from registry (if the reference count of the associated view reached zero) or
     * will decrement the reference count of the associated view in the registry.
     *
     * @param token unique token associated with the transitioning view
     */
    override fun unregister(token: ViewTransitionToken) {
        registry[token]?.let { info ->
            info.viewRefCount -= 1
            if (info.viewRefCount == 0) {
                remove(token)
            }
        }
    }

    /**
     * Removes the entry associated with the unique [token] in the registry.
     *
     * @param token unique token associated with the transitioning view
     */
    private fun remove(token: ViewTransitionToken) {
        registry.remove(token)?.let { removedInfo ->
            removedInfo.viewRef.get()?.let { view ->
                view.removeOnAttachStateChangeListener(listener)
                view.setTag(R.id.tag_view_transition_token, null)
            }
            removedInfo.viewRef.clear()
            onRegistryUpdate()
        }
    }

    /**
     * Access a view from registry using unique [token] associated with it. WARNING - this returns a
     * StrongReference to the View stored in the registry
     */
    override fun getView(token: ViewTransitionToken): View? {
        return registry[token]?.viewRef?.get()
    }

    /**
     * Return token mapped to the [view], if it is present in the registry.
     *
     * @param view the transitioning view whose token we are requesting
     * @return token associated with the [view] if present, else null
     */
    override fun getViewToken(view: View): ViewTransitionToken? {
        // extract token from the view if it is embedded inside it as a tag
        val token = view.getTag(R.id.tag_view_transition_token) as? ViewTransitionToken

        // this should never really happen, but if token embedded inside the view as tag, doesn't
        // point to a valid view in the registry, remove that token (tag) from the view and registry
        if (token != null && getView(token) == null) {
            view.setTag(R.id.tag_view_transition_token, null)
            remove(token)
            return null
        }

        return token
    }

    /** Event call to run on registry update (on both [register] and [unregister]). */
    override fun onRegistryUpdate() {
        emitCountForTrace()
    }

    /**
     * Utility function to emit number of non-null views in the registry whenever the registry is
     * updated (via [register] or [unregister]).
     */
    private fun emitCountForTrace() {
        Trace.setCounter("transition_registry_view_count", registry.count().toLong())
    }

    /** Information associated with each transitioning view in the registry. */
    private data class ViewTransitionInfo(

        /** View being transitioned */
        val viewRef: WeakReference<View>,

        /** Count of clients (users of this registry) referencing same transitioning view */
        var viewRefCount: Int = 1,
    )

    companion object {
        val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { ViewTransitionRegistryImpl() }
    }
}
+4 −4
Original line number Diff line number Diff line
@@ -19,16 +19,16 @@ package com.android.systemui.animation
import java.util.UUID

/**
 * A token uniquely mapped to a View in [ViewTransitionRegistry]. This token is guaranteed to be
 * A token uniquely mapped to a View in [ViewTransitionRegistryImpl]. This token is guaranteed to be
 * unique as timestamp is appended to the token string
 *
 * @property token String value of a unique token
 * @constructor creates an instance of [ViewTransitionToken] with token as "UUID" or
 *   "ClassName_UUID"
 *
 * @property token String value of a unique token
 */
@JvmInline
value class ViewTransitionToken private constructor(val token: String) {
    constructor() : this(token = UUID.randomUUID().toString())

    constructor(clazz: Class<*>) : this(token = clazz.simpleName + "_${UUID.randomUUID()}")
}
Loading