[Samsung Galaxy Note 8 Questions and Answers] forsel:
Here is a detailed explanation of how the dual messenger feature on Samsung phones works. As I don't really have a background in Android it took me some time to figure it out. All in all it seems like a somewhat complex setup to me. Apologies for what has become a somewhat long post.

After completing my investigation it seems clear what should be done to properly activate the dual messenger feature for non-supported (messenger) apps such as Signal. I haven't been able to test anything yet, but I think that with a rooted phone it should be easy to properly add e.g. Signal. Since I don't have a rooted phone and don't want to root mine (S10) I'm currently stuck.

Architecture & code​

The dual messenger feature consists of 2 main components:

A system app com.samsung.android.da.daagent which is located under /system/app/DAAgent/DAAgent.apk

A system service called 'dual_app' which is running as part of the system_server process.

Ad 1:

The system app mainly provides the various screens (Activities) under Settings->Advanced->Dual Messenger as well as a database (Content Provider). This db stores the list of installed dual apps.

Once the dual messenger feature is activated (i.e. 1 dual app is enabled/installed), then this system app is visible as the 'Dual Messenger" service under Settings->Developer options-Running services.

The main code is (obviously) in the apk. It contains a classes.dex which can be converted to a jar file with dex2jar tools.

The app uses various other 'framework' parts/system services via some classes in /system/framework/com.samsung.bbccommon.jar with package path com.samsung.android.da.daagent.fwwrapper

AmWrapper - programmatic access to ActivityManager service, similar to command 'am' in adb shell

DaWrapper - programmatic access to 'dual_app' service, which is component 2 above

LogWrapper - Logging

PmWrapper - programmatic access to 'package' service, similar to command 'pm' in adb shell

UmWrapper - User handling

UtilsWrapper - various

Since this is a system app, it runs under the 'system' user with uid=1000.

Ad 2:

The system service provides a generic way for other apps to interact with the dual app feature/system app. For example, it can be used to check if an app is an installed 'dual app' app, which is used in numerous places in other Settings related code.

All System services run under the process 'system_server'. This process is started by the 'zygote' process on boot, which in its turn is started by the 'init' process. The system_server process also runs under the 'system' user with uid=1000.

The code for this is in /system/framework/oat/arm64/services.odex. This file can be deodexed using baksmali/smali tools into a jar file. The interesting classes are:

com.android.server.SystemServer (this is a standard Android class, which is updated by Samsung to add its own system services)

com.android.server.DualAppManagerService, which contains the code we are looking for.

Furthermore, each system service normally has a ServiceManager class. This class provides a stub and remote interface to the system service. The class can be called from an app and then uses inter process communication to communicate with the system service in the system_server process. In Android this works with a Binder interface and Parcel objects.

For the DualAppManagerService above, the ServiceManager class can be found in /system/framework/framework.jar. This jar file contains classes.dex files and can be translated into jar files using the dex2jar tools. The relevant classes are:

com.samsung.android.app.SemDualAppManager - some wrapper class around the next class with some extra functionality

com.samsung.android.app.ISemDualAppManager - the actual stub/remote interface for the system service, this would normally be generated through an .aidl file in the original (Samsung) source code

Which dual apps allowed​

The DAAgent system app contains a hardcoded list of so-called 'whitelisted' apps. These are the only apps that can be officially installed as a dual app.

Class com.samsung.android.da.daagent.provider.WhiteListApps contains the following definitions:

public class WhiteListApps
{
public static final String[] CHINA_SALES_CODES = { "CHN", "CHM", "CBK", "CTC", "CHU", "CHC" };
public static final String[] DUAL_APP_WHITELIST_PACKAGES = { "com.facebook.katana", "com.whatsapp", "com.facebook.orca", "com.tencent.mobileqq", "com.tencent.mobileqqi", "com.tencent.mm", "com.sina.weibo", "com.skype.raider", "com.viber.voip", "jp.naver.line.android", "com.bbm", "org.telegram.messenger", "com.kakao.talk", "com.bsb.hike", "com.icq.mobile.client", "com.yahoo.mobile.client.android.im", "com.zing.zalo", "com.snapchat.android", "kik.android" };
public static final String[] DUAL_APP_WHITELIST_PACKAGES_FOR_CHINA = { "com.tencent.mm", "com.tencent.mobileqq", "com.sina.weibo" };
}

