Fix camera rotation for newer API levels.
This commit is contained in:
parent
17b00734ac
commit
3c09655949
8 changed files with 95 additions and 123 deletions
|
@ -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.
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue