@ququplay/admob 8.2.0 → 8.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -72,6 +72,8 @@ public class AdMob extends Plugin {
72
72
  @PluginMethod
73
73
  public void initialize(final PluginCall call) {
74
74
  this.setRequestConfiguration(call);
75
+ // The Unity Ads adapter starts Unity Ads here: give it the stored consent answer first.
76
+ adConsentExecutor.passConsentToUnityAds();
75
77
 
76
78
  try {
77
79
  MobileAds.initialize(
@@ -17,10 +17,14 @@ import com.google.android.ump.ConsentInformation;
17
17
  import com.google.android.ump.ConsentRequestParameters;
18
18
  import com.google.android.ump.UserMessagingPlatform;
19
19
  import java.util.ArrayList;
20
+ import java.util.Arrays;
20
21
  import java.util.List;
21
22
 
22
23
  public class AdConsentExecutor extends Executor {
23
24
 
25
+ // Unity Ads in the IAB TCF Global Vendor List ("Unity Technologies SF").
26
+ private static final int UNITY_ADS_VENDOR_ID = 1549;
27
+
24
28
  private ConsentInformation consentInformation;
25
29
 
26
30
  public AdConsentExecutor(
@@ -74,6 +78,7 @@ public class AdConsentExecutor extends Executor {
74
78
  .remove("IABTCF_PurposeLegitimateInterests")
75
79
  .remove("IABTCF_VendorLegitimateInterests")
76
80
  .remove("IABTCF_gdprApplies") // Force SDK to reassess context
81
+ .remove("IABTCF_AddtlConsent")
77
82
  .apply();
78
83
  Log.w(logTag, "Successfully purged all IABTCF tracking variables from SharedPreferences.");
79
84
  }
@@ -137,6 +142,49 @@ public class AdConsentExecutor extends Executor {
137
142
  return false;
138
143
  }
139
144
 
145
+ private boolean gdprApplies(SharedPreferences prefs) {
146
+ try {
147
+ return prefs.getInt("IABTCF_gdprApplies", 0) == 1;
148
+ } catch (ClassCastException ex) {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ /** Whether the player agreed to personalized ads (purposes 1, 3 and 4) from Unity Ads in the TC string. */
154
+ private boolean unityAdsConsent(SharedPreferences prefs) {
155
+ try {
156
+ String purposeConsent = prefs.getString("IABTCF_PurposeConsents", "");
157
+ String vendorConsent = prefs.getString("IABTCF_VendorConsents", "");
158
+ return hasConsentFor(Arrays.asList(1, 3, 4), purposeConsent, hasAttribute(vendorConsent, UNITY_ADS_VENDOR_ID));
159
+ } catch (ClassCastException ex) {
160
+ return false;
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Tells Unity Ads the player's answer, so it never shows its own consent dialog. The AdMob adapter passes it only
166
+ * when Google's Additional Consent string mentions Unity Ads, which Google's consent form never does: it records
167
+ * Unity Ads as an IAB vendor. Apps without Unity Ads skip this.
168
+ */
169
+ public void passConsentToUnityAds() {
170
+ Context context = contextSupplier.get();
171
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
172
+ if (!gdprApplies(prefs)) {
173
+ return;
174
+ }
175
+ boolean consent = unityAdsConsent(prefs);
176
+ try {
177
+ Class<?> metaDataClass = Class.forName("com.unity3d.ads.metadata.MetaData");
178
+ Object metaData = metaDataClass.getConstructor(Context.class).newInstance(context.getApplicationContext());
179
+ metaDataClass.getMethod("set", String.class, Object.class).invoke(metaData, "gdpr.consent", consent);
180
+ metaDataClass.getMethod("commit").invoke(metaData);
181
+ } catch (ClassNotFoundException ex) {
182
+ // No Unity Ads in this app.
183
+ } catch (ReflectiveOperationException ex) {
184
+ Log.w(logTag, "Could not pass consent to Unity Ads", ex);
185
+ }
186
+ }
187
+
140
188
  public boolean canShowAds() {
141
189
  Context context = contextSupplier.get();
142
190
  SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
@@ -204,6 +252,7 @@ public class AdConsentExecutor extends Executor {
204
252
  // Check (and possibly purge) outdated consent before reading the
205
253
  // remaining values, so they reflect the post-cleanup state.
206
254
  boolean isConsentOutdated = isConsentOutdated();
255
+ passConsentToUnityAds();
207
256
 
208
257
  JSObject consentInfo = new JSObject();
209
258
  consentInfo.put("status", getConsentStatusString(consentInformation.getConsentStatus()));
@@ -236,6 +285,7 @@ public class AdConsentExecutor extends Executor {
236
285
  if (formError != null) {
237
286
  call.reject("Error when show privacy form", formError.getMessage());
238
287
  } else {
288
+ passConsentToUnityAds();
239
289
  call.resolve();
240
290
  }
241
291
  })
@@ -268,6 +318,7 @@ public class AdConsentExecutor extends Executor {
268
318
  // Check (and possibly purge) outdated consent before reading the
269
319
  // remaining values, so they reflect the post-cleanup state.
270
320
  boolean isConsentOutdated = isConsentOutdated();
321
+ passConsentToUnityAds();
271
322
 
272
323
  JSObject consentFormInfo = new JSObject();
273
324
  consentFormInfo.put("status", getConsentStatusString(consentInformation.getConsentStatus()));
@@ -56,6 +56,8 @@ public class AdMobPlugin: CAPPlugin, CAPBridgedPlugin {
56
56
  self.adInterstitialExecutor.plugin = self
57
57
  self.consentExecutor.plugin = self
58
58
  self.setRequestConfiguration(call)
59
+ // The Unity Ads adapter starts Unity Ads here: give it the stored consent answer first.
60
+ self.consentExecutor.passConsentToUnityAds()
59
61
 
60
62
  MobileAds.shared.start(completionHandler: nil)
61
63
  call.resolve([:])
@@ -6,6 +6,9 @@ import UserMessagingPlatform
6
6
  class ConsentExecutor: NSObject {
7
7
  weak var plugin: AdMobPlugin?
8
8
 
9
+ // Unity Ads in the IAB TCF Global Vendor List ("Unity Technologies SF").
10
+ private let unityAdsVendorId = 1549
11
+
9
12
  func requestConsentInfo(_ call: CAPPluginCall, _ debugGeography: Int, _ testDeviceIdentifiers: [String], _ tagForUnderAgeOfConsent: Bool) {
10
13
  let parameters = RequestParameters()
11
14
  let debugSettings = DebugSettings()
@@ -37,6 +40,7 @@ class ConsentExecutor: NSObject {
37
40
  Task {
38
41
  do {
39
42
  try await ConsentForm.presentPrivacyOptionsForm(from: rootViewController)
43
+ passConsentToUnityAds()
40
44
  call.resolve()
41
45
  } catch {
42
46
  call.reject("Failed to show privacy options form: \(error.localizedDescription)")
@@ -74,6 +78,7 @@ class ConsentExecutor: NSObject {
74
78
  // Check (and possibly purge) outdated consent before reading the
75
79
  // remaining values, so they reflect the post-cleanup state.
76
80
  let isConsentOutdated = isConsentOutdated()
81
+ passConsentToUnityAds()
77
82
 
78
83
  return [
79
84
  "status": getConsentStatusString(ConsentInformation.shared.consentStatus),
@@ -173,6 +178,7 @@ class ConsentExecutor: NSObject {
173
178
  settings.removeObject(forKey: "IABTCF_PurposeLegitimateInterests")
174
179
  settings.removeObject(forKey: "IABTCF_VendorLegitimateInterests")
175
180
  settings.removeObject(forKey: "IABTCF_gdprApplies") // Force SDK to reassess context
181
+ settings.removeObject(forKey: "IABTCF_AddtlConsent")
176
182
  NSLog("AdMob: Successfully purged all IABTCF tracking variables from UserDefaults.")
177
183
  }
178
184
 
@@ -229,6 +235,26 @@ class ConsentExecutor: NSObject {
229
235
 
230
236
  return false
231
237
  }
238
+
239
+ /// Whether the player agreed to personalized ads (purposes 1, 3 and 4) from Unity Ads in the TC string.
240
+ private func unityAdsConsent(_ settings: UserDefaults) -> Bool {
241
+ let purposeConsent = settings.string(forKey: "IABTCF_PurposeConsents") ?? ""
242
+ let vendorConsent = settings.string(forKey: "IABTCF_VendorConsents") ?? ""
243
+ return hasConsentFor([1, 3, 4], purposeConsent, hasAttribute(input: vendorConsent, index: unityAdsVendorId))
244
+ }
245
+
246
+ /// Tells Unity Ads the player's answer, so it never shows its own consent dialog. The AdMob adapter passes it only
247
+ /// when Google's Additional Consent string mentions Unity Ads, which Google's consent form never does: it records
248
+ /// Unity Ads as an IAB vendor. Apps without Unity Ads skip this.
249
+ func passConsentToUnityAds() {
250
+ guard isGDPR(), let metaDataClass = NSClassFromString("UADSMetaData") as? NSObject.Type else {
251
+ return
252
+ }
253
+ let consent = unityAdsConsent(UserDefaults.standard)
254
+ let metaData = metaDataClass.init()
255
+ _ = metaData.perform(NSSelectorFromString("set:value:"), with: "gdpr.consent", with: NSNumber(value: consent))
256
+ _ = metaData.perform(NSSelectorFromString("commit"))
257
+ }
232
258
  func getPrivacyOptionsRequirementStatus(_ requirementStatus: PrivacyOptionsRequirementStatus) -> String {
233
259
  switch requirementStatus {
234
260
  case PrivacyOptionsRequirementStatus.required:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ququplay/admob",
3
- "version": "8.2.0",
3
+ "version": "8.2.2",
4
4
  "description": "A native plugin for AdMob",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",