As can be seen, there seems to be a specific/restricted definition for Chinese phones.

In order to officially support another app, e.g. Signal, it would need to be added here. So, the DUAL_APP_WHITELIST_PACKAGES string array would need to be extended with 'org.thoughtcrime.securesms' which is the packagename for Signal.

The simple straightforward approach would be to update this class. I've tried this and managed to get the class correctly updated. However, as I suspected there is no way to get the updated apk with updated class installed on an unrooted phone since everything is protected both on user level and through package signing. So, this approach failed and I started to look further.

System app db​

Another potential option would be to update the database. Although this would most likely not show the app in the list with allowed apps, it would make it possible to improve the workaround. So, install the app in the dual app user profile and then also update the database with this app as being installed.

I looked into this, but this is also not easily done since the app is running under the system user and the app is running in its own sandbox. Without more (root) access to the system it is not possible to get access to the app specific files such as a .db file. In this case, according to the code, the db file is DAAgent.db and it seems to contain:

paramSQLiteDatabase.execSQL("CREATE TABLE daApps (_id INTEGER PRIMARY KEY AUTOINCREMENT,pkgName TEXT UNIQUE, appType INTEGER default 1, isInstalled INTEGER default 0, appOption INTEGER, log_time TEXT default(datetime('now','localtime')))");
paramSQLiteDatabase.execSQL("CREATE TABLE daLogs (_id INTEGER PRIMARY KEY AUTOINCREMENT,logDate TEXT default(datetime('now','localtime')), logTitmeTick INTEGER default(strftime('%s','now')), logDesc TEXT)");
paramSQLiteDatabase.execSQL("CREATE TABLE daInfo (_id INTEGER PRIMARY KEY AUTOINCREMENT,infoType INTEGER NOT NULL, infoInt1 INTEGER, infoInt2 INTEGER, infoInt3 INTEGER, infoText1 TEXT, infoText2 TEXT, infoText3 TEXT)");
paramSQLiteDatabase.execSQL("CREATE TABLE IF NOT EXISTS daContacts (_id INTEGER PRIMARY KEY AUTOINCREMENT,lookup TEXT UNIQUE, data1 TEXT, data2 INTEGER)");

Here, the 'daApps' table is what we are interested in as it should contain installed apps. Although the table has an isInstalled column, it seems only really (dual-app) installed apps are stored here. So, it is not used to store the whitelisted apps which are not yet installed in the same table.

The database is accessed directly from within the app. However, part of it is also exposed as a 'content provider'. For this content provider, the following URLs are registered:

localUriMatcher.addURI("com.samsung.android.da.daagent.provider", "filterContacts", 1);
sUriMatcher.addURI("com.samsung.android.da.daagent.provider", "FilterContactsLookupKey", 2);
sUriMatcher.addURI("com.samsung.android.da.daagent.provider", "getWhitelistApps", 3);
sUriMatcher.addURI("com.samsung.android.da.daagent.provider", "getInstalledApps", 4);
sUriMatcher.addURI("com.samsung.android.da.daagent.provider", "getDefaultApps", 5);
sUriMatcher.addURI("com.samsung.android.da.daagent.provider", "recordUsage", 6);

This can be invoked through the 'content' command in adb shell. Unfortunately, this only provides some simple lookups, it does not allow manipulation of the db. Output for me looks like this:

