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

Commit 2bf82bbf authored by Emilian Peev's avatar Emilian Peev
Browse files

Camera: Keep sensor orientation consistent with device fold state

Allow foldable devices to advertise the mapping between device fold
state and sensor orientation.
Additionally return the current  sensor orientation
based on the specific device fold state.

Bug: 201005727
Test: Manual using AOSP Camera2 application,
Camera CTS

Change-Id: Idc76903da22567d29cc55d991b41df7e19490aa3
parent 05f5356e
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -17949,6 +17949,7 @@ package android.hardware.camera2 {
    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateOrientationMap> INFO_DEVICE_STATE_ORIENTATION_MAP;
    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SUPPORTED_HARDWARE_LEVEL;
    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION;
    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES;
@@ -18643,6 +18644,12 @@ package android.hardware.camera2.params {
    method public android.util.Rational getElement(int, int);
  }
  public final class DeviceStateOrientationMap {
    method public int getSensorOrientation(long);
    field public static final long FOLDED = 4L; // 0x4L
    field public static final long NORMAL = 0L; // 0x0L
  }
  public final class ExtensionSessionConfiguration {
    ctor public ExtensionSessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.StateCallback);
    method @NonNull public java.util.concurrent.Executor getExecutor();
+113 −2
Original line number Diff line number Diff line
@@ -22,15 +22,18 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
import android.hardware.camera2.params.DeviceStateOrientationMap;
import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.TypeReference;
import android.os.Build;
import android.util.Log;
import android.util.Rational;

import com.android.internal.annotations.GuardedBy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@@ -202,8 +205,25 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
    private List<CaptureResult.Key<?>> mAvailableResultKeys;
    private ArrayList<RecommendedStreamConfigurationMap> mRecommendedConfigurations;

    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private boolean mFoldedDeviceState;

    private final CameraManager.DeviceStateListener mFoldStateListener =
            new CameraManager.DeviceStateListener() {
                @Override
                public final void onDeviceStateChanged(boolean folded) {
                    synchronized (mLock) {
                        mFoldedDeviceState = folded;
                    }
                }};

    private static final String TAG = "CameraCharacteristics";

    /**
     * Takes ownership of the passed-in properties object
     *
     * @param properties Camera properties.
     * @hide
     */
    public CameraCharacteristics(CameraMetadataNative properties) {
@@ -219,6 +239,41 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
        return new CameraMetadataNative(mProperties);
    }

    /**
     * Return the device state listener for this Camera characteristics instance
     */
    CameraManager.DeviceStateListener getDeviceStateListener() { return mFoldStateListener; }

    /**
     * Overrides the property value
     *
     * <p>Check whether a given property value needs to be overridden in some specific
     * case.</p>
     *
     * @param key The characteristics field to override.
     * @return The value of overridden property, or {@code null} if the property doesn't need an
     * override.
     */
    @Nullable
    private <T> T overrideProperty(Key<T> key) {
        if (CameraCharacteristics.SENSOR_ORIENTATION.equals(key) && (mFoldStateListener != null) &&
                (mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS) != null)) {
            DeviceStateOrientationMap deviceStateOrientationMap =
                    mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATION_MAP);
            synchronized (mLock) {
                Integer ret = deviceStateOrientationMap.getSensorOrientation(mFoldedDeviceState ?
                        DeviceStateOrientationMap.FOLDED : DeviceStateOrientationMap.NORMAL);
                if (ret >= 0) {
                    return (T) ret;
                } else {
                    Log.w(TAG, "No valid device state to orientation mapping! Using default!");
                }
            }
        }

        return null;
    }

    /**
     * Get a camera characteristics field value.
     *
@@ -235,7 +290,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
     */
    @Nullable
    public <T> T get(Key<T> key) {
        return mProperties.get(key);
        T propertyOverride = overrideProperty(key);
        return (propertyOverride != null) ? propertyOverride : mProperties.get(key);
    }

    /**
@@ -3993,11 +4049,26 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
     * upright on the device screen in its native orientation.</p>
     * <p>Also defines the direction of rolling shutter readout, which is from top to bottom in
     * the sensor's coordinate system.</p>
     * <p>Starting with Android API level 32, camera clients that query the orientation via
     * {@link android.hardware.camera2.CameraCharacteristics#get } on foldable devices which
     * include logical cameras can receive a value that can dynamically change depending on the
     * device/fold state.
     * Clients are advised to not cache or store the orientation value of such logical sensors.
     * In case repeated queries to CameraCharacteristics are not preferred, then clients can
     * also access the entire mapping from device state to sensor orientation in
     * {@link android.hardware.camera2.params.DeviceStateOrientationMap }.
     * Do note that a dynamically changing sensor orientation value in camera characteristics
     * will not be the best way to establish the orientation per frame. Clients that want to
     * know the sensor orientation of a particular captured frame should query the
     * {@link CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID android.logicalMultiCamera.activePhysicalId} from the corresponding capture result and
     * check the respective physical camera orientation.</p>
     * <p><b>Units</b>: Degrees of clockwise rotation; always a multiple of
     * 90</p>
     * <p><b>Range of valid values:</b><br>
     * 0, 90, 180, 270</p>
     * <p>This key is available on all devices.</p>
     *
     * @see CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID
     */
    @PublicKey
    @NonNull
