com.devialab.camerarollextended.CameraRoll.java Source code

Java tutorial

Introduction

Here is the source code for com.devialab.camerarollextended.CameraRoll.java

Source

/**
 * 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);
                    }
                }
            }
        }
    }

}