vermilion10

Back

Note

Package names, device model, and Android version in this post are real (Galaxy S24 FE, One UI 8.5 on Android 16). I’ve redacted local network addresses since they aren’t relevant to the technique

at first this had nothing to do with dual apps at all. It started with trying to share a couple of Windows folders with my phone over SMB, tightened down to that one device instead of the whole Wi-Fi network. That went fine, but it left me wanting proper cloud storage access from Android instead of ad-hoc file shares, so I went looking for a trustworthy rclone/S3 client. Closed-source options were an easy no, so I picked two open-source ones, RSAF ↗ and a maintained fork of Round-Sync, and decided to build both from source rather than sideload someone else’s binary.

RSAF never built, its toolchain is pinned to an Android SDK version so new that even the maintainer’s own CI has to pin the emulator to a lower API “until the action supports it.” Round-Sync did build, after fixing a missing GOOS=android in its dependency resolution and a 16KB page-alignment issue in its native library. None of that is this post. This post is about a side effect I noticed while reinstalling Round-Sync over and over during that debugging: two identical app icons


Two icons, one APK#

I’d run adb install -r app-debug.apk, and the launcher would show a duplicate, same name, same icon, one with a small badge overlay. My first guess was Secure App, Samsung’s Knox-isolated profile. Nah, it was wrong, the badge didn’t look like Secure Folder’s lock icon, and a permission error from pm list packages alone doesn’t distinguish between Samsung’s various profile types anyway. Also, the Secure App only apear in Secure Folder, not on home screen. So I checked properly:

$ adb shell pm list users
Users:
	UserInfo{0:null:4c13} running
	UserInfo{95:DUAL_APP:20001010} running
	UserInfo{150:Secure Folder:61010} running
plaintext

Three profiles. 150 is Secure Folder, confirmed separately. 95 is named DUAL_APP, and a package check confirmed it:

$ adb shell pm list packages --user 95 | grep felixnuesse
package:de.felixnuesse.extract.debug
plaintext

the app showed up on both the main profile and this dual-app profile, though I’d never opened Samsung’s dual-app settings for it

Important

Settings > Advanced features > Dual Messenger on this device lists exactly two apps: WhatsApp and Facebook. There is no toggle for anything else. And yet an unrelated file-manager app was sitting in that profile.


Ruling out the obvious explanation#

My first guess was that Dual Messenger secretly supports more than it lets on, or maybe some Good Lock module got switched on somewhere along the way. Neither one held up though:

  • The Dual Messenger app list has exactly those two apps, no hidden “add more” screen.
  • Apps installed the normal way, tapping a downloaded APK through the system installer, never get cloned. A local test with an unrelated open-source app confirmed this: install by tapping the file, no duplicate appears.

The only apps that ended up duplicated were the ones I had installed via adb install in the same session. That’s the actual variable. The dual-app setting had nothing to do with it.

$ adb shell dumpsys user
  UserInfo{95:xxx:20001010} serialNo=95 isPrimary=false parentId=0
    Type: android.os.usertype.profile.CLONE
    Flags: 536875024 (DUALAPP_PROFILE|INITIALIZED|PROFILE)
    Created: +537d20h20m49s210ms ago
plaintext

android.os.usertype.profile.CLONE is AOSP’s generic clone-profile primitive. Samsung didn’t invent it. Dual Messenger is one front-end that writes into it, with a curated app allowlist baked into its own UI. The profile itself has no such allowlist, it’s a plain Android user, and pm doesn’t care which front-end you’d normally use to populate it.


The actual technique#

If the profile is generic, installing into it directly should work without going anywhere near Dual Messenger’s UI at all. Android’s package manager has a command built for exactly this: cloning an app that’s already installed into another user, without touching its APK.

$ adb shell pm list packages --user 0 | grep discord
package:com.discord

$ adb shell pm install-existing --user 95 com.discord
Package com.discord installed for user: 95

$ adb shell pm list packages --user 95 | grep discord
package:com.discord
plaintext

