# Prototype Plan — Path C Android Bridge

## Objective

Validate that an Android bridge can read Hevy's live workout state and relay it to the Pipo backend, which then feeds the Fitbit companion app on the same phone.

## Prerequisites

- Pixel 9a with:
  - Developer options enabled
  - USB debugging enabled
  - Hevy installed and logged in
  - Fitbit app installed, Sense 2 paired
- Linux dev machine with ADB installed
- Pipo backend accessible (Tailscale or public HTTPS)
- Tasker + AutoNotification (paid) installed on Pixel for Phase 1

## Phase 1: Notification Content Discovery (30 min)

### Goal
Determine exactly what data Hevy's live workout notification contains.

### Steps

1. **Start a workout in Hevy** (any routine, any exercises)
2. **Observe the notification**: Pull down notification shade, examine content
3. **Set up AutoNotification Intercept**:
   - Create Tasker profile: Event → Plugin → AutoNotification → Intercept
   - Filter: App = Hevy, Has Media Session = (try both)
   - In the task, Flash `%antitle` and `%antext` to see raw data
4. **Document notification fields**:
   - What fields are present: title, text, subtext, ticker, extras
   - What format is the text in (comma-separated, newline, structured?)
   - Are there notification actions? What labels?
   - Does the notification update when:
     - A set is completed?
     - Rest timer changes?
     - Exercise changes?
5. **Take screenshots** of the notification at different workout states
6. **Dump all notification extras**:
   ```kotlin
   // In a NotificationListenerService
   val extras = sbn.notification.extras
   for (key in extras.keySet()) {
       Log.d("HEVY_NOTIF", "$key = ${extras.get(key)}")
   }
   ```

### Acceptance Criteria
- [ ] Documented all available notification fields
- [ ] Confirmed notification updates on state change (set complete, rest timer, exercise change)
- [ ] Determined if rest timer countdown is in notification text or only in custom view
- [ ] Decision: Is notification data rich enough for MVP? If yes, proceed to Phase 2. If no, go to Phase 2 but plan for accessibility fallback.

## Phase 2: Backend State API (45 min)

### Goal
Create the backend endpoints that the bridge will POST to and the Fitbit companion will GET from.

### Steps

1. **Add to Pipo backend** (Node.js or Python):

```javascript
// POST /api/live-workout/state
// Body: { exercise: "Bench Press", set: "3/4", weight_kg: 100, reps: 8, rest_seconds: 45, is_resting: true }
// Response: { ok: true }

// GET /api/live-workout/state
// Response: { exercise: "...", set: "...", weight_kg: 100, reps: 8, rest_seconds: 45, is_resting: true, last_updated: "ISO8601" }

// POST /api/live-workout/command
// Body: { command: "skip_rest", timestamp: "..." }
// Response: { ok: true, command_id: "uuid" }

// GET /api/live-workout/command?since=<command_id>
// Response: { commands: [{ id: "uuid", command: "skip_rest", status: "pending|executed|failed" }] }
```

2. **Add health check**: `GET /api/live-workout/health` — returns bridge status, last heartbeat

3. **Test with curl**:
```bash
curl -X POST https://pipo-backend/api/live-workout/state \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <bridge-api-key>" \
  -d '{"exercise":"Bench Press","set":"3/4","weight_kg":100,"reps":8,"rest_seconds":45,"is_resting":true}'

curl https://pipo-backend/api/live-workout/state
```

### Acceptance Criteria
- [ ] State POST/GET works end-to-end
- [ ] Command queue works (post command, poll for commands)
- [ ] Health check endpoint returns accurate status
- [ ] API documented with example requests/responses

## Phase 3: Tasker Bridge Prototype (1 hour)

### Goal
Build an end-to-end prototype using Tasker + AutoNotification that proves the data pipeline.

### Steps

1. **Create Tasker profile**:
   - Event: AutoNotification Intercept
   - App: `com.hevy`
   - Notification Type: Only Created Notifications
   - Check "Get All Fields"

2. **Create Tasker task** (runs when Hevy notification changes):
   ```
   A1: Variable Set
       Name: %payload
       To: {"exercise":"%antitle","text":"%antext","time":"%TIME"}

   A2: HTTP Request
       Method: POST
       URL: https://pipo-backend/api/live-workout/state
       Headers: Content-Type: application/json
                Authorization: Bearer <api-key>
       Body: %payload
       Timeout: 10

   A3: Flash
       Text: State sent to backend
       Long: Off
   ```

3. **Add rest timer polling task**:
   - Separate profile: Time → Every 2 seconds
   - If Hevy notification present (AutoNotification Query), decrement local timer
   - POST updated timer to backend

4. **Test end-to-end**:
   - Start a workout in Hevy
   - Watch Tasker logs (Run Log)
   - Check `GET /api/live-workout/state` for data
   - Verify data correctness

