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.os.SystemProperties;
32import android.util.Log;
33import android.util.Size;
34import android.view.Surface;
35
36import java.lang.ref.WeakReference;
37import java.util.ArrayList;
38
39/**
40 * Manages the flashlight.
41 */
42public 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
69    public FlashlightController(Context mContext) {
70        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
71        initialize();
72    }
73
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
88    public synchronized void setFlashlight(boolean enabled) {
89        if (mFlashlightEnabled != enabled) {
90            mFlashlightEnabled = enabled;
91            postUpdateFlashlight();
92        }
93    }
94
95    public void killFlashlight() {
96        boolean enabled;
97        synchronized (this) {
98            enabled = mFlashlightEnabled;
99        }
100        if (enabled) {
101            mHandler.post(mKillFlashlightRunnable);
102        }
103    }
104
105    public synchronized boolean isAvailable() {
106        return mCameraAvailable;
107    }
108
109    public void addListener(FlashlightListener l) {
110        synchronized (mListeners) {
111            cleanUpListenersLocked(l);
112            mListeners.add(new WeakReference<>(l));
113        }
114    }
115
116    public void removeListener(FlashlightListener l) {
117        synchronized (mListeners) {
118            cleanUpListenersLocked(l);
119        }
120    }
121
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
130    private void startDevice() throws CameraAccessException {
131        mCameraManager.openCamera(getCameraId(), mCameraListener, mHandler);
132    }
133
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
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
161    private void postUpdateFlashlight() {
162        ensureHandler();
163        mHandler.post(mUpdateFlashlightRunnable);
164    }
165
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
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
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
229    private void handleError() {
230        synchronized (this) {
231            mFlashlightEnabled = false;
232        }
233        dispatchError();
234        dispatchOff();
235        updateFlashlight(true /* forceDisable */);
236    }
237
238    private void dispatchOff() {
239        dispatchListeners(DISPATCH_OFF, false /* argument (ignored) */);
240    }
241
242    private void dispatchError() {
243        dispatchListeners(DISPATCH_ERROR, false /* argument (ignored) */);
244    }
245
246    private void dispatchAvailabilityChanged(boolean available) {
247        dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available);
248    }
249
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
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         */
382        void onFlashlightOff();
383
384        /**
385         * Called when there is an error that turns the flashlight off.
386         */
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         */
393        void onFlashlightAvailabilityChanged(boolean available);
394    }
395}
396