summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityThread.java25
-rw-r--r--core/java/android/app/servertransaction/ClientTransactionListenerController.java65
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java119
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java34
4 files changed, 241 insertions, 2 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 41151c0dc647..a80d70e56c69 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -40,6 +40,7 @@ import static android.window.ConfigurationHelper.shouldUpdateResources;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
+import static com.android.window.flags.Flags.activityWindowInfoFlag;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -63,6 +64,7 @@ import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.app.servertransaction.DestroyActivityItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.PendingTransactionActions;
@@ -606,6 +608,8 @@ public final class ActivityThread extends ClientTransactionHandler
Configuration overrideConfig;
@NonNull
private ActivityWindowInfo mActivityWindowInfo;
+ @Nullable
+ private ActivityWindowInfo mLastReportedActivityWindowInfo;
// Used for consolidating configs before sending on to Activity.
private final Configuration tmpConfig = new Configuration();
@@ -4180,6 +4184,9 @@ public final class ActivityThread extends ClientTransactionHandler
pendingActions.setRestoreInstanceState(true);
pendingActions.setCallOnPostCreate(true);
}
+
+ // Trigger ActivityWindowInfo callback if first launch or change from relaunch.
+ handleActivityWindowInfoChanged(r);
} else {
// If there was an error, for any reason, tell the activity manager to stop us.
ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED,
@@ -6740,7 +6747,7 @@ public final class ActivityThread extends ClientTransactionHandler
// Perform updates.
r.overrideConfig = overrideConfig;
r.mActivityWindowInfo = activityWindowInfo;
- // TODO(b/287582673): notify on ActivityWindowInfo change
+
final ViewRootImpl viewRoot = r.activity.mDecor != null
? r.activity.mDecor.getViewRootImpl() : null;
@@ -6763,6 +6770,22 @@ public final class ActivityThread extends ClientTransactionHandler
viewRoot.updateConfiguration(displayId);
}
mSomeActivitiesChanged = true;
+
+ // Trigger ActivityWindowInfo callback if changed.
+ handleActivityWindowInfoChanged(r);
+ }
+
+ private void handleActivityWindowInfoChanged(@NonNull ActivityClientRecord r) {
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ if (r.mActivityWindowInfo == null
+ || r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) {
+ return;
+ }
+ r.mLastReportedActivityWindowInfo = r.mActivityWindowInfo;
+ ClientTransactionListenerController.getInstance().onActivityWindowInfoChanged(r.token,
+ r.mActivityWindowInfo);
}
final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 1a8136e06c28..7383d07c82e9 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -16,16 +16,24 @@
package android.app.servertransaction;
+import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.bundleClientTransactionFlag;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
+import android.app.Activity;
import android.app.ActivityThread;
import android.hardware.display.DisplayManagerGlobal;
+import android.os.IBinder;
+import android.util.ArraySet;
+import android.window.ActivityWindowInfo;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.function.BiConsumer;
+
/**
* Singleton controller to manage listeners to individual {@link ClientTransaction}.
*
@@ -35,8 +43,14 @@ public class ClientTransactionListenerController {
private static ClientTransactionListenerController sController;
+ private final Object mLock = new Object();
private final DisplayManagerGlobal mDisplayManager;
+ /** Listeners registered via {@link #registerActivityWindowInfoChangedListener(BiConsumer)}. */
+ @GuardedBy("mLock")
+ private final ArraySet<BiConsumer<IBinder, ActivityWindowInfo>>
+ mActivityWindowInfoChangedListeners = new ArraySet<>();
+
/** Gets the singleton controller. */
@NonNull
public static ClientTransactionListenerController getInstance() {
@@ -62,6 +76,57 @@ public class ClientTransactionListenerController {
}
/**
+ * Registers to listen on activity {@link ActivityWindowInfo} change.
+ * The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and
+ * {@link ActivityWindowInfo}.
+ */
+ public void registerActivityWindowInfoChangedListener(
+ @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ synchronized (mLock) {
+ mActivityWindowInfoChangedListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregisters the listener that was previously registered via
+ * {@link #registerActivityWindowInfoChangedListener(BiConsumer)}
+ */
+ public void unregisterActivityWindowInfoChangedListener(
+ @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ synchronized (mLock) {
+ mActivityWindowInfoChangedListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Called when receives a {@link ClientTransaction} that is updating an activity's
+ * {@link ActivityWindowInfo}.
+ */
+ public void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ final Object[] activityWindowInfoChangedListeners;
+ synchronized (mLock) {
+ if (mActivityWindowInfoChangedListeners.isEmpty()) {
+ return;
+ }
+ activityWindowInfoChangedListeners = mActivityWindowInfoChangedListeners.toArray();
+ }
+ for (Object activityWindowInfoChangedListener : activityWindowInfoChangedListeners) {
+ ((BiConsumer<IBinder, ActivityWindowInfo>) activityWindowInfoChangedListener)
+ .accept(activityToken, activityWindowInfo);
+ }
+ }
+
+ /**
* Called when receives a {@link ClientTransaction} that is updating display-related
* window configuration.
*/
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index d115bf306b45..2963acf17b9e 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -21,16 +21,22 @@ import static android.content.Intent.ACTION_EDIT;
import static android.content.Intent.ACTION_VIEW;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
@@ -46,6 +52,7 @@ import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.app.servertransaction.ConfigurationChangeItem;
import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.ResumeActivityItem;
@@ -60,7 +67,9 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.view.Display;
@@ -81,11 +90,14 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -103,11 +115,17 @@ public class ActivityThreadTest {
// few sequence numbers the framework used to launch the test activity.
private static final int BASE_SEQ = 10000000;
- @Rule
+ @Rule(order = 0)
public final ActivityTestRule<TestActivity> mActivityTestRule =
new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
+ @Rule(order = 1)
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
+ @Mock
+ private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+
private WindowTokenClientController mOriginalWindowTokenClientController;
private Configuration mOriginalAppConfig;
@@ -115,6 +133,8 @@ public class ActivityThreadTest {
@Before
public void setup() {
+ MockitoAnnotations.initMocks(this);
+
// Keep track of the original controller, so that it can be used to restore in tearDown()
// when there is override in some test cases.
mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
@@ -129,6 +149,8 @@ public class ActivityThreadTest {
mCreatedVirtualDisplays = null;
}
WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController);
+ ClientTransactionListenerController.getInstance()
+ .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
InstrumentationRegistry.getInstrumentation().runOnMainSync(
() -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig));
}
@@ -783,6 +805,101 @@ public class ActivityThreadTest {
verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken);
}
+ @Test
+ public void testActivityWindowInfoChanged_activityLaunch() {
+ mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+ mActivityWindowInfoListener);
+
+ final Activity activity = mActivityTestRule.launchActivity(new Intent());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
+
+ verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+ activityClientRecord.getActivityWindowInfo());
+ }
+
+ @Test
+ public void testActivityWindowInfoChanged_activityRelaunch() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+ mActivityWindowInfoListener);
+
+ final Activity activity = mActivityTestRule.launchActivity(new Intent());
+ final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
+ appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
+
+ // The same ActivityWindowInfo won't trigger duplicated callback.
+ verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+ activityClientRecord.getActivityWindowInfo());
+
+ final Configuration currentConfig = activity.getResources().getConfiguration();
+ final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+ activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
+ new Rect(0, 0, 1000, 1000));
+ final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain(
+ activity.getActivityToken(), null, null, 0,
+ new MergedConfiguration(currentConfig, currentConfig),
+ false /* preserveWindow */, activityWindowInfo);
+ final ClientTransaction transaction = newTransaction(activity);
+ transaction.addTransactionItem(relaunchItem);
+ appThread.scheduleTransaction(transaction);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+ activityWindowInfo);
+ }
+
+ @Test
+ public void testActivityWindowInfoChanged_activityConfigurationChanged()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+ mActivityWindowInfoListener);
+
+ final Activity activity = mActivityTestRule.launchActivity(new Intent());
+ final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ clearInvocations(mActivityWindowInfoListener);
+ final Configuration config = new Configuration(activity.getResources().getConfiguration());
+ config.seq++;
+ final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+ final Rect taskFragmentBounds = new Rect(0, 0, 1000, 1000);
+ final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+ activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
+ final ActivityConfigurationChangeItem activityConfigurationChangeItem =
+ ActivityConfigurationChangeItem.obtain(
+ activity.getActivityToken(), config, activityWindowInfo);
+ final ClientTransaction transaction = newTransaction(activity);
+ transaction.addTransactionItem(activityConfigurationChangeItem);
+ appThread.scheduleTransaction(transaction);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ verify(mActivityWindowInfoListener).accept(activity.getActivityToken(),
+ activityWindowInfo);
+
+ clearInvocations(mActivityWindowInfoListener);
+ final ActivityWindowInfo activityWindowInfo2 = new ActivityWindowInfo();
+ activityWindowInfo2.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
+ config.seq++;
+ final ActivityConfigurationChangeItem activityConfigurationChangeItem2 =
+ ActivityConfigurationChangeItem.obtain(
+ activity.getActivityToken(), config, activityWindowInfo2);
+ final ClientTransaction transaction2 = newTransaction(activity);
+ transaction2.addTransactionItem(activityConfigurationChangeItem2);
+ appThread.scheduleTransaction(transaction);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ // The same ActivityWindowInfo won't trigger duplicated callback.
+ verify(mActivityWindowInfoListener, never()).accept(any(), any());
+ }
+
/**
* Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
* Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 213fd7bd494d..77d31a5f27e7 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -22,21 +22,29 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.IDisplayManager;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.DisplayInfo;
+import android.window.ActivityWindowInfo;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.window.flags.Flags;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -44,6 +52,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.BiConsumer;
+
/**
* Tests for {@link ClientTransactionListenerController}.
*
@@ -62,6 +72,10 @@ public class ClientTransactionListenerControllerTest {
private IDisplayManager mIDisplayManager;
@Mock
private DisplayManager.DisplayListener mListener;
+ @Mock
+ private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+ @Mock
+ private IBinder mActivityToken;
private DisplayManagerGlobal mDisplayManager;
private Handler mHandler;
@@ -91,4 +105,24 @@ public class ClientTransactionListenerControllerTest {
verify(mListener).onDisplayChanged(123);
}
+
+ @Test
+ public void testActivityWindowInfoChangedListener() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ mController.registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+ final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+ activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
+ new Rect(0, 0, 1000, 1000));
+ mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo);
+
+ verify(mActivityWindowInfoListener).accept(mActivityToken, activityWindowInfo);
+
+ clearInvocations(mActivityWindowInfoListener);
+ mController.unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+
+ mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo);
+
+ verify(mActivityWindowInfoListener, never()).accept(any(), any());
+ }
}