@@ -4306,6 +4377,46 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
    public static final Key<String> INFO_VERSION =
            new Key<String>("android.info.version", String.class);

    /**
     * <p>This lists the mapping between a device folding state and
     * specific camera sensor orientation for logical cameras on a foldable device.</p>
     * <p>Logical cameras on foldable devices can support sensors with different orientation
     * values. The orientation value may need to change depending on the specific folding
     * state. Information about the mapping between the device folding state and the
     * sensor orientation can be obtained in
     * {@link android.hardware.camera2.params.DeviceStateOrientationMap }.
     * Device state orientation maps are optional and maybe present on devices that support
     * {@link CaptureRequest#SCALER_ROTATE_AND_CROP android.scaler.rotateAndCrop}.</p>
     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
     * <p><b>Limited capability</b> -
     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
     *
     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
     * @see CaptureRequest#SCALER_ROTATE_AND_CROP
     */
    @PublicKey
    @NonNull
    @SyntheticKey
    public static final Key<android.hardware.camera2.params.DeviceStateOrientationMap> INFO_DEVICE_STATE_ORIENTATION_MAP =
            new Key<android.hardware.camera2.params.DeviceStateOrientationMap>("android.info.deviceStateOrientationMap", android.hardware.camera2.params.DeviceStateOrientationMap.class);

    /**
     * <p>HAL must populate the array with
     * (hardware::camera::provider::V2_5::DeviceState, sensorOrientation) pairs for each
     * supported device state bitwise combination.</p>
     * <p><b>Units</b>: (device fold state, sensor orientation) x n</p>
     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
     * <p><b>Limited capability</b> -
     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
     *
     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
     * @hide
     */
    public static final Key<long[]> INFO_DEVICE_STATE_ORIENTATIONS =
            new Key<long[]>("android.info.deviceStateOrientations", long[].class);

    /**
     * <p>The maximum number of frames that can occur after a request
     * (different than the previous) has been submitted, and before the
+84 −2
Original line number Diff line number Diff line
@@ -30,15 +30,19 @@ import android.hardware.ICameraServiceListener;
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CameraInjectionSessionImpl;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.params.DeviceStateOrientationMap;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfiguration;
import android.hardware.camera2.utils.CameraIdAndSessionConfiguration;
import android.hardware.camera2.utils.ConcurrentCameraIdCombination;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -50,6 +54,10 @@ import android.util.Log;
import android.util.Size;
import android.view.Display;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -97,6 +105,80 @@ public final class CameraManager {
        synchronized(mLock) {
            mContext = context;
        }

        mHandlerThread = new HandlerThread(TAG);
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
        mFoldStateListener = new FoldStateListener(context);
        context.getSystemService(DeviceStateManager.class)
                .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
    }

    private HandlerThread mHandlerThread;
    private Handler mHandler;
    private FoldStateListener mFoldStateListener;
    @GuardedBy("mLock")
    private ArrayList<WeakReference<DeviceStateListener>> mDeviceStateListeners = new ArrayList<>();
    private boolean mFoldedDeviceState;

    /**
     * @hide
     */
    public interface DeviceStateListener {
        void onDeviceStateChanged(boolean folded);
    }

    private final class FoldStateListener implements DeviceStateManager.DeviceStateCallback {
        private final int[] mFoldedDeviceStates;

        public FoldStateListener(Context context) {
            mFoldedDeviceStates = context.getResources().getIntArray(
                    com.android.internal.R.array.config_foldedDeviceStates);
        }

        private void handleStateChange(int state) {
            boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
            synchronized (mLock) {
                mFoldedDeviceState = folded;
                ArrayList<WeakReference<DeviceStateListener>> invalidListeners = new ArrayList<>();
                for (WeakReference<DeviceStateListener> listener : mDeviceStateListeners) {
                    DeviceStateListener callback = listener.get();
                    if (callback != null) {
                        callback.onDeviceStateChanged(folded);
                    } else {
                        invalidListeners.add(listener);
                    }
                }
                if (!invalidListeners.isEmpty()) {
                    mDeviceStateListeners.removeAll(invalidListeners);
                }
            }
        }

        @Override
        public final void onBaseStateChanged(int state) {
            handleStateChange(state);
        }

        @Override
        public final void onStateChanged(int state) {
            handleStateChange(state);
        }
    }

    /**
     * Register a {@link CameraCharacteristics} device state listener
     *
     * @param chars Camera characteristics that need to receive device state updates
     *
     * @hide
     */
    public void registerDeviceStateListener(@NonNull CameraCharacteristics chars) {
        synchronized (mLock) {
            DeviceStateListener listener = chars.getDeviceStateListener();
            listener.onDeviceStateChanged(mFoldedDeviceState);
            mDeviceStateListeners.add(new WeakReference<>(listener));
        }
    }

    /**
@@ -504,6 +586,7 @@ public final class CameraManager {
                        "Camera service is currently unavailable", e);
            }
        }
        registerDeviceStateListener(characteristics);
        return characteristics;
    }

@@ -1327,8 +1410,7 @@ public final class CameraManager {
        private ICameraService mCameraService;

        // Singleton, don't allow construction
        private CameraManagerGlobal() {
        }
        private CameraManagerGlobal() { }

        public static final boolean sCameraServiceDisabled =
                SystemProperties.getBoolean("config.disable_cameraservice", false);
+23 −1
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfiguration
import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration;
import android.hardware.camera2.marshal.impl.MarshalQueryableString;
import android.hardware.camera2.params.Capability;
import android.hardware.camera2.params.DeviceStateOrientationMap;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.HighSpeedVideoConfiguration;
import android.hardware.camera2.params.LensShadingMap;
@@ -761,6 +762,15 @@ public class CameraMetadataNative implements Parcelable {
                        return (T) metadata.getLensShadingMap();
                    }
                });
        sGetCommandMap.put(
                CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATION_MAP.getNativeKey(),
                        new GetCommand() {
                    @Override
                    @SuppressWarnings("unchecked")
                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
                        return (T) metadata.getDeviceStateOrientationMap();
                    }
                });
        sGetCommandMap.put(
                CaptureResult.STATISTICS_OIS_SAMPLES.getNativeKey(),
                        new GetCommand() {
@@ -994,6 +1004,18 @@ public class CameraMetadataNative implements Parcelable {
        return map;
    }

    private DeviceStateOrientationMap getDeviceStateOrientationMap() {
        long[] mapArray = getBase(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS);

        // Do not warn if map is null while s is not. This is valid.
        if (mapArray == null) {
            return null;
        }

        DeviceStateOrientationMap map = new DeviceStateOrientationMap(mapArray);
        return map;
    }

    private Location getGpsLocation() {
        String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
        double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES);
+155 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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 android.hardware.camera2.params;

import android.annotation.LongDef;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.utils.HashCodeHelpers;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;

/**
 * Immutable class that maps the device fold state to sensor orientation.
 *
 * <p>Some {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA logical}
 * cameras on foldables can include physical sensors with different sensor orientation
 * values. As a result, the values of the logical camera device can potentially change depending
 * on the device fold state.</p>
 *
 * <p>The device fold state to sensor orientation map will contain information about the
 * respective logical camera sensor orientation given a device state. Clients
 * can query the mapping for all possible supported folded states.
 *
 * @see CameraCharacteristics#SENSOR_ORIENTATION
 */
