())
+ }
+ }
+}
diff --git a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/JSValue.java b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/JSValue.java
deleted file mode 100644
index d97ba91b..00000000
--- a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/JSValue.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.getcapacitor;
-
-import org.json.JSONException;
-
-/**
- * Represents a single user-data value of any type on the capacitor PluginCall object.
- */
-public class JSValue {
-
- private final Object value;
-
- /**
- * @param call The capacitor plugin call, used for accessing the value safely.
- * @param name The name of the property to access.
- */
- public JSValue(PluginCall call, String name) {
- this.value = this.toValue(call, name);
- }
-
- /**
- * Returns the coerced but uncasted underlying value.
- */
- public Object getValue() {
- return this.value;
- }
-
- @Override
- public String toString() {
- return this.getValue().toString();
- }
-
- /**
- * Returns the underlying value as a JSObject, or throwing if it cannot.
- *
- * @throws JSONException If the underlying value is not a JSObject.
- */
- public JSObject toJSObject() throws JSONException {
- if (this.value instanceof JSObject) return (JSObject) this.value;
- throw new JSONException("JSValue could not be coerced to JSObject.");
- }
-
- /**
- * Returns the underlying value as a JSArray, or throwing if it cannot.
- *
- * @throws JSONException If the underlying value is not a JSArray.
- */
- public JSArray toJSArray() throws JSONException {
- if (this.value instanceof JSArray) return (JSArray) this.value;
- throw new JSONException("JSValue could not be coerced to JSArray.");
- }
-
- /**
- * Returns the underlying value this object represents, coercing it into a capacitor-friendly object if supported.
- */
- private Object toValue(PluginCall call, String name) {
- Object value = null;
- value = call.getArray(name, null);
- if (value != null) return value;
- value = call.getObject(name, null);
- if (value != null) return value;
- value = call.getString(name, null);
- if (value != null) return value;
- return call.getData().opt(name);
- }
-}
diff --git a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/JSValue.kt b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/JSValue.kt
new file mode 100644
index 00000000..e57395ec
--- /dev/null
+++ b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/JSValue.kt
@@ -0,0 +1,62 @@
+package com.getcapacitor
+
+import org.json.JSONException
+
+/**
+ * Represents a single user-data value of any type on the capacitor PluginCall object.
+ */
+class JSValue(call: PluginCall, name: String) {
+ /**
+ * Returns the coerced but uncasted underlying value.
+ */
+ @JvmField
+ val value: Any
+
+ /**
+ * @param call The capacitor plugin call, used for accessing the value safely.
+ * @param name The name of the property to access.
+ */
+ init {
+ this.value = this.toValue(call, name)
+ }
+
+ override fun toString(): String {
+ return value.toString()
+ }
+
+ /**
+ * Returns the underlying value as a JSObject, or throwing if it cannot.
+ *
+ * @throws JSONException If the underlying value is not a JSObject.
+ */
+ @Throws(JSONException::class)
+ fun toJSObject(): JSObject {
+ if (value is JSObject) return value
+ throw JSONException("JSValue could not be coerced to JSObject.")
+ }
+
+ /**
+ * Returns the underlying value as a JSArray, or throwing if it cannot.
+ *
+ * @throws JSONException If the underlying value is not a JSArray.
+ */
+ @Throws(JSONException::class)
+ fun toJSArray(): JSArray {
+ if (value is JSArray) return value
+ throw JSONException("JSValue could not be coerced to JSArray.")
+ }
+
+ /**
+ * Returns the underlying value this object represents, coercing it into a capacitor-friendly object if supported.
+ */
+ private fun toValue(call: PluginCall, name: String): Any {
+ var value: Any? = null
+ value = call.getArray(name, null)
+ if (value != null) return value
+ value = call.getObject(name, null)
+ if (value != null) return value
+ value = call.getString(name, null)
+ if (value != null) return value
+ return call.data.opt(name)
+ }
+}
diff --git a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Logger.java b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Logger.java
deleted file mode 100644
index 9d24fedd..00000000
--- a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Logger.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.getcapacitor;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-public class Logger {
-
- public static final String LOG_TAG_CORE = "Capacitor";
- public static CapConfig config;
-
- private static Logger instance;
-
- private static Logger getInstance() {
- if (instance == null) {
- instance = new Logger();
- }
- return instance;
- }
-
- public static void init(CapConfig config) {
- Logger.getInstance().loadConfig(config);
- }
-
- private void loadConfig(CapConfig config) {
- Logger.config = config;
- }
-
- public static String tags(String... subtags) {
- if (subtags != null && subtags.length > 0) {
- return LOG_TAG_CORE + "/" + TextUtils.join("/", subtags);
- }
-
- return LOG_TAG_CORE;
- }
-
- public static void verbose(String message) {
- verbose(LOG_TAG_CORE, message);
- }
-
- public static void verbose(String tag, String message) {
- if (!shouldLog()) {
- return;
- }
-
- Log.v(tag, message);
- }
-
- public static void debug(String message) {
- debug(LOG_TAG_CORE, message);
- }
-
- public static void debug(String tag, String message) {
- if (!shouldLog()) {
- return;
- }
-
- Log.d(tag, message);
- }
-
- public static void info(String message) {
- info(LOG_TAG_CORE, message);
- }
-
- public static void info(String tag, String message) {
- if (!shouldLog()) {
- return;
- }
-
- Log.i(tag, message);
- }
-
- public static void warn(String message) {
- warn(LOG_TAG_CORE, message);
- }
-
- public static void warn(String tag, String message) {
- if (!shouldLog()) {
- return;
- }
-
- Log.w(tag, message);
- }
-
- public static void error(String message) {
- error(LOG_TAG_CORE, message, null);
- }
-
- public static void error(String message, Throwable e) {
- error(LOG_TAG_CORE, message, e);
- }
-
- public static void error(String tag, String message, Throwable e) {
- if (!shouldLog()) {
- return;
- }
-
- Log.e(tag, message, e);
- }
-
- public static boolean shouldLog() {
- return config == null || config.isLoggingEnabled();
- }
-}
diff --git a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Logger.kt b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Logger.kt
new file mode 100644
index 00000000..c80cbb2c
--- /dev/null
+++ b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Logger.kt
@@ -0,0 +1,109 @@
+package com.getcapacitor
+
+import android.text.TextUtils
+import android.util.Log
+
+class Logger {
+ private fun loadConfig(config: CapConfig) {
+ Companion.config = config
+ }
+
+ companion object {
+ const val LOG_TAG_CORE: String = "Capacitor"
+ var config: CapConfig? = null
+
+ private var instance: Logger? = null
+ get() {
+ if (field == null) {
+ field = Logger()
+ }
+ return field
+ }
+
+ fun init(config: CapConfig) {
+ instance!!.loadConfig(config)
+ }
+
+ @JvmStatic
+ fun tags(vararg subtags: String?): String {
+ if (subtags != null && subtags.size > 0) {
+ return LOG_TAG_CORE + "/" + TextUtils.join("/", subtags)
+ }
+
+ return LOG_TAG_CORE
+ }
+
+ fun verbose(message: String?) {
+ verbose(LOG_TAG_CORE, message)
+ }
+
+ fun verbose(tag: String?, message: String?) {
+ if (!shouldLog()) {
+ return
+ }
+
+ Log.v(tag, message!!)
+ }
+
+ fun debug(message: String?) {
+ debug(LOG_TAG_CORE, message)
+ }
+
+ @JvmStatic
+ fun debug(tag: String?, message: String?) {
+ if (!shouldLog()) {
+ return
+ }
+
+ Log.d(tag, message!!)
+ }
+
+ fun info(message: String?) {
+ info(LOG_TAG_CORE, message)
+ }
+
+ @JvmStatic
+ fun info(tag: String?, message: String?) {
+ if (!shouldLog()) {
+ return
+ }
+
+ Log.i(tag, message!!)
+ }
+
+ fun warn(message: String?) {
+ warn(LOG_TAG_CORE, message)
+ }
+
+ fun warn(tag: String?, message: String?) {
+ if (!shouldLog()) {
+ return
+ }
+
+ Log.w(tag, message!!)
+ }
+
+ @JvmStatic
+ fun error(message: String?) {
+ error(LOG_TAG_CORE, message, null)
+ }
+
+ @JvmStatic
+ fun error(message: String?, e: Throwable?) {
+ error(LOG_TAG_CORE, message, e)
+ }
+
+ @JvmStatic
+ fun error(tag: String?, message: String?, e: Throwable?) {
+ if (!shouldLog()) {
+ return
+ }
+
+ Log.e(tag, message, e)
+ }
+
+ fun shouldLog(): Boolean {
+ return config == null || config!!.isLoggingEnabled
+ }
+ }
+}
diff --git a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/MessageHandler.java b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/MessageHandler.java
deleted file mode 100644
index b71124e8..00000000
--- a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/MessageHandler.java
+++ /dev/null
@@ -1,159 +0,0 @@
-package com.getcapacitor;
-
-import android.webkit.JavascriptInterface;
-import android.webkit.WebView;
-import androidx.webkit.JavaScriptReplyProxy;
-import androidx.webkit.WebViewCompat;
-import androidx.webkit.WebViewFeature;
-import org.apache.cordova.PluginManager;
-
-/**
- * MessageHandler handles messages from the WebView, dispatching them
- * to plugins.
- */
-public class MessageHandler {
-
- private Bridge bridge;
- private WebView webView;
- private PluginManager cordovaPluginManager;
- private JavaScriptReplyProxy javaScriptReplyProxy;
-
- public MessageHandler(Bridge bridge, WebView webView, PluginManager cordovaPluginManager) {
- this.bridge = bridge;
- this.webView = webView;
- this.cordovaPluginManager = cordovaPluginManager;
-
- if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER) && !bridge.getConfig().isUsingLegacyBridge()) {
- WebViewCompat.WebMessageListener capListener = (view, message, sourceOrigin, isMainFrame, replyProxy) -> {
- if (isMainFrame) {
- postMessage(message.getData());
- javaScriptReplyProxy = replyProxy;
- } else {
- Logger.warn("Plugin execution is allowed in Main Frame only");
- }
- };
- try {
- WebViewCompat.addWebMessageListener(webView, "androidBridge", bridge.getAllowedOriginRules(), capListener);
- } catch (Exception ex) {
- webView.addJavascriptInterface(this, "androidBridge");
- }
- } else {
- webView.addJavascriptInterface(this, "androidBridge");
- }
- }
-
- /**
- * The main message handler that will be called from JavaScript
- * to send a message to the native bridge.
- * @param jsonStr
- */
- @JavascriptInterface
- @SuppressWarnings("unused")
- public void postMessage(String jsonStr) {
- try {
- JSObject postData = new JSObject(jsonStr);
-
- String type = postData.getString("type");
-
- boolean typeIsNotNull = type != null;
- boolean isCordovaPlugin = typeIsNotNull && type.equals("cordova");
- boolean isJavaScriptError = typeIsNotNull && type.equals("js.error");
-
- String callbackId = postData.getString("callbackId");
-
- if (isCordovaPlugin) {
- String service = postData.getString("service");
- String action = postData.getString("action");
- String actionArgs = postData.getString("actionArgs");
-
- Logger.verbose(
- Logger.tags("Plugin"),
- "To native (Cordova plugin): callbackId: " +
- callbackId +
- ", service: " +
- service +
- ", action: " +
- action +
- ", actionArgs: " +
- actionArgs
- );
-
- this.callCordovaPluginMethod(callbackId, service, action, actionArgs);
- } else if (isJavaScriptError) {
- Logger.error("JavaScript Error: " + jsonStr);
- } else {
- String pluginId = postData.getString("pluginId");
- String methodName = postData.getString("methodName");
- JSObject methodData = postData.getJSObject("options", new JSObject());
-
- Logger.verbose(
- Logger.tags("Plugin"),
- "To native (Capacitor plugin): callbackId: " + callbackId + ", pluginId: " + pluginId + ", methodName: " + methodName
- );
-
- this.callPluginMethod(callbackId, pluginId, methodName, methodData);
- }
- } catch (Exception ex) {
- Logger.error("Post message error:", ex);
- }
- }
-
- public void sendResponseMessage(PluginCall call, PluginResult successResult, PluginResult errorResult) {
- try {
- PluginResult data = new PluginResult();
- data.put("save", call.isKeptAlive());
- data.put("callbackId", call.getCallbackId());
- data.put("pluginId", call.getPluginId());
- data.put("methodName", call.getMethodName());
-
- boolean pluginResultInError = errorResult != null;
- if (pluginResultInError) {
- data.put("success", false);
- data.put("error", errorResult);
- Logger.debug("Sending plugin error: " + data.toString());
- } else {
- data.put("success", true);
- if (successResult != null) {
- data.put("data", successResult);
- }
- }
-
- boolean isValidCallbackId = !call.getCallbackId().equals(PluginCall.CALLBACK_ID_DANGLING);
- if (isValidCallbackId) {
- if (bridge.getConfig().isUsingLegacyBridge()) {
- legacySendResponseMessage(data);
- } else if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER) && javaScriptReplyProxy != null) {
- javaScriptReplyProxy.postMessage(data.toString());
- } else {
- legacySendResponseMessage(data);
- }
- } else {
- bridge.getApp().fireRestoredResult(data);
- }
- } catch (Exception ex) {
- Logger.error("sendResponseMessage: error: " + ex);
- }
- if (!call.isKeptAlive()) {
- call.release(bridge);
- }
- }
-
- private void legacySendResponseMessage(PluginResult data) {
- final String runScript = "window.Capacitor.fromNative(" + data.toString() + ")";
- final WebView webView = this.webView;
- webView.post(() -> webView.evaluateJavascript(runScript, null));
- }
-
- private void callPluginMethod(String callbackId, String pluginId, String methodName, JSObject methodData) {
- PluginCall call = new PluginCall(this, pluginId, callbackId, methodName, methodData);
- bridge.callPluginMethod(pluginId, methodName, call);
- }
-
- private void callCordovaPluginMethod(String callbackId, String service, String action, String actionArgs) {
- bridge.execute(
- () -> {
- cordovaPluginManager.exec(service, action, callbackId, actionArgs);
- }
- );
- }
-}
diff --git a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/MessageHandler.kt b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/MessageHandler.kt
new file mode 100644
index 00000000..3655b3ad
--- /dev/null
+++ b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/MessageHandler.kt
@@ -0,0 +1,176 @@
+package com.getcapacitor
+
+import android.net.Uri
+import android.webkit.JavascriptInterface
+import android.webkit.WebView
+import androidx.webkit.JavaScriptReplyProxy
+import androidx.webkit.WebMessageCompat
+import androidx.webkit.WebViewCompat
+import androidx.webkit.WebViewCompat.WebMessageListener
+import androidx.webkit.WebViewFeature
+import org.apache.cordova.PluginManager
+
+/**
+ * MessageHandler handles messages from the WebView, dispatching them
+ * to plugins.
+ */
+class MessageHandler(
+ private val bridge: Bridge,
+ private val webView: WebView,
+ private val cordovaPluginManager: PluginManager
+) {
+ private var javaScriptReplyProxy: JavaScriptReplyProxy? = null
+
+ init {
+ if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER) && !bridge.config.isUsingLegacyBridge) {
+ val capListener =
+ WebMessageListener { view: WebView?, message: WebMessageCompat, sourceOrigin: Uri?, isMainFrame: Boolean, replyProxy: JavaScriptReplyProxy? ->
+ if (isMainFrame) {
+ postMessage(message.data)
+ javaScriptReplyProxy = replyProxy
+ } else {
+ Logger.Companion.warn("Plugin execution is allowed in Main Frame only")
+ }
+ }
+ try {
+ WebViewCompat.addWebMessageListener(
+ webView,
+ "androidBridge",
+ bridge.getAllowedOriginRules(),
+ capListener
+ )
+ } catch (ex: Exception) {
+ webView.addJavascriptInterface(this, "androidBridge")
+ }
+ } else {
+ webView.addJavascriptInterface(this, "androidBridge")
+ }
+ }
+
+ /**
+ * The main message handler that will be called from JavaScript
+ * to send a message to the native bridge.
+ * @param jsonStr
+ */
+ @JavascriptInterface
+ @Suppress("unused")
+ fun postMessage(jsonStr: String?) {
+ try {
+ val postData = JSObject(jsonStr)
+
+ val type = postData.getString("type")
+
+ val typeIsNotNull = type != null
+ val isCordovaPlugin = typeIsNotNull && type == "cordova"
+ val isJavaScriptError = typeIsNotNull && type == "js.error"
+
+ val callbackId = postData.getString("callbackId")
+
+ if (isCordovaPlugin) {
+ val service = postData.getString("service")
+ val action = postData.getString("action")
+ val actionArgs = postData.getString("actionArgs")
+
+ Logger.Companion.verbose(
+ Logger.Companion.tags("Plugin"),
+ "To native (Cordova plugin): callbackId: " +
+ callbackId +
+ ", service: " +
+ service +
+ ", action: " +
+ action +
+ ", actionArgs: " +
+ actionArgs
+ )
+
+ this.callCordovaPluginMethod(callbackId, service, action, actionArgs)
+ } else if (isJavaScriptError) {
+ Logger.Companion.error("JavaScript Error: $jsonStr")
+ } else {
+ val pluginId = postData.getString("pluginId")
+ val methodName = postData.getString("methodName")
+ val methodData = postData.getJSObject("options", JSObject())
+
+ Logger.Companion.verbose(
+ Logger.Companion.tags("Plugin"),
+ "To native (Capacitor plugin): callbackId: $callbackId, pluginId: $pluginId, methodName: $methodName"
+ )
+
+ this.callPluginMethod(callbackId, pluginId, methodName, methodData)
+ }
+ } catch (ex: Exception) {
+ Logger.Companion.error("Post message error:", ex)
+ }
+ }
+
+ fun sendResponseMessage(
+ call: PluginCall,
+ successResult: PluginResult?,
+ errorResult: PluginResult?
+ ) {
+ try {
+ val data = PluginResult()
+ data.put("save", call.isKeptAlive)
+ data.put("callbackId", call.callbackId)
+ data.put("pluginId", call.pluginId)
+ data.put("methodName", call.methodName)
+
+ val pluginResultInError = errorResult != null
+ if (pluginResultInError) {
+ data.put("success", false)
+ data.put("error", errorResult)
+ Logger.Companion.debug("Sending plugin error: $data")
+ } else {
+ data.put("success", true)
+ if (successResult != null) {
+ data.put("data", successResult)
+ }
+ }
+
+ val isValidCallbackId = call.callbackId != PluginCall.Companion.CALLBACK_ID_DANGLING
+ if (isValidCallbackId) {
+ if (bridge.config.isUsingLegacyBridge) {
+ legacySendResponseMessage(data)
+ } else if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER) && javaScriptReplyProxy != null) {
+ javaScriptReplyProxy.postMessage(data.toString())
+ } else {
+ legacySendResponseMessage(data)
+ }
+ } else {
+ bridge.app.fireRestoredResult(data)
+ }
+ } catch (ex: Exception) {
+ Logger.Companion.error("sendResponseMessage: error: $ex")
+ }
+ if (!call.isKeptAlive) {
+ call.release(bridge)
+ }
+ }
+
+ private fun legacySendResponseMessage(data: PluginResult) {
+ val runScript = "window.Capacitor.fromNative($data)"
+ val webView = this.webView
+ webView.post { webView.evaluateJavascript(runScript, null) }
+ }
+
+ private fun callPluginMethod(
+ callbackId: String?,
+ pluginId: String?,
+ methodName: String?,
+ methodData: JSObject?
+ ) {
+ val call = PluginCall(this, pluginId, callbackId, methodName, methodData)
+ bridge.callPluginMethod(pluginId!!, methodName!!, call)
+ }
+
+ private fun callCordovaPluginMethod(
+ callbackId: String?,
+ service: String?,
+ action: String?,
+ actionArgs: String?
+ ) {
+ bridge.execute {
+ cordovaPluginManager.exec(service, action, callbackId, actionArgs)
+ }
+ }
+}
diff --git a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/NativePlugin.java b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/NativePlugin.kt
similarity index 53%
rename from @capacitor/android/capacitor/src/main/java/com/getcapacitor/NativePlugin.java
rename to @capacitor/android/capacitor/src/main/java/com/getcapacitor/NativePlugin.kt
index c4307624..62c84056 100644
--- a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/NativePlugin.java
+++ b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/NativePlugin.kt
@@ -1,37 +1,28 @@
-package com.getcapacitor;
-
-import com.getcapacitor.annotation.CapacitorPlugin;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+package com.getcapacitor
/**
* Base annotation for all Plugins
- * @deprecated
- * Use {@link CapacitorPlugin} instead
*/
-@Retention(RetentionPolicy.RUNTIME)
-@Deprecated
-public @interface NativePlugin {
+@Retention(AnnotationRetention.RUNTIME)
+@Deprecated("
Use {@link CapacitorPlugin} instead")
+annotation class NativePlugin(
/**
* Request codes this plugin uses and responds to, in order to tie
* Android events back the plugin to handle
*/
- int[] requestCodes() default {};
-
+ val requestCodes: IntArray = [],
/**
* Permissions this plugin needs, in order to make permission requests
* easy if the plugin only needs basic permission prompting
*/
- String[] permissions() default {};
-
+ val permissions: Array = [],
/**
* The request code to use when automatically requesting permissions
*/
- int permissionRequestCode() default 9000;
-
+ val permissionRequestCode: Int = 9000,
/**
* A custom name for the plugin, otherwise uses the
* simple class name.
*/
- String name() default "";
-}
+ val name: String = ""
+)
diff --git a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/PermissionState.java b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/PermissionState.java
deleted file mode 100644
index 382cff71..00000000
--- a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/PermissionState.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.getcapacitor;
-
-import java.util.Locale;
-
-/**
- * Represents the state of a permission
- *
- * @since 3.0.0
- */
-public enum PermissionState {
- GRANTED("granted"),
- DENIED("denied"),
- PROMPT("prompt"),
- PROMPT_WITH_RATIONALE("prompt-with-rationale");
-
- private String state;
-
- PermissionState(String state) {
- this.state = state;
- }
-
- @Override
- public String toString() {
- return state;
- }
-
- public static PermissionState byState(String state) {
- state = state.toUpperCase(Locale.ROOT).replace('-', '_');
- return valueOf(state);
- }
-}
diff --git a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/PermissionState.kt b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/PermissionState.kt
new file mode 100644
index 00000000..5b2c7c58
--- /dev/null
+++ b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/PermissionState.kt
@@ -0,0 +1,25 @@
+package com.getcapacitor
+
+/**
+ * Represents the state of a permission
+ *
+ * @since 3.0.0
+ */
+enum class PermissionState(private val state: String) {
+ GRANTED("granted"),
+ DENIED("denied"),
+ PROMPT("prompt"),
+ PROMPT_WITH_RATIONALE("prompt-with-rationale");
+
+ override fun toString(): String {
+ return state
+ }
+
+ companion object {
+ fun byState(state: String): PermissionState {
+ var state = state
+ state = state.uppercase().replace('-', '_')
+ return valueOf(state)
+ }
+ }
+}
diff --git a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Plugin.java
deleted file mode 100644
index d8a3e82a..00000000
--- a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Plugin.java
+++ /dev/null
@@ -1,1046 +0,0 @@
-package com.getcapacitor;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-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.ActivityCallback;
-import com.getcapacitor.annotation.CapacitorPlugin;
-import com.getcapacitor.annotation.Permission;
-import com.getcapacitor.annotation.PermissionCallback;
-import com.getcapacitor.util.PermissionHelper;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
-import org.json.JSONException;
-
-/**
- * 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
- */
-public class Plugin {
-
- // The key we will use inside of a persisted Bundle for the JSON blob
- // for a plugin call options.
- private static final String BUNDLE_PERSISTED_OPTIONS_JSON_KEY = "_json";
-
- // Reference to the Bridge
- protected Bridge bridge;
-
- // Reference to the PluginHandle wrapper for this Plugin
- protected PluginHandle handle;
-
- /**
- * A way for plugins to quickly save a call that they will need to reference
- * between activity/permissions starts/requests
- *
- * @deprecated store calls on the bridge using the methods
- * {@link com.getcapacitor.Bridge#saveCall(PluginCall)},
- * {@link com.getcapacitor.Bridge#getSavedCall(String)} and
- * {@link com.getcapacitor.Bridge#releaseCall(PluginCall)}
- */
- @Deprecated
- protected PluginCall savedLastCall;
-
- // Stored event listeners
- private final Map> eventListeners;
-
- /**
- * Launchers used by the plugin to handle activity results
- */
- private final Map> activityLaunchers = new HashMap<>();
-
- /**
- * Launchers used by the plugin to handle permission results
- */
- 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.
- private final Map> retainedEventArguments;
-
- public Plugin() {
- eventListeners = new HashMap<>();
- retainedEventArguments = new HashMap<>();
- }
-
- /**
- * Called when the plugin has been connected to the bridge
- * and is ready to start initializing.
- */
- public void load() {}
-
- /**
- * Registers activity result launchers defined on plugins, used for permission requests and
- * activities started for result.
- */
- void initializeActivityLaunchers() {
- List pluginClassMethods = new ArrayList<>();
- 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)) {
- // register callbacks annotated with ActivityCallback for activity results
- ActivityResultLauncher launcher = bridge.registerForActivityResult(
- new ActivityResultContracts.StartActivityForResult(),
- result -> triggerActivityCallback(method, result)
- );
-
- activityLaunchers.put(method.getName(), launcher);
- } else if (method.isAnnotationPresent(PermissionCallback.class)) {
- // register callbacks annotated with PermissionCallback for permission results
- ActivityResultLauncher launcher = bridge.registerForActivityResult(
- new ActivityResultContracts.RequestMultiplePermissions(),
- permissions -> triggerPermissionCallback(method, permissions)
- );
-
- permissionLaunchers.put(method.getName(), launcher);
- }
- }
- }
-
- 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)) {
- try {
- method.setAccessible(true);
- method.invoke(this, savedCall);
- } catch (IllegalAccessException | InvocationTargetException e) {
- e.printStackTrace();
- }
- }
- }
-
- private void triggerActivityCallback(Method method, ActivityResult result) {
- PluginCall savedCall = bridge.getSavedCall(lastPluginCallId);
- if (savedCall == null) {
- savedCall = bridge.getPluginCallForLastActivity();
- }
- // invoke the activity result callback
- try {
- method.setAccessible(true);
- method.invoke(this, savedCall, result);
- } catch (IllegalAccessException | InvocationTargetException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * 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
- * @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) {
- ActivityResultLauncher activityResultLauncher = getActivityLauncherOrReject(call, callbackName);
- if (activityResultLauncher == null) {
- // return when null since call was rejected in getLauncherOrReject
- return;
- }
- bridge.setPluginCallForLastActivity(call);
- lastPluginCallId = call.getCallbackId();
- bridge.saveCall(call);
- activityResultLauncher.launch(intent);
- }
-
- private void permissionActivityResult(PluginCall call, String[] permissionStrings, String callbackName) {
- ActivityResultLauncher permissionResultLauncher = getPermissionLauncherOrReject(call, callbackName);
- if (permissionResultLauncher == null) {
- // return when null since call was rejected in getLauncherOrReject
- return;
- }
-
- bridge.savePermissionCall(call);
- permissionResultLauncher.launch(permissionStrings);
- }
-
- /**
- * Get the main {@link Context} for the current Activity (your app)
- * @return the Context for the current activity
- */
- public Context getContext() {
- return this.bridge.getContext();
- }
-
- /**
- * Get the main {@link Activity} for the app
- * @return the Activity for the current app
- */
- public AppCompatActivity getActivity() {
- return this.bridge.getActivity();
- }
-
- /**
- * Set the Bridge instance for this plugin
- * @param bridge
- */
- public void setBridge(Bridge bridge) {
- this.bridge = bridge;
- }
-
- /**
- * Get the Bridge instance for this plugin
- */
- public Bridge getBridge() {
- return this.bridge;
- }
-
- /**
- * 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) {
- this.handle = 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() {
- return this.handle;
- }
-
- /**
- * Get the root App ID
- * @return
- */
- public String getAppId() {
- return getContext().getPackageName();
- }
-
- /**
- * 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
- public void saveCall(PluginCall lastCall) {
- this.savedLastCall = lastCall;
- }
-
- /**
- * Set the last saved call to null to free memory
- * @deprecated use {@link PluginCall#release(Bridge)}
- */
- @Deprecated
- public void freeSavedCall() {
- this.savedLastCall.release(bridge);
- this.savedLastCall = null;
- }
-
- /**
- * Get the last saved call, if any
- * @deprecated use {@link Bridge#getSavedCall(String)}
- *
- * @return
- */
- @Deprecated
- public PluginCall getSavedCall() {
- return this.savedLastCall;
- }
-
- /**
- * Get the config options for this plugin.
- *
- * @return a config object representing the plugin config options, or an empty config
- * if none exists
- */
- public PluginConfig getConfig() {
- return bridge.getConfig().getPluginConfiguration(handle.getId());
- }
-
- /**
- * 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
- public Object getConfigValue(String key) {
- try {
- PluginConfig pluginConfig = getConfig();
- return pluginConfig.getConfigJSON().get(key);
- } catch (JSONException ex) {
- return null;
- }
- }
-
- /**
- * Check whether any of the given permissions has been defined in the AndroidManifest.xml
- * @deprecated use {@link #isPermissionDeclared(String)}
- *
- * @param permissions
- * @return
- */
- @Deprecated
- public boolean hasDefinedPermissions(String[] permissions) {
- for (String permission : permissions) {
- if (!PermissionHelper.hasDefinedPermission(getContext(), permission)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Check if all annotated permissions have been defined in the AndroidManifest.xml
- * @deprecated use {@link #isPermissionDeclared(String)}
- *
- * @return true if permissions are all defined in the Manifest
- */
- @Deprecated
- public boolean hasDefinedRequiredPermissions() {
- CapacitorPlugin annotation = handle.getPluginAnnotation();
- if (annotation == null) {
- // Check for legacy plugin annotation, @NativePlugin
- NativePlugin legacyAnnotation = handle.getLegacyPluginAnnotation();
- return hasDefinedPermissions(legacyAnnotation.permissions());
- } else {
- for (Permission perm : annotation.permissions()) {
- for (String permString : perm.strings()) {
- if (!PermissionHelper.hasDefinedPermission(getContext(), permString)) {
- return false;
- }
- }
- }
- }
-
- return true;
- }
-
- /**
- * Checks if the given permission alias is correctly declared in AndroidManifest.xml
- * @param alias a permission alias defined on the plugin
- * @return true only if all permissions associated with the given alias are declared in the manifest
- */
- public boolean isPermissionDeclared(String alias) {
- CapacitorPlugin annotation = handle.getPluginAnnotation();
- if (annotation != null) {
- for (Permission perm : annotation.permissions()) {
- if (alias.equalsIgnoreCase(perm.alias())) {
- boolean result = true;
- for (String permString : perm.strings()) {
- result = result && PermissionHelper.hasDefinedPermission(getContext(), permString);
- }
-
- return result;
- }
- }
- }
-
- Logger.error(String.format("isPermissionDeclared: No alias defined for %s " + "or missing @CapacitorPlugin annotation.", alias));
- return false;
- }
-
- /**
- * Check whether the given permission has been granted by the user
- * @deprecated use {@link #getPermissionState(String)} and {@link #getPermissionStates()} to get
- * the states of permissions defined on the Plugin in conjunction with the @CapacitorPlugin
- * annotation. Use the Android API {@link ActivityCompat#checkSelfPermission(Context, String)}
- * methods to check permissions with Android permission strings
- *
- * @param permission
- * @return
- */
- @Deprecated
- public boolean hasPermission(String permission) {
- return ActivityCompat.checkSelfPermission(this.getContext(), permission) == PackageManager.PERMISSION_GRANTED;
- }
-
- /**
- * If the plugin annotation specified a set of permissions, this method checks if each is
- * granted
- * @deprecated use {@link #getPermissionState(String)} or {@link #getPermissionStates()} to
- * check whether permissions are granted or not
- *
- * @return
- */
- @Deprecated
- public boolean hasRequiredPermissions() {
- CapacitorPlugin annotation = handle.getPluginAnnotation();
- if (annotation == null) {
- // Check for legacy plugin annotation, @NativePlugin
- NativePlugin legacyAnnotation = handle.getLegacyPluginAnnotation();
- for (String perm : legacyAnnotation.permissions()) {
- if (ActivityCompat.checkSelfPermission(this.getContext(), perm) != PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- }
-
- return true;
- }
-
- for (Permission perm : annotation.permissions()) {
- for (String permString : perm.strings()) {
- if (ActivityCompat.checkSelfPermission(this.getContext(), permString) != PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- /**
- * 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.
- *
- * @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();
- if (annotation != null) {
- HashSet perms = new HashSet<>();
- for (Permission perm : annotation.permissions()) {
- perms.addAll(Arrays.asList(perm.strings()));
- }
-
- 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 PermissionCallback} annotation.
- *
- * @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);
- }
-
- /**
- * 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.
- *
- * @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) {
- Logger.error("No permission alias was provided");
- return;
- }
-
- String[] permissions = getPermissionStringsForAliases(aliases);
-
- if (permissions.length > 0) {
- permissionActivityResult(call, permissions, callbackName);
- }
- }
-
- /**
- * Gets the Android permission strings defined on the {@link CapacitorPlugin} annotation with
- * the provided aliases.
- *
- * @param aliases aliases for permissions defined on the plugin
- * @return Android permission strings associated with the provided aliases, if exists
- */
- private String[] getPermissionStringsForAliases(@NonNull String[] aliases) {
- CapacitorPlugin annotation = handle.getPluginAnnotation();
- HashSet perms = new HashSet<>();
- for (Permission perm : annotation.permissions()) {
- if (Arrays.asList(aliases).contains(perm.alias())) {
- perms.addAll(Arrays.asList(perm.strings()));
- }
- }
-
- return perms.toArray(new String[0]);
- }
-
- /**
- * Gets the activity launcher associated with the calling methodName, or rejects the call if
- * no registered launcher exists
- *
- * @param call the plugin call
- * @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 activityLauncher = activityLaunchers.get(methodName);
-
- // if there is no registered launcher, reject the call with an error and return null
- if (activityLauncher == null) {
- String registerError =
- "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 activityLauncher;
- }
-
- /**
- * Gets the permission launcher associated with the calling methodName, or rejects the call if
- * no registered launcher exists
- *
- * @param call the plugin call
- * @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 permissionLauncher = permissionLaunchers.get(methodName);
-
- // if there is no registered launcher, reject the call with an error and return null
- if (permissionLauncher == null) {
- String registerError =
- "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 permissionLauncher;
- }
-
- /**
- * Request all of the specified permissions in the CapacitorPlugin annotation (if any)
- *
- * @deprecated use {@link #requestAllPermissions(PluginCall, String)} in conjunction with @CapacitorPlugin
- */
- @Deprecated
- public void pluginRequestAllPermissions() {
- NativePlugin legacyAnnotation = handle.getLegacyPluginAnnotation();
- ActivityCompat.requestPermissions(getActivity(), legacyAnnotation.permissions(), legacyAnnotation.permissionRequestCode());
- }
-
- /**
- * Helper for requesting a specific permission
- *
- * @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);
- }
-
- /**
- * Helper for requesting specific permissions
- * @deprecated use {@link #requestPermissionForAliases(String[], PluginCall, String)} 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
- public void pluginRequestPermissions(String[] permissions, int requestCode) {
- ActivityCompat.requestPermissions(getActivity(), permissions, requestCode);
- }
-
- /**
- * Get the permission state for the provided permission alias.
- *
- * @param alias the permission alias to get
- * @return the state of the provided permission alias or null
- */
- public PermissionState getPermissionState(String alias) {
- return getPermissionStates().get(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.
- */
- public Map getPermissionStates() {
- return bridge.getPermissionStates(this);
- }
-
- /**
- * Add a listener for the given event
- * @param eventName
- * @param call
- */
- private void addEventListener(String eventName, PluginCall call) {
- List listeners = eventListeners.get(eventName);
- if (listeners == null || listeners.isEmpty()) {
- listeners = new ArrayList<>();
- eventListeners.put(eventName, listeners);
-
- // Must add the call before sending retained arguments
- listeners.add(call);
-
- sendRetainedArgumentsForEvent(eventName);
- } else {
- listeners.add(call);
- }
- }
-
- /**
- * Remove a listener from the given event
- * @param eventName
- * @param call
- */
- private void removeEventListener(String eventName, PluginCall call) {
- List listeners = eventListeners.get(eventName);
- if (listeners == null) {
- return;
- }
-
- listeners.remove(call);
- }
-
- /**
- * Notify all listeners that an event occurred
- * @param eventName
- * @param data
- */
- protected void notifyListeners(String eventName, JSObject data, boolean retainUntilConsumed) {
- Logger.verbose(getLogTag(), "Notifying listeners for event " + eventName);
- List listeners = eventListeners.get(eventName);
- if (listeners == null || listeners.isEmpty()) {
- Logger.debug(getLogTag(), "No listeners found for event " + eventName);
- if (retainUntilConsumed) {
- List argList = retainedEventArguments.get(eventName);
-
- if (argList == null) {
- argList = new ArrayList();
- }
-
- argList.add(data);
- retainedEventArguments.put(eventName, argList);
- }
- return;
- }
-
- CopyOnWriteArrayList listenersCopy = new CopyOnWriteArrayList(listeners);
- for (PluginCall call : listenersCopy) {
- call.resolve(data);
- }
- }
-
- /**
- * Notify all listeners that an event occurred
- * This calls {@link Plugin#notifyListeners(String, JSObject, boolean)}
- * with retainUntilConsumed set to false
- * @param eventName
- * @param data
- */
- protected void notifyListeners(String eventName, JSObject data) {
- notifyListeners(eventName, data, false);
- }
-
- /**
- * Check if there are any listeners for the given event
- */
- protected boolean hasListeners(String eventName) {
- List listeners = eventListeners.get(eventName);
- if (listeners == null) {
- return false;
- }
- return !listeners.isEmpty();
- }
-
- /**
- * 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) {
- // copy retained args and null source to prevent potential race conditions
- List retainedArgs = retainedEventArguments.get(eventName);
- if (retainedArgs == null) {
- return;
- }
-
- retainedEventArguments.remove(eventName);
-
- for (JSObject retained : retainedArgs) {
- notifyListeners(eventName, retained);
- }
- }
-
- /**
- * Exported plugin call for adding a listener to this plugin
- * @param call
- */
- @SuppressWarnings("unused")
- @PluginMethod(returnType = PluginMethod.RETURN_NONE)
- public void addListener(PluginCall call) {
- String eventName = call.getString("eventName");
- call.setKeepAlive(true);
- addEventListener(eventName, call);
- }
-
- /**
- * Exported plugin call to remove a listener from this plugin
- * @param call
- */
- @SuppressWarnings("unused")
- @PluginMethod(returnType = PluginMethod.RETURN_NONE)
- public void removeListener(PluginCall call) {
- String eventName = call.getString("eventName");
- String callbackId = call.getString("callbackId");
- PluginCall savedCall = bridge.getSavedCall(callbackId);
- if (savedCall != null) {
- removeEventListener(eventName, savedCall);
- bridge.releaseCall(savedCall);
- }
- }
-
- /**
- * Exported plugin call to remove all listeners from this plugin
- * @param call
- */
- @SuppressWarnings("unused")
- @PluginMethod(returnType = PluginMethod.RETURN_PROMISE)
- public void removeAllListeners(PluginCall call) {
- eventListeners.clear();
- call.resolve();
- }
-
- /**
- * Exported plugin call for checking the granted status for each permission
- * declared on the plugin. This plugin call responds with a mapping of permissions to
- * the associated granted status.
- *
- * @since 3.0.0
- */
- @PluginMethod
- @PermissionCallback
- public void checkPermissions(PluginCall pluginCall) {
- Map permissionsResult = getPermissionStates();
-
- if (permissionsResult.size() == 0) {
- // if no permissions are defined on the plugin, resolve undefined
- pluginCall.resolve();
- } else {
- JSObject permissionsResultJSON = new JSObject();
- for (Map.Entry entry : permissionsResult.entrySet()) {
- permissionsResultJSON.put(entry.getKey(), entry.getValue());
- }
-
- pluginCall.resolve(permissionsResultJSON);
- }
- }
-
- /**
- * Exported plugin call to request all permissions for this plugin.
- * To manually request permissions within a plugin use:
- * {@link #requestAllPermissions(PluginCall, String)}, or
- * {@link #requestPermissionForAlias(String, PluginCall, String)}, or
- * {@link #requestPermissionForAliases(String[], PluginCall, String)}
- *
- * @param call the plugin call
- */
- @PluginMethod
- public void requestPermissions(PluginCall call) {
- CapacitorPlugin annotation = handle.getPluginAnnotation();
- if (annotation == null) {
- handleLegacyPermission(call);
- } else {
- // handle permission requests for plugins defined with @CapacitorPlugin (since 3.0.0)
- String[] permAliases = null;
- Set autoGrantPerms = new HashSet<>();
-
- // If call was made with a list of specific permission aliases to request, save them
- // to be requested
- JSArray providedPerms = call.getArray("permissions");
- List providedPermsList = null;
-
- if (providedPerms != null) {
- try {
- providedPermsList = providedPerms.toList();
- } catch (JSONException ignore) {
- // do nothing
- }
- }
-
- // If call was made without any custom permissions, request all from plugin annotation
- Set aliasSet = new HashSet<>();
- if (providedPermsList == null || providedPermsList.isEmpty()) {
- for (Permission perm : annotation.permissions()) {
- // If a permission is defined with no permission strings, separate it for auto-granting.
- // Otherwise, the alias is added to the list to be requested.
- if (perm.strings().length == 0 || (perm.strings().length == 1 && perm.strings()[0].isEmpty())) {
- if (!perm.alias().isEmpty()) {
- autoGrantPerms.add(perm.alias());
- }
- } else {
- aliasSet.add(perm.alias());
- }
- }
-
- permAliases = aliasSet.toArray(new String[0]);
- } else {
- for (Permission perm : annotation.permissions()) {
- if (providedPermsList.contains(perm.alias())) {
- aliasSet.add(perm.alias());
- }
- }
-
- if (aliasSet.isEmpty()) {
- call.reject("No valid permission alias was requested of this plugin.");
- } else {
- permAliases = aliasSet.toArray(new String[0]);
- }
- }
-
- if (permAliases != null && permAliases.length > 0) {
- // request permissions using provided aliases or all defined on the plugin
- requestPermissionForAliases(permAliases, call, "checkPermissions");
- } else if (!autoGrantPerms.isEmpty()) {
- // if the plugin only has auto-grant permissions, return all as GRANTED
- JSObject permissionsResults = new JSObject();
-
- for (String perm : autoGrantPerms) {
- permissionsResults.put(perm, PermissionState.GRANTED.toString());
- }
-
- call.resolve(permissionsResults);
- } else {
- // no permissions are defined on the plugin, resolve undefined
- call.resolve();
- }
- }
- }
-
- @SuppressWarnings("deprecation")
- private void handleLegacyPermission(PluginCall call) {
- // handle permission requests for plugins defined with @NativePlugin (prior to 3.0.0)
- NativePlugin legacyAnnotation = this.handle.getLegacyPluginAnnotation();
- String[] perms = legacyAnnotation.permissions();
- if (perms.length > 0) {
- saveCall(call);
- pluginRequestPermissions(perms, legacyAnnotation.permissionRequestCode());
- } else {
- call.resolve();
- }
- }
-
- /**
- * 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
- protected void handleRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- if (!hasDefinedPermissions(permissions)) {
- StringBuilder builder = new StringBuilder();
- builder.append("Missing the following permissions in AndroidManifest.xml:\n");
- String[] missing = PermissionHelper.getUndefinedPermissions(getContext(), permissions);
- for (String perm : missing) {
- builder.append(perm + "\n");
- }
- savedLastCall.reject(builder.toString());
- savedLastCall = null;
- }
- }
-
- /**
- * Called before the app is destroyed to give a plugin the chance to
- * save the last call options for a saved plugin. By default, this
- * method saves the full JSON blob of the options call. Since Bundle sizes
- * 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() {
- PluginCall savedCall = bridge.getSavedCall(lastPluginCallId);
-
- if (savedCall == null) {
- return null;
- }
-
- Bundle ret = new Bundle();
- JSObject callData = savedCall.getData();
-
- if (callData != null) {
- ret.putString(BUNDLE_PERSISTED_OPTIONS_JSON_KEY, callData.toString());
- }
-
- return ret;
- }
-
- /**
- * Called when the app is opened with a previously un-handled
- * 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) {}
-
- /**
- * 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
- protected void handleOnActivityResult(int requestCode, int resultCode, Intent data) {}
-
- /**
- * Handle onNewIntent
- * @param intent
- */
- protected void handleOnNewIntent(Intent intent) {}
-
- /**
- * Handle onConfigurationChanged
- * @param newConfig
- */
- protected void handleOnConfigurationChanged(Configuration newConfig) {}
-
- /**
- * Handle onStart
- */
- protected void handleOnStart() {}
-
- /**
- * Handle onRestart
- */
- protected void handleOnRestart() {}
-
- /**
- * Handle onResume
- */
- protected void handleOnResume() {}
-
- /**
- * Handle onPause
- */
- protected void handleOnPause() {}
-
- /**
- * Handle onStop
- */
- protected void handleOnStop() {}
-
- /**
- * Handle onDestroy
- */
- protected void handleOnDestroy() {}
-
- /**
- * Give the plugins a chance to take control when a URL is about to be loaded in the WebView.
- * Returning true causes the WebView to abort loading the URL.
- * Returning false causes the WebView to continue loading the URL.
- * Returning null will defer to the default Capacitor policy
- */
- @SuppressWarnings("unused")
- public Boolean shouldOverrideLoad(Uri url) {
- return null;
- }
-
- /**
- * 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) {
- bridge.execute(runnable);
- }
-
- /**
- * Shortcut for getting the plugin log tag
- * @param subTags
- */
- protected String getLogTag(String... subTags) {
- return Logger.tags(subTags);
- }
-
- /**
- * Gets a plugin log tag with the child's class name as subTag.
- */
- protected String getLogTag() {
- return Logger.tags(this.getClass().getSimpleName());
- }
-}
diff --git a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Plugin.kt b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Plugin.kt
new file mode 100644
index 00000000..6addf332
--- /dev/null
+++ b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Plugin.kt
@@ -0,0 +1,1077 @@
+package com.getcapacitor
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+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.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
+import com.getcapacitor.annotation.ActivityCallback
+import com.getcapacitor.annotation.CapacitorPlugin
+import com.getcapacitor.annotation.PermissionCallback
+import com.getcapacitor.util.PermissionHelper
+import org.json.JSONException
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+import java.util.Arrays
+import java.util.Locale
+import java.util.concurrent.CopyOnWriteArrayList
+
+/**
+ * Plugin is the base class for all plugins, containing a number of
+ * convenient features for interacting with the [Bridge], managing
+ * plugin permissions, tracking lifecycle events, and more.
+ *
+ * You should inherit from this class when creating new plugins, along with
+ * adding the [CapacitorPlugin] annotation to add additional required
+ * metadata about the Plugin
+ */
+open class Plugin {
+ /**
+ * Get the Bridge instance for this plugin
+ */
+ /**
+ * Set the Bridge instance for this plugin
+ * @param bridge
+ */
+ // Reference to the Bridge
+ @JvmField
+ var bridge: Bridge? = null
+
+ /**
+ * Return the wrapper [PluginHandle] for this plugin.
+ *
+ * This wrapper contains additional metadata about the plugin instance,
+ * such as indexed methods for reflection, and [CapacitorPlugin] annotation data).
+ * @return
+ */
+ /**
+ * Set the wrapper [PluginHandle] instance for this plugin that
+ * contains additional metadata about the Plugin instance (such
+ * as indexed methods for reflection, and [CapacitorPlugin] annotation data).
+ * @param pluginHandle
+ */
+ // Reference to the PluginHandle wrapper for this Plugin
+ var pluginHandle: PluginHandle? = null
+
+ /**
+ * Get the last saved call, if any
+ * @return
+ */
+ /**
+ * A way for plugins to quickly save a call that they will need to reference
+ * between activity/permissions starts/requests
+ *
+ */
+ @get:Deprecated(
+ """use {@link Bridge#getSavedCall(String)}
+
+ """
+ )
+ @Deprecated(
+ """store calls on the bridge using the methods
+ {@link com.getcapacitor.Bridge#saveCall(PluginCall)},
+ {@link com.getcapacitor.Bridge#getSavedCall(String)} and
+ {@link com.getcapacitor.Bridge#releaseCall(PluginCall)}"""
+ )
+ var savedCall: PluginCall? = null
+ /**
+ * Get the last saved call, if any
+ * @return
+ */
+ @Deprecated(
+ """use {@link Bridge#getSavedCall(String)}
+
+ """
+ ) get
+ protected set
+
+ // Stored event listeners
+ private val eventListeners: MutableMap> =
+ HashMap()
+
+ /**
+ * Launchers used by the plugin to handle activity results
+ */
+ private val activityLaunchers: MutableMap> = HashMap()
+
+ /**
+ * Launchers used by the plugin to handle permission results
+ */
+ private val permissionLaunchers: MutableMap>> =
+ HashMap()
+
+ private var lastPluginCallId: String? = null
+
+ // Stored results of an event if an event was fired and
+ // no listeners were attached yet. Only stores the last value.
+ private val retainedEventArguments: MutableMap> =
+ HashMap()
+
+ /**
+ * Called when the plugin has been connected to the bridge
+ * and is ready to start initializing.
+ */
+ open fun load() {}
+
+ /**
+ * Registers activity result launchers defined on plugins, used for permission requests and
+ * activities started for result.
+ */
+ fun initializeActivityLaunchers() {
+ val pluginClassMethods: MutableList = ArrayList()
+ var pluginCursor: Class<*> = javaClass
+ while (pluginCursor.name != Any::class.java.name
+ ) {
+ pluginClassMethods.addAll(Arrays.asList(*pluginCursor.declaredMethods))
+ pluginCursor = pluginCursor.superclass
+ }
+
+ for (method in pluginClassMethods) {
+ if (method.isAnnotationPresent(ActivityCallback::class.java)) {
+ // register callbacks annotated with ActivityCallback for activity results
+ val launcher = bridge!!.registerForActivityResult(
+ StartActivityForResult()
+ ) { result: ActivityResult -> triggerActivityCallback(method, result) }
+
+ activityLaunchers[method.name] = launcher
+ } else if (method.isAnnotationPresent(PermissionCallback::class.java)) {
+ // register callbacks annotated with PermissionCallback for permission results
+ val launcher =
+ bridge!!.registerForActivityResult, Map>(
+ RequestMultiplePermissions()
+ ) { permissions: Map ->
+ triggerPermissionCallback(
+ method,
+ permissions
+ )
+ }
+
+ permissionLaunchers[method.name] = launcher
+ }
+ }
+ }
+
+ private fun triggerPermissionCallback(
+ method: Method,
+ permissionResultMap: Map
+ ) {
+ val savedCall = bridge!!.getPermissionCall(pluginHandle.getId())
+
+ // validate permissions and invoke the permission result callback
+ if (bridge!!.validatePermissions(this, savedCall!!, permissionResultMap)) {
+ try {
+ method.isAccessible = true
+ method.invoke(this, savedCall)
+ } catch (e: IllegalAccessException) {
+ e.printStackTrace()
+ } catch (e: InvocationTargetException) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ private fun triggerActivityCallback(method: Method, result: ActivityResult) {
+ var savedCall = bridge!!.getSavedCall(lastPluginCallId)
+ if (savedCall == null) {
+ savedCall = bridge!!.getPluginCallForLastActivity()
+ }
+ // invoke the activity result callback
+ try {
+ method.isAccessible = true
+ method.invoke(this, savedCall, result)
+ } catch (e: IllegalAccessException) {
+ e.printStackTrace()
+ } catch (e: InvocationTargetException) {
+ e.printStackTrace()
+ }
+ }
+
+ /**
+ * 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
+ * [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
+ */
+ fun startActivityForResult(call: PluginCall, intent: Intent, callbackName: String) {
+ val activityResultLauncher = getActivityLauncherOrReject(call, callbackName)
+ ?: // return when null since call was rejected in getLauncherOrReject
+ return
+ bridge!!.setPluginCallForLastActivity(call)
+ lastPluginCallId = call.callbackId
+ bridge!!.saveCall(call)
+ activityResultLauncher.launch(intent)
+ }
+
+ private fun permissionActivityResult(
+ call: PluginCall,
+ permissionStrings: Array,
+ callbackName: String
+ ) {
+ val permissionResultLauncher = getPermissionLauncherOrReject(call, callbackName)
+ ?: // return when null since call was rejected in getLauncherOrReject
+ return
+
+ bridge!!.savePermissionCall(call)
+ permissionResultLauncher.launch(permissionStrings)
+ }
+
+ val context: Context?
+ /**
+ * Get the main [Context] for the current Activity (your app)
+ * @return the Context for the current activity
+ */
+ get() = bridge!!.getContext()
+
+ val activity: AppCompatActivity?
+ /**
+ * Get the main [Activity] for the app
+ * @return the Activity for the current app
+ */
+ get() = bridge!!.activity
+
+ val appId: String
+ /**
+ * Get the root App ID
+ * @return
+ */
+ get() = context!!.packageName
+
+ /**
+ * Called to save a [PluginCall] in order to reference it
+ * later, such as in an activity or permissions result handler
+ * @param lastCall
+ */
+ @Deprecated(
+ """use {@link Bridge#saveCall(PluginCall)}
+
+ """
+ )
+ fun saveCall(lastCall: PluginCall?) {
+ this.savedCall = lastCall
+ }
+
+ /**
+ * Set the last saved call to null to free memory
+ */
+ @Deprecated("use {@link PluginCall#release(Bridge)}")
+ fun freeSavedCall() {
+ savedCall!!.release(bridge)
+ this.savedCall = null
+ }
+
+ val config: PluginConfig?
+ /**
+ * Get the config options for this plugin.
+ *
+ * @return a config object representing the plugin config options, or an empty config
+ * if none exists
+ */
+ get() = bridge!!.config.getPluginConfiguration(pluginHandle.getId())
+
+ /**
+ * Get the value for a key on the config for this plugin.
+ * @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.
+
+ """
+ )
+ fun getConfigValue(key: String?): Any? {
+ try {
+ val pluginConfig = config
+ return pluginConfig.getConfigJSON()[key]
+ } catch (ex: JSONException) {
+ return null
+ }
+ }
+
+ /**
+ * Check whether any of the given permissions has been defined in the AndroidManifest.xml
+ * @param permissions
+ * @return
+ */
+ @Deprecated(
+ """use {@link #isPermissionDeclared(String)}
+
+ """
+ )
+ fun hasDefinedPermissions(permissions: Array): Boolean {
+ for (permission in permissions) {
+ if (!PermissionHelper.hasDefinedPermission(context, permission)) {
+ return false
+ }
+ }
+ return true
+ }
+
+ /**
+ * Check if all annotated permissions have been defined in the AndroidManifest.xml
+ * @return true if permissions are all defined in the Manifest
+ */
+ @Deprecated(
+ """use {@link #isPermissionDeclared(String)}
+
+ """
+ )
+ fun hasDefinedRequiredPermissions(): Boolean {
+ val annotation = pluginHandle.getPluginAnnotation()
+ if (annotation == null) {
+ // Check for legacy plugin annotation, @NativePlugin
+ val legacyAnnotation = pluginHandle.getLegacyPluginAnnotation()
+ return hasDefinedPermissions(legacyAnnotation!!.permissions)
+ } else {
+ for (perm in annotation.permissions) {
+ for (permString in perm.strings) {
+ if (!PermissionHelper.hasDefinedPermission(context, permString)) {
+ return false
+ }
+ }
+ }
+ }
+
+ return true
+ }
+
+ /**
+ * Checks if the given permission alias is correctly declared in AndroidManifest.xml
+ * @param alias a permission alias defined on the plugin
+ * @return true only if all permissions associated with the given alias are declared in the manifest
+ */
+ fun isPermissionDeclared(alias: String): Boolean {
+ val annotation = pluginHandle.getPluginAnnotation()
+ if (annotation != null) {
+ for (perm in annotation.permissions) {
+ if (alias.equals(perm.alias, ignoreCase = true)) {
+ var result = true
+ for (permString in perm.strings) {
+ result = result && PermissionHelper.hasDefinedPermission(
+ context, permString
+ )
+ }
+
+ return result
+ }
+ }
+ }
+
+ Logger.Companion.error(
+ String.format(
+ "isPermissionDeclared: No alias defined for %s " + "or missing @CapacitorPlugin annotation.",
+ alias
+ )
+ )
+ return false
+ }
+
+ /**
+ * Check whether the given permission has been granted by the user
+ * @param permission
+ * @return
+ */
+ @Deprecated(
+ """use {@link #getPermissionState(String)} and {@link #getPermissionStates()} to get
+ the states of permissions defined on the Plugin in conjunction with the @CapacitorPlugin
+ annotation. Use the Android API {@link ActivityCompat#checkSelfPermission(Context, String)}
+ methods to check permissions with Android permission strings
+
+ """
+ )
+ fun hasPermission(permission: String?): Boolean {
+ return ActivityCompat.checkSelfPermission(
+ context!!,
+ permission!!
+ ) == PackageManager.PERMISSION_GRANTED
+ }
+
+ /**
+ * If the plugin annotation specified a set of permissions, this method checks if each is
+ * granted
+ * @return
+ */
+ @Deprecated(
+ """use {@link #getPermissionState(String)} or {@link #getPermissionStates()} to
+ check whether permissions are granted or not
+
+ """
+ )
+ fun hasRequiredPermissions(): Boolean {
+ val annotation = pluginHandle.getPluginAnnotation()
+ if (annotation == null) {
+ // Check for legacy plugin annotation, @NativePlugin
+ val legacyAnnotation = pluginHandle.getLegacyPluginAnnotation()
+ for (perm in legacyAnnotation!!.permissions) {
+ if (ActivityCompat.checkSelfPermission(
+ context!!,
+ perm!!
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ return false
+ }
+ }
+
+ return true
+ }
+
+ for (perm in annotation.permissions) {
+ for (permString in perm.strings) {
+ if (ActivityCompat.checkSelfPermission(
+ context!!,
+ permString!!
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ return false
+ }
+ }
+ }
+
+ return true
+ }
+
+ /**
+ * 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
+ * [PermissionCallback] annotation.
+ *
+ * @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 fun requestAllPermissions(call: PluginCall, callbackName: String) {
+ val annotation = pluginHandle.getPluginAnnotation()
+ if (annotation != null) {
+ val perms = HashSet()
+ for (perm in annotation.permissions) {
+ perms.addAll(Arrays.asList(*perm.strings))
+ }
+
+ permissionActivityResult(call, perms.toTypedArray(), 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
+ * [PermissionCallback] annotation.
+ *
+ * @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 fun requestPermissionForAlias(alias: String, call: PluginCall, callbackName: String) {
+ requestPermissionForAliases(arrayOf(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
+ * [PermissionCallback] annotation.
+ *
+ * @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 fun requestPermissionForAliases(
+ aliases: Array,
+ call: PluginCall,
+ callbackName: String
+ ) {
+ if (aliases.size == 0) {
+ Logger.Companion.error("No permission alias was provided")
+ return
+ }
+
+ val permissions = getPermissionStringsForAliases(aliases)
+
+ if (permissions.size > 0) {
+ permissionActivityResult(call, permissions, callbackName)
+ }
+ }
+
+ /**
+ * Gets the Android permission strings defined on the [CapacitorPlugin] annotation with
+ * the provided aliases.
+ *
+ * @param aliases aliases for permissions defined on the plugin
+ * @return Android permission strings associated with the provided aliases, if exists
+ */
+ private fun getPermissionStringsForAliases(aliases: Array): Array {
+ val annotation = pluginHandle.getPluginAnnotation()
+ val perms = HashSet()
+ for (perm in annotation!!.permissions) {
+ if (Arrays.asList(*aliases).contains(perm.alias)) {
+ perms.addAll(Arrays.asList(*perm.strings))
+ }
+ }
+
+ return perms.toTypedArray()
+ }
+
+ /**
+ * Gets the activity launcher associated with the calling methodName, or rejects the call if
+ * no registered launcher exists
+ *
+ * @param call the plugin call
+ * @param methodName the name of the activity callback method
+ * @return a launcher, or null if none found
+ */
+ private fun getActivityLauncherOrReject(
+ call: PluginCall,
+ methodName: String
+ ): ActivityResultLauncher? {
+ val activityLauncher = activityLaunchers[methodName]
+
+ // if there is no registered launcher, reject the call with an error and return null
+ if (activityLauncher == null) {
+ var registerError =
+ "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.Companion.error(registerError)
+ call.reject(registerError)
+ return null
+ }
+
+ return activityLauncher
+ }
+
+ /**
+ * Gets the permission launcher associated with the calling methodName, or rejects the call if
+ * no registered launcher exists
+ *
+ * @param call the plugin call
+ * @param methodName the name of the permission callback method
+ * @return a launcher, or null if none found
+ */
+ private fun getPermissionLauncherOrReject(
+ call: PluginCall,
+ methodName: String
+ ): ActivityResultLauncher>? {
+ val permissionLauncher = permissionLaunchers[methodName]
+
+ // if there is no registered launcher, reject the call with an error and return null
+ if (permissionLauncher == null) {
+ var registerError =
+ "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.Companion.error(registerError)
+ call.reject(registerError)
+ return null
+ }
+
+ return permissionLauncher
+ }
+
+ /**
+ * Request all of the specified permissions in the CapacitorPlugin annotation (if any)
+ *
+ */
+ @Deprecated("use {@link #requestAllPermissions(PluginCall, String)} in conjunction with @CapacitorPlugin")
+ fun pluginRequestAllPermissions() {
+ val legacyAnnotation = pluginHandle.getLegacyPluginAnnotation()
+ ActivityCompat.requestPermissions(
+ activity!!,
+ legacyAnnotation!!.permissions,
+ legacyAnnotation!!.permissionRequestCode
+ )
+ }
+
+ /**
+ * Helper for requesting a specific permission
+ *
+ * @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")
+ fun pluginRequestPermission(permission: String, requestCode: Int) {
+ ActivityCompat.requestPermissions(activity!!, arrayOf(permission), requestCode)
+ }
+
+ /**
+ * Helper for requesting specific permissions
+ * @param permissions the set of permissions to request
+ * @param requestCode the requestCode to use to associate the result with the plugin
+ */
+ @Deprecated(
+ """use {@link #requestPermissionForAliases(String[], PluginCall, String)} in conjunction
+ with @CapacitorPlugin
+
+ """
+ )
+ fun pluginRequestPermissions(permissions: Array?, requestCode: Int) {
+ ActivityCompat.requestPermissions(activity!!, permissions!!, requestCode)
+ }
+
+ /**
+ * Get the permission state for the provided permission alias.
+ *
+ * @param alias the permission alias to get
+ * @return the state of the provided permission alias or null
+ */
+ fun getPermissionState(alias: String): PermissionState? {
+ return permissionStates[alias]
+ }
+
+ val permissionStates: Map
+ /**
+ * 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.
+ */
+ get() = bridge!!.getPermissionStates(this)
+
+ /**
+ * Add a listener for the given event
+ * @param eventName
+ * @param call
+ */
+ private fun addEventListener(eventName: String?, call: PluginCall) {
+ var listeners = eventListeners[eventName]
+ if (listeners == null || listeners.isEmpty()) {
+ listeners = ArrayList()
+ eventListeners[eventName] = listeners
+
+ // Must add the call before sending retained arguments
+ listeners.add(call)
+
+ sendRetainedArgumentsForEvent(eventName)
+ } else {
+ listeners.add(call)
+ }
+ }
+
+ /**
+ * Remove a listener from the given event
+ * @param eventName
+ * @param call
+ */
+ private fun removeEventListener(eventName: String?, call: PluginCall) {
+ val listeners = eventListeners[eventName] ?: return
+
+ listeners.remove(call)
+ }
+
+ /**
+ * Notify all listeners that an event occurred
+ * @param eventName
+ * @param data
+ */
+ /**
+ * Notify all listeners that an event occurred
+ * This calls [Plugin.notifyListeners]
+ * with retainUntilConsumed set to false
+ * @param eventName
+ * @param data
+ */
+ protected fun notifyListeners(
+ eventName: String?,
+ data: JSObject,
+ retainUntilConsumed: Boolean = false
+ ) {
+ Logger.Companion.verbose(logTag, "Notifying listeners for event $eventName")
+ val listeners: List? = eventListeners[eventName]
+ if (listeners == null || listeners.isEmpty()) {
+ Logger.Companion.debug(logTag, "No listeners found for event $eventName")
+ if (retainUntilConsumed) {
+ var argList = retainedEventArguments[eventName]
+
+ if (argList == null) {
+ argList = ArrayList()
+ }
+
+ argList.add(data)
+ retainedEventArguments[eventName] = argList
+ }
+ return
+ }
+
+ val listenersCopy: CopyOnWriteArrayList = CopyOnWriteArrayList(listeners)
+ for (call in listenersCopy) {
+ call!!.resolve(data)
+ }
+ }
+
+ /**
+ * Check if there are any listeners for the given event
+ */
+ protected fun hasListeners(eventName: String?): Boolean {
+ val listeners = eventListeners[eventName]
+ ?: return false
+ return !listeners.isEmpty()
+ }
+
+ /**
+ * Send retained arguments (if any) for this event. This
+ * is called only when the first listener for an event is added
+ * @param eventName
+ */
+ private fun sendRetainedArgumentsForEvent(eventName: String?) {
+ // copy retained args and null source to prevent potential race conditions
+ val retainedArgs = retainedEventArguments[eventName]
+ ?: return
+
+ retainedEventArguments.remove(eventName)
+
+ for (retained in retainedArgs) {
+ notifyListeners(eventName, retained)
+ }
+ }
+
+ /**
+ * Exported plugin call for adding a listener to this plugin
+ * @param call
+ */
+ @Suppress("unused")
+ @PluginMethod(returnType = PluginMethod.Companion.RETURN_NONE)
+ fun addListener(call: PluginCall) {
+ val eventName = call.getString("eventName")
+ call.setKeepAlive(true)
+ addEventListener(eventName, call)
+ }
+
+ /**
+ * Exported plugin call to remove a listener from this plugin
+ * @param call
+ */
+ @Suppress("unused")
+ @PluginMethod(returnType = PluginMethod.Companion.RETURN_NONE)
+ fun removeListener(call: PluginCall) {
+ val eventName = call.getString("eventName")
+ val callbackId = call.getString("callbackId")
+ val savedCall = bridge!!.getSavedCall(callbackId)
+ if (savedCall != null) {
+ removeEventListener(eventName, savedCall)
+ bridge!!.releaseCall(savedCall)
+ }
+ }
+
+ /**
+ * Exported plugin call to remove all listeners from this plugin
+ * @param call
+ */
+ @Suppress("unused")
+ @PluginMethod(returnType = PluginMethod.Companion.RETURN_PROMISE)
+ fun removeAllListeners(call: PluginCall) {
+ eventListeners.clear()
+ call.resolve()
+ }
+
+ /**
+ * Exported plugin call for checking the granted status for each permission
+ * declared on the plugin. This plugin call responds with a mapping of permissions to
+ * the associated granted status.
+ *
+ * @since 3.0.0
+ */
+ @PluginMethod
+ @PermissionCallback
+ open fun checkPermissions(pluginCall: PluginCall) {
+ val permissionsResult = permissionStates
+
+ if (permissionsResult.size == 0) {
+ // if no permissions are defined on the plugin, resolve undefined
+ pluginCall.resolve()
+ } else {
+ val permissionsResultJSON = JSObject()
+ for ((key, value) in permissionsResult) {
+ permissionsResultJSON.put(key, value)
+ }
+
+ pluginCall.resolve(permissionsResultJSON)
+ }
+ }
+
+ /**
+ * Exported plugin call to request all permissions for this plugin.
+ * To manually request permissions within a plugin use:
+ * [.requestAllPermissions], or
+ * [.requestPermissionForAlias], or
+ * [.requestPermissionForAliases]
+ *
+ * @param call the plugin call
+ */
+ @PluginMethod
+ open fun requestPermissions(call: PluginCall) {
+ val annotation = pluginHandle.getPluginAnnotation()
+ if (annotation == null) {
+ handleLegacyPermission(call)
+ } else {
+ // handle permission requests for plugins defined with @CapacitorPlugin (since 3.0.0)
+ var permAliases: Array? = null
+ val autoGrantPerms: MutableSet = HashSet()
+
+ // If call was made with a list of specific permission aliases to request, save them
+ // to be requested
+ val providedPerms = call.getArray("permissions")
+ var providedPermsList: List? = null
+
+ if (providedPerms != null) {
+ try {
+ providedPermsList = providedPerms.toList()
+ } catch (ignore: JSONException) {
+ // do nothing
+ }
+ }
+
+ // If call was made without any custom permissions, request all from plugin annotation
+ val aliasSet: MutableSet = HashSet()
+ if (providedPermsList == null || providedPermsList.isEmpty()) {
+ for (perm in annotation.permissions) {
+ // If a permission is defined with no permission strings, separate it for auto-granting.
+ // Otherwise, the alias is added to the list to be requested.
+ if (perm.strings.size == 0 || (perm.strings.size == 1 && perm.strings[0].isEmpty())) {
+ if (!perm.alias.isEmpty()) {
+ autoGrantPerms.add(perm.alias)
+ }
+ } else {
+ aliasSet.add(perm.alias)
+ }
+ }
+
+ permAliases = aliasSet.toTypedArray()
+ } else {
+ for (perm in annotation.permissions) {
+ if (providedPermsList.contains(perm.alias)) {
+ aliasSet.add(perm.alias)
+ }
+ }
+
+ if (aliasSet.isEmpty()) {
+ call.reject("No valid permission alias was requested of this plugin.")
+ } else {
+ permAliases = aliasSet.toTypedArray()
+ }
+ }
+
+ if (permAliases != null && permAliases.size > 0) {
+ // request permissions using provided aliases or all defined on the plugin
+ requestPermissionForAliases(permAliases, call, "checkPermissions")
+ } else if (!autoGrantPerms.isEmpty()) {
+ // if the plugin only has auto-grant permissions, return all as GRANTED
+ val permissionsResults = JSObject()
+
+ for (perm in autoGrantPerms) {
+ permissionsResults.put(perm, PermissionState.GRANTED.toString())
+ }
+
+ call.resolve(permissionsResults)
+ } else {
+ // no permissions are defined on the plugin, resolve undefined
+ call.resolve()
+ }
+ }
+ }
+
+ @Suppress("deprecation")
+ private fun handleLegacyPermission(call: PluginCall) {
+ // handle permission requests for plugins defined with @NativePlugin (prior to 3.0.0)
+ val legacyAnnotation = pluginHandle.getLegacyPluginAnnotation()
+ val perms = legacyAnnotation!!.permissions
+ if (perms.size > 0) {
+ saveCall(call)
+ pluginRequestPermissions(perms, legacyAnnotation!!.permissionRequestCode)
+ } else {
+ call.resolve()
+ }
+ }
+
+ /**
+ * Handle request permissions result. A plugin using the deprecated [NativePlugin]
+ * should override this to handle the result, or this method will handle the result
+ * for our convenient requestPermissions call.
+ * @param requestCode
+ * @param permissions
+ * @param grantResults
+ */
+ @Deprecated(
+ """in favor of using callbacks in conjunction with {@link CapacitorPlugin}
+
+ """
+ )
+ protected fun handleRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray?
+ ) {
+ if (!hasDefinedPermissions(permissions)) {
+ val builder = StringBuilder()
+ builder.append("Missing the following permissions in AndroidManifest.xml:\n")
+ val missing = PermissionHelper.getUndefinedPermissions(
+ context, permissions
+ )
+ for (perm in missing) {
+ builder.append(perm + "\n")
+ }
+ savedCall!!.reject(builder.toString())
+ savedCall = null
+ }
+ }
+
+ /**
+ * Called before the app is destroyed to give a plugin the chance to
+ * save the last call options for a saved plugin. By default, this
+ * method saves the full JSON blob of the options call. Since Bundle sizes
+ * 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 [Bundle] to avoid exceeding limits.
+ * @return a new [Bundle] with fields set from the options of the last saved [PluginCall]
+ */
+ protected fun saveInstanceState(): Bundle? {
+ val savedCall = bridge!!.getSavedCall(lastPluginCallId) ?: return null
+
+ val ret = Bundle()
+ val callData = savedCall.data
+
+ if (callData != null) {
+ ret.putString(BUNDLE_PERSISTED_OPTIONS_JSON_KEY, callData.toString())
+ }
+
+ return ret
+ }
+
+ /**
+ * Called when the app is opened with a previously un-handled
+ * activity response. If the plugin that started the activity
+ * stored data in [Plugin.saveInstanceState] then this
+ * method will be called to allow the plugin to restore from that.
+ * @param state
+ */
+ protected fun restoreState(state: Bundle?) {}
+
+ /**
+ * Handle activity result, should be overridden by each plugin
+ *
+ * @param requestCode
+ * @param resultCode
+ * @param data
+ */
+ @Deprecated(
+ """provide a callback method using the {@link ActivityCallback} annotation and use
+ the {@link #startActivityForResult(PluginCall, Intent, String)} method
+
+ """
+ )
+ protected fun handleOnActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ }
+
+ /**
+ * Handle onNewIntent
+ * @param intent
+ */
+ protected open fun handleOnNewIntent(intent: Intent?) {}
+
+ /**
+ * Handle onConfigurationChanged
+ * @param newConfig
+ */
+ protected fun handleOnConfigurationChanged(newConfig: Configuration?) {}
+
+ /**
+ * Handle onStart
+ */
+ protected fun handleOnStart() {}
+
+ /**
+ * Handle onRestart
+ */
+ protected fun handleOnRestart() {}
+
+ /**
+ * Handle onResume
+ */
+ protected open fun handleOnResume() {}
+
+ /**
+ * Handle onPause
+ */
+ protected open fun handleOnPause() {}
+
+ /**
+ * Handle onStop
+ */
+ protected open fun handleOnStop() {}
+
+ /**
+ * Handle onDestroy
+ */
+ protected open fun handleOnDestroy() {}
+
+ /**
+ * Give the plugins a chance to take control when a URL is about to be loaded in the WebView.
+ * Returning true causes the WebView to abort loading the URL.
+ * Returning false causes the WebView to continue loading the URL.
+ * Returning null will defer to the default Capacitor policy
+ */
+ @Suppress("unused")
+ fun shouldOverrideLoad(url: Uri?): Boolean? {
+ return null
+ }
+
+ /**
+ * Start a new Activity.
+ *
+ * Note: This method must be used by all plugins instead of calling
+ * [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 fun startActivityForResult(call: PluginCall?, intent: Intent?, resultCode: Int) {
+ bridge!!.startActivityForPluginWithResult(call, intent, resultCode)
+ }
+
+ /**
+ * Execute the given runnable on the Bridge's task handler
+ * @param runnable
+ */
+ fun execute(runnable: Runnable?) {
+ bridge!!.execute(runnable)
+ }
+
+ /**
+ * Shortcut for getting the plugin log tag
+ * @param subTags
+ */
+ protected fun getLogTag(vararg subTags: String?): String {
+ return Logger.Companion.tags(*subTags)
+ }
+
+ protected val logTag: String
+ /**
+ * Gets a plugin log tag with the child's class name as subTag.
+ */
+ get() = Logger.Companion.tags(this.javaClass.simpleName)
+
+ companion object {
+ // The key we will use inside of a persisted Bundle for the JSON blob
+ // for a plugin call options.
+ private const val BUNDLE_PERSISTED_OPTIONS_JSON_KEY = "_json"
+ }
+}
diff --git a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/PluginCall.java b/@capacitor/android/capacitor/src/main/java/com/getcapacitor/PluginCall.java
deleted file mode 100644
index 18661d76..00000000
--- a/@capacitor/android/capacitor/src/main/java/com/getcapacitor/PluginCall.java
+++ /dev/null
@@ -1,440 +0,0 @@
-package com.getcapacitor;
-
-import androidx.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.List;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Wraps a call from the web layer to native
- */
-public class PluginCall {
-
- /**
- * A special callback id that indicates there is no matching callback
- * on the client to associate any PluginCall results back to. This is used
- * in the case of an app resuming with saved instance data, for example.
- */
- public static final String CALLBACK_ID_DANGLING = "-1";
-
- private final MessageHandler msgHandler;
- private final String pluginId;
- private final String callbackId;
- private final String methodName;
- private final JSObject data;
-
- private boolean keepAlive = false;
-
- /**
- * Indicates that this PluginCall was released, and should no longer be used
- */
- @Deprecated
- private boolean isReleased = false;
-
- public PluginCall(MessageHandler msgHandler, String pluginId, String callbackId, String methodName, JSObject data) {
- this.msgHandler = msgHandler;
- this.pluginId = pluginId;
- this.callbackId = callbackId;
- this.methodName = methodName;
- this.data = data;
- }
-
- public void successCallback(PluginResult successResult) {
- if (CALLBACK_ID_DANGLING.equals(this.callbackId)) {
- // don't send back response if the callbackId was "-1"
- return;
- }
-
- this.msgHandler.sendResponseMessage(this, successResult, null);
- }
-
- /**
- * @deprecated
- * Use {@link #resolve(JSObject data)}
- */
- @Deprecated
- public void success(JSObject data) {
- PluginResult result = new PluginResult(data);
- this.msgHandler.sendResponseMessage(this, result, null);
- }
-
- /**
- * @deprecated
- * Use {@link #resolve()}
- */
- @Deprecated
- public void success() {
- this.resolve(new JSObject());
- }
-
- public void resolve(JSObject data) {
- PluginResult result = new PluginResult(data);
- this.msgHandler.sendResponseMessage(this, result, null);
- }
-
- public void resolve() {
- this.msgHandler.sendResponseMessage(this, null, null);
- }
-
- public void errorCallback(String msg) {
- PluginResult errorResult = new PluginResult();
-
- try {
- errorResult.put("message", msg);
- } catch (Exception jsonEx) {
- Logger.error(Logger.tags("Plugin"), jsonEx.toString(), null);
- }
-
- this.msgHandler.sendResponseMessage(this, null, errorResult);
- }
-
- /**
- * @deprecated
- * Use {@link #reject(String msg, Exception ex)}
- */
- @Deprecated
- public void error(String msg, Exception ex) {
- reject(msg, ex);
- }
-
- /**
- * @deprecated
- * Use {@link #reject(String msg, String code, Exception ex)}
- */
- @Deprecated
- public void error(String msg, String code, Exception ex) {
- reject(msg, code, ex);
- }
-
- /**
- * @deprecated
- * Use {@link #reject(String msg)}
- */
- @Deprecated
- public void error(String msg) {
- reject(msg);
- }
-
- public void reject(String msg, String code, Exception ex, JSObject data) {
- PluginResult errorResult = new PluginResult();
-
- if (ex != null) {
- Logger.error(Logger.tags("Plugin"), msg, ex);
- }
-
- try {
- errorResult.put("message", msg);
- errorResult.put("code", code);
- if (null != data) {
- errorResult.put("data", data);
- }
- } catch (Exception jsonEx) {
- Logger.error(Logger.tags("Plugin"), jsonEx.getMessage(), jsonEx);
- }
-
- this.msgHandler.sendResponseMessage(this, null, errorResult);
- }
-
- public void reject(String msg, Exception ex, JSObject data) {
- reject(msg, null, ex, data);
- }
-
- public void reject(String msg, String code, JSObject data) {
- reject(msg, code, null, data);
- }
-
- public void reject(String msg, String code, Exception ex) {
- reject(msg, code, ex, null);
- }
-
- public void reject(String msg, JSObject data) {
- reject(msg, null, null, data);
- }
-
- public void reject(String msg, Exception ex) {
- reject(msg, null, ex, null);
- }
-
- public void reject(String msg, String code) {
- reject(msg, code, null, null);
- }
-
- public void reject(String msg) {
- reject(msg, null, null, null);
- }
-
- public void unimplemented() {
- unimplemented("not implemented");
- }
-
- public void unimplemented(String msg) {
- reject(msg, "UNIMPLEMENTED", null, null);
- }
-
- public void unavailable() {
- unavailable("not available");
- }
-
- public void unavailable(String msg) {
- reject(msg, "UNAVAILABLE", null, null);
- }
-
- public String getPluginId() {
- return this.pluginId;
- }
-
- public String getCallbackId() {
- return this.callbackId;
- }
-
- public String getMethodName() {
- return this.methodName;
- }
-
- public JSObject getData() {
- return this.data;
- }
-
- @Nullable
- public String getString(String name) {
- return this.getString(name, null);
- }
-
- @Nullable
- public String getString(String name, @Nullable String defaultValue) {
- Object value = this.data.opt(name);
- if (value == null) {
- return defaultValue;
- }
-
- if (value instanceof String) {
- return (String) value;
- }
- return defaultValue;
- }
-
- @Nullable
- public Integer getInt(String name) {
- return this.getInt(name, null);
- }
-
- @Nullable
- public Integer getInt(String name, @Nullable Integer defaultValue) {
- Object value = this.data.opt(name);
- if (value == null) {
- return defaultValue;
- }
-
- if (value instanceof Integer) {
- return (Integer) value;
- }
- return defaultValue;
- }
-
- @Nullable
- public Long getLong(String name) {
- return this.getLong(name, null);
- }
-
- @Nullable
- public Long getLong(String name, @Nullable Long defaultValue) {
- Object value = this.data.opt(name);
- if (value == null) {
- return defaultValue;
- }
-
- if (value instanceof Long) {
- return (Long) value;
- }
- return defaultValue;
- }
-
- @Nullable
- public Float getFloat(String name) {
- return this.getFloat(name, null);
- }
-
- @Nullable
- public Float getFloat(String name, @Nullable Float defaultValue) {
- Object value = this.data.opt(name);
- if (value == null) {
- return defaultValue;
- }
-
- if (value instanceof Float) {
- return (Float) value;
- }
- if (value instanceof Double) {
- return ((Double) value).floatValue();
- }
- if (value instanceof Integer) {
- return ((Integer) value).floatValue();
- }
- return defaultValue;
- }
-
- @Nullable
- public Double getDouble(String name) {
- return this.getDouble(name, null);
- }
-
- @Nullable
- public Double getDouble(String name, @Nullable Double defaultValue) {
- Object value = this.data.opt(name);
- if (value == null) {
- return defaultValue;
- }
-
- if (value instanceof Double) {
- return (Double) value;
- }
- if (value instanceof Float) {
- return ((Float) value).doubleValue();
- }
- if (value instanceof Integer) {
- return ((Integer) value).doubleValue();
- }
- return defaultValue;
- }
-
- @Nullable
- public Boolean getBoolean(String name) {
- return this.getBoolean(name, null);
- }
-
- @Nullable
- public Boolean getBoolean(String name, @Nullable Boolean defaultValue) {
- Object value = this.data.opt(name);
- if (value == null) {
- return defaultValue;
- }
-
- if (value instanceof Boolean) {
- return (Boolean) value;
- }
- return defaultValue;
- }
-
- public JSObject getObject(String name) {
- return this.getObject(name, null);
- }
-
- @Nullable
- public JSObject getObject(String name, JSObject defaultValue) {
- Object value = this.data.opt(name);
- if (value == null) {
- return defaultValue;
- }
-
- if (value instanceof JSONObject) {
- try {
- return JSObject.fromJSONObject((JSONObject) value);
- } catch (JSONException ex) {
- return defaultValue;
- }
- }
- return defaultValue;
- }
-
- public JSArray getArray(String name) {
- return this.getArray(name, null);
- }
-
- /**
- * Get a JSONArray and turn it into a JSArray
- * @param name
- * @param defaultValue
- * @return
- */
- @Nullable
- public JSArray getArray(String name, JSArray defaultValue) {
- Object value = this.data.opt(name);
- if (value == null) {
- return defaultValue;
- }
-
- if (value instanceof JSONArray) {
- try {
- JSONArray valueArray = (JSONArray) value;
- List