diff --git a/AGENTS.md b/AGENTS.md
index afbac00..502a5f1 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -14,3 +14,17 @@ Add JVM tests alongside production packages in `app/src/test` using JUnit4, nami
## Commit & Pull Request Guidelines
The current snapshot lacks Git history, so adopt Conventional Commit prefixes (`feat:`, `fix:`, `chore:`) for clarity. Keep the first line under 72 characters in the imperative mood and link issues using `Refs #123` in the body. Pull requests should summarize functional changes, call out affected components (e.g., tile service, resources), include screenshots or screen recordings when UI changes occur, and list verification steps such as emulator/API levels tested.
+
+## Agent skills
+
+### Issue tracker
+
+Issues and specs live in `StoneHub/screen-timeout-tile` GitHub Issues. Read `docs/agents/issue-tracker.md` before issue or planning work.
+
+### Triage labels
+
+Use the default Matt triage roles. Read `docs/agents/triage-labels.md` before triage work.
+
+### Domain docs
+
+This is a single-context project. Read `docs/agents/domain.md` before domain-modeling or architecture work.
diff --git a/PROJECT_GOAL.md b/PROJECT_GOAL.md
index 2c2ff0a..d52126e 100644
--- a/PROJECT_GOAL.md
+++ b/PROJECT_GOAL.md
@@ -91,4 +91,4 @@ implementation sample here.
* [ ] Test on API 24-32 devices for manual tile placement.
* [ ] Test on API 33+ devices for direct tile placement.
* [x] Handle permission flow.
-* [ ] Build release APK/AAB.
+* [x] Build and verify the release APK/AAB for version 1.1.0.
diff --git a/README.md b/README.md
index cc03f44..8e9c6e7 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,9 @@ It is built for the common Android workflow where you want the screen to stay aw
## Download
-Download the signed APK from the [latest GitHub release](https://github.com/StoneHub/screen-timeout-tile/releases/latest). Android 7.0 or newer is required.
+Download the signed APK for free from the [latest GitHub release](https://github.com/StoneHub/screen-timeout-tile/releases/latest). Android 7.0 or newer is required. The free APK will also remain available from the project website when that page is published.
+
+A paid Google Play listing is planned at a one-time price between $1 and $3, with no ads or subscription. Purchases should be eligible for [Google Play Family Library](https://support.google.com/googleplay/answer/7007852). Play Store publication has not happened yet.
Because this app is distributed outside Google Play, Android may ask you to allow installs from the browser or file manager you used to download it. That permission can be turned off again immediately after installation.
@@ -132,7 +134,7 @@ app/src/androidTest/java/ Instrumentation smoke tests
## Current Status
-Version 1.1.0 is the first signed GitHub release. Play Store publishing remains a separate future distribution path.
+Version 1.1.0 is published as the first signed GitHub release. The next distribution lane is a paid Google Play listing, while the signed GitHub and website APK stays free.
## Contributing
diff --git a/app/src/androidTest/java/com/stonecode/screentimeouttile/MainActivityInstrumentedTest.kt b/app/src/androidTest/java/com/stonecode/screentimeouttile/MainActivityInstrumentedTest.kt
index a5c581a..92bf0ca 100644
--- a/app/src/androidTest/java/com/stonecode/screentimeouttile/MainActivityInstrumentedTest.kt
+++ b/app/src/androidTest/java/com/stonecode/screentimeouttile/MainActivityInstrumentedTest.kt
@@ -18,7 +18,7 @@ class MainActivityInstrumentedTest {
fun shouldUseExpectedPackageName_whenAppContextIsLoaded() {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
- assertEquals("com.stonecode.screentimeouttile", appContext.packageName)
+ assertEquals("com.stonecode.screentimeouttile.debug", appContext.packageName)
}
@Test
@@ -28,6 +28,10 @@ class MainActivityInstrumentedTest {
onView(withId(R.id.permissionRationale)).check(matches(isDisplayed()))
onView(withId(R.id.tileButton)).check(matches(isDisplayed()))
onView(withId(R.id.nextAction)).check(matches(isDisplayed()))
+ onView(withId(R.id.setupProgress)).check(matches(isDisplayed()))
+ onView(withId(R.id.permissionStepBadge)).check(matches(isDisplayed()))
+ onView(withId(R.id.tileStepBadge)).check(matches(isDisplayed()))
+ onView(withId(R.id.useStepBadge)).check(matches(isDisplayed()))
}
}
}
diff --git a/app/src/main/java/com/stonecode/screentimeouttile/MainActivity.kt b/app/src/main/java/com/stonecode/screentimeouttile/MainActivity.kt
index 0d7caab..6fed5f8 100644
--- a/app/src/main/java/com/stonecode/screentimeouttile/MainActivity.kt
+++ b/app/src/main/java/com/stonecode/screentimeouttile/MainActivity.kt
@@ -7,25 +7,33 @@ import android.graphics.drawable.Icon
import android.os.Build
import android.os.Bundle
import android.provider.Settings
+import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
+import com.google.android.material.progressindicator.LinearProgressIndicator
class MainActivity : AppCompatActivity() {
private lateinit var controller: ScreenTimeoutController
private lateinit var statusView: TextView
private lateinit var timeoutView: TextView
+ private lateinit var permissionRationaleView: TextView
private lateinit var nextActionView: TextView
private lateinit var actionButton: Button
private lateinit var tileButton: Button
private lateinit var tileRequestStatusView: TextView
+ private lateinit var permissionStepBadge: TextView
+ private lateinit var tileStepBadge: TextView
+ private lateinit var useStepBadge: TextView
+ private lateinit var setupProgress: LinearProgressIndicator
private val permissionLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
@@ -43,10 +51,15 @@ class MainActivity : AppCompatActivity() {
controller = ScreenTimeoutController(this)
statusView = findViewById(R.id.permissionStatus)
timeoutView = findViewById(R.id.timeoutValue)
+ permissionRationaleView = findViewById(R.id.permissionRationale)
nextActionView = findViewById(R.id.nextAction)
actionButton = findViewById(R.id.permissionButton)
tileButton = findViewById(R.id.tileButton)
tileRequestStatusView = findViewById(R.id.tileRequestStatus)
+ permissionStepBadge = findViewById(R.id.permissionStepBadge)
+ tileStepBadge = findViewById(R.id.tileStepBadge)
+ useStepBadge = findViewById(R.id.useStepBadge)
+ setupProgress = findViewById(R.id.setupProgress)
actionButton.setOnClickListener { openWriteSettings() }
tileButton.setOnClickListener { requestTilePlacement() }
@@ -54,8 +67,8 @@ class MainActivity : AppCompatActivity() {
private fun configureSystemBars() {
WindowCompat.getInsetsController(window, window.decorView).apply {
- isAppearanceLightStatusBars = false
- isAppearanceLightNavigationBars = false
+ isAppearanceLightStatusBars = true
+ isAppearanceLightNavigationBars = true
}
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.screenContent)) { view, insets ->
val safeDrawing = insets.getInsets(
@@ -99,24 +112,6 @@ class MainActivity : AppCompatActivity() {
canRequestTilePlacement = canRequestTilePlacement(),
)
- statusView.text = if (hasPermission) {
- getString(R.string.permission_granted_status)
- } else {
- getString(R.string.permission_needed_status)
- }
-
- actionButton.isEnabled = state.primaryAction == SetupAction.GRANT_PERMISSION
- tileButton.isEnabled = state.canRequestTilePlacement
- nextActionView.text = when (state.primaryAction) {
- SetupAction.GRANT_PERMISSION -> getString(R.string.next_action_grant_permission)
- SetupAction.ADD_TILE -> getString(R.string.next_action_add_tile)
- SetupAction.MANUAL_ADD_TILE -> getString(R.string.next_action_manual_add_tile)
- }
-
- if (!state.canRequestTilePlacement && hasPermission) {
- tileRequestStatusView.text = getString(R.string.tile_request_status_unsupported)
- }
-
timeoutView.text = when (val status = state.timeoutStatus) {
is TimeoutStatus.Available -> getString(
R.string.current_timeout_value,
@@ -124,6 +119,67 @@ class MainActivity : AppCompatActivity() {
)
TimeoutStatus.Unavailable -> getString(R.string.current_timeout_unknown)
}
+
+ renderSetupState(state)
+ }
+
+ private fun renderSetupState(state: SetupUiState) {
+ val needsPermission = state.primaryAction == SetupAction.GRANT_PERMISSION
+
+ if (needsPermission) {
+ setupProgress.setProgressCompat(PROGRESS_PERMISSION, true)
+ nextActionView.text = getString(R.string.setup_progress_step_one)
+ statusView.text = getString(R.string.setup_permission_title)
+ permissionRationaleView.visibility = View.VISIBLE
+ timeoutView.visibility = View.GONE
+ actionButton.visibility = View.VISIBLE
+ actionButton.isEnabled = true
+ tileButton.visibility = View.VISIBLE
+ tileButton.isEnabled = false
+ tileRequestStatusView.text = getString(R.string.setup_tile_locked_description)
+ renderBadge(permissionStepBadge, "1", R.drawable.setup_step_badge_current, R.color.white)
+ renderBadge(tileStepBadge, "2", R.drawable.setup_step_badge_pending, R.color.setup_text_muted)
+ renderBadge(useStepBadge, "3", R.drawable.setup_step_badge_pending, R.color.setup_text_muted)
+ return
+ }
+
+ setupProgress.setProgressCompat(PROGRESS_TILE, true)
+ nextActionView.text = getString(R.string.setup_progress_step_two)
+ statusView.text = getString(R.string.setup_permission_complete_title)
+ permissionRationaleView.visibility = View.GONE
+ timeoutView.visibility = View.VISIBLE
+ actionButton.visibility = View.GONE
+ renderBadge(permissionStepBadge, CHECK_MARK, R.drawable.setup_step_badge_done, R.color.white)
+ renderBadge(tileStepBadge, "2", R.drawable.setup_step_badge_current, R.color.white)
+ renderBadge(useStepBadge, "3", R.drawable.setup_step_badge_pending, R.color.setup_text_muted)
+
+ if (state.canRequestTilePlacement) {
+ tileButton.visibility = View.VISIBLE
+ tileButton.isEnabled = true
+ tileRequestStatusView.text = getString(R.string.setup_tile_ready_description)
+ } else {
+ tileButton.visibility = View.GONE
+ tileRequestStatusView.text = getString(R.string.setup_tile_manual_description)
+ }
+ }
+
+ private fun renderBadge(
+ badge: TextView,
+ label: String,
+ backgroundResource: Int,
+ textColorResource: Int,
+ ) {
+ badge.text = label
+ badge.setBackgroundResource(backgroundResource)
+ badge.setTextColor(ContextCompat.getColor(this, textColorResource))
+ }
+
+ private fun renderSetupComplete() {
+ setupProgress.setProgressCompat(PROGRESS_COMPLETE, true)
+ nextActionView.text = getString(R.string.setup_progress_complete)
+ tileButton.isEnabled = false
+ renderBadge(tileStepBadge, CHECK_MARK, R.drawable.setup_step_badge_done, R.color.white)
+ renderBadge(useStepBadge, CHECK_MARK, R.drawable.setup_step_badge_done, R.color.white)
}
private fun canRequestTilePlacement(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
@@ -145,6 +201,9 @@ class MainActivity : AppCompatActivity() {
mainExecutor,
) { result ->
tileRequestStatusView.text = tileRequestStatusText(result)
+ if (isTilePlacementComplete(result)) {
+ renderSetupComplete()
+ }
}
}.onFailure {
tileRequestStatusView.text = getString(R.string.tile_request_status_error)
@@ -167,4 +226,15 @@ class MainActivity : AppCompatActivity() {
} else {
getString(R.string.tile_request_status_unsupported)
}
+
+ private fun isTilePlacementComplete(result: Int): Boolean =
+ result == StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED ||
+ result == StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED
+
+ companion object {
+ private const val CHECK_MARK = "✓"
+ private const val PROGRESS_PERMISSION = 33
+ private const val PROGRESS_TILE = 67
+ private const val PROGRESS_COMPLETE = 100
+ }
}
diff --git a/app/src/main/res/drawable/setup_step_badge_current.xml b/app/src/main/res/drawable/setup_step_badge_current.xml
new file mode 100644
index 0000000..e300722
--- /dev/null
+++ b/app/src/main/res/drawable/setup_step_badge_current.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/setup_step_badge_done.xml b/app/src/main/res/drawable/setup_step_badge_done.xml
new file mode 100644
index 0000000..4b2714e
--- /dev/null
+++ b/app/src/main/res/drawable/setup_step_badge_done.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/setup_step_badge_pending.xml b/app/src/main/res/drawable/setup_step_badge_pending.xml
new file mode 100644
index 0000000..c0c8eb0
--- /dev/null
+++ b/app/src/main/res/drawable/setup_step_badge_pending.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/setup_timeout_background.xml b/app/src/main/res/drawable/setup_timeout_background.xml
new file mode 100644
index 0000000..f659c7d
--- /dev/null
+++ b/app/src/main/res/drawable/setup_timeout_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 9e90053..6a148bf 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -4,6 +4,8 @@
android:id="@+id/screenContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:background="@color/setup_background"
+ android:clipToPadding="false"
android:fillViewport="true">
+ android:paddingBottom="28dp">
+
+
+ android:layout_marginTop="8dp"
+ android:fontFamily="sans-serif-medium"
+ android:text="@string/setup_title"
+ android:textColor="@color/setup_text_primary"
+ android:textSize="32sp" />
+
+
+
+
+ android:gravity="top"
+ android:orientation="horizontal">
-
-
-
+ android:layout_marginStart="12dp"
+ android:layout_weight="1"
+ app:cardBackgroundColor="@color/setup_surface"
+ app:cardCornerRadius="18dp"
+ app:cardElevation="0dp"
+ app:strokeColor="@color/setup_outline_current"
+ app:strokeWidth="1dp">
-
+
-
+
-
+
-
+
+
+
+
+
+ android:layout_marginTop="14dp"
+ android:gravity="top"
+ android:orientation="horizontal">
-
-
+ android:id="@+id/tileStepBadge"
+ android:layout_width="34dp"
+ android:layout_height="34dp"
+ android:background="@drawable/setup_step_badge_pending"
+ android:fontFamily="sans-serif-medium"
+ android:gravity="center"
+ android:text="2"
+ android:textColor="@color/setup_text_muted"
+ android:textSize="15sp" />
-
-
-
+ android:layout_marginStart="12dp"
+ android:layout_weight="1"
+ app:cardBackgroundColor="@color/setup_surface"
+ app:cardCornerRadius="18dp"
+ app:cardElevation="0dp"
+ app:strokeColor="@color/setup_outline"
+ app:strokeWidth="1dp">
+ android:orientation="vertical"
+ android:padding="18dp">
+ android:fontFamily="sans-serif-medium"
+ android:text="@string/setup_tile_title"
+ android:textColor="@color/setup_text_primary"
+ android:textSize="18sp" />
-
-
-
-
-
-
-
-
+ android:layout_marginTop="8dp"
+ android:accessibilityLiveRegion="polite"
+ android:lineSpacingExtra="1dp"
+ android:text="@string/setup_tile_locked_description"
+ android:textColor="@color/setup_text_secondary"
+ android:textSize="14sp" />
-
-
-
+ app:icon="@drawable/ic_qs_timeout_long"
+ app:iconGravity="textStart"
+ app:iconPadding="8dp" />
-
+
+
-
+
-
+
+
+
+ android:orientation="vertical"
+ android:padding="18dp">
+ android:fontFamily="sans-serif-medium"
+ android:text="@string/setup_use_title"
+ android:textColor="@color/setup_text_primary"
+ android:textSize="18sp" />
+ android:layout_marginTop="8dp"
+ android:lineSpacingExtra="1dp"
+ android:text="@string/instructions_text"
+ android:textColor="@color/setup_text_secondary"
+ android:textSize="14sp" />
-
+
+ android:layout_marginTop="22dp"
+ android:gravity="center"
+ android:lineSpacingExtra="1dp"
+ android:text="@string/setup_privacy_note"
+ android:textColor="@color/setup_text_muted"
+ android:textSize="12sp" />
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 279f113..9b27f8c 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -3,6 +3,16 @@
#FF2563EB
#FF1D4ED8
#FF14B8A6
+ #FFF4F2EC
+ #FFFFFFFF
+ #FF172033
+ #FF5B6473
+ #FF8A919D
+ #FFE2DED5
+ #FFBBD2FF
+ #FFDCE3EE
+ #FF16815A
+ #FFF1F5FA
#FF101418
#FF182026
#FFF7FAFC
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e3866a1..8dcc4c3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,6 +1,20 @@
Screen Timeout Tile
Switch between 30 seconds and 10 minutes from Quick Settings.
+ Two-minute setup
+ Get your tile ready
+ Complete these steps once. After that, every timeout change is one tap away.
+ Step 1 of 3
+ Step 2 of 3
+ Setup complete
+ Allow timeout access
+ Timeout access allowed
+ Add the Quick Settings tile
+ This unlocks after timeout access is allowed.
+ Android will ask where to place Screen Timeout in Quick Settings.
+ Open Quick Settings edit mode and drag Screen Timeout into an active slot.
+ Tap whenever you need it
+ No account, analytics, internet access, or background service.
Screen timeout
Grant write settings
Timeout unavailable
@@ -16,8 +30,8 @@
- %1$d minutes timeout
Open system settings
- Android’s Modify system settings access is required only to read and change the screen timeout. This app does not change any other setting.
- Could not open Modify system settings. Open Android Settings, search for “Modify system settings,” and allow Screen Timeout Tile.
+ Android requires explicit permission to read and change the screen timeout. No other setting is changed.
+ Could not open Modify system settings. Open Android Settings, search for "Modify system settings," and allow Screen Timeout Tile.
Add tile
Permission required
Ready to toggle screen timeout
@@ -33,12 +47,12 @@
Tile placement request failed. Use the manual Quick Settings edit flow.
Direct tile placement is available on Android 13 and newer.
What the tile means
- The tile is never “off.” It always shows one of two screen timeout presets.
+ The tile is never "off." It always shows one of two screen timeout presets.
Hourglass · 10 minutes
Highlighted tile. Keeps the screen on longer.
Clock with bolt · 30 seconds
Dim tile. Returns to the short timeout.
Lock · Permission needed
Tap the tile, then allow Modify system settings.
- After setup, each tap switches between the 30-second and 10-minute timeout.
+ Each tap switches between 30 seconds and 10 minutes. The tile is never off.
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index d07533f..899ea5b 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,15 +1,15 @@
-
@@ -18,6 +18,6 @@
- 0dp
- 0dp
- false
- - 10dp
+ - 12dp
diff --git a/docs/agents/domain.md b/docs/agents/domain.md
new file mode 100644
index 0000000..5bb0b81
--- /dev/null
+++ b/docs/agents/domain.md
@@ -0,0 +1,9 @@
+# Domain docs
+
+This repository uses a single domain context.
+
+Before domain-modeling or architecture work, read `CONTEXT.md` and relevant decisions under `docs/adr/` when they exist. Their absence is not an error. Create them only when the user settles a domain term or a durable architectural decision.
+
+`CONTEXT.md` is a glossary. Keep implementation details and feature requirements elsewhere. Use its canonical terms in issues, plans, code proposals, and tests.
+
+ADRs record decisions that are hard to reverse, surprising without context, and based on a real tradeoff. Call out any proposal that contradicts an existing ADR.
diff --git a/docs/agents/issue-tracker.md b/docs/agents/issue-tracker.md
new file mode 100644
index 0000000..97eda8b
--- /dev/null
+++ b/docs/agents/issue-tracker.md
@@ -0,0 +1,19 @@
+# Issue tracker
+
+Issues and specs for this repository live in GitHub Issues under `StoneHub/screen-timeout-tile`. Use the authenticated `gh` CLI from this checkout.
+
+## Operations
+
+- Create: `gh issue create --repo StoneHub/screen-timeout-tile`
+- Read: `gh issue view --repo StoneHub/screen-timeout-tile --comments`
+- List: `gh issue list --repo StoneHub/screen-timeout-tile`
+- Comment: `gh issue comment --repo StoneHub/screen-timeout-tile`
+- Label or close: use `gh issue edit` or `gh issue close` with the same explicit repository.
+
+## Pull requests
+
+Pull requests are not a triage request source. Inspect a PR when an issue, commit, or user request points to it.
+
+## Skill routing
+
+When a skill says to publish a ticket, create a GitHub issue. When it says to fetch a ticket, read the issue and its comments. Use GitHub sub-issues and native issue dependencies when a multi-ticket plan needs parent or blocking relationships.
diff --git a/docs/agents/triage-labels.md b/docs/agents/triage-labels.md
new file mode 100644
index 0000000..35f8058
--- /dev/null
+++ b/docs/agents/triage-labels.md
@@ -0,0 +1,11 @@
+# Triage labels
+
+| Matt role | GitHub label | Meaning |
+| --- | --- | --- |
+| `needs-triage` | `needs-triage` | Maintainer review is required |
+| `needs-info` | `needs-info` | Reporter information is required |
+| `ready-for-agent` | `ready-for-agent` | Fully specified for an agent |
+| `ready-for-human` | `ready-for-human` | Human action or judgment is required |
+| `wontfix` | `wontfix` | The project will not pursue the issue |
+
+Use the GitHub label in the middle column whenever a skill names a Matt role.
diff --git a/docs/modernization-summary.md b/docs/modernization-summary.md
index 1a8013d..cefe295 100644
--- a/docs/modernization-summary.md
+++ b/docs/modernization-summary.md
@@ -3,7 +3,7 @@
## What Changed
- Refactored timeout behavior behind `ScreenTimeoutSettingsStore` and pure policy/state models so core behavior is covered by JVM tests.
-- Reworked the launcher setup screen into a compact dark UI that fits the tested phone at `font_scale=1.3`.
+- Reworked the launcher setup screen into a compact UI that fits the tested phone at `font_scale=1.3`.
- Added Android 13+ direct Quick Settings tile placement from the setup screen.
- Added custom Quick Settings tile icons for short timeout, long timeout, and missing-permission states.
- Added target-SDK-36 edge-to-edge inset handling and accessible live status announcements.
@@ -12,7 +12,7 @@
- Replaced placeholder unit/instrumentation tests with focused behavior and launch smoke tests.
- Updated README and project goal notes to match the current setup flow.
-## Device Verification
+## Historical Version 1.0 Device Verification
- Device: `SM_F956U1`
- ADB serial: `RFCX61FNYPY`
@@ -35,3 +35,11 @@ git diff --check
- Permission rationale, tile state accessibility, local release-signing plumbing, CI, privacy policy, and wrapper checksum verification were added.
- Logged JVM tests, Android-test compilation, release lint, debug assembly, and unsigned release bundling passed.
- The exact debug APK was installed and cold-launched on Gina’s `SM-F766U1`; visual QA, permission approval, and tile placement remain blocked by the phone lock/human-consent gate.
+
+## August 24, 2026 Guided Setup
+
+- Replaced the combined status panel with the selected three-step guided setup screen.
+- Kept the existing `SetupUiState` and `ScreenTimeoutController` interfaces. The change is presentation-only.
+- Preserved Android 13+ direct tile placement and Android 7-12 manual tile instructions.
+- Fixed the instrumentation package assertion for the isolated `.debug` application ID.
+- Verified production Kotlin and Android-test compilation with the documented local Android SDK.
diff --git a/docs/release-signing.md b/docs/release-signing.md
index 803ea4a..bd1a2b7 100644
--- a/docs/release-signing.md
+++ b/docs/release-signing.md
@@ -1,6 +1,14 @@
# Release Signing
-The GitHub APK signing identity is intentionally stored outside the repository.
+Release signing identities are intentionally stored outside the repository.
+
+## Distribution identities
+
+- GitHub APK updates use the existing Screen Timeout Tile release keystore documented below.
+- Google Play will use [Play App Signing](https://developer.android.com/studio/publish/app-signing) for the store signing identity and a separate upload key for Monroe's submissions.
+- The Play upload key has not been created or connected to this project yet.
+
+Do not treat a signed GitHub APK, a signed Play upload bundle, and a Play-distributed artifact as the same proof gate.
## Local source of truth
@@ -28,4 +36,6 @@ Verify the resulting APK with Android SDK `apksigner` before publishing it.
## Recovery requirement
-Back up both the `.jks` keystore and its Keychain password in a secure location. GitHub-distributed updates must keep using this signing key. Never commit the keystore, passwords, or `release-signing.properties`.
+Before publishing version 1.1.1 or any later GitHub update, verify a second encrypted backup of both the `.jks` keystore and its password in a separate physical location. This is a hard release blocker. GitHub-distributed updates must keep using this signing key.
+
+Keep the Play upload key and its recovery record under the same two-backup rule once created. Never commit a keystore, password, or `release-signing.properties`.
diff --git a/docs/release-todo.md b/docs/release-todo.md
index b895232..c857aa3 100644
--- a/docs/release-todo.md
+++ b/docs/release-todo.md
@@ -1,9 +1,16 @@
# Release TODO
+## Product Decisions
+
+- [x] Keep the signed APK free on GitHub and the planned project website.
+- [x] Sell the Google Play app as a one-time purchase between $1 and $3.
+- [x] Keep the app free of ads, subscriptions, analytics, accounts, and network access.
+- [x] Keep the repository without an open-source license for now.
+- [ ] Confirm the published Play listing reports eligibility for Google Play Family Library.
+
## Monroe To Do
- Pick the public app name: keep `Screen Timeout Tile` or choose a shorter store name.
-- Decide whether the Play listing should be free-only, donation-supported, or paid.
- Create/confirm the Google Play Console app entry.
- Create a simple privacy policy page:
- no ads
@@ -20,7 +27,11 @@
- Get a screenshot of the low-contrast active Quick Settings tile state if it still happens after the bold icon update.
- Write the store short description and full description.
- Decide whether to publish under `StoneHub`/`StoneCode` branding.
-- Create an upload key and enable Play App Signing.
+- Create a separate Play upload key and enable Play App Signing.
+
+## Release Blockers
+
+- [ ] Before version 1.1.1 or any later GitHub release, verify a second encrypted backup of the GitHub APK signing keystore and password in a separate physical location.
## Code / Release Engineering To Do
@@ -31,9 +42,9 @@
- [x] Bump to `versionCode=2` and `versionName=1.1.0`.
- [x] Add release notes for 1.1.0.
- [x] Create the permanent GitHub APK signing key and store its password in macOS Keychain.
-- [ ] Back up the GitHub APK signing keystore and Keychain password in a second secure location.
+- [ ] Record and verify the backup location without writing the secret into this repository.
- [x] Build and verify the production-signed APK and AAB for version 1.1.0.
-- [ ] Publish GitHub Release `v1.1.0` with the signed APK and checksums.
+- [x] Published GitHub Release `v1.1.0` with the signed APK and checksums on August 18, 2026.
- Run manual QA on at least:
- Samsung Android 16 phone
- Android 13+ device for direct tile placement