| /* |
| * Copyright (C) 2013 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 android.hardware.camera2; |
| |
| import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; |
| import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA; |
| import static android.content.Context.DEVICE_ID_DEFAULT; |
| |
| import android.annotation.CallbackExecutor; |
| import android.annotation.FlaggedApi; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.RequiresPermission; |
| import android.annotation.SystemApi; |
| import android.annotation.SystemService; |
| import android.annotation.TestApi; |
| import android.app.ActivityManager; |
| import android.app.TaskInfo; |
| import android.app.compat.CompatChanges; |
| import android.companion.virtual.VirtualDeviceManager; |
| import android.compat.annotation.ChangeId; |
| import android.compat.annotation.EnabledSince; |
| import android.compat.annotation.Overridable; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.graphics.Point; |
| import android.hardware.CameraExtensionSessionStats; |
| import android.hardware.CameraStatus; |
| import android.hardware.ICameraService; |
| import android.hardware.ICameraServiceListener; |
| import android.hardware.camera2.CameraDevice.StateCallback; |
| import android.hardware.camera2.impl.CameraDeviceImpl; |
| import android.hardware.camera2.impl.CameraDeviceSetupImpl; |
| import android.hardware.camera2.impl.CameraInjectionSessionImpl; |
| import android.hardware.camera2.impl.CameraMetadataNative; |
| import android.hardware.camera2.params.ExtensionSessionConfiguration; |
| import android.hardware.camera2.params.SessionConfiguration; |
| import android.hardware.camera2.params.StreamConfiguration; |
| import android.hardware.camera2.utils.CameraIdAndSessionConfiguration; |
| import android.hardware.camera2.utils.ConcurrentCameraIdCombination; |
| import android.hardware.camera2.utils.ExceptionUtils; |
| import android.hardware.devicestate.DeviceState; |
| import android.hardware.devicestate.DeviceStateManager; |
| import android.hardware.display.DisplayManager; |
| import android.os.Binder; |
| import android.os.Handler; |
| import android.os.HandlerExecutor; |
| import android.os.HandlerThread; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.ServiceSpecificException; |
| import android.os.SystemProperties; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.util.Size; |
| import android.view.Display; |
| |
| import com.android.internal.camera.flags.Flags; |
| import com.android.internal.util.ArrayUtils; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.RejectedExecutionException; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * <p>A system service manager for detecting, characterizing, and connecting to |
| * {@link CameraDevice CameraDevices}.</p> |
| * |
| * <p>For more details about communicating with camera devices, read the Camera |
| * developer guide or the {@link android.hardware.camera2 camera2} |
| * package documentation.</p> |
| */ |
| @SystemService(Context.CAMERA_SERVICE) |
| public final class CameraManager { |
| |
| private static final String TAG = "CameraManager"; |
| private final boolean DEBUG = false; |
| |
| private static final int USE_CALLING_UID = -1; |
| |
| @SuppressWarnings("unused") |
| private static final int API_VERSION_1 = 1; |
| private static final int API_VERSION_2 = 2; |
| |
| private static final int CAMERA_TYPE_BACKWARD_COMPATIBLE = 0; |
| private static final int CAMERA_TYPE_ALL = 1; |
| |
| /** |
| * Caches the mapping between a logical camera ID and 'MultiResolutionStreamConfigurationMap' |
| * that is calculated by {@link #getPhysicalCameraMultiResolutionConfigs} as the calculation |
| * might take many binder calls. |
| * <p> |
| * Note, this is a map of maps. The structure is: |
| * <pre> |
| * { |
| * logicalCameraId_1 -> { |
| * physicalCameraId_1 -> [ |
| * streamConfiguration_1, |
| * streamConfiguration_2, |
| * ... |
| * ], |
| * physicalCameraId_2 -> [...], |
| * ... |
| * }, |
| * logicalCameraId_2 -> { |
| * ... |
| * }, |
| * ... |
| * } |
| * </pre> |
| * </p> |
| */ |
| private final Map<String, Map<String, StreamConfiguration[]>> |
| mCameraIdToMultiResolutionStreamConfigurationMap = new HashMap<>(); |
| |
| private final Context mContext; |
| private final Object mLock = new Object(); |
| |
| private static final String CAMERA_OPEN_CLOSE_LISTENER_PERMISSION = |
| "android.permission.CAMERA_OPEN_CLOSE_LISTENER"; |
| private final boolean mHasOpenCloseListenerPermission; |
| |
| private VirtualDeviceManager mVirtualDeviceManager; |
| |
| /** |
| * Force camera output to be rotated to portrait orientation on landscape cameras. |
| * Many apps do not handle this situation and display stretched images otherwise. |
| * @hide |
| */ |
| @ChangeId |
| @Overridable |
| @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE) |
| @TestApi |
| public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L; |
| |
| /** |
| * System property for allowing the above |
| * @hide |
| */ |
| @TestApi |
| public static final String LANDSCAPE_TO_PORTRAIT_PROP = |
| "camera.enable_landscape_to_portrait"; |
| |
| /** |
| * Does not override landscape feed to portrait. |
| * |
| * @hide |
| */ |
| @TestApi |
| @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM) |
| public static final int ROTATION_OVERRIDE_NONE = ICameraService.ROTATION_OVERRIDE_NONE; |
| |
| /** |
| * Crops and rotates landscape camera feed to portrait, and changes sensor orientation to |
| * portrait. |
| * |
| * @hide |
| */ |
| @TestApi |
| @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM) |
| public static final int ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT = |
| ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT; |
| |
| /** |
| * Crops and rotates landscape camera feed to portrait, but doesn't change sensor orientation. |
| * |
| * @hide |
| */ |
| @TestApi |
| @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM) |
| public static final int ROTATION_OVERRIDE_ROTATION_ONLY = |
| ICameraService.ROTATION_OVERRIDE_ROTATION_ONLY; |
| |
| /** |
| * Enable physical camera availability callbacks when the logical camera is unavailable |
| * |
| * <p>Previously once a logical camera becomes unavailable, no |
| * {@link AvailabilityCallback#onPhysicalCameraAvailable} or |
| * {@link AvailabilityCallback#onPhysicalCameraUnavailable} will |
| * be called until the logical camera becomes available again. The |
| * results in the app opening the logical camera not able to |
| * receive physical camera availability change.</p> |
| * |
| * <p>With this change, the {@link |
| * AvailabilityCallback#onPhysicalCameraAvailable} and {@link |
| * AvailabilityCallback#onPhysicalCameraUnavailable} can still be |
| * called while the logical camera is unavailable. </p> |
| */ |
| @ChangeId |
| @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| private static final long ENABLE_PHYSICAL_CAMERA_CALLBACK_FOR_UNAVAILABLE_LOGICAL_CAMERA = |
| 244358506L; |
| |
| /** |
| * @hide |
| */ |
| public CameraManager(Context context) { |
| synchronized(mLock) { |
| mContext = context; |
| mHasOpenCloseListenerPermission = |
| mContext.checkSelfPermission(CAMERA_OPEN_CLOSE_LISTENER_PERMISSION) == |
| PackageManager.PERMISSION_GRANTED; |
| } |
| } |
| |
| /** |
| * @hide |
| */ |
| public interface DeviceStateListener { |
| void onDeviceStateChanged(boolean folded); |
| } |
| |
| private static final class FoldStateListener implements DeviceStateManager.DeviceStateCallback { |
| private final int[] mFoldedDeviceStates; |
| |
| private ArrayList<WeakReference<DeviceStateListener>> mDeviceStateListeners = |
| new ArrayList<>(); |
| private boolean mFoldedDeviceState; |
| |
| public FoldStateListener(Context context) { |
| mFoldedDeviceStates = context.getResources().getIntArray( |
| com.android.internal.R.array.config_foldedDeviceStates); |
| } |
| |
| private synchronized void handleStateChange(int state) { |
| boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state); |
| |
| mFoldedDeviceState = folded; |
| Iterator<WeakReference<DeviceStateListener>> it = mDeviceStateListeners.iterator(); |
| while(it.hasNext()) { |
| DeviceStateListener callback = it.next().get(); |
| if (callback != null) { |
| callback.onDeviceStateChanged(folded); |
| } else { |
| it.remove(); |
| } |
| } |
| } |
| |
| public synchronized void addDeviceStateListener(DeviceStateListener listener) { |
| listener.onDeviceStateChanged(mFoldedDeviceState); |
| mDeviceStateListeners.removeIf(l -> l.get() == null); |
| mDeviceStateListeners.add(new WeakReference<>(listener)); |
| } |
| |
| @SuppressWarnings("FlaggedApi") |
| @Override |
| public void onDeviceStateChanged(DeviceState state) { |
| // Suppressing the FlaggedAPI warning as this specific API isn't new, just moved to |
| // system API which requires it to be flagged. |
| handleStateChange(state.getIdentifier()); |
| } |
| } |
| |
| /** |
| * Register a {@link CameraCharacteristics} device state listener |
| * |
| * @param chars Camera characteristics that need to receive device state updates |
| * |
| * @hide |
| */ |
| public void registerDeviceStateListener(@NonNull CameraCharacteristics chars) { |
| CameraManagerGlobal.get().registerDeviceStateListener(chars, mContext); |
| } |
| |
| /** |
| * Return the list of currently connected camera devices by identifier, including |
| * cameras that may be in use by other camera API clients. |
| * |
| * <p>Non-removable cameras use integers starting at 0 for their |
| * identifiers, while removable cameras have a unique identifier for each |
| * individual device, even if they are the same model.</p> |
| * |
| * <p>This list doesn't contain physical cameras that can only be used as part of a logical |
| * multi-camera device.</p> |
| * |
| * @return The list of currently connected camera devices. |
| */ |
| @NonNull |
| public String[] getCameraIdList() throws CameraAccessException { |
| return CameraManagerGlobal.get().getCameraIdList(mContext.getDeviceId(), |
| getDevicePolicyFromContext(mContext)); |
| } |
| |
| /** |
| * Similar to getCameraIdList(). However, getCamerIdListNoLazy() necessarily communicates with |
| * cameraserver in order to get the list of camera ids. This is to facilitate testing since some |
| * camera ids may go 'offline' without callbacks from cameraserver because of changes in |
| * SYSTEM_CAMERA permissions (though this is not a changeable permission, tests may call |
| * adopt(drop)ShellPermissionIdentity() and effectively change their permissions). This call |
| * affects the camera ids returned by getCameraIdList() as well. Tests which do adopt shell |
| * permission identity should not mix getCameraIdList() and getCameraListNoLazyCalls(). |
| * |
| * @hide |
| */ |
| @TestApi |
| public String[] getCameraIdListNoLazy() throws CameraAccessException { |
| return CameraManagerGlobal.get().getCameraIdListNoLazy(mContext.getDeviceId(), |
| getDevicePolicyFromContext(mContext)); |
| } |
| |
| /** |
| * Return the set of combinations of currently connected camera device identifiers, which |
| * support configuring camera device sessions concurrently. |
| * |
| * <p>The devices in these combinations can be concurrently configured by the same |
| * client camera application. Using these camera devices concurrently by two different |
| * applications is not guaranteed to be supported, however.</p> |
| * |
| * <p>For concurrent operation, in chronological order : |
| * <ul> |
| * <li> Applications must first close any open cameras that have sessions configured, using |
| * {@link CameraDevice#close}. </li> |
| * <li> All camera devices intended to be operated concurrently, must be opened using |
| * {@link #openCamera}, before configuring sessions on any of the camera devices.</li> |
| *</ul> |
| *</p> |
| * <p>Each device in a combination, is guaranteed to support stream combinations which may be |
| * obtained by querying {@link #getCameraCharacteristics} for the key |
| * {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS}.</p> |
| * |
| * <p>For concurrent operation, if a camera device has a non null zoom ratio range as specified |
| * by |
| * {@link android.hardware.camera2.CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE}, |
| * its complete zoom ratio range may not apply. Applications can use |
| * {@link android.hardware.camera2.CaptureRequest#CONTROL_ZOOM_RATIO} >=1 and <= |
| * {@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM} |
| * during concurrent operation. |
| * <p> |
| * |
| * <p>The set of combinations may include camera devices that may be in use by other camera API |
| * clients.</p> |
| * |
| * <p>Concurrent camera extension sessions {@link CameraExtensionSession} are not currently |
| * supported.</p> |
| * |
| * <p>The set of combinations doesn't contain physical cameras that can only be used as |
| * part of a logical multi-camera device.</p> |
| * |
| * <p> If a new camera id becomes available through |
| * {@link AvailabilityCallback#onCameraUnavailable(String)}, clients can call |
| * this method to check if new combinations of camera ids which can stream concurrently are |
| * available. |
| * |
| * @return The set of combinations of currently connected camera devices, that may have |
| * sessions configured concurrently. The set of combinations will be empty if no such |
| * combinations are supported by the camera subsystem. |
| * |
| * @throws CameraAccessException if the camera device has been disconnected. |
| */ |
| @NonNull |
| public Set<Set<String>> getConcurrentCameraIds() throws CameraAccessException { |
| return CameraManagerGlobal.get().getConcurrentCameraIds(mContext.getDeviceId(), |
| getDevicePolicyFromContext(mContext)); |
| } |
| |
| /** |
| * Checks whether the provided set of camera devices and their corresponding |
| * {@link SessionConfiguration} can be configured concurrently. |
| * |
| * <p>This method performs a runtime check of the given {@link SessionConfiguration} and camera |
| * id combinations. The result confirms whether or not the passed session configurations can be |
| * successfully used to create camera capture sessions concurrently, on the given camera |
| * devices using {@link CameraDevice#createCaptureSession(SessionConfiguration)}. |
| * </p> |
| * |
| * <p>The method can be called at any point before, during and after active capture sessions. |
| * It will not impact normal camera behavior in any way and must complete significantly |
| * faster than creating a regular or constrained capture session.</p> |
| * |
| * <p>Although this method is faster than creating a new capture session, it is not intended |
| * to be used for exploring the entire space of supported concurrent stream combinations. The |
| * available mandatory concurrent stream combinations may be obtained by querying |
| * {@link #getCameraCharacteristics} for the key |
| * {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS}. </p> |
| * |
| * <p>Note that session parameters will be ignored and calls to |
| * {@link SessionConfiguration#setSessionParameters} are not required.</p> |
| * |
| * @return {@code true} if the given combination of session configurations and corresponding |
| * camera ids are concurrently supported by the camera sub-system, |
| * {@code false} otherwise OR if the set of camera devices provided is not a subset of |
| * those returned by {@link #getConcurrentCameraIds}. |
| * |
| * @throws CameraAccessException if one of the camera devices queried is no longer connected. |
| * |
| */ |
| @RequiresPermission(android.Manifest.permission.CAMERA) |
| public boolean isConcurrentSessionConfigurationSupported( |
| @NonNull Map<String, SessionConfiguration> cameraIdAndSessionConfig) |
| throws CameraAccessException { |
| return CameraManagerGlobal.get().isConcurrentSessionConfigurationSupported( |
| cameraIdAndSessionConfig, mContext.getApplicationInfo().targetSdkVersion, |
| mContext.getDeviceId(), getDevicePolicyFromContext(mContext)); |
| } |
| |
| /** |
| * Register a callback to be notified about camera device availability. |
| * |
| * <p>Registering the same callback again will replace the handler with the |
| * new one provided.</p> |
| * |
| * <p>The first time a callback is registered, it is immediately called |
| * with the availability status of all currently known camera devices.</p> |
| * |
| * <p>{@link AvailabilityCallback#onCameraUnavailable(String)} will be called whenever a camera |
| * device is opened by any camera API client. As of API level 23, other camera API clients may |
| * still be able to open such a camera device, evicting the existing client if they have higher |
| * priority than the existing client of a camera device. See open() for more details.</p> |
| * |
| * <p>Since this callback will be registered with the camera service, remember to unregister it |
| * once it is no longer needed; otherwise the callback will continue to receive events |
| * indefinitely and it may prevent other resources from being released. Specifically, the |
| * callbacks will be invoked independently of the general activity lifecycle and independently |
| * of the state of individual CameraManager instances.</p> |
| * |
| * @param callback the new callback to send camera availability notices to |
| * @param handler The handler on which the callback should be invoked, or {@code null} to use |
| * the current thread's {@link android.os.Looper looper}. |
| * |
| * @throws IllegalArgumentException if the handler is {@code null} but the current thread has |
| * no looper. |
| */ |
| public void registerAvailabilityCallback(@NonNull AvailabilityCallback callback, |
| @Nullable Handler handler) { |
| CameraManagerGlobal.get().registerAvailabilityCallback(callback, |
| CameraDeviceImpl.checkAndWrapHandler(handler), mHasOpenCloseListenerPermission, |
| mContext.getDeviceId(), getDevicePolicyFromContext(mContext)); |
| } |
| |
| /** |
| * Register a callback to be notified about camera device availability. |
| * |
| * <p>The behavior of this method matches that of |
| * {@link #registerAvailabilityCallback(AvailabilityCallback, Handler)}, |
| * except that it uses {@link java.util.concurrent.Executor} as an argument |
| * instead of {@link android.os.Handler}.</p> |
| * |
| * <p>Note: If the order between some availability callbacks matters, the implementation of the |
| * executor should handle those callbacks in the same thread to maintain the callbacks' order. |
| * Some examples are:</p> |
| * |
| * <ul> |
| * |
| * <li>{@link AvailabilityCallback#onCameraAvailable} and |
| * {@link AvailabilityCallback#onCameraUnavailable} of the same camera ID.</li> |
| * |
| * <li>{@link AvailabilityCallback#onCameraAvailable} or |
| * {@link AvailabilityCallback#onCameraUnavailable} of a logical multi-camera, and {@link |
| * AvailabilityCallback#onPhysicalCameraUnavailable} or |
| * {@link AvailabilityCallback#onPhysicalCameraAvailable} of its physical |
| * cameras.</li> |
| * |
| * </ul> |
| * |
| * @param executor The executor which will be used to invoke the callback. |
| * @param callback the new callback to send camera availability notices to |
| * |
| * @throws IllegalArgumentException if the executor is {@code null}. |
| */ |
| public void registerAvailabilityCallback(@NonNull @CallbackExecutor Executor executor, |
| @NonNull AvailabilityCallback callback) { |
| if (executor == null) { |
| throw new IllegalArgumentException("executor was null"); |
| } |
| CameraManagerGlobal.get().registerAvailabilityCallback(callback, executor, |
| mHasOpenCloseListenerPermission, mContext.getDeviceId(), |
| getDevicePolicyFromContext(mContext)); |
| } |
| |
| /** |
| * Remove a previously-added callback; the callback will no longer receive connection and |
| * disconnection callbacks. |
| * |
| * <p>Removing a callback that isn't registered has no effect.</p> |
| * |
| * @param callback The callback to remove from the notification list |
| */ |
| public void unregisterAvailabilityCallback(@NonNull AvailabilityCallback callback) { |
| CameraManagerGlobal.get().unregisterAvailabilityCallback(callback); |
| } |
| |
| /** |
| * Register a callback to be notified about torch mode status. |
| * |
| * <p>Registering the same callback again will replace the handler with the |
| * new one provided.</p> |
| * |
| * <p>The first time a callback is registered, it is immediately called |
| * with the torch mode status of all currently known camera devices with a flash unit.</p> |
| * |
| * <p>Since this callback will be registered with the camera service, remember to unregister it |
| * once it is no longer needed; otherwise the callback will continue to receive events |
| * indefinitely and it may prevent other resources from being released. Specifically, the |
| * callbacks will be invoked independently of the general activity lifecycle and independently |
| * of the state of individual CameraManager instances.</p> |
| * |
| * @param callback The new callback to send torch mode status to |
| * @param handler The handler on which the callback should be invoked, or {@code null} to use |
| * the current thread's {@link android.os.Looper looper}. |
| * |
| * @throws IllegalArgumentException if the handler is {@code null} but the current thread has |
| * no looper. |
| */ |
| public void registerTorchCallback(@NonNull TorchCallback callback, @Nullable Handler handler) { |
| CameraManagerGlobal.get().registerTorchCallback(callback, |
| CameraDeviceImpl.checkAndWrapHandler(handler), mContext.getDeviceId(), |
| getDevicePolicyFromContext(mContext)); |
| } |
| |
| /** |
| * Register a callback to be notified about torch mode status. |
| * |
| * <p>The behavior of this method matches that of |
| * {@link #registerTorchCallback(TorchCallback, Handler)}, |
| * except that it uses {@link java.util.concurrent.Executor} as an argument |
| * instead of {@link android.os.Handler}.</p> |
| * |
| * @param executor The executor which will be used to invoke the callback |
| * @param callback The new callback to send torch mode status to |
| * |
| * @throws IllegalArgumentException if the executor is {@code null}. |
| */ |
| public void registerTorchCallback(@NonNull @CallbackExecutor Executor executor, |
| @NonNull TorchCallback callback) { |
| if (executor == null) { |
| throw new IllegalArgumentException("executor was null"); |
| } |
| CameraManagerGlobal.get().registerTorchCallback(callback, executor, mContext.getDeviceId(), |
| getDevicePolicyFromContext(mContext)); |
| } |
| |
| /** |
| * Remove a previously-added callback; the callback will no longer receive torch mode status |
| * callbacks. |
| * |
| * <p>Removing a callback that isn't registered has no effect.</p> |
| * |
| * @param callback The callback to remove from the notification list |
| */ |
| public void unregisterTorchCallback(@NonNull TorchCallback callback) { |
| CameraManagerGlobal.get().unregisterTorchCallback(callback); |
| } |
| |
| /** @hide */ |
| public int getDevicePolicyFromContext(@NonNull Context context) { |
| if (context.getDeviceId() == DEVICE_ID_DEFAULT |
| || !android.companion.virtual.flags.Flags.virtualCamera()) { |
| return DEVICE_POLICY_DEFAULT; |
| } |
| |
| if (mVirtualDeviceManager == null) { |
| mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class); |
| } |
| return mVirtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA); |
| } |
| |
| // TODO(b/147726300): Investigate how to support foldables/multi-display devices. |
| private Size getDisplaySize() { |
| Size ret = new Size(0, 0); |
| |
| try { |
| DisplayManager displayManager = |
| (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); |
| Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); |
| if (display != null) { |
| Point sz = new Point(); |
| display.getRealSize(sz); |
| int width = sz.x; |
| int height = sz.y; |
| |
| if (height > width) { |
| height = width; |
| width = sz.y; |
| } |
| |
| ret = new Size(width, height); |
| } else { |
| Log.e(TAG, "Invalid default display!"); |
| } |
| } catch (Exception e) { |
| Log.e(TAG, "getDisplaySize Failed. " + e); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * Get all physical cameras' multi-resolution stream configuration map |
| * |
| * <p>For a logical multi-camera, query the map between physical camera id and |
| * the physical camera's multi-resolution stream configuration. This map is in turn |
| * combined to form the logical camera's multi-resolution stream configuration map.</p> |
| * |
| * <p>For an ultra high resolution camera, directly use |
| * android.scaler.physicalCameraMultiResolutionStreamConfigurations as the camera device's |
| * multi-resolution stream configuration map.</p> |
| */ |
| private Map<String, StreamConfiguration[]> getPhysicalCameraMultiResolutionConfigs( |
| String cameraId, CameraMetadataNative info, ICameraService cameraService) |
| throws CameraAccessException { |
| if (mCameraIdToMultiResolutionStreamConfigurationMap.containsKey(cameraId)) { |
| return mCameraIdToMultiResolutionStreamConfigurationMap.get(cameraId); |
| } |
| |
| HashMap<String, StreamConfiguration[]> multiResolutionStreamConfigurations = |
| new HashMap<>(); |
| mCameraIdToMultiResolutionStreamConfigurationMap.put(cameraId, |
| multiResolutionStreamConfigurations); |
| |
| Boolean multiResolutionStreamSupported = info.get( |
| CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_SUPPORTED); |
| if (multiResolutionStreamSupported == null || !multiResolutionStreamSupported) { |
| return multiResolutionStreamConfigurations; |
| } |
| |
| // Query the characteristics of all physical sub-cameras, and combine the multi-resolution |
| // stream configurations. Alternatively, for ultra-high resolution camera, directly use |
| // its multi-resolution stream configurations. Note that framework derived formats such as |
| // HEIC and DEPTH_JPEG aren't supported as multi-resolution input or output formats. |
| Set<String> physicalCameraIds = info.getPhysicalCameraIds(); |
| if (physicalCameraIds.size() == 0 && info.isUltraHighResolutionSensor()) { |
| StreamConfiguration[] configs = info.get(CameraCharacteristics. |
| SCALER_PHYSICAL_CAMERA_MULTI_RESOLUTION_STREAM_CONFIGURATIONS); |
| if (configs != null) { |
| multiResolutionStreamConfigurations.put(cameraId, configs); |
| } |
| return multiResolutionStreamConfigurations; |
| } |
| try { |
| for (String physicalCameraId : physicalCameraIds) { |
| CameraMetadataNative physicalCameraInfo = |
| cameraService.getCameraCharacteristics(physicalCameraId, |
| mContext.getApplicationInfo().targetSdkVersion, |
| /*rotationOverride*/ ICameraService.ROTATION_OVERRIDE_NONE, |
| DEVICE_ID_DEFAULT, |
| DEVICE_POLICY_DEFAULT); |
| StreamConfiguration[] configs = physicalCameraInfo.get( |
| CameraCharacteristics. |
| SCALER_PHYSICAL_CAMERA_MULTI_RESOLUTION_STREAM_CONFIGURATIONS); |
| if (configs != null) { |
| multiResolutionStreamConfigurations.put(physicalCameraId, configs); |
| } |
| } |
| } catch (RemoteException e) { |
| ServiceSpecificException sse = new ServiceSpecificException( |
| ICameraService.ERROR_DISCONNECTED, |
| "Camera service is currently unavailable"); |
| throw ExceptionUtils.throwAsPublicException(sse); |
| } |
| |
| return multiResolutionStreamConfigurations; |
| } |
| |
| /** |
| * <p>Query the capabilities of a camera device. These capabilities are |
| * immutable for a given camera.</p> |
| * |
| * <p>From API level 29, this function can also be used to query the capabilities of physical |
| * cameras that can only be used as part of logical multi-camera. These cameras cannot be |
| * opened directly via {@link #openCamera}</p> |
| * |
| * <p>Also starting with API level 29, while most basic camera information is still available |
| * even without the CAMERA permission, some values are not available to apps that do not hold |
| * that permission. The keys not available are listed by |
| * {@link CameraCharacteristics#getKeysNeedingPermission}.</p> |
| * |
| * @param cameraId The id of the camera device to query. This could be either a standalone |
| * camera ID which can be directly opened by {@link #openCamera}, or a physical camera ID that |
| * can only used as part of a logical multi-camera. |
| * @return The properties of the given camera |
| * |
| * @throws IllegalArgumentException if the cameraId does not match any |
| * known camera device. |
| * @throws CameraAccessException if the camera device has been disconnected. |
| * |
| * @see #getCameraIdList |
| * @see android.app.admin.DevicePolicyManager#setCameraDisabled |
| */ |
| @NonNull |
| public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId) |
| throws CameraAccessException { |
| return getCameraCharacteristics(cameraId, getRotationOverride(mContext)); |
| } |
| |
| /** |
| * <p>Query the capabilities of a camera device. These capabilities are |
| * immutable for a given camera.</p> |
| * |
| * <p>The value of {@link CameraCharacteristics.SENSOR_ORIENTATION} will change for landscape |
| * cameras depending on whether overrideToPortrait is enabled. If enabled, these cameras will |
| * appear to be portrait orientation instead, provided that the override is supported by the |
| * camera device. Only devices that can be opened by {@link #openCamera} will report a changed |
| * {@link CameraCharacteristics.SENSOR_ORIENTATION}.</p> |
| * |
| * @param cameraId The id of the camera device to query. This could be either a standalone |
| * camera ID which can be directly opened by {@link #openCamera}, or a physical camera ID that |
| * can only used as part of a logical multi-camera. |
| * @param overrideToPortrait Whether to apply the landscape to portrait override. |
| * @return The properties of the given camera |
| * |
| * @hide |
| */ |
| @TestApi |
| @NonNull |
| public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId, |
| boolean overrideToPortrait) throws CameraAccessException { |
| return getCameraCharacteristics(cameraId, |
| overrideToPortrait |
| ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT |
| : ICameraService.ROTATION_OVERRIDE_NONE); |
| } |
| |
| @NonNull |
| private CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId, |
| int rotationOverride) throws CameraAccessException { |
| CameraCharacteristics characteristics = null; |
| if (CameraManagerGlobal.sCameraServiceDisabled) { |
| throw new IllegalArgumentException("No cameras available on device"); |
| } |
| synchronized (mLock) { |
| ICameraService cameraService = CameraManagerGlobal.get().getCameraService(); |
| if (cameraService == null) { |
| throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, |
| "Camera service is currently unavailable"); |
| } |
| try { |
| CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId, |
| mContext.getApplicationInfo().targetSdkVersion, rotationOverride, |
| mContext.getDeviceId(), getDevicePolicyFromContext(mContext)); |
| characteristics = prepareCameraCharacteristics(cameraId, info, cameraService); |
| } catch (ServiceSpecificException e) { |
| throw ExceptionUtils.throwAsPublicException(e); |
| } catch (RemoteException e) { |
| // Camera service died - act as if the camera was disconnected |
| throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, |
| "Camera service is currently unavailable", e); |
| } |
| } |
| registerDeviceStateListener(characteristics); |
| return characteristics; |
| } |
| |
| |
| /** |
| * Utility method to take a {@link CameraMetadataNative} object and wrap it into a |
| * {@link CameraCharacteristics} object that has all required fields and keys set and is fit |
| * for apps to consume. |
| * |
| * @param cameraId camera Id that the CameraMetadataNative was fetched for. |
| * @param metadata base CameraMetadataNative to be wrapped |
| * @param cameraService remote cameraservice instance to be used if binder calls need |
| * to be made. |
| * @return A CameraCharacteristics object that can be used by the apps. |
| * @hide |
| */ |
| @NonNull |
| public CameraCharacteristics prepareCameraCharacteristics( |
| @NonNull String cameraId, CameraMetadataNative metadata, ICameraService cameraService) |
| throws CameraAccessException { |
| synchronized (mLock) { |
| try { |
| metadata.setCameraId(Integer.parseInt(cameraId)); |
| } catch (NumberFormatException e) { |
| Log.v(TAG, "Failed to parse camera Id " + cameraId + " to integer"); |
| } |
| |
| boolean hasConcurrentStreams = |
| CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId, |
| mContext.getDeviceId(), getDevicePolicyFromContext(mContext)); |
| metadata.setHasMandatoryConcurrentStreams(hasConcurrentStreams); |
| |
| Size displaySize = getDisplaySize(); |
| metadata.setDisplaySize(displaySize); |
| |
| Map<String, StreamConfiguration[]> multiResolutionSizeMap = |
| getPhysicalCameraMultiResolutionConfigs(cameraId, metadata, cameraService); |
| if (!multiResolutionSizeMap.isEmpty()) { |
| metadata.setMultiResolutionStreamConfigurationMap(multiResolutionSizeMap); |
| } |
| |
| return new CameraCharacteristics(metadata); |
| } |
| } |
| |
| /** |
| * <p>Query the camera extension capabilities of a camera device.</p> |
| * |
| * @param cameraId The id of the camera device to query. This must be a standalone |
| * camera ID which can be directly opened by {@link #openCamera}. |
| * @return The properties of the given camera |
| * |
| * @throws IllegalArgumentException if the cameraId does not match any |
| * known camera device. |
| * @throws CameraAccessException if the camera device has been disconnected. |
| * |
| * @see CameraExtensionCharacteristics |
| * @see CameraDevice#createExtensionSession(ExtensionSessionConfiguration) |
| * @see CameraExtensionSession |
| */ |
| @NonNull |
| public CameraExtensionCharacteristics getCameraExtensionCharacteristics( |
| @NonNull String cameraId) throws CameraAccessException { |
| CameraCharacteristics chars = getCameraCharacteristics(cameraId); |
| Map<String, CameraCharacteristics> characteristicsMap = getPhysicalIdToCharsMap(chars); |
| characteristicsMap.put(cameraId, chars); |
| |
| return new CameraExtensionCharacteristics(mContext, cameraId, characteristicsMap); |
| } |
| |
| /** |
| * @hide |
| */ |
| public Map<String, CameraCharacteristics> getPhysicalIdToCharsMap( |
| CameraCharacteristics chars) throws CameraAccessException { |
| HashMap<String, CameraCharacteristics> physicalIdsToChars = |
| new HashMap<String, CameraCharacteristics>(); |
| Set<String> physicalCameraIds = chars.getPhysicalCameraIds(); |
| for (String physicalCameraId : physicalCameraIds) { |
| CameraCharacteristics physicalChars = getCameraCharacteristics(physicalCameraId); |
| physicalIdsToChars.put(physicalCameraId, physicalChars); |
| } |
| return physicalIdsToChars; |
| } |
| |
| /** |
| * Returns a {@link CameraDevice.CameraDeviceSetup} object for the given {@code cameraId}, |
| * which provides limited access to CameraDevice setup and query functionality without |
| * requiring an {@link #openCamera} call. The {@link CameraDevice} can later be obtained either |
| * by calling {@link #openCamera}, or {@link CameraDevice.CameraDeviceSetup#openCamera}. |
| * |
| * <p>Support for {@link CameraDevice.CameraDeviceSetup} for a given {@code cameraId} must be |
| * checked with {@link #isCameraDeviceSetupSupported}. If {@code isCameraDeviceSetupSupported} |
| * returns {@code false} for a {@code cameraId}, this method will throw an |
| * {@link UnsupportedOperationException}</p> |
| * |
| * @param cameraId The unique identifier of the camera device for which |
| * {@link CameraDevice.CameraDeviceSetup} object must be constructed. This |
| * identifier must be present in {@link #getCameraIdList()} |
| * |
| * @return {@link CameraDevice.CameraDeviceSetup} object corresponding to the provided |
| * {@code cameraId} |
| * |
| * @throws IllegalArgumentException If {@code cameraId} is null, or if {@code cameraId} does not |
| * match any device in {@link #getCameraIdList()}. |
| * @throws CameraAccessException if the camera device is not accessible |
| * @throws UnsupportedOperationException if {@link CameraDevice.CameraDeviceSetup} instance |
| * cannot be constructed for the given {@code cameraId}, i.e. |
| * {@link #isCameraDeviceSetupSupported} returns false. |
| * |
| * @see CameraDevice.CameraDeviceSetup |
| * @see #getCameraIdList() |
| * @see #openCamera |
| */ |
| @NonNull |
| @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) |
| public CameraDevice.CameraDeviceSetup getCameraDeviceSetup(@NonNull String cameraId) |
| throws CameraAccessException { |
| // isCameraDeviceSetup does all the error checking we need. |
| if (!isCameraDeviceSetupSupported(cameraId)) { |
| throw new UnsupportedOperationException( |
| "CameraDeviceSetup is not supported for Camera ID: " + cameraId); |
| } |
| |
| return getCameraDeviceSetupUnsafe(cameraId); |
| } |
| |
| /** |
| * Creates and returns a {@link CameraDeviceSetup} instance without any error checking. To |
| * be used (carefully) by callers who are sure that CameraDeviceSetup instance can be legally |
| * created and don't want to pay the latency cost of calling {@link #getCameraDeviceSetup}. |
| */ |
| private CameraDevice.CameraDeviceSetup getCameraDeviceSetupUnsafe(@NonNull String cameraId) { |
| return new CameraDeviceSetupImpl(cameraId, /*cameraManager=*/ this, mContext); |
| } |
| |
| /** |
| * Checks a Camera Device's characteristics to ensure that a |
| * {@link CameraDevice.CameraDeviceSetup} instance can be constructed for a given |
| * {@code cameraId}. If this method returns false for a {@code cameraId}, calling |
| * {@link #getCameraDeviceSetup} for that {@code cameraId} will throw an |
| * {@link UnsupportedOperationException}. |
| * |
| * <p>{@link CameraDevice.CameraDeviceSetup} is supported for all devices that report |
| * {@link CameraCharacteristics#INFO_SESSION_CONFIGURATION_QUERY_VERSION} > |
| * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}</p> |
| * |
| * @param cameraId The unique identifier of the camera device for which |
| * {@link CameraDevice.CameraDeviceSetup} support is being queried. This |
| * identifier must be present in {@link #getCameraIdList()}. |
| * |
| * @return {@code true} if {@link CameraDevice.CameraDeviceSetup} object can be constructed |
| * for the provided {@code cameraId}; {@code false} otherwise. |
| * |
| * @throws IllegalArgumentException If {@code cameraId} is null, or if {@code cameraId} does not |
| * match any device in {@link #getCameraIdList()}. |
| * @throws CameraAccessException if the camera device is not accessible |
| * |
| * @see CameraCharacteristics#INFO_SESSION_CONFIGURATION_QUERY_VERSION |
| * @see CameraDevice.CameraDeviceSetup |
| * @see #getCameraDeviceSetup(String) |
| * @see #getCameraIdList() |
| */ |
| @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) |
| public boolean isCameraDeviceSetupSupported(@NonNull String cameraId) |
| throws CameraAccessException { |
| if (cameraId == null) { |
| throw new IllegalArgumentException("Camera ID was null"); |
| } |
| |
| if (CameraManagerGlobal.sCameraServiceDisabled |
| || !Arrays.asList(CameraManagerGlobal.get().getCameraIdList(mContext.getDeviceId(), |
| getDevicePolicyFromContext(mContext))).contains(cameraId)) { |
| throw new IllegalArgumentException( |
| "Camera ID '" + cameraId + "' not available on device."); |
| } |
| |
| CameraCharacteristics chars = getCameraCharacteristics(cameraId); |
| return CameraDeviceSetupImpl.isCameraDeviceSetupSupported(chars); |
| } |
| |
| /** |
| * Helper for opening a connection to a camera with the given ID. |
| * |
| * @param cameraId The unique identifier of the camera device to open |
| * @param callback The callback for the camera. Must not be null. |
| * @param executor The executor to invoke the callback with. Must not be null. |
| * @param uid The UID of the application actually opening the camera. |
| * Must be USE_CALLING_UID unless the caller is a service |
| * that is trusted to open the device on behalf of an |
| * application and to forward the real UID. |
| * |
| * @throws CameraAccessException if the camera is disabled by device policy, |
| * too many camera devices are already open, or the cameraId does not match |
| * any currently available camera device. |
| * |
| * @throws SecurityException if the application does not have permission to |
| * access the camera |
| * @throws IllegalArgumentException if callback or handler is null. |
| * @return A handle to the newly-created camera device. |
| * |
| * @see #getCameraIdList |
| * @see android.app.admin.DevicePolicyManager#setCameraDisabled |
| */ |
| private CameraDevice openCameraDeviceUserAsync(String cameraId, |
| CameraDevice.StateCallback callback, Executor executor, final int uid, |
| final int oomScoreOffset, int rotationOverride) throws CameraAccessException { |
| CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); |
| CameraDevice device = null; |
| synchronized (mLock) { |
| ICameraDeviceUser cameraUser = null; |
| CameraDevice.CameraDeviceSetup cameraDeviceSetup = null; |
| if (Flags.cameraDeviceSetup() |
| && CameraDeviceSetupImpl.isCameraDeviceSetupSupported(characteristics)) { |
| cameraDeviceSetup = getCameraDeviceSetupUnsafe(cameraId); |
| } |
| |
| android.hardware.camera2.impl.CameraDeviceImpl deviceImpl = |
| new CameraDeviceImpl( |
| cameraId, |
| callback, |
| executor, |
| characteristics, |
| this, |
| mContext.getApplicationInfo().targetSdkVersion, |
| mContext, cameraDeviceSetup); |
| ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks(); |
| |
| try { |
| ICameraService cameraService = CameraManagerGlobal.get().getCameraService(); |
| if (cameraService == null) { |
| throw new ServiceSpecificException( |
| ICameraService.ERROR_DISCONNECTED, |
| "Camera service is currently unavailable"); |
| } |
| |
| cameraUser = cameraService.connectDevice(callbacks, cameraId, |
| mContext.getOpPackageName(), mContext.getAttributionTag(), uid, |
| oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion, |
| rotationOverride, mContext.getDeviceId(), |
| getDevicePolicyFromContext(mContext)); |
| } catch (ServiceSpecificException e) { |
| if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) { |
| throw new AssertionError("Should've gone down the shim path"); |
| } else if (e.errorCode == ICameraService.ERROR_CAMERA_IN_USE || |
| e.errorCode == ICameraService.ERROR_MAX_CAMERAS_IN_USE || |
| e.errorCode == ICameraService.ERROR_DISABLED || |
| e.errorCode == ICameraService.ERROR_DISCONNECTED || |
| e.errorCode == ICameraService.ERROR_INVALID_OPERATION) { |
| // Received one of the known connection errors |
| // The remote camera device cannot be connected to, so |
| // set the local camera to the startup error state |
| deviceImpl.setRemoteFailure(e); |
| |
| if (e.errorCode == ICameraService.ERROR_DISABLED || |
| e.errorCode == ICameraService.ERROR_DISCONNECTED || |
| e.errorCode == ICameraService.ERROR_CAMERA_IN_USE) { |
| // Per API docs, these failures call onError and throw |
| throw ExceptionUtils.throwAsPublicException(e); |
| } |
| } else { |
| // Unexpected failure - rethrow |
| throw ExceptionUtils.throwAsPublicException(e); |
| } |
| } catch (RemoteException e) { |
| // Camera service died - act as if it's a CAMERA_DISCONNECTED case |
| ServiceSpecificException sse = new ServiceSpecificException( |
| ICameraService.ERROR_DISCONNECTED, |
| "Camera service is currently unavailable"); |
| deviceImpl.setRemoteFailure(sse); |
| throw ExceptionUtils.throwAsPublicException(sse); |
| } |
| |
| // TODO: factor out callback to be non-nested, then move setter to constructor |
| // For now, calling setRemoteDevice will fire initial |
| // onOpened/onUnconfigured callbacks. |
| // This function call may post onDisconnected and throw CAMERA_DISCONNECTED if |
| // cameraUser dies during setup. |
| deviceImpl.setRemoteDevice(cameraUser); |
| device = deviceImpl; |
| } |
| |
| return device; |
| } |
| |
| /** |
| * Open a connection to a camera with the given ID. |
| * |
| * <p>Use {@link #getCameraIdList} to get the list of available camera |
| * devices. Note that even if an id is listed, open may fail if the device |
| * is disconnected between the calls to {@link #getCameraIdList} and |
| * {@link #openCamera}, or if a higher-priority camera API client begins using the |
| * camera device.</p> |
| * |
| * <p>As of API level 23, devices for which the |
| * {@link AvailabilityCallback#onCameraUnavailable(String)} callback has been called due to the |
| * device being in use by a lower-priority, background camera API client can still potentially |
| * be opened by calling this method when the calling camera API client has a higher priority |
| * than the current camera API client using this device. In general, if the top, foreground |
| * activity is running within your application process, your process will be given the highest |
| * priority when accessing the camera, and this method will succeed even if the camera device is |
| * in use by another camera API client. Any lower-priority application that loses control of the |
| * camera in this way will receive an |
| * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback. |
| * Opening the same camera ID twice in the same application will similarly cause the |
| * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback |
| * being fired for the {@link CameraDevice} from the first open call and all ongoing tasks |
| * being dropped.</p> |
| * |
| * <p>Once the camera is successfully opened, {@link CameraDevice.StateCallback#onOpened} will |
| * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up |
| * for operation by calling {@link CameraDevice#createCaptureSession} and |
| * {@link CameraDevice#createCaptureRequest}</p> |
| * |
| * <p>Before API level 30, when the application tries to open multiple {@link CameraDevice} of |
| * different IDs and the device does not support opening such combination, either the |
| * {@link #openCamera} will fail and throw a {@link CameraAccessException} or one or more of |
| * already opened {@link CameraDevice} will be disconnected and receive |
| * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback. Which |
| * behavior will happen depends on the device implementation and can vary on different devices. |
| * Starting in API level 30, if the device does not support the combination of cameras being |
| * opened, it is guaranteed the {@link #openCamera} call will fail and none of existing |
| * {@link CameraDevice} will be disconnected.</p> |
| * |
| * <!-- |
| * <p>Since the camera device will be opened asynchronously, any asynchronous operations done |
| * on the returned CameraDevice instance will be queued up until the device startup has |
| * completed and the callback's {@link CameraDevice.StateCallback#onOpened onOpened} method is |
| * called. The pending operations are then processed in order.</p> |
| * --> |
| * <p>If the camera becomes disconnected during initialization |
| * after this function call returns, |
| * {@link CameraDevice.StateCallback#onDisconnected} with a |
| * {@link CameraDevice} in the disconnected state (and |
| * {@link CameraDevice.StateCallback#onOpened} will be skipped).</p> |
| * |
| * <p>If opening the camera device fails, then the device callback's |
| * {@link CameraDevice.StateCallback#onError onError} method will be called, and subsequent |
| * calls on the camera device will throw a {@link CameraAccessException}.</p> |
| * |
| * @param cameraId |
| * The unique identifier of the camera device to open |
| * @param callback |
| * The callback which is invoked once the camera is opened |
| * @param handler |
| * The handler on which the callback should be invoked, or |
| * {@code null} to use the current thread's {@link android.os.Looper looper}. |
| * |
| * @throws CameraAccessException if the camera is disabled by device policy, |
| * has been disconnected, is being used by a higher-priority camera API client, or the device |
| * has reached its maximal resource and cannot open this camera device. |
| * |
| * @throws IllegalArgumentException if cameraId or the callback was null, |
| * or the cameraId does not match any currently or previously available |
| * camera device returned by {@link #getCameraIdList}. |
| * |
| * @throws SecurityException if the application does not have permission to |
| * access the camera |
| * |
| * @see #getCameraIdList |
| * @see android.app.admin.DevicePolicyManager#setCameraDisabled |
| */ |
| @RequiresPermission(android.Manifest.permission.CAMERA) |
| public void openCamera(@NonNull String cameraId, |
| @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler) |
| throws CameraAccessException { |
| openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler), |
| USE_CALLING_UID); |
| } |
| |
| /** |
| * Open a connection to a camera with the given ID. Also specify overrideToPortrait for testing. |
| * |
| * @param cameraId |
| * The unique identifier of the camera device to open |
| * @param handler |
| * The handler on which the callback should be invoked, or |
| * {@code null} to use the current thread's {@link android.os.Looper looper}. |
| * @param callback |
| * The callback which is invoked once the camera is opened |
| * @param overrideToPortrait |
| * Whether to apply the landscape to portrait override, using rotate and crop. |
| * |
| * @throws CameraAccessException if the camera is disabled by device policy, |
| * has been disconnected, or is being used by a higher-priority camera API client. |
| * |
| * @throws IllegalArgumentException if cameraId, the callback or the executor was null, |
| * or the cameraId does not match any currently or previously available |
| * camera device. |
| * |
| * @throws SecurityException if the application does not have permission to |
| * access the camera |
| * |
| * @see #getCameraIdList |
| * @see android.app.admin.DevicePolicyManager#setCameraDisabled |
| * |
| * @hide |
| */ |
| @TestApi |
| @RequiresPermission(android.Manifest.permission.CAMERA) |
| public void openCamera(@NonNull String cameraId, boolean overrideToPortrait, |
| @Nullable Handler handler, |
| @NonNull final CameraDevice.StateCallback callback) throws CameraAccessException { |
| openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler), |
| USE_CALLING_UID, /*oomScoreOffset*/0, |
| overrideToPortrait |
| ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT |
| : ICameraService.ROTATION_OVERRIDE_NONE); |
| } |
| |
| /** |
| * Open a connection to a camera with the given ID. |
| * |
| * <p>The behavior of this method matches that of |
| * {@link #openCamera(String, StateCallback, Handler)}, except that it uses |
| * {@link java.util.concurrent.Executor} as an argument instead of |
| * {@link android.os.Handler}.</p> |
| * |
| * <p>Do note that typically callbacks are expected to be dispatched |
| * by the executor in a single thread. If the executor uses two or |
| * more threads to dispatch callbacks, then clients must ensure correct |
| * synchronization and must also be able to handle potentially different |
| * ordering of the incoming callbacks.</p> |
| * |
| * @param cameraId |
| * The unique identifier of the camera device to open |
| * @param executor |
| * The executor which will be used when invoking the callback. |
| * @param callback |
| * The callback which is invoked once the camera is opened |
| * |
| * @throws CameraAccessException if the camera is disabled by device policy, |
| * has been disconnected, or is being used by a higher-priority camera API client. |
| * |
| * @throws IllegalArgumentException if cameraId, the callback or the executor was null, |
| * or the cameraId does not match any currently or previously available |
| * camera device. |
| * |
| * @throws SecurityException if the application does not have permission to |
| * access the camera |
| * |
| * @see #getCameraIdList |
| * @see android.app.admin.DevicePolicyManager#setCameraDisabled |
| */ |
| @RequiresPermission(android.Manifest.permission.CAMERA) |
| public void openCamera(@NonNull String cameraId, |
| @NonNull @CallbackExecutor Executor executor, |
| @NonNull final CameraDevice.StateCallback callback) |
| throws CameraAccessException { |
| if (executor == null) { |
| throw new IllegalArgumentException("executor was null"); |
| } |
| openCameraForUid(cameraId, callback, executor, USE_CALLING_UID); |
| } |
| |
| /** |
| * Open a connection to a camera with the given ID. Also specify what oom score must be offset |
| * by cameraserver for this client. This api can be useful for system |
| * components which want to assume a lower priority (for camera arbitration) than other clients |
| * which it might contend for camera devices with. Increasing the oom score of a client reduces |
| * its priority when the camera framework manages camera arbitration. |
| * Considering typical use cases: |
| * |
| * 1) oom score(apps hosting activities visible to the user) - oom score(of a foreground app) |
| * is approximately 100. |
| * |
| * 2) The oom score (process which hosts components which that are perceptible to the user / |
| * native vendor camera clients) - oom (foreground app) is approximately 200. |
| * |
| * 3) The oom score (process which is cached hosting activities not visible) - oom (foreground |
| * app) is approximately 999. |
| * |
| * <p>The behavior of this method matches that of |
| * {@link #openCamera(String, StateCallback, Handler)}, except that it uses |
| * {@link java.util.concurrent.Executor} as an argument instead of |
| * {@link android.os.Handler}.</p> |
| * |
| * @param cameraId |
| * The unique identifier of the camera device to open |
| * @param executor |
| * The executor which will be used when invoking the callback. |
| * @param callback |
| * The callback which is invoked once the camera is opened |
| * @param oomScoreOffset |
| * The value by which the oom score of this client must be offset by the camera |
| * framework in order to assist it with camera arbitration. This value must be > 0. |
| * A positive value lowers the priority of this camera client compared to what the |
| * camera framework would have originally seen. |
| * |
| * @throws CameraAccessException if the camera is disabled by device policy, |
| * has been disconnected, or is being used by a higher-priority camera API client. |
| * |
| * @throws IllegalArgumentException if cameraId, the callback or the executor was null, |
| * or the cameraId does not match any currently or previously available |
| * camera device. |
| * |
| * @throws SecurityException if the application does not have permission to |
| * access the camera |
| * |
| * @see #getCameraIdList |
| * @see android.app.admin.DevicePolicyManager#setCameraDisabled |
| * |
| * @hide |
| */ |
| @SystemApi |
| @TestApi |
| @RequiresPermission(allOf = { |
| android.Manifest.permission.SYSTEM_CAMERA, |
| android.Manifest.permission.CAMERA, |
| }) |
| public void openCamera(@NonNull String cameraId, int oomScoreOffset, |
| @NonNull @CallbackExecutor Executor executor, |
| @NonNull final CameraDevice.StateCallback callback) throws CameraAccessException { |
| if (executor == null) { |
| throw new IllegalArgumentException("executor was null"); |
| } |
| if (oomScoreOffset < 0) { |
| throw new IllegalArgumentException( |
| "oomScoreOffset < 0, cannot increase priority of camera client"); |
| } |
| openCameraForUid(cameraId, callback, executor, USE_CALLING_UID, oomScoreOffset, |
| getRotationOverride(mContext)); |
| } |
| |
| /** |
| * Open a connection to a camera with the given ID, on behalf of another application |
| * specified by clientUid. Also specify the minimum oom score and process state the application |
| * should have, as seen by the cameraserver. |
| * |
| * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows |
| * the caller to specify the UID to use for permission/etc verification. This can only be |
| * done by services trusted by the camera subsystem to act on behalf of applications and |
| * to forward the real UID.</p> |
| * |
| * @param clientUid |
| * The UID of the application on whose behalf the camera is being opened. |
| * Must be USE_CALLING_UID unless the caller is a trusted service. |
| * @param oomScoreOffset |
| * The minimum oom score that cameraservice must see for this client. |
| * @param rotationOverride |
| * The type of rotation override (none, override_to_portrait, rotation_only) |
| * that should be followed for this camera id connection |
| * @hide |
| */ |
| public void openCameraForUid(@NonNull String cameraId, |
| @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor, |
| int clientUid, int oomScoreOffset, int rotationOverride) |
| throws CameraAccessException { |
| |
| if (cameraId == null) { |
| throw new IllegalArgumentException("cameraId was null"); |
| } else if (callback == null) { |
| throw new IllegalArgumentException("callback was null"); |
| } |
| if (CameraManagerGlobal.sCameraServiceDisabled) { |
| throw new IllegalArgumentException("No cameras available on device"); |
| } |
| |
| openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset, |
| rotationOverride); |
| } |
| |
| /** |
| * Open a connection to a camera with the given ID, on behalf of another application |
| * specified by clientUid. |
| * |
| * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows |
| * the caller to specify the UID to use for permission/etc verification. This can only be |
| * done by services trusted by the camera subsystem to act on behalf of applications and |
| * to forward the real UID.</p> |
| * |
| * @param clientUid |
| * The UID of the application on whose behalf the camera is being opened. |
| * Must be USE_CALLING_UID unless the caller is a trusted service. |
| * |
| * @hide |
| */ |
| public void openCameraForUid(@NonNull String cameraId, |
| @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor, |
| int clientUid) throws CameraAccessException { |
| openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0, |
| getRotationOverride(mContext)); |
| } |
| |
| /** |
| * Set the flash unit's torch mode of the camera of the given ID without opening the camera |
| * device. |
| * |
| * <p>Use {@link #getCameraIdList} to get the list of available camera devices and use |
| * {@link #getCameraCharacteristics} to check whether the camera device has a flash unit. |
| * Note that even if a camera device has a flash unit, turning on the torch mode may fail |
| * if the camera device or other camera resources needed to turn on the torch mode are in use. |
| * </p> |
| * |
| * <p> If {@link #setTorchMode} is called to turn on or off the torch mode successfully, |
| * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked. |
| * However, even if turning on the torch mode is successful, the application does not have the |
| * exclusive ownership of the flash unit or the camera device. The torch mode will be turned |
| * off and becomes unavailable when the camera device that the flash unit belongs to becomes |
| * unavailable or when other camera resources to keep the torch on become unavailable ( |
| * {@link CameraManager.TorchCallback#onTorchModeUnavailable} will be invoked). Also, |
| * other applications are free to call {@link #setTorchMode} to turn off the torch mode ( |
| * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked). If the latest |
| * application that turned on the torch mode exits, the torch mode will be turned off. |
| * |
| * @param cameraId |
| * The unique identifier of the camera device that the flash unit belongs to. |
| * @param enabled |
| * The desired state of the torch mode for the target camera device. Set to |
| * {@code true} to turn on the torch mode. Set to {@code false} to turn off the |
| * torch mode. |
| * |
| * @throws CameraAccessException if it failed to access the flash unit. |
| * {@link CameraAccessException#CAMERA_IN_USE} will be thrown if the camera device |
| * is in use. {@link CameraAccessException#MAX_CAMERAS_IN_USE} will be thrown if |
| * other camera resources needed to turn on the torch mode are in use. |
| * {@link CameraAccessException#CAMERA_DISCONNECTED} will be thrown if camera |
| * service is not available. |
| * |
| * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently |
| * or previously available camera device, or the camera device doesn't have a |
| * flash unit. |
| */ |
| public void setTorchMode(@NonNull String cameraId, boolean enabled) |
| throws CameraAccessException { |
| if (CameraManagerGlobal.sCameraServiceDisabled) { |
| throw new IllegalArgumentException("No cameras available on device"); |
| } |
| CameraManagerGlobal.get().setTorchMode(cameraId, enabled, mContext.getDeviceId(), |
| getDevicePolicyFromContext(mContext)); |
| } |
| |
| /** |
| * Set the brightness level of the flashlight associated with the given cameraId in torch |
| * mode. If the torch is OFF and torchStrength is >= 1, torch will turn ON with the |
| * strength level specified in torchStrength. |
| * |
| * <p>Use |
| * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL} |
| * to check whether the camera device supports flash unit strength control or not. If this value |
| * is greater than 1, applications can call this API to control the flashlight brightness level. |
| * </p> |
| * |
| * <p>If {@link #turnOnTorchWithStrengthLevel} is called to change the brightness level of the |
| * flash unit {@link CameraManager.TorchCallback#onTorchStrengthLevelChanged} will be invoked. |
| * If the new desired strength level is same as previously set level, then this callback will |
| * not be invoked. |
| * If the torch is OFF and {@link #turnOnTorchWithStrengthLevel} is called with level >= 1, |
| * the torch will be turned ON with that brightness level. In this case |
| * {@link CameraManager.TorchCallback#onTorchModeChanged} will also be invoked. |
| * </p> |
| * |
| * <p>When the torch is turned OFF via {@link #setTorchMode}, the flashlight brightness level |
| * will reset to default value |
| * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL} |
| * In this case the {@link CameraManager.TorchCallback#onTorchStrengthLevelChanged} will not be |
| * invoked. |
| * </p> |
| * |
| * <p>If torch is enabled via {@link #setTorchMode} after calling |
| * {@link #turnOnTorchWithStrengthLevel} with level N then the flash unit will have the |
| * brightness level N. |
| * Since multiple applications are free to call {@link #setTorchMode}, when the latest |
| * application that turned ON the torch mode exits, the torch mode will be turned OFF |
| * and in this case the brightness level will reset to default level. |
| * </p> |
| * |
| * @param cameraId |
| * The unique identifier of the camera device that the flash unit belongs to. |
| * @param torchStrength |
| * The desired brightness level to be set for the flash unit in the range 1 to |
| * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL}. |
| * |
| * @throws CameraAccessException if it failed to access the flash unit. |
| * {@link CameraAccessException#CAMERA_IN_USE} will be thrown if the camera device |
| * is in use. {@link CameraAccessException#MAX_CAMERAS_IN_USE} will be thrown if |
| * other camera resources needed to turn on the torch mode are in use. |
| * {@link CameraAccessException#CAMERA_DISCONNECTED} will be thrown if camera |
| * service is not available. |
| * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently |
| * or previously available camera device, the camera device doesn't have a |
| * flash unit or if torchStrength is not within the range i.e. is greater than |
| * the maximum level |
| * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL} |
| * or <= 0. |
| * |
| */ |
| public void turnOnTorchWithStrengthLevel(@NonNull String cameraId, int torchStrength) |
| throws CameraAccessException { |
| if (CameraManagerGlobal.sCameraServiceDisabled) { |
| throw new IllegalArgumentException("No camera available on device"); |
| } |
| CameraManagerGlobal.get().turnOnTorchWithStrengthLevel(cameraId, torchStrength, |
| mContext.getDeviceId(), getDevicePolicyFromContext(mContext)); |
| } |
| |
| /** |
| * Returns the brightness level of the flash unit associated with the cameraId. |
| * |
| * @param cameraId |
| * The unique identifier of the camera device that the flash unit belongs to. |
| * @return The brightness level of the flash unit associated with cameraId. |
| * When the torch is turned OFF, the strength level will reset to a default level |
| * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL}. |
| * In this case the return value will be |
| * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL} |
| * rather than 0. |
| * |
| * @throws CameraAccessException if it failed to access the flash unit. |
| * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently |
| * or previously available camera device, or the camera device doesn't have a |
| * flash unit. |
| * |
| */ |
| public int getTorchStrengthLevel(@NonNull String cameraId) |
| throws CameraAccessException { |
| if (CameraManagerGlobal.sCameraServiceDisabled) { |
| throw new IllegalArgumentException("No camera available on device."); |
| } |
| return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId, mContext.getDeviceId(), |
| getDevicePolicyFromContext(mContext)); |
| } |
| |
| /** |
| * @hide |
| */ |
| public static int getRotationOverride(@Nullable Context context) { |
| PackageManager packageManager = null; |
| String packageName = null; |
| |
| if (context != null) { |
| packageManager = context.getPackageManager(); |
| packageName = context.getOpPackageName(); |
| } |
| |
| return getRotationOverride(context, packageManager, packageName); |
| } |
| |
| /** |
| * @hide |
| */ |
| public static int getRotationOverride(@Nullable Context context, |
| @Nullable PackageManager packageManager, @Nullable String packageName) { |
| if (com.android.window.flags.Flags.cameraCompatForFreeform()) { |
| return getRotationOverrideInternal(context, packageManager, packageName); |
| } else { |
| return shouldOverrideToPortrait(packageManager, packageName) |
| ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT |
| : ICameraService.ROTATION_OVERRIDE_NONE; |
| } |
| } |
| |
| /** |
| * @hide |
| */ |
| @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM) |
| @TestApi |
| public static int getRotationOverrideInternal(@Nullable Context context, |
| @Nullable PackageManager packageManager, @Nullable String packageName) { |
| if (!CameraManagerGlobal.sLandscapeToPortrait) { |
| return ICameraService.ROTATION_OVERRIDE_NONE; |
| } |
| |
| if (context != null) { |
| final ActivityManager activityManager = |
| context.getSystemService(ActivityManager.class); |
| for (ActivityManager.AppTask appTask : activityManager.getAppTasks()) { |
| final TaskInfo taskInfo = appTask.getTaskInfo(); |
| if (taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode |
| != 0 |
| && taskInfo.topActivity != null |
| && taskInfo.topActivity.getPackageName().equals(packageName)) { |
| // WindowManager has requested rotation override. |
| return ICameraService.ROTATION_OVERRIDE_ROTATION_ONLY; |
| } |
| } |
| } |
| |
| if (packageManager != null && packageName != null) { |
| try { |
| return packageManager.getProperty( |
| PackageManager.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT, |
| packageName).getBoolean() |
| ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT |
| : ICameraService.ROTATION_OVERRIDE_NONE; |
| } catch (PackageManager.NameNotFoundException e) { |
| // No such property |
| } |
| } |
| |
| return CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT) |
| ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT |
| : ICameraService.ROTATION_OVERRIDE_NONE; |
| } |
| |
| /** |
| * @hide |
| */ |
| @TestApi |
| public static boolean shouldOverrideToPortrait(@Nullable PackageManager packageManager, |
| @Nullable String packageName) { |
| if (!CameraManagerGlobal.sLandscapeToPortrait) { |
| return false; |
| } |
| |
| if (packageManager != null && packageName != null) { |
| try { |
| return packageManager.getProperty( |
| PackageManager.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT, |
| packageName).getBoolean(); |
| } catch (PackageManager.NameNotFoundException e) { |
| // No such property |
| } |
| } |
| |
| return CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT); |
| } |
| |
| |
| /** |
| * @hide |
| */ |
| public static boolean physicalCallbacksAreEnabledForUnavailableCamera() { |
| return CompatChanges.isChangeEnabled( |
| ENABLE_PHYSICAL_CAMERA_CALLBACK_FOR_UNAVAILABLE_LOGICAL_CAMERA); |
| } |
| |
| /** |
| * A callback for camera devices becoming available or unavailable to open. |
| * |
| * <p>Cameras become available when they are no longer in use, or when a new |
| * removable camera is connected. They become unavailable when some |
| * application or service starts using a camera, or when a removable camera |
| * is disconnected.</p> |
| * |
| * <p>Extend this callback and pass an instance of the subclass to |
| * {@link CameraManager#registerAvailabilityCallback} to be notified of such availability |
| * changes.</p> |
| * |
| * @see #registerAvailabilityCallback |
| */ |
| public static abstract class AvailabilityCallback { |
| |
| private int mDeviceId; |
| private int mDevicePolicy; |
| |
| /** |
| * A new camera has become available to use. |
| * |
| * <p>The default implementation of this method does nothing.</p> |
| * |
| * @param cameraId The unique identifier of the new camera. |
| */ |
| public void onCameraAvailable(@NonNull String cameraId) { |
| // default empty implementation |
| } |
| |
| /** |
| * A previously-available camera has become unavailable for use. |
| * |
| * <p>If an application had an active CameraDevice instance for the |
| * now-disconnected camera, that application will receive a |
| * {@link CameraDevice.StateCallback#onDisconnected disconnection error}.</p> |
| * |
| * <p>The default implementation of this method does nothing.</p> |
| * |
| * @param cameraId The unique identifier of the disconnected camera. |
| */ |
| public void onCameraUnavailable(@NonNull String cameraId) { |
| // default empty implementation |
| } |
| |
| /** |
| * Called whenever camera access priorities change. |
| * |
| * <p>Notification that camera access priorities have changed and the camera may |
| * now be openable. An application that was previously denied camera access due to |
| * a higher-priority user already using the camera, or that was disconnected from an |
| * active camera session due to a higher-priority user trying to open the camera, |
| * should try to open the camera again if it still wants to use it. Note that |
| * multiple applications may receive this callback at the same time, and only one of |
| * them will succeed in opening the camera in practice, depending on exact access |
| * priority levels and timing. This method is useful in cases where multiple |
| * applications may be in the resumed state at the same time, and the user switches |
| * focus between them, or if the current camera-using application moves between |
| * full-screen and Picture-in-Picture (PiP) states. In such cases, the camera |
| * available/unavailable callbacks will not be invoked, but another application may |
| * now have higher priority for camera access than the current camera-using |
| * application.</p> |
| * |
| * <p>The default implementation of this method does nothing.</p> |
| * |
| */ |
| public void onCameraAccessPrioritiesChanged() { |
| // default empty implementation |
| } |
| |
| /** |
| * A physical camera has become available for use again. |
| * |
| * <p>By default, all of the physical cameras of a logical multi-camera are |
| * available, so {@link #onPhysicalCameraAvailable} is not called for any of the physical |
| * cameras of a logical multi-camera, when {@link #onCameraAvailable} for the logical |
| * multi-camera is invoked. However, if some specific physical cameras are unavailable |
| * to begin with, {@link #onPhysicalCameraUnavailable} may be invoked after |
| * {@link #onCameraAvailable}.</p> |
| * |
| * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion} |
| * < {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, opening a logical camera |
| * disables the {@link #onPhysicalCameraAvailable} and {@link #onPhysicalCameraUnavailable} |
| * callbacks for its physical cameras. For example, if app A opens the camera device:</p> |
| * |
| * <ul> |
| * |
| * <li>All apps subscribing to ActivityCallback get {@link #onCameraUnavailable}.</li> |
| * |
| * <li>No app (including app A) subscribing to ActivityCallback gets |
| * {@link #onPhysicalCameraAvailable} or {@link #onPhysicalCameraUnavailable}, because |
| * the logical camera is unavailable (some app is using it).</li> |
| * |
| * </ul> |
| * |
| * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion} |
| * ≥ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}:</p> |
| * |
| * <ul> |
| * |
| * <li>A physical camera status change will trigger {@link #onPhysicalCameraAvailable} |
| * or {@link #onPhysicalCameraUnavailable} even after the logical camera becomes |
| * unavailable. A {@link #onCameraUnavailable} call for a logical camera doesn't reset the |
| * physical cameras' availability status. This makes it possible for an application opening |
| * the logical camera device to know which physical camera becomes unavailable or available |
| * to use.</li> |
| * |
| * <li>Similar to {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13} and earlier, |
| * the logical camera's {@link #onCameraAvailable} callback implies all of its physical |
| * cameras' status become available. {@link #onPhysicalCameraUnavailable} will be called |
| * for any unavailable physical cameras upon the logical camera becoming available.</li> |
| * |
| * </ul> |
| * |
| * <p>Given the pipeline nature of the camera capture through {@link |
| * android.hardware.camera2.CaptureRequest}, there may be frame drops if the application |
| * requests images from a physical camera of a logical multi-camera and that physical camera |
| * becomes unavailable. The application should stop requesting directly from an unavailable |
| * physical camera as soon as {@link #onPhysicalCameraUnavailable} is received, and also be |
| * ready to robustly handle frame drop errors for requests targeting physical cameras, |
| * since those errors may arrive before the unavailability callback.</p> |
| * |
| * <p>The default implementation of this method does nothing.</p> |
| * |
| * @param cameraId The unique identifier of the logical multi-camera. |
| * @param physicalCameraId The unique identifier of the physical camera. |
| * |
| * @see #onCameraAvailable |
| * @see #onPhysicalCameraUnavailable |
| */ |
| public void onPhysicalCameraAvailable(@NonNull String cameraId, |
| @NonNull String physicalCameraId) { |
| // default empty implementation |
| } |
| |
| /** |
| * A previously-available physical camera has become unavailable for use. |
| * |
| * <p>By default, all of the physical cameras of a logical multi-camera are |
| * unavailable if the logical camera itself is unavailable. |
| * No availability callbacks will be called for any of the physical |
| * cameras of its parent logical multi-camera, when {@link #onCameraUnavailable} for |
| * the logical multi-camera is invoked.</p> |
| * |
| * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion} |
| * < {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, opening a logical camera |
| * disables the {@link #onPhysicalCameraAvailable} and {@link #onPhysicalCameraUnavailable} |
| * callbacks for its physical cameras. For example, if app A opens the camera device:</p> |
| * |
| * <ul> |
| * |
| * <li>All apps subscribing to ActivityCallback get {@link #onCameraUnavailable}.</li> |
| * |
| * <li>No app (including app A) subscribing to ActivityCallback gets |
| * {@link #onPhysicalCameraAvailable} or {@link #onPhysicalCameraUnavailable}, because |
| * the logical camera is unavailable (some app is using it).</li> |
| * |
| * </ul> |
| * |
| * <p>If {@link android.content.pm.ApplicationInfo#targetSdkVersion targetSdkVersion} |
| * ≥ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}:</p> |
| * |
| * <ul> |
| * |
| * <li>A physical camera status change will trigger {@link #onPhysicalCameraAvailable} |
| * or {@link #onPhysicalCameraUnavailable} even after the logical camera becomes |
| * unavailable. A {@link #onCameraUnavailable} call for a logical camera doesn't reset the |
| * physical cameras' availability status. This makes it possible for an application opening |
| * the logical camera device to know which physical camera becomes unavailable or available |
| * to use.</li> |
| * |
| * <li>Similar to {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13} and earlier, |
| * the logical camera's {@link #onCameraAvailable} callback implies all of its physical |
| * cameras' status become available. {@link #onPhysicalCameraUnavailable} will be called |
| * for any unavailable physical cameras upon the logical camera becoming available.</li> |
| * |
| * </ul> |
| * |
| * <p>Given the pipeline nature of the camera capture through {@link |
| * android.hardware.camera2.CaptureRequest}, there may be frame drops if the application |
| * requests images from a physical camera of a logical multi-camera and that physical camera |
| * becomes unavailable. The application should stop requesting directly from an unavailable |
| * physical camera as soon as {@link #onPhysicalCameraUnavailable} is received, and also be |
| * ready to robustly handle frame drop errors for requests targeting physical cameras, |
| * since those errors may arrive before the unavailability callback.</p> |
| * |
| * <p>The default implementation of this method does nothing.</p> |
| * |
| * @param cameraId The unique identifier of the logical multi-camera. |
| * @param physicalCameraId The unique identifier of the physical camera. |
| * |
| * @see #onCameraAvailable |
| * @see #onPhysicalCameraAvailable |
| */ |
| public void onPhysicalCameraUnavailable(@NonNull String cameraId, |
| @NonNull String physicalCameraId) { |
| // default empty implementation |
| } |
| |
| /** |
| * A camera device has been opened by an application. |
| * |
| * <p>The default implementation of this method does nothing.</p> |
| * android.Manifest.permission.CAMERA_OPEN_CLOSE_LISTENER is required to receive this |
| * callback |
| * @param cameraId The unique identifier of the camera opened. |
| * @param packageId The package Id of the application opening the camera. |
| * |
| * @see #onCameraClosed |
| * @hide |
| */ |
| @SystemApi |
| @TestApi |
| @RequiresPermission(android.Manifest.permission.CAMERA_OPEN_CLOSE_LISTENER) |
| public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) { |
| // default empty implementation |
| } |
| |
| /** |
| * A previously-opened camera has been closed. |
| * |
| * <p>The default implementation of this method does nothing.</p> |
| * android.Manifest.permission.CAMERA_OPEN_CLOSE_LISTENER is required to receive this |
| * callback. |
| * @param cameraId The unique identifier of the closed camera. |
| * @hide |
| */ |
| @SystemApi |
| @TestApi |
| @RequiresPermission(android.Manifest.permission.CAMERA_OPEN_CLOSE_LISTENER) |
| public void onCameraClosed(@NonNull String cameraId) { |
| // default empty implementation |
| } |
| } |
| |
| /** |
| * A callback for camera flash torch modes becoming unavailable, disabled, or enabled. |
| * |
| * <p>The torch mode becomes unavailable when the camera device it belongs to becomes |
| * unavailable or other camera resources it needs become busy due to other higher priority |
| * camera activities. The torch mode becomes disabled when it was turned off or when the camera |
| * device it belongs to is no longer in use and other camera resources it needs are no longer |
| * busy. A camera's torch mode is turned off when an application calls {@link #setTorchMode} to |
| * turn off the camera's torch mode, or when an application turns on another camera's torch mode |
| * if keeping multiple torch modes on simultaneously is not supported. The torch mode becomes |
| * enabled when it is turned on via {@link #setTorchMode}.</p> |
| * |
| * <p>The torch mode is available to set via {@link #setTorchMode} only when it's in a disabled |
| * or enabled state.</p> |
| * |
| * <p>Extend this callback and pass an instance of the subclass to |
| * {@link CameraManager#registerTorchCallback} to be notified of such status changes. |
| * </p> |
| * |
| * @see #registerTorchCallback |
| */ |
| public static abstract class TorchCallback { |
| |
| private int mDeviceId; |
| private int mDevicePolicy; |
| |
| /** |
| * A camera's torch mode has become unavailable to set via {@link #setTorchMode}. |
| * |
| * <p>If torch mode was previously turned on by calling {@link #setTorchMode}, it will be |
| * turned off before {@link CameraManager.TorchCallback#onTorchModeUnavailable} is |
| * invoked. {@link #setTorchMode} will fail until the torch mode has entered a disabled or |
| * enabled state again.</p> |
| * |
| * <p>The default implementation of this method does nothing.</p> |
| * |
| * @param cameraId The unique identifier of the camera whose torch mode has become |
| * unavailable. |
| */ |
| public void onTorchModeUnavailable(@NonNull String cameraId) { |
| // default empty implementation |
| } |
| |
| /** |
| * A camera's torch mode has become enabled or disabled and can be changed via |
| * {@link #setTorchMode}. |
| * |
| * <p>The default implementation of this method does nothing.</p> |
| * |
| * @param cameraId The unique identifier of the camera whose torch mode has been changed. |
| * |
| * @param enabled The state that the torch mode of the camera has been changed to. |
| * {@code true} when the torch mode has become on and available to be turned |
| * off. {@code false} when the torch mode has becomes off and available to |
| * be turned on. |
| */ |
| public void onTorchModeChanged(@NonNull String cameraId, boolean enabled) { |
| // default empty implementation |
| } |
| |
| /** |
| * A camera's flash unit brightness level has been changed in torch mode via |
| * {@link #turnOnTorchWithStrengthLevel}. When the torch is turned OFF, this |
| * callback will not be triggered even though the torch strength level resets to |
| * default value |
| * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL} |
| * |
| * <p>The default implementation of this method does nothing.</p> |
| * |
| * @param cameraId The unique identifier of the camera whose flash unit brightness level has |
| * been changed. |
| * |
| * @param newStrengthLevel The brightness level of the flash unit that has been changed to. |
| */ |
| public void onTorchStrengthLevelChanged(@NonNull String cameraId, int newStrengthLevel) { |
| // default empty implementation |
| } |
| } |
| |
| /** |
| * Queries the camera service if a cameraId is a hidden physical camera that belongs to a |
| * logical camera device. |
| * |
| * A hidden physical camera is a camera that cannot be opened by the application. But it |
| * can be used as part of a logical camera. |
| * |
| * @param cameraId a non-{@code null} camera identifier |
| * @return {@code true} if cameraId is a hidden physical camera device |
| * |
| * @hide |
| */ |
| public static boolean isHiddenPhysicalCamera(String cameraId) { |
| try { |
| ICameraService cameraService = CameraManagerGlobal.get().getCameraService(); |
| // If no camera service, no support |
| if (cameraService == null) return false; |
| |
| return cameraService.isHiddenPhysicalCamera(cameraId); |
| } catch (RemoteException e) { |
| // Camera service is now down, no support for any API level |
| } |
| return false; |
| } |
| |
| /** |
| * Inject the external camera to replace the internal camera session. |
| * |
| * <p>If injecting the external camera device fails, then the injection callback's |
| * {@link CameraInjectionSession.InjectionStatusCallback#onInjectionError |
| * onInjectionError} method will be called.</p> |
| * |
| * @param packageName It scopes the injection to a particular app. |
| * @param internalCamId The id of one of the physical or logical cameras on the phone. |
| * @param externalCamId The id of one of the remote cameras that are provided by the dynamic |
| * camera HAL. |
| * @param executor The executor which will be used when invoking the callback. |
| * @param callback The callback which is invoked once the external camera is injected. |
| * |
| * @throws CameraAccessException If the camera device has been disconnected. |
| * {@link CameraAccessException#CAMERA_DISCONNECTED} will be |
| * thrown if camera service is not available. |
| * @throws SecurityException If the specific application that can cast to external |
| * devices does not have permission to inject the external |
| * camera. |
| * @throws IllegalArgumentException If cameraId doesn't match any currently or previously |
| * available camera device or some camera functions might not |
| * work properly or the injection camera runs into a fatal |
| * error. |
| * @hide |
| */ |
| @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA) |
| public void injectCamera(@NonNull String packageName, @NonNull String internalCamId, |
| @NonNull String externalCamId, @NonNull @CallbackExecutor Executor executor, |
| @NonNull CameraInjectionSession.InjectionStatusCallback callback) |
| throws CameraAccessException, SecurityException, |
| IllegalArgumentException { |
| if (CameraManagerGlobal.sCameraServiceDisabled) { |
| throw new IllegalArgumentException("No cameras available on device"); |
| } |
| ICameraService cameraService = CameraManagerGlobal.get().getCameraService(); |
| if (cameraService == null) { |
| throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, |
| "Camera service is currently unavailable"); |
| } |
| synchronized (mLock) { |
| try { |
| CameraInjectionSessionImpl injectionSessionImpl = |
| new CameraInjectionSessionImpl(callback, executor); |
| ICameraInjectionCallback cameraInjectionCallback = |
| injectionSessionImpl.getCallback(); |
| ICameraInjectionSession injectionSession = cameraService.injectCamera(packageName, |
| internalCamId, externalCamId, cameraInjectionCallback); |
| injectionSessionImpl.setRemoteInjectionSession(injectionSession); |
| } catch (ServiceSpecificException e) { |
| throw ExceptionUtils.throwAsPublicException(e); |
| } catch (RemoteException e) { |
| // Camera service died - act as if it's a CAMERA_DISCONNECTED case |
| ServiceSpecificException sse = new ServiceSpecificException( |
| ICameraService.ERROR_DISCONNECTED, |
| "Camera service is currently unavailable"); |
| throw ExceptionUtils.throwAsPublicException(sse); |
| } |
| } |
| } |
| |
| /** |
| * Injects session params into existing clients in the CameraService. |
| * |
| * @param cameraId The camera id of client to inject session params into. |
| * If no such client exists for cameraId, no injection will |
| * take place. |
| * @param sessionParams A {@link CaptureRequest} object containing the |
| * the sessionParams to inject into the existing client. |
| * |
| * @throws CameraAccessException {@link CameraAccessException#CAMERA_DISCONNECTED} will be |
| * thrown if camera service is not available. Further, if |
| * if no such client exists for cameraId, |
| * {@link CameraAccessException#CAMERA_ERROR} will be thrown. |
| * @throws SecurityException If the caller does not have permission to inject session |
| * params |
| * @hide |
| */ |
| @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA) |
| public void injectSessionParams(@NonNull String cameraId, @NonNull CaptureRequest sessionParams) |
| throws CameraAccessException, SecurityException { |
| CameraManagerGlobal.get().injectSessionParams(cameraId, sessionParams); |
| } |
| |
| /** |
| * Returns the current CameraService instance connected to Global |
| * @hide |
| */ |
| public ICameraService getCameraService() { |
| return CameraManagerGlobal.get().getCameraService(); |
| } |
| |
| /** |
| * Returns true if cameraservice is currently disabled. If true, {@link #getCameraService()} |
| * will definitely return null. |
| * @hide |
| */ |
| public boolean isCameraServiceDisabled() { |
| return CameraManagerGlobal.sCameraServiceDisabled; |
| } |
| |
| /** |
| * Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for |
| * currently active session. Validation is done downstream. |
| * |
| * @param extStats Extension Session stats to be logged by cameraservice |
| * |
| * @return the key to be used with the next call. |
| * See {@link ICameraService#reportExtensionSessionStats}. |
| * @hide |
| */ |
| public static String reportExtensionSessionStats(CameraExtensionSessionStats extStats) { |
| ICameraService cameraService = CameraManagerGlobal.get().getCameraService(); |
| if (cameraService == null) { |
| Log.e(TAG, "CameraService not available. Not reporting extension stats."); |
| return ""; |
| } |
| try { |
| return cameraService.reportExtensionSessionStats(extStats); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Failed to report extension session stats to cameraservice.", e); |
| } |
| return ""; |
| } |
| |
| /** |
| * A per-process global camera manager instance, to retain a connection to the camera service, |
| * and to distribute camera availability notices to API-registered callbacks |
| */ |
| private static final class CameraManagerGlobal extends ICameraServiceListener.Stub |
| implements IBinder.DeathRecipient { |
| |
| private static final String TAG = "CameraManagerGlobal"; |
| |
| private final boolean DEBUG = false; |
| |
| private final int CAMERA_SERVICE_RECONNECT_DELAY_MS = 1000; |
| |
| // Singleton instance |
| private static final CameraManagerGlobal gCameraManager = |
| new CameraManagerGlobal(); |
| |
| /** |
| * This must match the ICameraService definition |
| */ |
| private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; |
| |
| private final ScheduledExecutorService mScheduler = Executors.newScheduledThreadPool(1); |
| // Camera ID -> Status map |
| private final ArrayMap<DeviceCameraInfo, Integer> mDeviceStatus = new ArrayMap<>(); |
| // Camera ID -> (physical camera ID -> Status map) |
| private final ArrayMap<DeviceCameraInfo, ArrayList<String>> mUnavailablePhysicalDevices = |
| new ArrayMap<>(); |
| // Opened Camera ID -> apk name map |
| private final ArrayMap<DeviceCameraInfo, String> mOpenedDevices = new ArrayMap<>(); |
| |
| private final Set<Set<DeviceCameraInfo>> mConcurrentCameraIdCombinations = new ArraySet<>(); |
| |
| // Registered availability callbacks and their executors |
| private final ArrayMap<AvailabilityCallback, Executor> mCallbackMap = new ArrayMap<>(); |
| |
| // torch client binder to set the torch mode with. |
| private final Binder mTorchClientBinder = new Binder(); |
| |
| // Camera ID -> Torch status map |
| private final ArrayMap<DeviceCameraInfo, Integer> mTorchStatus = new ArrayMap<>(); |
| |
| // Registered torch callbacks and their executors |
| private final ArrayMap<TorchCallback, Executor> mTorchCallbackMap = new ArrayMap<>(); |
| |
| private final Object mLock = new Object(); |
| |
| // Access only through getCameraService to deal with binder death |
| private ICameraService mCameraService; |
| private boolean mHasOpenCloseListenerPermission = false; |
| |
| private HandlerThread mDeviceStateHandlerThread; |
| private Handler mDeviceStateHandler; |
| private FoldStateListener mFoldStateListener; |
| |
| // Singleton, don't allow construction |
| private CameraManagerGlobal() { } |
| |
| public static final boolean sCameraServiceDisabled = |
| SystemProperties.getBoolean("config.disable_cameraservice", false); |
| |
| public static final boolean sLandscapeToPortrait = |
| SystemProperties.getBoolean(LANDSCAPE_TO_PORTRAIT_PROP, false); |
| |
| public static CameraManagerGlobal get() { |
| return gCameraManager; |
| } |
| |
| public void registerDeviceStateListener(@NonNull CameraCharacteristics chars, |
| @NonNull Context ctx) { |
| synchronized(mLock) { |
| if (mDeviceStateHandlerThread == null) { |
| mDeviceStateHandlerThread = new HandlerThread(TAG); |
| mDeviceStateHandlerThread.start(); |
| mDeviceStateHandler = new Handler(mDeviceStateHandlerThread.getLooper()); |
| } |
| |
| if (mFoldStateListener == null) { |
| mFoldStateListener = new FoldStateListener(ctx); |
| try { |
| ctx.getSystemService(DeviceStateManager.class).registerCallback( |
| new HandlerExecutor(mDeviceStateHandler), mFoldStateListener); |
| } catch (IllegalStateException e) { |
| mFoldStateListener = null; |
| Log.v(TAG, "Failed to register device state listener!"); |
| Log.v(TAG, "Device state dependent characteristics updates will not be" + |
| "functional!"); |
| return; |
| } |
| } |
| |
| mFoldStateListener.addDeviceStateListener(chars.getDeviceStateListener()); |
| } |
| } |
| |
| @Override |
| public IBinder asBinder() { |
| return this; |
| } |
| |
| /** |
| * Return a best-effort ICameraService. |
| * |
| * <p>This will be null if the camera service is not currently available. If the camera |
| * service has died since the last use of the camera service, will try to reconnect to the |
| * service.</p> |
| */ |
| public ICameraService getCameraService() { |
| synchronized(mLock) { |
| connectCameraServiceLocked(); |
| if (mCameraService == null && !sCameraServiceDisabled) { |
| Log.e(TAG, "Camera service is unavailable"); |
| } |
| return mCameraService; |
| } |
| } |
| |
| /** |
| * Connect to the camera service if it's available, and set up listeners. |
| * If the service is already connected, do nothing. |
| * |
| * <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p> |
| */ |
| private void connectCameraServiceLocked() { |
| // Only reconnect if necessary |
| if (mCameraService != null || sCameraServiceDisabled) return; |
| |
| Log.i(TAG, "Connecting to camera service"); |
| |
| IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME); |
| if (cameraServiceBinder == null) { |
| // Camera service is now down, leave mCameraService as null |
| return; |
| } |
| try { |
| cameraServiceBinder.linkToDeath(this, /*flags*/ 0); |
| } catch (RemoteException e) { |
| // Camera service is now down, leave mCameraService as null |
| return; |
| } |
| |
| ICameraService cameraService = ICameraService.Stub.asInterface(cameraServiceBinder); |
| |
| try { |
| CameraMetadataNative.setupGlobalVendorTagDescriptor(); |
| } catch (ServiceSpecificException e) { |
| handleRecoverableSetupErrors(e); |
| } |
| |
| try { |
| CameraStatus[] cameraStatuses = cameraService.addListener(this); |
| for (CameraStatus cameraStatus : cameraStatuses) { |
| DeviceCameraInfo info = new DeviceCameraInfo(cameraStatus.cameraId, |
| cameraStatus.deviceId); |
| onStatusChangedLocked(cameraStatus.status, info); |
| |
| if (cameraStatus.unavailablePhysicalCameras != null) { |
| for (String unavailablePhysicalCamera : |
| cameraStatus.unavailablePhysicalCameras) { |
| onPhysicalCameraStatusChangedLocked( |
| ICameraServiceListener.STATUS_NOT_PRESENT, |
| info, unavailablePhysicalCamera); |
| } |
| } |
| |
| if (mHasOpenCloseListenerPermission |
| && cameraStatus.status == ICameraServiceListener.STATUS_NOT_AVAILABLE |
| && !cameraStatus.clientPackage.isEmpty()) { |
| onCameraOpenedLocked(info, cameraStatus.clientPackage); |
| } |
| } |
| mCameraService = cameraService; |
| } catch (ServiceSpecificException e) { |
| // Unexpected failure |
| throw new IllegalStateException("Failed to register a camera service listener", e); |
| } catch (RemoteException e) { |
| // Camera service is now down, leave mCameraService as null |
| } |
| |
| try { |
| ConcurrentCameraIdCombination[] cameraIdCombinations = |
| cameraService.getConcurrentCameraIds(); |
| for (ConcurrentCameraIdCombination comb : cameraIdCombinations) { |
| Set<Pair<String, Integer>> combination = |
| comb.getConcurrentCameraIdCombination(); |
| Set<DeviceCameraInfo> deviceCameraInfoSet = new ArraySet<>(); |
| for (Pair<String, Integer> entry : combination) { |
| deviceCameraInfoSet.add(new DeviceCameraInfo(entry.first, entry.second)); |
| } |
| mConcurrentCameraIdCombinations.add(deviceCameraInfoSet); |
| } |
| } catch (ServiceSpecificException e) { |
| // Unexpected failure |
| throw new IllegalStateException("Failed to get concurrent camera id combinations", |
| e); |
| } catch (RemoteException e) { |
| // Camera service died in all probability |
| } |
| } |
| |
| /** Injects session params into an existing client for cameraid. */ |
| public void injectSessionParams(@NonNull String cameraId, |
| @NonNull CaptureRequest sessionParams) |
| throws CameraAccessException, SecurityException { |
| synchronized (mLock) { |
| ICameraService cameraService = getCameraService(); |
| if (cameraService == null) { |
| throw new CameraAccessException( |
| CameraAccessException.CAMERA_DISCONNECTED, |
| "Camera service is currently unavailable."); |
| } |
| |
| try { |
| cameraService.injectSessionParams(cameraId, sessionParams.getNativeMetadata()); |
| } catch (ServiceSpecificException e) { |
| throw ExceptionUtils.throwAsPublicException(e); |
| } catch (RemoteException e) { |
| throw new CameraAccessException( |
| CameraAccessException.CAMERA_DISCONNECTED, |
| "Camera service is currently unavailable."); |
| } |
| } |
| } |
| |
| private String[] extractCameraIdListLocked(int deviceId, int devicePolicy) { |
| List<String> cameraIds = new ArrayList<>(); |
| for (int i = 0; i < mDeviceStatus.size(); i++) { |
| int status = mDeviceStatus.valueAt(i); |
| DeviceCameraInfo info = mDeviceStatus.keyAt(i); |
| if (status == ICameraServiceListener.STATUS_NOT_PRESENT |
| || status == ICameraServiceListener.STATUS_ENUMERATING |
| || shouldHideCamera(deviceId, devicePolicy, info)) { |
| continue; |
| } |
| cameraIds.add(info.mCameraId); |
| } |
| return cameraIds.toArray(new String[0]); |
| } |
| |
| private Set<Set<String>> extractConcurrentCameraIdListLocked(int deviceId, |
| int devicePolicy) { |
| Set<Set<String>> concurrentCameraIds = new ArraySet<>(); |
| for (Set<DeviceCameraInfo> deviceCameraInfos : mConcurrentCameraIdCombinations) { |
| Set<String> extractedCameraIds = new ArraySet<>(); |
| for (DeviceCameraInfo info : deviceCameraInfos) { |
| // if the camera id status is NOT_PRESENT or ENUMERATING; skip the device. |
| // TODO: Would a device status NOT_PRESENT ever be in the map ? it gets removed |
| // in the callback anyway. |
| Integer status = mDeviceStatus.get(info); |
| if (status == null) { |
| // camera id not present |
| continue; |
| } |
| if (status == ICameraServiceListener.STATUS_ENUMERATING |
| || status == ICameraServiceListener.STATUS_NOT_PRESENT) { |
| continue; |
| } |
| if (shouldHideCamera(deviceId, devicePolicy, info)) { |
| continue; |
| } |
| extractedCameraIds.add(info.mCameraId); |
| } |
| if (!extractedCameraIds.isEmpty()) { |
| concurrentCameraIds.add(extractedCameraIds); |
| } |
| } |
| return concurrentCameraIds; |
| } |
| |
| private static void sortCameraIds(String[] cameraIds) { |
| // The sort logic must match the logic in |
| // libcameraservice/common/CameraProviderManager.cpp::getAPI1CompatibleCameraDeviceIds |
| Arrays.sort(cameraIds, new Comparator<String>() { |
| @Override |
| public int compare(String s1, String s2) { |
| int s1Int = 0, s2Int = 0; |
| try { |
| s1Int = Integer.parseInt(s1); |
| } catch (NumberFormatException e) { |
| s1Int = -1; |
| } |
| |
| try { |
| s2Int = Integer.parseInt(s2); |
| } catch (NumberFormatException e) { |
| s2Int = -1; |
| } |
| |
| // Uint device IDs first |
| if (s1Int >= 0 && s2Int >= 0) { |
| return s1Int - s2Int; |
| } else if (s1Int >= 0) { |
| return -1; |
| } else if (s2Int >= 0) { |
| return 1; |
| } else { |
| // Simple string compare if both id are not uint |
| return s1.compareTo(s2); |
| } |
| }}); |
| } |
| |
| private boolean shouldHideCamera(int currentDeviceId, int devicePolicy, |
| DeviceCameraInfo info) { |
| if (!android.companion.virtualdevice.flags.Flags.cameraDeviceAwareness()) { |
| // Don't hide any cameras if the device-awareness feature flag is disabled. |
| return false; |
| } |
| |
| if (devicePolicy == DEVICE_POLICY_DEFAULT && info.mDeviceId == DEVICE_ID_DEFAULT) { |
| // Don't hide default-device cameras for a default-policy virtual device. |
| return false; |
| } |
| |
| return currentDeviceId != info.mDeviceId; |
| } |
| |
| private static boolean cameraStatusesContains(CameraStatus[] cameraStatuses, |
| DeviceCameraInfo info) { |
| for (CameraStatus c : cameraStatuses) { |
| if (c.cameraId.equals(info.mCameraId) && c.deviceId == info.mDeviceId) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public String[] getCameraIdListNoLazy(int deviceId, int devicePolicy) { |
| if (sCameraServiceDisabled) { |
| return new String[] {}; |
| } |
| |
| CameraStatus[] cameraStatuses; |
| ICameraServiceListener.Stub testListener = new ICameraServiceListener.Stub() { |
| @Override |
| public void onStatusChanged(int status, String id, int deviceId) |
| throws RemoteException { |
| } |
| @Override |
| public void onPhysicalCameraStatusChanged(int status, |
| String id, String physicalId, int deviceId) throws RemoteException { |
| } |
| @Override |
| public void onTorchStatusChanged(int status, String id, int deviceId) |
| throws RemoteException { |
| } |
| @Override |
| public void onTorchStrengthLevelChanged(String id, int newStrengthLevel, |
| int deviceId) throws RemoteException { |
| } |
| @Override |
| public void onCameraAccessPrioritiesChanged() { |
| } |
| @Override |
| public void onCameraOpened(String id, String clientPackageId, int deviceId) { |
| } |
| @Override |
| public void onCameraClosed(String id, int deviceId) { |
| }}; |
| |
| String[] cameraIds; |
| synchronized (mLock) { |
| connectCameraServiceLocked(); |
| try { |
| // The purpose of the addListener, removeListener pair here is to get a fresh |
| // list of camera ids from cameraserver. We do this since for in test processes, |
| // changes can happen w.r.t non-changeable permissions (eg: SYSTEM_CAMERA |
| // permissions can be effectively changed by calling |
| // adopt(drop)ShellPermissionIdentity()). |
| // Camera devices, which have their discovery affected by these permission |
| // changes, will not have clients get callbacks informing them about these |
| // devices going offline (in real world scenarios, these permissions aren't |
| // changeable). Future calls to getCameraIdList() will reflect the changes in |
| // the camera id list after getCameraIdListNoLazy() is called. |
| // We need to remove the torch ids which may have been associated with the |
| // devices removed as well. This is the same situation. |
| cameraStatuses = mCameraService.addListener(testListener); |
| mCameraService.removeListener(testListener); |
| for (CameraStatus cameraStatus : cameraStatuses) { |
| onStatusChangedLocked(cameraStatus.status, |
| new DeviceCameraInfo(cameraStatus.cameraId, cameraStatus.deviceId)); |
| } |
| Set<DeviceCameraInfo> deviceCameraInfos = mDeviceStatus.keySet(); |
| List<DeviceCameraInfo> deviceInfosToRemove = new ArrayList<>(); |
| for (DeviceCameraInfo info : deviceCameraInfos) { |
| // Its possible that a device id was removed without a callback notifying |
| // us. This may happen in case a process 'drops' system camera permissions |
| // (even though the permission isn't a changeable one, tests may call |
| // adoptShellPermissionIdentity() and then dropShellPermissionIdentity(). |
| if (!cameraStatusesContains(cameraStatuses, info)) { |
| deviceInfosToRemove.add(info); |
| } |
| } |
| for (DeviceCameraInfo info : deviceInfosToRemove) { |
| onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, info); |
| mTorchStatus.remove(info); |
| } |
| } catch (ServiceSpecificException e) { |
| // Unexpected failure |
| throw new IllegalStateException("Failed to register a camera service listener", |
| e); |
| } catch (RemoteException e) { |
| // Camera service is now down, leave mCameraService as null |
| } |
| cameraIds = extractCameraIdListLocked(deviceId, devicePolicy); |
| } |
| sortCameraIds(cameraIds); |
| return cameraIds; |
| } |
| |
| /** |
| * Get a list of all camera IDs that are at least PRESENT; ignore devices that are |
| * NOT_PRESENT or ENUMERATING, since they cannot be used by anyone. |
| */ |
| public String[] getCameraIdList(int deviceId, int devicePolicy) { |
| String[] cameraIds; |
| synchronized (mLock) { |
| // Try to make sure we have an up-to-date list of camera devices. |
| connectCameraServiceLocked(); |
| cameraIds = extractCameraIdListLocked(deviceId, devicePolicy); |
| } |
| sortCameraIds(cameraIds); |
| return cameraIds; |
| } |
| |
| public @NonNull Set<Set<String>> getConcurrentCameraIds(int deviceId, int devicePolicy) { |
| Set<Set<String>> concurrentStreamingCameraIds; |
| synchronized (mLock) { |
| // Try to make sure we have an up-to-date list of concurrent camera devices. |
| connectCameraServiceLocked(); |
| concurrentStreamingCameraIds = extractConcurrentCameraIdListLocked(deviceId, |
| devicePolicy); |
| } |
| // TODO: Some sort of sorting ? |
| return concurrentStreamingCameraIds; |
| } |
| |
| public boolean isConcurrentSessionConfigurationSupported( |
| @NonNull Map<String, SessionConfiguration> cameraIdsAndSessionConfigurations, |
| int targetSdkVersion, int deviceId, int devicePolicy) |
| throws CameraAccessException { |
| if (cameraIdsAndSessionConfigurations == null) { |
| throw new IllegalArgumentException("cameraIdsAndSessionConfigurations was null"); |
| } |
| |
| int size = cameraIdsAndSessionConfigurations.size(); |
| if (size == 0) { |
| throw new IllegalArgumentException("camera id and session combination is empty"); |
| } |
| |
| synchronized (mLock) { |
| // Go through all the elements and check if the camera ids are valid at least / |
| // belong to one of the combinations returned by getConcurrentCameraIds() |
| boolean subsetFound = false; |
| for (Set<DeviceCameraInfo> combination : mConcurrentCameraIdCombinations) { |
| Set<DeviceCameraInfo> infos = new ArraySet<>(); |
| for (String cameraId : cameraIdsAndSessionConfigurations.keySet()) { |
| infos.add(new DeviceCameraInfo(cameraId, |
| devicePolicy == DEVICE_POLICY_DEFAULT |
| ? DEVICE_ID_DEFAULT : deviceId)); |
| } |
| if (combination.containsAll(infos)) { |
| subsetFound = true; |
| } |
| } |
| if (!subsetFound) { |
| Log.v(TAG, "isConcurrentSessionConfigurationSupported called with a subset of" |
| + " camera ids not returned by getConcurrentCameraIds"); |
| return false; |
| } |
| CameraIdAndSessionConfiguration [] cameraIdsAndConfigs = |
| new CameraIdAndSessionConfiguration[size]; |
| int i = 0; |
| for (Map.Entry<String, SessionConfiguration> pair : |
| cameraIdsAndSessionConfigurations.entrySet()) { |
| cameraIdsAndConfigs[i] = |
| new CameraIdAndSessionConfiguration(pair.getKey(), pair.getValue()); |
| i++; |
| } |
| try { |
| return mCameraService.isConcurrentSessionConfigurationSupported( |
| cameraIdsAndConfigs, targetSdkVersion, deviceId, devicePolicy); |
| } catch (ServiceSpecificException e) { |
| throw ExceptionUtils.throwAsPublicException(e); |
| } catch (RemoteException e) { |
| // Camera service died - act as if the camera was disconnected |
| throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, |
| "Camera service is currently unavailable", e); |
| } |
| } |
| } |
| |
| /** |
| * Helper function to find out if a camera id is in the set of combinations returned by |
| * getConcurrentCameraIds() |
| * @param cameraId the unique identifier of the camera device to query |
| * @param deviceId the device id of the context |
| * @return Whether the camera device was found in the set of combinations returned by |
| * getConcurrentCameraIds |
| */ |
| public boolean cameraIdHasConcurrentStreamsLocked(String cameraId, int deviceId, |
| int devicePolicy) { |
| DeviceCameraInfo info = new DeviceCameraInfo(cameraId, |
| devicePolicy == DEVICE_POLICY_DEFAULT ? DEVICE_ID_DEFAULT : deviceId); |
| if (!mDeviceStatus.containsKey(info)) { |
| // physical camera ids aren't advertised in concurrent camera id combinations. |
| if (DEBUG) { |
| Log.v(TAG, " physical camera id " + cameraId + " is hidden." + |
| " Available logical camera ids : " + mDeviceStatus); |
| } |
| return false; |
| } |
| for (Set<DeviceCameraInfo> comb : mConcurrentCameraIdCombinations) { |
| if (comb.contains(info)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public void setTorchMode(String cameraId, boolean enabled, int deviceId, int devicePolicy) |
| throws CameraAccessException { |
| synchronized (mLock) { |
| if (cameraId == null) { |
| throw new IllegalArgumentException("cameraId was null"); |
| } |
| |
| ICameraService cameraService = getCameraService(); |
| if (cameraService == null) { |
| throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, |
| "Camera service is currently unavailable"); |
| } |
| |
| try { |
| cameraService.setTorchMode(cameraId, enabled, mTorchClientBinder, deviceId, |
| devicePolicy); |
| } catch(ServiceSpecificException e) { |
| throw ExceptionUtils.throwAsPublicException(e); |
| } catch (RemoteException e) { |
| throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, |
| "Camera service is currently unavailable"); |
| } |
| } |
| } |
| |
| public void turnOnTorchWithStrengthLevel(String cameraId, int torchStrength, int deviceId, |
| int devicePolicy) |
| throws CameraAccessException { |
| synchronized (mLock) { |
| if (cameraId == null) { |
| throw new IllegalArgumentException("cameraId was null"); |
| } |
| |
| ICameraService cameraService = getCameraService(); |
| if (cameraService == null) { |
| throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, |
| "Camera service is currently unavailable."); |
| } |
| |
| try { |
| cameraService.turnOnTorchWithStrengthLevel(cameraId, torchStrength, |
| mTorchClientBinder, deviceId, devicePolicy); |
| } catch(ServiceSpecificException e) { |
| throw ExceptionUtils.throwAsPublicException(e); |
| } catch (RemoteException e) { |
| throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, |
| "Camera service is currently unavailable."); |
| } |
| } |
| } |
| |
| public int getTorchStrengthLevel(String cameraId, int deviceId, int devicePolicy) |
| throws CameraAccessException { |
| int torchStrength; |
| synchronized (mLock) { |
| if (cameraId == null) { |
| throw new IllegalArgumentException("cameraId was null"); |
| } |
| |
| ICameraService cameraService = getCameraService(); |
| if (cameraService == null) { |
| throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, |
| "Camera service is currently unavailable."); |
| } |
| |
| try { |
| torchStrength = cameraService.getTorchStrengthLevel(cameraId, deviceId, |
| devicePolicy); |
| } catch(ServiceSpecificException e) { |
| throw ExceptionUtils.throwAsPublicException(e); |
| } catch (RemoteException e) { |
| throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, |
| "Camera service is currently unavailable."); |
| } |
| } |
| return torchStrength; |
| } |
| |
| private void handleRecoverableSetupErrors(ServiceSpecificException e) { |
| switch (e.errorCode) { |
| case ICameraService.ERROR_DISCONNECTED: |
| Log.w(TAG, e.getMessage()); |
| break; |
| default: |
| throw new IllegalStateException(e); |
| } |
| } |
| |
| private boolean isAvailable(int status) { |
| switch (status) { |
| case ICameraServiceListener.STATUS_PRESENT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| private boolean validStatus(int status) { |
| switch (status) { |
| case ICameraServiceListener.STATUS_NOT_PRESENT: |
| case ICameraServiceListener.STATUS_PRESENT: |
| case ICameraServiceListener.STATUS_ENUMERATING: |
| case ICameraServiceListener.STATUS_NOT_AVAILABLE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| private boolean validTorchStatus(int status) { |
| switch (status) { |
| case ICameraServiceListener.TORCH_STATUS_NOT_AVAILABLE: |
| case ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON: |
| case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| private void postSingleAccessPriorityChangeUpdate(final AvailabilityCallback callback, |
| final Executor executor) { |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| executor.execute(callback::onCameraAccessPrioritiesChanged); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| |
| private void postSingleCameraOpenedUpdate(final AvailabilityCallback callback, |
| final Executor executor, final String id, final String packageId) { |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| executor.execute(() -> callback.onCameraOpened(id, packageId)); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| |
| private void postSingleCameraClosedUpdate(final AvailabilityCallback callback, |
| final Executor executor, final String id) { |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| executor.execute(() -> callback.onCameraClosed(id)); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| |
| private void postSingleUpdate(final AvailabilityCallback callback, final Executor executor, |
| final String id, final String physicalId, final int status) { |
| if (isAvailable(status)) { |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| executor.execute( |
| () -> { |
| if (physicalId == null) { |
| callback.onCameraAvailable(id); |
| } else { |
| callback.onPhysicalCameraAvailable(id, physicalId); |
| } |
| }); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } else { |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| executor.execute( |
| () -> { |
| if (physicalId == null) { |
| callback.onCameraUnavailable(id); |
| } else { |
| callback.onPhysicalCameraUnavailable(id, physicalId); |
| } |
| }); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| } |
| |
| private void postSingleTorchUpdate(final TorchCallback callback, final Executor executor, |
| final String id, final int status) { |
| switch(status) { |
| case ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON: |
| case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF: { |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| executor.execute(() -> callback.onTorchModeChanged(id, status |
| == ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON)); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| break; |
| } |
| default: { |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| executor.execute(() -> callback.onTorchModeUnavailable(id)); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| break; |
| } |
| } |
| } |
| |
| private void postSingleTorchStrengthLevelUpdate(final TorchCallback callback, |
| final Executor executor, final String id, final int newStrengthLevel) { |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| executor.execute(() -> callback.onTorchStrengthLevelChanged(id, newStrengthLevel)); |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| |
| /** |
| * Send the state of all known cameras to the provided listener, to initialize |
| * the listener's knowledge of camera state. |
| */ |
| private void updateCallbackLocked(AvailabilityCallback callback, Executor executor) { |
| for (int i = 0; i < mDeviceStatus.size(); i++) { |
| DeviceCameraInfo info = mDeviceStatus.keyAt(i); |
| if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) { |
| continue; |
| } |
| |
| Integer status = mDeviceStatus.valueAt(i); |
| postSingleUpdate(callback, executor, info.mCameraId, null /* physicalId */, status); |
| |
| // Send the NOT_PRESENT state for unavailable physical cameras |
| if ((isAvailable(status) || physicalCallbacksAreEnabledForUnavailableCamera()) |
| && mUnavailablePhysicalDevices.containsKey(info)) { |
| List<String> unavailableIds = mUnavailablePhysicalDevices.get(info); |
| for (String unavailableId : unavailableIds) { |
| postSingleUpdate(callback, executor, info.mCameraId, unavailableId, |
| ICameraServiceListener.STATUS_NOT_PRESENT); |
| } |
| } |
| } |
| |
| for (int i = 0; i < mOpenedDevices.size(); i++) { |
| DeviceCameraInfo info = mOpenedDevices.keyAt(i); |
| if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) { |
| continue; |
| } |
| |
| String clientPackageId = mOpenedDevices.valueAt(i); |
| postSingleCameraOpenedUpdate(callback, executor, info.mCameraId, clientPackageId); |
| } |
| } |
| |
| private void onStatusChangedLocked(int status, DeviceCameraInfo info) { |
| if (DEBUG) { |
| Log.v(TAG, |
| String.format("Camera id %s has status changed to 0x%x for device %d", |
| info.mCameraId, status, info.mDeviceId)); |
| } |
| |
| if (!validStatus(status)) { |
| Log.e(TAG, String.format("Ignoring invalid camera %s status 0x%x for device %d", |
| info.mCameraId, status, info.mDeviceId)); |
| return; |
| } |
| |
| Integer oldStatus; |
| if (status == ICameraServiceListener.STATUS_NOT_PRESENT) { |
| oldStatus = mDeviceStatus.remove(info); |
| mUnavailablePhysicalDevices.remove(info); |
| } else { |
| oldStatus = mDeviceStatus.put(info, status); |
| if (oldStatus == null) { |
| mUnavailablePhysicalDevices.put(info, new ArrayList<>()); |
| } |
| } |
| |
| if (oldStatus != null && oldStatus == status) { |
| if (DEBUG) { |
| Log.v(TAG, String.format( |
| "Device status changed to 0x%x, which is what it already was", |
| status)); |
| } |
| return; |
| } |
| |
| // TODO: consider abstracting out this state minimization + transition |
| // into a separate |
| // more easily testable class |
| // i.e. (new State()).addState(STATE_AVAILABLE) |
| // .addState(STATE_NOT_AVAILABLE) |
| // .addTransition(STATUS_PRESENT, STATE_AVAILABLE), |
| // .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE) |
| // .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE); |
| // .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE); |
| |
| // Translate all the statuses to either 'available' or 'not available' |
| // available -> available => no new update |
| // not available -> not available => no new update |
| if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) { |
| if (DEBUG) { |
| Log.v(TAG, |
| String.format( |
| "Device status was previously available (%b), " + |
| " and is now again available (%b)" + |
| "so no new client visible update will be sent", |
| isAvailable(oldStatus), isAvailable(status))); |
| } |
| return; |
| } |
| |
| final int callbackCount = mCallbackMap.size(); |
| for (int i = 0; i < callbackCount; i++) { |
| final AvailabilityCallback callback = mCallbackMap.keyAt(i); |
| if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) { |
| continue; |
| } |
| |
| final Executor executor = mCallbackMap.valueAt(i); |
| postSingleUpdate(callback, executor, info.mCameraId, null /* physicalId */, status); |
| |
| // Send the NOT_PRESENT state for unavailable physical cameras |
| if (isAvailable(status) && mUnavailablePhysicalDevices.containsKey(info)) { |
| List<String> unavailableIds = mUnavailablePhysicalDevices.get(info); |
| for (String unavailableId : unavailableIds) { |
| postSingleUpdate(callback, executor, info.mCameraId, unavailableId, |
| ICameraServiceListener.STATUS_NOT_PRESENT); |
| } |
| } |
| } |
| } // onStatusChangedLocked |
| |
| private void onPhysicalCameraStatusChangedLocked(int status, DeviceCameraInfo info, |
| String physicalId) { |
| if (DEBUG) { |
| Log.v(TAG, |
| String.format("Camera id %s physical camera id %s has status changed " |
| + "to 0x%x for device %d", info.mCameraId, physicalId, status, |
| info.mDeviceId)); |
| } |
| |
| if (!validStatus(status)) { |
| Log.e(TAG, String.format( |
| "Ignoring invalid device %s physical device %s status 0x%x for device %d", |
| info.mCameraId, physicalId, status, info.mDeviceId)); |
| return; |
| } |
| |
| //TODO: Do we need to treat this as error? |
| if (!mDeviceStatus.containsKey(info) |
| || !mUnavailablePhysicalDevices.containsKey(info)) { |
| Log.e(TAG, String.format("Camera %s is not present. Ignore physical camera " |
| + "status change", info.mCameraId)); |
| return; |
| } |
| |
| List<String> unavailablePhysicalDevices = mUnavailablePhysicalDevices.get(info); |
| if (!isAvailable(status) |
| && !unavailablePhysicalDevices.contains(physicalId)) { |
| unavailablePhysicalDevices.add(physicalId); |
| } else if (isAvailable(status) |
| && unavailablePhysicalDevices.contains(physicalId)) { |
| unavailablePhysicalDevices.remove(physicalId); |
| } else { |
| if (DEBUG) { |
| Log.v(TAG, |
| String.format( |
| "Physical camera device status was previously available (%b), " |
| + " and is now again available (%b)" |
| + "so no new client visible update will be sent", |
| !unavailablePhysicalDevices.contains(physicalId), |
| isAvailable(status))); |
| } |
| return; |
| } |
| |
| if (!physicalCallbacksAreEnabledForUnavailableCamera() |
| && !isAvailable(mDeviceStatus.get(info))) { |
| Log.i(TAG, String.format("Camera %s is not available. Ignore physical camera " |
| + "status change callback(s)", info.mCameraId)); |
| return; |
| } |
| |
| final int callbackCount = mCallbackMap.size(); |
| for (int i = 0; i < callbackCount; i++) { |
| final AvailabilityCallback callback = mCallbackMap.keyAt(i); |
| if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) { |
| continue; |
| } |
| |
| final Executor executor = mCallbackMap.valueAt(i); |
| postSingleUpdate(callback, executor, info.mCameraId, physicalId, status); |
| } |
| } // onPhysicalCameraStatusChangedLocked |
| |
| private void updateTorchCallbackLocked(TorchCallback callback, Executor executor) { |
| for (int i = 0; i < mTorchStatus.size(); i++) { |
| DeviceCameraInfo info = mTorchStatus.keyAt(i); |
| if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) { |
| continue; |
| } |
| |
| Integer status = mTorchStatus.valueAt(i); |
| postSingleTorchUpdate(callback, executor, info.mCameraId, status); |
| } |
| } |
| |
| private void onTorchStatusChangedLocked(int status, DeviceCameraInfo info) { |
| if (DEBUG) { |
| Log.v(TAG, String.format( |
| "Camera id %s has torch status changed to 0x%x for device %d", |
| info.mCameraId, status, info.mDeviceId)); |
| } |
| |
| if (!validTorchStatus(status)) { |
| Log.e(TAG, String.format( |
| "Ignoring invalid camera %s torch status 0x%x for device %d", |
| info.mCameraId, status, info.mDeviceId)); |
| return; |
| } |
| |
| Integer oldStatus = mTorchStatus.put(info, status); |
| if (oldStatus != null && oldStatus == status) { |
| if (DEBUG) { |
| Log.v(TAG, String.format( |
| "Torch status changed to 0x%x, which is what it already was", |
| status)); |
| } |
| return; |
| } |
| |
| final int callbackCount = mTorchCallbackMap.size(); |
| for (int i = 0; i < callbackCount; i++) { |
| final TorchCallback callback = mTorchCallbackMap.keyAt(i); |
| if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) { |
| continue; |
| } |
| |
| final Executor executor = mTorchCallbackMap.valueAt(i); |
| postSingleTorchUpdate(callback, executor, info.mCameraId, status); |
| } |
| } // onTorchStatusChangedLocked |
| |
| private void onTorchStrengthLevelChangedLocked(DeviceCameraInfo info, |
| int newStrengthLevel) { |
| if (DEBUG) { |
| Log.v(TAG, String.format( |
| "Camera id %s has torch strength level changed to %d for device %d", |
| info.mCameraId, newStrengthLevel, info.mDeviceId)); |
| } |
| |
| final int callbackCount = mTorchCallbackMap.size(); |
| for (int i = 0; i < callbackCount; i++) { |
| final TorchCallback callback = mTorchCallbackMap.keyAt(i); |
| if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) { |
| continue; |
| } |
| |
| final Executor executor = mTorchCallbackMap.valueAt(i); |
| postSingleTorchStrengthLevelUpdate(callback, executor, info.mCameraId, |
| newStrengthLevel); |
| } |
| } // onTorchStrengthLevelChanged |
| |
| /** |
| * Register a callback to be notified about camera device availability with the |
| * global listener singleton. |
| * |
| * @param callback the new callback to send camera availability notices to |
| * @param executor The executor which should invoke the callback. May not be null. |
| * @param hasOpenCloseListenerPermission whether the client has permission for |
| * onCameraOpened/onCameraClosed callback |
| */ |
| public void registerAvailabilityCallback(AvailabilityCallback callback, Executor executor, |
| boolean hasOpenCloseListenerPermission, int deviceId, int devicePolicy) { |
| synchronized (mLock) { |
| // In practice, this permission doesn't change. So we don't need one flag for each |
| // callback object. |
| mHasOpenCloseListenerPermission = hasOpenCloseListenerPermission; |
| connectCameraServiceLocked(); |
| |
| callback.mDeviceId = deviceId; |
| callback.mDevicePolicy = devicePolicy; |
| |
| Executor oldExecutor = mCallbackMap.put(callback, executor); |
| // For new callbacks, provide initial availability information |
| if (oldExecutor == null) { |
| updateCallbackLocked(callback, executor); |
| } |
| |
| // If not connected to camera service, schedule a reconnect to camera service. |
| if (mCameraService == null) { |
| scheduleCameraServiceReconnectionLocked(); |
| } |
| } |
| } |
| |
| /** |
| * Remove a previously-added callback; the callback will no longer receive connection and |
| * disconnection callbacks, and is no longer referenced by the global listener singleton. |
| * |
| * @param callback The callback to remove from the notification list |
| */ |
| public void unregisterAvailabilityCallback(AvailabilityCallback callback) { |
| synchronized (mLock) { |
| mCallbackMap.remove(callback); |
| } |
| } |
| |
| public void registerTorchCallback(TorchCallback callback, Executor executor, int deviceId, |
| int devicePolicy) { |
| synchronized(mLock) { |
| connectCameraServiceLocked(); |
| |
| callback.mDeviceId = deviceId; |
| callback.mDevicePolicy = devicePolicy; |
| |
| Executor oldExecutor = mTorchCallbackMap.put(callback, executor); |
| // For new callbacks, provide initial torch information |
| if (oldExecutor == null) { |
| updateTorchCallbackLocked(callback, executor); |
| } |
| |
| // If not connected to camera service, schedule a reconnect to camera service. |
| if (mCameraService == null) { |
| scheduleCameraServiceReconnectionLocked(); |
| } |
| } |
| } |
| |
| public void unregisterTorchCallback(TorchCallback callback) { |
| synchronized(mLock) { |
| mTorchCallbackMap.remove(callback); |
| } |
| } |
| |
| /** |
| * Callback from camera service notifying the process about camera availability changes |
| */ |
| @Override |
| public void onStatusChanged(int status, String cameraId, int deviceId) |
| throws RemoteException { |
| synchronized(mLock) { |
| onStatusChangedLocked(status, new DeviceCameraInfo(cameraId, deviceId)); |
| } |
| } |
| |
| @Override |
| public void onPhysicalCameraStatusChanged(int status, String cameraId, |
| String physicalCameraId, int deviceId) throws RemoteException { |
| synchronized (mLock) { |
| onPhysicalCameraStatusChangedLocked(status, |
| new DeviceCameraInfo(cameraId, deviceId), physicalCameraId); |
| } |
| } |
| |
| @Override |
| public void onTorchStatusChanged(int status, String cameraId, int deviceId) |
| throws RemoteException { |
| synchronized (mLock) { |
| onTorchStatusChangedLocked(status, new DeviceCameraInfo(cameraId, deviceId)); |
| } |
| } |
| |
| @Override |
| public void onTorchStrengthLevelChanged(String cameraId, int newStrengthLevel, int deviceId) |
| throws RemoteException { |
| synchronized (mLock) { |
| onTorchStrengthLevelChangedLocked(new DeviceCameraInfo(cameraId, deviceId), |
| newStrengthLevel); |
| } |
| } |
| |
| @Override |
| public void onCameraAccessPrioritiesChanged() { |
| synchronized (mLock) { |
| final int callbackCount = mCallbackMap.size(); |
| for (int i = 0; i < callbackCount; i++) { |
| Executor executor = mCallbackMap.valueAt(i); |
| final AvailabilityCallback callback = mCallbackMap.keyAt(i); |
| |
| postSingleAccessPriorityChangeUpdate(callback, executor); |
| } |
| } |
| } |
| |
| @Override |
| public void onCameraOpened(String cameraId, String clientPackageId, int deviceId) { |
| synchronized (mLock) { |
| onCameraOpenedLocked(new DeviceCameraInfo(cameraId, deviceId), clientPackageId); |
| } |
| } |
| |
| private void onCameraOpenedLocked(DeviceCameraInfo info, String clientPackageId) { |
| String oldApk = mOpenedDevices.put(info, clientPackageId); |
| |
| if (oldApk != null) { |
| if (oldApk.equals(clientPackageId)) { |
| Log.w(TAG, |
| "onCameraOpened was previously called for " + oldApk |
| + " and is now again called for the same package name, " |
| + "so no new client visible update will be sent"); |
| return; |
| } else { |
| Log.w(TAG, |
| "onCameraOpened was previously called for " + oldApk |
| + " and is now called for " + clientPackageId |
| + " without onCameraClosed being called first"); |
| } |
| } |
| |
| final int callbackCount = mCallbackMap.size(); |
| for (int i = 0; i < callbackCount; i++) { |
| final AvailabilityCallback callback = mCallbackMap.keyAt(i); |
| if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) { |
| continue; |
| } |
| |
| final Executor executor = mCallbackMap.valueAt(i); |
| postSingleCameraOpenedUpdate(callback, executor, info.mCameraId, clientPackageId); |
| } |
| } |
| |
| @Override |
| public void onCameraClosed(String cameraId, int deviceId) { |
| synchronized (mLock) { |
| onCameraClosedLocked(new DeviceCameraInfo(cameraId, deviceId)); |
| } |
| } |
| |
| private void onCameraClosedLocked(DeviceCameraInfo info) { |
| mOpenedDevices.remove(info); |
| |
| final int callbackCount = mCallbackMap.size(); |
| for (int i = 0; i < callbackCount; i++) { |
| final AvailabilityCallback callback = mCallbackMap.keyAt(i); |
| if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) { |
| continue; |
| } |
| |
| final Executor executor = mCallbackMap.valueAt(i); |
| postSingleCameraClosedUpdate(callback, executor, info.mCameraId); |
| } |
| } |
| |
| /** |
| * Try to connect to camera service after some delay if any client registered camera |
| * availability callback or torch status callback. |
| */ |
| private void scheduleCameraServiceReconnectionLocked() { |
| if (mCallbackMap.isEmpty() && mTorchCallbackMap.isEmpty()) { |
| // Not necessary to reconnect camera service if no client registers a callback. |
| return; |
| } |
| |
| if (DEBUG) { |
| Log.v(TAG, "Reconnecting Camera Service in " + CAMERA_SERVICE_RECONNECT_DELAY_MS + |
| " ms"); |
| } |
| |
| try { |
| mScheduler.schedule(() -> { |
| ICameraService cameraService = getCameraService(); |
| if (cameraService == null) { |
| synchronized(mLock) { |
| if (DEBUG) { |
| Log.v(TAG, "Reconnecting Camera Service failed."); |
| } |
| scheduleCameraServiceReconnectionLocked(); |
| } |
| } |
| }, CAMERA_SERVICE_RECONNECT_DELAY_MS, TimeUnit.MILLISECONDS); |
| } catch (RejectedExecutionException e) { |
| Log.e(TAG, "Failed to schedule camera service re-connect: " + e); |
| } |
| } |
| |
| /** |
| * Listener for camera service death. |
| * |
| * <p>The camera service isn't supposed to die under any normal circumstances, but can be |
| * turned off during debug, or crash due to bugs. So detect that and null out the interface |
| * object, so that the next calls to the manager can try to reconnect.</p> |
| */ |
| public void binderDied() { |
| synchronized(mLock) { |
| // Only do this once per service death |
| if (mCameraService == null) return; |
| |
| mCameraService = null; |
| |
| // Tell listeners that the cameras and torch modes are unavailable and schedule a |
| // reconnection to camera service. When camera service is reconnected, the camera |
| // and torch statuses will be updated. |
| // Iterate from the end to the beginning because onStatusChangedLocked removes |
| // entries from the ArrayMap. |
| for (int i = mDeviceStatus.size() - 1; i >= 0; i--) { |
| DeviceCameraInfo info = mDeviceStatus.keyAt(i); |
| onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, info); |
| |
| if (mHasOpenCloseListenerPermission) { |
| onCameraClosedLocked(info); |
| } |
| } |
| |
| for (int i = 0; i < mTorchStatus.size(); i++) { |
| DeviceCameraInfo info = mTorchStatus.keyAt(i); |
| onTorchStatusChangedLocked(ICameraServiceListener.TORCH_STATUS_NOT_AVAILABLE, |
| info); |
| } |
| |
| mConcurrentCameraIdCombinations.clear(); |
| |
| scheduleCameraServiceReconnectionLocked(); |
| } |
| } |
| |
| private static final class DeviceCameraInfo { |
| private final String mCameraId; |
| private final int mDeviceId; |
| |
| DeviceCameraInfo(String cameraId, int deviceId) { |
| mCameraId = cameraId; |
| mDeviceId = deviceId; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| DeviceCameraInfo that = (DeviceCameraInfo) o; |
| return mDeviceId == that.mDeviceId && Objects.equals(mCameraId, that.mCameraId); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(mCameraId, mDeviceId); |
| } |
| } |
| } // CameraManagerGlobal |
| } // CameraManager |