From b9086ff7b6c42e065ae260a1ef8e8b54a7f9430b Mon Sep 17 00:00:00 2001 From: Nick Yang Date: Tue, 25 Aug 2026 00:01:33 +0200 Subject: [PATCH 1/3] Document test-device whitelisting and the IFA-non-zero caveat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The demo explains how to swap in your own app key, ad unit IDs and bundle identifier, but nothing says test mode is server-controlled — a publisher replacing the demo IDs with their own has to register the device's advertising ID in the CloudX dashboard as well. Adds that, plus the part that is easy to lose an afternoon to: the advertising ID reads back as all zeros until tracking consent is granted on the device (App Tracking Transparency on iOS, ad-personalization consent on Android), so a device can silently fail whitelisting for a reason that looks identical to a wrong dashboard entry. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_014Qe7sVn74q6KtnJiRk5P2c --- Assets/Scripts/DemoConfig.cs | 7 +++++++ README.md | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/Assets/Scripts/DemoConfig.cs b/Assets/Scripts/DemoConfig.cs index d80d42d..bf0779d 100644 --- a/Assets/Scripts/DemoConfig.cs +++ b/Assets/Scripts/DemoConfig.cs @@ -2,6 +2,13 @@ * Demo dashboard IDs so this sample runs without a CloudX account. * In your game, replace these with the app key and ad unit IDs from your * CloudX dashboard. Use one app key per process. + * + * Test mode is server-controlled: register this device's advertising ID in + * the CloudX dashboard, not here. That ID reads back as all zeros on an + * opted-out device -- App Tracking Transparency declined on iOS, ad + * personalization off on Android -- and a zeroed ID is a well-formed UUID + * that the dashboard accepts and then never matches. The demo logs the ID + * and says which of the two you have; see README.md > Test devices. */ public static class DemoConfig { diff --git a/README.md b/README.md index a24b2d7..af5f22d 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,22 @@ app or the SDK gets no fill. The AdMob ad units in `FirstLookConfig.cs` are Google's official test units and stay valid as they are; replace them with your own AdMob units when you take this into production. +### Test devices + +Test mode is server-controlled. A device serves CloudX test ads because its advertising ID is on the +test-device list in your dashboard, not because of anything in this build — there is no code change +that turns it on. + +The trap is that the advertising ID reads back as all zeros +(`00000000-0000-0000-0000-000000000000`) when the device is opted out. That is a well-formed UUID, so +it pastes into the dashboard without complaint and then matches nothing, which looks exactly like a +wrong dashboard entry rather than a consent problem. Check the device first: + +| Platform | The ID zeroes when | +| --- | --- | +| iOS | App Tracking Transparency was not authorized. The demo prompts on launch; iOS only asks once per install, so a refusal needs a reinstall to undo. | +| Android | Ad personalization is off (Settings > Google > Ads > Delete advertising ID), or the app targets SDK 33+ without declaring `com.google.android.gms.permission.AD_ID`. | + ### iOS target SDK The project is configured for the **Simulator** SDK. To build for a physical iOS device, switch From f74ca18e3f0f23972185c1d8e64fac64881ccfff Mon Sep 17 00:00:00 2001 From: Nick Yang Date: Tue, 25 Aug 2026 16:14:23 +0200 Subject: [PATCH 2/3] Report the advertising ID and whether it is zeroed The README now tells a publisher the advertising ID has to be on the dashboard's test-device list and that it reads back as all zeros without tracking consent. Neither is checkable from the demo: nothing prints the ID, and a zeroed one is invisible unless you already recognize the all-zeros UUID on sight. It is well-formed, so it pastes into the dashboard without complaint and then matches nothing -- which looks like a wrong dashboard entry rather than a consent problem. The demo now logs the ID in full at startup and carries a short usable/zeroed verdict on the existing status line. Deliberately not a new screen or a new scene control: the UI comes from HomeScene.unity as serialized fields, with no scrollable area, and the landscape reflow snapshots controls at Bind time -- a runtime-added label would sit outside that and risk the rotated layout. The log is where this demo already surfaces everything, having no on-screen log at all. Application.RequestAdvertisingIdentifierAsync covers both platforms in one call, so this needs no native plugin and no AdSupport link. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_014Qe7sVn74q6KtnJiRk5P2c --- Assets/Scripts/DemoAdvertisingId.cs | 118 +++++++++++++++++++++++ Assets/Scripts/DemoAdvertisingId.cs.meta | 11 +++ Assets/Scripts/GeneralScreen.cs | 20 +++- README.md | 10 +- 4 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 Assets/Scripts/DemoAdvertisingId.cs create mode 100644 Assets/Scripts/DemoAdvertisingId.cs.meta diff --git a/Assets/Scripts/DemoAdvertisingId.cs b/Assets/Scripts/DemoAdvertisingId.cs new file mode 100644 index 0000000..9209a1a --- /dev/null +++ b/Assets/Scripts/DemoAdvertisingId.cs @@ -0,0 +1,118 @@ +using System.Collections; +using UnityEngine; + +namespace CloudX.Demo +{ + /* + * Demo-only: reads this device's advertising ID so the demo can say whether the + * device can be registered as a CloudX test device. + * + * Test mode is server-controlled -- a device serves test ads because its advertising + * ID is on the dashboard's test-device list, not because of anything in this build. + * Nothing in the SDK surfaces that ID, so without this the only way to find it is to + * already know where to look. + * + * The case worth catching is the zeroed one. With tracking unauthorized, both + * platforms still return an ID, but they return ZeroedId -- the same all-zeros UUID + * on every opted-out device. It is a well-formed UUID, so it pastes into the + * dashboard without complaint and then matches nothing, which presents as ordinary + * no-fill and sends people to debug their integration instead of their consent flow. + * + * Like DemoAppTrackingiOS, this is demo scaffolding and must never move into the + * CloudXSdk package. + */ + public static class DemoAdvertisingId + { + /* What both platforms report when tracking is not authorized. */ + public const string ZeroedId = "00000000-0000-0000-0000-000000000000"; + + public static string Id { get; private set; } + public static bool TrackingEnabled { get; private set; } + public static string Error { get; private set; } + public static bool Resolved { get; private set; } + + /* A real ID that is worth registering on the dashboard. */ + public static bool IsUsable => + Resolved && string.IsNullOrEmpty(Error) && !string.IsNullOrEmpty(Id) && Id != ZeroedId; + + /* + * Unity does not document which thread the callback arrives on, so it only + * stores values; every caller reads them from the coroutine, i.e. on the Unity + * thread. Yields until the value lands or the timeout expires, so a platform + * that never calls back cannot hang the demo's startup. + */ + public static IEnumerator Resolve(float timeoutSeconds = 3f) + { + if (Resolved) + { + yield break; + } + + var started = Application.RequestAdvertisingIdentifierAsync( + (id, trackingEnabled, error) => + { + Id = id; + TrackingEnabled = trackingEnabled; + Error = error; + Resolved = true; + }); + + if (!started) + { + /* No advertising ID on this platform -- the Editor, most notably. */ + Error = "not available on this platform"; + Resolved = true; + yield break; + } + + var deadline = Time.realtimeSinceStartup + timeoutSeconds; + while (!Resolved && Time.realtimeSinceStartup < deadline) + { + yield return null; + } + + if (!Resolved) + { + Error = "timed out"; + Resolved = true; + } + } + + /* One line for the log: the full ID, which is the value to paste into the dashboard. */ + public static string Describe() + { + if (!Resolved) + { + return "not resolved yet"; + } + + if (!string.IsNullOrEmpty(Error)) + { + return $"unavailable ({Error})"; + } + + if (!IsUsable) + { + return $"{Id} - ZEROED, cannot be whitelisted (tracking not authorized)"; + } + + return $"{Id} - register this on the CloudX dashboard to serve test ads"; + } + + /* Short enough for the on-screen status line, which is narrow in landscape. */ + public static string ShortStatus() + { + if (!Resolved) + { + return null; + } + + if (!string.IsNullOrEmpty(Error)) + { + return "Ad ID unavailable"; + } + + return IsUsable ? "Ad ID ok" : "Ad ID zeroed - see log"; + } + } +} diff --git a/Assets/Scripts/DemoAdvertisingId.cs.meta b/Assets/Scripts/DemoAdvertisingId.cs.meta new file mode 100644 index 0000000..893d037 --- /dev/null +++ b/Assets/Scripts/DemoAdvertisingId.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bbd38fd555164d05a7e9634c8445db22 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/GeneralScreen.cs b/Assets/Scripts/GeneralScreen.cs index e945a4b..646448f 100644 --- a/Assets/Scripts/GeneralScreen.cs +++ b/Assets/Scripts/GeneralScreen.cs @@ -75,6 +75,16 @@ IEnumerator Start() */ yield return DemoAppTrackingiOS.EnsureRequested(); + /* + * Read the advertising ID once tracking has been answered, and log it in full. + * This is the value that has to be on the dashboard's test-device list for the + * device to serve test ads, and it is worth logging even when tracking was + * declined: a zeroed ID is the specific symptom that looks like a wrong + * dashboard entry rather than a consent problem. + */ + yield return DemoAdvertisingId.Resolve(); + Log($"Advertising ID: {DemoAdvertisingId.Describe()}"); + if (!DemoAppTrackingiOS.IsUsable(DemoAppTrackingiOS.Status)) { /* @@ -199,7 +209,15 @@ private void OnSdkInitialized(CloudXSdkConfiguration config) { Log("CloudX SDK initialized successfully"); _initAnswered = true; - _ui.SetInitializationStatus("Status: Initialized"); + /* + * Carry the advertising-ID verdict on the status line. A zeroed ID does not stop + * initialization -- it stops the device from ever matching the dashboard's + * test-device list -- so this is the only place the demo can flag it before the + * tester concludes their ad units are wrong. + */ + var adId = DemoAdvertisingId.ShortStatus(); + _ui.SetInitializationStatus( + string.IsNullOrEmpty(adId) ? "Status: Initialized" : $"Status: Initialized - {adId}"); _ui.SetActionsInteractable(true); InitializeBannerAds(); diff --git a/README.md b/README.md index af5f22d..d668e0b 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,15 @@ that turns it on. The trap is that the advertising ID reads back as all zeros (`00000000-0000-0000-0000-000000000000`) when the device is opted out. That is a well-formed UUID, so it pastes into the dashboard without complaint and then matches nothing, which looks exactly like a -wrong dashboard entry rather than a consent problem. Check the device first: +wrong dashboard entry rather than a consent problem. + +So the demo reads the ID for you. It logs it in full at startup under +`[CloudXUnityDemo] Advertising ID:` — that string is what you paste into the dashboard — and the +status line on the General screen carries a short verdict. The verdict is about the health of the +ID, never about whether your dashboard entry took effect: no CloudX SDK exposes the resolved test +flag, so "ok" means the ID is worth registering, not that it is registered. + +If it comes back zeroed, check the device: | Platform | The ID zeroes when | | --- | --- | From 84037cfd31d0896b608062c299e4e8ef44c282c5 Mon Sep 17 00:00:00 2001 From: Nick Yang Date: Thu, 3 Sep 2026 14:57:49 +0800 Subject: [PATCH 3/3] Read the advertising ID on Android too, not just iOS Application.RequestAdvertisingIdentifierAsync is documented as "an advertising ID for iOS and UWP". Unity dropped the Android implementation, so on Android the call returns false and the demo reported "unavailable (not available on this platform)" -- on the platform where CloudX test-device whitelisting is most often what someone is debugging. Split the resolve by platform. iOS keeps the Unity API; Android asks Google Play services for AdvertisingIdClient.getAdvertisingIdInfo, which throws if called on the main thread, so it runs on a background worker attached to the JVM by hand. Only strings and bools cross back to the Unity thread, behind a volatile flag written last, and the coroutine polls that flag the way the ATT gate already polls its own. A timeout now leaves the state unresolved rather than recording a failure, so a late answer still reaches the status line. Splitting the platforms is also what makes the advice correct. A zeroed ID means ATT was declined on iOS and ad personalization is off on Android -- different settings, different screens -- and the union of both was wrong on either. Android adds a case iOS does not have: a real ID with limit ad tracking on, which is registerable but still bids do-not-track, so it reads as a dashboard problem and is not one. play-services-ads-identifier only reached the classpath transitively through the Google Mobile Ads plugin, which just the First Look flow needs, so declare it. com.google.android.gms.permission.AD_ID likewise arrived only from that library's manifest; declaring it changes nothing in the merged manifest but stops the target-SDK-33+ requirement being something a publisher inherits by accident. FirstLookScreen has its own CloudXSdk.Initialize, so it logs the ID as well. --- Assets/Plugins/Android/AndroidManifest.xml | 6 + Assets/Scripts/DemoAdvertisingId.cs | 255 +++++++++++++++--- .../Scripts/Editor/CloudXDemoDependencies.xml | 9 + Assets/Scripts/FirstLook/FirstLookScreen.cs | 10 + 4 files changed, 240 insertions(+), 40 deletions(-) diff --git a/Assets/Plugins/Android/AndroidManifest.xml b/Assets/Plugins/Android/AndroidManifest.xml index d7e8ed5..74b5a95 100755 --- a/Assets/Plugins/Android/AndroidManifest.xml +++ b/Assets/Plugins/Android/AndroidManifest.xml @@ -15,4 +15,10 @@ + + diff --git a/Assets/Scripts/DemoAdvertisingId.cs b/Assets/Scripts/DemoAdvertisingId.cs index 9209a1a..56511a2 100644 --- a/Assets/Scripts/DemoAdvertisingId.cs +++ b/Assets/Scripts/DemoAdvertisingId.cs @@ -1,4 +1,6 @@ +using System; using System.Collections; +using System.Threading; using UnityEngine; namespace CloudX.Demo @@ -12,107 +14,280 @@ namespace CloudX.Demo * Nothing in the SDK surfaces that ID, so without this the only way to find it is to * already know where to look. * - * The case worth catching is the zeroed one. With tracking unauthorized, both - * platforms still return an ID, but they return ZeroedId -- the same all-zeros UUID - * on every opted-out device. It is a well-formed UUID, so it pastes into the - * dashboard without complaint and then matches nothing, which presents as ordinary - * no-fill and sends people to debug their integration instead of their consent flow. + * The case worth catching is the zeroed one. An opted-out device still returns an ID, + * but it returns ZeroedId -- the same all-zeros UUID on every opted-out device. It is + * a well-formed UUID, so it pastes into the dashboard without complaint and then + * matches nothing, which presents as ordinary no-fill and sends people to debug their + * integration instead of their consent flow. + * + * There is no one call that covers both platforms. Unity documents + * Application.RequestAdvertisingIdentifierAsync as "an advertising ID for iOS and + * UWP" -- it dropped the Android implementation years ago, and on Android the call + * returns false rather than the GAID. So only iOS uses that API, and Android asks + * Google Play services for AdvertisingIdClient directly. Keeping the two apart is + * also what lets each explain a zeroed ID in its own terms, which are not the same + * terms. * * Like DemoAppTrackingiOS, this is demo scaffolding and must never move into the * CloudXSdk package. */ public static class DemoAdvertisingId { - /* What both platforms report when tracking is not authorized. */ + /* What both platforms report when the device is opted out. */ public const string ZeroedId = "00000000-0000-0000-0000-000000000000"; - public static string Id { get; private set; } - public static bool TrackingEnabled { get; private set; } - public static string Error { get; private set; } - public static bool Resolved { get; private set; } + /* + * Written by the resolver -- on Android from a worker thread, on iOS from a + * callback whose thread Unity does not document -- and read from the coroutine, + * i.e. on the Unity thread. _resolved is volatile and is written last, so a + * reader that sees it true also sees the three values behind it. + */ + private static volatile bool _resolved; + private static string _id; + private static bool _trackingEnabled; + private static string _error; + +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR + /* + * Whether the platform call has been issued. Only ever touched on the Unity + * thread, and it is what keeps a second caller -- or a first caller retrying + * after a timeout -- from starting a second worker. GeneralScene and + * FirstLookScene are separate scenes today so only one ever runs, but the ATT + * gate needs the same guard and this one costs nothing. + */ + private static bool _started; +#endif + + public static string Id => _id; + public static bool TrackingEnabled => _trackingEnabled; + public static string Error => _error; + public static bool Resolved => _resolved; /* A real ID that is worth registering on the dashboard. */ public static bool IsUsable => - Resolved && string.IsNullOrEmpty(Error) && !string.IsNullOrEmpty(Id) && Id != ZeroedId; + _resolved && string.IsNullOrEmpty(_error) && !string.IsNullOrEmpty(_id) && _id != ZeroedId; /* - * Unity does not document which thread the callback arrives on, so it only - * stores values; every caller reads them from the coroutine, i.e. on the Unity - * thread. Yields until the value lands or the timeout expires, so a platform - * that never calls back cannot hang the demo's startup. + * Yields until the ID lands or the timeout expires, so a platform that never + * answers cannot hang the demo's startup. On timeout the state is deliberately + * left unresolved rather than marked failed: the answer usually arrives a moment + * later, and ShortStatus picks it up when initialization reports back. */ public static IEnumerator Resolve(float timeoutSeconds = 3f) { - if (Resolved) + if (_resolved) { yield break; } +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR + if (!_started) + { + _started = true; + StartPlatformResolve(); + } + + var deadline = Time.realtimeSinceStartup + timeoutSeconds; + while (!_resolved && Time.realtimeSinceStartup < deadline) + { + yield return null; + } +#else + /* Assigned explicitly so the Editor build does not warn them unassigned. */ + _id = null; + _trackingEnabled = false; + _error = "no advertising ID in the Editor"; + _resolved = true; + yield break; +#endif + } + +#if UNITY_IOS && !UNITY_EDITOR + /* + * iOS only. This call is what the Android path exists to replace: Unity documents + * it as "an advertising ID for iOS and UWP" and returns false for it on Android, + * which reads as "no advertising ID on this device" and is not what is happening. + */ + private static void StartPlatformResolve() + { var started = Application.RequestAdvertisingIdentifierAsync( (id, trackingEnabled, error) => { - Id = id; - TrackingEnabled = trackingEnabled; - Error = error; - Resolved = true; + _id = id; + _trackingEnabled = trackingEnabled; + _error = error; + _resolved = true; }); if (!started) { - /* No advertising ID on this platform -- the Editor, most notably. */ - Error = "not available on this platform"; - Resolved = true; - yield break; + _error = "the platform declined to report one"; + _resolved = true; } + } +#endif - var deadline = Time.realtimeSinceStartup + timeoutSeconds; - while (!Resolved && Time.realtimeSinceStartup < deadline) +#if UNITY_ANDROID && !UNITY_EDITOR + /* + * AdvertisingIdClient.getAdvertisingIdInfo binds to Google Play services and + * throws IllegalStateException if it is called on the main thread, so it runs on a + * worker. The thread has to be attached to the JVM by hand for JNI to work, and + * every AndroidJavaObject it creates is disposed on that same thread -- only + * plain strings and bools cross back. + * + * Background so a Play services bind that never returns cannot hold the process + * open, and fire-and-forget because the coroutine above is what waits. + */ + private static void StartPlatformResolve() + { + var worker = new Thread(() => { - yield return null; + AndroidJNI.AttachCurrentThread(); + try + { + using var player = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); + using var activity = player.GetStatic("currentActivity"); + using var client = + new AndroidJavaClass("com.google.android.gms.ads.identifier.AdvertisingIdClient"); + using var info = client.CallStatic("getAdvertisingIdInfo", activity); + + _id = info.Call("getId"); + /* + * Limit ad tracking is the pre-Android-12 form of opting out: the ID + * stays real but must not be used for ads. From Android 12 the ID + * itself zeroes instead, so on a modern device this is normally false + * whenever _id is usable. + */ + _trackingEnabled = !info.Call("isLimitAdTrackingEnabled"); + } + catch (Exception e) + { + _error = DescribeAndroidFailure(e); + } + finally + { + /* Last, and volatile, so the reader sees the values above it. */ + _resolved = true; + AndroidJNI.DetachCurrentThread(); + } + }) + { + IsBackground = true, + Name = "CloudXUnityDemoAdvertisingId" + }; + + worker.Start(); + } + + /* + * Unity surfaces a Java throwable as AndroidJavaException whose message opens with + * the Java class name, so match on that. Each of these is a different thing to go + * and fix, which "unavailable" on its own would not tell anyone. + */ + private static string DescribeAndroidFailure(Exception e) + { + var message = e.Message ?? string.Empty; + + if (message.Contains("GooglePlayServicesNotAvailableException")) + { + return "no Google Play services on this device"; } - if (!Resolved) + if (message.Contains("GooglePlayServicesRepairableException")) { - Error = "timed out"; - Resolved = true; + return "Google Play services needs updating"; } + + if (message.Contains("ClassNotFoundException") || message.Contains("NoClassDefFoundError")) + { + return "play-services-ads-identifier is not on the classpath"; + } + + if (message.Contains("IOException")) + { + return "could not reach Google Play services"; + } + + return message.Length == 0 ? e.GetType().Name : message; } +#endif - /* One line for the log: the full ID, which is the value to paste into the dashboard. */ + /* + * One line for the log: the full ID, which is the value to paste into the + * dashboard, plus what to do about it. + */ public static string Describe() { - if (!Resolved) + if (!_resolved) { return "not resolved yet"; } - if (!string.IsNullOrEmpty(Error)) + if (!string.IsNullOrEmpty(_error)) + { + return $"unavailable ({_error})"; + } + + if (string.IsNullOrEmpty(_id)) { - return $"unavailable ({Error})"; + return "unavailable (no ID reported)"; } - if (!IsUsable) + if (_id == ZeroedId) { - return $"{Id} - ZEROED, cannot be whitelisted (tracking not authorized)"; + return $"{_id} - ZEROED, cannot be whitelisted ({ZeroedCause()})"; } - return $"{Id} - register this on the CloudX dashboard to serve test ads"; + if (!_trackingEnabled) + { + /* + * A real ID the device has told us not to use for ads -- the + * pre-Android-12 form of opting out. Registering it works, so this is not + * the zeroed case, but fill will still look broken for a reason that has + * nothing to do with the dashboard. + */ + return $"{_id} - registerable, but this device is opted out of tracking, " + + "so bid requests go out as do-not-track and will not fill"; + } + + return $"{_id} - register this on the CloudX dashboard to serve test ads"; + } + + /* + * The two platforms zero the ID for different reasons and are fixed in different + * places, so say which one applies rather than the union of both. + */ + private static string ZeroedCause() + { +#if UNITY_ANDROID && !UNITY_EDITOR + return "turn on ad personalization in Settings > Google > Ads, and check the app declares " + + "com.google.android.gms.permission.AD_ID"; +#elif UNITY_IOS && !UNITY_EDITOR + return "App Tracking Transparency was not authorized; reinstall to be asked again"; +#else + return "the device is opted out of tracking"; +#endif } /* Short enough for the on-screen status line, which is narrow in landscape. */ public static string ShortStatus() { - if (!Resolved) + if (!_resolved) { return null; } - if (!string.IsNullOrEmpty(Error)) + if (!string.IsNullOrEmpty(_error) || string.IsNullOrEmpty(_id)) { return "Ad ID unavailable"; } - return IsUsable ? "Ad ID ok" : "Ad ID zeroed - see log"; + if (_id == ZeroedId) + { + return "Ad ID zeroed - see log"; + } + + return _trackingEnabled ? "Ad ID ok" : "Ad ID no-track - see log"; } } } diff --git a/Assets/Scripts/Editor/CloudXDemoDependencies.xml b/Assets/Scripts/Editor/CloudXDemoDependencies.xml index db75957..099b761 100644 --- a/Assets/Scripts/Editor/CloudXDemoDependencies.xml +++ b/Assets/Scripts/Editor/CloudXDemoDependencies.xml @@ -7,6 +7,15 @@ https://artifact.bytedance.com/repository/pangle https://artifact.taurusx.com/artifactory/taurusx-sdk/ + + + diff --git a/Assets/Scripts/FirstLook/FirstLookScreen.cs b/Assets/Scripts/FirstLook/FirstLookScreen.cs index 0a5543b..c339ee6 100644 --- a/Assets/Scripts/FirstLook/FirstLookScreen.cs +++ b/Assets/Scripts/FirstLook/FirstLookScreen.cs @@ -74,6 +74,16 @@ IEnumerator Start() */ yield return DemoAppTrackingiOS.EnsureRequested(); + /* + * Log the advertising ID once tracking has been answered. This screen has its + * own CloudXSdk.Initialize, so it needs the same read the General screen does - + * the CloudX leg no-fills the same way when the device is not whitelisted. + * Resolve is idempotent, and the status line here is already the two-part + * "CloudX | AdMob" summary, so the verdict stays in the log. + */ + yield return DemoAdvertisingId.Resolve(); + Log($"Advertising ID: {DemoAdvertisingId.Describe()}"); + if (!DemoAppTrackingiOS.IsUsable(DemoAppTrackingiOS.Status)) { Log($"Tracking not authorized ({DemoAppTrackingiOS.Status}), leaving the UI disabled");