This post is part of AppLovin Nonconsensual Installs > Execution Path. See important disclosures.
T-Mobile’s InstallerHelper sends execution to the separate APK com.tmobile.dm.cm:
public final InstallerHelperResult startInstall(String absPath, String packageName, boolean shortcut, int requestedScreen, int colX, int rowY, String triggerType, String className, String action, String extraData, String receiverPermission) throws RemoteException {
AbstractC4226k.m6579e(absPath, "absPath");
AbstractC4226k.m6579e(packageName, "packageName");
if (!isReady()) {
if (this.f19986b) {
Log.e("CM_TMO_SDK", "Cannot start, service not ready");
}
return new InstallerHelperResult.Fail(ConstantsKt.ERROR_KEY_NOT_BOUND, null);
}
...
Message messageObtain = Message.obtain();
messageObtain.what = 100;
messageObtain.replyTo = this.f19993i;
File file = new File(absPath);
Bundle bundle = new Bundle();
bundle.putString(EXTRA_LOCATION, Uri.fromFile(file).toString());
bundle.putString(EXTRA_PACKAGE, packageName);
bundle.putString("com.tmobile.dm.cm.extra.PACKAGE_NAME", this.f19985a.getPackageName());
bundle.putBoolean(EXTRA_SHORTCUT, shortcut);
...
messageObtain.setData(bundle);
try {
Messenger messenger = this.f19992h;
if (messenger != null) {
messenger.send(messageObtain);
}
return new InstallerHelperResult.Success(ConstantsKt.SUCCESS_KEY);
According to the InstallerHelper class definition, the constant messageObtain.what=100 denotes performing an installation:
public static final int REQUEST_INSTALL = 100;
T-Mobile’s com.tmobile.dm.cm has special permissions including the permission to install apps. Its manifest declares these permissions:
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
T-Mobile’s message dispatcher m14262a() receives the request from InstallerHelper, classifies the message based on message.what message type, and routes the message accordingly:
public final void m14262a(Message message) { //message dispatcher
...
int i8 = message.what;
...
switch (i8) {
case 100:
companion.mo19362i("doHandleMsg(): Install requested: %s", 100);
synchronized (this.f33457b) {
size3 = this.f33457b.size();
}
Bundle data3 = message.getData();
String m14260j = m14260j(data3.getString(AppConstants.EXTRA_REQUESTER_PACKAGE), data3.getStringArray(AppConstants.EXTRA_SERVICE_REQUESTER_PACKAGE));
String string = data3.getString("location");
String string2 = data3.getString(AppConstants.EXTRA_PACKAGE);
int i9 = data3.getInt(AppConstants.EXTRA_UID, 0);
boolean z7 = data3.getBoolean(AppConstants.EXTRA_SIGNED, true);
Intrinsics.checkNotNull(messenger);
LocalInstallHandlerParams localInstallHandlerParams = new LocalInstallHandlerParams(messenger, string, string2, i9, z7, m14260j);
localInstallHandlerParams.setAddShortcut(data3.getBoolean(AppConstants.EXTRA_SHORTCUT, false));
localInstallHandlerParams.setRequestedScreen(data3.getInt(AppConstants.EXTRA_REQUEST_SCREEN, 0));
localInstallHandlerParams.setColX(data3.getInt(AppConstants.EXTRA_COLX, 0));
localInstallHandlerParams.setRowY(data3.getInt(AppConstants.EXTRA_ROWY, 0));
localInstallHandlerParams.setTriggerType(data3.getString(AppConstants.EXTRA_TRIGGER_TYPE));
localInstallHandlerParams.setClassName(data3.getString(AppConstants.EXTRA_CLASS_NAME));
localInstallHandlerParams.setAction(data3.getString("action"));
localInstallHandlerParams.setExtraData(data3.getString(AppConstants.EXTRA_DATA));
localInstallHandlerParams.setReceiverPermission(data3.getString(AppConstants.EXTRA_BROADCAST_PERMISSION));
synchronized (this.f33457b) {
this.f33457b.add(size3, localInstallHandlerParams);
}
if (size3 == 0 && this.f33458c == null) {
this.f33460e.sendEmptyMessage(102);
return;
}
...
The sendEmptyMessage(102) method sends execution to m14262a case 102, which again calls startInstall(), this time with case 102:
case 102:
companion.mo19362i("doHandleMsg(): Processing install: %s", 102);
synchronized (this.f33457b) {
size4 = this.f33457b.size();
}
if (size4 > 0) {
synchronized (this.f33457b) {
handlerParams = (HandlerParams) this.f33457b.remove(0);
this.f33458c = handlerParams;
}
if (handlerParams != null) {
if (handlerParams instanceof ModifyParams) {
((ModifyParams) handlerParams).startModify(this);
return;
} else if (handlerParams instanceof UninstallParams) {
((UninstallParams) handlerParams).startUninstall(this);
return;
} else {
handlerParams.startInstall(this);
return;
}
}
return;
}
return;
Next handlerParams.startInstall() calls prepareInstall(), which in turn calls performInstallBundle():
public boolean startInstall(@NotNull ServiceHandler handler) {
Intrinsics.checkNotNullParameter(handler, "handler");
int prepareInstall = prepareInstall(handler);
...
public int prepareInstall(@NotNull ServiceHandler handler) {
...
return performInstallBundle(handler.getMContext());
}
Then performInstallBundle() passes execution to InstallParams method m14247a():
public final int performInstallBundle(@NotNull Context context) {
…
int m14247a = m14247a(context, "", arrayList, false);
Finally, m14247a tells the Android Package Manager to perform the install:
//pass install to Android Package Manager
public final int m14247a(Context context, String str, ArrayList arrayList, boolean z6) throws Throwable {
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
...
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(this.mInstallSessionMode);
sessionParams.setAppLabel(getAppName(context));
sessionParams.setAppPackageName(getCom.google.firebase.remoteconfig.RemoteConfigConstants.RequestFieldKey.PACKAGE_NAME java.lang.String());
...
setMInstallerSessionId$app_currentRelease(packageInstaller.createSession(sessionParams));
...
this.f33245C = packageInstaller.openSession(getMInstallerSessionId());
Iterator it = arrayList.iterator();
Intrinsics.checkNotNullExpressionValue(it, "iterator(...)");
int iM14248c2 = -999;
while (it.hasNext()) {
Object next = it.next();
Intrinsics.checkNotNullExpressionValue(next, "next(...)");
Timber.INSTANCE.getClass();
iM14248c2 = m14248c((String) next);
if (1 != iM14248c2) {
break;
}
}
...
Intent intent = new Intent(context, (Class<?>) InternalReceiver.class);
intent.setAction(InternalReceiver.ACTION_INSTALLER_RESULT);
PendingIntent broadcast = PendingIntent.getBroadcast(context, 0, intent, Build.VERSION.SDK_INT >= 31 ? 167772160 : 134217728);
PackageInstaller.Session session = this.f33245C;
if (session != null) {
session.commit(broadcast.getIntentSender());
}
AppHub uses a variety of installers with heightened privileges from manufacturer or carrier
The preceding section discusses AppHub passing execution to T-Mobile InstallerHelper. But consider devices that don’t have T-Mobile InstallerHelper. AppHub code shows AppLovin also using other install helpers from other manufacturers and carriers.
T-Mobile and Sprint
public final InstallerHelperResult bindToInstaller(boolean useForegroundServiceIfNeeded) {
...
PackageManager.PackageInfoFlags of;
PackageManager.PackageInfoFlags of2;
PackageManager.ResolveInfoFlags of3;
if (isReady()) {
if (this.f19986b) {
Log.e("CM_TMO_SDK", "Binding failed, service already bound");
}
return new InstallerHelperResult.Fail(ConstantsKt.ERROR_KEY_ALREADY_BOUND, null);
}
if (((Number) this.f19989e.getValue()).intValue() < 3000) {
return new InstallerHelperResult.Fail(ConstantsKt.ERROR_KEY_VERSION_NOT_SUPPORTED, null);
}
String str = "com.tmobile.dm.cm.extra.PACKAGE_NAME";
String str2 = "com.tmobile.dm.cm.extra.APP_LABEL";
if (AbstractC0945q.m2146P((String) this.f19987c.getValue(), "com.tmobile.dm.cm.permission.UPDATES_INSTALL", false)) {
intent = new Intent("com.tmobile.action.INSTALLER_SERVICE");
} else if (AbstractC0945q.m2146P((String) this.f19987c.getValue(), "com.tmobile.dm.cm.permission.TRUSTED_UPDATES_INSTALL", false)) {
intent = new Intent("com.tmobile.action.INSTALLER_TRUSTED_SERVICE");
} else {
str = "com.sprint.ce.updater.extra.PACKAGE_NAME";
str2 = "com.sprint.ce.updater.extra.APP_LABEL";
if (AbstractC0945q.m2146P((String) this.f19987c.getValue(), "com.sprint.permission.INSTALL_UPDATES", false)) {
intent = new Intent("com.sprint.action.INSTALLER_SERVICE");
} else {
if (!AbstractC0945q.m2146P((String) this.f19987c.getValue(), "com.sprint.ce.updater.permission.TRUSTED_INSTALL_UPDATES", false)) {
return new InstallerHelperResult.Fail(ConstantsKt.ERROR_KEY_PERMISSION_SECURITY_ISSUE, null);
}
intent = new Intent("com.sprint.action.INSTALLER_TRUSTED_SERVICE");
...
Samsung
public class SamsungBindInstallAgentService extends Hilt_SamsungBindInstallAgentService {
public static final String ACTION_INSTALL_PACKAGE_BY_SAMSUNG = "action_install_package_by_samsung";
private static final String INSTALL_AGENT_CLASS = "com.sec.android.app.samsungapps.api.InstallAgent";
private static final String INSTALL_AGENT_PACKAGE = "com.sec.android.app.samsungapps";
public static final String PARAM_DOWNLOAD_PACKAGE = "param_download_package";
ActiveDeliveryTrackerManager activeDeliveryTrackerManager;
AppDeliveryInfoDao appDeliveryInfoDao;
Executor deliveryCoordinatorExecutor;
private InterfaceC1402c installAgentAPI;
Logger logger;
SamsungErrorCodeManager samsungErrorCodeManager;
private final IBinder binder = new LocalBinder();
volatile boolean mBound = false;
volatile boolean isBinding = false;
IBinder samsungInstallerBinder = null;
String activeTargetPackageName = null;
private volatile ArrayDeque installTaskQueue = new ArrayDeque<>();
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
InterfaceC1402c c1400a;
SamsungBindInstallAgentService.this.logger.mo2231d( SamsungBindInstallAgentService.this.getClass().getSimpleName() + " : onServiceConnected() called with: className = [" + componentName + "], activeTargetPackageName = [" + SamsungBindInstallAgentService.this.activeTargetPackageName + "]");
SamsungBindInstallAgentService samsungBindInstallAgentService = SamsungBindInstallAgentService.this;
samsungBindInstallAgentService.samsungInstallerBinder = iBinder;
int i10 = AbstractBinderC1401b.f4081c;
if (iBinder == null) {
c1400a = null;
} else {
IInterface queryLocalInterface = iBinder.queryLocalInterface("com.sec.android.app.samsungapps.api.aidl.IInstallAgentAPI");
c1400a = (queryLocalInterface == null || !(queryLocalInterface instanceof InterfaceC1402c)) ? new C1400a(iBinder) : (InterfaceC1402c) queryLocalInterface;
}
samsungBindInstallAgentService.installAgentAPI = c1400a;
SamsungBindInstallAgentService.this.mBound = true;
SamsungBindInstallAgentService.this.isBinding = false;
if (TextUtils.isEmpty(SamsungBindInstallAgentService.this.activeTargetPackageName)) {
SamsungBindInstallAgentService.this.dequeueDownloadToken();
return;
}
try {
SamsungBindInstallAgentService.this.logger.mo2237i( SamsungBindInstallAgentService.this.getClass().getSimpleName() + " : resume install package after reconnected = " + SamsungBindInstallAgentService.this.activeTargetPackageName);
SamsungBindInstallAgentService samsungBindInstallAgentService2 = SamsungBindInstallAgentService.this;
samsungBindInstallAgentService2.startInstall( samsungBindInstallAgentService2.activeTargetPackageName);
} catch (Exception e10) {
e10.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
SamsungBindInstallAgentService.this.logger.mo2231d( SamsungBindInstallAgentService.this.getClass().getSimpleName() + " : onServiceDisconnected() called with: className = [" + componentName + "]");
SamsungBindInstallAgentService.this.installAgentAPI = null;
SamsungBindInstallAgentService.this.mBound = false;
SamsungBindInstallAgentService.this.isBinding = false;
}
};
Realme
Some versions of AppHub reference com.applovin.oem.p036am.device.realme.RealmeDownloader, which through its name indicates that it is a Realme library to perform downloads.
According to a May 28, 2023 change analysis, RealMe added AppHub to its phones beginning in its F.07 update.
Cricket, Oppo, Orange, Sliide, Tinno
In AppHub code, I found references to Cricket, Oppo, Orange, Sliide, Tinno. But I didn’t see full installation integrations for these carriers within the AppHub versions I received. That said, it seems AppLovin provides a different AppHub APK for each partner. When I bought a device from T-Mobile, I naturally received a phone with the T-Mobile AppHub APK. The lack of other carriers’ AppHub APKs on a T-Mobile device should not be seen as a surprise.
An AppLovin press release describes a partnership with OPPO for “mobile app recommendations that “connect[] users with a wide variety of apps, from popular games to productivity tools, designed to cater to the unique preferences of each user.” This could be a euphemism for installing games when users merely view ads for those apps.
Xiaomi and TCL
The Culper report discusses AppLovin AppHub partnerships with Xiaomi and TCL. I did not find evidence of integration between AppHub and these companies in the code I reviewed. Here too, there could be other APK versions that implement these integrations.