diff --git a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/CustomAdFactory.java b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/CustomAdFactory.java new file mode 100644 index 000000000..404753dd1 --- /dev/null +++ b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/CustomAdFactory.java @@ -0,0 +1,12 @@ +package io.flutter.plugins.googlemobileads; + +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.libraries.ads.mobile.sdk.nativead.CustomNativeAd; +import java.util.Map; + +public interface CustomAdFactory { + View createCustomAd( + @NonNull CustomNativeAd customNativeAd, @Nullable Map customOptions); +} diff --git a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAd.java b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAd.java index a6fd9b86b..7ce6baa1b 100644 --- a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAd.java +++ b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAd.java @@ -335,7 +335,10 @@ static class FlutterLoadAdError { if (error.getResponseInfo() != null) { responseInfo = new FlutterResponseInfo(error.getResponseInfo()); - domain = responseInfo.getMediationAdapterClassName(); + String adapterClassName = responseInfo.getMediationAdapterClassName(); + domain = adapterClassName != null + ? adapterClassName + : "com.google.android.libraries.ads.mobile.sdk"; } else { domain = "com.google.android.libraries.ads.mobile.sdk"; } diff --git a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdListener.java b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdListener.java index b7ea8ff13..ac3fad6b7 100644 --- a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdListener.java +++ b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdListener.java @@ -21,6 +21,7 @@ import com.google.android.libraries.ads.mobile.sdk.common.AdValue; import com.google.android.libraries.ads.mobile.sdk.common.FullScreenContentError; import com.google.android.libraries.ads.mobile.sdk.common.LoadAdError; +import com.google.android.libraries.ads.mobile.sdk.nativead.CustomNativeAd; import com.google.android.libraries.ads.mobile.sdk.nativead.NativeAd; import com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdEventCallback; import com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdLoaderCallback; @@ -165,4 +166,25 @@ public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) { callbackWeakReference.get().onAdFailedToLoad(loadAdError); } } + + @Override + public void onBannerAdLoaded(@NonNull BannerAd bannerAd) { + if (callbackWeakReference.get() != null) { + callbackWeakReference.get().onBannerAdLoaded(bannerAd); + } + } + + @Override + public void onCustomNativeAdLoaded(@NonNull CustomNativeAd customNativeAd) { + if (callbackWeakReference.get() != null) { + callbackWeakReference.get().onCustomNativeAdLoaded(customNativeAd); + } + } + + @Override + public void onNativeAdLoaded(@NonNull NativeAd nativeAd) { + if (callbackWeakReference.get() != null) { + callbackWeakReference.get().onNativeAdLoaded(nativeAd); + } + } } diff --git a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdLoader.java b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdLoader.java index c582a3070..4e796f3dd 100644 --- a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdLoader.java +++ b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdLoader.java @@ -17,13 +17,16 @@ import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.gms.ads.nativead.NativeAdOptions; import com.google.android.libraries.ads.mobile.sdk.appopen.AppOpenAd; +import com.google.android.libraries.ads.mobile.sdk.common.AdChoicesPlacement; import com.google.android.libraries.ads.mobile.sdk.common.AdLoadCallback; import com.google.android.libraries.ads.mobile.sdk.common.AdRequest; import com.google.android.libraries.ads.mobile.sdk.common.VideoOptions; import com.google.android.libraries.ads.mobile.sdk.interstitial.InterstitialAd; import com.google.android.libraries.ads.mobile.sdk.nativead.NativeAd.NativeAdType; +import com.google.android.libraries.ads.mobile.sdk.nativead.NativeAd.NativeMediaAspectRatio; import com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdLoader; import com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdLoaderCallback; import com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdRequest; @@ -124,19 +127,102 @@ public void loadAdManagerNativeAd( /** Load an ad loader ad. */ public void loadAdLoaderAd( @NonNull String adUnitId, - @NonNull NativeAdLoaderCallback adLoaderAdLoaderCallback) { + @NonNull FlutterAdRequest request, + @NonNull NativeAdLoaderCallback adLoaderAdLoaderCallback, + @Nullable FlutterAdLoaderAd.BannerParameters bannerParameters, + @Nullable FlutterAdLoaderAd.CustomParameters customParameters, + @Nullable FlutterAdLoaderAd.NativeParameters nativeParameters) { List nativeAdTypes = new ArrayList<>(); + if (bannerParameters != null) { + nativeAdTypes.add(NativeAdType.BANNER); + } + + if (customParameters != null) { + nativeAdTypes.add(NativeAdType.CUSTOM_NATIVE); + } + + if (nativeParameters != null) { + nativeAdTypes.add(NativeAdType.NATIVE); + } + + NativeAdRequest.Builder builder = new NativeAdRequest.Builder(adUnitId, nativeAdTypes); + + if (bannerParameters != null) { + builder.setAdSizes(bannerParameters.adSizes); + if (bannerParameters.adManagerAdViewOptions != null) { + builder.setManualImpressionEnabled(bannerParameters.adManagerAdViewOptions.manualImpressionsEnabled); + } + } + + if (customParameters != null) { + builder.setCustomFormatIds(new ArrayList<>(customParameters.factories.keySet())); + } + + if (nativeParameters != null && nativeParameters.nativeAdOptions != null) { + builder.setAdChoicesPlacement(convertAdChoicesPlacement(nativeParameters.nativeAdOptions.getAdChoicesPlacement())); + builder.setMediaAspectRatio(convertMediaAspectRatio(nativeParameters.nativeAdOptions.getMediaAspectRatio())); + com.google.android.gms.ads.VideoOptions videoOptions = nativeParameters.nativeAdOptions.getVideoOptions(); + if (videoOptions != null) { + builder.setVideoOptions(convertVideoOptions(videoOptions)); + } + } + + request.configureNativeAdRequestBuilder(builder, adUnitId); + NativeAdLoader.load( - new NativeAdRequest.Builder(adUnitId, nativeAdTypes) - .build(), + builder.build(), adLoaderAdLoaderCallback); } /** Load an ad manager ad loader ad. */ public void loadAdManagerAdLoaderAd( @NonNull String adUnitId, - @NonNull NativeAdLoaderCallback adLoaderAdLoaderCallback) { - loadAdLoaderAd(adUnitId, adLoaderAdLoaderCallback); + @NonNull FlutterAdManagerAdRequest adManagerRequest, + @NonNull NativeAdLoaderCallback adLoaderAdLoaderCallback, + @Nullable FlutterAdLoaderAd.BannerParameters bannerParameters, + @Nullable FlutterAdLoaderAd.CustomParameters customParameters, + @Nullable FlutterAdLoaderAd.NativeParameters nativeParameters) { + loadAdLoaderAd(adUnitId, adManagerRequest, adLoaderAdLoaderCallback, bannerParameters, customParameters, nativeParameters); + } + + static AdChoicesPlacement convertAdChoicesPlacement(int adChoicesPlacement) { + switch(adChoicesPlacement) { + case NativeAdOptions.ADCHOICES_BOTTOM_LEFT: + return AdChoicesPlacement.BOTTOM_LEFT; + case NativeAdOptions.ADCHOICES_BOTTOM_RIGHT: + return AdChoicesPlacement.BOTTOM_RIGHT; + case NativeAdOptions.ADCHOICES_TOP_LEFT: + return AdChoicesPlacement.TOP_LEFT; + case NativeAdOptions.ADCHOICES_TOP_RIGHT: + return AdChoicesPlacement.TOP_RIGHT; + default: + return AdChoicesPlacement.TOP_RIGHT; + } + } + + static NativeMediaAspectRatio convertMediaAspectRatio(int mediaAspectRatio) { + switch(mediaAspectRatio) { + case NativeAdOptions.NATIVE_MEDIA_ASPECT_RATIO_ANY: + return NativeMediaAspectRatio.ANY; + case NativeAdOptions.NATIVE_MEDIA_ASPECT_RATIO_LANDSCAPE: + return NativeMediaAspectRatio.LANDSCAPE; + case NativeAdOptions.NATIVE_MEDIA_ASPECT_RATIO_PORTRAIT: + return NativeMediaAspectRatio.PORTRAIT; + case NativeAdOptions.NATIVE_MEDIA_ASPECT_RATIO_SQUARE: + return NativeMediaAspectRatio.SQUARE; + case NativeAdOptions.NATIVE_MEDIA_ASPECT_RATIO_UNKNOWN: + return NativeMediaAspectRatio.UNKNOWN; + default: + return NativeMediaAspectRatio.UNKNOWN; + } + } + + static VideoOptions convertVideoOptions(com.google.android.gms.ads.VideoOptions videoOptions) { + return new VideoOptions.Builder() + .setClickToExpandRequested(videoOptions.getClickToExpandRequested()) + .setCustomControlsRequested(videoOptions.getCustomControlsRequested()) + .setStartMuted(videoOptions.getStartMuted()) + .build(); } } diff --git a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAd.java b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAd.java index 8f9bce672..81060789a 100644 --- a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAd.java +++ b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAd.java @@ -1,10 +1,20 @@ package io.flutter.plugins.googlemobileads; import android.util.Log; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.google.android.gms.ads.nativead.NativeAdOptions; +import com.google.android.libraries.ads.mobile.sdk.banner.AdSize; +import com.google.android.libraries.ads.mobile.sdk.banner.AdView; +import com.google.android.libraries.ads.mobile.sdk.banner.BannerAd; import com.google.android.libraries.ads.mobile.sdk.common.LoadAdError; +import com.google.android.libraries.ads.mobile.sdk.nativead.CustomNativeAd; +import com.google.android.libraries.ads.mobile.sdk.nativead.NativeAd; import com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdLoaderCallback; +import io.flutter.plugin.platform.PlatformView; +import java.util.List; +import java.util.Map; class FlutterAdLoaderAd extends FlutterAd implements FlutterAdLoadedListener, NativeAdLoaderCallback { private static final String TAG = "FlutterAdLoaderAd"; @@ -16,6 +26,10 @@ class FlutterAdLoaderAd extends FlutterAd implements FlutterAdLoadedListener, Na @Nullable private FlutterAdManagerAdRequest adManagerRequest; @Nullable private AdLoaderAdType type; @Nullable private String formatId; + @Nullable private View view; + @Nullable protected BannerParameters bannerParameters; + @Nullable protected CustomParameters customParameters; + @Nullable protected NativeParameters nativeParameters; static class Builder { @Nullable private AdInstanceManager manager; @@ -24,6 +38,11 @@ static class Builder { @Nullable private FlutterAdManagerAdRequest adManagerRequest; @Nullable private Integer id; @Nullable private FlutterAdLoader adLoader; + @Nullable private FlutterBannerParameters bannerParameters; + @Nullable private FlutterCustomParameters customParameters; + @Nullable private Map customFactories; + @Nullable private FlutterNativeParameters nativeParameters; + @Nullable private Map nativeFactories; public Builder setId(int id) { this.id = id; @@ -50,11 +69,38 @@ public Builder setAdManagerRequest(@NonNull FlutterAdManagerAdRequest adManagerR return this; } + public Builder setBanner(@Nullable FlutterBannerParameters bannerParameters) { + this.bannerParameters = bannerParameters; + return this; + } + + public Builder setCustom(@Nullable FlutterCustomParameters customParameters) { + this.customParameters = customParameters; + return this; + } + public Builder setFlutterAdLoader(@NonNull FlutterAdLoader adLoader) { this.adLoader = adLoader; return this; } + public Builder withAvailableCustomFactories( + @NonNull Map customFactories) { + this.customFactories = customFactories; + return this; + } + + public Builder setNative(@Nullable FlutterNativeParameters nativeParameters) { + this.nativeParameters = nativeParameters; + return this; + } + + public Builder withAvailableNativeFactories( + @NonNull Map nativeFactories) { + this.nativeFactories = nativeFactories; + return this; + } + FlutterAdLoaderAd build() { if (manager == null) { throw new IllegalStateException("manager must be provided"); @@ -75,10 +121,62 @@ FlutterAdLoaderAd build() { } else { adLoaderAd = new FlutterAdLoaderAd(id, manager, adUnitId, request, adLoader); } + + if (bannerParameters != null) { + adLoaderAd.bannerParameters = bannerParameters.asBannerParameters(); + } + + if (customParameters != null) { + adLoaderAd.customParameters = customParameters.asCustomParameters(customFactories); + } + + if (nativeParameters != null) { + adLoaderAd.nativeParameters = nativeParameters.asNativeParameters(nativeFactories); + } + return adLoaderAd; } } + static class BannerParameters { + @NonNull final List adSizes; + @Nullable final FlutterAdManagerAdViewOptions adManagerAdViewOptions; + + BannerParameters( + @NonNull List adSizes, + @Nullable FlutterAdManagerAdViewOptions adManagerAdViewOptions) { + this.adSizes = adSizes; + this.adManagerAdViewOptions = adManagerAdViewOptions; + } + } + + static class CustomParameters { + @NonNull final Map factories; + @Nullable final Map viewOptions; + + CustomParameters( + @NonNull Map factories, + @Nullable Map viewOptions) { + this.factories = factories; + this.viewOptions = viewOptions; + } + } + + static class NativeParameters { + @NonNull final NativeAdFactory factory; + @Nullable final NativeAdOptions nativeAdOptions; + @Nullable final Map viewOptions; + + NativeParameters( + @NonNull NativeAdFactory factory, + @Nullable NativeAdOptions nativeAdOptions, + @Nullable Map viewOptions) { + this.factory = factory; + this.nativeAdOptions = nativeAdOptions; + this.viewOptions = viewOptions; + } + } + protected FlutterAdLoaderAd( int adId, @NonNull AdInstanceManager manager, @@ -119,9 +217,11 @@ void load() { // Note we delegate loading the ad to FlutterAdLoader mainly for testing purposes. // As of 20.0.0 of GMA, mockito is unable to mock AdLoader. if (request != null) { - adLoader.loadAdLoaderAd(adUnitId, loadedListener); + adLoader.loadAdLoaderAd( + adUnitId, request, loadedListener, bannerParameters, customParameters, nativeParameters); } else if (adManagerRequest != null) { - adLoader.loadAdManagerAdLoaderAd(adUnitId, loadedListener); + adLoader.loadAdManagerAdLoaderAd( + adUnitId, adManagerRequest, loadedListener, bannerParameters, customParameters, nativeParameters); } else { Log.e(TAG, "A null or invalid ad request was provided."); } @@ -134,7 +234,14 @@ AdLoaderAdType getAdLoaderAdType() { @Nullable FlutterAdSize getAdSize() { - return null; + if (!(view instanceof AdView)) { + return null; + } + final BannerAd bannerAd = ((AdView) view).getBannerAd(); + if (bannerAd == null) { + return null; + } + return new FlutterAdSize(bannerAd.getAdSize()); } @Nullable @@ -143,7 +250,19 @@ String getFormatId() { } @Override - public void onAdLoaded() {} + @Nullable + public PlatformView getPlatformView() { + if (view == null) { + return null; + } + + return new FlutterPlatformView(view); + } + + @Override + public void onAdLoaded() { + // handled by onBannerAdLoaded + } @Override public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) { @@ -151,5 +270,53 @@ public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) { } @Override - void dispose() {} + public void onBannerAdLoaded(@NonNull BannerAd bannerAd) { + AdView adView = new AdView(manager.getActivity()); + adView.registerBannerAd(bannerAd, manager.getActivity()); + + view = adView; + type = AdLoaderAdType.BANNER; + + bannerAd.setAdEventCallback(new FlutterBannerAdListener(adId, manager, this)); + + manager.onAdLoaded(adId, bannerAd.getResponseInfo()); + } + + @Override + public void onCustomNativeAdLoaded(@NonNull CustomNativeAd customNativeAd) { + formatId = customNativeAd.getCustomFormatId(); + view = + customParameters + .factories + .get(formatId) + .createCustomAd(customNativeAd, customParameters.viewOptions); + type = AdLoaderAdType.CUSTOM; + + customNativeAd.setAdEventCallback(new FlutterNativeAdListener(adId, manager)); + + manager.onAdLoaded(adId, customNativeAd.getResponseInfo()); + } + + @Override + public void onNativeAdLoaded(@NonNull NativeAd nativeAd) { + view = nativeParameters.factory.createNativeAd(nativeAd, nativeParameters.viewOptions); + type = AdLoaderAdType.NATIVE; + + nativeAd.setAdEventCallback(new FlutterNativeAdListener(adId, manager)); + + manager.onAdLoaded(adId, nativeAd.getResponseInfo()); + } + + @Override + void dispose() { + if (view == null) { + return; + } + + if (view instanceof AdView) { + ((AdView) view).destroy(); + } + + view = null; + } } diff --git a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdRequest.java b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdRequest.java index da53ddd1a..f34c578a5 100644 --- a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdRequest.java +++ b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdRequest.java @@ -19,6 +19,7 @@ import com.google.android.libraries.ads.mobile.sdk.banner.AdSize; import com.google.android.libraries.ads.mobile.sdk.banner.BannerAdRequest; import com.google.android.libraries.ads.mobile.sdk.common.AdRequest; +import com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdRequest; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.List; import java.util.Map; @@ -149,6 +150,26 @@ protected BannerAdRequest.Builder toBannerAdRequestBuilder(String adUnitId, return builder; } + @Override + protected NativeAdRequest.Builder configureNativeAdRequestBuilder( + NativeAdRequest.Builder builder, String adUnitId) { + super.configureNativeAdRequestBuilder(builder, adUnitId); + if (customTargeting != null) { + for (final Map.Entry entry : customTargeting.entrySet()) { + builder.putCustomTargeting(entry.getKey(), entry.getValue()); + } + } + if (customTargetingLists != null) { + for (final Map.Entry> entry : customTargetingLists.entrySet()) { + builder.putCustomTargeting(entry.getKey(), entry.getValue()); + } + } + if (publisherProvidedId != null) { + builder.setPublisherProvidedId(publisherProvidedId); + } + return builder; + } + @Nullable protected Map getCustomTargeting() { return customTargeting; diff --git a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdViewOptions.java b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdViewOptions.java new file mode 100644 index 000000000..dc6f39064 --- /dev/null +++ b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdViewOptions.java @@ -0,0 +1,12 @@ +package io.flutter.plugins.googlemobileads; + +import androidx.annotation.Nullable; + +class FlutterAdManagerAdViewOptions { + + @Nullable final Boolean manualImpressionsEnabled; + + FlutterAdManagerAdViewOptions(@Nullable Boolean manualImpressionsEnabled) { + this.manualImpressionsEnabled = manualImpressionsEnabled; + } +} diff --git a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdRequest.java b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdRequest.java index 95ccf9b56..6fdda788a 100644 --- a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdRequest.java +++ b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterAdRequest.java @@ -23,6 +23,7 @@ import com.google.android.libraries.ads.mobile.sdk.banner.AdSize; import com.google.android.libraries.ads.mobile.sdk.banner.BannerAdRequest; import com.google.android.libraries.ads.mobile.sdk.common.AdRequest; +import com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdRequest; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.HashMap; import java.util.HashSet; @@ -324,6 +325,24 @@ protected BannerAdRequest.Builder toBannerAdRequestBuilder(String adUnitId, List return builder; } + /** Applies the common request properties onto a next-gen {@link NativeAdRequest.Builder}. */ + protected NativeAdRequest.Builder configureNativeAdRequestBuilder( + NativeAdRequest.Builder builder, String adUnitId) { + if (keywords != null) { + for (final String keyword : keywords) { + builder.addKeyword(keyword); + } + } + if (contentUrl != null) { + builder.setContentUrl(contentUrl); + } + if (neighboringContentUrls != null) { + builder.setNeighboringContentUrls(new HashSet(neighboringContentUrls)); + } + builder.setRequestAgent(requestAgent); + return builder; + } + AdRequest asAdRequest(String adUnitId) { return ((AdRequest.Builder) updateAdRequestBuilder(new AdRequest.Builder(adUnitId), adUnitId)) .build(); diff --git a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterBannerParameters.java b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterBannerParameters.java new file mode 100644 index 000000000..77e19c4e6 --- /dev/null +++ b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterBannerParameters.java @@ -0,0 +1,29 @@ +package io.flutter.plugins.googlemobileads; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.libraries.ads.mobile.sdk.banner.AdSize; +import java.util.ArrayList; +import java.util.List; + +class FlutterBannerParameters { + @NonNull final List sizes; + @Nullable final FlutterAdManagerAdViewOptions adManagerAdViewOptions; + + FlutterBannerParameters( + @NonNull List sizes, + @Nullable FlutterAdManagerAdViewOptions adManagerAdViewOptions) { + this.sizes = sizes; + this.adManagerAdViewOptions = adManagerAdViewOptions; + } + + FlutterAdLoaderAd.BannerParameters asBannerParameters() { + List adSizes = new ArrayList<>(sizes.size()); + for (FlutterAdSize size : sizes) { + adSizes.add(size.getAdSize()); + } + return new FlutterAdLoaderAd.BannerParameters( + adSizes, + adManagerAdViewOptions); + } +} diff --git a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterCustomParameters.java b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterCustomParameters.java new file mode 100644 index 000000000..7dec7bffb --- /dev/null +++ b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterCustomParameters.java @@ -0,0 +1,28 @@ +package io.flutter.plugins.googlemobileads; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugins.googlemobileads.CustomAdFactory; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +class FlutterCustomParameters { + @NonNull final List formatIds; + @Nullable final Map viewOptions; + + FlutterCustomParameters( + @NonNull List formatIds, @Nullable Map viewOptions) { + this.formatIds = formatIds; + this.viewOptions = viewOptions; + } + + FlutterAdLoaderAd.CustomParameters asCustomParameters( + @NonNull Map availableFactories) { + Map factories = new HashMap<>(); + for (String formatId : formatIds) { + factories.put(formatId, availableFactories.get(formatId)); + } + return new FlutterAdLoaderAd.CustomParameters(factories, viewOptions); + } +} diff --git a/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterNativeParameters.java b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterNativeParameters.java new file mode 100644 index 000000000..50abe3ea0 --- /dev/null +++ b/packages/google_mobile_ads/android/src/adsNextGenSdk/java/io/flutter/plugins/googlemobileads/FlutterNativeParameters.java @@ -0,0 +1,32 @@ +package io.flutter.plugins.googlemobileads; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.ads.nativead.NativeAdOptions; +import io.flutter.plugins.googlemobileads.NativeAdFactory; +import java.util.Map; + +class FlutterNativeParameters { + @NonNull final String factoryId; + @Nullable final FlutterNativeAdOptions nativeAdOptions; + @Nullable final Map viewOptions; + + FlutterNativeParameters( + @NonNull String factoryId, + @Nullable FlutterNativeAdOptions nativeAdOptions, + @Nullable Map viewOptions) { + this.factoryId = factoryId; + this.nativeAdOptions = nativeAdOptions; + this.viewOptions = viewOptions; + } + + FlutterAdLoaderAd.NativeParameters asNativeParameters( + @NonNull Map registeredFactories) { + NativeAdOptions nativeAdOptions = null; + if (this.nativeAdOptions != null) { + nativeAdOptions = this.nativeAdOptions.asNativeAdOptions(); + } + return new FlutterAdLoaderAd.NativeParameters( + registeredFactories.get(factoryId), nativeAdOptions, viewOptions); + } +} diff --git a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdLoaderAdType.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdLoaderAdType.java index f972e319f..799f0c3af 100644 --- a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdLoaderAdType.java +++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdLoaderAdType.java @@ -2,4 +2,7 @@ enum AdLoaderAdType { UNKNOWN, + BANNER, + CUSTOM, + NATIVE, } diff --git a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdMessageCodec.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdMessageCodec.java index 77c5382a9..92f248347 100644 --- a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdMessageCodec.java +++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/AdMessageCodec.java @@ -67,6 +67,10 @@ class AdMessageCodec extends StandardMessageCodec { private static final byte VALUE_NATIVE_TEMPLATE_TYPE = (byte) 152; private static final byte VALUE_COLOR = (byte) 153; private static final byte VALUE_MEDIATION_EXTRAS = (byte) 154; + private static final byte VALUE_AD_MANAGER_AD_VIEW_OPTIONS = (byte) 155; + private static final byte VALUE_BANNER_PARAMETERS = (byte) 156; + private static final byte VALUE_CUSTOM_PARAMETERS = (byte) 157; + private static final byte VALUE_NATIVE_PARAMETERS = (byte) 158; @NonNull Context context; @NonNull final FlutterAdSize.AdSizeFactory adSizeFactory; @@ -257,6 +261,26 @@ protected void writeValue(ByteArrayOutputStream stream, Object value) { writeValue(stream, Color.red(colorValue)); writeValue(stream, Color.green(colorValue)); writeValue(stream, Color.blue(colorValue)); + } else if (value instanceof FlutterAdManagerAdViewOptions) { + stream.write(VALUE_AD_MANAGER_AD_VIEW_OPTIONS); + FlutterAdManagerAdViewOptions options = (FlutterAdManagerAdViewOptions) value; + writeValue(stream, options.manualImpressionsEnabled); + } else if (value instanceof FlutterBannerParameters) { + stream.write(VALUE_BANNER_PARAMETERS); + FlutterBannerParameters bannerParameters = (FlutterBannerParameters) value; + writeValue(stream, bannerParameters.sizes); + writeValue(stream, bannerParameters.adManagerAdViewOptions); + } else if (value instanceof FlutterCustomParameters) { + stream.write(VALUE_CUSTOM_PARAMETERS); + FlutterCustomParameters customParameters = (FlutterCustomParameters) value; + writeValue(stream, customParameters.formatIds); + writeValue(stream, customParameters.viewOptions); + } else if (value instanceof FlutterNativeParameters) { + stream.write(VALUE_NATIVE_PARAMETERS); + FlutterNativeParameters nativeParameters = (FlutterNativeParameters) value; + writeValue(stream, nativeParameters.factoryId); + writeValue(stream, nativeParameters.nativeAdOptions); + writeValue(stream, nativeParameters.viewOptions); } else { super.writeValue(stream, value); } @@ -443,6 +467,21 @@ protected Object readValueOfType(byte type, ByteBuffer buffer) { final Integer green = (Integer) readValueOfType(buffer.get(), buffer); final Integer blue = (Integer) readValueOfType(buffer.get(), buffer); return new ColorDrawable(Color.argb(alpha, red, green, blue)); + case VALUE_AD_MANAGER_AD_VIEW_OPTIONS: + return new FlutterAdManagerAdViewOptions((Boolean) readValueOfType(buffer.get(), buffer)); + case VALUE_BANNER_PARAMETERS: + return new FlutterBannerParameters( + (List) readValueOfType(buffer.get(), buffer), + (FlutterAdManagerAdViewOptions) readValueOfType(buffer.get(), buffer)); + case VALUE_CUSTOM_PARAMETERS: + return new FlutterCustomParameters( + (List) readValueOfType(buffer.get(), buffer), + (Map) readValueOfType(buffer.get(), buffer)); + case VALUE_NATIVE_PARAMETERS: + return new FlutterNativeParameters( + (String) readValueOfType(buffer.get(), buffer), + (FlutterNativeAdOptions) readValueOfType(buffer.get(), buffer), + (Map) readValueOfType(buffer.get(), buffer)); default: return super.readValueOfType(type, buffer); } diff --git a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/GoogleMobileAdsPlugin.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/GoogleMobileAdsPlugin.java index a1b3645a5..4f1ccf903 100644 --- a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/GoogleMobileAdsPlugin.java +++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/GoogleMobileAdsPlugin.java @@ -60,6 +60,7 @@ private static T requireNonNull(T obj) { @Nullable private AppStateNotifier appStateNotifier; @Nullable private UserMessagingPlatformManager userMessagingPlatformManager; private final Map nativeAdFactories = new HashMap<>(); + private final Map customAdFactories = new HashMap<>(); @SuppressWarnings("deprecation") // Keeping for compatibility @Nullable @@ -123,6 +124,13 @@ public static boolean registerNativeAdFactory( return registerNativeAdFactory(gmaPlugin, factoryId, nativeAdFactory); } + public static boolean registerCustomAdFactory( + FlutterEngine engine, String formatId, CustomAdFactory customAdFactory) { + final GoogleMobileAdsPlugin gmaPlugin = + (GoogleMobileAdsPlugin) engine.getPlugins().get(GoogleMobileAdsPlugin.class); + return registerCustomAdFactory(gmaPlugin, formatId, customAdFactory); + } + /** * Registers a {@link MediationNetworkExtrasProvider} used to provide network extras to the plugin * when it creates ad requests. @@ -183,6 +191,19 @@ private static boolean registerNativeAdFactory( return plugin.addNativeAdFactory(factoryId, nativeAdFactory); } + private static boolean registerCustomAdFactory( + GoogleMobileAdsPlugin plugin, String formatId, CustomAdFactory customAdFactory) { + if (plugin == null) { + final String message = + String.format( + "Could not find a %s instance. The plugin may have not been registered.", + GoogleMobileAdsPlugin.class.getSimpleName()); + throw new IllegalStateException(message); + } + + return plugin.addCustomAdFactory(formatId, customAdFactory); + } + /** * Unregisters a {@link NativeAdFactory} * used to create {@link NativeAdView}s from a Native Ad @@ -205,6 +226,16 @@ public static NativeAdFactory unregisterNativeAdFactory(FlutterEngine engine, St return null; } + @Nullable + public static CustomAdFactory unregisterCustomAdFactory(FlutterEngine engine, String formatId) { + final FlutterPlugin gmaPlugin = engine.getPlugins().get(GoogleMobileAdsPlugin.class); + if (gmaPlugin != null) { + return ((GoogleMobileAdsPlugin) gmaPlugin).removeCustomAdFactory(formatId); + } + + return null; + } + private boolean addNativeAdFactory(String factoryId, NativeAdFactory nativeAdFactory) { if (nativeAdFactories.containsKey(factoryId)) { final String errorMessage = @@ -222,6 +253,23 @@ private NativeAdFactory removeNativeAdFactory(String factoryId) { return nativeAdFactories.remove(factoryId); } + private boolean addCustomAdFactory(String formatId, CustomAdFactory customAdFactory) { + if (customAdFactories.containsKey(formatId)) { + final String errorMessage = + String.format( + "A CustomAdFactory with the following formatId already exists: %s", formatId); + Log.e(GoogleMobileAdsPlugin.class.getSimpleName(), errorMessage); + return false; + } + + customAdFactories.put(formatId, customAdFactory); + return true; + } + + private CustomAdFactory removeCustomAdFactory(String formatId) { + return customAdFactories.remove(formatId); + } + @Override public void onAttachedToEngine(FlutterPluginBinding binding) { pluginBinding = binding; @@ -390,6 +438,30 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) result.success(null); break; case "loadAdLoaderAd": + final FlutterCustomParameters customParameters = + call.argument("custom"); + if (customParameters != null) { + for (String formatId : customParameters.formatIds) { + if (customAdFactories.get(formatId) == null) { + final String message = + String.format("Can't find CustomAdFactory with id: %s", formatId); + result.error("AdLoaderAdError", message, null); + return; + } + } + } + + final FlutterNativeParameters nativeParameters = + call.argument("native"); + if (nativeParameters != null) { + if (nativeAdFactories.get(nativeParameters.factoryId) == null) { + final String message = + String.format("Can't find NativeAdFactory with id: %s", nativeParameters.factoryId); + result.error("AdLoaderAdError", message, null); + return; + } + } + final FlutterAdLoaderAd adLoaderAd = new FlutterAdLoaderAd.Builder() .setManager(instanceManager) @@ -401,6 +473,11 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) adLoaderSupplier != null ? adLoaderSupplier.get() : new FlutterAdLoader(context)) + .setBanner(call.argument("banner")) + .setCustom(customParameters) + .withAvailableCustomFactories(customAdFactories) + .setNative(nativeParameters) + .withAvailableNativeFactories(nativeAdFactories) .build(); instanceManager.trackAd(adLoaderAd, call.argument("adId")); adLoaderAd.load(); diff --git a/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/CustomAdFactory.java b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/CustomAdFactory.java new file mode 100644 index 000000000..2fb47bce6 --- /dev/null +++ b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/CustomAdFactory.java @@ -0,0 +1,12 @@ +package io.flutter.plugins.googlemobileads; + +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.ads.nativead.NativeCustomFormatAd; +import java.util.Map; + +public interface CustomAdFactory { + View createCustomAd( + @NonNull NativeCustomFormatAd nativeAd, @Nullable Map customOptions); +} diff --git a/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdListener.java b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdListener.java index 065f74a98..1133e46e9 100644 --- a/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdListener.java +++ b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdListener.java @@ -16,8 +16,12 @@ import androidx.annotation.NonNull; import com.google.android.gms.ads.AdListener; import com.google.android.gms.ads.LoadAdError; +import com.google.android.gms.ads.admanager.AdManagerAdView; +import com.google.android.gms.ads.formats.OnAdManagerAdViewLoadedListener; import com.google.android.gms.ads.nativead.NativeAd; import com.google.android.gms.ads.nativead.NativeAd.OnNativeAdLoadedListener; +import com.google.android.gms.ads.nativead.NativeCustomFormatAd; +import com.google.android.gms.ads.nativead.NativeCustomFormatAd.OnCustomFormatAdLoadedListener; import java.lang.ref.WeakReference; /** Callback type to notify when an ad successfully loads. */ @@ -118,3 +122,37 @@ public void onNativeAdLoaded(@NonNull NativeAd nativeAd) { } } } + +/** {@link OnAdManagerAdViewLoadedListener} for banner ads. */ +class FlutterAdManagerAdViewLoadedListener implements OnAdManagerAdViewLoadedListener { + + private final WeakReference reference; + + FlutterAdManagerAdViewLoadedListener(OnAdManagerAdViewLoadedListener listener) { + reference = new WeakReference<>(listener); + } + + @Override + public void onAdManagerAdViewLoaded(AdManagerAdView adView) { + if (reference.get() != null) { + reference.get().onAdManagerAdViewLoaded(adView); + } + } +} + +/** {@link OnCustomFormatAdLoadedListener} for custom ads. */ +class FlutterCustomFormatAdLoadedListener implements OnCustomFormatAdLoadedListener { + + private final WeakReference reference; + + FlutterCustomFormatAdLoadedListener(OnCustomFormatAdLoadedListener listener) { + reference = new WeakReference<>(listener); + } + + @Override + public void onCustomFormatAdLoaded(NativeCustomFormatAd ad) { + if (reference.get() != null) { + reference.get().onCustomFormatAdLoaded(ad); + } + } +} diff --git a/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdLoader.java b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdLoader.java index d95a6a6b2..3fcc804e5 100644 --- a/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdLoader.java +++ b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdLoader.java @@ -16,6 +16,7 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.gms.ads.AdListener; import com.google.android.gms.ads.AdLoader; import com.google.android.gms.ads.AdRequest; @@ -140,18 +141,59 @@ public void loadAdManagerNativeAd( /** Load an ad loader ad. */ public void loadAdLoaderAd( - @NonNull String adUnitId, @NonNull AdListener adListener, @NonNull AdRequest request) { - new AdLoader.Builder(context, adUnitId).withAdListener(adListener).build().loadAd(request); + @NonNull String adUnitId, + @NonNull AdListener adListener, + @NonNull AdRequest request, + @Nullable FlutterAdLoaderAd.BannerParameters bannerParameters, + @Nullable FlutterAdLoaderAd.CustomParameters customParameters, + @Nullable FlutterAdLoaderAd.NativeParameters nativeParameters) { + AdLoader.Builder builder = new AdLoader.Builder(context, adUnitId); + if (bannerParameters != null) { + builder = builder.forAdManagerAdView(bannerParameters.listener, bannerParameters.adSizes); + if (bannerParameters.adManagerAdViewOptions != null) { + builder.withAdManagerAdViewOptions(bannerParameters.adManagerAdViewOptions); + } + } + if (customParameters != null) { + for (String formatId : customParameters.factories.keySet()) { + builder = builder.forCustomFormatAd(formatId, customParameters.listener, null); + } + } + if (nativeParameters != null) { + builder = builder.forNativeAd(nativeParameters.listener); + if (nativeParameters.nativeAdOptions != null) { + builder = builder.withNativeAdOptions(nativeParameters.nativeAdOptions); + } + } + builder.withAdListener(adListener).build().loadAd(request); } /** Load an ad manager ad loader ad. */ public void loadAdManagerAdLoaderAd( @NonNull String adUnitId, @NonNull AdListener adListener, - @NonNull AdManagerAdRequest adManagerAdRequest) { - new AdLoader.Builder(context, adUnitId) - .withAdListener(adListener) - .build() - .loadAd(adManagerAdRequest); + @NonNull AdManagerAdRequest adManagerAdRequest, + @Nullable FlutterAdLoaderAd.BannerParameters bannerParameters, + @Nullable FlutterAdLoaderAd.CustomParameters customParameters, + @Nullable FlutterAdLoaderAd.NativeParameters nativeParameters) { + AdLoader.Builder builder = new AdLoader.Builder(context, adUnitId); + if (bannerParameters != null) { + builder = builder.forAdManagerAdView(bannerParameters.listener, bannerParameters.adSizes); + if (bannerParameters.adManagerAdViewOptions != null) { + builder.withAdManagerAdViewOptions(bannerParameters.adManagerAdViewOptions); + } + } + if (customParameters != null) { + for (String formatId : customParameters.factories.keySet()) { + builder = builder.forCustomFormatAd(formatId, customParameters.listener, null); + } + } + if (nativeParameters != null) { + builder = builder.forNativeAd(nativeParameters.listener); + if (nativeParameters.nativeAdOptions != null) { + builder = builder.withNativeAdOptions(nativeParameters.nativeAdOptions); + } + } + builder.withAdListener(adListener).build().loadAd(adManagerAdRequest); } } diff --git a/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAd.java b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAd.java index a329ee2c6..4f036c88d 100644 --- a/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAd.java +++ b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAd.java @@ -15,15 +15,31 @@ package io.flutter.plugins.googlemobileads; import android.util.Log; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.gms.ads.AdListener; +import com.google.android.gms.ads.AdSize; +import com.google.android.gms.ads.BaseAdView; +import com.google.android.gms.ads.admanager.AdManagerAdView; +import com.google.android.gms.ads.formats.AdManagerAdViewOptions; +import com.google.android.gms.ads.formats.OnAdManagerAdViewLoadedListener; +import com.google.android.gms.ads.nativead.NativeAd; +import com.google.android.gms.ads.nativead.NativeAd.OnNativeAdLoadedListener; +import com.google.android.gms.ads.nativead.NativeAdOptions; +import com.google.android.gms.ads.nativead.NativeCustomFormatAd; +import com.google.android.gms.ads.nativead.NativeCustomFormatAd.OnCustomFormatAdLoadedListener; +import io.flutter.plugin.platform.PlatformView; +import java.util.Map; /** * A central wrapper for {@link AdManagerAdView}, {@link NativeCustomFormatAd} and {@link NativeAd} * instances served for a single {@link AdRequest} or {@link AdManagerAdRequest} */ -class FlutterAdLoaderAd extends FlutterAd { +class FlutterAdLoaderAd extends FlutterAd + implements OnAdManagerAdViewLoadedListener, + OnCustomFormatAdLoadedListener, + OnNativeAdLoadedListener { private static final String TAG = "FlutterAdLoaderAd"; @NonNull private final AdInstanceManager manager; @@ -33,6 +49,10 @@ class FlutterAdLoaderAd extends FlutterAd { @Nullable private FlutterAdManagerAdRequest adManagerRequest; @Nullable private AdLoaderAdType type; @Nullable private String formatId; + @Nullable private View view; + @Nullable protected BannerParameters bannerParameters; + @Nullable protected CustomParameters customParameters; + @Nullable protected NativeParameters nativeParameters; static class Builder { @Nullable private AdInstanceManager manager; @@ -41,6 +61,11 @@ static class Builder { @Nullable private FlutterAdManagerAdRequest adManagerRequest; @Nullable private Integer id; @Nullable private FlutterAdLoader adLoader; + @Nullable private FlutterBannerParameters bannerParameters; + @Nullable private FlutterCustomParameters customParameters; + @Nullable private Map customFactories; + @Nullable private FlutterNativeParameters nativeParameters; + @Nullable private Map nativeFactories; public Builder setId(int id) { this.id = id; @@ -72,6 +97,33 @@ public Builder setFlutterAdLoader(@NonNull FlutterAdLoader adLoader) { return this; } + public Builder setBanner(@Nullable FlutterBannerParameters bannerParameters) { + this.bannerParameters = bannerParameters; + return this; + } + + public Builder setCustom(@Nullable FlutterCustomParameters customParameters) { + this.customParameters = customParameters; + return this; + } + + public Builder withAvailableCustomFactories( + @NonNull Map customFactories) { + this.customFactories = customFactories; + return this; + } + + public Builder setNative(@Nullable FlutterNativeParameters nativeParameters) { + this.nativeParameters = nativeParameters; + return this; + } + + public Builder withAvailableNativeFactories( + @NonNull Map nativeFactories) { + this.nativeFactories = nativeFactories; + return this; + } + FlutterAdLoaderAd build() { if (manager == null) { throw new IllegalStateException("manager must be provided"); @@ -92,10 +144,77 @@ FlutterAdLoaderAd build() { } else { adLoaderAd = new FlutterAdLoaderAd(id, manager, adUnitId, request, adLoader); } + + if (bannerParameters != null) { + adLoaderAd.bannerParameters = + bannerParameters.asBannerParameters( + new FlutterAdManagerAdViewLoadedListener(adLoaderAd)); + } + + if (customParameters != null) { + adLoaderAd.customParameters = + customParameters.asCustomParameters( + new FlutterCustomFormatAdLoadedListener(adLoaderAd), customFactories); + } + + if (nativeParameters != null) { + adLoaderAd.nativeParameters = + nativeParameters.asNativeParameters( + new FlutterNativeAdLoadedListener(adLoaderAd), nativeFactories); + } + return adLoaderAd; } } + static class BannerParameters { + @NonNull final OnAdManagerAdViewLoadedListener listener; + @NonNull final AdSize[] adSizes; + @Nullable final AdManagerAdViewOptions adManagerAdViewOptions; + + BannerParameters( + @NonNull OnAdManagerAdViewLoadedListener listener, + @NonNull AdSize[] adSizes, + @Nullable AdManagerAdViewOptions adManagerAdViewOptions) { + this.listener = listener; + this.adSizes = adSizes; + this.adManagerAdViewOptions = adManagerAdViewOptions; + } + } + + static class CustomParameters { + @NonNull final OnCustomFormatAdLoadedListener listener; + @NonNull final Map factories; + @Nullable final Map viewOptions; + + CustomParameters( + @NonNull OnCustomFormatAdLoadedListener listener, + @NonNull Map factories, + @Nullable Map viewOptions) { + this.listener = listener; + this.factories = factories; + this.viewOptions = viewOptions; + } + } + + static class NativeParameters { + @NonNull final OnNativeAdLoadedListener listener; + @NonNull final NativeAdFactory factory; + @Nullable final NativeAdOptions nativeAdOptions; + @Nullable final Map viewOptions; + + NativeParameters( + @NonNull OnNativeAdLoadedListener listener, + @NonNull NativeAdFactory factory, + @Nullable NativeAdOptions nativeAdOptions, + @Nullable Map viewOptions) { + this.listener = listener; + this.factory = factory; + this.nativeAdOptions = nativeAdOptions; + this.viewOptions = viewOptions; + } + } + protected FlutterAdLoaderAd( int adId, @NonNull AdInstanceManager manager, @@ -136,13 +255,24 @@ void load() { // Note we delegate loading the ad to FlutterAdLoader mainly for testing purposes. // As of 20.0.0 of GMA, mockito is unable to mock AdLoader. if (request != null) { - adLoader.loadAdLoaderAd(adUnitId, adListener, request.asAdRequest(adUnitId)); + adLoader.loadAdLoaderAd( + adUnitId, + adListener, + request.asAdRequest(adUnitId), + bannerParameters, + customParameters, + nativeParameters); return; } if (adManagerRequest != null) { adLoader.loadAdManagerAdLoaderAd( - adUnitId, adListener, adManagerRequest.asAdManagerAdRequest(adUnitId)); + adUnitId, + adListener, + adManagerRequest.asAdManagerAdRequest(adUnitId), + bannerParameters, + customParameters, + nativeParameters); return; } @@ -156,7 +286,10 @@ AdLoaderAdType getAdLoaderAdType() { @Nullable FlutterAdSize getAdSize() { - return null; + if (view == null || !(view instanceof AdManagerAdView)) { + return null; + } + return new FlutterAdSize(((AdManagerAdView) view).getAdSize()); } @Nullable @@ -165,5 +298,49 @@ String getFormatId() { } @Override - void dispose() {} + @Nullable + public PlatformView getPlatformView() { + if (view == null) { + return null; + } + + return new FlutterPlatformView(view); + } + + @Override + public void onAdManagerAdViewLoaded(@NonNull AdManagerAdView adView) { + view = adView; + type = AdLoaderAdType.BANNER; + manager.onAdLoaded(adId, adView.getResponseInfo()); + } + + @Override + public void onCustomFormatAdLoaded(@NonNull NativeCustomFormatAd ad) { + formatId = ad.getCustomFormatId(); + view = + customParameters.factories.get(formatId).createCustomAd(ad, customParameters.viewOptions); + type = AdLoaderAdType.CUSTOM; + manager.onAdLoaded(adId, null); + } + + @Override + public void onNativeAdLoaded(@NonNull NativeAd ad) { + view = nativeParameters.factory.createNativeAd(ad, nativeParameters.viewOptions); + type = AdLoaderAdType.NATIVE; + ad.setOnPaidEventListener(new FlutterPaidEventListener(manager, this)); + manager.onAdLoaded(adId, ad.getResponseInfo()); + } + + @Override + void dispose() { + if (view == null) { + return; + } + + if (view instanceof BaseAdView) { + ((BaseAdView) view).destroy(); + } + + view = null; + } } diff --git a/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdViewOptions.java b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdViewOptions.java new file mode 100644 index 000000000..03cf55985 --- /dev/null +++ b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdViewOptions.java @@ -0,0 +1,37 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License.c language governing permissions and +// limitations under the License. + +package io.flutter.plugins.googlemobileads; + +import androidx.annotation.Nullable; +import com.google.android.gms.ads.formats.AdManagerAdViewOptions; + +/** A wrapper for {@link com.google.android.gms.ads.formats.AdManagerAdViewOptions}. */ +class FlutterAdManagerAdViewOptions { + + @Nullable final Boolean manualImpressionsEnabled; + + FlutterAdManagerAdViewOptions(@Nullable Boolean manualImpressionsEnabled) { + this.manualImpressionsEnabled = manualImpressionsEnabled; + } + + AdManagerAdViewOptions asAdManagerAdViewOptions() { + AdManagerAdViewOptions.Builder builder = new AdManagerAdViewOptions.Builder(); + if (manualImpressionsEnabled != null) { + builder.setManualImpressionsEnabled(manualImpressionsEnabled); + } + return builder.build(); + } +} diff --git a/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterBannerParameters.java b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterBannerParameters.java new file mode 100644 index 000000000..a81562ed4 --- /dev/null +++ b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterBannerParameters.java @@ -0,0 +1,46 @@ +// Copyright 20222 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.flutter.plugins.googlemobileads; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.ads.AdSize; +import com.google.android.gms.ads.formats.OnAdManagerAdViewLoadedListener; +import java.util.List; + +class FlutterBannerParameters { + @NonNull final List sizes; + @Nullable final FlutterAdManagerAdViewOptions adManagerAdViewOptions; + + FlutterBannerParameters( + @NonNull List sizes, + @Nullable FlutterAdManagerAdViewOptions adManagerAdViewOptions) { + this.sizes = sizes; + this.adManagerAdViewOptions = adManagerAdViewOptions; + } + + FlutterAdLoaderAd.BannerParameters asBannerParameters( + @NonNull OnAdManagerAdViewLoadedListener listener) { + AdSize[] adSizes = new AdSize[sizes.size()]; + int i = 0; + for (FlutterAdSize size : sizes) { + adSizes[i++] = size.getAdSize(); + } + return new FlutterAdLoaderAd.BannerParameters( + listener, + adSizes, + adManagerAdViewOptions != null ? adManagerAdViewOptions.asAdManagerAdViewOptions() : null); + } +} diff --git a/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterCustomParameters.java b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterCustomParameters.java new file mode 100644 index 000000000..de0792ab3 --- /dev/null +++ b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterCustomParameters.java @@ -0,0 +1,30 @@ +package io.flutter.plugins.googlemobileads; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.ads.nativead.NativeCustomFormatAd.OnCustomFormatAdLoadedListener; +import io.flutter.plugins.googlemobileads.CustomAdFactory; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +class FlutterCustomParameters { + @NonNull final List formatIds; + @Nullable final Map viewOptions; + + FlutterCustomParameters( + @NonNull List formatIds, @Nullable Map viewOptions) { + this.formatIds = formatIds; + this.viewOptions = viewOptions; + } + + FlutterAdLoaderAd.CustomParameters asCustomParameters( + @NonNull OnCustomFormatAdLoadedListener listener, + @NonNull Map registeredFactories) { + Map factories = new HashMap<>(); + for (String formatId : formatIds) { + factories.put(formatId, registeredFactories.get(formatId)); + } + return new FlutterAdLoaderAd.CustomParameters(listener, factories, viewOptions); + } +} diff --git a/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterNativeParameters.java b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterNativeParameters.java new file mode 100644 index 000000000..d1c144de2 --- /dev/null +++ b/packages/google_mobile_ads/android/src/playServices/java/io/flutter/plugins/googlemobileads/FlutterNativeParameters.java @@ -0,0 +1,47 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.flutter.plugins.googlemobileads; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.ads.nativead.NativeAd.OnNativeAdLoadedListener; +import com.google.android.gms.ads.nativead.NativeAdOptions; +import java.util.Map; + +class FlutterNativeParameters { + @NonNull final String factoryId; + @Nullable final FlutterNativeAdOptions nativeAdOptions; + @Nullable final Map viewOptions; + + FlutterNativeParameters( + @NonNull String factoryId, + @Nullable FlutterNativeAdOptions nativeAdOptions, + @Nullable Map viewOptions) { + this.factoryId = factoryId; + this.nativeAdOptions = nativeAdOptions; + this.viewOptions = viewOptions; + } + + FlutterAdLoaderAd.NativeParameters asNativeParameters( + @NonNull OnNativeAdLoadedListener listener, + @NonNull Map registeredFactories) { + NativeAdOptions nativeAdOptions = null; + if (this.nativeAdOptions != null) { + nativeAdOptions = this.nativeAdOptions.asNativeAdOptions(); + } + return new FlutterAdLoaderAd.NativeParameters( + listener, registeredFactories.get(factoryId), nativeAdOptions, viewOptions); + } +} diff --git a/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/AdMessageCodecTest.java b/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/AdMessageCodecTest.java index f1b80ac62..6bb9b3663 100644 --- a/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/AdMessageCodecTest.java +++ b/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/AdMessageCodecTest.java @@ -537,6 +537,89 @@ public void encodeRequestConfiguration() { RequestConfiguration.TAG_FOR_UNDER_AGE_OF_CONSENT_FALSE); assertEquals(result.getTestDeviceIds(), Arrays.asList("test-device-id")); } + + @Test + public void encodeAdManagerAdViewOptionsNull() { + final ByteBuffer data = codec.encodeMessage(new FlutterAdManagerAdViewOptions(null)); + + final FlutterAdManagerAdViewOptions result = + (FlutterAdManagerAdViewOptions) codec.decodeMessage((ByteBuffer) data.position(0)); + assertNull(result.manualImpressionsEnabled); + } + + @Test + public void encodeAdManagerAdViewOptionsTrue() { + final ByteBuffer data = codec.encodeMessage(new FlutterAdManagerAdViewOptions(true)); + + final FlutterAdManagerAdViewOptions result = + (FlutterAdManagerAdViewOptions) codec.decodeMessage((ByteBuffer) data.position(0)); + assertTrue(result.manualImpressionsEnabled); + } + + @Test + public void encodeAdManagerAdViewOptionsFalse() { + final ByteBuffer data = codec.encodeMessage(new FlutterAdManagerAdViewOptions(false)); + + final FlutterAdManagerAdViewOptions result = + (FlutterAdManagerAdViewOptions) codec.decodeMessage((ByteBuffer) data.position(0)); + assertFalse(result.manualImpressionsEnabled); + } + + @Test + public void encodeBannerParameters() { + final ByteBuffer data = + codec.encodeMessage( + new FlutterBannerParameters( + Collections.singletonList(new FlutterAdSize(1, 2)), + new FlutterAdManagerAdViewOptions(null))); + + final FlutterBannerParameters result = + (FlutterBannerParameters) codec.decodeMessage((ByteBuffer) data.position(0)); + + assertEquals(result.sizes.size(), 1); + assertEquals(result.sizes.get(0).width, 1); + assertEquals(result.sizes.get(0).height, 2); + assertNull(result.adManagerAdViewOptions.manualImpressionsEnabled); + } + + @Test + public void encodeCustomParameters() { + final ByteBuffer data = + codec.encodeMessage( + new FlutterCustomParameters( + Collections.singletonList("format-id"), Collections.singletonMap("key", "value"))); + + final FlutterCustomParameters result = + (FlutterCustomParameters) codec.decodeMessage((ByteBuffer) data.position(0)); + + assertEquals(result.formatIds.size(), 1); + assertEquals(result.formatIds.get(0), "format-id"); + assertEquals(result.viewOptions.size(), 1); + assertEquals(result.viewOptions.get("key"), "value"); + } + + @Test + public void encodeNativeParameters() { + final ByteBuffer data = + codec.encodeMessage( + new FlutterNativeParameters( + "factory-id", + new FlutterNativeAdOptions(1, 1, null, true, true, true), + Collections.singletonMap("key", "value"))); + + final FlutterNativeParameters result = + (FlutterNativeParameters) codec.decodeMessage((ByteBuffer) data.position(0)); + + assertEquals(result.factoryId, "factory-id"); + assertEquals(result.nativeAdOptions.adChoicesPlacement, Integer.valueOf(1)); + assertEquals(result.nativeAdOptions.mediaAspectRatio, Integer.valueOf(1)); + assertNull(result.nativeAdOptions.videoOptions); + assertTrue(result.nativeAdOptions.requestCustomMuteThisAd); + assertTrue(result.nativeAdOptions.shouldRequestMultipleImages); + assertTrue(result.nativeAdOptions.shouldReturnUrlsForImageAssets); + assertEquals(result.viewOptions.size(), 1); + assertEquals(result.viewOptions.get("key"), "value"); + } } class DummyMediationExtras extends FlutterMediationExtras { diff --git a/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAdTest.java b/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAdTest.java index eb2f61537..3a8ec903e 100644 --- a/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAdTest.java +++ b/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterAdLoaderAdTest.java @@ -14,25 +14,36 @@ package io.flutter.plugins.googlemobileads; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Activity; import com.google.android.gms.ads.AdListener; import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.AdSize; +import com.google.android.gms.ads.AdValue; import com.google.android.gms.ads.LoadAdError; +import com.google.android.gms.ads.ResponseInfo; import com.google.android.gms.ads.admanager.AdManagerAdRequest; +import com.google.android.gms.ads.admanager.AdManagerAdView; +import com.google.android.gms.ads.nativead.NativeAd; +import com.google.android.gms.ads.nativead.NativeCustomFormatAd; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.googlemobileads.FlutterAd.FlutterLoadAdError; +import java.util.Collections; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; @@ -78,12 +89,14 @@ public Object answer(InvocationOnMock invocation) { } }) .when(mockLoader) - .loadAdManagerAdLoaderAd(eq("testId"), any(AdListener.class), eq(mockRequest)); + .loadAdManagerAdLoaderAd( + eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), isNull(), isNull()); adLoaderAd.load(); verify(mockLoader) - .loadAdManagerAdLoaderAd(eq("testId"), any(AdListener.class), eq(mockRequest)); + .loadAdManagerAdLoaderAd( + eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), isNull(), isNull()); verify(testManager).onAdClicked(eq(1)); verify(testManager).onAdClosed(eq(1)); @@ -121,11 +134,90 @@ public Object answer(InvocationOnMock invocation) { } }) .when(mockLoader) - .loadAdLoaderAd(eq("testId"), any(AdListener.class), eq(mockRequest)); + .loadAdLoaderAd( + eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), isNull(), isNull()); adLoaderAd.load(); - verify(mockLoader).loadAdLoaderAd(eq("testId"), any(AdListener.class), eq(mockRequest)); + verify(mockLoader) + .loadAdLoaderAd( + eq("testId"), any(AdListener.class), eq(mockRequest), isNull(), isNull(), isNull()); + + verify(testManager).onAdClicked(eq(1)); + verify(testManager).onAdClosed(eq(1)); + FlutterLoadAdError expectedError = new FlutterLoadAdError(mockLoadAdError); + verify(testManager).onAdFailedToLoad(eq(1), eq(expectedError)); + verify(testManager).onAdImpression(eq(1)); + verify(testManager).onAdOpened(eq(1)); + } + + @Test + public void loadAdLoaderAdBannerWithAdManagerAdRequest() { + final FlutterAdManagerAdRequest mockFlutterRequest = mock(FlutterAdManagerAdRequest.class); + final AdManagerAdRequest mockRequest = mock(AdManagerAdRequest.class); + when(mockFlutterRequest.asAdManagerAdRequest(anyString())).thenReturn(mockRequest); + FlutterAdLoader mockLoader = mock(FlutterAdLoader.class); + final FlutterAdLoaderAd adLoaderAd = + new FlutterAdLoaderAd(1, testManager, "testId", mockFlutterRequest, mockLoader); + final FlutterAdManagerAdViewLoadedListener listener = + new FlutterAdManagerAdViewLoadedListener(adLoaderAd); + final FlutterAdLoaderAd.BannerParameters bannerParameters = + new FlutterAdLoaderAd.BannerParameters(listener, new AdSize[] {AdSize.BANNER}, null); + adLoaderAd.bannerParameters = bannerParameters; + + final LoadAdError mockLoadAdError = mock(LoadAdError.class); + when(mockLoadAdError.getCode()).thenReturn(1); + when(mockLoadAdError.getDomain()).thenReturn("2"); + when(mockLoadAdError.getMessage()).thenReturn("3"); + + final AdManagerAdView mockAdView = mock(AdManagerAdView.class); + when(mockAdView.getAdSize()).thenReturn(new AdSize(0, 0)); + final ResponseInfo mockResponseInfo = mock(ResponseInfo.class); + when(mockAdView.getResponseInfo()).thenReturn(mockResponseInfo); + + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + AdListener listener = invocation.getArgument(1); + listener.onAdClicked(); + listener.onAdClosed(); + listener.onAdFailedToLoad(mockLoadAdError); + listener.onAdImpression(); + listener.onAdOpened(); + + FlutterAdLoaderAd.BannerParameters bannerParameters = invocation.getArgument(3); + bannerParameters.listener.onAdManagerAdViewLoaded(mockAdView); + return null; + } + }) + .when(mockLoader) + .loadAdManagerAdLoaderAd( + eq("testId"), + any(AdListener.class), + eq(mockRequest), + eq(bannerParameters), + isNull(), + isNull()); + + adLoaderAd.load(); + + assertEquals(adLoaderAd.getAdLoaderAdType(), AdLoaderAdType.BANNER); + + final FlutterAdSize adSize = adLoaderAd.getAdSize(); + assertEquals(adSize.width, 0); + assertEquals(adSize.height, 0); + + verify(mockAdView, times(1)).getAdSize(); + + verify(mockLoader) + .loadAdManagerAdLoaderAd( + eq("testId"), + any(AdListener.class), + eq(mockRequest), + eq(bannerParameters), + isNull(), + isNull()); verify(testManager).onAdClicked(eq(1)); verify(testManager).onAdClosed(eq(1)); @@ -133,6 +225,174 @@ public Object answer(InvocationOnMock invocation) { verify(testManager).onAdFailedToLoad(eq(1), eq(expectedError)); verify(testManager).onAdImpression(eq(1)); verify(testManager).onAdOpened(eq(1)); + verify(testManager).onAdLoaded(eq(1), eq(mockResponseInfo)); + } + + @Test + public void loadAdLoaderAdCustomWithAdManagerAdRequest() { + final FlutterAdManagerAdRequest mockFlutterRequest = mock(FlutterAdManagerAdRequest.class); + final AdManagerAdRequest mockRequest = mock(AdManagerAdRequest.class); + when(mockFlutterRequest.asAdManagerAdRequest(anyString())).thenReturn(mockRequest); + FlutterAdLoader mockLoader = mock(FlutterAdLoader.class); + final FlutterAdLoaderAd adLoaderAd = + new FlutterAdLoaderAd(1, testManager, "testId", mockFlutterRequest, mockLoader); + final FlutterCustomFormatAdLoadedListener listener = + new FlutterCustomFormatAdLoadedListener(adLoaderAd); + final CustomAdFactory mockCustomAdFactory = mock(CustomAdFactory.class); + final FlutterAdLoaderAd.CustomParameters customParameters = + new FlutterAdLoaderAd.CustomParameters( + listener, Collections.singletonMap("formatId", mockCustomAdFactory), null); + adLoaderAd.customParameters = customParameters; + + final LoadAdError mockLoadAdError = mock(LoadAdError.class); + when(mockLoadAdError.getCode()).thenReturn(1); + when(mockLoadAdError.getDomain()).thenReturn("2"); + when(mockLoadAdError.getMessage()).thenReturn("3"); + + final NativeCustomFormatAd mockNativeCustomFormatAd = mock(NativeCustomFormatAd.class); + when(mockNativeCustomFormatAd.getCustomFormatId()).thenReturn("formatId"); + + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + AdListener listener = invocation.getArgument(1); + listener.onAdClicked(); + listener.onAdClosed(); + listener.onAdFailedToLoad(mockLoadAdError); + listener.onAdImpression(); + listener.onAdOpened(); + + FlutterAdLoaderAd.CustomParameters customParameters = invocation.getArgument(4); + customParameters.listener.onCustomFormatAdLoaded(mockNativeCustomFormatAd); + return null; + } + }) + .when(mockLoader) + .loadAdManagerAdLoaderAd( + eq("testId"), + any(AdListener.class), + eq(mockRequest), + isNull(), + eq(customParameters), + isNull()); + + adLoaderAd.load(); + + assertEquals(adLoaderAd.getAdLoaderAdType(), AdLoaderAdType.CUSTOM); + + assertEquals(adLoaderAd.getFormatId(), "formatId"); + + verify(mockLoader) + .loadAdManagerAdLoaderAd( + eq("testId"), + any(AdListener.class), + eq(mockRequest), + isNull(), + eq(customParameters), + isNull()); + + verify(testManager).onAdClicked(eq(1)); + verify(testManager).onAdClosed(eq(1)); + FlutterLoadAdError expectedError = new FlutterLoadAdError(mockLoadAdError); + verify(testManager).onAdFailedToLoad(eq(1), eq(expectedError)); + verify(testManager).onAdImpression(eq(1)); + verify(testManager).onAdOpened(eq(1)); + verify(testManager).onAdLoaded(eq(1), isNull()); + } + + @Test + public void loadAdLoaderAdNativeWithAdManagerAdRequest() { + final FlutterAdManagerAdRequest mockFlutterRequest = mock(FlutterAdManagerAdRequest.class); + final AdManagerAdRequest mockRequest = mock(AdManagerAdRequest.class); + when(mockFlutterRequest.asAdManagerAdRequest(anyString())).thenReturn(mockRequest); + FlutterAdLoader mockLoader = mock(FlutterAdLoader.class); + final FlutterAdLoaderAd adLoaderAd = + new FlutterAdLoaderAd(1, testManager, "testId", mockFlutterRequest, mockLoader); + final FlutterNativeAdLoadedListener listener = new FlutterNativeAdLoadedListener(adLoaderAd); + final NativeAdFactory mockNativeAdFactory = mock(NativeAdFactory.class); + final FlutterAdLoaderAd.NativeParameters nativeParameters = + new FlutterAdLoaderAd.NativeParameters(listener, mockNativeAdFactory, null, null); + adLoaderAd.nativeParameters = nativeParameters; + + final LoadAdError mockLoadAdError = mock(LoadAdError.class); + when(mockLoadAdError.getCode()).thenReturn(1); + when(mockLoadAdError.getDomain()).thenReturn("2"); + when(mockLoadAdError.getMessage()).thenReturn("3"); + + final ResponseInfo mockResponseInfo = mock(ResponseInfo.class); + final NativeAd mockNativeAd = mock(NativeAd.class); + when(mockNativeAd.getResponseInfo()).thenReturn(mockResponseInfo); + + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + AdListener listener = invocation.getArgument(1); + listener.onAdClicked(); + listener.onAdClosed(); + listener.onAdFailedToLoad(mockLoadAdError); + listener.onAdImpression(); + listener.onAdOpened(); + + FlutterAdLoaderAd.NativeParameters nativeParameters = invocation.getArgument(5); + nativeParameters.listener.onNativeAdLoaded(mockNativeAd); + return null; + } + }) + .when(mockLoader) + .loadAdManagerAdLoaderAd( + eq("testId"), + any(AdListener.class), + eq(mockRequest), + isNull(), + isNull(), + eq(nativeParameters)); + + final AdValue mockAdValue = mock(AdValue.class); + when(mockAdValue.getCurrencyCode()).thenReturn("Dollars"); + when(mockAdValue.getPrecisionType()).thenReturn(1); + when(mockAdValue.getValueMicros()).thenReturn(1000L); + + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + FlutterPaidEventListener listener = invocation.getArgument(0); + listener.onPaidEvent(mockAdValue); + return null; + } + }) + .when(mockNativeAd) + .setOnPaidEventListener(any(FlutterPaidEventListener.class)); + + adLoaderAd.load(); + + assertEquals(adLoaderAd.getAdLoaderAdType(), AdLoaderAdType.NATIVE); + + verify(mockLoader) + .loadAdManagerAdLoaderAd( + eq("testId"), + any(AdListener.class), + eq(mockRequest), + isNull(), + isNull(), + eq(nativeParameters)); + + verify(testManager).onAdClicked(eq(1)); + verify(testManager).onAdClosed(eq(1)); + FlutterLoadAdError expectedError = new FlutterLoadAdError(mockLoadAdError); + verify(testManager).onAdFailedToLoad(eq(1), eq(expectedError)); + verify(testManager).onAdImpression(eq(1)); + verify(testManager).onAdOpened(eq(1)); + verify(testManager).onAdLoaded(eq(1), eq(mockResponseInfo)); + + final ArgumentCaptor adValueCaptor = + ArgumentCaptor.forClass(FlutterAdValue.class); + verify(testManager).onPaidEvent(eq(adLoaderAd), adValueCaptor.capture()); + assertEquals(adValueCaptor.getValue().currencyCode, "Dollars"); + assertEquals(adValueCaptor.getValue().precisionType, 1); + assertEquals(adValueCaptor.getValue().valueMicros, 1000L); } @Test(expected = IllegalStateException.class) diff --git a/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdViewOptionsTest.java b/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdViewOptionsTest.java new file mode 100644 index 000000000..f5b911c6a --- /dev/null +++ b/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterAdManagerAdViewOptionsTest.java @@ -0,0 +1,59 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.flutter.plugins.googlemobileads; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.android.gms.ads.formats.AdManagerAdViewOptions; +import org.junit.Test; + +/** Tests for {@link FlutterAdManagerAdViewOptions}. */ +public class FlutterAdManagerAdViewOptionsTest { + + @Test + public void testAsAdManagerAdViewOptions_null() { + FlutterAdManagerAdViewOptions flutterAdManagerAdViewOptions = + new FlutterAdManagerAdViewOptions(null); + + AdManagerAdViewOptions adManagerAdViewOptions = + flutterAdManagerAdViewOptions.asAdManagerAdViewOptions(); + AdManagerAdViewOptions defaultOptions = new AdManagerAdViewOptions.Builder().build(); + assertEquals( + adManagerAdViewOptions.getManualImpressionsEnabled(), + defaultOptions.getManualImpressionsEnabled()); + } + + @Test + public void testAsAdManagerAdViewOptions_true() { + FlutterAdManagerAdViewOptions flutterAdManagerAdViewOptions = + new FlutterAdManagerAdViewOptions(true); + + AdManagerAdViewOptions adManagerAdViewOptions = + flutterAdManagerAdViewOptions.asAdManagerAdViewOptions(); + assertTrue(adManagerAdViewOptions.getManualImpressionsEnabled()); + } + + @Test + public void testAsAdManagerAdViewOptions_false() { + FlutterAdManagerAdViewOptions flutterAdManagerAdViewOptions = + new FlutterAdManagerAdViewOptions(false); + + AdManagerAdViewOptions adManagerAdViewOptions = + flutterAdManagerAdViewOptions.asAdManagerAdViewOptions(); + assertFalse(adManagerAdViewOptions.getManualImpressionsEnabled()); + } +} diff --git a/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterBannerParametersTest.java b/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterBannerParametersTest.java new file mode 100644 index 000000000..2193ccb63 --- /dev/null +++ b/packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterBannerParametersTest.java @@ -0,0 +1,49 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.flutter.plugins.googlemobileads; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.google.android.gms.ads.admanager.AdManagerAdView; +import com.google.android.gms.ads.formats.OnAdManagerAdViewLoadedListener; +import java.util.Collections; +import java.util.List; +import org.junit.Test; + +/** Tests for {@link FlutterBannerParameters}. */ +public class FlutterBannerParametersTest { + + @Test + public void testAsBannerParameters() { + List sizes = Collections.singletonList(new FlutterAdSize(100, 200)); + FlutterBannerParameters flutterBannerParameters = new FlutterBannerParameters(sizes, null); + + OnAdManagerAdViewLoadedListener listener = + new OnAdManagerAdViewLoadedListener() { + @Override + public void onAdManagerAdViewLoaded(AdManagerAdView adView) {} + }; + + FlutterAdLoaderAd.BannerParameters bannerParameters = + flutterBannerParameters.asBannerParameters(listener); + + assertEquals(bannerParameters.adSizes.length, 1); + assertEquals(bannerParameters.adSizes[0].getWidth(), 100); + assertEquals(bannerParameters.adSizes[0].getHeight(), 200); + assertNull(bannerParameters.adManagerAdViewOptions); + assertEquals(bannerParameters.listener, listener); + } +} diff --git a/packages/google_mobile_ads/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_mobile_ads/example/ios/Runner.xcodeproj/project.pbxproj index cea7d5c1b..5c8f4762b 100644 --- a/packages/google_mobile_ads/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/google_mobile_ads/example/ios/Runner.xcodeproj/project.pbxproj @@ -41,6 +41,7 @@ 9E61AA6129BBE8FD00801A83 /* FLTNativeTemplateFontStyleTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E61AA5C29BBE8FD00801A83 /* FLTNativeTemplateFontStyleTest.m */; }; 9E61AA6229BBE8FD00801A83 /* FLTNativeTemplateColorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E61AA5D29BBE8FD00801A83 /* FLTNativeTemplateColorTest.m */; }; E276E99229DFB5870052484E /* FLTAdLoaderAdTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E276E99129DFB5870052484E /* FLTAdLoaderAdTest.m */; }; + E2D4625F29DFC2560010C4D0 /* FLTAdManagerAdViewOptionsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E2D4625E29DFC2560010C4D0 /* FLTAdManagerAdViewOptionsTest.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -111,6 +112,7 @@ 9EB9FE57280F853D00DDBB4F /* UserMessagingPlatform.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = UserMessagingPlatform.xcframework; path = Pods/GoogleUserMessagingPlatform/Frameworks/Release/UserMessagingPlatform.xcframework; sourceTree = ""; }; 9EFEAB4E29B0019F000A063B /* GoogleMobileAds.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = GoogleMobileAds.xcframework; path = "Pods/Google-Mobile-Ads-SDK/Frameworks/GoogleMobileAdsFramework/GoogleMobileAds.xcframework"; sourceTree = ""; }; E276E99129DFB5870052484E /* FLTAdLoaderAdTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTAdLoaderAdTest.m; sourceTree = ""; }; + E2D4625E29DFC2560010C4D0 /* FLTAdManagerAdViewOptionsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTAdManagerAdViewOptionsTest.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -216,6 +218,7 @@ 9E61AA5829BBE8DA00801A83 /* NativeTemplates */, 9E61AA5529BBE8AF00801A83 /* UserMessagingPlatform */, E276E99129DFB5870052484E /* FLTAdLoaderAdTest.m */, + E2D4625E29DFC2560010C4D0 /* FLTAdManagerAdViewOptionsTest.m */, 9E61AA2729BBE66900801A83 /* FLTAdUtilTest.m */, 9E61AA2429BBE66900801A83 /* FLTAppOpenAdTest.m */, 9E61AA2229BBE66900801A83 /* FLTBannerAdTest.m */, @@ -418,6 +421,7 @@ E276E99229DFB5870052484E /* FLTAdLoaderAdTest.m in Sources */, 9E61AA5329BBE88000801A83 /* FLTUserMessagingPlatformReaderWriterTest.m in Sources */, 9E61AA3229BBE66900801A83 /* FLTAppOpenAdTest.m in Sources */, + E2D4625F29DFC2560010C4D0 /* FLTAdManagerAdViewOptionsTest.m in Sources */, 9E61AA5F29BBE8FD00801A83 /* FLTNativeTemplateTypeTest.m in Sources */, 9E61AA3429BBE66900801A83 /* FLTGamInterstitialAdTest.m in Sources */, 9E61AA6029BBE8FD00801A83 /* FLTNativeTemplateStyleTest.m in Sources */, diff --git a/packages/google_mobile_ads/example/ios/RunnerTests/FLTAdLoaderAdTest.m b/packages/google_mobile_ads/example/ios/RunnerTests/FLTAdLoaderAdTest.m index da591c26c..5fc1dffa5 100644 --- a/packages/google_mobile_ads/example/ios/RunnerTests/FLTAdLoaderAdTest.m +++ b/packages/google_mobile_ads/example/ios/RunnerTests/FLTAdLoaderAdTest.m @@ -29,7 +29,10 @@ - (void)testDelegates { [[FLTAdLoaderAd alloc] initWithAdUnitId:@"testAdUnitId" request:[[FLTAdRequest alloc] init] rootViewController:viewController - adId:@0]; + adId:@0 + banner:nil + custom:nil + native:nil]; ad.manager = manager; @@ -46,6 +49,194 @@ - (void)testDelegates { error:[OCMArg isEqual:error]]); } +- (void)testBannerDelegates { + UIViewController *viewController = OCMClassMock([UIViewController class]); + FLTAdInstanceManager *manager = OCMClassMock([FLTAdInstanceManager class]); + + FLTAdSize *adSize = [[FLTAdSize alloc] initWithWidth:@(1) height:@(2)]; + FLTAdLoaderAd *ad = [[FLTAdLoaderAd alloc] + initWithAdUnitId:@"testAdUnitId" + request:[[FLTAdRequest alloc] init] + rootViewController:viewController + adId:@0 + banner:[[FLTBannerParameters alloc] initWithSizes:@[ adSize ] + options:nil] + custom:nil + native:nil]; + + ad.manager = manager; + + [ad load]; + + // GAMBannerAdLoaderDelegate + NSArray *validSizes = [ad validBannerSizesForAdLoader:ad.adLoader]; + XCTAssertEqual(validSizes.count, 1); + XCTAssertEqualObjects(validSizes[0], NSValueFromGADAdSize(adSize.size)); + + GAMBannerView *bannerView = OCMClassMock([GAMBannerView class]); + OCMStub([bannerView adSize]).andReturn(GADAdSizeFromCGSize(CGSizeMake(0, 0))); + OCMStub([bannerView recordImpression]); + + [ad adLoader:ad.adLoader didReceiveGAMBannerView:bannerView]; + + XCTAssertEqual([ad adLoaderAdType], FLTAdLoaderAdTypeBanner); + + OCMVerify([bannerView setAppEventDelegate:[OCMArg isEqual:ad]]); + OCMVerify([bannerView setDelegate:[OCMArg isEqual:ad]]); + OCMVerify([manager onAdLoaded:[OCMArg isEqual:ad] + responseInfo:[OCMArg isEqual:nil]]); + + FLTAdSize *size = [ad adSize]; + XCTAssertEqualObjects(size.width, @0); + XCTAssertEqualObjects(size.height, @0); + + OCMVerify([bannerView adSize]); + + // GADBannerViewDelegate + NSError *error = [NSError errorWithDomain:@"domain" code:1 userInfo:nil]; + [ad bannerView:bannerView didFailToReceiveAdWithError:error]; + OCMVerify([manager onAdFailedToLoad:[OCMArg isEqual:ad] error:error]); + + [ad bannerViewDidRecordImpression:bannerView]; + OCMVerify([manager onBannerImpression:[OCMArg isEqual:ad]]); + + [ad bannerViewDidRecordClick:bannerView]; + OCMVerify([manager adDidRecordClick:[OCMArg isEqual:ad]]); + + [ad bannerViewWillPresentScreen:bannerView]; + OCMVerify([manager onBannerWillPresentScreen:[OCMArg isEqual:ad]]); + + [ad bannerViewWillDismissScreen:bannerView]; + OCMVerify([manager onBannerWillDismissScreen:[OCMArg isEqual:ad]]); + + [ad bannerViewDidDismissScreen:bannerView]; + OCMVerify([manager onBannerDidDismissScreen:[OCMArg isEqual:ad]]); + + // GADAppEventDelegate + [ad adView:bannerView didReceiveAppEvent:@"name" withInfo:@"info"]; + OCMVerify([manager onAppEvent:[OCMArg isEqual:ad] + name:[OCMArg isEqual:@"name"] + data:[OCMArg isEqual:@"info"]]); +} + +- (void)testCustomDelegates { + UIViewController *viewController = OCMClassMock([UIViewController class]); + FLTAdInstanceManager *manager = OCMClassMock([FLTAdInstanceManager class]); + + FLTCustomParameters *custom = + [[FLTCustomParameters alloc] initWithFormatIds:@[ @"12345678" ] + viewOptions:nil]; + id factory = + OCMProtocolMock(@protocol(FLTCustomAdFactory)); + [custom.factories setValue:factory forKey:@"12345678"]; + + FLTAdLoaderAd *ad = + [[FLTAdLoaderAd alloc] initWithAdUnitId:@"testAdUnitId" + request:[[FLTAdRequest alloc] init] + rootViewController:viewController + adId:@0 + banner:nil + custom:custom + native:nil]; + + ad.manager = manager; + + [ad load]; + + // GADCustomNativeAdLoaderDelegate + NSArray *formatIds = + [ad customNativeAdFormatIDsForAdLoader:ad.adLoader]; + XCTAssertEqual(formatIds.count, 1); + XCTAssertEqualObjects(formatIds[0], @"12345678"); + + GADCustomNativeAd *customNativeAd = OCMClassMock([GADCustomNativeAd class]); + OCMStub([customNativeAd formatID]).andReturn(@"12345678"); + + [ad adLoader:ad.adLoader didReceiveCustomNativeAd:customNativeAd]; + + XCTAssertEqual([ad adLoaderAdType], FLTAdLoaderAdTypeCustom); + + OCMVerify([customNativeAd setDelegate:[OCMArg isEqual:ad]]); + OCMVerify([factory createCustomNativeAd:[OCMArg isEqual:customNativeAd] + customOptions:[OCMArg isEqual:nil]]); + OCMVerify([manager onAdLoaded:[OCMArg isEqual:ad] + responseInfo:[OCMArg isEqual:nil]]); + + NSString *formatId = [ad formatId]; + XCTAssertEqual([ad formatId], @"12345678"); + + // GADCustomNativeAdDelegate + [ad customNativeAdDidRecordImpression:customNativeAd]; + OCMVerify([manager onCustomNativeAdImpression:[OCMArg isEqual:ad]]); + + [ad customNativeAdDidRecordClick:customNativeAd]; + OCMVerify([manager adDidRecordClick:[OCMArg isEqual:ad]]); + + [ad customNativeAdWillPresentScreen:customNativeAd]; + OCMVerify([manager onCustomNativeAdWillPresentScreen:[OCMArg isEqual:ad]]); + + [ad customNativeAdWillDismissScreen:customNativeAd]; + OCMVerify([manager onCustomNativeAdWillDismissScreen:[OCMArg isEqual:ad]]); + + [ad customNativeAdDidDismissScreen:customNativeAd]; + OCMVerify([manager onCustomNativeAdDidDismissScreen:[OCMArg isEqual:ad]]); +} + +- (void)testNativeDelegates { + UIViewController *viewController = OCMClassMock([UIViewController class]); + FLTAdInstanceManager *manager = OCMClassMock([FLTAdInstanceManager class]); + + FLTNativeParameters *native = + [[FLTNativeParameters alloc] initWithFactoryId:@"factoryId" + nativeAdOptions:nil + viewOptions:nil]; + id factory = + OCMProtocolMock(@protocol(FLTNativeAdFactory)); + native.factory = factory; + + FLTAdLoaderAd *ad = + [[FLTAdLoaderAd alloc] initWithAdUnitId:@"testAdUnitId" + request:[[FLTAdRequest alloc] init] + rootViewController:viewController + adId:@0 + banner:nil + custom:nil + native:native]; + + ad.manager = manager; + + [ad load]; + + // GADNativeAdLoaderDelegate + GADNativeAd *nativeAd = OCMClassMock([GADNativeAd class]); + + [ad adLoader:ad.adLoader didReceiveNativeAd:nativeAd]; + + XCTAssertEqual([ad adLoaderAdType], FLTAdLoaderAdTypeNative); + + OCMVerify([nativeAd setDelegate:[OCMArg isEqual:ad]]); + OCMVerify([factory createNativeAd:[OCMArg isEqual:nativeAd] + customOptions:[OCMArg isEqual:nil]]); + OCMVerify([manager onAdLoaded:[OCMArg isEqual:ad] + responseInfo:[OCMArg isEqual:nil]]); + + // GADNativeAdDelegate + [ad nativeAdDidRecordImpression:nativeAd]; + OCMVerify([manager onNativeAdImpression:[OCMArg isEqual:ad]]); + + [ad nativeAdDidRecordClick:nativeAd]; + OCMVerify([manager adDidRecordClick:[OCMArg isEqual:ad]]); + + [ad nativeAdWillPresentScreen:nativeAd]; + OCMVerify([manager onNativeAdWillPresentScreen:[OCMArg isEqual:ad]]); + + [ad nativeAdWillDismissScreen:nativeAd]; + OCMVerify([manager onNativeAdWillDismissScreen:[OCMArg isEqual:ad]]); + + [ad nativeAdDidDismissScreen:nativeAd]; + OCMVerify([manager onNativeAdDidDismissScreen:[OCMArg isEqual:ad]]); +} + - (void)testLoadAdLoaderAd { FLTAdRequest *request = [[FLTAdRequest alloc] init]; request.keywords = @[ @"apple" ]; @@ -64,7 +255,10 @@ - (void)testLoadAdLoaderAd:(FLTAdRequest *)request { FLTAdLoaderAd *ad = [[FLTAdLoaderAd alloc] initWithAdUnitId:@"testAdUnitId" request:request rootViewController:viewController - adId:@1]; + adId:@1 + banner:nil + custom:nil + native:nil]; XCTAssertEqual(ad.adLoader.adUnitID, @"testAdUnitId"); XCTAssertEqual(ad.adLoader.delegate, ad); diff --git a/packages/google_mobile_ads/example/ios/RunnerTests/FLTAdManagerAdViewOptionsTest.m b/packages/google_mobile_ads/example/ios/RunnerTests/FLTAdManagerAdViewOptionsTest.m new file mode 100644 index 000000000..c42015c18 --- /dev/null +++ b/packages/google_mobile_ads/example/ios/RunnerTests/FLTAdManagerAdViewOptionsTest.m @@ -0,0 +1,49 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "google_mobile_ads/FLTAd_Internal.h" + +@interface FLTAdManagerAdViewOptionsTest : XCTestCase +@end + +@implementation FLTAdManagerAdViewOptionsTest +- (void)testAsGADAdLoaderOptionsManualImpressionsEnabledUnset { + FLTAdManagerAdViewOptions *options = + [[FLTAdManagerAdViewOptions alloc] initWithManualImpressionsEnabled:nil]; + + XCTAssertEqual(options.asGADAdLoaderOptions.count, 0); +} + +- (void)testAsGADAdLoaderOptionsManualImpressionsEnabledYes { + FLTAdManagerAdViewOptions *options = + [[FLTAdManagerAdViewOptions alloc] initWithManualImpressionsEnabled:@YES]; + + XCTAssertEqual(options.asGADAdLoaderOptions.count, 1); + GADAdLoaderOptions *option = options.asGADAdLoaderOptions[0]; + XCTAssert([option isKindOfClass:[GAMBannerViewOptions class]]); + XCTAssertTrue(((GAMBannerViewOptions *)option).manualImpressionEnabled); +} + +- (void)testAsGADAdLoaderOptionsManualImpressionsEnabledNo { + FLTAdManagerAdViewOptions *options = + [[FLTAdManagerAdViewOptions alloc] initWithManualImpressionsEnabled:@NO]; + + XCTAssertEqual(options.asGADAdLoaderOptions.count, 1); + GADAdLoaderOptions *option = options.asGADAdLoaderOptions[0]; + XCTAssert([option isKindOfClass:[GAMBannerViewOptions class]]); + XCTAssertFalse(((GAMBannerViewOptions *)option).manualImpressionEnabled); +} +@end diff --git a/packages/google_mobile_ads/example/ios/RunnerTests/FLTGoogleMobileAdsReaderWriterTest.m b/packages/google_mobile_ads/example/ios/RunnerTests/FLTGoogleMobileAdsReaderWriterTest.m index bf273249e..68ccb119c 100644 --- a/packages/google_mobile_ads/example/ios/RunnerTests/FLTGoogleMobileAdsReaderWriterTest.m +++ b/packages/google_mobile_ads/example/ios/RunnerTests/FLTGoogleMobileAdsReaderWriterTest.m @@ -780,6 +780,124 @@ - (void)assertEqualTemplateTypes:(FLTNativeTemplateType *)first XCTAssertEqual(first.intValue, second.intValue); } +- (void)testEncodeDecodeAdManagerAdViewOptionsNil { + FLTAdManagerAdViewOptions *options = + [[FLTAdManagerAdViewOptions alloc] initWithManualImpressionsEnabled:nil]; + + NSData *encodedMessage = [_messageCodec encode:options]; + + FLTAdManagerAdViewOptions *decodedOptions = + [_messageCodec decode:encodedMessage]; + XCTAssertEqual(decodedOptions.manualImpressionsEnabled, nil); +} + +- (void)testEncodeDecodeAdManagerAdViewOptionsYes { + FLTAdManagerAdViewOptions *options = [[FLTAdManagerAdViewOptions alloc] + initWithManualImpressionsEnabled:@(YES)]; + + NSData *encodedMessage = [_messageCodec encode:options]; + + FLTAdManagerAdViewOptions *decodedOptions = + [_messageCodec decode:encodedMessage]; + XCTAssertEqual(decodedOptions.manualImpressionsEnabled, @(YES)); +} + +- (void)testEncodeDecodeAdManagerAdViewOptionsNo { + FLTAdManagerAdViewOptions *options = [[FLTAdManagerAdViewOptions alloc] + initWithManualImpressionsEnabled:@(NO)]; + + NSData *encodedMessage = [_messageCodec encode:options]; + + FLTAdManagerAdViewOptions *decodedOptions = + [_messageCodec decode:encodedMessage]; + XCTAssertEqual(decodedOptions.manualImpressionsEnabled, @(NO)); +} + +- (void)testEncodeDecodeBannerParameters { + FLTBannerParameters *parameters = [[FLTBannerParameters alloc] + initWithSizes:@[ [[FLTAdSize alloc] initWithWidth:@(1) height:@(2)] ] + options:[[FLTAdManagerAdViewOptions alloc] + initWithManualImpressionsEnabled:nil]]; + + NSData *encodedMessage = [_messageCodec encode:parameters]; + + FLTBannerParameters *decodedParameters = + [_messageCodec decode:encodedMessage]; + + NSArray *sizes = decodedParameters.sizes; + + XCTAssertEqual(sizes.count, 1); + XCTAssertEqualObjects(sizes[0].width, @(1)); + XCTAssertEqualObjects(sizes[0].height, @(2)); + + FLTAdManagerAdViewOptions *options = decodedParameters.options; + + XCTAssertNotNil(options); + XCTAssertNil(options.manualImpressionsEnabled); +} + +- (void)testEncodeDecodeCustomParameters { + FLTCustomParameters *parameters = [[FLTCustomParameters alloc] + initWithFormatIds:@[ @"formatId0", @"formatId1" ] + viewOptions:@{@"key" : @"value"}]; + + NSData *encodedMessage = [_messageCodec encode:parameters]; + + FLTCustomParameters *decodedParameters = + [_messageCodec decode:encodedMessage]; + + NSArray *formatIds = decodedParameters.formatIds; + + XCTAssertEqual(formatIds.count, 2); + XCTAssertEqualObjects(formatIds[0], @"formatId0"); + XCTAssertEqualObjects(formatIds[1], @"formatId1"); + + NSDictionary *viewOptions = decodedParameters.viewOptions; + + XCTAssertNotNil(viewOptions); + XCTAssertEqualObjects(viewOptions[@"key"], @"value"); + + NSDictionary> *factories = + decodedParameters.factories; + + XCTAssertEqual(factories.count, 0); +} + +- (void)testEncodeDecodeNativeParameters { + FLTNativeParameters *parameters = [[FLTNativeParameters alloc] + initWithFactoryId:@"factory-id" + nativeAdOptions:[[FLTNativeAdOptions alloc] + initWithAdChoicesPlacement:@(1) + mediaAspectRatio:@(1) + videoOptions:nil + requestCustomMuteThisAd:@YES + shouldRequestMultipleImages:@YES + shouldReturnUrlsForImageAssets:@YES] + viewOptions:@{@"key" : @"value"}]; + + NSData *encodedMessage = [_messageCodec encode:parameters]; + + FLTNativeParameters *decodedParameters = + [_messageCodec decode:encodedMessage]; + + XCTAssertEqualObjects(decodedParameters.factoryId, @"factory-id"); + + XCTAssertEqualObjects(parameters.nativeAdOptions.adChoicesPlacement, @(1)); + XCTAssertEqualObjects(parameters.nativeAdOptions.mediaAspectRatio, @(1)); + XCTAssertNil(parameters.nativeAdOptions.videoOptions); + XCTAssertEqualObjects(parameters.nativeAdOptions.requestCustomMuteThisAd, + @(YES)); + XCTAssertEqualObjects(parameters.nativeAdOptions.shouldRequestMultipleImages, + @(YES)); + XCTAssertEqualObjects( + parameters.nativeAdOptions.shouldReturnUrlsForImageAssets, @(YES)); + + NSDictionary *viewOptions = decodedParameters.viewOptions; + + XCTAssertNotNil(viewOptions); + XCTAssertEqualObjects(viewOptions[@"key"], @"value"); +} + @end @implementation FLTTestAdSizeFactory diff --git a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTAdInstanceManager_Internal.m b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTAdInstanceManager_Internal.m index f57a655ee..c50290ee6 100644 --- a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTAdInstanceManager_Internal.m +++ b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTAdInstanceManager_Internal.m @@ -126,6 +126,22 @@ - (void)onNativeAdWillDismissScreen:(nonnull id)ad { [self sendAdEvent:@"onNativeAdWillDismissScreen" ad:ad]; } +- (void)onCustomNativeAdImpression:(nonnull id)ad { + [self sendAdEvent:@"onCustomNativeAdImpression" ad:ad]; +} + +- (void)onCustomNativeAdWillPresentScreen:(nonnull id)ad { + [self sendAdEvent:@"onCustomNativeAdWillPresentScreen" ad:ad]; +} + +- (void)onCustomNativeAdDidDismissScreen:(nonnull id)ad { + [self sendAdEvent:@"onCustomNativeAdDidDismissScreen" ad:ad]; +} + +- (void)onCustomNativeAdWillDismissScreen:(nonnull id)ad { + [self sendAdEvent:@"onCustomNativeAdWillDismissScreen" ad:ad]; +} + - (void)onRewardedAdUserEarnedReward:(FLTRewardedAd *_Nonnull)ad reward:(FLTRewardItem *_Nonnull)reward { [_channel invokeMethod:@"onAdEvent" diff --git a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTAd_Internal.m b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTAd_Internal.m index f889603e1..fa2a12deb 100644 --- a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTAd_Internal.m +++ b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTAd_Internal.m @@ -1233,30 +1233,105 @@ - (UIView *)view { @end +@implementation FLTBannerParameters +- (nonnull instancetype)initWithSizes:(nonnull NSArray *)sizes + options:(nullable FLTAdManagerAdViewOptions *) + options { + self = [super init]; + _sizes = sizes; + _options = options; + return self; +} +@end + +@implementation FLTCustomParameters +- (nonnull instancetype) + initWithFormatIds:(nonnull NSArray *)formatIds + viewOptions:(nullable NSDictionary *)viewOptions { + self = [super init]; + _formatIds = formatIds; + _viewOptions = viewOptions; + _factories = [NSMutableDictionary dictionary]; + return self; +} +@end + +@implementation FLTNativeParameters +- (nonnull instancetype) + initWithFactoryId:(nonnull NSString *)factoryId + nativeAdOptions:(nullable FLTNativeAdOptions *)nativeAdOptions + viewOptions:(nullable NSDictionary *)viewOptions { + self = [super init]; + _factoryId = factoryId; + _nativeAdOptions = nativeAdOptions; + _viewOptions = viewOptions; + return self; +} +@end + #pragma mark - FLTAdLoaderAd @implementation FLTAdLoaderAd { NSString *_adUnitId; FLTAdRequest *_adRequest; -} - -- (nonnull instancetype)initWithAdUnitId:(nonnull NSString *)adUnitId - request:(nonnull FLTAdRequest *)request - rootViewController: - (nonnull UIViewController *)rootViewController - adId:(nonnull NSNumber *)adId { + NSMutableArray *_validAdSizes; + UIView *_view; + FLTBannerParameters *_banner; + FLTCustomParameters *_custom; + FLTNativeParameters *_native; +} + +- (nonnull instancetype) + initWithAdUnitId:(nonnull NSString *)adUnitId + request:(nonnull FLTAdRequest *)request + rootViewController:(nonnull UIViewController *)rootViewController + adId:(nonnull NSNumber *)adId + banner:(nullable FLTBannerParameters *)bannerParameters + custom:(nullable FLTCustomParameters *)customParameters + native:(nullable FLTNativeParameters *)nativeParameters { self = [super init]; if (self) { self.adId = adId; _adUnitId = adUnitId; _adRequest = request; _adLoaderAdType = FLTAdLoaderAdTypeUnknown; - _adSize = nil; _formatId = nil; NSMutableArray *adTypes = [[NSMutableArray alloc] init]; NSMutableArray *options = [[NSMutableArray alloc] init]; + if (![FLTAdUtil isNull:bannerParameters]) { + _banner = bannerParameters; + _validAdSizes = + [NSMutableArray arrayWithCapacity:bannerParameters.sizes.count]; + for (FLTAdSize *size in bannerParameters.sizes) { + [_validAdSizes addObject:NSValueFromGADAdSize(size.size)]; + } + + [adTypes addObject:GADAdLoaderAdTypeGAMBanner]; + + if (![FLTAdUtil isNull:_banner.options]) { + [options addObjectsFromArray:_banner.options.asGADAdLoaderOptions]; + } + } + + if (![FLTAdUtil isNull:customParameters]) { + _custom = customParameters; + + [adTypes addObject:GADAdLoaderAdTypeCustomNative]; + } + + if (![FLTAdUtil isNull:nativeParameters]) { + _native = nativeParameters; + + [adTypes addObject:GADAdLoaderAdTypeNative]; + + if (![FLTAdUtil isNull:_native.nativeAdOptions]) { + [options + addObjectsFromArray:_native.nativeAdOptions.asGADAdLoaderOptions]; + } + } + _adLoader = [[GADAdLoader alloc] initWithAdUnitID:_adUnitId rootViewController:rootViewController adTypes:adTypes @@ -1266,6 +1341,13 @@ - (nonnull instancetype)initWithAdUnitId:(nonnull NSString *)adUnitId return self; } +- (FLTAdSize *)adSize { + if (_view && [_view isKindOfClass:[GADBannerView class]]) { + return [[FLTAdSize alloc] initWithAdSize:((GADBannerView *)_view).adSize]; + } + return nil; +} + #pragma mark - FLTAd - (void)load { @@ -1285,10 +1367,176 @@ - (void)adLoader:(nonnull GADAdLoader *)adLoader [manager onAdFailedToLoad:self error:error]; } +#pragma mark - GAMBannerAdLoaderDelegate + +- (nonnull NSArray *)validBannerSizesForAdLoader: + (nonnull GADAdLoader *)adLoader { + return _validAdSizes; +} + +- (void)adLoader:(nonnull GADAdLoader *)adLoader + didReceiveGAMBannerView:(nonnull GAMBannerView *)bannerView { + _adLoaderAdType = FLTAdLoaderAdTypeBanner; + _view = bannerView; + + bannerView.appEventDelegate = self; + bannerView.delegate = self; + + __weak FLTAdLoaderAd *weakSelf = self; + bannerView.paidEventHandler = ^(GADAdValue *_Nonnull value) { + if (weakSelf.manager == nil) { + return; + } + [weakSelf.manager + onPaidEvent:weakSelf + value:[[FLTAdValue alloc] initWithValue:value.value + precision:(NSInteger)value.precision + currencyCode:value.currencyCode]]; + }; + + [bannerView recordImpression]; + + [manager onAdLoaded:self responseInfo:bannerView.responseInfo]; +} + +#pragma mark - GADBannerViewDelegate + +- (void)bannerViewDidReceiveAd:(nonnull GADBannerView *)bannerView { + // TODO handled by adLoader:didReceiveGAMBannerView: ? +} + +- (void)bannerView:(nonnull GADBannerView *)bannerView + didFailToReceiveAdWithError:(nonnull NSError *)error { + [manager onAdFailedToLoad:self error:error]; +} + +- (void)bannerViewDidRecordImpression:(nonnull GADBannerView *)bannerView { + [manager onBannerImpression:self]; +} + +- (void)bannerViewDidRecordClick:(nonnull GADBannerView *)bannerView { + [manager adDidRecordClick:self]; +} + +- (void)bannerViewWillPresentScreen:(nonnull GADBannerView *)bannerView { + [manager onBannerWillPresentScreen:self]; +} + +- (void)bannerViewWillDismissScreen:(nonnull GADBannerView *)bannerView { + [manager onBannerWillDismissScreen:self]; +} + +- (void)bannerViewDidDismissScreen:(nonnull GADBannerView *)bannerView { + [manager onBannerDidDismissScreen:self]; +} + +#pragma mark - GADAppEventDelegate + +- (void)adView:(nonnull GADBannerView *)banner + didReceiveAppEvent:(nonnull NSString *)name + withInfo:(nullable NSString *)info { + [self.manager onAppEvent:self name:name data:info]; +} + +#pragma mark - GADCustomNativeAdLoaderDelegate + +- (nonnull NSArray *)customNativeAdFormatIDsForAdLoader: + (nonnull GADAdLoader *)adLoader { + return _custom.formatIds; +} + +- (void)adLoader:(nonnull GADAdLoader *)adLoader + didReceiveCustomNativeAd:(nonnull GADCustomNativeAd *)customNativeAd { + // Use Nil instead of Null to fix crash with Swift integrations. + NSDictionary *customOptions = + [[NSNull null] isEqual:_custom.viewOptions] ? nil : _custom.viewOptions; + _adLoaderAdType = FLTAdLoaderAdTypeCustom; + _formatId = customNativeAd.formatID; + _view = [_custom.factories[_formatId] createCustomNativeAd:customNativeAd + customOptions:customOptions]; + + customNativeAd.delegate = self; + + [customNativeAd recordImpression]; + + [manager onAdLoaded:self responseInfo:customNativeAd.responseInfo]; +} + +#pragma mark - GADCustomNativeAdDelegate + +- (void)customNativeAdDidRecordImpression: + (nonnull GADCustomNativeAd *)nativeAd { + [manager onCustomNativeAdImpression:self]; +} + +- (void)customNativeAdDidRecordClick:(nonnull GADCustomNativeAd *)nativeAd { + [manager adDidRecordClick:self]; +} + +- (void)customNativeAdWillPresentScreen:(nonnull GADCustomNativeAd *)nativeAd { + [manager onCustomNativeAdWillPresentScreen:self]; +} + +- (void)customNativeAdWillDismissScreen:(nonnull GADCustomNativeAd *)nativeAd { + [manager onCustomNativeAdWillDismissScreen:self]; +} + +- (void)customNativeAdDidDismissScreen:(nonnull GADCustomNativeAd *)nativeAd { + [manager onCustomNativeAdDidDismissScreen:self]; +} + +#pragma mark - GADNativeAdLoaderDelegate + +- (void)adLoader:(nonnull GADAdLoader *)adLoader + didReceiveNativeAd:(nonnull GADNativeAd *)nativeAd { + // Use Nil instead of Null to fix crash with Swift integrations. + NSDictionary *customOptions = + [[NSNull null] isEqual:_native.viewOptions] ? nil : _native.viewOptions; + _adLoaderAdType = FLTAdLoaderAdTypeNative; + _view = [_native.factory createNativeAd:nativeAd customOptions:customOptions]; + nativeAd.delegate = self; + + __weak FLTAdLoaderAd *weakSelf = self; + nativeAd.paidEventHandler = ^(GADAdValue *_Nonnull value) { + if (weakSelf.manager == nil) { + return; + } + [weakSelf.manager + onPaidEvent:weakSelf + value:[[FLTAdValue alloc] initWithValue:value.value + precision:(NSInteger)value.precision + currencyCode:value.currencyCode]]; + }; + + [manager onAdLoaded:self responseInfo:nativeAd.responseInfo]; +} + +#pragma mark - GADNativeAdDelegate + +- (void)nativeAdDidRecordImpression:(nonnull GADNativeAd *)nativeAd { + [manager onNativeAdImpression:self]; +} + +- (void)nativeAdDidRecordClick:(nonnull GADNativeAd *)nativeAd { + [manager adDidRecordClick:self]; +} + +- (void)nativeAdWillPresentScreen:(nonnull GADNativeAd *)nativeAd { + [manager onNativeAdWillPresentScreen:self]; +} + +- (void)nativeAdWillDismissScreen:(nonnull GADNativeAd *)nativeAd { + [manager onNativeAdWillDismissScreen:self]; +} + +- (void)nativeAdDidDismissScreen:(nonnull GADNativeAd *)nativeAd { + [manager onNativeAdDidDismissScreen:self]; +} + #pragma mark - FlutterPlatformView - (nonnull UIView *)view { - return nil; // TODO not great + return _view; } @synthesize manager; @@ -1467,3 +1715,24 @@ @implementation FLTNativeAdOptions } @end + +@implementation FLTAdManagerAdViewOptions +- (nonnull instancetype)initWithManualImpressionsEnabled: + (nullable NSNumber *)manualImpressionsEnabled { + self = [super init]; + _manualImpressionsEnabled = manualImpressionsEnabled; + return self; +} + +- (nonnull NSArray *)asGADAdLoaderOptions { + NSMutableArray *options = [NSMutableArray array]; + if ([FLTAdUtil isNotNull:_manualImpressionsEnabled]) { + GAMBannerViewOptions *bannerViewOptions = + [[GAMBannerViewOptions alloc] init]; + bannerViewOptions.manualImpressionEnabled = + _manualImpressionsEnabled.boolValue; + [options addObject:bannerViewOptions]; + } + return options; +} +@end diff --git a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTGoogleMobileAdsPlugin.m b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTGoogleMobileAdsPlugin.m index 4e33ec14f..f6dbe71e2 100644 --- a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTGoogleMobileAdsPlugin.m +++ b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTGoogleMobileAdsPlugin.m @@ -25,6 +25,8 @@ @interface FLTGoogleMobileAdsPlugin () @property NSObject *registrar; @property NSMutableDictionary> *nativeAdFactories; +@property NSMutableDictionary> + *customAdFactories; @end /// Initialization handler for GMASDK. Invokes result at most once. @@ -67,6 +69,7 @@ - (void)handleInitializationComplete:(GADInitializationStatus *_Nonnull)status { @implementation FLTGoogleMobileAdsPlugin { NSMutableDictionary> *_nativeAdFactories; + NSMutableDictionary> *_customAdFactories; FLTAdInstanceManager *_manager; id _mediationNetworkExtrasProvider; FLTGoogleMobileAdsReaderWriter *_readerWriter; @@ -119,6 +122,7 @@ - (instancetype)initWithRegistrar: (NSObject*)registrar if (self) { _registrar = registrar; _nativeAdFactories = [NSMutableDictionary dictionary]; + _customAdFactories = [NSMutableDictionary dictionary]; _manager = [[FLTAdInstanceManager alloc] initWithBinaryMessenger:binaryMessenger]; _appStateNotifier = @@ -200,6 +204,33 @@ + (BOOL)registerNativeAdFactory:(id)registry return YES; } ++ (BOOL)registerCustomAdFactory:(id)registry + formatId:(NSString *)formatId + customAdFactory:(id)customAdFactory { + NSString *pluginClassName = + NSStringFromClass([FLTGoogleMobileAdsPlugin class]); + FLTGoogleMobileAdsPlugin *adMobPlugin = (FLTGoogleMobileAdsPlugin *)[registry + valuePublishedByPlugin:pluginClassName]; + if (!adMobPlugin) { + NSString *reason = + [NSString stringWithFormat:@"Could not find a %@ instance. The plugin " + @"may have not been registered.", + pluginClassName]; + @throw [NSException exceptionWithName:NSInvalidArgumentException + reason:reason + userInfo:nil]; + } + + if (adMobPlugin.customAdFactories[formatId]) { + NSLog(@"A CustomAdFactory with the following formatId already exists: %@", + formatId); + return NO; + } + + [adMobPlugin.customAdFactories setValue:customAdFactory forKey:formatId]; + return YES; +} + + (id)unregisterNativeAdFactory: (id)registry factoryId:(NSString *)factoryId { @@ -213,6 +244,19 @@ + (BOOL)registerNativeAdFactory:(id)registry return factory; } ++ (id)unregisterCustomAdFactory: + (id)registry + formatId:(NSString *)formatId { + FLTGoogleMobileAdsPlugin *adMobPlugin = (FLTGoogleMobileAdsPlugin *)[registry + valuePublishedByPlugin:NSStringFromClass( + [FLTGoogleMobileAdsPlugin class])]; + + id factory = adMobPlugin.customAdFactories[formatId]; + if (factory) + [adMobPlugin.customAdFactories removeObjectForKey:formatId]; + return factory; +} + - (UIViewController *)rootController { UIViewController* root = _registrar.viewController; if ([FLTAdUtil isNull:root]) { @@ -442,6 +486,40 @@ - (void)handleMethodCall:(FlutterMethodCall *)call [_manager loadAd:ad]; result(nil); } else if ([call.method isEqualToString:@"loadAdLoaderAd"]) { + FLTCustomParameters *custom = call.arguments[@"custom"]; + if ([FLTAdUtil isNotNull:custom]) { + for (NSString *formatId in custom.formatIds) { + id factory = _customAdFactories[formatId]; + if (!factory) { + NSString *message = [NSString + stringWithFormat:@"Can't find CustomAdFactory with id: %@", + formatId]; + result([FlutterError errorWithCode:@"AdLoaderAdError" + message:message + details:nil]); + return; + } + + [custom.factories setValue:factory forKey:formatId]; + } + } + + FLTNativeParameters *native = call.arguments[@"native"]; + if ([FLTAdUtil isNotNull:native]) { + id factory = _nativeAdFactories[native.factoryId]; + if (!factory) { + NSString *message = [NSString + stringWithFormat:@"Can't find NativeAdFactory with id: %@", + native.factoryId]; + result([FlutterError errorWithCode:@"AdLoaderAdError" + message:message + details:nil]); + return; + } + + native.factory = factory; + } + FLTAdRequest *request; if ([FLTAdUtil isNotNull:call.arguments[@"request"]]) { request = call.arguments[@"request"]; @@ -453,7 +531,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call [[FLTAdLoaderAd alloc] initWithAdUnitId:call.arguments[@"adUnitId"] request:request rootViewController:rootController - adId:call.arguments[@"adId"]]; + adId:call.arguments[@"adId"] + banner:call.arguments[@"banner"] + custom:custom + native:native]; [_manager loadAd:ad]; result(nil); } else if ([call.method isEqualToString:@"loadInterstitialAd"]) { diff --git a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTGoogleMobileAdsReaderWriter_Internal.m b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTGoogleMobileAdsReaderWriter_Internal.m index fa8bcb5b3..d6e68dc11 100644 --- a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTGoogleMobileAdsReaderWriter_Internal.m +++ b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/FLTGoogleMobileAdsReaderWriter_Internal.m @@ -47,8 +47,11 @@ typedef NS_ENUM(NSInteger, FLTAdMobField) { FLTAdmobFieldNativeTemplateFontStyle = 151, FLTAdmobFieldNativeTemplateType = 152, FLTAdmobFieldNativeTemplateColor = 153, - FLTAdmobFieldMediationExtras = 154 - + FLTAdmobFieldMediationExtras = 154, + FLTAdmobFieldAdManagerAdViewOptions = 155, + FLTAdmobBannerParameters = 156, + FLTAdmobCustomParameters = 157, + FLTAdmobNativeParameters = 158, }; @interface FLTGoogleMobileAdsWriter : FlutterStandardWriter @@ -342,6 +345,27 @@ - (id _Nullable)readValueOfType:(UInt8)type { green:green blue:blue]; } + case FLTAdmobFieldAdManagerAdViewOptions: { + return [[FLTAdManagerAdViewOptions alloc] + initWithManualImpressionsEnabled:[self + readValueOfType:[self readByte]]]; + } + case FLTAdmobBannerParameters: { + return [[FLTBannerParameters alloc] + initWithSizes:[self readValueOfType:[self readByte]] + options:[self readValueOfType:[self readByte]]]; + } + case FLTAdmobCustomParameters: { + return [[FLTCustomParameters alloc] + initWithFormatIds:[self readValueOfType:[self readByte]] + viewOptions:[self readValueOfType:[self readByte]]]; + } + case FLTAdmobNativeParameters: { + return [[FLTNativeParameters alloc] + initWithFactoryId:[self readValueOfType:[self readByte]] + nativeAdOptions:[self readValueOfType:[self readByte]] + viewOptions:[self readValueOfType:[self readByte]]]; + } } return [super readValueOfType:type]; } @@ -525,6 +549,26 @@ - (void)writeValue:(id)value { [self writeValue:templateStyle.secondaryTextStyle]; [self writeValue:templateStyle.tertiaryTextStyle]; [self writeValue:templateStyle.cornerRadius]; + } else if ([value isKindOfClass:[FLTAdManagerAdViewOptions class]]) { + [self writeByte:FLTAdmobFieldAdManagerAdViewOptions]; + FLTAdManagerAdViewOptions *options = value; + [self writeValue:options.manualImpressionsEnabled]; + } else if ([value isKindOfClass:[FLTBannerParameters class]]) { + [self writeByte:FLTAdmobBannerParameters]; + FLTBannerParameters *bannerParameters = value; + [self writeValue:bannerParameters.sizes]; + [self writeValue:bannerParameters.options]; + } else if ([value isKindOfClass:[FLTCustomParameters class]]) { + [self writeByte:FLTAdmobCustomParameters]; + FLTCustomParameters *customParameters = value; + [self writeValue:customParameters.formatIds]; + [self writeValue:customParameters.viewOptions]; + } else if ([value isKindOfClass:[FLTNativeParameters class]]) { + [self writeByte:FLTAdmobNativeParameters]; + FLTNativeParameters *nativeParameters = value; + [self writeValue:nativeParameters.factoryId]; + [self writeValue:nativeParameters.nativeAdOptions]; + [self writeValue:nativeParameters.viewOptions]; } else { [super writeValue:value]; } diff --git a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/include/google_mobile_ads/FLTAdInstanceManager_Internal.h b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/include/google_mobile_ads/FLTAdInstanceManager_Internal.h index 459356b84..72f108bf2 100644 --- a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/include/google_mobile_ads/FLTAdInstanceManager_Internal.h +++ b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/include/google_mobile_ads/FLTAdInstanceManager_Internal.h @@ -44,6 +44,10 @@ - (void)onNativeAdWillPresentScreen:(nonnull id)ad; - (void)onNativeAdDidDismissScreen:(nonnull id)ad; - (void)onNativeAdWillDismissScreen:(nonnull id)ad; +- (void)onCustomNativeAdImpression:(nonnull id)ad; +- (void)onCustomNativeAdWillPresentScreen:(nonnull id)ad; +- (void)onCustomNativeAdDidDismissScreen:(nonnull id)ad; +- (void)onCustomNativeAdWillDismissScreen:(nonnull id)ad; - (void)onRewardedAdUserEarnedReward:(FLTRewardedAd *_Nonnull)ad reward:(FLTRewardItem *_Nonnull)reward; - (void)onRewardedInterstitialAdUserEarnedReward: diff --git a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/include/google_mobile_ads/FLTAd_Internal.h b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/include/google_mobile_ads/FLTAd_Internal.h index abdd33aba..5d1916e9b 100644 --- a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/include/google_mobile_ads/FLTAd_Internal.h +++ b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/include/google_mobile_ads/FLTAd_Internal.h @@ -24,6 +24,7 @@ @class FLTAdInstanceManager; @protocol FLTNativeAdFactory; +@protocol FLTCustomAdFactory; @interface FLTAdSize : NSObject @property(readonly) GADAdSize size; @@ -287,6 +288,15 @@ - (NSArray *_Nonnull)asGADAdLoaderOptions; @end +@interface FLTAdManagerAdViewOptions : NSObject +@property(readonly, nullable) NSNumber *manualImpressionsEnabled; + +- (nonnull instancetype)initWithManualImpressionsEnabled: + (nullable NSNumber *)manualImpressionsEnabled; + +- (nonnull NSArray *)asGADAdLoaderOptions; +@end + @interface FLTNativeAd : FLTBaseAd @@ -304,19 +314,57 @@ typedef NS_ENUM(NSInteger, FLTAdLoaderAdType) { FLTAdLoaderAdTypeUnknown = 0, + FLTAdLoaderAdTypeBanner = 1, + FLTAdLoaderAdTypeCustom = 2, + FLTAdLoaderAdTypeNative = 3, }; +@interface FLTBannerParameters : NSObject +@property(readonly, nonnull) NSArray *sizes; +@property(readonly, nullable) FLTAdManagerAdViewOptions *options; +- (nonnull instancetype)initWithSizes:(nonnull NSArray *)sizes + options: + (nullable FLTAdManagerAdViewOptions *)options; +@end + +@interface FLTCustomParameters : NSObject +@property(readonly, nonnull) NSArray *formatIds; +@property(readonly, nullable) NSDictionary *viewOptions; +@property(nullable) NSDictionary> *factories; +- (nonnull instancetype) + initWithFormatIds:(nonnull NSArray *)formatIds + viewOptions:(NSDictionary *_Nullable)viewOptions; +@end + +@interface FLTNativeParameters : NSObject +@property(readonly, nonnull) NSString *factoryId; +@property(readonly, nullable) FLTNativeAdOptions *nativeAdOptions; +@property(readonly, nullable) NSDictionary *viewOptions; +@property(nullable) id factory; +- (nonnull instancetype) + initWithFactoryId:(nonnull NSString *)factoryId + nativeAdOptions:(nullable FLTNativeAdOptions *)nativeAdOptions + viewOptions:(nullable NSDictionary *)viewOptions; +@end + @interface FLTAdLoaderAd - : FLTBaseAd + : FLTBaseAd @property(readonly, nonnull) GADAdLoader *adLoader; @property(readonly) FLTAdLoaderAdType adLoaderAdType; @property(readonly, nullable) FLTAdSize *adSize; @property(readonly, nullable) NSString *formatId; -- (nonnull instancetype)initWithAdUnitId:(nonnull NSString *)adUnitId - request:(nonnull FLTAdRequest *)request - rootViewController: - (nonnull UIViewController *)rootViewController - adId:(nonnull NSNumber *)adId; +- (nonnull instancetype) + initWithAdUnitId:(nonnull NSString *)adUnitId + request:(nonnull FLTAdRequest *)request + rootViewController:(nonnull UIViewController *)rootViewController + adId:(nonnull NSNumber *)adId + banner:(nullable FLTBannerParameters *)bannerParameters + custom:(nullable FLTCustomParameters *)customParameters + native:(nullable FLTNativeParameters *)nativeParameters; @end @interface FLTRewardItem : NSObject diff --git a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/include/google_mobile_ads/FLTGoogleMobileAdsPlugin.h b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/include/google_mobile_ads/FLTGoogleMobileAdsPlugin.h index 9da745da3..d1c3abb36 100644 --- a/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/include/google_mobile_ads/FLTGoogleMobileAdsPlugin.h +++ b/packages/google_mobile_ads/ios/google_mobile_ads/Sources/google_mobile_ads/include/google_mobile_ads/FLTGoogleMobileAdsPlugin.h @@ -46,6 +46,13 @@ (NSDictionary *_Nullable)customOptions; @end +@protocol FLTCustomAdFactory +@required +- (nullable UIView *) + createCustomNativeAd:(nonnull GADCustomNativeAd *)customNativeAd + customOptions:(nullable NSDictionary *)customOptions; +@end + /** * Flutter plugin providing access to the Google Mobile Ads API. */ @@ -95,6 +102,10 @@ nativeAdFactory: (id _Nonnull)nativeAdFactory; ++ (BOOL)registerCustomAdFactory:(nonnull id)registry + formatId:(nonnull NSString *)formatId + customAdFactory:(nonnull id)customAdFactory; + /** * Unregisters a `FLTNativeAdFactory` used to create `GADNativeAdView`s from a * Native Ad created in Dart. @@ -108,4 +119,8 @@ + (id _Nullable) unregisterNativeAdFactory:(id _Nonnull)registry factoryId:(NSString *_Nonnull)factoryId; + ++ (nullable id) + unregisterCustomAdFactory:(nonnull id)registry + formatId:(nonnull NSString *)formatId; @end diff --git a/packages/google_mobile_ads/lib/src/ad_containers.dart b/packages/google_mobile_ads/lib/src/ad_containers.dart index 075415bd8..fa37f249c 100644 --- a/packages/google_mobile_ads/lib/src/ad_containers.dart +++ b/packages/google_mobile_ads/lib/src/ad_containers.dart @@ -1149,6 +1149,15 @@ class NativeAd extends AdWithView { enum AdLoaderAdType { /// Unknown ad type unknown, + + /// Banner ad type + banner, + + /// Custom ad type + custom, + + /// Native ad type + native, } /// An AdLoaderAd. @@ -1184,6 +1193,9 @@ class AdLoaderAd extends AdWithView { required String adUnitId, required this.listener, required AdRequest request, + this.banner, + this.custom, + this.native, }) : super(adUnitId: adUnitId, listener: listener) { if (request is AdManagerAdRequest) { adManagerRequest = request; @@ -1202,6 +1214,15 @@ class AdLoaderAd extends AdWithView { /// Targeting information used to fetch an [Ad] with Ad Manager. AdManagerAdRequest? adManagerRequest; + /// Optional parameters used to configure served "banner" ads + final BannerParameters? banner; + + /// Optional parameters used to configure served "custom" ads + final CustomParameters? custom; + + /// Optional parameters used to configure served "native" ads + final NativeParameters? native; + @override Future load() => instanceManager.loadAdLoaderAd(this); @@ -1631,6 +1652,100 @@ enum AdChoicesPlacement { bottomLeftCorner, } +/// Used to configure ad manager ad view requests. +class AdManagerAdViewOptions { + /// Whether manual impression reporting is enabled + /// + /// Default value is false. + final bool? manualImpressionsEnabled; + + /// Construct an [AdManagerAdViewOptions], an optional class used to further customize + /// ad manager ad view requests. + AdManagerAdViewOptions({this.manualImpressionsEnabled}); + + @override + bool operator ==(other) { + return other is AdManagerAdViewOptions && + manualImpressionsEnabled == other.manualImpressionsEnabled; + } +} + +/// Central configuration item for ad manager ad view requests served by +/// an [AdLoaderAd]. +class BannerParameters { + /// List of sizes the [AdLoaderAd] should expect + final List sizes; + + /// Additional options used when configuring the ad manager ad view + final AdManagerAdViewOptions? adManagerAdViewOptions; + + /// Construct a [BannerParameters], used by an [AdLoaderAd] to configure + /// ad manager ad views + BannerParameters({required this.sizes, this.adManagerAdViewOptions}); + + @override + bool operator ==(other) { + return other is BannerParameters && + listEquals(sizes, other.sizes) && + adManagerAdViewOptions == other.adManagerAdViewOptions; + } +} + +/// Central configuration item for custom format requests served +/// by an [AdLoaderAd] +class CustomParameters { + /// A list of format IDs, corresponding to those in the + /// Google Ad Manager console + final List formatIds; + + /// View options used to create the Platform view + /// + /// These options are passed to the platform's `CustomAdFactory` + Map? viewOptions; + + /// Construct a [CustomParameters] instance, used by an [AdLoaderAd] to + /// configure custom view + CustomParameters({required this.formatIds, this.viewOptions}); + + @override + bool operator ==(other) { + return other is CustomParameters && + listEquals(formatIds, other.formatIds) && + mapEquals(viewOptions, other.viewOptions); + } +} + +/// Central configuration item for native view requests served by an +/// [AdLoaderAd]. +class NativeParameters { + /// An identifier for the factory that creates the Platform view. + final String factoryId; + + /// Options to configure the native ad request. + final NativeAdOptions? nativeAdOptions; + + /// Optional options used to create the Platform view. + /// + /// These options are passed to the platform's `NativeAdFactory`. + final Map? viewOptions; + + /// Construct a [NativeParameters] instance, used by an [AdLoaderAd] to + /// configure native views. + NativeParameters({ + required this.factoryId, + this.nativeAdOptions, + this.viewOptions, + }); + + @override + bool operator ==(other) { + return other is NativeParameters && + factoryId == other.factoryId && + nativeAdOptions == other.nativeAdOptions && + mapEquals(viewOptions, other.viewOptions); + } +} + /// Used to configure native ad requests. class NativeAdOptions { /// Where to place the AdChoices icon. diff --git a/packages/google_mobile_ads/lib/src/ad_instance_manager.dart b/packages/google_mobile_ads/lib/src/ad_instance_manager.dart index 22cdf1a0f..48d69b8da 100644 --- a/packages/google_mobile_ads/lib/src/ad_instance_manager.dart +++ b/packages/google_mobile_ads/lib/src/ad_instance_manager.dart @@ -103,14 +103,17 @@ class AdInstanceManager { break; case 'onNativeAdWillPresentScreen': // Fall through case 'onBannerWillPresentScreen': + case 'onCustomNativeWillPresentScreen': _invokeOnAdOpened(ad, eventName); break; case 'onNativeAdDidDismissScreen': // Fall through case 'onBannerDidDismissScreen': + case 'onCustomNativeAdDidDismissScreen': _invokeOnAdClosed(ad, eventName); break; case 'onBannerWillDismissScreen': // Fall through case 'onNativeAdWillDismissScreen': + case 'onCustomNativeAdWillDismissScreen': if (ad is AdWithView) { ad.listener.onAdWillDismissScreen?.call(ad); } else { @@ -124,6 +127,7 @@ class AdInstanceManager { case 'onBannerImpression': case 'adDidRecordImpression': // Fall through case 'onNativeAdImpression': // Fall through + case 'onCustomNativeAdImpression': _invokeOnAdImpression(ad, eventName); break; case 'adWillPresentFullScreenContent': @@ -509,6 +513,12 @@ class AdInstanceManager { switch (adLoaderAdType) { case 0: return AdLoaderAdType.unknown; + case 1: + return AdLoaderAdType.banner; + case 2: + return AdLoaderAdType.custom; + case 3: + return AdLoaderAdType.native; default: debugPrint('Error: unknown AdLoaderAdType value: $adLoaderAdType'); return AdLoaderAdType.unknown; @@ -517,6 +527,12 @@ class AdInstanceManager { switch (adLoaderAdType) { case 0: return AdLoaderAdType.unknown; + case 1: + return AdLoaderAdType.banner; + case 2: + return AdLoaderAdType.custom; + case 3: + return AdLoaderAdType.native; default: debugPrint('Error: unknown AdLoaderAdType value: $adLoaderAdType'); return AdLoaderAdType.unknown; @@ -617,6 +633,9 @@ class AdInstanceManager { 'adUnitId': ad.adUnitId, 'request': ad.request, 'adManagerRequest': ad.adManagerRequest, + 'banner': ad.banner, + 'custom': ad.custom, + 'native': ad.native, }); } @@ -928,6 +947,10 @@ class AdMessageCodec extends StandardMessageCodec { static const int _valueNativeTemplateType = 152; static const int _valueColor = 153; static const int _valueMediationExtras = 154; + static const int _valueAdManagerAdViewOptions = 155; + static const int _valueBannerParameters = 156; + static const int _valueCustomParameters = 157; + static const int _valueNativeParameters = 158; @override void writeValue(WriteBuffer buffer, dynamic value) { @@ -1064,6 +1087,22 @@ class AdMessageCodec extends StandardMessageCodec { } else if (value is NativeTemplateFontStyle) { buffer.putUint8(_valueNativeTemplateFontStyle); writeValue(buffer, value.index); + } else if (value is AdManagerAdViewOptions) { + buffer.putUint8(_valueAdManagerAdViewOptions); + writeValue(buffer, value.manualImpressionsEnabled); + } else if (value is BannerParameters) { + buffer.putUint8(_valueBannerParameters); + writeValue(buffer, value.sizes); + writeValue(buffer, value.adManagerAdViewOptions); + } else if (value is CustomParameters) { + buffer.putUint8(_valueCustomParameters); + writeValue(buffer, value.formatIds); + writeValue(buffer, value.viewOptions); + } else if (value is NativeParameters) { + buffer.putUint8(_valueNativeParameters); + writeValue(buffer, value.factoryId); + writeValue(buffer, value.nativeAdOptions); + writeValue(buffer, value.viewOptions); } else { super.writeValue(buffer, value); } @@ -1153,8 +1192,8 @@ class AdMessageCodec extends StandardMessageCodec { ); case _valueRewardItem: return RewardItem( - readValueOfType(buffer.getUint8(), buffer), - readValueOfType(buffer.getUint8(), buffer), + readValueOfType(buffer.getUint8(), buffer) ?? 0, + _safeReadString(buffer), ); case _valueResponseInfo: return ResponseInfo( @@ -1239,7 +1278,7 @@ class AdMessageCodec extends StandardMessageCodec { buffer.getUint8(), buffer, ); - final String description = readValueOfType(buffer.getUint8(), buffer); + final String description = _safeReadString(buffer); double latency = readValueOfType(buffer.getUint8(), buffer).toDouble(); // Android provides this value as an int in milliseconds while iOS @@ -1330,6 +1369,32 @@ class AdMessageCodec extends StandardMessageCodec { buffer.getUint8(), buffer, )]; + case _valueAdManagerAdViewOptions: + return AdManagerAdViewOptions( + manualImpressionsEnabled: readValueOfType(buffer.getUint8(), buffer), + ); + case _valueBannerParameters: + return BannerParameters( + sizes: readValueOfType(buffer.getUint8(), buffer)?.cast(), + adManagerAdViewOptions: readValueOfType(buffer.getUint8(), buffer), + ); + case _valueCustomParameters: + return CustomParameters( + formatIds: readValueOfType(buffer.getUint8(), buffer).cast(), + viewOptions: readValueOfType( + buffer.getUint8(), + buffer, + )?.cast(), + ); + case _valueNativeParameters: + return NativeParameters( + factoryId: readValueOfType(buffer.getUint8(), buffer), + nativeAdOptions: readValueOfType(buffer.getUint8(), buffer), + viewOptions: readValueOfType( + buffer.getUint8(), + buffer, + )?.cast(), + ); default: return super.readValueOfType(type, buffer); } diff --git a/packages/google_mobile_ads/test/ad_containers_test.dart b/packages/google_mobile_ads/test/ad_containers_test.dart index ef0652449..d6c615756 100644 --- a/packages/google_mobile_ads/test/ad_containers_test.dart +++ b/packages/google_mobile_ads/test/ad_containers_test.dart @@ -1196,6 +1196,31 @@ void main() { expect(result.type, 'type'); }); + test('encode/decode $RewardItem with null values', () async { + final WriteBuffer buffer = WriteBuffer(); + buffer.putUint8(132); + codec.writeValue(buffer, null); + codec.writeValue(buffer, null); + + final RewardItem result = codec.decodeMessage(buffer.done()); + expect(result.amount, 0); + expect(result.type, ''); + }); + + test('encode/decode $AdapterStatus with null description', () async { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + final WriteBuffer buffer = WriteBuffer(); + buffer.putUint8(136); + codec.writeValue(buffer, AdapterInitializationState.ready); + codec.writeValue(buffer, null); + codec.writeValue(buffer, 1000); + + final AdapterStatus result = codec.decodeMessage(buffer.done()); + expect(result.state, AdapterInitializationState.ready); + expect(result.description, ''); + expect(result.latency, 1.0); + }); + test('encode/decode $InlineAdaptiveSize', () async { ByteData byteData = codec.encodeMessage( AdSize.getCurrentOrientationInlineAdaptiveBannerAdSize(100), diff --git a/packages/google_mobile_ads/test/ad_loader_ad_test.dart b/packages/google_mobile_ads/test/ad_loader_ad_test.dart index 516b0ac17..5cde4e6fd 100644 --- a/packages/google_mobile_ads/test/ad_loader_ad_test.dart +++ b/packages/google_mobile_ads/test/ad_loader_ad_test.dart @@ -71,6 +71,9 @@ void main() { 'adUnitId': 'test-ad-unit', 'request': request, 'adManagerRequest': null, + 'banner': null, + 'custom': null, + 'native': null, }, ), ]); @@ -96,6 +99,97 @@ void main() { 'adUnitId': 'test-ad-unit', 'request': null, 'adManagerRequest': request, + 'banner': null, + 'custom': null, + 'native': null, + }, + ), + ]); + + expect(instanceManager.adFor(0), isNotNull); + }); + + test('load with $BannerParameters', () async { + final BannerParameters banner = BannerParameters(sizes: [AdSize.banner]); + final AdLoaderAd adLoaderAd = AdLoaderAd( + adUnitId: 'test-ad-unit', + listener: AdLoaderAdListener(), + request: AdRequest(), + banner: banner, + ); + + await adLoaderAd.load(); + expect(log, [ + isMethodCall( + 'loadAdLoaderAd', + arguments: { + 'adId': 0, + 'adUnitId': 'test-ad-unit', + 'request': adLoaderAd.request, + 'adManagerRequest': null, + 'banner': banner, + 'custom': null, + 'native': null, + }, + ), + ]); + + expect(instanceManager.adFor(0), isNotNull); + }); + + test('load with $CustomParameters', () async { + final CustomParameters custom = CustomParameters( + formatIds: ['test-format-id'], + ); + final AdLoaderAd adLoaderAd = AdLoaderAd( + adUnitId: 'test-ad-unit', + listener: AdLoaderAdListener(), + request: AdRequest(), + custom: custom, + ); + + await adLoaderAd.load(); + expect(log, [ + isMethodCall( + 'loadAdLoaderAd', + arguments: { + 'adId': 0, + 'adUnitId': 'test-ad-unit', + 'request': adLoaderAd.request, + 'adManagerRequest': null, + 'banner': null, + 'custom': custom, + 'native': null, + }, + ), + ]); + + expect(instanceManager.adFor(0), isNotNull); + }); + + test('load with $NativeParameters', () async { + final NativeParameters native = NativeParameters( + factoryId: 'test-factory-id', + ); + final AdLoaderAd adLoaderAd = AdLoaderAd( + adUnitId: 'test-ad-unit', + listener: AdLoaderAdListener(), + request: AdRequest(), + native: native, + ); + + await adLoaderAd.load(); + expect(log, [ + isMethodCall( + 'loadAdLoaderAd', + arguments: { + 'adId': 0, + 'adUnitId': 'test-ad-unit', + 'request': adLoaderAd.request, + 'adManagerRequest': null, + 'banner': null, + 'custom': null, + 'native': native, }, ), ]); @@ -238,6 +332,205 @@ void main() { await testOnAdFailedToLoad('onAdFailedToLoad', 1); }); + test('onAdOpened event', () async { + var testOnAdOpened = (eventName, adId) async { + final Completer completer = Completer(); + + final AdLoaderAd ad = AdLoaderAd( + adUnitId: 'test-ad-unit', + listener: AdLoaderAdListener( + onAdOpened: (ad) => completer.complete(ad), + ), + request: AdRequest(), + ); + + await ad.load(); + + final MethodCall methodCall = MethodCall( + 'onAdEvent', + {'adId': adId, 'eventName': eventName}, + ); + + final ByteData data = instanceManager.channel.codec.encodeMethodCall( + methodCall, + ); + + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage( + 'plugins.flutter.io/google_mobile_ads', + data, + (ByteData? data) {}, + ); + + expect(completer.future, completion(ad)); + }; + + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + await testOnAdOpened('onBannerWillPresentScreen', 0); + + debugDefaultTargetPlatformOverride = TargetPlatform.android; + await testOnAdOpened('onAdOpened', 1); + }); + + test('onAdWillDismissScreen event', () async { + var testOnAdWillDismissScreen = (eventName, adId) async { + final Completer completer = Completer(); + + final AdLoaderAd ad = AdLoaderAd( + adUnitId: 'test-ad-unit', + listener: AdLoaderAdListener( + onAdWillDismissScreen: (ad) => completer.complete(ad), + ), + request: AdRequest(), + ); + + await ad.load(); + + final MethodCall methodCall = MethodCall( + 'onAdEvent', + {'adId': adId, 'eventName': eventName}, + ); + + final ByteData data = instanceManager.channel.codec.encodeMethodCall( + methodCall, + ); + + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage( + 'plugins.flutter.io/google_mobile_ads', + data, + (ByteData? data) {}, + ); + + expect(completer.future, completion(ad)); + }; + + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + await testOnAdWillDismissScreen('onBannerWillDismissScreen', 0); + }); + + test('onAdClosed event', () async { + var testOnAdClosed = (eventName, adId) async { + final Completer completer = Completer(); + + final AdLoaderAd ad = AdLoaderAd( + adUnitId: 'test-ad-unit', + listener: AdLoaderAdListener( + onAdClosed: (ad) => completer.complete(ad), + ), + request: AdRequest(), + ); + + await ad.load(); + + final MethodCall methodCall = MethodCall( + 'onAdEvent', + {'adId': adId, 'eventName': eventName}, + ); + + final ByteData data = instanceManager.channel.codec.encodeMethodCall( + methodCall, + ); + + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage( + 'plugins.flutter.io/google_mobile_ads', + data, + (ByteData? data) {}, + ); + + expect(completer.future, completion(ad)); + }; + + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + await testOnAdClosed('onBannerDidDismissScreen', 0); + + debugDefaultTargetPlatformOverride = TargetPlatform.android; + await testOnAdClosed('onAdClosed', 1); + }); + + test('onAdImpression event', () async { + var testOnAdImpression = (eventName, adId) async { + final Completer completer = Completer(); + + final AdLoaderAd ad = AdLoaderAd( + adUnitId: 'test-ad-unit', + listener: AdLoaderAdListener( + onAdImpression: (ad) => completer.complete(ad), + ), + request: AdRequest(), + ); + + await ad.load(); + + final MethodCall methodCall = MethodCall( + 'onAdEvent', + {'adId': adId, 'eventName': eventName}, + ); + + final ByteData data = instanceManager.channel.codec.encodeMethodCall( + methodCall, + ); + + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage( + 'plugins.flutter.io/google_mobile_ads', + data, + (ByteData? data) {}, + ); + + expect(completer.future, completion(ad)); + }; + + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + await testOnAdImpression('onBannerImpression', 0); + + debugDefaultTargetPlatformOverride = TargetPlatform.android; + await testOnAdImpression('onAdImpression', 1); + }); + + test('onPaidEvent event', () async { + var testOnPaidEvent = (eventName, adId) async { + final Completer completer = Completer(); + + final AdLoaderAd ad = AdLoaderAd( + adUnitId: 'test-ad-unit', + listener: AdLoaderAdListener( + onPaidEvent: (ad, micros, precision, currency) => + completer.complete(ad), + ), + request: AdRequest(), + ); + + await ad.load(); + + final MethodCall methodCall = + MethodCall('onAdEvent', { + 'adId': adId, + 'eventName': eventName, + 'valueMicros': 0, + 'precision': 0, + 'currencyCode': 'AUD', + }); + + final ByteData data = instanceManager.channel.codec.encodeMethodCall( + methodCall, + ); + + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage( + 'plugins.flutter.io/google_mobile_ads', + data, + (ByteData? data) {}, + ); + + expect(completer.future, completion(ad)); + }; + + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + await testOnPaidEvent('onPaidEvent', 0); + }); + test('onAdClicked event', () async { var testOnAdClicked = (eventName, adId) async { final Completer completer = Completer(); diff --git a/packages/google_mobile_ads/test/mobile_ads_test.dart b/packages/google_mobile_ads/test/mobile_ads_test.dart index ab7eddad7..c99bfd69f 100644 --- a/packages/google_mobile_ads/test/mobile_ads_test.dart +++ b/packages/google_mobile_ads/test/mobile_ads_test.dart @@ -634,5 +634,120 @@ void main() { expect(result.tertiaryTextStyle, templateStyle.tertiaryTextStyle); expect(result.mainBackgroundColor, templateStyle.mainBackgroundColor); }); + + test('encode/decode minimal $AdManagerAdViewOptions', () { + for (final platform in [TargetPlatform.android, TargetPlatform.iOS]) { + debugDefaultTargetPlatformOverride = platform; + ByteData byteData = codec.encodeMessage(AdManagerAdViewOptions())!; + + AdManagerAdViewOptions result = codec.decodeMessage(byteData); + expect(result.manualImpressionsEnabled, null); + } + }); + + test('encode/decode $AdManagerAdViewOptions', () { + for (final platform in [TargetPlatform.android, TargetPlatform.iOS]) { + debugDefaultTargetPlatformOverride = platform; + ByteData byteData = codec.encodeMessage( + AdManagerAdViewOptions(manualImpressionsEnabled: true), + )!; + + AdManagerAdViewOptions result = codec.decodeMessage(byteData); + expect(result.manualImpressionsEnabled, true); + + byteData = codec.encodeMessage( + AdManagerAdViewOptions(manualImpressionsEnabled: false), + )!; + + result = codec.decodeMessage(byteData); + expect(result.manualImpressionsEnabled, false); + + byteData = codec.encodeMessage( + AdManagerAdViewOptions(manualImpressionsEnabled: null), + )!; + + result = codec.decodeMessage(byteData); + expect(result.manualImpressionsEnabled, null); + } + }); + + test('encode/decode $BannerParameters', () { + for (final platform in [TargetPlatform.android, TargetPlatform.iOS]) { + debugDefaultTargetPlatformOverride = platform; + ByteData byteData = codec.encodeMessage( + BannerParameters( + sizes: [AdSize.banner], + adManagerAdViewOptions: AdManagerAdViewOptions( + manualImpressionsEnabled: true, + ), + ), + )!; + + BannerParameters result = codec.decodeMessage(byteData); + expect(result.sizes, [AdSize.banner]); + expect(result.adManagerAdViewOptions?.manualImpressionsEnabled, true); + } + }); + + test('encode/decode minimal $CustomParameters', () { + for (final platform in [TargetPlatform.android, TargetPlatform.iOS]) { + debugDefaultTargetPlatformOverride = platform; + ByteData byteData = codec.encodeMessage( + CustomParameters(formatIds: ['test-format-id']), + )!; + + CustomParameters result = codec.decodeMessage(byteData); + expect(result.formatIds, ['test-format-id']); + expect(result.viewOptions, null); + } + }); + + test('encode/decode $CustomParameters', () { + for (final platform in [TargetPlatform.android, TargetPlatform.iOS]) { + debugDefaultTargetPlatformOverride = platform; + ByteData byteData = codec.encodeMessage( + CustomParameters( + formatIds: ['test-format-id'], + viewOptions: {'key': 'value'}, + ), + )!; + + CustomParameters result = codec.decodeMessage(byteData); + expect(result.formatIds, ['test-format-id']); + expect(result.viewOptions, {'key': 'value'}); + } + }); + + test('encode/decode minimal $NativeParameters', () { + for (final platform in [TargetPlatform.android, TargetPlatform.iOS]) { + debugDefaultTargetPlatformOverride = platform; + ByteData byteData = codec.encodeMessage( + NativeParameters(factoryId: 'test-factory-id'), + )!; + + NativeParameters result = codec.decodeMessage(byteData); + expect(result.factoryId, 'test-factory-id'); + expect(result.nativeAdOptions, null); + expect(result.viewOptions, null); + } + }); + + test('encode/decode $NativeParameters', () { + for (final platform in [TargetPlatform.android, TargetPlatform.iOS]) { + debugDefaultTargetPlatformOverride = platform; + ByteData byteData = codec.encodeMessage( + NativeParameters( + factoryId: 'test-factory-id', + nativeAdOptions: NativeAdOptions(), + viewOptions: {'key': 'value'}, + ), + )!; + + NativeParameters result = codec.decodeMessage(byteData); + expect(result.factoryId, 'test-factory-id'); + expect(result.nativeAdOptions, NativeAdOptions()); + expect(result.viewOptions, {'key': 'value'}); + } + }); }); }