1/*
2 * Copyright (C) 2015 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.camera.device;
18
19import android.annotation.TargetApi;
20import android.hardware.camera2.CameraAccessException;
21import android.hardware.camera2.CameraDevice;
22import android.hardware.camera2.CameraManager;
23import android.os.Build.VERSION_CODES;
24import android.os.Handler;
25
26import com.android.camera.async.HandlerFactory;
27import com.android.camera.async.Lifetime;
28import com.android.camera.debug.Log.Tag;
29import com.android.camera.debug.Logger;
30
31import java.util.concurrent.Executor;
32
33import javax.annotation.ParametersAreNonnullByDefault;
34
35/**
36 * Set of device actions for opening and closing a single Camera2 device.
37 */
38@TargetApi(VERSION_CODES.LOLLIPOP)
39@ParametersAreNonnullByDefault
40public class Camera2Actions implements SingleDeviceActions<CameraDevice> {
41    private static final Tag TAG = new Tag("Camera2Act");
42
43    private final CameraDeviceKey mId;
44    private final CameraManager mCameraManager;
45    private final HandlerFactory mHandlerFactory;
46    private final Executor mBackgroundExecutor;
47    private final Logger mLogger;
48
49    public Camera2Actions(CameraDeviceKey id,
50          CameraManager cameraManager,
51          Executor backgroundExecutor,
52          HandlerFactory handlerFactory,
53          Logger.Factory logFactory) {
54        mId = id;
55        mCameraManager = cameraManager;
56        mBackgroundExecutor = backgroundExecutor;
57        mHandlerFactory = handlerFactory;
58        mLogger = logFactory.create(TAG);
59        mLogger.d("Created Camera2Request");
60    }
61
62    @Override
63    public void executeOpen(SingleDeviceOpenListener<CameraDevice> openListener,
64          Lifetime deviceLifetime) throws UnsupportedOperationException {
65        mLogger.i("executeOpen(id: " + mId.getCameraId() + ")");
66        mBackgroundExecutor.execute(new OpenCameraRunnable(mCameraManager,
67              mId.getCameraId().getValue(),
68              // TODO THIS IS BAD. If there are multiple requests to open,
69              // we don't want to add the handler to the lifetime until after
70              // the camera device is opened or the camera could be opened with
71              // an invalid thread.
72              mHandlerFactory.create(deviceLifetime, "Camera2 Lifetime"),
73              openListener, mLogger));
74    }
75
76    @Override
77    public void executeClose(SingleDeviceCloseListener closeListener, CameraDevice device)
78          throws UnsupportedOperationException {
79        mLogger.i("executeClose(" + device.getId() + ")");
80        mBackgroundExecutor.execute(new CloseCameraRunnable(device, closeListener, mLogger));
81    }
82
83    /**
84     * Internal runnable that executes a CameraManager openCamera call.
85     */
86    private static class OpenCameraRunnable implements Runnable {
87        private final SingleDeviceOpenListener<CameraDevice> mOpenListener;
88        private final String mCameraId;
89        private final Handler mHandler;
90        private final CameraManager mCameraManager;
91        private final Logger mLogger;
92
93        public OpenCameraRunnable(CameraManager cameraManager, String cameraId,
94              Handler handler, SingleDeviceOpenListener<CameraDevice> openListener,
95              Logger logger) {
96            mCameraManager = cameraManager;
97            mCameraId = cameraId;
98            mHandler = handler;
99            mOpenListener = openListener;
100            mLogger = logger;
101        }
102
103        @Override
104        public void run() {
105            try {
106                mLogger.i("mCameraManager.openCamera(id: " + mCameraId + ")");
107                mCameraManager.openCamera(mCameraId, new OpenCameraStateCallback(mOpenListener,
108                            mLogger), mHandler);
109            } catch (CameraAccessException | SecurityException | IllegalArgumentException e) {
110                mLogger.e("There was a problem opening camera " + mCameraId, e);
111                mOpenListener.onDeviceOpenException(e);
112            }
113        }
114    }
115
116    /**
117     * Internal runnable that executes a close on a cameraDevice.
118     */
119    private static class CloseCameraRunnable implements Runnable {
120        private final SingleDeviceCloseListener mCloseListener;
121        private final CameraDevice mCameraDevice;
122        private final Logger mLogger;
123
124        public CloseCameraRunnable(CameraDevice cameraDevice,
125              SingleDeviceCloseListener closeListener,
126              Logger logger) {
127            mCameraDevice = cameraDevice;
128            mCloseListener = closeListener;
129            mLogger = logger;
130        }
131
132        @Override
133        public void run() {
134            try {
135                mLogger.i("mCameraDevice.close(id: " + mCameraDevice.getId() + ")");
136                mCameraDevice.close();
137                mCloseListener.onDeviceClosed();
138            } catch (Exception e) {
139                mLogger.e("Closing the camera produced an exception!", e);
140                mCloseListener.onDeviceClosingException(e);
141            }
142        }
143    }
144
145    /**
146     * Internal callback that provides a camera device to a future.
147     */
148    private static class OpenCameraStateCallback extends CameraDevice.StateCallback {
149        private final SingleDeviceOpenListener<CameraDevice> mOpenListener;
150        private final Logger mLogger;
151        private boolean mHasBeenCalled = false;
152
153        public OpenCameraStateCallback(SingleDeviceOpenListener<CameraDevice> openListener,
154              Logger logger) {
155            mOpenListener = openListener;
156            mLogger = logger;
157        }
158
159        @Override
160        public void onOpened(CameraDevice cameraDevice) {
161            if (!called()) {
162                mLogger.i("onOpened(id: " + cameraDevice.getId() + ")");
163                mOpenListener.onDeviceOpened(cameraDevice);
164            }
165        }
166
167        @Override
168        public void onClosed(CameraDevice cameraDevice) {
169            if (!called()) {
170                mLogger.w("onClosed(id: " + cameraDevice.getId() + ")");
171                mOpenListener.onDeviceOpenException(cameraDevice);
172            }
173        }
174
175        @Override
176        public void onDisconnected(CameraDevice cameraDevice) {
177            if (!called()) {
178                mLogger.w("onDisconnected(id: " + cameraDevice.getId() + ")");
179                mOpenListener.onDeviceOpenException(cameraDevice);
180            }
181        }
182
183        @Override
184        public void onError(CameraDevice cameraDevice, int errorId) {
185            if (!called()) {
186                mLogger.e("onError(id: " + cameraDevice.getId()
187                      + ", errorId: " + errorId + ")");
188                mOpenListener.onDeviceOpenException(new CameraOpenException(errorId));
189            }
190        }
191
192        private boolean called() {
193            boolean result = mHasBeenCalled;
194            if (!mHasBeenCalled) {
195                mHasBeenCalled = true;
196            } else {
197                mLogger.v("Callback was re-executed.");
198            }
199
200            return result;
201        }
202    }
203}
204