3328 lines
154 KiB
Java
3328 lines
154 KiB
Java
/*
|
|
* 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
|