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
This commit is contained in:
Diego Vela
2022-12-16 00:03:13 +00:00
parent e8cf22be3d
commit 5e0dd14900
7 changed files with 127 additions and 43 deletions

View File

@@ -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()
);
}
}
}

View File

@@ -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<List<CommonFoldingFeature>> {

View File

@@ -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<DisplayFeature> displayFeatures = info.getDisplayFeatures();
if (displayFeatures.isEmpty()) {

View File

@@ -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<IBinder, ConfigurationChangeListener> 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<CommonFoldingFeature> 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<CommonFoldingFeature> 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<DisplayFeature> getDisplayFeatures(
@NonNull @UiContext Context context, List<CommonFoldingFeature> 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() {
}
}
}

View File

@@ -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 -> {

View File

@@ -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);

View File

@@ -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();