mirror of
https://github.com/yuga-hashimoto/openclaw-assistant.git
synced 2026-08-14 07:02:02 +00:00
chore: improve OSS docs and CI (#317)
This commit is contained in:
@@ -65,6 +65,34 @@ jobs:
|
||||
- name: Run Lint (standard/debug only)
|
||||
run: ./gradlew lintStandardDebug
|
||||
|
||||
# ── Unit Tests ────────────────────────────────────────────────────────────
|
||||
# Runs the standard/debug unit tests with the checked-in Firebase stub so
|
||||
# contributors can reproduce the exact command locally.
|
||||
unit-test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
FIREBASE_ENABLED: false
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup google-services.json
|
||||
run: cp app/google-services.json.example app/google-services.json
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Run Unit Tests (standard/debug)
|
||||
run: ./gradlew clean testStandardDebugUnitTest
|
||||
|
||||
# ── Build APKs ────────────────────────────────────────────────────────────
|
||||
# Builds Standard and Full debug APKs. Using --parallel lets Gradle compile
|
||||
# the two flavors concurrently within the same job.
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# Building OpenClaw Assistant
|
||||
|
||||
This repository can be built locally without access to the release signing key or production Firebase configuration.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- JDK 17
|
||||
- Android SDK Platform 35 and build tools installed
|
||||
- `local.properties` pointing to your Android SDK if you are not using Android Studio
|
||||
|
||||
## Debug builds
|
||||
|
||||
1. Copy the checked-in Firebase stub:
|
||||
|
||||
```bash
|
||||
cp app/google-services.json.example app/google-services.json
|
||||
```
|
||||
|
||||
2. Disable Firebase at build time when using the stub config:
|
||||
|
||||
```bash
|
||||
FIREBASE_ENABLED=false ./gradlew assembleStandardDebug
|
||||
```
|
||||
|
||||
3. Optional variants:
|
||||
|
||||
```bash
|
||||
FIREBASE_ENABLED=false ./gradlew assembleFullDebug
|
||||
FIREBASE_ENABLED=false ./gradlew lintStandardDebug
|
||||
FIREBASE_ENABLED=false ./gradlew clean testStandardDebugUnitTest
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Debug builds use the normal Android debug keystore. No manual keystore setup is required.
|
||||
- `standard` excludes VOICEVOX. `full` bundles VOICEVOX runtime assets and is larger.
|
||||
- The checked-in `google-services.json.example` is for local debug builds only. Do not use it for releases.
|
||||
|
||||
## Release builds
|
||||
|
||||
Release builds require your own Firebase config, signing key, and a populated `local.properties`.
|
||||
|
||||
Required `local.properties` keys:
|
||||
|
||||
```properties
|
||||
storeFile=release.keystore
|
||||
storePassword=...
|
||||
keyAlias=...
|
||||
keyPassword=...
|
||||
```
|
||||
|
||||
You also need a real `app/google-services.json` before running:
|
||||
|
||||
```bash
|
||||
./gradlew assembleStandardRelease assembleFullRelease bundleStandardRelease bundleFullRelease
|
||||
```
|
||||
|
||||
The GitHub release workflow uses repository secrets for these values; contributors do not need them for normal OSS development.
|
||||
+8
-3
@@ -38,9 +38,14 @@ Please follow these steps to have your contribution considered by the maintainer
|
||||
|
||||
1. **Fork the repository** and clone it locally.
|
||||
2. Create a **new branch** for your feature or fix: `git checkout -b feature/amazing-feature` or `git checkout -b fix/annoying-bug`.
|
||||
3. **Commit your changes** with a clear commit message.
|
||||
4. **Push to the branch**: `git push origin feature/amazing-feature`.
|
||||
5. **Submit a pull request** through GitHub.
|
||||
3. Follow the local setup and build instructions in [BUILDING.md](BUILDING.md).
|
||||
4. Run the baseline checks before opening a PR:
|
||||
- `cp app/google-services.json.example app/google-services.json`
|
||||
- `FIREBASE_ENABLED=false ./gradlew lintStandardDebug`
|
||||
- `FIREBASE_ENABLED=false ./gradlew clean testStandardDebugUnitTest`
|
||||
5. **Commit your changes** with a clear commit message.
|
||||
6. **Push to the branch**: `git push origin feature/amazing-feature`.
|
||||
7. **Submit a pull request** through GitHub.
|
||||
|
||||
## Styleguides
|
||||
|
||||
|
||||
@@ -111,6 +111,8 @@
|
||||
|
||||
Download APK from [Releases](https://github.com/yuga-hashimoto/OpenClawAssistant/releases), or build from source.
|
||||
|
||||
For local source builds, see [BUILDING.md](BUILDING.md). It covers the checked-in debug Firebase stub, test commands, and release signing requirements.
|
||||
|
||||
#### 2. Gateway Connection (Recommended)
|
||||
|
||||
The app connects to your OpenClaw server via the Gateway protocol.
|
||||
@@ -248,10 +250,14 @@ ngrok http 18789
|
||||
|
||||
Pull Requests welcome! Feel free to report issues.
|
||||
|
||||
Please follow [BUILDING.md](BUILDING.md) for local setup and run the documented lint/unit test commands before opening a PR.
|
||||
|
||||
### 📄 License
|
||||
|
||||
MIT License - See [LICENSE](LICENSE) for details.
|
||||
|
||||
Bundled third-party binaries and assets are documented in [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md).
|
||||
|
||||
---
|
||||
|
||||
## 日本語
|
||||
@@ -331,6 +337,8 @@ MIT License - See [LICENSE](LICENSE) for details.
|
||||
|
||||
[Releases](https://github.com/yuga-hashimoto/OpenClawAssistant/releases) からAPKをダウンロード、またはソースからビルド。
|
||||
|
||||
ローカルでのソースビルド手順は [BUILDING.md](BUILDING.md) を参照してください。debug 用 Firebase スタブ、テストコマンド、release 署名の前提をまとめています。
|
||||
|
||||
#### 2. Gateway接続(推奨)
|
||||
|
||||
アプリはGatewayプロトコルを通じてOpenClawサーバーと接続します。
|
||||
@@ -467,10 +475,14 @@ Chat Completions APIが有効であることを `moltbot.json` で確認:
|
||||
|
||||
Pull Requests歓迎!Issues報告もお気軽に。
|
||||
|
||||
PR 前のローカルセットアップと lint / unit test 手順は [BUILDING.md](BUILDING.md) を参照してください。
|
||||
|
||||
### 📄 ライセンス
|
||||
|
||||
MIT License - 詳細は [LICENSE](LICENSE) を参照。
|
||||
|
||||
同梱している第三者バイナリ・アセットは [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md) にまとめています。
|
||||
|
||||
---
|
||||
|
||||
## Star History
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Third-Party Licenses
|
||||
|
||||
This file tracks third-party binaries and assets that are checked into this repository or shipped in release artifacts.
|
||||
|
||||
It is not a complete list of all transitive Gradle dependencies. For those, refer to the dependency metadata resolved during the build.
|
||||
|
||||
## Bundled components
|
||||
|
||||
### Vosk Android bindings
|
||||
|
||||
- Usage: offline wake word and speech recognition support
|
||||
- Source in this repo: `com.alphacephei:vosk-android:0.3.75`
|
||||
- Upstream project: https://github.com/alphacep/vosk-api
|
||||
- Upstream license: Apache-2.0
|
||||
- Notes in this repo:
|
||||
- app code dependency declaration: `app/build.gradle.kts`
|
||||
- bundled model notice: `app/src/main/assets/model/README`
|
||||
|
||||
### Vosk English mobile model
|
||||
|
||||
- Usage: default offline English recognition model
|
||||
- Source in this repo: `app/src/main/assets/model/`
|
||||
- Bundled notice file: `app/src/main/assets/model/README`
|
||||
- Notes:
|
||||
- The checked-in model directory includes the upstream README notice.
|
||||
- If you replace or redistribute a different Vosk model, verify that model's own license terms from the upstream distribution.
|
||||
|
||||
### VOICEVOX CORE Android AAR
|
||||
|
||||
- Usage: VOICEVOX support in the `full` flavor
|
||||
- Source in this repo: `app/libs/voicevoxcore-android-0.16.4.aar`
|
||||
- Provenance metadata: `app/libs/java_packages.zip`
|
||||
- Upstream project: https://github.com/VOICEVOX/voicevox_core
|
||||
- Upstream license for version `0.16.4`: MIT
|
||||
- Notes:
|
||||
- VOICEVOX states that versions earlier than `0.16` used a different license. Keep the version pinned unless you re-review the license.
|
||||
|
||||
### ONNX Runtime Android library
|
||||
|
||||
- Usage: ONNX runtime support for VOICEVOX-related binaries
|
||||
- Source in this repo: `app/libs/onnxruntime-android.aar`
|
||||
- Upstream project: https://github.com/microsoft/onnxruntime
|
||||
- Upstream license: MIT
|
||||
|
||||
### Custom VOICEVOX ONNX Runtime binary
|
||||
|
||||
- Usage: runtime required by the bundled VOICEVOX full flavor
|
||||
- Source in this repo: `app/src/full/jniLibs/arm64-v8a/libvoicevox_onnxruntime.so`
|
||||
- Notes:
|
||||
- This binary is distributed together with the VOICEVOX full flavor assets.
|
||||
- Review upstream ONNX Runtime and VOICEVOX release materials again before upgrading or replacing it.
|
||||
|
||||
### Open JTalk dictionary
|
||||
|
||||
- Usage: Japanese text processing assets for VOICEVOX
|
||||
- Source in this repo: `app/src/full/assets/open_jtalk_dic_utf_8-1.11/`
|
||||
- Bundled license file: `app/src/full/assets/open_jtalk_dic_utf_8-1.11/COPYING`
|
||||
- Notes:
|
||||
- Keep `COPYING` with the dictionary assets when redistributing.
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "000000000000",
|
||||
"project_id": "mock-project-id",
|
||||
"storage_bucket": "mock-project-id.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:000000000000:android:0000000000000001",
|
||||
"android_client_info": {
|
||||
"package_name": "com.openclaw.assistant.debug"
|
||||
}
|
||||
},
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "mock-api-key"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"analytics_service": {
|
||||
"status": 1
|
||||
},
|
||||
"appinvite_service": {
|
||||
"status": 1,
|
||||
"other_platform_oauth_client": []
|
||||
},
|
||||
"ads_service": {
|
||||
"status": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.openclaw.assistant.node
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.openclaw.assistant.gateway.GatewaySession
|
||||
import io.mockk.every
|
||||
@@ -8,6 +9,7 @@ import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import androidx.core.content.ContextCompat
|
||||
import android.content.pm.PackageManager
|
||||
import android.Manifest
|
||||
@@ -15,7 +17,11 @@ import android.provider.CalendarContract
|
||||
import android.database.MatrixCursor
|
||||
import android.content.ContentResolver
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class CalendarHandlerTest {
|
||||
private val context = mockk<Context>()
|
||||
private val contentResolver = mockk<ContentResolver>()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.openclaw.assistant.node
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.openclaw.assistant.gateway.GatewaySession
|
||||
import io.mockk.every
|
||||
@@ -8,6 +9,7 @@ import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import androidx.core.content.ContextCompat
|
||||
import android.content.pm.PackageManager
|
||||
import android.Manifest
|
||||
@@ -15,7 +17,11 @@ import android.provider.ContactsContract
|
||||
import android.database.MatrixCursor
|
||||
import android.content.ContentResolver
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class ContactsHandlerTest {
|
||||
private val context = mockk<Context>()
|
||||
private val contentResolver = mockk<ContentResolver>()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.openclaw.assistant.node
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.openclaw.assistant.LocationMode
|
||||
import com.openclaw.assistant.SecurePrefs
|
||||
@@ -13,8 +14,10 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class DeviceHandlerTest {
|
||||
|
||||
private val context: Context = RuntimeEnvironment.getApplication()
|
||||
|
||||
@@ -1,51 +1,55 @@
|
||||
package com.openclaw.assistant.node
|
||||
|
||||
import android.app.Application
|
||||
import android.app.Notification
|
||||
import android.content.Context
|
||||
import com.openclaw.assistant.gateway.GatewaySession
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import android.service.notification.StatusBarNotification
|
||||
import io.mockk.unmockkStatic
|
||||
import android.provider.Settings
|
||||
import android.content.ContentResolver
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class NotificationsHandlerTest {
|
||||
private val context = mockk<Context>()
|
||||
private val contentResolver = mockk<ContentResolver>()
|
||||
private val context: Context = RuntimeEnvironment.getApplication()
|
||||
private val notificationManager = mockk<NotificationManager>()
|
||||
private val handler = NotificationsHandler(context, notificationManager)
|
||||
|
||||
@Test
|
||||
fun `handleList returns error when service disabled`() = runBlocking {
|
||||
every { context.contentResolver } returns contentResolver
|
||||
every { context.packageName } returns "com.openclaw.assistant"
|
||||
mockkStatic(Settings.Secure::class)
|
||||
every { Settings.Secure.getString(contentResolver, "enabled_notification_listeners") } returns ""
|
||||
Settings.Secure.putString(context.contentResolver, "enabled_notification_listeners", "")
|
||||
|
||||
val result = handler.handleList()
|
||||
|
||||
assertEquals(false, result.ok)
|
||||
assertEquals("NOTIFICATIONS_PERMISSION_REQUIRED", result.error?.code)
|
||||
unmockkStatic(Settings.Secure::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleList returns notifications when service enabled`() = runBlocking {
|
||||
every { context.contentResolver } returns contentResolver
|
||||
every { context.packageName } returns "com.openclaw.assistant"
|
||||
mockkStatic(Settings.Secure::class)
|
||||
every { Settings.Secure.getString(contentResolver, "enabled_notification_listeners") } returns "com.openclaw.assistant"
|
||||
Settings.Secure.putString(
|
||||
context.contentResolver,
|
||||
"enabled_notification_listeners",
|
||||
context.packageName
|
||||
)
|
||||
|
||||
val sbn = mockk<StatusBarNotification>()
|
||||
val notification = Notification.Builder(context, "test-channel")
|
||||
.setContentTitle("Title")
|
||||
.setContentText("Text")
|
||||
.build()
|
||||
every { sbn.key } returns "test_key"
|
||||
every { sbn.packageName } returns "com.test"
|
||||
every { sbn.postTime } returns 12345L
|
||||
every { sbn.notification.extras.getCharSequence("android.title") } returns "Title"
|
||||
every { sbn.notification.extras.getCharSequence("android.text") } returns "Text"
|
||||
every { sbn.notification } returns notification
|
||||
|
||||
every { notificationManager.getActiveNotifications() } returns listOf(sbn)
|
||||
|
||||
@@ -54,6 +58,5 @@ class NotificationsHandlerTest {
|
||||
assertEquals(true, result.ok)
|
||||
val json = result.payloadJson ?: ""
|
||||
assertEquals(true, json.contains("test_key"))
|
||||
unmockkStatic(Settings.Secure::class)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.openclaw.assistant.node
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.openclaw.assistant.gateway.GatewaySession
|
||||
import io.mockk.every
|
||||
@@ -8,15 +9,19 @@ import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import androidx.core.content.ContextCompat
|
||||
import android.content.pm.PackageManager
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import android.database.MatrixCursor
|
||||
import android.content.ContentResolver
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class PhotosHandlerTest {
|
||||
private val context = mockk<Context>()
|
||||
private val contentResolver = mockk<ContentResolver>()
|
||||
@@ -25,8 +30,7 @@ class PhotosHandlerTest {
|
||||
@Test
|
||||
fun `handleLatest returns error when permission missing`() = runBlocking {
|
||||
mockkStatic(ContextCompat::class)
|
||||
// Assume API < 33
|
||||
every { ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) } returns PackageManager.PERMISSION_DENIED
|
||||
every { ContextCompat.checkSelfPermission(context, any()) } returns PackageManager.PERMISSION_DENIED
|
||||
|
||||
val result = handler.handleLatest()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user