Skip to content
25 changes: 25 additions & 0 deletions Documentation/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,31 @@ Refer to the [Getting Started Guide](getting-started.md)
| [resetIdentities](#resetIdentities) |
| [setAdvertisingIdentifier](#setAdvertisingIdentifier) |
| [updateIdentities](#updateIdentities) |
| [updateDeviceAttributesBypassConsent](#updatedeviceattributesbypassconsent) |

------

### updateDeviceAttributesBypassConsent

Requests rule-authorized operational device attribute synchronization. The map overload supports JSON-compatible values, including empty strings, arrays, and objects. Null and non-JSON attribute leaves are dropped without discarding valid sibling attributes, matching the current iOS implementation. This API is fire-and-forget; it has no public callback.

```java
public static void updateDeviceAttributesBypassConsent(DeviceAttributes attributes)
public static void updateDeviceAttributesBypassConsent(Map<String, Object> attributes)
```

```java
Identity.updateDeviceAttributesBypassConsent(
Collections.singletonMap("timezone", "Asia/Tokyo"));
```

The data is sent to the Core rules engine. Each matching `forward-operational-data` rule returns only its enabled paths. Authorized operational values use the Edge bypass request under collect consent `yes`, `no`, or `pending`; ordinary Edge events remain governed by consent. Only when the latest collect consent is explicitly `y` does Identity also compare push tokens with Messaging's latest shared-state value and timezone with its persisted owner value. Missing, pending, or malformed consent does not authorize owner-store comparisons. Confirmed bypass values are compared at every consent state. Identity queues requests in event order and reads current configuration and owner state when each request is dequeued. It waits for a correlated terminal `success`, `failed`, or `dropped` result before advancing; malformed results are ignored. Confirmed values are persisted and deduplicated across app restarts; set `edgeidentity.optimizeOperationalProfileDataSync` to `false` to disable all dedup comparisons, but requests still run one at a time. If a request fails, its values are not stored and a queued duplicate can send them again. If no rule matches, or rules are not loaded yet, nothing is sent.

After Edge reports success, confirmed bypass values are persisted under `confirmedAttributes` in the separate `deviceAttributesDataStore` collection. Existing confirmed values from the earlier profile-store layout are restored when the new store has no saved values. Timezone success also updates the profile timezone owner. The complete confirmed state remains published under `profileAttributes` in Identity's standard shared state for the existing Android Messaging consumer. In addition, Identity emits a chained `Device attributes confirmed` event (`messaging` / `responseContent`) containing only the successfully sent `tokens`, matching iOS. Failed, dropped, malformed, and unrelated results do not confirm tokens.

An empty map or a typed object with no values is a no-op. The typed convenience object preserves explicitly supplied empty timezone and push-token strings. Null leaves are not sent by either overload. After rules and dedup filtering, every dispatched request includes SDK-owned `app.id` (the Android package name) and `app.platform: "fcm"`; caller-provided `app` values cannot override routing, and app-only requests are not sent. Live Activity owner-store comparison and APNs sandbox routing are iOS-specific; arbitrary rule-authorized arrays remain supported by the Android map overload.

See [the device attribute flow and local setup](device-attribute-transactions.md) for rule configuration and shared-state behavior.

------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import com.adobe.marketing.mobile.edge.consent.Consent
import com.adobe.marketing.mobile.edge.identity.Identity

class EdgeIdentityApplication : Application() {
// TODO: Set up the preferred Environment File ID from your mobile property configured in Data Collection UI
private var ENVIRONMENT_FILE_ID: String = ""
// Datastream ID (edge.configId) from the "Luma Mobile App - vinamra" datastream, not a Launch/Tags Environment File ID
private var EDGE_CONFIG_ID: String = "8f56dfea-5290-4cf7-8ea2-9e5f0fb5d051"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not commit the app ID here


override fun onCreate() {
super.onCreate()
Expand All @@ -32,7 +32,12 @@ class EdgeIdentityApplication : Application() {
MobileCore.registerExtensions(
listOf(Edge.EXTENSION, Identity.EXTENSION, Consent.EXTENSION, Assurance.EXTENSION)
) {
MobileCore.configureWithAppID(ENVIRONMENT_FILE_ID)
MobileCore.updateConfiguration(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to have consent update committed here?

mapOf(
"edge.configId" to EDGE_CONFIG_ID,
"consent.default" to mapOf("consents" to mapOf("collect" to mapOf("val" to "y")))
)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,16 @@
import java.util.Map;

/**
* Handles a single profile attribute end-to-end: it claims a key on the incoming
* {@code PROFILE_ATTRIBUTE} event, owns its dedup + persistence, and reports its
* contribution to the outgoing collated {@code profile.updateAttributes} Edge payload and
* to the profile-attributes shared state.
* Handles a single attribute end-to-end: it claims a key on the incoming event, owns its dedup
* and persistence, and reports its contribution to the outgoing payload.
*
* <p>Each attribute (timezone today; locale, push identifier, ... in the future) is one handler
* registered in {@link ProfileAttributeHandlers}. The collector layer in {@link IdentityState}
* iterates every registered handler and stitches their contributions into a single Edge event
* and into the shared state.
* registered independently for profile and device attribute paths. The two paths use separate
* stores so that a value on one path cannot suppress dispatch on the other.
*/
interface ProfileAttributeHandler {
interface AttributeHandler {
/**
* Returns the key on the incoming {@code PROFILE_ATTRIBUTE} event data that this handler
* Returns the key on the incoming event data that this handler
* claims (e.g. {@code "timeZone"}). The collector uses this purely as an input filter:
* {@link #collectFromEvent(Event)} is invoked only when this key is present in the event
* data.
Expand All @@ -41,24 +38,33 @@ interface ProfileAttributeHandler {
String getAttributeKey();

/**
* Reads this handler's value from the update {@code event}, dedups against persistence, and
* on change writes the new value to persistence before returning the contribution to add to
* the outgoing collated {@code profile.updateAttributes} Edge payload. Returns {@code null}
* Reads this handler's value from the update {@code event}, optionally dedups against persistence, and
* writes the new value to persistence before returning its contribution. Returns {@code null}
* (or an empty map) when nothing should be contributed (key absent, value empty, or
* unchanged).
*
* <p>Persistence is written <b>before</b> the caller dispatches so that a crash or process
* death between persist and dispatch leaves the pending value in storage; the next sync will
* pick it up via dedup.
*
* @param event the profile attributes update event
* @param event the attribute update event
* @param dedup whether unchanged values should be skipped
* @return the contribution to merge into the outgoing payload, or {@code null}
*/
Map<String, Object> collectFromEvent(Event event);
Map<String, Object> collectFromEvent(Event event, boolean dedup);

/**
* Returns this handler's persisted contribution as it should appear in the profile-attributes
* shared state, or {@code null} (or an empty map) when nothing is stored.
* Collects an attribute using the existing profile-path dedup behavior.
*
* @param event the attribute update event
* @return the changed attribute contribution, or {@code null}
*/
default Map<String, Object> collectFromEvent(final Event event) {
return collectFromEvent(event, true);
}

/**
* Returns this handler's persisted contribution, or {@code null} (or an empty map) when nothing is stored.
*
* @return the persisted contribution, or {@code null}
*/
Expand Down
Loading
Loading