[{"content":"I went to Kumamoto to see a castle. I left with a Kumamon plushie. I\u0026rsquo;m still not sure how that happened.\nKumamon is a mascot. A round black bear with red cheeks, created by the Kumamoto Prefectural Government in March 2010. He was supposed to be a tourism gimmick. The Kyūshū Shinkansen was about to connect the island by bullet train, and the prefecture was terrified people would just zip through to Kagoshima without stopping. So they commissioned a cute character and hoped for the best.\nIt worked, but not for the reasons anyone expected.\nThe name Kuma (熊) is Japanese for \u0026ldquo;bear.\u0026rdquo; Mon is local Kumamoto dialect, a variant of mono (thing/person) but lazier. \u0026ldquo;Bear-guy,\u0026rdquo; more or less. It doubles as a pun: a person from Kumamoto.\nThe designer, Mizuno Manabu, kept things simple. Round shape, black body (Kumamoto Castle\u0026rsquo;s dark exterior), red cheeks (Mount Aso, the volcano that looms over the entire prefecture). Nothing complicated. Nothing that would scare children or puzzle grandparents.\nThe licensing play that shouldn\u0026rsquo;t have worked Most mascots are cash cows. Companies pay licensing fees to use them on products. Kumamoto did the opposite.\nThey made Kumamon\u0026rsquo;s image completely free. Any business, anywhere in Japan, could print him on anything. No fees, no royalties. One condition: the product had to promote something from Kumamoto. In 2018 they extended this worldwide.\nGenuinely weird thing for a government to do. They gave away an asset. The result wasn\u0026rsquo;t lost revenue. It was a flood.\nRice farmers put Kumamon on rice bags. ANA painted him on planes. Banks stuck him on passbooks. Nissin put him on instant noodle cups. A sumo stable used him on mawashi belts at tournaments. Thousands of companies, none paying a yen, all spreading the brand further than any paid ad campaign could reach.\nThe numbers: ¥123.2 billion in economic activity over two years (2010 to 2012). Roughly a billion US dollars. From a cartoon bear with rosy cheeks.\nIn late 2011, Kumamon won Japan\u0026rsquo;s national yuru-chara (mascot character) contest with over 280,000 votes. By then he wasn\u0026rsquo;t just Kumamoto\u0026rsquo;s anymore.\nA civil servant who does pratfalls Kumamon\u0026rsquo;s official job title is \u0026ldquo;Sales and Happiness Manager\u0026rdquo; (営業部長兼しあわせ部長) of Kumamoto Prefecture. He\u0026rsquo;s a civil servant. He has business cards. He travels on official duties.\nThe prefecture committed to this bit completely. At one point Kumamon was publicly \u0026ldquo;demoted\u0026rdquo; for being too fat. There was a fitness campaign. He lost weight. He got reinstated. The whole thing was staged, the Japanese media covered it heavily, and somehow nobody cared that it was fake. Everyone was having too much fun.\nThis is what sneaks up on you. Kumamon isn\u0026rsquo;t just cute. He\u0026rsquo;s clumsy, mischievous, and famously bad at staying upright. He does pratfalls. He makes mistakes. Japanese mascots are supposed to be endearing, but Kumamon\u0026rsquo;s personality was dialed in with unusual care. He reads less like a brand asset and more like a beloved idiot who keeps getting into trouble at county fairs.\nWalking around Kumamoto The saturation is absurd. Buses, manhole covers, train station signs, airport greeters, shochu bottles, ramen packets, phone straps dangling from businessmen\u0026rsquo;s briefcases. Old ladies carry Kumamon tote bags with the casual indifference of someone who\u0026rsquo;s been seeing this bear every day for fifteen years.\nKumamon Square is the city\u0026rsquo;s dedicated gift shop and visitor center. Floor-to-ceiling merchandise. I went in thinking I\u0026rsquo;d look around for five minutes. I came out with a plushie. I have no defense.\nIn most places, this level of mascot saturation would feel oppressive. In Japan kawaii is a legitimate design language, not a marketing tactic, and somehow it works. The bear is everywhere and nobody seems bothered. If anything, people seem proud of him.\nWhy it worked Kumamon landed at the right moment: the yuru-chara boom of the early 2010s, when Japan\u0026rsquo;s regional mascot scene was exploding and there was room for a breakout star. The free licensing created an army of unpaid promoters. The personality (mischievous without being annoying, cute without being saccharine) gave people a reason to stay engaged after the novelty wore off.\nBut there\u0026rsquo;s something else. Kumamon wasn\u0026rsquo;t designed by a corporate focus group optimizing for quarterly ROI. He was designed by one guy, on a government commission, for a tourism campaign that could have easily failed and been forgotten. The prefecture didn\u0026rsquo;t optimize for licensing revenue. They optimized for reach. And they treated the whole thing — the civil servant title, the weight-loss stunt, the pratfalls — with a weird mix of total commitment and zero self-importance.\nIt\u0026rsquo;s the kind of idea that wouldn\u0026rsquo;t survive a committee review. That might be exactly why it worked.\n","permalink":"https://blog1.arsain.xyz/posts/kumamon/","summary":"I went to Kumamoto expecting a castle. I left with strong opinions about a cartoon bear\u0026rsquo;s licensing strategy.","title":"Kumamon: the bear that outsmarted everyone"},{"content":"About This is a test blog built with Hugo and the PaperMod theme.\nNothing serious here — just kicking the tires.\n","permalink":"https://blog1.arsain.xyz/about/","summary":"\u003ch1 id=\"about\"\u003eAbout\u003c/h1\u003e\n\u003cp\u003eThis is a test blog built with \u003ca href=\"https://gohugo.io/\"\u003eHugo\u003c/a\u003e and the \u003ca href=\"https://github.com/adityatelange/hugo-PaperMod\"\u003ePaperMod\u003c/a\u003e theme.\u003c/p\u003e\n\u003cp\u003eNothing serious here — just kicking the tires.\u003c/p\u003e","title":"About"},{"content":"This is the first post on Blog1. 🎉\nMore to come — stay tuned.\n","permalink":"https://blog1.arsain.xyz/posts/hello-world/","summary":"First post — the blog is alive!","title":"Hello World"},{"content":"Shelter runs two copies of itself. That\u0026rsquo;s the whole trick. Not many Android apps do this. Shelter, the open-source work profile manager, installs itself in both the main profile and the work profile, then wires them together with cross-profile intents. One copy has DevicePolicyManager authority inside the work profile. The other sits in the main profile and talks to it. No root. No ADB. Just two instances of the same app, one on each side of the profile wall, passing signed intents back and forth.\nI spent an afternoon reading through the entire codebase (~5,100 lines of Java across 25 files). Here\u0026rsquo;s how it works.\nThe core loop: bind, cross, bind When you open Shelter, MainActivity kicks off a three-step dance:\nBind to the local ShelterService — this one lists your apps and handles UI requests. Runs in the main profile, nothing special. Ping the work profile — fires a TRY_START_SERVICE intent across the boundary. If work mode is off, you get a prompt. If the profile doesn\u0026rsquo;t exist at all, you\u0026rsquo;re unprovisioned and Shelter tells you. Bind to the work profile\u0026rsquo;s ShelterService — now we\u0026rsquo;re talking. This one\u0026rsquo;s the profile owner. It can call setApplicationHidden(), manage cross-profile widgets, enforce policies. The first service was a librarian. This one\u0026rsquo;s the bouncer. Both services share the same AIDL interface. IShelterService is implemented twice, once without profile-owner powers (main), once with (work). The UI calls whichever side makes sense. Freeze an app? Work profile. Clone from main to work? Cross the boundary.\nDummyActivity: the translucent traffic cop Here\u0026rsquo;s where it gets clever. Android won\u0026rsquo;t let you fire arbitrary binder calls across profiles, but it will forward activity intents, as long as they\u0026rsquo;re in the crossProfileIntentFilter whitelist. Shelter exploits this with DummyActivity, a translucent activity that never shows any UI.\nDummyActivity sits in both profiles and handles about a dozen custom intent actions: INSTALL_PACKAGE, UNINSTALL_PACKAGE, UNFREEZE_AND_LAUNCH, START_FILE_SHUTTLE, and so on. When one profile wants something from the other, it wraps the request as an intent, signs it, and sends it to DummyActivity on the other side.\nAuthentication is TOFU: Trust On First Use. The first profile to set up generates an HMAC-SHA256 key and sends it across as an auth_key extra. The other side grabs that key and trusts it permanently. From then on, every intent carries a timestamp plus a signature. Timestamps expire after 30 seconds, so replaying an old intent doesn\u0026rsquo;t work.\nThere\u0026rsquo;s also a same-process shortcut. When ShelterService needs to launch DummyActivity for an install or uninstall, it registers the intent through a static volatile long with a 5-second validity window. No signature check needed, and since both components share a process, it\u0026rsquo;s fine.\nHow cloning actually works \u0026ldquo;Cloning\u0026rdquo; an app sounds like it duplicates an APK. It doesn\u0026rsquo;t. What happens depends on whether the app is a system app:\nSystem apps (pre-installed): Android already has the APK available in the work profile, just disabled. Shelter calls DevicePolicyManager.enableSystemApp() and the app appears. No file transfer, no install session, nothing. User-installed apps: Shelter reads the APK path from the source profile, then uses PackageInstaller to install it into the work profile. On Android 10+, ACTION_INSTALL_PACKAGE is deprecated, so Shelter does manual session management: open a PackageInstaller.Session, pipe the APK bytes through, commit with a PendingIntent callback. The APK file itself crosses the profile boundary through Shelter\u0026rsquo;s FileProviderProxy, which opens a file descriptor in the source profile and wraps it as a content URI accessible from the other side. It\u0026rsquo;s a one-way door: once installed in the work profile, the app has its own separate storage, its own data, its own lifecycle.\nCloning only goes main → work, not the reverse. You can\u0026rsquo;t promote a work app back to main.\nFreezing: the cheapest isolation you can get \u0026ldquo;Freezing\u0026rdquo; an app in Shelter is literally a single API call:\nmPolicyManager.setApplicationHidden(adminComponent, packageName, true); This hides the app from the launcher, prevents it from running, and stops it from receiving broadcasts. It\u0026rsquo;s not freezing a process. It\u0026rsquo;s telling the system to act like the app was never installed.\nThe auto-freeze feature (FreezeService) is a screen-off listener with a configurable delay. When the screen locks, it sets an AlarmManager alarm. If the screen comes back on before the alarm fires, the freeze gets cancelled. If the alarm goes off, it iterates through the auto-freeze list and hides every app in it.\nThere\u0026rsquo;s an optional \u0026ldquo;don\u0026rsquo;t freeze foreground apps\u0026rdquo; mode that snapshots UsageStatsManager at screen-lock time and skips any app that was recently in the foreground. Reasonable idea, though the implementation relies on a 1-second inactivity threshold that feels arbitrary.\nThe static sAppToFreeze list inside FreezeService is synchronized on the class lock, but there\u0026rsquo;s no protection against the process getting killed mid-operation. If the system decides to reap Shelter between the alarm firing and the loop finishing, some apps stay unfrozen. Not a catastrophic bug, but it means the auto-freeze guarantee is soft.\nFile sharing across the boundary Shelter has two mechanisms for moving files between profiles:\nFile Shuttle lets you browse the work profile\u0026rsquo;s storage from the system Files app. It\u0026rsquo;s a DocumentsProvider (CrossProfileDocumentsProvider) that proxies requests to FileShuttleService, which handles the actual filesystem operations. The service has a 10-second inactivity timer: if nothing happens for 10 seconds, it unbinds and stops itself. Keeps the AIDL connection from hanging around.\nUriForwardProxy is simpler and more specialized. It opens a file descriptor in one profile and hands a content URI to the other. Used exclusively for APK installation: the APK gets read in the main profile and piped to PackageInstaller in the work profile.\nNeither mechanism gives you transparent shared storage. Files still live in separate sandboxes. The shuttle is purely for manual transfers.\nSetup: the provisioning dance Getting Shelter set up is a multi-step wizard that ultimately calls DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE. This hands control to Android\u0026rsquo;s system provisioning UI, which creates the work profile and sets Shelter as its profile owner.\nThe tricky part is what happens after provisioning finishes. On Android 8+, Shelter hooks into ACTION_PROVISIONING_SUCCESSFUL via FinalizeActivity — an activity intent, which is way more reliable than a broadcast receiver. That activity runs the final configuration: enabling policies, setting intent filters, hiding Shelter\u0026rsquo;s own launcher icon inside the work profile. On Android 7 and below, the same work fires from ShelterDeviceAdminReceiver.onProfileProvisioningComplete(), but broadcast receivers are flaky for complex operations, so it delegates to DummyActivity through a notification tap.\nOne thing that caught my eye: the device_admin.xml file declares zero policies:\n\u0026lt;device-admin\u0026gt; \u0026lt;uses-policies\u0026gt; \u0026lt;/uses-policies\u0026gt; \u0026lt;/device-admin\u0026gt; All policy enforcement happens programmatically in Utility.enforceWorkProfilePolicies(). No static declarations to work around. Shelter has complete runtime control over what\u0026rsquo;s active and when.\nKillerService: the dirty hack that holds it together Android activities don\u0026rsquo;t get notified when the user swipes them from recents. This is a problem for Shelter because the work-profile ShelterService runs as a foreground service, and if the main activity disappears without cleanup, that service keeps running. Orphaned notification and all.\nThe fix is KillerService, a sticky service that watches onTaskRemoved, onTrimMemory(TRIM_MEMORY_BACKGROUND), and onDestroy. Any of these fires killEverything(), which calls stopShelterService() on both instances and then System.exit(0) on the work-profile process.\nThe code\u0026rsquo;s own comments call this \u0026ldquo;a dirty hack,\u0026rdquo; and they\u0026rsquo;re right. But it works. The main activity also calls finish() on onTrimMemory(TRIM_MEMORY_BACKGROUND), so the whole thing is aggressively eager to die. Better to clean up than to leave orphaned services running.\nWhat Shelter can\u0026rsquo;t do Some of these are Android limitations. Some are design choices.\nCan\u0026rsquo;t freeze apps in the main profile. Shelter is only the work profile owner, not the device owner. It has no authority over the main profile. Can\u0026rsquo;t clone apps both ways. Work → main cloning isn\u0026rsquo;t supported. No batch clone. You clone apps one at a time through context menus. The multi-select mode only handles unfreeze shortcuts. No data migration between profiles. Cloning installs a fresh copy of the app. Your data doesn\u0026rsquo;t come with it. No backup/restore of freeze lists. Your auto-freeze configuration lives in SharedPreferences with no export capability. No MIUI support for non-system app cloning. Xiaomi blocks it at the OS level. Shelter shows a warning but can\u0026rsquo;t override it. Fragile lifecycle management. The KillerService approach relies on Android calling lifecycle methods reliably, which isn\u0026rsquo;t guaranteed under memory pressure. No work profile backup. If you reset your device or re-provision the work profile, you\u0026rsquo;re starting from scratch. The project is in maintenance mode The README is upfront about this. Shelter\u0026rsquo;s author considers the app feature-complete given the APIs available. The commitment is to keep it working on new Android versions, not to add features. Looking at the code, I think that\u0026rsquo;s fair. Shelter already covers the full surface area of what unprivileged Work Profile APIs allow.\nThe codebase is clean, well-commented, and surprisingly compact. About 5,100 lines of Java, no DI frameworks, no Kotlin, no reactive libraries. Just AIDL services, intents, and DevicePolicyManager calls. You can read the whole thing in an afternoon and come out understanding exactly how it works.\nI wish more projects were like this.\n","permalink":"https://blog1.arsain.xyz/posts/shelter-deep-dive/","summary":"A code-level walkthrough of Shelter\u0026rsquo;s architecture: how it clones apps, freezes them, and routes intents across the profile boundary using a translucent Activity, AIDL services, and some creative hacks.","title":"How Shelter Tames Android Work Profiles: a Deep Dive"}]