public final class DeviceStateOrientationMap {
    /**
     *  Needs to be kept in sync with the HIDL/AIDL DeviceState
     */

    /**
     * The device is in its normal physical configuration. This is the default if the
     * device does not support multiple different states.
     */
    public static final long NORMAL = 0;

    /**
     * The device is folded.  If not set, the device is unfolded or does not
     * support folding.
     *
     * The exact point when this status change happens during the folding
     * operation is device-specific.
     */
    public static final long FOLDED = 1 << 2;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @LongDef(prefix = {"DEVICE_STATE"}, value =
            {NORMAL,
             FOLDED })
    public @interface DeviceState {};

    private final HashMap<Long, Integer> mDeviceStateOrientationMap = new HashMap<>();

    /**
     * Create a new immutable DeviceStateOrientationMap instance.
     *
     * <p>This constructor takes over the array; do not write to the array afterwards.</p>
     *
     * @param elements
     *          An array of elements describing the map
     *
     * @throws IllegalArgumentException
     *            if the {@code elements} array length is invalid, not divisible by 2 or contains
     *            invalid element values
     * @throws NullPointerException
     *            if {@code elements} is {@code null}
     *
     * @hide
     */
    public DeviceStateOrientationMap(final long[] elements) {
        mElements = Objects.requireNonNull(elements, "elements must not be null");
        if ((elements.length % 2) != 0) {
            throw new IllegalArgumentException("Device state orientation map length " +
                    elements.length + " is not even!");
        }

        for (int i = 0; i < elements.length; i += 2) {
            if ((elements[i+1] % 90) != 0) {
                throw new IllegalArgumentException("Sensor orientation not divisible by 90: " +
                        elements[i+1]);
            }

            mDeviceStateOrientationMap.put(elements[i], Math.toIntExact(elements[i + 1]));
        }
    }

