diff --git a/.sdd/manifest.json b/.sdd/manifest.json new file mode 100644 index 00000000..d605c452 --- /dev/null +++ b/.sdd/manifest.json @@ -0,0 +1,330 @@ +{ + "manifest_version": 1, + "repository": { + "name": "kitchenSink", + "purpose": "Cisco Webex Android SDK 'KitchenSink' demo app that showcases and exercises Webex SDK APIs (authentication, calling, messaging, people, search, webhooks) from a single Android application.", + "category": "cat1-legacy", + "primary_languages": ["kotlin"] + }, + "topology": "Single-repo", + "commands": { + "build": { "command": "./gradlew assembleFullDebug", "source_file": "app/build.gradle", "role": "build" }, + "unit-test": { "command": "./gradlew testFullDebugUnitTest", "source_file": "app/build.gradle", "role": "unit-test" }, + "instrumented-test": { "command": "./gradlew connectedFullDebugAndroidTest", "source_file": "app/build.gradle", "role": "e2e" }, + "lint": { "command": "./gradlew lintFullDebug", "source_file": "app/build.gradle", "role": "lint" }, + "clean": { "command": "./gradlew clean", "source_file": "build.gradle", "role": "other" } + }, + "toolchain": [ + { "name": "jdk", "version": "17", "origin": "repo-config", "source_file": "buildSrc/src/main/java/com/ciscowebex/androidsdk/build/Dependencies.kt" }, + { "name": "kotlin", "version": "2.1.20", "origin": "repo-config", "source_file": "buildSrc/src/main/java/com/ciscowebex/androidsdk/build/Dependencies.kt" }, + { "name": "android-gradle-plugin", "version": "8.7.3", "origin": "repo-config", "source_file": "build.gradle" }, + { "name": "android-compile-sdk", "version": "34", "origin": "repo-config", "source_file": "buildSrc/src/main/java/com/ciscowebex/androidsdk/build/Dependencies.kt" }, + { "name": "ndk", "version": "27.2.12479018", "origin": "repo-config", "source_file": "buildSrc/src/main/java/com/ciscowebex/androidsdk/build/Dependencies.kt" } + ], + "registries": [ + { "name": "webex-artifactory", "type": "maven", "host": "devhub.cisco.com/artifactory/webexsdk", "settings_file": "build.gradle", "egress_required": true, "origin": "repo-config", "source_file": "build.gradle" }, + { "name": "google-maven", "type": "maven", "host": "maven.google.com", "settings_file": "build.gradle", "egress_required": true, "origin": "repo-config", "source_file": "build.gradle" }, + { "name": "maven-central", "type": "maven", "host": "repo.maven.apache.org", "settings_file": "build.gradle", "egress_required": true, "origin": "repo-config", "source_file": "build.gradle" } + ], + "artifacts": [ + { "name": "Cisco Webex Android SDK (full)", "type": "maven-dependency", "coordinates": "com.ciscowebex:webexsdk", "version": "3.16.3", "registry": "webex-artifactory", "source_file": "app/build.gradle" }, + { "name": "Cisco Webex Android SDK (meeting)", "type": "maven-dependency", "coordinates": "com.ciscowebex:webexsdk-meeting", "version": "3.16.3", "registry": "webex-artifactory", "source_file": "app/build.gradle" }, + { "name": "Cisco Webex Android SDK (wxc)", "type": "maven-dependency", "coordinates": "com.ciscowebex:webexsdk-wxc", "version": "3.16.3", "registry": "webex-artifactory", "source_file": "app/build.gradle" }, + { "name": "Cisco Webex Android SDK (message)", "type": "maven-dependency", "coordinates": "com.ciscowebex:webexsdk-message", "version": "3.16.3", "registry": "webex-artifactory", "source_file": "app/build.gradle" }, + { "name": "Koin (DI)", "type": "maven-dependency", "coordinates": "io.insert-koin:koin-androidx-viewmodel", "version": "2.2.3", "registry": "maven-central", "source_file": "buildSrc/src/main/java/com/ciscowebex/androidsdk/build/Dependencies.kt" } + ], + "tests": { + "unit": { "command_ref": "unit-test", "dir": "app/src/test/java", "framework": "JUnit4" }, + "e2e": { "command_ref": "instrumented-test", "dir": "app/src/androidTest/java", "framework": "AndroidX Test / Espresso" } + }, + "quality_gates": { + "code_coverage": { + "origin": "unknown" + } + }, + "coverage_status_definitions": { + "specced": ">=80% public surface specced, drift <5% — spec is authoritative", + "partial": "40-80% specced — spec is a hint, cross-check code", + "untracked": "<40% specced — code is the source of truth" + }, + "modules": [ + { + "path": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/", + "coverage_status": "Untracked", + "coverage_evidence": "Assess-only bootstrap; no prior specs. Draft spec generated from KitchenSinkApp.kt, WebexRepository.kt, WebexViewModel.kt, and DI wiring (WebexModule.kt). Code remains source of truth until human confirmation.", + "canonical_spec": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/ai-docs/core-spec.md", + "last_assessed": "2026-07-31", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": false, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": null, + "stateful_transitions": null, + "exposes_wire_protocol": false, + "ui_multi_screen": true, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": null, + "published_package": false, + "embedded_in_host": false, + "holds_client_state": true, + "resolved_by": "claude-cli/us.anthropic.claude-opus-4-8 (invocation-input; assess-only bootstrap, code-grounded prior)", + "resolved_at": "2026-07-31T00:00:00Z" + } + }, + { + "path": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/auth/", + "coverage_status": "Untracked", + "coverage_evidence": "Assess-only bootstrap; no prior specs. Draft spec generated from LoginActivity.kt and the auth activity set declared in AndroidManifest.xml. Code remains source of truth until human confirmation.", + "canonical_spec": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/auth/ai-docs/auth-spec.md", + "last_assessed": "2026-07-31", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": false, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": null, + "stateful_transitions": null, + "exposes_wire_protocol": false, + "ui_multi_screen": true, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": null, + "published_package": false, + "embedded_in_host": false, + "holds_client_state": true, + "resolved_by": "claude-cli/us.anthropic.claude-opus-4-8 (invocation-input; assess-only bootstrap, code-grounded prior)", + "resolved_at": "2026-07-31T00:00:00Z" + } + }, + { + "path": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/", + "coverage_status": "Untracked", + "coverage_evidence": "Assess-only bootstrap; no prior specs. Draft spec generated from CallActivity.kt, CallModule.kt, and the calling activity/service set in AndroidManifest.xml. Code remains source of truth until human confirmation.", + "canonical_spec": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/ai-docs/calling-spec.md", + "last_assessed": "2026-07-31", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": false, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": null, + "stateful_transitions": true, + "exposes_wire_protocol": false, + "ui_multi_screen": true, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": null, + "published_package": false, + "embedded_in_host": false, + "holds_client_state": true, + "resolved_by": "claude-cli/us.anthropic.claude-opus-4-8 (invocation-input; assess-only bootstrap, code-grounded prior)", + "resolved_at": "2026-07-31T00:00:00Z" + } + }, + { + "path": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/", + "coverage_status": "Untracked", + "coverage_evidence": "Assess-only bootstrap; no prior specs. Draft spec generated from MessagingModule.kt and the messaging activity set in AndroidManifest.xml (spaces, teams, memberships, composer). Code remains source of truth until human confirmation.", + "canonical_spec": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/ai-docs/messaging-spec.md", + "last_assessed": "2026-07-31", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": false, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": null, + "stateful_transitions": null, + "exposes_wire_protocol": false, + "ui_multi_screen": true, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": null, + "published_package": false, + "embedded_in_host": false, + "holds_client_state": true, + "resolved_by": "claude-cli/us.anthropic.claude-opus-4-8 (invocation-input; assess-only bootstrap, code-grounded prior)", + "resolved_at": "2026-07-31T00:00:00Z" + } + }, + { + "path": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/person/", + "coverage_status": "Untracked", + "coverage_evidence": "Assess-only bootstrap; no prior specs. Draft spec generated from PersonModule.kt and the personModule DI registration in KitchenSinkApp.kt. Code remains source of truth until human confirmation.", + "canonical_spec": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/person/ai-docs/person-spec.md", + "last_assessed": "2026-07-31", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": false, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": null, + "stateful_transitions": false, + "exposes_wire_protocol": false, + "ui_multi_screen": null, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": null, + "published_package": false, + "embedded_in_host": false, + "holds_client_state": true, + "resolved_by": "claude-cli/us.anthropic.claude-opus-4-8 (invocation-input; assess-only bootstrap, code-grounded prior)", + "resolved_at": "2026-07-31T00:00:00Z" + } + }, + { + "path": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/", + "coverage_status": "Untracked", + "coverage_evidence": "Assess-only bootstrap; no prior specs. Draft spec generated from SearchModule.kt, the searchModule DI registration in KitchenSinkApp.kt, and the SearchActivity declared in AndroidManifest.xml. Code remains source of truth until human confirmation.", + "canonical_spec": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/ai-docs/search-spec.md", + "last_assessed": "2026-07-31", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": false, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": null, + "stateful_transitions": false, + "exposes_wire_protocol": false, + "ui_multi_screen": null, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": null, + "published_package": false, + "embedded_in_host": false, + "holds_client_state": true, + "resolved_by": "claude-cli/us.anthropic.claude-opus-4-8 (invocation-input; assess-only bootstrap, code-grounded prior)", + "resolved_at": "2026-07-31T00:00:00Z" + } + }, + { + "path": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/webhooks/", + "coverage_status": "Untracked", + "coverage_evidence": "Assess-only bootstrap; no prior specs. Draft spec generated from WebhooksModule.kt, the webhooksModule DI registration in KitchenSinkApp.kt, and the WebhooksActivity declared in AndroidManifest.xml. Code remains source of truth until human confirmation.", + "canonical_spec": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/webhooks/ai-docs/webhooks-spec.md", + "last_assessed": "2026-07-31", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": false, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": null, + "stateful_transitions": false, + "exposes_wire_protocol": false, + "ui_multi_screen": false, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": null, + "published_package": false, + "embedded_in_host": false, + "holds_client_state": true, + "resolved_by": "claude-cli/us.anthropic.claude-opus-4-8 (invocation-input; assess-only bootstrap, code-grounded prior)", + "resolved_at": "2026-07-31T00:00:00Z" + } + }, + { + "path": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/extras/", + "coverage_status": "Untracked", + "coverage_evidence": "Assess-only bootstrap; no prior specs. Draft spec generated from ExtrasModule.kt, the extrasModule DI registration in KitchenSinkApp.kt, and the ExtrasActivity declared in AndroidManifest.xml. Code remains source of truth until human confirmation.", + "canonical_spec": "app/src/main/java/com/ciscowebex/androidsdk/kitchensink/extras/ai-docs/extras-spec.md", + "last_assessed": "2026-07-31", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": false, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": null, + "stateful_transitions": false, + "exposes_wire_protocol": false, + "ui_multi_screen": null, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": null, + "published_package": false, + "embedded_in_host": false, + "holds_client_state": true, + "resolved_by": "claude-cli/us.anthropic.claude-opus-4-8 (invocation-input; assess-only bootstrap, code-grounded prior)", + "resolved_at": "2026-07-31T00:00:00Z" + } + } + ], + "spec_policy": { + "delta_grammar": { + "added": "## ADDED Requirements", + "modified": "## MODIFIED Requirements", + "removed": "## REMOVED Requirements" + }, + "require_what_and_why": true, + "require_provenance": true, + "protected_specs": [], + "required_sections_by_change_class": { + "core_always": ["Intent (WHAT)", "Rationale (WHY)", "Scope/Out-of-scope", "Acceptance criteria", "Contracts delta"], + "security_or_contract_or_perf_critical": ["Data", "Error Matrix", "Resilience", "Observability", "Operations"] + } + }, + "validation": { + "generator_runtime": "claude-cli", + "generator_model": "us.anthropic.claude-opus-4-8", + "generator_runtime_source": "invocation-input", + "minimum_independence": "different-runtime", + "blocking_severities": ["Blocking"], + "generator_run_id": "49c4cf08-3d2a-49a9-b497-f2edf82e1b61" + }, + "layout": { + "sdd_root": ".sdd", + "docs_root": "ai-docs", + "agent_entry_path": "AGENTS.md", + "standing_docs_root": "ai-docs", + "spec_index_path": "ai-docs/SPEC_INDEX.md", + "module_docs_strategy": "source-local", + "module_docs_folder_name": "ai-docs", + "template_roots": { + "canonical": ".sdd/templates", + "extensions": [".sdd/templates/extensions"] + }, + "repo_skills_root": ".sdd/skills", + "contracts_strategy": "root-index-module-detail" + }, + "substrate": { + "source": "No org repo-readiness (repo-standards) substrate detected in this repository during assess-only bootstrap.", + "consumed": [], + "compliance_tier": "legacy" + }, + "section_profiles": { + "repo": { + "owns_datastore": false, + "holds_client_state": true, + "components_interact": true, + "domain_data_across_components": false, + "caches_data": false, + "observability_convention": true, + "deploys_to_infra": false, + "shared_base_libs": false, + "is_monorepo": false, + "multi_platform": false, + "published_package": false, + "embedded_in_host": false, + "cross_repo_deps_material": true, + "security_arch_warranted": true + }, + "resolved_by": "claude-cli/us.anthropic.claude-opus-4-8 (invocation-input; assess-only bootstrap, code-grounded prior)", + "resolved_at": "2026-07-31T00:00:00Z" + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..b52f1500 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,85 @@ + +# AGENTS.md — kitchenSink + +> You are the agent entry point — read first. Next: router [`SPEC_INDEX.md`](ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](ai-docs/ARCHITECTURE.md). Load this + `SPEC_INDEX.md` first; pull module/standing docs on demand. +> Context-efficiency: link to canonical docs — don't duplicate them; keep this file compact. + +> Cross-tool context file. Auto-loaded by AI coding agents. A module's high-level design lives in the manifest-routed module spec, source-local as `/ai-docs/-spec.md`, not in an `AGENTS.md`. + +> **Draft (assess-only onboarding).** This doc tree was generated in assess-only mode. Code is the source of truth; every module is `Untracked` in `.sdd/manifest.json`. Confirmations marked `[NEEDS HUMAN INPUT]` are not yet answered. + +## Repo Overview +**kitchenSink** is a Cisco Webex Android SDK demonstration app (a "kitchen sink" sample) that exercises Webex SDK APIs — authentication, calling/meetings, messaging, people, search, and webhooks — from one Android application (`README.md`, `settings.gradle`). + +**What it is:** +- A single-module Android app (`:app`) that integrates the Webex Android SDK and shows how to call its APIs (`app/build.gradle`, `README.md`). +- A developer reference whose UI screens map onto SDK capabilities (`app/src/main/AndroidManifest.xml`). + +**What it is NOT:** +- ❌ The Webex Android SDK library itself — that is an external Maven dependency `com.ciscowebex:webexsdk` resolved from the Webex Artifactory registry (`app/build.gradle`). +- ❌ A published/consumed package — this app is not itself published to a registry (`app/build.gradle` has no publish config). +- ❌ A backend service — it owns no server, datastore, or HTTP API of its own. + +## Tech Stack +- Kotlin 2.1.20, Java 17 target, Android (compileSdk 34, minSdk 28), Android Gradle Plugin 8.7.3 (`buildSrc/src/main/java/com/ciscowebex/androidsdk/build/Dependencies.kt`, `app/build.gradle`). +- Koin for dependency injection; AndroidX (AppCompat, DataBinding, RecyclerView, ViewPager2, Lifecycle); RxJava2; Firebase (Messaging, Analytics, Crashlytics); Glide; Gson (`app/build.gradle`). +- Tests: JUnit4 unit tests under `app/src/test/java`; AndroidX Test / Espresso instrumented tests under `app/src/androidTest/java` (`app/build.gradle`). + +## Architecture +``` +Android UI (Activities/Fragments) + → ViewModels (WebexViewModel + per-feature ViewModels) + → Repositories (WebexRepository + per-feature repositories) + → Cisco Webex Android SDK (external: Webex, Phone, Message, Space, People…) +``` +→ Full repo architecture & component responsibilities: **[ARCHITECTURE.md](./ai-docs/ARCHITECTURE.md)** + +## Module / Package Structure +``` +app/src/main/java/com/ciscowebex/androidsdk/kitchensink/ +├── (core) # app shell: KitchenSinkApp, WebexRepository, WebexViewModel, DI wiring, services +├── auth/ # login flows: OAuth, JWT, Access-Token, UC/CUCM +├── calling/ # calls, meetings, closed captions, call UI/services +├── messaging/ # spaces, teams, memberships, message composer +├── person/ # current-user / person details +├── search/ # people/space search +├── webhooks/ # webhook management UI +├── extras/ # miscellaneous SDK feature demos +└── utils/ # shared helpers (SharedPrefUtils, CallObjectStorage, Constants) +``` +→ Per-module docs and the spec router: **[ai-docs/SPEC_INDEX.md](./ai-docs/SPEC_INDEX.md)** + +## Critical Rules +1. **Code is the source of truth.** Every module is `Untracked` in `.sdd/manifest.json`; never invent an SDK API, path, event, or constant — read the real file or the Webex SDK reference. +2. **Ask before coding.** Present a plan / Spec Summary; wait for confirmation. +3. **Never commit secrets.** `CLIENT_ID`, `CLIENT_SECRET`, `REDIRECT_URI`, `WEBHOOK_URL`, and `FEDRAMP_*` come from `local.properties`; `SCOPE` from `gradle.properties`. These are read into `BuildConfig` (`app/build.gradle`) and must never be hardcoded or logged. +4. **One SDK flavor at a time.** The four product flavors (`full`, `meeting`, `wxc`, `message`) each pull a different Webex SDK artifact; only one is active per build (`app/build.gradle`). + +## Essential Commands +| Task | Command | +|---|---| +| Install | Import into Android Studio / resolve Gradle; SDK artifacts require access to `devhub.cisco.com/artifactory/webexsdk` (`build.gradle`) | +| Build | `./gradlew assembleFullDebug` (`app/build.gradle`) | +| Test | `./gradlew testFullDebugUnitTest` (unit); `./gradlew connectedFullDebugAndroidTest` (instrumented) (`app/build.gradle`) | +| Lint/format | `./gradlew lintFullDebug` (`app/build.gradle`) | + +## Common Gotchas +1. **Missing credentials → non-functional login.** If `local.properties` lacks `CLIENT_ID`/`CLIENT_SECRET`/`REDIRECT_URI`, OAuth login cannot complete; the values default to empty strings in `app/build.gradle`. +2. **Wrong flavor dependency.** `fullImplementation`, `meetingImplementation`, `wxcImplementation`, and `messageImplementation` are flavor-scoped; building a flavor without its SDK artifact available fails to resolve (`app/build.gradle`). +3. **Koin module load order.** Feature Koin modules are loaded per login type in `KitchenSinkApp.loadKoinModules`; requesting a ViewModel before its module is loaded fails at runtime (`KitchenSinkApp.kt`). + +## Pre-Commit Checklist +- [ ] Tests pass (`./gradlew testFullDebugUnitTest`) +- [ ] Spec/docs updated in the same change (spec-currency) +- [ ] No hardcoded secrets; credentials still sourced from `local.properties` / `gradle.properties` +- [ ] Affected module spec under `/ai-docs/` updated when SDK usage changes + +--- +**SDD coverage:** this repo's per-module coverage state lives in `.sdd/manifest.json` (mirror in `ai-docs/SPEC_INDEX.md`). Use that state to decide whether the spec is authoritative or code must be cross-checked. All modules are currently `Untracked` (assess-only onboarding): code is authoritative. diff --git a/ai-docs/ARCHITECTURE.md b/ai-docs/ARCHITECTURE.md new file mode 100644 index 00000000..055b3f2c --- /dev/null +++ b/ai-docs/ARCHITECTURE.md @@ -0,0 +1,128 @@ + +# ARCHITECTURE — kitchenSink + +> Start here → root [`AGENTS.md`](../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](SPEC_INDEX.md). This is the system architecture; per-module detail lives in each manifest-routed module spec, source-local as `/ai-docs/-spec.md`. +> Context-efficiency: link to canonical docs — don't duplicate them; this loads on demand, not upfront. + +> **Draft (assess-only onboarding).** Diagrams and component claims are grounded in current source; all modules are `Untracked` in `.sdd/manifest.json` and code is authoritative. + +## Design Overview +kitchenSink is a single-module Android application (`:app`, `settings.gradle`) that demonstrates the Cisco Webex Android SDK. It follows an MVVM-style layering: Android `Activity`/`Fragment` screens observe `ViewModel`s, which delegate to `Repository` objects, which in turn call the external Webex SDK (`WebexViewModel.kt`, `WebexRepository.kt`). The Webex SDK is an external Maven dependency, not code owned by this repo (`app/build.gradle`). + +Dependency injection is provided by Koin. A core `webexModule` registers the shared `WebexRepository`, `RingerManager`, and `WebexViewModel`; each feature area (auth, calling, messaging, person, search, webhooks, extras, calendar meetings) contributes its own Koin module, loaded together per login type in `KitchenSinkApp.loadKoinModules` (`WebexModule.kt`, `KitchenSinkApp.kt`). This makes each feature a self-contained slice wired at app start. + +The app exposes no server API and owns no datastore. Its "contracts" are the SDK surfaces it consumes and the small amount of client-side state (call registry, login preferences, live-data streams) it maintains. Configuration and secrets (OAuth client credentials, webhook URL, scopes) are injected at build time from `local.properties`/`gradle.properties` into `BuildConfig` (`app/build.gradle`). + +## Component Inventory & Responsibilities +| Component | Responsibility (one line) | Docs | +|---|---|---| +| `kitchensink/` (core) | App shell, Koin wiring, shared `WebexRepository`/`WebexViewModel`, foreground/notification services | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/ai-docs/core-spec.md` | +| `auth/` | Login flows (OAuth, JWT, Access Token, UC/CUCM) and login-type persistence | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/auth/ai-docs/auth-spec.md` | +| `calling/` | Calls, meetings, closed captions, in-call UI and call services | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/ai-docs/calling-spec.md` | +| `messaging/` | Spaces, teams, memberships, message composer | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/ai-docs/messaging-spec.md` | +| `person/` | Current-user / person detail retrieval | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/person/ai-docs/person-spec.md` | +| `search/` | People/space search | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/ai-docs/search-spec.md` | +| `webhooks/` | Webhook management UI | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/webhooks/ai-docs/webhooks-spec.md` | +| `extras/` | Miscellaneous SDK feature demos | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/extras/ai-docs/extras-spec.md` | + +## Component Interaction +```mermaid +graph TD + UI[Activities / Fragments] --> VM[ViewModels] + VM --> REPO[Repositories] + REPO --> SDK[Cisco Webex Android SDK] + APP[KitchenSinkApp] -->|loadKoinModules| KOIN[Koin container] + KOIN --> VM + KOIN --> REPO + SDK -->|observer callbacks| REPO + REPO -->|LiveData| VM + VM -->|LiveData| UI +``` +The app entry `KitchenSinkApp` starts Koin and, on login, loads the feature modules (`KitchenSinkApp.kt`). Screens obtain their `ViewModel` via Koin (e.g. `LoginActivity` uses `WebexViewModel`, `LoginActivity.kt`). Repositories register observers on the SDK (space/membership/message/call observers in `WebexRepository.kt`) and republish results through `LiveData` back to the UI. + +## Execution & Flow +Init & Call Flow (representative): App launches → `KitchenSinkApp.onCreate` initializes Firebase and starts Koin → `LoginActivity` reads the saved login type and loads Koin modules for it → user authenticates (OAuth/JWT/Access Token) → `buildCrashEnabledWebex` constructs the `Webex` instance and `WebexRepository` binds itself as the SDK auth/UC delegate (`WebexModule.kt`, `WebexRepository.kt`) → feature screens call repository methods (e.g. `webex.spaces.get`, `webex.phone.fetchVirtualBackgrounds`) and observe `LiveData` for results (`WebexRepository.kt`). + +## Dependencies +| Dependency | Type (internal / external / peer) | How used | Failure / version handling | +|---|---|---|---| +| `com.ciscowebex:webexsdk` (+ meeting/wxc/message variants) | external | Core SDK the app demonstrates; one variant per product flavor | Pinned `3.16.3`; resolved from Webex Artifactory (`app/build.gradle`) | +| Koin `io.insert-koin:koin-androidx-viewmodel` | external | Dependency injection / ViewModel wiring | Pinned `2.2.3` (`Dependencies.kt`) | +| RxJava2 (`rxjava`/`rxandroid`/`rxkotlin`) | external | Reactive helpers | Pinned versions (`Dependencies.kt`) | +| Firebase (Messaging/Analytics/Crashlytics) | external | Push messaging, analytics, crash reporting | BoM `26.1.0`; requires `google-services.json` (`app/build.gradle`) | +| AndroidX (AppCompat, DataBinding, RecyclerView, Lifecycle, ViewPager2) | external | UI framework and lifecycle | Pinned versions (`Dependencies.kt`) | +| Glide, Gson, okhttp/okio, orhanobut logger | external | Image loading, JSON, HTTP, logging | Pinned versions (`app/build.gradle`, `Dependencies.kt`) | + + +### State Model +The app holds transient client-side state rather than persisted domain data: +- In-memory call registry: `CallObjectStorage` (a synchronized `ArrayList`) tracks active `Call` objects by id (`utils/CallObjectStorage.kt`). +- Shared call/UC state and `LiveData` streams in `WebexRepository` (e.g. `currentCallId`, `isSpaceCallStarted`, `ucServerConnectionStatus`, and the many `MutableLiveData` fields); `clearCallData()` resets them on call teardown (`WebexRepository.kt`). +- Login-type and email/FedRAMP preferences persisted via Android `SharedPreferences` (`utils/SharedPrefUtils.kt`). + +## Cross-Cutting Concerns +- **Security:** OAuth/JWT/Access-Token credentials and `WEBHOOK_URL` are injected from `local.properties` into `BuildConfig` at build time; `SCOPE` from `gradle.properties` (`app/build.gradle`). No secrets are committed. FedRAMP restrictions gate login behavior via `AppConfiguration.containsFedRampRestrictions()` (`auth/LoginActivity.kt`). See `SECURITY.md`. +- **Observability:** Android `Log` is used throughout repositories/view models (e.g. `Log.d(tag, ...)` in `WebexRepository.kt`); Firebase Crashlytics and Analytics are enabled (`app/build.gradle`, `KitchenSinkApp.kt`); the SDK's crash reporting is enabled in `buildCrashEnabledWebex` (`WebexModule.kt`). + +## Non-Functional Posture +Performance & Footprint (client app): the app builds per-ABI split APKs (`x86`, `x86_64`, `armeabi-v7a`, `arm64-v8a`) plus a universal APK (`app/build.gradle`); ProGuard/R8 minification is enabled for release builds. Real-time media (calling) performance is governed by the Webex SDK, not by this app. `[NEEDS HUMAN INPUT]` — no explicit performance targets are declared in the repo. + + +## Dependency / Interaction Topology +```mermaid +graph LR + LoginUI[auth] --> WVM[WebexViewModel/Repository core] + CallUI[calling] --> WVM + MsgUI[messaging] --> WVM + PersonUI[person] --> WVM + SearchUI[search] --> WVM + WebhookUI[webhooks] --> WVM + Extras[extras] --> WVM + WVM --> SDK[Webex SDK] + SDK -.observer callbacks.-> WVM +``` +| From | To | Kind (call / event) | Purpose | +|---|---|---|---| +| feature ViewModels | `WebexViewModel` / `WebexRepository` | call | Access the shared `Webex` instance and call registry | +| `WebexRepository` | Webex SDK (`webex.spaces`, `webex.messages`, `webex.memberships`, `webex.phone`, `webex.calendarMeetings`) | call | Invoke SDK operations | +| Webex SDK | `WebexRepository` observers | event | Space/membership/message/calendar/call callbacks re-published as `LiveData` | +| `KitchenSinkApp` | Koin container | call | Load/unload feature modules per login type | + + +## Observability Patterns +- **Logging:** Android `Log.d/e` with a per-class `tag` string (e.g. `tag = "WebexRepository"` in `WebexRepository.kt`). No structured/correlation-id convention is enforced. +- **Metrics:** Firebase Analytics is included (`Dependencies.firebaseAnalytics`, `app/build.gradle`). +- **Audit:** Firebase Crashlytics captures crashes; the Webex SDK crash reporting is enabled in `buildCrashEnabledWebex` (`WebexModule.kt`). No application-level audit log exists. + + +## Cross-Repo Dependency Graph +- **Internal (same org):** Cisco Webex Android SDK (`com.ciscowebex:webexsdk` and its `-meeting`/`-wxc`/`-message` variants), resolved from `devhub.cisco.com/artifactory/webexsdk` (`app/build.gradle`). This is the primary external artifact the app demonstrates. +- **Cross-project:** None declared beyond the SDK artifacts. +- **External read-only:** SDK usage reference is the upstream `webex/webex-android-sdk` README (`README.md`). +- **External services:** Webex cloud services (reached through the SDK), Firebase Cloud Messaging/Analytics/Crashlytics, and the configured `WEBHOOK_URL` endpoint (`app/build.gradle`). + + +## Security Architecture +Identity flows through the Webex SDK `Authenticator` implementations selected at login: OAuth (web), JWT, and Access Token; a UC/CUCM login path also exists (`auth/LoginActivity.kt`, `AndroidManifest.xml`). `WebexRepository` implements `WebexAuthDelegate`/`WebexUCLoginDelegate` and receives re-login/login-failure and UC connection callbacks (`WebexRepository.kt`). OAuth client credentials are supplied at build time via `BuildConfig` and never committed. FedRAMP mode is enforced through `AppConfiguration`/`SettingsStore` (`auth/LoginActivity.kt`). Detailed posture is in `SECURITY.md`. + +--- +→ Per-module orientation and detailed design live in each manifest-routed module spec, source-local as `/ai-docs/-spec.md`. Routing: `SPEC_INDEX.md`. + +## Architecture Reference Links +| Reference | Location | When to read | +|---|---|---| +| Architecture decisions | `adr/` | To understand why major design choices were made and what alternatives were rejected | +| Repo patterns | `patterns/` | To follow established implementation conventions reflected in this architecture | +| Enforceable rules | `RULES.md` + `rules/` | To understand constraints every architecture-affecting change must obey | + +## WS6 References +| WS6 artifact | Relevance to this repo | Link | +|---|---|---| +| N/A | No WS6 platform/enterprise architecture artifacts are referenced by this demo app. | — | diff --git a/ai-docs/CONTRACTS.md b/ai-docs/CONTRACTS.md new file mode 100644 index 00000000..500de5cb --- /dev/null +++ b/ai-docs/CONTRACTS.md @@ -0,0 +1,36 @@ + +# Contracts Catalog — kitchenSink + +> Start here → root [`AGENTS.md`](../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](SPEC_INDEX.md) · system [`ARCHITECTURE.md`](ARCHITECTURE.md). Then this root contract index; detailed contracts live with owning modules or canonical schema files. Machine source `.sdd/manifest.json`. +> Context-efficiency: link to canonical docs — don't duplicate them; load on demand, not upfront. + +> Read before adding any public-facing surface — check here first. Machine source of truth: `.sdd/manifest.json`. + +> **Public-surface note:** kitchenSink is a demo Android application. It does not publish a network API, an importable library/package, its own event bus, or a CLI. Its contracts are the external surfaces it **consumes** — primarily the Cisco Webex Android SDK — plus the push-messaging and webhook endpoints it integrates with. Those are captured in "Requires" below; the SDK-consumption detail lives in each owning module spec. + +## Requires — what this repo depends on +| Dependency (service / package / datastore) | What is consumed | Schema / detail link | Availability assumption | Fallback on failure | Version floor | +|---|---|---|---|---|---| +| Cisco Webex Android SDK (`com.ciscowebex:webexsdk` + `-meeting`/`-wxc`/`-message`) | `Webex`, `Phone`, `Message`, `Space`, `Membership`, `People`, `CalendarMeetings`, auth/observer APIs | Upstream SDK reference (`README.md` links `webex/webex-android-sdk`) | Assumed available at build (Webex Artifactory) and at runtime (Webex cloud) | Login/SDK errors surfaced via `LiveData`/`Toast` | `3.16.3` (`app/build.gradle`) | +| Webex identity/OAuth | OAuth/JWT/Access-Token authentication | `auth/` module spec | Assumed reachable | Login-failed callback (`WebexRepository.onLoginFailed`) | `[NEEDS HUMAN INPUT]` | +| Firebase Cloud Messaging / Analytics / Crashlytics | Push messaging, analytics, crash reporting | `KitchenSinkFCMService` (`AndroidManifest.xml`); Firebase BoM `26.1.0` (`app/build.gradle`) | Requires `google-services.json` | App still runs without push | Firebase BoM `26.1.0` | +| Configured webhook endpoint (`WEBHOOK_URL`) | Webhook target used by the webhooks demo | `webhooks/` module spec; `BuildConfig.WEBHOOK_URL` (`app/build.gradle`) | Supplied via `local.properties` | Empty default → feature inert | `[NEEDS HUMAN INPUT]` | + +## Compatibility & Deprecation Policy +- **Breaking-change rule:** this app has no external consumers, so there is no published-surface compatibility contract. When the pinned Webex SDK version changes (`app/build.gradle`), verify affected SDK calls in the owning module specs. +- **Deprecation:** track SDK-side deprecations from the upstream SDK; this repo carries none of its own. + + +## Detailed Interface Docs +- Exact SDK API names/types/events are the upstream Webex Android SDK reference (`README.md`). Per-module SDK usage is summarized in each module spec's `Public Surface` / `Requires` sections. + +## Maintenance +- When SDK usage or an integrated external endpoint is added/changed/removed, update the owning module spec summary and `.sdd/manifest.json` in the same change. +- Cross-reference: domain terms → `GLOSSARY.md`. diff --git a/ai-docs/GETTING_STARTED.md b/ai-docs/GETTING_STARTED.md new file mode 100644 index 00000000..7cd5e6aa --- /dev/null +++ b/ai-docs/GETTING_STARTED.md @@ -0,0 +1,47 @@ + +# Getting Started — kitchenSink + +> Start here → root [`AGENTS.md`](../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](SPEC_INDEX.md) · system [`ARCHITECTURE.md`](ARCHITECTURE.md). Then this doc to get a build/test loop running. +> Context-efficiency: link to canonical docs — don't duplicate them; load on demand, not upfront. + +## Prerequisites +- JDK 17, Kotlin 2.1.20, Android SDK (compileSdk 34, minSdk 28), Android Gradle Plugin 8.7.3, NDK `27.2.12479018` (`buildSrc/.../Dependencies.kt`, `build.gradle`). Android Studio is the expected IDE (`README.md`). +- Access to the Webex SDK Maven registry `https://devhub.cisco.com/artifactory/webexsdk/` (or the `WebexSDK.aar` in `libs/`/`aars/`) to resolve `com.ciscowebex:webexsdk` (`build.gradle`, `README.md`). +- A `google-services.json` for Firebase (referenced by the Google Services plugin, `app/build.gradle`). + +## Clone & Install +```bash +git clone +cd kitchenSink +# Open in Android Studio and let Gradle sync, or resolve from CLI: +./gradlew help +``` + +## Build / Run / Test +| Task | Command | +|---|---| +| Build | `./gradlew assembleFullDebug` | +| Run (local) | Install/run the `full` debug variant from Android Studio onto a device/emulator | +| Test | `./gradlew testFullDebugUnitTest` (unit); `./gradlew connectedFullDebugAndroidTest` (instrumented) | +| Lint / format | `./gradlew lintFullDebug` | + +> Flavors: `full`, `meeting`, `wxc`, `message` each pull a different Webex SDK artifact (`app/build.gradle`). Substitute the flavor name in the Gradle task (e.g. `assembleMeetingDebug`). + +## First-Run Verification +- After `assembleFullDebug` succeeds, launch the app; the login screen (`LoginActivity`) appears with OAuth/JWT/Access-Token options (`auth/LoginActivity.kt`). Completing OAuth requires valid credentials in `local.properties`. + + +## Configuration & Secrets +- In `gradle.properties`, set `SCOPE` (a default set of scopes is present). +- In `local.properties` (not committed), set: `CLIENT_ID`, `CLIENT_SECRET`, `REDIRECT_URI`, `WEBHOOK_URL`, and optionally `FEDRAMP_CLIENT_ID`/`FEDRAMP_CLIENT_SECRET`/`FEDRAMP_REDIRECT_URI` (`README.md`, `app/build.gradle`). Never hardcode these — see `SECURITY.md`. + +## Where to Go Next +- Agent entry: `../AGENTS.md` · System shape: `ARCHITECTURE.md` · Routing: `SPEC_INDEX.md` +- Conventions: `patterns/` + `rules/` (and `RULES.md`). diff --git a/ai-docs/GLOSSARY.md b/ai-docs/GLOSSARY.md new file mode 100644 index 00000000..af291708 --- /dev/null +++ b/ai-docs/GLOSSARY.md @@ -0,0 +1,40 @@ + +# Glossary — kitchenSink + +> Start here → root [`AGENTS.md`](../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](SPEC_INDEX.md) · system [`ARCHITECTURE.md`](ARCHITECTURE.md). Then this doc; related: `CONTRACTS.md`. +> Context-efficiency: link to canonical docs — don't duplicate them; load on demand, not upfront. + +> Read this before naming anything. Use the canonical name exactly; never introduce a synonym. + +## Domain Terms +| Term | Definition (one or two sentences) | Authoritative location (file/type) | Notes / synonyms to avoid | +|---|---|---|---| +| `Webex` | The external Webex SDK entry object the app builds and uses for all SDK operations. | `WebexModule.kt` (`buildCrashEnabledWebex`) | not "the app"; it is the SDK client | +| `WebexRepository` | Shared repository that binds SDK observers/delegates and republishes results as `LiveData`. | `WebexRepository.kt` | not "the model" | +| `WebexViewModel` | Central ViewModel exposing SDK operations and `LiveData` streams to screens. | `WebexViewModel.kt` | — | +| `KitchenSinkApp` | The `Application` subclass; initializes Firebase and Koin and loads feature modules per login type. | `KitchenSinkApp.kt` | not "MainActivity" | +| `LoginType` | Enum of supported authentication modes: OAuth, JWT, AccessToken. | `auth/LoginActivity.kt` | — | +| `CallObjectStorage` | In-memory synchronized registry of active SDK `Call` objects keyed by call id. | `utils/CallObjectStorage.kt` | not "call cache" | +| `Space` / `Team` / `Membership` | Webex messaging domain objects surfaced by the SDK and shown in the messaging feature. | `messaging/` package; SDK types | — | +| `Module` | A Koin DI module registering a feature's ViewModels/Repositories. | e.g. `MessagingModule.kt`, `CallModule.kt` | not "Gradle module" | + +## Abbreviations & Acronyms +| Abbreviation | Expansion | Meaning in this repo | +|---|---|---| +| SDK | Software Development Kit | The Cisco Webex Android SDK this app demonstrates | +| DI | Dependency Injection | Provided by Koin (`WebexModule.kt`) | +| UC / CUCM | Unified Communications / Cisco Unified Communications Manager | On-prem calling login path (`auth/`, `AndroidManifest.xml`) | +| MVVM | Model-View-ViewModel | The UI layering pattern (Activity → ViewModel → Repository) | +| ABI | Application Binary Interface | APK split dimension in `app/build.gradle` | +| FCM | Firebase Cloud Messaging | Push messaging service (`KitchenSinkFCMService`) | + +## Maintenance +- When a new domain concept is introduced (new SDK feature area, event, state), add it here in the same change. +- Cross-reference: public-surface terms → `CONTRACTS.md`. diff --git a/ai-docs/REVIEW_CHECKLIST.md b/ai-docs/REVIEW_CHECKLIST.md new file mode 100644 index 00000000..91fef382 --- /dev/null +++ b/ai-docs/REVIEW_CHECKLIST.md @@ -0,0 +1,47 @@ + +# Review-Check Catalog — kitchenSink + +> Start here → root [`AGENTS.md`](../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](SPEC_INDEX.md) · system [`ARCHITECTURE.md`](ARCHITECTURE.md). Then this doc at Review & Merge. +> Context-efficiency: link to canonical docs — don't duplicate them; load on demand, not upfront. + +> Each finding records: severity (Blocking / Important / Medium / Minor), check id, file path, what's wrong, why it matters, a concrete fix. Any Blocking finding fails the gate. + +## Core checks (always run) +| # | Check | What it verifies | Severity if it fails | +|---|---|---|---| +| C1 | Spec-currency + WHAT/WHY | Affected module spec under `/ai-docs/` changed in the same change as code; every requirement states WHAT and WHY | Blocking | +| C2 | Contract correctness | SDK-usage/public-surface changes reflected in the owning module spec and `CONTRACTS.md`; no undocumented breaking change | Blocking | +| C3 | Code-vs-spec match | Signatures, data-flow, and architecture claims match actual code (file path) | Blocking | +| C4 | Test adequacy | Each behavior change has a test with a positive AND a negative case where practical | Important | +| C5 | Error handling + input validation | UI/SDK inputs validated; `CompletionHandler` failures handled, not swallowed | Important | +| C6 | Security baseline | No hardcoded secrets; credentials stay in `local.properties`/`gradle.properties`; nothing sensitive logged (per `SECURITY.md`) | Blocking | + +## Coverage-conditional checks (run by the touched module's manifest coverage state) +| # | Check | When it applies | What it verifies | Severity | +|---|---|---|---|---| +| K1 | Regression guard | Modifying any module (all are `Untracked`) | Behavior the change claims NOT to alter still works; add characterization coverage where risky | Blocking | +| K2 | Grounding | Any module (all `Untracked`) | Claims cite real code (file path), not memory; uncovered surfaces flagged `[NEEDS HUMAN INPUT]` | Important | +| K3 | Drift threshold | Any tracked module | N/A while all modules are `Untracked`; revisit after a coverage assessment | Important | +| K4 | Coverage-state accuracy | Coverage-state change proposed | The recorded manifest coverage state matches evidence; promotion rules honored | Medium | + +## Cross-cutting checks (apply at higher risk / autonomy) +| # | Check | What it verifies | Severity | +|---|---|---|---| +| X1 | Cross-model review | The artifact was validated by a different runtime than the one that generated it (generator ≠ validator) | Blocking when required | +| X2 | Observability | Logging adequate for the change; nothing sensitive logged | Medium | +| X3 | Rollout safety | Flavor/build changes are safe; no accidental credential/scope default change | Important | + +## How the set is selected +1. Always run the 6 core checks. +2. Add the coverage-conditional checks whose "when it applies" matches the touched modules' manifest coverage state (currently all `Untracked` → K1/K2 apply). +3. Add the cross-cutting checks when the change is high-risk or runs at higher autonomy. + +## Output +- A compliance matrix + severity-sorted findings + a verdict (Pass / Pass-with-warnings / Blocked). Draft only; a human posts. diff --git a/ai-docs/RULES.md b/ai-docs/RULES.md new file mode 100644 index 00000000..8b29a6a7 --- /dev/null +++ b/ai-docs/RULES.md @@ -0,0 +1,73 @@ + +# Rules — kitchenSink + +> Start here → root [`AGENTS.md`](../AGENTS.md) (agent entry, carries the critical rules) · router [`SPEC_INDEX.md`](SPEC_INDEX.md) · system [`ARCHITECTURE.md`](ARCHITECTURE.md). Then this doc; per-language detail in `rules//`. +> Context-efficiency: link to canonical docs — don't duplicate them; load on demand, not upfront. + +> These rules are extracted from this repo's real conventions (file path). Assess-only onboarding: they surface current practice; they do not add new gates. + +## Coverage Map (which docs/specs to trust) +| Module | Manifest coverage state | What it means here | +|---|---|---| +| `kitchensink/` (core) | Untracked | Code is source of truth; cross-check every claim against source. | +| `auth/` | Untracked | Code is source of truth; cross-check against source. | +| `calling/` | Untracked | Code is source of truth; cross-check against source. | +| `messaging/` | Untracked | Code is source of truth; cross-check against source. | +| `person/` | Untracked | Code is source of truth; cross-check against source. | +| `search/` | Untracked | Code is source of truth; cross-check against source. | +| `webhooks/` | Untracked | Code is source of truth; cross-check against source. | +| `extras/` | Untracked | Code is source of truth; cross-check against source. | + +## Autonomy & Ask-First +- **May proceed:** low-risk UI/demo tweaks that do not change SDK usage, credentials handling, or build flavors. +- **Ask first / plan + confirm:** changes to authentication flows, credential/`BuildConfig` handling, Koin module wiring, product flavors, or dependency versions. +- **Never without explicit human approval:** committing credentials, changing `SCOPE`/`WEBHOOK_URL` defaults, or publishing artifacts. + +## Naming +- Kotlin packages are feature-scoped under `com.ciscowebex.androidsdk.kitchensink.` (e.g. `auth`, `calling`, `messaging`) (`app/src/main/java/...`). +- Koin modules are named `Module` (e.g. `messagingModule`, `callModule`, `personModule`) (`MessagingModule.kt`, `CallModule.kt`, `PersonModule.kt`). +- MVVM roles use suffixes `Activity`, `ViewModel`, `Repository` (e.g. `LoginActivity`, `WebexViewModel`, `WebexRepository`). +- SDK event enums live on `WebexRepository` (e.g. `CallEvent`, `MessageEvent`, `SpaceEvent`) (`WebexRepository.kt`). + +## Logging +- Use Android `Log` with a per-class `tag` constant (e.g. `private val tag = "WebexRepository"`) (`WebexRepository.kt`). +- Never log credentials, tokens, or `BuildConfig` secret fields (see `SECURITY.md`). + +## Error Handling +- SDK calls use the callback idiom `CompletionHandler`; results carry `isSuccessful` and errors (`WebexRepository.kt`). +- UI-facing failures are surfaced via `LiveData` streams (e.g. `_ucLiveData`, `_virtualBgError`) and `Toast`/dialog in activities (`WebexViewModel.kt`, `calling/CallActivity.kt`). +- Wrap risky SDK init in try/catch and log, as in `buildCrashEnabledWebex` (`WebexModule.kt`); do not swallow silently in new code. + +## Imports / Dependencies +- Dependency versions are centralized in `buildSrc/.../Dependencies.kt` (the `Versions`/`Dependencies` objects); reference these rather than hardcoding versions in `app/build.gradle`. +- SDK dependencies are flavor-scoped (`fullImplementation`, `meetingImplementation`, `wxcImplementation`, `messageImplementation`); only one is active per flavor (`app/build.gradle`). + +## Testing +- Unit tests: JUnit4 under `app/src/test/java`; instrumented tests: Espresso under `app/src/androidTest/java` (`app/build.gradle`). +- `[NEEDS HUMAN INPUT]` — no enforced coverage bar found; `enableCodeCoverage=OFF` in `gradle.properties`. + +## Security +- Credentials (`CLIENT_ID`, `CLIENT_SECRET`, `REDIRECT_URI`, `FEDRAMP_*`, `WEBHOOK_URL`) come from `local.properties`; `SCOPE` from `gradle.properties`; both are injected into `BuildConfig` (`app/build.gradle`). Never hardcode or log them. See `SECURITY.md`. + +## Spec-Currency & Drift Thresholds +- Update the affected module spec/docs in the SAME change as the code (spec-currency). +- All modules are `Untracked`; treat code as authoritative until a coverage assessment is run. + +## Secrets Policy +- No hardcoded secrets/tokens/keys/connection strings — ever. Source from `local.properties`/`gradle.properties` at build time; never log them. + + +## Concurrency & Async +- The app is reactive/event-driven: SDK observer callbacks are re-published through `LiveData` and RxJava2 is available (`WebexRepository.kt`, `Dependencies.kt`). +- `CallObjectStorage` guards its mutable list with `synchronized` blocks; `setCallObserver` is `@Synchronized` — preserve these guards when touching the call registry (`utils/CallObjectStorage.kt`, `WebexRepository.kt`). + +## Maintenance +- Add a rule when a review correction recurs; remove it when a lint rule starts enforcing it. +- Cross-reference: patterns → `patterns/`; per-language → `rules//`. diff --git a/ai-docs/SECURITY.md b/ai-docs/SECURITY.md new file mode 100644 index 00000000..4fadeb68 --- /dev/null +++ b/ai-docs/SECURITY.md @@ -0,0 +1,57 @@ + +# Security Baseline — kitchenSink + +> Start here → root [`AGENTS.md`](../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](SPEC_INDEX.md) · system [`ARCHITECTURE.md`](ARCHITECTURE.md). Then this doc; module-specific security behavior lives in each owning module spec. +> Context-efficiency: link to canonical docs — don't duplicate them; load on demand, not upfront. + +> Read before changing anything that touches input, identity, data, or external calls. This documents what the repo enforces today; gaps are marked `[NEEDS HUMAN INPUT]`. + +## Trust Boundaries +| Boundary | Untrusted side | Trusted side | What is enforced at the crossing | +|---|---|---|---| +| Login → Webex identity | end user | Webex cloud (via SDK) | OAuth/JWT/Access-Token authentication through SDK `Authenticator` (`auth/LoginActivity.kt`) | +| App ↔ Webex cloud | network | app process | Handled by the Webex SDK; app does not implement its own transport security | +| Build-time config | developer machine | app binary | Secrets injected from `local.properties`/`gradle.properties` into `BuildConfig`, not committed (`app/build.gradle`) | + +## Authentication & Authorization Model +- **Authentication:** OAuth (web), JWT, and Access-Token flows selected at login; a UC/CUCM login path exists. Identity is established through the Webex SDK `Authenticator`; `WebexRepository` implements `WebexAuthDelegate` and handles re-login/login-failure callbacks (`auth/LoginActivity.kt`, `WebexRepository.kt`). +- **Authorization:** Delegated to Webex cloud; the requested OAuth `SCOPE` is configured in `gradle.properties` and injected via `BuildConfig.SCOPE` (`app/build.gradle`, `gradle.properties`). +- **Default posture:** FedRAMP restrictions, when present, are enforced via `AppConfiguration.containsFedRampRestrictions()` / `SettingsStore.isFedRAMPEmployee()` (`auth/LoginActivity.kt`). + +## Secret & Credential Handling +- Secrets source: `local.properties` (`CLIENT_ID`, `CLIENT_SECRET`, `REDIRECT_URI`, `FEDRAMP_CLIENT_ID/SECRET/REDIRECT_URI`, `WEBHOOK_URL`) and `gradle.properties` (`SCOPE`); read into `BuildConfig` at build time (`app/build.gradle`). +- Injection: the running code reads `BuildConfig.*` constants; values default to empty strings when `local.properties` is absent (`app/build.gradle`). +- Rotation: `[NEEDS HUMAN INPUT]`. +- **Hard rule:** never commit secrets, tokens, keys, or connection strings; never log them. + +## Data Classification & Handling +| Data class | Examples | Storage rule | Logging rule | In transit | +|---|---|---|---|---| +| OAuth credentials | `CLIENT_ID`, `CLIENT_SECRET`, `REDIRECT_URI` | Build-time only, never committed | Never log | Handled by SDK/HTTPS | +| User email / login prefs | email, login type | Android `SharedPreferences` (device-local) (`utils/SharedPrefUtils.kt`) | Avoid logging PII | N/A (local) | +| Webex user/space/message content | SDK-fetched data | In-memory only; no app-owned datastore | Avoid logging content | Handled by SDK | + +## Input Validation & Output Encoding Posture +- User inputs (e.g. login email dialog, dialer input) are passed to the SDK; validate at UI boundaries where the SDK does not (`auth/LoginActivity.kt`). No SQL/command construction exists in the app (no owned datastore). + + +## Session & Cookie Posture +- Login type and email are persisted in Android `SharedPreferences` (`utils/SharedPrefUtils.kt`). Session/token lifecycle is owned by the Webex SDK; `WebexRepository.onReLoginRequired` signals re-authentication (`WebexRepository.kt`). Cookie/token storage internals are `[NEEDS HUMAN INPUT]` (SDK-owned). + + +## Known Sensitive Areas & Accepted Risks +| Area | Risk | Mitigation / why accepted | Owner | +|---|---|---|---| +| Sample credentials in `local.properties` | Leaked OAuth client secret if committed | `local.properties` is not committed; defaults empty | `[NEEDS HUMAN INPUT]` | +| Demo/sample nature | Not hardened for production use | This is an SDK demonstration app, not a shipping product (`README.md`) | `[NEEDS HUMAN INPUT]` | + +## Reporting & Review +- Security-relevant changes require `[NEEDS HUMAN INPUT]` review path. Suspected vulnerabilities: `[NEEDS HUMAN INPUT]`. +- Cross-reference: module-specific security behavior lives in the owning module spec (notably `auth/`). diff --git a/ai-docs/SERVICE_STATE.md b/ai-docs/SERVICE_STATE.md new file mode 100644 index 00000000..31950e83 --- /dev/null +++ b/ai-docs/SERVICE_STATE.md @@ -0,0 +1,59 @@ + +# Service State (living) — kitchenSink + +> Start here → root [`AGENTS.md`](../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](SPEC_INDEX.md) · system [`ARCHITECTURE.md`](ARCHITECTURE.md). Read this FIRST before adding a surface; stable contracts in `CONTRACTS.md`. +> Context-efficiency: link to canonical docs — don't duplicate them; load on demand, not upfront. + +> Source of truth for "does X already exist?" This is a client Android app: it exposes no server endpoints and owns no datastore. The as-built surfaces below are the Android components and external integrations that exist now. + +## Current Android Components (as-built) + +| Component | Kind | Declared at | +|---|---|---| +| `LoginActivity` (LAUNCHER) | Activity | `AndroidManifest.xml` | +| `HomeActivity`, `SetupActivity`, `SetupCameraActivity` | Activity | `AndroidManifest.xml` | +| `auth.*` (JWT/AccessToken/OAuthWeb login), `cucm.UCLoginActivity` | Activity | `AndroidManifest.xml` | +| `calling.*` (Call, CucmCall, Dialer, LockScreen, closed captions, calendar meeting details) | Activity | `AndroidManifest.xml` | +| `messaging.*` (Messaging, search, teams/space detail, memberships, composer) | Activity | `AndroidManifest.xml` | +| `search.SearchActivity`, `webhooks.WebhooksActivity`, `extras.ExtrasActivity` | Activity | `AndroidManifest.xml` | +| `firebase.KitchenSinkFCMService` | Service (FCM) | `AndroidManifest.xml` | +| `KitchenSinkForegroundService`, `CallManagementService`, `CallRejectService` | Service | `AndroidManifest.xml` | +| `FileProvider` (`${applicationId}.provider`) | ContentProvider | `AndroidManifest.xml` | + + +## Current Events +| Event / topic | Direction | Producer/consumer | Payload ref | +|---|---|---|---| +| Firebase `MESSAGING_EVENT` (push) | consume | `firebase.KitchenSinkFCMService` | `AndroidManifest.xml` | +| Webex SDK space/membership/message/calendar/call callbacks | consume | `WebexRepository` observers → `LiveData` | `WebexRepository.kt` | + +## External Dependencies +| Dependency | Used for | Timeout / retry | Circuit breaker / fallback | +|---|---|---|---| +| Webex cloud (via SDK) | All Webex operations | Owned by SDK | Errors surfaced via `LiveData`/`Toast` | +| Firebase (FCM/Analytics/Crashlytics) | Push, analytics, crash reporting | Owned by Firebase SDK | App runs without push if unavailable | +| Configured `WEBHOOK_URL` | Webhooks demo target | `[NEEDS HUMAN INPUT]` | Empty default → feature inert | + +## Feature Flags (current) + +| Flag / toggle | Kind | Default / current state | Controls | Source | +|---|---|---|---|---| +| Product flavor (`full`, `meeting`, `wxc`, `message`) | Build-time flavor | `full` (canonical build/test commands target `full`) | Which Webex SDK artifact is linked and which SDK surface is available | `app/build.gradle` | +| `enableCodeCoverage` | Gradle property | `OFF` | Whether coverage instrumentation is applied at build time | `gradle.properties` | +| FedRAMP mode | Runtime toggle via `AppConfiguration` / `SettingsStore` | `[NEEDS HUMAN INPUT]` (persisted per device; no committed default confirmed) | Gates FedRAMP-specific login behavior | `auth/LoginActivity.kt` | +| `WEBHOOK_URL` | `BuildConfig` value from `local.properties` | Empty by default → webhooks demo inert | Target URL for the webhooks demo | `app/build.gradle` | + + +## Compliance / Certifications +- FedRAMP mode is supported and gates login behavior via `AppConfiguration`/`SettingsStore` (`auth/LoginActivity.kt`); do not regress the FedRAMP toggle handling. + +## Maintenance +- Update the relevant row in the same change that adds/changes/removes an Android component, external dependency, or event integration. +- Cross-reference: stable contracts → `CONTRACTS.md`; security posture → `SECURITY.md`. diff --git a/ai-docs/SPEC_INDEX.md b/ai-docs/SPEC_INDEX.md new file mode 100644 index 00000000..eeab084b --- /dev/null +++ b/ai-docs/SPEC_INDEX.md @@ -0,0 +1,55 @@ + +# Spec Index — kitchenSink + +> Start here → root [`AGENTS.md`](../AGENTS.md) (agent entry). This file is the router (generated at `ai-docs/SPEC_INDEX.md`); system overview in [`ARCHITECTURE.md`](ARCHITECTURE.md). Load `AGENTS.md` + this file first; pull every other doc on demand. +> Context-efficiency: link to canonical docs — don't duplicate them; route to the minimum needed per task. + +> AI agent entry point after `AGENTS.md`. Load this once at session start; pull other docs on demand. +> **Source of truth:** `.sdd/manifest.json` (this file mirrors it for humans). + +## Module Registry +| Module | Responsibility | Manifest coverage state | Start here | +|---|---|---|---| +| `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/` (core) | App shell, Koin wiring, shared `WebexRepository`/`WebexViewModel`, services | Untracked | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/ai-docs/core-spec.md` | +| `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/auth/` | Login flows (OAuth, JWT, Access Token, UC/CUCM) and login-type persistence | Untracked | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/auth/ai-docs/auth-spec.md` | +| `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/` | Calls, meetings, closed captions, in-call UI and services | Untracked | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/ai-docs/calling-spec.md` | +| `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/` | Spaces, teams, memberships, message composer | Untracked | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/ai-docs/messaging-spec.md` | +| `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/person/` | Current-user / person detail retrieval | Untracked | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/person/ai-docs/person-spec.md` | +| `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/` | People/space search | Untracked | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/ai-docs/search-spec.md` | +| `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/webhooks/` | Webhook management UI | Untracked | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/webhooks/ai-docs/webhooks-spec.md` | +| `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/extras/` | Miscellaneous SDK feature demos | Untracked | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/extras/ai-docs/extras-spec.md` | + +## Task Routing +| If the task is… | Load | +|---|---| +| Understanding the system | `ARCHITECTURE.md` | +| Working in a feature area | that module's spec under `/ai-docs/-spec.md` | +| A change to SDK usage / public surface | the owning module spec + `CONTRACTS.md` | +| Running or changing tests | `TEST_INDEX.md` + the affected module spec | +| Updating docs after a code change | affected module specs + relevant standing indexes/contracts | + +## Incident History +| INC id | Date | Module | One-line | Link | +|---|---|---|---|---| +| N/A | — | — | No incident history recorded during assess-only onboarding. | — | + +## Spec Registry +| Doc | Location | Purpose | +|---|---|---| +| Patterns | `patterns/` | repo conventions, correct vs incorrect | +| Rules | `RULES.md` + `rules/` | enforceable do/don't beyond AGENTS.md critical rules | +| Glossary | `GLOSSARY.md` | ubiquitous language: term → definition → code location | +| Security | `SECURITY.md` | trust boundaries, authn/authz, secret handling, data classification | +| Contracts | `CONTRACTS.md` | root index of public-surface contracts; details live at owning modules or native contract sources | +| Service state | `SERVICE_STATE.md` | living as-built registry — read first to avoid duplicate/breaking surfaces | +| Test index | `TEST_INDEX.md` | test tiers, canonical commands, locations, frameworks, dependencies, and quality gates | +| Getting started | `GETTING_STARTED.md` | clone/build/run + configuration | +| Decision records | `adr/` | standing ADRs — why the architecture is the way it is | +| Review catalog | `REVIEW_CHECKLIST.md` | the 6-core + 4-coverage + 3-cross-cutting review checks | diff --git a/ai-docs/TEST_INDEX.md b/ai-docs/TEST_INDEX.md new file mode 100644 index 00000000..a62ce4e7 --- /dev/null +++ b/ai-docs/TEST_INDEX.md @@ -0,0 +1,35 @@ + +# Test Index — `kitchenSink` + +> Start here → root [`AGENTS.md`](../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](SPEC_INDEX.md) · system [`ARCHITECTURE.md`](ARCHITECTURE.md). This doc is the repo-wide map of the test surface. +> Context-efficiency: this is an INDEX, not a case list. It links to where cases live — it does not duplicate them. + +## Test Surface +| Tier | Command (role) | Test directory | Framework | External deps | +|---|---|---|---|---| +| Unit | `./gradlew testFullDebugUnitTest` | `app/src/test/java` | JUnit4 | none | +| E2E / System | `./gradlew connectedFullDebugAndroidTest` | `app/src/androidTest/java` | AndroidX Test / Espresso | connected device/emulator | + +> Note (assess-only): the repo ships example test scaffolding (`app/src/test/java/.../ExampleUnitTest.kt`); substantial coverage is not yet present. Instrumented UI tests use Espresso libraries declared in `app/build.gradle`. + +## Where the Cases Live +- **Unit test cases** → each module's spec, "Test-Case Strategy (module)" section (see `SPEC_INDEX.md` for the module registry). +- **Instrumented / UI / system cases** → `app/src/androidTest/java`. + +## Coverage / Quality Gate +- Minimum: `[NEEDS HUMAN INPUT]` · Measures: `[NEEDS HUMAN INPUT]` · Applies to: `[NEEDS HUMAN INPUT]` · Enforced in: `[NEEDS HUMAN INPUT]`. +- Evidence: `gradle.properties` sets `enableCodeCoverage=OFF`; no committed in-build coverage gate was found. A coverage/quality gate (if any) is unresolved and must be confirmed by the repo owner (`quality_gates.code_coverage.origin = unknown` in `.sdd/manifest.json`). + +## QA Dependencies & Environments +- Connected device or emulator is required for instrumented tests. `gradle.properties` toggles (`enableSDKTests`, `enableReleaseTesting`, `enableReleaseUITesting`) are `OFF` by default. No external manual-QA tracker is referenced in the repo. + +## Where to Go Next +- Agent entry: `../AGENTS.md` · System shape: `ARCHITECTURE.md` · Routing: `SPEC_INDEX.md` +- Machine source of truth: `.sdd/manifest.json` (`commands`, `tests`, `quality_gates`). diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/ai-docs/core-spec.md b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/ai-docs/core-spec.md new file mode 100644 index 00000000..aee5b75a --- /dev/null +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/ai-docs/core-spec.md @@ -0,0 +1,178 @@ +# Core (App Shell) — SPEC + +> Start here → root [`AGENTS.md`](../../../../../../../../../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](../../../../../../../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../../../../../../../ai-docs/ARCHITECTURE.md). This is the module's canonical spec. +> Context-efficiency: link to canonical docs — don't duplicate them; load specs on demand per `SPEC_INDEX.md`. + +## Metadata +| Field | Value | +|---|---| +| Module id | `core` | +| Source path(s) | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/` (top-level files: `KitchenSinkApp.kt`, `WebexRepository.kt`, `WebexViewModel.kt`, `WebexModule.kt`, `BaseActivity.kt`, `BaseViewModel.kt`, service classes) | +| Doc kind | Module spec | +| Coverage score | Pending coverage assessment | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | claude-cli / pending / 2026-07-31T00:00:00Z | +| Validation status | not-run | + +## Evidence Rules +Every requirement below cites concrete source evidence using `file path`. Assess-only onboarding: this module is `Untracked` in `.sdd/manifest.json`; code is authoritative. Unresolved facts are marked `[NEEDS HUMAN INPUT]`. + +## Source Material Register +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| Current Kotlin source (`KitchenSinkApp.kt`, `WebexRepository.kt`, `WebexViewModel.kt`, `WebexModule.kt`) | overview / architecture | used | Placed across Overview, Design, Data Flow, and Sequence sections | +| No prior SDD/design specs | none | none | First onboarding; no migration performed | + +## Overview +The core (app-shell) module owns application startup and the shared plumbing every feature depends on. `KitchenSinkApp` is the Android `Application`: it initializes Firebase, starts Koin, observes the process lifecycle, and loads/unloads the feature Koin modules based on the saved login type (`KitchenSinkApp.kt`). The core also owns the `Webex` SDK client construction (`buildCrashEnabledWebex`) and the shared `WebexRepository`/`WebexViewModel` registered in `webexModule` (`WebexModule.kt`). + +`WebexRepository` is the single hub that binds to the Webex SDK: it sets itself as the SDK auth and UC-login delegate, registers space/membership/message/calendar/call observers, and republishes their callbacks as `LiveData` and event enums (`WebexRepository.kt`). `WebexViewModel` exposes SDK operations and `LiveData` streams to the screens (`WebexViewModel.kt`). A maintainer changing cross-feature behavior (auth callbacks, call registry, DI wiring) starts here. + +## Purpose / Responsibility +Owns app lifecycle, dependency-injection wiring, the shared Webex SDK client, and the shared repository/view-model that mediate all SDK access. It does NOT own any single feature's UI (those live in `auth/`, `calling/`, `messaging/`, etc.). + +## Stack +Kotlin 2.1.20 (Java 17), Android; Koin DI; AndroidX Lifecycle (`ProcessLifecycleOwner`, `LiveData`); Firebase; Webex Android SDK (`WebexModule.kt`, `Dependencies.kt`). + +## Folder / Package Structure +``` +app/src/main/java/com/ciscowebex/androidsdk/kitchensink/ +├── KitchenSinkApp.kt # Application: Firebase + Koin init, module load/unload per login type +├── WebexModule.kt # core Koin module (webexModule) + buildCrashEnabledWebex +├── WebexRepository.kt # SDK delegate/observer hub; LiveData + event enums +├── WebexViewModel.kt # central ViewModel exposing SDK ops and LiveData +├── BaseActivity.kt / BaseViewModel.kt # shared base classes +├── firebase/ # FCM service + token registration +└── utils/ # SharedPrefUtils, CallObjectStorage, Constants, helpers +``` + +## Key Files (source of truth) +| File | Holds | +|---|---| +| `KitchenSinkApp.kt` | App init, Koin module list per `LoginType`, foreground flag | +| `WebexModule.kt` | `webexModule` registrations and SDK client construction | +| `WebexRepository.kt` | SDK auth/UC delegates, observers, call registry, event enums, `LiveData` | +| `utils/CallObjectStorage.kt` | In-memory active-call registry (synchronized) | +| `utils/SharedPrefUtils.kt` | Login-type/email/FedRAMP preference persistence | + +## Public Surface +Internal Surface — used by the app's own feature modules; not an externally published contract. +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `core.loadKoinModules` | SDK (internal) | `KitchenSinkApp.loadKoinModules(type)` | Load feature Koin modules for a login type | internal | `KitchenSinkApp.kt` | `../../../../../../../../../ai-docs/CONTRACTS.md` | +| `core.webexRepository` | SDK (internal) | `WebexRepository` singleton (Koin) | Shared SDK access + observers | internal | `WebexRepository.kt` | `../../../../../../../../../ai-docs/CONTRACTS.md` | +| `core.webexViewModel` | SDK (internal) | `WebexViewModel` (Koin `viewModel`) | SDK ops + `LiveData` for screens | internal | `WebexViewModel.kt` | `../../../../../../../../../ai-docs/CONTRACTS.md` | + +## Requires (dependencies) +Webex Android SDK (`Webex`, `Authenticator`, observers); Koin; Firebase; AndroidX Lifecycle. Feature modules require the core `webexModule` to be loaded before their ViewModels resolve (`KitchenSinkApp.kt`, `WebexModule.kt`). + +## Requirements +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `CORE-R-001` | On app start, Firebase is initialized and Koin is started before feature modules load | SDK/DI must be ready before any screen resolves a ViewModel | `KitchenSinkApp.kt` (`onCreate`) | None found | — | PRESENT | +| `CORE-R-002` | Feature Koin modules are loaded as a set keyed by `LoginType` | Each login type needs the same feature set wired consistently | `KitchenSinkApp.kt` (`loadKoinModules`) | None found | — | PRESENT | +| `CORE-R-003` | `WebexRepository` binds itself as the SDK auth and UC-login delegate on construction | Centralizes auth/UC callbacks in one hub | `WebexRepository.kt` (`init`) | None found | — | PRESENT | +| `CORE-R-004` | SDK observer callbacks are re-published to the UI as `LiveData`/event enums | Decouples SDK threading from screens | `WebexRepository.kt` (space/membership/message/calendar observers) | None found | — | PRESENT | +| `CORE-R-005` | The active-call registry is mutated only under synchronization | Prevent races on the shared call list | `utils/CallObjectStorage.kt`; `WebexRepository.setCallObserver` (`@Synchronized`) | None found | — | PRESENT | + +## Design Overview +The core follows MVVM with a repository hub. `WebexRepository` is a singleton (Koin `single`) that owns all SDK observer registration and translates SDK callbacks into `LiveData` streams and typed event enums (`CallEvent`, `MessageEvent`, `SpaceEvent`, `MembershipEvent`, `CalendarMeetingEvent`). This keeps SDK-threading and callback wiring in one place so feature ViewModels observe simple streams (`WebexRepository.kt`). DI is centralized in `webexModule`; feature modules are additive and loaded per login type, allowing unload on logout (`KitchenSinkApp.kt`). + +## Data Flow +```mermaid +flowchart TD + App[KitchenSinkApp.onCreate] --> Firebase[FirebaseApp.initializeApp] + App --> Koin[startKoin] + Login[LoginActivity] --> Load[loadKoinModules type] + Load --> Repo[WebexRepository single] + Repo --> SDK[Webex SDK] + SDK -. observer callbacks .-> Repo + Repo -. LiveData .-> VM[WebexViewModel] + VM -. LiveData .-> UI[Feature screens] +``` + +## Sequence Diagram(s) +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| App startup + module load | Startup sequence | `loadModules` returns false when no saved login type | +| SDK callback fan-out | Observer republish sequence | Re-login/login-failed callbacks propagate to `LiveData` list | + +```mermaid +sequenceDiagram + participant U as User + participant App as KitchenSinkApp + participant Koin as Koin + participant Repo as WebexRepository + participant SDK as Webex SDK + App->>App: onCreate (Firebase + startKoin) + U->>App: launch LoginActivity + App->>Koin: loadKoinModules(loginType) + Koin->>Repo: construct single + Repo->>SDK: delegate = this, authDelegate = this + SDK-->>Repo: onReLoginRequired / onLoginFailed + Repo-->>App: post to _authLiveDataList +``` + +## Class / Component Relationships +```mermaid +classDiagram + class KitchenSinkApp + class WebexRepository + class WebexViewModel + class CallObjectStorage + KitchenSinkApp --> WebexRepository : loads via Koin + WebexViewModel --> WebexRepository : uses + WebexRepository --> CallObjectStorage : call registry + WebexRepository ..|> WebexAuthDelegate + WebexRepository ..|> WebexUCLoginDelegate +``` +`WebexViewModel` and every feature repository depend on the shared `WebexRepository`; `WebexRepository` implements the SDK auth/UC delegate interfaces (`WebexRepository.kt`). + +## Use Cases +- **UC-1 App start:** app process starts → `KitchenSinkApp.onCreate` initializes Firebase + Koin → lifecycle observer registered. Evidence: `KitchenSinkApp.kt`. +- **UC-2 Load feature modules:** user picks/uses a login type → `loadKoinModules(type)` wires the feature set. Evidence: `KitchenSinkApp.kt`. +- **UC-3 SDK event fan-out:** SDK emits a space/message/call event → repository observer maps it to a `LiveData`/event enum → screen updates. Evidence: `WebexRepository.kt`. + + +### UI Flow (per use case) +The core provides `BaseActivity`/`BaseViewModel` and the app launcher path; concrete screens live in feature modules. Cross-service flow: startup wires the SDK client used by all feature screens (`WebexModule.kt`). + + +### Cross-service flow (per use case) +All SDK access crosses into the Webex SDK / Webex cloud via `WebexRepository` methods (e.g. `webex.spaces.get`, `webex.phone.*`) (`WebexRepository.kt`). + + +## State Model +Core holds transient client state: the `CallObjectStorage` active-call list, `WebexRepository` call/UC fields and `MutableLiveData` streams, and the app-level flags (`inForeground`, `isKoinModulesLoaded`, `isUCSSOLogin`) on `KitchenSinkApp` (`KitchenSinkApp.kt`, `WebexRepository.kt`). `clearCallData()` resets call state on teardown. + + +## Concurrency & Reactive Flow +SDK callbacks arrive asynchronously and are republished with `postValue` on `LiveData` (`WebexRepository.kt`). The active-call registry uses `synchronized` blocks (`CallObjectStorage.kt`) and `setCallObserver` is `@Synchronized`. Preserve these guards; the same call may be observed from multiple screens. + + +## Error Handling & Failure Modes +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| SDK re-auth needed | `onReLoginRequired` → `RE_LOGIN_REQUIRED` on auth `LiveData` list | Screen routes back to login | +| Login failed | `onLoginFailed` → `LOGIN_FAILED` | Screen shows failure and re-prompts | +| Webex client init throws | caught + logged in `buildCrashEnabledWebex` | Crash reporting stays disabled; app continues | +| No saved login type | `loadModules()` returns false | Caller prompts for login type | + +## Pitfalls +- Requesting a feature ViewModel before `loadKoinModules` runs fails DI resolution (`KitchenSinkApp.kt`). +- Bypassing `CallObjectStorage`/`WebexRepository` synchronization risks call-list races (`CallObjectStorage.kt`). +- `WebexRepository` sets itself as the SDK delegate in `init`; constructing two instances would contend for delegates (`WebexRepository.kt`). + +## Test-Case Strategy (module) +Assess-only: only example unit scaffolding exists (`app/src/test/java/.../ExampleUnitTest.kt`). Recommended: unit-test `CallObjectStorage` add/remove/synchronization; verify `loadKoinModules` wires the expected module set per `LoginType`. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| `CORE-R-002` | None found | No test asserts the per-login-type module set | +| `CORE-R-005` | None found | No concurrency test for the call registry | + +## Traceability +- Repo architecture: `../../../../../../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../../../../../../ai-docs/SPEC_INDEX.md` +- Coverage state & contracts baseline: `.sdd/manifest.json` diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/auth/ai-docs/auth-spec.md b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/auth/ai-docs/auth-spec.md new file mode 100644 index 00000000..0dd52244 --- /dev/null +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/auth/ai-docs/auth-spec.md @@ -0,0 +1,166 @@ +# Auth — SPEC + +> Start here → root [`AGENTS.md`](../../../../../../../../../../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](../../../../../../../../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../../../../../../../../ai-docs/ARCHITECTURE.md). This is the module's canonical spec. +> Context-efficiency: link to canonical docs — don't duplicate them; load specs on demand per `SPEC_INDEX.md`. + +## Metadata +| Field | Value | +|---|---| +| Module id | `auth` | +| Source path(s) | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/auth/` | +| Doc kind | Module spec | +| Coverage score | Pending coverage assessment | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | claude-cli / pending / 2026-07-31T00:00:00Z | +| Validation status | not-run | + +## Evidence Rules +Every requirement cites `file path` evidence. Assess-only onboarding: module is `Untracked`; code is authoritative. Unresolved facts are `[NEEDS HUMAN INPUT]`. + +## Source Material Register +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| `auth/LoginActivity.kt` and auth activity declarations in `AndroidManifest.xml` | overview / architecture | used | Placed in Overview, Use Cases, Sequence sections | +| No prior SDD/design specs | none | none | First onboarding | + +## Overview +The auth module owns the app's login experience across the authentication modes the Webex SDK supports. `LoginActivity` is the LAUNCHER activity: it presents JWT, OAuth, and Access-Token options, persists the chosen login type, loads the feature Koin modules for it, and routes to the matching login activity (`LoginActivity.kt`, `AndroidManifest.xml`). It also handles FedRAMP gating of the login toggle (`LoginActivity.kt`). + +Concrete per-mode screens (`JWTLoginActivity`, `AccessTokenLoginActivity`, `OAuthWebLoginActivity`) and the UC/CUCM path (`cucm.UCLoginActivity`) are declared in the manifest (`AndroidManifest.xml`). Authentication itself is performed by the Webex SDK; auth callbacks (re-login required, login failed, UC connection state) are handled centrally in `WebexRepository` (`WebexRepository.kt`). + +## Purpose / Responsibility +Owns login-mode selection, login-type persistence, and routing into the correct SDK authentication flow (OAuth/JWT/Access-Token/UC). It does NOT implement token exchange itself — that is the SDK's `Authenticator`. + +## Stack +Kotlin/Android; AndroidX AppCompat + DataBinding (`ActivityLoginBinding`); Koin `viewModel()`; Webex SDK auth APIs (`LoginActivity.kt`). + +## Folder / Package Structure +``` +app/src/main/java/com/ciscowebex/androidsdk/kitchensink/auth/ +├── LoginActivity.kt # launcher: mode selection + routing + FedRAMP toggle +├── JWTLoginActivity # JWT login screen (declared in AndroidManifest.xml) +├── AccessTokenLoginActivity # access-token login screen +├── OAuthWebLoginActivity # OAuth web login screen +└── (loginModule Koin module registered in KitchenSinkApp.kt) +``` + +## Key Files (source of truth) +| File | Holds | +|---|---| +| `LoginActivity.kt` | `LoginType` enum, mode selection, `loadKoinModules`, FedRAMP gating | +| `AndroidManifest.xml` | Declared auth activities (JWT/AccessToken/OAuthWeb, UC login) | + +## Public Surface +Internal Surface — Android entry activities; no externally published contract. +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `auth.LoginActivity` | UI | LAUNCHER activity | Select login mode and route | internal | `LoginActivity.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | +| `auth.LoginType` | SDK (internal) | enum {OAuth, JWT, AccessToken} | Drives which Koin modules + activity are used | internal | `LoginActivity.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | + +## Requires (dependencies) +Core `WebexViewModel`/`WebexRepository`; Webex SDK `Authenticator`/`AppConfiguration`/`SettingsStore`; `SharedPrefUtils` for login-type/email/FedRAMP persistence; OAuth credentials from `BuildConfig` (`LoginActivity.kt`, `utils/SharedPrefUtils.kt`, `app/build.gradle`). + +## Requirements +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `AUTH-R-001` | User can choose OAuth, JWT, or Access-Token login | Demonstrate all SDK auth modes | `LoginActivity.kt` (`buttonClicked`, `LoginType`) | None found | — | PRESENT | +| `AUTH-R-002` | Selected login type is persisted and reused on next launch | Return users to their last mode | `LoginActivity.kt` (`getLoginTypePref`); `utils/SharedPrefUtils.kt` | None found | — | PRESENT | +| `AUTH-R-003` | Choosing a mode loads the feature Koin modules for it before routing | Feature ViewModels must resolve after login | `LoginActivity.kt` (`loadKoinModules`) | None found | — | PRESENT | +| `AUTH-R-004` | OAuth path collects an email before starting OAuth | SDK OAuth flow keys on user email | `LoginActivity.kt` (`showEmailDialog`, `saveEmailPref`) | None found | — | PRESENT | +| `AUTH-R-005` | FedRAMP restriction disables/locks the FedRAMP toggle | Enforce FedRAMP employee constraints | `LoginActivity.kt` (`AppConfiguration.containsFedRampRestrictions`) | None found | Rotation/enforcement detail SDK-owned | PRESENT | + +## Design Overview +`LoginActivity` uses DataBinding to wire three login buttons; each sets `loginTypeCalled` and dispatches. OAuth first collects an email via a dialog, then starts `OAuthWebLoginActivity`; JWT and Access-Token route directly. Before routing, `loadKoinModules(type)` is called so the feature graph is ready, and `enableBackgroundConnection()` is invoked via `WebexViewModel` (`LoginActivity.kt`). The actual credential exchange is delegated to the SDK. + +## Data Flow +```mermaid +flowchart TD + User --> Login[LoginActivity] + Login -->|persist| Prefs[SharedPrefUtils] + Login -->|loadKoinModules| Koin + Login -->|route| ModeAct[JWT/OAuth/AccessToken Activity] + ModeAct --> SDK[Webex SDK Authenticator] + SDK -. auth callbacks .-> Repo[WebexRepository] +``` + +## Sequence Diagram(s) +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| Login mode selection + routing | Login sequence | Login-failed / re-login callbacks handled in `WebexRepository` | + +```mermaid +sequenceDiagram + participant U as User + participant L as LoginActivity + participant P as SharedPrefUtils + participant K as Koin + participant A as Mode Activity + U->>L: tap OAuth/JWT/AccessToken + L->>P: saveLoginTypePref + L->>K: loadKoinModules(type) + alt OAuth + L->>U: prompt email + U-->>L: email + L->>P: saveEmailPref + end + L->>A: startActivity(mode) + A->>A: SDK Authenticator flow +``` + +## Class / Component Relationships +```mermaid +classDiagram + class LoginActivity + class LoginType + class WebexViewModel + LoginActivity --> LoginType + LoginActivity --> WebexViewModel : viewModel() +``` + +## Use Cases +- **UC-1 OAuth login:** user taps OAuth → email dialog → modules loaded → `OAuthWebLoginActivity`. Evidence: `LoginActivity.kt`. +- **UC-2 JWT / Access-Token login:** user taps JWT/Access-Token → modules loaded → matching activity. Evidence: `LoginActivity.kt`. +- **UC-3 Returning user:** saved login type routes automatically on launch. Evidence: `LoginActivity.kt` (`loadModules`). + + +### UI Flow (per use case) +Login screen shows three buttons + a FedRAMP toggle; selecting a button hides the button layout and routes to the mode screen (`LoginActivity.toggleButtonsVisibility`, `LoginActivity.kt`). + + +### Cross-service flow (per use case) +Each mode activity invokes the Webex SDK `Authenticator`, which contacts Webex identity; results return through `WebexRepository` auth callbacks (`WebexRepository.kt`). + + +## State Model +Login type, email, and FedRAMP preference are persisted in Android `SharedPreferences` (`utils/SharedPrefUtils.kt`). `loginTypeCalled` holds the in-flight selection during a login attempt (`LoginActivity.kt`). + + +## Concurrency & Reactive Flow +Auth results are asynchronous SDK callbacks surfaced via `WebexViewModel` `LiveData` (auth live-data list in `WebexRepository.kt`). The UI observes these rather than blocking. + + +## Error Handling & Failure Modes +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| Login failed | `onLoginFailed` → `LOGIN_FAILED` on auth `LiveData` | Show failure, re-enable buttons | +| Re-login required | `onReLoginRequired` → `RE_LOGIN_REQUIRED` | Return to login | +| UC login failure | `onUCLoginFailed` → `UCCallEvent.OnUCLoginFailed` | Surface UC error (`WebexRepository.kt`) | + +## Pitfalls +- Routing before `loadKoinModules(type)` would leave feature ViewModels unresolved (`LoginActivity.kt`). +- Empty OAuth credentials (`BuildConfig`) silently prevent successful login (`app/build.gradle`). +- FedRAMP toggle is locked (non-clickable) when restrictions apply; do not re-enable it unconditionally (`LoginActivity.kt`). + +## Test-Case Strategy (module) +Assess-only: no auth-specific tests found. Recommended: verify login-type persistence round-trips via `SharedPrefUtils`, and that each `LoginType` routes to the correct activity and loads the expected module set. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| `AUTH-R-002` | None found | No persistence round-trip test | +| `AUTH-R-003` | None found | No module-load assertion | + +## Traceability +- Repo architecture: `../../../../../../../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../../../../../../../ai-docs/SPEC_INDEX.md` +- Coverage state & contracts baseline: `.sdd/manifest.json` diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/ai-docs/calling-spec.md b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/ai-docs/calling-spec.md new file mode 100644 index 00000000..d8e6ebc0 --- /dev/null +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/ai-docs/calling-spec.md @@ -0,0 +1,188 @@ +# Calling — SPEC + +> Start here → root [`AGENTS.md`](../../../../../../../../../../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](../../../../../../../../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../../../../../../../../ai-docs/ARCHITECTURE.md). This is the module's canonical spec. +> Context-efficiency: link to canonical docs — don't duplicate them; load specs on demand per `SPEC_INDEX.md`. + +## Metadata +| Field | Value | +|---|---| +| Module id | `calling` | +| Source path(s) | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/` | +| Doc kind | Module spec | +| Coverage score | Pending coverage assessment | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | claude-cli / pending / 2026-07-31T00:00:00Z | +| Validation status | not-run | + +## Evidence Rules +Every requirement cites `file path` evidence. Assess-only onboarding: module is `Untracked`; code is authoritative. Unresolved facts are `[NEEDS HUMAN INPUT]`. + +## Source Material Register +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| `calling/CallActivity.kt`, `calling/CallModule.kt`, call observers in `WebexRepository.kt`, calling declarations in `AndroidManifest.xml` | overview / architecture | used | Placed in Overview, Sequence, State Machine sections | +| No prior SDD/design specs | none | none | First onboarding | + +## Overview +The calling module owns the app's call and meeting experience: dialing, answering, in-call controls, closed captions, lock-screen/incoming-call handling, CUCM calls, and calendar-meeting details. Its screens are declared in the manifest (`CallActivity`, `CucmCallActivity`, `DialerActivity`, `LockScreenActivity`, closed-captions activities, `CalendarMeetingDetailsActivity`) (`AndroidManifest.xml`). The `callModule` Koin module registers `CallViewModel`, `ClosedCaptionsViewModel`, and `ClosedCaptionsRepository` (`CallModule.kt`). + +Call lifecycle is driven by the Webex SDK `Call`/`CallObserver`. `WebexRepository` registers a per-call observer (`WxCallObserver`) that fans out ~30 SDK call callbacks (ringing, connected, disconnected, media/membership changes, breakout sessions, closed captions) to registered observers, and it maintains the active-call registry via `CallObjectStorage` (`WebexRepository.kt`, `utils/CallObjectStorage.kt`). + +## Purpose / Responsibility +Owns the demonstration of Webex calling/meeting APIs and the in-call UI. It does NOT own the underlying media stack — that is the Webex SDK `Phone`/`Call`. + +## Stack +Kotlin/Android; DataBinding (`ActivityCallBinding`); Picture-in-Picture; foreground/notification services; Webex SDK `phone`/`Call` APIs (`CallActivity.kt`, `AndroidManifest.xml`). + +## Folder / Package Structure +``` +app/src/main/java/com/ciscowebex/androidsdk/kitchensink/calling/ +├── CallActivity.kt # in-call UI, PiP, call queue +├── CallModule.kt # callModule Koin registrations +├── captions/ # ClosedCaptions repo + view model + activities +├── calendarMeeting/ # calendar meeting details +└── (Dialer/LockScreen/CucmCall activities declared in AndroidManifest.xml) +``` + +## Key Files (source of truth) +| File | Holds | +|---|---| +| `CallActivity.kt` | In-call UI, permission handling, PiP, call queue adapter | +| `CallModule.kt` | `callModule` DI registrations | +| `WebexRepository.kt` | `WxCallObserver`, `CallEvent` enum, incoming-call listeners, call registry | +| `utils/CallObjectStorage.kt` | Active `Call` registry | + +## Public Surface +Internal Surface — Android call screens/services; no externally published contract. +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `calling.CallActivity` | UI | Activity | In-call experience + controls | internal | `CallActivity.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | +| `calling.CallEvent` | SDK (internal) | enum of dial/answer/association outcomes | Signal call operation results to UI | internal | `WebexRepository.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | +| `calling.incomingCallListener` | SDK (internal) | `Phone.IncomingCallListener` registration | Receive incoming calls | internal | `WebexRepository.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | + +## Requires (dependencies) +Core `WebexRepository`/`WebexViewModel`; Webex SDK `Phone`/`Call`/`CallObserver`/`MediaOption`; Android permissions (camera/mic/phone) and foreground services; `CallObjectStorage` (`CallActivity.kt`, `WebexRepository.kt`, `AndroidManifest.xml`). + +## Requirements +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `CALL-R-001` | Incoming calls are delivered to registered listeners and stored in the call registry | Multiple screens observe the same call | `WebexRepository.kt` (`registerIncomingCallListener`, `setIncomingCallListener`) | None found | — | PRESENT | +| `CALL-R-002` | A per-call observer fans SDK callbacks out to all registered observers for that call | Decouple SDK single-observer model from multiple UI observers | `WebexRepository.kt` (`WxCallObserver`, `setCallObserver`) | None found | — | PRESENT | +| `CALL-R-003` | On disconnect, the call is removed from the registry | Prevent stale call references | `WebexRepository.kt` (`onDisconnected` → `CallObjectStorage.removeCallObject`) | None found | — | PRESENT | +| `CALL-R-004` | Call permissions are requested; pending dial/answer retried after grant | Calls need camera/mic/phone permissions | `CallActivity.kt` (`callingPermissionLauncher`) | None found | — | PRESENT | +| `CALL-R-005` | Closed captions are surfaced during a call | Demonstrate captions API | `WebexRepository.kt` (`onClosedCaptionsArrived`); `calling/captions/` | None found | — | PRESENT | + +## Design Overview +`CallActivity` renders the in-call UI, handles runtime permissions via an `ActivityResultContracts.RequestMultiplePermissions` launcher, and supports Picture-in-Picture. Because the SDK `Call` accepts a single observer, `WebexRepository` interposes `WxCallObserver`, which holds a list of `CallObserver`s per call id and forwards every callback; observers register through `setCallObserver` and the first registration triggers `registerCallObserver` on the SDK `Call` (`WebexRepository.kt`). + +## Data Flow +```mermaid +flowchart TD + Dialer[DialerActivity] --> VM[CallViewModel/WebexViewModel] + VM --> Phone[Webex SDK Phone] + Phone -. IncomingCallListener .-> Repo[WebexRepository] + Repo --> Store[CallObjectStorage] + Phone -. CallObserver .-> WxObs[WxCallObserver] + WxObs --> UI[CallActivity observers] +``` + +## Sequence Diagram(s) +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| Incoming call + observe | Incoming-call sequence | Disconnect removes call from registry | +| Dial/answer | Outbound/answer sequence | Permission-denied → toast; retry on grant | + +```mermaid +sequenceDiagram + participant Phone as Webex Phone + participant Repo as WebexRepository + participant Store as CallObjectStorage + participant UI as CallActivity + Phone-->>Repo: onIncomingCall(call) + Repo->>Store: addCallObject(call) + Repo->>Repo: registerCallObserver(call) + UI->>Repo: setCallObserver(call, uiObserver) + Phone-->>Repo: onConnected/onMediaChanged/... + Repo-->>UI: forward callback + Phone-->>Repo: onDisconnected + Repo->>Store: removeCallObject +``` + +## Class / Component Relationships +```mermaid +classDiagram + class CallActivity + class WebexRepository + class WxCallObserver + class CallObjectStorage + CallActivity ..|> CallObserverInterface + WebexRepository --> WxCallObserver : per call id + WebexRepository --> CallObjectStorage + WxCallObserver ..|> CallObserver +``` + +## Use Cases +- **UC-1 Receive & answer:** SDK signals incoming call → registry + observer registered → user answers in `CallActivity`. Evidence: `WebexRepository.kt`, `CallActivity.kt`. +- **UC-2 Place call:** user dials → `Phone` dial → `CallEvent.DialCompleted`/`DialFailed`. Evidence: `WebexRepository.kt` (`CallEvent`). +- **UC-3 Closed captions:** captions arrive during call → surfaced to captions UI. Evidence: `WebexRepository.kt`, `calling/captions/`. + + +### UI Flow (per use case) +`CallActivity` manages the in-call layout, a call-queue adapter for multiple calls, and PiP transitions (`CallActivity.kt`). + + +### Cross-service flow (per use case) +All call operations go through the Webex SDK `Phone`/`Call`, which reaches Webex calling infrastructure; results return via `CallObserver` callbacks (`WebexRepository.kt`). + + +## State Model +`WebexRepository` holds call flags (`currentCallId`, `oldCallId`, `isAddedCall`, `isLocalVideoMuted`, `isRemoteScreenShareON`, mute maps) and `CallObjectStorage` holds active `Call`s; `clearCallData()` resets them (`WebexRepository.kt`). + + +## Concurrency & Reactive Flow +Call callbacks are asynchronous; `setCallObserver` is `@Synchronized` and `CallObjectStorage` uses `synchronized` blocks. The observer fan-out iterates a per-call observer list; keep it consistent when adding/removing observers (`WebexRepository.kt`, `CallObjectStorage.kt`). + + +## State Machine +```mermaid +stateDiagram-v2 + [*] --> Idle + Idle --> Ringing: onIncomingCall / onRinging + Idle --> Dialing: dial + Dialing --> Connected: onConnected + Ringing --> Connected: answer / onConnected + Connected --> Connected: onMediaChanged / onCallMembershipChanged + Connected --> Disconnected: onDisconnected + Ringing --> Disconnected: onDisconnected + Dialing --> Disconnected: DialFailed + Disconnected --> [*] +``` +States/transitions are inferred from `CallObserver` callbacks and `CallEvent` outcomes (`WebexRepository.kt`). Terminal `Disconnected` removes the call from the registry. + + +## Error Handling & Failure Modes +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| Dial failed | `CallEvent.DialFailed` | Show error, allow retry | +| Answer needs permission | `CallEvent.AnswerPermissionsRequired` | Request permissions; retry on grant | +| Meeting requires pin/password | `CallEvent.MeetingPinOrPasswordRequired` / `CaptchaRequired` | Prompt for credentials | +| Permission denied | permission launcher result false | Toast `permission_error` (`CallActivity.kt`) | + +## Pitfalls +- Registering a UI `CallObserver` without going through `setCallObserver` bypasses the fan-out and misses callbacks (`WebexRepository.kt`). +- Forgetting to remove observers on teardown leaks the per-call observer list (`removeCallObserver`/`clearCallObservers`). +- Foreground/PiP call services must be declared and running for background calls (`AndroidManifest.xml`). + +## Test-Case Strategy (module) +Assess-only: no calling tests found. Recommended: unit-test `WxCallObserver` fan-out (positive: all registered observers receive a callback; negative: removed observer does not) and registry removal on disconnect. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| `CALL-R-002` | None found | No fan-out test | +| `CALL-R-003` | None found | No disconnect-cleanup test | + +## Traceability +- Repo architecture: `../../../../../../../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../../../../../../../ai-docs/SPEC_INDEX.md` +- Coverage state & contracts baseline: `.sdd/manifest.json` diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/extras/ai-docs/extras-spec.md b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/extras/ai-docs/extras-spec.md new file mode 100644 index 00000000..e275e92a --- /dev/null +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/extras/ai-docs/extras-spec.md @@ -0,0 +1,144 @@ +# Extras — SPEC + +> Start here → root [`AGENTS.md`](../../../../../../../../../../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](../../../../../../../../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../../../../../../../../ai-docs/ARCHITECTURE.md). This is the module's canonical spec. +> Context-efficiency: link to canonical docs — don't duplicate them; load specs on demand per `SPEC_INDEX.md`. + +## Metadata +| Field | Value | +|---|---| +| Module id | `extras` | +| Source path(s) | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/extras/` | +| Doc kind | Module spec | +| Coverage score | Pending coverage assessment | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | claude-cli / pending / 2026-07-31T00:00:00Z | +| Validation status | not-run | + +## Evidence Rules +Every requirement cites `file path` evidence. Assess-only onboarding: module is `Untracked`; code is authoritative. Unresolved facts are `[NEEDS HUMAN INPUT]`. + +## Source Material Register +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| `extras/ExtrasModule.kt`, `extrasModule` registration in `KitchenSinkApp.kt`, `ExtrasActivity` in `AndroidManifest.xml` | overview / architecture | used | Placed in Overview, Public Surface, Use Cases | +| No prior SDD/design specs | none | none | First onboarding | + +## Overview +The extras module hosts miscellaneous SDK feature demonstrations that do not belong to a primary feature area. `extrasModule` registers `ExtrasRepository` and `ExtrasViewModel` (`ExtrasModule.kt`), loaded with the app's feature set (`KitchenSinkApp.kt`). `ExtrasActivity` is the screen (`AndroidManifest.xml`). `[NEEDS HUMAN INPUT]` — the exact set of "extra" SDK features shown is not fully enumerable from the DI wiring alone. + +## Purpose / Responsibility +Owns a catch-all screen for auxiliary Webex SDK feature demos. It does NOT own auth, calling, messaging, person, search, or webhooks features (those have dedicated modules). + +## Stack +Kotlin/Android; Koin; Webex SDK APIs (`ExtrasModule.kt`). + +## Folder / Package Structure +``` +app/src/main/java/com/ciscowebex/androidsdk/kitchensink/extras/ +├── ExtrasModule.kt # extrasModule DI (ExtrasViewModel + ExtrasRepository) +├── ExtrasRepository # auxiliary SDK access +├── ExtrasViewModel # exposes extras data to UI +└── ExtrasActivity # extras screen (declared in AndroidManifest.xml) +``` + +## Key Files (source of truth) +| File | Holds | +|---|---| +| `ExtrasModule.kt` | `extrasModule` DI registrations | +| `AndroidManifest.xml` | `extras.ExtrasActivity` | + +## Public Surface +Internal Surface — used by the app; no externally published contract. +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `extras.ExtrasViewModel` | SDK (internal) | Koin `viewModel` | Expose extras features to UI | internal | `ExtrasModule.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | +| `extras.ExtrasActivity` | UI | Activity | Extras/miscellaneous demo screen | internal | `AndroidManifest.xml` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | + +## Requires (dependencies) +Core `WebexRepository`; Webex SDK APIs (`ExtrasModule.kt`). + +## Requirements +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `EXTRAS-R-001` | Extras view model and repository are provided via Koin | Screen resolves extras features through DI | `ExtrasModule.kt` | None found | — | PRESENT | +| `EXTRAS-R-002` | An extras/miscellaneous demo screen exists | Group auxiliary SDK demos | `AndroidManifest.xml` (`ExtrasActivity`) | None found | Exact feature set `[NEEDS HUMAN INPUT]` | PRESENT | + +## Design Overview +A repository/ViewModel pair backing a single miscellaneous demo screen. `ExtrasRepository` wraps auxiliary SDK calls; `ExtrasViewModel` exposes results to `ExtrasActivity` (`ExtrasModule.kt`). + +## Data Flow +```mermaid +flowchart TD + UI[ExtrasActivity] --> VM[ExtrasViewModel] + VM --> Repo[ExtrasRepository] + Repo --> SDK[Webex SDK] + SDK -. results .-> Repo +``` + +## Sequence Diagram(s) +This is a single-operation-group demo module, so one sequence diagram is sufficient. + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| Invoke an extra SDK feature | Extras sequence | SDK failure surfaced to UI | + +```mermaid +sequenceDiagram + participant UI as ExtrasActivity + participant VM as ExtrasViewModel + participant SDK as Webex SDK + UI->>VM: invoke feature + VM->>SDK: SDK op + SDK-->>VM: result / error + VM-->>UI: render +``` + +## Class / Component Relationships +```mermaid +classDiagram + class ExtrasViewModel + class ExtrasRepository + ExtrasViewModel --> ExtrasRepository + ExtrasRepository --> WebexRepository +``` + +## Use Cases +- **UC-1 Try an extra feature:** user opens `ExtrasActivity` → invokes an auxiliary SDK feature → result shown. Evidence: `ExtrasModule.kt`, `AndroidManifest.xml`. + + +### UI Flow (per use case) +`ExtrasActivity` presents auxiliary demos. `[NEEDS HUMAN INPUT]` — whether it spans multiple screens is not determined from code. + + +### Cross-service flow (per use case) +Extras operations are served by Webex cloud via the SDK (`ExtrasModule.kt`). + + +## State Model +Extras feature state is held transiently in `ExtrasViewModel`. + + +## Concurrency & Reactive Flow +Extras operations complete asynchronously via SDK callbacks, exposed through `LiveData`. + + +## Error Handling & Failure Modes +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| Feature op failure | `CompletionHandler` result not successful | Show error in UI | + +## Pitfalls +- The exact feature set is not discoverable from DI wiring alone; read `ExtrasViewModel`/`ExtrasActivity` before changing behavior. + +## Test-Case Strategy (module) +Assess-only: no extras tests found. Recommended: unit-test `ExtrasRepository` per-feature success/failure once the feature set is confirmed. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| `EXTRAS-R-001` | None found | No extras-op test | + +## Traceability +- Repo architecture: `../../../../../../../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../../../../../../../ai-docs/SPEC_INDEX.md` +- Coverage state & contracts baseline: `.sdd/manifest.json` diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/ai-docs/messaging-spec.md b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/ai-docs/messaging-spec.md new file mode 100644 index 00000000..888681b7 --- /dev/null +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/ai-docs/messaging-spec.md @@ -0,0 +1,164 @@ +# Messaging — SPEC + +> Start here → root [`AGENTS.md`](../../../../../../../../../../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](../../../../../../../../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../../../../../../../../ai-docs/ARCHITECTURE.md). This is the module's canonical spec. +> Context-efficiency: link to canonical docs — don't duplicate them; load specs on demand per `SPEC_INDEX.md`. + +## Metadata +| Field | Value | +|---|---| +| Module id | `messaging` | +| Source path(s) | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/` | +| Doc kind | Module spec | +| Coverage score | Pending coverage assessment | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | claude-cli / pending / 2026-07-31T00:00:00Z | +| Validation status | not-run | + +## Evidence Rules +Every requirement cites `file path` evidence. Assess-only onboarding: module is `Untracked`; code is authoritative. Unresolved facts are `[NEEDS HUMAN INPUT]`. + +## Source Material Register +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| `messaging/MessagingModule.kt`, messaging activities in `AndroidManifest.xml`, message/space/membership observers in `WebexRepository.kt` | overview / architecture | used | Placed in Overview, Public Surface, Sequence sections | +| No prior SDD/design specs | none | none | First onboarding | + +## Overview +The messaging module owns the app's spaces, teams, memberships, and message-composition experience. `messagingModule` registers the ViewModels and Repositories for teams (`TeamsRepository`/`TeamsViewModel`, team detail/membership), spaces (`SpacesRepository`/`SpacesViewModel`, space detail, read-status), memberships (`MembershipRepository`/`MembershipViewModel`, read-status), and the message composer (`MessageComposerRepository`/`MessageComposerViewModel`) (`MessagingModule.kt`). Its screens are declared in the manifest (`MessagingActivity`, `TeamDetailActivity`, `SpaceDetailActivity`, `MembershipActivity`, `MessageComposerActivity`, etc.) (`AndroidManifest.xml`). + +Space, membership, and message change events flow through the SDK observers registered in `WebexRepository` (`setSpaceObserver`, `setMembershipObserver`, `setMessageObserver`), which map SDK events to `SpaceEvent`/`MembershipEvent`/`MessageEvent` enums republished as `LiveData` (`WebexRepository.kt`). + +## Purpose / Responsibility +Owns the demonstration of Webex messaging APIs (spaces, teams, memberships, messages) and their UI. It does NOT own message persistence — messages/spaces are fetched from Webex via the SDK. + +## Stack +Kotlin/Android; DataBinding; RecyclerView; Koin; Webex SDK `messages`/`spaces`/`memberships` APIs (`MessagingModule.kt`, `WebexRepository.kt`). + +## Folder / Package Structure +``` +app/src/main/java/com/ciscowebex/androidsdk/kitchensink/messaging/ +├── MessagingModule.kt # messagingModule DI registrations +├── spaces/ # SpacesRepository/ViewModel, detail, members, read-status, listeners +├── teams/ # TeamsRepository/ViewModel, detail, membership +├── composer/ # MessageComposerRepository/ViewModel +└── search/ # MessagingSearchActivity +``` + +## Key Files (source of truth) +| File | Holds | +|---|---| +| `MessagingModule.kt` | All messaging DI registrations | +| `WebexRepository.kt` | `SpaceEvent`/`MembershipEvent`/`MessageEvent` enums + observers + `listMessages`/`getSpace` | +| `spaces/listeners/SpaceEventListener` | Space event callback surface | + +## Public Surface +Internal Surface — Android messaging screens; no externally published contract. +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `messaging.MessageEvent` | SDK (internal) | enum {Received, Edited, Deleted, MessageThumbnailUpdated, Updated} | Signal message changes to UI | internal | `WebexRepository.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | +| `messaging.SpaceEvent` | SDK (internal) | enum {Created, Updated, CallStarted, CallEnded} | Signal space changes | internal | `WebexRepository.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | +| `messaging.MembershipEvent` | SDK (internal) | enum {Created, Updated, Deleted, MessageSeen} | Signal membership changes | internal | `WebexRepository.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | + +## Requires (dependencies) +Core `WebexRepository`; Webex SDK `webex.messages`/`webex.spaces`/`webex.memberships`; `SpaceEventListener` (`WebexRepository.kt`, `MessagingModule.kt`). + +## Requirements +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `MSG-R-001` | Space create/update/call events are observed and republished as `SpaceEvent` | UI reflects live space changes | `WebexRepository.kt` (`setSpaceObserver`) | None found | — | PRESENT | +| `MSG-R-002` | Membership create/update/delete/seen events are republished as `MembershipEvent` | UI reflects membership changes | `WebexRepository.kt` (`setMembershipObserver`) | None found | — | PRESENT | +| `MSG-R-003` | Message received/edited/deleted/updated events are republished as `MessageEvent` | UI reflects live messages | `WebexRepository.kt` (`setMessageObserver`) | None found | — | PRESENT | +| `MSG-R-004` | Messages for a space can be listed | Populate a space's message list | `WebexRepository.kt` (`listMessages`) | None found | Hardcoded max 10000 | PRESENT | +| `MSG-R-005` | Space call started/ended toggles `isSpaceCallStarted`/`spaceCallId` | Coordinate space-call UI | `WebexRepository.kt` (`setSpaceObserver`) | None found | — | PRESENT | + +## Design Overview +Each messaging concern (teams, spaces, memberships, composer) is a repository + ViewModel pair registered in `messagingModule`; screens obtain their ViewModel via Koin (`MessagingModule.kt`). Live updates are not polled: the shared `WebexRepository` registers SDK observers once and pushes typed events through `LiveData`, which the messaging screens observe (`WebexRepository.kt`). + +## Data Flow +```mermaid +flowchart TD + UI[Messaging screens] --> VM[Spaces/Teams/Membership/Composer VMs] + VM --> Repo[feature repositories] + Repo --> SDK[Webex messages/spaces/memberships] + SDK -. observers .-> WRepo[WebexRepository] + WRepo -. LiveData(SpaceEvent/MessageEvent/MembershipEvent) .-> VM +``` + +## Sequence Diagram(s) +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| Live message/space/membership updates | Observer republish sequence | SDK event with null payload guarded in observer | + +```mermaid +sequenceDiagram + participant SDK as Webex SDK + participant Repo as WebexRepository + participant VM as Messaging ViewModel + participant UI as Messaging screen + SDK-->>Repo: MessageReceived / SpaceUpdated / MembershipCreated + Repo->>Repo: map to MessageEvent/SpaceEvent/MembershipEvent + Repo-->>VM: postValue(LiveData) + VM-->>UI: observe → render +``` + +## Class / Component Relationships +```mermaid +classDiagram + class MessagingModule + class SpacesRepository + class TeamsRepository + class MembershipRepository + class MessageComposerRepository + class WebexRepository + MessagingModule --> SpacesRepository + MessagingModule --> TeamsRepository + MessagingModule --> MembershipRepository + MessagingModule --> MessageComposerRepository + SpacesRepository --> WebexRepository +``` + +## Use Cases +- **UC-1 List spaces & messages:** open messaging → list spaces → open a space → `listMessages`. Evidence: `WebexRepository.kt`, `MessagingModule.kt`. +- **UC-2 Live message update:** message arrives → `MessageEvent.Received` → list updates. Evidence: `WebexRepository.kt`. +- **UC-3 Compose/send:** composer view model sends a message via SDK. Evidence: `messaging/composer/`. + + +### UI Flow (per use case) +Multi-screen flow across spaces/teams lists, detail, memberships, read-status, and composer activities (`AndroidManifest.xml`). + + +### Cross-service flow (per use case) +All messaging operations go through the Webex SDK to Webex cloud; changes return through SDK observers (`WebexRepository.kt`). + + +## State Model +Transient: `spaceEventListener`, `isSpaceCallStarted`, `spaceCallId`, and the message/space/membership `LiveData` streams in `WebexRepository` (`WebexRepository.kt`); `clearSpaceData()` clears the listener. Screen lists are held in their ViewModels. + + +## Concurrency & Reactive Flow +Observer callbacks are asynchronous and posted via `postValue`; null payloads are guarded in the observers (`WebexRepository.kt`). Screens must observe on the main thread via `LiveData`. + + +## Error Handling & Failure Modes +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| SDK list/get failure | `CompletionHandler` result not successful | Surface error in UI | +| Null event payload | guarded in observer branches | Event ignored (`WebexRepository.kt`) | + +## Pitfalls +- `listMessages` uses a hardcoded max of 10000; large spaces may need paging (`WebexRepository.kt`). +- Observers are registered on the shared repository; registering duplicates elsewhere could double-handle events. + +## Test-Case Strategy (module) +Assess-only: no messaging tests found. Recommended: unit-test observer→`LiveData` mapping for each `MessageEvent`/`SpaceEvent`/`MembershipEvent` branch (positive: correct enum posted; negative: null payload ignored). + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| `MSG-R-003` | None found | No message-event mapping test | +| `MSG-R-001` | None found | No space-event mapping test | + +## Traceability +- Repo architecture: `../../../../../../../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../../../../../../../ai-docs/SPEC_INDEX.md` +- Coverage state & contracts baseline: `.sdd/manifest.json` diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/person/ai-docs/person-spec.md b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/person/ai-docs/person-spec.md new file mode 100644 index 00000000..2716f0ba --- /dev/null +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/person/ai-docs/person-spec.md @@ -0,0 +1,144 @@ +# Person — SPEC + +> Start here → root [`AGENTS.md`](../../../../../../../../../../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](../../../../../../../../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../../../../../../../../ai-docs/ARCHITECTURE.md). This is the module's canonical spec. +> Context-efficiency: link to canonical docs — don't duplicate them; load specs on demand per `SPEC_INDEX.md`. + +## Metadata +| Field | Value | +|---|---| +| Module id | `person` | +| Source path(s) | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/person/` | +| Doc kind | Module spec | +| Coverage score | Pending coverage assessment | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | claude-cli / pending / 2026-07-31T00:00:00Z | +| Validation status | not-run | + +## Evidence Rules +Every requirement cites `file path` evidence. Assess-only onboarding: module is `Untracked`; code is authoritative. Unresolved facts are `[NEEDS HUMAN INPUT]`. + +## Source Material Register +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| `person/PersonModule.kt`, `personModule` registration in `KitchenSinkApp.kt`, `webex.people` usage in `WebexRepository.kt` | overview / architecture | used | Placed in Overview, Public Surface, Use Cases | +| No prior SDD/design specs | none | none | First onboarding | + +## Overview +The person module owns retrieval and display of Webex person/user details. `personModule` registers `PersonRepository` and `PersonViewModel` (`PersonModule.kt`), loaded with the app's feature set (`KitchenSinkApp.kt`). Person lookups use the SDK `webex.people` API (e.g. `getPerson` in `WebexRepository.kt`). `PersonModel` is used to carry person data (referenced in `WebexViewModel.kt` token live-data). + +## Purpose / Responsibility +Owns person/user detail retrieval and its view model. It does NOT own authentication (see `auth/`) or messaging membership data (see `messaging/`). + +## Stack +Kotlin/Android; Koin; Webex SDK `people` API (`PersonModule.kt`, `WebexRepository.kt`). + +## Folder / Package Structure +``` +app/src/main/java/com/ciscowebex/androidsdk/kitchensink/person/ +├── PersonModule.kt # personModule DI (PersonViewModel + PersonRepository) +├── PersonRepository # person data access via SDK +├── PersonViewModel # exposes person data to UI +└── PersonModel # person data holder +``` + +## Key Files (source of truth) +| File | Holds | +|---|---| +| `PersonModule.kt` | `personModule` DI registrations | +| `WebexRepository.kt` | `getPerson(personId, handler)` via `webex.people.get` | + +## Public Surface +Internal Surface — used by the app; no externally published contract. +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `person.getPerson` | SDK (internal) | `WebexRepository.getPerson(id, handler)` | Fetch a person by id | internal | `WebexRepository.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | +| `person.PersonViewModel` | SDK (internal) | Koin `viewModel` | Expose person data to UI | internal | `PersonModule.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | + +## Requires (dependencies) +Core `WebexRepository`; Webex SDK `webex.people` (`PersonModule.kt`, `WebexRepository.kt`). + +## Requirements +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `PERSON-R-001` | A person can be fetched by id via the SDK | Show user/person details | `WebexRepository.kt` (`getPerson`) | None found | — | PRESENT | +| `PERSON-R-002` | `PersonViewModel`/`PersonRepository` are provided via Koin | Screens resolve person data through DI | `PersonModule.kt` | None found | — | PRESENT | + +## Design Overview +A thin repository/ViewModel pair over the SDK `people` API. `PersonRepository` wraps SDK calls; `PersonViewModel` exposes results to the UI. The token/current-user detail is surfaced through `WebexViewModel` (`tokenLiveData` pairs a token with a `PersonModel`) (`WebexViewModel.kt`). + +## Data Flow +```mermaid +flowchart TD + UI --> VM[PersonViewModel] + VM --> Repo[PersonRepository] + Repo --> SDK[webex.people.get] + SDK -. CompletionHandler .-> Repo +``` + +## Sequence Diagram(s) +This is a trivial pass-through/composition module with a single operation group (fetch person), so one sequence diagram is sufficient. + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| Fetch person | Fetch-person sequence | `CompletionHandler` failure surfaced to UI | + +```mermaid +sequenceDiagram + participant UI + participant VM as PersonViewModel + participant SDK as webex.people + UI->>VM: request person(id) + VM->>SDK: get(id, handler) + SDK-->>VM: Result + VM-->>UI: person or error +``` + +## Class / Component Relationships +```mermaid +classDiagram + class PersonViewModel + class PersonRepository + PersonViewModel --> PersonRepository + PersonRepository --> WebexRepository +``` + +## Use Cases +- **UC-1 View person:** UI requests a person by id → SDK `get` → details shown. Evidence: `WebexRepository.kt` (`getPerson`), `PersonModule.kt`. + + +### UI Flow (per use case) +Person details are presented by `PersonViewModel`-backed screens. `[NEEDS HUMAN INPUT]` — whether person detail is a standalone multi-screen flow or embedded in other screens is not fully determined from code. + + +### Cross-service flow (per use case) +Person data is fetched from Webex cloud via `webex.people` (`WebexRepository.kt`). + + +## State Model +Person data is held transiently in `PersonViewModel`/`PersonModel`; the current-user token pair is exposed via `WebexViewModel.tokenLiveData` (`WebexViewModel.kt`). + + +## Concurrency & Reactive Flow +Person fetch is asynchronous via `CompletionHandler`; results are delivered on the SDK callback and surfaced through `LiveData` (`WebexRepository.kt`, `WebexViewModel.kt`). + + +## Error Handling & Failure Modes +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| Person not found / fetch failure | `CompletionHandler` result not successful | Show error in UI | + +## Pitfalls +- Person lookups depend on a valid authenticated session; unauthenticated calls fail at the SDK layer. + +## Test-Case Strategy (module) +Assess-only: no person tests found. Recommended: unit-test `PersonRepository` fetch success/failure paths (positive: person returned; negative: error surfaced). + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| `PERSON-R-001` | None found | No fetch success/failure test | + +## Traceability +- Repo architecture: `../../../../../../../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../../../../../../../ai-docs/SPEC_INDEX.md` +- Coverage state & contracts baseline: `.sdd/manifest.json` diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/ai-docs/search-spec.md b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/ai-docs/search-spec.md new file mode 100644 index 00000000..d5cbdcab --- /dev/null +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/ai-docs/search-spec.md @@ -0,0 +1,144 @@ +# Search — SPEC + +> Start here → root [`AGENTS.md`](../../../../../../../../../../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](../../../../../../../../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../../../../../../../../ai-docs/ARCHITECTURE.md). This is the module's canonical spec. +> Context-efficiency: link to canonical docs — don't duplicate them; load specs on demand per `SPEC_INDEX.md`. + +## Metadata +| Field | Value | +|---|---| +| Module id | `search` | +| Source path(s) | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/` | +| Doc kind | Module spec | +| Coverage score | Pending coverage assessment | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | claude-cli / pending / 2026-07-31T00:00:00Z | +| Validation status | not-run | + +## Evidence Rules +Every requirement cites `file path` evidence. Assess-only onboarding: module is `Untracked`; code is authoritative. Unresolved facts are `[NEEDS HUMAN INPUT]`. + +## Source Material Register +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| `search/SearchModule.kt`, `searchModule`/`searchPeopleModule` registration in `KitchenSinkApp.kt`, `SearchActivity`/`MessagingSearchActivity` in `AndroidManifest.xml` | overview / architecture | used | Placed in Overview, Public Surface, Use Cases | +| No prior SDD/design specs | none | none | First onboarding | + +## Overview +The search module owns people/space search. `searchModule` registers `SearchViewModel` and `SearchRepository` (`SearchModule.kt`), loaded with the app's feature set (`KitchenSinkApp.kt`). A related `searchPeopleModule` and `MessagingSearchActivity` support people search within messaging; the standalone `SearchActivity` is declared in the manifest (`AndroidManifest.xml`, `KitchenSinkApp.kt`). + +## Purpose / Responsibility +Owns search over Webex people/spaces and its view model. It does NOT own the message/space data lifecycle (see `messaging/`). + +## Stack +Kotlin/Android; Koin; Webex SDK people/space APIs (`SearchModule.kt`). + +## Folder / Package Structure +``` +app/src/main/java/com/ciscowebex/androidsdk/kitchensink/search/ +├── SearchModule.kt # searchModule DI (SearchViewModel + SearchRepository) +├── SearchViewModel # exposes search results to UI +├── SearchRepository # search access via SDK +└── SearchActivity # search screen (declared in AndroidManifest.xml) +``` + +## Key Files (source of truth) +| File | Holds | +|---|---| +| `SearchModule.kt` | `searchModule` DI registrations | +| `AndroidManifest.xml` | `SearchActivity`, `messaging.search.MessagingSearchActivity` | + +## Public Surface +Internal Surface — used by the app; no externally published contract. +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `search.SearchViewModel` | SDK (internal) | Koin `viewModel` | Expose search results to UI | internal | `SearchModule.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | +| `search.SearchActivity` | UI | Activity | Search screen | internal | `AndroidManifest.xml` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | + +## Requires (dependencies) +Core `WebexRepository`; Webex SDK people/space search; `SearchViewModel` composes multiple dependencies (`SearchModule.kt` shows `SearchViewModel(get(), get(), get())`). + +## Requirements +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `SEARCH-R-001` | Search view model and repository are provided via Koin | Screens resolve search through DI | `SearchModule.kt` | None found | — | PRESENT | +| `SEARCH-R-002` | A dedicated search screen exists | Demonstrate search UI | `AndroidManifest.xml` (`SearchActivity`) | None found | — | PRESENT | + +## Design Overview +A repository/ViewModel pair over the SDK search surfaces. `SearchViewModel` is constructed with three collaborators via Koin (`SearchModule.kt`); the exact search operations are delegated to the SDK. People search is also embedded in messaging via `searchPeopleModule`/`MessagingSearchActivity` (`KitchenSinkApp.kt`, `AndroidManifest.xml`). + +## Data Flow +```mermaid +flowchart TD + UI[SearchActivity] --> VM[SearchViewModel] + VM --> Repo[SearchRepository] + Repo --> SDK[Webex people/space search] + SDK -. results .-> Repo +``` + +## Sequence Diagram(s) +This is a single-operation-group module (issue query → show results), so one sequence diagram is sufficient. + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| Search query | Search sequence | SDK failure surfaced to UI | + +```mermaid +sequenceDiagram + participant UI as SearchActivity + participant VM as SearchViewModel + participant SDK as Webex search + UI->>VM: query(term) + VM->>SDK: search + SDK-->>VM: results / error + VM-->>UI: render +``` + +## Class / Component Relationships +```mermaid +classDiagram + class SearchViewModel + class SearchRepository + SearchViewModel --> SearchRepository + SearchRepository --> WebexRepository +``` + +## Use Cases +- **UC-1 Search people/spaces:** user enters a term in `SearchActivity` → results shown. Evidence: `SearchModule.kt`, `AndroidManifest.xml`. + + +### UI Flow (per use case) +`SearchActivity` presents a query input and result list. `[NEEDS HUMAN INPUT]` — whether search spans multiple screens beyond the single activity is not fully determined from code. + + +### Cross-service flow (per use case) +Search queries are served by Webex cloud via the SDK (`SearchModule.kt`). + + +## State Model +Search terms and results are held transiently in `SearchViewModel`. + + +## Concurrency & Reactive Flow +Search results arrive asynchronously via SDK callbacks and are exposed through `LiveData` in the view model. + + +## Error Handling & Failure Modes +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| Search failure | `CompletionHandler` result not successful | Show error / empty state | + +## Pitfalls +- Search depends on an authenticated session; results are empty when unauthenticated. + +## Test-Case Strategy (module) +Assess-only: no search tests found. Recommended: unit-test `SearchRepository` query success/empty/failure handling. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| `SEARCH-R-001` | None found | No search-path test | + +## Traceability +- Repo architecture: `../../../../../../../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../../../../../../../ai-docs/SPEC_INDEX.md` +- Coverage state & contracts baseline: `.sdd/manifest.json` diff --git a/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/webhooks/ai-docs/webhooks-spec.md b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/webhooks/ai-docs/webhooks-spec.md new file mode 100644 index 00000000..4bc3c040 --- /dev/null +++ b/app/src/main/java/com/ciscowebex/androidsdk/kitchensink/webhooks/ai-docs/webhooks-spec.md @@ -0,0 +1,145 @@ +# Webhooks — SPEC + +> Start here → root [`AGENTS.md`](../../../../../../../../../../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](../../../../../../../../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../../../../../../../../ai-docs/ARCHITECTURE.md). This is the module's canonical spec. +> Context-efficiency: link to canonical docs — don't duplicate them; load specs on demand per `SPEC_INDEX.md`. + +## Metadata +| Field | Value | +|---|---| +| Module id | `webhooks` | +| Source path(s) | `app/src/main/java/com/ciscowebex/androidsdk/kitchensink/webhooks/` | +| Doc kind | Module spec | +| Coverage score | Pending coverage assessment | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | claude-cli / pending / 2026-07-31T00:00:00Z | +| Validation status | not-run | + +## Evidence Rules +Every requirement cites `file path` evidence. Assess-only onboarding: module is `Untracked`; code is authoritative. Unresolved facts are `[NEEDS HUMAN INPUT]`. + +## Source Material Register +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| `webhooks/WebhooksModule.kt`, `webhooksModule` registration in `KitchenSinkApp.kt`, `WebhooksActivity` in `AndroidManifest.xml`, `WEBHOOK_URL` in `app/build.gradle` | overview / architecture | used | Placed in Overview, Public Surface, Use Cases | +| No prior SDD/design specs | none | none | First onboarding | + +## Overview +The webhooks module owns the app's webhook-management demonstration. `webhooksModule` registers `WebhooksRepository` and `WebhooksViewModel` (`WebhooksModule.kt`), loaded with the app's feature set (`KitchenSinkApp.kt`). `WebhooksActivity` is the screen (`AndroidManifest.xml`). A configured `WEBHOOK_URL` is available via `BuildConfig` (`app/build.gradle`). + +## Purpose / Responsibility +Owns creating/listing/deleting Webex webhooks via the SDK and its UI. It does NOT receive webhook callbacks itself (those go to the configured external URL). + +## Stack +Kotlin/Android; Koin; Webex SDK webhooks API (`WebhooksModule.kt`). + +## Folder / Package Structure +``` +app/src/main/java/com/ciscowebex/androidsdk/kitchensink/webhooks/ +├── WebhooksModule.kt # webhooksModule DI (WebhooksViewModel + WebhooksRepository) +├── WebhooksRepository # webhook access via SDK +├── WebhooksViewModel # exposes webhook data to UI +└── WebhooksActivity # webhook management screen (declared in AndroidManifest.xml) +``` + +## Key Files (source of truth) +| File | Holds | +|---|---| +| `WebhooksModule.kt` | `webhooksModule` DI registrations | +| `AndroidManifest.xml` | `webhooks.WebhooksActivity` | +| `app/build.gradle` | `WEBHOOK_URL` `BuildConfig` field | + +## Public Surface +Internal Surface — used by the app; no externally published contract. +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `webhooks.WebhooksViewModel` | SDK (internal) | Koin `viewModel` | Expose webhook data to UI | internal | `WebhooksModule.kt` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | +| `webhooks.WebhooksActivity` | UI | Activity | Webhook management screen | internal | `AndroidManifest.xml` | `../../../../../../../../../../ai-docs/CONTRACTS.md` | + +## Requires (dependencies) +Core `WebexRepository`; Webex SDK webhooks API; `WEBHOOK_URL` from `BuildConfig` (`WebhooksModule.kt`, `app/build.gradle`). + +## Requirements +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `WEBHOOK-R-001` | Webhook view model and repository are provided via Koin | Screens resolve webhook access through DI | `WebhooksModule.kt` | None found | — | PRESENT | +| `WEBHOOK-R-002` | A webhook management screen exists | Demonstrate webhook CRUD UI | `AndroidManifest.xml` (`WebhooksActivity`) | None found | Exact CRUD ops `[NEEDS HUMAN INPUT]` | PRESENT | + +## Design Overview +A repository/ViewModel pair over the SDK webhook surface. `WebhooksRepository` wraps SDK webhook calls; `WebhooksViewModel` exposes results to `WebhooksActivity`. The demo target endpoint comes from the build-time `WEBHOOK_URL` (`app/build.gradle`). + +## Data Flow +```mermaid +flowchart TD + UI[WebhooksActivity] --> VM[WebhooksViewModel] + VM --> Repo[WebhooksRepository] + Repo --> SDK[Webex webhooks API] + SDK -. results .-> Repo +``` + +## Sequence Diagram(s) +This is a single-operation-group module (manage webhooks), so one sequence diagram is sufficient. + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| Manage webhooks | Webhook sequence | SDK failure surfaced to UI | + +```mermaid +sequenceDiagram + participant UI as WebhooksActivity + participant VM as WebhooksViewModel + participant SDK as Webex webhooks + UI->>VM: list / create / delete + VM->>SDK: webhook op + SDK-->>VM: result / error + VM-->>UI: render +``` + +## Class / Component Relationships +```mermaid +classDiagram + class WebhooksViewModel + class WebhooksRepository + WebhooksViewModel --> WebhooksRepository + WebhooksRepository --> WebexRepository +``` + +## Use Cases +- **UC-1 Manage webhooks:** user opens `WebhooksActivity` → lists/creates/deletes webhooks via SDK. Evidence: `WebhooksModule.kt`, `AndroidManifest.xml`. + + +### UI Flow (per use case) +`WebhooksActivity` presents webhook management on a single screen. + + +### Cross-service flow (per use case) +Webhook operations are served by Webex cloud via the SDK; delivered webhook events go to the external `WEBHOOK_URL`, not to the app (`app/build.gradle`). + + +## State Model +Webhook list/state is held transiently in `WebhooksViewModel`. + + +## Concurrency & Reactive Flow +Webhook operations complete asynchronously via SDK callbacks, exposed through `LiveData`. + + +## Error Handling & Failure Modes +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| Webhook op failure | `CompletionHandler` result not successful | Show error in UI | + +## Pitfalls +- An empty/invalid `WEBHOOK_URL` makes created webhooks non-functional (`app/build.gradle`). + +## Test-Case Strategy (module) +Assess-only: no webhook tests found. Recommended: unit-test `WebhooksRepository` list/create/delete success and failure paths. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| `WEBHOOK-R-001` | None found | No webhook-op test | + +## Traceability +- Repo architecture: `../../../../../../../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../../../../../../../ai-docs/SPEC_INDEX.md` +- Coverage state & contracts baseline: `.sdd/manifest.json`