Skip to content

Commit

Permalink
Merge pull request #682 from yangyansong-adbe/direct_boot
Browse files Browse the repository at this point in the history
Do not initialize SDK in direct boot mode
  • Loading branch information
yangyansong-adbe authored Jun 19, 2024
2 parents d044b40 + 5534eb7 commit 90638b4
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 9 deletions.
1 change: 1 addition & 0 deletions code/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,5 @@ dependencies {
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
//TODO: Consider moving this to the aep-library plugin later
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito-inline:2.28.3")
testImplementation("org.robolectric:robolectric:4.7")
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@

import android.app.Activity;
import android.app.Application;
import android.app.Application.ActivityLifecycleCallbacks;
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.DataMarshaller;
Expand Down Expand Up @@ -82,31 +86,49 @@ public static void setWrapperType(@NonNull final WrapperType wrapperType) {

/**
* Set the Android {@link Application}, which enables the SDK get the app {@code Context},
* register a {@link Application.ActivityLifecycleCallbacks} to monitor the lifecycle of the app
* and get the {@link android.app.Activity} on top of the screen.
* register a {@link ActivityLifecycleCallbacks} to monitor the lifecycle of the app and get the
* {@link Activity} on top of the screen.
*
* <p>NOTE: This method should be called right after the app starts, so it gives the SDK all the
* contexts it needed.
*
* @param application the Android {@link Application} instance. It should not be null.
*/
public static void setApplication(@NonNull final Application application) {

if (application == null) {
Log.error(
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(
CoreConstants.LOG_TAG,
LOG_TAG,
"Ignoring as setApplication was already called.");
"setApplication failed - ignoring as setApplication was already called.");
return;
}

if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O
|| android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O_MR1) {
if (VERSION.SDK_INT == VERSION_CODES.O || VERSION.SDK_INT == VERSION_CODES.O_MR1) {
// AMSDK-8502
// Workaround to prevent a crash happening on Android 8.0/8.1 related to
// TimeZoneNamesImpl
Expand Down
Original file line number Diff line number Diff line change
@@ -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`<Any> { 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`<Any> { 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`<Any> { 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())
}
}
18 changes: 15 additions & 3 deletions code/testapp/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<application
android:name="com.adobe.marketing.mobile.core.testapp.MyApp"
android:allowBackup="true"
Expand All @@ -10,9 +12,9 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AEPSDKCoreAndroid">
<profileable
android:shell="true"
tools:targetApi="q" />
<!-- <profileable-->
<!-- android:shell="true"-->
<!-- tools:targetApi="q" />-->

<activity
android:name="com.adobe.marketing.mobile.core.testapp.MainActivity"
Expand All @@ -24,6 +26,16 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<receiver android:name=".BootBroadcastReceiver"
android:exported="false"
android:directBootAware="true"
tools:targetApi="n">
<intent-filter>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
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.core.testapp;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.util.Log;
import androidx.core.os.BuildCompat;
import androidx.core.os.UserManagerCompat;

public class BootBroadcastReceiver extends BroadcastReceiver {

private static final String TAG = "BootBroadcastReceiver";

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.i(TAG, "Received action: " + action + ", isUserUnlocked(): " + UserManagerCompat
.isUserUnlocked(context));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@
package com.adobe.marketing.mobile.core.testapp

import android.app.Application
import com.adobe.marketing.mobile.MobileCore
import android.util.Log
import androidx.core.os.UserManagerCompat
import com.adobe.marketing.mobile.Identity
import com.adobe.marketing.mobile.Lifecycle
import com.adobe.marketing.mobile.LoggingMode
import com.adobe.marketing.mobile.MobileCore
import com.adobe.marketing.mobile.Signal
import com.adobe.marketing.mobile.core.testapp.extension.PerfExtension

class MyApp : Application() {

override fun onCreate() {
super.onCreate()
Log.i("MyApp", "Application.onCreate() - start to initialize Adobe SDK. UserManagerCompat.isUserUnlocked(): ${UserManagerCompat.isUserUnlocked(this)}")
MobileCore.setApplication(this)
MobileCore.setLogLevel(LoggingMode.VERBOSE)

Expand Down

0 comments on commit 90638b4

Please sign in to comment.