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