diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/database/impl/OSDatabase.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/database/impl/OSDatabase.kt index ceec1df67f..19013624a8 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/database/impl/OSDatabase.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/database/impl/OSDatabase.kt @@ -16,7 +16,7 @@ import com.onesignal.core.internal.database.IDatabase import com.onesignal.debug.internal.logging.Logging import com.onesignal.session.internal.outcomes.impl.OutcomeTableProvider import com.onesignal.session.internal.outcomes.impl.OutcomesDbContract.SQL_CREATE_OUTCOME_ENTRIES_V1 -import com.onesignal.session.internal.outcomes.impl.OutcomesDbContract.SQL_CREATE_OUTCOME_ENTRIES_V3 +import com.onesignal.session.internal.outcomes.impl.OutcomesDbContract.SQL_CREATE_OUTCOME_ENTRIES_V4 import com.onesignal.session.internal.outcomes.impl.OutcomesDbContract.SQL_CREATE_UNIQUE_OUTCOME_ENTRIES_V1 import com.onesignal.session.internal.outcomes.impl.OutcomesDbContract.SQL_CREATE_UNIQUE_OUTCOME_ENTRIES_V2 @@ -246,7 +246,7 @@ internal open class OSDatabase( override fun onCreate(db: SQLiteDatabase) { db.execSQL(SQL_CREATE_ENTRIES) - db.execSQL(SQL_CREATE_OUTCOME_ENTRIES_V3) + db.execSQL(SQL_CREATE_OUTCOME_ENTRIES_V4) db.execSQL(SQL_CREATE_UNIQUE_OUTCOME_ENTRIES_V2) db.execSQL(SQL_CREATE_IN_APP_MESSAGE_ENTRIES) for (ind in SQL_INDEX_ENTRIES) { @@ -277,6 +277,7 @@ internal open class OSDatabase( if (oldVersion == 5 && newVersion >= 6) upgradeFromV5ToV6(db) if (oldVersion < 7 && newVersion >= 7) upgradeToV7(db) if (oldVersion < 8 && newVersion >= 8) upgradeToV8(db) + if (oldVersion < 9 && newVersion >= 9) upgradeToV9(db) } // Add collapse_id field and index @@ -342,6 +343,10 @@ internal open class OSDatabase( _outcomeTableProvider.upgradeCacheOutcomeTableRevision1To2(db) } + private fun upgradeToV9(db: SQLiteDatabase) { + _outcomeTableProvider.upgradeOutcomeTableRevision3To4(db) + } + override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { Logging.warn("SDK version rolled back! Clearing $DATABASE_NAME as it could be in an unexpected state.") @@ -357,7 +362,7 @@ internal open class OSDatabase( } companion object { - private const val dbVersion = 8 + private const val dbVersion = 9 private val LOCK = Any() private const val DATABASE_NAME = "OneSignal.db" private const val INTEGER_PRIMARY_KEY_TYPE = " INTEGER PRIMARY KEY" diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/IOutcomeEvent.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/IOutcomeEvent.kt index 7baa602c00..8003b91224 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/IOutcomeEvent.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/IOutcomeEvent.kt @@ -8,5 +8,6 @@ interface IOutcomeEvent { val notificationIds: JSONArray? val name: String val timestamp: Long + val sessionTime: Long // in seconds val weight: Float } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/IOutcomeEventsController.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/IOutcomeEventsController.kt index 8abba092e1..3896951620 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/IOutcomeEventsController.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/IOutcomeEventsController.kt @@ -4,6 +4,11 @@ package com.onesignal.session.internal.outcomes * The gateway to outcomes logic. */ interface IOutcomeEventsController { + /** + * Send a session ending outcome event to the backend. + */ + suspend fun sendSessionEndOutcomeEvent(duration: Long): IOutcomeEvent? + /** * Send a unique outcome event to the backend. */ diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/IOutcomeEventsBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/IOutcomeEventsBackendService.kt index b620ed1e76..2afbecbe21 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/IOutcomeEventsBackendService.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/IOutcomeEventsBackendService.kt @@ -15,8 +15,9 @@ internal interface IOutcomeEventsBackendService { * @param appId The ID of the application this outcome event occurred under. * @param userId The OneSignal user ID that is active during the outcome event. * @param subscriptionId The subscription ID that is active during the outcome event. + * @param deviceType The type of device that the outcome event occurred on. * @param direct Whether this outcome event is direct. `true` if it is, `false` if it isn't, `null` if should not be specified. * @param event The outcome event to send up. */ - suspend fun sendOutcomeEvent(appId: String, userId: String, subscriptionId: String, direct: Boolean?, event: OutcomeEvent) + suspend fun sendOutcomeEvent(appId: String, userId: String, subscriptionId: String, deviceType: String, direct: Boolean?, event: OutcomeEvent) } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeConstants.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeConstants.kt index a5203ccc16..aeff4cce6c 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeConstants.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeConstants.kt @@ -6,6 +6,7 @@ internal object OutcomeConstants { const val OUTCOME_SOURCES = "sources" const val WEIGHT = "weight" const val TIMESTAMP = "timestamp" + const val SESSION_TIME = "session_time" // OSOutcomeSource Constants const val DIRECT = "direct" diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEvent.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEvent.kt index 5aac0723bd..d0311eb5ad 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEvent.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEvent.kt @@ -11,6 +11,7 @@ internal class OutcomeEvent( override val notificationIds: JSONArray?, override val name: String, override val timestamp: Long, + override val sessionTime: Long, override val weight: Float, ) : IOutcomeEvent { @Throws(JSONException::class) @@ -20,6 +21,7 @@ internal class OutcomeEvent( json.put(NOTIFICATION_IDS, notificationIds) json.put(OUTCOME_ID, name) json.put(TIMESTAMP, timestamp) + json.put(SESSION_TIME, sessionTime) json.put(WEIGHT, weight) return json } @@ -28,11 +30,11 @@ internal class OutcomeEvent( if (this === o) return true if (o == null || this.javaClass != o.javaClass) return false val event = o as OutcomeEvent - return session == event.session && notificationIds == event.notificationIds && name == event.name && timestamp == event.timestamp && weight == event.weight + return session == event.session && notificationIds == event.notificationIds && name == event.name && timestamp == event.timestamp && sessionTime == event.sessionTime && weight == event.weight } override fun hashCode(): Int { - val a = arrayOf(session, notificationIds, name, timestamp, weight) + val a = arrayOf(session, notificationIds, name, timestamp, sessionTime, weight) var result = 1 for (element in a) result = 31 * result + (element?.hashCode() ?: 0) return result @@ -44,6 +46,7 @@ internal class OutcomeEvent( ", notificationIds=" + notificationIds + ", name='" + name + '\'' + ", timestamp=" + timestamp + + ", sessionTime=" + sessionTime + ", weight=" + weight + '}' } @@ -53,6 +56,7 @@ internal class OutcomeEvent( private const val NOTIFICATION_IDS = "notification_ids" private const val OUTCOME_ID = "id" private const val TIMESTAMP = "timestamp" + private const val SESSION_TIME = "session_time" private const val WEIGHT = "weight" /** @@ -76,6 +80,7 @@ internal class OutcomeEvent( notificationId, outcomeEventParams.outcomeId, outcomeEventParams.timestamp, + outcomeEventParams.sessionTime, outcomeEventParams.weight, ) } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventParams.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventParams.kt index 881cfc50d5..91c914dd68 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventParams.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventParams.kt @@ -7,7 +7,8 @@ internal class OutcomeEventParams constructor( val outcomeId: String, val outcomeSource: OutcomeSource?, // This field is optional var weight: Float, // This field is optional. - var timestamp: Long = 0, + var sessionTime: Long, // This field is optional + var timestamp: Long, // This should start out as zero ) { @Throws(JSONException::class) fun toJSONObject(): JSONObject { @@ -18,6 +19,7 @@ internal class OutcomeEventParams constructor( } if (weight > 0) json.put(OutcomeConstants.WEIGHT, weight) if (timestamp > 0) json.put(OutcomeConstants.TIMESTAMP, timestamp) + if (sessionTime > 0) json.put(OutcomeConstants.SESSION_TIME, sessionTime) return json } @@ -29,6 +31,7 @@ internal class OutcomeEventParams constructor( ", outcomeSource=" + outcomeSource + ", weight=" + weight + ", timestamp=" + timestamp + + ", sessionTime=" + sessionTime + '}' } } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsBackendService.kt index 5b334d6904..7578c1f1df 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsBackendService.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsBackendService.kt @@ -7,14 +7,15 @@ import org.json.JSONObject internal class OutcomeEventsBackendService(private val _http: IHttpClient) : IOutcomeEventsBackendService { - override suspend fun sendOutcomeEvent(appId: String, userId: String, subscriptionId: String, direct: Boolean?, event: OutcomeEvent) { + override suspend fun sendOutcomeEvent(appId: String, userId: String, subscriptionId: String, deviceType: String, direct: Boolean?, event: OutcomeEvent) { val jsonObject = JSONObject() .put("app_id", appId) .put("onesignal_id", userId) .put( "subscription", JSONObject() - .put("id", subscriptionId), + .put("id", subscriptionId) + .put("type", deviceType) ) if (direct != null) { @@ -34,6 +35,10 @@ internal class OutcomeEventsBackendService(private val _http: IHttpClient) : jsonObject.put("timestamp", event.timestamp) } + if (event.sessionTime > 0) { + jsonObject.put("session_time", event.sessionTime) + } + val response = _http.post("outcomes/measure", jsonObject) if (!response.isSuccess) { diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt index 507b960e38..1365144b92 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt @@ -4,6 +4,7 @@ import android.os.Process import com.onesignal.common.exceptions.BackendException import com.onesignal.common.threading.suspendifyOnThread import com.onesignal.core.internal.config.ConfigModelStore +import com.onesignal.core.internal.device.IDeviceService import com.onesignal.core.internal.startup.IStartableService import com.onesignal.core.internal.time.ITime import com.onesignal.debug.internal.logging.Logging @@ -14,6 +15,7 @@ import com.onesignal.session.internal.influence.InfluenceType import com.onesignal.session.internal.outcomes.IOutcomeEventsController import com.onesignal.session.internal.session.ISessionLifecycleHandler import com.onesignal.session.internal.session.ISessionService +import com.onesignal.user.internal.backend.SubscriptionObjectType import com.onesignal.user.internal.identity.IdentityModelStore import com.onesignal.user.internal.subscriptions.ISubscriptionManager @@ -26,6 +28,7 @@ internal class OutcomeEventsController( private val _configModelStore: ConfigModelStore, private val _identityModelStore: IdentityModelStore, private val _subscriptionManager: ISubscriptionManager, + private val _deviceService: IDeviceService, private val _time: ITime, ) : IOutcomeEventsController, IStartableService, ISessionLifecycleHandler { // Keeps track of unique outcome events sent for UNATTRIBUTED sessions on a per session level @@ -78,6 +81,18 @@ Outcome event was cached and will be reattempted on app cold start""", } } + override suspend fun sendSessionEndOutcomeEvent(duration: Long): OutcomeEvent? { + val influences: List = _influenceManager.influences + + // only send the outcome if there are any influences associated with the session + for (influence in influences) { + if (influence.ids != null) { + return sendAndCreateOutcomeEvent("os__session_duration", 0f, duration, influences) + } + } + return null + } + override suspend fun sendUniqueOutcomeEvent(name: String): OutcomeEvent? { val sessionResult: List = _influenceManager.influences return sendUniqueOutcomeEvent(name, sessionResult) @@ -85,12 +100,12 @@ Outcome event was cached and will be reattempted on app cold start""", override suspend fun sendOutcomeEvent(name: String): OutcomeEvent? { val influences: List = _influenceManager.influences - return sendAndCreateOutcomeEvent(name, 0f, influences) + return sendAndCreateOutcomeEvent(name, 0f, 0, influences) } override suspend fun sendOutcomeEventWithValue(name: String, weight: Float): OutcomeEvent? { val influences: List = _influenceManager.influences - return sendAndCreateOutcomeEvent(name, weight, influences) + return sendAndCreateOutcomeEvent(name, weight, 0, influences) } /** @@ -131,7 +146,7 @@ Outcome event was cached and will be reattempted on app cold start""", // Return null to determine not a failure, but not a success in terms of the request made return null } - return sendAndCreateOutcomeEvent(name, 0f, uniqueInfluences) + return sendAndCreateOutcomeEvent(name, 0f, 0, uniqueInfluences) } else { // Make sure unique outcome has not been sent for current unattributed session if (unattributedUniqueOutcomeEventsSentOnSession.contains(name)) { @@ -147,13 +162,14 @@ Outcome event was cached and will be reattempted on app cold start""", return null } unattributedUniqueOutcomeEventsSentOnSession.add(name) - return sendAndCreateOutcomeEvent(name, 0f, influences) + return sendAndCreateOutcomeEvent(name, 0f, 0, influences) } } private suspend fun sendAndCreateOutcomeEvent( name: String, weight: Float, + sessionTime: Long, // Note: this is optional influences: List, ): OutcomeEvent? { val timestampSeconds: Long = _time.currentTimeMillis / 1000 @@ -183,7 +199,7 @@ Outcome event was cached and will be reattempted on app cold start""", } val source = OutcomeSource(directSourceBody, indirectSourceBody) - val eventParams = OutcomeEventParams(name, source, weight, 0) + val eventParams = OutcomeEventParams(name, source, weight, sessionTime, 0) try { requestMeasureOutcomeEvent(eventParams) @@ -267,10 +283,11 @@ Outcome event was cached and will be reattempted on app cold start""", private suspend fun requestMeasureOutcomeEvent(eventParams: OutcomeEventParams) { val appId: String = _configModelStore.model.appId val subscriptionId = _subscriptionManager.subscriptions.push.id + val deviceType = SubscriptionObjectType.fromDeviceType(_deviceService.deviceType).value // if we don't have a subscription ID yet, throw an exception. The outcome will be saved and processed // later, when we do have a subscription ID. - if (subscriptionId.isEmpty()) { + if (subscriptionId.isEmpty() || deviceType.isEmpty()) { throw BackendException(0) } @@ -282,6 +299,6 @@ Outcome event was cached and will be reattempted on app cold start""", else -> null } - _outcomeEventsBackend.sendOutcomeEvent(appId, _identityModelStore.model.onesignalId, subscriptionId, direct, event) + _outcomeEventsBackend.sendOutcomeEvent(appId, _identityModelStore.model.onesignalId, subscriptionId, deviceType, direct, event) } } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsRepository.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsRepository.kt index 85673d38bd..d542e870de 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsRepository.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsRepository.kt @@ -88,6 +88,7 @@ internal class OutcomeEventsRepository( put(OutcomeEventsTable.COLUMN_NAME_NAME, eventParams.outcomeId) put(OutcomeEventsTable.COLUMN_NAME_WEIGHT, eventParams.weight) put(OutcomeEventsTable.COLUMN_NAME_TIMESTAMP, eventParams.timestamp) + put(OutcomeEventsTable.COLUMN_NAME_SESSION_TIME, eventParams.sessionTime) }.also { values -> _databaseProvider.os.insert(OutcomeEventsTable.TABLE_NAME, null, values) } @@ -128,6 +129,8 @@ internal class OutcomeEventsRepository( cursor.getFloat(OutcomeEventsTable.COLUMN_NAME_WEIGHT) val timestamp = cursor.getLong(OutcomeEventsTable.COLUMN_NAME_TIMESTAMP) + val sessionTime = + cursor.getLong(OutcomeEventsTable.COLUMN_NAME_SESSION_TIME) try { val directSourceBody = OutcomeSourceBody() @@ -147,7 +150,7 @@ internal class OutcomeEventsRepository( it, ) } ?: OutcomeSource(null, null) - OutcomeEventParams(name, source, weight, timestamp).also { + OutcomeEventParams(name, source, weight, sessionTime, timestamp).also { events.add(it) } } catch (e: JSONException) { diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeTableProvider.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeTableProvider.kt index d13908cc2b..6413e5ed0c 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeTableProvider.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeTableProvider.kt @@ -78,6 +78,24 @@ internal class OutcomeTableProvider { } } + /** + * On the outcome table this adds the new session_time column. + * + * @param db + */ + fun upgradeOutcomeTableRevision3To4(db: SQLiteDatabase) { + try { + db.execSQL("BEGIN TRANSACTION;") + db.execSQL("ALTER TABLE " + OutcomeEventsTable.TABLE_NAME + " ADD COLUMN " + OutcomeEventsTable.COLUMN_NAME_SESSION_TIME + " INTEGER DEFAULT 1;") + // We intentionally choose to default session_time to 1 to address a bug on cached outcomes from v5.0.0-beta's + // os__session_duration requests expect a session_time and these will keep failing and caching, so let's just send them with a time of 1 for migrations + } catch (e: SQLiteException) { + e.printStackTrace() + } finally { + db.execSQL("COMMIT;") + } + } + /** * On the cache unique outcome table rename table, rename column notification id to influence id * Add column channel type diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomesDbContract.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomesDbContract.kt index 368852f99a..0cd0c0dfb6 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomesDbContract.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomesDbContract.kt @@ -20,6 +20,9 @@ internal object OutcomeEventsTable { const val COLUMN_NAME_WEIGHT = "weight" // Added on DB v5 SDK v3.12.1, migration added on DB v6 SDK v3.12.2 const val COLUMN_NAME_TIMESTAMP = "timestamp" // Added on DB v4 SDK v3.12.0 const val COLUMN_NAME_PARAMS = "params" // Added on DB v4 SDK v3.12.0 replaced with weight on DB v5 SDK v3.12.1, migration added on DB v6 SDK v3.12.2 + + // Session time + const val COLUMN_NAME_SESSION_TIME = "session_time" // Added on DB v9 SDK v5.0.0 (note that 5.0.0-beta's were still on v8) } internal object CachedUniqueOutcomeTable { @@ -72,6 +75,22 @@ internal object OutcomesDbContract { OutcomeEventsTable.COLUMN_NAME_TIMESTAMP + TIMESTAMP_TYPE + "," + // "params TEXT" Added in v4, removed in v5. OutcomeEventsTable.COLUMN_NAME_WEIGHT + FLOAT_TYPE + // New in v5, missing migration added in v6 ");" + + /** + * Adds a new column called session_time + */ + const val SQL_CREATE_OUTCOME_ENTRIES_V4 = "CREATE TABLE " + OutcomeEventsTable.TABLE_NAME + " (" + + OutcomeEventsTable.ID + INTEGER_PRIMARY_KEY_TYPE + "," + + OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE + TEXT_TYPE + "," + + OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE + TEXT_TYPE + "," + + OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS + TEXT_TYPE + "," + + OutcomeEventsTable.COLUMN_NAME_IAM_IDS + TEXT_TYPE + "," + + OutcomeEventsTable.COLUMN_NAME_NAME + TEXT_TYPE + "," + + OutcomeEventsTable.COLUMN_NAME_TIMESTAMP + TIMESTAMP_TYPE + "," + + OutcomeEventsTable.COLUMN_NAME_WEIGHT + FLOAT_TYPE + "," + + OutcomeEventsTable.COLUMN_NAME_SESSION_TIME + INT_TYPE + + ");" + const val SQL_CREATE_UNIQUE_OUTCOME_ENTRIES_V1 = "CREATE TABLE " + CachedUniqueOutcomeTable.TABLE_NAME_V1 + " (" + CachedUniqueOutcomeTable.ID + INTEGER_PRIMARY_KEY_TYPE + "," + CachedUniqueOutcomeTable.COLUMN_NAME_NOTIFICATION_ID + TEXT_TYPE + "," + diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionListener.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionListener.kt index 34787e5eba..9eda7dd100 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionListener.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionListener.kt @@ -47,10 +47,11 @@ internal class SessionListener( } override fun onSessionEnded(duration: Long) { - _operationRepo.enqueue(TrackSessionEndOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, duration)) + val durationInSeconds = duration / 1000 + _operationRepo.enqueue(TrackSessionEndOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, durationInSeconds)) suspendifyOnThread { - _outcomeEventsController.sendOutcomeEvent("os__session_duration") + _outcomeEventsController.sendSessionEndOutcomeEvent(durationInSeconds) } } } diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsBackendServiceTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsBackendServiceTests.kt index 9145a7e183..9b67c4592e 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsBackendServiceTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsBackendServiceTests.kt @@ -25,13 +25,13 @@ class OutcomeEventsBackendServiceTests : FunSpec({ test("send outcome event") { /* Given */ - val evnt = OutcomeEvent(InfluenceType.DIRECT, null, "EVENT_NAME", 0, 0F) + val evnt = OutcomeEvent(InfluenceType.DIRECT, null, "EVENT_NAME", 0, 0, 0F) val spyHttpClient = mockk() coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(200, null) val outcomeEventsController = OutcomeEventsBackendService(spyHttpClient) /* When */ - outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", null, evnt) + outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", "AndroidPush", null, evnt) /* Then */ coVerify { @@ -53,13 +53,13 @@ class OutcomeEventsBackendServiceTests : FunSpec({ test("send outcome event with weight") { /* Given */ - val evnt = OutcomeEvent(InfluenceType.DIRECT, null, "EVENT_NAME", 0, 1F) + val evnt = OutcomeEvent(InfluenceType.DIRECT, null, "EVENT_NAME", 0, 0, 1F) val spyHttpClient = mockk() coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(200, null) val outcomeEventsController = OutcomeEventsBackendService(spyHttpClient) /* When */ - outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", null, evnt) + outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", "AndroidPush", null, evnt) /* Then */ coVerify { @@ -81,13 +81,13 @@ class OutcomeEventsBackendServiceTests : FunSpec({ test("send outcome event with indirect") { /* Given */ - val evnt = OutcomeEvent(InfluenceType.DIRECT, null, "EVENT_NAME", 0, 0F) + val evnt = OutcomeEvent(InfluenceType.DIRECT, null, "EVENT_NAME", 0, 0, 0F) val spyHttpClient = mockk() coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(200, null) val outcomeEventsController = OutcomeEventsBackendService(spyHttpClient) /* When */ - outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", false, evnt) + outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", "AndroidPush", false, evnt) /* Then */ coVerify { @@ -109,13 +109,13 @@ class OutcomeEventsBackendServiceTests : FunSpec({ test("send outcome event with direct") { /* Given */ - val evnt = OutcomeEvent(InfluenceType.DIRECT, null, "EVENT_NAME", 0, 0F) + val evnt = OutcomeEvent(InfluenceType.DIRECT, null, "EVENT_NAME", 0, 0, 0F) val spyHttpClient = mockk() coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(200, null) val outcomeEventsController = OutcomeEventsBackendService(spyHttpClient) /* When */ - outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", true, evnt) + outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", "AndroidPush", true, evnt) /* Then */ coVerify { @@ -137,13 +137,13 @@ class OutcomeEventsBackendServiceTests : FunSpec({ test("send outcome event with timestamp") { /* Given */ - val evnt = OutcomeEvent(InfluenceType.DIRECT, null, "EVENT_NAME", 1111L, 0F) + val evnt = OutcomeEvent(InfluenceType.DIRECT, null, "EVENT_NAME", 1111L, 0, 0F) val spyHttpClient = mockk() coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(200, null) val outcomeEventsController = OutcomeEventsBackendService(spyHttpClient) /* When */ - outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", null, evnt) + outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", "AndroidPush", null, evnt) /* Then */ coVerify { @@ -165,14 +165,14 @@ class OutcomeEventsBackendServiceTests : FunSpec({ test("send outcome event with unsuccessful response") { /* Given */ - val evnt = OutcomeEvent(InfluenceType.DIRECT, null, "EVENT_NAME", 1111L, 0F) + val evnt = OutcomeEvent(InfluenceType.DIRECT, null, "EVENT_NAME", 1111L, 0, 0F) val spyHttpClient = mockk() coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(503, "SERVICE UNAVAILABLE") val outcomeEventsController = OutcomeEventsBackendService(spyHttpClient) /* When */ val exception = shouldThrowUnit { - outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", null, evnt) + outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", "AndroidPush", null, evnt) } /* Then */ diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsControllerTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsControllerTests.kt index a9df9db222..3c4b5318c2 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsControllerTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsControllerTests.kt @@ -74,6 +74,7 @@ class OutcomeEventsControllerTests : FunSpec({ MockHelper.configModelStore(), MockHelper.identityModelStore(), mockSubscriptionManager, + MockHelper.deviceService(), MockHelper.time(now), ) @@ -82,7 +83,7 @@ class OutcomeEventsControllerTests : FunSpec({ /* Then */ evnt shouldBe null - coVerify(exactly = 0) { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any(), any()) } + coVerify(exactly = 0) { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any(), any(), any()) } } test("send outcome with unattributed influences") { @@ -112,6 +113,7 @@ class OutcomeEventsControllerTests : FunSpec({ MockHelper.configModelStore(), MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, mockSubscriptionManager, + MockHelper.deviceService(), MockHelper.time(now), ) @@ -126,7 +128,7 @@ class OutcomeEventsControllerTests : FunSpec({ evnt.session shouldBe InfluenceType.UNATTRIBUTED evnt.timestamp shouldBe 0 // timestamp only set when it had to be saved. - coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", null, evnt) } + coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", "AndroidPush", null, evnt) } } test("send outcome with indirect influences") { @@ -157,6 +159,7 @@ class OutcomeEventsControllerTests : FunSpec({ MockHelper.configModelStore(), MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, mockSubscriptionManager, + MockHelper.deviceService(), MockHelper.time(now), ) @@ -172,7 +175,7 @@ class OutcomeEventsControllerTests : FunSpec({ evnt.session shouldBe InfluenceType.INDIRECT evnt.timestamp shouldBe 0 // timestamp only set when it had to be saved. - coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", false, evnt) } + coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", "AndroidPush", false, evnt) } } test("send outcome with direct influence") { @@ -203,6 +206,7 @@ class OutcomeEventsControllerTests : FunSpec({ MockHelper.configModelStore(), MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, mockSubscriptionManager, + MockHelper.deviceService(), MockHelper.time(now), ) @@ -218,7 +222,7 @@ class OutcomeEventsControllerTests : FunSpec({ evnt.session shouldBe InfluenceType.DIRECT evnt.timestamp shouldBe 0 // timestamp only set when it had to be saved. - coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", true, evnt) } + coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", "AndroidPush", true, evnt) } } test("send outcome with weight") { @@ -249,6 +253,7 @@ class OutcomeEventsControllerTests : FunSpec({ MockHelper.configModelStore(), MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, mockSubscriptionManager, + MockHelper.deviceService(), MockHelper.time(now), ) @@ -263,7 +268,7 @@ class OutcomeEventsControllerTests : FunSpec({ evnt.session shouldBe InfluenceType.UNATTRIBUTED evnt.timestamp shouldBe 0 // timestamp only set when it had to be saved. - coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", null, evnt) } + coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", "AndroidPush", null, evnt) } } test("send unique outcome with unattributed influences") { @@ -293,6 +298,7 @@ class OutcomeEventsControllerTests : FunSpec({ MockHelper.configModelStore(), MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, mockSubscriptionManager, + MockHelper.deviceService(), MockHelper.time(now), ) @@ -310,7 +316,7 @@ class OutcomeEventsControllerTests : FunSpec({ evnt2 shouldBe null - coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", any(), any()) } + coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", "AndroidPush", any(), any()) } } test("send unique outcome with same indirect influences") { @@ -346,6 +352,7 @@ class OutcomeEventsControllerTests : FunSpec({ MockHelper.configModelStore(), MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, mockSubscriptionManager, + MockHelper.deviceService(), MockHelper.time(now), ) @@ -366,7 +373,7 @@ class OutcomeEventsControllerTests : FunSpec({ evnt2 shouldBe null - coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", any(), any()) } + coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", "AndroidPush", any(), any()) } } test("send unique outcome with different indirect influences") { @@ -404,6 +411,7 @@ class OutcomeEventsControllerTests : FunSpec({ MockHelper.configModelStore(), MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, mockSubscriptionManager, + MockHelper.deviceService(), MockHelper.time(now), ) @@ -431,8 +439,8 @@ class OutcomeEventsControllerTests : FunSpec({ evnt2.timestamp shouldBe 0 // timestamp only set when it had to be saved. coVerifySequence { - mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", false, evnt1) - mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", true, evnt2) + mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", "AndroidPush", false, evnt1) + mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", "AndroidPush", true, evnt2) } } @@ -453,7 +461,7 @@ class OutcomeEventsControllerTests : FunSpec({ val mockOutcomeEventsRepository = spyk() val mockOutcomeEventsPreferences = spyk() val mockOutcomeEventsBackend = mockk() - coEvery { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any(), any()) } throws BackendException(408, null) + coEvery { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any(), any(), any()) } throws BackendException(408, null) val outcomeEventsController = OutcomeEventsController( mockSessionService, @@ -464,6 +472,7 @@ class OutcomeEventsControllerTests : FunSpec({ MockHelper.configModelStore(), MockHelper.identityModelStore(), mockSubscriptionManager, + MockHelper.deviceService(), MockHelper.time(now), ) @@ -500,12 +509,12 @@ class OutcomeEventsControllerTests : FunSpec({ coEvery { mockOutcomeEventsRepository.cleanCachedUniqueOutcomeEventNotifications() } just runs coEvery { mockOutcomeEventsRepository.deleteOldOutcomeEvent(any()) } just runs coEvery { mockOutcomeEventsRepository.getAllEventsToSend() } returns listOf( - OutcomeEventParams("outcomeId1", OutcomeSource(OutcomeSourceBody(JSONArray().put("notificationId1")), null), .4f, 1111), - OutcomeEventParams("outcomeId2", OutcomeSource(null, OutcomeSourceBody(JSONArray().put("notificationId2").put("notificationId3"))), .2f, 2222), + OutcomeEventParams("outcomeId1", OutcomeSource(OutcomeSourceBody(JSONArray().put("notificationId1")), null), .4f, 0, 1111), + OutcomeEventParams("outcomeId2", OutcomeSource(null, OutcomeSourceBody(JSONArray().put("notificationId2").put("notificationId3"))), .2f, 0, 2222), ) val mockOutcomeEventsPreferences = spyk() val mockOutcomeEventsBackend = mockk() - coEvery { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any(), any()) } just runs + coEvery { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any(), any(), any()) } just runs val outcomeEventsController = OutcomeEventsController( mockSessionService, @@ -516,6 +525,7 @@ class OutcomeEventsControllerTests : FunSpec({ MockHelper.configModelStore(), MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, mockSubscriptionManager, + MockHelper.deviceService(), MockHelper.time(now), ) @@ -530,6 +540,7 @@ class OutcomeEventsControllerTests : FunSpec({ "appId", "onesignalId", "subscriptionId", + "AndroidPush", true, withArg { it.name shouldBe "outcomeId1" @@ -544,6 +555,7 @@ class OutcomeEventsControllerTests : FunSpec({ "appId", "onesignalId", "subscriptionId", + "AndroidPush", false, withArg { it.name shouldBe "outcomeId2" @@ -587,12 +599,12 @@ class OutcomeEventsControllerTests : FunSpec({ val mockOutcomeEventsRepository = mockk() coEvery { mockOutcomeEventsRepository.cleanCachedUniqueOutcomeEventNotifications() } just runs coEvery { mockOutcomeEventsRepository.getAllEventsToSend() } returns listOf( - OutcomeEventParams("outcomeId1", OutcomeSource(OutcomeSourceBody(JSONArray().put("notificationId1")), null), .4f, 1111), - OutcomeEventParams("outcomeId2", OutcomeSource(null, OutcomeSourceBody(JSONArray().put("notificationId2").put("notificationId3"))), .2f, 2222), + OutcomeEventParams("outcomeId1", OutcomeSource(OutcomeSourceBody(JSONArray().put("notificationId1")), null), .4f, 0, 1111), + OutcomeEventParams("outcomeId2", OutcomeSource(null, OutcomeSourceBody(JSONArray().put("notificationId2").put("notificationId3"))), .2f, 0, 2222), ) val mockOutcomeEventsPreferences = spyk() val mockOutcomeEventsBackend = mockk() - coEvery { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any(), any()) } throws BackendException(408, null) + coEvery { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any(), any(), any()) } throws BackendException(408, null) val outcomeEventsController = OutcomeEventsController( mockSessionService, @@ -603,6 +615,7 @@ class OutcomeEventsControllerTests : FunSpec({ MockHelper.configModelStore(), MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, mockSubscriptionManager, + MockHelper.deviceService(), MockHelper.time(now), ) @@ -617,6 +630,7 @@ class OutcomeEventsControllerTests : FunSpec({ "appId", "onesignalId", "subscriptionId", + "AndroidPush", true, withArg { it.name shouldBe "outcomeId1" @@ -631,6 +645,7 @@ class OutcomeEventsControllerTests : FunSpec({ "appId", "onesignalId", "subscriptionId", + "AndroidPush", false, withArg { it.name shouldBe "outcomeId2" diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsRepositoryTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsRepositoryTests.kt index ab5c2e4eb7..3c378ce649 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsRepositoryTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsRepositoryTests.kt @@ -36,7 +36,7 @@ class OutcomeEventsRepositoryTests : FunSpec({ val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) /* When */ - outcomeEventsRepository.deleteOldOutcomeEvent(OutcomeEventParams("outcomeId", null, 0f, 1111)) + outcomeEventsRepository.deleteOldOutcomeEvent(OutcomeEventParams("outcomeId", null, 0f, 0, 1111)) /* Then */ verify(exactly = 1) { mockDatabasePair.second.delete(OutcomeEventsTable.TABLE_NAME, withArg { it.contains(OutcomeEventsTable.COLUMN_NAME_TIMESTAMP) }, withArg { it.contains("1111") }) } @@ -48,7 +48,7 @@ class OutcomeEventsRepositoryTests : FunSpec({ val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) /* When */ - outcomeEventsRepository.saveOutcomeEvent(OutcomeEventParams("outcomeId1", null, 0f, 1111)) + outcomeEventsRepository.saveOutcomeEvent(OutcomeEventParams("outcomeId1", null, 0f, 0, 1111)) outcomeEventsRepository.saveOutcomeEvent( OutcomeEventParams( "outcomeId2", @@ -57,6 +57,7 @@ class OutcomeEventsRepositoryTests : FunSpec({ OutcomeSourceBody(null, JSONArray().put("iamId1").put("iamId2")), ), .2f, + 0, 2222, ), ) @@ -68,6 +69,7 @@ class OutcomeEventsRepositoryTests : FunSpec({ null, ), .4f, + 0, 3333, ), ) @@ -79,6 +81,7 @@ class OutcomeEventsRepositoryTests : FunSpec({ OutcomeSourceBody(JSONArray().put("notificationId1"), JSONArray().put("iamId1").put("iamId2")), ), .6f, + 0, 4444, ), ) @@ -226,7 +229,7 @@ class OutcomeEventsRepositoryTests : FunSpec({ val outcomeEventsRepository = OutcomeEventsRepository(mockDatabasePair.first) /* When */ - outcomeEventsRepository.saveUniqueOutcomeEventParams(OutcomeEventParams("outcomeId1", null, 0f, 1111)) + outcomeEventsRepository.saveUniqueOutcomeEventParams(OutcomeEventParams("outcomeId1", null, 0f, 0, 1111)) /* Then */ verify(exactly = 0) { mockDatabasePair.second.insert(CachedUniqueOutcomeTable.COLUMN_NAME_NAME, null, any()) } @@ -246,6 +249,7 @@ class OutcomeEventsRepositoryTests : FunSpec({ OutcomeSourceBody(null, JSONArray().put("iamId1").put("iamId2")), ), .2f, + 0, 2222, ), ) @@ -296,6 +300,7 @@ class OutcomeEventsRepositoryTests : FunSpec({ OutcomeSourceBody(JSONArray().put("notificationId1").put("notificationId2")), ), .2f, + 0, 2222, ), ) @@ -346,6 +351,7 @@ class OutcomeEventsRepositoryTests : FunSpec({ null, ), .2f, + 0, 2222, ), ) @@ -387,6 +393,7 @@ class OutcomeEventsRepositoryTests : FunSpec({ OutcomeSourceBody(JSONArray().put("notificationId1").put("notificationId2"), JSONArray().put("iamId1").put("iamId2")), ), .2f, + 0, 2222, ), )