/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ex.camera2.utils; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest.Builder; import android.hardware.camera2.CaptureRequest.Key; import android.view.Surface; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * A set of settings to be used when filing a {@link CaptureRequest}. */ public class Camera2RequestSettingsSet { private final Map, Object> mDictionary; private long mRevision; /** * Create a new instance with no settings defined. * *

Creating a request from this object without first specifying any * properties on it is equivalent to just creating a request directly * from the template of choice. Its revision identifier is initially * {@code 0}, and will remain thus until its first modification.

*/ public Camera2RequestSettingsSet() { mDictionary = new HashMap<>(); mRevision = 0; } /** * Perform a deep copy of the defined settings and revision number. * * @param other The reference instance. * * @throws NullPointerException If {@code other} is {@code null}. */ public Camera2RequestSettingsSet(Camera2RequestSettingsSet other) { if (other == null) { throw new NullPointerException("Tried to copy null Camera2RequestSettingsSet"); } mDictionary = new HashMap<>(other.mDictionary); mRevision = other.mRevision; } /** * Specify a setting, potentially overriding the template's default choice. * *

Providing a {@code null} {@code value} will indicate a forced use of * the template's selection for that {@code key}; the difference here is * that this information will be propagated with unions as documented in * {@link #union}. This method increments the revision identifier if the new * choice is different than the existing selection.

* * @param key Which setting to alter. * @param value The new selection for that setting, or {@code null} to force * the use of the template's default selection for this field. * @return Whether the settings were updated, which only occurs if the * {@code value} is different from any already stored. * * @throws NullPointerException If {@code key} is {@code null}. */ public boolean set(Key key, T value) { if (key == null) { throw new NullPointerException("Received a null key"); } Object currentValue = get(key); // Only save the value if it's different from the one we already have if (!mDictionary.containsKey(key) || !Objects.equals(value, currentValue)) { mDictionary.put(key, value); ++mRevision; return true; } return false; } /** * Unsets a setting, preventing it from being propagated with unions or from * overriding the default when creating a capture request. * *

This method increments the revision identifier if a selection had * previously been made for that parameter.

* * @param key Which setting to reset. * @return Whether the settings were updated, which only occurs if the * specified setting already had a value or was forced to default. * * @throws NullPointerException If {@code key} is {@code null}. */ public boolean unset(Key key) { if (key == null) { throw new NullPointerException("Received a null key"); } if (mDictionary.containsKey(key)) { mDictionary.remove(key); ++mRevision; return true; } return false; } /** * Interrogate the current specialization of a setting. * * @param key Which setting to check. * @return The current selection for that setting, or {@code null} if the * setting is unset or forced to the template-defined default. * * @throws NullPointerException If {@code key} is {@code null}. */ @SuppressWarnings("unchecked") public T get(Key key) { if (key == null) { throw new NullPointerException("Received a null key"); } return (T) mDictionary.get(key); } /** * Query this instance for whether it prefers a particular choice for the * given request parameter. * *

This method can be used to detect whether a particular field is forced * to its default value or simply unset. While {@link #get} will return * {@code null} in both these cases, this method will return {@code true} * and {@code false}, respectively.

* * @param key Which setting to look for. * @return Whether that setting has a value that will propagate with unions. * * @throws NullPointerException If {@code key} is {@code null}. */ public boolean contains(Key key) { if (key == null) { throw new NullPointerException("Received a null key"); } return mDictionary.containsKey(key); } /** * Check whether the value of the specified setting matches the given one. * *

This method uses the {@code T} type's {@code equals} method, but is * {@code null}-tolerant.

* * @param key Which of this class's settings to check. * @param value Value to test for equality against. * @return Whether they are the same. */ public boolean matches(Key key, T value) { return Objects.equals(get(key), value); } /** * Get this set of settings's revision identifier, which can be compared * against cached past values to determine whether it has been modified. * *

Distinct revisions across the same object do not necessarily indicate * that the object's key/value pairs have changed at all, but the same * revision on the same object does imply that they've stayed the same.

* * @return The number of modifications made since the beginning of this * object's heritage. */ public long getRevision() { return mRevision; } /** * Add all settings choices defined by {@code moreSettings} to this object. * *

For any settings defined in both, the choice stored in the argument * to this method take precedence. Unset settings are not propagated, but * those forced to default as described in {@link set} are also forced to * default in {@code this} set. Invoking this method increments {@code this} * object's revision counter, but leaves the argument's unchanged.

* * @param moreSettings The source of the additional settings ({@code null} * is allowed here). * @return Whether these settings were updated, which can only fail if the * target itself is also given as the argument. */ public boolean union(Camera2RequestSettingsSet moreSettings) { if (moreSettings == null || moreSettings == this) { return false; } mDictionary.putAll(moreSettings.mDictionary); ++mRevision; return true; } /** * Create a {@link CaptureRequest} specialized for the specified * {@link CameraDevice} and targeting the given {@link Surface}s. * * @param camera The camera from which to capture. * @param template A {@link CaptureRequest} template defined in * {@link CameraDevice}. * @param targets The location(s) to draw the resulting image onto. * @return The request, ready to be passed to the camera framework. * * @throws CameraAccessException Upon an underlying framework API failure. * @throws NullPointerException If any argument is {@code null}. */ public CaptureRequest createRequest(CameraDevice camera, int template, Surface... targets) throws CameraAccessException { if (camera == null) { throw new NullPointerException("Tried to create request using null CameraDevice"); } Builder reqBuilder = camera.createCaptureRequest(template); for (Key key : mDictionary.keySet()) { setRequestFieldIfNonNull(reqBuilder, key); } for (Surface target : targets) { if (target == null) { throw new NullPointerException("Tried to add null Surface as request target"); } reqBuilder.addTarget(target); } return reqBuilder.build(); } private void setRequestFieldIfNonNull(Builder requestBuilder, Key key) { T value = get(key); if (value != null) { requestBuilder.set(key, value); } } }