From 5e0dd149001e1948cb05ff62ba4ce937a356fda6 Mon Sep 17 00:00:00 2001 From: Diego Vela Date: Fri, 16 Dec 2022 00:03:13 +0000 Subject: [PATCH] Use bounds to proxy if an app is full screen. Use bounds to proxy if an app is full screen. Using the windowing mode is not accurate when going to and from pip. There doesn't seem to be a simple solution on the pip side. This is a proxy until we figure out a long term solution. Bug: 239389499 Test: atest CtsWindowManagerJetpackTestCases:ExtensionWindowLayoutComponentTest Change-Id: Ic7bfba41a2ec493856bd82aa3726eb956055e40d --- .../extensions/WindowExtensionsImpl.java | 67 ++++++++++++++++--- .../extensions/embedding/SplitController.java | 18 ++--- .../extensions/embedding/SplitPresenter.java | 11 ++- .../layout/WindowLayoutComponentImpl.java | 57 ++++++++++++---- .../embedding/SplitControllerTest.java | 5 +- .../embedding/SplitPresenterTest.java | 5 +- .../embedding/TaskFragmentContainerTest.java | 7 +- 7 files changed, 127 insertions(+), 43 deletions(-) diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java index fb0a9db6a20b2..54edd9ec43358 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -17,9 +17,13 @@ package androidx.window.extensions; import android.app.ActivityThread; +import android.app.Application; import android.content.Context; +import android.window.TaskFragmentOrganizer; import androidx.annotation.NonNull; +import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; +import androidx.window.common.RawFoldingFeatureProducer; import androidx.window.extensions.area.WindowAreaComponent; import androidx.window.extensions.area.WindowAreaComponentImpl; import androidx.window.extensions.embedding.ActivityEmbeddingComponent; @@ -27,6 +31,8 @@ import androidx.window.extensions.embedding.SplitController; import androidx.window.extensions.layout.WindowLayoutComponent; import androidx.window.extensions.layout.WindowLayoutComponentImpl; +import java.util.Objects; + /** * The reference implementation of {@link WindowExtensions} that implements the initial API version. @@ -34,7 +40,8 @@ import androidx.window.extensions.layout.WindowLayoutComponentImpl; public class WindowExtensionsImpl implements WindowExtensions { private final Object mLock = new Object(); - private volatile WindowLayoutComponent mWindowLayoutComponent; + private volatile DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer; + private volatile WindowLayoutComponentImpl mWindowLayoutComponent; private volatile SplitController mSplitController; private volatile WindowAreaComponent mWindowAreaComponent; @@ -44,6 +51,49 @@ public class WindowExtensionsImpl implements WindowExtensions { return 1; } + @NonNull + private Application getApplication() { + return Objects.requireNonNull(ActivityThread.currentApplication()); + } + + @NonNull + private DeviceStateManagerFoldingFeatureProducer getFoldingFeatureProducer() { + if (mFoldingFeatureProducer == null) { + synchronized (mLock) { + if (mFoldingFeatureProducer == null) { + Context context = getApplication(); + RawFoldingFeatureProducer foldingFeatureProducer = + new RawFoldingFeatureProducer(context); + mFoldingFeatureProducer = + new DeviceStateManagerFoldingFeatureProducer(context, + foldingFeatureProducer); + } + } + } + return mFoldingFeatureProducer; + } + + @NonNull + private WindowLayoutComponentImpl getWindowLayoutComponentImpl() { + if (mWindowLayoutComponent == null) { + synchronized (mLock) { + if (mWindowLayoutComponent == null) { + Context context = getApplication(); + DeviceStateManagerFoldingFeatureProducer producer = + getFoldingFeatureProducer(); + // TODO(b/263263909) Use the organizer to tell if an Activity is embededed. + // Need to improve our Dependency Injection and centralize the logic. + TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(command -> { + throw new RuntimeException("Not allowed!"); + }); + mWindowLayoutComponent = new WindowLayoutComponentImpl(context, organizer, + producer); + } + } + } + return mWindowLayoutComponent; + } + /** * Returns a reference implementation of {@link WindowLayoutComponent} if available, * {@code null} otherwise. The implementation must match the API level reported in @@ -52,15 +102,7 @@ public class WindowExtensionsImpl implements WindowExtensions { */ @Override public WindowLayoutComponent getWindowLayoutComponent() { - if (mWindowLayoutComponent == null) { - synchronized (mLock) { - if (mWindowLayoutComponent == null) { - Context context = ActivityThread.currentApplication(); - mWindowLayoutComponent = new WindowLayoutComponentImpl(context); - } - } - } - return mWindowLayoutComponent; + return getWindowLayoutComponentImpl(); } /** @@ -74,7 +116,10 @@ public class WindowExtensionsImpl implements WindowExtensions { if (mSplitController == null) { synchronized (mLock) { if (mSplitController == null) { - mSplitController = new SplitController(); + mSplitController = new SplitController( + getWindowLayoutComponentImpl(), + getFoldingFeatureProducer() + ); } } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 1af1313f0bbb1..7d6688eed697c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -74,8 +74,8 @@ import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.window.common.CommonFoldingFeature; +import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; -import androidx.window.extensions.WindowExtensionsProvider; import androidx.window.extensions.embedding.TransactionManager.TransactionRecord; import androidx.window.extensions.layout.WindowLayoutComponentImpl; @@ -144,19 +144,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private final Handler mHandler; final Object mLock = new Object(); private final ActivityStartMonitor mActivityStartMonitor; - @NonNull - final WindowLayoutComponentImpl mWindowLayoutComponent; - public SplitController() { - this((WindowLayoutComponentImpl) Objects.requireNonNull(WindowExtensionsProvider - .getWindowExtensions().getWindowLayoutComponent())); - } - - @VisibleForTesting - SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent) { + public SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent, + @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) { final MainThreadExecutor executor = new MainThreadExecutor(); mHandler = executor.mHandler; - mPresenter = new SplitPresenter(executor, this); + mPresenter = new SplitPresenter(executor, windowLayoutComponent, this); mTransactionManager = new TransactionManager(mPresenter); final ActivityThread activityThread = ActivityThread.currentActivityThread(); final Application application = activityThread.getApplication(); @@ -167,8 +160,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen mActivityStartMonitor = new ActivityStartMonitor(); instrumentation.addMonitor(mActivityStartMonitor); - mWindowLayoutComponent = windowLayoutComponent; - mWindowLayoutComponent.addFoldingStateChangedCallback(new FoldingFeatureListener()); + foldingFeatureProducer.addDataChangedCallback(new FoldingFeatureListener()); } private class FoldingFeatureListener implements Consumer> { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 9db9f87881902..871ee6aa5d169 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -50,6 +50,7 @@ import androidx.window.extensions.embedding.SplitAttributesCalculator.SplitAttri import androidx.window.extensions.embedding.TaskContainer.TaskProperties; import androidx.window.extensions.layout.DisplayFeature; import androidx.window.extensions.layout.FoldingFeature; +import androidx.window.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutInfo; import com.android.internal.annotations.VisibleForTesting; @@ -136,10 +137,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { .setSplitType(new ExpandContainersSplitType()) .build(); + private final WindowLayoutComponentImpl mWindowLayoutComponent; private final SplitController mController; - SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) { + SplitPresenter(@NonNull Executor executor, + @NonNull WindowLayoutComponentImpl windowLayoutComponent, + @NonNull SplitController controller) { super(executor, controller); + mWindowLayoutComponent = windowLayoutComponent; mController = controller; registerOrganizer(); if (!SplitController.ENABLE_SHELL_TRANSITIONS) { @@ -532,7 +537,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes, minDimensionsPair); } - final WindowLayoutInfo windowLayoutInfo = mController.mWindowLayoutComponent + final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(), taskConfiguration.windowConfiguration); final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams( @@ -814,7 +819,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final int displayId = taskProperties.getDisplayId(); final WindowConfiguration windowConfiguration = taskProperties.getConfiguration() .windowConfiguration; - final WindowLayoutInfo info = mController.mWindowLayoutComponent + final WindowLayoutInfo info = mWindowLayoutComponent .getCurrentWindowLayoutInfo(displayId, windowConfiguration); final List displayFeatures = info.getDisplayFeatures(); if (displayFeatures.isEmpty()) { 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 84b2bfc385596..c9f870005eb95 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -35,6 +35,8 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; +import android.view.WindowManager; +import android.window.TaskFragmentOrganizer; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; @@ -43,13 +45,13 @@ import androidx.annotation.UiContext; import androidx.window.common.CommonFoldingFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; -import androidx.window.common.RawFoldingFeatureProducer; import androidx.window.util.DataProducer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; @@ -80,13 +82,16 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final Map mConfigurationChangeListeners = new ArrayMap<>(); - public WindowLayoutComponentImpl(@NonNull Context context) { + private final TaskFragmentOrganizer mTaskFragmentOrganizer; + + public WindowLayoutComponentImpl(@NonNull Context context, + @NonNull TaskFragmentOrganizer taskFragmentOrganizer, + @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) { ((Application) context.getApplicationContext()) .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); - RawFoldingFeatureProducer foldingFeatureProducer = new RawFoldingFeatureProducer(context); - mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context, - foldingFeatureProducer); + mFoldingFeatureProducer = foldingFeatureProducer; mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); + mTaskFragmentOrganizer = taskFragmentOrganizer; } /** Registers to listen to {@link CommonFoldingFeature} changes */ @@ -182,7 +187,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { @GuardedBy("mLock") private boolean isListeningForLayoutChanges(IBinder token) { - for (Context context: getContextsListeningForLayoutChanges()) { + for (Context context : getContextsListeningForLayoutChanges()) { if (token.equals(Context.getToken(context))) { return true; } @@ -230,8 +235,9 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { /** * Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a * valid state is found. + * * @param context a proxy for the {@link android.view.Window} that contains the - * {@link DisplayFeature}. + * {@link DisplayFeature}. */ private WindowLayoutInfo getWindowLayoutInfo(@NonNull @UiContext Context context, List storedFeatures) { @@ -243,7 +249,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * Gets the current {@link WindowLayoutInfo} computed with passed {@link WindowConfiguration}. * * @return current {@link WindowLayoutInfo} on the default display. Returns - * empty {@link WindowLayoutInfo} on secondary displays. + * empty {@link WindowLayoutInfo} on secondary displays. */ @NonNull public WindowLayoutInfo getCurrentWindowLayoutInfo(int displayId, @@ -254,7 +260,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } } - /** @see #getWindowLayoutInfo(Context, List) */ + /** @see #getWindowLayoutInfo(Context, List) */ private WindowLayoutInfo getWindowLayoutInfo(int displayId, @NonNull WindowConfiguration windowConfiguration, List storedFeatures) { @@ -278,7 +284,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * * @param context a proxy for the {@link android.view.Window} that contains the * {@link DisplayFeature}. - * are within the {@link android.view.Window} of the {@link Activity} + * @return a {@link List} of {@link DisplayFeature}s that are within the + * {@link android.view.Window} of the {@link Activity} */ private List getDisplayFeatures( @NonNull @UiContext Context context, List storedFeatures) { @@ -317,8 +324,11 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } /** - * Checks whether display features should be reported for the activity. + * Calculates if the display features should be reported for the UI Context. The calculation + * uses the task information because that is accurate for Activities in ActivityEmbedding mode. * TODO(b/238948678): Support reporting display features in all windowing modes. + * + * @return true if the display features should be reported for the UI Context, false otherwise. */ private boolean shouldReportDisplayFeatures(@NonNull @UiContext Context context) { int displayId = context.getDisplay().getDisplayId(); @@ -337,7 +347,27 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { // bounds in this case can't be computed correctly, so we should skip. return false; } - windowingMode = taskConfig.windowConfiguration.getWindowingMode(); + final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); + final WindowManager windowManager = Objects.requireNonNull( + context.getSystemService(WindowManager.class)); + final Rect currentBounds = windowManager.getCurrentWindowMetrics().getBounds(); + final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds(); + boolean isTaskExpanded = maxBounds.equals(taskBounds); + boolean isActivityExpanded = maxBounds.equals(currentBounds); + /* + * We need to proxy being in full screen because when a user enters PiP and exits PiP + * the task windowingMode will report multi-window/pinned until the transition is + * finished in WM Shell. + * maxBounds == taskWindowBounds is a proxy check to verify the window is full screen + * For tasks that are letterboxed, we use currentBounds == maxBounds to filter these + * out. + */ + // TODO(b/262900133) remove currentBounds check when letterboxed apps report bounds. + // currently we don't want to report to letterboxed apps since they do not update the + // window bounds when the Activity is moved. An inaccurate fold will be reported so + // we skip. + return isTaskExpanded && (isActivityExpanded + || mTaskFragmentOrganizer.isActivityEmbedded(activityToken)); } else { // TODO(b/242674941): use task windowing mode for window context that associates with // activity. @@ -390,6 +420,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } @Override - public void onLowMemory() {} + public void onLowMemory() { + } } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 3cc31f9761c12..81c39571bffa2 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -90,6 +90,7 @@ import android.window.WindowContainerTransaction; 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.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutInfo; @@ -142,7 +143,9 @@ public class SplitControllerTest { MockitoAnnotations.initMocks(this); doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) .getCurrentWindowLayoutInfo(anyInt(), any()); - mSplitController = new SplitController(mWindowLayoutComponent); + DeviceStateManagerFoldingFeatureProducer producer = + mock(DeviceStateManagerFoldingFeatureProducer.class); + mSplitController = new SplitController(mWindowLayoutComponent, producer); mSplitPresenter = mSplitController.mPresenter; mSplitInfos = new ArrayList<>(); mEmbeddingCallback = splitInfos -> { diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index 6dae0a1086b3f..0263d4daf88c5 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -71,6 +71,7 @@ import android.window.WindowContainerTransaction; 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.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutInfo; @@ -113,7 +114,9 @@ public class SplitPresenterTest { MockitoAnnotations.initMocks(this); doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) .getCurrentWindowLayoutInfo(anyInt(), any()); - mController = new SplitController(mWindowLayoutComponent); + DeviceStateManagerFoldingFeatureProducer producer = + mock(DeviceStateManagerFoldingFeatureProducer.class); + mController = new SplitController(mWindowLayoutComponent, producer); mPresenter = mController.mPresenter; spyOn(mController); spyOn(mPresenter); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 98772360a3211..7d9d8b0f3a066 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -45,6 +45,8 @@ import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; +import androidx.window.extensions.layout.WindowLayoutComponentImpl; import com.google.android.collect.Lists; @@ -82,7 +84,10 @@ public class TaskFragmentContainerTest { @Before public void setup() { MockitoAnnotations.initMocks(this); - mController = new SplitController(); + DeviceStateManagerFoldingFeatureProducer producer = + mock(DeviceStateManagerFoldingFeatureProducer.class); + WindowLayoutComponentImpl component = mock(WindowLayoutComponentImpl.class); + mController = new SplitController(component, producer); spyOn(mController); mActivity = createMockActivity(); mIntent = new Intent();