Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Android) Merge old PR, apply brief mods and update for Android 13 #842

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 134 additions & 27 deletions src/android/CameraLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ Licensed to the Apache Software Foundation (ASF) under one
package org.apache.cordova.camera;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.RecoverableSecurityException;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
Expand Down Expand Up @@ -78,6 +82,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private static final int PICTURE = 0; // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType
private static final int VIDEO = 1; // allow selection of video only, ONLY RETURNS URL
private static final int ALLMEDIA = 2; // allow selection from all media types
private static final int RECOVERABLE_DELETE_REQUEST = 3; // Result of Recoverable Security Exception

private static final int JPEG = 0; // Take a picture of type JPEG
private static final int PNG = 1; // Take a picture of type PNG
Expand Down Expand Up @@ -122,8 +127,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private boolean orientationCorrected; // Has the picture's orientation been corrected
private boolean allowEdit; // Should we allow the user to crop the image.

protected final static String[] permissions = { Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE };

public CallbackContext callbackContext;
private int numPics;

Expand All @@ -133,6 +136,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
private String croppedFilePath;
private ExifHelper exifData; // Exif data from source
private String applicationId;
private Uri pendingDeleteMediaUri;


/**
Expand All @@ -145,10 +149,12 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
*/
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext;

this.applicationId = cordova.getContext().getPackageName();
//Adding an API to CoreAndroid to get the BuildConfigValue
//This allows us to not make this a breaking change to embedding
this.applicationId = (String) BuildHelper.getBuildConfigValue(cordova.getActivity(), "APPLICATION_ID");
this.applicationId = preferences.getString("applicationId", this.applicationId);


if (action.equals(TAKE_PICTURE_ACTION)) {
this.srcType = CAMERA;
this.destType = FILE_URI;
Expand Down Expand Up @@ -193,8 +199,9 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo
}
else if ((this.srcType == PHOTOLIBRARY) || (this.srcType == SAVEDPHOTOALBUM)) {
// FIXME: Stop always requesting the permission
if(!PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
PermissionHelper.requestPermission(this, SAVE_TO_ALBUM_SEC, Manifest.permission.READ_EXTERNAL_STORAGE);
String[] permissions = getPermissions(true, mediaType);
if(!hasPermissions(permissions)) {
PermissionHelper.requestPermissions(this, SAVE_TO_ALBUM_SEC, permissions);
} else {
this.getImage(this.srcType, destType);
}
Expand All @@ -221,6 +228,37 @@ else if ((this.srcType == PHOTOLIBRARY) || (this.srcType == SAVEDPHOTOALBUM)) {
// LOCAL METHODS
//--------------------------------------------------------------------------

private String[] getPermissions(boolean storageOnly, int mediaType) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (storageOnly) {
switch (mediaType) {
case PICTURE:
return new String[]{ Manifest.permission.READ_MEDIA_IMAGES };
case VIDEO:
return new String[]{ Manifest.permission.READ_MEDIA_VIDEO };
default:
return new String[]{ Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO };
}
}
else {
switch (mediaType) {
case PICTURE:
return new String[]{ Manifest.permission.CAMERA, Manifest.permission.CAMERA, Manifest.permission.READ_MEDIA_IMAGES };
case VIDEO:
return new String[]{ Manifest.permission.CAMERA, Manifest.permission.READ_MEDIA_VIDEO };
default:
return new String[]{ Manifest.permission.CAMERA, Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO };
}
}
} else {
if (storageOnly) {
return new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
} else {
return new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
}
}
}

private String getTempDirectoryPath() {
File cache = cordova.getActivity().getCacheDir();
// Create the cache directory if it doesn't exist
Expand All @@ -243,8 +281,13 @@ private String getTempDirectoryPath() {
* @param encodingType Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
*/
public void callTakePicture(int returnType, int encodingType) {
boolean saveAlbumPermission = PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
&& PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
String[] storagePermissions = getPermissions(true, mediaType);
boolean saveAlbumPermission;
if (this.saveToPhotoAlbum) {
saveAlbumPermission = hasPermissions(storagePermissions);
} else {
saveAlbumPermission = true;
}
boolean takePicturePermission = PermissionHelper.hasPermission(this, Manifest.permission.CAMERA);

// CB-10120: The CAMERA permission does not need to be requested unless it is declared
Expand Down Expand Up @@ -275,10 +318,9 @@ public void callTakePicture(int returnType, int encodingType) {
} else if (saveAlbumPermission) {
PermissionHelper.requestPermission(this, TAKE_PIC_SEC, Manifest.permission.CAMERA);
} else if (takePicturePermission) {
PermissionHelper.requestPermissions(this, TAKE_PIC_SEC,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE});
PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, storagePermissions);
} else {
PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, permissions);
PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, getPermissions(false, mediaType));
}
}

