This post is part of AppLovin Nonconsensual Installs > Execution Path. See important disclosures.
First, the AppHub AndroidManifest.xml creates a service called AppHubService, which other apps connect to:
<service
android:name="com.applovin.oem.p431am.android.external.AppHubService"
android:exported="true">
<intent-filter>
<action android:name="com.applovin.am.intent.action.APPHUB_SERVICE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
Then other apps connect to AppHubService. For example, the Save The Girl manifest states:
<uses-permission android:name="com.applovin.array.apphub.permission.BIND_APPHUB_SERVICE"/>
I examined a variety of user complaints relating to unwanted installations while viewing ads within other apps. When I spot-checked the apps giving rise to complaints, they all had a BIND_APPHUB_SERVICE line in its permission. I did not see complaints pertaining to apps that lacked this permission.
Games embed the AppLovin SDK which sends execution to AppHubService
Games (and other apps) embed the AppLovin SDK in order to show AppLovin ads. Within that SDK, the AppLovinAdServiceImpl trackAndLaunchClick() click handler decides what to do when a user taps an ad. If Direct Downloads are enabled, execution flows to startDirectInstallOrDownloadProcess().
public void trackAndLaunchClick(final AbstractC1760e abstractC1760e, final AppLovinAdView appLovinAdView, final C1562b c1562b, final Uri uri, MotionEvent motionEvent, boolean z, Bundle bundle) { if (abstractC1760e == null) { if (C1886x.m8960Fn()) { this.logger.m8974i("AppLovinAdService", "Unable to track ad view click. No ad specified"); return; } return; } if (bundle != null && Boolean.parseBoolean(bundle.getString("skip_click_tracking"))) { if (C1886x.m8960Fn()) { this.logger.m8971f("AppLovinAdService", "Skipping tracking for click on an ad..."); } } else { if (C1886x.m8960Fn()) { this.logger.m8971f("AppLovinAdService", "Tracking click on an ad..."); } boolean z2 = bundle != null && Boolean.parseBoolean(bundle.getString("install_click")); maybeSubmitPersistentPostbacks(abstractC1760e.m7560a(motionEvent, z, z2)); if (this.sdk.m8051BW() != null) { this.sdk.m8051BW().m7426a(abstractC1760e.m7563d(motionEvent, false, z2), motionEvent); } } if (appLovinAdView != null && uri != null) { if (abstractC1760e.isDirectDownloadEnabled()) { this.sdk.m8093Cr().startDirectInstallOrDownloadProcess(abstractC1760e, bundle, new ArrayService.DirectDownloadListener() { @Override public void onAppDetailsDisplayed() { AppLovinAdServiceImpl.this.sdk.m8080Ce().pauseForClick(); C1562b c1562b2 = c1562b; if (c1562b2 != null) { c1562b2.m5913qX(); C1870m.m8646a(c1562b.m5907qQ(), abstractC1760e, appLovinAdView); } } ...
startDirectInstallOrDownloadProcess() sends execution to appHubService.showDirectDownloadAppDetailsWithExtra()
public void startDirectInstallOrDownloadProcess(ArrayDirectDownloadAd arrayDirectDownloadAd, Bundle bundle, DirectDownloadListener directDownloadListener) {
if (this.appHubService == null) {
if (C1886x.m8960Fn()) {
this.logger.m8974i(TAG, "Cannot begin Direct Install / Download process - service disconnected");
}
directDownloadListener.onFailure();
return;
}
if (!arrayDirectDownloadAd.isDirectDownloadEnabled()) {
if (C1886x.m8960Fn()) {
this.logger.m8974i(TAG, "Cannot begin Direct Install / Download process - missing token");
}
directDownloadListener.onFailure();
return;
}
try {
Bundle directDownloadParameters = arrayDirectDownloadAd.getDirectDownloadParameters();
if (bundle != null) {
directDownloadParameters.putAll(bundle);
}
this.currentDownloadState = new DirectDownloadState(arrayDirectDownloadAd.getDirectDownloadToken(), directDownloadParameters, directDownloadListener);
if (C1886x.m8960Fn()) {
this.logger.m8971f(TAG, "Starting Direct Download Activity");
}
if (this.appHubVersionCode >= 21) {
this.appHubService.showDirectDownloadAppDetailsWithExtra(this.currentDownloadState.adToken, this.currentDownloadState.parameters, this);
} else {
this.appHubService.showDirectDownloadAppDetails(this.currentDownloadState.adToken, this);
} ...
showDirectDownloadAppDetailsWithExtra() sends execution to AppHub using mRemote.transact():
public void showDirectDownloadAppDetailsWithExtra(String str, Bundle bundle, IAppHubDirectDownloadServiceCallback iAppHubDirectDownloadServiceCallback) throws RemoteException {
Parcel parcelObtain = Parcel.obtain();
try {
parcelObtain.writeInterfaceToken(Stub.DESCRIPTOR);
parcelObtain.writeString(str);
if (bundle != null) {
parcelObtain.writeInt(1);
bundle.writeToParcel(parcelObtain, 0);
} else {
parcelObtain.writeInt(0);
}
parcelObtain.writeStrongBinder(iAppHubDirectDownloadServiceCallback != null ? iAppHubDirectDownloadServiceCallback.asBinder() : null);
if (this.mRemote.transact(6, parcelObtain, null, 1) || Stub.getDefaultImpl() == null) {
return;
}
Stub.getDefaultImpl().showDirectDownloadAppDetailsWithExtra(str, bundle, iAppHubDirectDownloadServiceCallback);
} finally {
parcelObtain.recycle();
}
}
A constant in IAppHubService’s class definition states that parameter value 6 signifies TRANSACTION_showDirectDownloadAppDetailsWithExtra:
static final int TRANSACTION_showDirectDownloadAppDetailsWithExtra = 6;
AppHub receives execution and proceeds towards DirectDownloadActivity