Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/android-launch-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Android TWA startup smoke (emulator)

on:
pull_request:
paths:
- android/**
- scripts/android/launch-smoke.sh
- .github/workflows/android-launch-smoke.yml
workflow_dispatch:

permissions:
contents: read

jobs:
startup:
name: Launch Android TWA on a real emulator and capture crashes
runs-on: ubuntu-24.04
timeout-minutes: 35
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: android-actions/setup-android@v4
- run: sdkmanager "platforms;android-36" "build-tools;36.0.0"
- uses: gradle/actions/setup-gradle@v4
with:
gradle-version: "8.13"
- name: Assemble unsigned debug APK with real launcher configuration
working-directory: android
run: gradle --no-daemon --stacktrace :app:assembleDebug
- name: Enable hardware virtualization for emulator
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' |
sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Install, launch, and inspect TWA in Google Play emulator
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2
with:
api-level: 35
target: google_apis_playstore
arch: x86_64
profile: pixel_7
script: bash scripts/android/launch-smoke.sh
- name: Preserve startup logs for debugging even after a failure
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: menuminute-android-startup-logcat
path: android-launch-smoke/
if-no-files-found: warn
retention-days: 14
13 changes: 13 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,24 @@
android:usesCleartextTraffic="false"
android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:manageSpaceActivity="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity"
android:supportsRtl="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<meta-data
android:name="asset_statements"
android:resource="@string/asset_statements" />

<!-- Android Browser Helper's LauncherActivity registers this component
on every startup. Omitting it causes an immediate fatal exception
while setting up the website's Android site-settings shortcut. -->
<activity
android:name="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity"
android:exported="false">
<meta-data
android:name="android.support.customtabs.trusted.MANAGE_SPACE_URL"
android:value="https://food.bababuilds.com" />
</activity>

<activity
android:name="com.google.androidbrowserhelper.trusted.LauncherActivity"
android:exported="true"
Expand Down
33 changes: 33 additions & 0 deletions scripts/android/launch-smoke.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Run inside a clean Google Play emulator via android-emulator-runner.
# Captures startup failures from the actual launcher before further Play releases.
set -Eeuo pipefail
pkg="com.bababuilds.menuminute"
activity="com.google.androidbrowserhelper.trusted.LauncherActivity"
out="android-launch-smoke"
mkdir -p "$out"
adb wait-for-device
adb install -r android/app/build/outputs/apk/debug/app-debug.apk
adb shell pm list packages > "$out/packages.txt"
adb shell dumpsys package "$pkg" > "$out/installed-package.txt"
adb shell getprop ro.build.version.release > "$out/android-version.txt"
adb shell settings put global window_animation_scale 0
adb logcat -c
adb shell am force-stop "$pkg"
echo "Launching $pkg/$activity in the clean emulator..."
if ! adb shell am start -W -n "$pkg/$activity" > "$out/launch-result.txt" 2>&1; then
cat "$out/launch-result.txt"
exit 1
fi
sleep 22
adb logcat -d -v threadtime -b crash > "$out/crash-buffer.txt" 2>&1 || true
adb logcat -d -v threadtime AndroidRuntime:E TWALauncherActivity:V TwaLauncher:V QualityEnforcer:V ActivityTaskManager:W '*:S' > "$out/twa-startup-log.txt" 2>&1 || true
adb shell dumpsys activity activities > "$out/activity-state.txt" || true
cat "$out/launch-result.txt"
if grep -Eq "(Process: $pkg|Process $pkg .* has died|FATAL EXCEPTION)" "$out/crash-buffer.txt"; then
echo "::error::Android launcher reported a Java crash. See captured crash-buffer.txt artifact."
sed -n '1,100p' "$out/crash-buffer.txt"
exit 1
fi
echo "Clean-emulator startup did not report a Java fatal exception."
echo "NOTE: This checks the unsigned debug APK, not Google Play's re-signed bundle on the user's device."
35 changes: 35 additions & 0 deletions tests/android-launch-manifest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { readFileSync } from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";

const manifest = readFileSync(path.resolve("android/app/src/main/AndroidManifest.xml"), "utf8");
const application = manifest.split("<application")[1]?.split("</application>")[0] ?? "";

describe("Android Browser Helper startup manifest completeness", () => {
it("declares the helper's site-settings activity before the TWA launcher", () => {
// LauncherActivity calls ManageDataLauncherActivity.addSiteSettingsShortcut()
// unconditionally; Android throws if its component is missing from the manifest.
expect(application).toContain(
'android:manageSpaceActivity="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity"'
);
expect(application).toContain(
'android:name="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity"'
);
expect(application).toContain('android:exported="false"');
expect(application).toContain(
'android:name="android.support.customtabs.trusted.MANAGE_SPACE_URL"'
);
expect(application.indexOf("ManageDataLauncherActivity")).toBeLessThan(
application.indexOf('android:name="com.google.androidbrowserhelper.trusted.LauncherActivity"')
);
});

it("preserves the installed package, Play Billing integrations, and trusted origin", () => {
expect(application).toContain("@string/asset_statements");
expect(application).toContain("@string/launch_url");
expect(application).toContain('android:name=".ExtraFeaturesService"');
expect(application).toContain(
'android:name="com.google.androidbrowserhelper.playbilling.provider.PaymentActivity"'
);
});
});
Loading