com.test.onesignal.MainOneSignalClassRunner.java Source code

Java tutorial

Introduction

Here is the source code for com.test.onesignal.MainOneSignalClassRunner.java

Source

/**
 * Modified MIT License
 *
 * Copyright 2015 OneSignal
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * 1. The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * 2. All copies of substantial portions of the Software may only be used in connection
 * with services provided by OneSignal.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.test.onesignal;

import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.net.ConnectivityManager;
import android.os.Bundle;

import com.onesignal.BuildConfig;
import com.onesignal.NotificationBundleProcessor;
import com.onesignal.OneSignal;
import com.onesignal.ShadowLocationGMS;
import com.onesignal.ShadowOSUtils;
import com.onesignal.ShadowOneSignal;
import com.onesignal.ShadowOneSignalRestClient;
import com.onesignal.OneSignalPackagePrivateHelper;
import com.onesignal.ShadowPushRegistratorADM;
import com.onesignal.ShadowPushRegistratorGPS;
import com.onesignal.StaticResetHelper;
import com.onesignal.SyncService;
import com.onesignal.example.BlankActivity;

import junit.framework.Assert;

import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowConnectivityManager;
import org.robolectric.shadows.ShadowLog;
import org.robolectric.shadows.ShadowSystemClock;
import org.robolectric.util.ActivityController;

import java.lang.reflect.Field;

@Config(packageName = "com.onesignal.example", constants = BuildConfig.class, shadows = {
        ShadowOneSignalRestClient.class, ShadowPushRegistratorGPS.class, ShadowPushRegistratorADM.class,
        ShadowOSUtils.class }, sdk = 21)

@RunWith(CustomRobolectricTestRunner.class)
public class MainOneSignalClassRunner {

    private static final String ONESIGNAL_APP_ID = "b2f7f966-d8cc-11e4-bed1-df8f05be55ba";
    private static Field OneSignal_CurrentSubscription;
    private static int testSleepTime;
    private Activity blankActivity;
    private static String callBackUseId, getCallBackRegId;
    private static String notificationOpenedMessage;
    private static JSONObject lastGetTags;
    private ActivityController<BlankActivity> blankActivityController;

    private static void GetIdsAvailable() {
        OneSignal.idsAvailable(new OneSignal.IdsAvailableHandler() {
            @Override
            public void idsAvailable(String userId, String registrationId) {
                callBackUseId = userId;
                getCallBackRegId = registrationId;
            }
        });
    }

    private static void GetTags() {
        OneSignal.getTags(new OneSignal.GetTagsHandler() {
            @Override
            public void tagsAvailable(JSONObject tags) {
                lastGetTags = tags;
            }
        });
    }

    @BeforeClass // Runs only once, before any tests
    public static void setUpClass() throws Exception {
        ShadowLog.stream = System.out;

        testSleepTime = System.getenv("TRAVIS") != null ? 300 : 20;

        OneSignal_CurrentSubscription = OneSignal.class.getDeclaredField("subscribableStatus");
        OneSignal_CurrentSubscription.setAccessible(true);

        OneSignal.setLogLevel(OneSignal.LOG_LEVEL.VERBOSE, OneSignal.LOG_LEVEL.NONE);
        StaticResetHelper.saveStaticValues();
    }

    @Before // Before each test
    public void beforeEachTest() throws Exception {
        callBackUseId = getCallBackRegId = null;
        StaticResetHelper.restSetStaticFields();
        blankActivityController = Robolectric.buildActivity(BlankActivity.class).create();
        blankActivity = blankActivityController.get();

        ShadowOneSignalRestClient.nextSuccessResponse = null;
        ShadowOneSignalRestClient.failNext = false;
        ShadowOneSignalRestClient.failAll = false;
        ShadowOneSignalRestClient.interruptibleDelayNext = false;
        ShadowOneSignalRestClient.networkCallCount = 0;
        ShadowOneSignalRestClient.testThread = Thread.currentThread();

        ShadowPushRegistratorGPS.fail = false;
        notificationOpenedMessage = null;
        lastGetTags = null;
    }

    @Test
    public void testInitFromApplicationContext() throws Exception {
        // Application.onCreate
        OneSignal.init(RuntimeEnvironment.application, "123456789", ONESIGNAL_APP_ID);
        threadAndTaskWait();
        Assert.assertNotNull(ShadowOneSignalRestClient.lastPost);

        ShadowOneSignalRestClient.lastPost = null;
        StaticResetHelper.restSetStaticFields();

        // Restart app, should not send onSession automatically
        OneSignal.init(RuntimeEnvironment.application, "123456789", ONESIGNAL_APP_ID);
        threadAndTaskWait();
        Assert.assertNull(ShadowOneSignalRestClient.lastPost);

        // Starting of first Activity should trigger onSession
        blankActivityController.resume();
        threadAndTaskWait();
        Assert.assertNotNull(ShadowOneSignalRestClient.lastPost);
    }

    @Test
    public void testOpenFromNotificationWhenAppIsDead() throws Exception {
        OneSignal.handleNotificationOpened(blankActivity,
                new JSONArray("[{ \"alert\": \"Robo test message\", \"custom\": { \"i\": \"UUID\" } }]"), false);

        OneSignal.init(blankActivity, "123456789", ONESIGNAL_APP_ID, new OneSignal.NotificationOpenedHandler() {
            @Override
            public void notificationOpened(String message, JSONObject additionalData, boolean isActive) {
                notificationOpenedMessage = message;
            }
        });

        threadAndTaskWait();

        Assert.assertEquals("Robo test message", notificationOpenedMessage);
    }

    @Test
    public void shouldCorrectlyRemoveOpenedHandlerAndFireMissedOnesWhenAddedBack() throws Exception {
        OneSignal.NotificationOpenedHandler notifHandler = new OneSignal.NotificationOpenedHandler() {
            @Override
            public void notificationOpened(String message, JSONObject additionalData, boolean isActive) {
                notificationOpenedMessage = message;
            }
        };
        OneSignal.init(blankActivity, "123456789", ONESIGNAL_APP_ID, notifHandler);
        threadAndTaskWait();

        OneSignal.removeNotificationOpenedHandler();
        OneSignal.handleNotificationOpened(blankActivity,
                new JSONArray("[{ \"alert\": \"Robo test message\", \"custom\": { \"i\": \"UUID\" } }]"), false);
        Assert.assertNull(notificationOpenedMessage);

        OneSignal.init(blankActivity, "123456789", ONESIGNAL_APP_ID, notifHandler);
        Assert.assertEquals("Robo test message", notificationOpenedMessage);
    }

    @Test
    public void shouldNotFireNotificationOpenAgainAfterAppRestart() throws Exception {
        OneSignal.init(blankActivity, "123456789", ONESIGNAL_APP_ID, new OneSignal.NotificationOpenedHandler() {
            @Override
            public void notificationOpened(String message, JSONObject additionalData, boolean isActive) {
                notificationOpenedMessage = message;
            }
        });

        threadAndTaskWait();

        Bundle bundle = GenerateNotificationRunner.getBaseNotifBundle();
        NotificationBundleProcessor.Process(blankActivity, bundle);

        threadAndTaskWait();

        notificationOpenedMessage = null;

        // Restart app - Should omit notification_types
        StaticResetHelper.restSetStaticFields();
        OneSignal.init(blankActivity, "123456789", ONESIGNAL_APP_ID, new OneSignal.NotificationOpenedHandler() {
            @Override
            public void notificationOpened(String message, JSONObject additionalData, boolean isActive) {
                notificationOpenedMessage = message;
            }
        });

        threadAndTaskWait();

        Assert.assertEquals(null, notificationOpenedMessage);
    }

    @Test
    public void testOpenFromNotificationWhenAppIsInBackground() throws Exception {
        OneSignal.init(blankActivity, "123456789", ONESIGNAL_APP_ID, new OneSignal.NotificationOpenedHandler() {
            @Override
            public void notificationOpened(String message, JSONObject additionalData, boolean isActive) {
                notificationOpenedMessage = message;
            }
        });
        Assert.assertNull(notificationOpenedMessage);

        OneSignal.handleNotificationOpened(blankActivity,
                new JSONArray("[{ \"alert\": \"Test Msg\", \"custom\": { \"i\": \"UUID\" } }]"), false);
        Assert.assertEquals("Test Msg", notificationOpenedMessage);
        threadWait();
    }

    @Test
    public void testOpeningLauncherActivity() throws Exception {
        AddLauncherIntentFilter();

        // From app launching normally
        Assert.assertNotNull(Shadows.shadowOf(blankActivity).getNextStartedActivity());

        OneSignal.handleNotificationOpened(blankActivity,
                new JSONArray("[{ \"alert\": \"Test Msg\", \"custom\": { \"i\": \"UUID\" } }]"), false);

        Assert.assertNotNull(Shadows.shadowOf(blankActivity).getNextStartedActivity());
        Assert.assertNull(Shadows.shadowOf(blankActivity).getNextStartedActivity());
    }

    @Test
    public void testOpeningLaunchUrl() throws Exception {
        // Clear app launching normally
        Shadows.shadowOf(blankActivity).getNextStartedActivity();

        // No OneSignal init here to test case where it is located in an Activity.

        OneSignal.handleNotificationOpened(blankActivity, new JSONArray(
                "[{ \"alert\": \"Test Msg\", \"custom\": { \"i\": \"UUID\", \"u\": \"http://google.com\" } }]"),
                false);

        Intent intent = Shadows.shadowOf(blankActivity).getNextStartedActivity();
        Assert.assertEquals("android.intent.action.VIEW", intent.getAction());
        Assert.assertEquals("http://google.com", intent.getData().toString());
        Assert.assertNull(Shadows.shadowOf(blankActivity).getNextStartedActivity());
    }

    @Test
    public void testDisableOpeningLauncherActivityOnNotifiOpen() throws Exception {
        ShadowApplication.getInstance().getAppManifest().getApplicationMetaData()
                .put("com.onesignal.NotificationOpened.DEFAULT", "DISABLE");
        RuntimeEnvironment.getRobolectricPackageManager().addManifest(
                ShadowApplication.getInstance().getAppManifest(),
                ShadowApplication.getInstance().getResourceLoader());
        AddLauncherIntentFilter();

        // From app launching normally
        Assert.assertNotNull(Shadows.shadowOf(blankActivity).getNextStartedActivity());
        OneSignal.init(blankActivity, "123456789", ONESIGNAL_APP_ID, new OneSignal.NotificationOpenedHandler() {
            @Override
            public void notificationOpened(String message, JSONObject additionalData, boolean isActive) {
                notificationOpenedMessage = message;
            }
        });
        Assert.assertNull(notificationOpenedMessage);

        OneSignal.handleNotificationOpened(blankActivity,
                new JSONArray("[{ \"alert\": \"Test Msg\", \"custom\": { \"i\": \"UUID\" } }]"), false);

        Assert.assertNull(Shadows.shadowOf(blankActivity).getNextStartedActivity());
        Assert.assertEquals("Test Msg", notificationOpenedMessage);
    }

    @Test
    public void testNotificationReceivedWhenAppInFocus() throws Exception {
        OneSignal.init(blankActivity, "123456789", ONESIGNAL_APP_ID, new OneSignal.NotificationOpenedHandler() {
            @Override
            public void notificationOpened(String message, JSONObject additionalData, boolean isActive) {
                notificationOpenedMessage = message;
            }
        });
        threadAndTaskWait();
        Assert.assertNull(notificationOpenedMessage);

        NotificationBundleProcessor.Process(blankActivity, GenerateNotificationRunner.getBaseNotifBundle());
        threadAndTaskWait();
        Assert.assertEquals("Robo test message", notificationOpenedMessage);
    }

    @Test
    public void testInvalidGoogleProjectNumber() throws Exception {
        GetIdsAvailable();
        OneSignalInitWithBadProjectNum();

        threadAndTaskWait();
        Robolectric.getForegroundThreadScheduler().runOneTask();
        Assert.assertEquals(-6, ShadowOneSignalRestClient.lastPost.getInt("notification_types"));

        // Test that idsAvailable still fires
        Assert.assertEquals(ShadowOneSignalRestClient.testUserId, callBackUseId);
    }

    @Test
    public void testUnsubcribeShouldMakeRegIdNullToIdsAvailable() throws Exception {
        GetIdsAvailable();
        OneSignalInit();
        threadAndTaskWait();
        Assert.assertEquals(ShadowPushRegistratorGPS.regId,
                ShadowOneSignalRestClient.lastPost.getString("identifier"));

        Robolectric.getForegroundThreadScheduler().runOneTask();
        Assert.assertEquals(ShadowPushRegistratorGPS.regId, getCallBackRegId);

        OneSignal.setSubscription(false);
        GetIdsAvailable();
        threadAndTaskWait();
        Assert.assertNull(getCallBackRegId);
    }

    @Test
    public void testSetSubscriptionShouldNotOverrideSubscribeError() throws Exception {
        OneSignalInitWithBadProjectNum();
        threadAndTaskWait();

        // Should not try to update server
        ShadowOneSignalRestClient.lastPost = null;
        OneSignal.setSubscription(true);
        Assert.assertNull(ShadowOneSignalRestClient.lastPost);

        // Restart app - Should omit notification_types
        StaticResetHelper.restSetStaticFields();
        OneSignalInitWithBadProjectNum();
        threadAndTaskWait();
        Assert.assertFalse(ShadowOneSignalRestClient.lastPost.has("notification_types"));
    }

    @Test
    public void shouldNotResetSubscriptionOnSession() throws Exception {
        OneSignalInit();
        OneSignal.setSubscription(false);
        threadAndTaskWait();
        Assert.assertEquals(-2, ShadowOneSignalRestClient.lastPost.getInt("notification_types"));

        StaticResetHelper.restSetStaticFields();

        OneSignalInit();
        threadAndTaskWait();
        Assert.assertFalse(ShadowOneSignalRestClient.lastPost.has("notification_types"));
    }

    @Test
    public void shouldSetSubscriptionCorrectlyEvenAfterFirstOneSignalRestInitFail() throws Exception {
        // Failed to register with OneSignal but SetSubscription was called with false
        ShadowOneSignalRestClient.failAll = true;
        OneSignalInit();
        OneSignal.setSubscription(false);
        threadAndTaskWait();
        ShadowOneSignalRestClient.failAll = false;

        // Restart app - Should send unsubscribe with create player call.
        StaticResetHelper.restSetStaticFields();
        OneSignalInit();
        threadAndTaskWait();
        Assert.assertEquals(-2, ShadowOneSignalRestClient.lastPost.getInt("notification_types"));

        // Restart app again - Value synced last time so don't send again.
        StaticResetHelper.restSetStaticFields();
        OneSignalInit();
        threadAndTaskWait();
        Assert.assertFalse(ShadowOneSignalRestClient.lastPost.has("notification_types"));
    }

    @Test
    public void shouldUpdateNotificationTypesCorrectlyEvenWhenSetSubscriptionIsCalledInAnErrorState()
            throws Exception {
        OneSignalInitWithBadProjectNum();
        threadAndTaskWait();
        OneSignal.setSubscription(true);

        // Restart app - Should send subscribe with on_session call.
        StaticResetHelper.restSetStaticFields();
        OneSignalInit();
        threadAndTaskWait();
        Assert.assertEquals(1, ShadowOneSignalRestClient.lastPost.getInt("notification_types"));
    }

    @Test
    public void shouldAllowMultipleSetSubscription() throws Exception {
        OneSignalInit();
        threadAndTaskWait();

        OneSignal.setSubscription(false);
        threadAndTaskWait();

        Assert.assertEquals(-2, ShadowOneSignalRestClient.lastPost.getInt("notification_types"));

        // Should not resend same value
        ShadowOneSignalRestClient.lastPost = null;
        OneSignal.setSubscription(false);
        Assert.assertNull(ShadowOneSignalRestClient.lastPost);

        OneSignal.setSubscription(true);
        threadAndTaskWait();
        Assert.assertEquals(1, ShadowOneSignalRestClient.lastPost.getInt("notification_types"));

        // Should not resend same value
        ShadowOneSignalRestClient.lastPost = null;
        OneSignal.setSubscription(true);
        threadAndTaskWait();
        Assert.assertNull(ShadowOneSignalRestClient.lastPost);
    }

    private static boolean userIdWasNull = false;

    @Test
    public void shouldNotFireIdsAvailableWithoutUserId() throws Exception {
        ShadowOneSignalRestClient.failNext = true;
        ShadowPushRegistratorGPS.fail = true;

        OneSignal.idsAvailable(new OneSignal.IdsAvailableHandler() {
            @Override
            public void idsAvailable(String userId, String registrationId) {
                if (userId == null)
                    userIdWasNull = true;
            }
        });

        OneSignalInit();
        Assert.assertFalse(userIdWasNull);
        threadAndTaskWait();
    }

    @Test
    public void testGCMTimeOutThenSuccessesLater() throws Exception {
        // Init with a bad connection to Google.
        ShadowPushRegistratorGPS.fail = true;
        OneSignalInit();
        threadAndTaskWait();
        Assert.assertFalse(ShadowOneSignalRestClient.lastPost.has("identifier"));

        // Registers for GCM after a retry
        ShadowPushRegistratorGPS.fail = false;
        ShadowPushRegistratorGPS.manualFireRegisterForPush();
        threadAndTaskWait();
        Assert.assertEquals(ShadowPushRegistratorGPS.regId,
                ShadowOneSignalRestClient.lastPost.getString("identifier"));

        // Cold restart app, should not send the same identifier again.
        ShadowOneSignalRestClient.lastPost = null;
        StaticResetHelper.restSetStaticFields();
        OneSignalInit();
        threadAndTaskWait();
        Assert.assertFalse(ShadowOneSignalRestClient.lastPost.has("identifier"));
    }

    @Test
    public void testChangeAppId() throws Exception {
        OneSignalInit();
        threadAndTaskWait();

        int normalCreateFieldCount = ShadowOneSignalRestClient.lastPost.length();
        StaticResetHelper.restSetStaticFields();
        OneSignal.init(blankActivity, "123456789", "99f7f966-d8cc-11e4-bed1-df8f05be55b2");
        threadAndTaskWait();

        Assert.assertEquals(normalCreateFieldCount, ShadowOneSignalRestClient.lastPost.length());
    }

    @Test
    public void testUserDeletedFromServer() throws Exception {
        // First cold boot normal
        OneSignalInit();
        threadAndTaskWait();

        int normalCreateFieldCount = ShadowOneSignalRestClient.lastPost.length();
        ShadowOneSignalRestClient.lastPost = null;

        // Developer deletes user, cold boots apps should resend all fields
        StaticResetHelper.restSetStaticFields();
        ShadowOneSignalRestClient.failNext = true;
        ShadowOneSignalRestClient.failResponse = "{\"errors\":[\"Device type  is not a valid device_type. Valid options are: 0 = iOS, 1 = Android, 2 = Amazon, 3 = WindowsPhone(MPNS), 4 = ChromeApp, 5 = ChromeWebsite, 6 = WindowsPhone(WNS), 7 = Safari(APNS), 8 = Firefox\"]}";
        OneSignalInit();
        threadAndTaskWait();

        Assert.assertEquals(normalCreateFieldCount, ShadowOneSignalRestClient.lastPost.length());

        // Developer deletes users again from dashboard while app is running.
        ShadowOneSignalRestClient.lastPost = null;
        ShadowOneSignalRestClient.failNext = true;
        ShadowOneSignalRestClient.failResponse = "{\"errors\":[\"No user with this id found\"]}";
        OneSignal.sendTag("key1", "value1");
        threadAndTaskWait();

        Assert.assertEquals(normalCreateFieldCount, ShadowOneSignalRestClient.lastPost.length() - 1);
    }

    @Test
    public void testOfflineCrashes() throws Exception {
        ConnectivityManager connectivityManager = (ConnectivityManager) RuntimeEnvironment.application
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        ShadowConnectivityManager shadowConnectivityManager = Shadows.shadowOf(connectivityManager);
        shadowConnectivityManager.setActiveNetworkInfo(null);

        OneSignalInit();
        threadAndTaskWait();

        OneSignal.sendTag("key", "value");
        threadAndTaskWait();

        OneSignal.setSubscription(false);
        threadAndTaskWait();
    }

    // ####### SendTags Tests ########

    @Test
    public void shouldSendTags() throws Exception {
        OneSignalInit();
        OneSignal.sendTags(new JSONObject("{\"test1\": \"value1\", \"test2\": \"value2\"}"));
        threadAndTaskWait();
        Assert.assertEquals(1, ShadowOneSignalRestClient.networkCallCount);
        Assert.assertEquals(ONESIGNAL_APP_ID, ShadowOneSignalRestClient.lastPost.getString("app_id"));
        Assert.assertEquals("value1", ShadowOneSignalRestClient.lastPost.getJSONObject("tags").getString("test1"));
        Assert.assertEquals("value2", ShadowOneSignalRestClient.lastPost.getJSONObject("tags").getString("test2"));

        // Should omit sending repeated tags
        ShadowOneSignalRestClient.lastPost = null;
        OneSignal.sendTags(new JSONObject("{\"test1\": \"value1\", \"test2\": \"value2\"}"));
        threadAndTaskWait();
        Assert.assertEquals(1, ShadowOneSignalRestClient.networkCallCount);
        Assert.assertNull(ShadowOneSignalRestClient.lastPost);

        // Should only send changed and new tags
        OneSignal.sendTags(
                new JSONObject("{\"test1\": \"value1.5\", \"test2\": \"value2\", \"test3\": \"value3\"}"));
        threadAndTaskWait();
        Assert.assertEquals(2, ShadowOneSignalRestClient.networkCallCount);
        JSONObject sentTags = ShadowOneSignalRestClient.lastPost.getJSONObject("tags");
        Assert.assertEquals("value1.5", sentTags.getString("test1"));
        Assert.assertFalse(sentTags.has(("test2")));
        Assert.assertEquals("value3", sentTags.getString("test3"));
    }

    @Test
    public void shouldSendTagsWithRequestBatching() throws Exception {
        OneSignalInit();
        threadAndTaskWait();
        Assert.assertEquals(1, ShadowOneSignalRestClient.networkCallCount);
        OneSignal.sendTags(new JSONObject("{\"test1\": \"value1\"}"));
        OneSignal.sendTags(new JSONObject("{\"test2\": \"value2\"}"));

        GetTags();
        threadAndTaskWait();
        threadAndTaskWait();

        Assert.assertEquals("value1", lastGetTags.getString("test1"));
        Assert.assertEquals("value2", lastGetTags.getString("test2"));
        Assert.assertEquals(2, ShadowOneSignalRestClient.networkCallCount);
    }

    @Test
    public void testOldIntValues() throws Exception {
        final SharedPreferences prefs = blankActivity.getSharedPreferences(OneSignal.class.getSimpleName(),
                Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putString("ONESIGNAL_USERSTATE_SYNCVALYES_CURRENT_STATE", "{\"tags\": {\"int\": 123}}");
        editor.putString("ONESIGNAL_USERSTATE_SYNCVALYES_TOSYNC_STATE", "{\"tags\": {\"int\": 123}}");
        editor.commit();

        OneSignalInit();
        threadAndTaskWait();

        OneSignal.deleteTag("int");
        threadAndTaskWait();

        GetTags();

        final SharedPreferences prefs2 = blankActivity.getSharedPreferences(OneSignal.class.getSimpleName(),
                Context.MODE_PRIVATE);
        Assert.assertNull(lastGetTags);
    }

    @Test
    public void testSendTagNonStringValues() throws Exception {
        OneSignalInit();
        OneSignal.sendTags("{\"int\": 122, \"bool\": true, \"null\": null, \"array\": [123], \"object\": {}}");
        GetTags();

        Assert.assertEquals(String.class, lastGetTags.get("int").getClass());
        Assert.assertEquals("122", lastGetTags.get("int"));
        Assert.assertEquals(String.class, lastGetTags.get("bool").getClass());
        Assert.assertEquals("true", lastGetTags.get("bool"));

        // null should be the same as a blank string.
        Assert.assertFalse(lastGetTags.has("null"));

        Assert.assertFalse(lastGetTags.has("array"));
        Assert.assertFalse(lastGetTags.has("object"));
    }

    @Test
    public void shouldSaveToSyncIfKilledBeforeDelayedCompare() throws Exception {
        OneSignalInit();
        OneSignal.sendTag("key", "value");
        threadWait();

        OneSignalPackagePrivateHelper.SyncService_onTaskRemoved();
        OneSignalPackagePrivateHelper.resetRunnables();
        threadAndTaskWait();
        Assert.assertEquals(0, ShadowOneSignalRestClient.networkCallCount);

        StaticResetHelper.restSetStaticFields();

        OneSignalInit();
        threadAndTaskWait();
        Assert.assertEquals("value", ShadowOneSignalRestClient.lastPost.getJSONObject("tags").getString("key"));
    }

    @Test
    public void shouldSyncPendingChangesFromSyncService() throws Exception {
        OneSignalInit();
        threadAndTaskWait();

        OneSignal.sendTag("key", "value");
        OneSignalPackagePrivateHelper.SyncService_onTaskRemoved();
        OneSignalPackagePrivateHelper.resetRunnables();
        threadAndTaskWait();
        Assert.assertEquals(1, ShadowOneSignalRestClient.networkCallCount);

        StaticResetHelper.restSetStaticFields();

        Service service = new SyncService();
        service.onCreate();
        threadAndTaskWait();
        Assert.assertEquals("value", ShadowOneSignalRestClient.lastPost.getJSONObject("tags").getString("key"));
    }

    @Test
    public void shouldNotCrashIfOnTaskRemovedIsCalledBeforeInitIsDone() {
        OneSignalPackagePrivateHelper.SyncService_onTaskRemoved();
    }

    @Test
    public void testMethodCallsBeforeInit() throws Exception {
        GetTags();
        OneSignal.sendTag("key", "value");
        OneSignal.sendTags("{\"key\": \"value\"}");
        OneSignal.deleteTag("key");
        OneSignal.deleteTags("[\"key1\", \"key2\"]");
        OneSignal.setSubscription(false);
        OneSignal.enableVibrate(false);
        OneSignal.enableSound(false);
        OneSignal.promptLocation();
        OneSignal.postNotification("{}", new OneSignal.PostNotificationResponseHandler() {
            @Override
            public void onSuccess(JSONObject response) {
            }

            @Override
            public void onFailure(JSONObject response) {
            }
        });
        OneSignalInit();
        threadAndTaskWait();
    }

    // ####### DeleteTags Tests ######
    @Test
    public void testDeleteTags() throws Exception {
        OneSignalInit();
        OneSignal.sendTags("{\"str\": \"str1\", \"int\": 122, \"bool\": true}");
        OneSignal.deleteTag("int");
        GetTags();

        Assert.assertFalse(lastGetTags.has("int"));
        lastGetTags = null;

        // Makes sure they get sent
        OneSignal.sendTags("{\"str\": \"str1\", \"int\": 122, \"bool\": true}");
        threadAndTaskWait();
        Assert.assertEquals("str1", ShadowOneSignalRestClient.lastPost.getJSONObject("tags").get("str"));

        OneSignal.deleteTag("int");

        GetTags();

        Assert.assertFalse(lastGetTags.has("int"));

        // After the success response it should not store a "" string when saving to storage.
        threadAndTaskWait();

        final SharedPreferences prefs = blankActivity.getSharedPreferences(OneSignal.class.getSimpleName(),
                Context.MODE_PRIVATE);
        String syncValues = prefs.getString("ONESIGNAL_USERSTATE_SYNCVALYES_CURRENT_STATE", null);
        Assert.assertFalse(new JSONObject(syncValues).has("tags"));
    }

    @Test
    public void testDeleteTagsAfterSync() throws Exception {
        OneSignalInit();
        OneSignal.sendTags("{\"foo\": \"bar\", \"fuz\": \"baz\"}");
        threadAndTaskWait();
        Assert.assertEquals("bar", ShadowOneSignalRestClient.lastPost.getJSONObject("tags").get("foo"));
        Assert.assertEquals("baz", ShadowOneSignalRestClient.lastPost.getJSONObject("tags").get("fuz"));

        OneSignal.deleteTags("[\"foo\", \"fuz\"]");
        threadAndTaskWait();
        Assert.assertEquals("", ShadowOneSignalRestClient.lastPost.getJSONObject("tags").get("foo"));
        Assert.assertEquals("", ShadowOneSignalRestClient.lastPost.getJSONObject("tags").get("fuz"));

        GetTags();

        Assert.assertNull(lastGetTags);

        final SharedPreferences prefs = blankActivity.getSharedPreferences(OneSignal.class.getSimpleName(),
                Context.MODE_PRIVATE);
        JSONObject syncValues = new JSONObject(
                prefs.getString("ONESIGNAL_USERSTATE_SYNCVALYES_CURRENT_STATE", null));
        Assert.assertFalse(syncValues.has("tags"));
    }

    @Test
    public void testOmitDeletesOfNonExistingKeys() throws Exception {
        OneSignalInit();
        OneSignal.deleteTag("this_key_does_not_exist");
        threadAndTaskWait();

        Assert.assertFalse(ShadowOneSignalRestClient.lastPost.has("tags"));
    }

    // ####### GetTags Tests ########
    @Test
    public void testGetTagsWithNoTagsShouldBeNull() throws Exception {
        OneSignalInit();
        GetTags();

        Assert.assertNull(lastGetTags);
    }

    @Test
    public void shouldGetTags() throws Exception {
        OneSignalInit();
        OneSignal.sendTags(new JSONObject("{\"test1\": \"value1\", \"test2\": \"value2\"}"));
        threadAndTaskWait();
        GetTags();

        Assert.assertEquals("value1", lastGetTags.getString("test1"));
        Assert.assertEquals("value2", lastGetTags.getString("test2"));
    }

    // ####### on_focus Tests ########

    @Test
    public void sendsOnFocus() throws Exception {
        OneSignalInit();
        threadAndTaskWait();
        blankActivityController.resume();
        ShadowSystemClock.setCurrentTimeMillis(60 * 1000);

        blankActivityController.pause();
        threadAndTaskWait();
        Assert.assertEquals(60, ShadowOneSignalRestClient.lastPost.getInt("active_time"));
        Assert.assertEquals(2, ShadowOneSignalRestClient.networkCallCount);
    }

    /*
    // Can't get test to work from a app flow due to the main thread being locked one way or another in a robolectric env.
    // Running ActivityLifecycleListener.focusHandlerThread...advanceToNextPostedRunnable waits on the main thread.
    //    If it is put in its own thread then synchronized that is run when messages a runnable is added / removed hangs the main thread here too.
    @Test
    public void shouldNotDoubleCountFocusTime() throws Exception {
       System.out.println("TEST IS RUNNING ONE THREAD: " + Thread.currentThread());
        
       // Start app normally
       OneSignalInit();
       threadAndTaskWait();
        
       // Press home button after 30 sec
       blankActivityController.resume();
       ShadowSystemClock.setCurrentTimeMillis(30 * 1000);
       blankActivityController.pause();
       threadAndTaskWait();
        
       // Press home button after 30 more sec, with a network hang
       blankActivityController.resume();
       ShadowSystemClock.setCurrentTimeMillis(60 * 1000);
       ShadowOneSignalRestClient.interruptibleDelayNext = true;
       blankActivityController.pause();
       System.out.println("HERE1");
       threadAndTaskWait();
       System.out.println("HERE2"  + Thread.currentThread());
        
       // Open app and press home button again right away.
       blankActivityController.resume();
       System.out.println("HERE3: " + Thread.currentThread());
       blankActivityController.pause();
       System.out.println("HERE4");
       threadAndTaskWait();
       System.out.println("HERE5");
        
       ShadowOneSignalRestClient.interruptHTTPDelay();
       System.out.println("HERE6");
        
       threadWait();
       System.out.println("ShadowOneSignalRestClient.lastPost: " + ShadowOneSignalRestClient.lastPost);
       System.out.println("ShadowOneSignalRestClient.networkCallCount: " + ShadowOneSignalRestClient.networkCallCount);
        
       Assert.assertEquals(60, ShadowOneSignalRestClient.lastPost.getInt("active_time"));
       Assert.assertEquals(2, ShadowOneSignalRestClient.networkCallCount);
    }
    */

    // ####### Unit Test Location       ########

    @Test
    @Config(shadows = { ShadowLocationGMS.class })
    public void shouldUpdateAllLocationFieldsWhenAnyFieldsChange() throws Exception {
        OneSignalInit();
        threadAndTaskWait();
        Assert.assertEquals(1.0, ShadowOneSignalRestClient.lastPost.getDouble("lat"));
        Assert.assertEquals(2.0, ShadowOneSignalRestClient.lastPost.getDouble("long"));
        Assert.assertEquals(3.0, ShadowOneSignalRestClient.lastPost.getDouble("loc_acc"));
        Assert.assertEquals(0.0, ShadowOneSignalRestClient.lastPost.getDouble("loc_type"));

        ShadowOneSignalRestClient.lastPost = null;
        StaticResetHelper.restSetStaticFields();
        ShadowLocationGMS.lat = 30.0;
        ShadowLocationGMS.accuracy = 5.0f;

        OneSignalInit();
        threadAndTaskWait();
        Assert.assertEquals(30.0, ShadowOneSignalRestClient.lastPost.getDouble("lat"));
        Assert.assertEquals(2.0, ShadowOneSignalRestClient.lastPost.getDouble("long"));
        Assert.assertEquals(5.0, ShadowOneSignalRestClient.lastPost.getDouble("loc_acc"));
        Assert.assertEquals(0.0, ShadowOneSignalRestClient.lastPost.getDouble("loc_type"));
    }

    @Test
    @Config(shadows = { ShadowOneSignal.class })
    public void testLocationTimeout() throws Exception {
        //ShadowApplication.getInstance().grantPermissions(new String[]{"android.permission.YOUR_PERMISSION"});

        OneSignalInit();
        threadAndTaskWait();

        Class klass = Class.forName("com.onesignal.LocationGMS");
        klass.getDeclaredMethod("startFallBackThread").invoke(null);
        klass.getDeclaredMethod("fireFailedComplete").invoke(null);
        threadAndTaskWait();

        Assert.assertFalse(ShadowOneSignal.messages.contains("GoogleApiClient timedout"));
    }

    // ####### Unit test postNotification #####

    private static JSONObject postNotificationSuccess = null, postNotificationFailure = null;

    @Test
    public void testPostNotification() throws Exception {
        OneSignalInit();

        OneSignal.PostNotificationResponseHandler handler = new OneSignal.PostNotificationResponseHandler() {
            @Override
            public void onSuccess(JSONObject response) {
                postNotificationSuccess = response;
            }

            @Override
            public void onFailure(JSONObject response) {
                postNotificationFailure = response;
            }
        };

        // Not testing input here, just that HTTP 200 fires a success.
        OneSignal.postNotification("{}", handler);
        threadAndTaskWait();
        Assert.assertNotNull(postNotificationSuccess);
        Assert.assertNull(postNotificationFailure);
        postNotificationSuccess = postNotificationFailure = null;

        ShadowOneSignalRestClient.nextSuccessResponse = "{\"id\":\"\",\"recipients\":0,\"errors\":[\"All included players are not subscribed\"]}";
        OneSignal.postNotification("{}", handler);
        Assert.assertNull(postNotificationSuccess);
        Assert.assertNotNull(postNotificationFailure);
    }

    // ####### Unit test helper methods ########

    private static void threadWait() {
        try {
            Thread.sleep(1000);
        } catch (Throwable t) {
        }
    }

    private void threadAndTaskWait() {
        try {
            Thread.sleep(testSleepTime);
        } catch (Throwable t) {
        }
        OneSignalPackagePrivateHelper.runAllNetworkRunnables();
        OneSignalPackagePrivateHelper.runFocusRunnables();

        Robolectric.getForegroundThreadScheduler().runOneTask();
    }

    private void OneSignalInit() {
        OneSignal.setLogLevel(OneSignal.LOG_LEVEL.DEBUG, OneSignal.LOG_LEVEL.NONE);
        OneSignal.init(blankActivity, "123456789", ONESIGNAL_APP_ID);
    }

    private void OneSignalInitWithBadProjectNum() {
        OneSignal.init(blankActivity, "NOT A VALID Google project number", ONESIGNAL_APP_ID);
    }

    // For some reason Roboelctric does not automatically add this when it reads the AndroidManifest.xml
    //    Also it seems it has to be done in the test itself instead of the setup process.
    private static void AddLauncherIntentFilter() {
        Intent launchIntent = new Intent(Intent.ACTION_MAIN);
        launchIntent.setPackage("com.onesignal.example");
        launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        ResolveInfo resolveInfo = new ResolveInfo();
        resolveInfo.activityInfo = new ActivityInfo();
        resolveInfo.activityInfo.packageName = "com.onesignal.example";
        resolveInfo.activityInfo.name = "MainActivity";

        RuntimeEnvironment.getRobolectricPackageManager().addResolveInfoForIntent(launchIntent, resolveInfo);
    }
}