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 17 package com.android.systemui.statusbar.policy; 18 19 import android.content.Context; 20 import android.graphics.SurfaceTexture; 21 import android.hardware.camera2.CameraAccessException; 22 import android.hardware.camera2.CameraCaptureSession; 23 import android.hardware.camera2.CameraCharacteristics; 24 import android.hardware.camera2.CameraDevice; 25 import android.hardware.camera2.CameraManager; 26 import android.hardware.camera2.CameraMetadata; 27 import android.hardware.camera2.CaptureRequest; 28 import android.os.Handler; 29 import android.os.HandlerThread; 30 import android.os.Process; 31 import android.os.SystemProperties; 32 import android.util.Log; 33 import android.util.Size; 34 import android.view.Surface; 35 36 import java.lang.ref.WeakReference; 37 import java.util.ArrayList; 38 39 /** 40 * Manages the flashlight. 41 */ 42 public class FlashlightController { 43 44 private static final String TAG = "FlashlightController"; 45 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 46 47 private static final int DISPATCH_ERROR = 0; 48 private static final int DISPATCH_OFF = 1; 49 private static final int DISPATCH_AVAILABILITY_CHANGED = 2; 50 51 private final CameraManager mCameraManager; 52 /** Call {@link #ensureHandler()} before using */ 53 private Handler mHandler; 54 55 /** Lock on mListeners when accessing */ 56 private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1); 57 58 /** Lock on {@code this} when accessing */ 59 private boolean mFlashlightEnabled; 60 61 private String mCameraId; 62 private boolean mCameraAvailable; 63 private CameraDevice mCameraDevice; 64 private CaptureRequest mFlashlightRequest; 65 private CameraCaptureSession mSession; 66 private SurfaceTexture mSurfaceTexture; 67 private Surface mSurface; 68 FlashlightController(Context mContext)69 public FlashlightController(Context mContext) { 70 mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); 71 initialize(); 72 } 73 initialize()74 public void initialize() { 75 try { 76 mCameraId = getCameraId(); 77 } catch (Throwable e) { 78 Log.e(TAG, "Couldn't initialize.", e); 79 return; 80 } 81 82 if (mCameraId != null) { 83 ensureHandler(); 84 mCameraManager.registerAvailabilityCallback(mAvailabilityCallback, mHandler); 85 } 86 } 87 setFlashlight(boolean enabled)88 public synchronized void setFlashlight(boolean enabled) { 89 if (mFlashlightEnabled != enabled) { 90 mFlashlightEnabled = enabled; 91 postUpdateFlashlight(); 92 } 93 } 94 killFlashlight()95 public void killFlashlight() { 96 boolean enabled; 97 synchronized (this) { 98 enabled = mFlashlightEnabled; 99 } 100 if (enabled) { 101 mHandler.post(mKillFlashlightRunnable); 102 } 103 } 104 isAvailable()105 public synchronized boolean isAvailable() { 106 return mCameraAvailable; 107 } 108 addListener(FlashlightListener l)109 public void addListener(FlashlightListener l) { 110 synchronized (mListeners) { 111 cleanUpListenersLocked(l); 112 mListeners.add(new WeakReference<>(l)); 113 } 114 } 115 removeListener(FlashlightListener l)116 public void removeListener(FlashlightListener l) { 117 synchronized (mListeners) { 118 cleanUpListenersLocked(l); 119 } 120 } 121 ensureHandler()122 private synchronized void ensureHandler() { 123 if (mHandler == null) { 124 HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); 125 thread.start(); 126 mHandler = new Handler(thread.getLooper()); 127 } 128 } 129 startDevice()130 private void startDevice() throws CameraAccessException { 131 mCameraManager.openCamera(getCameraId(), mCameraListener, mHandler); 132 } 133 startSession()134 private void startSession() throws CameraAccessException { 135 mSurfaceTexture = new SurfaceTexture(false); 136 Size size = getSmallestSize(mCameraDevice.getId()); 137 mSurfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight()); 138 mSurface = new Surface(mSurfaceTexture); 139 ArrayList<Surface> outputs = new ArrayList<>(1); 140 outputs.add(mSurface); 141 mCameraDevice.createCaptureSession(outputs, mSessionListener, mHandler); 142 } 143 getSmallestSize(String cameraId)144 private Size getSmallestSize(String cameraId) throws CameraAccessException { 145 Size[] outputSizes = mCameraManager.getCameraCharacteristics(cameraId) 146 .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) 147 .getOutputSizes(SurfaceTexture.class); 148 if (outputSizes == null || outputSizes.length == 0) { 149 throw new IllegalStateException( 150 "Camera " + cameraId + "doesn't support any outputSize."); 151 } 152 Size chosen = outputSizes[0]; 153 for (Size s : outputSizes) { 154 if (chosen.getWidth() >= s.getWidth() && chosen.getHeight() >= s.getHeight()) { 155 chosen = s; 156 } 157 } 158 return chosen; 159 } 160 postUpdateFlashlight()161 private void postUpdateFlashlight() { 162 ensureHandler(); 163 mHandler.post(mUpdateFlashlightRunnable); 164 } 165 getCameraId()166 private String getCameraId() throws CameraAccessException { 167 String[] ids = mCameraManager.getCameraIdList(); 168 for (String id : ids) { 169 CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id); 170 Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); 171 Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING); 172 if (flashAvailable != null && flashAvailable 173 && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) { 174 return id; 175 } 176 } 177 return null; 178 } 179 updateFlashlight(boolean forceDisable)180 private void updateFlashlight(boolean forceDisable) { 181 try { 182 boolean enabled; 183 synchronized (this) { 184 enabled = mFlashlightEnabled && !forceDisable; 185 } 186 if (enabled) { 187 if (mCameraDevice == null) { 188 startDevice(); 189 return; 190 } 191 if (mSession == null) { 192 startSession(); 193 return; 194 } 195 if (mFlashlightRequest == null) { 196 CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest( 197 CameraDevice.TEMPLATE_PREVIEW); 198 builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH); 199 builder.addTarget(mSurface); 200 CaptureRequest request = builder.build(); 201 mSession.capture(request, null, mHandler); 202 mFlashlightRequest = request; 203 } 204 } else { 205 if (mCameraDevice != null) { 206 mCameraDevice.close(); 207 teardown(); 208 } 209 } 210 211 } catch (CameraAccessException|IllegalStateException|UnsupportedOperationException e) { 212 Log.e(TAG, "Error in updateFlashlight", e); 213 handleError(); 214 } 215 } 216 teardown()217 private void teardown() { 218 mCameraDevice = null; 219 mSession = null; 220 mFlashlightRequest = null; 221 if (mSurface != null) { 222 mSurface.release(); 223 mSurfaceTexture.release(); 224 } 225 mSurface = null; 226 mSurfaceTexture = null; 227 } 228 handleError()229 private void handleError() { 230 synchronized (this) { 231 mFlashlightEnabled = false; 232 } 233 dispatchError(); 234 dispatchOff(); 235 updateFlashlight(true /* forceDisable */); 236 } 237 dispatchOff()238 private void dispatchOff() { 239 dispatchListeners(DISPATCH_OFF, false /* argument (ignored) */); 240 } 241 dispatchError()242 private void dispatchError() { 243 dispatchListeners(DISPATCH_ERROR, false /* argument (ignored) */); 244 } 245 dispatchAvailabilityChanged(boolean available)246 private void dispatchAvailabilityChanged(boolean available) { 247 dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available); 248 } 249 dispatchListeners(int message, boolean argument)250 private void dispatchListeners(int message, boolean argument) { 251 synchronized (mListeners) { 252 final int N = mListeners.size(); 253 boolean cleanup = false; 254 for (int i = 0; i < N; i++) { 255 FlashlightListener l = mListeners.get(i).get(); 256 if (l != null) { 257 if (message == DISPATCH_ERROR) { 258 l.onFlashlightError(); 259 } else if (message == DISPATCH_OFF) { 260 l.onFlashlightOff(); 261 } else if (message == DISPATCH_AVAILABILITY_CHANGED) { 262 l.onFlashlightAvailabilityChanged(argument); 263 } 264 } else { 265 cleanup = true; 266 } 267 } 268 if (cleanup) { 269 cleanUpListenersLocked(null); 270 } 271 } 272 } 273 cleanUpListenersLocked(FlashlightListener listener)274 private void cleanUpListenersLocked(FlashlightListener listener) { 275 for (int i = mListeners.size() - 1; i >= 0; i--) { 276 FlashlightListener found = mListeners.get(i).get(); 277 if (found == null || found == listener) { 278 mListeners.remove(i); 279 } 280 } 281 } 282 283 private final CameraDevice.StateListener mCameraListener = new CameraDevice.StateListener() { 284 @Override 285 public void onOpened(CameraDevice camera) { 286 mCameraDevice = camera; 287 postUpdateFlashlight(); 288 } 289 290 @Override 291 public void onDisconnected(CameraDevice camera) { 292 if (mCameraDevice == camera) { 293 dispatchOff(); 294 teardown(); 295 } 296 } 297 298 @Override 299 public void onError(CameraDevice camera, int error) { 300 Log.e(TAG, "Camera error: camera=" + camera + " error=" + error); 301 if (camera == mCameraDevice || mCameraDevice == null) { 302 handleError(); 303 } 304 } 305 }; 306 307 private final CameraCaptureSession.StateListener mSessionListener = 308 new CameraCaptureSession.StateListener() { 309 @Override 310 public void onConfigured(CameraCaptureSession session) { 311 if (session.getDevice() == mCameraDevice) { 312 mSession = session; 313 } else { 314 session.close(); 315 } 316 postUpdateFlashlight(); 317 } 318 319 @Override 320 public void onConfigureFailed(CameraCaptureSession session) { 321 Log.e(TAG, "Configure failed."); 322 if (mSession == null || mSession == session) { 323 handleError(); 324 } 325 } 326 }; 327 328 private final Runnable mUpdateFlashlightRunnable = new Runnable() { 329 @Override 330 public void run() { 331 updateFlashlight(false /* forceDisable */); 332 } 333 }; 334 335 private final Runnable mKillFlashlightRunnable = new Runnable() { 336 @Override 337 public void run() { 338 synchronized (this) { 339 mFlashlightEnabled = false; 340 } 341 updateFlashlight(true /* forceDisable */); 342 dispatchOff(); 343 } 344 }; 345 346 private final CameraManager.AvailabilityCallback mAvailabilityCallback = 347 new CameraManager.AvailabilityCallback() { 348 @Override 349 public void onCameraAvailable(String cameraId) { 350 if (DEBUG) Log.d(TAG, "onCameraAvailable(" + cameraId + ")"); 351 if (cameraId.equals(mCameraId)) { 352 setCameraAvailable(true); 353 } 354 } 355 356 @Override 357 public void onCameraUnavailable(String cameraId) { 358 if (DEBUG) Log.d(TAG, "onCameraUnavailable(" + cameraId + ")"); 359 if (cameraId.equals(mCameraId)) { 360 setCameraAvailable(false); 361 } 362 } 363 364 private void setCameraAvailable(boolean available) { 365 boolean changed; 366 synchronized (FlashlightController.this) { 367 changed = mCameraAvailable != available; 368 mCameraAvailable = available; 369 } 370 if (changed) { 371 if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")"); 372 dispatchAvailabilityChanged(available); 373 } 374 } 375 }; 376 377 public interface FlashlightListener { 378 379 /** 380 * Called when the flashlight turns off unexpectedly. 381 */ onFlashlightOff()382 void onFlashlightOff(); 383 384 /** 385 * Called when there is an error that turns the flashlight off. 386 */ onFlashlightError()387 void onFlashlightError(); 388 389 /** 390 * Called when there is a change in availability of the flashlight functionality 391 * @param available true if the flashlight is currently available. 392 */ onFlashlightAvailabilityChanged(boolean available)393 void onFlashlightAvailabilityChanged(boolean available); 394 } 395 } 396