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