1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.hardware.camera2.params;
18 
19 import android.annotation.LongDef;
20 import android.annotation.NonNull;
21 import android.annotation.SuppressLint;
22 import android.hardware.camera2.CameraCharacteristics;
23 import android.hardware.camera2.utils.HashCodeHelpers;
24 
25 import java.lang.annotation.Retention;
26 import java.lang.annotation.RetentionPolicy;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 import java.util.Objects;
31 
32 /**
33  * Immutable class that maps the device fold state to sensor orientation.
34  *
35  * <p>Some {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA logical}
36  * cameras on foldables can include physical sensors with different sensor orientation
37  * values. As a result, the values of the logical camera device can potentially change depending
38  * on the device fold state.</p>
39  *
40  * <p>The device fold state to sensor orientation map will contain information about the
41  * respective logical camera sensor orientation given a device state. Clients
42  * can query the mapping for all possible supported folded states.
43  *
44  * @see CameraCharacteristics#SENSOR_ORIENTATION
45  */
46 public final class DeviceStateSensorOrientationMap {
47     /**
48      *  Needs to be kept in sync with the HIDL/AIDL DeviceState
49      */
50 
51     /**
52      * The device is in its normal physical configuration. This is the default if the
53      * device does not support multiple different states.
54      */
55     public static final long NORMAL = 0;
56 
57     /**
58      * The device is folded.  If not set, the device is unfolded or does not
59      * support folding.
60      *
61      * The exact point when this status change happens during the folding
62      * operation is device-specific.
63      */
64     public static final long FOLDED = 1 << 2;
65 
66     /** @hide */
67     @Retention(RetentionPolicy.SOURCE)
68     @LongDef(prefix = {"DEVICE_STATE"}, value =
69             {NORMAL,
70              FOLDED })
71     public @interface DeviceState {};
72 
73     private final HashMap<Long, Integer> mDeviceStateOrientationMap;
74 
75     /**
76      * Create a new immutable DeviceStateOrientationMap instance.
77      *
78      * <p>The array is a list of pairs of elements (deviceState, angle):</p>
79      *
80      * <code>[state0, angle0, state1, angle1,..., stateN, angleN]</code>
81      *
82      * <p>Each pair describes the camera sensor orientation when the device is in the
83      * matching deviceState. The angle is in degrees, and must be a multiple of 90.</p>
84      *
85      * <p>This constructor takes over the array; do not write to the array afterwards.</p>
86      *
87      * @param elements
88      *          An array of elements describing the map
89      *
90      * @throws IllegalArgumentException
91      *            if the {@code elements} array length is invalid, not divisible by 2 or contains
92      *            invalid element values
93      * @throws NullPointerException
94      *            if {@code elements} is {@code null}
95      *
96      * @hide
97      */
DeviceStateSensorOrientationMap(@onNull final long[] elements)98     public DeviceStateSensorOrientationMap(@NonNull final long[] elements) {
99         mElements = Objects.requireNonNull(elements, "elements must not be null");
100         mDeviceStateOrientationMap = new HashMap<>();
101         if ((elements.length % 2) != 0) {
102             throw new IllegalArgumentException("Device state sensor orientation map length " +
103                     elements.length + " is not even!");
104         }
105 
106         for (int i = 0; i < elements.length; i += 2) {
107             if ((elements[i+1] % 90) != 0) {
108                 throw new IllegalArgumentException("Sensor orientation not divisible by 90: " +
109                         elements[i+1]);
110             }
111 
112             mDeviceStateOrientationMap.put(elements[i], Math.toIntExact(elements[i + 1]));
113         }
114     }
115 
116     /**
117      * Used by the Builder only.
118      *
119      * @hide
120      */
DeviceStateSensorOrientationMap(@onNull final ArrayList<Long> elements, @NonNull final HashMap<Long, Integer> deviceStateOrientationMap)121     private DeviceStateSensorOrientationMap(@NonNull final ArrayList<Long> elements,
122             @NonNull final HashMap<Long, Integer> deviceStateOrientationMap) {
123         mElements = new long[elements.size()];
124         for (int i = 0; i < elements.size(); i++) {
125             mElements[i] = elements.get(i);
126         }
127         mDeviceStateOrientationMap = deviceStateOrientationMap;
128     }
129 
130     /**
131      * Return the logical camera sensor orientation given a specific device fold state.
132      *
133      * @param deviceState Device fold state
134      *
135      * @return Valid {@link android.hardware.camera2.CameraCharacteristics#SENSOR_ORIENTATION} for
136      *         any supported device fold state
137      *
138      * @throws IllegalArgumentException if the given device state is invalid
139      */
getSensorOrientation(@eviceState long deviceState)140     public int getSensorOrientation(@DeviceState long deviceState) {
141         if (!mDeviceStateOrientationMap.containsKey(deviceState)) {
142             throw new IllegalArgumentException("Invalid device state: " + deviceState);
143         }
144 
145         return mDeviceStateOrientationMap.get(deviceState);
146     }
147 
148     /**
149      * Check if this DeviceStateSensorOrientationMap is equal to another
150      * DeviceStateSensorOrientationMap.
151      *
152      * <p>Two device state orientation maps are equal if and only if all of their elements are
153      * {@link Object#equals equal}.</p>
154      *
155      * @return {@code true} if the objects were equal, {@code false} otherwise
156      */
157     @Override
equals(final Object obj)158     public boolean equals(final Object obj) {
159         if (obj == null) {
160             return false;
161         }
162         if (this == obj) {
163             return true;
164         }
165         if (obj instanceof DeviceStateSensorOrientationMap) {
166             final DeviceStateSensorOrientationMap other = (DeviceStateSensorOrientationMap) obj;
167             return Arrays.equals(mElements, other.mElements);
168         }
169         return false;
170     }
171 
172     /**
173      * {@inheritDoc}
174      */
175     @Override
hashCode()176     public int hashCode() {
177         return HashCodeHelpers.hashCodeGeneric(mElements);
178     }
179 
180     private final long[] mElements;
181 
182     /**
183      * Builds a DeviceStateSensorOrientationMap object.
184      *
185      * <p>This builder is public to allow for easier application testing by
186      * creating custom object instances. It's not necessary to construct these
187      * objects during normal use of the camera API.</p>
188      */
189     public static final class Builder {
Builder()190         public Builder() {
191             // Empty
192         }
193 
194         /**
195          * Add a sensor orientation for a given device state.
196          *
197          * <p>Each pair of deviceState and angle describes the camera sensor orientation when the
198          * device is in the matching deviceState. The angle is in degrees, and must be a multiple
199          * of 90.</p>
200          *
201          * @param deviceState The deviceState.
202          * @param angle The orientation angle in degrees.
203          * @return This builder.
204          *
205          */
206         @SuppressLint("MissingGetterMatchingBuilder")
addOrientationForState(@eviceState long deviceState, long angle)207         public @NonNull Builder addOrientationForState(@DeviceState long deviceState, long angle) {
208             if (angle % 90 != 0) {
209                 throw new IllegalArgumentException("Sensor orientation not divisible by 90: "
210                         + angle);
211             }
212             mDeviceStateOrientationMap.put(deviceState, Math.toIntExact(angle));
213             mElements.add(deviceState);
214             mElements.add(angle);
215             return this;
216         }
217 
218         /**
219          * Returns an instance of <code>DeviceStateSensorOrientationMap</code> created from the
220          * fields set on this builder.
221          *
222          * @return A DeviceStateSensorOrientationMap.
223          */
build()224         public @NonNull DeviceStateSensorOrientationMap build() {
225             if (mElements.size() == 0) {
226                 throw new IllegalStateException("Cannot build a DeviceStateSensorOrientationMap"
227                         + " with zero elements.");
228             }
229             return new DeviceStateSensorOrientationMap(mElements, mDeviceStateOrientationMap);
230         }
231 
232         private final ArrayList<Long> mElements = new ArrayList<>();
233         private final HashMap<Long, Integer> mDeviceStateOrientationMap = new HashMap<>();
234     }
235 }
236