That’s the whole trick: no APK re-download, no re-signing. It’s the same installed package already verified and running on your main profile, registered against a second user, so there’s no risk of ending up with a different build. The new icon appears in the launcher, with its own isolated /data/user/95/ storage: a second, independent login.

flowchart LR
    subgraph Device
        M[Main profile<br/>user 0]
        D[Dual App profile<br/>user 95, CLONE type]
        S[Secure Folder<br/>user 150]
    end
    UI[Dual Messenger UI] -->|writes only<br/>WhatsApp/Facebook| D
    ADB["pm install-existing --user 95 &lt;pkg&gt;"] -->|writes any package| D
    style ADB fill:#22c55e,color:#000
    style UI fill:#6b7280,color:#fff

The Dual Messenger UI and the raw pm command both write into the same profile. One gates it behind an allowlist, the other doesn’t gate it at all.

Tip

If the app isn’t installed on the main profile yet, pm install-existing has nothing to clone. Install it normally first (Play Store or a verified APK), then run the command above.


Then it disappeared#

I wrote the first draft of this post, logged into the cloned Discord, and moved on. Some time later the second icon was gone. The app and its data were gone too:

$ adb shell dumpsys package com.discord | grep "User 95"
    User 95: ceDataInode=-1 deDataInode=-1 installed=false ...
plaintext

ceDataInode=-1 means the profile’s copy and its data were deleted. Same for the two other apps I’d cloned. Profile 95 was back to exactly one third-party package: WhatsApp.

Android’s log buffer only holds a few hours, so whatever did it had scrolled away. I re-cloned Discord and started watching. Nothing happened for fourteen minutes of normal use. Then I rebooted the phone, and two minutes after boot:

18:13:19.385 E DualAppManagerService: getAllWhitelistedPackages : empty
18:13:19.385 E SemDualAppManager: getAllWhitelistedPackages : null returned. Return default
18:13:19.395 I PackageManager: START INSTALL-EXISTING PACKAGE: userId{95} pkg{com.samsung.android.permissioncontroller}
18:13:19.395 I PackageManager:           Request from pId{12519 / com.samsung.android.da.daagent}
18:13:19.447 I PackageManager: START DELETE PACKAGE: observer{138464497}
18:13:19.447 I PackageManager: pkg{com.discord}, user{95}, caller{1000} flags{0}
18:13:19.451 I ActivityManager: Force stopping com.discord appid=10378 user=95: deletePackageX
plaintext

That’s the whole mechanism in seven lines. com.samsung.android.da.daagent, the Dual Messenger agent, runs a reconciliation pass on every boot:

  1. Ask the system service for the allowlist. It comes back empty, so it falls back to the built-in default (WhatsApp and Facebook).
  2. install-existing its own helper packages into the profile, using the exact same command I’d used, which is a nice confirmation that this is the intended way to populate it.
  3. Delete every package in the profile that isn’t on the list. Data included.

The profile is generic, the restriction lives in a separate app, and that separate app is also a janitor that shows up after every reboot. Nothing in the fourteen minutes of runtime triggered it, only the boot did.

sequenceDiagram
    participant Boot
    participant DA as daagent
    participant PM as PackageManager
    participant P95 as Profile 95
    Boot->>DA: start
    DA->>PM: getAllWhitelistedPackages()
    PM-->>DA: empty → default [WhatsApp, Facebook]
    DA->>PM: install-existing helpers → user 95
    DA->>PM: for pkg in profile 95 not in list: deletePackage(pkg, 95)
    PM->>P95: remove com.discord + data

I looked for somewhere to write to that allowlist. dumpsys dual_app prints nothing, there are no settings keys for it, and daagent’s data directory is off-limits without root. Samsung either baked the list into the APK or feeds it from their servers. Either way, it isn’t ours to edit.


Making it stick#

If the allowlist can’t be changed, the other lever is the thing that enforces it. daagent is an ordinary app, and ordinary apps can be disabled per user:

$ adb shell pm install-existing --user 95 com.discord
Package com.discord installed for user: 95

$ adb shell pm disable-user --user 0 com.samsung.android.da.daagent
Package com.samsung.android.da.daagent new state: disabled-user
plaintext

Then reboot. Two minutes after boot, no START DELETE PACKAGE, no daagent process, and both clones still there and working, including the WhatsApp one that Samsung set up itself.

Important

Disabling daagent doesn’t uninstall anything or touch the profile’s data, it only stops the app that would delete things. In the “will my WhatsApp clone get wiped” sense, this makes the profile safer, not riskier. The one thing that can still wipe it is turning Dual Messenger off in Settings, which removes the whole profile, and that’s true with or without this trick.

Disabling daagent costs you a few things (from what i researched on the internet): the Dual Messenger settings page probably won’t work, and clone notifications might be less reliable since daagent handles some of the profile’s plumbing. The badge overlay on clone icons may go away too. All of it comes back with the mirror command:

$ adb shell pm enable com.samsung.android.da.daagent
plaintext

which also means the next reboot will sweep Discord out again.

Tip

Back up the WhatsApp clone (Settings → Chats → Chat backup, from inside the clone) before doing any of this. The commands above don’t threaten it. A clone profile with no backup is one accidental Settings toggle away from gone regardless.

If you’d rather not touch a system app at all, the fallback is to re-run install-existing after each reboot and log in again. Safe, but annoying.


What’s still a guess#

One thing I never nailed down is why plain adb install (no --user flag) put apps into the dual-app profile in the first place, which is what started all of this. The working theory is that adb install on this OEM build targets all users while the tap-to-install path targets only the current one, but I don’t have a controlled test to call that proven. Everything in the two sections above, the reboot sweep and the fix for it, is verified from the logs and reproducible on demand.


Caveats before you do this#

  • Permission prompts don’t stick. The cloned Discord asked for photo access every time and never got it. dumpsys package showed why: every runtime permission for user 95 was granted=false with no USER_SET flag, so my “Allow” taps were never recorded, and POST_NOTIFICATIONS was denied the same way. Grant them from the shell instead:

    $ adb shell pm grant --user 95 com.discord android.permission.READ_MEDIA_IMAGES
    $ adb shell pm grant --user 95 com.discord android.permission.POST_NOTIFICATIONS
    plaintext

    Same for READ_MEDIA_VIDEO, CAMERA, RECORD_AUDIO, whatever the app asks for. Force-close the clone afterwards.

  • Notifications can still be less reliable on a clone-profile instance of an app that was never designed with Samsung’s dual-app framework in mind, even once the permission is granted. Log in and send yourself a test message before relying on it.

  • This isn’t an officially supported configuration for arbitrary apps, only Dual Messenger’s own allowlisted apps get Samsung’s testing and support.

  • Check the app’s terms of service if the clone is for running a second account. Some services restrict multi-accounting, and this post is about the Android mechanism, not a statement on any particular app’s policy.

  • Removing a clone is the mirror command: adb shell pm uninstall --user 95 <package>.


Takeaways#

  1. A restrictive front-end UI doesn’t mean the underlying platform mechanism is restrictive too. Samsung’s Dual Messenger allowlist lives in its own app. The clone-profile primitive it writes to has none.
  2. pm install-existing --user <id> <package> clones an already-installed app into any user profile without touching the APK. Samsung’s own agent uses the same command.
  3. If something you set up reverts on its own, suspect a boot-time job before suspecting a bug. logcat right after a reboot, filtered on the package name, found the culprit in seconds.
  4. pm disable-user on the enforcing app is a legitimate, reversible fix when the policy it enforces isn’t editable. Know what else that app does before you switch it off.
Cloning Any App Into Samsung's Dual App Profile
https://pure.vermilion10.dev/blog/cloning-any-app-into-samsungs-dual-app-profile
Author vermilion10
Published at September 5, 2026