Merge "Add WindowContext support to WindowLayoutComponentImpl" into tm-qpr-dev

This commit is contained in:
Charles Chen
2022-08-25 08:28:25 +00:00
committed by Android (Google) Code Review
2 changed files with 113 additions and 53 deletions

View File

@@ -20,21 +20,26 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
import static androidx.window.util.ExtensionHelper.isZero;
import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityClient;
import android.app.Application;
import android.app.WindowConfiguration;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
import android.window.WindowContext;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiContext;
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
@@ -58,11 +63,14 @@ import java.util.function.Consumer;
public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private static final String TAG = "SampleExtension";
private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
private final Map<Context, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
new ArrayMap<>();
private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
new ArrayMap<>();
public WindowLayoutComponentImpl(@NonNull Context context) {
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
@@ -78,14 +86,42 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
* @param activity hosting a {@link android.view.Window}
* @param consumer interested in receiving updates to {@link WindowLayoutInfo}
*/
@Override
public void addWindowLayoutInfoListener(@NonNull Activity activity,
@NonNull Consumer<WindowLayoutInfo> consumer) {
addWindowLayoutInfoListener((Context) activity, consumer);
}
/**
* Similar to {@link #addWindowLayoutInfoListener(Activity, Consumer)}, but takes a UI Context
* as a parameter.
*/
// TODO(b/204073440): Add @Override to hook the API in WM extensions library.
public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
@NonNull Consumer<WindowLayoutInfo> consumer) {
if (mWindowLayoutChangeListeners.containsKey(context)
|| mWindowLayoutChangeListeners.containsValue(consumer)) {
// Early return if the listener or consumer has been registered.
return;
}
if (!context.isUiContext()) {
throw new IllegalArgumentException("Context must be a UI Context, which should be"
+ " an Activity or a WindowContext");
}
mFoldingFeatureProducer.getData((features) -> {
// Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, features);
WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
consumer.accept(newWindowLayout);
});
mWindowLayoutChangeListeners.put(activity, consumer);
mWindowLayoutChangeListeners.put(context, consumer);
if (context instanceof WindowContext) {
final IBinder windowContextToken = context.getWindowContextToken();
final WindowContextConfigListener listener =
new WindowContextConfigListener(windowContextToken);
context.registerComponentCallbacks(listener);
mWindowContextConfigListeners.put(windowContextToken, listener);
}
}
/**
@@ -93,18 +129,30 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*
* @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo}
*/
@Override
public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) {
for (Context context : mWindowLayoutChangeListeners.keySet()) {
if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) {
continue;
}
if (context instanceof WindowContext) {
final IBinder token = context.getWindowContextToken();
context.unregisterComponentCallbacks(mWindowContextConfigListeners.get(token));
mWindowContextConfigListeners.remove(token);
}
break;
}
mWindowLayoutChangeListeners.values().remove(consumer);
}
@NonNull
Set<Activity> getActivitiesListeningForLayoutChanges() {
Set<Context> getContextsListeningForLayoutChanges() {
return mWindowLayoutChangeListeners.keySet();
}
private boolean isListeningForLayoutChanges(IBinder token) {
for (Activity activity: getActivitiesListeningForLayoutChanges()) {
if (token.equals(activity.getWindow().getAttributes().token)) {
for (Context context: getContextsListeningForLayoutChanges()) {
if (token.equals(Context.getToken(context))) {
return true;
}
}
@@ -138,10 +186,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
for (Activity activity : getActivitiesListeningForLayoutChanges()) {
for (Context context : getContextsListeningForLayoutChanges()) {
// Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(activity);
WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, storedFeatures);
Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context);
WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, storedFeatures);
layoutConsumer.accept(newWindowLayout);
}
}
@@ -149,11 +197,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
/**
* Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a
* valid state is found.
* @param activity 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}.
*/
private WindowLayoutInfo getWindowLayoutInfo(
@NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) {
List<DisplayFeature> displayFeatureList = getDisplayFeatures(activity, storedFeatures);
private WindowLayoutInfo getWindowLayoutInfo(@NonNull @UiContext Context context,
List<CommonFoldingFeature> storedFeatures) {
List<DisplayFeature> displayFeatureList = getDisplayFeatures(context, storedFeatures);
return new WindowLayoutInfo(displayFeatureList);
}
@@ -170,18 +219,18 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
* bounds are not valid, constructing a {@link FoldingFeature} will throw an
* {@link IllegalArgumentException} since this can cause negative UI effects down stream.
*
* @param activity 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}.
* are within the {@link android.view.Window} of the {@link Activity}
*/
private List<DisplayFeature> getDisplayFeatures(
@NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) {
@NonNull @UiContext Context context, List<CommonFoldingFeature> storedFeatures) {
List<DisplayFeature> features = new ArrayList<>();
if (!shouldReportDisplayFeatures(activity)) {
if (!shouldReportDisplayFeatures(context)) {
return features;
}
int displayId = activity.getDisplay().getDisplayId();
int displayId = context.getDisplay().getDisplayId();
for (CommonFoldingFeature baseFeature : storedFeatures) {
Integer state = convertToExtensionState(baseFeature.getState());
if (state == null) {
@@ -189,9 +238,9 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
Rect featureRect = baseFeature.getRect();
rotateRectToDisplayRotation(displayId, featureRect);
transformToWindowSpaceRect(activity, featureRect);
transformToWindowSpaceRect(context, featureRect);
if (!isRectZero(featureRect)) {
if (!isZero(featureRect)) {
// TODO(b/228641877): Remove guarding when fixed.
features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
}
@@ -203,15 +252,21 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
* Checks whether display features should be reported for the activity.
* TODO(b/238948678): Support reporting display features in all windowing modes.
*/
private boolean shouldReportDisplayFeatures(@NonNull Activity activity) {
int displayId = activity.getDisplay().getDisplayId();
private boolean shouldReportDisplayFeatures(@NonNull @UiContext Context context) {
int displayId = context.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) {
// Display features are not supported on secondary displays.
return false;
}
final int taskWindowingMode = ActivityClient.getInstance().getTaskWindowingMode(
activity.getActivityToken());
if (taskWindowingMode == -1) {
final int windowingMode;
if (context instanceof Activity) {
windowingMode = ActivityClient.getInstance().getTaskWindowingMode(
context.getActivityToken());
} else {
windowingMode = context.getResources().getConfiguration().windowConfiguration
.getWindowingMode();
}
if (windowingMode == -1) {
// If we cannot determine the task windowing mode for any reason, it is likely that we
// won't be able to determine its position correctly as well. DisplayFeatures' bounds
// in this case can't be computed correctly, so we should skip.
@@ -219,36 +274,43 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
// It is recommended not to report any display features in multi-window mode, since it
// won't be possible to synchronize the display feature positions with window movement.
return !WindowConfiguration.inMultiWindowMode(taskWindowingMode);
return !WindowConfiguration.inMultiWindowMode(windowingMode);
}
/**
* Returns {@link true} if a {@link Rect} has zero width and zero height,
* {@code false} otherwise.
*/
private boolean isRectZero(Rect rect) {
return rect.width() == 0 && rect.height() == 0;
private void onDisplayFeaturesChangedIfListening(@NonNull IBinder token) {
if (isListeningForLayoutChanges(token)) {
mFoldingFeatureProducer.getData(
WindowLayoutComponentImpl.this::onDisplayFeaturesChanged);
}
}
private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
super.onActivityCreated(activity, savedInstanceState);
onDisplayFeaturesChangedIfListening(activity);
onDisplayFeaturesChangedIfListening(activity.getActivityToken());
}
@Override
public void onActivityConfigurationChanged(Activity activity) {
super.onActivityConfigurationChanged(activity);
onDisplayFeaturesChangedIfListening(activity);
}
private void onDisplayFeaturesChangedIfListening(Activity activity) {
IBinder token = activity.getWindow().getAttributes().token;
if (token == null || isListeningForLayoutChanges(token)) {
mFoldingFeatureProducer.getData(
WindowLayoutComponentImpl.this::onDisplayFeaturesChanged);
}
onDisplayFeaturesChangedIfListening(activity.getActivityToken());
}
}
private final class WindowContextConfigListener implements ComponentCallbacks {
final IBinder mToken;
WindowContextConfigListener(IBinder token) {
mToken = token;
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
onDisplayFeaturesChangedIfListening(mToken);
}
@Override
public void onLowMemory() {}
}
}

View File

@@ -21,14 +21,15 @@ import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiContext;
/**
* Util class for both Sidecar and Extensions.
@@ -86,12 +87,9 @@ public final class ExtensionHelper {
}
/** Transforms rectangle from absolute coordinate space to the window coordinate space. */
public static void transformToWindowSpaceRect(Activity activity, Rect inOutRect) {
Rect windowRect = getWindowBounds(activity);
if (windowRect == null) {
inOutRect.setEmpty();
return;
}
public static void transformToWindowSpaceRect(@NonNull @UiContext Context context,
Rect inOutRect) {
Rect windowRect = getWindowBounds(context);
if (!Rect.intersects(inOutRect, windowRect)) {
inOutRect.setEmpty();
return;
@@ -103,9 +101,9 @@ public final class ExtensionHelper {
/**
* Gets the current window bounds in absolute coordinates.
*/
@Nullable
private static Rect getWindowBounds(@NonNull Activity activity) {
return activity.getWindowManager().getCurrentWindowMetrics().getBounds();
@NonNull
private static Rect getWindowBounds(@NonNull @UiContext Context context) {
return context.getSystemService(WindowManager.class).getCurrentWindowMetrics().getBounds();
}
/**