Skip to content

Commit

Permalink
Merge pull request #663 from kevinlind/mob-20855-push-event
Browse files Browse the repository at this point in the history
Identity don't dispatch Analytics push event on privacy change
  • Loading branch information
praveek authored May 17, 2024
2 parents 1a7506e + d6e91e9 commit e2563e0
Show file tree
Hide file tree
Showing 3 changed files with 317 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ public final class IdentityExtension extends Extension {

private static final String LOG_SOURCE = "IdentityExtension";
private HitQueuing hitQueue;
private static boolean pushEnabled = false;
private static final Object pushEnabledMutex = new Object();
@VisibleForTesting ConfigurationSharedStateIdentity latestValidConfig;
private final NamedCollection namedCollection;
Expand Down Expand Up @@ -499,18 +498,8 @@ void handleIdentityRequestReset(@NonNull final Event event) {
"handleIdentityRequestReset: Privacy is opt-out, ignoring event.");
return;
}
mid = null;
advertisingIdentifier = null;
blob = null;
locationHint = null;
customerIds = null;
pushIdentifier = null;

if (namedCollection != null) {
namedCollection.remove(DataStoreKeys.AID_SYNCED_KEY);
namedCollection.remove(DataStoreKeys.PUSH_ENABLED);
}

clearIdentifiers();
savePersistently(); // clear datastore

// When resetting identifiers, need to generate new Experience Cloud ID for the user
Expand Down Expand Up @@ -1097,15 +1086,17 @@ void updatePushIdentifier(final String pushId) {
return;
}

if (pushId == null && !isPushEnabled()) {
final boolean pushEnabled = isPushEnabled();

if (pushId == null && !pushEnabled) {
changePushStatusAndHitAnalytics(false);
Log.debug(
IdentityConstants.LOG_TAG,
LOG_SOURCE,
"updatePushIdentifier : First time sending a.push.optin false");
} else if (pushId == null) { // push is enabled
changePushStatusAndHitAnalytics(false);
} else if (!isPushEnabled()) { // push ID is not null
} else if (!pushEnabled) { // push ID is not null
changePushStatusAndHitAnalytics(true);
}
}
Expand Down Expand Up @@ -1683,8 +1674,8 @@ private void updateAdvertisingIdentifier(final String adid) {
}

/**
* Updates the {@link #pushEnabled} field and dispatches an event to generate a corresponding
* Analytics request
* Updates the persisted {@code ADOBEMOBILE_PUSH_ENABLED} field and dispatches an event to
* generate a corresponding Analytics request
*
* @param isEnabled whether the user is opted in to receive push notifications
*/
Expand Down Expand Up @@ -1736,35 +1727,32 @@ private boolean isPushEnabled() {
return false;
}

pushEnabled = namedCollection.getBoolean(DataStoreKeys.PUSH_ENABLED, false);
return namedCollection.getBoolean(DataStoreKeys.PUSH_ENABLED, false);
}

return pushEnabled;
}

/**
* Updates the {@link #pushEnabled} flag in DataStore with the provided value.
* Updates the persisted {@code ADOBEMOBILE_PUSH_ENABLED} flag in DataStore with the provided
* value.
*
* @param enabled new push status value to be updated
*/
private void setPushStatus(final boolean enabled) {
synchronized (pushEnabledMutex) {
if (namedCollection != null) {
namedCollection.setBoolean(DataStoreKeys.PUSH_ENABLED, enabled);
Log.trace(
IdentityConstants.LOG_TAG,
LOG_SOURCE,
"setPushStatus : Push notifications status is now: "
+ (enabled ? "Enabled" : "Disabled"));
} else {
Log.trace(
IdentityConstants.LOG_TAG,
LOG_SOURCE,
"setPushStatus : Unable to update push flag because the"
+ " LocalStorageService was not available.");
}

pushEnabled = enabled;
Log.trace(
IdentityConstants.LOG_TAG,
LOG_SOURCE,
"setPushStatus : Push notifications status is now: "
+ (pushEnabled ? "Enabled" : "Disabled"));
}
}

Expand Down Expand Up @@ -1857,17 +1845,7 @@ void processPrivacyChange(final Event event, final Map<String, Object> eventData
privacyStatus.getValue());

