1/*
2 * Copyright (C) 2017 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.googlecode.android_scripting.facade;
18
19import android.app.Service;
20import android.content.Intent;
21import android.hardware.Camera;
22import android.hardware.Camera.AutoFocusCallback;
23import android.hardware.Camera.Parameters;
24import android.hardware.Camera.PictureCallback;
25import android.net.Uri;
26import android.os.Bundle;
27import android.provider.MediaStore;
28import android.view.SurfaceHolder;
29import android.view.SurfaceHolder.Callback;
30import android.view.SurfaceView;
31import android.view.WindowManager;
32
33import com.googlecode.android_scripting.BaseApplication;
34import com.googlecode.android_scripting.FileUtils;
35import com.googlecode.android_scripting.FutureActivityTaskExecutor;
36import com.googlecode.android_scripting.Log;
37import com.googlecode.android_scripting.future.FutureActivityTask;
38import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
39import com.googlecode.android_scripting.rpc.Rpc;
40import com.googlecode.android_scripting.rpc.RpcDefault;
41import com.googlecode.android_scripting.rpc.RpcParameter;
42
43import java.io.File;
44import java.io.FileNotFoundException;
45import java.io.FileOutputStream;
46import java.io.IOException;
47import java.lang.reflect.Method;
48import java.util.concurrent.CountDownLatch;
49
50/**
51 * Access Camera functions.
52 *
53 */
54public 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
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
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.")
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
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
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
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
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.")
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