FlashlightController.java revision b83777ba5c12295224cd566bba59a44b8860e449
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.systemui.statusbar.policy; 18 19import android.content.Context; 20import android.graphics.SurfaceTexture; 21import android.hardware.camera2.CameraAccessException; 22import android.hardware.camera2.CameraCaptureSession; 23import android.hardware.camera2.CameraCharacteristics; 24import android.hardware.camera2.CameraDevice; 25import android.hardware.camera2.CameraManager; 26import android.hardware.camera2.CameraMetadata; 27import android.hardware.camera2.CaptureRequest; 28import android.os.Handler; 29import android.os.HandlerThread; 30import android.os.Process; 31import android.util.Log; 32import android.util.Size; 33import android.view.Surface; 34 35import java.lang.ref.WeakReference; 36import java.util.ArrayList; 37 38/** 39 * Manages the flashlight. 40 */ 41public class FlashlightController { 42 43 private static final String TAG = "FlashlightController"; 44 45 private final CameraManager mCameraManager; 46 /** Call {@link #ensureHandler()} before using */ 47 private Handler mHandler; 48 49 /** Lock on mListeners when accessing */ 50 private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1); 51 52 /** Lock on {@code this} when accessing */ 53 private boolean mFlashlightEnabled; 54 55 private CameraDevice mCameraDevice; 56 private CaptureRequest mFlashlightRequest; 57 private CameraCaptureSession mSession; 58 private SurfaceTexture mSurfaceTexture; 59 private Surface mSurface; 60 61 public FlashlightController(Context mContext) { 62 mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); 63 } 64 65 public synchronized void setFlashlight(boolean enabled) { 66 if (mFlashlightEnabled != enabled) { 67 mFlashlightEnabled = enabled; 68 postUpdateFlashlight(); 69 } 70 } 71 72 public boolean isAvailable() { 73 try { 74 return getCameraId() != null; 75 } catch (CameraAccessException e) { 76 return false; 77 } 78 } 79 80 public void addListener(FlashlightListener l) { 81 synchronized (mListeners) { 82 cleanUpListenersLocked(l); 83 mListeners.add(new WeakReference<>(l)); 84 } 85 } 86 87 public void removeListener(FlashlightListener l) { 88 synchronized (mListeners) { 89 cleanUpListenersLocked(l); 90 } 91 } 92 93 private synchronized void ensureHandler() { 94 if (mHandler == null) { 95 HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); 96 thread.start(); 97 mHandler = new Handler(thread.getLooper()); 98 } 99 } 100 101 private void startDevice() throws CameraAccessException { 102 mCameraManager.openCamera(getCameraId(), mCameraListener, mHandler); 103 } 104 105 private void startSession() throws CameraAccessException { 106 mSurfaceTexture = new SurfaceTexture(false); 107 Size size = getSmallestSize(mCameraDevice.getId()); 108 mSurfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight()); 109 mSurface = new Surface(mSurfaceTexture); 110 ArrayList<Surface> outputs = new ArrayList<>(1); 111 outputs.add(mSurface); 112 mCameraDevice.createCaptureSession(outputs, mSessionListener, mHandler); 113 } 114 115 private Size getSmallestSize(String cameraId) throws CameraAccessException { 116 Size[] outputSizes = mCameraManager.getCameraCharacteristics(cameraId) 117 .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) 118 .getOutputSizes(SurfaceTexture.class); 119 if (outputSizes == null || outputSizes.length == 0) { 120 throw new IllegalStateException( 121 "Camera " + cameraId + "doesn't support any outputSize."); 122 } 123 Size chosen = outputSizes[0]; 124 for (Size s : outputSizes) { 125 if (chosen.getWidth() >= s.getWidth() && chosen.getHeight() >= s.getHeight()) { 126 chosen = s; 127 } 128 } 129 return chosen; 130 } 131 132 private void postUpdateFlashlight() { 133 ensureHandler(); 134 mHandler.post(mUpdateFlashlightRunnable); 135 } 136 137 private String getCameraId() throws CameraAccessException { 138 String[] ids = mCameraManager.getCameraIdList(); 139 for (String id : ids) { 140 CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id); 141 Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); 142 Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING); 143 if (flashAvailable != null && flashAvailable 144 && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) { 145 return id; 146 } 147 } 148 return null; 149 } 150 151 private void updateFlashlight(boolean forceDisable) { 152 try { 153 boolean enabled; 154 synchronized (this) { 155 enabled = mFlashlightEnabled && !forceDisable; 156 } 157 if (enabled) { 158 if (mCameraDevice == null) { 159 startDevice(); 160 return; 161 } 162 if (mSession == null) { 163 startSession(); 164 return; 165 } 166 if (mFlashlightRequest == null) { 167 CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest( 168 CameraDevice.TEMPLATE_PREVIEW); 169 builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH); 170 builder.addTarget(mSurface); 171 CaptureRequest request = builder.build(); 172 mSession.capture(request, null, mHandler); 173 mFlashlightRequest = request; 174 } 175 } else { 176 if (mCameraDevice != null) { 177 mCameraDevice.close(); 178 teardown(); 179 } 180 } 181 182 } catch (CameraAccessException|IllegalStateException|UnsupportedOperationException e) { 183 Log.e(TAG, "Error in updateFlashlight", e); 184 handleError(); 185 } 186 } 187 188 private void teardown() { 189 mCameraDevice = null; 190 mSession = null; 191 mFlashlightRequest = null; 192 if (mSurface != null) { 193 mSurface.release(); 194 mSurfaceTexture.release(); 195 } 196 mSurface = null; 197 mSurfaceTexture = null; 198 } 199 200 private void handleError() { 201 synchronized (this) { 202 mFlashlightEnabled = false; 203 } 204 dispatchError(); 205 dispatchOff(); 206 updateFlashlight(true /* forceDisable */); 207 } 208 209 private void dispatchOff() { 210 dispatchListeners(false, true /* off */); 211 } 212 213 private void dispatchError() { 214 dispatchListeners(true /* error */, false); 215 } 216 217 private void dispatchListeners(boolean error, boolean off) { 218 synchronized (mListeners) { 219 final int N = mListeners.size(); 220 boolean cleanup = false; 221 for (int i = 0; i < N; i++) { 222 FlashlightListener l = mListeners.get(i).get(); 223 if (l != null) { 224 if (error) { 225 l.onFlashlightError(); 226 } else if (off) { 227 l.onFlashlightOff(); 228 } 229 } else { 230 cleanup = true; 231 } 232 } 233 if (cleanup) { 234 cleanUpListenersLocked(null); 235 } 236 } 237 } 238 239 private void cleanUpListenersLocked(FlashlightListener listener) { 240 for (int i = mListeners.size() - 1; i >= 0; i--) { 241 FlashlightListener found = mListeners.get(i).get(); 242 if (found == null || found == listener) { 243 mListeners.remove(i); 244 } 245 } 246 } 247 248 private final CameraDevice.StateListener mCameraListener = new CameraDevice.StateListener() { 249 @Override 250 public void onOpened(CameraDevice camera) { 251 mCameraDevice = camera; 252 postUpdateFlashlight(); 253 } 254 255 @Override 256 public void onDisconnected(CameraDevice camera) { 257 if (mCameraDevice == camera) { 258 dispatchOff(); 259 teardown(); 260 } 261 } 262 263 @Override 264 public void onError(CameraDevice camera, int error) { 265 Log.e(TAG, "Camera error: camera=" + camera + " error=" + error); 266 if (camera == mCameraDevice || mCameraDevice == null) { 267 handleError(); 268 } 269 } 270 }; 271 272 private final CameraCaptureSession.StateListener mSessionListener = 273 new CameraCaptureSession.StateListener() { 274 @Override 275 public void onConfigured(CameraCaptureSession session) { 276 mSession = session; 277 postUpdateFlashlight(); 278 } 279 280 @Override 281 public void onConfigureFailed(CameraCaptureSession session) { 282 Log.e(TAG, "Configure failed."); 283 if (mSession == null || mSession == session) { 284 handleError(); 285 } 286 } 287 }; 288 289 private final Runnable mUpdateFlashlightRunnable = new Runnable() { 290 @Override 291 public void run() { 292 updateFlashlight(false /* forceDisable */); 293 } 294 }; 295 296 public interface FlashlightListener { 297 298 /** 299 * Called when the flashlight turns off unexpectedly. 300 */ 301 void onFlashlightOff(); 302 303 /** 304 * Called when there is an error that turns the flashlight off. 305 */ 306 void onFlashlightError(); 307 } 308} 309