Merge "Add WindowContext support to WindowLayoutComponentImpl" into tm-qpr-dev
This commit is contained in:
@@ -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() {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user