127|beyond1:/ $ content query --uri content://com.samsung.android.da.daagent.provider/getWhitelistApps
Row: 0 pkgName=com.facebook.katana
Row: 1 pkgName=com.whatsapp
Row: 2 pkgName=com.facebook.orca
Row: 3 pkgName=com.tencent.mobileqq
Row: 4 pkgName=com.tencent.mobileqqi
Row: 5 pkgName=com.tencent.mm
Row: 6 pkgName=com.sina.weibo
Row: 7 pkgName=com.skype.raider
Row: 8 pkgName=com.viber.voip
Row: 9 pkgName=jp.naver.line.android
Row: 10 pkgName=com.bbm
Row: 11 pkgName=org.telegram.messenger
Row: 12 pkgName=com.kakao.talk
Row: 13 pkgName=com.bsb.hike
Row: 14 pkgName=com.icq.mobile.client
Row: 15 pkgName=com.yahoo.mobile.client.android.im
Row: 16 pkgName=com.zing.zalo
Row: 17 pkgName=com.snapchat.android
Row: 18 pkgName=kik.android
beyond1:/ $ content query --uri content://com.samsung.android.da.daagent.provider/getInstalledApps
Row: 0 pkgName=com.whatsapp, UID=0
beyond1:/ $ content query --uri content://com.samsung.android.da.daagent.provider/getDefaultApps
Row: 0 pkgName=com.android.settings
Row: 1 pkgName=com.android.providers.settings
Row: 2 pkgName=com.sec.android.provider.badge
Row: 3 pkgName=android
Row: 4 pkgName=com.android.keychain
Row: 5 pkgName=com.google.android.gms
Row: 6 pkgName=com.google.android.webview
Row: 7 pkgName=com.bst.airmessage
Row: 8 pkgName=com.bst.floatingmsgproxy
Row: 9 pkgName=com.google.android.gsf
Row: 10 pkgName=com.google.android.gsf.login
Row: 11 pkgName=com.android.chrome
Row: 12 pkgName=com.android.server.telecom
Row: 13 pkgName=com.google.android.permissioncontroller
Row: 14 pkgName=com.samsung.android.permissioncontroller
Row: 15 pkgName=com.google.android.overlay.modules.permissioncontroller
Row: 16 pkgName=com.google.android.providers.media.module
Row: 17 pkgName=com.android.providers.media.module
Row: 18 pkgName=com.android.providers.downloads
Row: 19 pkgName=com.samsung.android.secsoundpicker

Notice that the provided values for getWhitelistApps is the same as the hardcoded values above. This is because the content provider code uses the hardcoded list to build the query result, i.e. this is not really read from the database. As can be seen, I have Whatsapp installed as dual app.

All in all this is a bit of a dead end, so .....

System service​

Next, let's look at the system service, why is it there and how is it used?

As already mentioned, the system service is called from various places in Settings and potentially in other apps as well. It is even called by the DAAgent system app itself (via the DaWrapper class mentioned earlier).

Interaction with system services can be done with the 'service' command via adb shell, for example:

beyond1:/ $ service list |grep dual
82 dual_app: [com.samsung.android.app.ISemDualAppManager]
beyond1:/ $ service check dual_app
Service dual_app: found

It is also possible to call a system service via 'service call'. The provided methods are:

static final int TRANSACTION_getAllInstalledWhitelistedPackages = 1;
static final int TRANSACTION_getAllWhitelistedPackages = 3;
static final int TRANSACTION_isInstalledWhitelistedPackage = 2;
static final int TRANSACTION_updateDualAppData = 4;

The update method looks promising. Calling the first 2 methods is easy and provides a similar result as before with the content provider:

