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><owner>.[optional.subCategories.]<optionId></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><owner>.[optional.subCategories.]<optionId></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 * <owner>.[optional.subCategories.]<optionId> 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