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

Commit 70ca84c4 authored by Austin Borger's avatar Austin Borger
Browse files

CameraManager: Provide flag for overriding camera output to portrait.

Apps commonly do not handle landscape orientation cameras correctly. In
order to prevent stretching and rotation issues in these apps, this
patch adds a flag to override the behavior of these landscape cameras
to produce a portrait image instead by changing the SENSOR_ORIENTATION
reported by CameraCharacteristics and applying a 90 degree rotate and
crop. It also adds a CompatChanges entry which will track which apps
require this treatment.

Test: Ran on foldable device with several camera apps to verify behavior.
Bug: 250678880
Change-Id: I92b6f227916b97828ab6a7f856eea5a1e427644d
Merged-In: I92b6f227916b97828ab6a7f856eea5a1e427644d
parent 47e17f78
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1157,6 +1157,7 @@ package android.hardware.camera2 {
  public final class CameraManager {
    method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException;
    method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
    field public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L; // 0xef10e60L
  }

  public abstract static class CameraManager.AvailabilityCallback {
+20 −4
Original line number Diff line number Diff line
@@ -29,12 +29,14 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraManager;
import android.media.AudioAttributes;
import android.media.IAudioService;
import android.os.Build;
@@ -45,6 +47,7 @@ import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RSIllegalArgumentException;
@@ -281,6 +284,14 @@ public class Camera {
     */
    public native static int getNumberOfCameras();

    private static final boolean sLandscapeToPortrait =
            SystemProperties.getBoolean(CameraManager.LANDSCAPE_TO_PORTRAIT_PROP, false);

    private static boolean shouldOverrideToPortrait() {
        return CompatChanges.isChangeEnabled(CameraManager.OVERRIDE_FRONT_CAMERA_APP_COMPAT)
                && sLandscapeToPortrait;
    }

    /**
     * Returns the information about a particular camera.
     * If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1.
@@ -290,7 +301,9 @@ public class Camera {
     *    low-level failure).
     */
    public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) {
        _getCameraInfo(cameraId, cameraInfo);
        boolean overrideToPortrait = shouldOverrideToPortrait();

        _getCameraInfo(cameraId, overrideToPortrait, cameraInfo);
        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
        IAudioService audioService = IAudioService.Stub.asInterface(b);
        try {
@@ -303,7 +316,8 @@ public class Camera {
            Log.e(TAG, "Audio service is unavailable for queries");
        }
    }
    private native static void _getCameraInfo(int cameraId, CameraInfo cameraInfo);
    private native static void _getCameraInfo(int cameraId, boolean overrideToPortrait,
            CameraInfo cameraInfo);

    /**
     * Information about a camera
@@ -484,8 +498,9 @@ public class Camera {
            mEventHandler = null;
        }

        boolean overrideToPortrait = shouldOverrideToPortrait();
        return native_setup(new WeakReference<Camera>(this), cameraId,
                ActivityThread.currentOpPackageName());
                ActivityThread.currentOpPackageName(), overrideToPortrait);
    }

    /** used by Camera#open, Camera#open(int) */
@@ -555,7 +570,8 @@ public class Camera {
    }

    @UnsupportedAppUsage
    private native int native_setup(Object cameraThis, int cameraId, String packageName);
    private native int native_setup(Object cameraThis, int cameraId, String packageName,
            boolean overrideToPortrait);

    private native final void native_release();

+39 −4
Original line number Diff line number Diff line
@@ -23,6 +23,10 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.Overridable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Point;
@@ -103,6 +107,24 @@ public final class CameraManager {
            "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
    private final boolean mHasOpenCloseListenerPermission;

    /**
     * Force camera output to be rotated to portrait orientation on landscape cameras.
     * Many apps do not handle this situation and display stretched images otherwise.
     * @hide
     */
    @ChangeId
    @Overridable
    @Disabled
    @TestApi
    public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L;

    /**
     * System property for allowing the above
     * @hide
     */
    public static final String LANDSCAPE_TO_PORTRAIT_PROP =
            "camera.enable_landscape_to_portrait";

    /**
     * @hide
     */
@@ -526,7 +548,8 @@ public final class CameraManager {
            for (String physicalCameraId : physicalCameraIds) {
                CameraMetadataNative physicalCameraInfo =
                        cameraService.getCameraCharacteristics(physicalCameraId,
                                mContext.getApplicationInfo().targetSdkVersion);
                                mContext.getApplicationInfo().targetSdkVersion,
                                /*overrideToPortrait*/false);
                StreamConfiguration[] configs = physicalCameraInfo.get(
                        CameraCharacteristics.
                                SCALER_PHYSICAL_CAMERA_MULTI_RESOLUTION_STREAM_CONFIGURATIONS);
@@ -585,8 +608,9 @@ public final class CameraManager {
            try {
                Size displaySize = getDisplaySize();

                boolean overrideToPortrait = shouldOverrideToPortrait();
                CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
                        mContext.getApplicationInfo().targetSdkVersion);
                        mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait);
                try {
                    info.setCameraId(Integer.parseInt(cameraId));
                } catch (NumberFormatException e) {
@@ -703,9 +727,12 @@ public final class CameraManager {
                        ICameraService.ERROR_DISCONNECTED,
                        "Camera service is currently unavailable");
                }

                boolean overrideToPortrait = shouldOverrideToPortrait();
                cameraUser = cameraService.connectDevice(callbacks, cameraId,
                    mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
                    oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion);
                    oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion,
                    overrideToPortrait);
            } catch (ServiceSpecificException e) {
                if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
                    throw new AssertionError("Should've gone down the shim path");
@@ -1133,6 +1160,11 @@ public final class CameraManager {
        return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId);
    }

    private static boolean shouldOverrideToPortrait() {
        return CompatChanges.isChangeEnabled(OVERRIDE_FRONT_CAMERA_APP_COMPAT)
                && CameraManagerGlobal.sLandscapeToPortrait;
    }

    /**
     * A callback for camera devices becoming available or unavailable to open.
     *
@@ -1579,6 +1611,9 @@ public final class CameraManager {
        public static final boolean sCameraServiceDisabled =
                SystemProperties.getBoolean("config.disable_cameraservice", false);

        public static final boolean sLandscapeToPortrait =
                SystemProperties.getBoolean(LANDSCAPE_TO_PORTRAIT_PROP, false);

        public static CameraManagerGlobal get() {
            return gCameraManager;
        }
+47 −97
Original line number Diff line number Diff line
@@ -529,9 +529,8 @@ static jint android_hardware_Camera_getNumberOfCameras(JNIEnv *env, jobject thiz
    return Camera::getNumberOfCameras();
}

static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz,
    jint cameraId, jobject info_obj)
{
static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, jint cameraId,
                                                  jboolean overrideToPortrait, jobject info_obj) {
    CameraInfo cameraInfo;
    if (cameraId >= Camera::getNumberOfCameras() || cameraId < 0) {
        ALOGE("%s: Unknown camera ID %d", __FUNCTION__, cameraId);
@@ -539,7 +538,7 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz,
        return;
    }

    status_t rc = Camera::getCameraInfo(cameraId, &cameraInfo);
    status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, &cameraInfo);
    if (rc != NO_ERROR) {
        jniThrowRuntimeException(env, "Fail to get camera info");
        return;
@@ -555,9 +554,9 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz,
}

// connect to camera service
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
    jobject weak_this, jint cameraId, jstring clientPackageName)
{
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
                                                 jint cameraId, jstring clientPackageName,
                                                 jboolean overrideToPortrait) {
    // Convert jstring to String16
    const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
        env->GetStringChars(clientPackageName, NULL));
@@ -567,8 +566,9 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
                            reinterpret_cast<const jchar*>(rawClientName));

    int targetSdkVersion = android_get_application_target_sdk_version();
    sp<Camera> camera = Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID,
                                        Camera::USE_CALLING_PID, targetSdkVersion);
    sp<Camera> camera =
            Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID, Camera::USE_CALLING_PID,
                            targetSdkVersion, overrideToPortrait);
    if (camera == NULL) {
        return -EACCES;
    }
@@ -596,7 +596,7 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,

    // Update default display orientation in case the sensor is reverse-landscape
    CameraInfo cameraInfo;
    status_t rc = Camera::getCameraInfo(cameraId, &cameraInfo);
    status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, &cameraInfo);
    if (rc != NO_ERROR) {
        ALOGE("%s: getCameraInfo error: %d", __FUNCTION__, rc);
        return rc;
@@ -1051,93 +1051,43 @@ static int32_t android_hardware_Camera_getAudioRestriction(
//-------------------------------------------------

static const JNINativeMethod camMethods[] = {
  { "getNumberOfCameras",
    "()I",
    (void *)android_hardware_Camera_getNumberOfCameras },
  { "_getCameraInfo",
    "(ILandroid/hardware/Camera$CameraInfo;)V",
        {"getNumberOfCameras", "()I", (void *)android_hardware_Camera_getNumberOfCameras},
        {"_getCameraInfo", "(IZLandroid/hardware/Camera$CameraInfo;)V",
         (void *)android_hardware_Camera_getCameraInfo},
  { "native_setup",
    "(Ljava/lang/Object;ILjava/lang/String;)I",
        {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;Z)I",
         (void *)android_hardware_Camera_native_setup},
  { "native_release",
    "()V",
    (void*)android_hardware_Camera_release },
  { "setPreviewSurface",
    "(Landroid/view/Surface;)V",
        {"native_release", "()V", (void *)android_hardware_Camera_release},
        {"setPreviewSurface", "(Landroid/view/Surface;)V",
         (void *)android_hardware_Camera_setPreviewSurface},
  { "setPreviewTexture",
    "(Landroid/graphics/SurfaceTexture;)V",
        {"setPreviewTexture", "(Landroid/graphics/SurfaceTexture;)V",
         (void *)android_hardware_Camera_setPreviewTexture},
  { "setPreviewCallbackSurface",
    "(Landroid/view/Surface;)V",
        {"setPreviewCallbackSurface", "(Landroid/view/Surface;)V",
         (void *)android_hardware_Camera_setPreviewCallbackSurface},
  { "startPreview",
    "()V",
    (void *)android_hardware_Camera_startPreview },
  { "_stopPreview",
    "()V",
    (void *)android_hardware_Camera_stopPreview },
  { "previewEnabled",
    "()Z",
    (void *)android_hardware_Camera_previewEnabled },
  { "setHasPreviewCallback",
    "(ZZ)V",
    (void *)android_hardware_Camera_setHasPreviewCallback },
  { "_addCallbackBuffer",
    "([BI)V",
    (void *)android_hardware_Camera_addCallbackBuffer },
  { "native_autoFocus",
    "()V",
    (void *)android_hardware_Camera_autoFocus },
  { "native_cancelAutoFocus",
    "()V",
    (void *)android_hardware_Camera_cancelAutoFocus },
  { "native_takePicture",
    "(I)V",
    (void *)android_hardware_Camera_takePicture },
  { "native_setParameters",
    "(Ljava/lang/String;)V",
        {"startPreview", "()V", (void *)android_hardware_Camera_startPreview},
        {"_stopPreview", "()V", (void *)android_hardware_Camera_stopPreview},
        {"previewEnabled", "()Z", (void *)android_hardware_Camera_previewEnabled},
        {"setHasPreviewCallback", "(ZZ)V", (void *)android_hardware_Camera_setHasPreviewCallback},
        {"_addCallbackBuffer", "([BI)V", (void *)android_hardware_Camera_addCallbackBuffer},
        {"native_autoFocus", "()V", (void *)android_hardware_Camera_autoFocus},
        {"native_cancelAutoFocus", "()V", (void *)android_hardware_Camera_cancelAutoFocus},
        {"native_takePicture", "(I)V", (void *)android_hardware_Camera_takePicture},
        {"native_setParameters", "(Ljava/lang/String;)V",
         (void *)android_hardware_Camera_setParameters},
  { "native_getParameters",
    "()Ljava/lang/String;",
        {"native_getParameters", "()Ljava/lang/String;",
         (void *)android_hardware_Camera_getParameters},
  { "reconnect",
    "()V",
    (void*)android_hardware_Camera_reconnect },
  { "lock",
    "()V",
    (void*)android_hardware_Camera_lock },
  { "unlock",
    "()V",
    (void*)android_hardware_Camera_unlock },
  { "startSmoothZoom",
    "(I)V",
    (void *)android_hardware_Camera_startSmoothZoom },
  { "stopSmoothZoom",
    "()V",
    (void *)android_hardware_Camera_stopSmoothZoom },
  { "setDisplayOrientation",
    "(I)V",
    (void *)android_hardware_Camera_setDisplayOrientation },
  { "_enableShutterSound",
    "(Z)Z",
    (void *)android_hardware_Camera_enableShutterSound },
  { "_startFaceDetection",
    "(I)V",
    (void *)android_hardware_Camera_startFaceDetection },
  { "_stopFaceDetection",
    "()V",
    (void *)android_hardware_Camera_stopFaceDetection},
  { "enableFocusMoveCallback",
    "(I)V",
        {"reconnect", "()V", (void *)android_hardware_Camera_reconnect},
        {"lock", "()V", (void *)android_hardware_Camera_lock},
        {"unlock", "()V", (void *)android_hardware_Camera_unlock},
        {"startSmoothZoom", "(I)V", (void *)android_hardware_Camera_startSmoothZoom},
        {"stopSmoothZoom", "()V", (void *)android_hardware_Camera_stopSmoothZoom},
        {"setDisplayOrientation", "(I)V", (void *)android_hardware_Camera_setDisplayOrientation},
        {"_enableShutterSound", "(Z)Z", (void *)android_hardware_Camera_enableShutterSound},
        {"_startFaceDetection", "(I)V", (void *)android_hardware_Camera_startFaceDetection},
        {"_stopFaceDetection", "()V", (void *)android_hardware_Camera_stopFaceDetection},
        {"enableFocusMoveCallback", "(I)V",
         (void *)android_hardware_Camera_enableFocusMoveCallback},
  { "setAudioRestriction",
    "(I)V",
    (void *)android_hardware_Camera_setAudioRestriction},
  { "getAudioRestriction",
    "()I",
    (void *)android_hardware_Camera_getAudioRestriction},
        {"setAudioRestriction", "(I)V", (void *)android_hardware_Camera_setAudioRestriction},
        {"getAudioRestriction", "()I", (void *)android_hardware_Camera_getAudioRestriction},
};

struct field {
+6 −3
Original line number Diff line number Diff line
@@ -85,7 +85,8 @@ public class CameraBinderTest extends AndroidTestCase {
    public void testCameraInfo() throws Exception {
        for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {

            CameraInfo info = mUtils.getCameraService().getCameraInfo(cameraId);
            CameraInfo info = mUtils.getCameraService().getCameraInfo(cameraId,
                    /*overrideToPortrait*/false);
            assertTrue("Facing was not set for camera " + cameraId, info.info.facing != -1);
            assertTrue("Orientation was not set for camera " + cameraId,
                    info.info.orientation != -1);
@@ -159,7 +160,8 @@ public class CameraBinderTest extends AndroidTestCase {
                    .connect(dummyCallbacks, cameraId, clientPackageName,
                            ICameraService.USE_CALLING_UID,
                            ICameraService.USE_CALLING_PID,
                            getContext().getApplicationInfo().targetSdkVersion);
                            getContext().getApplicationInfo().targetSdkVersion,
                            /*overrideToPortrait*/false);
            assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);

            Log.v(TAG, String.format("Camera %s connected", cameraId));
@@ -264,7 +266,8 @@ public class CameraBinderTest extends AndroidTestCase {
                        dummyCallbacks, String.valueOf(cameraId),
                        clientPackageName, clientAttributionTag,
                        ICameraService.USE_CALLING_UID, 0 /*oomScoreOffset*/,
                        getContext().getApplicationInfo().targetSdkVersion);
                        getContext().getApplicationInfo().targetSdkVersion,
                        /*overrideToPortrait*/false);
            assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);

            Log.v(TAG, String.format("Camera %s connected", cameraId));
Loading