diff --git a/code/core/build.gradle b/code/core/build.gradle index a96fd5838..a706e5953 100644 --- a/code/core/build.gradle +++ b/code/core/build.gradle @@ -117,7 +117,7 @@ dependencies { testImplementation 'org.mockito:mockito-inline:4.5.1' //noinspection GradleDependency testImplementation 'commons-codec:commons-codec:1.15' - testImplementation 'org.robolectric:robolectric:3.6.2' + testImplementation 'org.robolectric:robolectric:4.7' testImplementation "org.mockito.kotlin:mockito-kotlin:3.2.0" //noinspection GradleDependency testImplementation 'org.json:json:20160810' 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 4cc8fa170..f5c0073d6 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 @@ -13,9 +13,12 @@ import android.app.Activity; import android.app.Application; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.core.os.UserManagerCompat; import com.adobe.marketing.mobile.internal.AppResourceStore; import com.adobe.marketing.mobile.internal.CoreConstants; import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension; @@ -96,6 +99,23 @@ public static void setApplication(@NonNull final Application application) { CoreConstants.LOG_TAG, LOG_TAG, "setApplication failed - application is null"); return; } + // Direct boot mode is supported on Android N and above + if (VERSION.SDK_INT >= VERSION_CODES.N) { + if (UserManagerCompat.isUserUnlocked(application)) { + Log.debug( + CoreConstants.LOG_TAG, + LOG_TAG, + "setApplication - device is unlocked and not in direct boot mode," + + " initializing the SDK."); + } else { + Log.error( + CoreConstants.LOG_TAG, + LOG_TAG, + "setApplication failed - device is in direct boot mode, SDK will not be" + + " initialized."); + return; + } + } if (sdkInitializedWithContext.getAndSet(true)) { Log.debug( diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreRobolectricTests.kt b/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreRobolectricTests.kt new file mode 100644 index 000000000..6149b4d6f --- /dev/null +++ b/code/core/src/test/java/com/adobe/marketing/mobile/MobileCoreRobolectricTests.kt @@ -0,0 +1,86 @@ +/* + Copyright 2024 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile + +import android.app.Application +import androidx.core.os.UserManagerCompat +import com.adobe.marketing.mobile.internal.eventhub.EventHub +import junit.framework.TestCase.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito +import org.mockito.kotlin.any +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.test.assertFalse + +@RunWith(RobolectricTestRunner::class) +class MobileCoreRobolectricTests { + + @Before + fun setup() { + MobileCore.sdkInitializedWithContext = AtomicBoolean(false) + } + + @Test + @Config(sdk = [23]) + fun `test setApplication when the device doesn't support direct boot mode`() { + // Android supports direct boot mode on API level 24 and above + val app = RuntimeEnvironment.application as Application + val mockedEventHub = Mockito.mock(EventHub::class.java) + Mockito.mockStatic(UserManagerCompat::class.java).use { mockedStaticUserManagerCompat -> + mockedStaticUserManagerCompat.`when` { UserManagerCompat.isUserUnlocked(Mockito.any()) } + .thenReturn(false) + MobileCore.setApplication(app) + mockedStaticUserManagerCompat.verify({ UserManagerCompat.isUserUnlocked(Mockito.any()) }, never()) + } + verify(mockedEventHub, never()).executeInEventHubExecutor(any()) + assertTrue(MobileCore.sdkInitializedWithContext.get()) + } + + @Test + @Config(sdk = [24]) + fun `test setApplication when the app is not configured to run in direct boot mode`() { + val app = RuntimeEnvironment.application as Application + val mockedEventHub = Mockito.mock(EventHub::class.java) + EventHub.shared = mockedEventHub + Mockito.mockStatic(UserManagerCompat::class.java).use { mockedStaticUserManagerCompat -> + // when initializing SDK, the app is not in direct boot mode (device is unlocked) + mockedStaticUserManagerCompat.`when` { UserManagerCompat.isUserUnlocked(Mockito.any()) }.thenReturn(true) + MobileCore.setApplication(app) + mockedStaticUserManagerCompat.verify({ UserManagerCompat.isUserUnlocked(Mockito.any()) }, times(1)) + } + verify(mockedEventHub, times(1)).executeInEventHubExecutor(any()) + assertTrue(MobileCore.sdkInitializedWithContext.get()) + } + + @Test + @Config(sdk = [24]) + fun `test setApplication when the app is launched in direct boot mode`() { + val app = RuntimeEnvironment.application as Application + val mockedEventHub = Mockito.mock(EventHub::class.java) + Mockito.mockStatic(UserManagerCompat::class.java).use { mockedStaticUserManagerCompat -> + // when initializing SDK, the app is in direct boot mode (device is still locked) + mockedStaticUserManagerCompat.`when` { UserManagerCompat.isUserUnlocked(Mockito.any()) }.thenReturn(false) + MobileCore.setApplication(app) + mockedStaticUserManagerCompat.verify({ UserManagerCompat.isUserUnlocked(Mockito.any()) }, times(1)) + } + verify(mockedEventHub, never()).executeInEventHubExecutor(any()) + assertFalse(MobileCore.sdkInitializedWithContext.get()) + } +} diff --git a/code/testapp-kotlin/src/main/AndroidManifest.xml b/code/testapp-kotlin/src/main/AndroidManifest.xml index 97f437990..39966970c 100644 --- a/code/testapp-kotlin/src/main/AndroidManifest.xml +++ b/code/testapp-kotlin/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ - + + + + + + + + + \ No newline at end of file diff --git a/code/testapp-kotlin/src/main/java/com/adobe/marketing/mobile/app/kotlin/BootBroadcastReceiver.kt b/code/testapp-kotlin/src/main/java/com/adobe/marketing/mobile/app/kotlin/BootBroadcastReceiver.kt new file mode 100644 index 000000000..8abc9d348 --- /dev/null +++ b/code/testapp-kotlin/src/main/java/com/adobe/marketing/mobile/app/kotlin/BootBroadcastReceiver.kt @@ -0,0 +1,31 @@ +/* + Copyright 2024 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.app.kotlin + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import androidx.core.os.UserManagerCompat + +class BootBroadcastReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.action + Log.i( + TAG, "Received action: $action, isUserUnlocked(): " + UserManagerCompat + .isUserUnlocked(context) + ) + } + + companion object { + private const val TAG = "BootBroadcastReceiver" + } +} \ No newline at end of file diff --git a/code/testapp-kotlin/src/main/java/com/adobe/marketing/mobile/app/kotlin/MyApp.kt b/code/testapp-kotlin/src/main/java/com/adobe/marketing/mobile/app/kotlin/MyApp.kt index 5fb969de0..94e83672b 100644 --- a/code/testapp-kotlin/src/main/java/com/adobe/marketing/mobile/app/kotlin/MyApp.kt +++ b/code/testapp-kotlin/src/main/java/com/adobe/marketing/mobile/app/kotlin/MyApp.kt @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile.app.kotlin import android.app.Application +import android.util.Log +import androidx.core.os.UserManagerCompat import com.adobe.marketing.mobile.MobileCore import com.adobe.marketing.mobile.Identity import com.adobe.marketing.mobile.Lifecycle @@ -22,6 +24,8 @@ class MyApp : Application() { override fun onCreate() { super.onCreate() + + Log.i("MyApp", "[Android 2.x] Application.onCreate() - start to initialize Adobe SDK. UserManagerCompat.isUserUnlocked(): ${UserManagerCompat.isUserUnlocked(this)}") MobileCore.setApplication(this) MobileCore.setLogLevel(LoggingMode.VERBOSE) // MobileCore.configureWithAppID("YOUR_APP_ID")