Expand Down Expand Up @@ -871,6 +913,20 @@ public void run() {
} else {
this.failPicture("Selection did not complete!");
}
}
// retry media store deletion ...
else if(requestCode == RECOVERABLE_DELETE_REQUEST){
if (resultCode == Activity.RESULT_OK) {
ContentResolver contentResolver = this.cordova.getActivity().getContentResolver();
try {
contentResolver.delete(this.pendingDeleteMediaUri, null, null);
} catch (Exception e) {
LOG.e(LOG_TAG, "Unable to delete media store file after permission was granted");
}
this.pendingDeleteMediaUri = null;
} else {
LOG.e(LOG_TAG, "Failed to delete file. Permission not granted.");
}
}
}

Expand Down Expand Up @@ -976,7 +1032,7 @@ private Bitmap getScaledAndRotatedBitmap(String imageUrl) throws IOException {
This is the only way to determine the orientation of the photo coming from 3rd party providers (Google Drive, Dropbox,etc)
This also ensures we create a scaled bitmap with the correct orientation

We delete the temporary file once we are done
We delete the temporary file once we are done
*/
File localFile = null;
Uri galleryUri = null;
Expand Down Expand Up @@ -1193,7 +1249,7 @@ private Cursor queryImgDB(Uri contentStore) {
/**
* Cleans up after picture taking. Checking for duplicates and that kind of stuff.
*
* @param newImage
* @param newImage
*/
private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) {
if (bitmap != null) {
Expand Down Expand Up @@ -1232,13 +1288,36 @@ private void checkForDuplicateImage(int type) {
// delete the duplicate file if the difference is 2 for file URI or 1 for Data URL
if ((currentNumOfImages - numPics) == diff) {
cursor.moveToLast();
@SuppressLint("Range")
int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
if (diff == 2) {
id--;
}
Uri uri = Uri.parse(contentStore + "/" + id);
this.cordova.getActivity().getContentResolver().delete(uri, null, null);
cursor.close();
try {
this.cordova.getActivity().getContentResolver().delete(uri, null, null);
} catch (SecurityException securityException) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
RecoverableSecurityException recoverableSecurityException;
if (securityException instanceof RecoverableSecurityException) {
recoverableSecurityException = (RecoverableSecurityException) securityException;
} else {
throw new RuntimeException(securityException.getMessage(), securityException);
}
IntentSender requestPermission = recoverableSecurityException.getUserAction().getActionIntent().getIntentSender();
try {
this.cordova.setActivityResultCallback(this);
this.cordova.getActivity().startIntentSenderForResult(requestPermission,
RECOVERABLE_DELETE_REQUEST, null, 0, 0, 0, null);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
} else {
throw new RuntimeException(securityException.getMessage(), securityException);
}
} finally {
cursor.close();
}
}
}

Expand Down Expand Up @@ -1312,20 +1391,39 @@ public void onScanCompleted(String path, Uri uri) {
}

public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) {
for (int r : grantResults) {
if (r == PackageManager.PERMISSION_DENIED) {
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR));
return;
int[] grantResults) throws JSONException {
String version = android.os.Build.VERSION.RELEASE;
if(Integer.parseInt(version) == 13)
{
if(PermissionHelper.hasPermission(this, Manifest.permission.CAMERA)) {
switch (requestCode) {
case TAKE_PIC_SEC:
takePicture(this.destType, this.encodingType);
break;
case SAVE_TO_ALBUM_SEC:
this.getImage(this.srcType, this.destType, this.encodingType);
break;
}
}
else {
PermissionHelper.hasPermission(this, Manifest.permission.CAMERA);
}
}
switch (requestCode) {
case TAKE_PIC_SEC:
takePicture(this.destType, this.encodingType);
break;
case SAVE_TO_ALBUM_SEC:
this.getImage(this.srcType, this.destType);
break;
else{
for (int r : grantResults) {
if (r == PackageManager.PERMISSION_DENIED) {
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR));
return;
}
}
switch (requestCode) {
case TAKE_PIC_SEC:
takePicture(this.destType, this.encodingType);
break;
case SAVE_TO_ALBUM_SEC:
this.getImage(this.srcType, this.destType, this.encodingType);
break;
}
}
}

Expand Down Expand Up @@ -1391,4 +1489,13 @@ public void onRestoreStateForActivityResult(Bundle state, CallbackContext callba

this.callbackContext = callbackContext;
}

private boolean hasPermissions(String[] permissions) {
for (String permission: permissions) {
if (!PermissionHelper.hasPermission(this, permission)) {
return false;
}
}
return true;
}
}