Commit Graph

29 Commits

Author SHA1 Message Date
amir a95b05dada chore: bump to 1.20 (rings release)
Build APK / build (push) Successful in 1m48s
Build APK / build (pull_request) Successful in 1m33s
2026-06-12 08:13:24 +00:00
amir 10cc064f1f fix(rings): make widget rings fill available space + higher render res
Build APK / build (pull_request) Successful in 1m42s
Rings were capped at a fixed 86dp; now each gauge fills its half of the widget
(weighted ImageView) so they grow with the placed widget size. Bumped RingRenderer
default bitmap to 360px/36 stroke to stay crisp at the larger display size.
2026-06-12 08:02:11 +00:00
amir 952c8261e9 feat: ring widget style (selectable bars/rings)
Build APK / build (pull_request) Successful in 1m34s
Add RingRenderer (circular gauge mirror of BarRenderer), a widget_layout_rings
layout, a Bars/Rings preference + in-app toggle, and ring rendering branch in
ClaudeUsageWidget. Full (4x2) widget honors the chosen style; compact size stays
bars. Phase 1 of porting hamed-elfayome/Claude-Usage-Tracker features.
2026-06-12 07:45:45 +00:00
amir 58e3a0fcd7 v1.19: fix empty usage-history chart (always fetch utilization)
Build APK / build (push) Successful in 1m52s
recordHistory() only ever stored fiveHourUtilization/weeklyUtilization, but
fetchUsage() returned early with message-count data — before calling the
/usage utilization endpoint — whenever the org JSON carried a message limit.
So utilization was never populated and the history chart stayed stuck on
'Collecting history…'. The prior chart fix only corrected the throttle.

- UsageRepository: always attempt /usage (preferred signal that drives the
  weekly bar + history); fall back to org message-count data only when
  utilization is unavailable.
- UsageData.sessionReadingPct: utilization preferred, message-count % fallback,
  -1f when no reading — so message-only accounts also build history.
- PreferencesManager.recordHistory: record the session line from
  sessionReadingPct instead of utilization-only.
- UsageDataTest: cover sessionReadingPct.
2026-06-12 01:48:04 +00:00
amir 31e18ed5e9 Harden WebView nav + add test suite + fix lint for production
Build APK / build (push) Successful in 1m29s
Security:
- LoginActivity WebView now enforces a host allow-list in
  shouldOverrideUrlLoading: only claude.ai + required SSO/CDN hosts
  (Google, Apple, Cloudflare, gstatic, recaptcha) can navigate; everything
  else is blocked. market://intent:// still blocked; about:/data: allowed.
  Device-verified: claude.ai login + Cloudflare challenge still load.

Tests (33, pure-JVM JUnit4, no device needed):
- Extracted shouldRecordHistory() pure throttle decision (regression guard
  for the empty-history-chart bug) + HistoryThrottleTest.
- UsageDataTest (mergedWith last-good/partial-union, computed props),
  PaceCalcTest, PeakHoursTest.
- Added junit:junit:4.13.2 as testImplementation only.

Build quality:
- widget_layout.xml: suppress false-positive UseAppTint lint on the widget
  refresh button (app:tint doesn't work in RemoteViews; android:tint is
  correct here) so lintDebug is clean.

Verified locally: 33 unit tests pass, lintDebug 0 errors, signed
assembleRelease OK (apksigner verified, signer identity unchanged),
emulator smoke test launches + renders without crash.
2026-06-10 11:12:02 +00:00
amir a6d930415c Fix empty usage-history chart + externalize signing secrets
Build APK / build (push) Successful in 2m18s
History chart: recordHistory() threw away the previous point whenever a
new reading landed within the 2-min de-dup window, but the foreground loop
refreshes every 30s — so history could never grow past one point while the
app was open and the chart stayed stuck on 'Collecting history…'. Now it
throttles by SKIPPING a too-soon reading instead of replacing the last one,
so points accumulate during normal use.

Security:
- Remove hardcoded release keystore passwords from build.gradle.kts; read
  from env vars / gitignored keystore.properties; CI injects from Gitea
  secrets (KEYSTORE_PASSWORD/KEY_PASSWORD). Signing identity unchanged.
- Make the cookie-never-plaintext invariant explicit on the read path.
- Drop custom ACTION_REFRESH from the exported widget intent-filter so other
  apps can't trigger refreshes; internal explicit PendingIntent still works.
