Project 7: Command Surface Deck

Expose one automation command through a Quick Settings tile, shortcut, widget, and notification without duplicating business logic or lying about stale state.

Quick Reference

  • Main language: Kotlin
  • Alternative language: Java
  • Difficulty: Level 2 - Intermediate
  • Estimated time: 16-22 hours
  • Primary APIs: TileService, App Shortcuts, App Widgets, PendingIntent
  • Supporting tools: Jetpack Compose, Glance or RemoteViews, notifications
  • Main book: Design It! by Michael Keeling
  • Safety boundary: every surface creates the same bounded command envelope

1. Learning Objectives

You will learn to:

  1. Treat Android entry surfaces as disposable adapters rather than miniature automation engines.
  2. Define a versioned CommandEnvelope shared by every surface.
  3. Use stable routine identifiers instead of serializing mutable routine content.
  4. Re-resolve current routine state when a command arrives.
  5. Represent enabled, disabled, deleted, locked, busy, and unavailable outcomes truthfully.
  6. Design PendingIntent identity and mutability deliberately.
  7. Make a tile useful even when its service instance is recreated.
  8. Keep shortcuts and widgets correct after process death and app upgrades.
  9. Avoid launching background UI when Android or user policy does not allow it.
  10. Define repeat-click concurrency before implementation.
  11. Correlate commands from multiple surfaces in one redacted trace.
  12. Test stale surfaces as normal product states.

Mastery signal: Four Android surfaces invoke one routine through one command bus, and every stale or blocked state produces a visible, recoverable outcome.


2. Theoretical Foundation

2.1 Ports, Adapters, and One Command Bus

A tile, shortcut, widget, and notification action differ in lifecycle and presentation. They should not differ in the meaning of “run routine.” Each is an input adapter that validates surface-specific input and emits the same domain command.

tile ---------+
shortcut -----+--> CommandEnvelope --> command bus --> current routine --> plan
widget -------+
notification -+

If each surface contains its own execution logic, behavior drifts. One surface may ignore lock state, another may run a deleted routine, and another may use an obsolete action definition.

2.2 Stable Identity Versus Mutable State

External surfaces can outlive the process and the state snapshot that created them. Therefore, store only a stable routine ID, an envelope version, the source surface, and correlation metadata. Resolve the current routine from durable storage at invocation time.

Core invariant:

A surface may identify intent, but it may not assert that the target is still executable.

2.3 PendingIntent Identity

Android may reuse equivalent PendingIntent instances. Identity depends on operation type, target, action, data, categories, and request code; extras alone do not reliably distinguish identities. A good design uses explicit action names, stable data URIs, scoped components, intentional flags, and the narrowest mutability.

Use immutable tokens unless a documented platform integration must fill in data. Explicitly target your own receiver or activity. Never export a command receiver without a justified caller-authentication design.

2.4 Disposable Lifecycle

TileService instances are not permanent. Widgets are rendered from persisted configuration. Shortcuts may be cached by the launcher. Notifications may disappear or be recreated. None should be the source of truth.

The command bus and durable routine repository form the truth. Surfaces are projections that can be refreshed or rebuilt.

2.5 Lock State and Background Launch Restrictions

A command may arrive while the device is locked, the app is backgrounded, or an action requires confirmation. Do not assume you can start an activity immediately. Return a typed user_resolution_required outcome and expose a user-initiated path through a supported surface.

Never use a background-activity launch as a hidden dependency. The command should either complete without UI or explain why direct user action is required.

2.6 Repeat Semantics

Two clicks can arrive nearly together from the same or different surfaces. Decide whether a routine is:

  • Drop while running: duplicate requests are rejected.
  • Coalesce: duplicates attach to the active run.
  • Queue once: one follow-up run is retained.
  • Parallel safe: separate executions are allowed.
  • Toggle: a second command requests stop rather than another start.

This is domain policy, not a surface-specific accident.

2.7 State Projection

Each surface shows a lossy projection of current state:

  • A tile may show active, inactive, or unavailable.
  • A shortcut title may be stale until refreshed.
  • A widget can show last result and current eligibility.
  • A notification can expose Run and Stop actions.

Projections should include “last updated” semantics internally and avoid implying real-time certainty when none exists.

2.8 Why This Matters

Automation tools win adoption through convenient entry points. Convenience becomes technical debt when each entry point is its own product. A shared command contract lets you add surfaces without multiplying execution paths.

2.9 Common Misconceptions

Misconception: The Quick Settings tile service can hold current routine state.

Correction: its process and instance are disposable; resolve durable state on demand.

Misconception: Extras make two PendingIntent objects unique.

Correction: identity requires deliberate action, data, target, and request-code design.

Misconception: A shortcut always represents a valid current target.

Correction: launchers can retain pinned shortcuts after the routine changes.

Misconception: A blocked command should silently do nothing.

Correction: visible typed feedback is part of a trustworthy command surface.

Misconception: Every click should start another run.

Correction: concurrency must follow routine semantics and safety policy.

2.10 Key Terms

  • Command envelope: versioned message describing requested intent and provenance.
  • Surface adapter: Android-specific component translating interaction into a command.
  • Stable ID: durable identity independent of mutable presentation.
  • Projection: derived UI state that may lag source truth.
  • Correlation ID: identifier linking one command to its run and result.
  • Resolution: user action required to continue a blocked command.
  • Idempotency key: identifier used to suppress unintended duplicate effects.
  • Explicit intent: intent naming a specific component or package.

3. Project Specification

3.1 What You Will Build

Create a “Focus Routine” and expose it through:

  • one Quick Settings tile;
  • one dynamic or pinned shortcut;
  • one home-screen widget;
  • one ongoing notification with Run and Stop actions.

Every interaction emits the same envelope and appears in the same trace viewer.

3.2 Functional Requirements

  • Use a stable routine ID on all surfaces.
  • Include envelope schema version and source surface.
  • Resolve current routine state when handling the command.
  • Support Run and Stop command kinds.
  • Show tile active, inactive, and unavailable states.
  • Show the widget’s last result and last refresh time.
  • Disable or update dynamic shortcuts when targets become unavailable.
  • Preserve an explanatory landing page for stale pinned shortcuts.
  • Report locked, disabled, deleted, busy, and unavailable outcomes.
  • Use the same concurrency policy across all surfaces.
  • Correlate command, plan, run, and result.
  • Survive process death without incorrect success state.

3.3 Non-Functional Requirements

  • Surface adapters contain no rule-evaluation logic.
  • Receivers and services are scoped to the narrowest exposure.
  • All PendingIntent mutability choices are documented.
  • Command validation occurs before execution.
  • No long-lived service assumption is permitted.
  • State refresh remains bounded and battery-conscious.
  • The result is accessible by label, state description, and contrast.

3.4 Example Interaction

Quick Settings: Focus Routine [inactive]
User taps tile

CommandEnvelope
  version: 1
  command: run
  routine_id: routine.focus
  source: quick_settings
  correlation_id: 8f2...

Result
  status: completed
  tile: active
  widget: Last run succeeded 10:42
  notification: Stop

Deleted-target case:

Pinned shortcut opened
Result: target_deleted
Resolution: Choose another routine

3.5 Real-World Outcome

A reviewer invokes Focus Routine from all four surfaces and sees identical command semantics. Killing the process does not break correctness. Locking the device or deleting the routine results in a clear explanation. Repeated taps follow the documented concurrency rule rather than creating accidental parallel runs.


4. Solution Architecture

4.1 High-Level Architecture

 +----------+  +----------+  +--------+  +--------------+
 | Tile     |  | Shortcut |  | Widget |  | Notification |
 +----+-----+  +----+-----+  +---+----+  +------+-------+
      |             |            |              |
      +-------------+------------+--------------+
                           |
                    envelope factory
                           |
                    command validator
                           |
                       command bus
                           |
         +-----------------+------------------+
         |                                    |
 current routine repository              run registry
         |                                    |
         +-----------------+------------------+
                           |
                     execution planner
                           |
                       typed result
                           |
                 projection refresh coordinator

4.2 Component Responsibilities

Component Responsibility Must Not Do
Envelope factory create versioned bounded command embed mutable routine definition
Surface adapter translate user interaction execute actions directly
Validator verify version, kind, target, caller infer missing authority
Command bus serialize and correlate dispatch depend on one surface lifecycle
Repository return current routine and eligibility trust cached launcher state
Run registry enforce repeat semantics silently fork duplicates
Result projector refresh visible surfaces claim freshness it cannot prove
Resolution activity explain blocked command auto-run on open without consent