### Acceptance Criteria
- [ ] Hevy notification changes trigger backend POST within 1 second
- [ ] Backend state accurately reflects Hevy workout state
- [ ] Rest timer countdown works (whether from notification or local tracking)
- [ ] Multiple exercise transitions are handled correctly

## Phase 4: Fitbit Companion Integration (1 hour)

### Goal
Connect the Fitbit companion to the backend state API and display live Hevy state on the watch.

### Steps

1. **Update Fitbit companion** (`/companion/index.js`):

```javascript
import { peerSocket } from "messaging";
import asap from "fitbit-asap/companion";

const BACKEND_URL = "https://pipo-backend/api/live-workout/state";
const POLL_INTERVAL = 2000; // 2 seconds
let lastState = null;

async function pollState() {
  try {
    const response = await fetch(BACKEND_URL, {
      headers: { "Authorization": "Bearer <api-key>" }
    });
    const state = await response.json();
    
    // Only send to watch if state changed
    if (JSON.stringify(state) !== JSON.stringify(lastState)) {
      asap.send({ type: "workout_state", ...state });
      lastState = state;
    }
  } catch (e) {
    console.log("Poll failed: " + e.message);
  }
}

// Poll every 2 seconds
setInterval(pollState, POLL_INTERVAL);

// Also send watch events to backend
asap.onmessage = async (msg) => {
  if (msg.type === "set_logged") {
    await fetch("https://pipo-backend/api/live-workout/event", {
      method: "POST",
      headers: { "Content-Type": "application/json", "Authorization": "Bearer <api-key>" },
      body: JSON.stringify(msg)
    });
  }
};
```

2. **Update watch app** (`/app/index.js`):

```javascript
import asap from "fitbit-asap/app";

let workoutState = null;

asap.onmessage = (msg) => {
  if (msg.type === "workout_state") {
    workoutState = msg;
    updateDisplay(msg);
  }
};

function updateDisplay(state) {
  // Update exercise name, set info, rest timer on watch UI
  exerciseName.text = state.exercise || "Waiting...";
  setInfo.text = state.set || "";
  weightTarget.text = state.weight_kg ? `${state.weight_kg} kg` : "";
  repsTarget.text = state.reps ? `${state.reps} reps` : "";
  
  if (state.is_resting && state.rest_seconds > 0) {
    restTimer.text = formatTime(state.rest_seconds);
    startRestTimer(state.rest_seconds);
  }
}
```

3. **Build and sideload**:
```bash
npx fitbit
fitbit$ connect phone
fitbit$ connect device
fitbit$ build-and-install
```

### Acceptance Criteria
- [ ] Watch displays current Hevy exercise name
- [ ] Watch shows set number and target weight/reps
- [ ] Rest timer counts down on watch
- [ ] State updates within 2-3 seconds of Hevy state change
- [ ] Watch can log sets independently (local state)

## Phase 5: Notification Action Control (30 min)

### Goal
Enable the watch to control Hevy's rest timer (skip rest, extend rest) via notification actions.

### Steps

1. **Discover notification action labels**:
   - In AutoNotification, check action labels on Hevy notification
   - Common labels: "Skip Rest", "+15s", "Finish Workout"

2. **Add command handling in Tasker**:
   ```
   Profile: HTTP Request (poll commands from backend every 2s)
   
   Task:
   A1: HTTP Request
       Method: GET
       URL: https://pipo-backend/api/live-workout/command?since=%LastCommandId
       
   A2: If %http_response_code = 200
   A3:   For each command in response:
   A4:     If command = "skip_rest":
   A5:       AutoNotification Actions
             - Intercept: App = Hevy
             - Action: "Skip Rest" (or button index)
   A6:     POST command result to backend
   ```

3. **Add watch UI for rest control**:
   - Button: "Skip Rest" → sends message to companion → posts command to backend → bridge executes

### Acceptance Criteria
- [ ] Watch "Skip Rest" button skips Hevy rest timer within 3 seconds
- [ ] Watch "+15s" button extends Hevy rest timer
- [ ] Command status reported back to backend

## Phase 6: Custom Bridge APK — Minimum Viable (2-3 hours)

### Goal
Replace Tasker prototype with a dedicated, reliable Android APK.

### Steps

1. **Scaffold Android project** (Kotlin, min SDK 26, target SDK 34):
   - Package: `dev.pipo.hevybridge`

2. **Implement NotificationListenerService**:
   - Filter package `com.hevy`
   - Parse notification content using the format discovered in Phase 1
   - Extract: exercise name, set info, weight, reps, rest timer
   - POST to backend on every notification update

3. **Implement rest timer tracking**:
   - If notification doesn't include timer countdown, track locally
   - Start countdown when set-complete is detected
   - Sync to backend every second

