Implement getCurrentWindowLayoutInfo() API.
This commit implements the getCurrentWindowLayoutInfo() API in window
extension v9, targeting the 25Q2 release. It enables access to window
layout information during the onCreate lifecycle callback. Check
ag/29908303 for API definition and more details.
Bug: 337820752
Flag: com.android.window.flags.wlinfo_oncreate
Test: atest WMJetpackUnitTests:WindowLayoutComponentImplTest
Change-Id: I43403a633e9a396ab9ce2cc1aef4ff4c70547573
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/layout/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/layout/CommonFoldingFeature.java
index 85c4fe1..252974d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/layout/CommonFoldingFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/layout/CommonFoldingFeature.java
@@ -25,6 +25,7 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -212,7 +213,8 @@
@NonNull
private final Rect mRect;
- CommonFoldingFeature(int type, @State int state, @NonNull Rect rect) {
+ @VisibleForTesting
+ public CommonFoldingFeature(int type, @State int state, @NonNull Rect rect) {
assertReportableState(state);
this.mType = type;
this.mState = state;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 2ab0310..fcf3a37 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -57,6 +57,8 @@
*/
private static final int NO_LEVEL_OVERRIDE = -1;
+ private static final int EXTENSIONS_VERSION_V9 = 9;
+
private static final int EXTENSIONS_VERSION_V8 = 8;
private static final int EXTENSIONS_VERSION_V7 = 7;
@@ -80,6 +82,9 @@
*/
@VisibleForTesting
static int getExtensionsVersionCurrentPlatform() {
+ if (Flags.wlinfoOncreate()) {
+ return EXTENSIONS_VERSION_V9;
+ }
if (Flags.aeBackStackRestore()) {
return EXTENSIONS_VERSION_V8;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index f1ea19a..556da37 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -74,6 +74,12 @@
@GuardedBy("mLock")
private final DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer;
+ /**
+ * The last reported folding features from the device. This is initialized in the constructor
+ * because the data change callback added to {@link #mFoldingFeatureProducer} is immediately
+ * called. This is due to current device state from the device state manager already being
+ * available in the {@link DeviceStateManagerFoldingFeatureProducer}.
+ */
@GuardedBy("mLock")
private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
@@ -308,6 +314,7 @@
* @param context a proxy for the {@link android.view.Window} that contains the
* {@link DisplayFeature}.
*/
+ @NonNull
private WindowLayoutInfo getWindowLayoutInfo(@NonNull @UiContext Context context,
List<CommonFoldingFeature> storedFeatures) {
List<DisplayFeature> displayFeatureList = getDisplayFeatures(context, storedFeatures);
@@ -329,6 +336,14 @@
}
}
+ @Override
+ @NonNull
+ public WindowLayoutInfo getCurrentWindowLayoutInfo(@NonNull @UiContext Context context) {
+ synchronized (mLock) {
+ return getWindowLayoutInfo(context, mLastReportedFoldingFeatures);
+ }
+ }
+
/**
* Returns the {@link SupportedWindowFeatures} for the device. This list does not change over
* time.
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java
index ff0a82f..ed4eddf 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java
@@ -16,22 +16,30 @@
package androidx.window.extensions.layout;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.mock;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.ContextWrapper;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.common.layout.CommonFoldingFeature;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Collections;
+import java.util.List;
/**
* Test class for {@link WindowLayoutComponentImpl}.
@@ -44,31 +52,91 @@
@RunWith(AndroidJUnit4.class)
public class WindowLayoutComponentImplTest {
+ private final Context mAppContext = ApplicationProvider.getApplicationContext();
+
+ @NonNull
private WindowLayoutComponentImpl mWindowLayoutComponent;
@Before
public void setUp() {
- mWindowLayoutComponent = new WindowLayoutComponentImpl(
- ApplicationProvider.getApplicationContext(),
+ mWindowLayoutComponent = new WindowLayoutComponentImpl(mAppContext,
mock(DeviceStateManagerFoldingFeatureProducer.class));
}
@Test
- public void testAddWindowLayoutListenerOnFakeUiContext_noCrash() {
- final Context fakeUiContext = createTestContext();
+ public void testAddWindowLayoutListener_onFakeUiContext_noCrash() {
+ final Context fakeUiContext = new FakeUiContext(mAppContext);
mWindowLayoutComponent.addWindowLayoutInfoListener(fakeUiContext, info -> {});
mWindowLayoutComponent.onDisplayFeaturesChanged(Collections.emptyList());
}
- private static Context createTestContext() {
- return new FakeUiContext(ApplicationProvider.getApplicationContext());
+ @Test
+ public void testGetCurrentWindowLayoutInfo_noFoldingFeature_returnsEmptyList() {
+ final Context testUiContext = new TestUiContext(mAppContext);
+
+ final WindowLayoutInfo layoutInfo =
+ mWindowLayoutComponent.getCurrentWindowLayoutInfo(testUiContext);
+
+ assertThat(layoutInfo.getDisplayFeatures()).isEmpty();
+ }
+
+ @Test
+ public void testGetCurrentWindowLayoutInfo_hasFoldingFeature_returnsWindowLayoutInfo() {
+ final Context testUiContext = new TestUiContext(mAppContext);
+ final WindowConfiguration windowConfiguration =
+ testUiContext.getResources().getConfiguration().windowConfiguration;
+ final Rect featureRect = windowConfiguration.getBounds();
+ final CommonFoldingFeature foldingFeature = new CommonFoldingFeature(
+ CommonFoldingFeature.COMMON_TYPE_HINGE,
+ CommonFoldingFeature.COMMON_STATE_FLAT,
+ featureRect
+ );
+ mWindowLayoutComponent.onDisplayFeaturesChanged(List.of(foldingFeature));
+
+ final WindowLayoutInfo layoutInfo =
+ mWindowLayoutComponent.getCurrentWindowLayoutInfo(testUiContext);
+
+ assertThat(layoutInfo.getDisplayFeatures()).containsExactly(new FoldingFeature(
+ featureRect, FoldingFeature.TYPE_HINGE, FoldingFeature.STATE_FLAT));
+ }
+
+ @Test
+ public void testGetCurrentWindowLayoutInfo_nonUiContext_returnsEmptyList() {
+ final WindowLayoutInfo layoutInfo =
+ mWindowLayoutComponent.getCurrentWindowLayoutInfo(mAppContext);
+
+ assertThat(layoutInfo.getDisplayFeatures()).isEmpty();
}
/**
- * A {@link android.content.Context} overrides {@link android.content.Context#isUiContext} to
- * {@code true}.
+ * A {@link Context} that simulates a UI context specifically for testing purposes.
+ * This class overrides {@link Context#getAssociatedDisplayId()} to return
+ * {@link Display#DEFAULT_DISPLAY}, ensuring the context is tied to the default display,
+ * and {@link Context#isUiContext()} to always return {@code true}, simulating a UI context.
+ */
+ private static class TestUiContext extends ContextWrapper {
+
+ TestUiContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ public int getAssociatedDisplayId() {
+ return Display.DEFAULT_DISPLAY;
+ }
+
+ @Override
+ public boolean isUiContext() {
+ return true;
+ }
+ }
+
+ /**
+ * A {@link Context} that cheats by overriding {@link Context#isUiContext} to always
+ * return {@code true}. This is useful for scenarios where a UI context is needed,
+ * but the underlying context isn't actually a UI one.
*/
private static class FakeUiContext extends ContextWrapper {