Skip to content

Add WearOS companion app#3185

Open
blacklight wants to merge 24 commits into
CatimaLoyalty:mainfrom
blacklight:feat/wearos
Open

Add WearOS companion app#3185
blacklight wants to merge 24 commits into
CatimaLoyalty:mainfrom
blacklight:feat/wearos

Conversation

@blacklight

@blacklight blacklight commented Jul 2, 2026

Copy link
Copy Markdown

Add WearOS companion app

Adds a standalone Wear OS app that displays a user's loyalty cards and renders their barcodes directly on the watch.

Addresses:

Closes:

What's new

Wear OS module (wear/)

  • Card list screen showing all non-archived loyalty cards fetched from the phone, with per-card header colours
  • Card detail screen rendering the barcode (QR, EAN-13, Code 128, Aztec, Data Matrix, etc.) using ZXing, constrained to fit within a round watch face
  • Swipe-to-dismiss navigation between screens
  • BluetoothCardClient — GMS-free RFCOMM client that connects to any bonded device advertising the Catima service UUID and fetches card JSON over a newline-delimited protocol
  • WearDataListenerService — GMS WearableListenerService retained as automatic fallback for standard Android + Google Play Services setups
  • Graceful error state when neither transport can reach the phone

Phone module (app/)

  • BluetoothServerService — RFCOMM server that starts on app launch, listens for CARDS_REQUEST commands from the watch and responds with card list JSON
  • WearDataService — GMS WearableListenerService retained as fallback
  • BLUETOOTH_CONNECT runtime permission requested on first launch (Android 12+)

Transport strategy

The watch tries Bluetooth Classic (RFCOMM) first. This works without Google Play Services and is therefore compatible with GrapheneOS and other setups where sandboxed GMS cannot route Wearable Data Layer messages. If no bonded device responds on the Catima UUID, it falls back to the GMS MessageClient path, which works on standard Android.

Key fixes

  • org.json.JSONObject.optString() returns the literal string "null" for JSON null values; replaced with isNull() guards in WearCard.fromJson to prevent ZXing receiving "null" as a barcode value
  • Square barcodes (QR, Aztec, Data Matrix) are now constrained with a 15 % round-screen inset via BoxWithConstraints so they do not overflow the circular display boundary

Permissions added

  • BLUETOOTH / BLUETOOTH_ADMIN (≤ API 30, no prompt required)
  • BLUETOOTH_CONNECT (API 31+, runtime prompt on both phone and watch)

Testing

Verified on Pixel Watch 3 (Wear OS 5) paired with Pixel 9a running GrapheneOS with the F-Droid build of Catima. Full round-trip latency phone → watch is approximately 400 ms over Bluetooth Classic.

Screenshots

watch_media_2026-07-02_17_47_04 watch_media_2026-07-02_17_47_19

- BluetoothServerService: RFCOMM server on phone, listens for CARDS_REQUEST
  and responds with card list JSON over a newline-delimited protocol
- BluetoothCardClient: watch-side client, iterates bonded devices trying the
  Catima UUID, falls back to GMS Wearable Data Layer if BT fails
- Runtime BLUETOOTH_CONNECT permission requests on both phone and watch
- standalone=true + wearable lib optional on watch (no GMS dependency)
- Bluetooth-first with automatic GMS fallback ensures compatibility with
  both GrapheneOS/FOSS and standard Android+GMS setups
- Fix barcode not shown for cards with null barcodeId: org.json.optString()
  returns literal string 'null' for JSON null values; use isNull() check first
- Fix QR/Aztec/DataMatrix overflow on round watch: use BoxWithConstraints to
  measure screen size and apply a 15% round-screen inset to constrain the
  square barcode to stay within the circular display boundary
@TheLastProject

Copy link
Copy Markdown
Member

As written in the issues, WearOS requires proprietary dependencies. So this must be at very least be excluded from the foss flavour. That is a blocker, because F-Droid does not allow proprietary dependencies (and I don't want people to be forced to run proprietary Google code to run Catima, there must always be an option free is proprietary code)

Aside from that, cool :)

I don't have a WearOS device but I will look at this in an emulator soon. Are you willing to keep maintaining WearOS code long term? Just for my expectation of expected (future) workload :)

After a reboot the BT fetch completes while the watch screen is still
off/settling. The StateFlow update is lost because Compose hasn't
composed yet in that window. Fix by:
- Moving card fetch to onResume so it runs every time the user wakes
  the screen, not just once in onCreate
