
A market:// link opens a store chooser on SKT-sold Samsung phones: set the Play package first
On two Samsung phones sold by SKT in Korea (Galaxy S21 and S20), three store apps answer a market:// link. Pointing the intent at the Play Store app first skips the chooser and opens the Play listing.
On a Galaxy S21, tapping a store link in our app's drawer brought up a sheet asking whether to open ONE store (a Korean app store), Galaxy Store (Samsung's store) or Play, instead of the Play listing. It was October 9, 2026, and we were tapping through commit efc0acf, which added a menu of our puzzle games to the drawer of SugarSpeller, on the S21. All 300 unit tests had already passed. Fifty-nine minutes later, commit 8bbe179 changed the store link to target Play first.
Sources: the SugarSpeller repository's git history (f6ef50e, efc0acf, 8bbe179, b6cda27), CHANGELOG entry 1.6.026, the monitoring log (WATCH.md, Oct 23), and adb queries we ran again on a Galaxy S21 and S20 on 2026-10-30.
Contents (9)
- Three store apps answer one market:// link
- The old code read a failure as "no Play"
- Why 300 unit tests and two reviews missed it
- Try Play, then plain market, then https
- Keeping the chooser, or opening https first
- Don't pin Play if your app is on other stores too
- Two adb queries and one package assertion
- Still open: UTM installs and 16 drawer E2E tests
- FAQ
Three store apps answer one market:// link
On October 30 we asked two SKT-sold phones which apps handle market://details?id=…. A Galaxy S21 on Android 15 and a Galaxy S20 on Android 13 both listed three: Play, Galaxy Store and ONE store, each with isDefault=true, meaning its filter accepts implicit intents (it declares CATEGORY_DEFAULT). When such an intent has more than one candidate and no default app is set, Android asks the user to choose.

The drawer links to Block Turn, Arrow Knots and SugarCard, and all three are on Play only. The game links carry the referrer utm_source=sugarspeller&utm_medium=crosspromo&utm_campaign=drawer, which only Play reads and reports in the Play Console acquisition report. A user who picks another store lands on a page with no app, and even an install that follows doesn't count as cross-promotion.
The old code read a failure as "no Play"
The first version opened market:// and fell back to the https address on ActivityNotFoundException. The comment on that branch said the device had no Play Store app. The assumption was that only Play opens market:// links. That code first went in with the SugarCard menu in commit f6ef50e on September 20, and on October 9 efc0acf moved it unchanged into a launcher shared by the games and SugarCard. Claude co-authored both commits.
On SKT-sold Samsung phones that assumption means the fallback never runs. With three candidates there is no exception; the chooser appears, and the code treats the store as opened. The same check showed that the SugarCard menu had carried the same problem since the September 20 code.
Why 300 unit tests and two reviews missed it
The store test in efc0acf compared the started intent's URI and never looked at its package. Robolectric has no other store apps installed, and by default startActivity doesn't fail there. In the same piece of work, three analysis agents and two adversarial reviews found and closed three test gaps (a missing <queries> entry, hiding the rows on Android 6.0, and the Classic drawer wiring). None of them questioned the market:// assumption.
The chooser showed up when we tapped the menu on the S21 itself. The repository's working notes say runtime checks are done on a release APK, and the check after the fix ran on one (the routine is written up in the release gate note).
Try Play, then plain market, then https
8bbe179 keeps three candidates in a list and opens them in order, moving on only when one throws.
val attempts = listOf(
view("market://details?$query").setPackage(PLAY_STORE),
view("market://details?$query"), // no Play: any store
view("https://play.google.com/store/apps/details?$query"), // no store app: browser
)
An intent pinned to the Play package throws on a device without Play, which gives the list the failure signal the old code never had. The existing test gained an assertion that the started intent's package is Play, and a new test checks that with only another store present (checkActivities(true)) the plain market:// address opens with no package. Unit tests went to 301/301. Delete the setPackage call and the package comes back null, so the assertion fails (the same check we describe in deleting the guard).

On the S21 release APK that day, the installed Arrow Knots launched straight away, Block Turn, not installed, opened in Play with no chooser, and SugarCard went to Play as well. The GA4 log showed game_open{game=arrows,installed=1}, {game=blocks,installed=0} and sugarcard_open{installed=0}. The fix went into the 1.6.026 release branch, so the game menu pointed at Play from the first build users got, published the same morning.
Keeping the chooser, or opening https first
We dropped the idea of leaving the chooser alone because the apps are on Play only. Pinning Play with nothing after it would open nothing on a phone without Play, so plain market and https stay as fallbacks.
On the S21 on October 30, Play and Chrome both handle the Play https listing, and play.google.com is a system-verified link for Play, so opening https first would probably have opened Play too (we didn't tap it to check). But a browser accepts https on nearly every phone, so there is no exception, and which app opens depends on the device's settings. That doesn't fit a list that moves on when a candidate fails, so https stays last.
Don't pin Play if your app is on other stores too
If you send Korean users to an app that is only on Play, set the Play package on the market:// intent. If the app is also on ONE store or Galaxy Store, don't: the pin pulls those stores' users over to Play. On a phone without Play, the second candidate opens another store, which doesn't list our apps. We checked that path only in a Robolectric test; we have no such phone. We also checked only two Samsung phones sold by SKT, not KT or LG U+ models, unlocked models or other makers.
Two adb queries and one package assertion
- Connect the phone and run
adb shell cmd package query-activities --brief -a android.intent.action.VIEW -d "market://details?id=com.example.app". If the first line says 2 or more activities found, the phone can show a chooser; each candidate also shows its isDefault value. - Run it again with the Play Store app's package,
-p com.android.vending, at the end. You should get one. - Change the store link to try Play, then plain market, then https, catching only
ActivityNotFoundExceptionto move to the next one. - Assert the started intent's
packagein a unit test, and check once that the test fails when you remove setPackage. - Put the release APK on a phone sold in Korea and tap the link yourself.
Still open: UTM installs and 16 drawer E2E tests
From October 9 to 22 the game rows were tapped 33 times in GA4 (Block Turn 5, Arrow Knots 16, and 12 from 1.6.026 with no game value). Whether those taps became installs is only in the Play Console acquisition report, and we haven't judged it yet. On the S21, 16 E2E tests that open the drawer fail, and they fail the same way on the baseline build before the change (360bed3), so the game-row E2E tests never ran on the device and we tapped the rows by hand instead. Finding the cause of those failures comes next.
FAQ
Why does a Galaxy phone ask which store to use when I open a Play link?
More than one store app on the phone handles market:// addresses. On SKT-sold Galaxy S21 and S20 phones on 2026-10-30, Play, Galaxy Store and ONE store all did, and when the link names no target app, Android asks the user to pick.
How do I open the Play Store listing directly from my app?
Set the Play Store app as the package of the market://details intent with setPackage. On a device without Play that intent throws ActivityNotFoundException, so keep plain market:// and the https address as fallbacks, in that order.
This note is written from values SugarMount measured itself; an AI coding agent was used to draft it.
More AX notes
- Pre-gzipping Android assets, measured: zero APK savings, and .pack.gz became .pack with a FileNotFoundException
- A splash that never moved on: addOnCompleteListener(this) and onStop
- "6.7" < "6.11" is false: comparing Remote Config versions as strings froze content updates
- R8 kept 5 AdMob test IDs in our release build: one listOf().any line and a dex count
- A reverse-construction generator and the gate behind 1,100 arrow-puzzle levels
- Daily users rose after the redesign while organic D1 retention fell from 15.2% to 11.2%