Merge "Camera: Torch strength control API."
diff --git a/core/api/current.txt b/core/api/current.txt
index ee44198..08adbcb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18528,6 +18528,7 @@
     method @NonNull public android.hardware.camera2.CameraExtensionCharacteristics getCameraExtensionCharacteristics(@NonNull String) throws android.hardware.camera2.CameraAccessException;
     method @NonNull public String[] getCameraIdList() throws android.hardware.camera2.CameraAccessException;
     method @NonNull public java.util.Set<java.util.Set<java.lang.String>> getConcurrentCameraIds() throws android.hardware.camera2.CameraAccessException;
+    method public int getTorchStrengthLevel(@NonNull String) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(android.Manifest.permission.CAMERA) public boolean isConcurrentSessionConfigurationSupported(@NonNull java.util.Map<java.lang.String,android.hardware.camera2.params.SessionConfiguration>) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull android.hardware.camera2.CameraDevice.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
@@ -18536,6 +18537,7 @@
     method public void registerTorchCallback(@NonNull android.hardware.camera2.CameraManager.TorchCallback, @Nullable android.os.Handler);
     method public void registerTorchCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraManager.TorchCallback);
     method public void setTorchMode(@NonNull String, boolean) throws android.hardware.camera2.CameraAccessException;
+    method public void turnOnTorchWithStrengthLevel(@NonNull String, int) throws android.hardware.camera2.CameraAccessException;
     method public void unregisterAvailabilityCallback(@NonNull android.hardware.camera2.CameraManager.AvailabilityCallback);
     method public void unregisterTorchCallback(@NonNull android.hardware.camera2.CameraManager.TorchCallback);
   }
@@ -18553,6 +18555,7 @@
     ctor public CameraManager.TorchCallback();
     method public void onTorchModeChanged(@NonNull String, boolean);
     method public void onTorchModeUnavailable(@NonNull String);
+    method public void onTorchStrengthLevelChanged(@NonNull String, int);
   }
 
   public abstract class CameraMetadata<TKey> {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 93f1d61..c12e819 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1036,6 +1036,95 @@
     }
 
     /**
+     * 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);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
      * 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
@@ -1239,6 +1328,24 @@
         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
+        }
     }
 
     /**
@@ -1642,6 +1749,10 @@
                 public void onTorchStatusChanged(int status, String id) throws RemoteException {
                 }
                 @Override
+                public void onTorchStrengthLevelChanged(String id, int newStrengthLevel)
+                        throws RemoteException {
+                }
+                @Override
                 public void onCameraAccessPrioritiesChanged() {
                 }
                 @Override
@@ -1825,6 +1936,57 @@
             }
         }
 
+        public void turnOnTorchWithStrengthLevel(String cameraId, int torchStrength) 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);
+                } catch(ServiceSpecificException e) {
+                    throwAsPublicException(e);
+                } catch (RemoteException e) {
+                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+            }
+        }
+
+        public int getTorchStrengthLevel(String cameraId) throws CameraAccessException {
+            int torchStrength = 0;
+            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);
+                } catch(ServiceSpecificException e) {
+                    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:
@@ -1984,6 +2146,18 @@
             }
         }
 
+        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.
@@ -2167,6 +2341,22 @@
             }
         } // onTorchStatusChangedLocked
 
+        private void onTorchStrengthLevelChangedLocked(String cameraId, int newStrengthLevel) {
+            if (DEBUG) {
+
+                Log.v(TAG,
+                        String.format("Camera id %s has torch strength level changed to %d",
+                            cameraId, newStrengthLevel));
+            }
+
+            final int callbackCount = mTorchCallbackMap.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final Executor executor = mTorchCallbackMap.valueAt(i);
+                final TorchCallback callback = mTorchCallbackMap.keyAt(i);
+                postSingleTorchStrengthLevelUpdate(callback, executor, cameraId, newStrengthLevel);
+            }
+        } // onTorchStrengthLevelChanged
+
         /**
          * Register a callback to be notified about camera device availability with the
          * global listener singleton.
@@ -2258,6 +2448,14 @@
         }
 
         @Override
+        public void onTorchStrengthLevelChanged(String cameraId, int newStrengthLevel)
+                throws RemoteException {
+            synchronized (mLock) {
+                onTorchStrengthLevelChangedLocked(cameraId, newStrengthLevel);
+            }
+        }
+
+        @Override
         public void onCameraAccessPrioritiesChanged() {
             synchronized (mLock) {
                 final int callbackCount = mCallbackMap.size();
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 6dc05ad..a2eae2c 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -303,6 +303,11 @@
         public void onCameraClosed(String cameraId) {
             Log.v(TAG, String.format("Camera %s is closed", cameraId));
         }
+        @Override
+        public void onTorchStrengthLevelChanged(String cameraId, int torchStrength) {
+            Log.v(TAG, String.format("Camera " + cameraId + " torch strength level changed to "
+                    + torchStrength ));
+        }
     }
 
     /**