[go: nahoru, domu]

blob: 47e658a66498116370ad1b3fc89497b71fa6f88d [file] [log] [blame]
/*
* Copyright (C) 2019 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 androidx.camera.extensions;
import android.content.Context;
import android.util.Range;
import android.util.Size;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.CameraProvider;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.Logger;
import androidx.camera.core.Preview;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.camera.extensions.impl.InitializerImpl;
import androidx.camera.extensions.internal.ExtensionVersion;
import androidx.camera.extensions.internal.Version;
import androidx.camera.extensions.internal.VersionName;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.lifecycle.LifecycleOwner;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.ExecutionException;
/**
* Provides interfaces for third party app developers to get capabilities info of extension
* functions.
*
* <p>Only a single {@link ExtensionsManager} instance can exist within a process, and it can be
* retrieved with {@link #getInstance(Context)}. After retrieving the {@link ExtensionsManager}
* instance, the availability of a specific extension mode can be checked by
* {@link #isExtensionAvailable(CameraProvider, CameraSelector, int)}. For an available extension
* mode, an extension enabled {@link CameraSelector} can be obtained by calling
* {@link #getExtensionEnabledCameraSelector(CameraProvider, CameraSelector, int)}. After binding
* use cases by the extension enabled {@link CameraSelector}, the extension mode will be applied
* to the bound {@link Preview} and {@link ImageCapture}. The following sample code describes how
* to enable an extension mode for use cases.
* </p>
* <pre>
* void onCreate() {
* // Create a camera provider
* ProcessCameraProvider cameraProvider = ... // Get the provider instance
* // Create an extensions manager
* ExtensionsManager extensionsManager = ... // Get the extensions manager instance
*
* // Query if extension is available.
* if (mExtensionsManager.isExtensionAvailable(cameraProvider, DEFAULT_BACK_CAMERA,
* ExtensionMode.BOKEH)) {
* // Needs to unbind all use cases before enabling different extension mode.
* cameraProvider.unbindAll();
*
* // Retrieve extension enabled camera selector
* CameraSelector extensionCameraSelector;
* extensionCameraSelector = extensionsManager.getExtensionEnabledCameraSelector(
* cameraProvider, DEFAULT_BACK_CAMERA, ExtensionMode.BOKEH);
*
* // Bind image capture and preview use cases with the extension enabled camera selector.
* ImageCapture imageCapture = new ImageCapture.Builder().build();
* Preview preview = new Preview.Builder().build();
* cameraProvider.bindToLifecycle(this, extensionCameraSelector, imageCapture, preview);
* }
* }
* </pre>
*/
public final class ExtensionsManager {
private static final String TAG = "ExtensionsManager";
enum ExtensionsAvailability {
/**
* The device extensions library exists and has been correctly loaded.
*/
LIBRARY_AVAILABLE,
/**
* The device extensions library exists. However, there was some error loading the library.
*/
LIBRARY_UNAVAILABLE_ERROR_LOADING,
/**
* The device extensions library exists. However, the library is missing implementations.
*/
LIBRARY_UNAVAILABLE_MISSING_IMPLEMENTATION,
/**
* There are no extensions available on this device.
*/
NONE
}
// Singleton instance of the Extensions object
private static final Object EXTENSIONS_LOCK = new Object();
@GuardedBy("EXTENSIONS_LOCK")
private static ListenableFuture<ExtensionsManager> sInitializeFuture;
@GuardedBy("EXTENSIONS_LOCK")
private static ListenableFuture<Void> sDeinitializeFuture;
@GuardedBy("EXTENSIONS_LOCK")
private static ExtensionsManager sExtensionsManager;
private final ExtensionsAvailability mExtensionsAvailability;
/**
* Retrieves the {@link ExtensionsManager} associated with the current process.
*
* <p>An application must wait until the {@link ListenableFuture} completes to get an
* {@link ExtensionsManager} instance. The {@link ExtensionsManager} instance can be used to
* access the extensions related functions.
*/
@NonNull
public static ListenableFuture<ExtensionsManager> getInstance(@NonNull Context context) {
return getInstance(context, VersionName.getCurrentVersion());
}
static ListenableFuture<ExtensionsManager> getInstance(@NonNull Context context,
@NonNull VersionName versionName) {
synchronized (EXTENSIONS_LOCK) {
if (sDeinitializeFuture != null && !sDeinitializeFuture.isDone()) {
throw new IllegalStateException("Not yet done deinitializing extensions");
}
sDeinitializeFuture = null;
// Will be initialized, with an empty implementation which will report all extensions
// as unavailable
if (ExtensionVersion.getRuntimeVersion() == null) {
return Futures.immediateFuture(
getOrCreateExtensionsManager(ExtensionsAvailability.NONE));
}
// Prior to 1.1 no additional initialization logic required
if (ExtensionVersion.getRuntimeVersion().compareTo(Version.VERSION_1_1) < 0) {
return Futures.immediateFuture(
getOrCreateExtensionsManager(ExtensionsAvailability.LIBRARY_AVAILABLE));
}
if (sInitializeFuture == null) {
sInitializeFuture = CallbackToFutureAdapter.getFuture(completer -> {
try {
InitializerImpl.init(versionName.toVersionString(),
context,
new InitializerImpl.OnExtensionsInitializedCallback() {
@Override
public void onSuccess() {
Logger.d(TAG, "Successfully initialized extensions");
completer.set(getOrCreateExtensionsManager(
ExtensionsAvailability.LIBRARY_AVAILABLE));
}
@Override
public void onFailure(int error) {
Logger.e(TAG, "Failed to initialize extensions");
completer.set(getOrCreateExtensionsManager(
ExtensionsAvailability
.LIBRARY_UNAVAILABLE_ERROR_LOADING));
}
},
CameraXExecutors.directExecutor());
} catch (NoSuchMethodError | NoClassDefFoundError | AbstractMethodError e) {
Logger.e(TAG, "Failed to initialize extensions. Some classes or methods "
+ "are missed in the vendor library. " + e);
completer.set(getOrCreateExtensionsManager(
ExtensionsAvailability.LIBRARY_UNAVAILABLE_MISSING_IMPLEMENTATION));
} catch (RuntimeException e) {
// Catches all unexpected runtime exceptions and still returns an
// ExtensionsManager instance which performs default behavior.
Logger.e(TAG,
"Failed to initialize extensions. Something wents wrong when "
+ "initializing the vendor library. "
+ e);
completer.set(getOrCreateExtensionsManager(
ExtensionsAvailability.LIBRARY_UNAVAILABLE_ERROR_LOADING));
}
return "Initialize extensions";
});
}
return sInitializeFuture;
}
}
/**
* Shutdown the extensions.
*
* <p> For the moment only used for testing to shutdown the extensions. Calling this function
* can deinitialize the extensions vendor library and release the created
* {@link ExtensionsManager} instance. Tests should wait until the returned future is
* complete. Then, tests can call the {@link ExtensionsManager#getInstance(Context)} function
* again to initialize a new {@link ExtensionsManager} instance.
*
* @hide
*/
// TODO: Will need to be rewritten to be threadsafe with use in conjunction with
// ExtensionsManager.init(...) if this is to be released for use outside of testing.
@RestrictTo(RestrictTo.Scope.TESTS)
@NonNull
public ListenableFuture<Void> shutdown() {
synchronized (EXTENSIONS_LOCK) {
if (ExtensionVersion.getRuntimeVersion() == null) {
sInitializeFuture = null;
sExtensionsManager = null;
return Futures.immediateFuture(null);
}
// If initialization not yet attempted then deinit should succeed immediately.
if (sInitializeFuture == null) {
return Futures.immediateFuture(null);
}
// If already in progress of deinit then return the future
if (sDeinitializeFuture != null) {
return sDeinitializeFuture;
}
ExtensionsAvailability availability;
// Wait for the extension to be initialized before deinitializing. Block since
// this is only used for testing.
try {
sInitializeFuture.get();
sInitializeFuture = null;
availability = sExtensionsManager.mExtensionsAvailability;
sExtensionsManager = null;
} catch (ExecutionException | InterruptedException e) {
sDeinitializeFuture = Futures.immediateFailedFuture(e);
return sDeinitializeFuture;
}
// Once extension has been initialized start the deinit call
if (availability == ExtensionsAvailability.LIBRARY_AVAILABLE) {
sDeinitializeFuture = CallbackToFutureAdapter.getFuture(completer -> {
try {
InitializerImpl.deinit(
new InitializerImpl.OnExtensionsDeinitializedCallback() {
@Override
public void onSuccess() {
completer.set(null);
}
@Override
public void onFailure(int error) {
completer.setException(new Exception("Failed to "
+ "deinitialize extensions."));
}
},
CameraXExecutors.directExecutor());
} catch (NoSuchMethodError | NoClassDefFoundError e) {
completer.setException(e);
}
return null;
});
} else {
sDeinitializeFuture = Futures.immediateFuture(null);
}
return sDeinitializeFuture;
}
}
static ExtensionsManager getOrCreateExtensionsManager(
ExtensionsAvailability extensionsAvailability) {
synchronized (EXTENSIONS_LOCK) {
if (sExtensionsManager != null) {
return sExtensionsManager;
}
sExtensionsManager = new ExtensionsManager(extensionsAvailability);
return sExtensionsManager;
}
}
/**
* Returns a modified {@link CameraSelector} that will enable the specified extension mode.
*
* <p>The returned extension {@link CameraSelector} can be used to bind use cases to a
* desired {@link LifecycleOwner} and then the specified extension mode will be enabled on
* the camera.
*
* @param cameraProvider A {@link CameraProvider} will be used to query the information
* of cameras on the device. The {@link CameraProvider} can be the
* {@link androidx.camera.lifecycle.ProcessCameraProvider}
* which is obtained by
* {@link androidx.camera.lifecycle.ProcessCameraProvider#getInstance(Context)}.
* @param baseCameraSelector The base {@link CameraSelector} on top of which the extension
* config is applied.
* {@link #isExtensionAvailable(CameraProvider, CameraSelector, int)}
* can be used to check whether any camera can support the specified
* extension mode for the base camera selector.
* @param mode The target extension mode.
* @return a {@link CameraSelector} for the specified Extensions mode.
* @throws IllegalArgumentException If this device doesn't support extensions function, no
* camera can be found to support the specified extension
* mode, or the base {@link CameraSelector} has contained
* extension related configuration in it.
*/
@NonNull
public CameraSelector getExtensionEnabledCameraSelector(@NonNull CameraProvider cameraProvider,
@NonNull CameraSelector baseCameraSelector, @ExtensionMode.Mode int mode) {
// Directly return the input baseCameraSelector if the target extension mode is NONE.
if (mode == ExtensionMode.NONE) {
return baseCameraSelector;
}
if (mExtensionsAvailability != ExtensionsAvailability.LIBRARY_AVAILABLE) {
throw new IllegalArgumentException("This device doesn't support extensions function! "
+ "isExtensionAvailable should be checked first before calling "
+ "getExtensionEnabledCameraSelector.");
}
return ExtensionsInfo.getExtensionCameraSelectorAndInjectCameraConfig(cameraProvider,
baseCameraSelector, mode);
}
/**
* Returns true if the particular extension mode is available for the specified
* {@link CameraSelector}.
*
* @param cameraProvider A {@link CameraProvider} will be used to query the information
* of cameras on the device. The {@link CameraProvider} can be the
* {@link androidx.camera.lifecycle.ProcessCameraProvider}
* which is obtained by
* {@link androidx.camera.lifecycle.ProcessCameraProvider#getInstance(Context)}.
* @param baseCameraSelector The base {@link CameraSelector} to find a camera to use.
* @param mode The target extension mode to support.
*/
public boolean isExtensionAvailable(@NonNull CameraProvider cameraProvider,
@NonNull CameraSelector baseCameraSelector, @ExtensionMode.Mode int mode) {
if (mode == ExtensionMode.NONE) {
return true;
}
if (mExtensionsAvailability != ExtensionsAvailability.LIBRARY_AVAILABLE) {
// Returns false if extensions are not available.
return false;
}
return ExtensionsInfo.isExtensionAvailable(cameraProvider, baseCameraSelector, mode);
}
/**
* Returns the estimated capture latency range in milliseconds for the target capture
* resolution.
*
* <p>This includes the time spent processing the multi-frame capture request along with any
* additional time for encoding of the processed buffer in the framework if necessary.
*
* @param cameraProvider A {@link CameraProvider} will be used to query the information
* of cameras on the device. The {@link CameraProvider} can be the
* {@link androidx.camera.lifecycle.ProcessCameraProvider}
* which is obtained by
* {@link androidx.camera.lifecycle.ProcessCameraProvider#getInstance(Context)}.
* @param cameraSelector The {@link CameraSelector} to find a camera which supports the
* specified extension mode.
* @param mode The extension mode to check.
* @param surfaceResolution the surface resolution of the {@link ImageCapture} which will be
* used to take a picture. If the input value of this parameter is
* null or it is not included in the supported output sizes, the
* maximum capture output size is used to get the estimated range
* information.
* @return the range of estimated minimal and maximal capture latency in milliseconds.
* Returns null if no capture latency info can be provided.
* @throws IllegalArgumentException If this device doesn't support extensions function, or no
* camera can be found to support the specified extension mode.
*/
@Nullable
public Range<Long> getEstimatedCaptureLatencyRange(@NonNull CameraProvider cameraProvider,
@NonNull CameraSelector cameraSelector, @ExtensionMode.Mode int mode,
@Nullable Size surfaceResolution) {
if (mode == ExtensionMode.NONE
|| mExtensionsAvailability != ExtensionsAvailability.LIBRARY_AVAILABLE) {
throw new IllegalArgumentException(
"No camera can be found to support the specified extensions mode! "
+ "isExtensionAvailable should be checked first before calling "
+ "getEstimatedCaptureLatencyRange.");
}
return ExtensionsInfo.getEstimatedCaptureLatencyRange(cameraProvider, cameraSelector, mode,
surfaceResolution);
}
@VisibleForTesting
@NonNull
ExtensionsAvailability getExtensionsAvailability() {
return mExtensionsAvailability;
}
private ExtensionsManager(@NonNull ExtensionsAvailability extensionsAvailability) {
mExtensionsAvailability = extensionsAvailability;
}
}