4.3 Command Schema

CommandEnvelope {
  schemaVersion
  commandId
  commandKind = Run | Stop
  routineId
  sourceSurface = Tile | Shortcut | Widget | Notification
  issuedAtElapsed
  idempotencyKey
}

CommandOutcome = Completed | AlreadyRunning | Disabled | Deleted |
                 Locked | Unavailable | UserResolutionRequired | Failed

The schema is illustrative and not runnable code.

4.4 Dispatch Algorithm

receive explicit surface intent
  parse bounded envelope
  reject unsupported schema version
  resolve current routine by stable ID
  if missing: return Deleted
  evaluate current capability and lock requirements
  if user action required: return UserResolutionRequired
  apply repeat policy using run registry
  build current execution plan
  execute or attach to existing run
  persist typed result
  request bounded surface refresh

4.5 Truth Table

Target state Run behavior Surface message
enabled and idle start Running
enabled and active apply repeat policy Already running or queued
disabled reject Enable in app
deleted reject Choose another routine
capability absent reject Capability unavailable
locked, confirmation needed pause Unlock to continue
process restarted re-resolve Current durable state

4.6 Critical Invariants

  1. All surfaces emit the same envelope type.
  2. Mutable routine definitions never live in surface tokens.
  3. Current eligibility is resolved at command time.
  4. Every command produces one correlated outcome.
  5. PendingIntent identity is intentional and tested.
  6. Blocked commands do not silently launch background UI.
  7. Projections never become source truth.

5. Implementation Guide

5.1 Environment Setup

  • Use current stable Android Studio and SDK Platform 36.
  • Test on Android versions that cover tiles, dynamic shortcuts, and pinned shortcuts.
  • Add a launcher that supports widgets and shortcut pinning.
  • Enable notification permission where required by the tested Android version.
  • Keep a second emulator snapshot for stale pinned-shortcut scenarios.

5.2 Suggested Structure

command-deck/
  domain/commands/
  domain/routines/
  dispatch/
  surfaces/tile/
  surfaces/shortcuts/
  surfaces/widget/
  surfaces/notification/
  projections/
  resolution/
  tests/

5.3 Core Question

How can many Android entry surfaces invoke one current routine without each becoming a separate, stale automation implementation?

5.4 Concepts to Understand First

  1. Android component and process lifecycle.
  2. Explicit intents and PendingIntent identity.
  3. Tile state and launcher-owned shortcut state.
  4. App-widget update constraints.
  5. Command buses and stable identifiers.
  6. Idempotency and concurrency policy.
  7. Background activity launch restrictions.

5.5 Design Questions

  • Which fields are required in every envelope?
  • How will old envelope versions fail safely?
  • What makes two command tokens distinct?
  • What happens when two surfaces invoke within 100 milliseconds?
  • How does a pinned shortcut explain a deleted target?
  • Which surface states are authoritative versus advisory?
  • How will a locked-device resolution remain user initiated?

5.6 Thinking Exercise

Trace these cases with process, durable state, token, and visible projection:

  • tile click after process eviction;
  • pinned shortcut after routine deletion;
  • widget Run followed immediately by notification Run;
  • Stop while no run is active;
  • app upgrade with an old envelope version;
  • device locked when routine needs confirmation.

5.7 Interview Questions

  1. Why should extras not be the only PendingIntent differentiator?
  2. What state can safely live in a Quick Settings tile?
  3. How do pinned and dynamic shortcuts differ operationally?
  4. How would you prevent duplicate routine effects?
  5. Why must command handling re-read the routine?
  6. What is a safe response when background UI cannot launch?

5.8 Hints in Layers

Hint 1 - Build one in-app command first

Prove the command bus before adding system surfaces.

Hint 2 - Add stable identity

Pass only routine ID, command kind, version, and provenance.

Hint 3 - Make stale states deliberate

Delete and disable the target while surfaces still exist.

Hint 4 - Add one adapter at a time

Tile, shortcut, widget, then notification is a useful order.

