• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 com.android.deviceaswebcam;
18 
19 import android.content.Context;
20 import android.util.ArrayMap;
21 import android.util.JsonReader;
22 import android.util.Log;
23 import android.util.Range;
24 
25 import androidx.annotation.Nullable;
26 import androidx.core.util.Preconditions;
27 
28 import com.android.DeviceAsWebcam.R;
29 
30 import org.json.JSONArray;
31 import org.json.JSONException;
32 import org.json.JSONObject;
33 
34 import java.io.BufferedReader;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.InputStreamReader;
38 import java.nio.charset.StandardCharsets;
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.Objects;
42 
43 /**
44  * A class for providing camera related information overridden by vendors through resource overlays.
45  */
46 public class VendorCameraPrefs {
47     private static final String TAG = "VendorCameraPrefs";
48 
49     public static class PhysicalCameraInfo {
50         public final String physicalCameraId;
51         // Camera category which might help UI labelling while cycling through camera ids.
52         public final CameraCategory cameraCategory;
53         @Nullable
54         public final Range<Float> zoomRatioRange;
55 
PhysicalCameraInfo(String physicalCameraIdI, CameraCategory cameraCategoryI, @Nullable Range<Float> zoomRatioRangeI)56         PhysicalCameraInfo(String physicalCameraIdI, CameraCategory cameraCategoryI,
57                 @Nullable Range<Float> zoomRatioRangeI) {
58             physicalCameraId = physicalCameraIdI;
59             cameraCategory = cameraCategoryI;
60             zoomRatioRange = zoomRatioRangeI;
61         }
62     }
63 
VendorCameraPrefs(ArrayMap<String, List<PhysicalCameraInfo>> logicalToPhysicalMap, List<String> ignoredCameraList)64     public VendorCameraPrefs(ArrayMap<String, List<PhysicalCameraInfo>> logicalToPhysicalMap,
65             List<String> ignoredCameraList) {
66         mLogicalToPhysicalMap = logicalToPhysicalMap;
67         mIgnoredCameraList = ignoredCameraList;
68     }
69 
70     @Nullable
getPhysicalCameraInfos(String cameraId)71     public List<PhysicalCameraInfo> getPhysicalCameraInfos(String cameraId) {
72         return mLogicalToPhysicalMap.get(cameraId);
73     }
74 
75     /**
76      * Returns the custom physical camera zoom ratio range. Returns {@code null} if no custom value
77      * can be found.
78      *
79      * <p>This is used to specify the available zoom ratio range when the working camera is a
80      * physical camera under a logical camera.
81      */
82     @Nullable
getPhysicalCameraZoomRatioRange(CameraId cameraId)83     public Range<Float> getPhysicalCameraZoomRatioRange(CameraId cameraId) {
84         PhysicalCameraInfo physicalCameraInfo = getPhysicalCameraInfo(cameraId);
85         return physicalCameraInfo != null ? physicalCameraInfo.zoomRatioRange : null;
86     }
87 
88     /**
89      * Retrieves the {@link CameraCategory} if it is specified by the vendor camera prefs data.
90      */
getCameraCategory(CameraId cameraId)91     public CameraCategory getCameraCategory(CameraId cameraId) {
92         PhysicalCameraInfo physicalCameraInfo = getPhysicalCameraInfo(cameraId);
93         return physicalCameraInfo != null ? physicalCameraInfo.cameraCategory
94                 : CameraCategory.UNKNOWN;
95     }
96 
97     /**
98      * Returns the {@link PhysicalCameraInfo} corresponding to the specified camera id. Returns
99      * null if no item can be found.
100      */
getPhysicalCameraInfo(CameraId cameraId)101     private PhysicalCameraInfo getPhysicalCameraInfo(CameraId cameraId) {
102         List<PhysicalCameraInfo> physicalCameraInfos = getPhysicalCameraInfos(
103                 cameraId.mainCameraId);
104 
105         if (physicalCameraInfos != null) {
106             for (PhysicalCameraInfo physicalCameraInfo : physicalCameraInfos) {
107                 if (Objects.equals(physicalCameraInfo.physicalCameraId,
108                         cameraId.physicalCameraId)) {
109                     return physicalCameraInfo;
110                 }
111             }
112         }
113 
114         return null;
115     }
116 
117     /**
118      * Returns the ignored camera list.
119      */
getIgnoredCameraList()120     public List<String> getIgnoredCameraList() {
121         return mIgnoredCameraList;
122     }
123 
124     // logical camera -> PhysicalCameraInfo. The list of PhysicalCameraInfos
125     // is in order of preference for the physical streams that must be used by
126     // DeviceAsWebcam service.
127     private final ArrayMap<String, List<PhysicalCameraInfo>> mLogicalToPhysicalMap;
128     // The ignored camera list.
129     private final List<String> mIgnoredCameraList;
130 
131     /**
132      * Converts an InputStream into a String
133      *
134      * @param in InputStream
135      * @return InputStream converted to a String
136      */
inputStreamToString(InputStream in)137     private static String inputStreamToString(InputStream in) throws IOException {
138         StringBuilder builder = new StringBuilder();
139         try (BufferedReader reader =
140                 new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
141             reader.lines().forEach(builder::append);
142         }
143         return builder.toString();
144     }
145 
146     /**
147      * Returns an instance of {@link VendorCameraPrefs} that does not provide Physical
148      * Camera Mapping. Used for when we want to force CameraController to use the logical
149      * cameras. The returned VendorCameraPrefs still honors ignored cameras retrieved from
150      * {@link #getIgnoredCameralist}.
151      */
createEmptyVendorCameraPrefs(Context context)152     public static VendorCameraPrefs createEmptyVendorCameraPrefs(Context context) {
153         List<String> ignoredCameraList = getIgnoredCameralist(context);
154         return new VendorCameraPrefs(new ArrayMap<>(), ignoredCameraList);
155     }
156 
157     /**
158      * Reads the vendor camera preferences from the custom JSON files.
159      *
160      * @param context Application context which can be used to retrieve resources.
161      */
getVendorCameraPrefsFromJson(Context context)162     public static VendorCameraPrefs getVendorCameraPrefsFromJson(Context context) {
163         ArrayMap<String, Range<Float>> zoomRatioRangeInfo = getZoomRatioRangeInfo(context);
164         ArrayMap<String, List<PhysicalCameraInfo>> logicalToPhysicalMap =
165                 createLogicalToPhysicalMap(context, zoomRatioRangeInfo);
166         List<String> ignoredCameraList = getIgnoredCameralist(context);
167         return new VendorCameraPrefs(logicalToPhysicalMap, ignoredCameraList);
168     }
169 
170     /**
171      * Creates a logical to physical camera map by parsing the physical camera mapping info from
172      * the input which is expected to be a valid JSON stream.
173      *
174      * @param context            Application context which can be used to retrieve resources.
175      * @param zoomRatioRangeInfo A map contains the physical camera zoom ratio range info. This is
176      *                           used to created the PhysicalCameraInfo.
177      */
createLogicalToPhysicalMap( Context context, ArrayMap<String, Range<Float>> zoomRatioRangeInfo)178     private static ArrayMap<String, List<PhysicalCameraInfo>> createLogicalToPhysicalMap(
179             Context context, ArrayMap<String, Range<Float>> zoomRatioRangeInfo) {
180         InputStream in = context.getResources().openRawResource(R.raw.physical_camera_mapping);
181         ArrayMap<String, List<PhysicalCameraInfo>> logicalToPhysicalMap = new ArrayMap<>();
182         try {
183             JSONObject physicalCameraMapping = new JSONObject(inputStreamToString(in));
184             for (String logCam : physicalCameraMapping.keySet()) {
185                 JSONObject physicalCameraObj = physicalCameraMapping.getJSONObject(logCam);
186                 List<PhysicalCameraInfo> physicalCameraIds = new ArrayList<>();
187                 for (String physCam : physicalCameraObj.keySet()) {
188                     String identifier = CameraId.createIdentifier(logCam, physCam);
189                     physicalCameraIds.add(new PhysicalCameraInfo(physCam,
190                             convertLabelToCameraCategory(physicalCameraObj.getString(physCam)),
191                             zoomRatioRangeInfo.get(identifier)));
192                 }
193                 logicalToPhysicalMap.put(logCam, physicalCameraIds);
194             }
195         } catch (JSONException | IOException e) {
196             Log.e(TAG, "Failed to parse JSON", e);
197         }
198         return logicalToPhysicalMap;
199     }
200 
201     /**
202      * Converts the label string to corresponding {@link CameraCategory}.
203      */
convertLabelToCameraCategory(String label)204     private static CameraCategory convertLabelToCameraCategory(String label) {
205         return switch (label) {
206             case "W" -> CameraCategory.WIDE_ANGLE;
207             case "UW" -> CameraCategory.ULTRA_WIDE;
208             case "T" -> CameraCategory.TELEPHOTO;
209             case "S" -> CameraCategory.STANDARD;
210             case "O" -> CameraCategory.OTHER;
211             default -> CameraCategory.UNKNOWN;
212         };
213     }
214 
215     /**
216      * Obtains the zoom ratio range info from the input which is expected to be a valid
217      * JSON stream.
218      *
219      * @param context Application context which can be used to retrieve resources.
220      */
getZoomRatioRangeInfo(Context context)221     private static ArrayMap<String, Range<Float>> getZoomRatioRangeInfo(Context context) {
222         InputStream in = context.getResources().openRawResource(
223                 R.raw.physical_camera_zoom_ratio_ranges);
224         ArrayMap<String, Range<Float>> zoomRatioRangeInfo = new ArrayMap<>();
225         try {
226             JSONObject physicalCameraMapping = new JSONObject(inputStreamToString(in));
227             for (String logCam : physicalCameraMapping.keySet()) {
228                 JSONObject physicalCameraObj = physicalCameraMapping.getJSONObject(logCam);
229                 for (String physCam : physicalCameraObj.keySet()) {
230                     String identifier = CameraId.createIdentifier(logCam, physCam);
231                     JSONArray zoomRatioRangeArray = physicalCameraObj.getJSONArray(physCam);
232                     Preconditions.checkArgument(zoomRatioRangeArray.length() == 2,
233                             "Incorrect number of values in zoom ratio range. Expected: %d, Found:"
234                                     + " %d", 2, zoomRatioRangeArray.length());
235                     boolean isAvailable = zoomRatioRangeArray.getDouble(0) > 0.0
236                             && zoomRatioRangeArray.getDouble(1) > 0.0
237                             && zoomRatioRangeArray.getDouble(0) < zoomRatioRangeArray.getDouble(1);
238                     Preconditions.checkArgument(isAvailable,
239                             "Incorrect zoom ratio range values. All values should be > 0.0 and "
240                                     + "the first value should be lower than the second value.");
241                     zoomRatioRangeInfo.put(identifier,
242                             Range.create((float) zoomRatioRangeArray.getDouble(0),
243                                     (float)zoomRatioRangeArray.getDouble(1)));
244                 }
245             }
246         } catch (JSONException | IOException e) {
247             Log.e(TAG, "Failed to parse JSON", e);
248         }
249         return zoomRatioRangeInfo;
250     }
251 
252     /**
253      * Retrieves the ignored camera list from the input which is expected to be a valid JSON stream.
254      */
255     private static List<String> getIgnoredCameralist(Context context) {
256         List<String> ignoredCameras = new ArrayList<>();
257         try(InputStream in = context.getResources().openRawResource(R.raw.ignored_cameras);
258             JsonReader jsonReader = new JsonReader(new InputStreamReader(in))) {
259             jsonReader.beginArray();
260             while (jsonReader.hasNext()) {
261                 String node = jsonReader.nextString();
262                 ignoredCameras.add(node);
263             }
264             jsonReader.endArray();
265         } catch (IOException e) {
266             Log.e(TAG, "Failed to parse JSON. Running with a partial ignored camera list", e);
267         }
268 
269         return ignoredCameras;
270     }
271 }
272