From 40ed9062255d22500f8b0bb666be81c437b5f03c Mon Sep 17 00:00:00 2001 From: tjuric Date: Mon, 13 Mar 2017 13:40:28 +0100 Subject: [PATCH] MM-1388 - Geo reinitalization handled. Refactoring --- .../messaging/geo/GeoMonitoringTest.java | 146 +++++++++--------- .../mobile/messaging/geo/GeoStorageTest.java | 69 ++++++++- .../src/main/AndroidManifest.xml | 6 +- .../mobile/messaging/BootReceiver.java | 2 +- .../mobile/messaging/MobileMessagingCore.java | 31 +++- .../messaging/MobileMessagingProperty.java | 1 + .../messaging/dal/sqlite/DatabaseHelper.java | 7 + .../dal/sqlite/DatabaseHelperImpl.java | 6 + .../messaging/gcm/MobileMessageHandler.java | 1 + .../mobile/messaging/geo/GeoAreasHandler.java | 87 +++++++---- .../messaging/geo/GeoSQLiteMessageStore.java | 4 + .../mobile/messaging/geo/Geofencing.java | 136 ++++++++++++---- .../geo/GeofencingConsistencyReceiver.java | 55 +++++-- 13 files changed, 396 insertions(+), 155 deletions(-) diff --git a/infobip-mobile-messaging-android-sdk/src/androidTest/java/org/infobip/mobile/messaging/geo/GeoMonitoringTest.java b/infobip-mobile-messaging-android-sdk/src/androidTest/java/org/infobip/mobile/messaging/geo/GeoMonitoringTest.java index 9096b965..beff2a92 100644 --- a/infobip-mobile-messaging-android-sdk/src/androidTest/java/org/infobip/mobile/messaging/geo/GeoMonitoringTest.java +++ b/infobip-mobile-messaging-android-sdk/src/androidTest/java/org/infobip/mobile/messaging/geo/GeoMonitoringTest.java @@ -1,12 +1,13 @@ package org.infobip.mobile.messaging.geo; import android.content.Context; +import android.preference.PreferenceManager; import android.test.InstrumentationTestCase; import com.google.android.gms.location.Geofence; import org.infobip.mobile.messaging.Message; -import org.infobip.mobile.messaging.MobileMessaging; +import org.infobip.mobile.messaging.MobileMessagingCore; import org.infobip.mobile.messaging.MobileMessagingProperty; import org.infobip.mobile.messaging.api.support.Tuple; import org.infobip.mobile.messaging.api.support.http.serialization.JsonSerializer; @@ -14,6 +15,7 @@ import org.infobip.mobile.messaging.storage.SQLiteMessageStore; import org.infobip.mobile.messaging.util.DateTimeUtil; import org.infobip.mobile.messaging.util.PreferenceHelper; +import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; @@ -28,7 +30,7 @@ public class GeoMonitoringTest extends InstrumentationTestCase { private Context context; - private MessageStore messageStore; + private MessageStore geoStore; private Long now; @Override @@ -38,108 +40,115 @@ protected void setUp() throws Exception { context = getInstrumentation().getContext().getApplicationContext(); now = System.currentTimeMillis(); - Geofencing.getInstance(context); - + PreferenceManager.getDefaultSharedPreferences(context).edit().clear().commit(); PreferenceHelper.saveString(context, MobileMessagingProperty.MESSAGE_STORE_CLASS, SQLiteMessageStore.class.getName()); - messageStore = MobileMessaging.getInstance(context).getMessageStore(); - messageStore.deleteAll(context); + + Geofencing.getInstance(context); + geoStore = MobileMessagingCore.getInstance(context).getMessageStoreForGeo(); + geoStore.deleteAll(context); } - public void test_shouldCalculateRefreshDateForGeoStart() throws Exception { + public void test_shouldCalculateRefreshDatesForGeoStartAndExpired() throws Exception { // Given Long millis15MinAfterNow = now + 15 * 60 * 1000; Long millis30MinAfterNow = now + 30 * 60 * 1000; String date15MinAfterNow = DateTimeUtil.ISO8601DateToString(new Date(millis15MinAfterNow)); String date30MinAfterNow = DateTimeUtil.ISO8601DateToString(new Date(millis30MinAfterNow)); - Geo geo = new Geo(0.0, 0.0, new ArrayList() {{ - add(new Area("SomeAreaId", "SomeAreaTitle", 0.0, 0.0, 10)); - }}, null, new ArrayList(), date30MinAfterNow, date15MinAfterNow, "SomeCampaignId"); - - JSONObject internalData = new JSONObject(new JsonSerializer().serialize(geo)); - Message message = new Message( - "SomeMessageId", - "SomeTitle", - "SomeBody", - "SomeSound", - true, - "SomeIcon", - true, - "SomeCategory", - "SomeFrom", - now, - 0, - internalData, - null, - geo, - "SomeDestination", - Message.Status.UNKNOWN, - "SomeStatusMessage" - ); - messageStore.save(context, message); + saveGeoMessageToDb(date15MinAfterNow, date30MinAfterNow); // When - Tuple, Date> geofencesAndNextRefreshDate = Geofencing.calculateGeofencesToMonitorAndNextCheckDate(messageStore); + Tuple, Tuple> geofencesAndNextRefreshDate = Geofencing.calculateGeofencesToMonitorAndNextCheckDates(geoStore); // Then assertNotNull(geofencesAndNextRefreshDate); assertTrue(geofencesAndNextRefreshDate.getLeft().isEmpty()); assertNotNull(geofencesAndNextRefreshDate.getRight()); - assertEquals(millis15MinAfterNow, geofencesAndNextRefreshDate.getRight().getTime(), 3000); + + Date refreshStartDate = geofencesAndNextRefreshDate.getRight().getLeft(); + Date refreshExpiryDate = geofencesAndNextRefreshDate.getRight().getRight(); + assertEquals(millis15MinAfterNow, refreshStartDate.getTime(), 3000); + assertEquals(millis30MinAfterNow, refreshExpiryDate.getTime(), 3000); } - public void test_shouldNotCalculateRefreshDateIfGeoExpired() throws Exception { + public void test_shouldNotCalculateRefreshDateForGeoStartIfGeoExpired() throws Exception { // Given Long millis30MinBeforeNow = now - 30 * 60 * 1000; Long millis15MinBeforeNow = now - 15 * 60 * 1000; String date30MinBeforeNow = DateTimeUtil.ISO8601DateToString(new Date(millis30MinBeforeNow)); String date15MinBeforeNow = DateTimeUtil.ISO8601DateToString(new Date(millis15MinBeforeNow)); - Geo geo = new Geo(0.0, 0.0, new ArrayList() {{ - add(new Area("SomeAreaId", "SomeAreaTitle", 0.0, 0.0, 10)); - }}, null, new ArrayList(), date15MinBeforeNow, date30MinBeforeNow, "SomeCampaignId"); + saveGeoMessageToDb(date30MinBeforeNow, date15MinBeforeNow); - JSONObject internalData = new JSONObject(new JsonSerializer().serialize(geo)); - Message message = new Message( - "SomeMessageId", - "SomeTitle", - "SomeBody", - "SomeSound", - true, - "SomeIcon", - true, - "SomeCategory", - "SomeFrom", - now, - 0, - internalData, - null, - geo, - "SomeDestination", - Message.Status.UNKNOWN, - "SomeStatusMessage" - ); - messageStore.save(context, message); + // When + Tuple, Tuple> geofencesAndNextRefreshDate = Geofencing.calculateGeofencesToMonitorAndNextCheckDates(geoStore); + + // Then + assertNotNull(geofencesAndNextRefreshDate); + assertTrue(geofencesAndNextRefreshDate.getLeft().isEmpty()); + assertNull(geofencesAndNextRefreshDate.getRight().getLeft()); + } + + public void test_shouldCalculateRefreshDateForGeoExpiredIfGeoExpired() throws Exception { + // Given + Long millis30MinBeforeNow = now - 30 * 60 * 1000; + Long millis15MinBeforeNow = now - 15 * 60 * 1000; + String date30MinBeforeNow = DateTimeUtil.ISO8601DateToString(new Date(millis30MinBeforeNow)); + String date15MinBeforeNow = DateTimeUtil.ISO8601DateToString(new Date(millis15MinBeforeNow)); + + saveGeoMessageToDb(date30MinBeforeNow, date15MinBeforeNow); // When - Tuple, Date> geofencesAndNextRefreshDate = Geofencing.calculateGeofencesToMonitorAndNextCheckDate(messageStore); + Tuple, Tuple> geofencesAndNextRefreshDate = Geofencing.calculateGeofencesToMonitorAndNextCheckDates(geoStore); // Then assertNotNull(geofencesAndNextRefreshDate); assertTrue(geofencesAndNextRefreshDate.getLeft().isEmpty()); - assertNull(geofencesAndNextRefreshDate.getRight()); + assertNull(geofencesAndNextRefreshDate.getRight().getLeft()); + assertEquals(now, geofencesAndNextRefreshDate.getRight().getRight().getTime(), 3000); } - public void test_shouldNotCalculateRefreshDateIfGeoIsMonitoredNow() throws Exception { + public void test_shouldNotCalculateRefreshDateForGeoStartIfGeoIsMonitoredNow() throws Exception { // Given Long millis15MinBeforeNow = now - 15 * 60 * 1000; Long millis15MinAfterNow = now + 15 * 60 * 1000; String date15MinBeforeNow = DateTimeUtil.ISO8601DateToString(new Date(millis15MinBeforeNow)); String date15MinAfterNow = DateTimeUtil.ISO8601DateToString(new Date(millis15MinAfterNow)); + saveGeoMessageToDb(date15MinBeforeNow, date15MinAfterNow); + + // When + Tuple, Tuple> geofencesAndNextRefreshDate = Geofencing.calculateGeofencesToMonitorAndNextCheckDates(geoStore); + + // Then + assertNotNull(geofencesAndNextRefreshDate); + assertFalse(geofencesAndNextRefreshDate.getLeft().isEmpty()); + assertNull(geofencesAndNextRefreshDate.getRight().getLeft()); + } + + public void test_shouldCalculateRefreshDateForGeoExpiredIfGeoIsMonitoredNow() throws Exception { + // Given + Long millis15MinBeforeNow = now - 15 * 60 * 1000; + Long millis15MinAfterNow = now + 15 * 60 * 1000; + String date15MinBeforeNow = DateTimeUtil.ISO8601DateToString(new Date(millis15MinBeforeNow)); + String date15MinAfterNow = DateTimeUtil.ISO8601DateToString(new Date(millis15MinAfterNow)); + + saveGeoMessageToDb(date15MinBeforeNow, date15MinAfterNow); + + // When + Tuple, Tuple> geofencesAndNextRefreshDate = Geofencing.calculateGeofencesToMonitorAndNextCheckDates(geoStore); + + // Then + assertNotNull(geofencesAndNextRefreshDate); + assertFalse(geofencesAndNextRefreshDate.getLeft().isEmpty()); + assertNull(geofencesAndNextRefreshDate.getRight().getLeft()); + assertEquals(millis15MinAfterNow, geofencesAndNextRefreshDate.getRight().getRight().getTime(), 3000); + } + + private void saveGeoMessageToDb(String startTimeMillis, String expiryTimeMillis) throws JSONException { Geo geo = new Geo(0.0, 0.0, new ArrayList() {{ add(new Area("SomeAreaId", "SomeAreaTitle", 0.0, 0.0, 10)); - }}, null, new ArrayList(), date15MinBeforeNow, date15MinAfterNow, "SomeCampaignId"); + }}, null, new ArrayList(), expiryTimeMillis, startTimeMillis, "SomeCampaignId"); JSONObject internalData = new JSONObject(new JsonSerializer().serialize(geo)); Message message = new Message( @@ -161,14 +170,7 @@ public void test_shouldNotCalculateRefreshDateIfGeoIsMonitoredNow() throws Excep Message.Status.UNKNOWN, "SomeStatusMessage" ); - messageStore.save(context, message); - - // When - Tuple, Date> geofencesAndNextRefreshDate = Geofencing.calculateGeofencesToMonitorAndNextCheckDate(messageStore); - - // Then - assertNotNull(geofencesAndNextRefreshDate); - assertTrue(geofencesAndNextRefreshDate.getLeft().isEmpty()); - assertNull(geofencesAndNextRefreshDate.getRight()); + geoStore.save(context, message); } + } diff --git a/infobip-mobile-messaging-android-sdk/src/androidTest/java/org/infobip/mobile/messaging/geo/GeoStorageTest.java b/infobip-mobile-messaging-android-sdk/src/androidTest/java/org/infobip/mobile/messaging/geo/GeoStorageTest.java index f5577496..45aa3579 100644 --- a/infobip-mobile-messaging-android-sdk/src/androidTest/java/org/infobip/mobile/messaging/geo/GeoStorageTest.java +++ b/infobip-mobile-messaging-android-sdk/src/androidTest/java/org/infobip/mobile/messaging/geo/GeoStorageTest.java @@ -2,6 +2,7 @@ import android.content.Context; import android.content.Intent; +import android.preference.PreferenceManager; import android.test.InstrumentationTestCase; import org.infobip.mobile.messaging.Message; @@ -12,10 +13,13 @@ import org.infobip.mobile.messaging.gcm.MobileMessageHandler; import org.infobip.mobile.messaging.storage.MessageStore; import org.infobip.mobile.messaging.storage.SQLiteMessageStore; +import org.infobip.mobile.messaging.util.DateTimeUtil; import org.infobip.mobile.messaging.util.PreferenceHelper; +import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; +import java.util.Date; import java.util.List; /** @@ -29,19 +33,25 @@ public class GeoStorageTest extends InstrumentationTestCase { private MessageStore geoStore; private MessageStore commonStore; private MobileMessageHandler handler; + private long now; @Override protected void setUp() throws Exception { super.setUp(); - PreferenceHelper.saveString(getInstrumentation().getContext(), MobileMessagingProperty.MESSAGE_STORE_CLASS, SQLiteMessageStore.class.getName()); - context = getInstrumentation().getContext(); + + PreferenceManager.getDefaultSharedPreferences(context).edit().clear().commit(); + PreferenceHelper.saveString(context, MobileMessagingProperty.MESSAGE_STORE_CLASS, SQLiteMessageStore.class.getName()); + PreferenceHelper.saveBoolean(context, MobileMessagingProperty.PUSH_REGISTRATION_ENABLED, true); + PreferenceHelper.saveBoolean(context, MobileMessagingProperty.GEOFENCING_ACTIVATED, true); + handler = new MobileMessageHandler(); geoStore = MobileMessagingCore.getInstance(context).getMessageStoreForGeo(); geoStore.deleteAll(context); commonStore = MobileMessagingCore.getInstance(context).getMessageStore(); commonStore.deleteAll(context); + now = System.currentTimeMillis(); } public void test_shouldSaveGeoMessagesToGeoStore() throws Exception { @@ -114,4 +124,59 @@ public void test_shouldSaveMessagesToCorrespondingSeparateStores() throws Except assertEquals("SomeCampaignId2", messages.get(0).getGeo().getCampaignId()); assertEquals("SomeAreaId1", messages.get(0).getGeo().getAreasList().get(0).getId()); } + + public void test_shouldDeleteExpiredAreas() throws Exception { + // Given + long now = System.currentTimeMillis(); + Long millis30MinBeforeNow = now - 30 * 60 * 1000; + Long millis15MinBeforeNow = now - 15 * 60 * 1000; + Long millis15MinAfterNow = now + 15 * 60 * 1000; + String date30MinBeforeNow = DateTimeUtil.ISO8601DateToString(new Date(millis30MinBeforeNow)); + String date15MinBeforeNow = DateTimeUtil.ISO8601DateToString(new Date(millis15MinBeforeNow)); + String date15MinAfterNow = DateTimeUtil.ISO8601DateToString(new Date(millis15MinAfterNow)); + String nonExpiredMessageId = "SomeMessageId5"; + + saveGeoMessageToDb("SomeMessageId1", null, date30MinBeforeNow); + saveGeoMessageToDb("SomeMessageId2", null, date30MinBeforeNow); + saveGeoMessageToDb("SomeMessageId3", null, date15MinBeforeNow); + saveGeoMessageToDb("SomeMessageId4", null, date15MinBeforeNow); + saveGeoMessageToDb(nonExpiredMessageId, null, date15MinAfterNow); + + assertEquals(5, geoStore.countAll(context)); + + // When + MobileMessagingCore.getInstance(context).removeExpiredAreas(); + + // Then + assertEquals(1, geoStore.countAll(context)); + assertEquals(nonExpiredMessageId, geoStore.findAll(context).get(0).getMessageId()); + } + + private void saveGeoMessageToDb(String messageId, String startTimeMillis, String expiryTimeMillis) throws JSONException { + Geo geo = new Geo(0.0, 0.0, new ArrayList() {{ + add(new Area("SomeAreaId", "SomeAreaTitle", 0.0, 0.0, 10)); + }}, null, new ArrayList(), expiryTimeMillis, startTimeMillis, "SomeCampaignId"); + + JSONObject internalData = new JSONObject(new JsonSerializer().serialize(geo)); + Message message = new Message( + messageId, + "SomeTitle", + "SomeBody", + "SomeSound", + true, + "SomeIcon", + true, + "SomeCategory", + "SomeFrom", + now, + 0, + internalData, + null, + geo, + "SomeDestination", + Message.Status.UNKNOWN, + "SomeStatusMessage" + ); + geoStore.save(context, message); + } } diff --git a/infobip-mobile-messaging-android-sdk/src/main/AndroidManifest.xml b/infobip-mobile-messaging-android-sdk/src/main/AndroidManifest.xml index 3330751c..45ed54ec 100644 --- a/infobip-mobile-messaging-android-sdk/src/main/AndroidManifest.xml +++ b/infobip-mobile-messaging-android-sdk/src/main/AndroidManifest.xml @@ -49,9 +49,13 @@ + + + + - + diff --git a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/BootReceiver.java b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/BootReceiver.java index ab48063a..e5758d29 100644 --- a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/BootReceiver.java +++ b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/BootReceiver.java @@ -12,6 +12,6 @@ public class BootReceiver extends WakefulBroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { MobileMessagingLogger.i("Received boot completed intent"); - MobileMessagingCore.handleBootCompleted(context); + MobileMessagingCore.getInstance(context).handleBootCompleted(); } } diff --git a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/MobileMessagingCore.java b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/MobileMessagingCore.java index c773bbc9..32e05aa5 100644 --- a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/MobileMessagingCore.java +++ b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/MobileMessagingCore.java @@ -427,12 +427,33 @@ public static boolean isGeofencingActivated(Context context) { return PreferenceHelper.findBoolean(context, MobileMessagingProperty.GEOFENCING_ACTIVATED); } + public boolean areAllActiveGeoAreasMonitored() { + return PreferenceHelper.findBoolean(context, MobileMessagingProperty.ALL_ACTIVE_GEO_AREAS_MONITORED); + } + + public void setAllActiveGeoAreasMonitored(boolean allActiveGeoAreasMonitored) { + PreferenceHelper.saveBoolean(context, MobileMessagingProperty.ALL_ACTIVE_GEO_AREAS_MONITORED, allActiveGeoAreasMonitored); + } + + public void removeExpiredAreas() { + if (isGeofencingActivated(context) && isPushRegistrationEnabled()) { + if (geofencing == null) { + geofencing = Geofencing.getInstance(context); + } + + geofencing.removeExpiredAreasFromStorage(); + } + } + public void startGeoMonitoringIfNecessary() { if (isGeofencingActivated(context) && isPushRegistrationEnabled()) { if (geofencing == null) { geofencing = Geofencing.getInstance(context); } - geofencing.startGeoMonitoring(); + + if (!areAllActiveGeoAreasMonitored()) { + geofencing.startGeoMonitoring(); + } } } @@ -440,7 +461,9 @@ void activateGeofencing(Geofencing geofencing) { this.geofencing = geofencing; if (geofencing == null) return; setGeofencingActivated(context, true); - geofencing.startGeoMonitoring(); + if (!areAllActiveGeoAreasMonitored()) { + geofencing.startGeoMonitoring(); + } } void deactivateGeofencing() { @@ -601,7 +624,9 @@ public String[] getSuspendedCampaignIds() { return PreferenceHelper.findStringArray(context, MobileMessagingProperty.SUSPENDED_CAMPAIGN_IDS); } - static void handleBootCompleted(Context context) { + void handleBootCompleted() { + //active areas stop being monitored on boot and we need to re-register them + instance.setAllActiveGeoAreasMonitored(false); Geofencing.scheduleRefresh(context); } diff --git a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/MobileMessagingProperty.java b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/MobileMessagingProperty.java index 6558be03..f226ce8d 100644 --- a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/MobileMessagingProperty.java +++ b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/MobileMessagingProperty.java @@ -36,6 +36,7 @@ public enum MobileMessagingProperty { UNREPORTED_GEO_EVENTS("org.ninfobip.mobile.messaging.infobip.UNREPORTED_GEO_EVENTS", new String[0]), FINISHED_CAMPAIGN_IDS("org.infobip.mobile.messaging.infobip.FINISHED_CAMPAIGN_IDS", new String[0]), SUSPENDED_CAMPAIGN_IDS("org.infobip.mobile.messaging.infobip.SUSPENDED_CAMPAIGN_IDS", new String[0]), + ALL_ACTIVE_GEO_AREAS_MONITORED("org.infobip.mobile.messaging.infobip.ALL_ACTIVE_GEO_AREAS_MONITORED", false), GEOFENCING_ACTIVATED("org.infobip.mobile.messaging.infobip.GEOFENCING_ACTIVATED", false), PUSH_REGISTRATION_ENABLED("org.infobip.mobile.messaging.infobip.PUSH_REGISTRATION_ENABLED", true), diff --git a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/dal/sqlite/DatabaseHelper.java b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/dal/sqlite/DatabaseHelper.java index 77c15a25..3d290162 100644 --- a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/dal/sqlite/DatabaseHelper.java +++ b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/dal/sqlite/DatabaseHelper.java @@ -51,4 +51,11 @@ public interface DatabaseHelper { * @param primaryKey object primary key */ void delete(Class cls, String primaryKey); + + /** + * Deletes object from database + * @param cls object class + * @param primaryKeys object primary keys + */ + void delete(Class cls, String[] primaryKeys); } \ No newline at end of file diff --git a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/dal/sqlite/DatabaseHelperImpl.java b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/dal/sqlite/DatabaseHelperImpl.java index e3b0273a..0783f28c 100644 --- a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/dal/sqlite/DatabaseHelperImpl.java +++ b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/dal/sqlite/DatabaseHelperImpl.java @@ -123,6 +123,12 @@ public void delete(Class cls, @No db().delete(getTableName(cls), getPrimaryKeyColumn(cls) + "=?", new String[]{primaryKey}); } + @Override + public void delete(Class cls, String[] primaryKeys) { + db().delete(getTableName(cls), getPrimaryKeyColumn(cls) + + " IN (" + new String(new char[primaryKeys.length-1]).replace("\0", "?,") + "?)", primaryKeys); + } + @Override public void onCreate(SQLiteDatabase db) { db.beginTransaction(); diff --git a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/gcm/MobileMessageHandler.java b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/gcm/MobileMessageHandler.java index d6842cce..84388317 100644 --- a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/gcm/MobileMessageHandler.java +++ b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/gcm/MobileMessageHandler.java @@ -67,6 +67,7 @@ private void saveMessage(Context context, Message message) { messageStore.save(context, message); if (MobileMessagingCore.hasGeo(message)) { + MobileMessagingCore.getInstance(context).setAllActiveGeoAreasMonitored(false); MobileMessagingCore.getInstance(context).startGeoMonitoringIfNecessary(); } } catch (Exception e) { diff --git a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/GeoAreasHandler.java b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/GeoAreasHandler.java index 7445cad4..7e09a5d4 100644 --- a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/GeoAreasHandler.java +++ b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/GeoAreasHandler.java @@ -39,6 +39,8 @@ import java.util.concurrent.TimeUnit; import java.util.regex.PatternSyntaxException; +import static com.google.android.gms.location.GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE; + /** * @author pandric * @since 24.06.2016. @@ -60,7 +62,7 @@ class GeoAreasHandler { }}; private static SparseArray geofencingErrors = new SparseArray() {{ - put(GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE, "Geofence not available"); + put(GEOFENCE_NOT_AVAILABLE, "Geofence not available"); put(GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES, "Too many geofences"); put(GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS, "Too many pending intents"); }}; @@ -83,7 +85,12 @@ void handleTransition(Intent intent) { if (geofencingEvent == null) return; if (geofencingEvent.hasError()) { - MobileMessagingLogger.e(TAG, "ERROR:" + geofencingErrors.get(geofencingEvent.getErrorCode())); + int errorCode = geofencingEvent.getErrorCode(); + MobileMessagingLogger.e(TAG, "ERROR:" + geofencingErrors.get(errorCode)); + + if (GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE == errorCode) { + MobileMessagingCore.getInstance(context).setAllActiveGeoAreasMonitored(false); + } return; } @@ -101,33 +108,11 @@ void handleTransition(List triggeringGeofences, int geofenceTransition return; } - ArrayList geoReports = new ArrayList<>(filteredMessagesAndAreas.size()); - ArrayList> geoDataToNotify = new ArrayList<>(filteredMessagesAndAreas.size()); - - for (Message message : filteredMessagesAndAreas.keySet()) { - - List geofenceAreasList = filteredMessagesAndAreas.get(message); - logGeofences(geofenceAreasList, geofenceTransition); - - for (final Area area : geofenceAreasList) { - - if (!shouldReportTransition(message, geofenceTransition)) { - continue; - } - - Geo geoToReportAndNotify = new Geo( - triggeringLocation.getLatitude(), - triggeringLocation.getLongitude(), - new ArrayList() {{ - add(area); - }}); + Pair, ArrayList>> geoReportsAndDataToNotify = + prepareGeoReportsAndDataToNotify(geofenceTransition, triggeringLocation, filteredMessagesAndAreas); - geoDataToNotify.add(new Pair<>(geoToReportAndNotify, message)); - - geoReports.add(new GeoReport(message.getGeo().getCampaignId(), message.getMessageId(), - transitionReportEvents.get(geofenceTransition), area, System.currentTimeMillis())); - } - } + ArrayList geoReports = geoReportsAndDataToNotify.first; + ArrayList> geoDataToNotify = geoReportsAndDataToNotify.second; if (!geoReports.isEmpty() && !geoDataToNotify.isEmpty()) { MobileMessagingCore mobileMessagingCore = MobileMessagingCore.getInstance(context); @@ -136,6 +121,7 @@ void handleTransition(List triggeringGeofences, int geofenceTransition List finishedCampaignIds = Arrays.asList(mobileMessagingCore.getFinishedCampaignIds()); List suspendedCampaignIds = Arrays.asList(mobileMessagingCore.getSuspendedCampaignIds()); + //send reports try { final GeoReportingResult result = new GeoReportingResult(MobileApiResourceProvider.INSTANCE.getMobileApiGeo(context) .report(new EventReports(reports))); @@ -153,24 +139,59 @@ void handleTransition(List triggeringGeofences, int geofenceTransition GeoReporter.handleError(context, mobileMessagingCore, e, geoReports); } + //display notifications displayNotificationsForActiveCampaigns(geoDataToNotify, geofenceTransition, finishedCampaignIds, suspendedCampaignIds); } } + private Pair, ArrayList>> prepareGeoReportsAndDataToNotify(int geofenceTransition, + Location triggeringLocation, + Map> filteredMessagesAndAreas) { + ArrayList geoReports = new ArrayList<>(filteredMessagesAndAreas.size()); + ArrayList> geoDataToNotify = new ArrayList<>(filteredMessagesAndAreas.size()); + + for (Message message : filteredMessagesAndAreas.keySet()) { + + List geofenceAreasList = filteredMessagesAndAreas.get(message); + logGeofences(geofenceAreasList, geofenceTransition); + + for (final Area area : geofenceAreasList) { + + if (!shouldReportTransition(message, geofenceTransition)) { + continue; + } + + Geo geoToReportAndNotify = new Geo( + triggeringLocation.getLatitude(), + triggeringLocation.getLongitude(), + new ArrayList() {{ + add(area); + }}); + + geoDataToNotify.add(new Pair<>(geoToReportAndNotify, message)); + + geoReports.add(new GeoReport(message.getGeo().getCampaignId(), message.getMessageId(), + transitionReportEvents.get(geofenceTransition), area, System.currentTimeMillis())); + } + } + + return new Pair<>(geoReports, geoDataToNotify); + } + private void displayNotificationsForActiveCampaigns(ArrayList> geoDataToNotify, int geofenceTransition, List finishedCampaignIds, List suspendedCampaignIds) { for (Pair geoData : geoDataToNotify) { final Geo geo = geoData.first; final Message message = geoData.second; - if (finishedCampaignIds.contains(message.getGeo().getCampaignId()) || suspendedCampaignIds.contains(message.getGeo().getCampaignId())) { + if (finishedCampaignIds.contains(geo.getCampaignId()) || suspendedCampaignIds.contains(geo.getCampaignId())) { continue; } - setLastNotificationTimeForArea(message.getGeo().getCampaignId(), geofenceTransition, System.currentTimeMillis()); - setNumberOfDisplayedNotificationsForArea(message.getGeo().getCampaignId(), geofenceTransition, - getNumberOfDisplayedNotificationsForArea(message.getGeo().getCampaignId(), geofenceTransition) + 1); + setLastNotificationTimeForArea(geo.getCampaignId(), geofenceTransition, System.currentTimeMillis()); + setNumberOfDisplayedNotificationsForArea(geo.getCampaignId(), geofenceTransition, + getNumberOfDisplayedNotificationsForArea(geo.getCampaignId(), geofenceTransition) + 1); - notifyAboutTransition(context, geo, geofenceTransition, geoData.second); + notifyAboutTransition(context, geo, geofenceTransition, message); } } diff --git a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/GeoSQLiteMessageStore.java b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/GeoSQLiteMessageStore.java index 22e320af..7ffcafec 100644 --- a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/GeoSQLiteMessageStore.java +++ b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/GeoSQLiteMessageStore.java @@ -62,4 +62,8 @@ public void deleteAll(Context context) { public void deleteById(Context context, String messageId) { MobileMessagingCore.getDatabaseHelper(context).delete(SqliteGeoMessage.class, messageId); } + + public void deleteByIds(Context context, String[] messageIds) { + MobileMessagingCore.getDatabaseHelper(context).delete(SqliteGeoMessage.class, messageIds); + } } diff --git a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/Geofencing.java b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/Geofencing.java index 3c812db4..9f164794 100644 --- a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/Geofencing.java +++ b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/Geofencing.java @@ -49,11 +49,19 @@ public class Geofencing implements GoogleApiClient.ConnectionCallbacks, GoogleAp private List geofences; private PendingIntent geofencePendingIntent; private MessageStore messageStore; + private GoogleApiClientRequestType requestType; + + private enum GoogleApiClientRequestType { + ADD_GEOFENCES, + REMOVE_GEOFENCES, + NONE + } private Geofencing(Context context) { checkRequiredService(context, GeofenceTransitionsIntentService.class); Geofencing.context = context; + requestType = GoogleApiClientRequestType.NONE; geofences = new ArrayList<>(); messageStore = MobileMessagingCore.getInstance(context).getMessageStoreForGeo(); googleApiClient = new GoogleApiClient.Builder(context) @@ -76,22 +84,55 @@ public static void scheduleRefresh(Context context) { scheduleRefresh(context, new Date()); } - public static void scheduleRefresh(Context context, Date when) { + private static void scheduleRefresh(Context context, Date when) { MobileMessagingLogger.i(TAG, "Next refresh in: " + when); - if (when == null) { - return; + GeofencingConsistencyReceiver.scheduleConsistencyAlarm(context, AlarmManager.RTC_WAKEUP, when, + GeofencingConsistencyReceiver.SCHEDULED_GEO_REFRESH_ACTION, 0); + } + + public static void scheduleExpiry(Context context, Date when) { + GeofencingConsistencyReceiver.scheduleConsistencyAlarm(context, AlarmManager.RTC_WAKEUP, when, + GeofencingConsistencyReceiver.SCHEDULED_GEO_EXPIRE_ACTION, 0); + } + + public void removeExpiredAreasFromStorage() { + GeoSQLiteMessageStore messageStoreForGeo = (GeoSQLiteMessageStore) MobileMessagingCore.getInstance(context).getMessageStoreForGeo(); + List messages = messageStoreForGeo.findAll(context); + List messageIdsToDelete = new ArrayList<>(messages.size()); + Date now = new Date(); + + for (Message message : messages) { + Geo geo = message.getGeo(); + if (geo == null) { + continue; + } + + List areasList = geo.getAreasList(); + Date expiryDate = geo.getExpiryDate(); + + if (areasList == null || areasList.isEmpty()) { + continue; + } + + for (Area area : areasList) { + if (!area.isValid() || expiryDate == null) { + continue; + } + + if (expiryDate.before(now)) { + messageIdsToDelete.add(message.getMessageId()); + } + } } - AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(context, GeofencingConsistencyReceiver.class); - intent.setAction(GeofencingConsistencyReceiver.SCHEDULED_GEO_REFRESH_ACTION); - alarmManager.set(AlarmManager.RTC_WAKEUP, when.getTime(), PendingIntent.getBroadcast(context, 0, intent, 0)); + messageStoreForGeo.deleteByIds(context, messageIdsToDelete.toArray(new String[]{})); } @SuppressWarnings("WeakerAccess") - static Tuple, Date> calculateGeofencesToMonitorAndNextCheckDate(MessageStore messageStore) { - Date nextCheckDate = null; + static Tuple, Tuple> calculateGeofencesToMonitorAndNextCheckDates(MessageStore messageStore) { + Date nextCheckRefreshDate = null; + Date nextCheckExpireDate = null; Map geofences = new HashMap<>(); Map expiryDates = new HashMap<>(); List messages = messageStore.findAll(context); @@ -102,6 +143,8 @@ static Tuple, Date> calculateGeofencesToMonitorAndNextCheckDate(M continue; } + nextCheckExpireDate = calculateNextCheckDateForGeoExpiry(geo, nextCheckExpireDate); + final List finishedCampaignIds = Arrays.asList(MobileMessagingCore.getInstance(context).getFinishedCampaignIds()); if (finishedCampaignIds.contains(geo.getCampaignId())) { continue; @@ -124,14 +167,14 @@ static Tuple, Date> calculateGeofencesToMonitorAndNextCheckDate(M } } - nextCheckDate = calculateNextCheckDateForGeo(geo, nextCheckDate); + nextCheckRefreshDate = calculateNextCheckDateForGeoStart(geo, nextCheckRefreshDate); } List geofenceList = new ArrayList<>(geofences.values()); - return new Tuple<>(geofenceList, nextCheckDate); + return new Tuple<>(geofenceList, new Tuple<>(nextCheckRefreshDate, nextCheckExpireDate)); } - private static Date calculateNextCheckDateForGeo(Geo geo, Date oldCheckDate) { + private static Date calculateNextCheckDateForGeoStart(Geo geo, Date oldCheckDate) { Date now = new Date(); Date expiryDate = geo.getExpiryDate(); if (expiryDate != null && expiryDate.before(now)) { @@ -150,6 +193,36 @@ private static Date calculateNextCheckDateForGeo(Geo geo, Date oldCheckDate) { return startDate; } + private static Date calculateNextCheckDateForGeoExpiry(Geo geo, Date oldCheckDate) { + Date now = new Date(); + Date expiryDate = geo.getExpiryDate(); + + if (expiryDate == null) { + if (oldCheckDate == null) { + return null; + + } else { + return oldCheckDate; + } + } + + if (oldCheckDate != null && oldCheckDate.before(expiryDate)) { + if (oldCheckDate.before(now)) { + return now; + + } else { + return oldCheckDate; + } + } + + if (expiryDate.before(now)) { + return now; + + } else { + return expiryDate; + } + } + @SuppressWarnings("MissingPermission") public void startGeoMonitoring() { @@ -162,14 +235,19 @@ public void startGeoMonitoring() { return; } - Tuple, Date> tuple = calculateGeofencesToMonitorAndNextCheckDate(messageStore); - scheduleRefresh(context, tuple.getRight()); + Tuple, Tuple> tuple = calculateGeofencesToMonitorAndNextCheckDates(messageStore); + Date nextRefreshDate = tuple.getRight().getLeft(); + Date nextExpireDate = tuple.getRight().getRight(); + + scheduleRefresh(context, nextRefreshDate); + scheduleExpiry(context, nextExpireDate); geofences = tuple.getLeft(); if (geofences.isEmpty()) { return; } + requestType = GoogleApiClientRequestType.ADD_GEOFENCES; if (!googleApiClient.isConnected()) { googleApiClient.connect(); return; @@ -180,6 +258,8 @@ public void startGeoMonitoring() { @Override public void onResult(@NonNull Status status) { logGeofenceStatus(status, true); + requestType = GoogleApiClientRequestType.NONE; + MobileMessagingCore.getInstance(context).setAllActiveGeoAreasMonitored(status.isSuccess()); } }); } @@ -190,6 +270,7 @@ public void stopGeoMonitoring() { return; } + requestType = GoogleApiClientRequestType.REMOVE_GEOFENCES; if (!googleApiClient.isConnected()) { googleApiClient.connect(); return; @@ -200,28 +281,12 @@ public void stopGeoMonitoring() { @Override public void onResult(@NonNull Status status) { logGeofenceStatus(status, false); + requestType = GoogleApiClientRequestType.NONE; + MobileMessagingCore.getInstance(context).setAllActiveGeoAreasMonitored(!status.isSuccess()); } }); } - /** - * Has an async call to {@link GoogleApiClient#connect()} - * - * @param geofenceRequestIds - */ - public void removeGeofencesFromMonitoring(List geofenceRequestIds) { - if (!checkRequiredPermissions()) { - return; - } - - if (!googleApiClient.isConnected()) { - googleApiClient.connect(); - return; - } - - LocationServices.GeofencingApi.removeGeofences(googleApiClient, geofenceRequestIds); - } - private boolean checkRequiredPermissions() { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { MobileMessagingLogger.e("Unable to initialize geofencing", new ConfigurationException(Reason.MISSING_REQUIRED_PERMISSION, Manifest.permission.ACCESS_FINE_LOCATION)); @@ -275,7 +340,12 @@ private PendingIntent geofencePendingIntent() { @Override public void onConnected(@Nullable Bundle bundle) { MobileMessagingLogger.d(TAG, "GoogleApiClient connected"); - startGeoMonitoring(); + if (GoogleApiClientRequestType.ADD_GEOFENCES.equals(requestType)) { + startGeoMonitoring(); + + } else if (GoogleApiClientRequestType.REMOVE_GEOFENCES.equals(requestType)) { + stopGeoMonitoring(); + } } @Override diff --git a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/GeofencingConsistencyReceiver.java b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/GeofencingConsistencyReceiver.java index 204134c7..ff8c1311 100644 --- a/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/GeofencingConsistencyReceiver.java +++ b/infobip-mobile-messaging-android-sdk/src/main/java/org/infobip/mobile/messaging/geo/GeofencingConsistencyReceiver.java @@ -3,27 +3,34 @@ import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.location.LocationManager; +import android.net.Uri; +import android.support.v4.content.WakefulBroadcastReceiver; + +import com.google.android.gms.common.GoogleApiAvailability; import org.infobip.mobile.messaging.MobileMessagingCore; import org.infobip.mobile.messaging.MobileMessagingLogger; +import java.util.Date; + /** * Created by tjuric on 20/02/17. */ -public class GeofencingConsistencyReceiver extends BroadcastReceiver { +public class GeofencingConsistencyReceiver extends WakefulBroadcastReceiver { public static final String NETWORK_PROVIDER_ENABLED_ACTION = "org.infobip.mobile.messaging.geo.intent.NETWORK_PROVIDER_ENABLED"; public static final String SCHEDULED_GEO_REFRESH_ACTION = "org.infobip.mobile.messaging.geo.intent.SCHEDULED_GEO_REFRESH"; + public static final String SCHEDULED_GEO_EXPIRE_ACTION = "org.infobip.mobile.messaging.geo.intent.SCHEDULED_GEO_EXPIRE_ACTION"; @Override public void onReceive(final Context context, final Intent intent) { String action = intent.getAction(); if (action != null) { MobileMessagingLogger.i(String.format("[%s]", action)); + final LocationManager lm = (LocationManager) context.getSystemService(Service.LOCATION_SERVICE); switch (action) { /** @@ -37,25 +44,53 @@ public void onReceive(final Context context, final Intent intent) { * be removed and an intent is generated by the provided pending intent. */ case LocationManager.PROVIDERS_CHANGED_ACTION: - final LocationManager lm = (LocationManager) context.getSystemService(Service.LOCATION_SERVICE); if (lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { - final AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - final Intent locationSendingIntent = new Intent(context, GeofencingConsistencyReceiver.class); - locationSendingIntent.setAction(NETWORK_PROVIDER_ENABLED_ACTION); - final PendingIntent pi = PendingIntent.getBroadcast(context, 0, locationSendingIntent, 0); - mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 15 * 1000, pi); + Date triggerDate = new Date(System.currentTimeMillis() + 15 * 1000); + scheduleConsistencyAlarm(context, AlarmManager.RTC, triggerDate, NETWORK_PROVIDER_ENABLED_ACTION, 0); } break; /** * NETWORK_PROVIDER_ENABLED_ACTION - scheduled 15 seconds after NETWORK_PROVIDER is enabled. Starts monitoring geofences from storage if geo is enabled. - * SCHEDULED_GEO_REFRESH_ACTION - scheduled to start after geofence expires + * SCHEDULED_GEO_REFRESH_ACTION - scheduled to start when campaign needs to be started and area monitored */ case NETWORK_PROVIDER_ENABLED_ACTION: case SCHEDULED_GEO_REFRESH_ACTION: - MobileMessagingCore.getInstance(context).startGeoMonitoringIfNecessary(); + MobileMessagingCore.getInstance(context).setAllActiveGeoAreasMonitored(false); + + if (lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { + MobileMessagingCore.getInstance(context).startGeoMonitoringIfNecessary(); + } + break; + + case Intent.ACTION_PACKAGE_DATA_CLEARED: + Uri data = intent.getData(); + if (data != null && GoogleApiAvailability.GOOGLE_PLAY_SERVICES_PACKAGE.equals(data.getSchemeSpecificPart())) { + //we want geo to start monitoring areas from scratch + MobileMessagingCore.getInstance(context).setAllActiveGeoAreasMonitored(false); + + if (lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { + MobileMessagingCore.getInstance(context).startGeoMonitoringIfNecessary(); + } + } + break; + + case SCHEDULED_GEO_EXPIRE_ACTION: + MobileMessagingCore.getInstance(context).removeExpiredAreas(); break; } } } + + public static void scheduleConsistencyAlarm(Context context, int alarmType, Date when, String action, int flags) { + if (when == null) { + return; + } + + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, GeofencingConsistencyReceiver.class); + intent.setAction(action); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, flags); + alarmManager.set(alarmType, when.getTime(), pendingIntent); + } } \ No newline at end of file