diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/AdvertisingJava009Snippet.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/AdvertisingJava009Snippet.java
new file mode 100644
index 00000000000..20d8581e837
--- /dev/null
+++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/AdvertisingJava009Snippet.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Codename One designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codenameone.developerguide.snippets.generated;
+
+import com.codename1.gpu.*;
+import com.codename1.ui.*;
+import com.codename1.ui.animations.*;
+import com.codename1.ui.events.*;
+import com.codename1.ui.geom.*;
+import com.codename1.ui.layouts.*;
+import com.codename1.ui.list.*;
+import com.codename1.ui.plaf.*;
+import com.codename1.ui.util.*;
+import com.codename1.components.*;
+import com.codename1.charts.models.*;
+import com.codename1.charts.renderers.*;
+import com.codename1.charts.views.*;
+import com.codename1.capture.*;
+import com.codename1.io.*;
+import com.codename1.l10n.*;
+import com.codename1.location.*;
+import com.codename1.maps.*;
+import com.codename1.media.*;
+import com.codename1.messaging.*;
+import com.codename1.payment.*;
+import com.codename1.processing.*;
+import com.codename1.properties.*;
+import com.codename1.push.*;
+import com.codename1.security.*;
+import com.codename1.social.*;
+import com.codename1.ui.spinner.*;
+import java.io.*;
+import com.codename1.analytics.*;
+import com.codename1.appreview.*;
+import com.codename1.ads.*;
+import com.codename1.util.*;
+import java.util.*;
+
+
+class AdvertisingJava009Snippet {
+
+ Form form;
+
+ // tag::advertising-java-009[]
+ void startAds() {
+ AdConfig cfg = new AdConfig()
+ .testMode(true)
+ .addTestDevice("YOUR_TEST_DEVICE_ID")
+ .tagForChildDirectedTreatment(AdConfig.TAG_FALSE)
+ .maxAdContentRating(AdConfig.RATING_G);
+
+ AdManager.initialize(cfg, ready -> {
+ // false when no provider was installed. It is not a report that
+ // the network is up: every provider hands its config to the native
+ // bridge and answers true straight away, while the SDK behind it
+ // initializes asynchronously. Worth checking anyway, because with
+ // no provider at all AdConsent reports consent as not required and
+ // canRequestAds() answers true, so carrying on would walk into
+ // loadAds() with nothing behind it.
+ if (!ready) {
+ showAdFreeUi();
+ return;
+ }
+
+ // Consent has to be settled before the first load, not before
+ // initialize: requestConsent presents the GDPR form and, on iOS,
+ // the App Tracking Transparency prompt, and both need the SDK up.
+ AdConsent.requestConsent(status -> {
+ if (AdConsent.canRequestAds()) {
+ loadAds();
+ } else {
+ // STATUS_REQUIRED with consent withheld. Personalized ads
+ // are off the table; show the app without them rather
+ // than blocking on a prompt the user already declined.
+ showAdFreeUi();
+ }
+ });
+ });
+ }
+
+ void loadAds() {
+ }
+
+ void showAdFreeUi() {
+ }
+ // end::advertising-java-009[]
+}
diff --git a/docs/demos/common/src/main/snippets/developer-guide/deep-links-routing.json b/docs/demos/common/src/main/snippets/developer-guide/deep-links-routing.json
new file mode 100644
index 00000000000..401fd82fa84
--- /dev/null
+++ b/docs/demos/common/src/main/snippets/developer-guide/deep-links-routing.json
@@ -0,0 +1,30 @@
+// Generated from docs/developer-guide source blocks. Edit the guide snippets here, not inline.
+
+// tag::deep-links-routing-json-001[]
+{
+ "applinks": {
+ "details": [
+ {
+ "appIDs": ["ABCDE12345.com.example.app"],
+ "components": [
+ { "/": "/users/*" },
+ { "/": "/promo/*" }
+ ]
+ }
+ ]
+ }
+}
+// end::deep-links-routing-json-001[]
+
+// tag::deep-links-routing-json-002[]
+[
+ {
+ "relation": ["delegate_permission/common.handle_all_urls"],
+ "target": {
+ "namespace": "android_app",
+ "package_name": "com.example.app",
+ "sha256_cert_fingerprints": ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5", "7B:5A:1F:C9:0E:52:44:7C:82:19:6E:38:0A:B7:D4:11:5C:63:9F:28:E1:47:8B:30:A6:C2:5D:94:F0:6B:38:19"]
+ }
+ }
+]
+// end::deep-links-routing-json-002[]
diff --git a/docs/demos/common/src/main/snippets/developer-guide/deep-links-routing.properties b/docs/demos/common/src/main/snippets/developer-guide/deep-links-routing.properties
index 092d29f5379..c662c1b3389 100644
--- a/docs/demos/common/src/main/snippets/developer-guide/deep-links-routing.properties
+++ b/docs/demos/common/src/main/snippets/developer-guide/deep-links-routing.properties
@@ -3,3 +3,7 @@
// tag::deep-links-routing-properties-001[]
codename1.arg.ios.associatedDomains=applinks:example.com,applinks:www.example.com
// end::deep-links-routing-properties-001[]
+
+// tag::deep-links-routing-properties-002[]
+codename1.arg.android.xintent_filter=\n \n \n \n \n
+// end::deep-links-routing-properties-002[]
diff --git a/docs/developer-guide/Advertising.asciidoc b/docs/developer-guide/Advertising.asciidoc
index cc5be36ea3f..f865e920ab7 100644
--- a/docs/developer-guide/Advertising.asciidoc
+++ b/docs/developer-guide/Advertising.asciidoc
@@ -74,8 +74,11 @@ include::../demos/common/src/main/snippets/developer-guide/advertising.xml[tag=a
=== Enabling a provider
-Enable a provider once at startup. Each library exposes a static `install()`
-method that registers its provider with `AdManager`:
+Enable a provider once at startup, from your app's `init(Object)`, by calling
+the static `install()` method on the provider class named in the table above --
+`AdMobProvider.install()`, `AppLovinProvider.install()`,
+`LevelPlayProvider.install()` or `MockAdProvider.install()`. Each one registers
+its provider with `AdManager` and takes no arguments.
That single call binds the provider; the rest of your code uses only the
@@ -92,6 +95,34 @@ wrap Google's User Messaging Platform or an equivalent), and on iOS the App
Tracking Transparency prompt must be presented to access the advertising
identifier. The recommended order is to initialize, gather consent, then load:
+[source,java]
+----
+include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/AdvertisingJava009Snippet.java[tag=advertising-java-009,indent=0]
+----
+
+
+Two things about that flow are worth knowing before you rely on it.
+
+The readiness flag says a provider was installed and took the config; it isn't
+a report that the network came up. Every provider passes the config to its
+native bridge and answers `true` immediately, while the SDK behind it
+initializes asynchronously -- so an invalid SDK key surfaces as failing loads
+later, not as `false` here.
+
+And `AdConsent.requestConsent` presents a form only where the provider
+implements one. AdMob does, through Google's User Messaging Platform. AppLovin
+MAX and Unity LevelPlay read consent from an external CMP and the IAB TCF
+string instead, so their implementations report `STATUS_NOT_REQUIRED` at once
+and `canRequestAds()` always answers `true`. On those two the consent flow is
+the CMP's, not this one, and it has to be in place before the first load for
+EEA/UK users.
+
+WARNING: On iOS 14 and later the AdMob provider starts the App Tracking
+Transparency prompt alongside the User Messaging Platform request and fires its
+consent callback when UMP finishes, without waiting for the tracking answer. The
+callback can therefore arrive while the system prompt is still on screen, and a
+load started from it runs before tracking authorization is settled. Present the ATT
+prompt yourself at a point you control, and load after the user has answered.
`AdConfig` also carries the global compliance flags every network requires:
test mode, test device ids, child directed treatment, under-age-of-consent
diff --git a/docs/developer-guide/Deep-Links-Routing.asciidoc b/docs/developer-guide/Deep-Links-Routing.asciidoc
index 74758fb9dab..0f9029a2e0c 100644
--- a/docs/developer-guide/Deep-Links-Routing.asciidoc
+++ b/docs/developer-guide/Deep-Links-Routing.asciidoc
@@ -85,6 +85,11 @@ Host an `apple-app-site-association` JSON file at
`https://your.domain/.well-known/apple-app-site-association` over HTTPS
without redirects. The plugin's `AasaBuilder` produces the payload:
+[source,json]
+----
+include::../demos/common/src/main/snippets/developer-guide/deep-links-routing.json[tag=deep-links-routing-json-001,indent=0]
+----
+
Tell iOS which domains your app claims by setting the
`ios.associatedDomains` build hint -- a comma-separated list of
@@ -103,10 +108,33 @@ Host an `assetlinks.json` file at
`https://your.domain/.well-known/assetlinks.json`. The plugin's
`AssetLinksBuilder` produces the payload:
+[source,json]
+----
+include::../demos/common/src/main/snippets/developer-guide/deep-links-routing.json[tag=deep-links-routing-json-002,indent=0]
+----
+
Tell Android which URLs to intercept by setting the
-`android.xintent_filter` build hint with a verified intent filter for
-your domain. The Android builder injects the filter into the manifest.
+`android.xintent_filter` build hint. The Android builder injects the
+value into the manifest verbatim, so it's the whole ``
+element, with `\n` for the line breaks:
+
+[source,properties]
+----
+include::../demos/common/src/main/snippets/developer-guide/deep-links-routing.properties[tag=deep-links-routing-properties-002,indent=0]
+----
+
+Three parts of that carry weight. `android:autoVerify="true"` is what makes
+Android fetch the `assetlinks.json` above at install time and route the link
+straight into your app. Leaving it out doesn't fall back to an app chooser:
+from Android 12 onward an unverified web link goes to the default browser, and
+your filter never sees it until the user turns the association on by hand under
+**Settings > Apps > Open by default**. The `` element needs
+`android:scheme="https"` and the host, and narrowing it with
+`android:pathPrefix` keeps the app from claiming every URL on the domain. And
+the hint holds one value, so an app that also registers a custom scheme puts
+both `` elements in the same string rather than setting the hint
+twice.
The SHA-256 fingerprint comes from `keytool -list -v -keystore ...`, or
from the Play Console under **Setup > App integrity** when using Play
diff --git a/scripts/developer-guide/missing-code-blocks-baseline.txt b/scripts/developer-guide/missing-code-blocks-baseline.txt
index 2d64765057a..23ba36a6adc 100644
--- a/scripts/developer-guide/missing-code-blocks-baseline.txt
+++ b/scripts/developer-guide/missing-code-blocks-baseline.txt
@@ -1,12 +1,8 @@
# Prose that promises a code block where none follows.
# A ratchet: entries may be removed as holes are filled, never added.
# Regenerate with check-missing-code-blocks.py --write-baseline.
-Advertising.asciidoc identifier. The recommended order is to initialize, gather consent, then load:
-Advertising.asciidoc method that registers its provider with `AdManager`:
Annotation-JSON-XML-Mapping.asciidoc Hand-write a `Mapper` and register it at startup:
Authentication-And-Identity.asciidoc Firebase Auth isn't an OIDC provider -- it issues Google-Identity-Toolkit-style tokens via REST endpoints. `com.codename1.social.FirebaseAuth` wraps those endpoints:
-Deep-Links-Routing.asciidoc `AssetLinksBuilder` produces the payload:
-Deep-Links-Routing.asciidoc without redirects. The plugin's `AasaBuilder` produces the payload:
Maven-Creating-CN1Libs.adoc Now try it out. Try adding the following code to your application project's main class (or anywhere in the application project, for that matter):
Maven-Creating-CN1Libs.adoc The simulator dispatches every action on the Codename One EDT through `Display.callSerially`, so your method can call `Display.getInstance()`, `Form.show()`, `Dialog.show()`, `ToastBar.showInfoMessage()` and any other CN1 API. Reflection uses the same classloader that loaded `Display`, so cn1lib internals (including package-private classes) resolve normally:
Miscellaneous-Features.asciidoc To solve this sort of used case you have two APIs in `Display`: