From 9093c78253dc54fb1cc7663594c217199e881114 Mon Sep 17 00:00:00 2001
From: jjliu15 <58007281+jjliu15@users.noreply.github.com>
Date: Thu, 29 Jul 2021 23:58:49 -0700
Subject: [PATCH] Native ad options (#311)
* Support the native ad options API.
---
packages/google_mobile_ads/CHANGELOG.md | 4 +
packages/google_mobile_ads/README.md | 92 ++++++++++++
.../googlemobileads/AdMessageCodec.java | 30 ++++
.../plugins/googlemobileads/Constants.java | 2 +-
.../googlemobileads/FlutterNativeAd.java | 33 ++++-
.../FlutterNativeAdOptions.java | 68 +++++++++
.../googlemobileads/FlutterVideoOptions.java | 49 +++++++
.../GoogleMobileAdsPlugin.java | 1 +
.../googlemobileads/AdMessageCodecTest.java | 48 +++++++
.../FlutterNativeAdOptionsTest.java | 64 +++++++++
.../googlemobileads/FlutterNativeAdTest.java | 16 ++-
.../FlutterVideoOptionsTest.java | 50 +++++++
.../ios/Runner.xcodeproj/project.pbxproj | 6 +
.../google_mobile_ads/ios/Classes/FLTAdUtil.h | 23 +++
.../google_mobile_ads/ios/Classes/FLTAdUtil.m | 27 ++++
.../ios/Classes/FLTAd_Internal.h | 33 ++++-
.../ios/Classes/FLTAd_Internal.m | 131 +++++++++++++++++-
.../ios/Classes/FLTConstants.h | 2 +-
.../ios/Classes/FLTGoogleMobileAdsPlugin.m | 3 +-
.../FLTGoogleMobileAdsReaderWriter_Internal.m | 33 +++++
.../ios/Tests/FLTGoogleMobileAdsTest.m | 15 +-
.../ios/Tests/FLTNativeAdTest.m | 10 +-
.../lib/src/ad_containers.dart | 128 +++++++++++++++++
.../lib/src/ad_instance_manager.dart | 107 +++++++++++++-
packages/google_mobile_ads/pubspec.yaml | 2 +-
.../test/ad_containers_test.dart | 15 +-
.../test/mobile_ads_test.dart | 76 ++++++++++
27 files changed, 1041 insertions(+), 27 deletions(-)
create mode 100644 packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterNativeAdOptions.java
create mode 100644 packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterVideoOptions.java
create mode 100644 packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterNativeAdOptionsTest.java
create mode 100644 packages/google_mobile_ads/android/src/test/java/io/flutter/plugins/googlemobileads/FlutterVideoOptionsTest.java
create mode 100644 packages/google_mobile_ads/ios/Classes/FLTAdUtil.h
create mode 100644 packages/google_mobile_ads/ios/Classes/FLTAdUtil.m
diff --git a/packages/google_mobile_ads/CHANGELOG.md b/packages/google_mobile_ads/CHANGELOG.md
index e9f786584..c7ac2b5a5 100644
--- a/packages/google_mobile_ads/CHANGELOG.md
+++ b/packages/google_mobile_ads/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.13.3
+
+* Adds support for NativeAdOptions. More documentation also available for [Android](https://developers.google.com/admob/android/native/options) and [iOS](https://developers.google.com/admob/ios/native/options)
+
## 0.13.2+1
* Fixes [Issue #130](https://github.com/googleads/googleads-mobile-flutter/issues/130)
diff --git a/packages/google_mobile_ads/README.md b/packages/google_mobile_ads/README.md
index 5d94b4ed0..92364561b 100644
--- a/packages/google_mobile_ads/README.md
+++ b/packages/google_mobile_ads/README.md
@@ -663,6 +663,52 @@ final NativeAdListener listener = NativeAdListener(
);
```
+#### NativeAdOptions
+
+`NativeAds` have an optional argument, `nativeAdOptions`, which can be used to set specific options on the native ad.
+
+`shouldRequestMultipleImages`
+
If set to `true`, the SDK will not load image asset content and native ad
+image URLs can be used to fetch content. Defaults to false.
+
+`shouldRequestMultipleImages`
+
+Some image assets will contain a series of images rather than just one. By setting this value to true,
+your app indicates that it's prepared to display all the images for any assets that have more than one.
+By setting it to false (the default) your app instructs the SDK to provide just the first image for any assets that contain a series.
+
+If no `NativeadOptions` are passed in when initializing a `NativeAd`, the default value for each property will be used.
+
+
+`adChoicesPlacement`
+
+The [AdChoices overlay](https://developers.google.com/admob/android/native/advanced#adchoices_overlay) is set to the top right corner by default.
+Apps can change which corner this overlay is rendered in by setting this property to one of the following:
+
+* AdChoicesPlacement.topRightCorner
+* AdChoicesPlacement.topLeftCorner
+* AdChoicesPlacement.bottomRightCorner
+* AdChoicesPlacement.bottomLeftCorner
+
+
+`videoOptions`
+
+Can be used to set video options for video assets returned as part of a native ad.
+
+
+`mediaAspectRatio`
+
+This sets the aspect ratio for image or video to be returned for the native ad.
+Setting NativeMediaAspectRatio to one of the following constants will cause only ads with media of the specified aspect ratio to be returned:
+
+* MediaAspectRatio.landscape
+* MediaAspectRatio.portrait
+* MediaAspectRatio.square
+* MediaAspectRatio.any
+
+If not set, ads with any aspect ratio will be returned.
+
+
### Load Native Ad
After a `NativeAd` is instantiated, `load()` must be called before it can be shown on the screen.
@@ -1339,6 +1385,52 @@ final NativeAdListener listener = NativeAdListener(
);
```
+#### NativeAdOptions
+
+`NativeAds` have an optional argument, `nativeAdOptions`, which can be used to set specific options on the native ad.
+
+`shouldRequestMultipleImages`
+If set to `true`, the SDK will not load image asset content and native ad
+image URLs can be used to fetch content. Defaults to false.
+
+`shouldRequestMultipleImages`
+
+Some image assets will contain a series of images rather than just one. By setting this value to true,
+your app indicates that it's prepared to display all the images for any assets that have more than one.
+By setting it to false (the default) your app instructs the SDK to provide just the first image for any assets that contain a series.
+
+If no `NativeadOptions` are passed in when initializing a `NativeAd`, the default value for each property will be used.
+
+
+`adChoicesPlacement`
+
+The [AdChoices overlay](https://developers.google.com/admob/android/native/advanced#adchoices_overlay) is set to the top right corner by default.
+Apps can change which corner this overlay is rendered in by setting this property to one of the following:
+
+* AdChoicesPlacement.topRightCorner
+* AdChoicesPlacement.topLeftCorner
+* AdChoicesPlacement.bottomRightCorner
+* AdChoicesPlacement.bottomLeftCorner
+
+
+`videoOptions`
+
+Can be used to set video options for video assets returned as part of a native ad.
+
+
+`mediaAspectRatio`
+
+This sets the aspect ratio for image or video to be returned for the native ad.
+Setting NativeMediaAspectRatio to one of the following constants will cause only ads with media of the specified aspect ratio to be returned:
+
+* MediaAspectRatio.landscape
+* MediaAspectRatio.portrait
+* MediaAspectRatio.square
+* MediaAspectRatio.any
+
+If not set, ads with any aspect ratio will be returned.
+
+
### Load Native Ad
After a `NativeAd` is instantiated, `load()` must be called before it can be shown on the screen.
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 694431017..382017450 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
@@ -47,6 +47,8 @@ class AdMessageCodec extends StandardMessageCodec {
private static final byte VALUE_ADAPTER_RESPONSE_INFO = (byte) 141;
static final byte VALUE_ANCHORED_ADAPTIVE_BANNER_AD_SIZE = (byte) 142;
static final byte VALUE_SMART_BANNER_AD_SIZE = (byte) 143;
+ static final byte VALUE_NATIVE_AD_OPTIONS = (byte) 144;
+ static final byte VALUE_VIDEO_OPTIONS = (byte) 145;
@NonNull final Context context;
@NonNull final FlutterAdSize.AdSizeFactory adSizeFactory;
@@ -141,6 +143,21 @@ protected void writeValue(ByteArrayOutputStream stream, Object value) {
FlutterServerSideVerificationOptions options = (FlutterServerSideVerificationOptions) value;
writeValue(stream, options.getUserId());
writeValue(stream, options.getCustomData());
+ } else if (value instanceof FlutterNativeAdOptions) {
+ stream.write(VALUE_NATIVE_AD_OPTIONS);
+ FlutterNativeAdOptions options = (FlutterNativeAdOptions) value;
+ writeValue(stream, options.adChoicesPlacement);
+ writeValue(stream, options.mediaAspectRatio);
+ writeValue(stream, options.videoOptions);
+ writeValue(stream, options.requestCustomMuteThisAd);
+ writeValue(stream, options.shouldRequestMultipleImages);
+ writeValue(stream, options.shouldReturnUrlsForImageAssets);
+ } else if (value instanceof FlutterVideoOptions) {
+ stream.write(VALUE_VIDEO_OPTIONS);
+ FlutterVideoOptions options = (FlutterVideoOptions) value;
+ writeValue(stream, options.clickToExpandRequested);
+ writeValue(stream, options.customControlsRequested);
+ writeValue(stream, options.startMuted);
} else {
super.writeValue(stream, value);
}
@@ -226,6 +243,19 @@ protected Object readValueOfType(byte type, ByteBuffer buffer) {
return new FlutterServerSideVerificationOptions(
(String) readValueOfType(buffer.get(), buffer),
(String) readValueOfType(buffer.get(), buffer));
+ case VALUE_NATIVE_AD_OPTIONS:
+ return new FlutterNativeAdOptions(
+ (Integer) readValueOfType(buffer.get(), buffer),
+ (Integer) readValueOfType(buffer.get(), buffer),
+ (FlutterVideoOptions) readValueOfType(buffer.get(), buffer),
+ (Boolean) readValueOfType(buffer.get(), buffer),
+ (Boolean) readValueOfType(buffer.get(), buffer),
+ (Boolean) readValueOfType(buffer.get(), buffer));
+ case VALUE_VIDEO_OPTIONS:
+ return new FlutterVideoOptions(
+ (Boolean) readValueOfType(buffer.get(), buffer),
+ (Boolean) readValueOfType(buffer.get(), buffer),
+ (Boolean) 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/Constants.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/Constants.java
index 6417b1813..1c411b86a 100644
--- a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/Constants.java
+++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/Constants.java
@@ -17,5 +17,5 @@
/** Constants used in the plugin. */
public class Constants {
/** Version request agent. Should be bumped alongside plugin versions. */
- public static final String REQUEST_AGENT_PREFIX_VERSIONED = "Flutter-GMA-0.13.2+1";
+ public static final String REQUEST_AGENT_PREFIX_VERSIONED = "Flutter-GMA-0.13.3";
}
diff --git a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterNativeAd.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterNativeAd.java
index d05237915..c476107a4 100644
--- a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterNativeAd.java
+++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterNativeAd.java
@@ -38,6 +38,7 @@ class FlutterNativeAd extends FlutterAd {
@Nullable private FlutterAdManagerAdRequest adManagerRequest;
@Nullable private Map customOptions;
@Nullable private NativeAdView nativeAdView;
+ @Nullable private FlutterNativeAdOptions nativeAdOptions;
static class Builder {
@Nullable private AdInstanceManager manager;
@@ -47,6 +48,7 @@ static class Builder {
@Nullable private FlutterAdManagerAdRequest adManagerRequest;
@Nullable private Map customOptions;
@Nullable private Integer id;
+ @Nullable private FlutterNativeAdOptions nativeAdOptions;
public Builder setAdFactory(@NonNull NativeAdFactory adFactory) {
this.adFactory = adFactory;
@@ -83,6 +85,11 @@ public Builder setAdManagerRequest(@NonNull FlutterAdManagerAdRequest request) {
return this;
}
+ public Builder setNativeAdOptions(@Nullable FlutterNativeAdOptions nativeAdOptions) {
+ this.nativeAdOptions = nativeAdOptions;
+ return this;
+ }
+
FlutterNativeAd build() {
if (manager == null) {
throw new IllegalStateException("AdInstanceManager cannot not be null.");
@@ -104,11 +111,19 @@ FlutterNativeAd build() {
adFactory,
adManagerRequest,
new FlutterAdLoader(),
- customOptions);
+ customOptions,
+ nativeAdOptions);
} else {
nativeAd =
new FlutterNativeAd(
- id, manager, adUnitId, adFactory, request, new FlutterAdLoader(), customOptions);
+ id,
+ manager,
+ adUnitId,
+ adFactory,
+ request,
+ new FlutterAdLoader(),
+ customOptions,
+ nativeAdOptions);
}
return nativeAd;
}
@@ -121,7 +136,8 @@ protected FlutterNativeAd(
@NonNull NativeAdFactory adFactory,
@NonNull FlutterAdRequest request,
@NonNull FlutterAdLoader flutterAdLoader,
- @Nullable Map customOptions) {
+ @Nullable Map customOptions,
+ @Nullable FlutterNativeAdOptions nativeAdOptions) {
super(adId);
this.manager = manager;
this.adUnitId = adUnitId;
@@ -129,6 +145,7 @@ protected FlutterNativeAd(
this.request = request;
this.flutterAdLoader = flutterAdLoader;
this.customOptions = customOptions;
+ this.nativeAdOptions = nativeAdOptions;
}
protected FlutterNativeAd(
@@ -138,7 +155,8 @@ protected FlutterNativeAd(
@NonNull NativeAdFactory adFactory,
@NonNull FlutterAdManagerAdRequest adManagerRequest,
@NonNull FlutterAdLoader flutterAdLoader,
- @Nullable Map customOptions) {
+ @Nullable Map customOptions,
+ @Nullable FlutterNativeAdOptions nativeAdOptions) {
super(adId);
this.manager = manager;
this.adUnitId = adUnitId;
@@ -146,16 +164,19 @@ protected FlutterNativeAd(
this.adManagerRequest = adManagerRequest;
this.flutterAdLoader = flutterAdLoader;
this.customOptions = customOptions;
+ this.nativeAdOptions = nativeAdOptions;
}
@Override
void load() {
final OnNativeAdLoadedListener loadedListener = new FlutterNativeAdLoadedListener(this);
final AdListener adListener = new FlutterNativeAdListener(adId, manager);
- final NativeAdOptions options = new NativeAdOptions.Builder().build();
-
// 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.
+ final NativeAdOptions options =
+ this.nativeAdOptions == null
+ ? new NativeAdOptions.Builder().build()
+ : nativeAdOptions.asNativeAdOptions();
if (request != null) {
flutterAdLoader.loadNativeAd(
manager.activity, adUnitId, loadedListener, options, adListener, request.asAdRequest());
diff --git a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterNativeAdOptions.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterNativeAdOptions.java
new file mode 100644
index 000000000..fe2bb7751
--- /dev/null
+++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterNativeAdOptions.java
@@ -0,0 +1,68 @@
+// Copyright 2021 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.nativead.NativeAdOptions;
+
+/** A wrapper for {@link com.google.android.gms.ads.nativead.NativeAdOptions}. */
+class FlutterNativeAdOptions {
+
+ @Nullable final Integer adChoicesPlacement;
+ @Nullable final Integer mediaAspectRatio;
+ @Nullable final FlutterVideoOptions videoOptions;
+ @Nullable final Boolean requestCustomMuteThisAd;
+ @Nullable final Boolean shouldRequestMultipleImages;
+ @Nullable final Boolean shouldReturnUrlsForImageAssets;
+
+ FlutterNativeAdOptions(
+ @Nullable Integer adChoicesPlacement,
+ @Nullable Integer mediaAspectRatio,
+ @Nullable FlutterVideoOptions videoOptions,
+ @Nullable Boolean requestCustomMuteThisAd,
+ @Nullable Boolean shouldRequestMultipleImages,
+ @Nullable Boolean shouldReturnUrlsForImageAssets) {
+ this.adChoicesPlacement = adChoicesPlacement;
+ this.mediaAspectRatio = mediaAspectRatio;
+ this.videoOptions = videoOptions;
+ this.requestCustomMuteThisAd = requestCustomMuteThisAd;
+ this.shouldRequestMultipleImages = shouldRequestMultipleImages;
+ this.shouldReturnUrlsForImageAssets = shouldReturnUrlsForImageAssets;
+ }
+
+ NativeAdOptions asNativeAdOptions() {
+ NativeAdOptions.Builder builder = new NativeAdOptions.Builder();
+ if (adChoicesPlacement != null) {
+ builder.setAdChoicesPlacement(adChoicesPlacement);
+ }
+ if (mediaAspectRatio != null) {
+ builder.setMediaAspectRatio(mediaAspectRatio);
+ }
+ if (videoOptions != null) {
+ builder.setVideoOptions(videoOptions.asVideoOptions());
+ }
+ if (requestCustomMuteThisAd != null) {
+ builder.setRequestCustomMuteThisAd(requestCustomMuteThisAd);
+ }
+ if (shouldRequestMultipleImages != null) {
+ builder.setRequestMultipleImages(shouldRequestMultipleImages);
+ }
+ if (shouldReturnUrlsForImageAssets != null) {
+ builder.setReturnUrlsForImageAssets(shouldReturnUrlsForImageAssets);
+ }
+ return builder.build();
+ }
+}
diff --git a/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterVideoOptions.java b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterVideoOptions.java
new file mode 100644
index 000000000..25a29e6bb
--- /dev/null
+++ b/packages/google_mobile_ads/android/src/main/java/io/flutter/plugins/googlemobileads/FlutterVideoOptions.java
@@ -0,0 +1,49 @@
+// Copyright 2021 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.VideoOptions;
+
+/** A wrapper for {@link com.google.android.gms.ads.VideoOptions}. */
+class FlutterVideoOptions {
+ @Nullable final Boolean clickToExpandRequested;
+ @Nullable final Boolean customControlsRequested;
+ @Nullable final Boolean startMuted;
+
+ FlutterVideoOptions(
+ @Nullable Boolean clickToExpandRequested,
+ @Nullable Boolean customControlsRequested,
+ @Nullable Boolean startMuted) {
+ this.clickToExpandRequested = clickToExpandRequested;
+ this.customControlsRequested = customControlsRequested;
+ this.startMuted = startMuted;
+ }
+
+ VideoOptions asVideoOptions() {
+ VideoOptions.Builder builder = new VideoOptions.Builder();
+ if (clickToExpandRequested != null) {
+ builder.setClickToExpandRequested(clickToExpandRequested);
+ }
+ if (customControlsRequested != null) {
+ builder.setCustomControlsRequested(customControlsRequested);
+ }
+ if (startMuted != null) {
+ builder.setStartMuted(startMuted);
+ }
+ return builder.build();
+ }
+}
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 cc733a38f..783484048 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
@@ -286,6 +286,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
.setAdManagerRequest(call.argument("adManagerRequest"))
.setCustomOptions(call.