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; package androidx.window.extensions;
import android.app.ActivityThread; import android.app.ActivityThread;
import android.app.Application;
import android.content.Context; import android.content.Context;
import android.window.TaskFragmentOrganizer;
import androidx.annotation.NonNull; 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.WindowAreaComponent;
import androidx.window.extensions.area.WindowAreaComponentImpl; import androidx.window.extensions.area.WindowAreaComponentImpl;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent; 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.WindowLayoutComponent;
import androidx.window.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import java.util.Objects;
/** /**
* The reference implementation of {@link WindowExtensions} that implements the initial API version. * 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 { public class WindowExtensionsImpl implements WindowExtensions {
private final Object mLock = new Object(); private final Object mLock = new Object();
private volatile WindowLayoutComponent mWindowLayoutComponent; private volatile DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer;
private volatile WindowLayoutComponentImpl mWindowLayoutComponent;
private volatile SplitController mSplitController; private volatile SplitController mSplitController;
private volatile WindowAreaComponent mWindowAreaComponent; private volatile WindowAreaComponent mWindowAreaComponent;
@@ -44,6 +51,49 @@ public class WindowExtensionsImpl implements WindowExtensions {
return 1; 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, * Returns a reference implementation of {@link WindowLayoutComponent} if available,
* {@code null} otherwise. The implementation must match the API level reported in * {@code null} otherwise. The implementation must match the API level reported in
@@ -52,15 +102,7 @@ public class WindowExtensionsImpl implements WindowExtensions {
*/ */
@Override @Override
public WindowLayoutComponent getWindowLayoutComponent() { public WindowLayoutComponent getWindowLayoutComponent() {
if (mWindowLayoutComponent == null) { return getWindowLayoutComponentImpl();
synchronized (mLock) {
if (mWindowLayoutComponent == null) {
Context context = ActivityThread.currentApplication();
mWindowLayoutComponent = new WindowLayoutComponentImpl(context);
}
}
}
return mWindowLayoutComponent;
} }
/** /**
@@ -74,7 +116,10 @@ public class WindowExtensionsImpl implements WindowExtensions {
if (mSplitController == null) { if (mSplitController == null) {
synchronized (mLock) { synchronized (mLock) {
if (mSplitController == null) { 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.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.window.common.CommonFoldingFeature; import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.extensions.WindowExtensionsProvider;
import androidx.window.extensions.embedding.TransactionManager.TransactionRecord; import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
import androidx.window.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutComponentImpl;
@@ -145,19 +145,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private final Handler mHandler; private final Handler mHandler;
final Object mLock = new Object(); final Object mLock = new Object();
private final ActivityStartMonitor mActivityStartMonitor; private final ActivityStartMonitor mActivityStartMonitor;
@NonNull
final WindowLayoutComponentImpl mWindowLayoutComponent;
public SplitController() { public SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent,
this((WindowLayoutComponentImpl) Objects.requireNonNull(WindowExtensionsProvider @NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
.getWindowExtensions().getWindowLayoutComponent()));
}
@VisibleForTesting
SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent) {
final MainThreadExecutor executor = new MainThreadExecutor(); final MainThreadExecutor executor = new MainThreadExecutor();
mHandler = executor.mHandler; mHandler = executor.mHandler;
mPresenter = new SplitPresenter(executor, this); mPresenter = new SplitPresenter(executor, windowLayoutComponent, this);
mTransactionManager = new TransactionManager(mPresenter); mTransactionManager = new TransactionManager(mPresenter);
final ActivityThread activityThread = ActivityThread.currentActivityThread(); final ActivityThread activityThread = ActivityThread.currentActivityThread();
final Application application = activityThread.getApplication(); final Application application = activityThread.getApplication();
@@ -168,8 +161,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
mActivityStartMonitor = new ActivityStartMonitor(); mActivityStartMonitor = new ActivityStartMonitor();
instrumentation.addMonitor(mActivityStartMonitor); instrumentation.addMonitor(mActivityStartMonitor);
mWindowLayoutComponent = windowLayoutComponent; foldingFeatureProducer.addDataChangedCallback(new FoldingFeatureListener());
mWindowLayoutComponent.addFoldingStateChangedCallback(new FoldingFeatureListener());
} }
private class FoldingFeatureListener implements Consumer<List<CommonFoldingFeature>> { 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.embedding.TaskContainer.TaskProperties;
import androidx.window.extensions.layout.DisplayFeature; import androidx.window.extensions.layout.DisplayFeature;
import androidx.window.extensions.layout.FoldingFeature; import androidx.window.extensions.layout.FoldingFeature;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo; import androidx.window.extensions.layout.WindowLayoutInfo;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
@@ -137,10 +138,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
.setSplitType(new ExpandContainersSplitType()) .setSplitType(new ExpandContainersSplitType())
.build(); .build();
private final WindowLayoutComponentImpl mWindowLayoutComponent;
private final SplitController mController; private final SplitController mController;
SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) { SplitPresenter(@NonNull Executor executor,
@NonNull WindowLayoutComponentImpl windowLayoutComponent,
@NonNull SplitController controller) {
super(executor, controller); super(executor, controller);
mWindowLayoutComponent = windowLayoutComponent;
mController = controller; mController = controller;
registerOrganizer(); registerOrganizer();
if (!SplitController.ENABLE_SHELL_TRANSITIONS) { if (!SplitController.ENABLE_SHELL_TRANSITIONS) {
@@ -556,7 +561,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes, return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes,
minDimensionsPair); minDimensionsPair);
} }
final WindowLayoutInfo windowLayoutInfo = mController.mWindowLayoutComponent final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent
.getCurrentWindowLayoutInfo(taskProperties.getDisplayId(), .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
taskConfiguration.windowConfiguration); taskConfiguration.windowConfiguration);
final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams( final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams(
@@ -838,7 +843,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final int displayId = taskProperties.getDisplayId(); final int displayId = taskProperties.getDisplayId();
final WindowConfiguration windowConfiguration = taskProperties.getConfiguration() final WindowConfiguration windowConfiguration = taskProperties.getConfiguration()
.windowConfiguration; .windowConfiguration;
final WindowLayoutInfo info = mController.mWindowLayoutComponent final WindowLayoutInfo info = mWindowLayoutComponent
.getCurrentWindowLayoutInfo(displayId, windowConfiguration); .getCurrentWindowLayoutInfo(displayId, windowConfiguration);
final List<DisplayFeature> displayFeatures = info.getDisplayFeatures(); final List<DisplayFeature> displayFeatures = info.getDisplayFeatures();
if (displayFeatures.isEmpty()) { if (displayFeatures.isEmpty()) {

View File

@@ -35,6 +35,8 @@ import android.graphics.Rect;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.view.WindowManager;
import android.window.TaskFragmentOrganizer;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -43,13 +45,13 @@ import androidx.annotation.UiContext;
import androidx.window.common.CommonFoldingFeature; import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.common.RawFoldingFeatureProducer;
import androidx.window.util.DataProducer; import androidx.window.util.DataProducer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -80,13 +82,16 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private final Map<IBinder, ConfigurationChangeListener> mConfigurationChangeListeners = private final Map<IBinder, ConfigurationChangeListener> mConfigurationChangeListeners =
new ArrayMap<>(); 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()) ((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
RawFoldingFeatureProducer foldingFeatureProducer = new RawFoldingFeatureProducer(context); mFoldingFeatureProducer = foldingFeatureProducer;
mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context,
foldingFeatureProducer);
mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
mTaskFragmentOrganizer = taskFragmentOrganizer;
} }
/** Registers to listen to {@link CommonFoldingFeature} changes */ /** Registers to listen to {@link CommonFoldingFeature} changes */
@@ -182,7 +187,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
@GuardedBy("mLock") @GuardedBy("mLock")
private boolean isListeningForLayoutChanges(IBinder token) { private boolean isListeningForLayoutChanges(IBinder token) {
for (Context context: getContextsListeningForLayoutChanges()) { for (Context context : getContextsListeningForLayoutChanges()) {
if (token.equals(Context.getToken(context))) { if (token.equals(Context.getToken(context))) {
return true; return true;
} }
@@ -230,6 +235,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
/** /**
* Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a * Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a
* valid state is found. * valid state is found.
*
* @param context a proxy for the {@link android.view.Window} that contains the * @param context a proxy for the {@link android.view.Window} that contains the
* {@link DisplayFeature}. * {@link DisplayFeature}.
*/ */
@@ -278,7 +284,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
* *
* @param context a proxy for the {@link android.view.Window} that contains the * @param context a proxy for the {@link android.view.Window} that contains the
* {@link DisplayFeature}. * {@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( private List<DisplayFeature> getDisplayFeatures(
@NonNull @UiContext Context context, List<CommonFoldingFeature> storedFeatures) { @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. * 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) { private boolean shouldReportDisplayFeatures(@NonNull @UiContext Context context) {
int displayId = context.getDisplay().getDisplayId(); 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. // bounds in this case can't be computed correctly, so we should skip.
return false; 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 { } else {
// TODO(b/242674941): use task windowing mode for window context that associates with // TODO(b/242674941): use task windowing mode for window context that associates with
// activity. // activity.
@@ -390,6 +420,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
} }
@Override @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.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo; import androidx.window.extensions.layout.WindowLayoutInfo;
@@ -142,7 +143,9 @@ public class SplitControllerTest {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
.getCurrentWindowLayoutInfo(anyInt(), any()); .getCurrentWindowLayoutInfo(anyInt(), any());
mSplitController = new SplitController(mWindowLayoutComponent); DeviceStateManagerFoldingFeatureProducer producer =
mock(DeviceStateManagerFoldingFeatureProducer.class);
mSplitController = new SplitController(mWindowLayoutComponent, producer);
mSplitPresenter = mSplitController.mPresenter; mSplitPresenter = mSplitController.mPresenter;
mSplitInfos = new ArrayList<>(); mSplitInfos = new ArrayList<>();
mEmbeddingCallback = splitInfos -> { mEmbeddingCallback = splitInfos -> {

View File

@@ -75,6 +75,7 @@ import android.window.WindowContainerTransaction;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo; import androidx.window.extensions.layout.WindowLayoutInfo;
@@ -117,7 +118,9 @@ public class SplitPresenterTest {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
.getCurrentWindowLayoutInfo(anyInt(), any()); .getCurrentWindowLayoutInfo(anyInt(), any());
mController = new SplitController(mWindowLayoutComponent); DeviceStateManagerFoldingFeatureProducer producer =
mock(DeviceStateManagerFoldingFeatureProducer.class);
mController = new SplitController(mWindowLayoutComponent, producer);
mPresenter = mController.mPresenter; mPresenter = mController.mPresenter;
spyOn(mController); spyOn(mController);
spyOn(mPresenter); spyOn(mPresenter);

View File

@@ -45,6 +45,8 @@ import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.google.android.collect.Lists; import com.google.android.collect.Lists;
@@ -82,7 +84,10 @@ public class TaskFragmentContainerTest {
@Before @Before
public void setup() { public void setup() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mController = new SplitController(); DeviceStateManagerFoldingFeatureProducer producer =
mock(DeviceStateManagerFoldingFeatureProducer.class);
WindowLayoutComponentImpl component = mock(WindowLayoutComponentImpl.class);
mController = new SplitController(component, producer);
spyOn(mController); spyOn(mController);
mActivity = createMockActivity(); mActivity = createMockActivity();
mIntent = new Intent(); mIntent = new Intent();