Skip to content

Commit ad3e83a

Browse files
authored
Merge pull request #1539 from OneSignal/fix/outcomeEventsController_init_npe
Fix NPE for `outcomeEventsController` during initialization
2 parents ac8c2a7 + fd9c957 commit ad3e83a

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,8 @@ static Activity getCurrentActivity() {
391391
private static String smsId = null;
392392
private static int subscribableStatus = Integer.MAX_VALUE;
393393

394-
private static LanguageContext languageContext = null;
394+
// changed from private to package-private for unit test access
395+
static LanguageContext languageContext = null;
395396

396397
static OSRemoteNotificationReceivedHandler remoteNotificationReceivedHandler;
397398
static OSNotificationWillShowInForegroundHandler notificationWillShowInForegroundHandler;
@@ -463,6 +464,22 @@ static OSSharedPreferences getSharedPreferences() {
463464
@Nullable private static OSOutcomeEventsController outcomeEventsController;
464465
@Nullable private static OSOutcomeEventsFactory outcomeEventsFactory;
465466
@Nullable private static OSNotificationDataController notificationDataController;
467+
private static final Object outcomeEventsControllerSyncLock = new Object() {};
468+
469+
static OSOutcomeEventsController getOutcomeEventsController() {
470+
if (outcomeEventsController == null) {
471+
synchronized(outcomeEventsControllerSyncLock) {
472+
if (outcomeEventsController == null) {
473+
if (outcomeEventsFactory == null) {
474+
OneSignalDbHelper dbHelper = getDBHelperInstance();
475+
outcomeEventsFactory = new OSOutcomeEventsFactory(logger, apiClient, dbHelper, preferences);
476+
}
477+
outcomeEventsController = new OSOutcomeEventsController(sessionManager, outcomeEventsFactory);
478+
}
479+
}
480+
}
481+
return outcomeEventsController;
482+
}
466483

467484
@SuppressWarnings("WeakerAccess")
468485
public static String sdkType = "native";
@@ -855,7 +872,7 @@ synchronized private static void init(Context context) {
855872
initDone = true;
856873
OneSignal.Log(LOG_LEVEL.VERBOSE, "OneSignal SDK initialization done.");
857874

858-
outcomeEventsController.sendSavedOutcomes();
875+
getOutcomeEventsController().sendSavedOutcomes();
859876

860877
// Clean up any pending tasks that were queued up before initialization
861878
taskRemoteController.startPendingTasks();
@@ -892,8 +909,7 @@ private static void setupContextListeners(boolean wasAppContextNull) {
892909
outcomeEventsFactory = new OSOutcomeEventsFactory(logger, apiClient, dbHelper, preferences);
893910

894911
sessionManager.initSessionFromCache();
895-
outcomeEventsController = new OSOutcomeEventsController(sessionManager, outcomeEventsFactory);
896-
outcomeEventsController.cleanCachedUniqueOutcomes();
912+
getOutcomeEventsController().cleanCachedUniqueOutcomes();
897913
}
898914
}
899915

@@ -971,7 +987,7 @@ private static void doSessionInit() {
971987
logger.debug("Starting new session with appEntryState: " + getAppEntryState());
972988

973989
OneSignalStateSynchronizer.setNewSession();
974-
outcomeEventsController.cleanOutcomes();
990+
getOutcomeEventsController().cleanOutcomes();
975991
sessionManager.restartSessionIfNeeded(getAppEntryState());
976992
getInAppMessageController().resetSessionLaunchTime();
977993
setLastSessionTime(time.getCurrentTimeMillis());
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.onesignal;
2+
3+
import com.onesignal.language.LanguageContext;
4+
5+
import org.robolectric.annotation.Implementation;
6+
import org.robolectric.annotation.Implements;
7+
8+
/**
9+
* This shadow is added for the test initWithContext_setupContextListenersNotCompleted_doesNotProduceNPE
10+
* Changes the behavior of one method without affecting other unit tests using ShadowOneSignal
11+
*/
12+
@Implements(OneSignal.class)
13+
public class ShadowOneSignalWithMockSetupContextListeners {
14+
15+
/**
16+
* Simulates setupContextListeners() in initWithContext() not completing.
17+
* However, languageContext initialization is needed for later, so that is the only code kept
18+
*/
19+
@Implementation
20+
public static void setupContextListeners(boolean wasAppContextNull) {
21+
22+
// Do work here that should only happen once or at the start of a new lifecycle
23+
if (wasAppContextNull) {
24+
OneSignal.languageContext = new LanguageContext(OneSignal.getSharedPreferences());
25+
}
26+
}
27+
}

OneSignalSDK/unittest/src/test/java/com/test/onesignal/OneSignalInitializationIntegrationTestsRunner.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.onesignal.ShadowCustomTabsClient;
88
import com.onesignal.ShadowCustomTabsSession;
99
import com.onesignal.ShadowOSUtils;
10+
import com.onesignal.ShadowOneSignalWithMockSetupContextListeners;
1011
import com.onesignal.ShadowOneSignalRestClient;
1112
import com.onesignal.ShadowPushRegistratorFCM;
1213
import com.onesignal.StaticResetHelper;
@@ -88,6 +89,19 @@ public void setRequiresUserPrivacyConsent_withTrue_CalledFirst_DoesNOTCreatePlay
8889
RestClientAsserts.assertRemoteParamsWasTheOnlyNetworkCall();
8990
}
9091

92+
// This test reproduces https://github.com/OneSignal/OneSignal-Android-SDK/issues/1514
93+
@Test
94+
@Config(shadows = { ShadowOneSignalWithMockSetupContextListeners.class })
95+
public void initWithContext_setupContextListenersNotCompleted_doesNotProduceNPE() throws Exception {
96+
OneSignal.setAppId(APP_ID);
97+
98+
// call initWithContext() but don't complete setupContextListeners() via the Shadow class
99+
// this prevents the initialization of outcomeEventsController in setupContextListeners()
100+
helper_OneSignal_initWithAppContext();
101+
threadAndTaskWait();
102+
// we implicitly test that no exception is thrown
103+
}
104+
91105
@Test
92106
public void setRequiresUserPrivacyConsent_withFalseAndRemoteTrue_DoesNOTCreatePlayer() throws Exception {
93107
ShadowOneSignalRestClient.setRemoteParamsRequirePrivacyConsent(true);

0 commit comments

Comments
 (0)