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

Commit 183f5d82 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Add support to listen the proposed rotation from window manager

This exposes window-based rotation. The event is generally from
WindowOrientationListener, which may use a more explicit and
accurate TYPE_DEVICE_ORIENTATION sensor (currently non-public)
if available. It can also consider other non-sensor information
(depending on the implementation of external rotation resolver)
to provide a proper rotation. So the event from it will be more
reliable. Also it is easier to use because the reported value
will be a specific rotation instead of raw degrees.

Some use cases of android.view.OrientationEventListener can
change to use this new listener if the purpose is to monitor
specific rotation.

Bug: 251440125
Test: atest WindowManagerTests#testProposedRotationListener

Change-Id: Ia9fc52e9d22bedea4be4b8962b90d03c5b342e95
parent a9233ab0
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -53593,11 +53593,13 @@ package android.view {
  public interface WindowManager extends android.view.ViewManager {
    method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
    method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
    method public default void addProposedRotationListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
    method @NonNull public default android.view.WindowMetrics getCurrentWindowMetrics();
    method @Deprecated public android.view.Display getDefaultDisplay();
    method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
    method public default boolean isCrossWindowBlurEnabled();
    method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
    method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
    method public void removeViewImmediate(android.view.View);
    field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
    field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
+3 −0
Original line number Diff line number Diff line
@@ -284,6 +284,9 @@ interface IWindowManager
    @UnsupportedAppUsage
    void removeRotationWatcher(IRotationWatcher watcher);

    /** Registers the listener to the context token and returns the current proposed rotation. */
    int registerProposedRotationListener(IBinder contextToken, IRotationWatcher listener);

    /**
     * Determine the preferred edge of the screen to pin the compact options menu against.
     *
+33 −0
Original line number Diff line number Diff line
@@ -129,6 +129,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;

/**
 * The interface that apps use to talk to the window manager.
@@ -1357,6 +1358,38 @@ public interface WindowManager extends ViewManager {
    default void removeCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) {
    }

    /**
     * Adds a listener to start monitoring the proposed rotation of the current associated context.
     * It reports the current recommendation for the rotation that takes various factors (e.g.
     * sensor, context, device state, etc) into account. The proposed rotation might not be applied
     * by the system automatically due to the application's active preference to lock the
     * orientation (e.g. with {@link android.app.Activity#setRequestedOrientation(int)}). This
     * listener gives application an opportunity to selectively react to device orientation changes.
     * The newly added listener will be called with current proposed rotation. Note that the context
     * of this window manager instance must be a {@link android.annotation.UiContext}.
     *
     * @param executor The executor on which callback method will be invoked.
     * @param listener Called when the proposed rotation for the context is being delivered.
     *                 The reported rotation can be {@link Surface#ROTATION_0},
     *                 {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180} and
     *                 {@link Surface#ROTATION_270}.
     * @throws UnsupportedOperationException if this method is called on an instance that is not
     *         associated with a {@link android.annotation.UiContext}.
     */
    default void addProposedRotationListener(@NonNull @CallbackExecutor Executor executor,
            @NonNull IntConsumer listener) {
    }

    /**
     * Removes a listener, previously added with {@link #addProposedRotationListener}. It is
     * recommended to call when the associated context no longer has visible components. No-op if
     * the provided listener is not registered.
     *
     * @param listener The listener to be removed.
     */
    default void removeProposedRotationListener(@NonNull IntConsumer listener) {
    }

    /**
     * @hide
     */
+127 −0
Original line number Diff line number Diff line
@@ -38,7 +38,11 @@ import com.android.internal.util.FastPrintWriter;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;

/**
 * Provides low-level communication with the system window manager for
@@ -136,6 +140,9 @@ public final class WindowManagerGlobal {

    private final ArrayList<ViewRootImpl> mWindowlessRoots = new ArrayList<ViewRootImpl>();

    /** A context token only has one remote registration to system. */
    private WeakHashMap<IBinder, ProposedRotationListenerDelegate> mProposedRotationListenerMap;

    private Runnable mSystemPropertyUpdater;

    private WindowManagerGlobal() {
@@ -666,6 +673,126 @@ public final class WindowManagerGlobal {
        }
    }

    /** Registers the listener to the context token and returns the current proposed rotation. */
    public void registerProposedRotationListener(IBinder contextToken, Executor executor,
            IntConsumer listener) {
        ProposedRotationListenerDelegate delegate;
        synchronized (mLock) {
            if (mProposedRotationListenerMap == null) {
                mProposedRotationListenerMap = new WeakHashMap<>(1);
            }
            delegate = mProposedRotationListenerMap.get(contextToken);
            final ProposedRotationListenerDelegate existingDelegate = delegate;
            if (delegate == null) {
                mProposedRotationListenerMap.put(contextToken,
                        delegate = new ProposedRotationListenerDelegate());
            }
            if (!delegate.add(executor, listener)) {
                // Duplicated listener.
                return;
            }
            if (existingDelegate != null) {
                executor.execute(() -> listener.accept(existingDelegate.mLastRotation));
                return;
            }
        }
        try {
            final int currentRotation = getWindowManagerService().registerProposedRotationListener(
                    contextToken, delegate);
            delegate.onRotationChanged(currentRotation);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /** Unregisters the proposed rotation listener of the given token. */
    public void unregisterProposedRotationListener(IBinder contextToken, IntConsumer listener) {
        final ProposedRotationListenerDelegate delegate;
        synchronized (mLock) {
            if (mProposedRotationListenerMap == null) {
                return;
            }
            delegate = mProposedRotationListenerMap.get(contextToken);
            if (delegate == null) {
                return;
            }
            if (delegate.remove(listener)) {
                // The delegate becomes empty.
                mProposedRotationListenerMap.remove(contextToken);
            } else {
                // The delegate still contains other listeners.
                return;
            }
        }
        try {
            getWindowManagerService().removeRotationWatcher(delegate);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
    }

    private static class ProposedRotationListenerDelegate extends IRotationWatcher.Stub {
        static class ListenerWrapper {
            final Executor mExecutor;
            final WeakReference<IntConsumer> mListener;

            ListenerWrapper(Executor executor, IntConsumer listener) {
                mExecutor = executor;
                mListener = new WeakReference<>(listener);
            }
        }

        /** The registered listeners. */
        private final ArrayList<ListenerWrapper> mListeners = new ArrayList<>(1);
        /** A thread-safe copy of registered listeners for dispatching events. */
        private volatile ListenerWrapper[] mListenerArray;
        int mLastRotation;

        boolean add(Executor executor, IntConsumer listener) {
            for (int i = mListeners.size() - 1; i >= 0; i--) {
                if (mListeners.get(i).mListener.get() == listener) {
                    // Ignore adding duplicated listener.
                    return false;
                }
            }
            mListeners.add(new ListenerWrapper(executor, listener));
            mListenerArray = mListeners.toArray(new ListenerWrapper[0]);
            return true;
        }

        boolean remove(IntConsumer listener) {
            for (int i = mListeners.size() - 1; i >= 0; i--) {
                if (mListeners.get(i).mListener.get() == listener) {
                    mListeners.remove(i);
                    mListenerArray = mListeners.toArray(new ListenerWrapper[0]);
                    return mListeners.isEmpty();
                }
            }
            return false;
        }

        @Override
        public void onRotationChanged(int rotation) {
            mLastRotation = rotation;
            boolean alive = false;
            for (ListenerWrapper listenerWrapper : mListenerArray) {
                final IntConsumer listener = listenerWrapper.mListener.get();
                if (listener != null) {
                    listenerWrapper.mExecutor.execute(() -> listener.accept(rotation));
                    alive = true;
                }
            }
            if (!alive) {
                // Unregister if there is no strong reference.
                try {
                    getWindowManagerService().removeRotationWatcher(this);
                } catch (RemoteException e) {
                    e.rethrowFromSystemServer();
                }
            }
        }
    }

    /** @hide */
    public void addWindowlessRoot(ViewRootImpl impl) {
        synchronized (mLock) {
+21 −0
Original line number Diff line number Diff line
@@ -47,9 +47,11 @@ import com.android.internal.os.IResultReceiver;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;

/**
 * Provides low-level communication with the system window manager for
@@ -337,6 +339,25 @@ public final class WindowManagerImpl implements WindowManager {
        CrossWindowBlurListeners.getInstance().removeListener(listener);
    }

    @Override
    public void addProposedRotationListener(@NonNull @CallbackExecutor Executor executor,
            @NonNull IntConsumer listener) {
        Objects.requireNonNull(executor, "executor must not be null");
        Objects.requireNonNull(listener, "listener must not be null");
        final IBinder contextToken = Context.getToken(mContext);
        if (contextToken == null) {
            throw new UnsupportedOperationException("The context of this window manager instance "
                    + "must be a UI context, e.g. an Activity or a Context created by "
                    + "Context#createWindowContext()");
        }
        mGlobal.registerProposedRotationListener(contextToken, executor, listener);
    }

    @Override
    public void removeProposedRotationListener(@NonNull IntConsumer listener) {
        mGlobal.unregisterProposedRotationListener(Context.getToken(mContext), listener);
    }

    @Override
    public boolean isTaskSnapshotSupported() {
        try {
Loading