    /**
     * Return the logical camera sensor orientation given a specific device fold state.
     *
     * @param deviceState Device fold state
     *
     * @return Valid {@link android.hardware.camera2.CameraCharacteristics#SENSOR_ORIENTATION} for
     *         any supported device fold state
     *
     * @throws IllegalArgumentException if the given device state is invalid
     */
    public int getSensorOrientation(@DeviceState long deviceState) {
        if (!mDeviceStateOrientationMap.containsKey(deviceState)) {
            throw new IllegalArgumentException("Invalid device state: " + deviceState);
        }

        return mDeviceStateOrientationMap.get(deviceState);
    }

    /**
     * Check if this DeviceStateOrientationMap is equal to another DeviceStateOrientationMap.
     *
     * <p>Two device state orientation maps are equal if and only if all of their elements are
     * {@link Object#equals equal}.</p>
     *
     * @return {@code true} if the objects were equal, {@code false} otherwise
     */
    @Override
    public boolean equals(final Object obj) {
        if (obj == null) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        if (obj instanceof DeviceStateOrientationMap) {
            final DeviceStateOrientationMap other = (DeviceStateOrientationMap) obj;
            return Arrays.equals(mElements, other.mElements);
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return HashCodeHelpers.hashCodeGeneric(mElements);
    }

    private final long[] mElements;
}
Loading