Fix camera rotation for newer API levels.

This commit is contained in:
Alex Hart 2022-07-15 13:19:10 -03:00 committed by Cody Henthorne
parent 17b00734ac
commit 3c09655949
8 changed files with 95 additions and 123 deletions

View file

@ -67,10 +67,13 @@ import androidx.lifecycle.LiveData;
import com.google.common.util.concurrent.ListenableFuture;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.mediasend.RotationListener;
import java.io.File;
import java.util.concurrent.Executor;
import io.reactivex.rxjava3.disposables.Disposable;
/**
* A {@link View} that displays a preview of the camera with methods {@link
* #takePicture(Executor, OnImageCapturedCallback)},
@ -134,6 +137,10 @@ public final class SignalCameraView extends FrameLayout {
// BEGIN Custom Signal Code Block
private Consumer<Throwable> errorConsumer;
private Throwable pendingError;
private RotationListener rotationListener;
private Disposable rotationDisposable;
private RotationListener.Rotation rotation;
// END Custom Signal Code Block
public SignalCameraView(@NonNull Context context) {
@ -190,6 +197,7 @@ public final class SignalCameraView extends FrameLayout {
addView(mPreviewView = new PreviewView(getContext()), 0 /* view position */);
// Begin custom signal code block
rotationListener = new RotationListener(context);
mPreviewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
mCameraModule = new SignalCameraXModule(this, error -> {
if (errorConsumer != null) {
@ -314,6 +322,9 @@ public final class SignalCameraView extends FrameLayout {
DisplayManager dpyMgr =
(DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
dpyMgr.registerDisplayListener(mDisplayListener, new Handler(Looper.getMainLooper()));
rotationDisposable = rotationListener.getObservable().distinctUntilChanged().subscribe(rotation -> {
this.rotation = rotation;
});
}
@Override
@ -322,6 +333,7 @@ public final class SignalCameraView extends FrameLayout {
DisplayManager dpyMgr =
(DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
dpyMgr.unregisterDisplayListener(mDisplayListener);
rotationDisposable.dispose();
}
/**
@ -368,22 +380,28 @@ public final class SignalCameraView extends FrameLayout {
super.onLayout(changed, left, top, right, bottom);
}
// BEGIN Custom Signal Code Block
/**
* @return One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, {@link
* Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
*/
int getDisplaySurfaceRotation() {
Display display = getDisplay();
if (rotation == null) {
Display display = getDisplay();
// Null when the View is detached. If we were in the middle of a background operation,
// better to not NPE. When the background operation finishes, it'll realize that the camera
// was closed.
if (display == null) {
return 0;
// Null when the View is detached. If we were in the middle of a background operation,
// better to not NPE. When the background operation finishes, it'll realize that the camera
// was closed.
if (display == null) {
return 0;
}
return display.getRotation();
} else {
return rotation.getSurfaceRotation();
}
return display.getRotation();
}
// END Custom Signal Code Block
/**
* Returns the scale type used to scale the preview.

View file

@ -125,11 +125,6 @@ public class AvatarSelectionActivity extends AppCompatActivity implements Camera
transaction.commit();
}
@Override
public int getDisplayRotation() {
return getWindowManager().getDefaultDisplay().getRotation();
}
@Override
public void onCameraCountButtonClicked() {
throw new UnsupportedOperationException("Cannot select more than one photo");

View file

@ -53,6 +53,8 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.ByteArrayOutputStream;
import java.util.Optional;
import io.reactivex.rxjava3.disposables.Disposable;
/**
* Camera capture implemented with the legacy camera API's. Should only be used if sdk < 21.
*/
@ -71,6 +73,8 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
private Controller controller;
private OrderEnforcer<Stage> orderEnforcer;
private Camera1Controller.Properties properties;
private RotationListener rotationListener;
private Disposable rotationListenerDisposable;
private final Observer<Optional<Media>> thumbObserver = this::presentRecentItemThumbnail;
private boolean isThumbAvailable;
@ -116,6 +120,7 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
super.onViewCreated(view, savedInstanceState);
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
rotationListener = new RotationListener(requireContext());
cameraPreview = view.findViewById(R.id.camera_preview);
controlsContainer = view.findViewById(R.id.camera_controls_container);
@ -161,9 +166,17 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
orderEnforcer.run(Stage.SURFACE_AVAILABLE, () -> {
camera.linkSurface(cameraPreview.getSurfaceTexture());
camera.setScreenRotation(controller.getDisplayRotation());
});
rotationListenerDisposable = rotationListener.getObservable()
.distinctUntilChanged()
.filter(rotation -> rotation != RotationListener.Rotation.ROTATION_180)
.subscribe(rotation -> {
orderEnforcer.run(Stage.SURFACE_AVAILABLE, () -> {
camera.setScreenRotation(rotation.getSurfaceRotation());
});
});
orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale);
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
@ -171,6 +184,7 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
@Override
public void onPause() {
super.onPause();
rotationListenerDisposable.dispose();
camera.release();
orderEnforcer.reset();
}
@ -232,7 +246,6 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment,
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
orderEnforcer.run(Stage.SURFACE_AVAILABLE, () -> camera.setScreenRotation(controller.getDisplayRotation()));
orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale);
}

View file

@ -53,7 +53,6 @@ public interface CameraFragment {
void onVideoCaptured(@NonNull FileDescriptor fd);
void onVideoCaptureError();
void onGalleryClicked();
int getDisplayRotation();
void onCameraCountButtonClicked();
@NonNull LiveData<Optional<Media>> getMostRecentMediaItem();
@NonNull MediaConstraints getMediaConstraints();

View file

@ -70,11 +70,11 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
private static final String TAG = Log.tag(CameraXFragment.class);
private static final String IS_VIDEO_ENABLED = "is_video_enabled";
private SignalCameraView camera;
private ViewGroup controlsContainer;
private Controller controller;
private View selfieFlash;
private MemoryFileDescriptor videoFileDescriptor;
private SignalCameraView camera;
private ViewGroup controlsContainer;
private Controller controller;
private View selfieFlash;
private MemoryFileDescriptor videoFileDescriptor;
private final Observer<Optional<Media>> thumbObserver = this::presentRecentItemThumbnail;
private boolean isThumbAvailable;

View file

@ -1,92 +0,0 @@
package org.thoughtcrime.securesms.mediasend;
import android.os.Build;
import java.util.HashSet;
import java.util.Set;
public final class LegacyCameraModels {
private static final Set<String> LEGACY_MODELS = new HashSet<String>() {{
// Pixel 4
add("Pixel 4");
add("Pixel 4 XL");
// Huawei Mate 10
add("ALP-L29");
add("ALP-L09");
add("ALP-AL00");
// Huawei Mate 10 Pro
add("BLA-L29");
add("BLA-L09");
add("BLA-AL00");
add("BLA-A09");
// Huawei Mate 20
add("HMA-L29");
add("HMA-L09");
add("HMA-LX9");
add("HMA-AL00");
// Huawei Mate 20 Pro
add("LYA-L09");
add("LYA-L29");
add("LYA-AL00");
add("LYA-AL10");
add("LYA-TL00");
add("LYA-L0C");
// Huawei P20
add("EML-L29C");
add("EML-L09C");
add("EML-AL00");
add("EML-TL00");
add("EML-L29");
add("EML-L09");
// Huawei P20 Pro
add("CLT-L29C");
add("CLT-L29");
add("CLT-L09C");
add("CLT-L09");
add("CLT-AL00");
add("CLT-AL01");
add("CLT-TL01");
add("CLT-AL00L");
add("CLT-L04");
add("HW-01K");
// Huawei P30
add("ELE-L29");
add("ELE-L09");
add("ELE-AL00");
add("ELE-TL00");
add("ELE-L04");
// Huawei P30 Pro
add("VOG-L29");
add("VOG-L09");
add("VOG-AL00");
add("VOG-TL00");
add("VOG-L04");
add("VOG-AL10");
// Huawei Honor 10
add("COL-AL10");
add("COL-L29");
add("COL-L19");
// Samsung Galaxy S6
add("SM-G920F");
// Honor View 10
add("BLK-L09");
}};
private LegacyCameraModels() {
}
public static boolean isLegacyCameraModel() {
return LEGACY_MODELS.contains(Build.MODEL);
}
}

View file

@ -0,0 +1,49 @@
package org.thoughtcrime.securesms.mediasend
import android.content.Context
import android.view.OrientationEventListener
import android.view.Surface
import io.reactivex.rxjava3.subjects.BehaviorSubject
import io.reactivex.rxjava3.subjects.Subject
/**
* Utilizes the OrientationEventListener to determine relative surface rotation.
*
* @param context A context, which will be held on to for the lifespan of the listener.
*/
class RotationListener(
context: Context
) : OrientationEventListener(context) {
private val subject: Subject<Rotation> = BehaviorSubject.create()
/**
* Observes the stream of orientation changes. This can emit a lot of data, as it does
* not perform any duplication.
*/
val observable = subject
.doOnSubscribe { enable() }
.doOnTerminate { disable() }
override fun onOrientationChanged(orientation: Int) {
subject.onNext(
when {
orientation == ORIENTATION_UNKNOWN -> Rotation.ROTATION_0
orientation > 315 || orientation < 45 -> Rotation.ROTATION_0
orientation < 135 -> Rotation.ROTATION_270
orientation < 225 -> Rotation.ROTATION_180
else -> Rotation.ROTATION_90
}
)
}
/**
* Expresses the rotation as a handy enum.
*/
enum class Rotation(val surfaceRotation: Int) {
ROTATION_0(Surface.ROTATION_0),
ROTATION_90(Surface.ROTATION_90),
ROTATION_180(Surface.ROTATION_180),
ROTATION_270(Surface.ROTATION_270)
}
}

View file

@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.mediasend.v2.capture
import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.Toast
@ -143,15 +142,6 @@ class MediaCaptureFragment : Fragment(R.layout.fragment_container), CameraFragme
}
}
override fun getDisplayRotation(): Int {
return if (Build.VERSION.SDK_INT >= 30) {
requireContext().display?.rotation ?: 0
} else {
@Suppress("DEPRECATION")
requireActivity().windowManager.defaultDisplay.rotation
}
}
override fun onCameraCountButtonClicked() {
val controller = findNavController()
captureChildFragment.fadeOutControls {