RotationReceiver.java

/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.camera.view;

import android.content.Context;
import android.hardware.SensorManager;
import android.view.OrientationEventListener;
import android.view.Surface;

import androidx.annotation.NonNull;
import androidx.camera.core.UseCase;

/**
 * Helper class for receiving rotation updates from the {@link SensorManager} when the
 * orientation of the device has changed.
 *
 * <p> This class is an wrapper of {@link OrientationEventListener} that notifies the app about
 * physical orientation changes in the format of {@link Surface} rotation. It's useful when the
 * device UI is in a fixed portrait or landscape orientation, while the app still wants to set the
 * {@link UseCase} target rotation based on the device's physical orientation.
 *
 * <pre><code>
 * rotationReceiver = new RotationReceiver(context) {
 *         public void onRotationChanged(int rotation) {
 *             mImageCapture.setTargetRotation(rotation);
 *         }
 * };
 * if (rotationReceiver.canDetectOrientation()) {
 *     rotationReceiver.enable();
 * }
 *
 * // Disable it when it's no longer needed.
 * rotationReceiver.disable();
 * </code></pre>
 *
 * @see OrientationEventListener
 */
public abstract class RotationReceiver {

    private static final int INVALID_SURFACE_ROTATION = -1;

    // Synthetic access
    @SuppressWarnings("WeakerAccess")
    int mRotation = INVALID_SURFACE_ROTATION;

    private final OrientationEventListener mOrientationEventListener;

    public RotationReceiver(@NonNull Context context) {
        mOrientationEventListener = new OrientationEventListener(context) {
            @Override
            public void onOrientationChanged(int orientation) {
                if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
                    // Short-circuit if orientation is unknown. Unknown rotation can't be handled
                    // so it shouldn't be sent.
                    return;
                }

                int newRotation;
                if (orientation >= 315 || orientation < 45) {
                    newRotation = Surface.ROTATION_0;
                } else if (orientation >= 225) {
                    newRotation = Surface.ROTATION_90;
                } else if (orientation >= 135) {
                    newRotation = Surface.ROTATION_180;
                } else {
                    newRotation = Surface.ROTATION_270;
                }
                if (mRotation != newRotation) {
                    mRotation = newRotation;
                    onRotationChanged(newRotation);
                }
            }
        };
    }

    /**
     * Checks if the RotationReceiver can detect orientation changes.
     *
     * @see OrientationEventListener#canDetectOrientation()
     */
    public boolean canDetectOrientation() {
        return mOrientationEventListener.canDetectOrientation();
    }

    /**
     * Enables the RotationReceiver so it will monitor the sensor and call onRotationChanged when
     * the device orientation changes.
     *
     * <p> By default, the receiver is not enabled.
     *
     * @see OrientationEventListener#enable()
     */
    public void enable() {
        mOrientationEventListener.enable();
    }

    /**
     * Disables the RotationReceiver.
     *
     * @see OrientationEventListener#disable()
     */
    public void disable() {
        mOrientationEventListener.disable();
    }

    /**
     * Called when the physical rotation of the device changes.
     *
     * <p> The rotation is one of the {@link Surface} rotations mapped from orientation
     * degrees.
     *
     * <table summary="Orientation degrees to Surface rotation mapping">
     * <tr><th>Orientation degrees</th><th>Surface rotation</th></tr>
     * <tr><td>[-45°, 45°)</td><td>{@link Surface#ROTATION_0}</td></tr>
     * <tr><td>[45°, 135°)</td><td>{@link Surface#ROTATION_270}</td></tr>
     * <tr><td>[135°, 225°)</td><td>{@link Surface#ROTATION_180}</td></tr>
     * <tr><td>[225°, 315°)</td><td>{@link Surface#ROTATION_90}</td></tr>
     * </table>
     *
     * @see OrientationEventListener#onOrientationChanged(int)
     */
    public abstract void onRotationChanged(int rotation);
}