beyond1:/ $ service call dual_app 1
Result: Parcel(
0x00000000: 00000000 00000001 0000000c 006f0063 '............c.o.'
0x00000010: 002e006d 00680077 00740061 00610073 'm...w.h.a.t.s.a.'
0x00000020: 00700070 00000000 'p.p..... ')
beyond1:/ $ service call dual_app 3
Result: Parcel(
0x00000000: 00000000 00000013 0000000c 006f0063 '............c.o.'
0x00000010: 002e006d 00680077 00740061 00610073 'm...w.h.a.t.s.a.'
0x00000020: 00700070 00000000 0000000b 0069006b 'p.p.........k.i.'
0x00000030: 002e006b 006e0061 00720064 0069006f 'k...a.n.d.r.o.i.'
0x00000040: 00000064 0000000c 006f0063 002e006d 'd.......c.o.m...'
0x00000050: 00730062 002e0062 00690068 0065006b 'b.s.b...h.i.k.e.'
0x00000060: 00000000 00000014 006f0063 002e006d '........c.o.m...'
0x00000070: 006e0073 00700061 00680063 00740061 's.n.a.p.c.h.a.t.'
0x00000080: 0061002e 0064006e 006f0072 00640069 '..a.n.d.r.o.i.d.'
0x00000090: 00000000 00000015 006f0063 002e006d '........c.o.m...'
0x000000a0: 00650074 0063006e 006e0065 002e0074 't.e.n.c.e.n.t...'
0x000000b0: 006f006d 00690062 0065006c 00710071 'm.o.b.i.l.e.q.q.'
0x000000c0: 00000069 00000022 006f0063 002e006d 'i..."...c.o.m...'
0x000000d0: 00610079 006f0068 002e006f 006f006d 'y.a.h.o.o...m.o.'
0x000000e0: 00690062 0065006c 0063002e 0069006c 'b.i.l.e...c.l.i.'
0x000000f0: 006e0065 002e0074 006e0061 00720064 'e.n.t...a.n.d.r.'
0x00000100: 0069006f 002e0064 006d0069 00000000 'o.i.d...i.m.....'
0x00000110: 00000013 006f0063 002e006d 00610066 '....c.o.m...f.a.'
0x00000120: 00650063 006f0062 006b006f 006b002e 'c.e.b.o.o.k...k.'
0x00000130: 00740061 006e0061 00000061 0000000e 'a.t.a.n.a.......'
0x00000140: 006f0063 002e006d 00650074 0063006e 'c.o.m...t.e.n.c.'
0x00000150: 006e0065 002e0074 006d006d 00000000 'e.n.t...m.m.....'
0x00000160: 0000000e 006f0063 002e006d 00690073 '....c.o.m...s.i.'
0x00000170: 0061006e 0077002e 00690065 006f0062 'n.a...w.e.i.b.o.'
0x00000180: 00000000 00000007 006f0063 002e006d '........c.o.m...'
0x00000190: 00620062 0000006d 00000014 006f0063 'b.b.m.......c.o.'
0x000001a0: 002e006d 00650074 0063006e 006e0065 'm...t.e.n.c.e.n.'
0x000001b0: 002e0074 006f006d 00690062 0065006c 't...m.o.b.i.l.e.'
0x000001c0: 00710071 00000000 00000010 006f0063 'q.q.........c.o.'
0x000001d0: 002e006d 006b0073 00700079 002e0065 'm...s.k.y.p.e...'
0x000001e0: 00610072 00640069 00720065 00000000 'r.a.i.d.e.r.....'
0x000001f0: 0000000d 006f0063 002e006d 0069007a '....c.o.m...z.i.'
0x00000200: 0067006e 007a002e 006c0061 0000006f 'n.g...z.a.l.o...'
0x00000210: 00000015 0070006a 006e002e 00760061 '....j.p...n.a.v.'
0x00000220: 00720065 006c002e 006e0069 002e0065 'e.r...l.i.n.e...'
0x00000230: 006e0061 00720064 0069006f 00000064 'a.n.d.r.o.i.d...'
0x00000240: 00000015 006f0063 002e006d 00630069 '....c.o.m...i.c.'
0x00000250: 002e0071 006f006d 00690062 0065006c 'q...m.o.b.i.l.e.'
0x00000260: 0063002e 0069006c 006e0065 00000074 '..c.l.i.e.n.t...'
0x00000270: 0000000e 006f0063 002e006d 00690076 '....c.o.m...v.i.'
0x00000280: 00650062 002e0072 006f0076 00700069 'b.e.r...v.o.i.p.'
0x00000290: 00000000 0000000e 006f0063 002e006d '........c.o.m...'
0x000002a0: 0061006b 0061006b 002e006f 00610074 'k.a.k.a.o...t.a.'
0x000002b0: 006b006c 00000000 00000016 0072006f 'l.k.........o.r.'
0x000002c0: 002e0067 00650074 0065006c 00720067 'g...t.e.l.e.g.r.'
0x000002d0: 006d0061 006d002e 00730065 00650073 'a.m...m.e.s.s.e.'
0x000002e0: 0067006e 00720065 00000000 00000011 'n.g.e.r.........'
0x000002f0: 006f0063 002e006d 00610066 00650063 'c.o.m...f.a.c.e.'
0x00000300: 006f0062 006b006f 006f002e 00630072 'b.o.o.k...o.r.c.'
0x00000310: 00000061 'a... ')

