Java tutorial
/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.devialab.camerarollextended; import javax.annotation.Nullable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.graphics.BitmapFactory; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Environment; import android.provider.MediaStore; import android.provider.MediaStore.Images; import android.text.TextUtils; import com.facebook.common.logging.FLog; import com.facebook.react.bridge.GuardedAsyncTask; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeArray; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.common.ReactConstants; // TODO #6015104: rename to something less iOSish /** * {@link NativeModule} that allows JS to interact with the photos on the device (i.e. * {@link MediaStore.Images}). */ public class CameraRoll extends ReactContextBaseJavaModule { private static final String ERROR_UNABLE_TO_LOAD = "E_UNABLE_TO_LOAD"; private static final String ERROR_UNABLE_TO_LOAD_PERMISSION = "E_UNABLE_TO_LOAD_PERMISSION"; private static final String ERROR_UNABLE_TO_SAVE = "E_UNABLE_TO_SAVE"; public static final boolean IS_JELLY_BEAN_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; private static final String[] PROJECTION; static { if (IS_JELLY_BEAN_OR_LATER) { PROJECTION = new String[] { Images.Media._ID, Images.Media.MIME_TYPE, Images.Media.BUCKET_DISPLAY_NAME, Images.Media.DATE_TAKEN, Images.Media.WIDTH, Images.Media.HEIGHT, Images.Media.LONGITUDE, Images.Media.LATITUDE }; } else { PROJECTION = new String[] { Images.Media._ID, Images.Media.MIME_TYPE, Images.Media.BUCKET_DISPLAY_NAME, Images.Media.DATE_TAKEN, Images.Media.LONGITUDE, Images.Media.LATITUDE }; } } private static final String SELECTION_BUCKET = Images.Media.BUCKET_DISPLAY_NAME + " = ?"; private static final String SELECTION_DATE_TAKEN = Images.Media.DATE_TAKEN + " < ?"; public CameraRoll(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return "RKCameraRollExtendedManager"; } @Override public Map<String, Object> getConstants() { return Collections.emptyMap(); } /** * Save an image to the gallery (i.e. {@link MediaStore.Images}). This copies the original file * from wherever it may be to the external storage pictures directory, so that it can be scanned * by the MediaScanner. * * @param uri the file:// URI of the image to save * @param promise to be resolved or rejected */ @ReactMethod public void saveToCameraRoll(ReadableMap tag, String type, Promise promise) { MediaType parsedType = type.equals("video") ? MediaType.VIDEO : MediaType.PHOTO; new SaveToCameraRoll(getReactApplicationContext(), Uri.parse(tag.getString("uri")), tag.getString("album"), parsedType, promise).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } private enum MediaType { PHOTO, VIDEO }; private static class SaveToCameraRoll extends GuardedAsyncTask<Void, Void> { private final Context mContext; private final Uri mUri; private final String mAlbum; private final Promise mPromise; private final MediaType mType; public SaveToCameraRoll(ReactContext context, Uri uri, String album, MediaType type, Promise promise) { super(context); mContext = context; mUri = uri; mAlbum = album; mPromise = promise; mType = type; } @Override protected void doInBackgroundGuarded(Void... params) { File source = new File(mUri.getPath()); FileChannel input = null, output = null; try { File exportDir = (mType == MediaType.PHOTO) ? Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) : Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES); exportDir = new File(exportDir, mAlbum); exportDir.mkdirs(); if (!exportDir.isDirectory()) { mPromise.reject(ERROR_UNABLE_TO_LOAD, "External media storage directory not available"); return; } File dest = new File(exportDir, source.getName()); int n = 0; String fullSourceName = source.getName(); String sourceName, sourceExt; if (fullSourceName.indexOf('.') >= 0) { sourceName = fullSourceName.substring(0, fullSourceName.lastIndexOf('.')); sourceExt = fullSourceName.substring(fullSourceName.lastIndexOf('.')); } else { sourceName = fullSourceName; sourceExt = ""; } while (!dest.createNewFile()) { dest = new File(exportDir, sourceName + "_" + (n++) + sourceExt); } input = new FileInputStream(source).getChannel(); output = new FileOutputStream(dest).getChannel(); output.transferFrom(input, 0, input.size()); input.close(); output.close(); MediaScannerConnection.scanFile(mContext, new String[] { dest.getAbsolutePath() }, null, new MediaScannerConnection.OnScanCompletedListener() { @Override public void onScanCompleted(String path, Uri uri) { if (uri != null) { mPromise.resolve(uri.toString()); } else { mPromise.reject(ERROR_UNABLE_TO_SAVE, "Could not add image to gallery"); } } }); } catch (IOException e) { mPromise.reject(e); } finally { if (input != null && input.isOpen()) { try { input.close(); } catch (IOException e) { FLog.e(ReactConstants.TAG, "Could not close input channel", e); } } if (output != null && output.isOpen()) { try { output.close(); } catch (IOException e) { FLog.e(ReactConstants.TAG, "Could not close output channel", e); } } } } } }