if (privacyStatus == MobilePrivacyStatus.OPT_OUT) {
mid = null;
advertisingIdentifier = null;
blob = null;
locationHint = null;
customerIds = null;

if (namedCollection != null) {
namedCollection.remove(DataStoreKeys.AID_SYNCED_KEY);
}

updatePushIdentifier(null);
clearIdentifiers();
savePersistently(); // clear datastore
getApi().createSharedState(packageEventData(), event);
} else if (StringUtils.isNullOrEmpty(mid)) {
Expand Down Expand Up @@ -2451,6 +2429,24 @@ private void loadPrivacyStatusFromConfigurationState(final Map<String, Object> c
privacyStatus = MobilePrivacyStatus.fromString(privacyString);
}

/** Clears in-memory identifiers and persisted push ID flags. */
private void clearIdentifiers() {
mid = null;
advertisingIdentifier = null;
blob = null;
locationHint = null;
customerIds = null;
pushIdentifier = null;

if (namedCollection != null) {
namedCollection.remove(DataStoreKeys.AID_SYNCED_KEY);
namedCollection.remove(DataStoreKeys.ANALYTICS_PUSH_SYNC);
synchronized (pushEnabledMutex) {
namedCollection.remove(DataStoreKeys.PUSH_ENABLED);
}
}
}

@VisibleForTesting
String getMid() {
return mid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.adobe.marketing.mobile.services.ServiceProvider
import com.adobe.marketing.mobile.util.DataReader
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
Expand All @@ -37,6 +38,7 @@ import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.never
Expand Down Expand Up @@ -636,6 +638,79 @@ class IdentityExtensionTests {
verify(spiedIdentityExtension, never()).processIdentityRequest(any())
}

@Test
fun `handleIdentityRequestReset() - clears identifiers`() {
val identityExtension = initializeSpiedIdentityExtension()
identityExtension.latestValidConfig = ConfigurationSharedStateIdentity(
mapOf(
"experienceCloud.org" to "orgid",
"global.privacy" to "optunknown"
)
)

identityExtension.mid = "test-mid"

val persistedData = capturePersistedData()
val clearedPersistedDataList = captureRemovedPersistedData()

identityExtension.handleIdentityRequestReset(
Event.Builder("event", EventType.IDENTITY, EventSource.REQUEST_RESET).build()
)

// Verify expected keys passed to NamedCollection.remove()
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_PERSISTED_MID"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_ADVERTISING_IDENTIFIER"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_PUSH_IDENTIFIER"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_VISITORID_IDS"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_PERSISTED_MID_HINT"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_PERSISTED_MID_BLOB"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_AID_SYNCED"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_PUSH_ENABLED"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_ANALYTICS_PUSH_SYNC"))

// Verify new ECID set to persistence
assertEquals(1, persistedData.size)
assertNotNull(persistedData["ADOBEMOBILE_PERSISTED_MID"])
assertNotEquals("test-mid", persistedData["ADOBEMOBILE_PERSISTED_MID"])
assertEquals(persistedData["ADOBEMOBILE_PERSISTED_MID"], identityExtension.mid)
}

@Test
fun `processPrivacyChange() - on optedout clears identifiers`() {
val identityExtension = initializeSpiedIdentityExtension()
identityExtension.latestValidConfig = ConfigurationSharedStateIdentity(
mapOf(
"experienceCloud.org" to "orgid",
"global.privacy" to "optunknown"
)
)

identityExtension.mid = "test-mid"

val persistedData = capturePersistedData()
val clearedPersistedDataList = captureRemovedPersistedData()

identityExtension.processPrivacyChange(
Event.Builder("event", EventType.CONFIGURATION, EventSource.RESPONSE_CONTENT).build(),
mapOf("global.privacy" to "optedout")
)

// Verify expected keys passed to NamedCollection.remove()
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_PERSISTED_MID"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_ADVERTISING_IDENTIFIER"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_PUSH_IDENTIFIER"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_VISITORID_IDS"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_PERSISTED_MID_HINT"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_PERSISTED_MID_BLOB"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_AID_SYNCED"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_PUSH_ENABLED"))
assertTrue(clearedPersistedDataList.contains("ADOBEMOBILE_ANALYTICS_PUSH_SYNC"))

// Verify ECID is not set
assertTrue(persistedData.isEmpty())
assertNull(identityExtension.mid)
}

// ==============================================================================================================
// void handleAnalyticsResponseIdentity()
// ==============================================================================================================
Expand Down Expand Up @@ -1862,4 +1937,35 @@ class IdentityExtensionTests {
}
return customerIdString.toString()
}

private fun capturePersistedData(): Map<String, Any> {
val persistedData = mutableMapOf<String, Any>()
doAnswer {
val key = it.arguments[0] as String
val value = it.arguments[1] as String
persistedData[key] = value
}.`when`(mockedNamedCollection).setString(
ArgumentMatchers.anyString(),
ArgumentMatchers.anyString()
)
doAnswer {
val key = it.arguments[0] as String
val value = it.arguments[1] as Boolean
persistedData[key] = value
}.`when`(mockedNamedCollection).setBoolean(
ArgumentMatchers.anyString(),
ArgumentMatchers.anyBoolean()
)
return persistedData
}

private fun captureRemovedPersistedData(): List<String> {
val removedData = mutableListOf<String>()
doAnswer {
val key = it.arguments[0] as String
removedData.add(key)
}.`when`(mockedNamedCollection).remove(ArgumentMatchers.anyString())

return removedData
}
}
Loading

0 comments on commit e2563e0

Please sign in to comment.