summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chris Li <lihongyu@google.com> 2023-06-13 16:31:34 +0800
committer Chris Li <lihongyu@google.com> 2023-08-25 16:43:18 +0800
commit1144e9de3bda4ffa4d2f032a993cbc0a26bee8e1 (patch)
treeaa9101fc18262e798382440804b657ac846fe4cd
parent579d9cd8727e67a2083ab99a8f5034d740a58b79 (diff)
Synchronize window config updates (2/n)
Introduce ClientTransactionListenerController to keep track of the registered listeners. Bug: 260873529 Test: ClientTransactionListenerControllerTest Change-Id: I98a7fddda24ef74a5f894e9d769456e0ba1993d0
-rw-r--r--core/java/android/app/servertransaction/ClientTransactionListenerController.java142
-rw-r--r--core/java/android/app/servertransaction/TransactionExecutor.java5
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java76
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());
+ }
+}