Skip to content
Open
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
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: gradle
directory: /Speedtest-Android
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
Binary file added .github/logo-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/logo-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshot-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshot-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/screenshot-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
108 changes: 108 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: CI

on:
push:
branches: [ "**" ]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

defaults:
run:
working-directory: Speedtest-Android

jobs:
build:
name: Lint, tests & debug build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
- uses: gradle/actions/setup-gradle@v6
- name: Lint, unit and screenshot tests
run: ./gradlew :app:lintDebug :app:testDebugUnitTest -Proborazzi.test.verify=true
- name: Upload screenshot diffs
if: failure()
uses: actions/upload-artifact@v7
with:
name: roborazzi-diffs
path: |
Speedtest-Android/app/src/test/snapshots/*_compare.png
Speedtest-Android/app/build/outputs/roborazzi/
if-no-files-found: ignore
- name: Build debug APK
run: ./gradlew :app:assembleDebug
- uses: actions/upload-artifact@v7
with:
name: librespeed-debug-apk
path: Speedtest-Android/app/build/outputs/apk/debug/*.apk
- name: Upload Gradle problems report
if: always()
uses: actions/upload-artifact@v7
with:
name: gradle-problems-report
path: Speedtest-Android/build/reports/problems/
if-no-files-found: ignore

integration:
name: Integration test against LibreSpeed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
- uses: gradle/actions/setup-gradle@v6
- name: Start a LibreSpeed backend
run: |
# the image serves on port 8080 inside the container
docker run -d --name librespeed -p 8080:8080 ghcr.io/librespeed/speedtest:latest
for i in $(seq 1 30); do
curl -fs http://localhost:8080/backend/empty.php > /dev/null && break
sleep 2
done
curl -fs http://localhost:8080/backend/empty.php > /dev/null || { docker logs librespeed; exit 1; }
- name: Run the engine against the real backend
env:
LIBRESPEED_URL: http://localhost:8080
run: ./gradlew :app:testDebugUnitTest --tests "org.librespeed.speedtest.engine.LibrespeedIntegrationTest"

instrumentation:
name: Instrumentation (API ${{ matrix.api-level }})
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# minSdk, per-app language boundary, current stable, targetSdk
api-level: [ 26, 33, 35, 36 ]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
- uses: gradle/actions/setup-gradle@v6
- name: Enable KVM
working-directory: .
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
- uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
with:
api-level: ${{ matrix.api-level }}
arch: x86_64
target: ${{ matrix.api-level >= 30 && 'google_apis' || 'default' }}
working-directory: Speedtest-Android
script: ./gradlew :app:connectedDebugAndroidTest
30 changes: 30 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: CodeQL

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
schedule:
- cron: "30 4 * * 1"
workflow_dispatch:

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
- uses: github/codeql-action/init@v4
with:
languages: java-kotlin
# buildless analysis; also enables incremental overlay databases
build-mode: none
- uses: github/codeql-action/analyze@v4
55 changes: 55 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Publish to Google Play

# Manual only — uploads the signed AAB to the chosen track, never to production.
on:
workflow_dispatch:
inputs:
track:
description: Play track
type: choice
default: internal
options: [ internal, alpha, beta ]

permissions:
contents: read

defaults:
run:
working-directory: Speedtest-Android

jobs:
publish:
name: Upload AAB to Play (${{ inputs.track }})
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
- uses: gradle/actions/setup-gradle@v6
- name: Check required secrets
run: |
if [ -z "${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}" ] || [ -z "${{ secrets.SIGNING_KEYSTORE_BASE64 }}" ]; then
echo "PLAY_SERVICE_ACCOUNT_JSON and SIGNING_* secrets are required" >&2
exit 1
fi
- name: Prepare signing keystore
run: |
echo "${{ secrets.SIGNING_KEYSTORE_BASE64 }}" | base64 -d > "$RUNNER_TEMP/keystore.jks"
{
echo "SIGNING_KEYSTORE=$RUNNER_TEMP/keystore.jks"
echo "SIGNING_STORE_PASSWORD=${{ secrets.SIGNING_STORE_PASSWORD }}"
echo "SIGNING_KEY_ALIAS=${{ secrets.SIGNING_KEY_ALIAS }}"
echo "SIGNING_KEY_PASSWORD=${{ secrets.SIGNING_KEY_PASSWORD }}"
} >> "$GITHUB_ENV"
- name: Tests and release bundle
run: ./gradlew :app:testReleaseUnitTest :app:bundleRelease
- uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1.1.5
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
packageName: org.librespeed.speedtest
releaseFiles: Speedtest-Android/app/build/outputs/bundle/release/*.aab
track: ${{ inputs.track }}
status: draft
mappingFile: Speedtest-Android/app/build/outputs/mapping/release/mapping.txt
65 changes: 65 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Release

on:
push:
tags: [ "v*" ]

permissions:
contents: read

defaults:
run:
working-directory: Speedtest-Android

jobs:
release:
name: Release build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
- uses: gradle/actions/setup-gradle@v6
- name: Check version against the tag
run: |
VERSION=$(sed -nE 's/.*versionName = "(.*)"/\1/p' app/build.gradle.kts)
if [[ "$GITHUB_REF_NAME" != "v$VERSION" ]]; then
echo "Tag $GITHUB_REF_NAME does not match versionName $VERSION" >&2
exit 1
fi
- name: Prepare signing keystore
run: |
if [ -z "${{ secrets.SIGNING_KEYSTORE_BASE64 }}" ]; then
echo "Release builds require the SIGNING_* secrets; refusing to ship a debug-signed release" >&2
exit 1
fi
echo "${{ secrets.SIGNING_KEYSTORE_BASE64 }}" | base64 -d > "$RUNNER_TEMP/keystore.jks"
{
echo "SIGNING_KEYSTORE=$RUNNER_TEMP/keystore.jks"
echo "SIGNING_STORE_PASSWORD=${{ secrets.SIGNING_STORE_PASSWORD }}"
echo "SIGNING_KEY_ALIAS=${{ secrets.SIGNING_KEY_ALIAS }}"
echo "SIGNING_KEY_PASSWORD=${{ secrets.SIGNING_KEY_PASSWORD }}"
} >> "$GITHUB_ENV"
- name: Lint and unit tests
run: ./gradlew :app:lintRelease :app:testReleaseUnitTest
- name: Build release APK and AAB
run: ./gradlew :app:assembleRelease :app:bundleRelease
- name: Collect artifacts
run: |
VERSION=$(sed -nE 's/.*versionName = "(.*)"/\1/p' app/build.gradle.kts)
cp app/build/outputs/apk/release/*.apk "librespeed_${VERSION}.apk"
cp app/build/outputs/bundle/release/*.aab "librespeed_${VERSION}.aab"
- uses: actions/upload-artifact@v7
with:
name: librespeed-release
path: |
Speedtest-Android/librespeed_*.apk
Speedtest-Android/librespeed_*.aab
- uses: softprops/action-gh-release@v3
with:
draft: true
files: Speedtest-Android/librespeed_*.apk
16 changes: 16 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Security

on:
pull_request:

permissions:
contents: read

jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/dependency-review-action@v4
with:
fail-on-severity: moderate
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/_PRIVATE
.directory
.DS_Store


26 changes: 26 additions & 0 deletions PRIVACY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Privacy Policy

LibreSpeed for Android does not collect, store or share any personal data by itself.

## What happens during a test

Running a speed test transfers data between your device and the LibreSpeed server you selected. Like any web server, the tested server can see your IP address and technical connection details for the duration of the test. The app displays your public IP address and ISP information as reported by the tested server; this information stays on your device.

## Telemetry (off by default)

The app contains an optional, explicitly opt-in telemetry switch in Settings. When — and only when — you enable it, the following is submitted to the tested server after each test:

- the test result (download, upload, ping, jitter),
- client information (application identifier and version),
- your IP address and ISP information as seen by the server,
- a technical log of the test run.

Nothing is submitted while the switch is off. Telemetry is processed by the operator of the server you tested against, not by the LibreSpeed project.

## Local data

Test history, favorite servers, custom servers and settings are stored only on your device and can be deleted at any time from within the app or by clearing the app's data.

## Permissions

The app requests the INTERNET permission only, which is required to run a speed test.
Loading