From 1ed01d143629542d0ad23c987e05aac2f8bbf8ac Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Wed, 20 Jan 2021 13:24:06 -0600 Subject: [PATCH 01/12] WIP --- .../com/getcapacitor/ActivityCallback.java | 7 + .../main/java/com/getcapacitor/Bridge.java | 11 +- .../main/java/com/getcapacitor/Callback.java | 4 + .../main/java/com/getcapacitor/Plugin.java | 347 ++++++++++-------- .../java/com/getcapacitor/PluginMethod.java | 6 - .../annotation/ActivityResultCallback.java | 11 + .../annotation/CapacitorPlugin.java | 12 +- 7 files changed, 230 insertions(+), 168 deletions(-) create mode 100644 android/capacitor/src/main/java/com/getcapacitor/ActivityCallback.java create mode 100644 android/capacitor/src/main/java/com/getcapacitor/Callback.java create mode 100644 android/capacitor/src/main/java/com/getcapacitor/annotation/ActivityResultCallback.java diff --git a/android/capacitor/src/main/java/com/getcapacitor/ActivityCallback.java b/android/capacitor/src/main/java/com/getcapacitor/ActivityCallback.java new file mode 100644 index 000000000..e857c5aed --- /dev/null +++ b/android/capacitor/src/main/java/com/getcapacitor/ActivityCallback.java @@ -0,0 +1,7 @@ +package com.getcapacitor; + +import androidx.activity.result.ActivityResult; + +public interface ActivityCallback { + void onResult(PluginCall call, ActivityResult result); +} diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index b9d13f8ea..f0a15cd7f 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -484,6 +484,7 @@ public PluginHandle getPlugin(String pluginId) { * @param requestCode * @return */ + @Deprecated public PluginHandle getPluginWithRequestCode(int requestCode) { for (PluginHandle handle : this.plugins.values()) { int[] requestCodes; @@ -501,13 +502,11 @@ public PluginHandle getPluginWithRequestCode(int requestCode) { } requestCodes = legacyPluginAnnotation.requestCodes(); - } else { - requestCodes = pluginAnnotation.requestCodes(); - } - for (int rc : requestCodes) { - if (rc == requestCode) { - return handle; + for (int rc : requestCodes) { + if (rc == requestCode) { + return handle; + } } } } diff --git a/android/capacitor/src/main/java/com/getcapacitor/Callback.java b/android/capacitor/src/main/java/com/getcapacitor/Callback.java new file mode 100644 index 000000000..358c6de28 --- /dev/null +++ b/android/capacitor/src/main/java/com/getcapacitor/Callback.java @@ -0,0 +1,4 @@ +package com.getcapacitor; + +public @interface Callback { +} diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index 210c21f14..e6b18a6d1 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -7,16 +7,20 @@ import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; + +import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; + +import com.getcapacitor.annotation.ActivityResultCallback; import com.getcapacitor.annotation.CapacitorPlugin; import com.getcapacitor.annotation.Permission; import com.getcapacitor.util.PermissionHelper; -import java.lang.annotation.Annotation; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -27,13 +31,19 @@ import java.util.Locale; import java.util.Map; import java.util.Set; + import org.json.JSONException; +import static androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions.ACTION_REQUEST_PERMISSIONS; +import static androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions.EXTRA_PERMISSIONS; +import static androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions.EXTRA_PERMISSION_GRANT_RESULTS; +import static java.util.Collections.emptyMap; + /** * Plugin is the base class for all plugins, containing a number of * convenient features for interacting with the {@link Bridge}, managing * plugin permissions, tracking lifecycle events, and more. - * + *

* You should inherit from this class when creating new plugins, along with * adding the {@link CapacitorPlugin} annotation to add additional required * metadata about the Plugin @@ -66,14 +76,16 @@ public class Plugin { private final Map> eventListeners; /** - * Base activity result launcher used by the {@link #requestPermissions(PluginCall)} plugin call + * Launchers used by the plugin to handle activity results */ - private ActivityResultLauncher basePermissionLauncher = null; + private final Map> activityLaunchers = new HashMap<>(); /** - * Launchers used by the plugin to request permissions + * Launchers used by the plugin to handle permission results */ - private final Map> permissionLaunchers = new HashMap<>(); + private final Map> permissionLaunchers = new HashMap<>(); + + private String lastPluginCallId; // Stored results of an event if an event was fired and // no listeners were attached yet. Only stores the last value. @@ -88,85 +100,119 @@ public Plugin() { * Called when the plugin has been connected to the bridge * and is ready to start initializing. */ - public void load() {} + public void load() { + } /** * Registers the permission launchers used by the {@link #requestPermissions(PluginCall)} plugin call and * those defined on plugins */ void initializePermissionLaunchers() { - basePermissionLauncher = - bridge - .getActivity() - .registerForActivityResult( - new ActivityResultContracts.RequestMultiplePermissions(), - permissions -> { - PluginCall call = bridge.getPermissionCall(handle.getId()); - checkPermissions(call); - } - ); + try { + Method method = getClass().getSuperclass().getMethod("checkPermissions", PluginCall.class); + activityLaunchers.put("checkPermissions", bridge.getActivity().registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + triggerPermissionCallback(method, result); + })); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } for (final Method method : getClass().getDeclaredMethods()) { - if (method.isAnnotationPresent(PluginMethod.class)) { - PluginMethod pluginAnnotation = method.getAnnotation(PluginMethod.class); - if (pluginAnnotation == null) { - continue; - } - - // get the defined permission callback, skip if default (empty string) - String permResponseMethodName = pluginAnnotation.permissionCallback(); - if (permResponseMethodName.isEmpty()) { - continue; - } - - try { - Method permResponseMethod = getClass().getDeclaredMethod(permResponseMethodName, PluginCall.class); - - if (permResponseMethod != null) { - permissionLaunchers.put( - method.getName(), - bridge + if (method.isAnnotationPresent(ActivityResultCallback.class)) { + activityLaunchers.put( + method.getName(), + bridge .getActivity() .registerForActivityResult( - new ActivityResultContracts.RequestMultiplePermissions(), - permissions -> { - PluginCall savedPermissionCall = bridge.getPermissionCall(handle.getId()); - - if (bridge.validatePermissions(this, savedPermissionCall, permissions)) { - // handle request permissions call - try { - permResponseMethod.setAccessible(true); - permResponseMethod.invoke(this, savedPermissionCall); - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); - } - - if (!savedPermissionCall.isReleased() && !savedPermissionCall.isSaved()) { - savedPermissionCall.release(bridge); + new ActivityResultContracts.StartActivityForResult(), + result -> { + Intent resultData = result.getData(); + String resultAction = resultData.getAction(); + + if (resultAction != null && resultAction.equalsIgnoreCase(ACTION_REQUEST_PERMISSIONS)) { + // request permission + triggerPermissionCallback(method, result); + } else { + // regular activity + triggerActivityCallback(method, result); } } - } ) - ); - } - } catch (NoSuchMethodException e) { - Logger.error( - String.format( - "No method found by the name %s to register as a permission handler. " + - "Please check that it exists and has the correct signature: (PluginCall)", - permResponseMethodName - ) - ); - - // if the provided method name is not a valid permission handling method, default to base method - permissionLaunchers.put(method.getName(), basePermissionLauncher); + ); + } + } + } + + private void triggerPermissionCallback(Method method, ActivityResult result) { + PluginCall savedPermissionCall = bridge.getPermissionCall(handle.getId()); + Map permissionResultMap = emptyMap(); + + Intent resultData = result.getData(); + + if (result.getResultCode() == Activity.RESULT_OK) { + if (resultData != null) { + String[] permissions = resultData.getStringArrayExtra(EXTRA_PERMISSIONS); + int[] grantResults = resultData.getIntArrayExtra(EXTRA_PERMISSION_GRANT_RESULTS); + for (int i = 0, size = permissions.length; i < size; i++) { + permissionResultMap.put(permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED); } } } + + if (bridge.validatePermissions(this, savedPermissionCall, permissionResultMap)) { + // handle request permissions call + try { + method.setAccessible(true); + method.invoke(this, savedPermissionCall); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + + if (!savedPermissionCall.isReleased() && !savedPermissionCall.isSaved()) { + savedPermissionCall.release(bridge); + } + } + } + + private void triggerActivityCallback(Method method, Object result) { + PluginCall call = bridge.getSavedCall(lastPluginCallId); + + // handle result call + try { + method.setAccessible(true); + method.invoke(this, call, result); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + + if (!call.isReleased() && !call.isSaved()) { + call.release(bridge); + } + } + + public void startActivityForResult(PluginCall call, Intent intent, String callbackName) { + lastPluginCallId = call.getCallbackId(); + bridge.saveCall(call); + activityLaunchers.get(callbackName).launch(intent); + } + + private void permissionActivityResult(PluginCall call, String[] permissionStrings, String callbackName) { + ActivityResultLauncher activityResultLauncher = getLauncherOrReject(call, callbackName); + if (activityResultLauncher == null) { + // return when null since call was rejected in getLauncherOrReject + return; + } + + Intent permissionIntent = new Intent().putExtra(EXTRA_PERMISSIONS, permissionStrings); + permissionIntent.setAction(ACTION_REQUEST_PERMISSIONS); + bridge.savePermissionCall(call); + + activityResultLauncher.launch(permissionIntent); } /** * Get the main {@link Context} for the current Activity (your app) + * * @return the Context for the current activity */ public Context getContext() { @@ -175,6 +221,7 @@ public Context getContext() { /** * Get the main {@link Activity} for the app + * * @return the Activity for the current app */ public AppCompatActivity getActivity() { @@ -183,6 +230,7 @@ public AppCompatActivity getActivity() { /** * Set the Bridge instance for this plugin + * * @param bridge */ public void setBridge(Bridge bridge) { @@ -200,6 +248,7 @@ public Bridge getBridge() { * Set the wrapper {@link PluginHandle} instance for this plugin that * contains additional metadata about the Plugin instance (such * as indexed methods for reflection, and {@link CapacitorPlugin} annotation data). + * * @param pluginHandle */ public void setPluginHandle(PluginHandle pluginHandle) { @@ -208,9 +257,10 @@ public void setPluginHandle(PluginHandle pluginHandle) { /** * Return the wrapper {@link PluginHandle} for this plugin. - * + *

* This wrapper contains additional metadata about the plugin instance, * such as indexed methods for reflection, and {@link CapacitorPlugin} annotation data). + * * @return */ public PluginHandle getPluginHandle() { @@ -219,6 +269,7 @@ public PluginHandle getPluginHandle() { /** * Get the root App ID + * * @return */ public String getAppId() { @@ -228,9 +279,9 @@ public String getAppId() { /** * Called to save a {@link PluginCall} in order to reference it * later, such as in an activity or permissions result handler - * @deprecated use {@link Bridge#saveCall(PluginCall)} * * @param lastCall + * @deprecated use {@link Bridge#saveCall(PluginCall)} */ @Deprecated public void saveCall(PluginCall lastCall) { @@ -239,6 +290,7 @@ public void saveCall(PluginCall lastCall) { /** * Set the last saved call to null to free memory + * * @deprecated use {@link PluginCall#release(Bridge)} */ @Deprecated @@ -251,9 +303,9 @@ public void freeSavedCall() { /** * Get the last saved call, if any - * @deprecated use {@link Bridge#getSavedCall(String)} * * @return + * @deprecated use {@link Bridge#getSavedCall(String)} */ @Deprecated public PluginCall getSavedCall() { @@ -272,11 +324,11 @@ public PluginConfig getConfig() { /** * Get the value for a key on the config for this plugin. - * @deprecated use {@link #getConfig()} and access config values using the methods available - * depending on the type. * * @param key the key for the config value * @return some object containing the value from the config + * @deprecated use {@link #getConfig()} and access config values using the methods available + * depending on the type. */ @Deprecated public Object getConfigValue(String key) { @@ -290,6 +342,7 @@ public Object getConfigValue(String key) { /** * Check whether any of the given permissions has been defined in the AndroidManifest.xml + * * @param permissions * @return */ @@ -304,6 +357,7 @@ public boolean hasDefinedPermissions(String[] permissions) { /** * Check whether any of the given permissions has been defined in the AndroidManifest.xml + * * @param permissions * @return */ @@ -336,6 +390,7 @@ public boolean hasDefinedRequiredPermissions() { /** * Check whether the given permission has been granted by the user + * * @param permission * @return */ @@ -348,6 +403,7 @@ public boolean hasPermission(String permission) { * this method checks if each is granted. Note: if you are okay * with a limited subset of the permissions being granted, check * each one individually instead with hasPermission + * * @return */ public boolean hasRequiredPermissions() { @@ -377,22 +433,15 @@ public boolean hasRequiredPermissions() { /** * Request all of the specified permissions in the CapacitorPlugin annotation (if any) - * + *

* If there is no registered permission callback for the PluginCall passed in, the call will * be rejected. Make sure a valid permission callback method is registered using the * {@link PluginMethod#permissionCallback()} annotation. * - * @since 3.0.0 * @param call the plugin call + * @since 3.0.0 */ - protected void requestAllPermissions(@NonNull PluginCall call) { - String callMethodName = call.getMethodName(); - ActivityResultLauncher activityResultLauncher = getLauncherOrReject(call, callMethodName); - if (activityResultLauncher == null) { - // return when null since call was rejected in getLauncherOrReject - return; - } - + protected void requestAllPermissions(@NonNull PluginCall call, @NonNull String callbackName) { CapacitorPlugin annotation = handle.getPluginAnnotation(); if (annotation != null) { HashSet perms = new HashSet<>(); @@ -400,66 +449,45 @@ protected void requestAllPermissions(@NonNull PluginCall call) { perms.addAll(Arrays.asList(perm.strings())); } - bridge.savePermissionCall(call); - activityResultLauncher.launch(perms.toArray(new String[0])); + permissionActivityResult(call, perms.toArray(new String[0]), callbackName); } } /** * Request permissions using an alias defined on the plugin. - * + *

* If there is no registered permission callback for the PluginCall passed in, the call will * be rejected. Make sure a valid permission callback method is registered using the * {@link PluginMethod#permissionCallback()} annotation. * * @param alias an alias defined on the plugin - * @param call the plugin call involved in originating the request + * @param call the plugin call involved in originating the request */ - protected void requestPermissionForAlias(@NonNull String alias, @NonNull PluginCall call) { - requestPermissionForAliases(new String[] { alias }, call); + protected void requestPermissionForAlias(@NonNull String alias, @NonNull PluginCall call, @NonNull String callbackName) { + requestPermissionForAliases(new String[]{alias}, call, callbackName); } /** * Request permissions using aliases defined on the plugin. - * + *

* If there is no registered permission callback for the PluginCall passed in, the call will * be rejected. Make sure a valid permission callback method is registered using the * {@link PluginMethod#permissionCallback()} annotation. * * @param aliases a set of aliases defined on the plugin - * @param call the plugin call involved in originating the request + * @param call the plugin call involved in originating the request */ - protected void requestPermissionForAliases(@NonNull String[] aliases, @NonNull PluginCall call) { - String callMethodName = call.getMethodName(); - ActivityResultLauncher activityResultLauncher = getLauncherOrReject(call, callMethodName); - if (activityResultLauncher == null) { - // return when null since call was rejected in getLauncherOrReject - return; - } - + protected void requestPermissionForAliases(@NonNull String[] aliases, @NonNull PluginCall call, @NonNull String callbackName) { if (aliases.length == 0) { Logger.error("No permission alias was provided"); return; } - requestPermissionForAliases(aliases, call, activityResultLauncher); - } - - /** - * Request permissions using aliases defined on the plugin with a provided activityResultLauncher. - * Plugin authors should use {@link #requestPermissionForAliases(String[], PluginCall)} with - * a registered callback method for typical permission request use. - */ - private void requestPermissionForAliases( - @NonNull String[] aliases, - @NonNull PluginCall call, - ActivityResultLauncher activityResultLauncher - ) { String[] permissions = getPermissionStringsForAliases(aliases); if (permissions.length > 0) { bridge.savePermissionCall(call); - activityResultLauncher.launch(permissions); + permissionActivityResult(call, permissions, callbackName); } } @@ -486,25 +514,20 @@ private String[] getPermissionStringsForAliases(@NonNull String[] aliases) { * Gets the permission launcher associated with the calling methodName, or rejects the call if * no registered launcher exists * - * @param call the plugin call + * @param call the plugin call * @param methodName the name of the plugin method requesting a permission * @return a launcher, or null if none found */ - private @Nullable ActivityResultLauncher getLauncherOrReject(PluginCall call, String methodName) { - ActivityResultLauncher activityResultLauncher = permissionLaunchers.get(methodName); - - // if there is no registered result launcher but the method is the default requestPermissions - // method, associate the base permission launcher to make sure states are returned - if (activityResultLauncher == null && methodName.equals("requestPermissions")) { - activityResultLauncher = basePermissionLauncher; - } + private @Nullable + ActivityResultLauncher getLauncherOrReject(PluginCall call, String methodName) { + ActivityResultLauncher activityResultLauncher = activityLaunchers.get(methodName); // if there is no registered launcher, reject the call with an error and return null if (activityResultLauncher == null) { String registerError = - "There is no permission callback method registered for the plugin method %s. " + - "Please define a permissionCallback method name in the annotation and provide a " + - "method that has the correct signature: (PluginCall)"; + "There is no permission callback method registered for the plugin method %s. " + + "Please define a permissionCallback method name in the annotation and provide a " + + "method that has the correct signature: (PluginCall)"; registerError = String.format(Locale.US, registerError, methodName); Logger.error(registerError); call.reject(registerError); @@ -516,10 +539,10 @@ private String[] getPermissionStringsForAliases(@NonNull String[] aliases) { /** * Helper for requesting specific permissions - * @deprecated use {@link #requestPermissions(PluginCall)} in conjunction with @CapacitorPlugin * * @param permissions the set of permissions to request * @param requestCode the requestCode to use to associate the result with the plugin + * @deprecated use {@link #requestPermissions(PluginCall)} in conjunction with @CapacitorPlugin */ @Deprecated public void pluginRequestPermissions(String[] permissions, int requestCode) { @@ -528,7 +551,8 @@ public void pluginRequestPermissions(String[] permissions, int requestCode) { /** * Request all of the specified permissions in the CapacitorPlugin annotation (if any) - * @deprecated use {@link #requestAllPermissions(PluginCall)} in conjunction with @CapacitorPlugin + * + * @deprecated use {@link #requestAllPermissions(PluginCall, String)} in conjunction with @CapacitorPlugin */ @Deprecated public void pluginRequestAllPermissions() { @@ -538,14 +562,14 @@ public void pluginRequestAllPermissions() { /** * Helper for requesting a specific permission - * @deprecated use {@link #requestPermissionForAlias(String, PluginCall)} in conjunction with @CapacitorPlugin * - * @param permission the permission to request + * @param permission the permission to request * @param requestCode the requestCode to use to associate the result with the plugin + * @deprecated use {@link #requestPermissionForAlias(String, PluginCall, String)} in conjunction with @CapacitorPlugin */ @Deprecated public void pluginRequestPermission(String permission, int requestCode) { - ActivityCompat.requestPermissions(getActivity(), new String[] { permission }, requestCode); + ActivityCompat.requestPermissions(getActivity(), new String[]{permission}, requestCode); } /** @@ -561,8 +585,8 @@ public PermissionState getPermissionState(String alias) { /** * Helper to check all permissions defined on a plugin and see the state of each. * - * @since 3.0.0 * @return A mapping of permission aliases to the associated granted status. + * @since 3.0.0 */ public Map getPermissionStates() { return bridge.getPermissionStates(this); @@ -570,6 +594,7 @@ public Map getPermissionStates() { /** * Add a listener for the given event + * * @param eventName * @param call */ @@ -590,6 +615,7 @@ private void addEventListener(String eventName, PluginCall call) { /** * Remove a listener from the given event + * * @param eventName * @param call */ @@ -604,6 +630,7 @@ private void removeEventListener(String eventName, PluginCall call) { /** * Notify all listeners that an event occurred + * * @param eventName * @param data */ @@ -627,6 +654,7 @@ protected void notifyListeners(String eventName, JSObject data, boolean retainUn * Notify all listeners that an event occurred * This calls {@link Plugin#notifyListeners(String, JSObject, boolean)} * with retainUntilConsumed set to false + * * @param eventName * @param data */ @@ -648,6 +676,7 @@ protected boolean hasListeners(String eventName) { /** * Send retained arguments (if any) for this event. This * is called only when the first listener for an event is added + * * @param eventName */ private void sendRetainedArgumentsForEvent(String eventName) { @@ -662,6 +691,7 @@ private void sendRetainedArgumentsForEvent(String eventName) { /** * Exported plugin call for adding a listener to this plugin + * * @param call */ @SuppressWarnings("unused") @@ -674,6 +704,7 @@ public void addListener(PluginCall call) { /** * Exported plugin call to remove a listener from this plugin + * * @param call */ @SuppressWarnings("unused") @@ -690,6 +721,7 @@ public void removeListener(PluginCall call) { /** * Exported plugin call to remove all listeners from this plugin + * * @param call */ @SuppressWarnings("unused") @@ -706,6 +738,7 @@ public void removeAllListeners(PluginCall call) { * @since 3.0.0 */ @PluginMethod + @ActivityResultCallback public void checkPermissions(PluginCall pluginCall) { Map permissionsResult = getPermissionStates(); @@ -725,9 +758,9 @@ public void checkPermissions(PluginCall pluginCall) { /** * Exported plugin call to request all permissions for this plugin. * To manually request permissions within a plugin use: - * {@link #requestAllPermissions(PluginCall)}, or - * {@link #requestPermissionForAlias(String, PluginCall)}, or - * {@link #requestPermissionForAliases(String[], PluginCall)} + * {@link #requestAllPermissions(PluginCall, String)}, or + * {@link #requestPermissionForAlias(String, PluginCall, String)}, or + * {@link #requestPermissionForAliases(String[], PluginCall, String)} * * @param call the plugin call */ @@ -792,7 +825,7 @@ public void requestPermissions(PluginCall call) { if (permAliases != null && permAliases.length > 0) { // request permissions using provided aliases or all defined on the plugin - requestPermissionForAliases(permAliases, call, basePermissionLauncher); + requestPermissionForAliases(permAliases, call, "checkPermissions"); } else if (!autoGrantPerms.isEmpty()) { // if the plugin only has auto-grant permissions, return all as GRANTED JSObject permissionsResults = new JSObject(); @@ -813,11 +846,11 @@ public void requestPermissions(PluginCall call) { * Handle request permissions result. A plugin using the deprecated {@link NativePlugin} * should override this to handle the result, or this method will handle the result * for our convenient requestPermissions call. - * @deprecated in favor of using callbacks in conjunction with {@link CapacitorPlugin} * * @param requestCode * @param permissions * @param grantResults + * @deprecated in favor of using callbacks in conjunction with {@link CapacitorPlugin} */ @Deprecated protected void handleRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { @@ -840,6 +873,7 @@ protected void handleRequestPermissionsResult(int requestCode, String[] permissi * may be limited, plugins that expect to be called with large data * objects (such as a file), should override this method and selectively * store option values in a {@link Bundle} to avoid exceeding limits. + * * @return a new {@link Bundle} with fields set from the options of the last saved {@link PluginCall} */ protected Bundle saveInstanceState() { @@ -864,72 +898,87 @@ protected Bundle saveInstanceState() { * activity response. If the plugin that started the activity * stored data in {@link Plugin#saveInstanceState()} then this * method will be called to allow the plugin to restore from that. + * * @param state */ - protected void restoreState(Bundle state) {} + protected void restoreState(Bundle state) { + } /** * Handle activity result, should be overridden by each plugin + * * @param lastPluginCall * @param requestCode * @param resultCode * @param data */ - protected void handleOnActivityResult(PluginCall lastPluginCall, int requestCode, int resultCode, Intent data) {} + protected void handleOnActivityResult(PluginCall lastPluginCall, int requestCode, int resultCode, Intent data) { + } /** * Handle activity result, should be overridden by each plugin - * @deprecated use {@link #handleOnActivityResult(PluginCall, int, int, Intent)} in - * conjunction with @CapacitorPlugin * * @param requestCode * @param resultCode * @param data + * @deprecated use {@link #handleOnActivityResult(PluginCall, int, int, Intent)} in + * conjunction with @CapacitorPlugin */ @Deprecated - protected void handleOnActivityResult(int requestCode, int resultCode, Intent data) {} + protected void handleOnActivityResult(int requestCode, int resultCode, Intent data) { + } /** * Handle onNewIntent + * * @param intent */ - protected void handleOnNewIntent(Intent intent) {} + protected void handleOnNewIntent(Intent intent) { + } /** * Handle onConfigurationChanged + * * @param newConfig */ - protected void handleOnConfigurationChanged(Configuration newConfig) {} + protected void handleOnConfigurationChanged(Configuration newConfig) { + } /** * Handle onStart */ - protected void handleOnStart() {} + protected void handleOnStart() { + } /** * Handle onRestart */ - protected void handleOnRestart() {} + protected void handleOnRestart() { + } /** * Handle onResume */ - protected void handleOnResume() {} + protected void handleOnResume() { + } /** * Handle onPause */ - protected void handleOnPause() {} + protected void handleOnPause() { + } /** * Handle onStop */ - protected void handleOnStop() {} + protected void handleOnStop() { + } /** * Handle onDestroy */ - protected void handleOnDestroy() {} + protected void handleOnDestroy() { + } /** * Give the plugins a chance to take control when a URL is about to be loaded in the WebView. @@ -944,20 +993,23 @@ public Boolean shouldOverrideLoad(Uri url) { /** * Start a new Activity. - * + *

* Note: This method must be used by all plugins instead of calling * {@link Activity#startActivityForResult} as it associates the plugin with * any resulting data from the new Activity even if this app * is destroyed by the OS (to free up memory, for example). + * * @param intent * @param resultCode */ + @Deprecated protected void startActivityForResult(PluginCall call, Intent intent, int resultCode) { bridge.startActivityForPluginWithResult(call, intent, resultCode); } /** * Execute the given runnable on the Bridge's task handler + * * @param runnable */ public void execute(Runnable runnable) { @@ -966,6 +1018,7 @@ public void execute(Runnable runnable) { /** * Shortcut for getting the plugin log tag + * * @param subTags */ protected String getLogTag(String... subTags) { diff --git a/android/capacitor/src/main/java/com/getcapacitor/PluginMethod.java b/android/capacitor/src/main/java/com/getcapacitor/PluginMethod.java index ed4ccbc95..856630431 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/PluginMethod.java +++ b/android/capacitor/src/main/java/com/getcapacitor/PluginMethod.java @@ -12,10 +12,4 @@ String RETURN_NONE = "none"; String returnType() default RETURN_PROMISE; - - /** - * The name of a method that should be called on the result of a permission request. This method - * should be defined in the class with the parameters (PluginCall). - */ - String permissionCallback() default ""; } diff --git a/android/capacitor/src/main/java/com/getcapacitor/annotation/ActivityResultCallback.java b/android/capacitor/src/main/java/com/getcapacitor/annotation/ActivityResultCallback.java new file mode 100644 index 000000000..2dc875062 --- /dev/null +++ b/android/capacitor/src/main/java/com/getcapacitor/annotation/ActivityResultCallback.java @@ -0,0 +1,11 @@ +package com.getcapacitor.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ActivityResultCallback { +} diff --git a/android/capacitor/src/main/java/com/getcapacitor/annotation/CapacitorPlugin.java b/android/capacitor/src/main/java/com/getcapacitor/annotation/CapacitorPlugin.java index 235a28102..80294e0d1 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/annotation/CapacitorPlugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/annotation/CapacitorPlugin.java @@ -9,20 +9,14 @@ @Retention(RetentionPolicy.RUNTIME) public @interface CapacitorPlugin { /** - * Request codes this plugin uses and responds to, in order to tie - * Android events back the plugin to handle + * A custom name for the plugin, otherwise uses the + * simple class name. */ - int[] requestCodes() default {}; + String name() default ""; /** * Permissions this plugin needs, in order to make permission requests * easy if the plugin only needs basic permission prompting */ Permission[] permissions() default {}; - - /** - * A custom name for the plugin, otherwise uses the - * simple class name. - */ - String name() default ""; } From 2815b3c5c9d4919bb2f391c328f08ea43e2f260b Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Mon, 25 Jan 2021 13:55:09 -0600 Subject: [PATCH 02/12] refactor and update comments --- .../main/java/com/getcapacitor/Plugin.java | 77 ++++++++++--------- ...ultCallback.java => ActivityCallback.java} | 2 +- .../annotation/PermissionCallback.java | 11 +++ 3 files changed, 54 insertions(+), 36 deletions(-) rename android/capacitor/src/main/java/com/getcapacitor/annotation/{ActivityResultCallback.java => ActivityCallback.java} (86%) create mode 100644 android/capacitor/src/main/java/com/getcapacitor/annotation/PermissionCallback.java diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index cdce31b6a..49e6be76e 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -1,8 +1,5 @@ package com.getcapacitor; -import static androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions.ACTION_REQUEST_PERMISSIONS; -import static androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions.EXTRA_PERMISSIONS; - import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -10,16 +7,17 @@ import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; +import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; -import com.getcapacitor.annotation.ActivityResultCallback; +import com.getcapacitor.annotation.ActivityCallback; import com.getcapacitor.annotation.CapacitorPlugin; import com.getcapacitor.annotation.Permission; -import com.getcapacitor.annotation.PermissionResultCallback; +import com.getcapacitor.annotation.PermissionCallback; import com.getcapacitor.util.PermissionHelper; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -97,11 +95,12 @@ public Plugin() { public void load() {} /** - * Registers the permission launchers used by the {@link #requestPermissions(PluginCall)} plugin call and - * those defined on plugins + * Registers activity result launchers defined on plugins, used for permission requests and + * activities started for result. */ void initializePermissionLaunchers() { try { + // load the default checkPermission callback from the plugin parent class Method method = getClass().getSuperclass().getMethod("checkPermissions", PluginCall.class); permissionLaunchers.put( "checkPermissions", @@ -109,9 +108,7 @@ void initializePermissionLaunchers() { .getActivity() .registerForActivityResult( new ActivityResultContracts.RequestMultiplePermissions(), - permissions -> { - triggerPermissionCallback(method, permissions); - } + permissions -> triggerPermissionCallback(method, permissions) ) ); } catch (NoSuchMethodException e) { @@ -119,7 +116,8 @@ void initializePermissionLaunchers() { } for (final Method method : getClass().getDeclaredMethods()) { - if (method.isAnnotationPresent(ActivityResultCallback.class)) { + if (method.isAnnotationPresent(ActivityCallback.class)) { + // register callbacks annotated with ActivityCallback for activity results activityLaunchers.put( method.getName(), bridge @@ -129,7 +127,8 @@ void initializePermissionLaunchers() { result -> triggerActivityCallback(method, result) ) ); - } else if (method.isAnnotationPresent(PermissionResultCallback.class)) { + } else if (method.isAnnotationPresent(PermissionCallback.class)) { + // register callbacks annotated with PermissionCallback for permission results permissionLaunchers.put( method.getName(), bridge @@ -146,8 +145,8 @@ void initializePermissionLaunchers() { private void triggerPermissionCallback(Method method, Map permissionResultMap) { PluginCall savedCall = bridge.getPermissionCall(handle.getId()); + // validate permissions and invoke the permission result callback if (bridge.validatePermissions(this, savedCall, permissionResultMap)) { - // handle request permissions call try { method.setAccessible(true); method.invoke(this, savedCall); @@ -161,10 +160,10 @@ private void triggerPermissionCallback(Method method, Map permi } } - private void triggerActivityCallback(Method method, Object result) { + private void triggerActivityCallback(Method method, ActivityResult result) { PluginCall savedCall = bridge.getSavedCall(lastPluginCallId); - // handle result savedCall + // invoke the activity result callback try { method.setAccessible(true); method.invoke(this, savedCall, result); @@ -177,6 +176,16 @@ private void triggerActivityCallback(Method method, Object result) { } } + /** + * Start activity for result with the provided Intent and resolve with the provided callback method name. + *

+ * If there is no registered activity callback for the method name passed in, the call will + * be rejected. Make sure a valid activity result callback method is registered using the + * {@link ActivityCallback} annotation. + * + * @param call the plugin call + * @since 3.0.0 + */ public void startActivityForResult(PluginCall call, Intent intent, String callbackName) { ActivityResultLauncher activityResultLauncher = getActivityLauncherOrReject(call, callbackName); if (activityResultLauncher == null) { @@ -426,7 +435,7 @@ public boolean hasRequiredPermissions() { *

* If there is no registered permission callback for the PluginCall passed in, the call will * be rejected. Make sure a valid permission callback method is registered using the - * {@link PermissionResultCallback} annotation. + * {@link PermissionCallback} annotation. * * @param call the plugin call * @since 3.0.0 @@ -448,7 +457,7 @@ protected void requestAllPermissions(@NonNull PluginCall call, @NonNull String c *

* If there is no registered permission callback for the PluginCall passed in, the call will * be rejected. Make sure a valid permission callback method is registered using the - * {@link PermissionResultCallback} annotation. + * {@link PermissionCallback} annotation. * * @param alias an alias defined on the plugin * @param call the plugin call involved in originating the request @@ -462,7 +471,7 @@ protected void requestPermissionForAlias(@NonNull String alias, @NonNull PluginC *

* If there is no registered permission callback for the PluginCall passed in, the call will * be rejected. Make sure a valid permission callback method is registered using the - * {@link PermissionResultCallback} annotation. + * {@link PermissionCallback} annotation. * * @param aliases a set of aliases defined on the plugin * @param call the plugin call involved in originating the request @@ -505,26 +514,25 @@ private String[] getPermissionStringsForAliases(@NonNull String[] aliases) { * no registered launcher exists * * @param call the plugin call - * @param methodName the name of the activity result callback method + * @param methodName the name of the activity callback method * @return a launcher, or null if none found */ private @Nullable ActivityResultLauncher getActivityLauncherOrReject(PluginCall call, String methodName) { - ActivityResultLauncher activityResultLauncher = activityLaunchers.get(methodName); + ActivityResultLauncher activityLauncher = activityLaunchers.get(methodName); // if there is no registered launcher, reject the call with an error and return null - if (activityResultLauncher == null) { - // TODO: fix the err msg + if (activityLauncher == null) { String registerError = - "There is no activity result callback method registered for: %s. " + - "Please define a permissionCallback method name in the annotation and provide a " + - "method that has the correct signature: (PluginCall)"; + "There is no ActivityCallback method registered for the name: %s. " + + "Please define a callback method annotated with @ActivityCallback " + + "that receives arguments: (PluginCall, ActivityResult)"; registerError = String.format(Locale.US, registerError, methodName); Logger.error(registerError); call.reject(registerError); return null; } - return activityResultLauncher; + return activityLauncher; } /** @@ -532,26 +540,25 @@ private String[] getPermissionStringsForAliases(@NonNull String[] aliases) { * no registered launcher exists * * @param call the plugin call - * @param methodName the name of the permission result callback method + * @param methodName the name of the permission callback method * @return a launcher, or null if none found */ private @Nullable ActivityResultLauncher getPermissionLauncherOrReject(PluginCall call, String methodName) { - ActivityResultLauncher permissionResultLauncher = permissionLaunchers.get(methodName); + ActivityResultLauncher permissionLauncher = permissionLaunchers.get(methodName); // if there is no registered launcher, reject the call with an error and return null - if (permissionResultLauncher == null) { - // TODO: fix the err msg + if (permissionLauncher == null) { String registerError = - "There is no activity result callback method registered for: %s. " + - "Please define a permissionCallback method name in the annotation and provide a " + - "method that has the correct signature: (PluginCall)"; + "There is no PermissionCallback method registered for the name: %s. " + + "Please define a callback method annotated with @PermissionCallback " + + "that receives arguments: (PluginCall)"; registerError = String.format(Locale.US, registerError, methodName); Logger.error(registerError); call.reject(registerError); return null; } - return permissionResultLauncher; + return permissionLauncher; } /** @@ -755,7 +762,7 @@ public void removeAllListeners(PluginCall call) { * @since 3.0.0 */ @PluginMethod - @ActivityResultCallback + @PermissionCallback public void checkPermissions(PluginCall pluginCall) { Map permissionsResult = getPermissionStates(); diff --git a/android/capacitor/src/main/java/com/getcapacitor/annotation/ActivityResultCallback.java b/android/capacitor/src/main/java/com/getcapacitor/annotation/ActivityCallback.java similarity index 86% rename from android/capacitor/src/main/java/com/getcapacitor/annotation/ActivityResultCallback.java rename to android/capacitor/src/main/java/com/getcapacitor/annotation/ActivityCallback.java index 2dc875062..a158145d7 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/annotation/ActivityResultCallback.java +++ b/android/capacitor/src/main/java/com/getcapacitor/annotation/ActivityCallback.java @@ -7,5 +7,5 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -public @interface ActivityResultCallback { +public @interface ActivityCallback { } diff --git a/android/capacitor/src/main/java/com/getcapacitor/annotation/PermissionCallback.java b/android/capacitor/src/main/java/com/getcapacitor/annotation/PermissionCallback.java new file mode 100644 index 000000000..d4ca09921 --- /dev/null +++ b/android/capacitor/src/main/java/com/getcapacitor/annotation/PermissionCallback.java @@ -0,0 +1,11 @@ +package com.getcapacitor.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface PermissionCallback { +} From fdee4afdaba181d0ea90a2ae209c21d3e99ba1b8 Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Wed, 27 Jan 2021 19:34:14 -0600 Subject: [PATCH 03/12] rename initializer --- android/capacitor/src/main/java/com/getcapacitor/Plugin.java | 2 +- .../capacitor/src/main/java/com/getcapacitor/PluginHandle.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index 49e6be76e..96b448945 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -98,7 +98,7 @@ public void load() {} * Registers activity result launchers defined on plugins, used for permission requests and * activities started for result. */ - void initializePermissionLaunchers() { + void initializeActivityLaunchers() { try { // load the default checkPermission callback from the plugin parent class Method method = getClass().getSuperclass().getMethod("checkPermissions", PluginCall.class); diff --git a/android/capacitor/src/main/java/com/getcapacitor/PluginHandle.java b/android/capacitor/src/main/java/com/getcapacitor/PluginHandle.java index 7c6d80e90..b08cbad54 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/PluginHandle.java +++ b/android/capacitor/src/main/java/com/getcapacitor/PluginHandle.java @@ -93,7 +93,7 @@ public Plugin load() throws PluginLoadException { this.instance.setPluginHandle(this); this.instance.setBridge(this.bridge); this.instance.load(); - this.instance.initializePermissionLaunchers(); + this.instance.initializeActivityLaunchers(); return this.instance; } catch (InstantiationException | IllegalAccessException ex) { throw new PluginLoadException("Unable to load plugin instance. Ensure plugin is publicly accessible"); From 73e230e5df59ef715e9612b842771a353e5635fa Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Wed, 27 Jan 2021 19:57:38 -0600 Subject: [PATCH 04/12] changed onActivityResult to match onRequestPermissionResult --- .../main/java/com/getcapacitor/Bridge.java | 40 +++++++++---------- .../java/com/getcapacitor/BridgeActivity.java | 22 ++++++++-- .../main/java/com/getcapacitor/Plugin.java | 13 +----- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index 9cb8cfea7..f19d41af0 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -899,36 +899,36 @@ protected Map getPermissionStates(Plugin plugin) { * @param resultCode * @param data */ - public void onActivityResult(int requestCode, int resultCode, Intent data) { + boolean onActivityResult(int requestCode, int resultCode, Intent data) { PluginHandle plugin = getPluginWithRequestCode(requestCode); if (plugin == null || plugin.getInstance() == null) { Logger.debug("Unable to find a Capacitor plugin to handle requestCode, trying Cordova plugins " + requestCode); - cordovaInterface.onActivityResult(requestCode, resultCode, data); - return; + return cordovaInterface.onActivityResult(requestCode, resultCode, data); } - // deprecated, to be removed - PluginCall lastCall = plugin.getInstance().getSavedCall(); + CapacitorPlugin pluginAnnotation = plugin.getPluginClass().getAnnotation(CapacitorPlugin.class); + if (pluginAnnotation == null) { + // deprecated, to be removed + PluginCall lastCall = plugin.getInstance().getSavedCall(); + + // If we don't have a saved last call (because our app was killed and restarted, for example), + // Then we should see if we have any saved plugin call information and generate a new, + // "dangling" plugin call (a plugin call that doesn't have a corresponding web callback) + // and then send that to the plugin + if (lastCall == null && pluginCallForLastActivity != null) { + plugin.getInstance().saveCall(pluginCallForLastActivity); + } - // If we don't have a saved last call (because our app was killed and restarted, for example), - // Then we should see if we have any saved plugin call information and generate a new, - // "dangling" plugin call (a plugin call that doesn't have a corresponding web callback) - // and then send that to the plugin - if (lastCall == null && pluginCallForLastActivity != null) { - plugin.getInstance().saveCall(pluginCallForLastActivity); - } + plugin.getInstance().handleOnActivityResult(requestCode, resultCode, data); - CapacitorPlugin pluginAnnotation = plugin.getPluginClass().getAnnotation(CapacitorPlugin.class); - if (pluginAnnotation != null) { - // Use new callback with new @CapacitorPlugin plugins - plugin.getInstance().handleOnActivityResult(pluginCallForLastActivity, requestCode, resultCode, data); + // Clear the plugin call we may have re-hydrated on app launch + pluginCallForLastActivity = null; + + return true; } else { - plugin.getInstance().handleOnActivityResult(requestCode, resultCode, data); + return false; } - - // Clear the plugin call we may have re-hydrated on app launch - pluginCallForLastActivity = null; } /** diff --git a/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java b/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java index e7bbf95b6..651aeba70 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java +++ b/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java @@ -191,15 +191,31 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in } } + /** + * Handles activity results. + * + * Capacitor is backwards compatible such that plugins using legacy activity result codes + * may coexist with plugins using the AndroidX Activity v1.2 activity callback flow introduced + * in Capacitor 3.0. + * + * In this method, plugins are checked first for ownership of the legacy request code. If the + * {@link Bridge#onActivityResult(int, int, Intent)} method indicates it has handled the activity + * result, then the callback will be considered complete. Otherwise, the result will be handled + * using the AndroidX Activiy flow. + * + * @param requestCode the request code associated with the activity result + * @param resultCode the result code + * @param data any data included with the activity result + */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (this.bridge == null) { return; } - this.bridge.onActivityResult(requestCode, resultCode, data); + if (!bridge.onActivityResult(requestCode, resultCode, data)) { + super.onActivityResult(requestCode, resultCode, data); + } } @Override diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index 96b448945..9ccef6168 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -930,21 +930,12 @@ protected void restoreState(Bundle state) {} /** * Handle activity result, should be overridden by each plugin * - * @param lastPluginCall - * @param requestCode - * @param resultCode - * @param data - */ - protected void handleOnActivityResult(PluginCall lastPluginCall, int requestCode, int resultCode, Intent data) {} - - /** - * Handle activity result, should be overridden by each plugin + * @deprecated provide a callback method using the {@link ActivityCallback} annotation and use + * the {@link #startActivityForResult(PluginCall, Intent, String)} method * * @param requestCode * @param resultCode * @param data - * @deprecated use {@link #handleOnActivityResult(PluginCall, int, int, Intent)} in - * conjunction with @CapacitorPlugin */ @Deprecated protected void handleOnActivityResult(int requestCode, int resultCode, Intent data) {} From 64af6dca8f2ed29a595ef33b53cc50dbc9537c90 Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Thu, 28 Jan 2021 10:57:11 -0600 Subject: [PATCH 05/12] Fixing unintended change to a swift file --- ios/Capacitor/Capacitor/Array+Capacitor.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ios/Capacitor/Capacitor/Array+Capacitor.swift b/ios/Capacitor/Capacitor/Array+Capacitor.swift index e9d7a6f81..dffdfd7a0 100644 --- a/ios/Capacitor/Capacitor/Array+Capacitor.swift +++ b/ios/Capacitor/Capacitor/Array+Capacitor.swift @@ -1,7 +1,7 @@ // convenience wrappers to transform Arrays between NSNull and Optional values, for interoperability with Obj-C extension Array: CapacitorExtension {} -extension CapacitorExtensionTypeWrapper where T == [JSValue] { - public func replacingNullValues() -> [JSValue?] { +extension CapacitorExtensionTypeWrapper where T == Array { + public func replacingNullValues() -> Array { return baseType.map({ (value) -> JSValue? in if value is NSNull { return nil @@ -9,18 +9,18 @@ extension CapacitorExtensionTypeWrapper where T == [JSValue] { return value }) } - - public func replacingOptionalValues() -> [JSValue] { + + public func replacingOptionalValues() -> Array { return baseType } } -extension CapacitorExtensionTypeWrapper where T == [JSValue?] { - public func replacingNullValues() -> [JSValue?] { +extension CapacitorExtensionTypeWrapper where T == Array { + public func replacingNullValues() -> Array { return baseType } - - public func replacingOptionalValues() -> [JSValue] { + + public func replacingOptionalValues() -> Array { return baseType.map({ (value) -> JSValue in if let value = value { return value From c51cf945371522ed0632a0695721ade0eb284ae5 Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Thu, 28 Jan 2021 13:59:27 -0600 Subject: [PATCH 06/12] Undo Android Studio auto-formatting changes --- .../main/java/com/getcapacitor/Plugin.java | 44 +++++-------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index 9ccef6168..7ebcabe72 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -35,7 +35,7 @@ * Plugin is the base class for all plugins, containing a number of * convenient features for interacting with the {@link Bridge}, managing * plugin permissions, tracking lifecycle events, and more. - *

+ * * You should inherit from this class when creating new plugins, along with * adding the {@link CapacitorPlugin} annotation to add additional required * metadata about the Plugin @@ -211,7 +211,6 @@ private void permissionActivityResult(PluginCall call, String[] permissionString /** * Get the main {@link Context} for the current Activity (your app) - * * @return the Context for the current activity */ public Context getContext() { @@ -220,7 +219,6 @@ public Context getContext() { /** * Get the main {@link Activity} for the app - * * @return the Activity for the current app */ public AppCompatActivity getActivity() { @@ -229,7 +227,6 @@ public AppCompatActivity getActivity() { /** * Set the Bridge instance for this plugin - * * @param bridge */ public void setBridge(Bridge bridge) { @@ -256,10 +253,9 @@ public void setPluginHandle(PluginHandle pluginHandle) { /** * Return the wrapper {@link PluginHandle} for this plugin. - *

+ * * This wrapper contains additional metadata about the plugin instance, * such as indexed methods for reflection, and {@link CapacitorPlugin} annotation data). - * * @return */ public PluginHandle getPluginHandle() { @@ -268,7 +264,6 @@ public PluginHandle getPluginHandle() { /** * Get the root App ID - * * @return */ public String getAppId() { @@ -289,7 +284,6 @@ public void saveCall(PluginCall lastCall) { /** * Set the last saved call to null to free memory - * * @deprecated use {@link PluginCall#release(Bridge)} */ @Deprecated @@ -323,11 +317,11 @@ public PluginConfig getConfig() { /** * Get the value for a key on the config for this plugin. + * @deprecated use {@link #getConfig()} and access config values using the methods available + * depending on the type. * * @param key the key for the config value * @return some object containing the value from the config - * @deprecated use {@link #getConfig()} and access config values using the methods available - * depending on the type. */ @Deprecated public Object getConfigValue(String key) { @@ -341,7 +335,6 @@ public Object getConfigValue(String key) { /** * Check whether any of the given permissions has been defined in the AndroidManifest.xml - * * @param permissions * @return */ @@ -356,7 +349,6 @@ public boolean hasDefinedPermissions(String[] permissions) { /** * Check whether any of the given permissions has been defined in the AndroidManifest.xml - * * @param permissions * @return */ @@ -389,7 +381,6 @@ public boolean hasDefinedRequiredPermissions() { /** * Check whether the given permission has been granted by the user - * * @param permission * @return */ @@ -402,7 +393,6 @@ public boolean hasPermission(String permission) { * this method checks if each is granted. Note: if you are okay * with a limited subset of the permissions being granted, check * each one individually instead with hasPermission - * * @return */ public boolean hasRequiredPermissions() { @@ -432,13 +422,13 @@ public boolean hasRequiredPermissions() { /** * Request all of the specified permissions in the CapacitorPlugin annotation (if any) - *

+ * * If there is no registered permission callback for the PluginCall passed in, the call will * be rejected. Make sure a valid permission callback method is registered using the * {@link PermissionCallback} annotation. * - * @param call the plugin call * @since 3.0.0 + * @param call the plugin call */ protected void requestAllPermissions(@NonNull PluginCall call, @NonNull String callbackName) { CapacitorPlugin annotation = handle.getPluginAnnotation(); @@ -454,7 +444,7 @@ protected void requestAllPermissions(@NonNull PluginCall call, @NonNull String c /** * Request permissions using an alias defined on the plugin. - *

+ * * If there is no registered permission callback for the PluginCall passed in, the call will * be rejected. Make sure a valid permission callback method is registered using the * {@link PermissionCallback} annotation. @@ -609,8 +599,8 @@ public PermissionState getPermissionState(String alias) { /** * Helper to check all permissions defined on a plugin and see the state of each. * - * @return A mapping of permission aliases to the associated granted status. * @since 3.0.0 + * @return A mapping of permission aliases to the associated granted status. */ public Map getPermissionStates() { return bridge.getPermissionStates(this); @@ -618,7 +608,6 @@ public Map getPermissionStates() { /** * Add a listener for the given event - * * @param eventName * @param call */ @@ -639,7 +628,6 @@ private void addEventListener(String eventName, PluginCall call) { /** * Remove a listener from the given event - * * @param eventName * @param call */ @@ -654,7 +642,6 @@ private void removeEventListener(String eventName, PluginCall call) { /** * Notify all listeners that an event occurred - * * @param eventName * @param data */ @@ -678,7 +665,6 @@ protected void notifyListeners(String eventName, JSObject data, boolean retainUn * Notify all listeners that an event occurred * This calls {@link Plugin#notifyListeners(String, JSObject, boolean)} * with retainUntilConsumed set to false - * * @param eventName * @param data */ @@ -700,7 +686,6 @@ protected boolean hasListeners(String eventName) { /** * Send retained arguments (if any) for this event. This * is called only when the first listener for an event is added - * * @param eventName */ private void sendRetainedArgumentsForEvent(String eventName) { @@ -715,7 +700,6 @@ private void sendRetainedArgumentsForEvent(String eventName) { /** * Exported plugin call for adding a listener to this plugin - * * @param call */ @SuppressWarnings("unused") @@ -728,7 +712,6 @@ public void addListener(PluginCall call) { /** * Exported plugin call to remove a listener from this plugin - * * @param call */ @SuppressWarnings("unused") @@ -745,7 +728,6 @@ public void removeListener(PluginCall call) { /** * Exported plugin call to remove all listeners from this plugin - * * @param call */ @SuppressWarnings("unused") @@ -870,11 +852,11 @@ public void requestPermissions(PluginCall call) { * Handle request permissions result. A plugin using the deprecated {@link NativePlugin} * should override this to handle the result, or this method will handle the result * for our convenient requestPermissions call. + * @deprecated in favor of using callbacks in conjunction with {@link CapacitorPlugin} * * @param requestCode * @param permissions * @param grantResults - * @deprecated in favor of using callbacks in conjunction with {@link CapacitorPlugin} */ @Deprecated protected void handleRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { @@ -897,7 +879,6 @@ protected void handleRequestPermissionsResult(int requestCode, String[] permissi * may be limited, plugins that expect to be called with large data * objects (such as a file), should override this method and selectively * store option values in a {@link Bundle} to avoid exceeding limits. - * * @return a new {@link Bundle} with fields set from the options of the last saved {@link PluginCall} */ protected Bundle saveInstanceState() { @@ -922,7 +903,6 @@ protected Bundle saveInstanceState() { * activity response. If the plugin that started the activity * stored data in {@link Plugin#saveInstanceState()} then this * method will be called to allow the plugin to restore from that. - * * @param state */ protected void restoreState(Bundle state) {} @@ -942,14 +922,12 @@ protected void handleOnActivityResult(int requestCode, int resultCode, Intent da /** * Handle onNewIntent - * * @param intent */ protected void handleOnNewIntent(Intent intent) {} /** * Handle onConfigurationChanged - * * @param newConfig */ protected void handleOnConfigurationChanged(Configuration newConfig) {} @@ -997,7 +975,7 @@ public Boolean shouldOverrideLoad(Uri url) { /** * Start a new Activity. - *

+ * * Note: This method must be used by all plugins instead of calling * {@link Activity#startActivityForResult} as it associates the plugin with * any resulting data from the new Activity even if this app @@ -1013,7 +991,6 @@ protected void startActivityForResult(PluginCall call, Intent intent, int result /** * Execute the given runnable on the Bridge's task handler - * * @param runnable */ public void execute(Runnable runnable) { @@ -1022,7 +999,6 @@ public void execute(Runnable runnable) { /** * Shortcut for getting the plugin log tag - * * @param subTags */ protected String getLogTag(String... subTags) { From 2093322b308d691287ea8d0b359156c967ab8af0 Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Thu, 28 Jan 2021 14:02:59 -0600 Subject: [PATCH 07/12] More removal of auto-format --- .../capacitor/src/main/java/com/getcapacitor/Plugin.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index 7ebcabe72..c641e71da 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -244,7 +244,6 @@ public Bridge getBridge() { * Set the wrapper {@link PluginHandle} instance for this plugin that * contains additional metadata about the Plugin instance (such * as indexed methods for reflection, and {@link CapacitorPlugin} annotation data). - * * @param pluginHandle */ public void setPluginHandle(PluginHandle pluginHandle) { @@ -273,9 +272,9 @@ public String getAppId() { /** * Called to save a {@link PluginCall} in order to reference it * later, such as in an activity or permissions result handler + * @deprecated use {@link Bridge#saveCall(PluginCall)} * * @param lastCall - * @deprecated use {@link Bridge#saveCall(PluginCall)} */ @Deprecated public void saveCall(PluginCall lastCall) { @@ -296,9 +295,9 @@ public void freeSavedCall() { /** * Get the last saved call, if any + * @deprecated use {@link Bridge#getSavedCall(String)} * * @return - * @deprecated use {@link Bridge#getSavedCall(String)} */ @Deprecated public PluginCall getSavedCall() { @@ -458,7 +457,7 @@ protected void requestPermissionForAlias(@NonNull String alias, @NonNull PluginC /** * Request permissions using aliases defined on the plugin. - *

+ * * If there is no registered permission callback for the PluginCall passed in, the call will * be rejected. Make sure a valid permission callback method is registered using the * {@link PermissionCallback} annotation. @@ -980,7 +979,6 @@ public Boolean shouldOverrideLoad(Uri url) { * {@link Activity#startActivityForResult} as it associates the plugin with * any resulting data from the new Activity even if this app * is destroyed by the OS (to free up memory, for example). - * * @param intent * @param resultCode */ From 26a790aa915580fd78fb28f636f4c7affda4b200 Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Thu, 28 Jan 2021 14:53:53 -0600 Subject: [PATCH 08/12] pluginCallForLastActivity for new activity result --- .../capacitor/src/main/java/com/getcapacitor/Bridge.java | 6 ++++++ .../capacitor/src/main/java/com/getcapacitor/Plugin.java | 3 +++ 2 files changed, 9 insertions(+) diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index f19d41af0..48404aa7d 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -637,6 +637,12 @@ public PluginCall getSavedCall(String callbackId) { return this.savedCalls.get(callbackId); } + PluginCall getPluginCallForLastActivity() { + PluginCall pluginCallForLastActivity = this.pluginCallForLastActivity; + this.pluginCallForLastActivity = null; + return pluginCallForLastActivity; + } + /** * Release a retained call * @param call diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index c641e71da..07e7299b4 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -162,6 +162,9 @@ private void triggerPermissionCallback(Method method, Map permi private void triggerActivityCallback(Method method, ActivityResult result) { PluginCall savedCall = bridge.getSavedCall(lastPluginCallId); + if (savedCall == null) { + savedCall = bridge.getPluginCallForLastActivity(); + } // invoke the activity result callback try { From 5f444b8d20ce90055da5140c2436f7c652cdabbe Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Sun, 31 Jan 2021 15:27:34 -0600 Subject: [PATCH 09/12] remove hardcoded checkPermissions launcher for simpler Plugin class check --- .../main/java/com/getcapacitor/Plugin.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index 07e7299b4..55c053ffa 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -99,23 +99,11 @@ public void load() {} * activities started for result. */ void initializeActivityLaunchers() { - try { - // load the default checkPermission callback from the plugin parent class - Method method = getClass().getSuperclass().getMethod("checkPermissions", PluginCall.class); - permissionLaunchers.put( - "checkPermissions", - bridge - .getActivity() - .registerForActivityResult( - new ActivityResultContracts.RequestMultiplePermissions(), - permissions -> triggerPermissionCallback(method, permissions) - ) - ); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } + List pluginClassMethods = new ArrayList<>(); + pluginClassMethods.addAll(Arrays.asList(getClass().getSuperclass().getDeclaredMethods())); + pluginClassMethods.addAll(Arrays.asList(getClass().getDeclaredMethods())); - for (final Method method : getClass().getDeclaredMethods()) { + for (final Method method : pluginClassMethods) { if (method.isAnnotationPresent(ActivityCallback.class)) { // register callbacks annotated with ActivityCallback for activity results activityLaunchers.put( From 586561ebf08ae2e24149d9114c0453e9457efb85 Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Sun, 31 Jan 2021 17:25:25 -0600 Subject: [PATCH 10/12] javadoc --- android/capacitor/src/main/java/com/getcapacitor/Plugin.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index 55c053ffa..f1d586f5a 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -175,6 +175,8 @@ private void triggerActivityCallback(Method method, ActivityResult result) { * {@link ActivityCallback} annotation. * * @param call the plugin call + * @param intent the intent used to start an activity + * @param callbackName the name of the callback to run when the launched activity is finished * @since 3.0.0 */ public void startActivityForResult(PluginCall call, Intent intent, String callbackName) { @@ -419,6 +421,7 @@ public boolean hasRequiredPermissions() { * * @since 3.0.0 * @param call the plugin call + * @param callbackName the name of the callback to run when the permission request is complete */ protected void requestAllPermissions(@NonNull PluginCall call, @NonNull String callbackName) { CapacitorPlugin annotation = handle.getPluginAnnotation(); @@ -441,6 +444,7 @@ protected void requestAllPermissions(@NonNull PluginCall call, @NonNull String c * * @param alias an alias defined on the plugin * @param call the plugin call involved in originating the request + * @param callbackName the name of the callback to run when the permission request is complete */ protected void requestPermissionForAlias(@NonNull String alias, @NonNull PluginCall call, @NonNull String callbackName) { requestPermissionForAliases(new String[] { alias }, call, callbackName); @@ -455,6 +459,7 @@ protected void requestPermissionForAlias(@NonNull String alias, @NonNull PluginC * * @param aliases a set of aliases defined on the plugin * @param call the plugin call involved in originating the request + * @param callbackName the name of the callback to run when the permission request is complete */ protected void requestPermissionForAliases(@NonNull String[] aliases, @NonNull PluginCall call, @NonNull String callbackName) { if (aliases.length == 0) { From 95d5d65d454d8431016199b6ea9b29f30257cdf2 Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Mon, 1 Feb 2021 16:09:29 -0600 Subject: [PATCH 11/12] ensure all children of Plugin class have annotated callbacks registered --- .../capacitor/src/main/java/com/getcapacitor/Plugin.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index f1d586f5a..2a7307b19 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -100,8 +100,13 @@ public void load() {} */ void initializeActivityLaunchers() { List pluginClassMethods = new ArrayList<>(); - pluginClassMethods.addAll(Arrays.asList(getClass().getSuperclass().getDeclaredMethods())); - pluginClassMethods.addAll(Arrays.asList(getClass().getDeclaredMethods())); + for ( + Class pluginCursor = getClass(); + !pluginCursor.getName().equals(Object.class.getName()); + pluginCursor = pluginCursor.getSuperclass() + ) { + pluginClassMethods.addAll(Arrays.asList(pluginCursor.getDeclaredMethods())); + } for (final Method method : pluginClassMethods) { if (method.isAnnotationPresent(ActivityCallback.class)) { From 6508e82cab279888c6fdaaf54be7fef6c0f3ca6d Mon Sep 17 00:00:00 2001 From: Carl Poole Date: Wed, 3 Feb 2021 13:00:24 -0600 Subject: [PATCH 12/12] Fixed crash on subsequent premission requests due to double saving --- android/capacitor/src/main/java/com/getcapacitor/Plugin.java | 1 - 1 file changed, 1 deletion(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index 2a7307b19..d818bd25c 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -475,7 +475,6 @@ protected void requestPermissionForAliases(@NonNull String[] aliases, @NonNull P String[] permissions = getPermissionStringsForAliases(aliases); if (permissions.length > 0) { - bridge.savePermissionCall(call); permissionActivityResult(call, permissions, callbackName); } }