Hint 5 - Kill the process repeatedly

Correctness must derive from durable truth, not instance fields.

5.9 Books That Will Help

Topic Book Suggested focus
Architecture Design It! ports, adapters, tradeoffs
Messaging Enterprise Integration Patterns command messages and correlation
Reliability Release It! duplicate work and failure handling
UX Designing with the Mind in Mind clear state and feedback

5.10 Implementation Phases

Phase 1 - Domain command

  • Define the versioned envelope.
  • Define typed results.
  • Create stable routine IDs.
  • Write validator tests.

Phase 2 - Command bus

  • Resolve current routine state.
  • Enforce capability checks.
  • Add correlation and redacted traces.
  • Define repeat policy.

Phase 3 - Quick Settings tile

  • Register the tile service.
  • Map clicks to envelopes.
  • Refresh from repository state.
  • Test service recreation.

Phase 4 - Shortcuts

  • Publish a dynamic shortcut.
  • Request a pinned shortcut through user action.
  • Handle disabled and deleted targets.
  • Test an old launcher projection.

Phase 5 - Widget

  • Configure a routine target.
  • Add Run interaction.
  • Display last result and freshness.
  • Recover after process death.

Phase 6 - Notification surface

  • Create Run and Stop actions.
  • Give each token intentional identity.
  • Update projection after results.
  • Handle notification removal.

Phase 7 - Resolution and hardening

  • Add locked-device handling.
  • Add user-resolution screens.
  • Test duplicate cross-surface clicks.
  • Document all exported components.

5.11 Key Decisions

  • One bus, not four execution paths.
  • Stable IDs, not serialized routines.
  • Immutable PendingIntent by default.
  • Explicit components for internal commands.
  • Typed stale-state results.
  • Refresh projections after source-truth changes.

6. Testing Strategy

6.1 Unit Tests

  • Every adapter produces an equivalent semantic envelope.
  • Unsupported versions are rejected.
  • Missing, disabled, and unavailable targets map correctly.
  • Repeat policy handles duplicates deterministically.
  • Stop while idle returns a typed no-op outcome.
  • Correlation persists from command to result.

6.2 Integration Tests

  • Invoke Focus Routine from each surface.
  • Kill the process before each invocation.
  • Delete the routine while a pinned shortcut remains.
  • Disable the routine while the tile appears active.
  • Tap two surfaces concurrently.
  • Lock the device before a confirmation-required command.
  • Upgrade from a previous envelope schema fixture.

6.3 Token Tests

  • Verify Run and Stop resolve to distinct tokens.
  • Verify two routines cannot collide.
  • Verify token target is explicit.
  • Verify mutability matches documented need.
  • Verify stale extras cannot override current repository state.

6.4 Accessibility Tests

  • Tile label and state description are meaningful.
  • Widget controls have descriptive labels.
  • Notification actions are unambiguous.
  • Resolution screen explains cause and next action.
  • State is not communicated by color alone.

6.5 Failure Matrix

Failure Expected behavior
process killed command re-resolves current state
target deleted Deleted plus selection resolution
target disabled Disabled plus enable resolution
device locked no hidden launch; user resolution
duplicate command documented repeat policy
old envelope typed unsupported-version result
notification absent other surfaces remain functional

6.6 Acceptance Evidence

Record one trace per surface with the same routine ID and command semantics. Include process-death, deletion, lock, repeat-click, Run, and Stop cases. Capture surface screenshots only with fictional data.


7. Common Pitfalls & Debugging

Problem 1: Tile state resets unexpectedly

  • Why: instance memory was treated as source truth.
  • Fix: re-read repository and active-run state during lifecycle callbacks.
  • Quick test: evict process, reopen tile panel, compare with durable truth.

Problem 2: Wrong routine runs from widget

  • Why: PendingIntent identities collided.
  • Fix: use explicit target, unique stable data, and tested request identity.
  • Quick test: configure two widgets and inspect resolved targets.

Problem 3: Pinned shortcut opens dead screen

  • Why: deletion was not modeled.
  • Fix: route through a resolution activity that revalidates the target.
  • Quick test: pin, delete, then invoke.

Problem 4: Double tap runs twice

  • Why: no central run registry or idempotency decision.
  • Fix: enforce repeat policy in the command bus.
  • Quick test: issue two correlated commands concurrently.

