1/*
2 * Copyright 2012-2015 the original author or authors.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18package eu.siacs.conversations.ui.service;
19
20import java.io.IOException;
21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.Comparator;
25import java.util.List;
26
27import com.google.zxing.PlanarYUVLuminanceSource;
28
29import android.annotation.SuppressLint;
30import android.graphics.Rect;
31import android.graphics.RectF;
32import android.hardware.Camera;
33import android.hardware.Camera.CameraInfo;
34import android.hardware.Camera.PreviewCallback;
35import android.util.Log;
36import android.view.TextureView;
37
38import eu.siacs.conversations.Config;
39
40/**
41 * @author Andreas Schildbach
42 */
43@SuppressWarnings("deprecation")
44public final class CameraManager {
45 private static final int MIN_FRAME_SIZE = 240;
46 private static final int MAX_FRAME_SIZE = 600;
47 private static final int MIN_PREVIEW_PIXELS = 470 * 320; // normal screen
48 private static final int MAX_PREVIEW_PIXELS = 1280 * 720;
49
50 private Camera camera;
51 private CameraInfo cameraInfo = new CameraInfo();
52 private Camera.Size cameraResolution;
53 private Rect frame;
54 private RectF framePreview;
55
56 public Rect getFrame() {
57 return frame;
58 }
59
60 public RectF getFramePreview() {
61 return framePreview;
62 }
63
64 public int getFacing() {
65 return cameraInfo.facing;
66 }
67
68 public int getOrientation() {
69 return cameraInfo.orientation;
70 }
71
72 public Camera open(final TextureView textureView, final int displayOrientation, final boolean continuousAutoFocus)
73 throws IOException {
74 final int cameraId = determineCameraId();
75 Camera.getCameraInfo(cameraId, cameraInfo);
76
77 camera = Camera.open(cameraId);
78
79 if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
80 camera.setDisplayOrientation((720 - displayOrientation - cameraInfo.orientation) % 360);
81 else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK)
82 camera.setDisplayOrientation((720 - displayOrientation + cameraInfo.orientation) % 360);
83 else
84 throw new IllegalStateException("facing: " + cameraInfo.facing);
85
86 camera.setPreviewTexture(textureView.getSurfaceTexture());
87
88 final Camera.Parameters parameters = camera.getParameters();
89
90 cameraResolution = findBestPreviewSizeValue(parameters, textureView.getWidth(), textureView.getHeight());
91
92 final int width = textureView.getWidth();
93 final int height = textureView.getHeight();
94
95 final int rawSize = Math.min(width * 2 / 3, height * 2 / 3);
96 final int frameSize = Math.max(MIN_FRAME_SIZE, Math.min(MAX_FRAME_SIZE, rawSize));
97
98 final int leftOffset = (width - frameSize) / 2;
99 final int topOffset = (height - frameSize) / 2;
100 frame = new Rect(leftOffset, topOffset, leftOffset + frameSize, topOffset + frameSize);
101 framePreview = new RectF(frame.left * cameraResolution.width / width,
102 frame.top * cameraResolution.height / height, frame.right * cameraResolution.width / width,
103 frame.bottom * cameraResolution.height / height);
104
105 final String savedParameters = parameters == null ? null : parameters.flatten();
106
107 try {
108 setDesiredCameraParameters(camera, cameraResolution, continuousAutoFocus);
109 } catch (final RuntimeException x) {
110 if (savedParameters != null) {
111 final Camera.Parameters parameters2 = camera.getParameters();
112 parameters2.unflatten(savedParameters);
113 try {
114 camera.setParameters(parameters2);
115 setDesiredCameraParameters(camera, cameraResolution, continuousAutoFocus);
116 } catch (final RuntimeException x2) {
117 Log.d(Config.LOGTAG,"problem setting camera parameters", x2);
118 }
119 }
120 }
121
122 try {
123 camera.startPreview();
124 return camera;
125 } catch (final RuntimeException x) {
126 Log.w(Config.LOGTAG,"something went wrong while starting camera preview", x);
127 camera.release();
128 throw x;
129 }
130 }
131
132 private int determineCameraId() {
133 final int cameraCount = Camera.getNumberOfCameras();
134 final CameraInfo cameraInfo = new CameraInfo();
135
136 // prefer back-facing camera
137 for (int i = 0; i < cameraCount; i++) {
138 Camera.getCameraInfo(i, cameraInfo);
139 if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK)
140 return i;
141 }
142
143 // fall back to front-facing camera
144 for (int i = 0; i < cameraCount; i++) {
145 Camera.getCameraInfo(i, cameraInfo);
146 if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
147 return i;
148 }
149
150 return -1;
151 }
152
153 public void close() {
154 if (camera != null) {
155 try {
156 camera.stopPreview();
157 } catch (final RuntimeException x) {
158 Log.w(Config.LOGTAG,"something went wrong while stopping camera preview", x);
159 }
160
161 camera.release();
162 }
163 }
164
165 private static final Comparator<Camera.Size> numPixelComparator = new Comparator<Camera.Size>() {
166 @Override
167 public int compare(final Camera.Size size1, final Camera.Size size2) {
168 final int pixels1 = size1.height * size1.width;
169 final int pixels2 = size2.height * size2.width;
170
171 if (pixels1 < pixels2)
172 return 1;
173 else if (pixels1 > pixels2)
174 return -1;
175 else
176 return 0;
177 }
178 };
179
180 private static Camera.Size findBestPreviewSizeValue(final Camera.Parameters parameters, int width, int height) {
181 if (height > width) {
182 final int temp = width;
183 width = height;
184 height = temp;
185 }
186
187 final float screenAspectRatio = (float) width / (float) height;
188
189 final List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
190 if (rawSupportedSizes == null)
191 return parameters.getPreviewSize();
192
193 // sort by size, descending
194 final List<Camera.Size> supportedPreviewSizes = new ArrayList<Camera.Size>(rawSupportedSizes);
195 Collections.sort(supportedPreviewSizes, numPixelComparator);
196
197 Camera.Size bestSize = null;
198 float diff = Float.POSITIVE_INFINITY;
199
200 for (final Camera.Size supportedPreviewSize : supportedPreviewSizes) {
201 final int realWidth = supportedPreviewSize.width;
202 final int realHeight = supportedPreviewSize.height;
203 final int realPixels = realWidth * realHeight;
204 if (realPixels < MIN_PREVIEW_PIXELS || realPixels > MAX_PREVIEW_PIXELS)
205 continue;
206
207 final boolean isCandidatePortrait = realWidth < realHeight;
208 final int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
209 final int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
210 if (maybeFlippedWidth == width && maybeFlippedHeight == height)
211 return supportedPreviewSize;
212
213 final float aspectRatio = (float) maybeFlippedWidth / (float) maybeFlippedHeight;
214 final float newDiff = Math.abs(aspectRatio - screenAspectRatio);
215 if (newDiff < diff) {
216 bestSize = supportedPreviewSize;
217 diff = newDiff;
218 }
219 }
220
221 if (bestSize != null)
222 return bestSize;
223 else
224 return parameters.getPreviewSize();
225 }
226
227 @SuppressLint("InlinedApi")
228 private static void setDesiredCameraParameters(final Camera camera, final Camera.Size cameraResolution,
229 final boolean continuousAutoFocus) {
230 final Camera.Parameters parameters = camera.getParameters();
231 if (parameters == null)
232 return;
233
234 final List<String> supportedFocusModes = parameters.getSupportedFocusModes();
235 final String focusMode = continuousAutoFocus
236 ? findValue(supportedFocusModes, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
237 Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, Camera.Parameters.FOCUS_MODE_AUTO,
238 Camera.Parameters.FOCUS_MODE_MACRO)
239 : findValue(supportedFocusModes, Camera.Parameters.FOCUS_MODE_AUTO, Camera.Parameters.FOCUS_MODE_MACRO);
240 if (focusMode != null)
241 parameters.setFocusMode(focusMode);
242
243 parameters.setPreviewSize(cameraResolution.width, cameraResolution.height);
244
245 camera.setParameters(parameters);
246 }
247
248 public void requestPreviewFrame(final PreviewCallback callback) {
249 try {
250 camera.setOneShotPreviewCallback(callback);
251 } catch (final RuntimeException x) {
252 Log.d(Config.LOGTAG,"problem requesting preview frame, callback won't be called", x);
253 }
254 }
255
256 public PlanarYUVLuminanceSource buildLuminanceSource(final byte[] data) {
257 return new PlanarYUVLuminanceSource(data, cameraResolution.width, cameraResolution.height,
258 (int) framePreview.left, (int) framePreview.top, (int) framePreview.width(),
259 (int) framePreview.height(), false);
260 }
261
262 public void setTorch(final boolean enabled) {
263 if (enabled != getTorchEnabled(camera))
264 setTorchEnabled(camera, enabled);
265 }
266
267 private static boolean getTorchEnabled(final Camera camera) {
268 final Camera.Parameters parameters = camera.getParameters();
269 if (parameters != null) {
270 final String flashMode = camera.getParameters().getFlashMode();
271 return flashMode != null && (Camera.Parameters.FLASH_MODE_ON.equals(flashMode)
272 || Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode));
273 }
274
275 return false;
276 }
277
278 private static void setTorchEnabled(final Camera camera, final boolean enabled) {
279 final Camera.Parameters parameters = camera.getParameters();
280
281 final List<String> supportedFlashModes = parameters.getSupportedFlashModes();
282 if (supportedFlashModes != null) {
283 final String flashMode;
284 if (enabled)
285 flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_TORCH,
286 Camera.Parameters.FLASH_MODE_ON);
287 else
288 flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_OFF);
289
290 if (flashMode != null) {
291 camera.cancelAutoFocus(); // autofocus can cause conflict
292
293 parameters.setFlashMode(flashMode);
294 camera.setParameters(parameters);
295 }
296 }
297 }
298
299 private static String findValue(final Collection<String> values, final String... valuesToFind) {
300 for (final String valueToFind : valuesToFind)
301 if (values.contains(valueToFind))
302 return valueToFind;
303
304 return null;
305 }
306}