Skip to content

Commit f6cbc76

Browse files
Merge pull request #691 from yangyansong-adbe/v2/direct_boot
[Android 2.x] Do not initialize SDK in direct boot mode
2 parents 84ba8a8 + 08537bb commit f6cbc76

File tree

6 files changed

+154
-2
lines changed

6 files changed

+154
-2
lines changed

code/core/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ dependencies {
117117
testImplementation 'org.mockito:mockito-inline:4.5.1'
118118
//noinspection GradleDependency
119119
testImplementation 'commons-codec:commons-codec:1.15'
120-
testImplementation 'org.robolectric:robolectric:3.6.2'
120+
testImplementation 'org.robolectric:robolectric:4.7'
121121
testImplementation "org.mockito.kotlin:mockito-kotlin:3.2.0"
122122
//noinspection GradleDependency
123123
testImplementation 'org.json:json:20160810'

code/core/src/phone/java/com/adobe/marketing/mobile/MobileCore.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313

1414
import android.app.Activity;
1515
import android.app.Application;
16+
import android.os.Build.VERSION;
17+
import android.os.Build.VERSION_CODES;
1618
import androidx.annotation.NonNull;
1719
import androidx.annotation.Nullable;
1820
import androidx.annotation.VisibleForTesting;
21+
import androidx.core.os.UserManagerCompat;
1922
import com.adobe.marketing.mobile.internal.AppResourceStore;
2023
import com.adobe.marketing.mobile.internal.CoreConstants;
2124
import com.adobe.marketing.mobile.internal.configuration.ConfigurationExtension;
@@ -96,6 +99,23 @@ public static void setApplication(@NonNull final Application application) {
9699
CoreConstants.LOG_TAG, LOG_TAG, "setApplication failed - application is null");
97100
return;
98101
}
102+
// Direct boot mode is supported on Android N and above
103+
if (VERSION.SDK_INT >= VERSION_CODES.N) {
104+
if (UserManagerCompat.isUserUnlocked(application)) {
105+
Log.debug(
106+
CoreConstants.LOG_TAG,
107+
LOG_TAG,
108+
"setApplication - device is unlocked and not in direct boot mode,"
109+
+ " initializing the SDK.");
110+
} else {
111+
Log.error(
112+
CoreConstants.LOG_TAG,
113+
LOG_TAG,
114+
"setApplication failed - device is in direct boot mode, SDK will not be"
115+
+ " initialized.");
116+
return;
117+
}
118+
}
99119

