2016-01-14 04:31:51 -08:00
|
|
|
/**
|
2018-09-11 15:27:47 -07:00
|
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
2016-01-14 04:31:51 -08:00
|
|
|
*
|
2018-02-16 18:24:55 -08:00
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
2016-01-14 04:31:51 -08:00
|
|
|
*/
|
|
|
|
|
|
2019-02-24 12:29:24 +08:00
|
|
|
package com.reactnativecommunity.cameraroll;
|
2016-01-14 04:31:51 -08:00
|
|
|
|
|
|
|
|
import android.content.ContentResolver;
|
2019-11-08 00:13:24 +11:00
|
|
|
import android.content.ContentUris;
|
2016-01-14 04:31:51 -08:00
|
|
|
import android.content.Context;
|
|
|
|
|
import android.content.res.AssetFileDescriptor;
|
|
|
|
|
import android.database.Cursor;
|
|
|
|
|
import android.graphics.BitmapFactory;
|
2017-08-15 11:36:42 -07:00
|
|
|
import android.media.MediaMetadataRetriever;
|
2016-01-14 04:31:51 -08:00
|
|
|
import android.media.MediaScannerConnection;
|
|
|
|
|
import android.net.Uri;
|
|
|
|
|
import android.os.AsyncTask;
|
|
|
|
|
import android.os.Environment;
|
|
|
|
|
import android.provider.MediaStore;
|
|
|
|
|
import android.provider.MediaStore.Images;
|
|
|
|
|
import android.text.TextUtils;
|
2020-04-30 17:44:37 +05:30
|
|
|
import android.media.ExifInterface;
|
2019-02-24 13:30:55 +08:00
|
|
|
|
2016-01-14 04:31:51 -08:00
|
|
|
import com.facebook.common.logging.FLog;
|
|
|
|
|
import com.facebook.react.bridge.GuardedAsyncTask;
|
2017-08-02 19:59:54 -07:00
|
|
|
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
|
|
|
|
import com.facebook.react.bridge.NativeModule;
|
2016-01-21 08:07:01 -08:00
|
|
|
import com.facebook.react.bridge.Promise;
|
2016-01-14 04:31:51 -08:00
|
|
|
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.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;
|
2016-09-02 16:15:11 -07:00
|
|
|
import com.facebook.react.module.annotations.ReactModule;
|
2019-02-24 13:30:55 +08:00
|
|
|
|
2017-08-02 19:59:54 -07:00
|
|
|
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;
|
2020-06-16 02:07:25 -07:00
|
|
|
import java.util.HashSet;
|
2017-08-02 19:59:54 -07:00
|
|
|
import java.util.List;
|
2020-05-20 17:59:54 +02:00
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.HashMap;
|
2020-06-16 02:07:25 -07:00
|
|
|
import java.util.Set;
|
2016-01-14 04:31:51 -08:00
|
|
|
|
2019-02-24 13:30:55 +08:00
|
|
|
import javax.annotation.Nullable;
|
|
|
|
|
|
2016-01-14 04:31:51 -08:00
|
|
|
/**
|
2019-01-28 07:31:29 -08:00
|
|
|
* {@link NativeModule} that allows JS to interact with the photos and videos on the device (i.e.
|
2016-01-14 04:31:51 -08:00
|
|
|
* {@link MediaStore.Images}).
|
|
|
|
|
*/
|
2019-02-24 13:30:55 +08:00
|
|
|
@ReactModule(name = CameraRollModule.NAME)
|
|
|
|
|
public class CameraRollModule extends ReactContextBaseJavaModule {
|
2016-01-14 04:31:51 -08:00
|
|
|
|
2019-03-03 14:39:57 +08:00
|
|
|
public static final String NAME = "RNCCameraRoll";
|
2017-01-31 04:49:58 -08:00
|
|
|
|
2016-01-21 08:07:01 -08:00
|
|
|
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";
|
2019-11-08 00:13:24 +11:00
|
|
|
private static final String ERROR_UNABLE_TO_DELETE = "E_UNABLE_TO_DELETE";
|
2019-01-28 07:31:29 -08:00
|
|
|
private static final String ERROR_UNABLE_TO_FILTER = "E_UNABLE_TO_FILTER";
|
|
|
|
|
|
|
|
|
|
private static final String ASSET_TYPE_PHOTOS = "Photos";
|
|
|
|
|
private static final String ASSET_TYPE_VIDEOS = "Videos";
|
|
|
|
|
private static final String ASSET_TYPE_ALL = "All";
|
|
|
|
|
|
2020-06-16 02:07:25 -07:00
|
|
|
private static final String INCLUDE_FILENAME = "filename";
|
|
|
|
|
private static final String INCLUDE_FILE_SIZE = "fileSize";
|
|
|
|
|
private static final String INCLUDE_LOCATION = "location";
|
|
|
|
|
|
2019-02-05 03:05:10 -08:00
|
|
|
private static final String[] PROJECTION = {
|
|
|
|
|
Images.Media._ID,
|
|
|
|
|
Images.Media.MIME_TYPE,
|
|
|
|
|
Images.Media.BUCKET_DISPLAY_NAME,
|
|
|
|
|
Images.Media.DATE_TAKEN,
|
|
|
|
|
MediaStore.MediaColumns.WIDTH,
|
|
|
|
|
MediaStore.MediaColumns.HEIGHT,
|
2020-05-20 19:35:59 +02:00
|
|
|
MediaStore.MediaColumns.SIZE,
|
2019-02-05 03:05:10 -08:00
|
|
|
MediaStore.MediaColumns.DATA
|
|
|
|
|
};
|
2016-01-14 04:31:51 -08:00
|
|
|
|
|
|
|
|
private static final String SELECTION_BUCKET = Images.Media.BUCKET_DISPLAY_NAME + " = ?";
|
|
|
|
|
private static final String SELECTION_DATE_TAKEN = Images.Media.DATE_TAKEN + " < ?";
|
|
|
|
|
|
2019-02-24 13:30:55 +08:00
|
|
|
public CameraRollModule(ReactApplicationContext reactContext) {
|
2016-01-14 04:31:51 -08:00
|
|
|
super(reactContext);
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-12 15:49:13 -07:00
|
|
|
@Override
|
|
|
|
|
public String getName() {
|
2017-01-31 04:49:58 -08:00
|
|
|
return NAME;
|
2016-08-12 15:49:13 -07:00
|
|
|
}
|
|
|
|
|
|
2016-01-14 04:31:51 -08:00
|
|
|
/**
|
|
|
|
|
* 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
|
2016-01-21 08:07:01 -08:00
|
|
|
* @param promise to be resolved or rejected
|
2016-01-14 04:31:51 -08:00
|
|
|
*/
|
|
|
|
|
@ReactMethod
|
2019-08-14 21:00:20 +02:00
|
|
|
public void saveToCameraRoll(String uri, ReadableMap options, Promise promise) {
|
|
|
|
|
new SaveToCameraRoll(getReactApplicationContext(), Uri.parse(uri), options, promise)
|
2016-01-14 04:31:51 -08:00
|
|
|
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-07 16:37:48 -07:00
|
|
|
private static class SaveToCameraRoll extends GuardedAsyncTask<Void, Void> {
|
2016-01-14 04:31:51 -08:00
|
|
|
|
|
|
|
|
private final Context mContext;
|
|
|
|
|
private final Uri mUri;
|
2016-01-21 08:07:01 -08:00
|
|
|
private final Promise mPromise;
|
2019-08-14 21:00:20 +02:00
|
|
|
private final ReadableMap mOptions;
|
2016-01-14 04:31:51 -08:00
|
|
|
|
2019-08-14 21:00:20 +02:00
|
|
|
public SaveToCameraRoll(ReactContext context, Uri uri, ReadableMap options, Promise promise) {
|
2020-03-26 11:56:13 +02:00
|
|
|
super(context);
|
2016-01-14 04:31:51 -08:00
|
|
|
mContext = context;
|
|
|
|
|
mUri = uri;
|
2016-01-21 08:07:01 -08:00
|
|
|
mPromise = promise;
|
2019-08-14 21:00:20 +02:00
|
|
|
mOptions = options;
|
2016-01-14 04:31:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void doInBackgroundGuarded(Void... params) {
|
|
|
|
|
File source = new File(mUri.getPath());
|
|
|
|
|
FileChannel input = null, output = null;
|
|
|
|
|
try {
|
2020-05-26 12:16:54 +02:00
|
|
|
boolean isAlbumPresent = !"".equals(mOptions.getString("album"));
|
|
|
|
|
|
|
|
|
|
final File environment;
|
|
|
|
|
// Media is not saved into an album when using Environment.DIRECTORY_DCIM.
|
|
|
|
|
if (isAlbumPresent) {
|
|
|
|
|
if ("video".equals(mOptions.getString("type"))) {
|
|
|
|
|
environment = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
|
|
|
|
|
} else {
|
|
|
|
|
environment = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
|
|
|
|
}
|
2020-01-20 18:04:36 +08:00
|
|
|
} else {
|
2020-05-26 12:16:54 +02:00
|
|
|
environment = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
|
2020-01-20 18:04:36 +08:00
|
|
|
}
|
2020-05-26 12:16:54 +02:00
|
|
|
|
2019-08-14 21:00:20 +02:00
|
|
|
File exportDir;
|
2020-05-26 12:16:54 +02:00
|
|
|
if (isAlbumPresent) {
|
2019-08-14 21:00:20 +02:00
|
|
|
exportDir = new File(environment, mOptions.getString("album"));
|
|
|
|
|
if (!exportDir.exists() && !exportDir.mkdirs()) {
|
|
|
|
|
mPromise.reject(ERROR_UNABLE_TO_LOAD, "Album Directory not created. Did you request WRITE_EXTERNAL_STORAGE?");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
exportDir = environment;
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-07 16:37:48 -07:00
|
|
|
if (!exportDir.isDirectory()) {
|
|
|
|
|
mPromise.reject(ERROR_UNABLE_TO_LOAD, "External media storage directory not available");
|
2016-01-14 04:31:51 -08:00
|
|
|
return;
|
|
|
|
|
}
|
2016-06-07 16:37:48 -07:00
|
|
|
File dest = new File(exportDir, source.getName());
|
2016-01-14 04:31:51 -08:00
|
|
|
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()) {
|
2016-06-07 16:37:48 -07:00
|
|
|
dest = new File(exportDir, sourceName + "_" + (n++) + sourceExt);
|
2016-01-14 04:31:51 -08:00
|
|
|
}
|
|
|
|
|
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) {
|
2016-01-21 08:07:01 -08:00
|
|
|
mPromise.resolve(uri.toString());
|
2016-01-14 04:31:51 -08:00
|
|
|
} else {
|
2016-01-26 10:29:47 -08:00
|
|
|
mPromise.reject(ERROR_UNABLE_TO_SAVE, "Could not add image to gallery");
|
2016-01-14 04:31:51 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (IOException e) {
|
2016-01-21 08:07:01 -08:00
|
|
|
mPromise.reject(e);
|
2016-01-14 04:31:51 -08:00
|
|
|
} 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get photos from {@link MediaStore.Images}, most recent first.
|
|
|
|
|
*
|
|
|
|
|
* @param params a map containing the following keys:
|
|
|
|
|
* <ul>
|
|
|
|
|
* <li>first (mandatory): a number representing the number of photos to fetch</li>
|
|
|
|
|
* <li>
|
|
|
|
|
* after (optional): a cursor that matches page_info[end_cursor] returned by a
|
|
|
|
|
* previous call to {@link #getPhotos}
|
|
|
|
|
* </li>
|
|
|
|
|
* <li>groupName (optional): an album name</li>
|
|
|
|
|
* <li>
|
|
|
|
|
* mimeType (optional): restrict returned images to a specific mimetype (e.g.
|
|
|
|
|
* image/jpeg)
|
|
|
|
|
* </li>
|
2017-05-10 03:17:11 -07:00
|
|
|
* <li>
|
|
|
|
|
* assetType (optional): chooses between either photos or videos from the camera roll.
|
|
|
|
|
* Valid values are "Photos" or "Videos". Defaults to photos.
|
|
|
|
|
* </li>
|
2016-01-14 04:31:51 -08:00
|
|
|
* </ul>
|
2016-01-21 08:07:01 -08:00
|
|
|
* @param promise the Promise to be resolved when the photos are loaded; for a format of the
|
2016-01-14 04:31:51 -08:00
|
|
|
* parameters passed to this callback, see {@code getPhotosReturnChecker} in CameraRoll.js
|
|
|
|
|
*/
|
|
|
|
|
@ReactMethod
|
2016-01-21 08:07:01 -08:00
|
|
|
public void getPhotos(final ReadableMap params, final Promise promise) {
|
2016-01-14 04:31:51 -08:00
|
|
|
int first = params.getInt("first");
|
|
|
|
|
String after = params.hasKey("after") ? params.getString("after") : null;
|
|
|
|
|
String groupName = params.hasKey("groupName") ? params.getString("groupName") : null;
|
2019-01-28 07:31:29 -08:00
|
|
|
String assetType = params.hasKey("assetType") ? params.getString("assetType") : ASSET_TYPE_PHOTOS;
|
2020-02-05 16:20:54 +07:00
|
|
|
long fromTime = params.hasKey("fromTime") ? (long) params.getDouble("fromTime") : 0;
|
|
|
|
|
long toTime = params.hasKey("toTime") ? (long) params.getDouble("toTime") : 0;
|
2016-01-14 04:31:51 -08:00
|
|
|
ReadableArray mimeTypes = params.hasKey("mimeTypes")
|
|
|
|
|
? params.getArray("mimeTypes")
|
|
|
|
|
: null;
|
2020-06-16 02:07:25 -07:00
|
|
|
ReadableArray include = params.hasKey("include") ? params.getArray("include") : null;
|
2016-01-14 04:31:51 -08:00
|
|
|
|
2019-01-28 07:31:29 -08:00
|
|
|
new GetMediaTask(
|
2016-01-14 04:31:51 -08:00
|
|
|
getReactApplicationContext(),
|
|
|
|
|
first,
|
|
|
|
|
after,
|
|
|
|
|
groupName,
|
|
|
|
|
mimeTypes,
|
2017-05-10 03:17:11 -07:00
|
|
|
assetType,
|
2020-02-05 16:20:54 +07:00
|
|
|
fromTime,
|
|
|
|
|
toTime,
|
2020-06-16 02:07:25 -07:00
|
|
|
include,
|
2016-01-21 08:07:01 -08:00
|
|
|
promise)
|
2016-01-14 04:31:51 -08:00
|
|
|
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-28 07:31:29 -08:00
|
|
|
private static class GetMediaTask extends GuardedAsyncTask<Void, Void> {
|
2016-01-14 04:31:51 -08:00
|
|
|
private final Context mContext;
|
|
|
|
|
private final int mFirst;
|
|
|
|
|
private final @Nullable String mAfter;
|
|
|
|
|
private final @Nullable String mGroupName;
|
|
|
|
|
private final @Nullable ReadableArray mMimeTypes;
|
2016-01-21 08:07:01 -08:00
|
|
|
private final Promise mPromise;
|
2019-01-28 07:31:29 -08:00
|
|
|
private final String mAssetType;
|
2020-02-05 16:20:54 +07:00
|
|
|
private final long mFromTime;
|
|
|
|
|
private final long mToTime;
|
2020-06-16 02:07:25 -07:00
|
|
|
private final Set<String> mInclude;
|
2016-01-14 04:31:51 -08:00
|
|
|
|
2019-01-28 07:31:29 -08:00
|
|
|
private GetMediaTask(
|
2016-01-14 04:31:51 -08:00
|
|
|
ReactContext context,
|
|
|
|
|
int first,
|
|
|
|
|
@Nullable String after,
|
|
|
|
|
@Nullable String groupName,
|
|
|
|
|
@Nullable ReadableArray mimeTypes,
|
2019-01-28 07:31:29 -08:00
|
|
|
String assetType,
|
2020-02-05 16:20:54 +07:00
|
|
|
long fromTime,
|
|
|
|
|
long toTime,
|
2020-06-16 02:07:25 -07:00
|
|
|
@Nullable ReadableArray include,
|
2016-01-21 08:07:01 -08:00
|
|
|
Promise promise) {
|
2020-03-26 11:56:13 +02:00
|
|
|
super(context);
|
2016-01-14 04:31:51 -08:00
|
|
|
mContext = context;
|
|
|
|
|
mFirst = first;
|
|
|
|
|
mAfter = after;
|
|
|
|
|
mGroupName = groupName;
|
|
|
|
|
mMimeTypes = mimeTypes;
|
2016-01-21 08:07:01 -08:00
|
|
|
mPromise = promise;
|
2017-05-10 03:17:11 -07:00
|
|
|
mAssetType = assetType;
|
2020-02-05 16:20:54 +07:00
|
|
|
mFromTime = fromTime;
|
|
|
|
|
mToTime = toTime;
|
2020-06-16 02:07:25 -07:00
|
|
|
mInclude = createSetFromIncludeArray(include);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Set<String> createSetFromIncludeArray(@Nullable ReadableArray includeArray) {
|
|
|
|
|
Set<String> includeSet = new HashSet<>();
|
|
|
|
|
|
|
|
|
|
if (includeArray == null) {
|
|
|
|
|
return includeSet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < includeArray.size(); i++) {
|
|
|
|
|
@Nullable String includeItem = includeArray.getString(i);
|
|
|
|
|
if (includeItem != null) {
|
|
|
|
|
includeSet.add(includeItem);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return includeSet;
|
2016-01-14 04:31:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void doInBackgroundGuarded(Void... params) {
|
|
|
|
|
StringBuilder selection = new StringBuilder("1");
|
|
|
|
|
List<String> selectionArgs = new ArrayList<>();
|
|
|
|
|
if (!TextUtils.isEmpty(mGroupName)) {
|
|
|
|
|
selection.append(" AND " + SELECTION_BUCKET);
|
|
|
|
|
selectionArgs.add(mGroupName);
|
|
|
|
|
}
|
2019-01-28 07:31:29 -08:00
|
|
|
|
|
|
|
|
if (mAssetType.equals(ASSET_TYPE_PHOTOS)) {
|
|
|
|
|
selection.append(" AND " + MediaStore.Files.FileColumns.MEDIA_TYPE + " = "
|
|
|
|
|
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
|
|
|
|
|
} else if (mAssetType.equals(ASSET_TYPE_VIDEOS)) {
|
|
|
|
|
selection.append(" AND " + MediaStore.Files.FileColumns.MEDIA_TYPE + " = "
|
|
|
|
|
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
|
|
|
|
|
} else if (mAssetType.equals(ASSET_TYPE_ALL)) {
|
|
|
|
|
selection.append(" AND " + MediaStore.Files.FileColumns.MEDIA_TYPE + " IN ("
|
|
|
|
|
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO + ","
|
|
|
|
|
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE + ")");
|
|
|
|
|
} else {
|
|
|
|
|
mPromise.reject(
|
|
|
|
|
ERROR_UNABLE_TO_FILTER,
|
|
|
|
|
"Invalid filter option: '" + mAssetType + "'. Expected one of '"
|
|
|
|
|
+ ASSET_TYPE_PHOTOS + "', '" + ASSET_TYPE_VIDEOS + "' or '" + ASSET_TYPE_ALL + "'."
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-01-14 04:31:51 -08:00
|
|
|
if (mMimeTypes != null && mMimeTypes.size() > 0) {
|
|
|
|
|
selection.append(" AND " + Images.Media.MIME_TYPE + " IN (");
|
|
|
|
|
for (int i = 0; i < mMimeTypes.size(); i++) {
|
|
|
|
|
selection.append("?,");
|
|
|
|
|
selectionArgs.add(mMimeTypes.getString(i));
|
|
|
|
|
}
|
|
|
|
|
selection.replace(selection.length() - 1, selection.length(), ")");
|
|
|
|
|
}
|
2020-01-20 19:52:42 +11:00
|
|
|
|
2020-02-05 16:20:54 +07:00
|
|
|
if (mFromTime > 0) {
|
|
|
|
|
selection.append(" AND " + Images.Media.DATE_TAKEN + " > ?");
|
|
|
|
|
selectionArgs.add(mFromTime + "");
|
|
|
|
|
}
|
|
|
|
|
if (mToTime > 0) {
|
|
|
|
|
selection.append(" AND " + Images.Media.DATE_TAKEN + " <= ?");
|
|
|
|
|
selectionArgs.add(mToTime + "");
|
|
|
|
|
}
|
2020-05-12 18:50:58 +03:00
|
|
|
|
2016-01-14 04:31:51 -08:00
|
|
|
WritableMap response = new WritableNativeMap();
|
|
|
|
|
ContentResolver resolver = mContext.getContentResolver();
|
2020-05-12 18:50:58 +03:00
|
|
|
|
2016-01-14 04:31:51 -08:00
|
|
|
try {
|
2020-01-20 19:52:42 +11:00
|
|
|
// set LIMIT to first + 1 so that we know how to populate page_info
|
|
|
|
|
String limit = "limit=" + (mFirst + 1);
|
|
|
|
|
|
|
|
|
|
if (!TextUtils.isEmpty(mAfter)) {
|
|
|
|
|
limit = "limit=" + mAfter + "," + (mFirst + 1);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-28 07:31:29 -08:00
|
|
|
Cursor media = resolver.query(
|
2020-01-20 19:52:42 +11:00
|
|
|
MediaStore.Files.getContentUri("external").buildUpon().encodedQuery(limit).build(),
|
2016-01-14 04:31:51 -08:00
|
|
|
PROJECTION,
|
|
|
|
|
selection.toString(),
|
|
|
|
|
selectionArgs.toArray(new String[selectionArgs.size()]),
|
2020-01-20 19:52:42 +11:00
|
|
|
Images.Media.DATE_ADDED + " DESC, " + Images.Media.DATE_MODIFIED + " DESC");
|
2019-01-28 07:31:29 -08:00
|
|
|
if (media == null) {
|
|
|
|
|
mPromise.reject(ERROR_UNABLE_TO_LOAD, "Could not get media");
|
2016-01-14 04:31:51 -08:00
|
|
|
} else {
|
|
|
|
|
try {
|
2020-06-16 02:07:25 -07:00
|
|
|
putEdges(resolver, media, response, mFirst, mInclude);
|
2020-01-20 19:52:42 +11:00
|
|
|
putPageInfo(media, response, mFirst, !TextUtils.isEmpty(mAfter) ? Integer.parseInt(mAfter) : 0);
|
2016-01-14 04:31:51 -08:00
|
|
|
} finally {
|
2019-01-28 07:31:29 -08:00
|
|
|
media.close();
|
2016-01-21 08:07:01 -08:00
|
|
|
mPromise.resolve(response);
|
2016-01-14 04:31:51 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (SecurityException e) {
|
2016-01-21 08:07:01 -08:00
|
|
|
mPromise.reject(
|
|
|
|
|
ERROR_UNABLE_TO_LOAD_PERMISSION,
|
2019-01-28 07:31:29 -08:00
|
|
|
"Could not get media: need READ_EXTERNAL_STORAGE permission",
|
2016-01-21 08:07:01 -08:00
|
|
|
e);
|
2016-01-14 04:31:51 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-08 10:17:56 +07:00
|
|
|
@ReactMethod
|
|
|
|
|
public void getAlbums(final ReadableMap params, final Promise promise) {
|
|
|
|
|
String assetType = params.hasKey("assetType") ? params.getString("assetType") : ASSET_TYPE_ALL;
|
|
|
|
|
StringBuilder selection = new StringBuilder("1");
|
|
|
|
|
List<String> selectionArgs = new ArrayList<>();
|
|
|
|
|
if (assetType.equals(ASSET_TYPE_PHOTOS)) {
|
|
|
|
|
selection.append(" AND " + MediaStore.Files.FileColumns.MEDIA_TYPE + " = "
|
|
|
|
|
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
|
|
|
|
|
} else if (assetType.equals(ASSET_TYPE_VIDEOS)) {
|
|
|
|
|
selection.append(" AND " + MediaStore.Files.FileColumns.MEDIA_TYPE + " = "
|
|
|
|
|
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
|
|
|
|
|
} else if (assetType.equals(ASSET_TYPE_ALL)) {
|
|
|
|
|
selection.append(" AND " + MediaStore.Files.FileColumns.MEDIA_TYPE + " IN ("
|
|
|
|
|
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO + ","
|
|
|
|
|
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE + ")");
|
|
|
|
|
} else {
|
|
|
|
|
promise.reject(
|
|
|
|
|
ERROR_UNABLE_TO_FILTER,
|
|
|
|
|
"Invalid filter option: '" + assetType + "'. Expected one of '"
|
|
|
|
|
+ ASSET_TYPE_PHOTOS + "', '" + ASSET_TYPE_VIDEOS + "' or '" + ASSET_TYPE_ALL + "'."
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-05-20 17:59:54 +02:00
|
|
|
|
|
|
|
|
final String[] projection = {MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME};
|
|
|
|
|
|
2020-02-08 10:17:56 +07:00
|
|
|
try {
|
|
|
|
|
Cursor media = getReactApplicationContext().getContentResolver().query(
|
2020-05-20 17:59:54 +02:00
|
|
|
MediaStore.Files.getContentUri("external"),
|
2020-02-08 10:17:56 +07:00
|
|
|
projection,
|
|
|
|
|
selection.toString(),
|
|
|
|
|
selectionArgs.toArray(new String[selectionArgs.size()]),
|
|
|
|
|
null);
|
|
|
|
|
if (media == null) {
|
|
|
|
|
promise.reject(ERROR_UNABLE_TO_LOAD, "Could not get media");
|
|
|
|
|
} else {
|
|
|
|
|
WritableArray response = new WritableNativeArray();
|
|
|
|
|
try {
|
|
|
|
|
if (media.moveToFirst()) {
|
2020-05-20 17:59:54 +02:00
|
|
|
Map<String, Integer> albums = new HashMap<>();
|
2020-02-08 10:17:56 +07:00
|
|
|
do {
|
2020-05-20 17:59:54 +02:00
|
|
|
String albumName = media.getString(media.getColumnIndex(MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME));
|
|
|
|
|
if (albumName != null) {
|
|
|
|
|
Integer albumCount = albums.get(albumName);
|
|
|
|
|
if (albumCount == null) {
|
|
|
|
|
albums.put(albumName, 1);
|
|
|
|
|
} else {
|
|
|
|
|
albums.put(albumName, albumCount + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-08 10:17:56 +07:00
|
|
|
} while (media.moveToNext());
|
2020-05-20 17:59:54 +02:00
|
|
|
|
|
|
|
|
for (Map.Entry<String, Integer> albumEntry : albums.entrySet()) {
|
|
|
|
|
WritableMap album = new WritableNativeMap();
|
|
|
|
|
album.putString("title", albumEntry.getKey());
|
|
|
|
|
album.putInt("count", albumEntry.getValue());
|
|
|
|
|
response.pushMap(album);
|
|
|
|
|
}
|
2020-02-08 10:17:56 +07:00
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
media.close();
|
|
|
|
|
promise.resolve(response);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-20 17:59:54 +02:00
|
|
|
} catch (Exception e) {
|
|
|
|
|
promise.reject(ERROR_UNABLE_TO_LOAD, "Could not get media", e);
|
|
|
|
|
}
|
2020-02-08 10:17:56 +07:00
|
|
|
}
|
|
|
|
|
|
2020-01-20 19:52:42 +11:00
|
|
|
private static void putPageInfo(Cursor media, WritableMap response, int limit, int offset) {
|
2016-01-14 04:31:51 -08:00
|
|
|
WritableMap pageInfo = new WritableNativeMap();
|
2019-01-28 07:31:29 -08:00
|
|
|
pageInfo.putBoolean("has_next_page", limit < media.getCount());
|
|
|
|
|
if (limit < media.getCount()) {
|
2016-01-14 04:31:51 -08:00
|
|
|
pageInfo.putString(
|
2020-01-20 19:52:42 +11:00
|
|
|
"end_cursor",
|
|
|
|
|
Integer.toString(offset + limit)
|
|
|
|
|
);
|
2016-01-14 04:31:51 -08:00
|
|
|
}
|
|
|
|
|
response.putMap("page_info", pageInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void putEdges(
|
|
|
|
|
ContentResolver resolver,
|
2019-01-28 07:31:29 -08:00
|
|
|
Cursor media,
|
2016-01-14 04:31:51 -08:00
|
|
|
WritableMap response,
|
2020-06-16 02:07:25 -07:00
|
|
|
int limit,
|
|
|
|
|
Set<String> include) {
|
2016-01-14 04:31:51 -08:00
|
|
|
WritableArray edges = new WritableNativeArray();
|
2019-01-28 07:31:29 -08:00
|
|
|
media.moveToFirst();
|
|
|
|
|
int mimeTypeIndex = media.getColumnIndex(Images.Media.MIME_TYPE);
|
|
|
|
|
int groupNameIndex = media.getColumnIndex(Images.Media.BUCKET_DISPLAY_NAME);
|
|
|
|
|
int dateTakenIndex = media.getColumnIndex(Images.Media.DATE_TAKEN);
|
2019-02-05 03:05:10 -08:00
|
|
|
int widthIndex = media.getColumnIndex(MediaStore.MediaColumns.WIDTH);
|
|
|
|
|
int heightIndex = media.getColumnIndex(MediaStore.MediaColumns.HEIGHT);
|
2020-05-20 19:35:59 +02:00
|
|
|
int sizeIndex = media.getColumnIndex(MediaStore.MediaColumns.SIZE);
|
2019-01-28 07:31:29 -08:00
|
|
|
int dataIndex = media.getColumnIndex(MediaStore.MediaColumns.DATA);
|
2016-01-14 04:31:51 -08:00
|
|
|
|
2020-06-16 02:07:25 -07:00
|
|
|
boolean includeLocation = include.contains(INCLUDE_LOCATION);
|
|
|
|
|
boolean includeFilename = include.contains(INCLUDE_FILENAME);
|
|
|
|
|
boolean includeFileSize = include.contains(INCLUDE_FILE_SIZE);
|
|
|
|
|
|
2019-01-28 07:31:29 -08:00
|
|
|
for (int i = 0; i < limit && !media.isAfterLast(); i++) {
|
2016-01-14 04:31:51 -08:00
|
|
|
WritableMap edge = new WritableNativeMap();
|
|
|
|
|
WritableMap node = new WritableNativeMap();
|
|
|
|
|
boolean imageInfoSuccess =
|
2020-06-16 02:07:25 -07:00
|
|
|
putImageInfo(resolver, media, node, widthIndex, heightIndex, sizeIndex, dataIndex,
|
|
|
|
|
mimeTypeIndex, includeFilename, includeFileSize);
|
2016-01-14 04:31:51 -08:00
|
|
|
if (imageInfoSuccess) {
|
2019-01-28 07:31:29 -08:00
|
|
|
putBasicNodeInfo(media, node, mimeTypeIndex, groupNameIndex, dateTakenIndex);
|
2020-06-16 02:07:25 -07:00
|
|
|
putLocationInfo(media, node, dataIndex, includeLocation);
|
2016-01-14 04:31:51 -08:00
|
|
|
|
|
|
|
|
edge.putMap("node", node);
|
|
|
|
|
edges.pushMap(edge);
|
|
|
|
|
} else {
|
|
|
|
|
// we skipped an image because we couldn't get its details (e.g. width/height), so we
|
|
|
|
|
// decrement i in order to correctly reach the limit, if the cursor has enough rows
|
|
|
|
|
i--;
|
|
|
|
|
}
|
2019-01-28 07:31:29 -08:00
|
|
|
media.moveToNext();
|
2016-01-14 04:31:51 -08:00
|
|
|
}
|
|
|
|
|
response.putArray("edges", edges);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void putBasicNodeInfo(
|
2019-01-28 07:31:29 -08:00
|
|
|
Cursor media,
|
2016-01-14 04:31:51 -08:00
|
|
|
WritableMap node,
|
|
|
|
|
int mimeTypeIndex,
|
|
|
|
|
int groupNameIndex,
|
|
|
|
|
int dateTakenIndex) {
|
2019-01-28 07:31:29 -08:00
|
|
|
node.putString("type", media.getString(mimeTypeIndex));
|
|
|
|
|
node.putString("group_name", media.getString(groupNameIndex));
|
|
|
|
|
node.putDouble("timestamp", media.getLong(dateTakenIndex) / 1000d);
|
2016-01-14 04:31:51 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static boolean putImageInfo(
|
|
|
|
|
ContentResolver resolver,
|
2019-01-28 07:31:29 -08:00
|
|
|
Cursor media,
|
2016-01-14 04:31:51 -08:00
|
|
|
WritableMap node,
|
|
|
|
|
int widthIndex,
|
2017-08-02 19:59:54 -07:00
|
|
|
int heightIndex,
|
2020-05-20 19:35:59 +02:00
|
|
|
int sizeIndex,
|
2019-05-07 21:59:07 +02:00
|
|
|
int dataIndex,
|
2020-06-16 02:07:25 -07:00
|
|
|
int mimeTypeIndex,
|
|
|
|
|
boolean includeFilename,
|
|
|
|
|
boolean includeFileSize) {
|
2016-01-14 04:31:51 -08:00
|
|
|
WritableMap image = new WritableNativeMap();
|
2019-01-28 07:31:29 -08:00
|
|
|
Uri photoUri = Uri.parse("file://" + media.getString(dataIndex));
|
2016-01-14 04:31:51 -08:00
|
|
|
image.putString("uri", photoUri.toString());
|
2019-02-05 03:05:10 -08:00
|
|
|
float width = media.getInt(widthIndex);
|
|
|
|
|
float height = media.getInt(heightIndex);
|
2019-05-07 21:59:07 +02:00
|
|
|
String mimeType = media.getString(mimeTypeIndex);
|
2019-01-28 07:31:29 -08:00
|
|
|
|
|
|
|
|
if (mimeType != null
|
2019-02-05 03:05:10 -08:00
|
|
|
&& mimeType.startsWith("video")) {
|
2016-01-14 04:31:51 -08:00
|
|
|
try {
|
|
|
|
|
AssetFileDescriptor photoDescriptor = resolver.openAssetFileDescriptor(photoUri, "r");
|
2017-08-15 11:36:44 -07:00
|
|
|
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
|
|
|
|
retriever.setDataSource(photoDescriptor.getFileDescriptor());
|
|
|
|
|
|
2017-10-09 22:34:40 -07:00
|
|
|
try {
|
|
|
|
|
if (width <= 0 || height <= 0) {
|
|
|
|
|
width =
|
|
|
|
|
Integer.parseInt(
|
|
|
|
|
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
|
|
|
|
|
height =
|
|
|
|
|
Integer.parseInt(
|
|
|
|
|
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
|
|
|
|
|
}
|
|
|
|
|
int timeInMillisec =
|
2017-08-15 11:36:42 -07:00
|
|
|
Integer.parseInt(
|
2017-10-09 22:34:40 -07:00
|
|
|
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
|
|
|
|
|
int playableDuration = timeInMillisec / 1000;
|
|
|
|
|
image.putInt("playableDuration", playableDuration);
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
FLog.e(
|
|
|
|
|
ReactConstants.TAG,
|
|
|
|
|
"Number format exception occurred while trying to fetch video metadata for "
|
|
|
|
|
+ photoUri.toString(),
|
|
|
|
|
e);
|
|
|
|
|
return false;
|
|
|
|
|
} finally {
|
|
|
|
|
retriever.release();
|
|
|
|
|
photoDescriptor.close();
|
2017-08-15 11:36:42 -07:00
|
|
|
}
|
2018-10-01 14:24:59 -07:00
|
|
|
} catch (Exception e) {
|
2017-08-15 11:36:44 -07:00
|
|
|
FLog.e(ReactConstants.TAG, "Could not get video metadata for " + photoUri.toString(), e);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (width <= 0 || height <= 0) {
|
|
|
|
|
try {
|
|
|
|
|
AssetFileDescriptor photoDescriptor = resolver.openAssetFileDescriptor(photoUri, "r");
|
|
|
|
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
|
|
|
|
// Set inJustDecodeBounds to true so we don't actually load the Bitmap, but only get its
|
|
|
|
|
// dimensions instead.
|
|
|
|
|
options.inJustDecodeBounds = true;
|
|
|
|
|
BitmapFactory.decodeFileDescriptor(photoDescriptor.getFileDescriptor(), null, options);
|
|
|
|
|
width = options.outWidth;
|
|
|
|
|
height = options.outHeight;
|
2016-01-14 04:31:51 -08:00
|
|
|
photoDescriptor.close();
|
|
|
|
|
} catch (IOException e) {
|
2016-01-26 10:29:47 -08:00
|
|
|
FLog.e(ReactConstants.TAG, "Could not get width/height for " + photoUri.toString(), e);
|
2016-01-14 04:31:51 -08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
image.putDouble("width", width);
|
|
|
|
|
image.putDouble("height", height);
|
2020-06-16 02:07:25 -07:00
|
|
|
|
|
|
|
|
if (includeFilename) {
|
|
|
|
|
File file = new File(media.getString(dataIndex));
|
|
|
|
|
String strFileName = file.getName();
|
|
|
|
|
image.putString("filename", strFileName);
|
|
|
|
|
} else {
|
|
|
|
|
image.putNull("filename");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (includeFileSize) {
|
|
|
|
|
image.putDouble("fileSize", media.getLong(sizeIndex));
|
|
|
|
|
} else {
|
|
|
|
|
image.putNull("fileSize");
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-14 04:31:51 -08:00
|
|
|
node.putMap("image", image);
|
2019-01-28 07:31:29 -08:00
|
|
|
|
2016-01-14 04:31:51 -08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void putLocationInfo(
|
2019-01-28 07:31:29 -08:00
|
|
|
Cursor media,
|
2016-01-14 04:31:51 -08:00
|
|
|
WritableMap node,
|
2020-06-16 02:07:25 -07:00
|
|
|
int dataIndex,
|
|
|
|
|
boolean includeLocation) {
|
|
|
|
|
if (!includeLocation) {
|
|
|
|
|
node.putNull("location");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-30 17:44:37 +05:30
|
|
|
try {
|
|
|
|
|
// location details are no longer indexed for privacy reasons using string Media.LATITUDE, Media.LONGITUDE
|
|
|
|
|
// we manually obtain location metadata using ExifInterface#getLatLong(float[]).
|
|
|
|
|
// ExifInterface is added in API level 5
|
|
|
|
|
final ExifInterface exif = new ExifInterface(media.getString(dataIndex));
|
|
|
|
|
float[] imageCoordinates = new float[2];
|
|
|
|
|
boolean hasCoordinates = exif.getLatLong(imageCoordinates);
|
|
|
|
|
if (hasCoordinates) {
|
|
|
|
|
double longitude = imageCoordinates[1];
|
|
|
|
|
double latitude = imageCoordinates[0];
|
|
|
|
|
WritableMap location = new WritableNativeMap();
|
|
|
|
|
location.putDouble("longitude", longitude);
|
|
|
|
|
location.putDouble("latitude", latitude);
|
|
|
|
|
node.putMap("location", location);
|
|
|
|
|
}
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
FLog.e(ReactConstants.TAG, "Could not read the metadata", e);
|
|
|
|
|
}
|
2016-01-14 04:31:51 -08:00
|
|
|
}
|
2019-11-08 00:13:24 +11:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Delete a set of images.
|
|
|
|
|
*
|
|
|
|
|
* @param uris array of file:// URIs of the images to delete
|
|
|
|
|
* @param promise to be resolved
|
|
|
|
|
*/
|
|
|
|
|
@ReactMethod
|
|
|
|
|
public void deletePhotos(ReadableArray uris, Promise promise) {
|
|
|
|
|
if (uris.size() == 0) {
|
|
|
|
|
promise.reject(ERROR_UNABLE_TO_DELETE, "Need at least one URI to delete");
|
|
|
|
|
} else {
|
|
|
|
|
new DeletePhotos(getReactApplicationContext(), uris, promise)
|
|
|
|
|
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static class DeletePhotos extends GuardedAsyncTask<Void, Void> {
|
|
|
|
|
|
|
|
|
|
private final Context mContext;
|
|
|
|
|
private final ReadableArray mUris;
|
|
|
|
|
private final Promise mPromise;
|
|
|
|
|
|
|
|
|
|
public DeletePhotos(ReactContext context, ReadableArray uris, Promise promise) {
|
2020-03-26 11:56:13 +02:00
|
|
|
super(context);
|
2019-11-08 00:13:24 +11:00
|
|
|
mContext = context;
|
|
|
|
|
mUris = uris;
|
|
|
|
|
mPromise = promise;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void doInBackgroundGuarded(Void... params) {
|
|
|
|
|
ContentResolver resolver = mContext.getContentResolver();
|
|
|
|
|
|
|
|
|
|
// Set up the projection (we only need the ID)
|
|
|
|
|
String[] projection = { MediaStore.Images.Media._ID };
|
|
|
|
|
|
|
|
|
|
// Match on the file path
|
|
|
|
|
String innerWhere = "?";
|
|
|
|
|
for (int i = 1; i < mUris.size(); i++) {
|
|
|
|
|
innerWhere += ", ?";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String selection = MediaStore.Images.Media.DATA + " IN (" + innerWhere + ")";
|
|
|
|
|
// Query for the ID of the media matching the file path
|
|
|
|
|
Uri queryUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
|
|
|
|
|
|
|
|
|
String[] selectionArgs = new String[mUris.size()];
|
|
|
|
|
for (int i = 0; i < mUris.size(); i++) {
|
|
|
|
|
Uri uri = Uri.parse(mUris.getString(i));
|
|
|
|
|
selectionArgs[i] = uri.getPath();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Cursor cursor = resolver.query(queryUri, projection, selection, selectionArgs, null);
|
|
|
|
|
int deletedCount = 0;
|
|
|
|
|
|
|
|
|
|
while (cursor.moveToNext()) {
|
|
|
|
|
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
|
|
|
|
|
Uri deleteUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
|
|
|
|
|
|
|
|
|
|
if (resolver.delete(deleteUri, null, null) == 1) {
|
|
|
|
|
deletedCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cursor.close();
|
|
|
|
|
|
|
|
|
|
if (deletedCount == mUris.size()) {
|
2020-02-05 20:21:59 +11:00
|
|
|
mPromise.resolve(true);
|
2019-11-08 00:13:24 +11:00
|
|
|
} else {
|
|
|
|
|
mPromise.reject(ERROR_UNABLE_TO_DELETE,
|
|
|
|
|
"Could not delete all media, only deleted " + deletedCount + " photos.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-01-14 04:31:51 -08:00
|
|
|
}
|