@capacitor-plus/android 8.0.6 → 8.3.2

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.
@@ -53,7 +53,7 @@ android {
53
53
  buildTypes {
54
54
  release {
55
55
  minifyEnabled false
56
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
56
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
57
57
  }
58
58
  }
59
59
  lintOptions {
@@ -505,6 +505,10 @@ var nativeBridge = (function (exports) {
505
505
  if (typeof resource === 'string') {
506
506
  return await win.CapacitorWebFetch(createProxyUrl(resource, win), options);
507
507
  }
508
+ else if (resource instanceof URL) {
509
+ const modifiedURL = new URL(createProxyUrl(resource.toString(), win));
510
+ return await win.CapacitorWebFetch(modifiedURL, options);
511
+ }
508
512
  else if (resource instanceof Request) {
509
513
  const modifiedRequest = new Request(createProxyUrl(resource.url, win), resource);
510
514
  return await win.CapacitorWebFetch(modifiedRequest, options);
@@ -434,7 +434,7 @@ public class Bridge {
434
434
  PackageManager pm = getContext().getPackageManager();
435
435
  PackageInfo pInfo = InternalUtils.getPackageInfo(pm, getContext().getPackageName());
436
436
  versionCode = Integer.toString((int) PackageInfoCompat.getLongVersionCode(pInfo));
437
- versionName = pInfo.versionName;
437
+ versionName = pInfo.versionName != null ? pInfo.versionName : "";
438
438
  } catch (Exception ex) {
439
439
  Logger.error("Unable to get package info", ex);
440
440
  }
@@ -628,11 +628,11 @@ public class Bridge {
628
628
  try {
629
629
  URL appUrlObject = new URL(appUrlConfig);
630
630
  authorities.add(appUrlObject.getAuthority());
631
+ localUrl = appUrlObject.getProtocol() + "://" + appUrlObject.getAuthority();
631
632
  } catch (Exception ex) {
632
633
  Logger.error("Provided server url is invalid: " + ex.getMessage());
633
634
  return;
634
635
  }
635
- localUrl = appUrlConfig;
636
636
  appUrl = appUrlConfig;
637
637
  } else {
638
638
  appUrl = localUrl;
@@ -66,7 +66,9 @@ public class BridgeActivity extends AppCompatActivity {
66
66
  @Override
67
67
  public void onSaveInstanceState(Bundle outState) {
68
68
  super.onSaveInstanceState(outState);
69
- bridge.saveInstanceState(outState);
69
+ if (bridge != null) {
70
+ bridge.saveInstanceState(outState);
71
+ }
70
72
  }
71
73
 
72
74
  @Override
@@ -82,8 +84,10 @@ public class BridgeActivity extends AppCompatActivity {
82
84
  @Override
83
85
  public void onRestart() {
84
86
  super.onRestart();
85
- this.bridge.onRestart();
86
- Logger.debug("App restarted");
87
+ if (this.bridge != null) {
88
+ this.bridge.onRestart();
89
+ Logger.debug("App restarted");
90
+ }
87
91
  }
88
92
 
89
93
  @Override
@@ -131,7 +135,9 @@ public class BridgeActivity extends AppCompatActivity {
131
135
  @Override
132
136
  public void onDetachedFromWindow() {
133
137
  super.onDetachedFromWindow();
134
- this.bridge.onDetachedFromWindow();
138
+ if (this.bridge != null) {
139
+ this.bridge.onDetachedFromWindow();
140
+ }
135
141
  }
136
142
 
137
143
  /**
@@ -128,9 +128,22 @@ public class WebViewLocalServer {
128
128
  return reasonPhrase;
129
129
  }
130
130
 
131
+ /**
132
+ * @deprecated This method may return incorrect headers in concurrent range requests.
133
+ * <p>
134
+ * Use {@link #buildDefaultResponseHeaders()} instead, which returns a copy of the map.
135
+ * </p>
136
+ * This method will be removed in a future major version of Capacitor.
137
+ * </p>
138
+ */
139
+ @Deprecated(forRemoval = true) // adjust version as appropriate
131
140
  public Map<String, String> getResponseHeaders() {
132
141
  return responseHeaders;
133
142
  }
143
+
144
+ public Map<String, String> buildDefaultResponseHeaders() {
145
+ return new HashMap<>(responseHeaders);
146
+ }
134
147
  }
135
148
 
136
149
  WebViewLocalServer(Context context, Bridge bridge, JSInjector jsInjector, ArrayList<String> authorities, boolean html5mode) {
@@ -326,14 +339,16 @@ public class WebViewLocalServer {
326
339
  private WebResourceResponse handleLocalRequest(WebResourceRequest request, PathHandler handler) {
327
340
  String path = request.getUrl().getPath();
328
341
 
329
- if (request.getRequestHeaders().get("Range") != null) {
342
+ Map<String, String> requestHeaders = request.getRequestHeaders();
343
+ String rangeString = requestHeaders.get("Range") != null ? requestHeaders.get("Range") : requestHeaders.get("range");
344
+
345
+ if (rangeString != null) {
330
346
  InputStream responseStream = new LollipopLazyInputStream(handler, request);
331
347
  String mimeType = getMimeType(path, responseStream);
332
- Map<String, String> tempResponseHeaders = handler.getResponseHeaders();
348
+ Map<String, String> tempResponseHeaders = handler.buildDefaultResponseHeaders();
333
349
  int statusCode = 206;
334
350
  try {
335
351
  int totalRange = responseStream.available();
336
- String rangeString = request.getRequestHeaders().get("Range");
337
352
  String[] parts = rangeString.split("=");
338
353
  String[] streamParts = parts[1].split("-");
339
354
  String fromRange = streamParts[0];
@@ -365,7 +380,7 @@ public class WebViewLocalServer {
365
380
  handler.getEncoding(),
366
381
  statusCode,
367
382
  handler.getReasonPhrase(),
368
- handler.getResponseHeaders(),
383
+ handler.buildDefaultResponseHeaders(),
369
384
  responseStream
370
385
  );
371
386
  }
@@ -376,7 +391,7 @@ public class WebViewLocalServer {
376
391
  handler.getEncoding(),
377
392
  handler.getStatusCode(),
378
393
  handler.getReasonPhrase(),
379
- handler.getResponseHeaders(),
394
+ handler.buildDefaultResponseHeaders(),
380
395
  null
381
396
  );
382
397
  }
@@ -411,7 +426,7 @@ public class WebViewLocalServer {
411
426
  handler.getEncoding(),
412
427
  statusCode,
413
428
  handler.getReasonPhrase(),
414
- handler.getResponseHeaders(),
429
+ handler.buildDefaultResponseHeaders(),
415
430
  responseStream
416
431
  );
417
432
  }
@@ -442,7 +457,7 @@ public class WebViewLocalServer {
442
457
  handler.getEncoding(),
443
458
  statusCode,
444
459
  handler.getReasonPhrase(),
445
- handler.getResponseHeaders(),
460
+ handler.buildDefaultResponseHeaders(),
446
461
  responseStream
447
462
  );
448
463
  }
@@ -517,7 +532,7 @@ public class WebViewLocalServer {
517
532
  handler.getEncoding(),
518
533
  handler.getStatusCode(),
519
534
  handler.getReasonPhrase(),
520
- handler.getResponseHeaders(),
535
+ handler.buildDefaultResponseHeaders(),
521
536
  responseStream
522
537
  );
523
538
  }
@@ -1,9 +1,12 @@
1
1
  package com.getcapacitor.plugin;
2
2
 
3
+ import android.content.Context;
4
+ import android.content.pm.PackageInfo;
3
5
  import android.content.res.Configuration;
6
+ import android.content.res.Resources;
4
7
  import android.os.Build;
8
+ import android.util.TypedValue;
5
9
  import android.view.View;
6
- import android.view.ViewGroup;
7
10
  import android.view.Window;
8
11
  import android.webkit.JavascriptInterface;
9
12
  import android.webkit.WebView;
@@ -12,6 +15,7 @@ import androidx.core.view.ViewCompat;
12
15
  import androidx.core.view.WindowCompat;
13
16
  import androidx.core.view.WindowInsetsCompat;
14
17
  import androidx.core.view.WindowInsetsControllerCompat;
18
+ import androidx.webkit.WebViewCompat;
15
19
  import com.getcapacitor.Plugin;
16
20
  import com.getcapacitor.PluginCall;
17
21
  import com.getcapacitor.PluginMethod;
@@ -31,6 +35,11 @@ public class SystemBars extends Plugin {
31
35
  static final String INSETS_HANDLING_CSS = "css";
32
36
  static final String INSETS_HANDLING_DISABLE = "disable";
33
37
 
38
+ // https://issues.chromium.org/issues/40699457
39
+ private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_FIX = 140;
40
+ // https://issues.chromium.org/issues/457682720
41
+ private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX = 144;
42
+
34
43
  static final String viewportMetaJSFunction = """
35
44
  function capacitorSystemBarsCheckMetaViewport() {
36
45
  const meta = document.querySelectorAll("meta[name=viewport]");
@@ -47,6 +56,9 @@ public class SystemBars extends Plugin {
47
56
  private boolean insetHandlingEnabled = true;
48
57
  private boolean hasViewportCover = false;
49
58
 
59
+ private String currentStatusBarStyle = STYLE_DEFAULT;
60
+ private String currentGestureBarStyle = STYLE_DEFAULT;
61
+
50
62
  @Override
51
63
  public void load() {
52
64
  getBridge().getWebView().addJavascriptInterface(this, "CapacitorSystemBarsAndroidInterface");
@@ -73,7 +85,9 @@ public class SystemBars extends Plugin {
73
85
  @Override
74
86
  protected void handleOnConfigurationChanged(Configuration newConfig) {
75
87
  super.handleOnConfigurationChanged(newConfig);
76
- setStyle(STYLE_DEFAULT, "");
88
+
89
+ setStyle(currentGestureBarStyle, BAR_GESTURE_BAR);
90
+ setStyle(currentStatusBarStyle, BAR_STATUS_BAR);
77
91
  }
78
92
 
79
93
  private void initSystemBars() {
@@ -86,7 +100,7 @@ public class SystemBars extends Plugin {
86
100
  }
87
101
 
88
102
  initWindowInsetsListener();
89
- initSafeAreaInsets();
103
+ initSafeAreaCSSVariables();
90
104
 
91
105
  getBridge().executeOnMainThread(() -> {
92
106
  setStyle(style, "");
@@ -143,10 +157,13 @@ public class SystemBars extends Plugin {
143
157
 
144
158
  private Insets calcSafeAreaInsets(WindowInsetsCompat insets) {
145
159
  Insets safeArea = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
160
+ if (insets.isVisible(WindowInsetsCompat.Type.ime())) {
161
+ return Insets.of(safeArea.left, safeArea.top, safeArea.right, 0);
162
+ }
146
163
  return Insets.of(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom);
147
164
  }
148
165
 
149
- private void initSafeAreaInsets() {
166
+ private void initSafeAreaCSSVariables() {
150
167
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) {
151
168
  View v = (View) this.getBridge().getWebView().getParent();
152
169
  WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(v);
@@ -158,37 +175,59 @@ public class SystemBars extends Plugin {
158
175
  }
159
176
 
160
177
  private void initWindowInsetsListener() {
161
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) {
162
- ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> {
163
- if (hasViewportCover) {
164
- Insets safeAreaInsets = calcSafeAreaInsets(insets);
165
- boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
178
+ ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> {
179
+ boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover;
166
180
 
167
- if (keyboardVisible) {
168
- safeAreaInsets = Insets.of(safeAreaInsets.left, safeAreaInsets.top, safeAreaInsets.right, 0);
181
+ Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
182
+ Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime());
183
+ boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
169
184
 
170
- Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime());
171
- setViewMargins(v, Insets.of(0, 0, 0, imeInsets.bottom));
172
- } else {
173
- setViewMargins(v, Insets.NONE);
174
- }
185
+ if (shouldPassthroughInsets) {
186
+ // We need to correct for a possible shown IME
187
+ v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
175
188
 
189
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) {
190
+ Insets safeAreaInsets = calcSafeAreaInsets(insets);
176
191
  injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
177
- return WindowInsetsCompat.CONSUMED;
178
192
  }
179
193
 
180
- return insets;
181
- });
182
- }
183
- }
194
+ return new WindowInsetsCompat.Builder(insets)
195
+ .setInsets(
196
+ WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(),
197
+ Insets.of(
198
+ systemBarsInsets.left,
199
+ systemBarsInsets.top,
200
+ systemBarsInsets.right,
201
+ getBottomInset(systemBarsInsets, keyboardVisible)
202
+ )
203
+ )
204
+ .build();
205
+ }
206
+
207
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
208
+ // We need to correct for a possible shown IME
209
+ v.setPadding(
210
+ systemBarsInsets.left,
211
+ systemBarsInsets.top,
212
+ systemBarsInsets.right,
213
+ keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom
214
+ );
215
+ }
216
+
217
+ // Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets
218
+ // So we have to explicitly set insets to `0`
219
+ // See: https://issues.chromium.org/issues/461332423
220
+ WindowInsetsCompat newInsets = new WindowInsetsCompat.Builder(insets)
221
+ .setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0))
222
+ .build();
184
223
 
185
- private void setViewMargins(View v, Insets insets) {
186
- ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
187
- mlp.leftMargin = insets.left;
188
- mlp.bottomMargin = insets.bottom;
189
- mlp.rightMargin = insets.right;
190
- mlp.topMargin = insets.top;
191
- v.setLayoutParams(mlp);
224
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) {
225
+ Insets safeAreaInsets = calcSafeAreaInsets(newInsets);
226
+ injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
227
+ }
228
+
229
+ return newInsets;
230
+ });
192
231
  }
193
232
 
194
233
  private void injectSafeAreaCSS(int top, int right, int bottom, int left) {
@@ -231,12 +270,16 @@ public class SystemBars extends Plugin {
231
270
  Window window = getActivity().getWindow();
232
271
  WindowInsetsControllerCompat windowInsetsControllerCompat = WindowCompat.getInsetsController(window, window.getDecorView());
233
272
  if (bar.isEmpty() || bar.equals(BAR_STATUS_BAR)) {
273
+ currentStatusBarStyle = style;
234
274
  windowInsetsControllerCompat.setAppearanceLightStatusBars(!style.equals(STYLE_DARK));
235
275
  }
236
276
 
237
277
  if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) {
278
+ currentGestureBarStyle = style;
238
279
  windowInsetsControllerCompat.setAppearanceLightNavigationBars(!style.equals(STYLE_DARK));
239
280
  }
281
+
282
+ getActivity().getWindow().getDecorView().setBackgroundColor(getThemeColor(getContext(), android.R.attr.windowBackground));
240
283
  }
241
284
 
242
285
  private void setHidden(boolean hide, String bar) {
@@ -268,4 +311,36 @@ public class SystemBars extends Plugin {
268
311
  }
269
312
  return STYLE_DARK;
270
313
  }
314
+
315
+ public int getThemeColor(Context context, int attrRes) {
316
+ TypedValue typedValue = new TypedValue();
317
+
318
+ Resources.Theme theme = context.getTheme();
319
+ theme.resolveAttribute(attrRes, typedValue, true);
320
+ return typedValue.data;
321
+ }
322
+
323
+ private Integer getWebViewMajorVersion() {
324
+ PackageInfo info = WebViewCompat.getCurrentWebViewPackage(getContext());
325
+ if (info != null && info.versionName != null) {
326
+ String[] versionSegments = info.versionName.split("\\.");
327
+ return Integer.valueOf(versionSegments[0]);
328
+ }
329
+
330
+ return 0;
331
+ }
332
+
333
+ private int getBottomInset(Insets systemBarsInsets, boolean keyboardVisible) {
334
+ if (getWebViewMajorVersion() < WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX) {
335
+ // This is a workaround for webview versions that have a bug
336
+ // that causes the bottom inset to be incorrect if the IME is visible
337
+ // See: https://issues.chromium.org/issues/457682720
338
+
339
+ if (keyboardVisible) {
340
+ return 0;
341
+ }
342
+ }
343
+
344
+ return systemBarsInsets.bottom;
345
+ }
271
346
  }
@@ -24,6 +24,7 @@ import java.util.Iterator;
24
24
  import java.util.List;
25
25
  import java.util.Locale;
26
26
  import java.util.Map;
27
+ import java.util.UUID;
27
28
  import javax.net.ssl.HttpsURLConnection;
28
29
  import javax.net.ssl.SSLSocketFactory;
29
30
  import org.json.JSONException;
@@ -224,7 +225,16 @@ public class CapacitorHttpUrlConnection implements ICapacitorHttpUrlConnection {
224
225
  this.writeRequestBody(body.toString());
225
226
  }
226
227
  } else if (bodyType != null && bodyType.equals("formData")) {
227
- this.writeFormDataRequestBody(contentType, body.toJSArray());
228
+ String boundary = extractBoundaryFromContentType(contentType);
229
+ if (boundary == null) {
230
+ // If no boundary is provided, generate a random one and set the Content-Type header accordingly
231
+ // or otherwise servers will not be able to parse the request body. Browsers do this automatically
232
+ // but here we need to do this manually in order to comply with browser api behavior.
233
+ boundary = UUID.randomUUID().toString();
234
+ connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
235
+ }
236
+
237
+ this.writeFormDataRequestBody(boundary, body.toJSArray());
228
238
  } else {
229
239
  this.writeRequestBody(body.toString());
230
240
  }
@@ -260,9 +270,8 @@ public class CapacitorHttpUrlConnection implements ICapacitorHttpUrlConnection {
260
270
  }
261
271
  }
262
272
 
263
- private void writeFormDataRequestBody(String contentType, JSArray entries) throws IOException, JSONException {
273
+ private void writeFormDataRequestBody(String boundary, JSArray entries) throws IOException, JSONException {
264
274
  try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) {
265
- String boundary = contentType.split(";")[1].split("=")[1];
266
275
  String lineEnd = "\r\n";
267
276
  String twoHyphens = "--";
268
277
 
@@ -303,6 +312,39 @@ public class CapacitorHttpUrlConnection implements ICapacitorHttpUrlConnection {
303
312
  }
304
313
  }
305
314
 
315
+ /**
316
+ * Extracts the boundary value from the `Content-Type` header for multipart/form-data requests, if provided.
317
+ *
318
+ * The boundary value might be surrounded by double quotes (") which will be stripped away.
319
+ *
320
+ * @param contentType The `Content-Type` header string.
321
+ * @return The boundary value if found, otherwise `null`.
322
+ */
323
+ public static String extractBoundaryFromContentType(String contentType) {
324
+ String boundaryPrefix = "boundary=";
325
+ int boundaryIndex = contentType.indexOf(boundaryPrefix);
326
+ if (boundaryIndex == -1) {
327
+ return null;
328
+ }
329
+
330
+ // Extract the substring starting right after "boundary="
331
+ String boundary = contentType.substring(boundaryIndex + boundaryPrefix.length());
332
+
333
+ // Find the end of the boundary value by looking for the next ";"
334
+ int endIndex = boundary.indexOf(";");
335
+ if (endIndex != -1) {
336
+ boundary = boundary.substring(0, endIndex);
337
+ }
338
+
339
+ // Remove surrounding double quotes if present
340
+ boundary = boundary.trim();
341
+ if (boundary.startsWith("\"") && boundary.endsWith("\"")) {
342
+ boundary = boundary.substring(1, boundary.length() - 1);
343
+ }
344
+
345
+ return boundary;
346
+ }
347
+
306
348
  /**
307
349
  * Opens a communications link to the resource referenced by this
308
350
  * URL, if such a connection has not already been established.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capacitor-plus/android",
3
- "version": "8.0.6",
3
+ "version": "8.3.2",
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.0.0",
27
- "@capacitor/core": "^8.0.0"
26
+ "@capacitor-plus/core": "^8.3.0",
27
+ "@capacitor/core": "^8.3.0"
28
28
  },
29
29
  "publishConfig": {
30
30
  "access": "public"