What I Did
Prep and submission for Just3's first Play Store closed test release. No new features โ this session was entirely about getting the app in front of real testers.
- Injected realistic demo data into the Room DB via ADB for screenshots
- Captured screenshots in both Korean and English
- Generated the 512ร512 app icon and 1024ร500 feature graphic with Python/PIL
- Fixed the foreground alarm dialog not appearing when the app was open
- Built and signed the release AAB, submitted to Play Console closed testing
ADB Demo Data Injection
The app needs to look lived-in for screenshots โ empty state screenshots are useless on a store listing. The approach: pull the Room DB off the device, edit it with the local sqlite3 CLI, push it back.
Room uses WAL (Write-Ahead Logging) by default. If you push a modified .db file without handling the WAL files, the old -wal gets replayed on top of your edits and corrupts them. Fix: delete the WAL and SHM files before pushing, and set the journal mode to DELETE so no WAL is generated on next open.
# Pull DB
adb shell "run-as com.youngdevsbin.just3 cat databases/just3.db" > /tmp/just3.db
# Edit with local sqlite3
sqlite3 /tmp/just3.db
> PRAGMA journal_mode=DELETE;
> INSERT INTO brain_dump_items ...
> .quit
# Push back
adb shell "run-as com.youngdevsbin.just3 dd of=databases/just3.db bs=4096" < /tmp/just3.db
# Delete WAL/SHM
adb shell "run-as com.youngdevsbin.just3 rm -f databases/just3.db-wal databases/just3.db-shm"Timezone gotcha: the device was on US Pacific (PDT = UTC-7), not KST. All epoch millisecond timestamps in the DB had to be calculated in PDT, not Korean local time. Decoded the existing timestamps first with date -r to confirm the offset before inserting.
Per-App Locale Switching
Play Store requires screenshots in each supported language. Changing the system language kills the ADB session and disrupts the workflow. Android supports per-app locale overrides without touching the system locale:
# Switch Just3 to Korean
adb shell cmd locale set-app-locales com.youngdevsbin.just3 --locales ko-KR
# Switch back to English
adb shell cmd locale set-app-locales com.youngdevsbin.just3 --locales en-USThe app relaunches in the target language instantly. No system reboot, no ADB reconnect. Also had to swap the DB content to match โ Korean task names for KR screenshots, English names for EN screenshots.
Tap coordinate gotcha: UIAutomator dumps give logical coordinates, but adb shell input tap takes physical pixel coordinates scaled by display density. Found the correct coordinates by dumping the UI hierarchy and checking the bounds attribute:
adb shell uiautomator dump /sdcard/ui.xml
adb shell cat /sdcard/ui.xml | grep -o 'bounds="[^"]*"'Asset Generation with Python/PIL
Both the app icon (512ร512) and feature graphic (1024ร500) were generated programmatically with Pillow instead of a design tool. Useful for iterating quickly โ change a color or layout and regenerate in seconds.
The icon uses 4x supersampling (draw at 2048ร2048, downsample to 512ร512 with Image.LANCZOS) for clean antialiased edges without a vector renderer.
img = Image.new("RGBA", (S, S), (0, 0, 0, 0)) # S = 512 * 4
draw = ImageDraw.Draw(img)
# draw at 4x resolution...
result = img.resize((512, 512), Image.LANCZOS)
result.save("icon.png")Foreground Alarm Fix
setFullScreenIntent on a notification only triggers the full-screen activity when the screen is off or locked. When the app is in the foreground (screen on, app visible), the system downgrades to a heads-up notification โ the alarm popup never appears.
Fix: call startActivity() directly from AlarmService.onStartCommand() right after startForeground():
startForeground(NOTIFICATION_ID, notification, ...)
// setFullScreenIntent only fires on locked screen โ launch directly so the
// alarm popup always shows even when the app is already in the foreground.
startActivity(
Intent(this, AlarmAlertActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
putExtra(EXTRA_TITLE, title)
putExtra(EXTRA_ID, taskId)
}
)FLAG_ACTIVITY_SINGLE_TOP prevents stacking multiple AlarmAlertActivity instances if the alarm fires while the alert is already visible.
Release Build
The project's build.gradle.kts already had a signingConfigs block reading from keystore.properties. Building the signed AAB:
./gradlew bundleRelease
# โ app/build/outputs/bundle/release/app-release.aab (8.3 MB)isMinifyEnabled = true + isShrinkResources = true in the release build type โ R8 handles both code shrinking and resource stripping.
What I Learned
WAL mode survives a DB file replacement. Pushing a clean .db isn't enough โ the old -wal file is replayed on top the next time Room opens the database. You must delete both -wal and -shm, and set PRAGMA journal_mode=DELETE inside the pushed file so Room doesn't immediately create a new WAL.
setFullScreenIntent is not a general-purpose "show over everything" API. It's specifically for the locked/dark screen case. For an alarm app that needs to interrupt the foreground, you need both: the setFullScreenIntent for when the screen is off, and a direct startActivity() for when it's on.
Per-app locale override works without system locale change. cmd locale set-app-locales is available from API 33+ and survives across app restarts. The app's AppCompatDelegate picks it up automatically โ no code changes required.
Display density scales tap coordinates. uiautomator dump reports logical dp coordinates. adb shell input tap takes physical pixel coordinates. On a 1.21x density display, a button at logical (540, 909) needs to be tapped at physical (540, 1099). Always verify with a UIAutomator dump before scripting tap sequences.