100120
if (sdkInitializedWithContext.getAndSet(true)) {
101121
Log.debug(
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
Copyright 2024 Adobe. All rights reserved.
3+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License. You may obtain a copy
5+
of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
Unless required by applicable law or agreed to in writing, software distributed under
7+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8+
OF ANY KIND, either express or implied. See the License for the specific language
9+
governing permissions and limitations under the License.
10+
*/
11+
12+
package com.adobe.marketing.mobile
13+
14+
import android.app.Application
15+
import androidx.core.os.UserManagerCompat
16+
import com.adobe.marketing.mobile.internal.eventhub.EventHub
17+
import junit.framework.TestCase.assertTrue
18+
import org.junit.Before
19+
import org.junit.Test
20+
import org.junit.runner.RunWith
21+
import org.mockito.Mockito
22+
import org.mockito.kotlin.any
23+
import org.mockito.kotlin.never
24+
import org.mockito.kotlin.times
25+
import org.mockito.kotlin.verify
26+
import org.robolectric.RobolectricTestRunner
27+
import org.robolectric.RuntimeEnvironment
28+
import org.robolectric.annotation.Config
29+
import java.util.concurrent.atomic.AtomicBoolean
30+
import kotlin.test.assertFalse
31+
32+
@RunWith(RobolectricTestRunner::class)
33+
class MobileCoreRobolectricTests {
34+
35+
@Before
36+
fun setup() {
37+
MobileCore.sdkInitializedWithContext = AtomicBoolean(false)
38+
}
39+
40+
@Test
41+
@Config(sdk = [23])
42+
fun `test setApplication when the device doesn't support direct boot mode`() {
43+
// Android supports direct boot mode on API level 24 and above
44+
val app = RuntimeEnvironment.application as Application
45+
val mockedEventHub = Mockito.mock(EventHub::class.java)
46+
Mockito.mockStatic(UserManagerCompat::class.java).use { mockedStaticUserManagerCompat ->
47+
mockedStaticUserManagerCompat.`when`<Any> { UserManagerCompat.isUserUnlocked(Mockito.any()) }
48+
.thenReturn(false)
49+
MobileCore.setApplication(app)
50+
mockedStaticUserManagerCompat.verify({ UserManagerCompat.isUserUnlocked(Mockito.any()) }, never())
51+
}
52+
verify(mockedEventHub, never()).executeInEventHubExecutor(any())
53+
assertTrue(MobileCore.sdkInitializedWithContext.get())
54+
}
55+
56+
@Test
57+
@Config(sdk = [24])
58+
fun `test setApplication when the app is not configured to run in direct boot mode`() {
59+
val app = RuntimeEnvironment.application as Application
60+
val mockedEventHub = Mockito.mock(EventHub::class.java)
61+
EventHub.shared = mockedEventHub
62+
Mockito.mockStatic(UserManagerCompat::class.java).use { mockedStaticUserManagerCompat ->
63+
// when initializing SDK, the app is not in direct boot mode (device is unlocked)
64+
mockedStaticUserManagerCompat.`when`<Any> { UserManagerCompat.isUserUnlocked(Mockito.any()) }.thenReturn(true)
65+
MobileCore.setApplication(app)
66+
mockedStaticUserManagerCompat.verify({ UserManagerCompat.isUserUnlocked(Mockito.any()) }, times(1))
67+
}
68+
verify(mockedEventHub, times(1)).executeInEventHubExecutor(any())
69+
assertTrue(MobileCore.sdkInitializedWithContext.get())
70+
}
71+
72+
@Test
73+
@Config(sdk = [24])
74+
fun `test setApplication when the app is launched in direct boot mode`() {
75+
val app = RuntimeEnvironment.application as Application
76+
val mockedEventHub = Mockito.mock(EventHub::class.java)
77+
Mockito.mockStatic(UserManagerCompat::class.java).use { mockedStaticUserManagerCompat ->
78+
// when initializing SDK, the app is in direct boot mode (device is still locked)
79+
mockedStaticUserManagerCompat.`when`<Any> { UserManagerCompat.isUserUnlocked(Mockito.any()) }.thenReturn(false)
80+
MobileCore.setApplication(app)
81+
mockedStaticUserManagerCompat.verify({ UserManagerCompat.isUserUnlocked(Mockito.any()) }, times(1))
82+
}
83+
verify(mockedEventHub, never()).executeInEventHubExecutor(any())
84+
assertFalse(MobileCore.sdkInitializedWithContext.get())
85+
}
86+
}

code/testapp-kotlin/src/main/AndroidManifest.xml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:tools="http://schemas.android.com/tools"
44
package="com.adobe.marketing.mobile.app.kotlin">
5-
5+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
66
<application
77
android:name=".MyApp"
88
android:allowBackup="true"
@@ -26,6 +26,17 @@
2626
<category android:name="android.intent.category.LAUNCHER" />
2727
</intent-filter>
2828
</activity>
29+
30+
31+
<receiver android:name=".BootBroadcastReceiver"
32+
android:exported="false"
33+
android:directBootAware="true"
34+
tools:targetApi="n">
35+
<intent-filter>
36+
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
37+
<action android:name="android.intent.action.BOOT_COMPLETED" />
38+
</intent-filter>
39+
</receiver>
2940
</application>
3041

3142
</manifest>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
Copyright 2024 Adobe. All rights reserved.
3+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License. You may obtain a copy
5+
of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
Unless required by applicable law or agreed to in writing, software distributed under
7+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8+
OF ANY KIND, either express or implied. See the License for the specific language
9+
governing permissions and limitations under the License.
10+
*/
11+
package com.adobe.marketing.mobile.app.kotlin
12+
13+
import android.content.BroadcastReceiver
14+
import android.content.Context
15+
import android.content.Intent
16+
import android.util.Log
17+
import androidx.core.os.UserManagerCompat
18+
19+
class BootBroadcastReceiver : BroadcastReceiver() {
20+
override fun onReceive(context: Context, intent: Intent) {
21+
val action = intent.action
22+
Log.i(
23+
TAG, "Received action: $action, isUserUnlocked(): " + UserManagerCompat
24+
.isUserUnlocked(context)
25+
)
26+
}
27+
28+
companion object {
29+
private const val TAG = "BootBroadcastReceiver"
30+
}
31+
}

code/testapp-kotlin/src/main/java/com/adobe/marketing/mobile/app/kotlin/MyApp.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
package com.adobe.marketing.mobile.app.kotlin
1212

1313
import android.app.Application
14+
import android.util.Log
15+
import androidx.core.os.UserManagerCompat
1416
import com.adobe.marketing.mobile.MobileCore
1517
import com.adobe.marketing.mobile.Identity
1618
import com.adobe.marketing.mobile.Lifecycle
@@ -22,6 +24,8 @@ class MyApp : Application() {
2224

2325
override fun onCreate() {
2426
super.onCreate()
27+
28+
Log.i("MyApp", "[Android 2.x] Application.onCreate() - start to initialize Adobe SDK. UserManagerCompat.isUserUnlocked(): ${UserManagerCompat.isUserUnlocked(this)}")
2529
MobileCore.setApplication(this)
2630
MobileCore.setLogLevel(LoggingMode.VERBOSE)
2731
// MobileCore.configureWithAppID("YOUR_APP_ID")

0 commit comments

Comments
 (0)