The next isInstalledWhitelistedPackage method can also be called relatively easily by providing a packagename as input string, e.g.:

beyond1:/ $ service call dual_app 2 s16 com.whatsapp
Result: Parcel(00000000 00000001 '........')
beyond1:/ $ service call dual_app 2 s16 org.telegram.messenger
Result: Parcel(00000000 00000000 '........')

This is correct as it gives back a '1' = true for Whatsapp

Finally, the update method is much harder as it expects much more input which depends on an update command and is based on a Bundle class. This is not directly supported by the 'service' command.

The update commands that are supported by the update method are:

renewInstalledWhitelistedPkgs

addInstalledWhitelistedPkg

removeInstalledWhitelistedPkg

removeAllInstalledWhitelistedPkgs

printInstalledWhitelistedPkg

setDualAppNotificationSound

updateWhitelistPkgs

Here, the updateWhitelistPkgs seems very interesting.

Looking into the code of the system service, what it does is keep track of a list of whitelisted apps and a list of installed whitelisted apps internally in 2 hashmaps. Both hashmaps are initialized at boot time by the DAAgent system app where it uses the hardcoded whitelist apps list and the db entries for the installed apps. It seems that further checks on whether an app is a whitelisted app is then asked via the system service and that only looks at its internal hashmap.

Furthermore, the updateWhitelistPkgs allows that hashmap to be updated. So, it should be possible to run this update command after boot and thereby enter an extra app in the whitelist, which will then last until the next reboot. However, this should allow such an app to be then installed and once it is installed it has an entry in the db and that is what we are looking for!

There are 2 issues:

1. Calling this method/command is not possible via the command line. The way around this is to call it programmatically. The easiest way seems to be via a shell app. I did just that successfully, so create a Java class with the necessary code, compile it and then call it via 'app_process' which is sort of equivalent to the 'java' executable on other platforms. This works and I'm able to call the various methods.

2. The other methods to get the lists are working in this way. However, the update method is not working. The reason can be found directly in the code:

public Bundle updateDualAppData(String paramString, int paramInt, Bundle paramBundle)
{
try
{
if (("com.samsung.android.da.daagent".equals(paramString)) && (Binder.getCallingUid() == 1000))
{

This method is protected in that it is only callable by the system user (uid=1000). The positive news is that it doesn't need root as user. However, system privileges are still difficult to get. I've been looking for a non-intrusive method for a Samsung S10, but so far no luck.

Conclusion​

If you have a rooted phone, it should be fairly easy to do the above steps and if my analysis is correct, this should allow other apps to be added as whitelisted apps.

As I don't have a rooted phone and don't want to root it (and trip knox and potentially have issues with banking apps and such) the only alternative is to find some other way to gain system privileges. This however cannot be an easy task as such vulnerabilities will likely be patched all the time, since having system privileges is a step closer to having root privileges.

PS: See also https://xdaforums.com/t/telegram-is-not-shown-in-dual-messenger.4356079/#post-85875279 for some related info.