- Adding fetchInFlight guard to prevent concurrent duplicate requests
- Calling WearCardRepository.reset() at the start of each fetch to
  clear stale state and re-show the spinner during refresh
Android's app standby mechanism stops background services when the app
is idle (~5 min). Promote BluetoothServerService to a foreground service
with a silent low-priority notification so the OS cannot kill it.

- Add FOREGROUND_SERVICE + FOREGROUND_SERVICE_CONNECTED_DEVICE permissions
- Add foregroundServiceType="connectedDevice" to manifest declaration
- Call startForeground() on service start with a silent IMPORTANCE_MIN
  notification (uses FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE on API 34+)
- Restart the service via stopSelf() if the accept loop exits unexpectedly,
  relying on START_STICKY to have the OS bring it back up
@blacklight

blacklight commented Jul 2, 2026

Copy link
Copy Markdown
Author

@TheLastProject thank you for your reply!

I had the same uncertainty around the non-free WearOS dependencies.

I eventually resorted to implementing my own Bluetooth service to let the phone and watch communicate (also because GMS didn't work on my sandboxed Google Play Services on GrapheneOS), but there's still a GMS fallback that makes the app dependent on Google's non-free libraries, I agree.

I'm not very familiar with F-Droid's requirements when it comes to WearOS apps and I don't know if there are some relevant examples I could take a look at.

But my guess is that without the GMS dependency and with a custom Bluetooth listener to replace it the lights should be green, right?

If that's still not the case I'll be happy to split the WearOS implementation and make it only available for the Play Store build.

Are you willing to keep maintaining WearOS code long term?

Of course 🙂

- Add KeepScreenOnAtMaxBrightness() composable using DisposableEffect:
  sets FLAG_KEEP_SCREEN_ON and BRIGHTNESS_OVERRIDE_FULL on the window
  when CardDetail is composed, restores original flags/brightness on dispose
- Switch CardDetail to white background with black text/barcode so codes
  are displayed as dark-on-light (required for scanner compatibility and
  better contrast under store lighting)
@TheLastProject

Copy link
Copy Markdown
Member

I eventually resorted to implementing my own Bluetooth service to let the phone and watch communicate (also because GMS didn't work on my sandboxed Google Play Services on GrapheneOS), but there's still a GMS fallback that makes the app dependent on Google's non-free libraries, I agree.

I'm not very familiar with F-Droid's requirements when it comes to WearOS apps and I don't know if there are some relevant examples I could take a look at.

But my guess is that without the GMS dependency and with a custom Bluetooth listener to replace it the lights should be green, right?

Yeah, the issue is specifically

    // Wearable Data Layer
    implementation(libs.com.google.android.gms.play.services.wearable)

in app/build.gradle.kts. That has to be excluded from the foss flavour.

I think that can be solved by just saying gPlayImplementation instead of implementation to only include it in the gPlay flavour.

With regards to testing, I see you removed the .debug suffix from the debug builds. This will make it very hard for me to do an actual on-device test. Maybe it's possible to add a debug version to the Wear app as well with debug in the package name? I believe there is this annoying Google restricting where the wear app must almost share a package name or so, which I guess is why you did that.

Are you willing to keep maintaining WearOS code long term?

Of course 🙂

That's great :)

I see you're still pushing to this occasionally. Would you like me to start reviewing already or give you some times to finish up some stuff first? :)

The Bluetooth RFCOMM transport is now the sole data channel, making
GMS a hard dependency with no benefit. Removed entirely:

- WearDataListenerService (wear) - GMS WearableListenerService
- WearDataService (phone) - GMS WearableListenerService
- requestCardsViaGms() in wear MainActivity
- GMS path/key constants from WearProtocol (only BT constants remain)
- WearDataListenerService + WearDataService from both manifests
- com.google.android.wearable uses-library + standalone meta-data from wear manifest
- play-services-wearable from wear/build.gradle.kts
- play-services-wearable from app/build.gradle.kts
- com-google-android-gms-play-services-wearable from libs.versions.toml

BT failure now goes directly to setPhoneNotReachable() showing the
'phone not connected' error state.
Cards are now stored in SharedPreferences after each successful BT sync.
On launch, cached cards are shown immediately before the BT request
completes, so the UI is never blank if the phone isn't available.

- WearCardStore: new singleton that reads/writes card list JSON to
  SharedPreferences (catima_wear_cards / cards_json)
- WearCardRepository: loadCache() pre-populates _cards from store on
  app start; updateCards() now persists to store after each sync;
  new syncing StateFlow; setPhoneNotReachable() always sets the flag
  so the UI can indicate offline state even when cards are shown
- MainActivity: calls loadCache() before setContent so cached cards
  are available for the first composition; setSyncing(true) replaces
  reset() so existing cards remain visible during refresh; passes
  Context to updateCards()
- CardListScreen: new syncing param; shows 'Syncing with phone…'
  footer while BT request is in flight; shows 'Offline – showing
  cached cards' footer when phone was unreachable but cards exist;
  full 'phone not connected' screen only shown when cards=null+offline
@TheLastProject

Copy link
Copy Markdown
Member

I just merged the Katastima APK scanner into main. That should make it clear which dependencies are in which flavour if you rebase your MR on main :)

- Added again to the `app` build.gradle.kts
- Added also to the `wear` build
@blacklight

blacklight commented Jul 2, 2026

Copy link
Copy Markdown
Author

I just pushed two last changes:

  • 744a7453 to remove all the GMS dependencies (no more GMS fallback, only Bluetooth RFCOMM service).
  • aa72cc19 adds watch storage support, so cached cards will still be displayed also if the phone is disconnected.

The code should be ready for review now.

One last open question would be about the packaging/release process.

I don't know of other WearOS apps distributed on F-Droid so I'm not sure how the distribution process for companion apps would look like.

On the Play Store the APKs are usually bundled, not sure about F-Droid - if they can be released together or the WearOS app needs another store entry.

Also, I guess that a mention in the README/docs is also needed?

@TheLastProject

Copy link
Copy Markdown
Member

The code should be ready for review now.

Great, I'll look soon. Though the lint seems to be failing. We should also see if we can do the whole build/test of the WearOS app in GitHub Actions. But that's not something to delay review (what delays review is me not having time yet but I'll make time soon!)

One last open question would be about the packaging/release process.

I don't know of other WearOS apps distributed on F-Droid so I'm not sure how the distribution process for companion apps would look like.

On the Play Store the APKs are usually bundled, not sure about F-Droid - if they can be released together or the WearOS app needs another store entry.

I don't believe it is possible to write WearOS apps without proprietary dependencies, so that's a no-go for F-Droid. However, I can post it to GitHub and maybe it can be published to IzzyOnDroid. I don't really know enough yet about installing apps on WearOS, trying to hunt down a cheap second-hand WearOS watch and then discuss with Izzy afterwards :)

Also, I guess that a mention in the README/docs is also needed?

I guess so, but no rush, we can also set the documentation up after this is merged :)

@TheLastProject

Copy link
Copy Markdown
Member

Not completely sure but I think this may be what we want as a new wear.yml GitHub action (based on android.yml), so we can automatically build and lint the wear app too each commit :)

