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