4. **Implement foreground service**:
   - Persistent notification: "Hevy Bridge Active"
   - Keeps process alive during workout
   - Action button: "Stop Bridge"

5. **Implement command polling**:
   - Poll `GET /api/live-workout/command` every 2 seconds
   - Execute commands: click notification actions
   - Report results to backend

6. **Build and install**:
```bash
./gradlew assembleDebug
adb install app/build/outputs/apk/debug/app-debug.apk
```

7. **Configure**:
   - Open app, enter backend URL and API key
   - Enable notification access (prompt takes user to settings)
   - Start bridge service

### Acceptance Criteria
- [ ] APK installs and runs on Pixel 9a
- [ ] Notification listener receives Hevy notifications
- [ ] State correctly parsed and POSTed to backend
- [ ] Foreground service keeps running during workout
- [ ] Commands from backend are executed
- [ ] No Tasker dependency

## Phase 7: Accessibility Fallback (1-2 hours)

### Goal
Add AccessibilityService as fallback for richer data or when notification is insufficient.

### Steps

1. **Add AccessibilityService to bridge APK** (from Phase 6)
2. **Implement UI tree walker**:
   - On `TYPE_WINDOW_CONTENT_CHANGED`, walk tree
   - Find exercise name, weight/rep inputs, rest timer
3. **Use as data enrichment**:
   - If notification has exercise name but not weight target, get it from accessibility
   - Merge data sources
4. **Implement UI control** (optional for MVP):
   - Tap "Finish Set" button if command received
   - Fill weight/rep fields if needed (unlikely — watch is primary input)

### Acceptance Criteria
- [ ] Accessibility service reads Hevy workout screen
- [ ] Merged notification + accessibility data is richer than either alone
- [ ] Resource IDs documented for key UI elements

## Phase 8: Hevy Internal API Discovery (1-2 hours)

### Goal
Determine if Hevy has undocumented live-workout API endpoints.

### Steps

1. **Set up mitmproxy** on Linux dev machine
2. **Configure Pixel proxy** to route through mitmproxy
3. **Install mitmproxy CA cert** on Pixel
4. **Start Hevy workout**, observe all API traffic
5. **Document all endpoints** called during:
   - Workout start
   - Set logging
   - Rest timer events
   - Workout finish/save
6. **Test discovered endpoints** with curl to verify
7. **If live endpoint found**: Evaluate if it can replace notification parsing

### Acceptance Criteria
- [ ] All Hevy API traffic documented
- [ ] Live workout endpoint confirmed present or absent
- [ ] If present: token/auth flow understood, endpoint tested
- [ ] Decision: Use internal API as primary or keep notification layer?

## Timeline Summary

| Phase | Description | Time | Depends On |
|-------|-------------|------|------------|
| 1 | Notification Content Discovery | 30 min | Hevy installed on Pixel |
| 2 | Backend State API | 45 min | Pipo backend running |
| 3 | Tasker Bridge Prototype | 1 hour | Phase 1 + 2 |
| 4 | Fitbit Companion Integration | 1 hour | Phase 2 + 3 |
| 5 | Notification Action Control | 30 min | Phase 3 |
| 6 | Custom Bridge APK | 2-3 hours | Phase 3 (validated format) |
| 7 | Accessibility Fallback | 1-2 hours | Phase 6 |
| 8 | Internal API Discovery | 1-2 hours | MITM setup |

**Total estimated time**: 8-11 hours

## What to Skip for First Prototype

For the absolute fastest path to working end-to-end:

1. ✅ Phase 1: Notification discovery (REQUIRED — need to know data format)
2. ✅ Phase 2: Backend API (REQUIRED — central data hub)
3. ✅ Phase 3: Tasker prototype (REQUIRED — fast validation)
4. ✅ Phase 4: Fitbit companion (REQUIRED — proves watch can display data)
5. Skip Phase 5: Notification actions (not needed for MVP — watch is control surface)
6. Skip Phase 6: Custom APK (Tasker is good enough for first prototype)
7. Skip Phase 7: Accessibility (only if notification data is insufficient)
8. Skip Phase 8: Internal API (stretch goal, do later)

**MVP path: Phases 1-4 = ~3 hours 15 minutes to working end-to-end prototype.**

## Success Criteria for First Prototype

After completing Phases 1-4:
- [ ] Hevy workout started on phone → within 3 seconds, Fitbit Sense 2 shows current exercise
- [ ] Hevy rest timer counting down → Sense 2 shows countdown
- [ ] Exercise changes in Hevy → Sense 2 updates within 3 seconds
- [ ] Sense 2 can log a set locally (reps, weight, RIR)
- [ ] When workout finished, backend receives complete session data
