Merge "Use bounds to proxy if an app is full screen." into tm-qpr-dev

This commit is contained in:
Diego Vela
2023-01-06 00:00:29 +00:00
committed by Android (Google) Code Review
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

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

View File

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

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

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