Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +22 −5 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import androidx.window.common.CommonFoldingFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.extensions.core.util.function.Consumer; import androidx.window.extensions.util.DeduplicateConsumer; import java.util.ArrayList; import java.util.Collections; Loading @@ -62,7 +63,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final Object mLock = new Object(); @GuardedBy("mLock") private final Map<Context, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners = private final Map<Context, DeduplicateConsumer<WindowLayoutInfo>> mWindowLayoutChangeListeners = new ArrayMap<>(); @GuardedBy("mLock") Loading Loading @@ -130,7 +131,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { if (mWindowLayoutChangeListeners.containsKey(context) // In theory this method can be called on the same consumer with different // context. || mWindowLayoutChangeListeners.containsValue(consumer)) { || containsConsumer(consumer)) { return; } if (!context.isUiContext()) { Loading @@ -141,7 +142,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features); consumer.accept(newWindowLayout); }); mWindowLayoutChangeListeners.put(context, consumer); mWindowLayoutChangeListeners.put(context, new DeduplicateConsumer<>(consumer)); final IBinder windowContextToken = context.getWindowContextToken(); if (windowContextToken != null) { Loading Loading @@ -176,20 +177,36 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { @Override public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) { synchronized (mLock) { DeduplicateConsumer<WindowLayoutInfo> consumerToRemove = null; for (Context context : mWindowLayoutChangeListeners.keySet()) { if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) { final DeduplicateConsumer<WindowLayoutInfo> deduplicateConsumer = mWindowLayoutChangeListeners.get(context); if (!deduplicateConsumer.matchesConsumer(consumer)) { continue; } final IBinder token = context.getWindowContextToken(); consumerToRemove = deduplicateConsumer; if (token != null) { context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token)); mConfigurationChangeListeners.remove(token); } break; } mWindowLayoutChangeListeners.values().remove(consumer); if (consumerToRemove != null) { mWindowLayoutChangeListeners.values().remove(consumerToRemove); } } } @GuardedBy("mLock") private boolean containsConsumer(@NonNull Consumer<WindowLayoutInfo> consumer) { for (DeduplicateConsumer<WindowLayoutInfo> c : mWindowLayoutChangeListeners.values()) { if (c.matchesConsumer(consumer)) { return true; } } return false; } @GuardedBy("mLock") private void updateListenerRegistrations() { Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/util/DeduplicateConsumer.java 0 → 100644 +63 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 androidx.window.extensions.util; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.window.extensions.core.util.function.Consumer; /** * A utility class that will not report a value if it is the same as the last reported value. * @param <T> generic values to be reported. */ public class DeduplicateConsumer<T> implements Consumer<T> { private final Object mLock = new Object(); @GuardedBy("mLock") @Nullable private T mLastReportedValue = null; @NonNull private final Consumer<T> mConsumer; public DeduplicateConsumer(@NonNull Consumer<T> consumer) { mConsumer = consumer; } /** * Returns {@code true} if the given consumer matches this object or the wrapped * {@link Consumer}, {@code false} otherwise */ public boolean matchesConsumer(@NonNull Consumer<T> consumer) { return consumer == this || mConsumer.equals(consumer); } /** * Accepts a new value and relays it if it is different from * the last reported value. * @param value to report if different. */ @Override public void accept(@NonNull T value) { synchronized (mLock) { if (mLastReportedValue != null && mLastReportedValue.equals(value)) { return; } mLastReportedValue = value; } mConsumer.accept(value); } } libs/WindowManager/Jetpack/tests/unittest/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ package { android_test { name: "WMJetpackUnitTests", team: "trendy_team_windowing_sdk", // To make the test run via TEST_MAPPING test_suites: ["device-tests"], Loading @@ -32,6 +33,7 @@ android_test { static_libs: [ "androidx.window.extensions", "androidx.window.extensions.core_core", "junit", "androidx.test.runner", "androidx.test.rules", Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/util/DeduplicateConsumerTest.java 0 → 100644 +97 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 androidx.window.extensions.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import androidx.window.extensions.core.util.function.Consumer; import org.junit.Test; import java.util.ArrayList; import java.util.List; /** * A class to validate {@link DeduplicateConsumer}. */ public class DeduplicateConsumerTest { @Test public void test_duplicate_value_is_filtered() { String value = "test_value"; List<String> expected = new ArrayList<>(); expected.add(value); RecordingConsumer recordingConsumer = new RecordingConsumer(); DeduplicateConsumer<String> deduplicateConsumer = new DeduplicateConsumer<>(recordingConsumer); deduplicateConsumer.accept(value); deduplicateConsumer.accept(value); assertEquals(expected, recordingConsumer.getValues()); } @Test public void test_different_value_is_filtered() { String value = "test_value"; String newValue = "test_value_new"; List<String> expected = new ArrayList<>(); expected.add(value); expected.add(newValue); RecordingConsumer recordingConsumer = new RecordingConsumer(); DeduplicateConsumer<String> deduplicateConsumer = new DeduplicateConsumer<>(recordingConsumer); deduplicateConsumer.accept(value); deduplicateConsumer.accept(value); deduplicateConsumer.accept(newValue); assertEquals(expected, recordingConsumer.getValues()); } @Test public void test_match_against_consumer_property_returns_true() { RecordingConsumer recordingConsumer = new RecordingConsumer(); DeduplicateConsumer<String> deduplicateConsumer = new DeduplicateConsumer<>(recordingConsumer); assertTrue(deduplicateConsumer.matchesConsumer(recordingConsumer)); } @Test public void test_match_against_self_returns_true() { RecordingConsumer recordingConsumer = new RecordingConsumer(); DeduplicateConsumer<String> deduplicateConsumer = new DeduplicateConsumer<>(recordingConsumer); assertTrue(deduplicateConsumer.matchesConsumer(deduplicateConsumer)); } private static final class RecordingConsumer implements Consumer<String> { private final List<String> mValues = new ArrayList<>(); @Override public void accept(String s) { mValues.add(s); } public List<String> getValues() { return mValues; } } } Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +22 −5 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import androidx.window.common.CommonFoldingFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.extensions.core.util.function.Consumer; import androidx.window.extensions.util.DeduplicateConsumer; import java.util.ArrayList; import java.util.Collections; Loading @@ -62,7 +63,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final Object mLock = new Object(); @GuardedBy("mLock") private final Map<Context, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners = private final Map<Context, DeduplicateConsumer<WindowLayoutInfo>> mWindowLayoutChangeListeners = new ArrayMap<>(); @GuardedBy("mLock") Loading Loading @@ -130,7 +131,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { if (mWindowLayoutChangeListeners.containsKey(context) // In theory this method can be called on the same consumer with different // context. || mWindowLayoutChangeListeners.containsValue(consumer)) { || containsConsumer(consumer)) { return; } if (!context.isUiContext()) { Loading @@ -141,7 +142,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features); consumer.accept(newWindowLayout); }); mWindowLayoutChangeListeners.put(context, consumer); mWindowLayoutChangeListeners.put(context, new DeduplicateConsumer<>(consumer)); final IBinder windowContextToken = context.getWindowContextToken(); if (windowContextToken != null) { Loading Loading @@ -176,20 +177,36 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { @Override public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) { synchronized (mLock) { DeduplicateConsumer<WindowLayoutInfo> consumerToRemove = null; for (Context context : mWindowLayoutChangeListeners.keySet()) { if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) { final DeduplicateConsumer<WindowLayoutInfo> deduplicateConsumer = mWindowLayoutChangeListeners.get(context); if (!deduplicateConsumer.matchesConsumer(consumer)) { continue; } final IBinder token = context.getWindowContextToken(); consumerToRemove = deduplicateConsumer; if (token != null) { context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token)); mConfigurationChangeListeners.remove(token); } break; } mWindowLayoutChangeListeners.values().remove(consumer); if (consumerToRemove != null) { mWindowLayoutChangeListeners.values().remove(consumerToRemove); } } } @GuardedBy("mLock") private boolean containsConsumer(@NonNull Consumer<WindowLayoutInfo> consumer) { for (DeduplicateConsumer<WindowLayoutInfo> c : mWindowLayoutChangeListeners.values()) { if (c.matchesConsumer(consumer)) { return true; } } return false; } @GuardedBy("mLock") private void updateListenerRegistrations() { Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/util/DeduplicateConsumer.java 0 → 100644 +63 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 androidx.window.extensions.util; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.window.extensions.core.util.function.Consumer; /** * A utility class that will not report a value if it is the same as the last reported value. * @param <T> generic values to be reported. */ public class DeduplicateConsumer<T> implements Consumer<T> { private final Object mLock = new Object(); @GuardedBy("mLock") @Nullable private T mLastReportedValue = null; @NonNull private final Consumer<T> mConsumer; public DeduplicateConsumer(@NonNull Consumer<T> consumer) { mConsumer = consumer; } /** * Returns {@code true} if the given consumer matches this object or the wrapped * {@link Consumer}, {@code false} otherwise */ public boolean matchesConsumer(@NonNull Consumer<T> consumer) { return consumer == this || mConsumer.equals(consumer); } /** * Accepts a new value and relays it if it is different from * the last reported value. * @param value to report if different. */ @Override public void accept(@NonNull T value) { synchronized (mLock) { if (mLastReportedValue != null && mLastReportedValue.equals(value)) { return; } mLastReportedValue = value; } mConsumer.accept(value); } }
libs/WindowManager/Jetpack/tests/unittest/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ package { android_test { name: "WMJetpackUnitTests", team: "trendy_team_windowing_sdk", // To make the test run via TEST_MAPPING test_suites: ["device-tests"], Loading @@ -32,6 +33,7 @@ android_test { static_libs: [ "androidx.window.extensions", "androidx.window.extensions.core_core", "junit", "androidx.test.runner", "androidx.test.rules", Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/util/DeduplicateConsumerTest.java 0 → 100644 +97 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 androidx.window.extensions.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import androidx.window.extensions.core.util.function.Consumer; import org.junit.Test; import java.util.ArrayList; import java.util.List; /** * A class to validate {@link DeduplicateConsumer}. */ public class DeduplicateConsumerTest { @Test public void test_duplicate_value_is_filtered() { String value = "test_value"; List<String> expected = new ArrayList<>(); expected.add(value); RecordingConsumer recordingConsumer = new RecordingConsumer(); DeduplicateConsumer<String> deduplicateConsumer = new DeduplicateConsumer<>(recordingConsumer); deduplicateConsumer.accept(value); deduplicateConsumer.accept(value); assertEquals(expected, recordingConsumer.getValues()); } @Test public void test_different_value_is_filtered() { String value = "test_value"; String newValue = "test_value_new"; List<String> expected = new ArrayList<>(); expected.add(value); expected.add(newValue); RecordingConsumer recordingConsumer = new RecordingConsumer(); DeduplicateConsumer<String> deduplicateConsumer = new DeduplicateConsumer<>(recordingConsumer); deduplicateConsumer.accept(value); deduplicateConsumer.accept(value); deduplicateConsumer.accept(newValue); assertEquals(expected, recordingConsumer.getValues()); } @Test public void test_match_against_consumer_property_returns_true() { RecordingConsumer recordingConsumer = new RecordingConsumer(); DeduplicateConsumer<String> deduplicateConsumer = new DeduplicateConsumer<>(recordingConsumer); assertTrue(deduplicateConsumer.matchesConsumer(recordingConsumer)); } @Test public void test_match_against_self_returns_true() { RecordingConsumer recordingConsumer = new RecordingConsumer(); DeduplicateConsumer<String> deduplicateConsumer = new DeduplicateConsumer<>(recordingConsumer); assertTrue(deduplicateConsumer.matchesConsumer(deduplicateConsumer)); } private static final class RecordingConsumer implements Consumer<String> { private final List<String> mValues = new ArrayList<>(); @Override public void accept(String s) { mValues.add(s); } public List<String> getValues() { return mValues; } } }