/** * Created by Fabrice Armisen (farmisen@gmail.com) on 1/4/16. */ package com.lwansbrough.RCTCamera; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.hardware.Camera; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; import android.util.Base64; import android.util.Log; import com.facebook.react.bridge.*; import javax.annotation.Nullable; import java.io.*; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; public class RCTCameraModule extends ReactContextBaseJavaModule { private static final String TAG = "RCTCameraModule"; public static final int RCT_CAMERA_ASPECT_FILL = 0; public static final int RCT_CAMERA_ASPECT_FIT = 1; public static final int RCT_CAMERA_ASPECT_STRETCH = 2; public static final int RCT_CAMERA_CAPTURE_MODE_STILL = 0; public static final int RCT_CAMERA_CAPTURE_MODE_VIDEO = 1; public static final int RCT_CAMERA_CAPTURE_TARGET_MEMORY = 0; public static final int RCT_CAMERA_CAPTURE_TARGET_DISK = 1; public static final int RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL = 2; public static final int RCT_CAMERA_CAPTURE_TARGET_TEMP = 3; public static final int RCT_CAMERA_ORIENTATION_AUTO = 0; public static final int RCT_CAMERA_ORIENTATION_LANDSCAPE_LEFT = 1; public static final int RCT_CAMERA_ORIENTATION_LANDSCAPE_RIGHT = 2; public static final int RCT_CAMERA_ORIENTATION_PORTRAIT = 3; public static final int RCT_CAMERA_ORIENTATION_PORTRAIT_UPSIDE_DOWN = 4; public static final int RCT_CAMERA_TYPE_FRONT = 1; public static final int RCT_CAMERA_TYPE_BACK = 2; public static final int RCT_CAMERA_FLASH_MODE_OFF = 0; public static final int RCT_CAMERA_FLASH_MODE_ON = 1; public static final int RCT_CAMERA_FLASH_MODE_AUTO = 2; public static final int RCT_CAMERA_TORCH_MODE_OFF = 0; public static final int RCT_CAMERA_TORCH_MODE_ON = 1; public static final int RCT_CAMERA_TORCH_MODE_AUTO = 2; public static final int MEDIA_TYPE_IMAGE = 1; public static final int MEDIA_TYPE_VIDEO = 2; private final ReactApplicationContext _reactContext; public RCTCameraModule(ReactApplicationContext reactContext) { super(reactContext); _reactContext = reactContext; } @Override public String getName() { return "RCTCameraModule"; } @Nullable @Override public Map getConstants() { return Collections.unmodifiableMap(new HashMap() { { put("Aspect", getAspectConstants()); put("Type", getTypeConstants()); put("CaptureMode", getCaptureModeConstants()); put("CaptureTarget", getCaptureTargetConstants()); put("Orientation", getOrientationConstants()); put("FlashMode", getFlashModeConstants()); put("TorchMode", getTorchModeConstants()); } private Map getAspectConstants() { return Collections.unmodifiableMap(new HashMap() { { put("stretch", RCT_CAMERA_ASPECT_STRETCH); put("fit", RCT_CAMERA_ASPECT_FIT); put("fill", RCT_CAMERA_ASPECT_FILL); } }); } private Map getTypeConstants() { return Collections.unmodifiableMap(new HashMap() { { put("front", RCT_CAMERA_TYPE_FRONT); put("back", RCT_CAMERA_TYPE_BACK); } }); } private Map getCaptureModeConstants() { return Collections.unmodifiableMap(new HashMap() { { put("still", RCT_CAMERA_CAPTURE_MODE_STILL); put("video", RCT_CAMERA_CAPTURE_MODE_VIDEO); } }); } private Map getCaptureTargetConstants() { return Collections.unmodifiableMap(new HashMap() { { put("memory", RCT_CAMERA_CAPTURE_TARGET_MEMORY); put("disk", RCT_CAMERA_CAPTURE_TARGET_DISK); put("cameraRoll", RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL); put("temp", RCT_CAMERA_CAPTURE_TARGET_TEMP); } }); } private Map getOrientationConstants() { return Collections.unmodifiableMap(new HashMap() { { put("auto", RCT_CAMERA_ORIENTATION_AUTO); put("landscapeLeft", RCT_CAMERA_ORIENTATION_LANDSCAPE_LEFT); put("landscapeRight", RCT_CAMERA_ORIENTATION_LANDSCAPE_RIGHT); put("portrait", RCT_CAMERA_ORIENTATION_PORTRAIT); put("portraitUpsideDown", RCT_CAMERA_ORIENTATION_PORTRAIT_UPSIDE_DOWN); } }); } private Map getFlashModeConstants() { return Collections.unmodifiableMap(new HashMap() { { put("off", RCT_CAMERA_FLASH_MODE_OFF); put("on", RCT_CAMERA_FLASH_MODE_ON); put("auto", RCT_CAMERA_FLASH_MODE_AUTO); } }); } private Map getTorchModeConstants() { return Collections.unmodifiableMap(new HashMap() { { put("off", RCT_CAMERA_TORCH_MODE_OFF); put("on", RCT_CAMERA_TORCH_MODE_ON); put("auto", RCT_CAMERA_TORCH_MODE_AUTO); } }); } }); } @ReactMethod public void capture(final ReadableMap options, final Promise promise) { Camera camera = RCTCamera.getInstance().acquireCameraInstance(options.getInt("type")); if (null == camera) { promise.reject("No camera found."); return; } camera.takePicture(null, null, new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { camera.stopPreview(); camera.startPreview(); switch (options.getInt("target")) { case RCT_CAMERA_CAPTURE_TARGET_MEMORY: String encoded = Base64.encodeToString(data, Base64.DEFAULT); promise.resolve(encoded); break; case RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL: BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, bitmapOptions); String url = MediaStore.Images.Media.insertImage( _reactContext.getContentResolver(), bitmap, options.getString("title"), options.getString("description")); promise.resolve(url); break; case RCT_CAMERA_CAPTURE_TARGET_DISK: File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE); if (pictureFile == null) { promise.reject("Error creating media file."); return; } try { FileOutputStream fos = new FileOutputStream(pictureFile); fos.write(data); fos.close(); } catch (FileNotFoundException e) { promise.reject("File not found: " + e.getMessage()); } catch (IOException e) { promise.reject("Error accessing file: " + e.getMessage()); } promise.resolve(Uri.fromFile(pictureFile).toString()); break; case RCT_CAMERA_CAPTURE_TARGET_TEMP: File tempFile = getTempMediaFile(MEDIA_TYPE_IMAGE); if (tempFile == null) { callback.invoke("Error creating media file.", null); return; } try { FileOutputStream fos = new FileOutputStream(tempFile); fos.write(data); fos.close(); } catch (FileNotFoundException e) { callback.invoke("File not found: " + e.getMessage(), null); } catch (IOException e) { callback.invoke("Error accessing file: " + e.getMessage(), null); } callback.invoke(null, Uri.fromFile(tempFile).toString()); break; } } }); } @ReactMethod public void stopCapture(final ReadableMap options, final Promise promise) { // TODO: implement video capture } private File getOutputMediaFile(int type) { File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES), "RCTCameraModule"); // Create the storage directory if it does not exist if (!mediaStorageDir.exists()) { if (!mediaStorageDir.mkdirs()) { Log.e(TAG, "failed to create directory:" + mediaStorageDir.getAbsolutePath()); return null; } } // Create a media file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); File mediaFile; if (type == MEDIA_TYPE_IMAGE) { mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"); } else if (type == MEDIA_TYPE_VIDEO) { mediaFile = new File(mediaStorageDir.getPath() + File.separator + "VID_" + timeStamp + ".mp4"); } else { Log.e(TAG, "Unsupported media type:" + type); return null; } return mediaFile; } private File getTempMediaFile(int type) { try { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); File outputDir = _reactContext.getCacheDir(); File outputFile; if (type == MEDIA_TYPE_IMAGE) { outputFile = File.createTempFile("IMG_" + timeStamp, ".jpg", outputDir); } else if (type == MEDIA_TYPE_VIDEO) { outputFile = File.createTempFile("VID_" + timeStamp, ".mp4", outputDir); } else { Log.e(TAG, "Unsupported media type:" + type); return null; } return outputFile; } catch (Exception e) { Log.e(TAG, e.getMessage()); return null; } } }