Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.d4rk.androidtutorials.java.ads.managers;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
Expand All @@ -11,6 +13,7 @@

import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.d4rk.androidtutorials.java.R;
import com.google.android.gms.ads.AdListener;
Expand Down Expand Up @@ -49,7 +52,7 @@ public static void load(@NonNull Context context,
@NonNull AdRequest adRequest,
@androidx.annotation.Nullable AdListener listener) {
AdLoader.Builder builder = new AdLoader.Builder(context, context.getString(R.string.native_ad_banner_unit_id))
.forNativeAd(nativeAd -> {
.forNativeAd(nativeAd -> postToMainThread(() -> {
LayoutInflater inflater = LayoutInflater.from(context);
NativeAdView adView = (NativeAdView) inflater.inflate(layoutRes, container, false);
adView.setLayoutParams(new ViewGroup.LayoutParams(
Expand All @@ -59,22 +62,116 @@ public static void load(@NonNull Context context,
container.getPaddingRight(), container.getPaddingBottom());
container.setPadding(0, 0, 0, 0);
populateNativeAdView(nativeAd, adView);
container.setVisibility(View.VISIBLE);
container.removeAllViews();
container.addView(adView);
container.requestLayout();
}));

builder.withAdListener(createAdListener(container, listener));

AdLoader adLoader = builder.build();
adLoader.loadAd(adRequest);
}

private static AdListener createAdListener(@NonNull ViewGroup container,
@androidx.annotation.Nullable AdListener listener) {
return new AdListener() {
@Override
public void onAdLoaded() {
postToMainThread(() -> {
if (listener != null) {
listener.onAdLoaded();
}
});
}

builder.withAdListener(listener != null ? listener : new AdListener() {
@Override
public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
Log.w(TAG, "Failed to load native ad: " + loadAdError.getMessage());
container.removeAllViews();
container.setVisibility(View.GONE);
postToMainThread(() -> {
Log.w(TAG, "Failed to load native ad: " + loadAdError.getMessage());
container.removeAllViews();
container.setVisibility(View.GONE);
if (listener != null) {
listener.onAdFailedToLoad(loadAdError);
}
});
}
});

AdLoader adLoader = builder.build();
adLoader.loadAd(adRequest);
@Override
public void onAdOpened() {
postToMainThread(() -> {
if (listener != null) {
listener.onAdOpened();
}
});
}

@Override
public void onAdClosed() {
postToMainThread(() -> {
if (listener != null) {
listener.onAdClosed();
}
});
}

@Override
public void onAdClicked() {
postToMainThread(() -> {
if (listener != null) {
listener.onAdClicked();
}
});
}

@Override
public void onAdImpression() {
postToMainThread(() -> {
if (listener != null) {
listener.onAdImpression();
}
});
}
};
}

interface MainThreadExecutor {
void post(@NonNull Runnable runnable);
}

private static final class HandlerMainThreadExecutor implements MainThreadExecutor {
private final Handler handler;

private HandlerMainThreadExecutor() {
Looper looper = Looper.getMainLooper();
handler = looper != null ? new Handler(looper) : null;
}

@Override
public void post(@NonNull Runnable runnable) {
if (handler != null) {
handler.post(runnable);
} else {
runnable.run();
}
}
}

private static MainThreadExecutor mainThreadExecutor = new HandlerMainThreadExecutor();

private static void postToMainThread(@NonNull Runnable runnable) {
mainThreadExecutor.post(runnable);
}

@VisibleForTesting
static void setMainThreadExecutorForTesting(@NonNull MainThreadExecutor executor) {
mainThreadExecutor = executor;
}

@VisibleForTesting
static void resetMainThreadExecutorForTesting() {
mainThreadExecutor = new HandlerMainThreadExecutor();
}

private static void populateNativeAdView(@NonNull NativeAd nativeAd, @NonNull NativeAdView adView) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.d4rk.androidtutorials.java.ads.managers;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
Expand All @@ -16,8 +18,10 @@

import com.d4rk.androidtutorials.java.ads.AdUtils;
import com.d4rk.androidtutorials.java.ads.managers.AppOpenAd.OnShowAdCompleteListener;
import com.google.android.gms.ads.AdError;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.FullScreenContentCallback;
import com.google.android.gms.ads.LoadAdError;
import com.google.android.gms.ads.appopen.AppOpenAd.AppOpenAdLoadCallback;

import org.junit.Before;
Expand Down Expand Up @@ -131,6 +135,101 @@ public void showAdIfAvailable_withAd_doesNotShowTwiceWhileShowing() throws Excep
verify(ad, times(1)).show(activity);
}

@Test
public void showAdIfAvailable_withAd_handlesDismissAndReloads() throws Exception {
Activity activity = mock(Activity.class);
OnShowAdCompleteListener listener = mock(OnShowAdCompleteListener.class);
com.google.android.gms.ads.appopen.AppOpenAd ad = mock(com.google.android.gms.ads.appopen.AppOpenAd.class);

setField("appOpenAd", ad);
setLongField("loadTime", System.currentTimeMillis());

try (MockedStatic<AdUtils> adUtils = mockStatic(AdUtils.class);
MockedStatic<com.google.android.gms.ads.appopen.AppOpenAd> appOpenAdStatic =
mockStatic(com.google.android.gms.ads.appopen.AppOpenAd.class)) {
appOpenAdStatic
.when(() -> com.google.android.gms.ads.appopen.AppOpenAd.load(
any(Context.class),
anyString(),
any(AdRequest.class),
any(AppOpenAdLoadCallback.class)))
.thenAnswer(invocation -> {
AppOpenAdLoadCallback callback = invocation.getArgument(3);
callback.onAdLoaded(mock(com.google.android.gms.ads.appopen.AppOpenAd.class));
return null;
});

invokeShowAdIfAvailable(activity, listener);

ArgumentCaptor<FullScreenContentCallback> callbackCaptor =
ArgumentCaptor.forClass(FullScreenContentCallback.class);
verify(ad).setFullScreenContentCallback(callbackCaptor.capture());
verify(ad).show(activity);
assertTrue(getBooleanField("isShowingAd"));

callbackCaptor.getValue().onAdDismissedFullScreenContent();

assertFalse(getBooleanField("isShowingAd"));
assertFalse(getBooleanField("isLoadingAd"));
assertNotNull(getFieldValue("appOpenAd"));
verify(listener, times(1)).onShowAdComplete();
adUtils.verify(() -> AdUtils.initialize(any(Context.class)));
appOpenAdStatic.verify(() -> com.google.android.gms.ads.appopen.AppOpenAd.load(
any(Context.class),
anyString(),
any(AdRequest.class),
any(AppOpenAdLoadCallback.class)));
}
}

@Test
public void showAdIfAvailable_withAd_handlesShowFailureAndReloadFailure() throws Exception {
Activity activity = mock(Activity.class);
OnShowAdCompleteListener listener = mock(OnShowAdCompleteListener.class);
com.google.android.gms.ads.appopen.AppOpenAd ad = mock(com.google.android.gms.ads.appopen.AppOpenAd.class);
LoadAdError loadAdError = mock(LoadAdError.class);
AdError adError = mock(AdError.class);

setField("appOpenAd", ad);
setLongField("loadTime", System.currentTimeMillis());

try (MockedStatic<AdUtils> adUtils = mockStatic(AdUtils.class);
MockedStatic<com.google.android.gms.ads.appopen.AppOpenAd> appOpenAdStatic =
mockStatic(com.google.android.gms.ads.appopen.AppOpenAd.class)) {
appOpenAdStatic
.when(() -> com.google.android.gms.ads.appopen.AppOpenAd.load(
any(Context.class),
anyString(),
any(AdRequest.class),
any(AppOpenAdLoadCallback.class)))
.thenAnswer(invocation -> {
AppOpenAdLoadCallback callback = invocation.getArgument(3);
callback.onAdFailedToLoad(loadAdError);
return null;
});

invokeShowAdIfAvailable(activity, listener);

ArgumentCaptor<FullScreenContentCallback> callbackCaptor =
ArgumentCaptor.forClass(FullScreenContentCallback.class);
verify(ad).setFullScreenContentCallback(callbackCaptor.capture());
verify(ad).show(activity);

callbackCaptor.getValue().onAdFailedToShowFullScreenContent(adError);

assertFalse(getBooleanField("isShowingAd"));
assertFalse(getBooleanField("isLoadingAd"));
assertNull(getFieldValue("appOpenAd"));
verify(listener, times(1)).onShowAdComplete();
adUtils.verify(() -> AdUtils.initialize(any(Context.class)));
appOpenAdStatic.verify(() -> com.google.android.gms.ads.appopen.AppOpenAd.load(
any(Context.class),
anyString(),
any(AdRequest.class),
any(AppOpenAdLoadCallback.class)));
}
}

private Class<?> findManagerClass() {
for (Class<?> clazz : AppOpenAd.class.getDeclaredClasses()) {
if ("AppOpenAdManager".equals(clazz.getSimpleName())) {
Expand All @@ -156,6 +255,18 @@ private void invokeShowAdIfAvailable(Activity activity, OnShowAdCompleteListener
method.invoke(manager, activity, listener);
}

private boolean getBooleanField(String fieldName) throws Exception {
Field field = managerClass.getDeclaredField(fieldName);
field.setAccessible(true);
return field.getBoolean(manager);
}

private Object getFieldValue(String fieldName) throws Exception {
Field field = managerClass.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(manager);
}

private void setField(String fieldName, Object value) throws Exception {
Field field = managerClass.getDeclaredField(fieldName);
field.setAccessible(true);
Expand Down
Loading
Loading