Loading core/java/android/app/ActivityThread.java +24 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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, Loading Loading @@ -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; Loading @@ -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) { Loading core/java/android/app/servertransaction/ClientTransactionListenerController.java +65 −0 Original line number Diff line number Diff line Loading @@ -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}. * Loading @@ -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() { Loading @@ -61,6 +75,57 @@ public class ClientTransactionListenerController { mDisplayManager = requireNonNull(displayManager); } /** * 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. Loading core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +118 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; /** Loading @@ -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; Loading @@ -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(); Loading @@ -129,6 +149,8 @@ public class ActivityThreadTest { mCreatedVirtualDisplays = null; } WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController); ClientTransactionListenerController.getInstance() .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig)); } Loading Loading @@ -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 Loading core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +34 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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}. * Loading @@ -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; Loading Loading @@ -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()); } } Loading
core/java/android/app/ActivityThread.java +24 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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, Loading Loading @@ -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; Loading @@ -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) { Loading
core/java/android/app/servertransaction/ClientTransactionListenerController.java +65 −0 Original line number Diff line number Diff line Loading @@ -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}. * Loading @@ -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() { Loading @@ -61,6 +75,57 @@ public class ClientTransactionListenerController { mDisplayManager = requireNonNull(displayManager); } /** * 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. Loading
core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +118 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; /** Loading @@ -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; Loading @@ -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(); Loading @@ -129,6 +149,8 @@ public class ActivityThreadTest { mCreatedVirtualDisplays = null; } WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController); ClientTransactionListenerController.getInstance() .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig)); } Loading Loading @@ -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 Loading
core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +34 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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}. * Loading @@ -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; Loading Loading @@ -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()); } }