Problem 5: Command disappears on locked device

  • Why: background UI launch was assumed.
  • Fix: return resolution required and notify through a supported user surface.
  • Quick test: lock before invoking a confirmation-required routine.

Problem 6: Four surfaces show four answers

  • Why: each adapter owns independent status.
  • Fix: project all status from one result repository.
  • Quick test: compare timestamps and run IDs after one command.

8. Extensions & Challenges

Extension 1: Surface Configuration

Let a user assign different routines to several widget instances.

Extension 2: Parameter Presets

Reference a durable preset ID without embedding secret or mutable values in tokens.

Extension 3: App Actions

Explore voice invocation using supported App Actions and built-in intents, preserving the same command contract.

Extension 4: Multi-Device Projection

Design, but do not assume, how a wearable companion would request the same command.

Extension 5: Freshness Diagnostics

Show when each projection was last reconciled and why it may be stale.

Forbidden Extensions

  • Hidden background activity launches.
  • Mutable tokens without documented need.
  • Exported unauthenticated command receivers.
  • Passing passwords, tokens, or full routines through extras.
  • Claiming real-time state when only cached state is known.

9. Real-World Connections

The adapter-and-command-bus pattern appears in home automation, media controls, enterprise launchers, accessibility utilities, and incident-response tools. Production products must handle launcher diversity, OEM tile behavior, localization, app upgrades, analytics privacy, and supportable stale-state recovery.

The same design generalizes beyond Android: menu-bar commands, CLI invocations, webhooks, and voice assistants should all create bounded intent and converge on one current execution model.

Production Gap Analysis

Learning build Production requirement
one routine migration-safe catalogs and account sync
one launcher OEM and launcher compatibility matrix
local traces retention, export, support consent
basic labels full localization and accessibility QA
one envelope version negotiated migration and telemetry

10. Resources

Official Android Documentation

Policy

Suggested Reading Order

  1. Read tile lifecycle guidance.
  2. Compare static, dynamic, and pinned shortcuts.
  3. Study PendingIntent identity and mutability.
  4. Review background-launch restrictions.
  5. Revisit the parent guide’s command-surface chapter.

11. Self-Assessment Checklist

Knowledge

  • I can explain why all four surfaces are adapters.
  • I understand PendingIntent identity.
  • I can distinguish pinned and dynamic shortcut lifecycle.
  • I can explain background launch restrictions.
  • I can define repeat semantics for a routine.

Design

  • Every surface emits the same envelope schema.
  • Tokens contain stable IDs, not mutable routines.
  • Current eligibility is resolved at invocation.
  • My command receiver exposure is justified.
  • Projection freshness is explicit.

Verification

  • I invoked the routine from all four surfaces.
  • I tested after process death.
  • I tested disabled and deleted targets.
  • I tested concurrent cross-surface taps.
  • I tested locked-device resolution.

Reflection

  • I can defend my token uniqueness design.
  • I can explain which surface state is advisory.
  • I documented one rejected architecture alternative.
  • I can describe the migration path for version 2 envelopes.

12. Submission / Completion Criteria

Submit:

  1. Shared command-envelope specification.
  2. Diagram showing four adapters and one command bus.
  3. PendingIntent identity and mutability decision record.
  4. Routine-state and outcome truth table.
  5. Repeat-click concurrency policy.
  6. Component export and caller-scope audit.
  7. Test report covering all surfaces and process death.
  8. Redacted traces with correlation IDs.
  9. Accessibility review for labels and state.
  10. Background-launch and user-resolution explanation.

Definition of Done

  • Tile, shortcut, widget, and notification use one versioned envelope.
  • Every surface resolves the current routine by stable ID.
  • Disabled, deleted, locked, busy, and unavailable are visible.
  • Run and Stop tokens cannot collide.
  • Repeat behavior is deterministic across surfaces.
  • Process death does not create false success state.
  • No surface contains execution business logic.
  • No long-lived service is assumed.
  • All exported components have a documented need and protection.
  • Another developer can reproduce every acceptance case.

Expanded from ANDROID_AUTOMATION_APPS_MASTERY.md. For the complete learning path, see the parent guide.