Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
165 changes: 165 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
name: Android CI

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Grant execute permission for gradlew
run: chmod +x android/gradlew

- name: Run unit tests
run: |
cd android
./gradlew test --stacktrace

- name: Build debug APK
run: |
cd android
./gradlew assembleDebug --stacktrace

- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: android/app/build/reports/tests/

build-fdroid:
Comment on lines +11 to +52

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 7 months ago

To fix the issue, we will add a permissions block at the root of the workflow file. This block will define the minimum required permissions for the workflow. Based on the provided workflow, the jobs primarily involve reading repository contents, caching dependencies, and uploading artifacts. Therefore, the contents: read permission is sufficient. If any job requires additional permissions (e.g., pull-requests: write), they can be specified at the job level.

Suggested changeset 1
.github/workflows/android.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -8,2 +8,5 @@
 
+permissions:
+  contents: read
+
 jobs:
EOF
@@ -8,2 +8,5 @@

permissions:
contents: read

jobs:
Copilot is powered by AI and may make mistakes. Always verify output.
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Set up Ruby
uses: ruby/setup-ruby@v1

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Android CI' step
Uses Step
uses 'ruby/setup-ruby' with ref 'v1', not a pinned commit hash
with:
ruby-version: '3.0'
bundler-cache: true
working-directory: android

- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Grant execute permission for gradlew
run: chmod +x android/gradlew

- name: Install Fastlane
run: |
cd android
gem install fastlane

- name: Build F-Droid release
run: |
cd android
fastlane fdroid_release

