@capacitor-plus/android 8.3.10 → 8.3.11

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.
@@ -44,6 +44,33 @@ var nativeBridge = (function (exports) {
44
44
  reader.onerror = reject;
45
45
  reader.readAsBinaryString(file);
46
46
  });
47
+ const readArrayBufferAsBase64 = (arrayBuffer) => {
48
+ const bytes = new Uint8Array(arrayBuffer);
49
+ const chunkSize = 0x8000;
50
+ let binary = '';
51
+ for (let i = 0; i < bytes.length; i += chunkSize) {
52
+ binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
53
+ }
54
+ return btoa(binary);
55
+ };
56
+ const readBlobAsBase64 = (blob) => new Promise((resolve, reject) => {
57
+ const reader = new FileReader();
58
+ reader.onload = () => {
59
+ const result = reader.result;
60
+ resolve(result.indexOf(',') >= 0 ? result.split(',')[1] : result);
61
+ };
62
+ reader.onerror = reject;
63
+ reader.readAsDataURL(blob);
64
+ });
65
+ const readDataViewAsBase64 = (dataView) => readArrayBufferAsBase64(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength));
66
+ const base64StringToArrayBuffer = (base64) => {
67
+ const binary = atob(base64);
68
+ const bytes = new Uint8Array(binary.length);
69
+ for (let i = 0; i < binary.length; i++) {
70
+ bytes[i] = binary.charCodeAt(i);
71
+ }
72
+ return bytes.buffer;
73
+ };
47
74
  const convertFormData = async (formData) => {
48
75
  const newFormData = [];
49
76
  for (const pair of formData.entries()) {
@@ -65,9 +92,38 @@ var nativeBridge = (function (exports) {
65
92
  return newFormData;
66
93
  };
67
94
  const convertBody = async (body, contentType) => {
68
- if (body instanceof ReadableStream || body instanceof Uint8Array) {
95
+ if (body instanceof File) {
96
+ const fileData = await readFileAsBase64(body);
97
+ return {
98
+ data: fileData,
99
+ type: 'file',
100
+ headers: { 'Content-Type': body.type },
101
+ };
102
+ }
103
+ else if (body instanceof Blob) {
104
+ return {
105
+ data: await readBlobAsBase64(body),
106
+ type: 'binary',
107
+ headers: { 'Content-Type': contentType || body.type || 'application/octet-stream' },
108
+ };
109
+ }
110
+ else if (body instanceof ArrayBuffer) {
111
+ return {
112
+ data: readArrayBufferAsBase64(body),
113
+ type: 'binary',
114
+ headers: { 'Content-Type': contentType || 'application/octet-stream' },
115
+ };
116
+ }
117
+ else if (ArrayBuffer.isView(body)) {
118
+ return {
119
+ data: readDataViewAsBase64(body),
120
+ type: 'binary',
121
+ headers: { 'Content-Type': contentType || 'application/octet-stream' },
122
+ };
123
+ }
124
+ if ((typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) || body instanceof Uint8Array) {
69
125
  let encodedData;
70
- if (body instanceof ReadableStream) {
126
+ if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) {
71
127
  const reader = body.getReader();
72
128
  const chunks = [];
73
129
  while (true) {
@@ -87,7 +143,9 @@ var nativeBridge = (function (exports) {
87
143
  else {
88
144
  encodedData = body;
89
145
  }
90
- let data = new TextDecoder().decode(encodedData);
146
+ let data = (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('image')) || contentType === 'application/octet-stream'
147
+ ? readDataViewAsBase64(encodedData)
148
+ : new TextDecoder().decode(encodedData);
91
149
  let type;
92
150
  if (contentType === 'application/json') {
93
151
  try {
@@ -102,7 +160,7 @@ var nativeBridge = (function (exports) {
102
160
  type = 'formData';
103
161
  }
104
162
  else if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('image')) {
105
- type = 'image';
163
+ type = 'binary';
106
164
  }
107
165
  else if (contentType === 'application/octet-stream') {
108
166
  type = 'binary';
@@ -128,14 +186,6 @@ var nativeBridge = (function (exports) {
128
186
  type: 'formData',
129
187
  };
130
188
  }
131
- else if (body instanceof Blob) {
132
- const fileData = await readFileAsBase64(body);
133
- return {
134
- data: fileData,
135
- type: 'file',
136
- headers: { 'Content-Type': body.type },
137
- };
138
- }
139
189
  return { data: body, type: 'json' };
140
190
  };
141
191
  const CAPACITOR_HTTP_INTERCEPTOR = '/_capacitor_http_interceptor_';
@@ -535,15 +585,15 @@ var nativeBridge = (function (exports) {
535
585
  data: requestData,
536
586
  dataType: type,
537
587
  headers: nativeHeaders,
588
+ responseType: 'arraybuffer',
538
589
  });
539
590
  const contentType = nativeResponse.headers['Content-Type'] || nativeResponse.headers['content-type'];
540
- let data = (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/json'))
541
- ? JSON.stringify(nativeResponse.data)
542
- : nativeResponse.data;
543
591
  // use null data for 204 No Content HTTP response
544
- if (nativeResponse.status === 204) {
545
- data = null;
546
- }
592
+ const data = nativeResponse.status === 204
593
+ ? null
594
+ : (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/json'))
595
+ ? JSON.stringify(nativeResponse.data)
596
+ : base64StringToArrayBuffer(nativeResponse.data);
547
597
  // intercept & parse response before returning
548
598
  const response = new Response(data, {
549
599
  headers: nativeResponse.headers,
@@ -265,8 +265,15 @@ public class Bridge {
265
265
  JSInjector injector = getJSInjector();
266
266
  if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
267
267
  String allowedOrigin = Uri.parse(appUrl).buildUpon().path(null).fragment(null).clearQuery().build().toString();
268
+ final String finalAllowedOrigin = allowedOrigin;
269
+ Set<String> allowedOrigins = new HashSet<String>() {
270
+ {
271
+ add(finalAllowedOrigin);
272
+ addAll(allowedOriginRules);
273
+ }
274
+ };
268
275
  try {
269
- WebViewCompat.addDocumentStartJavaScript(webView, injector.getScriptString(), Collections.singleton(allowedOrigin));
276
+ WebViewCompat.addDocumentStartJavaScript(webView, injector.getScriptString(), allowedOrigins);
270
277
  injector = null;
271
278
  } catch (IllegalArgumentException ex) {
272
279
  Logger.warn("Invalid url, using fallback");
@@ -26,8 +26,7 @@ public class MessageHandler {
26
26
  if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER) && !bridge.getConfig().isUsingLegacyBridge()) {
27
27
  WebViewCompat.WebMessageListener capListener = (view, message, sourceOrigin, isMainFrame, replyProxy) -> {
28
28
  if (isMainFrame) {
29
- postMessage(message.getData());
30
- javaScriptReplyProxy = replyProxy;
29
+ onModernBridgeMessage(message.getData(), replyProxy);
31
30
  } else {
32
31
  Logger.warn("Plugin execution is allowed in Main Frame only");
33
32
  }
@@ -42,6 +41,11 @@ public class MessageHandler {
42
41
  }
43
42
  }
44
43
 
44
+ void onModernBridgeMessage(String data, JavaScriptReplyProxy replyProxy) {
45
+ javaScriptReplyProxy = replyProxy;
46
+ postMessage(data);
47
+ }
48
+
45
49
  /**
46
50
  * The main message handler that will be called from JavaScript
47
51
  * to send a message to the native bridge.
@@ -348,16 +348,22 @@ public class WebViewLocalServer {
348
348
  Map<String, String> tempResponseHeaders = handler.buildDefaultResponseHeaders();
349
349
  int statusCode = 206;
350
350
  try {
351
- int totalRange = responseStream.available();
351
+ int totalSize = responseStream.available();
352
352
  String[] parts = rangeString.split("=");
353
353
  String[] streamParts = parts[1].split("-");
354
- String fromRange = streamParts[0];
355
- int range = totalRange - 1;
356
- if (streamParts.length > 1) {
357
- range = Integer.parseInt(streamParts[1]);
354
+ int fromRange = Integer.parseInt(streamParts[0]);
355
+ int endRange = totalSize - 1;
356
+ if (streamParts.length > 1 && !streamParts[1].isEmpty()) {
357
+ endRange = Integer.parseInt(streamParts[1]);
358
358
  }
359
+
360
+ // Truncate the stream at the end of the requested range.
361
+ // Somewhere in the request pipeline, the stream gets automatically
362
+ // seeked to the correct start position, so we don't need to skip to the fromRange position here.
363
+ responseStream = new BoundedInputStream(responseStream, endRange + 1);
364
+
359
365
  tempResponseHeaders.put("Accept-Ranges", "bytes");
360
- tempResponseHeaders.put("Content-Range", "bytes " + fromRange + "-" + range + "/" + totalRange);
366
+ tempResponseHeaders.put("Content-Range", "bytes " + fromRange + "-" + endRange + "/" + totalSize);
361
367
  } catch (IOException e) {
362
368
  statusCode = 404;
363
369
  }
@@ -750,6 +756,48 @@ public class WebViewLocalServer {
750
756
  }
751
757
  }
752
758
 
759
+ /**
760
+ * An InputStream wrapper that limits the number of bytes that can be read.
761
+ */
762
+ static class BoundedInputStream extends InputStream {
763
+
764
+ private final InputStream in;
765
+ private long remaining;
766
+
767
+ public BoundedInputStream(InputStream in, long limit) {
768
+ this.in = in;
769
+ this.remaining = limit;
770
+ }
771
+
772
+ @Override
773
+ public int available() throws IOException {
774
+ int available = in.available();
775
+ return (int) Math.min(available, remaining);
776
+ }
777
+
778
+ @Override
779
+ public int read() throws IOException {
780
+ if (remaining <= 0) return -1;
781
+ int result = in.read();
782
+ if (result != -1) remaining--;
783
+ return result;
784
+ }
785
+
786
+ @Override
787
+ public int read(byte[] b, int off, int len) throws IOException {
788
+ if (remaining <= 0) return -1;
789
+ int toRead = (int) Math.min(len, remaining);
790
+ int result = in.read(b, off, toRead);
791
+ if (result > 0) remaining -= result;
792
+ return result;
793
+ }
794
+
795
+ @Override
796
+ public void close() throws IOException {
797
+ in.close();
798
+ }
799
+ }
800
+
753
801
  // For L and above.
754
802
  private static class LollipopLazyInputStream extends LazyInputStream {
755
803
 
@@ -1,5 +1,6 @@
1
1
  package com.getcapacitor.plugin;
2
2
 
3
+ import android.annotation.SuppressLint;
3
4
  import android.content.Context;
4
5
  import android.content.pm.PackageInfo;
5
6
  import android.content.res.Configuration;
@@ -59,6 +60,8 @@ public class SystemBars extends Plugin {
59
60
  private String currentStatusBarStyle = STYLE_DEFAULT;
60
61
  private String currentGestureBarStyle = STYLE_DEFAULT;
61
62
 
63
+ private boolean navBarVisible = true;
64
+
62
65
  @Override
63
66
  public void load() {
64
67
  getBridge().getWebView().addJavascriptInterface(this, "CapacitorSystemBarsAndroidInterface");
@@ -76,7 +79,15 @@ public class SystemBars extends Plugin {
76
79
  @Override
77
80
  public void onPageCommitVisible(WebView view, String url) {
78
81
  super.onPageCommitVisible(view, url);
79
- getBridge().getWebView().requestApplyInsets();
82
+ View parentView = (View) getBridge().getWebView().getParent();
83
+ ViewCompat.requestApplyInsets(parentView);
84
+ if (insetHandlingEnabled) {
85
+ WindowInsetsCompat rootInsets = ViewCompat.getRootWindowInsets(parentView);
86
+ if (rootInsets != null) {
87
+ Insets safeArea = calcSafeAreaInsets(rootInsets);
88
+ injectSafeAreaCSS(safeArea.top, safeArea.right, safeArea.bottom, safeArea.left);
89
+ }
90
+ }
80
91
  }
81
92
  }
82
93
  );
@@ -103,8 +114,17 @@ public class SystemBars extends Plugin {
103
114
  initSafeAreaCSSVariables();
104
115
 
105
116
  getBridge().executeOnMainThread(() -> {
117
+ Window window = getActivity().getWindow();
118
+ WindowCompat.setDecorFitsSystemWindows(window, false);
119
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
120
+ window.setNavigationBarColor(android.graphics.Color.TRANSPARENT);
121
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
122
+ window.setNavigationBarContrastEnforced(false);
123
+ }
124
+ }
106
125
  setStyle(style, "");
107
126
  setHidden(hidden, "");
127
+ ViewCompat.requestApplyInsets((View) getBridge().getWebView().getParent());
108
128
  });
109
129
  }
110
130
 
@@ -144,43 +164,82 @@ public class SystemBars extends Plugin {
144
164
  call.resolve();
145
165
  }
146
166
 
167
+ @Override
168
+ protected void handleOnResume() {
169
+ super.handleOnResume();
170
+ getBridge().executeOnMainThread(() -> {
171
+ Window window = getActivity().getWindow();
172
+ WindowCompat.setDecorFitsSystemWindows(window, false);
173
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
174
+ window.setNavigationBarColor(android.graphics.Color.TRANSPARENT);
175
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
176
+ window.setNavigationBarContrastEnforced(false);
177
+ }
178
+ }
179
+ setStyle(currentGestureBarStyle, BAR_GESTURE_BAR);
180
+ setStyle(currentStatusBarStyle, BAR_STATUS_BAR);
181
+ ViewCompat.requestApplyInsets((View) getBridge().getWebView().getParent());
182
+ });
183
+ }
184
+
147
185
  @JavascriptInterface
148
186
  public void onDOMReady() {
149
187
  getActivity().runOnUiThread(() -> {
150
188
  this.bridge.getWebView().evaluateJavascript(viewportMetaJSFunction, (res) -> {
151
189
  hasViewportCover = res.equals("true");
152
-
153
- getBridge().getWebView().requestApplyInsets();
190
+ ViewCompat.requestApplyInsets((View) getBridge().getWebView().getParent());
154
191
  });
155
192
  });
156
193
  }
157
194
 
158
195
  private Insets calcSafeAreaInsets(WindowInsetsCompat insets) {
159
196
  Insets safeArea = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
160
- if (insets.isVisible(WindowInsetsCompat.Type.ime())) {
197
+
198
+ int bottom = safeArea.bottom;
199
+
200
+ if (bottom == 0 && Build.VERSION.SDK_INT < Build.VERSION_CODES.R && safeArea.left == 0 && safeArea.right == 0) {
201
+ // skip if nav bar is on a side (landscape)
202
+ bottom = getNavBarHeightFromResources();
203
+ }
204
+
205
+ boolean imeVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
206
+ ? insets.isVisible(WindowInsetsCompat.Type.ime())
207
+ : insets.getInsets(WindowInsetsCompat.Type.ime()).bottom > 0;
208
+
209
+ if (imeVisible) {
161
210
  return Insets.of(safeArea.left, safeArea.top, safeArea.right, 0);
162
211
  }
163
- return Insets.of(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom);
212
+ return Insets.of(safeArea.left, safeArea.top, safeArea.right, bottom);
164
213
  }
165
214
 
166
- private void initSafeAreaCSSVariables() {
167
- WindowInsetsCompat insets;
215
+ // Only reached on API < 30 when the window reports no inset on any edge, so the
216
+ // choice is this approximation or a hardcoded 0. Lint rejects the internal
217
+ // resource because OEMs may change it; a wrong height still beats no inset.
218
+ @SuppressLint({ "InternalInsetResource", "DiscouragedApi" })
219
+ private int getNavBarHeightFromResources() {
220
+ if (!navBarVisible) return 0;
221
+ android.content.res.Resources res = getActivity().getResources();
222
+ int heightId = res.getIdentifier("navigation_bar_height", "dimen", "android");
223
+ return heightId > 0 ? res.getDimensionPixelSize(heightId) : 0;
224
+ }
168
225
 
169
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
226
+ private void initSafeAreaCSSVariables() {
227
+ if (insetHandlingEnabled) {
170
228
  View v = (View) this.getBridge().getWebView().getParent();
171
- insets = ViewCompat.getRootWindowInsets(v);
172
- } else {
173
- insets = WindowInsetsCompat.CONSUMED;
174
- }
175
-
176
- if (insets != null) {
177
- Insets safeAreaInsets = calcSafeAreaInsets(insets);
178
- injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
229
+ WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(v);
230
+ if (insets != null) {
231
+ Insets safeAreaInsets = calcSafeAreaInsets(insets);
232
+ injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
233
+ }
179
234
  }
180
235
  }
181
236
 
182
237
  private void initWindowInsetsListener() {
183
238
  ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> {
239
+ // getRootWindowInsets() bypasses AppCompat's intermediate view consuming the bottom inset on API < 30
240
+ WindowInsetsCompat rawInsets = ViewCompat.getRootWindowInsets(v);
241
+ WindowInsetsCompat safeAreaSource = (rawInsets != null) ? rawInsets : insets;
242
+
184
243
  boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover;
185
244
 
186
245
  Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
@@ -191,8 +250,10 @@ public class SystemBars extends Plugin {
191
250
  // We need to correct for a possible shown IME
192
251
  v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
193
252
 
194
- Insets safeAreaInsets = calcSafeAreaInsets(insets);
195
- injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
253
+ if (hasViewportCover && insetHandlingEnabled) {
254
+ Insets safeAreaInsets = calcSafeAreaInsets(safeAreaSource);
255
+ injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
256
+ }
196
257
 
197
258
  return new WindowInsetsCompat.Builder(insets)
198
259
  .setInsets(
@@ -207,15 +268,7 @@ public class SystemBars extends Plugin {
207
268
  .build();
208
269
  }
209
270
 
210
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
211
- // We need to correct for a possible shown IME
212
- v.setPadding(
213
- systemBarsInsets.left,
214
- systemBarsInsets.top,
215
- systemBarsInsets.right,
216
- keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom
217
- );
218
- }
271
+ v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
219
272
 
220
273
  // Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets
221
274
  // So we have to explicitly set insets to `0`
@@ -224,8 +277,10 @@ public class SystemBars extends Plugin {
224
277
  .setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0))
225
278
  .build();
226
279
 
227
- Insets safeAreaInsets = calcSafeAreaInsets(newInsets);
228
- injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
280
+ if (insetHandlingEnabled) {
281
+ Insets safeAreaInsets = calcSafeAreaInsets(safeAreaSource);
282
+ injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
283
+ }
229
284
 
230
285
  return newInsets;
231
286
  });
@@ -264,6 +319,7 @@ public class SystemBars extends Plugin {
264
319
  }
265
320
 
266
321
  private void setStyle(String style, String bar) {
322
+ String requestedStyle = style;
267
323
  if (style.equals(STYLE_DEFAULT)) {
268
324
  style = getStyleForTheme();
269
325
  }
@@ -271,12 +327,12 @@ public class SystemBars extends Plugin {
271
327
  Window window = getActivity().getWindow();
272
328
  WindowInsetsControllerCompat windowInsetsControllerCompat = WindowCompat.getInsetsController(window, window.getDecorView());
273
329
  if (bar.isEmpty() || bar.equals(BAR_STATUS_BAR)) {
274
- currentStatusBarStyle = style;
330
+ currentStatusBarStyle = requestedStyle;
275
331
  windowInsetsControllerCompat.setAppearanceLightStatusBars(!style.equals(STYLE_DARK));
276
332
  }
277
333
 
278
334
  if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) {
279
- currentGestureBarStyle = style;
335
+ currentGestureBarStyle = requestedStyle;
280
336
  windowInsetsControllerCompat.setAppearanceLightNavigationBars(!style.equals(STYLE_DARK));
281
337
  }
282
338
 
@@ -293,6 +349,7 @@ public class SystemBars extends Plugin {
293
349
  }
294
350
  if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) {
295
351
  windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.navigationBars());
352
+ navBarVisible = false;
296
353
  }
297
354
  return;
298
355
  }
@@ -302,6 +359,7 @@ public class SystemBars extends Plugin {
302
359
  }
303
360
  if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) {
304
361
  windowInsetsControllerCompat.show(WindowInsetsCompat.Type.navigationBars());
362
+ navBarVisible = true;
305
363
  }
306
364
  }
307
365
 
@@ -1,6 +1,5 @@
1
1
  package com.getcapacitor.plugin.util;
2
2
 
3
- import android.os.Build;
4
3
  import android.os.LocaleList;
5
4
  import android.text.TextUtils;
6
5
  import com.getcapacitor.Bridge;
@@ -19,7 +18,6 @@ import java.net.URL;
19
18
  import java.net.URLEncoder;
20
19
  import java.net.UnknownServiceException;
21
20
  import java.nio.charset.StandardCharsets;
22
- import java.util.Base64;
23
21
  import java.util.Iterator;
24
22
  import java.util.List;
25
23
  import java.util.Locale;
@@ -209,13 +207,8 @@ public class CapacitorHttpUrlConnection implements ICapacitorHttpUrlConnection {
209
207
  dataString = call.getString("data");
210
208
  }
211
209
  this.writeRequestBody(dataString != null ? dataString : "");
212
- } else if (bodyType != null && bodyType.equals("file")) {
213
- try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) {
214
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
215
- os.write(Base64.getDecoder().decode(body.toString()));
216
- }
217
- os.flush();
218
- }
210
+ } else if (bodyType != null && (bodyType.equals("file") || bodyType.equals("binary"))) {
211
+ this.writeRequestBody(decodeBase64RequestBody(body.toString()));
219
212
  } else if (contentType.contains("application/x-www-form-urlencoded")) {
220
213
  try {
221
214
  JSObject obj = body.toJSObject();
@@ -252,6 +245,17 @@ public class CapacitorHttpUrlConnection implements ICapacitorHttpUrlConnection {
252
245
  }
253
246
  }
254
247
 
248
+ private void writeRequestBody(byte[] body) throws IOException {
249
+ try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) {
250
+ os.write(body);
251
+ os.flush();
252
+ }
253
+ }
254
+
255
+ static byte[] decodeBase64RequestBody(String body) {
256
+ return android.util.Base64.decode(body, android.util.Base64.DEFAULT);
257
+ }
258
+
255
259
  private void writeObjectRequestBody(JSObject object) throws IOException, JSONException {
256
260
  try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) {
257
261
  Iterator<String> keys = object.keys();
@@ -296,11 +300,7 @@ public class CapacitorHttpUrlConnection implements ICapacitorHttpUrlConnection {
296
300
  os.writeBytes("Content-Transfer-Encoding: binary" + lineEnd);
297
301
  os.writeBytes(lineEnd);
298
302
 
299
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
300
- os.write(Base64.getDecoder().decode(value));
301
- } else {
302
- os.write(android.util.Base64.decode(value, android.util.Base64.DEFAULT));
303
- }
303
+ os.write(decodeBase64RequestBody(value));
304
304
 
305
305
  os.writeBytes(lineEnd);
306
306
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capacitor-plus/android",
3
- "version": "8.3.10",
3
+ "version": "8.3.11",
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)",