@capacitor-plus/android 8.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/capacitor/build.gradle +94 -0
- package/capacitor/lint-baseline.xml +136 -0
- package/capacitor/lint.xml +9 -0
- package/capacitor/proguard-rules.pro +28 -0
- package/capacitor/src/main/AndroidManifest.xml +3 -0
- package/capacitor/src/main/assets/native-bridge.js +1029 -0
- package/capacitor/src/main/java/com/getcapacitor/AndroidProtocolHandler.java +94 -0
- package/capacitor/src/main/java/com/getcapacitor/App.java +61 -0
- package/capacitor/src/main/java/com/getcapacitor/AppUUID.java +65 -0
- package/capacitor/src/main/java/com/getcapacitor/Bridge.java +1627 -0
- package/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java +212 -0
- package/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java +467 -0
- package/capacitor/src/main/java/com/getcapacitor/BridgeWebViewClient.java +105 -0
- package/capacitor/src/main/java/com/getcapacitor/CapConfig.java +709 -0
- package/capacitor/src/main/java/com/getcapacitor/CapacitorWebView.java +57 -0
- package/capacitor/src/main/java/com/getcapacitor/FileUtils.java +303 -0
- package/capacitor/src/main/java/com/getcapacitor/InvalidPluginException.java +8 -0
- package/capacitor/src/main/java/com/getcapacitor/InvalidPluginMethodException.java +16 -0
- package/capacitor/src/main/java/com/getcapacitor/JSArray.java +51 -0
- package/capacitor/src/main/java/com/getcapacitor/JSExport.java +208 -0
- package/capacitor/src/main/java/com/getcapacitor/JSExportException.java +16 -0
- package/capacitor/src/main/java/com/getcapacitor/JSInjector.java +127 -0
- package/capacitor/src/main/java/com/getcapacitor/JSObject.java +164 -0
- package/capacitor/src/main/java/com/getcapacitor/JSValue.java +65 -0
- package/capacitor/src/main/java/com/getcapacitor/Logger.java +103 -0
- package/capacitor/src/main/java/com/getcapacitor/MessageHandler.java +157 -0
- package/capacitor/src/main/java/com/getcapacitor/NativePlugin.java +37 -0
- package/capacitor/src/main/java/com/getcapacitor/PermissionState.java +31 -0
- package/capacitor/src/main/java/com/getcapacitor/Plugin.java +1050 -0
- package/capacitor/src/main/java/com/getcapacitor/PluginCall.java +394 -0
- package/capacitor/src/main/java/com/getcapacitor/PluginConfig.java +116 -0
- package/capacitor/src/main/java/com/getcapacitor/PluginHandle.java +160 -0
- package/capacitor/src/main/java/com/getcapacitor/PluginInvocationException.java +16 -0
- package/capacitor/src/main/java/com/getcapacitor/PluginLoadException.java +19 -0
- package/capacitor/src/main/java/com/getcapacitor/PluginManager.java +56 -0
- package/capacitor/src/main/java/com/getcapacitor/PluginMethod.java +15 -0
- package/capacitor/src/main/java/com/getcapacitor/PluginMethodHandle.java +33 -0
- package/capacitor/src/main/java/com/getcapacitor/PluginResult.java +84 -0
- package/capacitor/src/main/java/com/getcapacitor/ProcessedRoute.java +37 -0
- package/capacitor/src/main/java/com/getcapacitor/RouteProcessor.java +8 -0
- package/capacitor/src/main/java/com/getcapacitor/ServerPath.java +25 -0
- package/capacitor/src/main/java/com/getcapacitor/UriMatcher.java +184 -0
- package/capacitor/src/main/java/com/getcapacitor/WebViewListener.java +57 -0
- package/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java +758 -0
- package/capacitor/src/main/java/com/getcapacitor/annotation/ActivityCallback.java +10 -0
- package/capacitor/src/main/java/com/getcapacitor/annotation/CapacitorPlugin.java +35 -0
- package/capacitor/src/main/java/com/getcapacitor/annotation/Permission.java +22 -0
- package/capacitor/src/main/java/com/getcapacitor/annotation/PermissionCallback.java +10 -0
- package/capacitor/src/main/java/com/getcapacitor/cordova/CapacitorCordovaCookieManager.java +42 -0
- package/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaInterfaceImpl.java +39 -0
- package/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java +282 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorCookieManager.java +239 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorCookies.java +119 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java +119 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +220 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/WebView.java +48 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/util/AssetUtil.java +358 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java +473 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/util/HttpRequestHandler.java +465 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/util/ICapacitorHttpUrlConnection.java +15 -0
- package/capacitor/src/main/java/com/getcapacitor/plugin/util/MimeType.java +17 -0
- package/capacitor/src/main/java/com/getcapacitor/util/HostMask.java +123 -0
- package/capacitor/src/main/java/com/getcapacitor/util/InternalUtils.java +27 -0
- package/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java +166 -0
- package/capacitor/src/main/java/com/getcapacitor/util/PermissionHelper.java +114 -0
- package/capacitor/src/main/java/com/getcapacitor/util/WebColor.java +28 -0
- package/capacitor/src/main/res/layout/capacitor_bridge_layout_main.xml +15 -0
- package/capacitor/src/main/res/layout/no_webview.xml +14 -0
- package/capacitor/src/main/res/values/colors.xml +6 -0
- package/capacitor/src/main/res/values/strings.xml +3 -0
- package/capacitor/src/main/res/values/styles.xml +6 -0
- package/package.json +31 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
package com.getcapacitor;
|
|
2
|
+
|
|
3
|
+
import android.content.Intent;
|
|
4
|
+
import android.content.res.Configuration;
|
|
5
|
+
import android.os.Bundle;
|
|
6
|
+
import androidx.appcompat.app.AppCompatActivity;
|
|
7
|
+
import com.getcapacitor.android.R;
|
|
8
|
+
import java.util.ArrayList;
|
|
9
|
+
import java.util.List;
|
|
10
|
+
|
|
11
|
+
public class BridgeActivity extends AppCompatActivity {
|
|
12
|
+
|
|
13
|
+
protected Bridge bridge;
|
|
14
|
+
protected boolean keepRunning = true;
|
|
15
|
+
protected CapConfig config;
|
|
16
|
+
|
|
17
|
+
protected int activityDepth = 0;
|
|
18
|
+
protected List<Class<? extends Plugin>> initialPlugins = new ArrayList<>();
|
|
19
|
+
protected final Bridge.Builder bridgeBuilder = new Bridge.Builder(this);
|
|
20
|
+
|
|
21
|
+
@Override
|
|
22
|
+
protected void onCreate(Bundle savedInstanceState) {
|
|
23
|
+
super.onCreate(savedInstanceState);
|
|
24
|
+
bridgeBuilder.setInstanceState(savedInstanceState);
|
|
25
|
+
getApplication().setTheme(R.style.AppTheme_NoActionBar);
|
|
26
|
+
setTheme(R.style.AppTheme_NoActionBar);
|
|
27
|
+
try {
|
|
28
|
+
setContentView(R.layout.capacitor_bridge_layout_main);
|
|
29
|
+
} catch (Exception ex) {
|
|
30
|
+
setContentView(R.layout.no_webview);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
PluginManager loader = new PluginManager(getAssets());
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
bridgeBuilder.addPlugins(loader.loadPluginClasses());
|
|
38
|
+
} catch (PluginLoadException ex) {
|
|
39
|
+
Logger.error("Error loading plugins.", ex);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.load();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected void load() {
|
|
46
|
+
Logger.debug("Starting BridgeActivity");
|
|
47
|
+
|
|
48
|
+
bridge = bridgeBuilder.addPlugins(initialPlugins).setConfig(config).create();
|
|
49
|
+
|
|
50
|
+
this.keepRunning = bridge.shouldKeepRunning();
|
|
51
|
+
this.onNewIntent(getIntent());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public void registerPlugin(Class<? extends Plugin> plugin) {
|
|
55
|
+
bridgeBuilder.addPlugin(plugin);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public void registerPlugins(List<Class<? extends Plugin>> plugins) {
|
|
59
|
+
bridgeBuilder.addPlugins(plugins);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public Bridge getBridge() {
|
|
63
|
+
return this.bridge;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Override
|
|
67
|
+
public void onSaveInstanceState(Bundle outState) {
|
|
68
|
+
super.onSaveInstanceState(outState);
|
|
69
|
+
bridge.saveInstanceState(outState);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Override
|
|
73
|
+
public void onStart() {
|
|
74
|
+
super.onStart();
|
|
75
|
+
activityDepth++;
|
|
76
|
+
if (this.bridge != null) {
|
|
77
|
+
this.bridge.onStart();
|
|
78
|
+
Logger.debug("App started");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@Override
|
|
83
|
+
public void onRestart() {
|
|
84
|
+
super.onRestart();
|
|
85
|
+
this.bridge.onRestart();
|
|
86
|
+
Logger.debug("App restarted");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@Override
|
|
90
|
+
public void onResume() {
|
|
91
|
+
super.onResume();
|
|
92
|
+
if (bridge != null) {
|
|
93
|
+
bridge.getApp().fireStatusChange(true);
|
|
94
|
+
this.bridge.onResume();
|
|
95
|
+
Logger.debug("App resumed");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Override
|
|
100
|
+
public void onPause() {
|
|
101
|
+
super.onPause();
|
|
102
|
+
if (bridge != null) {
|
|
103
|
+
this.bridge.onPause();
|
|
104
|
+
Logger.debug("App paused");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@Override
|
|
109
|
+
public void onStop() {
|
|
110
|
+
super.onStop();
|
|
111
|
+
if (bridge != null) {
|
|
112
|
+
activityDepth = Math.max(0, activityDepth - 1);
|
|
113
|
+
if (activityDepth == 0) {
|
|
114
|
+
bridge.getApp().fireStatusChange(false);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.bridge.onStop();
|
|
118
|
+
Logger.debug("App stopped");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@Override
|
|
123
|
+
public void onDestroy() {
|
|
124
|
+
super.onDestroy();
|
|
125
|
+
if (this.bridge != null) {
|
|
126
|
+
this.bridge.onDestroy();
|
|
127
|
+
Logger.debug("App destroyed");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@Override
|
|
132
|
+
public void onDetachedFromWindow() {
|
|
133
|
+
super.onDetachedFromWindow();
|
|
134
|
+
this.bridge.onDetachedFromWindow();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Handles permission request results.
|
|
139
|
+
*
|
|
140
|
+
* Capacitor is backwards compatible such that plugins using legacy permission request codes
|
|
141
|
+
* may coexist with plugins using the AndroidX Activity v1.2 permission callback flow introduced
|
|
142
|
+
* in Capacitor 3.0.
|
|
143
|
+
*
|
|
144
|
+
* In this method, plugins are checked first for ownership of the legacy permission request code.
|
|
145
|
+
* If the {@link Bridge#onRequestPermissionsResult(int, String[], int[])} method indicates it has
|
|
146
|
+
* handled the permission, then the permission callback will be considered complete. Otherwise,
|
|
147
|
+
* the permission will be handled using the AndroidX Activity flow.
|
|
148
|
+
*
|
|
149
|
+
* @param requestCode the request code associated with the permission request
|
|
150
|
+
* @param permissions the Android permission strings requested
|
|
151
|
+
* @param grantResults the status result of the permission request
|
|
152
|
+
*/
|
|
153
|
+
@Override
|
|
154
|
+
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
|
155
|
+
if (this.bridge == null) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!bridge.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
|
|
160
|
+
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Handles activity results.
|
|
166
|
+
*
|
|
167
|
+
* Capacitor is backwards compatible such that plugins using legacy activity result codes
|
|
168
|
+
* may coexist with plugins using the AndroidX Activity v1.2 activity callback flow introduced
|
|
169
|
+
* in Capacitor 3.0.
|
|
170
|
+
*
|
|
171
|
+
* In this method, plugins are checked first for ownership of the legacy request code. If the
|
|
172
|
+
* {@link Bridge#onActivityResult(int, int, Intent)} method indicates it has handled the activity
|
|
173
|
+
* result, then the callback will be considered complete. Otherwise, the result will be handled
|
|
174
|
+
* using the AndroidX Activiy flow.
|
|
175
|
+
*
|
|
176
|
+
* @param requestCode the request code associated with the activity result
|
|
177
|
+
* @param resultCode the result code
|
|
178
|
+
* @param data any data included with the activity result
|
|
179
|
+
*/
|
|
180
|
+
@Override
|
|
181
|
+
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
182
|
+
if (this.bridge == null) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!bridge.onActivityResult(requestCode, resultCode, data)) {
|
|
187
|
+
super.onActivityResult(requestCode, resultCode, data);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@Override
|
|
192
|
+
protected void onNewIntent(Intent intent) {
|
|
193
|
+
super.onNewIntent(intent);
|
|
194
|
+
|
|
195
|
+
if (this.bridge == null || intent == null) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.bridge.onNewIntent(intent);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@Override
|
|
203
|
+
public void onConfigurationChanged(Configuration newConfig) {
|
|
204
|
+
super.onConfigurationChanged(newConfig);
|
|
205
|
+
|
|
206
|
+
if (this.bridge == null) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this.bridge.onConfigurationChanged(newConfig);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
package com.getcapacitor;
|
|
2
|
+
|
|
3
|
+
import android.Manifest;
|
|
4
|
+
import android.annotation.SuppressLint;
|
|
5
|
+
import android.app.Activity;
|
|
6
|
+
import android.app.AlertDialog;
|
|
7
|
+
import android.content.ActivityNotFoundException;
|
|
8
|
+
import android.content.Intent;
|
|
9
|
+
import android.net.Uri;
|
|
10
|
+
import android.os.Build;
|
|
11
|
+
import android.os.Environment;
|
|
12
|
+
import android.provider.MediaStore;
|
|
13
|
+
import android.view.View;
|
|
14
|
+
import android.webkit.ConsoleMessage;
|
|
15
|
+
import android.webkit.GeolocationPermissions;
|
|
16
|
+
import android.webkit.JsPromptResult;
|
|
17
|
+
import android.webkit.JsResult;
|
|
18
|
+
import android.webkit.MimeTypeMap;
|
|
19
|
+
import android.webkit.PermissionRequest;
|
|
20
|
+
import android.webkit.ValueCallback;
|
|
21
|
+
import android.webkit.WebChromeClient;
|
|
22
|
+
import android.webkit.WebView;
|
|
23
|
+
import android.widget.EditText;
|
|
24
|
+
import androidx.activity.result.ActivityResult;
|
|
25
|
+
import androidx.activity.result.ActivityResultCallback;
|
|
26
|
+
import androidx.activity.result.ActivityResultLauncher;
|
|
27
|
+
import androidx.activity.result.contract.ActivityResultContracts;
|
|
28
|
+
import androidx.core.content.FileProvider;
|
|
29
|
+
import com.getcapacitor.util.PermissionHelper;
|
|
30
|
+
import java.io.File;
|
|
31
|
+
import java.io.IOException;
|
|
32
|
+
import java.text.SimpleDateFormat;
|
|
33
|
+
import java.util.*;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Custom WebChromeClient handler, required for showing dialogs, confirms, etc. in our
|
|
37
|
+
* WebView instance.
|
|
38
|
+
*/
|
|
39
|
+
public class BridgeWebChromeClient extends WebChromeClient {
|
|
40
|
+
|
|
41
|
+
private interface PermissionListener {
|
|
42
|
+
void onPermissionSelect(Boolean isGranted);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private interface ActivityResultListener {
|
|
46
|
+
void onActivityResult(ActivityResult result);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private ActivityResultLauncher permissionLauncher;
|
|
50
|
+
private ActivityResultLauncher activityLauncher;
|
|
51
|
+
private PermissionListener permissionListener;
|
|
52
|
+
private ActivityResultListener activityListener;
|
|
53
|
+
|
|
54
|
+
private Bridge bridge;
|
|
55
|
+
|
|
56
|
+
public BridgeWebChromeClient(Bridge bridge) {
|
|
57
|
+
this.bridge = bridge;
|
|
58
|
+
|
|
59
|
+
ActivityResultCallback<Map<String, Boolean>> permissionCallback = (Map<String, Boolean> isGranted) -> {
|
|
60
|
+
if (permissionListener != null) {
|
|
61
|
+
boolean granted = true;
|
|
62
|
+
for (Map.Entry<String, Boolean> permission : isGranted.entrySet()) {
|
|
63
|
+
if (!permission.getValue()) granted = false;
|
|
64
|
+
}
|
|
65
|
+
permissionListener.onPermissionSelect(granted);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
permissionLauncher = bridge.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), permissionCallback);
|
|
70
|
+
activityLauncher = bridge.registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), (result) -> {
|
|
71
|
+
if (activityListener != null) {
|
|
72
|
+
activityListener.onActivityResult(result);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Render web content in `view`.
|
|
79
|
+
*
|
|
80
|
+
* Both this method and {@link #onHideCustomView()} are required for
|
|
81
|
+
* rendering web content in full screen.
|
|
82
|
+
*
|
|
83
|
+
* @see <a href="https://developer.android.com/reference/android/webkit/WebChromeClient#onShowCustomView(android.view.View,%20android.webkit.WebChromeClient.CustomViewCallback)">onShowCustomView() docs</a>
|
|
84
|
+
*/
|
|
85
|
+
@Override
|
|
86
|
+
public void onShowCustomView(View view, CustomViewCallback callback) {
|
|
87
|
+
callback.onCustomViewHidden();
|
|
88
|
+
super.onShowCustomView(view, callback);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Render web content in the original Web View again.
|
|
93
|
+
*
|
|
94
|
+
* Do not remove this method--@see #onShowCustomView(View, CustomViewCallback).
|
|
95
|
+
*/
|
|
96
|
+
@Override
|
|
97
|
+
public void onHideCustomView() {
|
|
98
|
+
super.onHideCustomView();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@Override
|
|
102
|
+
public void onPermissionRequest(final PermissionRequest request) {
|
|
103
|
+
List<String> permissionList = new ArrayList<>();
|
|
104
|
+
if (Arrays.asList(request.getResources()).contains("android.webkit.resource.VIDEO_CAPTURE")) {
|
|
105
|
+
permissionList.add(Manifest.permission.CAMERA);
|
|
106
|
+
}
|
|
107
|
+
if (Arrays.asList(request.getResources()).contains("android.webkit.resource.AUDIO_CAPTURE")) {
|
|
108
|
+
permissionList.add(Manifest.permission.MODIFY_AUDIO_SETTINGS);
|
|
109
|
+
permissionList.add(Manifest.permission.RECORD_AUDIO);
|
|
110
|
+
}
|
|
111
|
+
if (!permissionList.isEmpty()) {
|
|
112
|
+
String[] permissions = permissionList.toArray(new String[0]);
|
|
113
|
+
permissionListener = (isGranted) -> {
|
|
114
|
+
if (isGranted) {
|
|
115
|
+
request.grant(request.getResources());
|
|
116
|
+
} else {
|
|
117
|
+
request.deny();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
permissionLauncher.launch(permissions);
|
|
121
|
+
} else {
|
|
122
|
+
request.grant(request.getResources());
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Show the browser alert modal
|
|
128
|
+
* @param view
|
|
129
|
+
* @param url
|
|
130
|
+
* @param message
|
|
131
|
+
* @param result
|
|
132
|
+
* @return
|
|
133
|
+
*/
|
|
134
|
+
@Override
|
|
135
|
+
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
|
|
136
|
+
if (bridge.getActivity().isFinishing()) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
|
|
141
|
+
builder
|
|
142
|
+
.setMessage(message)
|
|
143
|
+
.setPositiveButton("OK", (dialog, buttonIndex) -> {
|
|
144
|
+
dialog.dismiss();
|
|
145
|
+
result.confirm();
|
|
146
|
+
})
|
|
147
|
+
.setOnCancelListener((dialog) -> {
|
|
148
|
+
dialog.dismiss();
|
|
149
|
+
result.cancel();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
AlertDialog dialog = builder.create();
|
|
153
|
+
|
|
154
|
+
dialog.show();
|
|
155
|
+
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Show the browser confirm modal
|
|
161
|
+
* @param view
|
|
162
|
+
* @param url
|
|
163
|
+
* @param message
|
|
164
|
+
* @param result
|
|
165
|
+
* @return
|
|
166
|
+
*/
|
|
167
|
+
@Override
|
|
168
|
+
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
|
|
169
|
+
if (bridge.getActivity().isFinishing()) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
|
|
174
|
+
|
|
175
|
+
builder
|
|
176
|
+
.setMessage(message)
|
|
177
|
+
.setPositiveButton("OK", (dialog, buttonIndex) -> {
|
|
178
|
+
dialog.dismiss();
|
|
179
|
+
result.confirm();
|
|
180
|
+
})
|
|
181
|
+
.setNegativeButton("Cancel", (dialog, buttonIndex) -> {
|
|
182
|
+
dialog.dismiss();
|
|
183
|
+
result.cancel();
|
|
184
|
+
})
|
|
185
|
+
.setOnCancelListener((dialog) -> {
|
|
186
|
+
dialog.dismiss();
|
|
187
|
+
result.cancel();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
AlertDialog dialog = builder.create();
|
|
191
|
+
|
|
192
|
+
dialog.show();
|
|
193
|
+
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Show the browser prompt modal
|
|
199
|
+
* @param view
|
|
200
|
+
* @param url
|
|
201
|
+
* @param message
|
|
202
|
+
* @param defaultValue
|
|
203
|
+
* @param result
|
|
204
|
+
* @return
|
|
205
|
+
*/
|
|
206
|
+
@Override
|
|
207
|
+
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
|
|
208
|
+
if (bridge.getActivity().isFinishing()) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
|
|
213
|
+
final EditText input = new EditText(view.getContext());
|
|
214
|
+
|
|
215
|
+
builder
|
|
216
|
+
.setMessage(message)
|
|
217
|
+
.setView(input)
|
|
218
|
+
.setPositiveButton("OK", (dialog, buttonIndex) -> {
|
|
219
|
+
dialog.dismiss();
|
|
220
|
+
|
|
221
|
+
String inputText1 = input.getText().toString().trim();
|
|
222
|
+
result.confirm(inputText1);
|
|
223
|
+
})
|
|
224
|
+
.setNegativeButton("Cancel", (dialog, buttonIndex) -> {
|
|
225
|
+
dialog.dismiss();
|
|
226
|
+
result.cancel();
|
|
227
|
+
})
|
|
228
|
+
.setOnCancelListener((dialog) -> {
|
|
229
|
+
dialog.dismiss();
|
|
230
|
+
result.cancel();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
AlertDialog dialog = builder.create();
|
|
234
|
+
|
|
235
|
+
dialog.show();
|
|
236
|
+
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Handle the browser geolocation permission prompt
|
|
242
|
+
* @param origin
|
|
243
|
+
* @param callback
|
|
244
|
+
*/
|
|
245
|
+
@Override
|
|
246
|
+
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
|
|
247
|
+
super.onGeolocationPermissionsShowPrompt(origin, callback);
|
|
248
|
+
Logger.debug("onGeolocationPermissionsShowPrompt: DOING IT HERE FOR ORIGIN: " + origin);
|
|
249
|
+
final String[] geoPermissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION };
|
|
250
|
+
|
|
251
|
+
if (!PermissionHelper.hasPermissions(bridge.getContext(), geoPermissions)) {
|
|
252
|
+
permissionListener = (isGranted) -> {
|
|
253
|
+
if (isGranted) {
|
|
254
|
+
callback.invoke(origin, true, false);
|
|
255
|
+
} else {
|
|
256
|
+
final String[] coarsePermission = { Manifest.permission.ACCESS_COARSE_LOCATION };
|
|
257
|
+
if (
|
|
258
|
+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
|
259
|
+
PermissionHelper.hasPermissions(bridge.getContext(), coarsePermission)
|
|
260
|
+
) {
|
|
261
|
+
callback.invoke(origin, true, false);
|
|
262
|
+
} else {
|
|
263
|
+
callback.invoke(origin, false, false);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
permissionLauncher.launch(geoPermissions);
|
|
268
|
+
} else {
|
|
269
|
+
// permission is already granted
|
|
270
|
+
callback.invoke(origin, true, false);
|
|
271
|
+
Logger.debug("onGeolocationPermissionsShowPrompt: has required permission");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@Override
|
|
276
|
+
public boolean onShowFileChooser(
|
|
277
|
+
WebView webView,
|
|
278
|
+
final ValueCallback<Uri[]> filePathCallback,
|
|
279
|
+
final FileChooserParams fileChooserParams
|
|
280
|
+
) {
|
|
281
|
+
List<String> acceptTypes = Arrays.asList(fileChooserParams.getAcceptTypes());
|
|
282
|
+
boolean captureEnabled = fileChooserParams.isCaptureEnabled();
|
|
283
|
+
boolean capturePhoto = captureEnabled && acceptTypes.contains("image/*");
|
|
284
|
+
final boolean captureVideo = captureEnabled && acceptTypes.contains("video/*");
|
|
285
|
+
if ((capturePhoto || captureVideo)) {
|
|
286
|
+
if (isMediaCaptureSupported()) {
|
|
287
|
+
showMediaCaptureOrFilePicker(filePathCallback, fileChooserParams, captureVideo);
|
|
288
|
+
} else {
|
|
289
|
+
permissionListener = (isGranted) -> {
|
|
290
|
+
if (isGranted) {
|
|
291
|
+
showMediaCaptureOrFilePicker(filePathCallback, fileChooserParams, captureVideo);
|
|
292
|
+
} else {
|
|
293
|
+
Logger.warn(Logger.tags("FileChooser"), "Camera permission not granted");
|
|
294
|
+
filePathCallback.onReceiveValue(null);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
final String[] camPermission = { Manifest.permission.CAMERA };
|
|
298
|
+
permissionLauncher.launch(camPermission);
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
showFilePicker(filePathCallback, fileChooserParams);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private boolean isMediaCaptureSupported() {
|
|
308
|
+
String[] permissions = { Manifest.permission.CAMERA };
|
|
309
|
+
return (
|
|
310
|
+
PermissionHelper.hasPermissions(bridge.getContext(), permissions) ||
|
|
311
|
+
!PermissionHelper.hasDefinedPermission(bridge.getContext(), Manifest.permission.CAMERA)
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private void showMediaCaptureOrFilePicker(ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams, boolean isVideo) {
|
|
316
|
+
boolean shown;
|
|
317
|
+
if (isVideo) {
|
|
318
|
+
shown = showVideoCapturePicker(filePathCallback);
|
|
319
|
+
} else {
|
|
320
|
+
shown = showImageCapturePicker(filePathCallback);
|
|
321
|
+
}
|
|
322
|
+
if (!shown) {
|
|
323
|
+
Logger.warn(Logger.tags("FileChooser"), "Media capture intent could not be launched. Falling back to default file picker.");
|
|
324
|
+
showFilePicker(filePathCallback, fileChooserParams);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
@SuppressLint("QueryPermissionsNeeded")
|
|
329
|
+
private boolean showImageCapturePicker(final ValueCallback<Uri[]> filePathCallback) {
|
|
330
|
+
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
|
331
|
+
if (takePictureIntent.resolveActivity(bridge.getActivity().getPackageManager()) == null) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
final Uri imageFileUri;
|
|
336
|
+
try {
|
|
337
|
+
imageFileUri = createImageFileUri();
|
|
338
|
+
} catch (Exception ex) {
|
|
339
|
+
Logger.error("Unable to create temporary media capture file: " + ex.getMessage());
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri);
|
|
343
|
+
activityListener = (activityResult) -> {
|
|
344
|
+
Uri[] result = null;
|
|
345
|
+
if (activityResult.getResultCode() == Activity.RESULT_OK) {
|
|
346
|
+
result = new Uri[] { imageFileUri };
|
|
347
|
+
}
|
|
348
|
+
filePathCallback.onReceiveValue(result);
|
|
349
|
+
};
|
|
350
|
+
activityLauncher.launch(takePictureIntent);
|
|
351
|
+
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
@SuppressLint("QueryPermissionsNeeded")
|
|
356
|
+
private boolean showVideoCapturePicker(final ValueCallback<Uri[]> filePathCallback) {
|
|
357
|
+
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
|
|
358
|
+
if (takeVideoIntent.resolveActivity(bridge.getActivity().getPackageManager()) == null) {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
activityListener = (activityResult) -> {
|
|
363
|
+
Uri[] result = null;
|
|
364
|
+
if (activityResult.getResultCode() == Activity.RESULT_OK) {
|
|
365
|
+
result = new Uri[] { activityResult.getData().getData() };
|
|
366
|
+
}
|
|
367
|
+
filePathCallback.onReceiveValue(result);
|
|
368
|
+
};
|
|
369
|
+
activityLauncher.launch(takeVideoIntent);
|
|
370
|
+
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private void showFilePicker(final ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
|
|
375
|
+
Intent intent = fileChooserParams.createIntent();
|
|
376
|
+
if (fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE) {
|
|
377
|
+
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
|
|
378
|
+
}
|
|
379
|
+
if (fileChooserParams.getAcceptTypes().length > 1 || intent.getType().startsWith(".")) {
|
|
380
|
+
String[] validTypes = getValidTypes(fileChooserParams.getAcceptTypes());
|
|
381
|
+
intent.putExtra(Intent.EXTRA_MIME_TYPES, validTypes);
|
|
382
|
+
if (intent.getType().startsWith(".")) {
|
|
383
|
+
intent.setType(validTypes[0]);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
activityListener = (activityResult) -> {
|
|
388
|
+
Uri[] result;
|
|
389
|
+
Intent resultIntent = activityResult.getData();
|
|
390
|
+
if (activityResult.getResultCode() == Activity.RESULT_OK && resultIntent.getClipData() != null) {
|
|
391
|
+
final int numFiles = resultIntent.getClipData().getItemCount();
|
|
392
|
+
result = new Uri[numFiles];
|
|
393
|
+
for (int i = 0; i < numFiles; i++) {
|
|
394
|
+
result[i] = resultIntent.getClipData().getItemAt(i).getUri();
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
result = WebChromeClient.FileChooserParams.parseResult(activityResult.getResultCode(), resultIntent);
|
|
398
|
+
}
|
|
399
|
+
filePathCallback.onReceiveValue(result);
|
|
400
|
+
};
|
|
401
|
+
activityLauncher.launch(intent);
|
|
402
|
+
} catch (ActivityNotFoundException e) {
|
|
403
|
+
filePathCallback.onReceiveValue(null);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private String[] getValidTypes(String[] currentTypes) {
|
|
408
|
+
List<String> validTypes = new ArrayList<>();
|
|
409
|
+
MimeTypeMap mtm = MimeTypeMap.getSingleton();
|
|
410
|
+
for (String mime : currentTypes) {
|
|
411
|
+
if (mime.startsWith(".")) {
|
|
412
|
+
String extension = mime.substring(1);
|
|
413
|
+
String extensionMime = mtm.getMimeTypeFromExtension(extension);
|
|
414
|
+
if (extensionMime != null && !validTypes.contains(extensionMime)) {
|
|
415
|
+
validTypes.add(extensionMime);
|
|
416
|
+
}
|
|
417
|
+
} else if (!validTypes.contains(mime)) {
|
|
418
|
+
validTypes.add(mime);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
Object[] validObj = validTypes.toArray();
|
|
422
|
+
return Arrays.copyOf(validObj, validObj.length, String[].class);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
@Override
|
|
426
|
+
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
|
|
427
|
+
String tag = Logger.tags("Console");
|
|
428
|
+
if (consoleMessage.message() != null && isValidMsg(consoleMessage.message())) {
|
|
429
|
+
String msg = String.format(
|
|
430
|
+
"File: %s - Line %d - Msg: %s",
|
|
431
|
+
consoleMessage.sourceId(),
|
|
432
|
+
consoleMessage.lineNumber(),
|
|
433
|
+
consoleMessage.message()
|
|
434
|
+
);
|
|
435
|
+
String level = consoleMessage.messageLevel().name();
|
|
436
|
+
if ("ERROR".equalsIgnoreCase(level)) {
|
|
437
|
+
Logger.error(tag, msg, null);
|
|
438
|
+
} else if ("WARNING".equalsIgnoreCase(level)) {
|
|
439
|
+
Logger.warn(tag, msg);
|
|
440
|
+
} else if ("TIP".equalsIgnoreCase(level)) {
|
|
441
|
+
Logger.debug(tag, msg);
|
|
442
|
+
} else {
|
|
443
|
+
Logger.info(tag, msg);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
public boolean isValidMsg(String msg) {
|
|
450
|
+
return !(msg.contains("%cresult %c") || (msg.contains("%cnative %c")) || msg.equalsIgnoreCase("console.groupEnd"));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private Uri createImageFileUri() throws IOException {
|
|
454
|
+
Activity activity = bridge.getActivity();
|
|
455
|
+
File photoFile = createImageFile(activity);
|
|
456
|
+
return FileProvider.getUriForFile(activity, bridge.getContext().getPackageName() + ".fileprovider", photoFile);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
private File createImageFile(Activity activity) throws IOException {
|
|
460
|
+
// Create an image file name
|
|
461
|
+
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
|
462
|
+
String imageFileName = "JPEG_" + timeStamp + "_";
|
|
463
|
+
File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
|
464
|
+
|
|
465
|
+
return File.createTempFile(imageFileName, ".jpg", storageDir);
|
|
466
|
+
}
|
|
467
|
+
}
|