Skip to content
Draft
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
37 changes: 37 additions & 0 deletions .github/scripts/run-android-runtime-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail

# Bound every adb operation: a stuck device must not consume the entire job timeout.
adb_command() { timeout --kill-after=5s 120s adb -s "${ANDROID_SERIAL:-emulator-5554}" "$@"; }
collect_diagnostics() {
local result=$?
trap - EXIT
timeout --kill-after=5s 15s adb -s "${ANDROID_SERIAL:-emulator-5554}" logcat -d -v threadtime > runtime-logcat.log 2>&1 || true
timeout --kill-after=5s 15s adb -s "${ANDROID_SERIAL:-emulator-5554}" shell dumpsys activity services > runtime-services.log 2>&1 || true
exit "$result"
}
trap collect_diagnostics EXIT

run_test() {
local logfile=$1 test_class=$2 runner=$3 status=0
adb_command shell am instrument -w -r -e class "$test_class" "$runner" > "$logfile" 2>&1 || status=$?
cat "$logfile"
# am instrument can exit zero even when JUnit fails or the process crashes.
if [[ "$status" != 0 ]] || ! grep -Eq '^OK \([1-9][0-9]* tests?\)' "$logfile"; then
echo "::error::Runtime test failed ($test_class, adb exit $status); see $logfile and runtime-logcat.log"
return 1
fi
}

adb_command logcat -c
adb_command install -r runtime-tests/common-debug-androidTest.apk
result=0
run_test browser-runtime.log com.shilapi.xcertplay.web.BrowserPageRuntimeTest \
com.shilapi.xcertplay.host.test/androidx.test.runner.AndroidJUnitRunner || result=1

adb_command install -r plugin-host/mobile-debug.apk
adb_command install -r plugin-sdk/projection-plugin-sample/build/outputs/apk/debug/projection-plugin-sample-debug.apk
adb_command install -r plugin-tests/mobile-debug-androidTest.apk
run_test plugin-runtime.log moe.feng.wheelplay.plugin.PluginSurfaceIntegrationTest \
moe.feng.wheelplay.debug.test/androidx.test.runner.AndroidJUnitRunner || result=1
exit "$result"
100 changes: 38 additions & 62 deletions .github/workflows/debug-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,17 @@ jobs:
echo "$ANDROID_HOME/platform-tools" >> "$GITHUB_PATH"
echo "root=$ANDROID_HOME" >> "$GITHUB_OUTPUT"

- name: Cache required Android toolchain
# Platform and build-tools are preinstalled and root-owned on GitHub runners.
- name: Cache downloaded native Android toolchain
uses: actions/cache@v4
with:
path: |
${{ steps.android-sdk.outputs.root }}/platforms/android-${{ env.ANDROID_API_LEVEL }}
${{ steps.android-sdk.outputs.root }}/build-tools/${{ env.ANDROID_BUILD_TOOLS }}
${{ steps.android-sdk.outputs.root }}/ndk/${{ env.ANDROID_NDK_VERSION }}
${{ steps.android-sdk.outputs.root }}/cmake/${{ env.ANDROID_CMAKE_VERSION }}
key: android-toolchain-v1-ubuntu24-${{ runner.arch }}-api${{ env.ANDROID_API_LEVEL }}-bt${{ env.ANDROID_BUILD_TOOLS }}-ndk${{ env.ANDROID_NDK_VERSION }}-cmake${{ env.ANDROID_CMAKE_VERSION }}
key: android-native-toolchain-v2-ubuntu24-${{ runner.arch }}-ndk${{ env.ANDROID_NDK_VERSION }}-cmake${{ env.ANDROID_CMAKE_VERSION }}

- name: Verify SDK installation helper
run: node --test test/ci-android-toolchain.test.js
run: node --test test/ci-android-*.test.js

- name: Install missing Android packages
run: |
Expand All @@ -80,8 +79,12 @@ jobs:
"ndk;$ANDROID_NDK_VERSION" \
"cmake;$ANDROID_CMAKE_VERSION"

