@capacitor-plus/android 8.5.3 → 8.6.1
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/capacitor/proguard-rules.pro +25 -0
- package/capacitor/src/main/assets/native-bridge.js +0 -6
- package/capacitor/src/main/java/com/getcapacitor/Bridge.java +36 -1
- package/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java +0 -3
- package/capacitor/src/main/java/com/getcapacitor/Plugin.java +16 -4
- package/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java +27 -3
- package/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java +8 -1
- package/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +72 -118
- package/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java +14 -2
- package/capacitor/src/main/java/com/getcapacitor/plugin/util/HttpRequestHandler.java +13 -1
- package/package.json +3 -3
|
@@ -15,14 +15,39 @@
|
|
|
15
15
|
|
|
16
16
|
-keep public class * extends com.getcapacitor.Plugin { *; }
|
|
17
17
|
|
|
18
|
+
# The annotation types are read at runtime via reflection. If they are not kept, R8 full mode
|
|
19
|
+
# treats them as never instantiated and optimizes the annotation cached in PluginHandle to null,
|
|
20
|
+
# even with -keepattributes *Annotation*.
|
|
21
|
+
-keep @interface com.getcapacitor.annotation.** { *; }
|
|
22
|
+
-keep @interface com.getcapacitor.PluginMethod { *; }
|
|
23
|
+
|
|
24
|
+
# Callback methods are found by reflection, including on non-public base classes.
|
|
25
|
+
-keepclassmembers class * extends com.getcapacitor.Plugin {
|
|
26
|
+
@com.getcapacitor.annotation.ActivityCallback <methods>;
|
|
27
|
+
@com.getcapacitor.annotation.PermissionCallback <methods>;
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
# Rules for Capacitor v2 plugins and annotations
|
|
19
31
|
# These are deprecated but can still be used with Capacitor for now
|
|
20
32
|
-keep @com.getcapacitor.NativePlugin public class * {
|
|
21
33
|
@com.getcapacitor.PluginMethod public <methods>;
|
|
22
34
|
}
|
|
35
|
+
-keep @interface com.getcapacitor.NativePlugin { *; }
|
|
23
36
|
|
|
24
37
|
# Rules for Cordova plugins
|
|
25
38
|
-keep public class * extends org.apache.cordova.* {
|
|
26
39
|
public <methods>;
|
|
27
40
|
public <fields>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# CapacitorHttp loads these via reflection when an SSL pinning plugin is present.
|
|
44
|
+
-keep class io.ionic.sslpinning.SSLPinning {
|
|
45
|
+
<init>();
|
|
46
|
+
public javax.net.ssl.SSLSocketFactory getSSLSocketFactory(com.getcapacitor.Bridge);
|
|
47
|
+
public boolean isDomainExcluded(com.getcapacitor.Bridge, java.net.URL);
|
|
48
|
+
}
|
|
49
|
+
-keep class com.getcapacitor.community.sslpinning.SSLPinning {
|
|
50
|
+
<init>();
|
|
51
|
+
public javax.net.ssl.SSLSocketFactory getSSLSocketFactory(com.getcapacitor.Bridge);
|
|
52
|
+
public boolean isDomainExcluded(com.getcapacitor.Bridge, java.net.URL);
|
|
28
53
|
}
|
|
@@ -417,12 +417,6 @@ var nativeBridge = (function (exports) {
|
|
|
417
417
|
}
|
|
418
418
|
};
|
|
419
419
|
const platform = getPlatformId(win);
|
|
420
|
-
if (platform == 'android' && typeof win.CapacitorSystemBarsAndroidInterface !== 'undefined') {
|
|
421
|
-
// add DOM ready listener for System Bars
|
|
422
|
-
document.addEventListener('DOMContentLoaded', function () {
|
|
423
|
-
win.CapacitorSystemBarsAndroidInterface.onDOMReady();
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
420
|
if (platform == 'android' || platform == 'ios') {
|
|
427
421
|
// patch document.cookie on Android/iOS
|
|
428
422
|
win.CapacitorCookiesDescriptor =
|
|
@@ -393,7 +393,39 @@ public class Bridge {
|
|
|
393
393
|
return 0;
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
+
private static int effectivePort(Uri uri) {
|
|
397
|
+
int port = uri.getPort();
|
|
398
|
+
if (port != -1) {
|
|
399
|
+
return port;
|
|
400
|
+
}
|
|
401
|
+
if ("https".equals(uri.getScheme())) {
|
|
402
|
+
return 443;
|
|
403
|
+
}
|
|
404
|
+
if ("http".equals(uri.getScheme())) {
|
|
405
|
+
return 80;
|
|
406
|
+
}
|
|
407
|
+
return -1;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
public static boolean shouldBlockHttpInterceptorNavigation(Uri url, Uri appOrigin) {
|
|
411
|
+
String path = url.getPath();
|
|
412
|
+
if (path == null || !path.startsWith(CAPACITOR_HTTP_INTERCEPTOR_START)) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
return (
|
|
416
|
+
appOrigin.getHost().equals(url.getHost()) &&
|
|
417
|
+
url.getScheme().equals(appOrigin.getScheme()) &&
|
|
418
|
+
effectivePort(appOrigin) == effectivePort(url)
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
396
422
|
public boolean launchIntent(Uri url) {
|
|
423
|
+
// The proxy returns a remote body at the app origin, so block it before plugins can allow it.
|
|
424
|
+
Uri appUri = Uri.parse(appUrl);
|
|
425
|
+
if (shouldBlockHttpInterceptorNavigation(url, appUri)) {
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
|
|
397
429
|
/*
|
|
398
430
|
* Give plugins the chance to handle the url
|
|
399
431
|
*/
|
|
@@ -411,7 +443,6 @@ public class Bridge {
|
|
|
411
443
|
return false;
|
|
412
444
|
}
|
|
413
445
|
|
|
414
|
-
Uri appUri = Uri.parse(appUrl);
|
|
415
446
|
if (
|
|
416
447
|
!(appUri.getHost().equals(url.getHost()) && url.getScheme().equals(appUri.getScheme())) &&
|
|
417
448
|
!appAllowNavigationMask.matches(url.getHost())
|
|
@@ -1262,6 +1293,10 @@ public class Bridge {
|
|
|
1262
1293
|
}
|
|
1263
1294
|
}
|
|
1264
1295
|
}
|
|
1296
|
+
} else {
|
|
1297
|
+
Logger.warn(
|
|
1298
|
+
String.format("getPermissionStates: missing @CapacitorPlugin annotation for plugin %s", plugin.getPluginHandle().getId())
|
|
1299
|
+
);
|
|
1265
1300
|
}
|
|
1266
1301
|
|
|
1267
1302
|
return permissionsResults;
|
|
@@ -412,12 +412,9 @@ public class BridgeWebChromeClient extends WebChromeClient {
|
|
|
412
412
|
}
|
|
413
413
|
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri);
|
|
414
414
|
takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
415
|
-
|
|
416
|
-
// Store in static variables to survive activity recreation
|
|
417
415
|
pendingFilePathCallback = filePathCallback;
|
|
418
416
|
pendingImageFileUri = imageFileUri;
|
|
419
417
|
pendingFileChooserType = FileChooserType.IMAGE_CAPTURE;
|
|
420
|
-
|
|
421
418
|
activityListener = (activityResult) -> {
|
|
422
419
|
Uri[] result = null;
|
|
423
420
|
if (activityResult.getResultCode() == Activity.RESULT_OK) {
|
|
@@ -485,6 +485,13 @@ public class Plugin {
|
|
|
485
485
|
return;
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
+
if (handle.getPluginAnnotation() == null) {
|
|
489
|
+
String error = String.format("Cannot request permissions for plugin %s without a @CapacitorPlugin annotation", handle.getId());
|
|
490
|
+
Logger.error(error);
|
|
491
|
+
call.reject(error);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
488
495
|
String[] permissions = getPermissionStringsForAliases(aliases);
|
|
489
496
|
|
|
490
497
|
if (permissions.length > 0) {
|
|
@@ -502,10 +509,14 @@ public class Plugin {
|
|
|
502
509
|
private String[] getPermissionStringsForAliases(@NonNull String[] aliases) {
|
|
503
510
|
CapacitorPlugin annotation = handle.getPluginAnnotation();
|
|
504
511
|
HashSet<String> perms = new HashSet<>();
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
512
|
+
if (annotation != null) {
|
|
513
|
+
for (Permission perm : annotation.permissions()) {
|
|
514
|
+
if (Arrays.asList(aliases).contains(perm.alias())) {
|
|
515
|
+
perms.addAll(Arrays.asList(perm.strings()));
|
|
516
|
+
}
|
|
508
517
|
}
|
|
518
|
+
} else {
|
|
519
|
+
Logger.warn(String.format("getPermissionStringsForAliases: missing @CapacitorPlugin annotation for plugin %s", handle.getId()));
|
|
509
520
|
}
|
|
510
521
|
|
|
511
522
|
return perms.toArray(new String[0]);
|
|
@@ -1003,7 +1014,8 @@ public class Plugin {
|
|
|
1003
1014
|
* Give the plugins a chance to take control when a URL is about to be loaded in the WebView.
|
|
1004
1015
|
* Returning true causes the WebView to abort loading the URL.
|
|
1005
1016
|
* Returning false causes the WebView to continue loading the URL.
|
|
1006
|
-
* Returning null will defer to the default Capacitor policy
|
|
1017
|
+
* Returning null will defer to the default Capacitor policy.
|
|
1018
|
+
* Not called for Capacitor's internal HTTP proxy path, which is always blocked.
|
|
1007
1019
|
*/
|
|
1008
1020
|
@SuppressWarnings("unused")
|
|
1009
1021
|
public Boolean shouldOverrideLoad(Uri url) {
|
|
@@ -185,6 +185,11 @@ public class WebViewLocalServer {
|
|
|
185
185
|
Uri loadingUrl = request.getUrl();
|
|
186
186
|
|
|
187
187
|
if (null != loadingUrl.getPath() && loadingUrl.getPath().startsWith(Bridge.CAPACITOR_HTTP_INTERCEPTOR_START)) {
|
|
188
|
+
// Only fetch/XHR should reach the proxy; a document would run remote content at the app origin.
|
|
189
|
+
boolean httpEnabled = bridge.getConfig().getPluginConfiguration("CapacitorHttp").getBoolean("enabled", false);
|
|
190
|
+
if (!httpEnabled || isDocumentRequest(request)) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
188
193
|
Logger.debug("Handling CapacitorHttp request: " + loadingUrl);
|
|
189
194
|
try {
|
|
190
195
|
return handleCapacitorHttpRequest(request);
|
|
@@ -210,6 +215,24 @@ public class WebViewLocalServer {
|
|
|
210
215
|
}
|
|
211
216
|
}
|
|
212
217
|
|
|
218
|
+
/** isForMainFrame() is false for an iframe and a fetch alike; only navigations send this header. */
|
|
219
|
+
private boolean isDocumentRequest(WebResourceRequest request) {
|
|
220
|
+
if (request.isForMainFrame()) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
Map<String, String> headers = request.getRequestHeaders();
|
|
224
|
+
if (headers == null) {
|
|
225
|
+
// The proxy needs the headers too, so the request fails there anyway.
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
for (String header : headers.keySet()) {
|
|
229
|
+
if ("Upgrade-Insecure-Requests".equalsIgnoreCase(header)) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
213
236
|
private boolean isLocalFile(Uri uri) {
|
|
214
237
|
String path = uri.getPath();
|
|
215
238
|
return path.startsWith(capacitorContentStart) || path.startsWith(capacitorFileStart);
|
|
@@ -333,6 +356,9 @@ public class WebViewLocalServer {
|
|
|
333
356
|
int responseCode = connection.getResponseCode();
|
|
334
357
|
String reasonPhrase = getReasonPhraseFromResponseCode(responseCode);
|
|
335
358
|
|
|
359
|
+
// Nothing should render this. If anything does, sandbox keeps it inert and off the app origin.
|
|
360
|
+
responseHeaders.put("Content-Security-Policy", "sandbox; frame-ancestors 'none'");
|
|
361
|
+
|
|
336
362
|
return new WebResourceResponse(mimeType, encoding, responseCode, reasonPhrase, responseHeaders, inputStream);
|
|
337
363
|
}
|
|
338
364
|
|
|
@@ -756,9 +782,7 @@ public class WebViewLocalServer {
|
|
|
756
782
|
}
|
|
757
783
|
}
|
|
758
784
|
|
|
759
|
-
|
|
760
|
-
* An InputStream wrapper that limits the number of bytes that can be read.
|
|
761
|
-
*/
|
|
785
|
+
// For L and above.
|
|
762
786
|
static class BoundedInputStream extends InputStream {
|
|
763
787
|
|
|
764
788
|
private final InputStream in;
|
|
@@ -192,7 +192,14 @@ public class MockCordovaWebViewImpl implements CordovaWebView {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
public void triggerDocumentEvent(final String eventName) {
|
|
195
|
-
|
|
195
|
+
// Pages served without the bridge (e.g. server.errorPath) have no window.Capacitor; drop the event rather than throw
|
|
196
|
+
eval(
|
|
197
|
+
"if (window.Capacitor != null && typeof window.Capacitor.triggerEvent === 'function') {" +
|
|
198
|
+
"window.Capacitor.triggerEvent('" +
|
|
199
|
+
eventName +
|
|
200
|
+
"', 'document'); }",
|
|
201
|
+
(s) -> {}
|
|
202
|
+
);
|
|
196
203
|
}
|
|
197
204
|
|
|
198
205
|
@Override
|
|
@@ -9,7 +9,6 @@ import android.os.Build;
|
|
|
9
9
|
import android.util.TypedValue;
|
|
10
10
|
import android.view.View;
|
|
11
11
|
import android.view.Window;
|
|
12
|
-
import android.webkit.JavascriptInterface;
|
|
13
12
|
import android.webkit.WebView;
|
|
14
13
|
import androidx.core.graphics.Insets;
|
|
15
14
|
import androidx.core.view.ViewCompat;
|
|
@@ -37,6 +36,7 @@ public class SystemBars extends Plugin {
|
|
|
37
36
|
// TODO: In Cap 9, add an additional option "full"
|
|
38
37
|
static final String INSETS_HANDLING_CSS = "css";
|
|
39
38
|
static final String INSETS_HANDLING_DISABLE = "disable";
|
|
39
|
+
static final String INSETS_HANDLING_NATIVE = "native";
|
|
40
40
|
|
|
41
41
|
// https://issues.chromium.org/issues/40699457
|
|
42
42
|
private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_FIX = 140;
|
|
@@ -62,11 +62,22 @@ public class SystemBars extends Plugin {
|
|
|
62
62
|
private String currentStatusBarStyle = STYLE_DEFAULT;
|
|
63
63
|
private String currentGestureBarStyle = STYLE_DEFAULT;
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
// Declare variable at this scope to help prevent adding multiple listeners.
|
|
66
|
+
private WebViewListener webViewListener;
|
|
67
|
+
|
|
68
|
+
private void warnAboutUnsupportedConfigurationValues() {
|
|
69
|
+
boolean keyboardResizeOnFullScreen = bridge.getConfig().getPluginConfiguration("Keyboard").getBoolean("resizeOnFullScreen", false);
|
|
70
|
+
|
|
71
|
+
if (!INSETS_HANDLING_DISABLE.equals(insetsHandling) && keyboardResizeOnFullScreen) {
|
|
72
|
+
Logger.warn(
|
|
73
|
+
"SystemBars",
|
|
74
|
+
"You should omit `Keyboard.resizeOnFullScreen` in your `capacitor.config.json`. Other values can lead to unexpected behavior."
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
66
78
|
|
|
67
79
|
@Override
|
|
68
80
|
public void load() {
|
|
69
|
-
getBridge().getWebView().addJavascriptInterface(this, "CapacitorSystemBarsAndroidInterface");
|
|
70
81
|
super.load();
|
|
71
82
|
|
|
72
83
|
initSystemBars();
|
|
@@ -76,23 +87,27 @@ public class SystemBars extends Plugin {
|
|
|
76
87
|
protected void handleOnStart() {
|
|
77
88
|
super.handleOnStart();
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
if (INSETS_HANDLING_DISABLE.equals(insetsHandling)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (webViewListener == null) {
|
|
95
|
+
webViewListener = new WebViewListener() {
|
|
81
96
|
@Override
|
|
82
97
|
public void onPageCommitVisible(WebView view, String url) {
|
|
83
98
|
super.onPageCommitVisible(view, url);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
}
|
|
99
|
+
bridge
|
|
100
|
+
.getWebView()
|
|
101
|
+
.evaluateJavascript(viewportMetaJSFunction, (res) -> {
|
|
102
|
+
hasViewportCover = "true".equals(res);
|
|
103
|
+
|
|
104
|
+
// Request new execution tree of `setOnApplyWindowInsetsListener`
|
|
105
|
+
bridge.getWebView().requestApplyInsets();
|
|
106
|
+
});
|
|
93
107
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
108
|
+
};
|
|
109
|
+
this.getBridge().addWebViewListener(webViewListener);
|
|
110
|
+
}
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
@Override
|
|
@@ -104,11 +119,20 @@ public class SystemBars extends Plugin {
|
|
|
104
119
|
}
|
|
105
120
|
|
|
106
121
|
private void initSystemBars() {
|
|
122
|
+
// If you already know what the value of the `viewport-fit=` meta tag is going to be,
|
|
123
|
+
// passing it here through `initialViewportFitValueHint` can help prevent layout shifting.
|
|
124
|
+
String configuredInitialViewportFitValueHint = getConfig().getString("initialViewportFitValueHint", "");
|
|
125
|
+
hasViewportCover = "cover".equals(configuredInitialViewportFitValueHint);
|
|
126
|
+
|
|
107
127
|
String style = getConfig().getString("style", STYLE_DEFAULT).toUpperCase(Locale.US);
|
|
108
128
|
boolean hidden = getConfig().getBoolean("hidden", false);
|
|
109
129
|
|
|
110
130
|
String configuredInsetsHandling = getConfig().getString("insetsHandling", INSETS_HANDLING_CSS);
|
|
111
|
-
if (
|
|
131
|
+
if (
|
|
132
|
+
INSETS_HANDLING_CSS.equals(configuredInsetsHandling) ||
|
|
133
|
+
INSETS_HANDLING_DISABLE.equals(configuredInsetsHandling) ||
|
|
134
|
+
INSETS_HANDLING_NATIVE.equals(configuredInsetsHandling)
|
|
135
|
+
) {
|
|
112
136
|
insetsHandling = configuredInsetsHandling;
|
|
113
137
|
} else {
|
|
114
138
|
Logger.warn(
|
|
@@ -118,8 +142,9 @@ public class SystemBars extends Plugin {
|
|
|
118
142
|
insetsHandling = INSETS_HANDLING_CSS;
|
|
119
143
|
}
|
|
120
144
|
|
|
145
|
+
warnAboutUnsupportedConfigurationValues();
|
|
146
|
+
|
|
121
147
|
initWindowInsetsListener();
|
|
122
|
-
initSafeAreaCSSVariables();
|
|
123
148
|
|
|
124
149
|
getBridge().executeOnMainThread(() -> {
|
|
125
150
|
Window window = getActivity().getWindow();
|
|
@@ -172,96 +197,14 @@ public class SystemBars extends Plugin {
|
|
|
172
197
|
call.resolve();
|
|
173
198
|
}
|
|
174
199
|
|
|
175
|
-
@Override
|
|
176
|
-
protected void handleOnResume() {
|
|
177
|
-
super.handleOnResume();
|
|
178
|
-
getBridge().executeOnMainThread(() -> {
|
|
179
|
-
Window window = getActivity().getWindow();
|
|
180
|
-
WindowCompat.setDecorFitsSystemWindows(window, false);
|
|
181
|
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
|
182
|
-
window.setNavigationBarColor(android.graphics.Color.TRANSPARENT);
|
|
183
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
184
|
-
window.setNavigationBarContrastEnforced(false);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
setStyle(currentGestureBarStyle, BAR_GESTURE_BAR);
|
|
188
|
-
setStyle(currentStatusBarStyle, BAR_STATUS_BAR);
|
|
189
|
-
ViewCompat.requestApplyInsets((View) getBridge().getWebView().getParent());
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
@JavascriptInterface
|
|
194
|
-
public void onDOMReady() {
|
|
195
|
-
if (INSETS_HANDLING_CSS.equals(insetsHandling)) {
|
|
196
|
-
getActivity().runOnUiThread(() -> {
|
|
197
|
-
this.bridge.getWebView().evaluateJavascript(viewportMetaJSFunction, (res) -> {
|
|
198
|
-
hasViewportCover = res.equals("true");
|
|
199
|
-
|
|
200
|
-
getBridge().getWebView().requestApplyInsets();
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
private Insets calcSafeAreaInsets(WindowInsetsCompat insets) {
|
|
207
|
-
Insets safeArea = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
|
|
208
|
-
|
|
209
|
-
int bottom = safeArea.bottom;
|
|
210
|
-
|
|
211
|
-
if (bottom == 0 && Build.VERSION.SDK_INT < Build.VERSION_CODES.R && safeArea.left == 0 && safeArea.right == 0) {
|
|
212
|
-
// skip if nav bar is on a side (landscape)
|
|
213
|
-
bottom = getNavBarHeightFromResources();
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
boolean imeVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
|
217
|
-
? insets.isVisible(WindowInsetsCompat.Type.ime())
|
|
218
|
-
: insets.getInsets(WindowInsetsCompat.Type.ime()).bottom > 0;
|
|
219
|
-
|
|
220
|
-
if (imeVisible) {
|
|
221
|
-
return Insets.of(safeArea.left, safeArea.top, safeArea.right, 0);
|
|
222
|
-
}
|
|
223
|
-
return Insets.of(safeArea.left, safeArea.top, safeArea.right, bottom);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Only reached on API < 30 when the window reports no inset on any edge, so the
|
|
227
|
-
// choice is this approximation or a hardcoded 0. Lint rejects the internal
|
|
228
|
-
// resource because OEMs may change it; a wrong height still beats no inset.
|
|
229
|
-
@SuppressLint({ "InternalInsetResource", "DiscouragedApi" })
|
|
230
|
-
private int getNavBarHeightFromResources() {
|
|
231
|
-
if (!navBarVisible) return 0;
|
|
232
|
-
android.content.res.Resources res = getActivity().getResources();
|
|
233
|
-
int heightId = res.getIdentifier("navigation_bar_height", "dimen", "android");
|
|
234
|
-
return heightId > 0 ? res.getDimensionPixelSize(heightId) : 0;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
private void initSafeAreaCSSVariables() {
|
|
238
|
-
if (INSETS_HANDLING_CSS.equals(insetsHandling)) {
|
|
239
|
-
WindowInsetsCompat insets;
|
|
240
|
-
|
|
241
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
|
242
|
-
View v = (View) this.getBridge().getWebView().getParent();
|
|
243
|
-
insets = ViewCompat.getRootWindowInsets(v);
|
|
244
|
-
} else {
|
|
245
|
-
insets = WindowInsetsCompat.CONSUMED;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (insets != null) {
|
|
249
|
-
Insets safeAreaInsets = calcSafeAreaInsets(insets);
|
|
250
|
-
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
200
|
private void initWindowInsetsListener() {
|
|
256
201
|
if (INSETS_HANDLING_DISABLE.equals(insetsHandling)) {
|
|
257
202
|
return;
|
|
258
203
|
}
|
|
259
204
|
|
|
260
|
-
|
|
261
|
-
// getRootWindowInsets() bypasses AppCompat's intermediate view consuming the bottom inset on API < 30
|
|
262
|
-
WindowInsetsCompat rawInsets = ViewCompat.getRootWindowInsets(v);
|
|
263
|
-
WindowInsetsCompat safeAreaSource = (rawInsets != null) ? rawInsets : insets;
|
|
205
|
+
View view = getActivity().getWindow().getDecorView();
|
|
264
206
|
|
|
207
|
+
ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> {
|
|
265
208
|
boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover;
|
|
266
209
|
|
|
267
210
|
Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
|
|
@@ -272,10 +215,7 @@ public class SystemBars extends Plugin {
|
|
|
272
215
|
// We need to correct for a possible shown IME
|
|
273
216
|
v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
|
|
274
217
|
|
|
275
|
-
|
|
276
|
-
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
|
|
277
|
-
|
|
278
|
-
return new WindowInsetsCompat.Builder(insets)
|
|
218
|
+
WindowInsetsCompat newInsets = new WindowInsetsCompat.Builder(insets)
|
|
279
219
|
.setInsets(
|
|
280
220
|
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(),
|
|
281
221
|
Insets.of(
|
|
@@ -286,9 +226,19 @@ public class SystemBars extends Plugin {
|
|
|
286
226
|
)
|
|
287
227
|
)
|
|
288
228
|
.build();
|
|
229
|
+
|
|
230
|
+
injectSafeAreaCSS(newInsets);
|
|
231
|
+
|
|
232
|
+
return newInsets;
|
|
289
233
|
}
|
|
290
234
|
|
|
291
|
-
|
|
235
|
+
// We need to correct for a possible shown IME
|
|
236
|
+
v.setPadding(
|
|
237
|
+
systemBarsInsets.left,
|
|
238
|
+
systemBarsInsets.top,
|
|
239
|
+
systemBarsInsets.right,
|
|
240
|
+
keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom
|
|
241
|
+
);
|
|
292
242
|
|
|
293
243
|
// Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets
|
|
294
244
|
// So we have to explicitly set insets to `0`
|
|
@@ -297,20 +247,28 @@ public class SystemBars extends Plugin {
|
|
|
297
247
|
.setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0))
|
|
298
248
|
.build();
|
|
299
249
|
|
|
300
|
-
|
|
301
|
-
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
|
|
250
|
+
injectSafeAreaCSS(insets);
|
|
302
251
|
|
|
303
252
|
return newInsets;
|
|
304
253
|
});
|
|
305
254
|
}
|
|
306
255
|
|
|
307
|
-
private void injectSafeAreaCSS(
|
|
256
|
+
private void injectSafeAreaCSS(WindowInsetsCompat insets) {
|
|
257
|
+
if (!INSETS_HANDLING_CSS.equals(insetsHandling)) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
|
|
262
|
+
boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
|
|
263
|
+
|
|
308
264
|
// Convert pixels to density-independent pixels
|
|
309
265
|
float density = getActivity().getResources().getDisplayMetrics().density;
|
|
310
|
-
float topPx = top / density;
|
|
311
|
-
float rightPx = right / density;
|
|
312
|
-
|
|
313
|
-
|
|
266
|
+
float topPx = systemBarsInsets.top / density;
|
|
267
|
+
float rightPx = systemBarsInsets.right / density;
|
|
268
|
+
// For native insets the value gets automatically corrected when the IME is visible (in newer WebView versions),
|
|
269
|
+
// but for these injected values we have to handle that manually (for all WebView versions).
|
|
270
|
+
float bottomPx = (keyboardVisible ? 0 : systemBarsInsets.bottom) / density;
|
|
271
|
+
float leftPx = systemBarsInsets.left / density;
|
|
314
272
|
|
|
315
273
|
// Execute JavaScript to inject the CSS
|
|
316
274
|
getBridge().executeOnMainThread(() -> {
|
|
@@ -364,24 +322,20 @@ public class SystemBars extends Plugin {
|
|
|
364
322
|
if (hide) {
|
|
365
323
|
if (bar.isEmpty()) {
|
|
366
324
|
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.systemBars());
|
|
367
|
-
navBarVisible = false;
|
|
368
325
|
} else if (bar.equals(BAR_STATUS_BAR)) {
|
|
369
326
|
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.statusBars());
|
|
370
327
|
} else if (bar.equals(BAR_GESTURE_BAR)) {
|
|
371
328
|
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.navigationBars());
|
|
372
|
-
navBarVisible = false;
|
|
373
329
|
}
|
|
374
330
|
return;
|
|
375
331
|
}
|
|
376
332
|
|
|
377
333
|
if (bar.isEmpty()) {
|
|
378
334
|
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.systemBars());
|
|
379
|
-
navBarVisible = true;
|
|
380
335
|
} else if (bar.equals(BAR_STATUS_BAR)) {
|
|
381
336
|
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.statusBars());
|
|
382
337
|
} else if (bar.equals(BAR_GESTURE_BAR)) {
|
|
383
338
|
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.navigationBars());
|
|
384
|
-
navBarVisible = true;
|
|
385
339
|
}
|
|
386
340
|
}
|
|
387
341
|
|
package/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java
CHANGED
|
@@ -499,9 +499,12 @@ public class CapacitorHttpUrlConnection implements ICapacitorHttpUrlConnection {
|
|
|
499
499
|
}
|
|
500
500
|
|
|
501
501
|
public void setSSLSocketFactory(Bridge bridge) {
|
|
502
|
-
// Attach SSL Certificates if
|
|
502
|
+
// Attach SSL Certificates if an SSL Pinning plugin is available
|
|
503
503
|
try {
|
|
504
|
-
Class<?> sslPinningImpl =
|
|
504
|
+
Class<?> sslPinningImpl = findSSLPinningClass();
|
|
505
|
+
if (sslPinningImpl == null) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
505
508
|
Method method = sslPinningImpl.getDeclaredMethod("getSSLSocketFactory", Bridge.class);
|
|
506
509
|
SSLSocketFactory sslSocketFactory = (SSLSocketFactory) method.invoke(
|
|
507
510
|
sslPinningImpl.getDeclaredConstructor().newInstance(),
|
|
@@ -512,4 +515,13 @@ public class CapacitorHttpUrlConnection implements ICapacitorHttpUrlConnection {
|
|
|
512
515
|
}
|
|
513
516
|
} catch (Exception ignored) {}
|
|
514
517
|
}
|
|
518
|
+
|
|
519
|
+
private static Class<?> findSSLPinningClass() {
|
|
520
|
+
for (String className : new String[] { "io.ionic.sslpinning.SSLPinning", "com.getcapacitor.community.sslpinning.SSLPinning" }) {
|
|
521
|
+
try {
|
|
522
|
+
return Class.forName(className);
|
|
523
|
+
} catch (ClassNotFoundException ignored) {}
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
515
527
|
}
|
|
@@ -450,7 +450,10 @@ public class HttpRequestHandler {
|
|
|
450
450
|
|
|
451
451
|
public static Boolean isDomainExcludedFromSSL(Bridge bridge, URL url) {
|
|
452
452
|
try {
|
|
453
|
-
Class<?> sslPinningImpl =
|
|
453
|
+
Class<?> sslPinningImpl = findSSLPinningClass();
|
|
454
|
+
if (sslPinningImpl == null) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
454
457
|
Method method = sslPinningImpl.getDeclaredMethod("isDomainExcluded", Bridge.class, URL.class);
|
|
455
458
|
return (Boolean) method.invoke(sslPinningImpl.getDeclaredConstructor().newInstance(), bridge, url);
|
|
456
459
|
} catch (Exception ignored) {
|
|
@@ -458,6 +461,15 @@ public class HttpRequestHandler {
|
|
|
458
461
|
}
|
|
459
462
|
}
|
|
460
463
|
|
|
464
|
+
private static Class<?> findSSLPinningClass() {
|
|
465
|
+
for (String className : new String[] { "io.ionic.sslpinning.SSLPinning", "com.getcapacitor.community.sslpinning.SSLPinning" }) {
|
|
466
|
+
try {
|
|
467
|
+
return Class.forName(className);
|
|
468
|
+
} catch (ClassNotFoundException ignored) {}
|
|
469
|
+
}
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
|
|
461
473
|
@FunctionalInterface
|
|
462
474
|
public interface ProgressEmitter {
|
|
463
475
|
void emit(Integer bytes, Integer contentLength);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capacitor-plus/android",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.6.1",
|
|
4
4
|
"description": "Capacitor+: Enhanced Capacitor with automated upstream sync - Cross-platform apps with JavaScript and the web",
|
|
5
5
|
"homepage": "https://capgo.app/docs/plugins/capacitor-plus/",
|
|
6
6
|
"author": "Capgo Team <support@capgo.app> (https://capgo.app)",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"verify": "./gradlew clean lint build test -b capacitor/build.gradle"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@capacitor-plus/core": "^8.
|
|
27
|
-
"@capacitor/core": "^8.
|
|
26
|
+
"@capacitor-plus/core": "^8.6.0",
|
|
27
|
+
"@capacitor/core": "^8.6.0"
|
|
28
28
|
},
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public"
|