blob: 1fe57f4de5a7df6b641480e460750e3763d66d40 [file] [log] [blame]
/*
* Copyright (C) 2023 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 com.android.DeviceAsWebcam;
import android.annotation.IntRange;
import android.content.Context;
import android.hardware.SensorManager;
import android.hardware.display.DisplayManager;
import android.view.Display;
import android.view.OrientationEventListener;
import android.view.Surface;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Provider for receiving rotation updates from the {@link SensorManager} when the rotation of
* the device has changed.
*
* <p> This class monitors motion sensor and notifies the listener about physical orientation
* changes in the rotation degrees value which can be used to rotate the stream images to the
* upright orientation.
*
* <pre><code>
* // Create a provider.
* RotationProvider mRotationProvider = new RotationProvider(getApplicationContext());
*
* // Add listener to receive updates.
* mRotationProvider.addListener(rotation -> {
* // Apply the rotation values to the related targets
* });
*
* // Remove when no longer needed.
* mRotationProvider.clearListener();
* </code></pre>
*/
public final class RotationProvider {
private final Object mLock = new Object();
private final OrientationEventListener mOrientationListener;
private final Map<Listener, ListenerWrapper> mListeners = new HashMap<>();
private int mRotation;
private int mSensorOrientation;
/**
* Creates a new RotationProvider.
*
* @param applicationContext the application context used to register
* {@link OrientationEventListener} or get display rotation.
* @param sensorOrientation the camera sensor orientation value
*/
public RotationProvider(Context applicationContext, int sensorOrientation) {
int displayRotation = applicationContext.getSystemService(DisplayManager.class).getDisplay(
Display.DEFAULT_DISPLAY).getRotation();
mRotation = displayRotation == Surface.ROTATION_270 ? 180 : 0;
mSensorOrientation = sensorOrientation;
mOrientationListener = new OrientationEventListener(applicationContext) {
@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 = sensorOrientationToRotationDegrees(orientation);
int originalRotation;
List<ListenerWrapper> listeners = new ArrayList<>();
// Take a snapshot for thread safety.
synchronized (mLock) {
originalRotation = mRotation;
if (mRotation != newRotation) {
mRotation = newRotation;
listeners.addAll(mListeners.values());
}
}
if (originalRotation != newRotation) {
if (!listeners.isEmpty()) {
for (ListenerWrapper listenerWrapper : listeners) {
listenerWrapper.onRotationChanged(newRotation);
}
}
}
}
};
}
public int getRotation() {
synchronized (mLock) {
return mRotation;
}
}
/**
* Sets a {@link Listener} that listens for rotation changes.
*
* @param executor The executor in which the {@link Listener#onRotationChanged(int)} will be
* run.
* @return false if the device cannot detection rotation changes. In that case, the listener
* will not be set.
*/
public boolean addListener(Executor executor, Listener listener) {
synchronized (mLock) {
if (!mOrientationListener.canDetectOrientation()) {
return false;
}
mListeners.put(listener, new ListenerWrapper(listener, executor));
mOrientationListener.enable();
}
return true;
}
/**
* Removes the given {@link Listener} from this object.
*
* <p> The removed listener will no longer receive rotation updates.
*/
public void removeListener(Listener listener) {
synchronized (mLock) {
ListenerWrapper listenerWrapper = mListeners.get(listener);
if (listenerWrapper != null) {
listenerWrapper.disable();
mListeners.remove(listener);
}
if (mListeners.isEmpty()) {
mOrientationListener.disable();
}
}
}
/**
* Converts sensor orientation degrees to the image rotation degrees.
*
* <p>Currently, the returned value can only be 0 or 180 because DeviceAsWebcam only support
* in the landscape mode. The webcam stream images will be rotated to upright orientation when
* the device is in the landscape orientation.
*/
private int sensorOrientationToRotationDegrees(@IntRange(from = 0, to = 359) int orientation) {
if ((mSensorOrientation % 180 == 90 && orientation >= 45 && orientation < 135) || (
mSensorOrientation % 180 == 0 && orientation >= 135 && orientation < 225)) {
return 180;
} else {
return 0;
}
}
/**
* Wrapper of {@link Listener} with the executor and a tombstone flag.
*/
private static class ListenerWrapper {
private final Listener mListener;
private final Executor mExecutor;
private final AtomicBoolean mEnabled;
ListenerWrapper(Listener listener, Executor executor) {
mListener = listener;
mExecutor = executor;
mEnabled = new AtomicBoolean(true);
}
void onRotationChanged(int rotation) {
mExecutor.execute(() -> {
if (mEnabled.get()) {
mListener.onRotationChanged(rotation);
}
});
}
/**
* Once disabled, the app will not receive callback even if it has already been posted on
* the callback thread.
*/
void disable() {
mEnabled.set(false);
}
}
/**
* Callback interface to receive rotation updates.
*
* <p>Currently, CameraController only sets an empty listener to enable the RotationProvider's
* orientation listener. In the coming CLs, a real implementation will be set to notify the
* preview activity to rotate the UI controls to correct orientation.
*/
public interface Listener {
/**
* Called when the physical rotation of the device changes to cause the corresponding
* rotation value is changed.
*
* <p>Currently, the returned value can only be 0 or 180 because DeviceAsWebcam only
* support in the landscape mode. The webcam stream images will be rotated to upright
* orientation when the device is in the landscape orientation.
*/
void onRotationChanged(int rotation);
}
}