diff options
| author | 2023-06-13 16:31:34 +0800 | |
|---|---|---|
| committer | 2023-08-25 16:43:18 +0800 | |
| commit | 1144e9de3bda4ffa4d2f032a993cbc0a26bee8e1 (patch) | |
| tree | aa9101fc18262e798382440804b657ac846fe4cd | |
| parent | 579d9cd8727e67a2083ab99a8f5034d740a58b79 (diff) | |
Synchronize window config updates (2/n)
Introduce ClientTransactionListenerController to keep track of the
registered listeners.
Bug: 260873529
Test: ClientTransactionListenerControllerTest
Change-Id: I98a7fddda24ef74a5f894e9d769456e0ba1993d0
3 files changed, 222 insertions, 1 deletions
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java new file mode 100644 index 000000000000..e2aee839b0c3 --- /dev/null +++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2023 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 android.app.servertransaction; + +import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag; + +import static java.util.Objects.requireNonNull; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.os.Process; +import android.util.ArrayMap; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.concurrent.Executor; +import java.util.function.IntConsumer; + +/** + * Singleton controller to manage listeners to individual {@link ClientTransaction}. + * + * TODO(b/260873529) make as TestApi to allow CTS. + * @hide + */ +public class ClientTransactionListenerController { + + private static ClientTransactionListenerController sController; + + private final Object mLock = new Object(); + + /** + * Mapping from client registered listener for display change to the corresponding + * {@link Executor} to invoke the listener on. + * @see #registerDisplayChangeListener(IntConsumer, Executor) + */ + @GuardedBy("mLock") + private final ArrayMap<IntConsumer, Executor> mDisplayChangeListeners = new ArrayMap<>(); + + private final ArrayList<IntConsumer> mTmpDisplayChangeListeners = new ArrayList<>(); + + /** Gets the singleton controller. */ + @NonNull + public static ClientTransactionListenerController getInstance() { + synchronized (ClientTransactionListenerController.class) { + if (sController == null) { + sController = new ClientTransactionListenerController(); + } + return sController; + } + } + + /** Creates a new instance for test only. */ + @VisibleForTesting + @NonNull + public static ClientTransactionListenerController createInstanceForTesting() { + return new ClientTransactionListenerController(); + } + + private ClientTransactionListenerController() {} + + /** + * Registers a new listener for display change. It will be invoked when receives a + * {@link ClientTransaction} that is updating display-related window configuration, such as + * bounds and rotation. + * + * WHen triggered, the listener will be invoked with the logical display id that was changed. + * + * @param listener the listener to invoke when receives a transaction with Display change. + * @param executor the executor on which callback method will be invoked. + */ + public void registerDisplayChangeListener(@NonNull IntConsumer listener, + @NonNull @CallbackExecutor Executor executor) { + if (!isSyncWindowConfigUpdateFlagEnabled()) { + return; + } + requireNonNull(listener); + requireNonNull(executor); + synchronized (mLock) { + mDisplayChangeListeners.put(listener, executor); + } + } + + /** + * Unregisters the listener for display change that was previously registered through + * {@link #registerDisplayChangeListener}. + */ + public void unregisterDisplayChangeListener(@NonNull IntConsumer listener) { + if (!isSyncWindowConfigUpdateFlagEnabled()) { + return; + } + synchronized (mLock) { + mDisplayChangeListeners.remove(listener); + } + } + + /** + * Called when receives a {@link ClientTransaction} that is updating display-related + * window configuration. + */ + public void onDisplayChanged(int displayId) { + if (!isSyncWindowConfigUpdateFlagEnabled()) { + return; + } + synchronized (mLock) { + // Make a copy of the list to avoid listener removal during callback. + mTmpDisplayChangeListeners.addAll(mDisplayChangeListeners.keySet()); + final int num = mTmpDisplayChangeListeners.size(); + try { + for (int i = 0; i < num; i++) { + final IntConsumer listener = mTmpDisplayChangeListeners.get(i); + final Executor executor = mDisplayChangeListeners.get(listener); + executor.execute(() -> listener.accept(displayId)); + } + } finally { + mTmpDisplayChangeListeners.clear(); + } + } + } + + /** Whether {@link #syncWindowConfigUpdateFlag} feature flag is enabled. */ + @VisibleForTesting + public boolean isSyncWindowConfigUpdateFlagEnabled() { + // Can't read flag from isolated process. + return !Process.isIsolated() && syncWindowConfigUpdateFlag(); + } +} diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index 09a9ab326ef7..d080162e4b8c 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -184,9 +184,12 @@ public class TransactionExecutor { } if (configUpdatedDisplays != null) { + final ClientTransactionListenerController controller = + ClientTransactionListenerController.getInstance(); final int displayCount = configUpdatedDisplays.size(); for (int i = 0; i < displayCount; i++) { - // TODO(b/260873529): trigger onDisplayChanged. + final int displayId = configUpdatedDisplays.valueAt(i); + controller.onDisplayChanged(displayId); } } } diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java new file mode 100644 index 000000000000..0f62b1c23ab4 --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 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 android.app.servertransaction; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.IntConsumer; + +/** + * Tests for {@link ClientTransactionListenerController}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:ClientTransactionListenerControllerTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class ClientTransactionListenerControllerTest { + @Mock + private IntConsumer mDisplayChangeListener; + + private ClientTransactionListenerController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mController = spy(ClientTransactionListenerController.createInstanceForTesting()); + doReturn(true).when(mController).isSyncWindowConfigUpdateFlagEnabled(); + } + + @Test + public void testRegisterDisplayChangeListener() { + mController.registerDisplayChangeListener(mDisplayChangeListener, Runnable::run); + + mController.onDisplayChanged(123); + + verify(mDisplayChangeListener).accept(123); + + clearInvocations(mDisplayChangeListener); + mController.unregisterDisplayChangeListener(mDisplayChangeListener); + + mController.onDisplayChanged(321); + + verify(mDisplayChangeListener, never()).accept(anyInt()); + } +} |