@capacitor/android 8.5.2-nightly-20260909T151228.0 → 8.5.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.
@@ -367,12 +367,6 @@ var nativeBridge = (function (exports) {
367
367
  }
368
368
  };
369
369
  const platform = getPlatformId(win);
370
- if (platform == 'android' && typeof win.CapacitorSystemBarsAndroidInterface !== 'undefined') {
371
- // add DOM ready listener for System Bars
372
- document.addEventListener('DOMContentLoaded', function () {
373
- win.CapacitorSystemBarsAndroidInterface.onDOMReady();
374
- });
375
- }
376
370
  if (platform == 'android' || platform == 'ios') {
377
371
  // patch document.cookie on Android/iOS
378
372
  win.CapacitorCookiesDescriptor =
@@ -1220,45 +1220,51 @@ public class Bridge {
1220
1220
  protected Map<String, PermissionState> getPermissionStates(Plugin plugin) {
1221
1221
  Map<String, PermissionState> permissionsResults = new HashMap<>();
1222
1222
  CapacitorPlugin annotation = plugin.getPluginHandle().getPluginAnnotation();
1223
- for (Permission perm : annotation.permissions()) {
1224
- // If a permission is defined with no permission constants, return GRANTED for it.
1225
- // Otherwise, get its true state.
1226
- if (perm.strings().length == 0 || (perm.strings().length == 1 && perm.strings()[0].isEmpty())) {
1227
- String key = perm.alias();
1228
- if (!key.isEmpty()) {
1229
- PermissionState existingResult = permissionsResults.get(key);
1230
-
1231
- // auto set permission state to GRANTED if the alias is empty.
1232
- if (existingResult == null) {
1233
- permissionsResults.put(key, PermissionState.GRANTED);
1234
- }
1235
- }
1236
- } else {
1237
- for (String permString : perm.strings()) {
1238
- String key = perm.alias().isEmpty() ? permString : perm.alias();
1239
- PermissionState permissionStatus;
1240
- if (ActivityCompat.checkSelfPermission(this.getContext(), permString) == PackageManager.PERMISSION_GRANTED) {
1241
- permissionStatus = PermissionState.GRANTED;
1242
- } else {
1243
- permissionStatus = PermissionState.PROMPT;
1244
-
1245
- // Check if there is a cached permission state for the "Never ask again" state
1246
- SharedPreferences prefs = getContext().getSharedPreferences(PERMISSION_PREFS_NAME, Activity.MODE_PRIVATE);
1247
- String state = prefs.getString(permString, null);
1248
-
1249
- if (state != null) {
1250
- permissionStatus = PermissionState.byState(state);
1223
+ if (annotation != null) {
1224
+ for (Permission perm : annotation.permissions()) {
1225
+ // If a permission is defined with no permission constants, return GRANTED for it.
1226
+ // Otherwise, get its true state.
1227
+ if (perm.strings().length == 0 || (perm.strings().length == 1 && perm.strings()[0].isEmpty())) {
1228
+ String key = perm.alias();
1229
+ if (!key.isEmpty()) {
1230
+ PermissionState existingResult = permissionsResults.get(key);
1231
+
1232
+ // auto set permission state to GRANTED if the alias is empty.
1233
+ if (existingResult == null) {
1234
+ permissionsResults.put(key, PermissionState.GRANTED);
1251
1235
  }
1252
1236
  }
1237
+ } else {
1238
+ for (String permString : perm.strings()) {
1239
+ String key = perm.alias().isEmpty() ? permString : perm.alias();
1240
+ PermissionState permissionStatus;
1241
+ if (ActivityCompat.checkSelfPermission(this.getContext(), permString) == PackageManager.PERMISSION_GRANTED) {
1242
+ permissionStatus = PermissionState.GRANTED;
1243
+ } else {
1244
+ permissionStatus = PermissionState.PROMPT;
1245
+
1246
+ // Check if there is a cached permission state for the "Never ask again" state
1247
+ SharedPreferences prefs = getContext().getSharedPreferences(PERMISSION_PREFS_NAME, Activity.MODE_PRIVATE);
1248
+ String state = prefs.getString(permString, null);
1249
+
1250
+ if (state != null) {
1251
+ permissionStatus = PermissionState.byState(state);
1252
+ }
1253
+ }
1253
1254
 
1254
- PermissionState existingResult = permissionsResults.get(key);
1255
+ PermissionState existingResult = permissionsResults.get(key);
1255
1256
 
1256
- // multiple permissions with the same alias must all be true, otherwise all false.
1257
- if (existingResult == null || existingResult == PermissionState.GRANTED) {
1258
- permissionsResults.put(key, permissionStatus);
1257
+ // multiple permissions with the same alias must all be true, otherwise all false.
1258
+ if (existingResult == null || existingResult == PermissionState.GRANTED) {
1259
+ permissionsResults.put(key, permissionStatus);
1260
+ }
1259
1261
  }
1260
1262
  }
1261
1263
  }
1264
+ } else {
1265
+ Logger.warn(
1266
+ String.format("getPermissionStates: missing @CapacitorPlugin annotation for plugin %s", plugin.getPluginHandle().getId())
1267
+ );
1262
1268
  }
1263
1269
 
1264
1270
  return permissionsResults;
@@ -502,10 +502,14 @@ public class Plugin {
502
502
  private String[] getPermissionStringsForAliases(@NonNull String[] aliases) {
503
503
  CapacitorPlugin annotation = handle.getPluginAnnotation();
504
504
  HashSet<String> perms = new HashSet<>();
505
- for (Permission perm : annotation.permissions()) {
506
- if (Arrays.asList(aliases).contains(perm.alias())) {
507
- perms.addAll(Arrays.asList(perm.strings()));
505
+ if (annotation != null) {
506
+ for (Permission perm : annotation.permissions()) {
507
+ if (Arrays.asList(aliases).contains(perm.alias())) {
508
+ perms.addAll(Arrays.asList(perm.strings()));
509
+ }
508
510
  }
511
+ } else {
512
+ Logger.warn(String.format("getPermissionStringsForAliases: missing @CapacitorPlugin annotation for plugin %s", handle.getId()));
509
513
  }
510
514
 
511
515
  return perms.toArray(new String[0]);
@@ -4,11 +4,9 @@ import android.content.Context;
4
4
  import android.content.pm.PackageInfo;
5
5
  import android.content.res.Configuration;
6
6
  import android.content.res.Resources;
7
- import android.os.Build;
8
7
  import android.util.TypedValue;
9
8
  import android.view.View;
10
9
  import android.view.Window;
11
- import android.webkit.JavascriptInterface;
12
10
  import android.webkit.WebView;
13
11
  import androidx.core.graphics.Insets;
14
12
  import androidx.core.view.ViewCompat;
@@ -33,9 +31,9 @@ public class SystemBars extends Plugin {
33
31
  static final String BAR_STATUS_BAR = "StatusBar";
34
32
  static final String BAR_GESTURE_BAR = "NavigationBar";
35
33
 
36
- // TODO: In Cap 9, add an additional option "full"
37
34
  static final String INSETS_HANDLING_CSS = "css";
38
35
  static final String INSETS_HANDLING_DISABLE = "disable";
36
+ static final String INSETS_HANDLING_NATIVE = "native";
39
37
 
40
38
  // https://issues.chromium.org/issues/40699457
41
39
  private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_FIX = 140;
@@ -61,9 +59,22 @@ public class SystemBars extends Plugin {
61
59
  private String currentStatusBarStyle = STYLE_DEFAULT;
62
60
  private String currentGestureBarStyle = STYLE_DEFAULT;
63
61
 
62
+ // Declare variable at this scope to help prevent adding multiple listeners.
63
+ private WebViewListener webViewListener;
64
+
65
+ private void warnAboutUnsupportedConfigurationValues() {
66
+ boolean keyboardResizeOnFullScreen = bridge.getConfig().getPluginConfiguration("Keyboard").getBoolean("resizeOnFullScreen", false);
67
+
68
+ if (!INSETS_HANDLING_DISABLE.equals(insetsHandling) && keyboardResizeOnFullScreen) {
69
+ Logger.warn(
70
+ "SystemBars",
71
+ "You should omit `Keyboard.resizeOnFullScreen` in your `capacitor.config.json`. Other values can lead to unexpected behavior."
72
+ );
73
+ }
74
+ }
75
+
64
76
  @Override
65
77
  public void load() {
66
- getBridge().getWebView().addJavascriptInterface(this, "CapacitorSystemBarsAndroidInterface");
67
78
  super.load();
68
79
 
69
80
  initSystemBars();
@@ -73,15 +84,25 @@ public class SystemBars extends Plugin {
73
84
  protected void handleOnStart() {
74
85
  super.handleOnStart();
75
86
 
76
- this.getBridge().addWebViewListener(
77
- new WebViewListener() {
87
+ if (INSETS_HANDLING_DISABLE.equals(insetsHandling)) {
88
+ return;
89
+ }
90
+
91
+ if (webViewListener == null) {
92
+ webViewListener = new WebViewListener() {
78
93
  @Override
79
94
  public void onPageCommitVisible(WebView view, String url) {
80
95
  super.onPageCommitVisible(view, url);
81
- getBridge().getWebView().requestApplyInsets();
96
+ bridge.getWebView().evaluateJavascript(viewportMetaJSFunction, (res) -> {
97
+ hasViewportCover = res.equals("true");
98
+
99
+ // Request new execution tree of `setOnApplyWindowInsetsListener`
100
+ bridge.getWebView().requestApplyInsets();
101
+ });
82
102
  }
83
- }
84
- );
103
+ };
104
+ this.getBridge().addWebViewListener(webViewListener);
105
+ }
85
106
  }
86
107
 
87
108
  @Override
@@ -93,11 +114,20 @@ public class SystemBars extends Plugin {
93
114
  }
94
115
 
95
116
  private void initSystemBars() {
117
+ // If you already know what the value of the `viewport-fit=` meta tag is going to be,
118
+ // passing it here through `initialViewportFitValueHint` can help prevent layout shifting.
119
+ String configuredInitialViewportFitValueHint = getConfig().getString("initialViewportFitValueHint", "");
120
+ hasViewportCover = "cover".equals(configuredInitialViewportFitValueHint);
121
+
96
122
  String style = getConfig().getString("style", STYLE_DEFAULT).toUpperCase(Locale.US);
97
123
  boolean hidden = getConfig().getBoolean("hidden", false);
98
124
 
99
125
  String configuredInsetsHandling = getConfig().getString("insetsHandling", INSETS_HANDLING_CSS);
100
- if (INSETS_HANDLING_CSS.equals(configuredInsetsHandling) || INSETS_HANDLING_DISABLE.equals(configuredInsetsHandling)) {
126
+ if (
127
+ INSETS_HANDLING_CSS.equals(configuredInsetsHandling) ||
128
+ INSETS_HANDLING_DISABLE.equals(configuredInsetsHandling) ||
129
+ INSETS_HANDLING_NATIVE.equals(configuredInsetsHandling)
130
+ ) {
101
131
  insetsHandling = configuredInsetsHandling;
102
132
  } else {
103
133
  Logger.warn(
@@ -107,8 +137,9 @@ public class SystemBars extends Plugin {
107
137
  insetsHandling = INSETS_HANDLING_CSS;
108
138
  }
109
139
 
140
+ warnAboutUnsupportedConfigurationValues();
141
+
110
142
  initWindowInsetsListener();
111
- initSafeAreaCSSVariables();
112
143
 
113
144
  getBridge().executeOnMainThread(() -> {
114
145
  setStyle(style, "");
@@ -152,51 +183,14 @@ public class SystemBars extends Plugin {
152
183
  call.resolve();
153
184
  }
154
185
 
155
- @JavascriptInterface
156
- public void onDOMReady() {
157
- if (INSETS_HANDLING_CSS.equals(insetsHandling)) {
158
- getActivity().runOnUiThread(() -> {
159
- this.bridge.getWebView().evaluateJavascript(viewportMetaJSFunction, (res) -> {
160
- hasViewportCover = res.equals("true");
161
-
162
- getBridge().getWebView().requestApplyInsets();
163
- });
164
- });
165
- }
166
- }
167
-
168
- private Insets calcSafeAreaInsets(WindowInsetsCompat insets) {
169
- Insets safeArea = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
170
- if (insets.isVisible(WindowInsetsCompat.Type.ime())) {
171
- return Insets.of(safeArea.left, safeArea.top, safeArea.right, 0);
172
- }
173
- return Insets.of(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom);
174
- }
175
-
176
- private void initSafeAreaCSSVariables() {
177
- if (INSETS_HANDLING_CSS.equals(insetsHandling)) {
178
- WindowInsetsCompat insets;
179
-
180
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
181
- View v = (View) this.getBridge().getWebView().getParent();
182
- insets = ViewCompat.getRootWindowInsets(v);
183
- } else {
184
- insets = WindowInsetsCompat.CONSUMED;
185
- }
186
-
187
- if (insets != null) {
188
- Insets safeAreaInsets = calcSafeAreaInsets(insets);
189
- injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
190
- }
191
- }
192
- }
193
-
194
186
  private void initWindowInsetsListener() {
195
187
  if (INSETS_HANDLING_DISABLE.equals(insetsHandling)) {
196
188
  return;
197
189
  }
198
190
 
199
- ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> {
191
+ View view = getActivity().getWindow().getDecorView();
192
+
193
+ ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> {
200
194
  boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover;
201
195
 
202
196
  Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
@@ -207,10 +201,7 @@ public class SystemBars extends Plugin {
207
201
  // We need to correct for a possible shown IME
208
202
  v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
209
203
 
210
- Insets safeAreaInsets = calcSafeAreaInsets(insets);
211
- injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
212
-
213
- return new WindowInsetsCompat.Builder(insets)
204
+ WindowInsetsCompat newInsets = new WindowInsetsCompat.Builder(insets)
214
205
  .setInsets(
215
206
  WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(),
216
207
  Insets.of(
@@ -221,18 +212,20 @@ public class SystemBars extends Plugin {
221
212
  )
222
213
  )
223
214
  .build();
224
- }
225
215
 
226
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
227
- // We need to correct for a possible shown IME
228
- v.setPadding(
229
- systemBarsInsets.left,
230
- systemBarsInsets.top,
231
- systemBarsInsets.right,
232
- keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom
233
- );
216
+ injectSafeAreaCSS(newInsets);
217
+
218
+ return newInsets;
234
219
  }
235
220
 
221
+ // We need to correct for a possible shown IME
222
+ v.setPadding(
223
+ systemBarsInsets.left,
224
+ systemBarsInsets.top,
225
+ systemBarsInsets.right,
226
+ keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom
227
+ );
228
+
236
229
  // Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets
237
230
  // So we have to explicitly set insets to `0`
238
231
  // See: https://issues.chromium.org/issues/461332423
@@ -240,20 +233,28 @@ public class SystemBars extends Plugin {
240
233
  .setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0))
241
234
  .build();
242
235
 
243
- Insets safeAreaInsets = calcSafeAreaInsets(newInsets);
244
- injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
236
+ injectSafeAreaCSS(newInsets);
245
237
 
246
238
  return newInsets;
247
239
  });
248
240
  }
249
241
 
250
- private void injectSafeAreaCSS(int top, int right, int bottom, int left) {
242
+ private void injectSafeAreaCSS(WindowInsetsCompat insets) {
243
+ if (!INSETS_HANDLING_CSS.equals(insetsHandling)) {
244
+ return;
245
+ }
246
+
247
+ Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
248
+ boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
249
+
251
250
  // Convert pixels to density-independent pixels
252
251
  float density = getActivity().getResources().getDisplayMetrics().density;
253
- float topPx = top / density;
254
- float rightPx = right / density;
255
- float bottomPx = bottom / density;
256
- float leftPx = left / density;
252
+ float topPx = systemBarsInsets.top / density;
253
+ float rightPx = systemBarsInsets.right / density;
254
+ // For native insets the value gets automatically corrected when the IME is visible (in newer WebView versions),
255
+ // but for these injected values we have to handle that manually (for all WebView versions).
256
+ float bottomPx = (keyboardVisible ? 0 : systemBarsInsets.bottom) / density;
257
+ float leftPx = systemBarsInsets.left / density;
257
258
 
258
259
  // Execute JavaScript to inject the CSS
259
260
  getBridge().executeOnMainThread(() -> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capacitor/android",
3
- "version": "8.5.2-nightly-20260909T151228.0",
3
+ "version": "8.5.2",
4
4
  "description": "Capacitor: Cross-platform apps with JavaScript and the web",
5
5
  "homepage": "https://capacitorjs.com",
6
6
  "author": "Ionic Team <hi@ionic.io> (https://ionic.io)",
@@ -23,7 +23,7 @@
23
23
  "verify": "./gradlew clean lint build test -b capacitor/build.gradle"
24
24
  },
25
25
  "peerDependencies": {
26
- "@capacitor/core": "^8.5.0-nightly-20260909T151228.0"
26
+ "@capacitor/core": "^8.5.0"
27
27
  },
28
28
  "publishConfig": {
29
29
  "access": "public"