1 /*
2  * Copyright (C) 2016 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.googlecode.android_scripting.facade;
18 
19 import android.app.Service;
20 import android.content.Intent;
21 import android.hardware.Camera;
22 import android.hardware.Camera.AutoFocusCallback;
23 import android.hardware.Camera.Parameters;
24 import android.hardware.Camera.PictureCallback;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.provider.MediaStore;
28 import android.view.SurfaceHolder;
29 import android.view.SurfaceHolder.Callback;
30 import android.view.SurfaceView;
31 import android.view.WindowManager;
32 
33 import com.googlecode.android_scripting.BaseApplication;
34 import com.googlecode.android_scripting.FileUtils;
35 import com.googlecode.android_scripting.FutureActivityTaskExecutor;
36 import com.googlecode.android_scripting.Log;
37 import com.googlecode.android_scripting.future.FutureActivityTask;
38 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
39 import com.googlecode.android_scripting.rpc.Rpc;
40 import com.googlecode.android_scripting.rpc.RpcDefault;
41 import com.googlecode.android_scripting.rpc.RpcParameter;
42 
43 import java.io.File;
44 import java.io.FileNotFoundException;
45 import java.io.FileOutputStream;
46 import java.io.IOException;
47 import java.lang.reflect.Method;
48 import java.util.concurrent.CountDownLatch;
49 
50 /**
51  * Access Camera functions.
52  *
53  */
54 public class CameraFacade extends RpcReceiver {
55 
56   private final Service mService;
57   private final Parameters mParameters;
58 
59   private class BooleanResult {
60     boolean mmResult = false;
61   }
62 
openCamera(int cameraId)63   public Camera openCamera(int cameraId) throws Exception {
64     int sSdkLevel = Integer.parseInt(android.os.Build.VERSION.SDK);
65     Camera result;
66     if (sSdkLevel < 9) {
67       result = Camera.open();
68     } else {
69       Method openCamera = Camera.class.getMethod("open", int.class);
70       result = (Camera) openCamera.invoke(null, cameraId);
71     }
72     return result;
73   }
74 
CameraFacade(FacadeManager manager)75   public CameraFacade(FacadeManager manager) throws Exception {
76     super(manager);
77     mService = manager.getService();
78     Camera camera = openCamera(0);
79     try {
80       mParameters = camera.getParameters();
81     } finally {
82       camera.release();
83     }
84   }
85 
86   @Rpc(description = "Take a picture and save it to the specified path.", returns = "A map of Booleans autoFocus and takePicture where True indicates success. cameraId also included.")
cameraCapturePicture( @pcParametername = "targetPath") final String targetPath, @RpcParameter(name = "useAutoFocus") @RpcDefault("true") Boolean useAutoFocus, @RpcParameter(name = "cameraId", description = "Id of camera to use. SDK 9") @RpcDefault("0") Integer cameraId)87   public Bundle cameraCapturePicture(
88       @RpcParameter(name = "targetPath") final String targetPath,
89       @RpcParameter(name = "useAutoFocus") @RpcDefault("true") Boolean useAutoFocus,
90       @RpcParameter(name = "cameraId", description = "Id of camera to use. SDK 9") @RpcDefault("0") Integer cameraId)
91       throws Exception {
92     final BooleanResult autoFocusResult = new BooleanResult();
93     final BooleanResult takePictureResult = new BooleanResult();
94     Camera camera = openCamera(cameraId);
95     camera.setParameters(mParameters);
96 
97     try {
98       Method method = camera.getClass().getMethod("setDisplayOrientation", int.class);
99       method.invoke(camera, 90);
100     } catch (Exception e) {
101       Log.e(e);
102     }
103 
104     try {
105       FutureActivityTask<SurfaceHolder> previewTask = setPreviewDisplay(camera);
106       camera.startPreview();
107       if (useAutoFocus) {
108         autoFocus(autoFocusResult, camera);
109       }
110       takePicture(new File(targetPath), takePictureResult, camera);
111       previewTask.finish();
112     } catch (Exception e) {
113       Log.e(e);
114     } finally {
115       camera.release();
116     }
117 
118     Bundle result = new Bundle();
119     result.putBoolean("autoFocus", autoFocusResult.mmResult);
120     result.putBoolean("takePicture", takePictureResult.mmResult);
121     result.putInt("cameraId", cameraId);
122     return result;
123   }
124 
setPreviewDisplay(Camera camera)125   private FutureActivityTask<SurfaceHolder> setPreviewDisplay(Camera camera) throws IOException,
126       InterruptedException {
127     FutureActivityTask<SurfaceHolder> task = new FutureActivityTask<SurfaceHolder>() {
128       @Override
129       public void onCreate() {
130         super.onCreate();
131         final SurfaceView view = new SurfaceView(getActivity());
132         getActivity().setContentView(view);
133         getActivity().getWindow().setSoftInputMode(
134             WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED);
135         view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
136         view.getHolder().addCallback(new Callback() {
137           @Override
138           public void surfaceDestroyed(SurfaceHolder holder) {
139           }
140 
141           @Override
142           public void surfaceCreated(SurfaceHolder holder) {
143             setResult(view.getHolder());
144           }
145 
146           @Override
147           public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
148           }
149         });
150       }
151     };
152     FutureActivityTaskExecutor taskQueue =
153         ((BaseApplication) mService.getApplication()).getTaskExecutor();
154     taskQueue.execute(task);
155     camera.setPreviewDisplay(task.getResult());
156     return task;
157   }
158 
takePicture(final File file, final BooleanResult takePictureResult, final Camera camera)159   private void takePicture(final File file, final BooleanResult takePictureResult,
160       final Camera camera) throws InterruptedException {
161     final CountDownLatch latch = new CountDownLatch(1);
162     camera.takePicture(null, null, new PictureCallback() {
163       @Override
164       public void onPictureTaken(byte[] data, Camera camera) {
165         if (!FileUtils.makeDirectories(file.getParentFile(), 0755)) {
166           takePictureResult.mmResult = false;
167           return;
168         }
169         try {
170           FileOutputStream output = new FileOutputStream(file);
171           output.write(data);
172           output.close();
173           takePictureResult.mmResult = true;
174         } catch (FileNotFoundException e) {
175           Log.e("Failed to save picture.", e);
176           takePictureResult.mmResult = false;
177           return;
178         } catch (IOException e) {
179           Log.e("Failed to save picture.", e);
180           takePictureResult.mmResult = false;
181           return;
182         } finally {
183           latch.countDown();
184         }
185       }
186     });
187     latch.await();
188   }
189 
autoFocus(final BooleanResult result, final Camera camera)190   private void autoFocus(final BooleanResult result, final Camera camera)
191       throws InterruptedException {
192     final CountDownLatch latch = new CountDownLatch(1);
193     {
194       camera.autoFocus(new AutoFocusCallback() {
195         @Override
196         public void onAutoFocus(boolean success, Camera camera) {
197           result.mmResult = success;
198           latch.countDown();
199         }
200       });
201       latch.await();
202     }
203   }
204 
205   @Override
shutdown()206   public void shutdown() {
207     // Nothing to clean up.
208   }
209 
210   @Rpc(description = "Starts the image capture application to take a picture and saves it to the specified path.")
cameraInteractiveCapturePicture( @pcParametername = "targetPath") final String targetPath)211   public void cameraInteractiveCapturePicture(
212       @RpcParameter(name = "targetPath") final String targetPath) {
213     Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
214     File file = new File(targetPath);
215     intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
216     AndroidFacade facade = mManager.getReceiver(AndroidFacade.class);
217     facade.startActivityForResult(intent);
218   }
219 }
220