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.