1 /*
2  * Copyright (C) 2018 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 static com.android.internal.util.Preconditions.*;
20 import static android.hardware.camera2.params.StreamConfigurationMap.checkArgumentFormat;
21 
22 import android.annotation.IntRange;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.graphics.ImageFormat;
27 import android.graphics.ImageFormat.Format;
28 import android.hardware.camera2.CameraCharacteristics;
29 import android.hardware.camera2.CameraCharacteristics.Key;
30 import android.hardware.camera2.CameraDevice;
31 import android.hardware.camera2.CameraManager;
32 import android.hardware.camera2.CameraMetadata;
33 import android.hardware.camera2.params.StreamConfigurationMap;
34 import android.hardware.camera2.utils.HashCodeHelpers;
35 import android.graphics.PixelFormat;
36 import android.media.CamcorderProfile;
37 import android.util.Size;
38 import android.util.Log;
39 import android.util.Pair;
40 
41 import java.util.Arrays;
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.Comparator;
45 import java.util.HashMap;
46 import java.util.List;
47 
48 /**
49  * Immutable class to store the available mandatory stream combination.
50  *
51  * <p>A mandatory stream combination refers to a specific entry in the documented sets of
52  * required stream {@link CameraDevice#createCaptureSession combinations}.
53  * These combinations of streams are required to be supported by the camera device.
54  *
55  * <p>The list of stream combinations is available by invoking
56  * {@link CameraCharacteristics#get} and passing key
57  * {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_STREAM_COMBINATIONS}.</p>
58  */
59 public final class MandatoryStreamCombination {
60     private static final String TAG = "MandatoryStreamCombination";
61     /**
62      * Immutable class to store available mandatory stream information.
63      */
64     public static final class MandatoryStreamInformation {
65         private final int mFormat;
66         private final ArrayList<Size> mAvailableSizes = new ArrayList<Size> ();
67         private final boolean mIsInput;
68 
69         /**
70          * Create a new {@link MandatoryStreamInformation}.
71          *
72            @param availableSizes List of possible stream sizes.
73          * @param format Image format.
74          *
75          * @throws IllegalArgumentException
76          *              if sizes is empty or if the format was not user-defined in
77          *              ImageFormat/PixelFormat.
78          * @hide
79          */
MandatoryStreamInformation(@onNull List<Size> availableSizes, int format)80         public MandatoryStreamInformation(@NonNull List<Size> availableSizes, int format) {
81             this(availableSizes, format, /*isInput*/false);
82         }
83 
84         /**
85          * Create a new {@link MandatoryStreamInformation}.
86          *
87            @param availableSizes List of possible stream sizes.
88          * @param format Image format.
89          * @param isInput Flag indicating whether this stream is input.
90          *
91          * @throws IllegalArgumentException
92          *              if sizes is empty or if the format was not user-defined in
93          *              ImageFormat/PixelFormat.
94          * @hide
95          */
MandatoryStreamInformation(@onNull List<Size> availableSizes, @Format int format, boolean isInput)96         public MandatoryStreamInformation(@NonNull List<Size> availableSizes, @Format int format,
97                 boolean isInput) {
98             if (availableSizes.isEmpty()) {
99                 throw new IllegalArgumentException("No available sizes");
100             }
101             mAvailableSizes.addAll(availableSizes);
102             mFormat = checkArgumentFormat(format);
103             mIsInput = isInput;
104         }
105 
106         /**
107          * Confirms whether or not this is an input stream.
108          * @return true in case the stream is input, false otherwise.
109          */
isInput()110         public boolean isInput() {
111             return mIsInput;
112         }
113 
114         /**
115          * Return the list of available sizes for this mandatory stream.
116          *
117          * <p>Per documented {@link CameraDevice#createCaptureSession guideline} the largest
118          * resolution in the result will be tested and guaranteed to work. If clients want to use
119          * smaller sizes, then the resulting
120          * {@link android.hardware.camera2.params.SessionConfiguration session configuration} can
121          * be tested either by calling {@link CameraDevice#createCaptureSession} or
122          * {@link CameraDevice#isSessionConfigurationSupported}.
123          *
124          * @return non-modifiable ascending list of available sizes.
125          */
getAvailableSizes()126         public @NonNull List<Size> getAvailableSizes() {
127             return Collections.unmodifiableList(mAvailableSizes);
128         }
129 
130         /**
131          * Retrieve the mandatory stream {@code format}.
132          *
133          * @return integer format.
134          */
getFormat()135         public @Format int getFormat() {
136             return mFormat;
137         }
138 
139         /**
140          * Check if this {@link MandatoryStreamInformation} is equal to another
141          * {@link MandatoryStreamInformation}.
142          *
143          * <p>Two vectors are only equal if and only if each of the respective elements is
144          * equal.</p>
145          *
146          * @return {@code true} if the objects were equal, {@code false} otherwise
147          */
148         @Override
equals(final Object obj)149         public boolean equals(final Object obj) {
150             if (obj == null) {
151                 return false;
152             }
153             if (this == obj) {
154                 return true;
155             }
156             if (obj instanceof MandatoryStreamInformation) {
157                 final MandatoryStreamInformation other = (MandatoryStreamInformation) obj;
158                 if ((mFormat != other.mFormat) || (mIsInput != other.mIsInput) ||
159                         (mAvailableSizes.size() != other.mAvailableSizes.size())) {
160                     return false;
161                 }
162 
163                 return mAvailableSizes.equals(other.mAvailableSizes);
164             }
165 
166             return false;
167         }
168 
169         /**
170          * {@inheritDoc}
171          */
172         @Override
hashCode()173         public int hashCode() {
174             return HashCodeHelpers.hashCode(mFormat, Boolean.hashCode(mIsInput),
175                     mAvailableSizes.hashCode());
176         }
177     }
178 
179     private final String mDescription;
180     private final boolean mIsReprocessable;
181     private final ArrayList<MandatoryStreamInformation> mStreamsInformation =
182             new ArrayList<MandatoryStreamInformation>();
183     /**
184      * Create a new {@link MandatoryStreamCombination}.
185      *
186      * @param streamsInformation list of available streams in the stream combination.
187      * @param description Summary of the stream combination use case.
188      * @param isReprocessable Flag whether the mandatory stream combination is reprocessable.
189      *
190      * @throws IllegalArgumentException
191      *              if stream information is empty
192      * @hide
193      */
MandatoryStreamCombination(@onNull List<MandatoryStreamInformation> streamsInformation, @NonNull String description, boolean isReprocessable)194     public MandatoryStreamCombination(@NonNull List<MandatoryStreamInformation> streamsInformation,
195             @NonNull String description, boolean isReprocessable) {
196         if (streamsInformation.isEmpty()) {
197             throw new IllegalArgumentException("Empty stream information");
198         }
199         mStreamsInformation.addAll(streamsInformation);
200         mDescription = description;
201         mIsReprocessable = isReprocessable;
202     }
203 
204     /**
205      * Get the mandatory stream combination description.
206      *
207      * @return CharSequence with the mandatory combination description.
208      */
getDescription()209     public @NonNull CharSequence getDescription() {
210         return mDescription;
211     }
212 
213     /**
214      * Indicates whether the mandatory stream combination is reprocessable. Reprocessable is defined
215      * as a stream combination that contains one input stream
216      * ({@link MandatoryStreamInformation#isInput} return true).
217      *
218      * @return {@code true} in case the mandatory stream combination contains an input,
219      *         {@code false} otherwise.
220      */
isReprocessable()221     public boolean isReprocessable() {
222         return mIsReprocessable;
223     }
224 
225     /**
226      * Get information about each stream in the mandatory combination.
227      *
228      * @return Non-modifiable list of stream information.
229      *
230      */
getStreamsInformation()231     public @NonNull List<MandatoryStreamInformation> getStreamsInformation() {
232         return Collections.unmodifiableList(mStreamsInformation);
233     }
234 
235     /**
236      * Check if this {@link MandatoryStreamCombination} is equal to another
237      * {@link MandatoryStreamCombination}.
238      *
239      * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p>
240      *
241      * @return {@code true} if the objects were equal, {@code false} otherwise
242      */
243     @Override
equals(final Object obj)244     public boolean equals(final Object obj) {
245         if (obj == null) {
246             return false;
247         }
248         if (this == obj) {
249             return true;
250         }
251         if (obj instanceof MandatoryStreamCombination) {
252             final MandatoryStreamCombination other = (MandatoryStreamCombination) obj;
253             if ((mDescription != other.mDescription) ||
254                     (mIsReprocessable != other.mIsReprocessable) ||
255                     (mStreamsInformation.size() != other.mStreamsInformation.size())) {
256                 return false;
257             }
258 
259             return mStreamsInformation.equals(other.mStreamsInformation);
260         }
261 
262         return false;
263     }
264 
265     /**
266      * {@inheritDoc}
267      */
268     @Override
hashCode()269     public int hashCode() {
270         return HashCodeHelpers.hashCode(Boolean.hashCode(mIsReprocessable), mDescription.hashCode(),
271                 mStreamsInformation.hashCode());
272     }
273 
274     private static enum SizeThreshold { VGA, PREVIEW, RECORD, MAXIMUM }
275     private static enum ReprocessType { NONE, PRIVATE, YUV }
276     private static final class StreamTemplate {
277         public int mFormat;
278         public SizeThreshold mSizeThreshold;
279         public boolean mIsInput;
StreamTemplate(int format, SizeThreshold sizeThreshold)280         public StreamTemplate(int format, SizeThreshold sizeThreshold) {
281             this(format, sizeThreshold, /*isInput*/false);
282         }
283 
StreamTemplate(@ormat int format, @NonNull SizeThreshold sizeThreshold, boolean isInput)284         public StreamTemplate(@Format int format, @NonNull SizeThreshold sizeThreshold,
285                 boolean isInput) {
286             mFormat = format;
287             mSizeThreshold = sizeThreshold;
288             mIsInput = isInput;
289         }
290     }
291 
292     private static final class StreamCombinationTemplate {
293         public StreamTemplate[] mStreamTemplates;
294         public String mDescription;
295         public ReprocessType mReprocessType;
296 
StreamCombinationTemplate(@onNull StreamTemplate[] streamTemplates, @NonNull String description)297         public StreamCombinationTemplate(@NonNull StreamTemplate[] streamTemplates,
298                 @NonNull String description) {
299             this(streamTemplates, description, /*reprocessType*/ReprocessType.NONE);
300         }
301 
StreamCombinationTemplate(@onNull StreamTemplate[] streamTemplates, @NonNull String description, ReprocessType reprocessType)302         public StreamCombinationTemplate(@NonNull StreamTemplate[] streamTemplates,
303                 @NonNull String description,
304                 ReprocessType reprocessType) {
305             mStreamTemplates = streamTemplates;
306             mReprocessType = reprocessType;
307             mDescription = description;
308         }
309     }
310 
311     private static StreamCombinationTemplate sLegacyCombinations[] = {
312         new StreamCombinationTemplate(new StreamTemplate [] {
313                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) },
314                 "Simple preview, GPU video processing, or no-preview video recording"),
315         new StreamCombinationTemplate(new StreamTemplate [] {
316                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
317                 "No-viewfinder still image capture"),
318         new StreamCombinationTemplate(new StreamTemplate [] {
319                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
320                 "In-application video/image processing"),
321         new StreamCombinationTemplate(new StreamTemplate [] {
322                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
323                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
324                 "Standard still imaging"),
325         new StreamCombinationTemplate(new StreamTemplate [] {
326                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
327                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
328                 "In-app processing plus still capture"),
329         new StreamCombinationTemplate(new StreamTemplate [] {
330                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
331                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW) },
332                 "Standard recording"),
333         new StreamCombinationTemplate(new StreamTemplate [] {
334                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
335                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW) },
336                 "Preview plus in-app processing"),
337         new StreamCombinationTemplate(new StreamTemplate [] {
338                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
339                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
340                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
341                 "Still capture plus in-app processing")
342     };
343 
344     private static StreamCombinationTemplate sLimitedCombinations[] = {
345         new StreamCombinationTemplate(new StreamTemplate [] {
346                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
347                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD)},
348                 "High-resolution video recording with preview"),
349         new StreamCombinationTemplate(new StreamTemplate [] {
350                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
351                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD)},
352                 "High-resolution in-app video processing with preview"),
353         new StreamCombinationTemplate(new StreamTemplate [] {
354                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
355                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) },
356                 "Two-input in-app video processing"),
357         new StreamCombinationTemplate(new StreamTemplate [] {
358                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
359                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD),
360                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD) },
361                 "High-resolution recording with video snapshot"),
362         new StreamCombinationTemplate(new StreamTemplate [] {
363                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
364                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD),
365                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD) },
366                 "High-resolution in-app processing with video snapshot"),
367         new StreamCombinationTemplate(new StreamTemplate [] {
368                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
369                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
370                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
371                 "Two-input in-app processing with still capture")
372     };
373 
374     private static StreamCombinationTemplate sBurstCombinations[] = {
375         new StreamCombinationTemplate(new StreamTemplate [] {
376                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
377                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) },
378                 "Maximum-resolution GPU processing with preview"),
379         new StreamCombinationTemplate(new StreamTemplate [] {
380                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
381                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
382                 "Maximum-resolution in-app processing with preview"),
383         new StreamCombinationTemplate(new StreamTemplate [] {
384                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
385                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
386                 "Maximum-resolution two-input in-app processsing")
387     };
388 
389     private static StreamCombinationTemplate sFullCombinations[] = {
390         new StreamCombinationTemplate(new StreamTemplate [] {
391                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM),
392                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM) },
393                 "Maximum-resolution GPU processing with preview"),
394         new StreamCombinationTemplate(new StreamTemplate [] {
395                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.MAXIMUM),
396                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
397                 "Maximum-resolution in-app processing with preview"),
398         new StreamCombinationTemplate(new StreamTemplate [] {
399                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM),
400                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
401                 "Maximum-resolution two-input in-app processsing"),
402         new StreamCombinationTemplate(new StreamTemplate [] {
403                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
404                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
405                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
406                 "Video recording with maximum-size video snapshot"),
407         new StreamCombinationTemplate(new StreamTemplate [] {
408                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.VGA),
409                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
410                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
411                 "Standard video recording plus maximum-resolution in-app processing"),
412         new StreamCombinationTemplate(new StreamTemplate [] {
413                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.VGA),
414                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
415                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
416                 "Preview plus two-input maximum-resolution in-app processing")
417     };
418 
419     private static StreamCombinationTemplate sRawCombinations[] = {
420         new StreamCombinationTemplate(new StreamTemplate [] {
421                 new StreamTemplate(ImageFormat.RAW_SENSOR,  SizeThreshold.MAXIMUM) },
422                 "No-preview DNG capture"),
423         new StreamCombinationTemplate(new StreamTemplate [] {
424                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
425                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
426                 "Standard DNG capture"),
427         new StreamCombinationTemplate(new StreamTemplate [] {
428                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
429                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
430                 "In-app processing plus DNG capture"),
431         new StreamCombinationTemplate(new StreamTemplate [] {
432                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
433                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
434                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
435                 "Video recording with DNG capture"),
436         new StreamCombinationTemplate(new StreamTemplate [] {
437                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
438                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
439                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
440                 "Preview with in-app processing and DNG capture"),
441         new StreamCombinationTemplate(new StreamTemplate [] {
442                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
443                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
444                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
445                 "Two-input in-app processing plus DNG capture"),
446         new StreamCombinationTemplate(new StreamTemplate [] {
447                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
448                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
449                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
450                 "Still capture with simultaneous JPEG and DNG"),
451         new StreamCombinationTemplate(new StreamTemplate [] {
452                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
453                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
454                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
455                 "In-app processing with simultaneous JPEG and DNG")
456     };
457 
458     private static StreamCombinationTemplate sLevel3Combinations[] = {
459         new StreamCombinationTemplate(new StreamTemplate [] {
460                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
461                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
462                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM),
463                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
464                 "In-app viewfinder analysis with dynamic selection of output format"),
465         new StreamCombinationTemplate(new StreamTemplate [] {
466                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
467                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
468                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
469                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
470                 "In-app viewfinder analysis with dynamic selection of output format")
471     };
472 
473     private static StreamCombinationTemplate sLimitedPrivateReprocCombinations[] = {
474         new StreamCombinationTemplate(new StreamTemplate [] {
475                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
476                 "No-viewfinder still image reprocessing",
477                 /*reprocessType*/ ReprocessType.PRIVATE),
478         new StreamCombinationTemplate(new StreamTemplate [] {
479                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
480                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
481                 "ZSL(Zero-Shutter-Lag) still imaging",
482                 /*reprocessType*/ ReprocessType.PRIVATE),
483         new StreamCombinationTemplate(new StreamTemplate [] {
484                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
485                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
486                 "ZSL still and in-app processing imaging",
487                 /*reprocessType*/ ReprocessType.PRIVATE),
488         new StreamCombinationTemplate(new StreamTemplate [] {
489                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
490                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
491                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
492                 "ZSL in-app processing with still capture",
493                 /*reprocessType*/ ReprocessType.PRIVATE),
494     };
495 
496     private static StreamCombinationTemplate sLimitedYUVReprocCombinations[] = {
497         new StreamCombinationTemplate(new StreamTemplate [] {
498                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
499                 "No-viewfinder still image reprocessing",
500                 /*reprocessType*/ ReprocessType.YUV),
501         new StreamCombinationTemplate(new StreamTemplate [] {
502                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
503                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
504                 "ZSL(Zero-Shutter-Lag) still imaging",
505                 /*reprocessType*/ ReprocessType.YUV),
506         new StreamCombinationTemplate(new StreamTemplate [] {
507                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
508                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
509                 "ZSL still and in-app processing imaging",
510                 /*reprocessType*/ ReprocessType.YUV),
511         new StreamCombinationTemplate(new StreamTemplate [] {
512                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
513                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
514                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
515                 "ZSL in-app processing with still capture",
516                 /*reprocessType*/ ReprocessType.YUV),
517     };
518 
519     private static StreamCombinationTemplate sFullPrivateReprocCombinations[] = {
520         new StreamCombinationTemplate(new StreamTemplate [] {
521                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
522                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) },
523                 "High-resolution ZSL in-app video processing with regular preview",
524                 /*reprocessType*/ ReprocessType.PRIVATE),
525         new StreamCombinationTemplate(new StreamTemplate [] {
526                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
527                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
528                 "Maximum-resolution ZSL in-app processing with regular preview",
529                 /*reprocessType*/ ReprocessType.PRIVATE),
530         new StreamCombinationTemplate(new StreamTemplate [] {
531                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
532                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
533                 "Maximum-resolution two-input ZSL in-app processing",
534                 /*reprocessType*/ ReprocessType.PRIVATE),
535         new StreamCombinationTemplate(new StreamTemplate [] {
536                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
537                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
538                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
539                 "ZSL still capture and in-app processing",
540                 /*reprocessType*/ ReprocessType.PRIVATE),
541     };
542 
543     private static StreamCombinationTemplate sFullYUVReprocCombinations[] = {
544         new StreamCombinationTemplate(new StreamTemplate [] {
545                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW) },
546                 "Maximum-resolution multi-frame image fusion in-app processing with regular "
547                 + "preview",
548                 /*reprocessType*/ ReprocessType.YUV),
549         new StreamCombinationTemplate(new StreamTemplate [] {
550                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW) },
551                 "Maximum-resolution multi-frame image fusion two-input in-app processing",
552                 /*reprocessType*/ ReprocessType.YUV),
553         new StreamCombinationTemplate(new StreamTemplate [] {
554                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
555                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD) },
556                 "High-resolution ZSL in-app video processing with regular preview",
557                 /*reprocessType*/ ReprocessType.YUV),
558         new StreamCombinationTemplate(new StreamTemplate [] {
559                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
560                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
561                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
562                 "ZSL still capture and in-app processing",
563                 /*reprocessType*/ ReprocessType.YUV),
564     };
565 
566     private static StreamCombinationTemplate sRAWPrivateReprocCombinations[] = {
567         new StreamCombinationTemplate(new StreamTemplate [] {
568                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
569                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
570                 "Mutually exclusive ZSL in-app processing and DNG capture",
571                 /*reprocessType*/ ReprocessType.PRIVATE),
572         new StreamCombinationTemplate(new StreamTemplate [] {
573                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
574                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
575                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
576                 "Mutually exclusive ZSL in-app processing and preview with DNG capture",
577                 /*reprocessType*/ ReprocessType.PRIVATE),
578         new StreamCombinationTemplate(new StreamTemplate [] {
579                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
580                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
581                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
582                 "Mutually exclusive ZSL two-input in-app processing and DNG capture",
583                 /*reprocessType*/ ReprocessType.PRIVATE),
584         new StreamCombinationTemplate(new StreamTemplate [] {
585                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
586                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
587                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
588                 "Mutually exclusive ZSL still capture and preview with DNG capture",
589                 /*reprocessType*/ ReprocessType.PRIVATE),
590         new StreamCombinationTemplate(new StreamTemplate [] {
591                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
592                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
593                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
594                 "Mutually exclusive ZSL in-app processing with still capture and DNG capture",
595                 /*reprocessType*/ ReprocessType.PRIVATE),
596     };
597 
598     private static StreamCombinationTemplate sRAWYUVReprocCombinations[] = {
599         new StreamCombinationTemplate(new StreamTemplate [] {
600                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
601                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
602                 "Mutually exclusive ZSL in-app processing and DNG capture",
603                 /*reprocessType*/ ReprocessType.YUV),
604         new StreamCombinationTemplate(new StreamTemplate [] {
605                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
606                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
607                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
608                 "Mutually exclusive ZSL in-app processing and preview with DNG capture",
609                 /*reprocessType*/ ReprocessType.YUV),
610         new StreamCombinationTemplate(new StreamTemplate [] {
611                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
612                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
613                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
614                 "Mutually exclusive ZSL two-input in-app processing and DNG capture",
615                 /*reprocessType*/ ReprocessType.YUV),
616         new StreamCombinationTemplate(new StreamTemplate [] {
617                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
618                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
619                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
620                 "Mutually exclusive ZSL still capture and preview with DNG capture",
621                 /*reprocessType*/ ReprocessType.YUV),
622         new StreamCombinationTemplate(new StreamTemplate [] {
623                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW),
624                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM),
625                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
626                 "Mutually exclusive ZSL in-app processing with still capture and DNG capture",
627                 /*reprocessType*/ ReprocessType.YUV),
628     };
629 
630     private static StreamCombinationTemplate sLevel3PrivateReprocCombinations[] = {
631         new StreamCombinationTemplate(new StreamTemplate [] {
632                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
633                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
634                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM),
635                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
636                 "In-app viewfinder analysis with ZSL, RAW, and JPEG reprocessing output",
637                 /*reprocessType*/ ReprocessType.PRIVATE),
638     };
639 
640     private static StreamCombinationTemplate sLevel3YUVReprocCombinations[] = {
641         new StreamCombinationTemplate(new StreamTemplate [] {
642                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
643                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
644                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM) },
645                 "In-app viewfinder analysis with ZSL and RAW",
646                 /*reprocessType*/ ReprocessType.YUV),
647         new StreamCombinationTemplate(new StreamTemplate [] {
648                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
649                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.VGA),
650                 new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM),
651                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM) },
652                 "In-app viewfinder analysis with ZSL, RAW, and JPEG reprocessing output",
653                 /*reprocessType*/ ReprocessType.YUV),
654     };
655 
656     /**
657      * Helper builder class to generate a list of available mandatory stream combinations.
658      * @hide
659      */
660     public static final class Builder {
661         private Size mDisplaySize;
662         private List<Integer> mCapabilities;
663         private int mHwLevel, mCameraId;
664         private StreamConfigurationMap mStreamConfigMap;
665         private boolean mIsHiddenPhysicalCamera;
666 
667         private final Size kPreviewSizeBound = new Size(1920, 1088);
668 
669         /**
670          * Helper class to be used to generate the available mandatory stream combinations.
671          *
672          * @param cameraId Current camera id.
673          * @param hwLevel The camera HW level as reported by android.info.supportedHardwareLevel.
674          * @param displaySize The device display size.
675          * @param capabilities The camera device capabilities.
676          * @param sm The camera device stream configuration map.
677          */
Builder(int cameraId, int hwLevel, @NonNull Size displaySize, @NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm)678         public Builder(int cameraId, int hwLevel, @NonNull Size displaySize,
679                 @NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm) {
680             mCameraId = cameraId;
681             mDisplaySize = displaySize;
682             mCapabilities = capabilities;
683             mStreamConfigMap = sm;
684             mHwLevel = hwLevel;
685             mIsHiddenPhysicalCamera =
686                     CameraManager.isHiddenPhysicalCamera(Integer.toString(mCameraId));
687         }
688 
689         /**
690          * Retrieve a list of all available mandatory stream combinations.
691          *
692          * @return a non-modifiable list of supported mandatory stream combinations or
693          *         null in case device is not backward compatible or the method encounters
694          *         an error.
695          */
696         public @Nullable List<MandatoryStreamCombination>
getAvailableMandatoryStreamCombinations()697             getAvailableMandatoryStreamCombinations() {
698             if (!isColorOutputSupported()) {
699                 Log.v(TAG, "Device is not backward compatible!");
700                 return null;
701             }
702 
703             if ((mCameraId < 0) && !isExternalCamera()) {
704                 Log.i(TAG, "Invalid camera id");
705                 return null;
706             }
707 
708             ArrayList<StreamCombinationTemplate> availableTemplates =
709                     new ArrayList<StreamCombinationTemplate> ();
710             if (isHardwareLevelAtLeastLegacy()) {
711                 availableTemplates.addAll(Arrays.asList(sLegacyCombinations));
712             }
713 
714             // External devices are identical to limited devices w.r.t. stream combinations.
715             if (isHardwareLevelAtLeastLimited() || isExternalCamera()) {
716                 availableTemplates.addAll(Arrays.asList(sLimitedCombinations));
717 
718                 if (isPrivateReprocessingSupported()) {
719                     availableTemplates.addAll(Arrays.asList(sLimitedPrivateReprocCombinations));
720                 }
721 
722                 if (isYUVReprocessingSupported()) {
723                     availableTemplates.addAll(Arrays.asList(sLimitedYUVReprocCombinations));
724                 }
725 
726             }
727 
728             if (isCapabilitySupported(
729                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE)) {
730                 availableTemplates.addAll(Arrays.asList(sBurstCombinations));
731             }
732 
733             if (isHardwareLevelAtLeastFull()) {
734                 availableTemplates.addAll(Arrays.asList(sFullCombinations));
735 
736                 if (isPrivateReprocessingSupported()) {
737                     availableTemplates.addAll(Arrays.asList(sFullPrivateReprocCombinations));
738                 }
739 
740                 if (isYUVReprocessingSupported()) {
741                     availableTemplates.addAll(Arrays.asList(sFullYUVReprocCombinations));
742                 }
743 
744             }
745 
746             if (isCapabilitySupported(
747                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
748                 availableTemplates.addAll(Arrays.asList(sRawCombinations));
749 
750                 if (isPrivateReprocessingSupported()) {
751                     availableTemplates.addAll(Arrays.asList(sRAWPrivateReprocCombinations));
752                 }
753 
754                 if (isYUVReprocessingSupported()) {
755                     availableTemplates.addAll(Arrays.asList(sRAWYUVReprocCombinations));
756                 }
757 
758             }
759 
760             if (isHardwareLevelAtLeastLevel3()) {
761                 availableTemplates.addAll(Arrays.asList(sLevel3Combinations));
762 
763                 if (isPrivateReprocessingSupported()) {
764                     availableTemplates.addAll(Arrays.asList(sLevel3PrivateReprocCombinations));
765                 }
766 
767                 if (isYUVReprocessingSupported()) {
768                     availableTemplates.addAll(Arrays.asList(sLevel3YUVReprocCombinations));
769                 }
770 
771             }
772 
773             return generateAvailableCombinations(availableTemplates);
774         }
775 
776         /**
777          * Helper method to generate the available stream combinations given the
778          * list of available combination templates.
779          *
780          * @param availableTemplates a list of templates supported by the camera device.
781          * @return a non-modifiable list of supported mandatory stream combinations or
782          *         null in case of errors.
783          */
generateAvailableCombinations( @onNull ArrayList<StreamCombinationTemplate> availableTemplates)784         private @Nullable List<MandatoryStreamCombination> generateAvailableCombinations(
785                 @NonNull ArrayList<StreamCombinationTemplate> availableTemplates) {
786             if (availableTemplates.isEmpty()) {
787                 Log.e(TAG, "No available stream templates!");
788                 return null;
789             }
790 
791             HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes =
792                 enumerateAvailableSizes();
793             if (availableSizes == null) {
794                 Log.e(TAG, "Available size enumeration failed!");
795                 return null;
796             }
797 
798             // RAW only uses MAXIMUM size threshold
799             Size[] rawSizes = mStreamConfigMap.getOutputSizes(ImageFormat.RAW_SENSOR);
800             ArrayList<Size> availableRawSizes = new ArrayList<Size>();
801             if (rawSizes != null) {
802                 availableRawSizes.ensureCapacity(rawSizes.length);
803                 availableRawSizes.addAll(Arrays.asList(rawSizes));
804             }
805 
806             Size maxPrivateInputSize = new Size(0, 0);
807             if (isPrivateReprocessingSupported()) {
808                 maxPrivateInputSize = getMaxSize(mStreamConfigMap.getInputSizes(
809                             ImageFormat.PRIVATE));
810             }
811 
812             Size maxYUVInputSize = new Size(0, 0);
813             if (isYUVReprocessingSupported()) {
814                 maxYUVInputSize = getMaxSize(mStreamConfigMap.getInputSizes(
815                             ImageFormat.YUV_420_888));
816             }
817 
818             // Generate the available mandatory stream combinations given the supported templates
819             // and size ranges.
820             ArrayList<MandatoryStreamCombination> availableStreamCombinations =
821                     new ArrayList<MandatoryStreamCombination>();
822             availableStreamCombinations.ensureCapacity(availableTemplates.size());
823             for (StreamCombinationTemplate combTemplate : availableTemplates) {
824                 ArrayList<MandatoryStreamInformation> streamsInfo =
825                         new ArrayList<MandatoryStreamInformation>();
826                 streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length);
827                 boolean isReprocessable = combTemplate.mReprocessType != ReprocessType.NONE;
828                 if (isReprocessable) {
829                     // The first and second streams in a reprocessable combination have the
830                     // same size and format. The first is the input and the second is the output
831                     // used for generating the subsequent input buffers.
832                     ArrayList<Size> inputSize = new ArrayList<Size>();
833                     int format;
834                     if (combTemplate.mReprocessType == ReprocessType.PRIVATE) {
835                         inputSize.add(maxPrivateInputSize);
836                         format = ImageFormat.PRIVATE;
837                     } else {
838                         inputSize.add(maxYUVInputSize);
839                         format = ImageFormat.YUV_420_888;
840                     }
841 
842                     streamsInfo.add(new MandatoryStreamInformation(inputSize, format,
843                                 /*isInput*/true));
844                     streamsInfo.add(new MandatoryStreamInformation(inputSize, format));
845                 }
846 
847                 for (StreamTemplate template : combTemplate.mStreamTemplates) {
848                     List<Size> sizes = null;
849                     if (template.mFormat == ImageFormat.RAW_SENSOR) {
850                         sizes = availableRawSizes;
851                     } else {
852                         Pair<SizeThreshold, Integer> pair;
853                         pair = new Pair<SizeThreshold, Integer>(template.mSizeThreshold,
854                                 new Integer(template.mFormat));
855                         sizes = availableSizes.get(pair);
856                     }
857 
858                     MandatoryStreamInformation streamInfo;
859                     try {
860                         streamInfo = new MandatoryStreamInformation(sizes, template.mFormat);
861                     } catch (IllegalArgumentException e) {
862                         Log.e(TAG, "No available sizes found for format: " + template.mFormat +
863                                 " size threshold: " + template.mSizeThreshold + " combination: " +
864                                 combTemplate.mDescription);
865                         return null;
866                     }
867 
868                     streamsInfo.add(streamInfo);
869                 }
870 
871                 MandatoryStreamCombination streamCombination;
872                 try {
873                     streamCombination = new MandatoryStreamCombination(streamsInfo,
874                             combTemplate.mDescription, isReprocessable);
875                 } catch (IllegalArgumentException e) {
876                     Log.e(TAG, "No stream information for mandatory combination: "
877                             + combTemplate.mDescription);
878                     return null;
879                 }
880 
881                 availableStreamCombinations.add(streamCombination);
882             }
883 
884             return Collections.unmodifiableList(availableStreamCombinations);
885         }
886 
887         /**
888          * Helper method to enumerate all available sizes according to size threshold and format.
889          */
890         private @Nullable HashMap<Pair<SizeThreshold, Integer>, List<Size>>
enumerateAvailableSizes()891             enumerateAvailableSizes() {
892             final int[] formats = {
893                 ImageFormat.PRIVATE,
894                 ImageFormat.YUV_420_888,
895                 ImageFormat.JPEG
896             };
897             Size recordingMaxSize = new Size(0, 0);
898             Size previewMaxSize = new Size(0, 0);
899             Size vgaSize = new Size(640, 480);
900             // For external camera, or hidden physical camera, CamcorderProfile may not be
901             // available, so get maximum recording size using stream configuration map.
902             if (isExternalCamera() || mIsHiddenPhysicalCamera) {
903                 recordingMaxSize = getMaxCameraRecordingSize();
904             } else {
905                 recordingMaxSize = getMaxRecordingSize();
906             }
907             if (recordingMaxSize == null) {
908                 Log.e(TAG, "Failed to find maximum recording size!");
909                 return null;
910             }
911 
912             HashMap<Integer, Size[]> allSizes = new HashMap<Integer, Size[]>();
913             for (int format : formats) {
914                 Integer intFormat = new Integer(format);
915                 allSizes.put(intFormat, mStreamConfigMap.getOutputSizes(format));
916             }
917 
918             List<Size> previewSizes = getSizesWithinBound(
919                     allSizes.get(new Integer(ImageFormat.PRIVATE)), kPreviewSizeBound);
920             if ((previewSizes == null) || (previewSizes.isEmpty())) {
921                 Log.e(TAG, "No preview sizes within preview size bound!");
922                 return null;
923             }
924             List<Size> orderedPreviewSizes = getAscendingOrderSizes(previewSizes,
925                     /*ascending*/false);
926             previewMaxSize = getMaxPreviewSize(orderedPreviewSizes);
927 
928             HashMap<Pair<SizeThreshold, Integer>, List<Size>> availableSizes =
929                     new HashMap<Pair<SizeThreshold, Integer>, List<Size>>();
930 
931             for (int format : formats) {
932                 Integer intFormat = new Integer(format);
933                 Size[] sizes = allSizes.get(intFormat);
934                 Pair<SizeThreshold, Integer> pair = new Pair<SizeThreshold, Integer>(
935                         SizeThreshold.VGA, intFormat);
936                 availableSizes.put(pair, getSizesWithinBound(sizes, vgaSize));
937 
938                 pair = new Pair<SizeThreshold, Integer>(SizeThreshold.PREVIEW, intFormat);
939                 availableSizes.put(pair, getSizesWithinBound(sizes, previewMaxSize));
940 
941                 pair = new Pair<SizeThreshold, Integer>(SizeThreshold.RECORD, intFormat);
942                 availableSizes.put(pair, getSizesWithinBound(sizes, recordingMaxSize));
943 
944                 pair = new Pair<SizeThreshold, Integer>(SizeThreshold.MAXIMUM, intFormat);
945                 availableSizes.put(pair, Arrays.asList(sizes));
946             }
947 
948             return availableSizes;
949         }
950 
951         /**
952          * Compile a list of sizes smaller than or equal to given bound.
953          * Return an empty list if there is no size smaller than or equal to the bound.
954          */
getSizesWithinBound(@onNull Size[] sizes, @NonNull Size bound)955         private static @Nullable List<Size> getSizesWithinBound(@NonNull Size[] sizes,
956                 @NonNull Size bound) {
957             ArrayList<Size> ret = new ArrayList<Size>();
958             for (Size size : sizes) {
959                 if (size.getWidth() <= bound.getWidth() && size.getHeight() <= bound.getHeight()) {
960                     ret.add(size);
961                 }
962             }
963 
964             return ret;
965         }
966 
967         /**
968          * Get the largest size by area.
969          *
970          * @param sizes an array of sizes, must have at least 1 element
971          *
972          * @return Largest Size
973          *
974          * @throws IllegalArgumentException if sizes was null or had 0 elements
975          */
getMaxSize(@onNull Size... sizes)976         public static @Nullable Size getMaxSize(@NonNull Size... sizes) {
977             if (sizes == null || sizes.length == 0) {
978                 throw new IllegalArgumentException("sizes was empty");
979             }
980 
981             Size sz = sizes[0];
982             for (Size size : sizes) {
983                 if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) {
984                     sz = size;
985                 }
986             }
987 
988             return sz;
989         }
990 
991         /**
992          * Whether or not the hardware level reported by android.info.supportedHardwareLevel is
993          * at least the desired one (but could be higher)
994          */
isHardwareLevelAtLeast(int level)995         private boolean isHardwareLevelAtLeast(int level) {
996             final int[] sortedHwLevels = {
997                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
998                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL,
999                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
1000                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
1001                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
1002             };
1003             if (level == mHwLevel) {
1004                 return true;
1005             }
1006 
1007             for (int sortedlevel : sortedHwLevels) {
1008                 if (sortedlevel == level) {
1009                     return true;
1010                 } else if (sortedlevel == mHwLevel) {
1011                     return false;
1012                 }
1013             }
1014 
1015             return false;
1016         }
1017 
1018         /**
1019          * Whether or not the camera is an external camera.
1020          *
1021          * @return {@code true} if the device is external, {@code false} otherwise.
1022          */
isExternalCamera()1023         private boolean isExternalCamera() {
1024             return mHwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL;
1025         }
1026 
1027         /**
1028          * Whether or not the hardware level is at least legacy.
1029          *
1030          * @return {@code true} if the device is {@code LEGACY}, {@code false} otherwise.
1031          */
isHardwareLevelAtLeastLegacy()1032         private boolean isHardwareLevelAtLeastLegacy() {
1033             return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
1034         }
1035 
1036         /**
1037          * Whether or not the hardware level is at least limited.
1038          *
1039          * @return {@code true} if the device is {@code LIMITED} or {@code FULL},
1040          *         {@code false} otherwise (i.e. LEGACY).
1041          */
isHardwareLevelAtLeastLimited()1042         private boolean isHardwareLevelAtLeastLimited() {
1043             return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED);
1044         }
1045 
1046         /**
1047          * Whether or not the hardware level is at least full.
1048          *
1049          * @return {@code true} if the device is {@code FULL}, {@code false} otherwise.
1050          */
isHardwareLevelAtLeastFull()1051         private boolean isHardwareLevelAtLeastFull() {
1052             return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
1053         }
1054 
1055         /**
1056          * Whether or not the hardware level is at least Level 3.
1057          *
1058          * @return {@code true} if the device is {@code LEVEL3}, {@code false} otherwise.
1059          */
isHardwareLevelAtLeastLevel3()1060         private boolean isHardwareLevelAtLeastLevel3() {
1061             return isHardwareLevelAtLeast(CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3);
1062         }
1063 
1064         /**
1065          * Determine whether the current device supports a capability or not.
1066          *
1067          * @param capability (non-negative)
1068          *
1069          * @return {@code true} if the capability is supported, {@code false} otherwise.
1070          *
1071          */
isCapabilitySupported(int capability)1072         private boolean isCapabilitySupported(int capability) {
1073             return mCapabilities.contains(capability);
1074         }
1075 
1076         /**
1077          * Check whether the current device is backward compatible.
1078          */
isColorOutputSupported()1079         private boolean isColorOutputSupported() {
1080             return isCapabilitySupported(
1081                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE);
1082         }
1083 
1084         /**
1085          * Check whether the current device supports private reprocessing.
1086          */
isPrivateReprocessingSupported()1087         private boolean isPrivateReprocessingSupported() {
1088             return isCapabilitySupported(
1089                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
1090         }
1091 
1092         /**
1093          * Check whether the current device supports YUV reprocessing.
1094          */
isYUVReprocessingSupported()1095         private boolean isYUVReprocessingSupported() {
1096             return isCapabilitySupported(
1097                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING);
1098         }
1099 
1100         /**
1101          * Return the maximum supported video size using the camcorder profile information.
1102          *
1103          * @return Maximum supported video size.
1104          */
getMaxRecordingSize()1105         private @Nullable Size getMaxRecordingSize() {
1106             int quality =
1107                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_2160P) ?
1108                         CamcorderProfile.QUALITY_2160P :
1109                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_1080P) ?
1110                         CamcorderProfile.QUALITY_1080P :
1111                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_720P) ?
1112                         CamcorderProfile.QUALITY_720P :
1113                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_480P) ?
1114                         CamcorderProfile.QUALITY_480P :
1115                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_QVGA) ?
1116                         CamcorderProfile.QUALITY_QVGA :
1117                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_CIF) ?
1118                         CamcorderProfile.QUALITY_CIF :
1119                     CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_QCIF) ?
1120                         CamcorderProfile.QUALITY_QCIF :
1121                         -1;
1122 
1123             if (quality < 0) {
1124                 return null;
1125             }
1126 
1127             CamcorderProfile maxProfile = CamcorderProfile.get(mCameraId, quality);
1128             return new Size(maxProfile.videoFrameWidth, maxProfile.videoFrameHeight);
1129         }
1130 
1131         /**
1132          * Return the maximum supported video size for cameras using data from
1133          * the stream configuration map.
1134          *
1135          * @return Maximum supported video size.
1136          */
getMaxCameraRecordingSize()1137         private @NonNull Size getMaxCameraRecordingSize() {
1138             final Size FULLHD = new Size(1920, 1080);
1139 
1140             Size[] videoSizeArr = mStreamConfigMap.getOutputSizes(
1141                     android.media.MediaRecorder.class);
1142             List<Size> sizes = new ArrayList<Size>();
1143             for (Size sz: videoSizeArr) {
1144                 if (sz.getWidth() <= FULLHD.getWidth() && sz.getHeight() <= FULLHD.getHeight()) {
1145                     sizes.add(sz);
1146                 }
1147             }
1148             List<Size> videoSizes = getAscendingOrderSizes(sizes, /*ascending*/false);
1149             for (Size sz : videoSizes) {
1150                 long minFrameDuration = mStreamConfigMap.getOutputMinFrameDuration(
1151                         android.media.MediaRecorder.class, sz);
1152                 // Give some margin for rounding error
1153                 if (minFrameDuration > (1e9 / 30.1)) {
1154                     Log.i(TAG, "External camera " + mCameraId + " has max video size:" + sz);
1155                     return sz;
1156                 }
1157             }
1158             Log.w(TAG, "Camera " + mCameraId + " does not support any 30fps video output");
1159             return FULLHD; // doesn't matter what size is returned here
1160         }
1161 
getMaxPreviewSize(List<Size> orderedPreviewSizes)1162         private @NonNull Size getMaxPreviewSize(List<Size> orderedPreviewSizes) {
1163             if (orderedPreviewSizes != null) {
1164                 for (Size size : orderedPreviewSizes) {
1165                     if ((mDisplaySize.getWidth() >= size.getWidth()) &&
1166                             (mDisplaySize.getHeight() >= size.getHeight())) {
1167                         return size;
1168                     }
1169                 }
1170             }
1171 
1172             Log.w(TAG,"Camera " + mCameraId + " maximum preview size search failed with "
1173                     + "display size " + mDisplaySize);
1174             return kPreviewSizeBound;
1175         }
1176 
1177         /**
1178          * Size comparison method used by size comparators.
1179          */
compareSizes(int widthA, int heightA, int widthB, int heightB)1180         private static int compareSizes(int widthA, int heightA, int widthB, int heightB) {
1181             long left = widthA * (long) heightA;
1182             long right = widthB * (long) heightB;
1183             if (left == right) {
1184                 left = widthA;
1185                 right = widthB;
1186             }
1187             return (left < right) ? -1 : (left > right ? 1 : 0);
1188         }
1189 
1190         /**
1191          * Size comparator that compares the number of pixels it covers.
1192          *
1193          * <p>If two the areas of two sizes are same, compare the widths.</p>
1194          */
1195         public static class SizeComparator implements Comparator<Size> {
1196             @Override
compare(@onNull Size lhs, @NonNull Size rhs)1197             public int compare(@NonNull Size lhs, @NonNull Size rhs) {
1198                 return compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(),
1199                         rhs.getHeight());
1200             }
1201         }
1202 
1203         /**
1204          * Get a sorted list of sizes from a given size list.
1205          *
1206          * <p>
1207          * The size is compare by area it covers, if the areas are same, then
1208          * compare the widths.
1209          * </p>
1210          *
1211          * @param sizeList The input size list to be sorted
1212          * @param ascending True if the order is ascending, otherwise descending order
1213          * @return The ordered list of sizes
1214          */
getAscendingOrderSizes( @onNull final List<Size> sizeList, boolean ascending)1215         private static @NonNull List<Size> getAscendingOrderSizes(
1216                 @NonNull final List<Size> sizeList, boolean ascending) {
1217             Comparator<Size> comparator = new SizeComparator();
1218             List<Size> sortedSizes = new ArrayList<Size>();
1219             sortedSizes.addAll(sizeList);
1220             Collections.sort(sortedSizes, comparator);
1221             if (!ascending) {
1222                 Collections.reverse(sortedSizes);
1223             }
1224 
1225             return sortedSizes;
1226         }
1227     }
1228 }
1229