1 /*
<lambda>null2  * Copyright 2023 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 androidx.window.layout.adapter.extensions
18 
19 import android.app.Activity
20 import android.content.Context
21 import androidx.annotation.GuardedBy
22 import androidx.annotation.UiContext
23 import androidx.annotation.VisibleForTesting
24 import androidx.core.util.Consumer
25 import androidx.window.RequiresWindowSdkExtension
26 import androidx.window.core.ConsumerAdapter
27 import androidx.window.extensions.layout.WindowLayoutComponent
28 import androidx.window.layout.WindowLayoutInfo
29 import java.util.concurrent.Executor
30 import java.util.concurrent.locks.ReentrantLock
31 import kotlin.concurrent.withLock
32 
33 @RequiresWindowSdkExtension(version = 2)
34 internal open class ExtensionWindowBackendApi2(
35     component: WindowLayoutComponent,
36     adapter: ConsumerAdapter
37 ) : ExtensionWindowBackendApi1(component, adapter) {
38 
39     private val globalLock = ReentrantLock()
40 
41     @GuardedBy("globalLock")
42     private val contextToListeners = mutableMapOf<Context, MulticastConsumerApi2>()
43 
44     @GuardedBy("globalLock")
45     private val listenerToContext = mutableMapOf<Consumer<WindowLayoutInfo>, Context>()
46 
47     /**
48      * Registers a listener to consume new values of [WindowLayoutInfo]. If there was a listener
49      * registered for a given [Context] then the new listener will receive a replay of the last
50      * known value.
51      *
52      * @param context the host of a [android.view.Window] or an area on the screen. Has to be an
53      *   [Activity] or a [UiContext] created with [Context#createWindowContext] or
54      *   InputMethodService.
55      * @param executor an executor from the parent interface
56      * @param callback the listener that will receive new values
57      */
58     // TODO(b/289377381) remove duplicate code.
59     override fun registerLayoutChangeCallback(
60         @UiContext context: Context,
61         executor: Executor,
62         callback: Consumer<WindowLayoutInfo>
63     ) {
64         globalLock.withLock {
65             contextToListeners[context]?.let { listener ->
66                 listener.addListener(callback)
67                 listenerToContext[callback] = context
68             }
69                 ?: run {
70                     val consumer = MulticastConsumerApi2(context)
71                     contextToListeners[context] = consumer
72                     listenerToContext[callback] = context
73                     consumer.addListener(callback)
74 
75                     component.addWindowLayoutInfoListener(context, consumer)
76                 }
77         }
78     }
79 
80     /**
81      * Unregisters a listener, if this is the last listener for a [UiContext] then the listener is
82      * removed from the [WindowLayoutComponent]. Calling with the same listener multiple times in a
83      * row does not have an effect.
84      *
85      * @param callback a listener that may have been registered
86      */
87     override fun unregisterLayoutChangeCallback(callback: Consumer<WindowLayoutInfo>) {
88         globalLock.withLock {
89             val context = listenerToContext[callback] ?: return
90             val multicastListener = contextToListeners[context] ?: return
91             multicastListener.removeListener(callback)
92             listenerToContext.remove(callback)
93             if (multicastListener.isEmpty()) {
94                 contextToListeners.remove(context)
95                 component.removeWindowLayoutInfoListener(multicastListener)
96             }
97         }
98     }
99 
100     /** Returns {@code true} if all the collections are empty, {@code false} otherwise */
101     @VisibleForTesting
102     override fun hasRegisteredListeners(): Boolean {
103         return !(contextToListeners.isEmpty() && listenerToContext.isEmpty())
104     }
105 }
106