diff --git a/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java b/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java index d01dfc072..3f3fdadbc 100644 --- a/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java +++ b/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java @@ -91,7 +91,7 @@ public void testGetDataQueue_DataQueueMigrationFromCacheDirectory() { FileUtils.deleteFile(context.getDatabasePath(TEST_DATABASE_NAME).getParentFile(), true); assertFalse(context.getDatabasePath(TEST_DATABASE_NAME).exists()); File cacheDatabaseFile = new File(context.getCacheDir(), TEST_DATABASE_NAME); - DataQueue dataQueue = new SQLiteDataQueue(cacheDatabaseFile.getPath()); + DataQueue dataQueue = new SQLiteDataQueue(TEST_DATABASE_NAME, cacheDatabaseFile.getPath()); dataQueue.add(new DataEntity("test_data_1")); DataQueue dataQueueExisting = new DataQueueService().getDataQueue(TEST_DATABASE_NAME); Assert.assertEquals("test_data_1", dataQueueExisting.peek().getData()); @@ -102,7 +102,7 @@ public void testGetDataQueue_DataQueueMigrationFromCacheDirectory() { public void testGetDataQueue_DataQueueMigrationFromCacheDirectory_DatabasesDirectoryAbsent() { assertFalse(context.getDatabasePath(TEST_DATABASE_NAME).exists()); File cacheDatabaseFile = new File(context.getCacheDir(), TEST_DATABASE_NAME); - DataQueue dataQueue = new SQLiteDataQueue(cacheDatabaseFile.getPath()); + DataQueue dataQueue = new SQLiteDataQueue(TEST_DATABASE_NAME, cacheDatabaseFile.getPath()); dataQueue.add(new DataEntity("test_data_1")); DataQueue dataQueueExisting = new DataQueueService().getDataQueue(TEST_DATABASE_NAME); Assert.assertEquals("test_data_1", dataQueueExisting.peek().getData()); @@ -112,7 +112,7 @@ public void testGetDataQueue_DataQueueMigrationFromCacheDirectory_DatabasesDirec @Test public void testGetDataQueue_DataQueueExistsInDatabaseDirectory() { File databaseFile = context.getDatabasePath(TEST_DATABASE_NAME); - DataQueue dataQueue = new SQLiteDataQueue(databaseFile.getPath()); + DataQueue dataQueue = new SQLiteDataQueue(TEST_DATABASE_NAME, databaseFile.getPath()); dataQueue.add(new DataEntity("test_data_1")); DataQueue dataQueueExisting = new DataQueueService().getDataQueue(TEST_DATABASE_NAME); Assert.assertEquals("test_data_1", dataQueueExisting.peek().getData()); diff --git a/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java b/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java index 330a290c3..02c0ba716 100644 --- a/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java +++ b/code/core/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java @@ -33,7 +33,7 @@ public class SqliteDataQueueTests { @Before public void setUp() { Context context = ApplicationProvider.getApplicationContext(); - dataQueue = new SQLiteDataQueue(context.getDatabasePath(QUEUE_NAME).getPath()); + dataQueue = new SQLiteDataQueue(QUEUE_NAME, context.getDatabasePath(QUEUE_NAME).getPath()); } @After diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/internal/CoreConstants.kt b/code/core/src/main/java/com/adobe/marketing/mobile/internal/CoreConstants.kt index 7e39e5a02..9db2f03af 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/internal/CoreConstants.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/internal/CoreConstants.kt @@ -13,7 +13,7 @@ package com.adobe.marketing.mobile.internal internal object CoreConstants { const val LOG_TAG = "MobileCore" - const val VERSION = "3.0.1" + const val VERSION = "3.0.2" object EventDataKeys { /** diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/core/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 74ea39b48..de401e2a6 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/core/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -199,6 +199,13 @@ internal class EventHub { ).get() } + /** + * Submits a task to be executed in the event hub executor. + */ + fun executeInEventHubExecutor(task: () -> Unit) { + eventHubExecutor.submit(task) + } + /** * Initializes event history. This must be called after the SDK has application context. */ diff --git a/code/core/src/main/java/com/adobe/marketing/mobile/internal/util/SQLiteDatabaseHelper.java b/code/core/src/main/java/com/adobe/marketing/mobile/internal/util/SQLiteDatabaseHelper.java index db868436d..a5682f6da 100644 --- a/code/core/src/main/java/com/adobe/marketing/mobile/internal/util/SQLiteDatabaseHelper.java +++ b/code/core/src/main/java/com/adobe/marketing/mobile/internal/util/SQLiteDatabaseHelper.java @@ -44,9 +44,9 @@ public static boolean createTableIfNotExist(final String dbPath, final String qu CoreConstants.LOG_TAG, LOG_PREFIX, String.format( - "createTableIfNotExists - Error in creating/accessing table. Error:" - + " (%s)", - e.getMessage())); + "createTableIfNotExists - Error in creating/accessing database (%s)." + + "Error: (%s)", + dbPath, e.getMessage())); return false; } finally { closeDatabase(database); @@ -73,9 +73,9 @@ public static int getTableSize(final String dbPath, final String tableName) { CoreConstants.LOG_TAG, LOG_PREFIX, String.format( - "getTableSize - Error in querying table(%s) size. Returning 0. Error:" - + " (%s)", - tableName, e.getMessage())); + "getTableSize - Error in querying table(%s) size from database(%s)." + + "Returning 0. Error: (%s)", + tableName, dbPath, e.getMessage())); return 0; } finally { closeDatabase(database); @@ -101,9 +101,9 @@ public static boolean clearTable(final String dbPath, final String tableName) { CoreConstants.LOG_TAG, LOG_PREFIX, String.format( - "clearTable - Error in clearing table(%s). Returning false. Error:" - + " (%s)", - tableName, e.getMessage())); + "clearTable - Error in clearing table(%s) from database(%s)." + + "Returning false. Error: (%s)", + tableName, dbPath, e.getMessage())); return false; } finally { closeDatabase(database); @@ -208,7 +208,9 @@ public static boolean process( Log.warning( CoreConstants.LOG_TAG, LOG_PREFIX, - "Failed to open database -" + e.getLocalizedMessage()); + "Failed to open database (%s). Error: %s", + filePath, + e.getLocalizedMessage()); return false; } finally { if (database != null) { diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index 16667eb12..626cb20fa 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -123,18 +123,28 @@ public static void setApplication(@NonNull final Application application) { ServiceProvider.getInstance().getAppContextService().setApplication(application); App.INSTANCE.registerActivityResumedListener(MobileCore::collectLaunchInfo); - try { - V4Migrator migrator = new V4Migrator(); - migrator.migrate(); - } catch (Exception e) { - Log.error( - CoreConstants.LOG_TAG, - LOG_TAG, - "Migration from V4 SDK failed with error - " + e.getLocalizedMessage()); - } - - // Initialize event history - EventHub.Companion.getShared().initializeEventHistory(); + // Migration and EventHistory operations must complete in a background thread before any + // extensions are registered. + // To ensure these tasks are completed before any registerExtension calls are made, + // reuse the eventHubExecutor instead of using a separate executor instance. + EventHub.Companion.getShared() + .executeInEventHubExecutor( + () -> { + try { + V4Migrator migrator = new V4Migrator(); + migrator.migrate(); + } catch (Exception e) { + Log.error( + CoreConstants.LOG_TAG, + LOG_TAG, + "Migration from V4 SDK failed with error - " + + e.getLocalizedMessage()); + } + + // Initialize event history + EventHub.Companion.getShared().initializeEventHistory(); + return null; + }); } /** diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java index 1dc306075..4004f6d78 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java @@ -59,7 +59,7 @@ public DataQueue getDataQueue(final String databaseName) { databaseName); return null; } - dataQueue = new SQLiteDataQueue(databaseDirDataQueue.getPath()); + dataQueue = new SQLiteDataQueue(databaseName, databaseDirDataQueue.getPath()); dataQueueCache.put(databaseName, dataQueue); } } diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java b/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java index 67bf84276..e5a303c06 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java @@ -30,12 +30,13 @@ final class SQLiteDataQueue implements DataQueue { private static final String TB_KEY_UNIQUE_IDENTIFIER = "uniqueIdentifier"; private static final String TB_KEY_TIMESTAMP = "timestamp"; private static final String TB_KEY_DATA = "data"; - private static final String LOG_PREFIX = "SQLiteDataQueue"; + private final String LOG_PREFIX; private final String databasePath; private boolean isClose = false; private final Object dbMutex = new Object(); - SQLiteDataQueue(final String databasePath) { + SQLiteDataQueue(final String databaseName, final String databasePath) { + this.LOG_PREFIX = "SQLiteDataQueue-" + databaseName; this.databasePath = databasePath; createTableIfNotExists(); } @@ -121,26 +122,22 @@ public List peek(final int n) { ServiceConstants.LOG_TAG, LOG_PREFIX, String.format( - "query - Successfully read %d rows from table(%s)", - rows.size(), TABLE_NAME)); + "query - Successfully read %d rows from table.", + rows.size())); return true; } catch (final SQLiteException e) { Log.warning( ServiceConstants.LOG_TAG, LOG_PREFIX, String.format( - "query - Error in querying database table (%s). Error:" + "query - Error in querying database table. Error:" + " (%s)", - TABLE_NAME, e.getLocalizedMessage())); + e.getLocalizedMessage())); return false; } }); } - if (rows.isEmpty()) { - return new ArrayList<>(); - } - final List dataEntitiesList = new ArrayList<>(rows.size()); for (ContentValues row : rows) { @@ -238,8 +235,8 @@ public boolean remove(final int n) { LOG_PREFIX, String.format( "removeRows - Error in deleting rows from" - + " table(%s). Returning 0. Error: (%s)", - TABLE_NAME, e.getMessage())); + + " table. Returning 0. Error: (%s)", + e.getMessage())); return false; } }); @@ -273,8 +270,7 @@ public boolean clear() { ServiceConstants.LOG_TAG, LOG_PREFIX, String.format( - "clear - %s in clearing Table %s", - (result ? "Successful" : "Failed"), TABLE_NAME)); + "clear - %s in clearing table", (result ? "Successful" : "Failed"))); if (!result) { resetDatabase(); @@ -323,10 +319,8 @@ private void createTableIfNotExists() { Log.trace( ServiceConstants.LOG_TAG, LOG_PREFIX, - String.format( - "createTableIfNotExists - Successfully created/already existed" - + " table (%s) ", - TABLE_NAME)); + "createTableIfNotExists - Successfully created/already existed" + + " table."); return; } } @@ -334,9 +328,7 @@ private void createTableIfNotExists() { Log.warning( ServiceConstants.LOG_TAG, LOG_PREFIX, - String.format( - "createTableIfNotExists - Error creating/accessing table (%s) ", - TABLE_NAME)); + "createTableIfNotExists - Error creating/accessing table."); } /** diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt b/code/core/src/test/java/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt index 5bb0f3953..9fe9e4d5d 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt @@ -43,6 +43,7 @@ import java.lang.UnsupportedOperationException import java.util.Locale import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull @@ -148,6 +149,23 @@ internal class EventHubTests { } } + private class TestExtension_InitCallback(api: ExtensionApi) : Extension(api) { + companion object { + const val EXTENSION_NAME = "TestExtension_InitCallback" + + // Calls this during initialization + var initCallback: (() -> Unit)? = null + } + + init { + initCallback?.invoke() + } + + override fun getName(): String { + return EXTENSION_NAME + } + } + private lateinit var eventHub: EventHub private val eventType = "Type" private val eventSource = "Source" @@ -199,6 +217,26 @@ internal class EventHubTests { assertEquals(EventHubError.None, ret) } + @Test + fun testExecutionOrderBeforeExtensionInitialization() { + val latch = CountDownLatch(1) + val flag = AtomicBoolean(false) + TestExtension_InitCallback.initCallback = { + if (flag.get()) { + latch.countDown() + } + } + + // This should complete before the extension instance is created. + eventHub.executeInEventHubExecutor { + flag.set(true) + } + val ret = registerExtension(TestExtension_InitCallback::class.java) + assertEquals(EventHubError.None, ret) + + assertTrue { latch.await(250, TimeUnit.MILLISECONDS) } + } + @Test fun testRegisterExtensionFailure_DuplicateExtension() { var ret = registerExtension(TestExtension2::class.java) diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java b/code/core/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java index a523c684a..83eaeb92a 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java +++ b/code/core/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java @@ -32,7 +32,8 @@ public class SqliteDataQueueTests { private DataQueue dataQueue; - private static final String DATABASE_NAME = "test.sqlite"; + private static final String DATABASE_PATH = "test.sqlite"; + private static final String DATABASE_NAME = "test_database"; private static final String TABLE_NAME = "TB_AEP_DATA_ENTITY"; private static final String EMPTY_JSON_STRING = "{}"; @@ -48,7 +49,7 @@ public void addDataEntitySuccess() { DataEntity dataEntity = new DataEntity(EMPTY_JSON_STRING); try (MockedStatic helperMock = Mockito.mockStatic(SQLiteDatabaseHelper.class)) { - dataQueue = new SQLiteDataQueue(DATABASE_NAME); + dataQueue = new SQLiteDataQueue(DATABASE_NAME, DATABASE_PATH); helperMock .when( () -> @@ -74,7 +75,7 @@ public void addDataEntityFailure() { DataEntity dataEntity = new DataEntity(EMPTY_JSON_STRING); try (MockedStatic helperMock = Mockito.mockStatic(SQLiteDatabaseHelper.class)) { - dataQueue = new SQLiteDataQueue(DATABASE_NAME); + dataQueue = new SQLiteDataQueue(DATABASE_NAME, DATABASE_PATH); helperMock .when( () -> @@ -104,7 +105,7 @@ public void testClearTable() { .when(() -> SQLiteDatabaseHelper.clearTable(anyString(), anyString())) .thenReturn(true); // Actions - boolean result = SQLiteDatabaseHelper.clearTable(DATABASE_NAME, TABLE_NAME); + boolean result = SQLiteDatabaseHelper.clearTable(DATABASE_PATH, TABLE_NAME); // Assertions assertTrue(result); @@ -121,7 +122,7 @@ public void testTableCount() { .when(() -> SQLiteDatabaseHelper.getTableSize(anyString(), anyString())) .thenReturn(mockedTableSize); // Actions - int tableSize = SQLiteDatabaseHelper.getTableSize(DATABASE_NAME, TABLE_NAME); + int tableSize = SQLiteDatabaseHelper.getTableSize(DATABASE_PATH, TABLE_NAME); // Assertions assertEquals(tableSize, mockedTableSize); @@ -132,7 +133,7 @@ public void testTableCount() { public void testClose() { try (MockedStatic helperMock = Mockito.mockStatic(SQLiteDatabaseHelper.class)) { - dataQueue = new SQLiteDataQueue(DATABASE_NAME); + dataQueue = new SQLiteDataQueue(DATABASE_NAME, DATABASE_PATH); helperMock .when( () -> @@ -159,7 +160,7 @@ public void testClose() { public void addDataEntityWithDatabaseOpenError() { try (MockedStatic helperMock = Mockito.mockStatic(SQLiteDatabaseHelper.class)) { - dataQueue = new SQLiteDataQueue(DATABASE_NAME); + dataQueue = new SQLiteDataQueue(DATABASE_NAME, DATABASE_PATH); helperMock .when( () -> @@ -176,7 +177,7 @@ public void addDataEntityWithDatabaseOpenError() { .when( () -> SQLiteDatabaseHelper.openDatabase( - DATABASE_NAME, + DATABASE_PATH, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE)) .thenThrow(SQLiteException.class); boolean result = dataQueue.add(new DataEntity(EMPTY_JSON_STRING)); @@ -190,7 +191,7 @@ public void addDataEntityWithDatabaseOpenError() { public void clearTableWithDatabaseOpenError() { try (MockedStatic helperMock = Mockito.mockStatic(SQLiteDatabaseHelper.class)) { - dataQueue = new SQLiteDataQueue(DATABASE_NAME); + dataQueue = new SQLiteDataQueue(DATABASE_NAME, DATABASE_PATH); helperMock .when( () -> @@ -204,7 +205,7 @@ public void clearTableWithDatabaseOpenError() { .when( () -> SQLiteDatabaseHelper.openDatabase( - DATABASE_NAME, + DATABASE_PATH, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE)) .thenThrow(SQLiteException.class); boolean result = dataQueue.clear(); @@ -218,7 +219,7 @@ public void clearTableWithDatabaseOpenError() { public void getTableSizeWithDatabaseOpenError() { try (MockedStatic helperMock = Mockito.mockStatic(SQLiteDatabaseHelper.class)) { - dataQueue = new SQLiteDataQueue(DATABASE_NAME); + dataQueue = new SQLiteDataQueue(DATABASE_NAME, DATABASE_PATH); helperMock .when( () -> @@ -232,7 +233,7 @@ public void getTableSizeWithDatabaseOpenError() { .when( () -> SQLiteDatabaseHelper.openDatabase( - DATABASE_NAME, + DATABASE_PATH, SQLiteDatabaseHelper.DatabaseOpenMode.READ_ONLY)) .thenThrow(SQLiteException.class); int result = dataQueue.count(); diff --git a/code/gradle.properties b/code/gradle.properties index 3fa0ad0e9..2804dfe45 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -5,7 +5,7 @@ android.useAndroidX=true #Maven artifacts #Core extension -coreExtensionVersion=3.0.1 +coreExtensionVersion=3.0.2 coreExtensionName=core coreMavenRepoName=AdobeMobileCoreSdk coreMavenRepoDescription=Android Core Extension for Adobe Mobile Marketing