- name: Build and verify Debug
run: ./gradlew --no-daemon --build-cache :projection-plugin-api:testDebugUnitTest :projection-plugin-api:assembleRelease :projection-plugin-sample:lintDebug :projection-plugin-sample:assembleDebug :shared:testDebugUnitTest :common:testDebugUnitTest :common:assembleDebugAndroidTest :mobile:lintDebug :mobile:assembleDebug :mobile:assembleDebugAndroidTest
- name: Build and lint Debug artifacts
run: ./gradlew --no-daemon --build-cache :projection-plugin-api:assembleRelease :projection-plugin-sample:lintDebug :projection-plugin-sample:assembleDebug :common:assembleDebugAndroidTest :mobile:lintDebug :mobile:assembleDebug :mobile:assembleDebugAndroidTest

# Native compilation and time-sensitive socket tests must not compete for runner CPU.
- name: Run unit tests
run: ./gradlew --no-daemon --build-cache :projection-plugin-api:testDebugUnitTest :shared:testDebugUnitTest :common:testDebugUnitTest

- name: Upload plugin SDK and independent sample APK
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -146,25 +149,21 @@ jobs:
matrix:
api-level: [28, 36]
steps:
- name: Locate emulator cache directories
id: emulator-cache
- uses: actions/checkout@v4

- name: Locate Android SDK
id: runtime-sdk
run: |
echo "sdk=$ANDROID_HOME" >> "$GITHUB_OUTPUT"
echo "image-version=$ImageVersion" >> "$GITHUB_OUTPUT"
echo "root=$ANDROID_HOME" >> "$GITHUB_OUTPUT"
echo "$ANDROID_HOME/platform-tools" >> "$GITHUB_PATH"

# Cache only this API's image and clean AVD. The runner image version also
# invalidates snapshots when the preinstalled emulator/host software changes.
- name: Restore Android emulator and clean snapshot
id: avd-cache
uses: actions/cache/restore@v4
# Only cache downloaded images. Preinstalled platform directories are root-owned;
# restoring them caused thousands of tar errors. Each run gets a fresh AVD.
- name: Cache Android system image
uses: actions/cache@v4
with:
path: |
${{ steps.emulator-cache.outputs.sdk }}/emulator
${{ steps.emulator-cache.outputs.sdk }}/platforms/android-${{ matrix.api-level }}
${{ steps.emulator-cache.outputs.sdk }}/system-images/android-${{ matrix.api-level }}/google_apis/x86_64
~/.android/avd
~/.android/adb*
key: android-avd-v1-ubuntu24-${{ runner.arch }}-${{ steps.emulator-cache.outputs.image-version }}-api${{ matrix.api-level }}-google_apis-x86_64
path: ${{ steps.runtime-sdk.outputs.root }}/system-images/android-${{ matrix.api-level }}/google_apis/x86_64
key: android-system-image-v2-ubuntu24-${{ runner.arch }}-api${{ matrix.api-level }}-google_apis-x86_64

- name: Download browser runtime test APK
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -193,53 +192,26 @@ jobs:
- name: Enable KVM
run: sudo chmod a+rw /dev/kvm

- name: Create clean emulator snapshot on cache miss
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
arch: x86_64
target: google_apis
avd-name: browser-runtime
force-avd-creation: false
disable-animations: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
script: echo 'Clean emulator snapshot created.'
- name: Start adb outside the emulator action
run: adb start-server > adb-server.log 2>&1

# Save before installing or running tests so later jobs restore a clean device.
- name: Save clean emulator snapshot
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
${{ steps.emulator-cache.outputs.sdk }}/emulator
${{ steps.emulator-cache.outputs.sdk }}/platforms/android-${{ matrix.api-level }}
${{ steps.emulator-cache.outputs.sdk }}/system-images/android-${{ matrix.api-level }}/google_apis/x86_64
~/.android/avd
~/.android/adb*
key: ${{ steps.avd-cache.outputs.cache-primary-key }}

