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 8da591860c2d9..1cd3ea5592e3b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -75,8 +75,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; @@ -145,19 +145,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(); @@ -168,8 +161,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 7b2af4933e664..14d244bbb6cee 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -51,6 +51,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; @@ -137,10 +138,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) { @@ -556,7 +561,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( @@ -838,7 +843,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 fcd4d621e7534..121e81394b2d7 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 @@ -75,6 +75,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; @@ -117,7 +118,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();