1 /*
2  * Copyright 2019 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 androidx.camera.core.impl;
18 
19 import androidx.camera.core.impl.utils.ResolutionSelectorUtil;
20 import androidx.camera.core.resolutionselector.ResolutionSelector;
21 
22 import com.google.auto.value.AutoValue;
23 
24 import org.jspecify.annotations.NonNull;
25 import org.jspecify.annotations.Nullable;
26 
27 import java.util.Objects;
28 import java.util.Set;
29 
30 /**
31  * A Config is a collection of options and values.
32  *
33  * <p>Config object hold pairs of Options/Values and offer methods for querying whether
34  * Options are contained in the configuration along with methods for retrieving the associated
35  * values for options.
36  *
37  * <p>Config allows different values to be set with different {@link OptionPriority} on the same
38  * Option. While {@link Config#retrieveOption} will return the option value of the highest priority,
39  * {@link Config#retrieveOptionWithPriority} and {@link Config#getPriorities} can be used to
40  * retrieve option value of specified priority.
41  */
42 public interface Config {
43 
44     /**
45      * Returns whether this configuration contains the supplied option.
46      *
47      * @param id The {@link Option} to search for in this configuration.
48      * @return {@code true} if this configuration contains the supplied option; {@code false}
49      * otherwise.
50      */
containsOption(@onNull Option<?> id)51     boolean containsOption(@NonNull Option<?> id);
52 
53     /**
54      * Retrieves the value for the specified option if it exists in the configuration.
55      *
56      * <p>If the option does not exist, an exception will be thrown. If there are multiple values
57      * being set with multiple {@link OptionPriority}, it will return the value of highest
58      * priority.
59      *
60      * @param id       The {@link Option} to search for in this configuration.
61      * @param <ValueT> The type for the value associated with the supplied {@link Option}.
62      * @return The value stored in this configuration.
63      * @throws IllegalArgumentException if the given option does not exist in this configuration.
64      */
retrieveOption(@onNull Option<ValueT> id)65     <ValueT> @Nullable ValueT retrieveOption(@NonNull Option<ValueT> id);
66 
67     /**
68      * Retrieves the value for the specified option if it exists in the configuration.
69      *
70      * <p>If the option does not exist, <code>valueIfMissing</code> will be returned. If there are
71      * multiple values being set with multiple {@link OptionPriority}, it will return the value of
72      * highest priority.
73      *
74      * @param id             The {@link Option} to search for in this configuration.
75      * @param valueIfMissing The value to return if the specified {@link Option} does not exist in
76      *                       this configuration.
77      * @param <ValueT>       The type for the value associated with the supplied {@link Option}.
78      * @return The value stored in this configuration, or {@code valueIfMissing} if it does
79      * not exist.
80      */
retrieveOption(@onNull Option<ValueT> id, @Nullable ValueT valueIfMissing)81     <ValueT> @Nullable ValueT retrieveOption(@NonNull Option<ValueT> id,
82             @Nullable ValueT valueIfMissing);
83 
84     /**
85      * Retrieves the value for the specified option and specified priority if it exists in the
86      * configuration.
87      *
88      * <p>If the option does not exist, an exception will be thrown.
89      *
90      * @param id             The {@link Option} to search for in this configuration.
91      * @param <ValueT>       The type for the value associated with the supplied {@link Option}.
92      * @throws IllegalArgumentException if the given option with specified priority does not exist
93      * in this configuration.
94      */
retrieveOptionWithPriority(@onNull Option<ValueT> id, @NonNull OptionPriority priority)95     <ValueT> @Nullable ValueT retrieveOptionWithPriority(@NonNull Option<ValueT> id,
96             @NonNull OptionPriority priority);
97 
98     /**
99      * Returns the current priority of the value for the specified option.
100      *
101      * <p>If there are multiple values of various priorities for the specified options, the highest
102      * priority will be returned. If the option does not exist, an
103      * {@link IllegalArgumentException} will be thrown.
104      */
getOptionPriority(@onNull Option<?> opt)105     @NonNull OptionPriority getOptionPriority(@NonNull Option<?> opt);
106 
107     /**
108      * Search the configuration for {@link Option}s whose id match the supplied search string.
109      *
110      * @param idSearchString The id string to search for. This could be a fully qualified id such as
111      *                       \"<code>camerax.core.example.option</code>\" or the stem for an
112      *                       option such as \"<code>
113      *                       camerax.core.example</code>\".
114      * @param matcher        A callback used to receive results of the search. Results will be
115      *                       sent to {@link OptionMatcher#onOptionMatched(Option)} in the order
116      *                       in which they are found inside this configuration. Subsequent
117      *                       results will continue to be sent as long as {@link
118      *                       OptionMatcher#onOptionMatched(Option)} returns {@code true}.
119      */
findOptions(@onNull String idSearchString, @NonNull OptionMatcher matcher)120     void findOptions(@NonNull String idSearchString, @NonNull OptionMatcher matcher);
121 
122     /**
123      * Lists all options contained within this configuration.
124      *
125      * @return A {@link Set} of {@link Option}s contained within this configuration.
126      */
listOptions()127     @NonNull Set<Option<?>> listOptions();
128 
129     /**
130      *
131      * Returns a {@link Set} of all priorities set for the specified option.
132      *
133      */
getPriorities(@onNull Option<?> option)134     @NonNull Set<OptionPriority> getPriorities(@NonNull Option<?> option);
135 
136     /**
137      * A callback for retrieving results of a {@link Config.Option} search.
138      */
139     interface OptionMatcher {
140         /**
141          * Receives results from {@link Config#findOptions(String, OptionMatcher)}.
142          *
143          * <p>When searching for a specific option in a {@link Config}, {@link Option}s will
144          * be sent to {@link #onOptionMatched(Option)} in the order in which they are found.
145          *
146          * @param option The matched option.
147          * @return <code>false</code> if no further results are needed; <code>true</code> otherwise.
148          */
onOptionMatched(@onNull Option<?> option)149         boolean onOptionMatched(@NonNull Option<?> option);
150     }
151 
152     /**
153      * An {@link Option} is used to set and retrieve values for settings defined in a {@link
154      * Config}.
155      *
156      * <p>{@link Option}s can be thought of as the key in a key/value pair that makes up a setting.
157      * As the name suggests, {@link Option}s are optional, and may or may not exist inside a {@link
158      * Config}.
159      *
160      * @param <T> The type of the value for this option.
161      */
162     @AutoValue
163     abstract class Option<T> {
164 
165         /** Prevent subclassing */
Option()166         Option() {
167         }
168 
169         /**
170          * Creates an {@link Option} from an id and value class.
171          *
172          * @param id         A unique string identifier for this option. This generally follows
173          *                   the scheme
174          *                   <code>&lt;owner&gt;.[optional.subCategories.]&lt;optionId&gt;</code>.
175          * @param valueClass The class of the value stored by this option.
176          * @param <T>        The type of the value stored by this option.
177          * @return An {@link Option} object which can be used to store/retrieve values from a {@link
178          * Config}.
179          */
create(@onNull String id, @NonNull Class<?> valueClass)180         public static <T> @NonNull Option<T> create(@NonNull String id,
181                 @NonNull Class<?> valueClass) {
182             return Option.create(id, valueClass, /*token=*/ null);
183         }
184 
185         /**
186          * Creates an {@link Option} from an id, value class and token.
187          *
188          * @param id         A unique string identifier for this option. This generally follows
189          *                   the scheme
190          *                   <code>&lt;owner&gt;.[optional.subCategories.]&lt;optionId&gt;</code>.
191          * @param valueClass The class of the value stored by this option.
192          * @param <T>        The type of the value stored by this option.
193          * @param token      An optional, type-erased object for storing more context for this
194          *                   specific option. Generally this object should have static scope and be
195          *                   immutable.
196          * @return An {@link Option} object which can be used to store/retrieve values from a {@link
197          * Config}.
198          */
199         @SuppressWarnings("unchecked")
create(@onNull String id, @NonNull Class<?> valueClass, @Nullable Object token)200         public static <T> @NonNull Option<T> create(@NonNull String id,
201                 @NonNull Class<?> valueClass, @Nullable Object token) {
202             return new AutoValue_Config_Option<>(id, (Class<T>) valueClass, token);
203         }
204 
205         /**
206          * Returns the unique string identifier for this option.
207          *
208          * <p>This generally follows the scheme * <code>
209          * &lt;owner&gt;.[optional.subCategories.]&lt;optionId&gt;
210          * </code>.
211          *
212          * @return The identifier.
213          */
getId()214         public abstract @NonNull String getId();
215 
216         /**
217          * Returns the class object associated with the value for this option.
218          *
219          * @return The class object for the value's type.
220          */
getValueClass()221         public abstract @NonNull Class<T> getValueClass();
222 
223         /**
224          * Returns the optional type-erased context object for this option.
225          *
226          * <p>Generally this object should have static scope and be immutable.
227          *
228          * @return The type-erased context object.
229          */
getToken()230         public abstract @Nullable Object getToken();
231     }
232 
233     /**
234      * Defines the priorities for resolving conflicting options.
235      *
236      * <p>Priority must be declared from high priority to low priority.
237      */
238     enum OptionPriority {
239         /**
240          * It takes precedence over any other option values at the risk of causing unexpected
241          * behavior.
242          *
243          * <p>If the same option is already set, the option with this priority will overwrite the
244          * value.
245          *
246          * <p>This priority should only be used to explicitly specify an option, such as used by
247          * {@code Camera2Interop} or {@code Camera2CameraControl} to override an option.
248          */
249         ALWAYS_OVERRIDE,
250 
251         /**
252          * This priority is higher than {@link #REQUIRED} and {@link #OPTIONAL}, and it is designed
253          * to override the options internally to work around some device specific issues.
254          *
255          * <p>When two option values are set with this priority, the newer value takes precedence
256          * over the old one. Options with this priority can still be overridden by
257          * {@link #ALWAYS_OVERRIDE} which are normally used by {@code Camera2Interop}.
258          */
259         HIGH_PRIORITY_REQUIRED,
260 
261         /**
262          * It's a required option value in order to achieve expected CameraX behavior. It takes
263          * precedence over {@link #OPTIONAL} option values.
264          *
265          * <p>If two values are set to the same option, the value with {@link #ALWAYS_OVERRIDE}
266          * priority will overwrite this priority and can potentially cause unexpected behaviors.
267          *
268          * <p>If two values are set to the same option with this priority, it might indicate a
269          * programming error internally and an exception will be thrown when merging the configs.
270          */
271         REQUIRED,
272 
273         /**
274          * The lowest priority, it can be overridden by any other option value. When two option
275          * values are set with this priority, the newer value takes precedence over the old one.
276          */
277         OPTIONAL
278     }
279 
280     /**
281      * Returns if values with these {@link OptionPriority} conflict or not.
282      *
283      * <p>Currently it is not allowed the same option to have different values with priority
284      * {@link OptionPriority#REQUIRED}.
285      */
hasConflict(@onNull OptionPriority priority1, @NonNull OptionPriority priority2)286     static boolean hasConflict(@NonNull OptionPriority priority1,
287             @NonNull OptionPriority priority2) {
288         return priority1 == OptionPriority.REQUIRED
289                 && priority2 == OptionPriority.REQUIRED;
290     }
291 
292     /**
293      * Merges two configs.
294      *
295      * @param extendedConfig the extended config. The options in the extendedConfig will be applied
296      *                       on top of the baseConfig based on the option priorities.
297      * @param baseConfig the base config.
298      * @return a {@link MutableOptionsBundle} of the merged config.
299      */
mergeConfigs(@ullable Config extendedConfig, @Nullable Config baseConfig)300     static @NonNull Config mergeConfigs(@Nullable Config extendedConfig,
301             @Nullable Config baseConfig) {
302         if (extendedConfig == null && baseConfig == null) {
303             return OptionsBundle.emptyBundle();
304         }
305 
306         MutableOptionsBundle mergedConfig;
307 
308         if (baseConfig != null) {
309             mergedConfig = MutableOptionsBundle.from(baseConfig);
310         } else {
311             mergedConfig = MutableOptionsBundle.create();
312         }
313 
314         if (extendedConfig != null) {
315             // If any options need special handling, this is the place to do it. For now we'll
316             // just copy over all options.
317             for (Config.Option<?> opt : extendedConfig.listOptions()) {
318                 mergeOptionValue(mergedConfig, baseConfig, extendedConfig, opt);
319             }
320         }
321 
322         return OptionsBundle.from(mergedConfig);
323     }
324 
325     /**
326      * Merges a specific option value from two configs.
327      *
328      * @param mergedConfig   the final output config.
329      * @param baseConfig     the base config contains the option value which might be overridden by
330      *                       the corresponding option value in the extend config.
331      * @param extendedConfig the extended config contains the option value which might override
332      *                       the corresponding option value in the base config.
333      * @param opt            the option to merge.
334      */
mergeOptionValue(@onNull MutableOptionsBundle mergedConfig, @NonNull Config baseConfig, @NonNull Config extendedConfig, @NonNull Option<?> opt)335     static void mergeOptionValue(@NonNull MutableOptionsBundle mergedConfig,
336             @NonNull Config baseConfig,
337             @NonNull Config extendedConfig,
338             @NonNull Option<?> opt) {
339         @SuppressWarnings("unchecked") // Options/values are being copied directly
340         Config.Option<Object> objectOpt = (Config.Option<Object>) opt;
341 
342         // ResolutionSelector needs special handling to merge the underlying settings.
343         if (Objects.equals(objectOpt, ImageOutputConfig.OPTION_RESOLUTION_SELECTOR)) {
344             ResolutionSelector resolutionSelectorToOverride =
345                     (ResolutionSelector) extendedConfig.retrieveOption(objectOpt, null);
346             ResolutionSelector baseResolutionSelector =
347                     (ResolutionSelector) baseConfig.retrieveOption(objectOpt, null);
348             mergedConfig.insertOption(objectOpt,
349                     extendedConfig.getOptionPriority(opt),
350                     ResolutionSelectorUtil.overrideResolutionSelectors(
351                             baseResolutionSelector, resolutionSelectorToOverride));
352         } else {
353             mergedConfig.insertOption(objectOpt,
354                     extendedConfig.getOptionPriority(opt),
355                     extendedConfig.retrieveOption(objectOpt));
356         }
357     }
358 }
359