init
This commit is contained in:
83
node_modules/@capacitor/camera/android/build.gradle
generated
vendored
Normal file
83
node_modules/@capacitor/camera/android/build.gradle
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
ext {
|
||||
capacitorVersion = System.getenv('CAPACITOR_VERSION')
|
||||
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
||||
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
|
||||
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
|
||||
androidxExifInterfaceVersion = project.hasProperty('androidxExifInterfaceVersion') ? rootProject.ext.androidxExifInterfaceVersion : '1.3.7'
|
||||
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
|
||||
androidxMaterialVersion = project.hasProperty('androidxMaterialVersion') ? rootProject.ext.androidxMaterialVersion : '1.12.0'
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.7.2'
|
||||
if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") {
|
||||
classpath 'io.github.gradle-nexus:publish-plugin:1.3.0'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") {
|
||||
apply plugin: 'io.github.gradle-nexus.publish-plugin'
|
||||
apply from: file('../../scripts/android/publish-root.gradle')
|
||||
apply from: file('../../scripts/android/publish-module.gradle')
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "com.capacitorjs.plugins.camera"
|
||||
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
|
||||
defaultConfig {
|
||||
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
|
||||
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_21
|
||||
targetCompatibility JavaVersion.VERSION_21
|
||||
}
|
||||
publishing {
|
||||
singleVariant("release")
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") {
|
||||
implementation "com.capacitorjs:core:$capacitorVersion"
|
||||
} else {
|
||||
implementation project(':capacitor-android')
|
||||
}
|
||||
|
||||
implementation "androidx.exifinterface:exifinterface:$androidxExifInterfaceVersion"
|
||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||
implementation "com.google.android.material:material:$androidxMaterialVersion"
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||
}
|
||||
7
node_modules/@capacitor/camera/android/src/main/AndroidManifest.xml
generated
vendored
Normal file
7
node_modules/@capacitor/camera/android/src/main/AndroidManifest.xml
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.media.action.IMAGE_CAPTURE" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
124
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraBottomSheetDialogFragment.java
generated
vendored
Normal file
124
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraBottomSheetDialogFragment.java
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
package com.capacitorjs.plugins.camera;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import java.util.List;
|
||||
|
||||
public class CameraBottomSheetDialogFragment extends BottomSheetDialogFragment {
|
||||
|
||||
interface BottomSheetOnSelectedListener {
|
||||
void onSelected(int index);
|
||||
}
|
||||
|
||||
interface BottomSheetOnCanceledListener {
|
||||
void onCanceled();
|
||||
}
|
||||
|
||||
private BottomSheetOnSelectedListener selectedListener;
|
||||
private BottomSheetOnCanceledListener canceledListener;
|
||||
private List<String> options;
|
||||
private String title;
|
||||
|
||||
void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
void setOptions(List<String> options, BottomSheetOnSelectedListener selectedListener, BottomSheetOnCanceledListener canceledListener) {
|
||||
this.options = options;
|
||||
this.selectedListener = selectedListener;
|
||||
this.canceledListener = canceledListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
super.onCancel(dialog);
|
||||
if (canceledListener != null) {
|
||||
this.canceledListener.onCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
private BottomSheetBehavior.BottomSheetCallback mBottomSheetBehaviorCallback = new BottomSheetBehavior.BottomSheetCallback() {
|
||||
@Override
|
||||
public void onStateChanged(@NonNull View bottomSheet, int newState) {
|
||||
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlide(@NonNull View bottomSheet, float slideOffset) {}
|
||||
};
|
||||
|
||||
@Override
|
||||
@SuppressLint("RestrictedApi")
|
||||
public void setupDialog(Dialog dialog, int style) {
|
||||
super.setupDialog(dialog, style);
|
||||
|
||||
if (options == null || options.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Window w = dialog.getWindow();
|
||||
|
||||
final float scale = getResources().getDisplayMetrics().density;
|
||||
|
||||
float layoutPaddingDp16 = 16.0f;
|
||||
float layoutPaddingDp12 = 12.0f;
|
||||
float layoutPaddingDp8 = 8.0f;
|
||||
int layoutPaddingPx16 = (int) (layoutPaddingDp16 * scale + 0.5f);
|
||||
int layoutPaddingPx12 = (int) (layoutPaddingDp12 * scale + 0.5f);
|
||||
int layoutPaddingPx8 = (int) (layoutPaddingDp8 * scale + 0.5f);
|
||||
|
||||
CoordinatorLayout parentLayout = new CoordinatorLayout(getContext());
|
||||
|
||||
LinearLayout layout = new LinearLayout(getContext());
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setPadding(layoutPaddingPx16, layoutPaddingPx16, layoutPaddingPx16, layoutPaddingPx16);
|
||||
TextView ttv = new TextView(getContext());
|
||||
ttv.setTextColor(Color.parseColor("#757575"));
|
||||
ttv.setPadding(layoutPaddingPx8, layoutPaddingPx8, layoutPaddingPx8, layoutPaddingPx8);
|
||||
ttv.setText(title);
|
||||
layout.addView(ttv);
|
||||
|
||||
for (int i = 0; i < options.size(); i++) {
|
||||
final int optionIndex = i;
|
||||
|
||||
TextView tv = new TextView(getContext());
|
||||
tv.setTextColor(Color.parseColor("#000000"));
|
||||
tv.setPadding(layoutPaddingPx12, layoutPaddingPx12, layoutPaddingPx12, layoutPaddingPx12);
|
||||
tv.setText(options.get(i));
|
||||
tv.setOnClickListener(
|
||||
view -> {
|
||||
if (selectedListener != null) {
|
||||
selectedListener.onSelected(optionIndex);
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
);
|
||||
layout.addView(tv);
|
||||
}
|
||||
|
||||
parentLayout.addView(layout.getRootView());
|
||||
|
||||
dialog.setContentView(parentLayout.getRootView());
|
||||
|
||||
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) ((View) parentLayout.getParent()).getLayoutParams();
|
||||
CoordinatorLayout.Behavior<View> behavior = params.getBehavior();
|
||||
|
||||
if (behavior instanceof BottomSheetBehavior) {
|
||||
BottomSheetBehavior<View> bottomSheetBehavior = (BottomSheetBehavior<View>) behavior;
|
||||
bottomSheetBehavior.addBottomSheetCallback(mBottomSheetBehaviorCallback);
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
}
|
||||
}
|
||||
}
|
||||
923
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraPlugin.java
generated
vendored
Normal file
923
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraPlugin.java
generated
vendored
Normal file
@@ -0,0 +1,923 @@
|
||||
package com.capacitorjs.plugins.camera;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Base64;
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import androidx.activity.result.ActivityResultCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.ActivityResultRegistryOwner;
|
||||
import androidx.activity.result.PickVisualMediaRequest;
|
||||
import androidx.activity.result.contract.ActivityResultContract;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.FileProvider;
|
||||
import com.getcapacitor.FileUtils;
|
||||
import com.getcapacitor.JSArray;
|
||||
import com.getcapacitor.JSObject;
|
||||
import com.getcapacitor.Logger;
|
||||
import com.getcapacitor.PermissionState;
|
||||
import com.getcapacitor.Plugin;
|
||||
import com.getcapacitor.PluginCall;
|
||||
import com.getcapacitor.PluginMethod;
|
||||
import com.getcapacitor.annotation.ActivityCallback;
|
||||
import com.getcapacitor.annotation.CapacitorPlugin;
|
||||
import com.getcapacitor.annotation.Permission;
|
||||
import com.getcapacitor.annotation.PermissionCallback;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.json.JSONException;
|
||||
|
||||
/**
|
||||
* The Camera plugin makes it easy to take a photo or have the user select a photo
|
||||
* from their albums.
|
||||
*
|
||||
* On Android, this plugin sends an intent that opens the stock Camera app.
|
||||
*
|
||||
* Adapted from https://developer.android.com/training/camera/photobasics.html
|
||||
*/
|
||||
@SuppressLint("InlinedApi")
|
||||
@CapacitorPlugin(
|
||||
name = "Camera",
|
||||
permissions = {
|
||||
@Permission(strings = { Manifest.permission.CAMERA }, alias = CameraPlugin.CAMERA),
|
||||
@Permission(strings = {}, alias = CameraPlugin.PHOTOS),
|
||||
// SDK VERSIONS 29 AND BELOW
|
||||
@Permission(
|
||||
strings = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE },
|
||||
alias = CameraPlugin.SAVE_GALLERY
|
||||
),
|
||||
/*
|
||||
SDK VERSIONS 30-32
|
||||
This alias is a placeholder and the SAVE_GALLERY alias will be updated to use this permission
|
||||
so that the end user does not need to explicitly use separate aliases depending
|
||||
on the SDK version.
|
||||
*/
|
||||
@Permission(strings = { Manifest.permission.READ_EXTERNAL_STORAGE }, alias = CameraPlugin.READ_EXTERNAL_STORAGE)
|
||||
}
|
||||
)
|
||||
public class CameraPlugin extends Plugin {
|
||||
|
||||
// Permission alias constants
|
||||
static final String CAMERA = "camera";
|
||||
static final String PHOTOS = "photos";
|
||||
static final String SAVE_GALLERY = "saveGallery";
|
||||
static final String READ_EXTERNAL_STORAGE = "readExternalStorage";
|
||||
|
||||
// Message constants
|
||||
private static final String INVALID_RESULT_TYPE_ERROR = "Invalid resultType option";
|
||||
private static final String PERMISSION_DENIED_ERROR_CAMERA = "User denied access to camera";
|
||||
private static final String NO_CAMERA_ERROR = "Device doesn't have a camera available";
|
||||
private static final String NO_CAMERA_ACTIVITY_ERROR = "Unable to resolve camera activity";
|
||||
private static final String NO_PHOTO_ACTIVITY_ERROR = "Unable to resolve photo activity";
|
||||
private static final String IMAGE_FILE_SAVE_ERROR = "Unable to create photo on disk";
|
||||
private static final String IMAGE_PROCESS_NO_FILE_ERROR = "Unable to process image, file not found on disk";
|
||||
private static final String UNABLE_TO_PROCESS_IMAGE = "Unable to process image";
|
||||
private static final String IMAGE_EDIT_ERROR = "Unable to edit image";
|
||||
private static final String IMAGE_GALLERY_SAVE_ERROR = "Unable to save the image in the gallery";
|
||||
private static final String USER_CANCELLED = "User cancelled photos app";
|
||||
|
||||
private String imageFileSavePath;
|
||||
private String imageEditedFileSavePath;
|
||||
private Uri imageFileUri;
|
||||
private Uri imagePickedContentUri;
|
||||
private boolean isEdited = false;
|
||||
private boolean isFirstRequest = true;
|
||||
private boolean isSaved = false;
|
||||
private ActivityResultLauncher<PickVisualMediaRequest> pickMultipleMedia = null;
|
||||
private ActivityResultLauncher<PickVisualMediaRequest> pickMedia = null;
|
||||
|
||||
private final AtomicInteger mNextLocalRequestCode = new AtomicInteger();
|
||||
|
||||
private CameraSettings settings = new CameraSettings();
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
super.load();
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
public void getPhoto(PluginCall call) {
|
||||
isEdited = false;
|
||||
settings = getSettings(call);
|
||||
doShow(call);
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
public void pickImages(PluginCall call) {
|
||||
settings = getSettings(call);
|
||||
openPhotos(call, true);
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
public void pickLimitedLibraryPhotos(PluginCall call) {
|
||||
call.unimplemented("not supported on android");
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
public void getLimitedLibraryPhotos(PluginCall call) {
|
||||
call.unimplemented("not supported on android");
|
||||
}
|
||||
|
||||
private void doShow(PluginCall call) {
|
||||
switch (settings.getSource()) {
|
||||
case CAMERA:
|
||||
showCamera(call);
|
||||
break;
|
||||
case PHOTOS:
|
||||
showPhotos(call);
|
||||
break;
|
||||
default:
|
||||
showPrompt(call);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void showPrompt(final PluginCall call) {
|
||||
// We have all necessary permissions, open the camera
|
||||
List<String> options = new ArrayList<>();
|
||||
options.add(call.getString("promptLabelPhoto", "From Photos"));
|
||||
options.add(call.getString("promptLabelPicture", "Take Picture"));
|
||||
|
||||
final CameraBottomSheetDialogFragment fragment = new CameraBottomSheetDialogFragment();
|
||||
fragment.setTitle(call.getString("promptLabelHeader", "Photo"));
|
||||
fragment.setOptions(
|
||||
options,
|
||||
index -> {
|
||||
if (index == 0) {
|
||||
settings.setSource(CameraSource.PHOTOS);
|
||||
openPhotos(call);
|
||||
} else if (index == 1) {
|
||||
settings.setSource(CameraSource.CAMERA);
|
||||
openCamera(call);
|
||||
}
|
||||
},
|
||||
() -> call.reject(USER_CANCELLED)
|
||||
);
|
||||
fragment.show(getActivity().getSupportFragmentManager(), "capacitorModalsActionSheet");
|
||||
}
|
||||
|
||||
private void showCamera(final PluginCall call) {
|
||||
if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) {
|
||||
call.reject(NO_CAMERA_ERROR);
|
||||
return;
|
||||
}
|
||||
openCamera(call);
|
||||
}
|
||||
|
||||
private void showPhotos(final PluginCall call) {
|
||||
openPhotos(call);
|
||||
}
|
||||
|
||||
private boolean checkCameraPermissions(PluginCall call) {
|
||||
// if the manifest does not contain the camera permissions key, we don't need to ask the user
|
||||
boolean needCameraPerms = isPermissionDeclared(CAMERA);
|
||||
boolean hasCameraPerms = !needCameraPerms || getPermissionState(CAMERA) == PermissionState.GRANTED;
|
||||
boolean hasGalleryPerms = getPermissionState(SAVE_GALLERY) == PermissionState.GRANTED;
|
||||
|
||||
// If we want to save to the gallery, we need two permissions
|
||||
// actually we only need permissions to save to gallery for Android <= 9 (API 28)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// we might still need to request permission for the camera
|
||||
if (!hasCameraPerms) {
|
||||
requestPermissionForAlias(CAMERA, call, "cameraPermissionsCallback");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// we need to request permissions to save to gallery for Android <= 9
|
||||
if (settings.isSaveToGallery() && !(hasCameraPerms && hasGalleryPerms) && isFirstRequest) {
|
||||
isFirstRequest = false;
|
||||
String[] aliases;
|
||||
if (needCameraPerms) {
|
||||
aliases = new String[] { CAMERA, SAVE_GALLERY };
|
||||
} else {
|
||||
aliases = new String[] { SAVE_GALLERY };
|
||||
}
|
||||
requestPermissionForAliases(aliases, call, "cameraPermissionsCallback");
|
||||
return false;
|
||||
}
|
||||
// If we don't need to save to the gallery, we can just ask for camera permissions
|
||||
else if (!hasCameraPerms) {
|
||||
requestPermissionForAlias(CAMERA, call, "cameraPermissionsCallback");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes the plugin call after a camera permission request
|
||||
*
|
||||
* @see #getPhoto(PluginCall)
|
||||
* @param call the plugin call
|
||||
*/
|
||||
@PermissionCallback
|
||||
private void cameraPermissionsCallback(PluginCall call) {
|
||||
if (call.getMethodName().equals("pickImages")) {
|
||||
openPhotos(call, true);
|
||||
} else {
|
||||
if (settings.getSource() == CameraSource.CAMERA && getPermissionState(CAMERA) != PermissionState.GRANTED) {
|
||||
Logger.debug(getLogTag(), "User denied camera permission: " + getPermissionState(CAMERA).toString());
|
||||
call.reject(PERMISSION_DENIED_ERROR_CAMERA);
|
||||
return;
|
||||
}
|
||||
doShow(call);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestPermissionForAliases(@NonNull String[] aliases, @NonNull PluginCall call, @NonNull String callbackName) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
for (int i = 0; i < aliases.length; i++) {
|
||||
if (aliases[i].equals(SAVE_GALLERY)) {
|
||||
aliases[i] = READ_EXTERNAL_STORAGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
super.requestPermissionForAliases(aliases, call, callbackName);
|
||||
}
|
||||
|
||||
private CameraSettings getSettings(PluginCall call) {
|
||||
CameraSettings settings = new CameraSettings();
|
||||
settings.setResultType(getResultType(call.getString("resultType")));
|
||||
settings.setSaveToGallery(call.getBoolean("saveToGallery", CameraSettings.DEFAULT_SAVE_IMAGE_TO_GALLERY));
|
||||
settings.setAllowEditing(call.getBoolean("allowEditing", false));
|
||||
settings.setQuality(call.getInt("quality", CameraSettings.DEFAULT_QUALITY));
|
||||
settings.setWidth(call.getInt("width", 0));
|
||||
settings.setHeight(call.getInt("height", 0));
|
||||
settings.setShouldResize(settings.getWidth() > 0 || settings.getHeight() > 0);
|
||||
settings.setShouldCorrectOrientation(call.getBoolean("correctOrientation", CameraSettings.DEFAULT_CORRECT_ORIENTATION));
|
||||
try {
|
||||
settings.setSource(CameraSource.valueOf(call.getString("source", CameraSource.PROMPT.getSource())));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
settings.setSource(CameraSource.PROMPT);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
private CameraResultType getResultType(String resultType) {
|
||||
if (resultType == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return CameraResultType.valueOf(resultType.toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logger.debug(getLogTag(), "Invalid result type \"" + resultType + "\", defaulting to base64");
|
||||
return CameraResultType.BASE64;
|
||||
}
|
||||
}
|
||||
|
||||
public void openCamera(final PluginCall call) {
|
||||
if (checkCameraPermissions(call)) {
|
||||
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
if (takePictureIntent.resolveActivity(getContext().getPackageManager()) != null) {
|
||||
// If we will be saving the photo, send the target file along
|
||||
try {
|
||||
String appId = getAppId();
|
||||
File photoFile = CameraUtils.createImageFile(getActivity());
|
||||
imageFileSavePath = photoFile.getAbsolutePath();
|
||||
// TODO: Verify provider config exists
|
||||
imageFileUri = FileProvider.getUriForFile(getActivity(), appId + ".fileprovider", photoFile);
|
||||
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri);
|
||||
} catch (Exception ex) {
|
||||
call.reject(IMAGE_FILE_SAVE_ERROR, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
startActivityForResult(call, takePictureIntent, "processCameraImage");
|
||||
} else {
|
||||
call.reject(NO_CAMERA_ACTIVITY_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void openPhotos(final PluginCall call) {
|
||||
openPhotos(call, false);
|
||||
}
|
||||
|
||||
private <I, O> ActivityResultLauncher<I> registerActivityResultLauncher(
|
||||
ActivityResultContract<I, O> contract,
|
||||
ActivityResultCallback<O> callback
|
||||
) {
|
||||
String key = "cap_activity_rq#" + mNextLocalRequestCode.getAndIncrement();
|
||||
if (bridge.getFragment() != null) {
|
||||
Object host = bridge.getFragment().getHost();
|
||||
if (host instanceof ActivityResultRegistryOwner) {
|
||||
return ((ActivityResultRegistryOwner) host).getActivityResultRegistry().register(key, contract, callback);
|
||||
}
|
||||
return bridge.getFragment().requireActivity().getActivityResultRegistry().register(key, contract, callback);
|
||||
}
|
||||
return bridge.getActivity().getActivityResultRegistry().register(key, contract, callback);
|
||||
}
|
||||
|
||||
private ActivityResultContract<PickVisualMediaRequest, List<Uri>> getContractForCall(final PluginCall call) {
|
||||
int limit = call.getInt("limit", 0);
|
||||
if (limit > 1) {
|
||||
return new ActivityResultContracts.PickMultipleVisualMedia(limit);
|
||||
} else {
|
||||
return new ActivityResultContracts.PickMultipleVisualMedia();
|
||||
}
|
||||
}
|
||||
|
||||
private void openPhotos(final PluginCall call, boolean multiple) {
|
||||
try {
|
||||
if (multiple) {
|
||||
pickMultipleMedia =
|
||||
registerActivityResultLauncher(
|
||||
getContractForCall(call),
|
||||
uris -> {
|
||||
if (!uris.isEmpty()) {
|
||||
Executor executor = Executors.newSingleThreadExecutor();
|
||||
executor.execute(
|
||||
() -> {
|
||||
JSObject ret = new JSObject();
|
||||
JSArray photos = new JSArray();
|
||||
for (Uri imageUri : uris) {
|
||||
try {
|
||||
JSObject processResult = processPickedImages(imageUri);
|
||||
if (
|
||||
processResult.getString("error") != null && !processResult.getString("error").isEmpty()
|
||||
) {
|
||||
call.reject(processResult.getString("error"));
|
||||
return;
|
||||
} else {
|
||||
photos.put(processResult);
|
||||
}
|
||||
} catch (SecurityException ex) {
|
||||
call.reject("SecurityException");
|
||||
}
|
||||
}
|
||||
ret.put("photos", photos);
|
||||
call.resolve(ret);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
call.reject(USER_CANCELLED);
|
||||
}
|
||||
pickMultipleMedia.unregister();
|
||||
}
|
||||
);
|
||||
pickMultipleMedia.launch(
|
||||
new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build()
|
||||
);
|
||||
} else {
|
||||
pickMedia =
|
||||
registerActivityResultLauncher(
|
||||
new ActivityResultContracts.PickVisualMedia(),
|
||||
uri -> {
|
||||
if (uri != null) {
|
||||
imagePickedContentUri = uri;
|
||||
processPickedImage(uri, call);
|
||||
} else {
|
||||
call.reject(USER_CANCELLED);
|
||||
}
|
||||
pickMedia.unregister();
|
||||
}
|
||||
);
|
||||
pickMedia.launch(
|
||||
new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build()
|
||||
);
|
||||
}
|
||||
} catch (ActivityNotFoundException ex) {
|
||||
call.reject(NO_PHOTO_ACTIVITY_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@ActivityCallback
|
||||
public void processCameraImage(PluginCall call, ActivityResult result) {
|
||||
settings = getSettings(call);
|
||||
if (imageFileSavePath == null) {
|
||||
call.reject(IMAGE_PROCESS_NO_FILE_ERROR);
|
||||
return;
|
||||
}
|
||||
// Load the image as a Bitmap
|
||||
File f = new File(imageFileSavePath);
|
||||
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
|
||||
Uri contentUri = Uri.fromFile(f);
|
||||
Bitmap bitmap = BitmapFactory.decodeFile(imageFileSavePath, bmOptions);
|
||||
|
||||
if (bitmap == null) {
|
||||
call.reject(USER_CANCELLED);
|
||||
return;
|
||||
}
|
||||
|
||||
returnResult(call, bitmap, contentUri);
|
||||
}
|
||||
|
||||
public void processPickedImage(PluginCall call, ActivityResult result) {
|
||||
settings = getSettings(call);
|
||||
Intent data = result.getData();
|
||||
if (data == null) {
|
||||
call.reject(USER_CANCELLED);
|
||||
return;
|
||||
}
|
||||
|
||||
Uri u = data.getData();
|
||||
|
||||
imagePickedContentUri = u;
|
||||
|
||||
processPickedImage(u, call);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private ArrayList<Parcelable> getLegacyParcelableArrayList(Bundle bundle, String key) {
|
||||
return bundle.getParcelableArrayList(key);
|
||||
}
|
||||
|
||||
private void processPickedImage(Uri imageUri, PluginCall call) {
|
||||
InputStream imageStream = null;
|
||||
|
||||
try {
|
||||
imageStream = getContext().getContentResolver().openInputStream(imageUri);
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(imageStream);
|
||||
|
||||
if (bitmap == null) {
|
||||
call.reject("Unable to process bitmap");
|
||||
return;
|
||||
}
|
||||
|
||||
returnResult(call, bitmap, imageUri);
|
||||
} catch (OutOfMemoryError err) {
|
||||
call.reject("Out of memory");
|
||||
} catch (FileNotFoundException ex) {
|
||||
call.reject("No such image found", ex);
|
||||
} finally {
|
||||
if (imageStream != null) {
|
||||
try {
|
||||
imageStream.close();
|
||||
} catch (IOException e) {
|
||||
Logger.error(getLogTag(), UNABLE_TO_PROCESS_IMAGE, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JSObject processPickedImages(Uri imageUri) {
|
||||
InputStream imageStream = null;
|
||||
JSObject ret = new JSObject();
|
||||
try {
|
||||
imageStream = getContext().getContentResolver().openInputStream(imageUri);
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(imageStream);
|
||||
|
||||
if (bitmap == null) {
|
||||
ret.put("error", "Unable to process bitmap");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ExifWrapper exif = ImageUtils.getExifData(getContext(), bitmap, imageUri);
|
||||
try {
|
||||
bitmap = prepareBitmap(bitmap, imageUri, exif);
|
||||
} catch (IOException e) {
|
||||
ret.put("error", UNABLE_TO_PROCESS_IMAGE);
|
||||
return ret;
|
||||
}
|
||||
// Compress the final image and prepare for output to client
|
||||
ByteArrayOutputStream bitmapOutputStream = new ByteArrayOutputStream();
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, settings.getQuality(), bitmapOutputStream);
|
||||
|
||||
Uri newUri = getTempImage(imageUri, bitmapOutputStream);
|
||||
exif.copyExif(newUri.getPath());
|
||||
if (newUri != null) {
|
||||
ret.put("format", "jpeg");
|
||||
ret.put("exif", exif.toJson());
|
||||
ret.put("path", newUri.toString());
|
||||
ret.put("webPath", FileUtils.getPortablePath(getContext(), bridge.getLocalUrl(), newUri));
|
||||
} else {
|
||||
ret.put("error", UNABLE_TO_PROCESS_IMAGE);
|
||||
}
|
||||
return ret;
|
||||
} catch (OutOfMemoryError err) {
|
||||
ret.put("error", "Out of memory");
|
||||
} catch (FileNotFoundException ex) {
|
||||
ret.put("error", "No such image found");
|
||||
Logger.error(getLogTag(), "No such image found", ex);
|
||||
} finally {
|
||||
if (imageStream != null) {
|
||||
try {
|
||||
imageStream.close();
|
||||
} catch (IOException e) {
|
||||
Logger.error(getLogTag(), UNABLE_TO_PROCESS_IMAGE, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ActivityCallback
|
||||
private void processEditedImage(PluginCall call, ActivityResult result) {
|
||||
isEdited = true;
|
||||
settings = getSettings(call);
|
||||
if (result.getResultCode() == Activity.RESULT_CANCELED) {
|
||||
// User cancelled the edit operation, if this file was picked from photos,
|
||||
// process the original picked image, otherwise process it as a camera photo
|
||||
if (imagePickedContentUri != null) {
|
||||
processPickedImage(imagePickedContentUri, call);
|
||||
} else {
|
||||
processCameraImage(call, result);
|
||||
}
|
||||
} else {
|
||||
processPickedImage(call, result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the modified image on the same path,
|
||||
* or on a temporary location if it's a content url
|
||||
* @param uri
|
||||
* @param is
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private Uri saveImage(Uri uri, InputStream is) throws IOException {
|
||||
File outFile = null;
|
||||
if (uri.getScheme().equals("content")) {
|
||||
outFile = getTempFile(uri);
|
||||
} else {
|
||||
outFile = new File(uri.getPath());
|
||||
}
|
||||
try {
|
||||
writePhoto(outFile, is);
|
||||
} catch (FileNotFoundException ex) {
|
||||
// Some gallery apps return read only file url, create a temporary file for modifications
|
||||
outFile = getTempFile(uri);
|
||||
writePhoto(outFile, is);
|
||||
}
|
||||
return Uri.fromFile(outFile);
|
||||
}
|
||||
|
||||
private void writePhoto(File outFile, InputStream is) throws IOException {
|
||||
FileOutputStream fos = new FileOutputStream(outFile);
|
||||
byte[] buffer = new byte[1024];
|
||||
int len;
|
||||
while ((len = is.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, len);
|
||||
}
|
||||
fos.close();
|
||||
}
|
||||
|
||||
private File getTempFile(Uri uri) {
|
||||
String filename = Uri.parse(Uri.decode(uri.toString())).getLastPathSegment();
|
||||
if (!filename.contains(".jpg") && !filename.contains(".jpeg")) {
|
||||
filename += "." + (new java.util.Date()).getTime() + ".jpeg";
|
||||
}
|
||||
File cacheDir = getContext().getCacheDir();
|
||||
return new File(cacheDir, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* After processing the image, return the final result back to the caller.
|
||||
* @param call
|
||||
* @param bitmap
|
||||
* @param u
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void returnResult(PluginCall call, Bitmap bitmap, Uri u) {
|
||||
ExifWrapper exif = ImageUtils.getExifData(getContext(), bitmap, u);
|
||||
try {
|
||||
bitmap = prepareBitmap(bitmap, u, exif);
|
||||
} catch (IOException e) {
|
||||
call.reject(UNABLE_TO_PROCESS_IMAGE);
|
||||
return;
|
||||
}
|
||||
// Compress the final image and prepare for output to client
|
||||
ByteArrayOutputStream bitmapOutputStream = new ByteArrayOutputStream();
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, settings.getQuality(), bitmapOutputStream);
|
||||
|
||||
if (settings.isAllowEditing() && !isEdited) {
|
||||
editImage(call, u, bitmapOutputStream);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean saveToGallery = call.getBoolean("saveToGallery", CameraSettings.DEFAULT_SAVE_IMAGE_TO_GALLERY);
|
||||
if (saveToGallery && (imageEditedFileSavePath != null || imageFileSavePath != null)) {
|
||||
isSaved = true;
|
||||
try {
|
||||
String fileToSavePath = imageEditedFileSavePath != null ? imageEditedFileSavePath : imageFileSavePath;
|
||||
File fileToSave = new File(fileToSavePath);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
ContentResolver resolver = getContext().getContentResolver();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileToSave.getName());
|
||||
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
|
||||
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
|
||||
|
||||
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
Uri uri = resolver.insert(contentUri, values);
|
||||
|
||||
if (uri == null) {
|
||||
throw new IOException("Failed to create new MediaStore record.");
|
||||
}
|
||||
|
||||
OutputStream stream = resolver.openOutputStream(uri);
|
||||
if (stream == null) {
|
||||
throw new IOException("Failed to open output stream.");
|
||||
}
|
||||
|
||||
Boolean inserted = bitmap.compress(Bitmap.CompressFormat.JPEG, settings.getQuality(), stream);
|
||||
|
||||
if (!inserted) {
|
||||
isSaved = false;
|
||||
}
|
||||
} else {
|
||||
String inserted = MediaStore.Images.Media.insertImage(
|
||||
getContext().getContentResolver(),
|
||||
fileToSavePath,
|
||||
fileToSave.getName(),
|
||||
""
|
||||
);
|
||||
|
||||
if (inserted == null) {
|
||||
isSaved = false;
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
isSaved = false;
|
||||
Logger.error(getLogTag(), IMAGE_GALLERY_SAVE_ERROR, e);
|
||||
} catch (IOException e) {
|
||||
isSaved = false;
|
||||
Logger.error(getLogTag(), IMAGE_GALLERY_SAVE_ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.getResultType() == CameraResultType.BASE64) {
|
||||
returnBase64(call, exif, bitmapOutputStream);
|
||||
} else if (settings.getResultType() == CameraResultType.URI) {
|
||||
returnFileURI(call, exif, bitmap, u, bitmapOutputStream);
|
||||
} else if (settings.getResultType() == CameraResultType.DATAURL) {
|
||||
returnDataUrl(call, exif, bitmapOutputStream);
|
||||
} else {
|
||||
call.reject(INVALID_RESULT_TYPE_ERROR);
|
||||
}
|
||||
// Result returned, clear stored paths and images
|
||||
if (settings.getResultType() != CameraResultType.URI) {
|
||||
deleteImageFile();
|
||||
}
|
||||
imageFileSavePath = null;
|
||||
imageFileUri = null;
|
||||
imagePickedContentUri = null;
|
||||
imageEditedFileSavePath = null;
|
||||
}
|
||||
|
||||
private void deleteImageFile() {
|
||||
if (imageFileSavePath != null && !settings.isSaveToGallery()) {
|
||||
File photoFile = new File(imageFileSavePath);
|
||||
if (photoFile.exists()) {
|
||||
photoFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void returnFileURI(PluginCall call, ExifWrapper exif, Bitmap bitmap, Uri u, ByteArrayOutputStream bitmapOutputStream) {
|
||||
Uri newUri = getTempImage(u, bitmapOutputStream);
|
||||
exif.copyExif(newUri.getPath());
|
||||
if (newUri != null) {
|
||||
JSObject ret = new JSObject();
|
||||
ret.put("format", "jpeg");
|
||||
ret.put("exif", exif.toJson());
|
||||
ret.put("path", newUri.toString());
|
||||
ret.put("webPath", FileUtils.getPortablePath(getContext(), bridge.getLocalUrl(), newUri));
|
||||
ret.put("saved", isSaved);
|
||||
call.resolve(ret);
|
||||
} else {
|
||||
call.reject(UNABLE_TO_PROCESS_IMAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private Uri getTempImage(Uri u, ByteArrayOutputStream bitmapOutputStream) {
|
||||
ByteArrayInputStream bis = null;
|
||||
Uri newUri = null;
|
||||
try {
|
||||
bis = new ByteArrayInputStream(bitmapOutputStream.toByteArray());
|
||||
newUri = saveImage(u, bis);
|
||||
} catch (IOException ex) {} finally {
|
||||
if (bis != null) {
|
||||
try {
|
||||
bis.close();
|
||||
} catch (IOException e) {
|
||||
Logger.error(getLogTag(), UNABLE_TO_PROCESS_IMAGE, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return newUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply our standard processing of the bitmap, returning a new one and
|
||||
* recycling the old one in the process
|
||||
* @param bitmap
|
||||
* @param imageUri
|
||||
* @param exif
|
||||
* @return
|
||||
*/
|
||||
private Bitmap prepareBitmap(Bitmap bitmap, Uri imageUri, ExifWrapper exif) throws IOException {
|
||||
if (settings.isShouldCorrectOrientation()) {
|
||||
final Bitmap newBitmap = ImageUtils.correctOrientation(getContext(), bitmap, imageUri, exif);
|
||||
bitmap = replaceBitmap(bitmap, newBitmap);
|
||||
}
|
||||
|
||||
if (settings.isShouldResize()) {
|
||||
final Bitmap newBitmap = ImageUtils.resize(bitmap, settings.getWidth(), settings.getHeight());
|
||||
bitmap = replaceBitmap(bitmap, newBitmap);
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private Bitmap replaceBitmap(Bitmap bitmap, final Bitmap newBitmap) {
|
||||
if (bitmap != newBitmap) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
bitmap = newBitmap;
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private void returnDataUrl(PluginCall call, ExifWrapper exif, ByteArrayOutputStream bitmapOutputStream) {
|
||||
byte[] byteArray = bitmapOutputStream.toByteArray();
|
||||
String encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP);
|
||||
|
||||
JSObject data = new JSObject();
|
||||
data.put("format", "jpeg");
|
||||
data.put("dataUrl", "data:image/jpeg;base64," + encoded);
|
||||
data.put("exif", exif.toJson());
|
||||
call.resolve(data);
|
||||
}
|
||||
|
||||
private void returnBase64(PluginCall call, ExifWrapper exif, ByteArrayOutputStream bitmapOutputStream) {
|
||||
byte[] byteArray = bitmapOutputStream.toByteArray();
|
||||
String encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP);
|
||||
|
||||
JSObject data = new JSObject();
|
||||
data.put("format", "jpeg");
|
||||
data.put("base64String", encoded);
|
||||
data.put("exif", exif.toJson());
|
||||
call.resolve(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
@PluginMethod
|
||||
public void requestPermissions(PluginCall call) {
|
||||
// If the camera permission is defined in the manifest, then we have to prompt the user
|
||||
// or else we will get a security exception when trying to present the camera. If, however,
|
||||
// it is not defined in the manifest then we don't need to prompt and it will just work.
|
||||
if (isPermissionDeclared(CAMERA)) {
|
||||
// just request normally
|
||||
super.requestPermissions(call);
|
||||
} else {
|
||||
// the manifest does not define camera permissions, so we need to decide what to do
|
||||
// first, extract the permissions being requested
|
||||
JSArray providedPerms = call.getArray("permissions");
|
||||
List<String> permsList = null;
|
||||
if (providedPerms != null) {
|
||||
try {
|
||||
permsList = providedPerms.toList();
|
||||
} catch (JSONException e) {}
|
||||
}
|
||||
|
||||
if (
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ||
|
||||
(permsList != null && permsList.size() == 1 && (permsList.contains(CAMERA) || permsList.contains(PHOTOS)))
|
||||
) {
|
||||
// either we're on Android 13+ (storage permissions do not apply)
|
||||
// or the only thing being asked for was the camera so we can just return the current state
|
||||
checkPermissions(call);
|
||||
} else {
|
||||
requestPermissionForAlias(SAVE_GALLERY, call, "checkPermissions");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, PermissionState> getPermissionStates() {
|
||||
Map<String, PermissionState> permissionStates = super.getPermissionStates();
|
||||
|
||||
// If Camera is not in the manifest and therefore not required, say the permission is granted
|
||||
if (!isPermissionDeclared(CAMERA)) {
|
||||
permissionStates.put(CAMERA, PermissionState.GRANTED);
|
||||
}
|
||||
|
||||
if (permissionStates.containsKey(PHOTOS)) {
|
||||
permissionStates.put(PHOTOS, PermissionState.GRANTED);
|
||||
}
|
||||
|
||||
// If the SDK version is 30 or higher, update the SAVE_GALLERY state to match the READ_EXTERNAL_STORAGE state.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
String alias = READ_EXTERNAL_STORAGE;
|
||||
if (permissionStates.containsKey(alias)) {
|
||||
permissionStates.put(SAVE_GALLERY, permissionStates.get(alias));
|
||||
}
|
||||
}
|
||||
|
||||
return permissionStates;
|
||||
}
|
||||
|
||||
private void editImage(PluginCall call, Uri uri, ByteArrayOutputStream bitmapOutputStream) {
|
||||
try {
|
||||
Uri tempImage = getTempImage(uri, bitmapOutputStream);
|
||||
Intent editIntent = createEditIntent(tempImage);
|
||||
if (editIntent != null) {
|
||||
startActivityForResult(call, editIntent, "processEditedImage");
|
||||
} else {
|
||||
call.reject(IMAGE_EDIT_ERROR);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
call.reject(IMAGE_EDIT_ERROR, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Intent createEditIntent(Uri origPhotoUri) {
|
||||
try {
|
||||
File editFile = new File(origPhotoUri.getPath());
|
||||
Uri editUri = FileProvider.getUriForFile(getActivity(), getContext().getPackageName() + ".fileprovider", editFile);
|
||||
Intent editIntent = new Intent(Intent.ACTION_EDIT);
|
||||
editIntent.setDataAndType(editUri, "image/*");
|
||||
imageEditedFileSavePath = editFile.getAbsolutePath();
|
||||
int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
||||
editIntent.addFlags(flags);
|
||||
editIntent.putExtra(MediaStore.EXTRA_OUTPUT, editUri);
|
||||
|
||||
List<ResolveInfo> resInfoList;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
resInfoList =
|
||||
getContext()
|
||||
.getPackageManager()
|
||||
.queryIntentActivities(editIntent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
|
||||
} else {
|
||||
resInfoList = legacyQueryIntentActivities(editIntent);
|
||||
}
|
||||
|
||||
for (ResolveInfo resolveInfo : resInfoList) {
|
||||
String packageName = resolveInfo.activityInfo.packageName;
|
||||
getContext().grantUriPermission(packageName, editUri, flags);
|
||||
}
|
||||
return editIntent;
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private List<ResolveInfo> legacyQueryIntentActivities(Intent intent) {
|
||||
return getContext().getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bundle saveInstanceState() {
|
||||
Bundle bundle = super.saveInstanceState();
|
||||
if (bundle != null) {
|
||||
bundle.putString("cameraImageFileSavePath", imageFileSavePath);
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void restoreState(Bundle state) {
|
||||
String storedImageFileSavePath = state.getString("cameraImageFileSavePath");
|
||||
if (storedImageFileSavePath != null) {
|
||||
imageFileSavePath = storedImageFileSavePath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister activity result launches to prevent leaks.
|
||||
*/
|
||||
@Override
|
||||
protected void handleOnDestroy() {
|
||||
if (pickMedia != null) {
|
||||
pickMedia.unregister();
|
||||
}
|
||||
if (pickMultipleMedia != null) {
|
||||
pickMultipleMedia.unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraResultType.java
generated
vendored
Normal file
17
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraResultType.java
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.capacitorjs.plugins.camera;
|
||||
|
||||
public enum CameraResultType {
|
||||
BASE64("base64"),
|
||||
URI("uri"),
|
||||
DATAURL("dataUrl");
|
||||
|
||||
private String type;
|
||||
|
||||
CameraResultType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
90
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSettings.java
generated
vendored
Normal file
90
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSettings.java
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
package com.capacitorjs.plugins.camera;
|
||||
|
||||
public class CameraSettings {
|
||||
|
||||
public static final int DEFAULT_QUALITY = 90;
|
||||
public static final boolean DEFAULT_SAVE_IMAGE_TO_GALLERY = false;
|
||||
public static final boolean DEFAULT_CORRECT_ORIENTATION = true;
|
||||
|
||||
private CameraResultType resultType = CameraResultType.BASE64;
|
||||
private int quality = DEFAULT_QUALITY;
|
||||
private boolean shouldResize = false;
|
||||
private boolean shouldCorrectOrientation = DEFAULT_CORRECT_ORIENTATION;
|
||||
private boolean saveToGallery = DEFAULT_SAVE_IMAGE_TO_GALLERY;
|
||||
private boolean allowEditing = false;
|
||||
private int width = 0;
|
||||
private int height = 0;
|
||||
private CameraSource source = CameraSource.PROMPT;
|
||||
|
||||
public CameraResultType getResultType() {
|
||||
return resultType;
|
||||
}
|
||||
|
||||
public void setResultType(CameraResultType resultType) {
|
||||
this.resultType = resultType;
|
||||
}
|
||||
|
||||
public int getQuality() {
|
||||
return quality;
|
||||
}
|
||||
|
||||
public void setQuality(int quality) {
|
||||
this.quality = quality;
|
||||
}
|
||||
|
||||
public boolean isShouldResize() {
|
||||
return shouldResize;
|
||||
}
|
||||
|
||||
public void setShouldResize(boolean shouldResize) {
|
||||
this.shouldResize = shouldResize;
|
||||
}
|
||||
|
||||
public boolean isShouldCorrectOrientation() {
|
||||
return shouldCorrectOrientation;
|
||||
}
|
||||
|
||||
public void setShouldCorrectOrientation(boolean shouldCorrectOrientation) {
|
||||
this.shouldCorrectOrientation = shouldCorrectOrientation;
|
||||
}
|
||||
|
||||
public boolean isSaveToGallery() {
|
||||
return saveToGallery;
|
||||
}
|
||||
|
||||
public void setSaveToGallery(boolean saveToGallery) {
|
||||
this.saveToGallery = saveToGallery;
|
||||
}
|
||||
|
||||
public boolean isAllowEditing() {
|
||||
return allowEditing;
|
||||
}
|
||||
|
||||
public void setAllowEditing(boolean allowEditing) {
|
||||
this.allowEditing = allowEditing;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(int width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(int height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public CameraSource getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public void setSource(CameraSource source) {
|
||||
this.source = source;
|
||||
}
|
||||
}
|
||||
17
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSource.java
generated
vendored
Normal file
17
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSource.java
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.capacitorjs.plugins.camera;
|
||||
|
||||
public enum CameraSource {
|
||||
PROMPT("PROMPT"),
|
||||
CAMERA("CAMERA"),
|
||||
PHOTOS("PHOTOS");
|
||||
|
||||
private String source;
|
||||
|
||||
CameraSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return this.source;
|
||||
}
|
||||
}
|
||||
34
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraUtils.java
generated
vendored
Normal file
34
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraUtils.java
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package com.capacitorjs.plugins.camera;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import androidx.core.content.FileProvider;
|
||||
import com.getcapacitor.Logger;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class CameraUtils {
|
||||
|
||||
public static Uri createImageFileUri(Activity activity, String appId) throws IOException {
|
||||
File photoFile = CameraUtils.createImageFile(activity);
|
||||
return FileProvider.getUriForFile(activity, appId + ".fileprovider", photoFile);
|
||||
}
|
||||
|
||||
public static File createImageFile(Activity activity) throws IOException {
|
||||
// Create an image file name
|
||||
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
||||
String imageFileName = "JPEG_" + timeStamp + "_";
|
||||
File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
||||
|
||||
File image = File.createTempFile(imageFileName, /* prefix */".jpg", /* suffix */storageDir/* directory */);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
protected static String getLogTag() {
|
||||
return Logger.tags("CameraUtils");
|
||||
}
|
||||
}
|
||||
205
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/ExifWrapper.java
generated
vendored
Normal file
205
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/ExifWrapper.java
generated
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
package com.capacitorjs.plugins.camera;
|
||||
|
||||
import static androidx.exifinterface.media.ExifInterface.*;
|
||||
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
import com.getcapacitor.JSObject;
|
||||
|
||||
public class ExifWrapper {
|
||||
|
||||
private final ExifInterface exif;
|
||||
private final String[] attributes = new String[] {
|
||||
TAG_APERTURE_VALUE,
|
||||
TAG_ARTIST,
|
||||
TAG_BITS_PER_SAMPLE,
|
||||
TAG_BODY_SERIAL_NUMBER,
|
||||
TAG_BRIGHTNESS_VALUE,
|
||||
TAG_CAMERA_OWNER_NAME,
|
||||
TAG_CFA_PATTERN,
|
||||
TAG_COLOR_SPACE,
|
||||
TAG_COMPONENTS_CONFIGURATION,
|
||||
TAG_COMPRESSED_BITS_PER_PIXEL,
|
||||
TAG_COMPRESSION,
|
||||
TAG_CONTRAST,
|
||||
TAG_COPYRIGHT,
|
||||
TAG_CUSTOM_RENDERED,
|
||||
TAG_DATETIME,
|
||||
TAG_DATETIME_DIGITIZED,
|
||||
TAG_DATETIME_ORIGINAL,
|
||||
TAG_DEFAULT_CROP_SIZE,
|
||||
TAG_DEVICE_SETTING_DESCRIPTION,
|
||||
TAG_DIGITAL_ZOOM_RATIO,
|
||||
TAG_DNG_VERSION,
|
||||
TAG_EXIF_VERSION,
|
||||
TAG_EXPOSURE_BIAS_VALUE,
|
||||
TAG_EXPOSURE_INDEX,
|
||||
TAG_EXPOSURE_MODE,
|
||||
TAG_EXPOSURE_PROGRAM,
|
||||
TAG_EXPOSURE_TIME,
|
||||
TAG_FILE_SOURCE,
|
||||
TAG_FLASH,
|
||||
TAG_FLASHPIX_VERSION,
|
||||
TAG_FLASH_ENERGY,
|
||||
TAG_FOCAL_LENGTH,
|
||||
TAG_FOCAL_LENGTH_IN_35MM_FILM,
|
||||
TAG_FOCAL_PLANE_RESOLUTION_UNIT,
|
||||
TAG_FOCAL_PLANE_X_RESOLUTION,
|
||||
TAG_FOCAL_PLANE_Y_RESOLUTION,
|
||||
TAG_F_NUMBER,
|
||||
TAG_GAIN_CONTROL,
|
||||
TAG_GAMMA,
|
||||
TAG_GPS_ALTITUDE,
|
||||
TAG_GPS_ALTITUDE_REF,
|
||||
TAG_GPS_AREA_INFORMATION,
|
||||
TAG_GPS_DATESTAMP,
|
||||
TAG_GPS_DEST_BEARING,
|
||||
TAG_GPS_DEST_BEARING_REF,
|
||||
TAG_GPS_DEST_DISTANCE,
|
||||
TAG_GPS_DEST_DISTANCE_REF,
|
||||
TAG_GPS_DEST_LATITUDE,
|
||||
TAG_GPS_DEST_LATITUDE_REF,
|
||||
TAG_GPS_DEST_LONGITUDE,
|
||||
TAG_GPS_DEST_LONGITUDE_REF,
|
||||
TAG_GPS_DIFFERENTIAL,
|
||||
TAG_GPS_DOP,
|
||||
TAG_GPS_H_POSITIONING_ERROR,
|
||||
TAG_GPS_IMG_DIRECTION,
|
||||
TAG_GPS_IMG_DIRECTION_REF,
|
||||
TAG_GPS_LATITUDE,
|
||||
TAG_GPS_LATITUDE_REF,
|
||||
TAG_GPS_LONGITUDE,
|
||||
TAG_GPS_LONGITUDE_REF,
|
||||
TAG_GPS_MAP_DATUM,
|
||||
TAG_GPS_MEASURE_MODE,
|
||||
TAG_GPS_PROCESSING_METHOD,
|
||||
TAG_GPS_SATELLITES,
|
||||
TAG_GPS_SPEED,
|
||||
TAG_GPS_SPEED_REF,
|
||||
TAG_GPS_STATUS,
|
||||
TAG_GPS_TIMESTAMP,
|
||||
TAG_GPS_TRACK,
|
||||
TAG_GPS_TRACK_REF,
|
||||
TAG_GPS_VERSION_ID,
|
||||
TAG_IMAGE_DESCRIPTION,
|
||||
TAG_IMAGE_LENGTH,
|
||||
TAG_IMAGE_UNIQUE_ID,
|
||||
TAG_IMAGE_WIDTH,
|
||||
TAG_INTEROPERABILITY_INDEX,
|
||||
TAG_ISO_SPEED,
|
||||
TAG_ISO_SPEED_LATITUDE_YYY,
|
||||
TAG_ISO_SPEED_LATITUDE_ZZZ,
|
||||
TAG_JPEG_INTERCHANGE_FORMAT,
|
||||
TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
|
||||
TAG_LENS_MAKE,
|
||||
TAG_LENS_MODEL,
|
||||
TAG_LENS_SERIAL_NUMBER,
|
||||
TAG_LENS_SPECIFICATION,
|
||||
TAG_LIGHT_SOURCE,
|
||||
TAG_MAKE,
|
||||
TAG_MAKER_NOTE,
|
||||
TAG_MAX_APERTURE_VALUE,
|
||||
TAG_METERING_MODE,
|
||||
TAG_MODEL,
|
||||
TAG_NEW_SUBFILE_TYPE,
|
||||
TAG_OECF,
|
||||
TAG_OFFSET_TIME,
|
||||
TAG_OFFSET_TIME_DIGITIZED,
|
||||
TAG_OFFSET_TIME_ORIGINAL,
|
||||
TAG_ORF_ASPECT_FRAME,
|
||||
TAG_ORF_PREVIEW_IMAGE_LENGTH,
|
||||
TAG_ORF_PREVIEW_IMAGE_START,
|
||||
TAG_ORF_THUMBNAIL_IMAGE,
|
||||
TAG_ORIENTATION,
|
||||
TAG_PHOTOGRAPHIC_SENSITIVITY,
|
||||
TAG_PHOTOMETRIC_INTERPRETATION,
|
||||
TAG_PIXEL_X_DIMENSION,
|
||||
TAG_PIXEL_Y_DIMENSION,
|
||||
TAG_PLANAR_CONFIGURATION,
|
||||
TAG_PRIMARY_CHROMATICITIES,
|
||||
TAG_RECOMMENDED_EXPOSURE_INDEX,
|
||||
TAG_REFERENCE_BLACK_WHITE,
|
||||
TAG_RELATED_SOUND_FILE,
|
||||
TAG_RESOLUTION_UNIT,
|
||||
TAG_ROWS_PER_STRIP,
|
||||
TAG_RW2_ISO,
|
||||
TAG_RW2_JPG_FROM_RAW,
|
||||
TAG_RW2_SENSOR_BOTTOM_BORDER,
|
||||
TAG_RW2_SENSOR_LEFT_BORDER,
|
||||
TAG_RW2_SENSOR_RIGHT_BORDER,
|
||||
TAG_RW2_SENSOR_TOP_BORDER,
|
||||
TAG_SAMPLES_PER_PIXEL,
|
||||
TAG_SATURATION,
|
||||
TAG_SCENE_CAPTURE_TYPE,
|
||||
TAG_SCENE_TYPE,
|
||||
TAG_SENSING_METHOD,
|
||||
TAG_SENSITIVITY_TYPE,
|
||||
TAG_SHARPNESS,
|
||||
TAG_SHUTTER_SPEED_VALUE,
|
||||
TAG_SOFTWARE,
|
||||
TAG_SPATIAL_FREQUENCY_RESPONSE,
|
||||
TAG_SPECTRAL_SENSITIVITY,
|
||||
TAG_STANDARD_OUTPUT_SENSITIVITY,
|
||||
TAG_STRIP_BYTE_COUNTS,
|
||||
TAG_STRIP_OFFSETS,
|
||||
TAG_SUBFILE_TYPE,
|
||||
TAG_SUBJECT_AREA,
|
||||
TAG_SUBJECT_DISTANCE,
|
||||
TAG_SUBJECT_DISTANCE_RANGE,
|
||||
TAG_SUBJECT_LOCATION,
|
||||
TAG_SUBSEC_TIME,
|
||||
TAG_SUBSEC_TIME_DIGITIZED,
|
||||
TAG_SUBSEC_TIME_ORIGINAL,
|
||||
TAG_THUMBNAIL_IMAGE_LENGTH,
|
||||
TAG_THUMBNAIL_IMAGE_WIDTH,
|
||||
TAG_TRANSFER_FUNCTION,
|
||||
TAG_USER_COMMENT,
|
||||
TAG_WHITE_BALANCE,
|
||||
TAG_WHITE_POINT,
|
||||
TAG_XMP,
|
||||
TAG_X_RESOLUTION,
|
||||
TAG_Y_CB_CR_COEFFICIENTS,
|
||||
TAG_Y_CB_CR_POSITIONING,
|
||||
TAG_Y_CB_CR_SUB_SAMPLING,
|
||||
TAG_Y_RESOLUTION
|
||||
};
|
||||
|
||||
public ExifWrapper(ExifInterface exif) {
|
||||
this.exif = exif;
|
||||
}
|
||||
|
||||
public JSObject toJson() {
|
||||
JSObject ret = new JSObject();
|
||||
|
||||
if (this.exif == null) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (int i = 0; i < attributes.length; i++) {
|
||||
p(ret, attributes[i]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void p(JSObject o, String tag) {
|
||||
String val = exif.getAttribute(tag);
|
||||
o.put(tag, val);
|
||||
}
|
||||
|
||||
public void copyExif(String destFile) {
|
||||
try {
|
||||
ExifInterface destExif = new ExifInterface(destFile);
|
||||
for (int i = 0; i < attributes.length; i++) {
|
||||
String value = exif.getAttribute(attributes[i]);
|
||||
if (value != null) {
|
||||
destExif.setAttribute(attributes[i], value);
|
||||
}
|
||||
}
|
||||
destExif.saveAttributes();
|
||||
} catch (Exception ex) {}
|
||||
}
|
||||
|
||||
public void resetOrientation() {
|
||||
exif.resetOrientation();
|
||||
}
|
||||
}
|
||||
124
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/ImageUtils.java
generated
vendored
Normal file
124
node_modules/@capacitor/camera/android/src/main/java/com/capacitorjs/plugins/camera/ImageUtils.java
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
package com.capacitorjs.plugins.camera;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Matrix;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
import com.getcapacitor.Logger;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class ImageUtils {
|
||||
|
||||
/**
|
||||
* Resize an image to the given max width and max height. Constraint can be put
|
||||
* on one dimension, or both. Resize will always preserve aspect ratio.
|
||||
* @param bitmap
|
||||
* @param desiredMaxWidth
|
||||
* @param desiredMaxHeight
|
||||
* @return a new, scaled Bitmap
|
||||
*/
|
||||
public static Bitmap resize(Bitmap bitmap, final int desiredMaxWidth, final int desiredMaxHeight) {
|
||||
return ImageUtils.resizePreservingAspectRatio(bitmap, desiredMaxWidth, desiredMaxHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize an image to the given max width and max height. Constraint can be put
|
||||
* on one dimension, or both. Resize will always preserve aspect ratio.
|
||||
* @param bitmap
|
||||
* @param desiredMaxWidth
|
||||
* @param desiredMaxHeight
|
||||
* @return a new, scaled Bitmap
|
||||
*/
|
||||
private static Bitmap resizePreservingAspectRatio(Bitmap bitmap, final int desiredMaxWidth, final int desiredMaxHeight) {
|
||||
int width = bitmap.getWidth();
|
||||
int height = bitmap.getHeight();
|
||||
|
||||
// 0 is treated as 'no restriction'
|
||||
int maxHeight = desiredMaxHeight == 0 ? height : desiredMaxHeight;
|
||||
int maxWidth = desiredMaxWidth == 0 ? width : desiredMaxWidth;
|
||||
|
||||
// resize with preserved aspect ratio
|
||||
float newWidth = Math.min(width, maxWidth);
|
||||
float newHeight = (height * newWidth) / width;
|
||||
|
||||
if (newHeight > maxHeight) {
|
||||
newWidth = (width * maxHeight) / height;
|
||||
newHeight = maxHeight;
|
||||
}
|
||||
return Bitmap.createScaledBitmap(bitmap, Math.round(newWidth), Math.round(newHeight), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an image with the given matrix
|
||||
* @param bitmap
|
||||
* @param matrix
|
||||
* @return
|
||||
*/
|
||||
private static Bitmap transform(final Bitmap bitmap, final Matrix matrix) {
|
||||
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the orientation of an image by reading its exif information and rotating
|
||||
* the appropriate amount for portrait mode
|
||||
* @param bitmap
|
||||
* @param imageUri
|
||||
* @param exif
|
||||
* @return
|
||||
*/
|
||||
public static Bitmap correctOrientation(final Context c, final Bitmap bitmap, final Uri imageUri, ExifWrapper exif) throws IOException {
|
||||
final int orientation = getOrientation(c, imageUri);
|
||||
if (orientation != 0) {
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postRotate(orientation);
|
||||
exif.resetOrientation();
|
||||
return transform(bitmap, matrix);
|
||||
} else {
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getOrientation(final Context c, final Uri imageUri) throws IOException {
|
||||
int result = 0;
|
||||
|
||||
try (InputStream iStream = c.getContentResolver().openInputStream(imageUri)) {
|
||||
final ExifInterface exifInterface = new ExifInterface(iStream);
|
||||
|
||||
final int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
|
||||
|
||||
if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
|
||||
result = 90;
|
||||
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {
|
||||
result = 180;
|
||||
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {
|
||||
result = 270;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ExifWrapper getExifData(final Context c, final Bitmap bitmap, final Uri imageUri) {
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = c.getContentResolver().openInputStream(imageUri);
|
||||
final ExifInterface exifInterface = new ExifInterface(stream);
|
||||
|
||||
return new ExifWrapper(exifInterface);
|
||||
} catch (IOException ex) {
|
||||
Logger.error("Error loading exif data from image", ex);
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
return new ExifWrapper(null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user