From f671b9f4b472806ef43db6dcf302d4503cf1828c Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Tue, 9 Feb 2021 16:55:29 -0600 Subject: [PATCH] feat(android): implements Activity Result API changes for permissions and activity results (#222) Co-authored-by: jcesarmobile --- .../plugins/camera/CameraPlugin.java | 99 +++++++++---------- .../plugins/filesystem/FilesystemPlugin.java | 44 +++++---- .../geolocation/GeolocationPlugin.java | 11 ++- .../LocalNotificationsPlugin.java | 6 -- .../plugins/share/SharePlugin.java | 37 ++++--- 5 files changed, 92 insertions(+), 105 deletions(-) diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraPlugin.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraPlugin.java index 6d6362b20..9c03484d9 100644 --- a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraPlugin.java +++ b/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraPlugin.java @@ -1,7 +1,6 @@ package com.capacitorjs.plugins.camera; import android.Manifest; -import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; @@ -10,6 +9,7 @@ import android.os.Bundle; import android.provider.MediaStore; import android.util.Base64; +import androidx.activity.result.ActivityResult; import androidx.core.content.FileProvider; import com.getcapacitor.FileUtils; import com.getcapacitor.JSArray; @@ -19,9 +19,10 @@ import com.getcapacitor.Plugin; import com.getcapacitor.PluginCall; import com.getcapacitor.PluginMethod; -import com.getcapacitor.PluginRequestCodes; +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; @@ -45,17 +46,19 @@ @CapacitorPlugin( name = "Camera", permissions = { - @Permission(strings = { Manifest.permission.CAMERA }, alias = "camera"), - @Permission(strings = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }, alias = "photos") - }, - requestCodes = { CameraPlugin.REQUEST_IMAGE_CAPTURE, CameraPlugin.REQUEST_IMAGE_PICK, CameraPlugin.REQUEST_IMAGE_EDIT } + @Permission(strings = { Manifest.permission.CAMERA }, alias = CameraPlugin.CAMERA), + @Permission( + strings = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }, + alias = CameraPlugin.PHOTOS + ) + } ) public class CameraPlugin extends Plugin { - // Request codes - static final int REQUEST_IMAGE_CAPTURE = PluginRequestCodes.CAMERA_IMAGE_CAPTURE; - static final int REQUEST_IMAGE_PICK = PluginRequestCodes.CAMERA_IMAGE_PICK; - static final int REQUEST_IMAGE_EDIT = PluginRequestCodes.CAMERA_IMAGE_EDIT; + // Permission alias constants + static final String CAMERA = "camera"; + static final String PHOTOS = "photos"; + // Message constants private static final String INVALID_RESULT_TYPE_ERROR = "Invalid resultType option"; private static final String PERMISSION_DENIED_ERROR = "Unable to access camera, user denied permission request"; @@ -74,7 +77,7 @@ public class CameraPlugin extends Plugin { private CameraSettings settings = new CameraSettings(); - @PluginMethod(permissionCallback = "cameraPermissionsCallback") + @PluginMethod public void getPhoto(PluginCall call) { isEdited = false; @@ -138,31 +141,31 @@ private void showPhotos(final PluginCall 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 = hasDefinedPermissions(new String[] { Manifest.permission.CAMERA }); - boolean hasCameraPerms = !needCameraPerms || getPermissionState("camera") == PermissionState.GRANTED; - boolean hasPhotoPerms = getPermissionState("photos") == PermissionState.GRANTED; + boolean hasCameraPerms = !needCameraPerms || getPermissionState(CAMERA) == PermissionState.GRANTED; + boolean hasPhotoPerms = getPermissionState(PHOTOS) == PermissionState.GRANTED; // If we want to save to the gallery, we need two permissions if (settings.isSaveToGallery() && !(hasCameraPerms && hasPhotoPerms)) { String[] aliases; if (needCameraPerms) { - aliases = new String[] { "camera", "photos" }; + aliases = new String[] { CAMERA, PHOTOS }; } else { - aliases = new String[] { "photos" }; + aliases = new String[] { PHOTOS }; } - requestPermissionForAliases(aliases, call); + 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); + requestPermissionForAlias(CAMERA, call, "cameraPermissionsCallback"); return false; } return true; } private boolean checkPhotosPermissions(PluginCall call) { - if (getPermissionState("photos") != PermissionState.GRANTED) { - requestPermissionForAlias("photos", call); + if (getPermissionState(PHOTOS) != PermissionState.GRANTED) { + requestPermissionForAlias(PHOTOS, call, "cameraPermissionsCallback"); return false; } return true; @@ -174,13 +177,14 @@ private boolean checkPhotosPermissions(PluginCall call) { * @see #getPhoto(PluginCall) * @param call the plugin call */ + @PermissionCallback private void cameraPermissionsCallback(PluginCall call) { - if (settings.getSource() == CameraSource.CAMERA && getPermissionState("camera") != PermissionState.GRANTED) { - Logger.debug(getLogTag(), "User denied camera permission: " + getPermissionState("camera").toString()); + if (settings.getSource() == CameraSource.CAMERA && getPermissionState(CAMERA) != PermissionState.GRANTED) { + Logger.debug(getLogTag(), "User denied camera permission: " + getPermissionState(CAMERA).toString()); call.reject(PERMISSION_DENIED_ERROR); return; - } else if (settings.getSource() == CameraSource.PHOTOS && getPermissionState("photos") != PermissionState.GRANTED) { - Logger.debug(getLogTag(), "User denied photos permission: " + getPermissionState("photos").toString()); + } else if (settings.getSource() == CameraSource.PHOTOS && getPermissionState(PHOTOS) != PermissionState.GRANTED) { + Logger.debug(getLogTag(), "User denied photos permission: " + getPermissionState(PHOTOS).toString()); call.reject(PERMISSION_DENIED_ERROR); return; } @@ -235,7 +239,7 @@ public void openCamera(final PluginCall call) { return; } - startActivityForResult(call, takePictureIntent, REQUEST_IMAGE_CAPTURE); + startActivityForResult(call, takePictureIntent, "processCameraImage"); } else { call.reject(NO_CAMERA_ACTIVITY_ERROR); } @@ -246,11 +250,12 @@ public void openPhotos(final PluginCall call) { if (checkPhotosPermissions(call)) { Intent intent = new Intent(Intent.ACTION_PICK); intent.setType("image/*"); - startActivityForResult(call, intent, REQUEST_IMAGE_PICK); + startActivityForResult(call, intent, "processPickedImage"); } } - public void processCameraImage(PluginCall call) { + @ActivityCallback + public void processCameraImage(PluginCall call, ActivityResult result) { if (imageFileSavePath == null) { call.reject(IMAGE_PROCESS_NO_FILE_ERROR); return; @@ -269,7 +274,9 @@ public void processCameraImage(PluginCall call) { returnResult(call, bitmap, contentUri); } - public void processPickedImage(PluginCall call, Intent data) { + @ActivityCallback + public void processPickedImage(PluginCall call, ActivityResult result) { + Intent data = result.getData(); if (data == null) { call.reject("No image picked"); return; @@ -304,6 +311,12 @@ public void processPickedImage(PluginCall call, Intent data) { } } + @ActivityCallback + private void processEditedImage(PluginCall call, ActivityResult result) { + isEdited = true; + processPickedImage(call, result); + } + /** * Save the modified image we've created to a temporary location, so we can * return a URI to it later @@ -483,12 +496,12 @@ public void requestPermissions(PluginCall call) { permsList = providedPerms.toList(); } catch (JSONException e) {} - if (permsList != null && permsList.size() == 1 && permsList.contains("camera")) { + if (permsList != null && permsList.size() == 1 && permsList.contains(CAMERA)) { // the only thing being asked for was the camera so we can just return the current state checkPermissions(call); } else { // we need to ask about photos so request storage permissions - requestPermissionForAlias("photos", call); + requestPermissionForAlias(PHOTOS, call, "checkPermissions"); } } } @@ -499,34 +512,12 @@ public Map getPermissionStates() { // If Camera is not in the manifest and therefore not required, say the permission is granted if (!hasDefinedPermissions(new String[] { Manifest.permission.CAMERA })) { - permissionStates.put("camera", PermissionState.GRANTED); + permissionStates.put(CAMERA, PermissionState.GRANTED); } return permissionStates; } - @Override - protected void handleOnActivityResult(PluginCall savedCall, int requestCode, int resultCode, Intent data) { - if (savedCall == null) { - return; - } - - settings = getSettings(savedCall); - - if (requestCode == REQUEST_IMAGE_CAPTURE) { - processCameraImage(savedCall); - } else if (requestCode == REQUEST_IMAGE_PICK) { - processPickedImage(savedCall, data); - } else if (requestCode == REQUEST_IMAGE_EDIT && resultCode == Activity.RESULT_OK) { - isEdited = true; - processPickedImage(savedCall, data); - } else if (resultCode == Activity.RESULT_CANCELED && imageFileSavePath != null) { - imageEditedFileSavePath = null; - isEdited = true; - processCameraImage(savedCall); - } - } - private void editImage(PluginCall call, Bitmap bitmap, Uri uri, ByteArrayOutputStream bitmapOutputStream) { Uri origPhotoUri = uri; if (imageFileUri != null) { @@ -534,12 +525,12 @@ private void editImage(PluginCall call, Bitmap bitmap, Uri uri, ByteArrayOutputS } try { Intent editIntent = createEditIntent(origPhotoUri, false); - startActivityForResult(call, editIntent, REQUEST_IMAGE_EDIT); + startActivityForResult(call, editIntent, "processEditedImage"); } catch (SecurityException ex) { Uri tempImage = getTempImage(bitmap, uri, bitmapOutputStream); Intent editIntent = createEditIntent(tempImage, true); if (editIntent != null) { - startActivityForResult(call, editIntent, REQUEST_IMAGE_EDIT); + startActivityForResult(call, editIntent, "processEditedImage"); } else { call.reject(IMAGE_EDIT_ERROR); } diff --git a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/FilesystemPlugin.java b/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/FilesystemPlugin.java index 35808a20a..68a00c5b7 100644 --- a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/FilesystemPlugin.java +++ b/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/FilesystemPlugin.java @@ -15,6 +15,7 @@ import com.getcapacitor.PluginMethod; import com.getcapacitor.annotation.CapacitorPlugin; import com.getcapacitor.annotation.Permission; +import com.getcapacitor.annotation.PermissionCallback; import java.io.*; import java.nio.charset.Charset; import org.json.JSONException; @@ -39,7 +40,7 @@ public void load() { private static final String PERMISSION_DENIED_ERROR = "Unable to do file operation, user denied permission request"; - @PluginMethod(permissionCallback = "permissionCallback") + @PluginMethod public void readFile(PluginCall call) { String path = call.getString("path"); String directory = getDirectoryParameter(call); @@ -52,7 +53,7 @@ public void readFile(PluginCall call) { } if (!isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call); + requestAllPermissions(call, "permissionCallback"); } else { try { String dataStr = implementation.readFile(path, directory, charset); @@ -69,7 +70,7 @@ public void readFile(PluginCall call) { } } - @PluginMethod(permissionCallback = "permissionCallback") + @PluginMethod public void writeFile(PluginCall call) { String path = call.getString("path"); String data = call.getString("data"); @@ -90,7 +91,7 @@ public void writeFile(PluginCall call) { String directory = getDirectoryParameter(call); if (directory != null) { if (!isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call); + requestAllPermissions(call, "permissionCallback"); } else { // create directory because it might not exist File androidDir = implementation.getDirectory(directory); @@ -120,7 +121,7 @@ public void writeFile(PluginCall call) { // do not know where the file is being store so checking the permission to be secure // TODO to prevent permission checking we need a property from the call if (!isStoragePermissionGranted()) { - requestAllPermissions(call); + requestAllPermissions(call, "permissionCallback"); } else { if ( fileObject.getParentFile() == null || @@ -168,7 +169,7 @@ private void saveFile(PluginCall call, File file, String data) { } } - @PluginMethod(permissionCallback = "permissionCallback") + @PluginMethod public void appendFile(PluginCall call) { try { call.getData().putOpt("append", true); @@ -177,12 +178,12 @@ public void appendFile(PluginCall call) { this.writeFile(call); } - @PluginMethod(permissionCallback = "permissionCallback") + @PluginMethod public void deleteFile(PluginCall call) { String file = call.getString("path"); String directory = getDirectoryParameter(call); if (!isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call); + requestAllPermissions(call, "permissionCallback"); } else { try { boolean deleted = implementation.deleteFile(file, directory); @@ -197,13 +198,13 @@ public void deleteFile(PluginCall call) { } } - @PluginMethod(permissionCallback = "permissionCallback") + @PluginMethod public void mkdir(PluginCall call) { String path = call.getString("path"); String directory = getDirectoryParameter(call); boolean recursive = call.getBoolean("recursive", false).booleanValue(); if (!isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call); + requestAllPermissions(call, "permissionCallback"); } else { try { boolean created = implementation.mkdir(path, directory, recursive); @@ -218,7 +219,7 @@ public void mkdir(PluginCall call) { } } - @PluginMethod(permissionCallback = "permissionCallback") + @PluginMethod public void rmdir(PluginCall call) { String path = call.getString("path"); String directory = getDirectoryParameter(call); @@ -227,7 +228,7 @@ public void rmdir(PluginCall call) { File fileObject = implementation.getFileObject(path, directory); if (!isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call); + requestAllPermissions(call, "permissionCallback"); } else { if (!fileObject.exists()) { call.reject("Directory does not exist"); @@ -254,13 +255,13 @@ public void rmdir(PluginCall call) { } } - @PluginMethod(permissionCallback = "permissionCallback") + @PluginMethod public void readdir(PluginCall call) { String path = call.getString("path"); String directory = getDirectoryParameter(call); if (!isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call); + requestAllPermissions(call, "permissionCallback"); } else { try { String[] files = implementation.readdir(path, directory); @@ -277,7 +278,7 @@ public void readdir(PluginCall call) { } } - @PluginMethod(permissionCallback = "permissionCallback") + @PluginMethod public void getUri(PluginCall call) { String path = call.getString("path"); String directory = getDirectoryParameter(call); @@ -285,7 +286,7 @@ public void getUri(PluginCall call) { File fileObject = implementation.getFileObject(path, directory); if (!isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call); + requestAllPermissions(call, "permissionCallback"); } else { JSObject data = new JSObject(); data.put("uri", Uri.fromFile(fileObject).toString()); @@ -293,7 +294,7 @@ public void getUri(PluginCall call) { } } - @PluginMethod(permissionCallback = "permissionCallback") + @PluginMethod public void stat(PluginCall call) { String path = call.getString("path"); String directory = getDirectoryParameter(call); @@ -301,7 +302,7 @@ public void stat(PluginCall call) { File fileObject = implementation.getFileObject(path, directory); if (!isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call); + requestAllPermissions(call, "permissionCallback"); } else { if (!fileObject.exists()) { call.reject("File does not exist"); @@ -318,12 +319,12 @@ public void stat(PluginCall call) { } } - @PluginMethod(permissionCallback = "permissionCallback") + @PluginMethod public void rename(PluginCall call) { this._copy(call, true); } - @PluginMethod(permissionCallback = "permissionCallback") + @PluginMethod public void copy(PluginCall call) { this._copy(call, false); } @@ -340,7 +341,7 @@ private void _copy(PluginCall call, Boolean doRename) { } if (isPublicDirectory(directory) || isPublicDirectory(toDirectory)) { if (!isStoragePermissionGranted()) { - requestAllPermissions(call); + requestAllPermissions(call, "permissionCallback"); return; } } @@ -354,6 +355,7 @@ private void _copy(PluginCall call, Boolean doRename) { } } + @PermissionCallback private void permissionCallback(PluginCall call) { if (!isStoragePermissionGranted()) { Logger.debug(getLogTag(), "User denied storage permission"); diff --git a/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/GeolocationPlugin.java b/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/GeolocationPlugin.java index a5a5c801c..9cee28dc5 100644 --- a/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/GeolocationPlugin.java +++ b/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/GeolocationPlugin.java @@ -10,6 +10,7 @@ import com.getcapacitor.PluginMethod; import com.getcapacitor.annotation.CapacitorPlugin; import com.getcapacitor.annotation.Permission; +import com.getcapacitor.annotation.PermissionCallback; import java.util.HashMap; import java.util.Map; @@ -35,10 +36,10 @@ public void load() { * * @param call Plugin call */ - @PluginMethod(permissionCallback = "completeCurrentPosition") + @PluginMethod public void getCurrentPosition(final PluginCall call) { if (!hasRequiredPermissions()) { - requestAllPermissions(call); + requestAllPermissions(call, "completeCurrentPosition"); } else { getPosition(call); } @@ -49,6 +50,7 @@ public void getCurrentPosition(final PluginCall call) { * @see #getCurrentPosition(PluginCall) * @param call the plugin call */ + @PermissionCallback private void completeCurrentPosition(PluginCall call) { if (getPermissionState("location") == PermissionState.GRANTED) { boolean enableHighAccuracy = call.getBoolean("enableHighAccuracy", false); @@ -81,11 +83,11 @@ public void error(String message) { * * @param call the plugin call */ - @PluginMethod(returnType = PluginMethod.RETURN_CALLBACK, permissionCallback = "completeWatchPosition") + @PluginMethod(returnType = PluginMethod.RETURN_CALLBACK) public void watchPosition(PluginCall call) { call.save(); if (!hasRequiredPermissions()) { - requestAllPermissions(call); + requestAllPermissions(call, "completeWatchPosition"); } else { startWatch(call); } @@ -96,6 +98,7 @@ public void watchPosition(PluginCall call) { * @see #watchPosition(PluginCall) * @param call the plugin call */ + @PermissionCallback private void completeWatchPosition(PluginCall call) { if (getPermissionState("location") == PermissionState.GRANTED) { startWatch(call); diff --git a/local-notifications/android/src/main/java/com/capacitorjs/plugins/localnotifications/LocalNotificationsPlugin.java b/local-notifications/android/src/main/java/com/capacitorjs/plugins/localnotifications/LocalNotificationsPlugin.java index e5e49ca61..83d6a2d28 100644 --- a/local-notifications/android/src/main/java/com/capacitorjs/plugins/localnotifications/LocalNotificationsPlugin.java +++ b/local-notifications/android/src/main/java/com/capacitorjs/plugins/localnotifications/LocalNotificationsPlugin.java @@ -44,12 +44,6 @@ protected void handleOnNewIntent(Intent data) { } } - @Override - protected void handleOnActivityResult(PluginCall savedCall, int requestCode, int resultCode, Intent data) { - super.handleOnActivityResult(savedCall, requestCode, resultCode, data); - this.handleOnNewIntent(data); - } - /** * Schedule a notification call from JavaScript * Creates local notification in system. diff --git a/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java b/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java index 07732980f..7d693bdca 100644 --- a/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java +++ b/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java @@ -6,19 +6,19 @@ import android.net.Uri; import android.os.Build; import android.webkit.MimeTypeMap; +import androidx.activity.result.ActivityResult; import androidx.core.content.FileProvider; import com.getcapacitor.JSObject; import com.getcapacitor.Plugin; import com.getcapacitor.PluginCall; import com.getcapacitor.PluginMethod; +import com.getcapacitor.annotation.ActivityCallback; import com.getcapacitor.annotation.CapacitorPlugin; import java.io.File; -@CapacitorPlugin(name = "Share", requestCodes = { SharePlugin.REQUEST_SHARE }) +@CapacitorPlugin(name = "Share") public class SharePlugin extends Plugin { - static final int REQUEST_SHARE = 9023; - private BroadcastReceiver broadcastReceiver; private boolean stopped = false; private ComponentName chosenComponent; @@ -37,6 +37,17 @@ public void onReceive(Context context, Intent intent) { } } + @ActivityCallback + private void activityResult(PluginCall call, ActivityResult result) { + if (result.getResultCode() == Activity.RESULT_CANCELED && !stopped) { + call.reject("Share canceled"); + } else { + JSObject callResult = new JSObject(); + callResult.put("activityType", chosenComponent != null ? chosenComponent.getPackageName() : ""); + call.resolve(callResult); + } + } + @PluginMethod public void share(PluginCall call) { String title = call.getString("title", ""); @@ -87,9 +98,10 @@ public void share(PluginCall call) { Intent chooser = null; if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + // requestCode parameter is not used. Providing 0 PendingIntent pi = PendingIntent.getBroadcast( getContext(), - REQUEST_SHARE, + 0, new Intent(Intent.EXTRA_CHOSEN_COMPONENT), PendingIntent.FLAG_UPDATE_CURRENT ); @@ -100,22 +112,7 @@ public void share(PluginCall call) { } chooser.addCategory(Intent.CATEGORY_DEFAULT); stopped = false; - startActivityForResult(call, chooser, REQUEST_SHARE); - } - - @Override - protected void handleOnActivityResult(PluginCall savedCall, int requestCode, int resultCode, Intent data) { - if (savedCall == null) { - return; - } - - if (resultCode == Activity.RESULT_CANCELED && !stopped) { - savedCall.reject("Share canceled"); - } else { - JSObject result = new JSObject(); - result.put("activityType", chosenComponent != null ? chosenComponent.getPackageName() : ""); - savedCall.resolve(result); - } + startActivityForResult(call, chooser, "activityResult"); } @Override