- name: Upload F-Droid APK
uses: actions/upload-artifact@v4
with:
name: fdroid-apk
path: android/fastlane/outputs/*.apk

screenshots:
Comment on lines +53 to +102

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 7 months ago

To fix the issue, we will add a permissions block at the root of the workflow file. This block will define the minimal permissions required for the workflow to function correctly. Based on the actions used in the workflow, such as actions/checkout, actions/cache, and actions/upload-artifact, the workflow primarily requires contents: read and write permissions for artifacts. We will also ensure that each job inherits these permissions unless explicitly overridden.


Suggested changeset 1
.github/workflows/android.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -8,2 +8,6 @@
 
+permissions:
+  contents: read
+  actions: write
+
 jobs:
EOF
@@ -8,2 +8,6 @@

permissions:
contents: read
actions: write

jobs:
Copilot is powered by AI and may make mistakes. Always verify output.
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Set up Ruby
uses: ruby/setup-ruby@v1

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Android CI' step
Uses Step
uses 'ruby/setup-ruby' with ref 'v1', not a pinned commit hash
with:
ruby-version: '3.0'
bundler-cache: true
working-directory: android

- name: Enable KVM
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: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Cache AVD
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-28

- name: Create AVD and generate screenshots
uses: reactivecircus/android-emulator-runner@v2

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Android CI' step
Uses Step
uses 'reactivecircus/android-emulator-runner' with ref 'v2', not a pinned commit hash
with:
api-level: 28
target: default
arch: x86_64
profile: Nexus 6
script: |
cd android
gem install fastlane
./gradlew assembleDebug assembleDebugAndroidTest
fastlane screenshots

- name: Upload screenshots
uses: actions/upload-artifact@v4
with:
name: screenshots
path: android/fastlane/metadata/android/en-US/images/
Comment on lines +103 to +165

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 7 months ago

To fix the issue, we will add a permissions block at the root of the workflow file. This block will define the minimal permissions required for the workflow to function correctly. Based on the actions used in the workflow, the following permissions are necessary:

  • contents: read for accessing repository files.
  • actions: write for caching and uploading artifacts.

The permissions block will be added at the root level to apply to all jobs in the workflow. If any job requires additional permissions, they can be overridden within the specific job.


Suggested changeset 1
.github/workflows/android.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -8,2 +8,6 @@
 
+permissions:
+  contents: read
+  actions: write
+
 jobs:
EOF
@@ -8,2 +8,6 @@

permissions:
contents: read
actions: write

jobs:
Copilot is powered by AI and may make mistakes. Always verify output.
4 changes: 4 additions & 0 deletions android/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source "https://rubygems.org"

gem "fastlane"
gem "screengrab"
180 changes: 180 additions & 0 deletions android/TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# LibrePods Android Testing & F-Droid Setup

This directory contains comprehensive testing infrastructure and F-Droid deployment configuration for the LibrePods Android app.

## Testing Infrastructure

### Overview
The testing setup includes:
- **Unit Tests**: Test core functionality with mock data
- **Instrumented Tests**: UI tests that bypass root setup
- **Screenshot Tests**: Automated screenshot generation for F-Droid
- **Mock Data Providers**: Simulate various AirPods states without hardware

### Root Setup Bypass
The key innovation in this testing setup is bypassing the root requirement for testing:

1. **Mock RadareOffsetFinder**: Tests mock `isHookOffsetAvailable()` to return `true`
2. **Skip Onboarding**: Navigation starts at "settings" instead of "onboarding"
3. **Mock AirPods State**: Use `MockData` class to simulate various device states

### Running Tests

#### Unit Tests
```bash
cd android
./gradlew test
```

#### Instrumented Tests
```bash
cd android
./gradlew connectedAndroidTest
```

#### Screenshot Generation
```bash
cd android
fastlane screenshots
```

### Test Structure

```
app/src/
β”œβ”€β”€ test/java/me/kavishdevar/librepods/
β”‚ β”œβ”€β”€ MockData.kt # Mock data providers
β”‚ β”œβ”€β”€ MainActivityTest.kt # Activity unit tests
β”‚ └── RootBypassTest.kt # Root bypass validation
β”œβ”€β”€ androidTest/java/me/kavishdevar/librepods/
β”‚ β”œβ”€β”€ LibrePodsUITest.kt # UI component tests
β”‚ β”œβ”€β”€ NavigationTest.kt # Navigation flow tests
β”‚ └── screenshots/
β”‚ └── ScreenshotTest.kt # Automated screenshot generation
```

### Mock Data

The `MockData` object provides various AirPods states for testing:

- `defaultMockState`: Normal connected state with good battery
- `lowBatteryMockState`: Low battery warning scenario
- `disconnectedMockState`: Disconnected AirPods
- `oneEarbudOutMockState`: One earbud removed

## F-Droid Setup

### Fastlane Configuration

The app includes Fastlane configuration optimized for F-Droid:

#### Available Lanes
- `fastlane test`: Run all tests
- `fastlane debug`: Build debug APK
- `fastlane fdroid_release`: Build F-Droid optimized release APK
- `fastlane screenshots`: Generate automated screenshots
- `fastlane prepare_fdroid`: Complete F-Droid preparation pipeline

#### F-Droid Specific Features
- Unsigned APK generation for F-Droid signing
- Screenshot automation for app store listings
- Metadata generation in F-Droid format
- APK validation and size checking

### Metadata Structure

```
fastlane/metadata/android/en-US/
β”œβ”€β”€ title.txt # App title
β”œβ”€β”€ short_description.txt # Brief description
β”œβ”€β”€ full_description.txt # Detailed description
β”œβ”€β”€ changelogs/
β”‚ └── 7.txt # Version 7 changelog
└── images/ # Generated screenshots
β”œβ”€β”€ phoneScreenshots/
└── tenInchScreenshots/
```

### CI/CD Integration

GitHub Actions workflow includes:
- Automated testing on push/PR
- F-Droid APK builds on main branch
- Screenshot generation with Android emulator
- Artifact uploads for releases

### Build Variants

The build configuration supports:
- **Debug**: Development builds with debugging enabled
- **Release**: F-Droid optimized builds (unsigned)

### Dependencies

Testing dependencies added:
- JUnit 4 for unit testing
- Espresso for UI testing
- MockK for mocking
- Robolectric for Android unit tests
- Screengrab for automated screenshots
- Compose UI testing framework

## Usage for F-Droid Submission

1. **Run full pipeline**:
```bash
cd android
fastlane prepare_fdroid
```

2. **Review generated files**:
- APK: `fastlane/outputs/app-release-unsigned.apk`
- Screenshots: `fastlane/metadata/android/en-US/images/`
- Metadata: `fastlane/metadata/android/en-US/`

3. **Submit to F-Droid**:
- Use the generated metadata and APK
- Screenshots are automatically optimized for F-Droid format

## Development Notes

### Testing Without Root
- Tests use mocked `RadareOffsetFinder` to bypass root checks
- UI tests can access all app screens without actual root access
- Mock data simulates real AirPods behavior patterns

### Screenshot Automation
- Screenshots are generated using real UI components
- Mock data ensures consistent visual state
- Multiple device orientations and screen sizes supported
- Automatic localization support (currently en-US)

### F-Droid Compliance
- No proprietary dependencies
- Reproducible builds
- Proper AGPL v3 licensing
- No tracking or telemetry in F-Droid builds

## Troubleshooting

### Common Issues

1. **Gradle sync fails**: Check Android SDK and JDK versions
2. **Screenshot tests fail**: Ensure emulator has sufficient resources
3. **Mock data not working**: Verify MockK setup in test dependencies

### Debug Commands

```bash
# Check test configuration
./gradlew tasks --all | grep test

# Verbose test output
./gradlew test --info

# Clean build
./gradlew clean build

# Check APK details
aapt dump badging app/build/outputs/apk/release/app-release-unsigned.apk
```
Loading