I removed all the test stuff, as the wear app seems to lack tests (which is understandable, I'm not going to complain).

name: Android Wear CI
on:
  workflow_dispatch:
  push:
    branches:
      - main
      - staging
      - trying
  pull_request:
    branches:
      - main
permissions:
  actions: none
  checks: none
  contents: read
  deployments: none
  discussions: none
  id-token: none
  issues: none
  packages: none
  pages: none
  pull-requests: none
  repository-projects: none
  security-events: none
  statuses: none
env:
  JAVA_HOME: /usr/lib/jvm/java-25-openjdk-amd64
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7.0.0
      - uses: gradle/actions/wrapper-validation@v6.2.0
      - name: set up OpenJDK 25
        run: |
          sudo apt-get update
          sudo apt-get install -y openjdk-25-jdk-headless
          sudo update-alternatives --auto java
      - name: Build
        run: ./gradlew :wear:assembleRelease
      - name: Check for non-free libraries
        run: |
          wget -q https://codeberg.org/Katastima/apkscanner/releases/download/v0.0.7/apk-scanner_v0.0.7.jar
          java -jar apk-scanner_v0.0.7.jar scan-apk "$(echo 'wear/build/outputs/apk/release/wear-release-unsigned.apk')"
      - name: Check lint
        run: ./gradlew :wear:lintRelease

@TheLastProject

Copy link
Copy Markdown
Member

Sadly my attempts to test this have been foiled by the Google Pixel Watch app, which is required to link a WearOS app according to Android Studio, crashing on real devices and emulators for me.

image

Maybe the link-to-emulated-watch flow is just broken. I'll try to get my hands on a second hand Android WearOS watch soon and see if that behaves better.

I'll take a look at the code itself soon :)

@TheLastProject TheLastProject left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks quite reasonable! I feel I don't have much complaints about the general structure, great.

I haven't been able to test it yet, but I'm in talks with someone to get a second hand WearOS device. I may run into some more issues when testing on device, but for now, this is my feedback (also don't forget about the suggested GitHub Actions workflow please :))

Comment thread app/src/main/java/protect/card_locker/BluetoothServerService.kt Outdated
Comment thread app/src/main/java/protect/card_locker/BluetoothServerService.kt Outdated
Comment thread app/src/main/java/protect/card_locker/BluetoothServerService.kt Outdated
Comment thread app/src/main/java/protect/card_locker/wearos/BluetoothServerService.kt Outdated
Comment thread wear/src/main/java/me/hackerchick/catima/wear/WearCardRepository.kt
Comment thread wear/src/main/java/me/hackerchick/catima/wear/WearCardStore.kt Outdated
Comment thread wear/src/main/java/me/hackerchick/catima/wear/WearProtocol.kt
Comment thread wear/build.gradle.kts
Comment thread .gitignore
Add PROTOCOL_VERSION (1) to the phone↔watch Bluetooth protocol so
both sides can detect an outdated companion app.

- Phone: accept V1/CARDS_REQUEST and return {"version":1,"cards":[...]}
- Phone: keep plain-array response for legacy CARDS_REQUEST
- Wear: send V1/CARDS_REQUEST and parse the version envelope
- Wear: display "phone outdated" / "watch outdated" messages

Closes: CatimaLoyalty#3185 (comment)
Add a "Sync with Wear OS" toggle in settings and only start the
Bluetooth companion server when the setting is enabled and the
BLUETOOTH_CONNECT permission is granted.

- Add Wear OS preference category and toggle
- Gate service start in LoyaltyCardLockerApplication
- Remove redundant direct startService in MainActivity.onCreate
- Keep permission-callback start for first-time grant

Addresses: CatimaLoyalty#3185 (comment)
@TheLastProject

Copy link
Copy Markdown
Member

I expect to have a WearOS smartwatch by tuesday evening so I may be able to test this on-device Tuesday evening or Wednesday, assuming the official WearOS app actually works properly. So I can return to this with more on-device experience then.

…ings

- Default pref_wear_sync to false (opt-in rather than opt-out)
- Remove BT permission request from MainActivity; it now lives exclusively
  in SettingsFragment where the toggle is visible and context is clear
- Request BLUETOOTH_CONNECT immediately when the toggle is turned on
- Start/stop BluetoothServerService reactively on toggle change
- Re-request permission on SettingsFragment resume to handle cases where
  Android revoked it while the setting was still enabled
- Simplify processParseResultList() by removing unused group/closeApp params

Addresses: CatimaLoyalty#3185 (comment)
…Screen

- Replace unsafe `LocalContext.current as? Activity` with `LocalActivity.current`
  in `KeepScreenOnAtMaxBrightness`.
- Refactor `generateBarcode` to return a `BarcodeResult` carrying both
  the bitmap and `isSquare` flag, removing the redundant `BarcodeFormat`
  lookup in the composable.

Addresses: CatimaLoyalty#3185 (comment)
Previously, permission denial, Bluetooth being disabled, and no paired
device all collapsed into a single NO_DEVICE status, showing a generic
"could not reach phone" message. Users who denied the Bluetooth permission
had no way to know what went wrong.

Add PERMISSION_DENIED and BLUETOOTH_DISABLED to BluetoothCardClient.FetchStatus
and thread them through WearCardRepository, MainActivity, and CardListScreen,
showing targeted messages in both the full-screen (no cached cards) and
footer (cached cards present) states.

Addresses: CatimaLoyalty#3185 (comment)
…y swallowing them

A JSON parse error in WearCardRepository.updateCards was only logged,
leaving the UI with no indication that the sync had failed. Add a
syncError StateFlow that is set on parse failure and cleared on a
successful update or new sync attempt, then thread it through
MainActivity and CardListScreen with a targeted message in both the
full-screen and footer states.

Addresses: CatimaLoyalty#3185 (comment)
@blacklight

Copy link
Copy Markdown
Author

@TheLastProject I have tried to address all the comments. Let me know if you have any luck testing this on a real WearOS device 🙂

@blacklight blacklight requested a review from TheLastProject July 6, 2026 14:51
@TheLastProject

Copy link
Copy Markdown
Member

I'll review soon, don't know if I'll still have time tonight. Can you add the GitHub Actions file suggested in #3185 (comment)? Then we can also build and lint the WearOS app directly in the MR, which may show some new stuff :)

@TheLastProject TheLastProject left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sadly not able to get the watch and the phone to connect even though the devices are connected over Bluetooth. Could you double check this?

<resources>
<string name="app_name">Catima</string>
<string name="no_cards">No cards found.\nOpen Catima on your phone to add cards.</string>
<string name="phone_not_connected">Could not reach Catima on your phone.\nMake sure the app is installed and your watch is connected.</string>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<string name="phone_not_connected">Could not reach Catima on your phone.\nMake sure the app is installed and your watch is connected.</string>
<string name="phone_not_connected">Could not reach Catima on your phone.\nMake sure the app is installed, your watch is connected and WearOS is enabled in Catima settings.</string>

Comment on lines +183 to +201
val wearSyncPreference = findPreference<SwitchPreferenceCompat>(getString(R.string.settings_key_wear_sync))
wearSyncPreference!!.setOnPreferenceChangeListener { _, newValue ->
val enabled = newValue as Boolean
val ctx = requireContext()
if (enabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
ContextCompat.checkSelfPermission(ctx, android.Manifest.permission.BLUETOOTH_CONNECT)
!= PackageManager.PERMISSION_GRANTED
) {
mBtPermissionLauncher.launch(android.Manifest.permission.BLUETOOTH_CONNECT)
} else {
ctx.startService(Intent(ctx, BluetoothServerService::class.java))
}
} else {
ctx.stopService(Intent(ctx, BluetoothServerService::class.java))
}
true
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my test devices (Samsung Galaxy S20+ 5G (Android 13), Samsung Galaxy Watch 4) toggling this on does not seem to work. I've tried to restart the watch and the phone after enabling to no avail. I keep getting the "Could not reach Catima on your phone" alert. Anything I'm missing?

I also noticed it didn't ask for bluetooth/location permission. Could it be you broke the permission request code in refactoring?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was wrong about the permission request issue. I seemed to have had that permission granted already and forgot 😅 Sadly it not syncing is staying an issue.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed this in the logging.

Watch side:

2026-07-07 20:50:03.420 10266-10293 BluetoothSocket         me.hackerchick.catima.wear.debug     I  connect(), socket connected. mPort=10
2026-07-07 20:50:03.452  1115-5468  BluetoothAdapterService com.android.bluetooth                E  setSocketUsed() [10] me.hackerchick.catima.wear.debug (RFCOMM) / true
2026-07-07 20:50:03.484 10266-10293 CatimaBtClient          me.hackerchick.catima.wear.debug     D  Connected to Galaxy S20+ 5G
2026-07-07 20:50:03.956 10266-10293 CatimaBtClient          me.hackerchick.catima.wear.debug     D  Failed to connect to Galaxy S20+ 5G: bt socket closed, read return: -1
2026-07-07 20:50:03.957 10266-10293 BluetoothSocket         me.hackerchick.catima.wear.debug     D  close() this: XX:XX:XX:XX:C9:AD, channel: 10, mSocketIS: android.net.LocalSocketImpl$SocketInputStream@44509f9, mSocketOS: android.net.LocalSocketImpl$SocketOutputStream@dbef79f, mSocket: android.net.LocalSocket@8c34cb5 impl:android.net.LocalSocketImpl@584ff4a fd:java.io.FileDescriptor@442069d, mSocketState: CONNECTED
2026-07-07 20:50:03.978  1115-5468  BluetoothAdapterService com.android.bluetooth                E  setSocketUsed() [10] me.hackerchick.catima.wear.debug (RFCOMM) / false
2026-07-07 20:50:03.979 10266-10293 CatimaBtClient          me.hackerchick.catima.wear.debug     W  No Catima phone found among bonded devices
2026-07-07 20:50:03.980 10266-10293 CatimaWear              me.hackerchick.catima.wear.debug     W  Bluetooth failed, phone not reachable
2026-07-07 20:50:03.660 20648-20730 BluetoothSocket         me.hackerchick.catima.debug          I  acceptSocket() for device D01B49_F
2026-07-07 20:50:03.663 20648-20730 BluetoothSocket         me.hackerchick.catima.debug          D  return acceptedSocket=android.bluetooth.BluetoothSocket@1aa187c
2026-07-07 20:50:03.666 20648-20730 CatimaBtServer          me.hackerchick.catima.debug          D  Connected to Galaxy Watch4 (45SX)
2026-07-07 20:50:03.667 20648-20730 BluetoothSocket         me.hackerchick.catima.debug          D  read in:  android.net.LocalSocketImpl$SocketInputStream@4ca6505 len: 8192
2026-07-07 20:50:03.764 20648-20730 BluetoothSocket         me.hackerchick.catima.debug          D  read out:  android.net.LocalSocketImpl$SocketInputStream@4ca6505 ret: 17
2026-07-07 20:50:03.765 20648-20730 CatimaBtServer          me.hackerchick.catima.debug          D  Received command: V1/CARDS_REQUEST from Galaxy Watch4 (45SX)
2026-07-07 20:50:03.791 20648-20730 BluetoothSocket         me.hackerchick.catima.debug          D  write: android.net.LocalSocketImpl$SocketOutputStream@fbe4068 length: 8192
2026-07-07 20:50:03.791 20648-20730 BluetoothSocket         me.hackerchick.catima.debug          D  write out: android.net.LocalSocketImpl$SocketOutputStream@fbe4068 length: 8192
2026-07-07 20:50:03.792 20648-20730 BluetoothSocket         me.hackerchick.catima.debug          D  write: android.net.LocalSocketImpl$SocketOutputStream@fbe4068 length: 8192
2026-07-07 20:50:03.792 20648-20730 BluetoothSocket         me.hackerchick.catima.debug          D  write out: android.net.LocalSocketImpl$SocketOutputStream@fbe4068 length: 8192
2026-07-07 20:50:03.792 20648-20730 BluetoothSocket         me.hackerchick.catima.debug          D  write: android.net.LocalSocketImpl$SocketOutputStream@fbe4068 length: 7326
2026-07-07 20:50:03.792 20648-20730 BluetoothSocket         me.hackerchick.catima.debug          D  write out: android.net.LocalSocketImpl$SocketOutputStream@fbe4068 length: 7326
2026-07-07 20:50:03.792 20648-20730 CatimaBtServer          me.hackerchick.catima.debug          D  Sent 23709 bytes to Galaxy Watch4 (45SX)
2026-07-07 20:50:03.792 20648-20730 BluetoothSocket         me.hackerchick.catima.debug          D  close() this: android.bluetooth.BluetoothSocket@1aa187c, channel: 10, mSocketIS: android.net.LocalSocketImpl$SocketInputStream@4ca6505, mSocketOS: android.net.LocalSocketImpl$SocketOutputStream@fbe4068, mSocket: android.net.LocalSocket@4114c81 impl:android.net.LocalSocketImpl@d731126 fd:java.io.FileDescriptor@29c1167, mSocketState: CONNECTED
2026-07-07 20:50:03.794 20648-20730 BluetoothServerSocket   me.hackerchick.catima.debug          W  BluetoothServerSocket: accept() called. mSocket=android.bluetooth.BluetoothSocket@e1460af,timeout=-1

So it looks like it does connect, retrieve the card request and data gets sent, but somehow the watch thinks the connection got closed unexpectedly?

@TheLastProject TheLastProject Jul 7, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a lot of cards (194 to be exact) on this test device because I was also testing some lag issues. Maybe that's the issue here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can simplify the return value. If we just have the watch call a /VERSIONS endpoint and the Android app returns supported versions (for now only [1] or something like that), the watch app checks if its own version is in that list and if it's not displays an error to tell you to update the watch app and Android app and otherwise just calls the relevant, in this case, V1/CARDS_REQUEST endpoint.

By doing versioning like that, we can also simplify buildCardsJson to not need to return a version and thus maybe more easily paginate and keep it simple?

I think paginating is a requirement sadly, though it shouldn't be too hard.

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Catima</string>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a debug strings.xml too, like main Catima has, so I can tell regular and debug Catima WearOS apart :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants