authorclover caruso <git@paperclover.net> 2026-10-16 13:51:14-07:00
committerclover caruso <git@paperclover.net> 2026-10-16 14:25:20-07:00
log20174448f624d39ffedda6ad2291f07b96542644
tree21a40c982e7614309df3c330ac47e951e43bd755
parent4534c84f0003ef1bc0827ad8d1ab37a6ce30655f
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU (~clover)

fix(mobile): draw the page on Android 8 after the keyboard resizes it

On a device without Vulkan, wgpu kept one EGL surface across resizes, and the emulator's EGL took the window's new size a resize late. A cold start that opened with the keyboard showing drew the page into buffers of the old height, leaving it blank above a black band until a tap; the next resize left a stray copy of the page's title box near the bottom. A resize under GL now makes a new surface, as GLSurfaceView does, and the Android check fails when the reopened page leaves the screen black. - The page draws on Android 8 as soon as it opens, without a tap. Fixes #153 Assisted-by: claude-opus-5.5

2 files changed, 29 insertions(+), 3 deletions(-)

apps/android/check.py+11-1
...@@ -6,12 +6,14 @@ the caller starts and stops:...@@ -6,12 +6,14 @@ the caller starts and stops:
66
7It types a token into the page, waits until the section reports it saved, force-stops the app,7It types a token into the page, waits until the section reports it saved, force-stops the app,
8turns the network off, starts it again and screenshots the page, then reads the section file8turns the network off, starts it again and screenshots the page, then reads the section file
9back with onestore's inspect example and looks for the token in it.9back with onestore's inspect example and looks for the token in it. The page must have drawn
10where the keyboard was, which GL on Android 8 once left black.
10"""11"""
11import argparse12import argparse
12import json13import json
13from pathlib import Path14from pathlib import Path
14import secrets15import secrets
16import struct
15import subprocess17import subprocess
16import time18import time
1719
...@@ -70,6 +72,11 @@ def main():...@@ -70,6 +72,11 @@ def main():
70 adb('shell', 'cmd', 'connectivity', 'airplane-mode', 'enable')72 adb('shell', 'cmd', 'connectivity', 'airplane-mode', 'enable')
71 launch()73 launch()
72 (output / 'reopened.png').write_bytes(adb('exec-out', 'screencap', '-p', binary=True))74 (output / 'reopened.png').write_bytes(adb('exec-out', 'screencap', '-p', binary=True))
75 # A raw capture: width and height, then RGBA rows after a header whose size varies by release.
76 screen = adb('exec-out', 'screencap', binary=True)
77 columns, rows = struct.unpack_from('<2I', screen)
78 pixel = len(screen) - columns * rows * 4 + (rows * 3 // 4 * columns + columns // 2) * 4
79 drawn = any(screen[pixel:pixel + 3])
73 section = output / 'section.one'80 section = output / 'section.one'
74 section.write_bytes(adb('exec-out', 'run-as', PACKAGE, 'cat', SECTION, binary=True))81 section.write_bytes(adb('exec-out', 'run-as', PACKAGE, 'cat', SECTION, binary=True))
75 (output / 'logcat.txt').write_text(adb('logcat', '-d'))82 (output / 'logcat.txt').write_text(adb('logcat', '-d'))
...@@ -84,11 +91,14 @@ def main():...@@ -84,11 +91,14 @@ def main():
84 'intact': inspected.endswith(' 0 checksum mismatches'),91 'intact': inspected.endswith(' 0 checksum mismatches'),
85 # Text is stored as UTF-16, or as single bytes where it fits them.92 # Text is stored as UTF-16, or as single bytes where it fits them.
86 'tokenInSection': token.encode('utf-16-le') in data or token.encode() in data,93 'tokenInSection': token.encode('utf-16-le') in data or token.encode() in data,
94 'drawn': drawn,
87 }95 }
88 (output / 'result.json').write_text(json.dumps(result, indent=2) + '\n')96 (output / 'result.json').write_text(json.dumps(result, indent=2) + '\n')
89 print(json.dumps(result, indent=2))97 print(json.dumps(result, indent=2))
90 if not (result['intact'] and result['tokenInSection']):98 if not (result['intact'] and result['tokenInSection']):
91 raise SystemExit('The edit did not survive')99 raise SystemExit('The edit did not survive')
100 if not drawn:
101 raise SystemExit('The reopened page left the screen black')
92102
93103
94if __name__ == '__main__':104if __name__ == '__main__':
crates/mobile/src/lib.rs+18-2
...@@ -1167,8 +1167,8 @@ pub unsafe extern "C" fn sb_view_free(view: *mut View) {...@@ -1167,8 +1167,8 @@ pub unsafe extern "C" fn sb_view_free(view: *mut View) {
11671167
1168#[unsafe(no_mangle)]1168#[unsafe(no_mangle)]
1169pub extern "C" fn sb_view_resize(view: &mut View, width: f32, height: f32, scale: f32) {1169pub extern "C" fn sb_view_resize(view: &mut View, width: f32, height: f32, scale: f32) {
1170 let gpu = gpu();1170 let guard = gpu();
1171 let Some(gpu) = gpu.as_ref() else { return };1171 let Some(gpu) = guard.as_ref() else { return };
1172 let Some(pixels) = report(view_pixels(1172 let Some(pixels) = report(view_pixels(
1173 [width, height],1173 [width, height],
1174 scale,1174 scale,
...@@ -1182,6 +1182,22 @@ pub extern "C" fn sb_view_resize(view: &mut View, width: f32, height: f32, scale...@@ -1182,6 +1182,22 @@ pub extern "C" fn sb_view_resize(view: &mut View, width: f32, height: f32, scale
1182 let _ = report(canvas.page.scale_factor_changed(scale));1182 let _ = report(canvas.page.scale_factor_changed(scale));
1183 }1183 }
1184 let _ = report(canvas.page.resized(pixels));1184 let _ = report(canvas.page.resized(pixels));
1185 #[cfg(target_os = "android")]
1186 if gpu.adapter.get_info().backend == wgpu::Backend::Gl
1187 && let Some(Presentation {
1188 surface,
1189 _window: window,
1190 ..
1191 }) = view.presentation.take()
1192 {
1193 // The emulator's EGL takes a window's new size late, so GL makes a new surface, as
1194 // GLSurfaceView does.
1195 drop(surface);
1196 drop(guard);
1197 // SAFETY: `window` holds a reference until the new presentation has acquired its own.
1198 view.presentation = report(unsafe { Presentation::new(window.0.as_ptr(), pixels) });
1199 return;
1200 }
1185 if let Some(presentation) = &mut view.presentation {1201 if let Some(presentation) = &mut view.presentation {
1186 presentation.config.width = pixels[0];1202 presentation.config.width = pixels[0];
1187 presentation.config.height = pixels[1];1203 presentation.config.height = pixels[1];