Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e86d5c0e authored by Charles Chen's avatar Charles Chen Committed by Automerger Merge Worker
Browse files

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

Merge "Add WindowContext support to WindowLayoutComponentImpl" into tm-qpr-dev am: e85f7613 am: 1f8cb8f5

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19394534



Change-Id: I114e2ff916a168ead34a8a4a5e3105f7013fad89
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 6a1b1bed 1f8cb8f5
Loading
Loading
Loading
Loading
+102 −40
Original line number Diff line number Diff line
@@ -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);
            onDisplayFeaturesChangedIfListening(activity.getActivityToken());
        }
    }

        private void onDisplayFeaturesChangedIfListening(Activity activity) {
            IBinder token = activity.getWindow().getAttributes().token;
            if (token == null || isListeningForLayoutChanges(token)) {
                mFoldingFeatureProducer.getData(
                        WindowLayoutComponentImpl.this::onDisplayFeaturesChanged);
    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() {}
    }
}
+9 −11
Original line number Diff line number Diff line
@@ -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();
    }

    /**