- Gate an unguarded Log.w behind BuildConfig.DEBUG.
2026-06-10 10:28:37 +00:00
amir c69147530e v1.18: center the tank + driver in the launcher icon
Build APK / build (push) Successful in 1m58s
The subject was filling the frame edge-to-edge, so launcher masking
clipped the gun barrel, treads, and the driver. Auto-detect the tank+bot
(erode away the debris specks, take that bbox), recenter on a square
canvas at ~74% with matching navy #0B1D27 margin so the whole subject
stays inside any mask shape. Regenerated all densities + icon.png.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:40:00 +00:00
amir 5a5f6ed1e4 v1.17: updated tank launcher icon (close-up, navy #16222B)
Build APK / build (push) Successful in 1m55s
Swap in the closer-up tank art. Source padded to square (no distortion),
adaptive foreground inset ~10% so edges don't clip on round masks, bg
color updated to #16222B. Regenerated all densities + repo icon.png.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:31:02 +00:00
amir a43fa5be92 v1.16: simplify usage alerts to fixed 90% and 100% (less aggressive)
Build APK / build (push) Successful in 1m50s
Replace the configurable threshold sliders with two fixed alert levels —
90% and 100% — per metric. Anti-spam now uses hysteresis instead of the
API reset-epoch (which could drift and re-fire): each level fires once
when crossed and re-arms only after usage drops back below it. Alerts are
posted only by the background worker, never the in-app refresh loop, so
you're not pinged while looking at the app. UI drops the sliders for a
one-line description; settings keep just the on/off switch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:43:14 +00:00
amir 1d4356c1d7 v1.15: new pixel-art tank launcher icon
Build APK / build (push) Successful in 1m29s
Replace the hexagon "C" launcher icon with the tank-crushing-electronics
art. Adaptive icon (anydpi-v26): full art as foreground over teal
#284950 background, so it masks cleanly to any launcher shape; legacy
PNG bitmaps generated for all densities. Refreshes repo-root icon.png.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 13:34:17 +00:00
amir 4470a6f7ba Show weekday + date on weekly reset labels
Build APK / build (push) Successful in 2m7s
Weekly reset now reads "Resets Friday, Jun 6 · 3:00 PM" in the app and
full widget; small widget uses a compact "Fri, Jun 6" via a new
formatResetShort(). (Amir's local UI polish, folded in on top of v1.14.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 13:27:24 +00:00
amir 1b5c764ee8 Fix widget/app showing stale or no data; add live in-app refresh
Three reliability bugs made data inconsistent:

1. Empty-overwrite: a failed or partial fetch returned an empty
   UsageData that the worker/app saved unconditionally, wiping the last
   good reading and blanking the widget. Added UsageData.mergedWith()
   so a fetch that returns nothing usable keeps the previous snapshot,
   and a partial fetch falls back per-metric. Never blank again.

2. No in-app auto-refresh: onResume only refreshed when the cache was
   >5 min old and there was no live timer. Replaced with a foreground
   lifecycle loop that refreshes on open and every 30s while visible,
   always painting cached data first. Manual button keeps the spinner;
   the loop is silent. App refresh now also pushes the widget update.

3. Spurious logout: a single transient 401/403 (e.g. a Cloudflare
   challenge) called clearSession() immediately, logging the user out
   and showing "Not signed in". Now clears only after 3 consecutive
   auth failures; the counter resets on any successful read.

Battery-friendly: no foreground service. Background widget refresh
stays on the existing alarm + 15-min WorkManager, but with the merge
fix the widget always shows the last data it pulled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:00:07 +00:00
amir 0520f0dc5e v1.14: usage history chart + threshold notifications
Add an in-app 7-day history chart and opt-in usage alerts, the two
features requested from the macOS Claude-Usage-Tracker that map cleanly
to an Android widget app.

History:
- UsageSnapshot model; PreferencesManager records session/weekly
  utilization on every refresh (7-day retention, <=600 points, collapses
  readings under 2 min apart to avoid worker+manual double-logging).
- HistoryChartView: dependency-free Canvas line chart (session/weekly,
  0/50/100% gridlines), breaks the line across >35-min gaps.
- New HISTORY card with chart + legend.

Notifications:
- Notifier posts when session/weekly crosses a user threshold, at most
  once per limit window (keyed on reset-epoch, re-arms on rollover).
- USAGE ALERTS card: enable switch + session/weekly sliders (50-100%,
  defaults 90/85). POST_NOTIFICATIONS permission + runtime request.
- Wired into the existing 5-min background worker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:49:47 +00:00
amir 1d89b2631c v1.13: drop session marker, single-color weekly marker, weekday reset
- Remove the pace marker from the 5-hour (session) bar entirely.
- Weekly bar marker is now a single color (white), no green→purple tiers.
- Marker is a clean rounded tick; removed the white-halo/tier styling.
- Remove the '% over/under pace' text everywhere (widget + app).
- Weekly reset label now shows the weekday ('Resets Friday 3:00 PM'),
  never 'tomorrow'.

versionCode 14 / versionName 1.13. Includes rebuilt signed release APK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 05:53:50 +00:00
amir d6d7daa30f v1.12: restore reset-time labels, bolder pace tick
Fix v1.11 regression where the pace tag overwrote the reset-time label
(gone entirely on the small widget). The widget reset lines now show the
actual reset time again; pace is conveyed by the bar tick.

Make the pace tick more prominent: wider core + white halo so it stands
out against any fill color.

versionCode 13 / versionName 1.12. Includes rebuilt signed release APK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 02:42:45 +00:00
amir 838b10f2fd v1.11: pace markers + peak-hours burst icon
Add a pace tick to each usage bar showing where you should be to finish
at 100% by reset, color-coded by projected tier, plus a Claude burst icon
that lights up during Anthropic peak hours (5-11 AM PT, Mon-Fri). Bars now
rendered as bitmaps so the same renderer drives both the widget and the app.

New: PaceCalc, PeakHours, BarRenderer, ic_claude_burst drawable.
versionCode 12 / versionName 1.11. Includes rebuilt signed release APK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 02:31:10 +00:00
amir 6f3c5e6ea1 v1.10: all widget text white and bold
Make every TextView in both widget layouts fully white (#FFFFFF) with
textStyle=bold — SESSION/WEEKLY labels, session/weekly sub-labels,
status line, and last-updated timestamp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 21:16:24 +00:00
amir 6934017519 security: restrict network to system CAs, tighten WebView capabilities; v1.9
- AndroidManifest: add networkSecurityConfig to explicitly trust only system
  CAs, preventing user-installed CA cert MITM attacks on claude.ai sessions
- LoginActivity: set javaScriptCanOpenWindowsAutomatically=false (not needed
  for claude.ai login) and databaseEnabled=false (deprecated WebSQL)
- build.gradle.kts: enable buildConfig generation (required for
  BuildConfig.DEBUG guards already used in UsageRepository)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 20:00:01 +00:00
amir ee68b11ad0 v1.9: fix Android 16 status loss, bigger widget icons/fonts, security fixes
Android 16 bug: EncryptedSharedPreferences threw on ANY exception (Keystore
busy during screen-lock/BG wakeup) and the code deleted the encrypted prefs
file on any failure, permanently erasing session cookies. Now only
KeyPermanentlyInvalidatedException (biometric/PIN change) triggers delete;
transient failures preserve the file for the next session.

Also prevents saving cookies to plain-text fallback prefs if encrypted prefs
are unavailable.

WorkManager periodic (15 min, requires network) added alongside AlarmManager
as a Doze-mode backup for Android 16, where inexact alarms can be batched up
to 75 min.

UI: sync icon 24→32dp (large widget), 20→28dp (small); reset-time font
9→11sp (large), 8→10sp (small).

Security:
- All Log.d response-body and URL-bearing logs gated behind BuildConfig.DEBUG
- Cookie header value stripped of CRLF to prevent HTTP header injection
- LoginActivity coroutine migrated from bare CoroutineScope to lifecycleScope
- Widget removed from keyguard (lock-screen) category — usage data is sensitive

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 03:15:44 +00:00
amir 695c54f03c v1.8: fix black screen on resume and crash on sync
- onResume() no longer triggers a refresh every time — only fetches when
  data is >5 min stale, so returning to app shows cached data instantly
  without a loading spinner
- Fix CancellationException being swallowed by catch(Exception) in
  refreshUsage(), which caused updates to run on a destroyed activity
- EncryptedSharedPreferences key invalidation (caused by enabling/changing
  biometrics or screen lock) now deletes the stale encrypted file and
  recreates it cleanly, rather than silently using empty fallback prefs
- Wrap all securePrefs read/write ops in try-catch so a mid-session
  Keystore failure degrades gracefully instead of crashing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 20:59:10 +00:00
amir 3dc0448942 v1.7: fix widget losing data after screen lock/reboot
Two root causes:
- Alarms don't survive reboot — BootReceiver now restarts alarm + triggers
  an immediate fetch on BOOT_COMPLETED
- onUpdate() drew from cached prefs but never fetched fresh data — now
  triggers an immediate refresh so the widget is live on every launcher redraw

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:39:56 +00:00
amir 8d1cf21966 v1.6: fix HIGH security vuln — remove plaintext cookie backup
- Remove KEY_COOKIES_BACKUP plaintext fallback from PreferencesManager
- getCookies() now fails closed (force re-login) if EncryptedSharedPreferences unavailable
- Set android:allowBackup="false" to prevent adb backup extraction of session data
- Add missing gradle-wrapper.jar to repo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:28:26 +00:00
amir b3b69dd2b2 v1.5: time-based rotation angle — constant speed regardless of IPC load 2026-05-22 16:10:09 +00:00
amir 8965477cc7 v1.4: faster rotation (12deg/frame = ~1 full spin per second) 2026-05-22 16:02:33 +00:00
amir f6f7accfa5 v1.3: smooth 30fps rotation (6° steps), guaranteed minimum one full spin per tap 2026-05-22 16:00:17 +00:00
amir d86ffabb98 v1.2: fix rotation — use full updateWidget calls with currentRotation state instead of unreliable partiallyUpdateAppWidget 2026-05-22 15:55:47 +00:00
amir fafa5a3bb7 v1.2: refresh icon rotates while fetching (12fps via partial RemoteViews updates) 2026-05-22 15:49:13 +00:00
amir adb389984e v1.1: refresh icon turns orange when refreshing, reset times show day/time, weekly reset time added, release signing configured 2026-05-22 15:39:33 +00:00
amir 33ac02ead4 Initial release: Claude Pro usage widget for Android 2026-05-22 15:11:56 +00:00