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