/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.statusbar.policy; import android.content.Context; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureRequest; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.os.SystemProperties; import android.util.Log; import android.util.Size; import android.view.Surface; import java.lang.ref.WeakReference; import java.util.ArrayList; /** * Manages the flashlight. */ public class FlashlightController { private static final String TAG = "FlashlightController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int DISPATCH_ERROR = 0; private static final int DISPATCH_OFF = 1; private static final int DISPATCH_AVAILABILITY_CHANGED = 2; private final CameraManager mCameraManager; /** Call {@link #ensureHandler()} before using */ private Handler mHandler; /** Lock on mListeners when accessing */ private final ArrayList> mListeners = new ArrayList<>(1); /** Lock on {@code this} when accessing */ private boolean mFlashlightEnabled; private String mCameraId; private boolean mCameraAvailable; private CameraDevice mCameraDevice; private CaptureRequest mFlashlightRequest; private CameraCaptureSession mSession; private SurfaceTexture mSurfaceTexture; private Surface mSurface; public FlashlightController(Context mContext) { mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); initialize(); } public void initialize() { try { mCameraId = getCameraId(); } catch (Throwable e) { Log.e(TAG, "Couldn't initialize.", e); return; } if (mCameraId != null) { ensureHandler(); mCameraManager.registerAvailabilityCallback(mAvailabilityCallback, mHandler); } } public synchronized void setFlashlight(boolean enabled) { if (mFlashlightEnabled != enabled) { mFlashlightEnabled = enabled; postUpdateFlashlight(); } } public void killFlashlight() { boolean enabled; synchronized (this) { enabled = mFlashlightEnabled; } if (enabled) { mHandler.post(mKillFlashlightRunnable); } } public synchronized boolean isAvailable() { return mCameraAvailable; } public void addListener(FlashlightListener l) { synchronized (mListeners) { cleanUpListenersLocked(l); mListeners.add(new WeakReference<>(l)); } } public void removeListener(FlashlightListener l) { synchronized (mListeners) { cleanUpListenersLocked(l); } } private synchronized void ensureHandler() { if (mHandler == null) { HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mHandler = new Handler(thread.getLooper()); } } private void startDevice() throws CameraAccessException { mCameraManager.openCamera(getCameraId(), mCameraListener, mHandler); } private void startSession() throws CameraAccessException { mSurfaceTexture = new SurfaceTexture(false); Size size = getSmallestSize(mCameraDevice.getId()); mSurfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight()); mSurface = new Surface(mSurfaceTexture); ArrayList outputs = new ArrayList<>(1); outputs.add(mSurface); mCameraDevice.createCaptureSession(outputs, mSessionListener, mHandler); } private Size getSmallestSize(String cameraId) throws CameraAccessException { Size[] outputSizes = mCameraManager.getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(SurfaceTexture.class); if (outputSizes == null || outputSizes.length == 0) { throw new IllegalStateException( "Camera " + cameraId + "doesn't support any outputSize."); } Size chosen = outputSizes[0]; for (Size s : outputSizes) { if (chosen.getWidth() >= s.getWidth() && chosen.getHeight() >= s.getHeight()) { chosen = s; } } return chosen; } private void postUpdateFlashlight() { ensureHandler(); mHandler.post(mUpdateFlashlightRunnable); } private String getCameraId() throws CameraAccessException { String[] ids = mCameraManager.getCameraIdList(); for (String id : ids) { CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id); Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING); if (flashAvailable != null && flashAvailable && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) { return id; } } return null; } private void updateFlashlight(boolean forceDisable) { try { boolean enabled; synchronized (this) { enabled = mFlashlightEnabled && !forceDisable; } if (enabled) { if (mCameraDevice == null) { startDevice(); return; } if (mSession == null) { startSession(); return; } if (mFlashlightRequest == null) { CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW); builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH); builder.addTarget(mSurface); CaptureRequest request = builder.build(); mSession.capture(request, null, mHandler); mFlashlightRequest = request; } } else { if (mCameraDevice != null) { mCameraDevice.close(); teardown(); } } } catch (CameraAccessException|IllegalStateException|UnsupportedOperationException e) { Log.e(TAG, "Error in updateFlashlight", e); handleError(); } } private void teardown() { mCameraDevice = null; mSession = null; mFlashlightRequest = null; if (mSurface != null) { mSurface.release(); mSurfaceTexture.release(); } mSurface = null; mSurfaceTexture = null; } private void handleError() { synchronized (this) { mFlashlightEnabled = false; } dispatchError(); dispatchOff(); updateFlashlight(true /* forceDisable */); } private void dispatchOff() { dispatchListeners(DISPATCH_OFF, false /* argument (ignored) */); } private void dispatchError() { dispatchListeners(DISPATCH_ERROR, false /* argument (ignored) */); } private void dispatchAvailabilityChanged(boolean available) { dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available); } private void dispatchListeners(int message, boolean argument) { synchronized (mListeners) { final int N = mListeners.size(); boolean cleanup = false; for (int i = 0; i < N; i++) { FlashlightListener l = mListeners.get(i).get(); if (l != null) { if (message == DISPATCH_ERROR) { l.onFlashlightError(); } else if (message == DISPATCH_OFF) { l.onFlashlightOff(); } else if (message == DISPATCH_AVAILABILITY_CHANGED) { l.onFlashlightAvailabilityChanged(argument); } } else { cleanup = true; } } if (cleanup) { cleanUpListenersLocked(null); } } } private void cleanUpListenersLocked(FlashlightListener listener) { for (int i = mListeners.size() - 1; i >= 0; i--) { FlashlightListener found = mListeners.get(i).get(); if (found == null || found == listener) { mListeners.remove(i); } } } private final CameraDevice.StateListener mCameraListener = new CameraDevice.StateListener() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; postUpdateFlashlight(); } @Override public void onDisconnected(CameraDevice camera) { if (mCameraDevice == camera) { dispatchOff(); teardown(); } } @Override public void onError(CameraDevice camera, int error) { Log.e(TAG, "Camera error: camera=" + camera + " error=" + error); if (camera == mCameraDevice || mCameraDevice == null) { handleError(); } } }; private final CameraCaptureSession.StateListener mSessionListener = new CameraCaptureSession.StateListener() { @Override public void onConfigured(CameraCaptureSession session) { if (session.getDevice() == mCameraDevice) { mSession = session; } else { session.close(); } postUpdateFlashlight(); } @Override public void onConfigureFailed(CameraCaptureSession session) { Log.e(TAG, "Configure failed."); if (mSession == null || mSession == session) { handleError(); } } }; private final Runnable mUpdateFlashlightRunnable = new Runnable() { @Override public void run() { updateFlashlight(false /* forceDisable */); } }; private final Runnable mKillFlashlightRunnable = new Runnable() { @Override public void run() { synchronized (this) { mFlashlightEnabled = false; } updateFlashlight(true /* forceDisable */); dispatchOff(); } }; private final CameraManager.AvailabilityCallback mAvailabilityCallback = new CameraManager.AvailabilityCallback() { @Override public void onCameraAvailable(String cameraId) { if (DEBUG) Log.d(TAG, "onCameraAvailable(" + cameraId + ")"); if (cameraId.equals(mCameraId)) { setCameraAvailable(true); } } @Override public void onCameraUnavailable(String cameraId) { if (DEBUG) Log.d(TAG, "onCameraUnavailable(" + cameraId + ")"); if (cameraId.equals(mCameraId)) { setCameraAvailable(false); } } private void setCameraAvailable(boolean available) { boolean changed; synchronized (FlashlightController.this) { changed = mCameraAvailable != available; mCameraAvailable = available; } if (changed) { if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")"); dispatchAvailabilityChanged(available); } } }; public interface FlashlightListener { /** * Called when the flashlight turns off unexpectedly. */ void onFlashlightOff(); /** * Called when there is an error that turns the flashlight off. */ void onFlashlightError(); /** * Called when there is a change in availability of the flashlight functionality * @param available true if the flashlight is currently available. */ void onFlashlightAvailabilityChanged(boolean available); } }