- name: Verify localized pages on Android ICU
- name: Verify browser and plugin runtimes
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
arch: x86_64
target: google_apis
avd-name: browser-runtime
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
force-avd-creation: true
# Detach output from the action pipes so emulator helper processes cannot
# keep a successful step alive after adb emu kill (seen on API 28).
emulator-options: -no-snapshot -no-metrics -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none > emulator.log 2>&1 < /dev/null
disable-animations: true
script: |
adb install -r runtime-tests/common-debug-androidTest.apk
adb shell am instrument -w -r -e class com.shilapi.xcertplay.web.BrowserPageRuntimeTest com.shilapi.xcertplay.host.test/androidx.test.runner.AndroidJUnitRunner > browser-runtime.log
adb install -r plugin-host/mobile-debug.apk
adb install -r plugin-sdk/projection-plugin-sample/build/outputs/apk/debug/projection-plugin-sample-debug.apk
adb install -r plugin-tests/mobile-debug-androidTest.apk
adb shell am instrument -w -r -e class moe.feng.wheelplay.plugin.PluginSurfaceIntegrationTest moe.feng.wheelplay.debug.test/androidx.test.runner.AndroidJUnitRunner > plugin-runtime.log
cat plugin-runtime.log
grep -Eq '^OK \([1-9][0-9]* tests?\)' plugin-runtime.log
cat browser-runtime.log
grep -Eq '^OK \([1-9][0-9]* tests?\)' browser-runtime.log
script: bash .github/scripts/run-android-runtime-tests.sh

- name: Stop adb
if: always()
run: timeout --kill-after=5s 15s adb kill-server || true

- name: Upload runtime test result
if: always()
Expand All @@ -249,4 +221,8 @@ jobs:
path: |
browser-runtime.log
plugin-runtime.log
runtime-logcat.log
runtime-services.log
emulator.log
adb-server.log
if-no-files-found: warn
1 change: 1 addition & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,6 @@ tasks.withType<Test>().configureEach {
// The full Robolectric suite loads several Android SDK resource tables. Gradle's
// default 512 MiB test worker heap is insufficient; org.gradle.jvmargs only sizes the daemon.
maxHeapSize = "2g"
testLogging.exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
jvmArgs("--add-exports=java.base/jdk.internal.access=ALL-UNNAMED")
}
41 changes: 21 additions & 20 deletions common/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
android:authorities="${applicationId}.shizuku" android:multiprocess="false"
android:enabled="true" android:exported="true"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
<activity android:name="com.shilapi.xcertplay.DiPlayActivity"
<activity android:name="moe.feng.wheelplay.WheelPlayActivity"
android:exported="true" android:launchMode="singleTop"
android:configChanges="density|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:theme="@style/Theme.Xcertplay">
Expand All @@ -47,41 +47,42 @@
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" android:mimeType="application/vnd.android.package-archive" />
</intent-filter>
</activity>
<service android:name="com.shilapi.xcertplay.DiPlaySessionService"
android:exported="false" android:foregroundServiceType="connectedDevice|microphone|mediaPlayback" />

<activity
android:name="com.shilapi.xcertplay.CarPlayHostActivity"
android:configChanges="density|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:exported="true"
android:launchMode="singleTask"
android:resizeableActivity="true"
android:screenOrientation="fullSensor"
android:theme="@style/Theme.Xcertplay"
android:windowSoftInputMode="adjustResize">

<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/usb_device_filter" />
</activity>
<service android:name="moe.feng.wheelplay.WheelPlaySessionService"
android:exported="false" android:foregroundServiceType="connectedDevice|microphone|mediaPlayback" />

<activity
android:name="com.shilapi.xcertplay.MainActivity"
android:exported="false"
android:theme="@style/Theme.Xcertplay" />
android:name="moe.feng.wheelplay.CarPlayHostActivity"
android:configChanges="density|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:exported="true"
android:launchMode="singleTop"
android:resizeableActivity="true"
android:screenOrientation="fullSensor"
android:theme="@style/Theme.WheelPlay.ConnectionDialog"
android:windowSoftInputMode="adjustResize" />

<activity
android:name="com.shilapi.xcertplay.ImageCropActivity"
android:name="moe.feng.wheelplay.ImageCropActivity"
android:exported="false"
android:screenOrientation="fullSensor"
android:theme="@style/Theme.Xcertplay" />

<!-- Keep existing shortcuts and explicit launch intents working after the UI package move. -->
<activity-alias android:name="com.shilapi.xcertplay.DiPlayActivity"
android:targetActivity="moe.feng.wheelplay.WheelPlayActivity"
android:exported="true" />
<activity-alias android:name="com.shilapi.xcertplay.CarPlayHostActivity"
android:targetActivity="moe.feng.wheelplay.CarPlayHostActivity"
android:exported="true" />

<receiver
android:name="com.shilapi.xcertplay.BootReceiver"
android:name="moe.feng.wheelplay.BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
Expand Down
21 changes: 21 additions & 0 deletions common/src/main/assets/licenses/crop-kit.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2022 Tanish Ranjan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading
Loading