diff --git a/OneSignalSDK.jar b/OneSignalSDK.jar index 6bfaea29dd..f99bb5e95e 100644 Binary files a/OneSignalSDK.jar and b/OneSignalSDK.jar differ diff --git a/OneSignalSDK/onesignal/consumer-proguard-rules.pro b/OneSignalSDK/onesignal/consumer-proguard-rules.pro index 880bd86e4f..58c529fb9c 100644 --- a/OneSignalSDK/onesignal/consumer-proguard-rules.pro +++ b/OneSignalSDK/onesignal/consumer-proguard-rules.pro @@ -33,7 +33,7 @@ -keep class com.onesignal.shortcutbadger.impl.AdwHomeBadger { (...); } -keep class com.onesignal.shortcutbadger.impl.ApexHomeBadger { (...); } --keep class com.onesignal.shortcutbadger.impl.AsusHomeLauncher { (...); } +-keep class com.onesignal.shortcutbadger.impl.AsusHomeBadger { (...); } -keep class com.onesignal.shortcutbadger.impl.DefaultBadger { (...); } -keep class com.onesignal.shortcutbadger.impl.EverythingMeHomeBadger { (...); } -keep class com.onesignal.shortcutbadger.impl.HuaweiHomeBadger { (...); } diff --git a/OneSignalSDK/onesignal/maven-push.gradle b/OneSignalSDK/onesignal/maven-push.gradle index c958b25110..1e56f71dfe 100644 --- a/OneSignalSDK/onesignal/maven-push.gradle +++ b/OneSignalSDK/onesignal/maven-push.gradle @@ -24,7 +24,7 @@ class Global { static def POM_NAME = 'OneSignal' static def POM_ARTIFACT_ID = 'OneSignal' static def POM_PACKAGING = 'aar' - static def VERSION_NAME = '3.15.1-OS' + static def VERSION_NAME = '3.15.5-OS' static def GROUP_ID = 'com.onesignal' static def POM_DESCRIPTION = 'OneSignal Android SDK' diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/ActivityLifecycleHandler.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/ActivityLifecycleHandler.java index 088ed13f97..dfe99113ba 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/ActivityLifecycleHandler.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/ActivityLifecycleHandler.java @@ -50,7 +50,10 @@ abstract static class ActivityAvailableListener { void available(@NonNull Activity activity) { } - void stopped(WeakReference reference) { + void stopped() { + } + + void lostFocus() { } } @@ -145,7 +148,7 @@ static void onActivityStopped(Activity activity) { } for (Map.Entry entry : sActivityAvailableListeners.entrySet()) { - entry.getValue().stopped(new WeakReference<>(activity)); + entry.getValue().stopped(); } logCurActivity(); @@ -185,7 +188,7 @@ private static void onOrientationChanged() { // Remove view handleLostFocus(); for (Map.Entry entry : sActivityAvailableListeners.entrySet()) { - entry.getValue().stopped(new WeakReference<>(curActivity)); + entry.getValue().stopped(); } // Show view @@ -260,6 +263,9 @@ public void run() { return; backgrounded = true; + for (Map.Entry entry : sActivityAvailableListeners.entrySet()) { + entry.getValue().lostFocus(); + } OneSignal.onAppLostFocus(); completed = true; } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/BadgeCountUpdater.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/BadgeCountUpdater.java index c57096ba3e..1f64e6807b 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/BadgeCountUpdater.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/BadgeCountUpdater.java @@ -27,22 +27,19 @@ package com.onesignal; -import android.app.NotificationManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.os.Build; import android.os.Bundle; import android.service.notification.StatusBarNotification; import android.support.annotation.RequiresApi; +import com.onesignal.OneSignalDbContract.NotificationTable; import com.onesignal.shortcutbadger.ShortcutBadgeException; import com.onesignal.shortcutbadger.ShortcutBadger; -import com.onesignal.OneSignalDbContract.NotificationTable; - import static com.onesignal.NotificationLimitManager.MAX_NUMBER_OF_NOTIFICATIONS_STR; class BadgeCountUpdater { @@ -75,14 +72,14 @@ private static boolean areBadgesEnabled(Context context) { return areBadgeSettingsEnabled(context) && OSUtils.areNotificationsEnabled(context); } - static void update(SQLiteDatabase readableDb, Context context) { + static void update(OneSignalDb db, Context context) { if (!areBadgesEnabled(context)) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) updateStandard(context); else - updateFallback(readableDb, context); + updateFallback(db, context); } @RequiresApi(api = Build.VERSION_CODES.M) @@ -99,8 +96,8 @@ private static void updateStandard(Context context) { updateCount(runningCount, context); } - private static void updateFallback(SQLiteDatabase readableDb, Context context) { - Cursor cursor = readableDb.query( + private static void updateFallback(OneSignalDb db, Context context) { + Cursor cursor = db.query( NotificationTable.TABLE_NAME, null, OneSignalDbHelper.recentUninteractedWithNotificationsWhere().toString(), diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/FocusTimeController.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/FocusTimeController.java index 6c01afd207..85dfebe3da 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/FocusTimeController.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/FocusTimeController.java @@ -173,8 +173,10 @@ protected void saveInfluences(List influences) { protected boolean timeTypeApplies(@NonNull List influences) { for (OSInfluence influence : influences) { // Is true is at least one channel attributed the session - if (influence.getInfluenceType().isAttributed()) + if (influence.getInfluenceType().isAttributed()) { + OneSignal.Log(OneSignal.LOG_LEVEL.DEBUG, this.getClass().getSimpleName() + ":timeTypeApplies for influences: " + influences.toString() + " true"); return true; + } } return false; } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java index 3c20470c8c..b3fe4a7dc9 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/GenerateNotification.java @@ -27,20 +27,6 @@ package com.onesignal; -import java.lang.reflect.Field; -import java.math.BigInteger; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.security.SecureRandom; -import java.util.Random; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import android.R.drawable; import android.app.Activity; import android.app.AlertDialog; @@ -53,7 +39,6 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; @@ -68,6 +53,20 @@ import com.onesignal.OneSignalDbContract.NotificationTable; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Field; +import java.math.BigInteger; +import java.net.URL; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Random; + import static com.onesignal.OSUtils.getResourceString; class GenerateNotification { @@ -528,8 +527,6 @@ private static void createSummaryNotification(NotificationGenerationJob notifJob Cursor cursor = null; try { - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); - String[] retColumn = { NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID, NotificationTable.COLUMN_NAME_FULL_DATA, NotificationTable.COLUMN_NAME_IS_SUMMARY, @@ -545,7 +542,7 @@ private static void createSummaryNotification(NotificationGenerationJob notifJob if (!updateSummary && notifJob.getAndroidId() != -1) whereStr += " AND " + NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID + " <> " + notifJob.getAndroidId(); - cursor = readableDb.query( + cursor = dbHelper.query( NotificationTable.TABLE_NAME, retColumn, whereStr, @@ -769,28 +766,11 @@ private static Intent createBaseSummaryIntent(int summaryNotificationId, JSONObj private static void createSummaryIdDatabaseEntry(OneSignalDbHelper dbHelper, String group, int id) { // There currently isn't a visible notification from for this groupid. // Save the group summary notification id so it can be updated later. - SQLiteDatabase writableDb = null; - try { - writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - writableDb.beginTransaction(); - - ContentValues values = new ContentValues(); - values.put(NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID, id); - values.put(NotificationTable.COLUMN_NAME_GROUP_ID, group); - values.put(NotificationTable.COLUMN_NAME_IS_SUMMARY, 1); - writableDb.insertOrThrow(NotificationTable.TABLE_NAME, null, values); - writableDb.setTransactionSuccessful(); - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error adding summary notification record! ", t); - } finally { - if (writableDb != null) { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error closing transaction! ", t); - } - } - } + ContentValues values = new ContentValues(); + values.put(NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID, id); + values.put(NotificationTable.COLUMN_NAME_GROUP_ID, group); + values.put(NotificationTable.COLUMN_NAME_IS_SUMMARY, 1); + dbHelper.insertOrThrow(NotificationTable.TABLE_NAME, null, values); } // Keep 'throws Throwable' as 'onesignal_bgimage_notif_layout' may not be available diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/ImmutableJSONObject.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/ImmutableJSONObject.java new file mode 100644 index 0000000000..392ec1eb37 --- /dev/null +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/ImmutableJSONObject.java @@ -0,0 +1,89 @@ +/** + * Modified MIT License + *

+ * Copyright 2020 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.onesignal; + +import org.json.JSONException; +import org.json.JSONObject; + +class ImmutableJSONObject { + + private final JSONObject jsonObject; + + public ImmutableJSONObject() { + this.jsonObject = new JSONObject(); + } + + public ImmutableJSONObject(JSONObject jsonObject) { + this.jsonObject = jsonObject; + } + + public long getLong(String name) throws JSONException { + return jsonObject.getLong(name); + } + + public boolean has(String name) { + return jsonObject.has(name); + } + + public Object opt(String name) { + return jsonObject.opt(name); + } + + public String optString(String name) { + return jsonObject.optString(name); + } + + public String optString(String name, String fallback) { + return jsonObject.optString(name, fallback); + } + + public boolean optBoolean(String name) { + return jsonObject.optBoolean(name); + } + + public boolean optBoolean(String name, boolean fallback) { + return jsonObject.optBoolean(name, fallback); + } + + public long optLong(String name) { + return jsonObject.optLong(name); + } + + public int optInt(String name) { + return jsonObject.optInt(name); + } + + public int optInt(String name, int fallback) { + return jsonObject.optInt(name, fallback); + } + + public JSONObject optJSONObject(String name) { + return jsonObject.optJSONObject(name); + } + +} diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/InAppMessageView.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/InAppMessageView.java index 3386e86fb9..8587f60ae9 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/InAppMessageView.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/InAppMessageView.java @@ -7,6 +7,7 @@ import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; +import android.os.Build; import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -43,6 +44,8 @@ */ class InAppMessageView { + private static final String IN_APP_MESSAGE_CARD_VIEW_TAG = "IN_APP_MESSAGE_CARD_VIEW_TAG"; + private static final int ACTIVITY_BACKGROUND_COLOR_EMPTY = Color.parseColor("#00000000"); private static final int ACTIVITY_BACKGROUND_COLOR_FULL = Color.parseColor("#BB000000"); @@ -121,8 +124,8 @@ void updateHeight(final int pageHeight) { public void run() { if (webView == null) { OneSignal.onesignalLog( - OneSignal.LOG_LEVEL.WARN, - "WebView height update skipped, new height will be used once it is displayed."); + OneSignal.LOG_LEVEL.WARN, + "WebView height update skipped, new height will be used once it is displayed."); return; } @@ -156,10 +159,10 @@ void showInAppMessageView(Activity currentActivity) { LinearLayout.LayoutParams linearLayoutParams = hasBackground ? createParentLinearLayoutParams() : null; showDraggableView( - displayLocation, - webViewLayoutParams, - linearLayoutParams, - createDraggableLayoutParams(pageHeight, displayLocation) + displayLocation, + webViewLayoutParams, + linearLayoutParams, + createDraggableLayoutParams(pageHeight, displayLocation) ); } @@ -227,7 +230,7 @@ private void showDraggableView(final WebViewManager.Position displayLocation, @Override public void run() { if (webView == null) - return; + return; webView.setLayoutParams(relativeLayoutParams); @@ -241,51 +244,51 @@ public void run() { messageController.onMessageWasShown(); } - startDismissTimerIfNeeded(); - } + startDismissTimerIfNeeded(); + } }); } - /** - * Create a new Android PopupWindow that draws over the current Activity - * - * @param parentRelativeLayout root layout to attach to the pop up window - */ + /** + * Create a new Android PopupWindow that draws over the current Activity + * + * @param parentRelativeLayout root layout to attach to the pop up window + */ private void createPopupWindow(@NonNull RelativeLayout parentRelativeLayout) { - popupWindow = new PopupWindow( - parentRelativeLayout, - hasBackground ? WindowManager.LayoutParams.MATCH_PARENT : pageWidth, - hasBackground ? WindowManager.LayoutParams.MATCH_PARENT : WindowManager.LayoutParams.WRAP_CONTENT - ); - popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - popupWindow.setTouchable(true); - - int gravity = 0; - if (!hasBackground) { - switch (displayLocation) { - case TOP_BANNER: - gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; - break; - case BOTTOM_BANNER: - gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; - break; - } - } - - // Using this instead of TYPE_APPLICATION_PANEL so the layout background does not get - // cut off in immersive mode. - PopupWindowCompat.setWindowLayoutType( - popupWindow, - WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG - ); - - popupWindow.showAtLocation( - currentActivity.getWindow().getDecorView().getRootView(), - gravity, - 0, - 0 - ); - } + popupWindow = new PopupWindow( + parentRelativeLayout, + hasBackground ? WindowManager.LayoutParams.MATCH_PARENT : pageWidth, + hasBackground ? WindowManager.LayoutParams.MATCH_PARENT : WindowManager.LayoutParams.WRAP_CONTENT + ); + popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + popupWindow.setTouchable(true); + + int gravity = 0; + if (!hasBackground) { + switch (displayLocation) { + case TOP_BANNER: + gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; + break; + case BOTTOM_BANNER: + gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; + break; + } + } + + // Using this instead of TYPE_APPLICATION_PANEL so the layout background does not get + // cut off in immersive mode. + PopupWindowCompat.setWindowLayoutType( + popupWindow, + WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG + ); + + popupWindow.showAtLocation( + currentActivity.getWindow().getDecorView().getRootView(), + gravity, + 0, + 0 + ); + } private void setUpParentLinearLayout(Context context) { parentRelativeLayout = new RelativeLayout(context); @@ -320,9 +323,10 @@ public void onDragEnd() { }); if (webView.getParent() != null) - ((ViewGroup) webView.getParent()).removeAllViews(); + ((ViewGroup) webView.getParent()).removeAllViews(); CardView cardView = createCardView(context); + cardView.setTag(IN_APP_MESSAGE_CARD_VIEW_TAG); cardView.addView(webView); draggableRelativeLayout.setPadding(MARGIN_PX_SIZE, MARGIN_PX_SIZE, MARGIN_PX_SIZE, MARGIN_PX_SIZE); @@ -346,21 +350,27 @@ private CardView createCardView(Context context) { CardView cardView = new CardView(context); int height = displayLocation == WebViewManager.Position.FULL_SCREEN ? - ViewGroup.LayoutParams.MATCH_PARENT : - ViewGroup.LayoutParams.WRAP_CONTENT; + ViewGroup.LayoutParams.MATCH_PARENT : + ViewGroup.LayoutParams.WRAP_CONTENT; RelativeLayout.LayoutParams cardViewLayoutParams = new RelativeLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - height + ViewGroup.LayoutParams.MATCH_PARENT, + height ); cardViewLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); cardView.setLayoutParams(cardViewLayoutParams); - cardView.setRadius(dpToPx(8)); - cardView.setCardElevation(dpToPx(5)); + // Set the initial elevation of the CardView to 0dp if using Android 6 API 23 + // Fixes bug when animating a elevated CardView class + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) + cardView.setCardElevation(0); + else + cardView.setCardElevation(dpToPx(5)); + cardView.setRadius(dpToPx(8)); cardView.setClipChildren(false); cardView.setClipToPadding(false); cardView.setPreventCornerOverlap(false); + return cardView; } @@ -369,10 +379,10 @@ private CardView createCardView(Context context) { */ private void startDismissTimerIfNeeded() { if (dismissDuration <= 0) - return; + return; if (scheduleDismissRunnable != null) - return; + return; scheduleDismissRunnable = new Runnable() { public void run() { @@ -431,7 +441,7 @@ public void run() { else { cleanupViewsAfterDismiss(); if (callback != null) - callback.onComplete(); + callback.onComplete(); } } }, ACTIVITY_FINISH_AFTER_DISMISS_DELAY_MS); @@ -441,27 +451,27 @@ public void run() { * IAM has been fully dismissed, remove all views and call the onMessageWasDismissed callback */ private void cleanupViewsAfterDismiss() { - OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "InAppMessageView cleanupViewsAfterDismiss"); removeAllViews(); if (messageController != null) messageController.onMessageWasDismissed(); } - /** - * Remove all views and dismiss PopupWindow - */ + /** + * Remove all views and dismiss PopupWindow + */ void removeAllViews() { - if (scheduleDismissRunnable != null) { - // Dismissed before the dismiss delay - handler.removeCallbacks(scheduleDismissRunnable); - scheduleDismissRunnable = null; - } - if (draggableRelativeLayout != null) - draggableRelativeLayout.removeAllViews(); + OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "InAppMessageView removing views"); + if (scheduleDismissRunnable != null) { + // Dismissed before the dismiss delay + handler.removeCallbacks(scheduleDismissRunnable); + scheduleDismissRunnable = null; + } + if (draggableRelativeLayout != null) + draggableRelativeLayout.removeAllViews(); - if (popupWindow != null) - popupWindow.dismiss(); - dereferenceViews(); + if (popupWindow != null) + popupWindow.dismiss(); + dereferenceViews(); } /** @@ -475,24 +485,48 @@ private void dereferenceViews() { } private void animateInAppMessage(WebViewManager.Position displayLocation, View messageView, View backgroundView) { + final CardView messageViewCardView = messageView.findViewWithTag(IN_APP_MESSAGE_CARD_VIEW_TAG); + + Animation.AnimationListener cardViewAnimCallback = null; + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) + cardViewAnimCallback = createAnimationListenerForAndroidApi23Elevation(messageViewCardView); + // Based on the location of the in app message apply and animation to match switch (displayLocation) { case TOP_BANNER: - View topBannerMessageViewChild = ((ViewGroup) messageView).getChildAt(0); - animateTop(topBannerMessageViewChild, webView.getHeight()); + animateTop(messageViewCardView, webView.getHeight(), cardViewAnimCallback); break; case BOTTOM_BANNER: - View bottomBannerMessageViewChild = ((ViewGroup) messageView).getChildAt(0); - animateBottom(bottomBannerMessageViewChild, webView.getHeight()); + animateBottom(messageViewCardView, webView.getHeight(), cardViewAnimCallback); break; case CENTER_MODAL: case FULL_SCREEN: - animateCenter(messageView, backgroundView); + animateCenter(messageView, backgroundView, cardViewAnimCallback, null); break; } } - private void animateTop(View messageView, int height) { + private Animation.AnimationListener createAnimationListenerForAndroidApi23Elevation(final CardView messageViewCardView) { + return new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + // For Android 6 API 23 devices, waits until end of animation to set elevation of CardView class + messageViewCardView.setCardElevation(dpToPx(5)); + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }; + } + + private void animateTop(View messageView, int height, Animation.AnimationListener cardViewAnimCallback) { // Animate the message view from above the screen downward to the top OneSignalAnimate.animateViewByTranslation( messageView, @@ -500,11 +534,11 @@ private void animateTop(View messageView, int height) { 0f, IN_APP_BANNER_ANIMATION_DURATION_MS, new OneSignalBounceInterpolator(0.1, 8.0), - null) + cardViewAnimCallback) .start(); } - private void animateBottom(View messageView, int height) { + private void animateBottom(View messageView, int height, Animation.AnimationListener cardViewAnimCallback) { // Animate the message view from under the screen upward to the bottom OneSignalAnimate.animateViewByTranslation( messageView, @@ -512,17 +546,17 @@ private void animateBottom(View messageView, int height) { 0f, IN_APP_BANNER_ANIMATION_DURATION_MS, new OneSignalBounceInterpolator(0.1, 8.0), - null) + cardViewAnimCallback) .start(); } - private void animateCenter(View messageView, final View backgroundView) { + private void animateCenter(View messageView, final View backgroundView, Animation.AnimationListener cardViewAnimCallback, Animator.AnimatorListener backgroundAnimCallback) { // Animate the message view by scale since it settles at the center of the screen Animation messageAnimation = OneSignalAnimate.animateViewSmallToLarge( messageView, IN_APP_CENTER_ANIMATION_DURATION_MS, new OneSignalBounceInterpolator(0.1, 8.0), - null); + cardViewAnimCallback); // Animate background behind the message so it doesn't just show the dark transparency ValueAnimator backgroundAnimation = animateBackgroundColor( @@ -530,7 +564,7 @@ private void animateCenter(View messageView, final View backgroundView) { IN_APP_BACKGROUND_ANIMATION_DURATION_MS, ACTIVITY_BACKGROUND_COLOR_EMPTY, ACTIVITY_BACKGROUND_COLOR_FULL, - null); + backgroundAnimCallback); messageAnimation.start(); backgroundAnimation.start(); @@ -541,8 +575,8 @@ private void animateAndDismissLayout(View backgroundView, final WebViewManager.O @Override public void onAnimationEnd(Animator animation) { cleanupViewsAfterDismiss(); - if (callback != null) - callback.onComplete(); + if (callback != null) + callback.onComplete(); } }; @@ -564,4 +598,4 @@ private ValueAnimator animateBackgroundColor(View backgroundView, int duration, endColor, animCallback); } -} +} \ No newline at end of file diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/JSONUtils.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/JSONUtils.java index 599e6819f1..82a8349490 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/JSONUtils.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/JSONUtils.java @@ -130,7 +130,7 @@ static String toStringNE(JSONArray jsonArray) { return strArray + "]"; } - static JSONObject getJSONObjectWithoutBlankValues(JSONObject jsonObject, String getKey) { + static JSONObject getJSONObjectWithoutBlankValues(ImmutableJSONObject jsonObject, String getKey) { if (!jsonObject.has(getKey)) return null; diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationBundleProcessor.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationBundleProcessor.java index 4695f30c91..46df7378ad 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationBundleProcessor.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationBundleProcessor.java @@ -27,23 +27,22 @@ package com.onesignal; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import com.onesignal.OneSignalDbContract.NotificationTable; - import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.onesignal.OneSignalDbContract.NotificationTable; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import java.util.ArrayList; import java.util.Set; @@ -113,7 +112,7 @@ static int ProcessJobForDisplay(NotificationGenerationJob notifJob) { boolean doDisplay = shouldDisplayNotif(notifJob); if (doDisplay) - GenerateNotification.fromJsonPayload(notifJob); + GenerateNotification.fromJsonPayload(notifJob); if (!notifJob.restoring && !notifJob.isInAppPreviewPush) { processNotification(notifJob, false); @@ -121,7 +120,9 @@ static int ProcessJobForDisplay(NotificationGenerationJob notifJob) { JSONObject jsonObject = new JSONObject(notifJob.jsonPayload.toString()); jsonObject.put(BUNDLE_KEY_ANDROID_NOTIFICATION_ID, notifJob.getAndroidId()); OneSignal.handleNotificationReceived(newJsonArray(jsonObject), true, notifJob.showAsAlert); - } catch(Throwable t) {} + } catch (JSONException t) { + t.printStackTrace(); + } } return notifJob.getAndroidId(); @@ -180,66 +181,48 @@ private static void saveNotification(NotificationGenerationJob notifiJob, boolea JSONObject customJSON = getCustomJSONObject(notifiJob.jsonPayload); OneSignalDbHelper dbHelper = OneSignalDbHelper.getInstance(notifiJob.context); - SQLiteDatabase writableDb = null; - try { - writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - - writableDb.beginTransaction(); - - // Count any notifications with duplicated android notification ids as dismissed. - // -1 is used to note never displayed - if (notifiJob.isNotificationToDisplay()) { - String whereStr = NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID + " = " + notifiJob.getAndroidIdWithoutCreate(); - - ContentValues values = new ContentValues(); - values.put(NotificationTable.COLUMN_NAME_DISMISSED, 1); - - writableDb.update(NotificationTable.TABLE_NAME, values, whereStr, null); - BadgeCountUpdater.update(writableDb, context); - } + // Count any notifications with duplicated android notification ids as dismissed. + // -1 is used to note never displayed + if (notifiJob.isNotificationToDisplay()) { + String whereStr = NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID + " = " + notifiJob.getAndroidIdWithoutCreate(); - // Save just received notification to DB ContentValues values = new ContentValues(); - values.put(NotificationTable.COLUMN_NAME_NOTIFICATION_ID, customJSON.optString("i")); - if (jsonPayload.has("grp")) - values.put(NotificationTable.COLUMN_NAME_GROUP_ID, jsonPayload.optString("grp")); - if (jsonPayload.has("collapse_key") && !"do_not_collapse".equals(jsonPayload.optString("collapse_key"))) - values.put(NotificationTable.COLUMN_NAME_COLLAPSE_ID, jsonPayload.optString("collapse_key")); - - values.put(NotificationTable.COLUMN_NAME_OPENED, opened ? 1 : 0); - if (!opened) - values.put(NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID, notifiJob.getAndroidIdWithoutCreate()); - - if (notifiJob.getTitle() != null) - values.put(NotificationTable.COLUMN_NAME_TITLE, notifiJob.getTitle().toString()); - if (notifiJob.getBody() != null) - values.put(NotificationTable.COLUMN_NAME_MESSAGE, notifiJob.getBody().toString()); - - // Set expire_time - long sentTime = jsonPayload.optLong("google.sent_time", SystemClock.currentThreadTimeMillis()) / 1_000L; - int ttl = jsonPayload.optInt("google.ttl", NotificationRestorer.DEFAULT_TTL_IF_NOT_IN_PAYLOAD); - long expireTime = sentTime + ttl; - values.put(NotificationTable.COLUMN_NAME_EXPIRE_TIME, expireTime); - - values.put(NotificationTable.COLUMN_NAME_FULL_DATA, jsonPayload.toString()); - - writableDb.insertOrThrow(NotificationTable.TABLE_NAME, null, values); - - if (!opened) - BadgeCountUpdater.update(writableDb, context); - writableDb.setTransactionSuccessful(); - } catch (Exception e) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error saving notification record! ", e); - } finally { - if (writableDb != null) { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error closing transaction! ", t); - } - } + values.put(NotificationTable.COLUMN_NAME_DISMISSED, 1); + + dbHelper.update(NotificationTable.TABLE_NAME, values, whereStr, null); + BadgeCountUpdater.update(dbHelper, context); } + + // Save just received notification to DB + ContentValues values = new ContentValues(); + values.put(NotificationTable.COLUMN_NAME_NOTIFICATION_ID, customJSON.optString("i")); + if (jsonPayload.has("grp")) + values.put(NotificationTable.COLUMN_NAME_GROUP_ID, jsonPayload.optString("grp")); + if (jsonPayload.has("collapse_key") && !"do_not_collapse".equals(jsonPayload.optString("collapse_key"))) + values.put(NotificationTable.COLUMN_NAME_COLLAPSE_ID, jsonPayload.optString("collapse_key")); + + values.put(NotificationTable.COLUMN_NAME_OPENED, opened ? 1 : 0); + if (!opened) + values.put(NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID, notifiJob.getAndroidIdWithoutCreate()); + + if (notifiJob.getTitle() != null) + values.put(NotificationTable.COLUMN_NAME_TITLE, notifiJob.getTitle().toString()); + if (notifiJob.getBody() != null) + values.put(NotificationTable.COLUMN_NAME_MESSAGE, notifiJob.getBody().toString()); + + // Set expire_time + long sentTime = jsonPayload.optLong("google.sent_time", SystemClock.currentThreadTimeMillis()) / 1_000L; + int ttl = jsonPayload.optInt("google.ttl", NotificationRestorer.DEFAULT_TTL_IF_NOT_IN_PAYLOAD); + long expireTime = sentTime + ttl; + values.put(NotificationTable.COLUMN_NAME_EXPIRE_TIME, expireTime); + + values.put(NotificationTable.COLUMN_NAME_FULL_DATA, jsonPayload.toString()); + + dbHelper.insertOrThrow(NotificationTable.TABLE_NAME, null, values); + + if (!opened) + BadgeCountUpdater.update(dbHelper, context); } catch (JSONException e) { e.printStackTrace(); } @@ -252,31 +235,12 @@ static void markRestoredNotificationAsDismissed(NotificationGenerationJob notifi String whereStr = NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID + " = " + notifiJob.getAndroidIdWithoutCreate(); OneSignalDbHelper dbHelper = OneSignalDbHelper.getInstance(notifiJob.context); - SQLiteDatabase writableDb = null; - - try { - writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - writableDb.beginTransaction(); - - ContentValues values = new ContentValues(); - values.put(NotificationTable.COLUMN_NAME_DISMISSED, 1); - writableDb.update(NotificationTable.TABLE_NAME, values, whereStr, null); - BadgeCountUpdater.update(writableDb, notifiJob.context); + ContentValues values = new ContentValues(); + values.put(NotificationTable.COLUMN_NAME_DISMISSED, 1); - writableDb.setTransactionSuccessful(); - - } catch (Exception e) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error saving notification record! ", e); - } finally { - if (writableDb != null) { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error closing transaction! ", t); - } - } - } + dbHelper.update(NotificationTable.TABLE_NAME, values, whereStr, null); + BadgeCountUpdater.update(dbHelper, notifiJob.context); } static @NonNull JSONObject bundleAsJSONObject(Bundle bundle) { @@ -385,7 +349,7 @@ static OSNotificationPayload OSNotificationPayloadFrom(JSONObject currentJsonPay } catch (Throwable t) { OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error assigning OSNotificationPayload.backgroundImageLayout values!", t); } - } catch (Throwable t) { + } catch (JSONException t) { OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error assigning OSNotificationPayload values!", t); } @@ -429,32 +393,22 @@ private static void processCollapseKey(NotificationGenerationJob notifJob) { return; String collapse_id = notifJob.jsonPayload.optString("collapse_key"); - OneSignalDbHelper dbHelper = OneSignalDbHelper.getInstance(notifJob.context); - Cursor cursor = null; - - try { - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); - cursor = readableDb.query( - NotificationTable.TABLE_NAME, - new String[]{NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID}, // retColumn - NotificationTable.COLUMN_NAME_COLLAPSE_ID + " = ? AND " + - NotificationTable.COLUMN_NAME_DISMISSED + " = 0 AND " + - NotificationTable.COLUMN_NAME_OPENED + " = 0 ", - new String[] {collapse_id}, - null, null, null); - - if (cursor.moveToFirst()) { - int androidNotificationId = cursor.getInt(cursor.getColumnIndex(NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID)); - notifJob.setAndroidIdWithOutOverriding(androidNotificationId); - } - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Could not read DB to find existing collapse_key!", t); - } - finally { - if (cursor != null && !cursor.isClosed()) - cursor.close(); + Cursor cursor = dbHelper.query( + NotificationTable.TABLE_NAME, + new String[]{NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID}, // retColumn + NotificationTable.COLUMN_NAME_COLLAPSE_ID + " = ? AND " + + NotificationTable.COLUMN_NAME_DISMISSED + " = 0 AND " + + NotificationTable.COLUMN_NAME_OPENED + " = 0 ", + new String[]{collapse_id}, + null, null, null); + + if (cursor.moveToFirst()) { + int androidNotificationId = cursor.getInt(cursor.getColumnIndex(NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID)); + notifJob.setAndroidIdWithOutOverriding(androidNotificationId); } + + cursor.close(); } // Process bundle passed from gcm / adm broadcast receiver. @@ -476,7 +430,7 @@ private static void processCollapseKey(NotificationGenerationJob notifJob) { // If app is in focus display the IAMs preview now if (OneSignal.isAppActive()) { result.inAppPreviewShown = true; - OSInAppMessageController.getController().displayPreviewMessage(previewUUID); + OneSignal.getInAppMessageController().displayPreviewMessage(previewUUID); } // Return early, we don't want the extender service or etc. to fire for IAM previews return result; diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationLimitManager.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationLimitManager.java index 1fddb2d6d2..b4b5e0afe1 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationLimitManager.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationLimitManager.java @@ -1,21 +1,18 @@ package com.onesignal; import android.app.Notification; -import android.app.NotificationManager; import android.content.Context; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.os.Build; import android.service.notification.StatusBarNotification; import android.support.annotation.RequiresApi; -import java.util.ArrayList; +import com.onesignal.OneSignalDbContract.NotificationTable; + import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; -import com.onesignal.OneSignalDbContract.NotificationTable; - // Ensures old notifications are cleared up to a limit before displaying new ones class NotificationLimitManager { @@ -84,8 +81,7 @@ static void clearOldestOverLimitFallback(Context context, int notifsToMakeRoomFo Cursor cursor = null; try { - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); - cursor = readableDb.query( + cursor = dbHelper.query( NotificationTable.TABLE_NAME, new String[] { NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID }, OneSignalDbHelper.recentUninteractedWithNotificationsWhere().toString(), diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationOpenedProcessor.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationOpenedProcessor.java index 7f804df551..ce061a3904 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationOpenedProcessor.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationOpenedProcessor.java @@ -32,14 +32,14 @@ import android.content.Context; import android.content.Intent; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.support.annotation.NonNull; import android.os.Build; +import android.support.annotation.NonNull; import android.support.v4.app.NotificationManagerCompat; import com.onesignal.OneSignalDbContract.NotificationTable; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import static com.onesignal.GenerateNotification.BUNDLE_KEY_ANDROID_NOTIFICATION_ID; @@ -92,41 +92,24 @@ static void processIntent(Context context, Intent intent) { jsonData.put(BUNDLE_KEY_ANDROID_NOTIFICATION_ID, intent.getIntExtra(BUNDLE_KEY_ANDROID_NOTIFICATION_ID, 0)); intent.putExtra(BUNDLE_KEY_ONESIGNAL_DATA, jsonData.toString()); dataArray = NotificationBundleProcessor.newJsonArray(new JSONObject(intent.getStringExtra(BUNDLE_KEY_ONESIGNAL_DATA))); - } catch (Throwable t) { - t.printStackTrace(); + } catch (JSONException e) { + e.printStackTrace(); } } OneSignalDbHelper dbHelper = OneSignalDbHelper.getInstance(context); - SQLiteDatabase writableDb = null; - try { - writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - writableDb.beginTransaction(); + // We just opened a summary notification. + if (!dismissed && summaryGroup != null) + addChildNotifications(dataArray, summaryGroup, dbHelper); - // We just opened a summary notification. - if (!dismissed && summaryGroup != null) - addChildNotifications(dataArray, summaryGroup, writableDb); + markNotificationsConsumed(context, intent, dbHelper, dismissed); - markNotificationsConsumed(context, intent, writableDb, dismissed); - - // Notification is not a summary type but a single notification part of a group. - if (summaryGroup == null) { - String group = intent.getStringExtra("grp"); - if (group != null) - NotificationSummaryManager.updateSummaryNotificationAfterChildRemoved(context, writableDb, group, dismissed); - } - writableDb.setTransactionSuccessful(); - } catch (Exception e) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error processing notification open or dismiss record! ", e); - } finally { - if (writableDb != null) { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error closing transaction! ", t); - } - } + // Notification is not a summary type but a single notification part of a group. + if (summaryGroup == null) { + String group = intent.getStringExtra("grp"); + if (group != null) + NotificationSummaryManager.updateSummaryNotificationAfterChildRemoved(context, dbHelper, group, dismissed); } if (!dismissed) @@ -140,11 +123,11 @@ static boolean handleIAMPreviewOpen(@NonNull Context context, @NonNull JSONObjec return false; OneSignal.startOrResumeApp(context); - OSInAppMessageController.getController().displayPreviewMessage(previewUUID); + OneSignal.getInAppMessageController().displayPreviewMessage(previewUUID); return true; } - private static void addChildNotifications(JSONArray dataArray, String summaryGroup, SQLiteDatabase writableDb) { + private static void addChildNotifications(JSONArray dataArray, String summaryGroup, OneSignalDbHelper writableDb) { String[] retColumn = { NotificationTable.COLUMN_NAME_FULL_DATA }; String[] whereArgs = { summaryGroup }; @@ -164,7 +147,7 @@ private static void addChildNotifications(JSONArray dataArray, String summaryGro try { String jsonStr = cursor.getString(cursor.getColumnIndex(NotificationTable.COLUMN_NAME_FULL_DATA)); dataArray.put(new JSONObject(jsonStr)); - } catch (Throwable t) { + } catch (JSONException e) { OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Could not parse JSON of sub notification in group: " + summaryGroup); } } while (cursor.moveToNext()); @@ -173,7 +156,7 @@ private static void addChildNotifications(JSONArray dataArray, String summaryGro cursor.close(); } - private static void markNotificationsConsumed(Context context, Intent intent, SQLiteDatabase writableDb, boolean dismissed) { + private static void markNotificationsConsumed(Context context, Intent intent, OneSignalDbHelper writableDb, boolean dismissed) { String summaryGroup = intent.getStringExtra("summary"); String whereStr; String[] whereArgs = null; @@ -212,7 +195,7 @@ private static void markNotificationsConsumed(Context context, Intent intent, SQ /** * Handles clearing the status bar notifications when opened */ - private static void clearStatusBarNotifications(Context context, SQLiteDatabase writableDb, String summaryGroup) { + private static void clearStatusBarNotifications(Context context, OneSignalDbHelper writableDb, String summaryGroup) { // Handling for clearing the notification when opened if (summaryGroup != null) NotificationSummaryManager.clearNotificationOnSummaryClick(context, writableDb, summaryGroup); diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationRestorer.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationRestorer.java index 01db9e36df..21af195eb9 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationRestorer.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationRestorer.java @@ -35,7 +35,6 @@ import android.content.Context; import android.content.Intent; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.os.Build; import android.os.Process; import android.service.notification.StatusBarNotification; @@ -137,8 +136,7 @@ private static void queryAndRestoreNotificationsAndBadgeCount( Cursor cursor = null; try { - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); - cursor = readableDb.query( + cursor = dbHelper.query( NotificationTable.TABLE_NAME, COLUMNS_FOR_RESTORE, dbQuerySelection.toString(), @@ -149,7 +147,7 @@ private static void queryAndRestoreNotificationsAndBadgeCount( NotificationLimitManager.MAX_NUMBER_OF_NOTIFICATIONS_STR // limit ); showNotificationsFromCursor(context, cursor, DELAY_BETWEEN_NOTIFICATION_RESTORES_MS); - BadgeCountUpdater.update(readableDb, context); + BadgeCountUpdater.update(dbHelper, context); } catch (Throwable t) { OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error restoring notification records! ", t); } finally { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationSummaryManager.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationSummaryManager.java index cff4c27292..d80fdff4e4 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationSummaryManager.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationSummaryManager.java @@ -4,20 +4,17 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.os.Build; -import android.support.annotation.RequiresApi; + +import com.onesignal.OneSignalDbContract.NotificationTable; import org.json.JSONException; import org.json.JSONObject; -import com.onesignal.OneSignalDbContract.NotificationTable; - class NotificationSummaryManager { // A notification was just dismissed, check if it was a child to a summary notification and update it. - static void updatePossibleDependentSummaryOnDismiss(Context context, SQLiteDatabase writableDb, int androidNotificationId) { - Cursor cursor = writableDb.query( + static void updatePossibleDependentSummaryOnDismiss(Context context, OneSignalDb db, int androidNotificationId) { + Cursor cursor = db.query( NotificationTable.TABLE_NAME, new String[] { NotificationTable.COLUMN_NAME_GROUP_ID }, // retColumn NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID + " = " + androidNotificationId, @@ -28,17 +25,17 @@ static void updatePossibleDependentSummaryOnDismiss(Context context, SQLiteDatab cursor.close(); if (group != null) - updateSummaryNotificationAfterChildRemoved(context, writableDb, group, true); + updateSummaryNotificationAfterChildRemoved(context, db, group, true); } else cursor.close(); } // Called from an opened / dismissed / cancel event of a single notification to update it's parent the summary notification. - static void updateSummaryNotificationAfterChildRemoved(Context context, SQLiteDatabase writableDb, String group, boolean dismissed) { + static void updateSummaryNotificationAfterChildRemoved(Context context, OneSignalDb db, String group, boolean dismissed) { Cursor cursor = null; try { - cursor = internalUpdateSummaryNotificationAfterChildRemoved(context, writableDb, group, dismissed); + cursor = internalUpdateSummaryNotificationAfterChildRemoved(context, db, group, dismissed); } catch (Throwable t) { OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error running updateSummaryNotificationAfterChildRemoved!", t); } finally { @@ -47,8 +44,8 @@ static void updateSummaryNotificationAfterChildRemoved(Context context, SQLiteDa } } - private static Cursor internalUpdateSummaryNotificationAfterChildRemoved(Context context, SQLiteDatabase writableDb, String group, boolean dismissed) { - Cursor cursor = writableDb.query( + private static Cursor internalUpdateSummaryNotificationAfterChildRemoved(Context context, OneSignalDb db, String group, boolean dismissed) { + Cursor cursor = db.query( NotificationTable.TABLE_NAME, new String[] { NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID, // return columns NotificationTable.COLUMN_NAME_CREATED_TIME }, @@ -68,7 +65,7 @@ private static Cursor internalUpdateSummaryNotificationAfterChildRemoved(Context if (notifsInGroup == 0) { cursor.close(); - Integer androidNotifId = getSummaryNotificationId(writableDb, group); + Integer androidNotifId = getSummaryNotificationId(db, group); if (androidNotifId == null) return cursor; @@ -79,7 +76,7 @@ private static Cursor internalUpdateSummaryNotificationAfterChildRemoved(Context // Mark the summary notification as opened or dismissed. ContentValues values = new ContentValues(); values.put(dismissed ? NotificationTable.COLUMN_NAME_DISMISSED : NotificationTable.COLUMN_NAME_OPENED, 1); - writableDb.update(NotificationTable.TABLE_NAME, + db.update(NotificationTable.TABLE_NAME, values, NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID + " = " + androidNotifId, null); @@ -91,7 +88,7 @@ private static Cursor internalUpdateSummaryNotificationAfterChildRemoved(Context // only have one notification now. if (notifsInGroup == 1) { cursor.close(); - Integer androidNotifId = getSummaryNotificationId(writableDb, group); + Integer androidNotifId = getSummaryNotificationId(db, group); if (androidNotifId == null) return cursor; restoreSummary(context, group); @@ -107,7 +104,7 @@ private static Cursor internalUpdateSummaryNotificationAfterChildRemoved(Context Long datetime = cursor.getLong(cursor.getColumnIndex(NotificationTable.COLUMN_NAME_CREATED_TIME)); cursor.close(); - Integer androidNotifId = getSummaryNotificationId(writableDb, group); + Integer androidNotifId = getSummaryNotificationId(db, group); if (androidNotifId == null) return cursor; @@ -133,8 +130,7 @@ private static void restoreSummary(Context context, String group) { String[] whereArgs = { group }; try { - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); - cursor = readableDb.query( + cursor = dbHelper.query( NotificationTable.TABLE_NAME, NotificationRestorer.COLUMNS_FOR_RESTORE, NotificationTable.COLUMN_NAME_GROUP_ID + " = ? AND " + @@ -156,7 +152,7 @@ private static void restoreSummary(Context context, String group) { } } - static Integer getSummaryNotificationId(SQLiteDatabase writableDb, String group) { + static Integer getSummaryNotificationId(OneSignalDb db, String group) { Integer androidNotifId = null; Cursor cursor = null; @@ -168,7 +164,7 @@ static Integer getSummaryNotificationId(SQLiteDatabase writableDb, String group) try { // Get the Android Notification ID of the summary notification - cursor = writableDb.query( + cursor = db.query( NotificationTable.TABLE_NAME, new String[] { NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID }, // retColumn whereStr, @@ -197,14 +193,14 @@ static Integer getSummaryNotificationId(SQLiteDatabase writableDb, String group) /** * Clears notifications from the status bar based on a few parameters */ - static void clearNotificationOnSummaryClick(Context context, SQLiteDatabase db, String group) { + static void clearNotificationOnSummaryClick(Context context, OneSignalDbHelper dbHelper, String group) { // Obtain the group to clear notifications from - Integer groupId = NotificationSummaryManager.getSummaryNotificationId(db, group); + Integer groupId = NotificationSummaryManager.getSummaryNotificationId(dbHelper, group); boolean isGroupless = group.equals(OneSignalNotificationManager.getGrouplessSummaryKey()); NotificationManager notificationManager = OneSignalNotificationManager.getNotificationManager(context); // Obtain the most recent notification id - Integer mostRecentId = OneSignalNotificationManager.getMostRecentNotifIdFromGroup(db, group, isGroupless); + Integer mostRecentId = OneSignalNotificationManager.getMostRecentNotifIdFromGroup(dbHelper, group, isGroupless); if (mostRecentId != null) { boolean shouldDismissAll = OneSignal.getClearGroupSummaryClick(); if (shouldDismissAll) { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSDynamicTriggerController.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSDynamicTriggerController.java index 40bf55f68b..5e5ba69782 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSDynamicTriggerController.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSDynamicTriggerController.java @@ -9,6 +9,8 @@ class OSDynamicTriggerController { interface OSDynamicTriggerControllerObserver { + // Alerts the observer that a trigger evaluated to true + void messageDynamicTriggerCompleted(String triggerId); // Alerts the observer that a trigger timer has fired void messageTriggerConditionChanged(); } @@ -18,17 +20,16 @@ interface OSDynamicTriggerControllerObserver { private static final double REQUIRED_ACCURACY = 0.3; // Assume last time an In-App Message was displayed a very very long time ago. private static final long DEFAULT_LAST_IN_APP_TIME_AGO = 999_999; + private static Date sessionLaunchTime = new Date(); private final ArrayList scheduledMessages; - static Date sessionLaunchTime = new Date(); - OSDynamicTriggerController(OSDynamicTriggerControllerObserver triggerObserver) { scheduledMessages = new ArrayList<>(); observer = triggerObserver; } - boolean dynamicTriggerShouldFire(final OSTrigger trigger) { + boolean dynamicTriggerShouldFire(OSTrigger trigger) { if (trigger.value == null) return false; @@ -43,9 +44,9 @@ boolean dynamicTriggerShouldFire(final OSTrigger trigger) { currentTimeInterval = new Date().getTime() - sessionLaunchTime.getTime(); break; case TIME_SINCE_LAST_IN_APP: - if (OSInAppMessageController.getController().isInAppMessageShowing()) + if (OneSignal.getInAppMessageController().isInAppMessageShowing()) return false; - Date lastTimeAppDismissed = OSInAppMessageController.getController().lastTimeInAppDismissed; + Date lastTimeAppDismissed = OneSignal.getInAppMessageController().lastTimeInAppDismissed; if (lastTimeAppDismissed == null) currentTimeInterval = DEFAULT_LAST_IN_APP_TIME_AGO; else @@ -53,32 +54,40 @@ boolean dynamicTriggerShouldFire(final OSTrigger trigger) { break; } + final String triggerId = trigger.triggerId; long requiredTimeInterval = (long) (((Number) trigger.value).doubleValue() * 1_000); - if (evaluateTimeIntervalWithOperator(requiredTimeInterval, currentTimeInterval, trigger.operatorType)) + if (evaluateTimeIntervalWithOperator(requiredTimeInterval, currentTimeInterval, trigger.operatorType)) { + observer.messageDynamicTriggerCompleted(triggerId); return true; + } long offset = requiredTimeInterval - currentTimeInterval; if (offset <= 0L) return false; // Prevents re-scheduling timers for messages that we're already waiting on - if (scheduledMessages.contains(trigger.triggerId)) + if (scheduledMessages.contains(triggerId)) return false; OSDynamicTriggerTimer.scheduleTrigger(new TimerTask() { @Override public void run() { - scheduledMessages.remove(trigger.triggerId); + scheduledMessages.remove(triggerId); observer.messageTriggerConditionChanged(); } - }, trigger.triggerId, offset); + }, triggerId, offset); - scheduledMessages.add(trigger.triggerId); + scheduledMessages.add(triggerId); } return false; } + + static void resetSessionLaunchTime() { + sessionLaunchTime = new Date(); + } + private static boolean evaluateTimeIntervalWithOperator(double timeInterval, double currentTimeInterval, OSTriggerOperator operator) { switch (operator) { case LESS_THAN: diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSDynamicTriggerTimer.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSDynamicTriggerTimer.java index 189b932b5d..d6a9b8ed30 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSDynamicTriggerTimer.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSDynamicTriggerTimer.java @@ -7,6 +7,7 @@ // that schedules the timer. class OSDynamicTriggerTimer { static void scheduleTrigger(TimerTask task, String triggerId, long delay) { + OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "scheduleTrigger: " + triggerId + " delay: " + delay); Timer timer = new Timer("trigger_timer:" + triggerId); timer.schedule(task, delay); } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageController.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageController.java index e469ecfc18..d081791b81 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageController.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageController.java @@ -2,7 +2,6 @@ import android.app.AlertDialog; import android.content.DialogInterface; -import android.os.Build; import android.os.Process; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -18,6 +17,7 @@ import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -32,6 +32,7 @@ class OSInAppMessageController implements OSDynamicTriggerControllerObserver, OS public static final String IN_APP_MESSAGES_JSON_KEY = "in_app_messages"; private static final String OS_SAVE_IN_APP_MESSAGE = "OS_SAVE_IN_APP_MESSAGE"; + private static final Object LOCK = new Object(); OSTriggerController triggerController; private OSSystemConditionController systemConditionController; @@ -65,26 +66,9 @@ class OSInAppMessageController implements OSDynamicTriggerControllerObserver, OS private boolean inAppMessageShowing = false; @Nullable - Date lastTimeInAppDismissed; + Date lastTimeInAppDismissed = null; private int htmlNetworkRequestAttemptCount = 0; - @Nullable - private static OSInAppMessageController sharedInstance; - - public static synchronized OSInAppMessageController getController() { - OneSignalDbHelper dbHelper = OneSignal.getDBHelperInstance(); - - // Make sure only Android 4.4 devices and higher can use IAMs - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) { - sharedInstance = new OSInAppMessageDummyController(null); - } - - if (sharedInstance == null) - sharedInstance = new OSInAppMessageController(dbHelper); - - return sharedInstance; - } - protected OSInAppMessageController(OneSignalDbHelper dbHelper) { messages = new ArrayList<>(); dismissedMessages = OSUtils.newConcurrentSet(); @@ -135,27 +119,39 @@ protected void initRedisplayData(OneSignalDbHelper dbHelper) { OneSignal.Log(OneSignal.LOG_LEVEL.DEBUG, "redisplayedInAppMessages: " + redisplayedInAppMessages.toString()); } + void resetSessionLaunchTime() { + OSDynamicTriggerController.resetSessionLaunchTime(); + } + // Normally we wait until on_session call to download the latest IAMs // however an on session won't happen void initWithCachedInAppMessages() { // Do not reload from cache if already loaded. - if (!messages.isEmpty()) + if (!messages.isEmpty()) { + OneSignal.Log(OneSignal.LOG_LEVEL.DEBUG, "initWithCachedInAppMessages with already in memory messages: " + messages); return; + } - String cachedIamsStr = OneSignalPrefs.getString( + String cachedInAppMessageString = OneSignalPrefs.getString( OneSignalPrefs.PREFS_ONESIGNAL, OneSignalPrefs.PREFS_OS_CACHED_IAMS, null ); - OneSignal.Log(OneSignal.LOG_LEVEL.DEBUG, "initWithCachedInAppMessages: " + cachedIamsStr); + OneSignal.Log(OneSignal.LOG_LEVEL.DEBUG, "initWithCachedInAppMessages: " + cachedInAppMessageString); - if (cachedIamsStr == null) + if (cachedInAppMessageString == null || cachedInAppMessageString.isEmpty()) return; - try { - processInAppMessageJson(new JSONArray(cachedIamsStr)); - } catch (JSONException e) { - e.printStackTrace(); + synchronized (LOCK) { + try { + // Second check to avoid getting the lock while message list is being set + if (!messages.isEmpty()) + return; + + processInAppMessageJson(new JSONArray(cachedInAppMessageString)); + } catch (JSONException e) { + e.printStackTrace(); + } } } @@ -171,6 +167,7 @@ void receivedInAppMessageJson(@NonNull JSONArray json) throws JSONException { json.toString()); resetRedisplayMessagesBySession(); + processInAppMessageJson(json); } @@ -181,27 +178,37 @@ private void resetRedisplayMessagesBySession() { } private void processInAppMessageJson(@NonNull JSONArray json) throws JSONException { - ArrayList newMessages = new ArrayList<>(); - for (int i = 0; i < json.length(); i++) { - JSONObject messageJson = json.getJSONObject(i); - OSInAppMessage message = new OSInAppMessage(messageJson); - newMessages.add(message); + synchronized (LOCK) { + ArrayList newMessages = new ArrayList<>(); + for (int i = 0; i < json.length(); i++) { + JSONObject messageJson = json.getJSONObject(i); + OSInAppMessage message = new OSInAppMessage(messageJson); + + newMessages.add(message); + } + + messages = newMessages; } - messages = newMessages; evaluateInAppMessages(); } private void evaluateInAppMessages() { + OneSignal.Log(OneSignal.LOG_LEVEL.DEBUG, "Starting evaluateInAppMessages"); + for (OSInAppMessage message : messages) { - setDataForRedisplay(message); - if (!dismissedMessages.contains(message.messageId) && triggerController.evaluateMessageTriggers(message)) - queueMessageForDisplay(message); + // Make trigger evaluation first, dynamic trigger might change "trigger changed" flag value for redisplay messages + if (triggerController.evaluateMessageTriggers(message)) { + setDataForRedisplay(message); + + if (!dismissedMessages.contains(message.messageId)) { + queueMessageForDisplay(message); + } + } } } - private static @Nullable - String variantIdForMessage(@NonNull OSInAppMessage message) { + private static @Nullable String variantIdForMessage(@NonNull OSInAppMessage message) { String languageIdentifier = OSUtils.getCorrectedLanguage(); for (String variant : PREFERRED_VARIANT_ORDER) { @@ -470,17 +477,20 @@ private void setDataForRedisplay(OSInAppMessage message) { int index = redisplayedInAppMessages.indexOf(message); if (messageDismissed && index != -1) { - OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "setDataForRedisplay: " + message.messageId); - OSInAppMessage savedIAM = redisplayedInAppMessages.get(index); message.getRedisplayStats().setDisplayStats(savedIAM.getRedisplayStats()); // Message that don't have triggers should display only once per session boolean triggerHasChanged = message.isTriggerChanged() || (!savedIAM.isDisplayedInSession() && message.triggers.isEmpty()); + + OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "setDataForRedisplay: " + message.toString() + " triggerHasChanged: " + triggerHasChanged); + // Check if conditions are correct for redisplay if (triggerHasChanged && message.getRedisplayStats().isDelayTimeSatisfied() && message.getRedisplayStats().shouldDisplayAgain()) { + OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "setDataForRedisplay message available for redisplay: " + message.messageId); + dismissedMessages.remove(message.messageId); impressionedMessages.remove(message.messageId); message.clearClickIds(); @@ -542,9 +552,6 @@ void messageWasDismissed(@NonNull OSInAppMessage message) { } void messageWasDismissed(@NonNull OSInAppMessage message, boolean failed) { - // Remove DIRECT influence due to ClickHandler of ClickAction outcomes - OneSignal.getSessionManager().onDirectInfluenceFromIAMClickFinished(); - if (!message.isPreview) { dismissedMessages.add(message.messageId); // If failed we will retry on next session @@ -565,12 +572,21 @@ void messageWasDismissed(@NonNull OSInAppMessage message, boolean failed) { dismissCurrentMessage(message); } + void messageWasDismissedByBackPress(@NonNull OSInAppMessage message) { + OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "OSInAppMessageController messageWasDismissed by back press: " + message.toString()); + // IAM was not dismissed by user, will be redisplay again until user dismiss it + dismissCurrentMessage(message); + } + /** * Removes first item from the queue and attempts to show the next IAM in the queue * * @param message The message dismissed, preview messages are null */ private void dismissCurrentMessage(@Nullable OSInAppMessage message) { + // Remove DIRECT influence due to ClickHandler of ClickAction outcomes + OneSignal.getSessionManager().onDirectInfluenceFromIAMClickFinished(); + if (currentPrompt != null) { OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "Stop evaluateMessageDisplayQueue because prompt is currently displayed"); return; @@ -721,8 +737,34 @@ void onSuccess(String response) { }, null); } + /** + * Part of redisplay logic + *

+ * Will update redisplay messages depending on dynamic triggers before setDataForRedisplay is called. + * @see OSInAppMessageController#setDataForRedisplay(OSInAppMessage) + * + * We can't depend only on messageTriggerConditionChanged, due to trigger evaluation to true before scheduling + * @see OSInAppMessageController#messageTriggerConditionChanged() + */ + @Override + public void messageDynamicTriggerCompleted(String triggerId) { + OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "messageDynamicTriggerCompleted called with triggerId: " + triggerId); + Set triggerIds = new HashSet<>(); + triggerIds.add(triggerId); + makeRedisplayMessagesAvailableWithTriggers(triggerIds); + } + + /** + * Dynamic trigger logic + *

+ * This will re evaluate messages due to dynamic triggers evaluating to true potentially + * + * @see OSInAppMessageController#setDataForRedisplay(OSInAppMessage) + */ @Override public void messageTriggerConditionChanged() { + OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "messageTriggerConditionChanged called"); + // This method is called when a time-based trigger timer fires, meaning the message can // probably be shown now. So the current message conditions should be re-evaluated evaluateInAppMessages(); @@ -748,8 +790,9 @@ public void systemConditionChanged() { */ private void makeRedisplayMessagesAvailableWithTriggers(Collection newTriggersKeys) { for (OSInAppMessage message : messages) { - if (redisplayedInAppMessages.contains(message) && + if (!message.isTriggerChanged() && redisplayedInAppMessages.contains(message) && triggerController.isTriggerOnMessage(message, newTriggersKeys)) { + OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "Trigger changed for message: " + message.toString()); message.setTriggerChanged(true); } } @@ -763,12 +806,14 @@ private void makeRedisplayMessagesAvailableWithTriggers(Collection newTr * conditions have changed. */ void addTriggers(Map newTriggers) { + OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "Add trigger called with: " + newTriggers.toString()); triggerController.addTriggers(newTriggers); makeRedisplayMessagesAvailableWithTriggers(newTriggers.keySet()); evaluateInAppMessages(); } void removeTriggersForKeys(Collection keys) { + OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "Remove trigger called with keys: " + keys); triggerController.removeTriggersForKeys(keys); makeRedisplayMessagesAvailableWithTriggers(keys); evaluateInAppMessages(); diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageControllerFactory.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageControllerFactory.java new file mode 100644 index 0000000000..a03cb78ec9 --- /dev/null +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageControllerFactory.java @@ -0,0 +1,52 @@ +/** + * Modified MIT License + *

+ * Copyright 2020 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.onesignal; + +import android.os.Build; + +class OSInAppMessageControllerFactory { + + private static final Object LOCK = new Object(); + + private OSInAppMessageController controller; + + public OSInAppMessageController getController(OneSignalDbHelper dbHelper) { + if (controller == null) { + synchronized (LOCK) { + if (controller == null) { + // Make sure only Android 4.4 devices and higher can use IAMs + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) + controller = new OSInAppMessageDummyController(null); + else + controller = new OSInAppMessageController(dbHelper); + } + } + } + return controller; + } +} diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageRedisplayStats.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageRedisplayStats.java index 581777c691..3249023184 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageRedisplayStats.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageRedisplayStats.java @@ -83,13 +83,15 @@ void setDisplayDelay(long displayDelay) { } boolean shouldDisplayAgain() { - return displayQuantity < displayLimit; + boolean result = displayQuantity < displayLimit; + OneSignal.Log(OneSignal.LOG_LEVEL.DEBUG, "OSInAppMessage shouldDisplayAgain: " + result); + return result; } boolean isDelayTimeSatisfied() { - if (lastDisplayTime < 0) { + if (lastDisplayTime < 0) return true; - } + long currentTimeInSeconds = System.currentTimeMillis() / 1000; // Calculate gap between display times long diffInSeconds = currentTimeInSeconds - lastDisplayTime; diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageRepository.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageRepository.java index 06f4a0a286..212d76d7a5 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageRepository.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageRepository.java @@ -2,8 +2,6 @@ import android.content.ContentValues; import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; import android.support.annotation.WorkerThread; import org.json.JSONArray; @@ -25,39 +23,26 @@ class OSInAppMessageRepository { @WorkerThread synchronized void saveInAppMessage(OSInAppMessage inAppMessage) { - SQLiteDatabase writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - writableDb.beginTransaction(); - try { - ContentValues values = new ContentValues(); - values.put(OneSignalDbContract.InAppMessageTable.COLUMN_NAME_MESSAGE_ID, inAppMessage.messageId); - values.put(OneSignalDbContract.InAppMessageTable.COLUMN_NAME_DISPLAY_QUANTITY, inAppMessage.getRedisplayStats().getDisplayQuantity()); - values.put(OneSignalDbContract.InAppMessageTable.COLUMN_NAME_LAST_DISPLAY, inAppMessage.getRedisplayStats().getLastDisplayTime()); - values.put(OneSignalDbContract.InAppMessageTable.COLUMN_CLICK_IDS, inAppMessage.getClickedClickIds().toString()); - values.put(OneSignalDbContract.InAppMessageTable.COLUMN_DISPLAYED_IN_SESSION, inAppMessage.isDisplayedInSession()); - - int rowsUpdated = writableDb.update(OneSignalDbContract.InAppMessageTable.TABLE_NAME, values, - OneSignalDbContract.InAppMessageTable.COLUMN_NAME_MESSAGE_ID + " = ?", new String[]{inAppMessage.messageId}); - if (rowsUpdated == 0) - writableDb.insert(OneSignalDbContract.InAppMessageTable.TABLE_NAME, null, values); - - writableDb.setTransactionSuccessful(); - } finally { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (SQLException e) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error closing transaction! ", e); - } - } + ContentValues values = new ContentValues(); + values.put(OneSignalDbContract.InAppMessageTable.COLUMN_NAME_MESSAGE_ID, inAppMessage.messageId); + values.put(OneSignalDbContract.InAppMessageTable.COLUMN_NAME_DISPLAY_QUANTITY, inAppMessage.getRedisplayStats().getDisplayQuantity()); + values.put(OneSignalDbContract.InAppMessageTable.COLUMN_NAME_LAST_DISPLAY, inAppMessage.getRedisplayStats().getLastDisplayTime()); + values.put(OneSignalDbContract.InAppMessageTable.COLUMN_CLICK_IDS, inAppMessage.getClickedClickIds().toString()); + values.put(OneSignalDbContract.InAppMessageTable.COLUMN_DISPLAYED_IN_SESSION, inAppMessage.isDisplayedInSession()); + + int rowsUpdated = dbHelper.update(OneSignalDbContract.InAppMessageTable.TABLE_NAME, values, + OneSignalDbContract.InAppMessageTable.COLUMN_NAME_MESSAGE_ID + " = ?", new String[]{inAppMessage.messageId}); + if (rowsUpdated == 0) + dbHelper.insert(OneSignalDbContract.InAppMessageTable.TABLE_NAME, null, values); } @WorkerThread synchronized List getCachedInAppMessages() { - List iams = new ArrayList<>(); + List inAppMessages = new ArrayList<>(); Cursor cursor = null; try { - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); - cursor = readableDb.query( + cursor = dbHelper.query( OneSignalDbContract.InAppMessageTable.TABLE_NAME, null, null, @@ -78,7 +63,7 @@ synchronized List getCachedInAppMessages() { Set clickIdsSet = OSUtils.newStringSetFromJSONArray(new JSONArray(clickIds)); OSInAppMessage inAppMessage = new OSInAppMessage(messageId, clickIdsSet, displayed, new OSInAppMessageRedisplayStats(displayQuantity, lastDisplay)); - iams.add(inAppMessage); + inAppMessages.add(inAppMessage); } while (cursor.moveToNext()); } } catch (JSONException e) { @@ -88,13 +73,11 @@ synchronized List getCachedInAppMessages() { cursor.close(); } - return iams; + return inAppMessages; } @WorkerThread synchronized void cleanCachedInAppMessages() { - SQLiteDatabase writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - // 1. Query for all old message ids and old clicked click ids String[] retColumns = new String[]{ OneSignalDbContract.InAppMessageTable.COLUMN_NAME_MESSAGE_ID, @@ -111,7 +94,7 @@ synchronized void cleanCachedInAppMessages() { Cursor cursor = null; try { - cursor = writableDb.query(OneSignalDbContract.InAppMessageTable.TABLE_NAME, + cursor = dbHelper.query(OneSignalDbContract.InAppMessageTable.TABLE_NAME, retColumns, whereStr, whereArgs, @@ -144,22 +127,11 @@ synchronized void cleanCachedInAppMessages() { if (cursor != null && !cursor.isClosed()) cursor.close(); } - - writableDb.beginTransaction(); - try { - // 2. Delete old IAMs from SQL - writableDb.delete( - OneSignalDbContract.InAppMessageTable.TABLE_NAME, - whereStr, - whereArgs); - writableDb.setTransactionSuccessful(); - } finally { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (SQLException e) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error closing transaction! ", e); - } - } + // 2. Delete old IAMs from SQL + dbHelper.delete( + OneSignalDbContract.InAppMessageTable.TABLE_NAME, + whereStr, + whereArgs); // 3. Use queried data to clean SharedPreferences cleanInAppMessageIds(oldMessageIds); @@ -168,11 +140,12 @@ synchronized void cleanCachedInAppMessages() { /** * Clean up 6 month old IAM ids in {@link android.content.SharedPreferences}: - * 1. Dismissed message ids - * 2. Impressioned message ids + * 1. Dismissed message ids + * 2. Impressioned message ids *

* Note: This should only ever be called by {@link OSInAppMessageRepository#cleanCachedInAppMessages()} *

+ * * @see OneSignalCacheCleaner#cleanCachedInAppMessages(OneSignalDbHelper) * @see OSInAppMessageRepository#cleanCachedInAppMessages() */ @@ -208,10 +181,11 @@ private void cleanInAppMessageIds(Set oldMessageIds) { /** * Clean up 6 month old IAM clicked click ids in {@link android.content.SharedPreferences}: - * 1. Clicked click ids from elements within IAM + * 1. Clicked click ids from elements within IAM *

* Note: This should only ever be called by {@link OSInAppMessageRepository#cleanCachedInAppMessages()} *

+ * * @see OneSignalCacheCleaner#cleanCachedInAppMessages(OneSignalDbHelper) * @see OSInAppMessageRepository#cleanCachedInAppMessages() */ @@ -233,5 +207,4 @@ private void cleanInAppMessageClickedClickIds(Set oldClickedClickIds) { } - } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSSessionManager.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSSessionManager.java index e37babb901..f721c2529e 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSSessionManager.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSSessionManager.java @@ -1,5 +1,6 @@ package com.onesignal; +import android.os.Process; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -24,6 +25,8 @@ */ public class OSSessionManager { + private static final String OS_END_CURRENT_SESSION = "OS_END_CURRENT_SESSION"; + public interface SessionListener { // Fire with the last OSInfluence that just ended. void onSessionEnding(@NonNull List lastInfluences); @@ -234,10 +237,17 @@ private boolean willChangeSession(@NonNull OSChannelTracker channelTracker, !JSONUtils.compareJSONArrays(channelTracker.getIndirectIds(), indirectNotificationIds); } - private void sendSessionEndingWithInfluences(List endingInfluences) { + private void sendSessionEndingWithInfluences(final List endingInfluences) { logger.debug("OneSignal SessionManager sendSessionEndingWithInfluences with influences: " + endingInfluences); // Only end session if there are influences available to end - if (endingInfluences.size() > 0) - sessionListener.onSessionEnding(endingInfluences); + if (endingInfluences.size() > 0) { + new Thread(new Runnable() { + @Override + public void run() { + Thread.currentThread().setPriority(Process.THREAD_PRIORITY_BACKGROUND); + sessionListener.onSessionEnding(endingInfluences); + } + }, OS_END_CURRENT_SESSION).start(); + } } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSTriggerController.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSTriggerController.java index 6301492e87..c78f25ee54 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSTriggerController.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSTriggerController.java @@ -172,10 +172,15 @@ private boolean triggerMatchesNumericValue(@NonNull Number triggerValue, @NonNul * If trigger key is part of message triggers, then return true, otherwise false * */ boolean isTriggerOnMessage(OSInAppMessage message, Collection newTriggersKeys) { + if (message.triggers == null) + return false; + for (String triggerKey : newTriggersKeys) { for (ArrayList andConditions : message.triggers) { for (OSTrigger trigger : andConditions) { - if (triggerKey.equals(trigger.property)) { + // Dynamic triggers depends on triggerId + // Common triggers changed by user depends on property + if (triggerKey.equals(trigger.property) || triggerKey.equals(trigger.triggerId)) { // At least one trigger has changed return true; } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSUtils.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSUtils.java index 43412c55d8..4c7546fbee 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSUtils.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSUtils.java @@ -475,6 +475,10 @@ static boolean areNotificationsEnabled(Context context) { return true; } + static boolean isRunningOnMainThread() { + return Thread.currentThread().equals(Looper.getMainLooper().getThread()); + } + static void runOnMainUIThread(Runnable runnable) { if (Looper.getMainLooper().getThread() == Thread.currentThread()) runnable.run(); diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java index b71c9f9147..df38406f96 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java @@ -37,7 +37,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; @@ -431,19 +430,23 @@ static boolean isForeground() { private static TrackAmazonPurchase trackAmazonPurchase; private static TrackFirebaseAnalytics trackFirebaseAnalytics; - public static final String VERSION = "031501"; + public static final String VERSION = "031505"; private static OSSessionManager.SessionListener sessionListener = new OSSessionManager.SessionListener() { @Override public void onSessionEnding(@NonNull List lastInfluences) { if (outcomeEventsController == null) - OneSignal.Log(LOG_LEVEL.WARN, "OneSignal onSessionEnding called before initZ"); + OneSignal.Log(LOG_LEVEL.WARN, "OneSignal onSessionEnding called before init!"); if (outcomeEventsController != null) outcomeEventsController.cleanOutcomes(); FocusTimeController.getInstance().onSessionEnded(lastInfluences); } }; + private static OSInAppMessageControllerFactory inAppMessageControllerFactory = new OSInAppMessageControllerFactory(); + static OSInAppMessageController getInAppMessageController() { + return inAppMessageControllerFactory.getController(getDBHelperInstance()); + } private static OSLogger logger = new OSLogWrapper(); private static OneSignalAPIClient apiClient = new OneSignalRestClientWrapper(); private static OSSharedPreferences preferences = new OSSharedPreferencesWrapper(); @@ -843,15 +846,18 @@ private static void handleAmazonPurchase() { private static void doSessionInit() { // Check session time to determine whether to start a new session or not if (isPastOnSessionTime()) { - OneSignalStateSynchronizer.setNewSession(); + OneSignal.onesignalLog(LOG_LEVEL.DEBUG, "Starting new session"); + OneSignalStateSynchronizer.setNewSession(); if (foreground) { outcomeEventsController.cleanOutcomes(); sessionManager.restartSessionIfNeeded(getAppEntryState()); + getInAppMessageController().resetSessionLaunchTime(); } } else if (foreground) { - OSInAppMessageController.getController().initWithCachedInAppMessages(); + OneSignal.onesignalLog(LOG_LEVEL.DEBUG, "Continue on same session"); sessionManager.attemptSessionUpgrade(getAppEntryState()); } + getInAppMessageController().initWithCachedInAppMessages(); // We still want register the user to OneSignal if the SDK was initialized // in the background for the first time. @@ -1245,6 +1251,7 @@ static void logHttpError(String errorString, int statusCode, Throwable throwable // Returns true if there is active time that is unsynced. @WorkerThread static void onAppLostFocus() { + Log(LOG_LEVEL.DEBUG, "Application lost focus"); foreground = false; appEntryState = AppEntryAction.APP_CLOSE; @@ -1278,6 +1285,7 @@ private static boolean scheduleSyncService() { } static void onAppFocus() { + Log(LOG_LEVEL.DEBUG, "Application on focus"); foreground = true; // If the app gains focus and has not been set to NOTIFICATION_CLICK yet we can assume this is a normal app open @@ -1541,11 +1549,18 @@ public void run() { } public static void setExternalUserId(@NonNull final String externalId) { - setExternalUserId(externalId, null); + setExternalUserId(externalId, null, null); } public static void setExternalUserId(@NonNull final String externalId, @Nullable final OSExternalUserIdUpdateCompletionHandler completionCallback) { + setExternalUserId(externalId, null, completionCallback); + } + + public static void setExternalUserId(@NonNull final String externalId, @Nullable final String externalIdAuthHash) { + setExternalUserId(externalId, externalIdAuthHash, null); + } + public static void setExternalUserId(@NonNull final String externalId, @Nullable final String externalIdAuthHash, @Nullable final OSExternalUserIdUpdateCompletionHandler completionCallback) { if (shouldLogUserPrivacyConsentErrorMessageForMethodName("setExternalUserId()")) return; @@ -1557,8 +1572,18 @@ public void run() { return; } + if (remoteParams != null && remoteParams.useUserIdAuth && externalIdAuthHash == null) { + String errorMessage = "External Id authentication (auth token) is set to REQUIRED for this application. Please provide an auth token from your backend server or change the setting in the OneSignal dashboard."; + Log(LOG_LEVEL.ERROR, errorMessage); + return; + } + + String lowerCaseIdAuthHash = externalIdAuthHash; + if (lowerCaseIdAuthHash != null) + lowerCaseIdAuthHash = externalIdAuthHash.toLowerCase(); + try { - OneSignalStateSynchronizer.setExternalUserId(externalId, completionCallback); + OneSignalStateSynchronizer.setExternalUserId(externalId, lowerCaseIdAuthHash, completionCallback); } catch (JSONException exception) { String operation = externalId.equals("") ? "remove" : "set"; onesignalLog(LOG_LEVEL.ERROR, "Attempted to " + operation + " external ID but encountered a JSON exception"); @@ -2625,61 +2650,35 @@ public void run() { NotificationManager notificationManager = OneSignalNotificationManager.getNotificationManager(appContext); OneSignalDbHelper dbHelper = OneSignalDbHelper.getInstance(appContext); - Cursor cursor = null; - try { - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); - - String[] retColumn = {OneSignalDbContract.NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID}; - - cursor = readableDb.query( - OneSignalDbContract.NotificationTable.TABLE_NAME, - retColumn, - OneSignalDbContract.NotificationTable.COLUMN_NAME_DISMISSED + " = 0 AND " + - OneSignalDbContract.NotificationTable.COLUMN_NAME_OPENED + " = 0", - null, - null, // group by - null, // filter by row groups - null // sort order - ); - - if (cursor.moveToFirst()) { - do { - int existingId = cursor.getInt(cursor.getColumnIndex(OneSignalDbContract.NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID)); - notificationManager.cancel(existingId); - } while (cursor.moveToNext()); - } + String[] retColumn = {OneSignalDbContract.NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID}; + + Cursor cursor = dbHelper.query( + OneSignalDbContract.NotificationTable.TABLE_NAME, + retColumn, + OneSignalDbContract.NotificationTable.COLUMN_NAME_DISMISSED + " = 0 AND " + + OneSignalDbContract.NotificationTable.COLUMN_NAME_OPENED + " = 0", + null, + null, // group by + null, // filter by row groups + null // sort order + ); + if (cursor.moveToFirst()) { + do { + int existingId = cursor.getInt(cursor.getColumnIndex(OneSignalDbContract.NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID)); + notificationManager.cancel(existingId); + } while (cursor.moveToNext()); + } - // Mark all notifications as dismissed unless they were already opened. - SQLiteDatabase writableDb = null; - try { - writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - writableDb.beginTransaction(); - - String whereStr = NotificationTable.COLUMN_NAME_OPENED + " = 0"; - ContentValues values = new ContentValues(); - values.put(NotificationTable.COLUMN_NAME_DISMISSED, 1); - writableDb.update(NotificationTable.TABLE_NAME, values, whereStr, null); - writableDb.setTransactionSuccessful(); - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error marking all notifications as dismissed! ", t); - } finally { - if (writableDb != null) { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error closing transaction! ", t); - } - } - } + // Mark all notifications as dismissed unless they were already opened. + String whereStr = NotificationTable.COLUMN_NAME_OPENED + " = 0"; + ContentValues values = new ContentValues(); + values.put(NotificationTable.COLUMN_NAME_DISMISSED, 1); + dbHelper.update(NotificationTable.TABLE_NAME, values, whereStr, null); - BadgeCountUpdater.updateCount(0, appContext); - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error canceling all notifications! ", t); - } finally { - if (cursor != null) - cursor.close(); - } + BadgeCountUpdater.updateCount(0, appContext); + + cursor.close(); } }; @@ -2705,36 +2704,18 @@ public static void cancelNotification(final int id) { @Override public void run() { OneSignalDbHelper dbHelper = OneSignalDbHelper.getInstance(appContext); - SQLiteDatabase writableDb = null; - try { - writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - writableDb.beginTransaction(); - - String whereStr = NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID + " = " + id + " AND " + - NotificationTable.COLUMN_NAME_OPENED + " = 0 AND " + - NotificationTable.COLUMN_NAME_DISMISSED + " = 0"; + String whereStr = NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID + " = " + id + " AND " + + NotificationTable.COLUMN_NAME_OPENED + " = 0 AND " + + NotificationTable.COLUMN_NAME_DISMISSED + " = 0"; - ContentValues values = new ContentValues(); - values.put(NotificationTable.COLUMN_NAME_DISMISSED, 1); + ContentValues values = new ContentValues(); + values.put(NotificationTable.COLUMN_NAME_DISMISSED, 1); - int records = writableDb.update(NotificationTable.TABLE_NAME, values, whereStr, null); + int records = dbHelper.update(NotificationTable.TABLE_NAME, values, whereStr, null); - if (records > 0) - NotificationSummaryManager.updatePossibleDependentSummaryOnDismiss(appContext, writableDb, id); - BadgeCountUpdater.update(writableDb, appContext); - - writableDb.setTransactionSuccessful(); - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error marking a notification id " + id + " as dismissed! ", t); - } finally { - if (writableDb != null) { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error closing transaction! ", t); - } - } - } + if (records > 0) + NotificationSummaryManager.updatePossibleDependentSummaryOnDismiss(appContext, dbHelper, id); + BadgeCountUpdater.update(dbHelper, appContext); NotificationManager notificationManager = OneSignalNotificationManager.getNotificationManager(appContext); notificationManager.cancel(id); @@ -2766,67 +2747,38 @@ public void run() { NotificationManager notificationManager = OneSignalNotificationManager.getNotificationManager(appContext); OneSignalDbHelper dbHelper = OneSignalDbHelper.getInstance(appContext); - Cursor cursor = null; - - try { - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); - - String[] retColumn = { NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID }; - - String whereStr = NotificationTable.COLUMN_NAME_GROUP_ID + " = ? AND " + - NotificationTable.COLUMN_NAME_DISMISSED + " = 0 AND " + - NotificationTable.COLUMN_NAME_OPENED + " = 0"; - String[] whereArgs = { group }; - - cursor = readableDb.query( - NotificationTable.TABLE_NAME, - retColumn, - whereStr, - whereArgs, - null, null, null); - - while (cursor.moveToNext()) { - int notifId = cursor.getInt(cursor.getColumnIndex(NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID)); - if (notifId != -1) - notificationManager.cancel(notifId); - } - } - catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error getting android notifications part of group: " + group, t); - } - finally { - if (cursor != null && !cursor.isClosed()) - cursor.close(); - } - SQLiteDatabase writableDb = null; - try { - writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - writableDb.beginTransaction(); + String[] retColumn = {NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID}; - String whereStr = NotificationTable.COLUMN_NAME_GROUP_ID + " = ? AND " + - NotificationTable.COLUMN_NAME_OPENED + " = 0 AND " + - NotificationTable.COLUMN_NAME_DISMISSED + " = 0"; - String[] whereArgs = { group }; + final String[] whereArgs = {group}; - ContentValues values = new ContentValues(); - values.put(NotificationTable.COLUMN_NAME_DISMISSED, 1); + String whereStr = NotificationTable.COLUMN_NAME_GROUP_ID + " = ? AND " + + NotificationTable.COLUMN_NAME_DISMISSED + " = 0 AND " + + NotificationTable.COLUMN_NAME_OPENED + " = 0"; - writableDb.update(NotificationTable.TABLE_NAME, values, whereStr, whereArgs); - BadgeCountUpdater.update(writableDb, appContext); + Cursor cursor = dbHelper.query( + NotificationTable.TABLE_NAME, + retColumn, + whereStr, + whereArgs, + null, null, null); - writableDb.setTransactionSuccessful(); - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error marking a notifications with group " + group + " as dismissed! ", t); - } finally { - if (writableDb != null) { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (Throwable t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error closing transaction! ", t); - } - } + while (cursor.moveToNext()) { + int notificationId = cursor.getInt(cursor.getColumnIndex(NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID)); + if (notificationId != -1) + notificationManager.cancel(notificationId); } + cursor.close(); + + whereStr = NotificationTable.COLUMN_NAME_GROUP_ID + " = ? AND " + + NotificationTable.COLUMN_NAME_OPENED + " = 0 AND " + + NotificationTable.COLUMN_NAME_DISMISSED + " = 0"; + + ContentValues values = new ContentValues(); + values.put(NotificationTable.COLUMN_NAME_DISMISSED, 1); + + dbHelper.update(NotificationTable.TABLE_NAME, values, whereStr, whereArgs); + BadgeCountUpdater.update(dbHelper, appContext); } }; @@ -2997,7 +2949,7 @@ public static OSPermissionSubscriptionState getPermissionSubscriptionState() { * Triggers are used for targeting in-app messages. */ public static void addTriggers(Map triggers) { - OSInAppMessageController.getController().addTriggers(triggers); + getInAppMessageController().addTriggers(triggers); } /** @@ -3020,13 +2972,13 @@ public static void addTrigger(String key, Object object) { HashMap triggerMap = new HashMap<>(); triggerMap.put(key, object); - OSInAppMessageController.getController().addTriggers(triggerMap); + getInAppMessageController().addTriggers(triggerMap); } /** Removes a list/collection of triggers from their keys with a Collection of Strings */ public static void removeTriggersForKeys(Collection keys) { - OSInAppMessageController.getController().removeTriggersForKeys(keys); + getInAppMessageController().removeTriggersForKeys(keys); } /** Removes a list/collection of triggers from their keys with a JSONArray String. @@ -3040,7 +2992,7 @@ public static void removeTriggersForKeysFromJsonArrayString(@NonNull String keys // Some keys were filtered, log as warning if (jsonArray.length() != keysCollection.size()) OneSignal.Log(LOG_LEVEL.WARN, "removeTriggersForKeysFromJsonArrayString: Skipped removing non-String type keys "); - OSInAppMessageController.getController().removeTriggersForKeys(keysCollection); + getInAppMessageController().removeTriggersForKeys(keysCollection); } catch (JSONException e) { OneSignal.Log(LOG_LEVEL.ERROR, "removeTriggersForKeysFromJsonArrayString, invalid json", e); } @@ -3051,13 +3003,13 @@ public static void removeTriggerForKey(String key) { ArrayList triggerKeys = new ArrayList<>(); triggerKeys.add(key); - OSInAppMessageController.getController().removeTriggersForKeys(triggerKeys); + getInAppMessageController().removeTriggersForKeys(triggerKeys); } /** Returns a single trigger value for the given key (if it exists, otherwise returns null) */ @Nullable public static Object getTriggerValueForKey(String key) { - return OSInAppMessageController.getController().getTriggerValue(key); + return getInAppMessageController().getTriggerValue(key); } /*** @@ -3067,40 +3019,28 @@ public static Object getTriggerValueForKey(String key) { * @param pause The boolean that pauses/resumes in-app messages */ public static void pauseInAppMessages(boolean pause) { - OSInAppMessageController.getController().setInAppMessagingEnabled(!pause); + getInAppMessageController().setInAppMessagingEnabled(!pause); } private static boolean isDuplicateNotification(String id, Context context) { if (id == null || "".equals(id)) return false; - boolean exists = false; - OneSignalDbHelper dbHelper = OneSignalDbHelper.getInstance(context); - Cursor cursor = null; - try { - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); + String[] retColumn = {NotificationTable.COLUMN_NAME_NOTIFICATION_ID}; + String[] whereArgs = {id}; - String[] retColumn = {NotificationTable.COLUMN_NAME_NOTIFICATION_ID}; - String[] whereArgs = {id}; + Cursor cursor = dbHelper.query( + NotificationTable.TABLE_NAME, + retColumn, + NotificationTable.COLUMN_NAME_NOTIFICATION_ID + " = ?", // Where String + whereArgs, + null, null, null); - cursor = readableDb.query( - NotificationTable.TABLE_NAME, - retColumn, - NotificationTable.COLUMN_NAME_NOTIFICATION_ID + " = ?", // Where String - whereArgs, - null, null, null); + boolean exists = cursor.moveToFirst(); - exists = cursor.moveToFirst(); - } - catch (Throwable t) { - OneSignal.Log(LOG_LEVEL.ERROR, "Could not check for duplicate, assuming unique.", t); - } - finally { - if (cursor != null) - cursor.close(); - } + cursor.close(); if (exists) { Log(LOG_LEVEL.DEBUG, "Duplicate GCM message received, skip processing of " + id); diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalCacheCleaner.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalCacheCleaner.java index fd4f74ea86..9bd01e019a 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalCacheCleaner.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalCacheCleaner.java @@ -1,8 +1,6 @@ package com.onesignal; import android.content.Context; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; import android.os.Process; import android.support.annotation.WorkerThread; @@ -24,9 +22,7 @@ class OneSignalCacheCleaner { */ static void cleanOldCachedData(final Context context) { OneSignalDbHelper dbHelper = OneSignalDbHelper.getInstance(context); - SQLiteDatabase writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - - cleanNotificationCache(writableDb); + cleanNotificationCache(dbHelper); cleanCachedInAppMessages(dbHelper); } @@ -35,24 +31,14 @@ static void cleanOldCachedData(final Context context) { * 1. NotificationTable.TABLE_NAME * 2. CachedUniqueOutcomeNotificationTable.TABLE_NAME */ - synchronized static void cleanNotificationCache(final SQLiteDatabase writableDb) { + synchronized static void cleanNotificationCache(final OneSignalDbHelper writableDb) { new Thread(new Runnable() { @Override public void run() { Thread.currentThread().setPriority(Process.THREAD_PRIORITY_BACKGROUND); - writableDb.beginTransaction(); - try { - cleanCachedNotifications(writableDb); - cleanCachedUniqueOutcomeEventNotifications(writableDb); - writableDb.setTransactionSuccessful(); - } finally { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (SQLException t) { - OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error closing transaction! ", t); - } - } + cleanCachedNotifications(writableDb); + cleanCachedUniqueOutcomeEventNotifications(writableDb); } }, OS_DELETE_CACHED_NOTIFICATIONS_THREAD).start(); @@ -71,8 +57,8 @@ synchronized static void cleanCachedInAppMessages(final OneSignalDbHelper dbHelp public void run() { Thread.currentThread().setPriority(Process.THREAD_PRIORITY_BACKGROUND); - OSInAppMessageRepository inAppMessageRepository = OSInAppMessageController - .getController() + OSInAppMessageRepository inAppMessageRepository = OneSignal + .getInAppMessageController() .getInAppMessageRepository(dbHelper); inAppMessageRepository.cleanCachedInAppMessages(); } @@ -82,15 +68,16 @@ public void run() { /** * Deletes notifications with created timestamps older than 7 days *

- * Note: This should only ever be called by {@link OneSignalCacheCleaner#cleanNotificationCache(SQLiteDatabase)} + * Note: This should only ever be called by {@link OneSignalCacheCleaner#cleanNotificationCache(OneSignalDbHelper)} *

- * @see OneSignalCacheCleaner#cleanNotificationCache(SQLiteDatabase) + * + * @see OneSignalCacheCleaner#cleanNotificationCache(OneSignalDbHelper) */ - private static void cleanCachedNotifications(SQLiteDatabase writableDb) { + private static void cleanCachedNotifications(OneSignalDbHelper writableDb) { String whereStr = NotificationTable.COLUMN_NAME_CREATED_TIME + " < ?"; String sevenDaysAgoInSeconds = String.valueOf((System.currentTimeMillis() / 1_000L) - NOTIFICATION_CACHE_DATA_LIFETIME); - String[] whereArgs = new String[]{ sevenDaysAgoInSeconds }; + String[] whereArgs = new String[]{sevenDaysAgoInSeconds}; writableDb.delete( NotificationTable.TABLE_NAME, @@ -101,11 +88,12 @@ private static void cleanCachedNotifications(SQLiteDatabase writableDb) { /** * Deletes cached unique outcome notifications whose ids do not exist inside of the NotificationTable.TABLE_NAME *

- * Note: This should only ever be called by {@link OneSignalCacheCleaner#cleanNotificationCache(SQLiteDatabase)} + * Note: This should only ever be called by {@link OneSignalCacheCleaner#cleanNotificationCache(OneSignalDbHelper)} *

- * @see OneSignalCacheCleaner#cleanNotificationCache(SQLiteDatabase) + * + * @see OneSignalCacheCleaner#cleanNotificationCache(OneSignalDbHelper) */ - private static void cleanCachedUniqueOutcomeEventNotifications(SQLiteDatabase writableDb) { + private static void cleanCachedUniqueOutcomeEventNotifications(OneSignalDbHelper writableDb) { String whereStr = "NOT EXISTS(" + "SELECT NULL FROM " + NotificationTable.TABLE_NAME + " n " + "WHERE" + " n." + NotificationTable.COLUMN_NAME_NOTIFICATION_ID + " = " + OSOutcomeTableProvider.CACHE_UNIQUE_OUTCOME_COLUMN_CHANNEL_INFLUENCE_ID + @@ -117,5 +105,5 @@ private static void cleanCachedUniqueOutcomeEventNotifications(SQLiteDatabase wr whereStr, null); } - + } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalDb.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalDb.java index f358bf3159..d8d354168a 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalDb.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalDb.java @@ -1,9 +1,27 @@ package com.onesignal; -import android.database.sqlite.SQLiteDatabase; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.SQLException; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; public interface OneSignalDb { - SQLiteDatabase getSQLiteDatabaseWithRetries(); + Cursor query(@NonNull String table, @Nullable String[] columns, @Nullable String selection, + String[] selectionArgs, @Nullable String groupBy, @Nullable String having, + @Nullable String orderBy); + Cursor query(@NonNull String table, @Nullable String[] columns, @Nullable String selection, + @Nullable String[] selectionArgs, @Nullable String groupBy, @Nullable String having, + @Nullable String orderBy, @Nullable String limit); + + void insert(@NonNull String table, @Nullable String nullColumnHack, @Nullable ContentValues values); + + void insertOrThrow(@NonNull String table, @Nullable String nullColumnHack, @Nullable ContentValues values) + throws SQLException; + + int update(@NonNull String table, @NonNull ContentValues values, @Nullable String whereClause, @Nullable String[] whereArgs); + + void delete(@NonNull String table, @Nullable String whereClause, @Nullable String[] whereArgs); } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalDbHelper.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalDbHelper.java index 72b9fd329b..ca50378801 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalDbHelper.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalDbHelper.java @@ -27,14 +27,18 @@ package com.onesignal; +import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.SQLException; import android.database.sqlite.SQLiteCantOpenDatabaseException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabaseLockedException; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import com.onesignal.OneSignalDbContract.InAppMessageTable; import com.onesignal.OneSignalDbContract.NotificationTable; @@ -49,7 +53,9 @@ import static com.onesignal.outcomes.OSOutcomeTableProvider.SQL_CREATE_UNIQUE_OUTCOME_ENTRIES_V2; class OneSignalDbHelper extends SQLiteOpenHelper implements OneSignalDb { + static final int DATABASE_VERSION = 8; + private static final Object LOCK = new Object(); private static final String DATABASE_NAME = "OneSignal.db"; private static final String INTEGER_PRIMARY_KEY_TYPE = " INTEGER PRIMARY KEY"; @@ -98,6 +104,7 @@ class OneSignalDbHelper extends SQLiteOpenHelper implements OneSignalDb { NotificationTable.INDEX_CREATE_EXPIRE_TIME }; + private static OSLogger logger = new OSLogWrapper(); private static OneSignalDbHelper sInstance; private static OSOutcomeTableProvider outcomeTableProvider = new OSOutcomeTableProvider(); @@ -117,9 +124,13 @@ private static int getDbVersion() { } - public static synchronized OneSignalDbHelper getInstance(Context context) { - if (sInstance == null) - sInstance = new OneSignalDbHelper(context.getApplicationContext()); + public static OneSignalDbHelper getInstance(Context context) { + if (sInstance == null) { + synchronized (LOCK) { + if (sInstance == null) + sInstance = new OneSignalDbHelper(context.getApplicationContext()); + } + } return sInstance; } @@ -132,17 +143,19 @@ public static synchronized OneSignalDbHelper getInstance(Context context) { *

* @see StackOverflow | What are best practices for SQLite on Android */ - synchronized SQLiteDatabase getSQLiteDatabase() { - try { - return getWritableDatabase(); - } catch (SQLiteCantOpenDatabaseException | SQLiteDatabaseLockedException e) { - // SQLiteCantOpenDatabaseException - // Retry in-case of rare device issues with opening database. - // https://github.com/OneSignal/OneSignal-Android-SDK/issues/136 - // SQLiteDatabaseLockedException - // Retry in-case of rare device issues with locked database. - // https://github.com/OneSignal/OneSignal-Android-SDK/issues/988 - throw e; + private SQLiteDatabase getSQLiteDatabase() { + synchronized (LOCK) { + try { + return getWritableDatabase(); + } catch (SQLiteCantOpenDatabaseException | SQLiteDatabaseLockedException e) { + // SQLiteCantOpenDatabaseException + // Retry in-case of rare device issues with opening database. + // https://github.com/OneSignal/OneSignal-Android-SDK/issues/136 + // SQLiteDatabaseLockedException + // Retry in-case of rare device issues with locked database. + // https://github.com/OneSignal/OneSignal-Android-SDK/issues/988 + throw e; + } } } @@ -152,16 +165,145 @@ synchronized SQLiteDatabase getSQLiteDatabase() { *

* @see OneSignalDbHelper#getSQLiteDatabase() */ + private SQLiteDatabase getSQLiteDatabaseWithRetries() { + synchronized (LOCK) { + int count = 0; + while (true) { + try { + return getSQLiteDatabase(); + } catch (SQLiteCantOpenDatabaseException | SQLiteDatabaseLockedException e) { + if (++count >= DB_OPEN_RETRY_MAX) + throw e; + SystemClock.sleep(count * DB_OPEN_RETRY_BACKOFF); + } + } + } + } + + @Override + public Cursor query(@NonNull String table, @Nullable String[] columns, @Nullable String selection, + String[] selectionArgs, @Nullable String groupBy, @Nullable String having, + @Nullable String orderBy) { + synchronized (LOCK) { + return getSQLiteDatabaseWithRetries().query(table, columns, selection, selectionArgs, groupBy, having, orderBy); + } + } + @Override - public synchronized SQLiteDatabase getSQLiteDatabaseWithRetries() { - int count = 0; - while(true) { + public Cursor query(@NonNull String table, @Nullable String[] columns, @Nullable String selection, + @Nullable String[] selectionArgs, @Nullable String groupBy, @Nullable String having, + @Nullable String orderBy, @Nullable String limit) { + synchronized (LOCK) { + return getSQLiteDatabaseWithRetries().query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit); + } + } + + @Override + public void insert(@NonNull String table, @Nullable String nullColumnHack, @Nullable ContentValues values) { + synchronized (LOCK) { + SQLiteDatabase writableDb = getSQLiteDatabaseWithRetries(); try { - return getWritableDatabase(); - } catch (SQLiteCantOpenDatabaseException | SQLiteDatabaseLockedException e) { - if (++count >= DB_OPEN_RETRY_MAX) - throw e; - SystemClock.sleep(count * DB_OPEN_RETRY_BACKOFF); + writableDb.beginTransaction(); + writableDb.insert(table, nullColumnHack, values); + writableDb.setTransactionSuccessful(); + } catch (SQLiteException e) { + logger.error("Error inserting on table: " + table + " with nullColumnHack: " + nullColumnHack + " and values: " + values, e); + } catch (IllegalStateException e) { + logger.error("Error under inserting transaction under table: " + table + " with nullColumnHack: " + nullColumnHack + " and values: " + values, e); + } finally { + if (writableDb != null) { + try { + writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. + } catch (IllegalStateException e) { + logger.error("Error closing transaction! ", e); + } catch (SQLiteException e) { + logger.error("Error closing transaction! ", e); + } + } + } + } + } + + @Override + public void insertOrThrow(@NonNull String table, @Nullable String nullColumnHack, @Nullable ContentValues values) + throws SQLException { + synchronized (LOCK) { + SQLiteDatabase writableDb = getSQLiteDatabaseWithRetries(); + try { + writableDb.beginTransaction(); + writableDb.insertOrThrow(table, nullColumnHack, values); + writableDb.setTransactionSuccessful(); + } catch (SQLiteException e) { + logger.error("Error inserting or throw on table: " + table + " with nullColumnHack: " + nullColumnHack + " and values: " + values, e); + } catch (IllegalStateException e) { + logger.error("Error under inserting or throw transaction under table: " + table + " with nullColumnHack: " + nullColumnHack + " and values: " + values, e); + } finally { + if (writableDb != null) { + try { + writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. + } catch (IllegalStateException e) { + logger.error("Error closing transaction! ", e); + } catch (SQLiteException e) { + logger.error("Error closing transaction! ", e); + } + } + } + } + } + + @Override + public int update(@NonNull String table, @NonNull ContentValues values, @Nullable String whereClause, @Nullable String[] whereArgs) { + int result = 0; + if (values == null || values.toString().isEmpty()) + return result; + + synchronized (LOCK) { + SQLiteDatabase writableDb = getSQLiteDatabaseWithRetries(); + try { + writableDb.beginTransaction(); + result = writableDb.update(table, values, whereClause, whereArgs); + writableDb.setTransactionSuccessful(); + } catch (SQLiteException e) { + logger.error("Error updating on table: " + table + " with whereClause: " + whereClause + " and whereArgs: " + whereArgs, e); + } catch (IllegalStateException e) { + logger.error("Error under update transaction under table: " + table + " with whereClause: " + whereClause + " and whereArgs: " + whereArgs, e); + } finally { + if (writableDb != null) { + try { + writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. + } catch (IllegalStateException e) { + logger.error("Error closing transaction! ", e); + } catch (SQLiteException e) { + logger.error("Error closing transaction! ", e); + } + } + } + } + return result; + } + + @Override + public void delete(@NonNull String table, @Nullable String whereClause, @Nullable String[] whereArgs) { + synchronized (LOCK) { + SQLiteDatabase writableDb = getSQLiteDatabaseWithRetries(); + try { + writableDb.beginTransaction(); + writableDb.delete(table, whereClause, whereArgs); + writableDb.setTransactionSuccessful(); + } catch (SQLiteException e) { + logger.error("Error deleting on table: " + table + " with whereClause: " + whereClause + " and whereArgs: " + whereArgs, e); + } catch (IllegalStateException e) { + logger.error("Error under delete transaction under table: " + table + " with whereClause: " + whereClause + " and whereArgs: " + whereArgs, e); + } finally { + if (writableDb != null) { + try { + writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. + } catch (IllegalStateException e) { + logger.error("Error closing transaction! ", e); + } catch (SQLiteException e) { + logger.error("Error closing transaction! ", e); + } + } } } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalNotificationManager.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalNotificationManager.java index fb9b40eb3a..cdbf4f1dc2 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalNotificationManager.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalNotificationManager.java @@ -4,7 +4,6 @@ import android.app.NotificationManager; import android.content.Context; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.os.Build; import android.service.notification.StatusBarNotification; import android.support.annotation.RequiresApi; @@ -119,10 +118,7 @@ static void assignGrouplessNotifications(Context context, ArrayList LOCATION_FIELDS_SET = new HashSet<>(Arrays.asList(LOCATION_FIELDS)); - // Object to synchronize on to prevent concurrent modifications on syncValues and dependValues - private static final Object syncLock = new Object() {}; - private String persistKey; - JSONObject dependValues, syncValues; + private JSONObject dependValues, syncValues; + + public ImmutableJSONObject getDependValues() { + try { + return new ImmutableJSONObject(getDependValuesCopy()); + } catch (JSONException e) { + e.printStackTrace(); + } + return new ImmutableJSONObject(); + } + + void setDependValues(JSONObject dependValues) { + synchronized (LOCK) { + this.dependValues = dependValues; + } + } + + JSONObject getDependValuesCopy() throws JSONException { + synchronized (LOCK) { + return new JSONObject(dependValues.toString()); + } + } + + public ImmutableJSONObject getSyncValues() { + try { + return new ImmutableJSONObject(getSyncValuesCopy()); + } catch (JSONException e) { + e.printStackTrace(); + } + return new ImmutableJSONObject(); + } + + public JSONObject getSyncValuesCopy() throws JSONException { + synchronized (LOCK) { + return new JSONObject(syncValues.toString()); + } + } + + public void setSyncValues(JSONObject syncValues) { + synchronized (LOCK) { + this.syncValues = syncValues; + } + } UserState(String inPersistKey, boolean load) { persistKey = inPersistKey; - if (load) + if (load) { loadState(); - else { + } else { dependValues = new JSONObject(); syncValues = new JSONObject(); } @@ -62,8 +108,8 @@ UserState deepClone(String persistKey) { UserState clonedUserState = newInstance(persistKey); try { - clonedUserState.dependValues = new JSONObject(dependValues.toString()); - clonedUserState.syncValues = new JSONObject(syncValues.toString()); + clonedUserState.dependValues = getDependValuesCopy(); + clonedUserState.syncValues = getSyncValuesCopy(); } catch (JSONException e) { e.printStackTrace(); } @@ -78,8 +124,14 @@ UserState deepClone(String persistKey) { private Set getGroupChangeFields(UserState changedTo) { try { if (dependValues.optLong("loc_time_stamp") != changedTo.dependValues.getLong("loc_time_stamp")) { - changedTo.syncValues.put("loc_bg", changedTo.dependValues.opt("loc_bg")); - changedTo.syncValues.put("loc_time_stamp", changedTo.dependValues.opt("loc_time_stamp")); + + HashMap syncValuesToPut = new HashMap<>(); + + syncValuesToPut.put("loc_bg", changedTo.dependValues.opt("loc_bg")); + syncValuesToPut.put("loc_time_stamp", changedTo.dependValues.opt("loc_time_stamp")); + + putValues(changedTo.syncValues, syncValuesToPut); + return LOCATION_FIELDS_SET; } } catch (Throwable t) {} @@ -87,14 +139,68 @@ private Set getGroupChangeFields(UserState changedTo) { return null; } + void putOnSyncValues(String key, Object value) throws JSONException { + synchronized (LOCK) { + syncValues.put(key, value); + } + } + + + void putOnDependValues(String key, Object value) throws JSONException { + synchronized (LOCK) { + dependValues.put(key, value); + } + } + + private void putValues(JSONObject jsonObject, HashMap values) throws JSONException { + synchronized (LOCK) { + for (Map.Entry entry : values.entrySet()) { + jsonObject.put(entry.getKey(), entry.getValue()); + } + } + } + + void removeFromSyncValues(String key) { + synchronized (LOCK) { + syncValues.remove(key); + } + } + + void removeFromSyncValues(List keys) { + synchronized (LOCK) { + for (String key : keys) { + syncValues.remove(key); + } + } + } + + void removeFromDependValues(String key) { + synchronized (LOCK) { + dependValues.remove(key); + } + } + + void removeFromDependValues(List keys) { + synchronized (LOCK) { + for (String key : keys) { + dependValues.remove(key); + } + } + } + void setLocation(LocationController.LocationPoint point) { try { - syncValues.put("lat", point.lat); - syncValues.put("long",point.log); - syncValues.put("loc_acc", point.accuracy); - syncValues.put("loc_type", point.type); - dependValues.put("loc_bg", point.bg); - dependValues.put("loc_time_stamp", point.timeStamp); + HashMap syncValuesToPut = new HashMap<>(); + syncValuesToPut.put("lat", point.lat); + syncValuesToPut.put("long",point.log); + syncValuesToPut.put("loc_acc", point.accuracy); + syncValuesToPut.put("loc_type", point.type); + putValues(syncValues, syncValuesToPut); + + HashMap dependValuesToPut = new HashMap<>(); + dependValuesToPut.put("loc_bg", point.bg); + dependValuesToPut.put("loc_time_stamp", point.timeStamp); + putValues(dependValues, dependValuesToPut); } catch (JSONException e) { e.printStackTrace(); } @@ -102,16 +208,19 @@ void setLocation(LocationController.LocationPoint point) { void clearLocation() { try { - syncValues.put("lat", null); - syncValues.put("long", null); - syncValues.put("loc_acc", null); - syncValues.put("loc_type", null); - - syncValues.put("loc_bg", null); - syncValues.put("loc_time_stamp", null); - - dependValues.put("loc_bg", null); - dependValues.put("loc_time_stamp", null); + HashMap syncValuesToPut = new HashMap<>(); + syncValuesToPut.put("lat", null); + syncValuesToPut.put("long", null); + syncValuesToPut.put("loc_acc", null); + syncValuesToPut.put("loc_type", null); + syncValuesToPut.put("loc_bg", null); + syncValuesToPut.put("loc_time_stamp", null); + putValues(syncValues, syncValuesToPut); + + HashMap dependValuesToPut = new HashMap<>(); + dependValuesToPut.put("loc_bg", null); + dependValuesToPut.put("loc_time_stamp", null); + putValues(dependValues, dependValuesToPut); } catch (JSONException e) { e.printStackTrace(); } @@ -131,6 +240,8 @@ JSONObject generateJsonDiff(UserState newState, boolean isSessionCall) { sendJson.put("app_id", syncValues.optString("app_id")); if (syncValues.has("email_auth_hash")) sendJson.put("email_auth_hash", syncValues.optString("email_auth_hash")); + if (syncValues.has("external_user_id_auth_hash")) + sendJson.put("external_user_id_auth_hash", syncValues.optString("external_user_id_auth_hash")); } catch (JSONException e) { e.printStackTrace(); } @@ -138,21 +249,13 @@ JSONObject generateJsonDiff(UserState newState, boolean isSessionCall) { return sendJson; } - void set(String key, Object value) { - try { - syncValues.put(key, value); - } catch (JSONException e) { - e.printStackTrace(); - } - } - private void loadState() { // null if first run of a 2.0+ version. String dependValuesStr = OneSignalPrefs.getString(OneSignalPrefs.PREFS_ONESIGNAL, OneSignalPrefs.PREFS_ONESIGNAL_USERSTATE_DEPENDVALYES_ + persistKey,null); if (dependValuesStr == null) { - dependValues = new JSONObject(); + setDependValues(new JSONObject()); try { int subscribableStatus; boolean userSubscribePref = true; @@ -169,13 +272,15 @@ private void loadState() { userSubscribePref = false; } - dependValues.put("subscribableStatus", subscribableStatus); - dependValues.put("userSubscribePref", userSubscribePref); + HashMap dependValuesToPut = new HashMap<>(); + dependValuesToPut.put("subscribableStatus", subscribableStatus); + dependValuesToPut.put("userSubscribePref", userSubscribePref); + putValues(dependValues, dependValuesToPut); } catch (JSONException e) {} - } - else { + } else { try { - dependValues = new JSONObject(dependValuesStr); + JSONObject dependValues = new JSONObject(dependValuesStr); + setDependValues(dependValues); } catch (JSONException e) { e.printStackTrace(); } @@ -184,21 +289,24 @@ private void loadState() { String syncValuesStr = OneSignalPrefs.getString(OneSignalPrefs.PREFS_ONESIGNAL, OneSignalPrefs.PREFS_ONESIGNAL_USERSTATE_SYNCVALYES_ + persistKey,null); try { + JSONObject syncValues; if (syncValuesStr == null) { syncValues = new JSONObject(); String gtRegistrationId = OneSignalPrefs.getString(OneSignalPrefs.PREFS_ONESIGNAL, OneSignalPrefs.PREFS_GT_REGISTRATION_ID,null); syncValues.put("identifier", gtRegistrationId); - } - else + } else { syncValues = new JSONObject(syncValuesStr); + } + + setSyncValues(syncValues); } catch (JSONException e) { e.printStackTrace(); } } void persistState() { - synchronized(syncLock) { + synchronized(LOCK) { OneSignalPrefs.saveString(OneSignalPrefs.PREFS_ONESIGNAL, OneSignalPrefs.PREFS_ONESIGNAL_USERSTATE_SYNCVALYES_ + persistKey, syncValues.toString()); OneSignalPrefs.saveString(OneSignalPrefs.PREFS_ONESIGNAL, @@ -220,43 +328,70 @@ void persistStateAfterSync(JSONObject inDependValues, JSONObject inSyncValues) { } void mergeTags(JSONObject inSyncValues, JSONObject omitKeys) { - synchronized (syncLock) { - if (inSyncValues.has("tags")) { - JSONObject newTags; - if (syncValues.has("tags")) { - try { - newTags = new JSONObject(syncValues.optString("tags")); - } catch (JSONException e) { - newTags = new JSONObject(); - } + if (!inSyncValues.has(TAGS)) + return; + + try { + JSONObject syncValues = getSyncValuesCopy(); + JSONObject newTags; + if (syncValues.has(TAGS)) { + try { + newTags = new JSONObject(syncValues.optString(TAGS)); + } catch (JSONException e) { + newTags = new JSONObject(); } + } else { + newTags = new JSONObject(); + } + JSONObject curTags = inSyncValues.optJSONObject(TAGS); + Iterator keys = curTags.keys(); + String key; + + while (keys.hasNext()) { + key = keys.next(); + if ("".equals(curTags.optString(key))) + newTags.remove(key); + else if (omitKeys == null || !omitKeys.has(key)) + newTags.put(key, curTags.optString(key)); + } + + synchronized (LOCK) { + if (newTags.toString().equals("{}")) + this.syncValues.remove(TAGS); else - newTags = new JSONObject(); + this.syncValues.put(TAGS, newTags); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } - JSONObject curTags = inSyncValues.optJSONObject("tags"); - Iterator keys = curTags.keys(); - String key; + JSONObject generateJsonDiffFromIntoSyncValued(JSONObject changedTo, Set includeFields) { + synchronized (LOCK) { + return JSONUtils.generateJsonDiff(syncValues, changedTo, syncValues, includeFields); + } + } - try { - while (keys.hasNext()) { - key = keys.next(); - if ("".equals(curTags.optString(key))) - newTags.remove(key); - else if (omitKeys == null || !omitKeys.has(key)) - newTags.put(key, curTags.optString(key)); - } - - if (newTags.toString().equals("{}")) - syncValues.remove("tags"); - else - syncValues.put("tags", newTags); - } catch (Throwable t) {} - } + JSONObject generateJsonDiffFromSyncValued(UserState changedTo, Set includeFields) { + synchronized (LOCK) { + return JSONUtils.generateJsonDiff(syncValues, changedTo.syncValues, null, includeFields); + } + } + + JSONObject generateJsonDiffFromIntoDependValues(JSONObject changedTo, Set includeFields) { + synchronized (LOCK) { + return JSONUtils.generateJsonDiff(dependValues, changedTo, dependValues, includeFields); + } + } + + JSONObject generateJsonDiffFromDependValues(UserState changedTo, Set includeFields) { + synchronized (LOCK) { + return JSONUtils.generateJsonDiff(dependValues, changedTo.dependValues, null, includeFields); } } private static JSONObject generateJsonDiff(JSONObject cur, JSONObject changedTo, JSONObject baseOutput, Set includeFields) { - synchronized (syncLock) { + synchronized (LOCK) { return JSONUtils.generateJsonDiff(cur, changedTo, baseOutput, includeFields); } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateEmailSynchronizer.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateEmailSynchronizer.java index 428fa7d468..ef9a667fc4 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateEmailSynchronizer.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateEmailSynchronizer.java @@ -7,6 +7,9 @@ import org.json.JSONException; import org.json.JSONObject; +import java.util.ArrayList; +import java.util.List; + class UserStateEmailSynchronizer extends UserStateSynchronizer { UserStateEmailSynchronizer() { @@ -73,7 +76,8 @@ protected void scheduleSyncToServer() { } void setEmail(String email, String emailAuthHash) { - JSONObject syncValues = getUserStateForModification().syncValues; + UserState userState = getUserStateForModification(); + ImmutableJSONObject syncValues = userState.getSyncValues(); boolean noChange = email.equals(syncValues.optString("identifier")) && syncValues.optString("email_auth_hash").equals(emailAuthHash == null ? "" : emailAuthHash); @@ -102,7 +106,7 @@ void setEmail(String email, String emailAuthHash) { } } - generateJsonDiff(syncValues, emailJSON, syncValues, null); + userState.generateJsonDiffFromIntoSyncValued(emailJSON, null); scheduleSyncToServer(); } catch (JSONException e) { @@ -135,10 +139,12 @@ void logoutEmail() { OneSignal.saveEmailId(""); resetCurrentState(); - getToSyncUserState().syncValues.remove("identifier"); - toSyncUserState.syncValues.remove("email_auth_hash"); - toSyncUserState.syncValues.remove("device_player_id"); - toSyncUserState.syncValues.remove("external_user_id"); + getToSyncUserState().removeFromSyncValues("identifier"); + List keysToRemove = new ArrayList<>(); + keysToRemove.add("email_auth_hash"); + keysToRemove.add("device_player_id"); + keysToRemove.add("external_user_id"); + toSyncUserState.removeFromSyncValues(keysToRemove); toSyncUserState.persistState(); OneSignal.getPermissionSubscriptionState().emailSubscriptionStatus.clearEmailAndId(); diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStatePush.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStatePush.java index bd97db9bc3..5fbafe405e 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStatePush.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStatePush.java @@ -16,20 +16,20 @@ UserState newInstance(String persistKey) { @Override protected void addDependFields() { try { - syncValues.put("notification_types", getNotificationTypes()); + putOnSyncValues("notification_types", getNotificationTypes()); } catch (JSONException e) {} } private int getNotificationTypes() { - int subscribableStatus = dependValues.optInt("subscribableStatus", 1); + int subscribableStatus = getDependValues().optInt("subscribableStatus", 1); if (subscribableStatus < PUSH_STATUS_UNSUBSCRIBE) return subscribableStatus; - boolean androidPermission = dependValues.optBoolean("androidPermission", true); + boolean androidPermission = getDependValues().optBoolean("androidPermission", true); if (!androidPermission) return PUSH_STATUS_NO_PERMISSION; - boolean userSubscribePref = dependValues.optBoolean("userSubscribePref", true); + boolean userSubscribePref = getDependValues().optBoolean("userSubscribePref", true); if (!userSubscribePref) return PUSH_STATUS_UNSUBSCRIBE; diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStatePushSynchronizer.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStatePushSynchronizer.java index c74aa4c277..4d0828d2a8 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStatePushSynchronizer.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStatePushSynchronizer.java @@ -50,12 +50,12 @@ void onSuccess(String responseStr) { try { JSONObject lastGetTagsResponse = new JSONObject(responseStr); if (lastGetTagsResponse.has("tags")) { - synchronized(syncLock) { - JSONObject dependDiff = generateJsonDiff(currentUserState.syncValues.optJSONObject("tags"), - getToSyncUserState().syncValues.optJSONObject("tags"), + synchronized(LOCK) { + JSONObject dependDiff = generateJsonDiff(currentUserState.getSyncValues().optJSONObject("tags"), + getToSyncUserState().getSyncValues().optJSONObject("tags"), null, null); - currentUserState.syncValues.put("tags", lastGetTagsResponse.optJSONObject("tags")); + currentUserState.putOnSyncValues("tags", lastGetTagsResponse.optJSONObject("tags")); currentUserState.persistState(); // Allow server side tags to overwrite local tags expect for any pending changes @@ -71,15 +71,15 @@ void onSuccess(String responseStr) { }, OneSignalRestClient.CACHE_KEY_GET_TAGS); } - synchronized(syncLock) { - return new GetTagsResult(serverSuccess, JSONUtils.getJSONObjectWithoutBlankValues(toSyncUserState.syncValues, "tags")); + synchronized(LOCK) { + return new GetTagsResult(serverSuccess, JSONUtils.getJSONObjectWithoutBlankValues(toSyncUserState.getSyncValues(), "tags")); } } @Override @Nullable String getExternalId(boolean fromServer) { - synchronized(syncLock) { - return toSyncUserState.syncValues.optString("external_user_id", null); + synchronized(LOCK) { + return toSyncUserState.getSyncValues().optString("external_user_id", null); } } @@ -96,8 +96,8 @@ void updateState(JSONObject pushState) { if (pushState.has("device_type")) syncUpdate.put("device_type", pushState.optInt("device_type")); syncUpdate.putOpt("parent_player_id", pushState.optString("parent_player_id", null)); - JSONObject toSync = getUserStateForModification().syncValues; - generateJsonDiff(toSync, syncUpdate, toSync, null); + UserState userState = getUserStateForModification(); + userState.generateJsonDiffFromIntoSyncValued(syncUpdate, null); } catch(JSONException t) { t.printStackTrace(); } @@ -108,8 +108,9 @@ void updateState(JSONObject pushState) { dependUpdate.put("subscribableStatus", pushState.optInt("subscribableStatus")); if (pushState.has("androidPermission")) dependUpdate.put("androidPermission", pushState.optBoolean("androidPermission")); - JSONObject dependValues = getUserStateForModification().dependValues; - generateJsonDiff(dependValues, dependUpdate, dependValues, null); + + UserState userState = getUserStateForModification(); + userState.generateJsonDiffFromIntoDependValues(dependUpdate, null); } catch(JSONException t) { t.printStackTrace(); } @@ -119,10 +120,8 @@ void setEmail(String email, String emailAuthHash) { try { UserState userState = getUserStateForModification(); - userState.dependValues.put("email_auth_hash", emailAuthHash); - - JSONObject syncValues = userState.syncValues; - generateJsonDiff(syncValues, new JSONObject().put("email", email), syncValues, null); + userState.putOnDependValues("email_auth_hash", emailAuthHash); + userState.generateJsonDiffFromIntoSyncValued(new JSONObject().put("email", email), null); } catch (JSONException e) { e.printStackTrace(); @@ -132,7 +131,7 @@ void setEmail(String email, String emailAuthHash) { @Override void setSubscription(boolean enable) { try { - getUserStateForModification().dependValues.put("userSubscribePref", enable); + getUserStateForModification().putOnDependValues("userSubscribePref", enable); } catch (JSONException e) { e.printStackTrace(); } @@ -140,13 +139,13 @@ void setSubscription(boolean enable) { @Override public boolean getUserSubscribePreference() { - return getToSyncUserState().dependValues.optBoolean("userSubscribePref", true); + return getToSyncUserState().getDependValues().optBoolean("userSubscribePref", true); } @Override public void setPermission(boolean enable) { try { - getUserStateForModification().dependValues.put("androidPermission", enable); + getUserStateForModification().putOnDependValues("androidPermission", enable); } catch (JSONException e) { e.printStackTrace(); } @@ -168,7 +167,7 @@ protected void addOnSessionOrCreateExtras(JSONObject jsonBody) {} @Override void logoutEmail() { try { - getUserStateForModification().dependValues.put("logoutEmail", true); + getUserStateForModification().putOnDependValues("logoutEmail", true); } catch (JSONException e) { e.printStackTrace(); } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateSynchronizer.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateSynchronizer.java index 50257f9453..123e43b776 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateSynchronizer.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateSynchronizer.java @@ -22,7 +22,11 @@ abstract class UserStateSynchronizer { + // Object to synchronize on to prevent concurrent modifications on syncValues and dependValues + protected final Object LOCK = new Object(); + private UserStateSynchronizerType channel; + private boolean canMakeUpdates; UserStateSynchronizer(UserStateSynchronizerType channel) { this.channel = channel; @@ -46,15 +50,10 @@ static class GetTagsResult { } } - private boolean canMakeUpdates; - - // Object to synchronize on to prevent concurrent modifications on syncValues and dependValues - protected final Object syncLock = new Object() {}; - abstract boolean getSubscribed(); String getRegistrationId() { - return getToSyncUserState().syncValues.optString("identifier", null); + return getToSyncUserState().getSyncValues().optString("identifier", null); } abstract GetTagsResult getTags(boolean fromServer); @@ -147,13 +146,13 @@ boolean doRetry() { protected UserState currentUserState, toSyncUserState; protected JSONObject generateJsonDiff(JSONObject cur, JSONObject changedTo, JSONObject baseOutput, Set includeFields) { - synchronized (syncLock) { + synchronized (LOCK) { return JSONUtils.generateJsonDiff(cur, changedTo, baseOutput, includeFields); } } protected UserState getCurrentUserState() { - synchronized (syncLock) { + synchronized (LOCK) { if (currentUserState == null) currentUserState = newUserState("CURRENT_STATE", true); } @@ -162,7 +161,7 @@ protected UserState getCurrentUserState() { } protected UserState getToSyncUserState() { - synchronized (syncLock) { + synchronized (LOCK) { if (toSyncUserState == null) toSyncUserState = newUserState("TOSYNC_STATE", true); } @@ -171,7 +170,7 @@ protected UserState getToSyncUserState() { } void initUserState() { - synchronized (syncLock) { + synchronized (LOCK) { if (currentUserState == null) currentUserState = newUserState("CURRENT_STATE", true); } @@ -188,7 +187,7 @@ void clearLocation() { boolean persist() { if (toSyncUserState != null) { - synchronized (syncLock) { + synchronized (LOCK) { boolean unSynced = currentUserState.generateJsonDiff(toSyncUserState, isSessionCall()) != null; toSyncUserState.persistState(); return unSynced; @@ -201,12 +200,12 @@ boolean persist() { protected abstract String getId(); private boolean isSessionCall() { - boolean toSyncSession = getToSyncUserState().dependValues.optBoolean("session"); + boolean toSyncSession = getToSyncUserState().getDependValues().optBoolean("session"); return (toSyncSession || getId() == null) && !waitingForSessionResponse; } private boolean syncEmailLogout() { - return getToSyncUserState().dependValues.optBoolean("logoutEmail", false); + return getToSyncUserState().getDependValues().optBoolean("logoutEmail", false); } void syncUserState(boolean fromSyncService) { @@ -228,9 +227,10 @@ private void internalSyncUserState(boolean fromSyncService) { final boolean isSessionCall = !fromSyncService && isSessionCall(); JSONObject jsonBody, dependDiff; - synchronized (syncLock) { + synchronized (LOCK) { jsonBody = currentUserState.generateJsonDiff(getToSyncUserState(), isSessionCall); - dependDiff = generateJsonDiff(currentUserState.dependValues, getToSyncUserState().dependValues, null, null); + UserState toSyncState = getToSyncUserState(); + dependDiff = currentUserState.generateJsonDiffFromDependValues(toSyncState, null);; // Updates did not result in a server side change, skipping network call if (jsonBody == null) { @@ -252,11 +252,11 @@ private void doEmailLogout(String userId) { String urlStr = "players/" + userId + "/email_logout"; JSONObject jsonBody = new JSONObject(); try { - JSONObject dependValues = currentUserState.dependValues; + ImmutableJSONObject dependValues = currentUserState.getDependValues(); if (dependValues.has("email_auth_hash")) jsonBody.put("email_auth_hash", dependValues.optString("email_auth_hash")); - JSONObject syncValues = currentUserState.syncValues; + ImmutableJSONObject syncValues = currentUserState.getSyncValues(); if (syncValues.has("parent_player_id")) jsonBody.put("parent_player_id", syncValues.optString("parent_player_id")); @@ -289,15 +289,15 @@ void onSuccess(String response) { } private void logoutEmailSyncSuccess() { - getToSyncUserState().dependValues.remove("logoutEmail"); - toSyncUserState.dependValues.remove("email_auth_hash"); - toSyncUserState.syncValues.remove("parent_player_id"); + getToSyncUserState().removeFromDependValues("logoutEmail"); + toSyncUserState.removeFromDependValues("email_auth_hash"); + toSyncUserState.removeFromSyncValues("parent_player_id"); toSyncUserState.persistState(); - currentUserState.dependValues.remove("email_auth_hash"); - currentUserState.syncValues.remove("parent_player_id"); - String emailLoggedOut = currentUserState.syncValues.optString("email"); - currentUserState.syncValues.remove("email"); + currentUserState.removeFromDependValues("email_auth_hash"); + currentUserState.removeFromSyncValues("parent_player_id"); + String emailLoggedOut = currentUserState.getSyncValues().optString("email"); + currentUserState.removeFromSyncValues("email"); OneSignalStateSynchronizer.setNewSessionForEmail(); @@ -318,7 +318,7 @@ private void doPutSync(String userId, final JSONObject jsonBody, final JSONObjec void onFailure(int statusCode, String response, Throwable throwable) { OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Failed PUT sync request with status code: " + statusCode + " and response: " + response); - synchronized (syncLock) { + synchronized (LOCK) { if (response400WithErrorsContaining(statusCode, response, "No user with this id found")) handlePlayerDeletedFromServer(); else @@ -336,7 +336,7 @@ void onFailure(int statusCode, String response, Throwable throwable) { @Override void onSuccess(String response) { - synchronized (syncLock) { + synchronized (LOCK) { currentUserState.persistStateAfterSync(dependDiff, jsonBody); onSuccessfulSync(jsonBody); } @@ -362,7 +362,7 @@ private void doCreateOrNewSession(final String userId, final JSONObject jsonBody OneSignalRestClient.postSync(urlStr, jsonBody, new OneSignalRestClient.ResponseHandler() { @Override void onFailure(int statusCode, String response, Throwable throwable) { - synchronized (syncLock) { + synchronized (LOCK) { waitingForSessionResponse = false; OneSignal.Log(OneSignal.LOG_LEVEL.WARN, "Failed last request. statusCode: " + statusCode + "\nresponse: " + response); @@ -375,7 +375,7 @@ void onFailure(int statusCode, String response, Throwable throwable) { @Override void onSuccess(String response) { - synchronized (syncLock) { + synchronized (LOCK) { waitingForSessionResponse = false; currentUserState.persistStateAfterSync(dependDiff, jsonBody); @@ -391,12 +391,12 @@ void onSuccess(String response) { else OneSignal.Log(OneSignal.LOG_LEVEL.INFO, "session sent, UserId = " + userId); - getUserStateForModification().dependValues.put("session", false); + getUserStateForModification().putOnDependValues("session", false); getUserStateForModification().persistState(); // List of in app messages to evaluate for the session if (jsonResponse.has(IN_APP_MESSAGES_JSON_KEY)) - OSInAppMessageController.getController().receivedInAppMessageJson(jsonResponse.getJSONArray(IN_APP_MESSAGES_JSON_KEY)); + OneSignal.getInAppMessageController().receivedInAppMessageJson(jsonResponse.getJSONArray(IN_APP_MESSAGES_JSON_KEY)); onSuccessfulSync(jsonBody); } catch (JSONException e) { @@ -427,7 +427,7 @@ private void fireNetworkFailureEvents() { if (jsonBody != null) fireEventsForUpdateFailure(jsonBody); - if (getToSyncUserState().dependValues.optBoolean("logoutEmail", false)) + if (getToSyncUserState().getDependValues().optBoolean("logoutEmail", false)) OneSignal.handleFailedEmailLogout(); } @@ -471,16 +471,15 @@ protected UserState getUserStateForModification() { abstract protected void scheduleSyncToServer(); void updateDeviceInfo(JSONObject deviceInfo) { - JSONObject toSync = getUserStateForModification().syncValues; - generateJsonDiff(toSync, deviceInfo, toSync, null); + getUserStateForModification().generateJsonDiffFromIntoSyncValued(deviceInfo, null); } abstract void updateState(JSONObject state); void setNewSession() { try { - synchronized (syncLock) { - getUserStateForModification().dependValues.put("session", true); + synchronized (LOCK) { + getUserStateForModification().putOnDependValues("session", true); getUserStateForModification().persistState(); } } catch (JSONException e) { @@ -489,25 +488,28 @@ void setNewSession() { } boolean getSyncAsNewSession() { - return getUserStateForModification().dependValues.optBoolean("session"); + return getUserStateForModification().getDependValues().optBoolean("session"); } void sendTags(JSONObject tags, @Nullable ChangeTagsUpdateHandler handler) { if (handler != null) this.sendTagsHandlers.add(handler); - JSONObject userStateTags = getUserStateForModification().syncValues; - generateJsonDiff(userStateTags, tags, userStateTags, null); + UserState userStateTags = getUserStateForModification(); + userStateTags.generateJsonDiffFromIntoSyncValued(tags, null); } void syncHashedEmail(JSONObject emailFields) { - JSONObject syncValues = getUserStateForModification().syncValues; - generateJsonDiff(syncValues, emailFields, syncValues, null); + getUserStateForModification().generateJsonDiffFromIntoSyncValued(emailFields, null); } - void setExternalUserId(final String externalId, OneSignal.OSInternalExternalUserIdUpdateCompletionHandler handler) throws JSONException { + void setExternalUserId(final String externalId, final String externalIdAuthHash, OneSignal.OSInternalExternalUserIdUpdateCompletionHandler handler) throws JSONException { if (handler != null) this.externalUserIdUpdateHandlers.add(handler); - getUserStateForModification().syncValues.put("external_user_id", externalId); + + UserState userState = getUserStateForModification(); + userState.putOnSyncValues("external_user_id", externalId); + if (externalIdAuthHash != null) + userState.putOnSyncValues("external_user_id_auth_hash", externalIdAuthHash); } abstract void setSubscription(boolean enable); @@ -521,7 +523,7 @@ private void handlePlayerDeletedFromServer() { } void resetCurrentState() { - currentUserState.syncValues = new JSONObject(); + currentUserState.setSyncValues(new JSONObject()); currentUserState.persistState(); } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/WebViewManager.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/WebViewManager.java index 2456377405..646f6e0fc5 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/WebViewManager.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/WebViewManager.java @@ -84,6 +84,7 @@ protected WebViewManager(@NonNull OSInAppMessage message, @NonNull Activity acti */ static void showHTMLString(@NonNull final OSInAppMessage message, @NonNull final String htmlStr) { final Activity currentActivity = ActivityLifecycleHandler.curActivity; + OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "in app message showHTMLString on currentActivity: " + currentActivity); /* IMPORTANT * This is the starting route for grabbing the current Activity and passing it to InAppMessageView */ if (currentActivity != null) { @@ -98,9 +99,9 @@ public void onComplete() { initInAppMessage(currentActivity, message, htmlStr); } }); - } - else + } else { initInAppMessage(currentActivity, message, htmlStr); + } return; } @@ -206,9 +207,9 @@ private void handleActionTaken(JSONObject jsonObject) throws JSONException { JSONObject body = jsonObject.getJSONObject("body"); String id = body.optString("id", null); if (message.isPreview) { - OSInAppMessageController.getController().onMessageActionOccurredOnPreview(message, body); + OneSignal.getInAppMessageController().onMessageActionOccurredOnPreview(message, body); } else if (id != null) { - OSInAppMessageController.getController().onMessageActionOccurredOnMessage(message, body); + OneSignal.getInAppMessageController().onMessageActionOccurredOnMessage(message, body); } boolean close = body.getBoolean("close"); @@ -239,12 +240,17 @@ private static int pageRectToViewHeight(final @NonNull Activity activity, @NonNu // Every time an Activity is shown we update the height of the WebView since the available // screen size may have changed. (Expect for Fullscreen) private void calculateHeightAndShowWebViewAfterNewActivity() { + if (messageView == null) + return; + // Don't need a CSS / HTML height update for fullscreen if (messageView.getDisplayPosition() == Position.FULL_SCREEN) { showMessageView(null); return; } + OneSignal.Log(OneSignal.LOG_LEVEL.DEBUG, "In app message new activity, calculate height and show "); + // Using post to ensure that the status bar inset is already added to the view OSViewUtils.decorViewReady(activity, new Runnable() { @Override @@ -277,17 +283,25 @@ void available(final @NonNull Activity activity) { } @Override - void stopped(WeakReference reference) { + void stopped() { if (messageView != null) messageView.removeAllViews(); } + @Override + void lostFocus() { + OneSignal.getInAppMessageController().messageWasDismissedByBackPress(message); + removeActivityListener(); + messageView = null; + } + private void showMessageView(@Nullable Integer newHeight) { if (messageView == null) { OneSignal.Log(OneSignal.LOG_LEVEL.WARN, "No messageView found to update a with a new height."); return; } + OneSignal.Log(OneSignal.LOG_LEVEL.DEBUG, "In app message, showing first one with height: " + newHeight); messageView.setWebView(webView); if (newHeight != null) messageView.updateHeight(newHeight); @@ -344,13 +358,13 @@ private void createNewInAppMessageView(@NonNull Position displayLocation, int pa @Override public void onMessageWasShown() { firstShow = false; - OSInAppMessageController.getController().onMessageWasShown(message); + OneSignal.getInAppMessageController().onMessageWasShown(message); } @Override public void onMessageWasDismissed() { - OSInAppMessageController.getController().messageWasDismissed(message); - ActivityLifecycleHandler.removeActivityAvailableListener(TAG + message.messageId); + OneSignal.getInAppMessageController().messageWasDismissed(message); + removeActivityListener(); } }); @@ -374,6 +388,9 @@ private static int getWebViewMaxSizeY(Activity activity) { return OSViewUtils.getWindowHeight(activity) - (MARGIN_PX_SIZE * 2); } + private void removeActivityListener() { + ActivityLifecycleHandler.removeActivityAvailableListener(TAG + message.messageId); + } /** * Trigger the {@link #messageView} dismiss animation flow */ diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/outcomes/OSOutcomeEventsCache.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/outcomes/OSOutcomeEventsCache.java index 2d2195f92a..5103b65ec6 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/outcomes/OSOutcomeEventsCache.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/outcomes/OSOutcomeEventsCache.java @@ -2,9 +2,6 @@ import android.content.ContentValues; import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; import android.support.annotation.NonNull; import android.support.annotation.WorkerThread; @@ -69,24 +66,8 @@ void saveUnattributedUniqueOutcomeEventsSentByChannel(Set unattributedUn */ @WorkerThread synchronized void deleteOldOutcomeEvent(OSOutcomeEventParams event) { - SQLiteDatabase writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - - try { - writableDb.beginTransaction(); - writableDb.delete(OutcomeEventsTable.TABLE_NAME, - OutcomeEventsTable.COLUMN_NAME_TIMESTAMP + " = ?", new String[]{String.valueOf(event.getTimestamp())}); - writableDb.setTransactionSuccessful(); - } catch (SQLiteException e) { - logger.error("Error deleting old outcome event records! ", e); - } finally { - if (writableDb != null) { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (SQLiteException e) { - logger.error("Error closing transaction! ", e); - } - } - } + dbHelper.delete(OutcomeEventsTable.TABLE_NAME, + OutcomeEventsTable.COLUMN_NAME_TIMESTAMP + " = ?", new String[]{String.valueOf(event.getTimestamp())}); } /** @@ -132,31 +113,19 @@ synchronized void saveOutcomeEvent(OSOutcomeEventParams eventParams) { } } } - - SQLiteDatabase writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - writableDb.beginTransaction(); - try { - ContentValues values = new ContentValues(); - // Save influence ids - values.put(OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS, notificationIds.toString()); - values.put(OutcomeEventsTable.COLUMN_NAME_IAM_IDS, iamIds.toString()); - // Save influence types - values.put(OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE, notificationInfluenceType.toString().toLowerCase()); - values.put(OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE, iamInfluenceType.toString().toLowerCase()); - // Save outcome data - values.put(OutcomeEventsTable.COLUMN_NAME_NAME, eventParams.getOutcomeId()); - values.put(OutcomeEventsTable.COLUMN_NAME_WEIGHT, eventParams.getWeight()); - values.put(OutcomeEventsTable.COLUMN_NAME_TIMESTAMP, eventParams.getTimestamp()); - - writableDb.insert(OutcomeEventsTable.TABLE_NAME, null, values); - writableDb.setTransactionSuccessful(); - } finally { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (SQLException e) { - logger.error("Error closing transaction! ", e); - } - } + ContentValues values = new ContentValues(); + // Save influence ids + values.put(OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS, notificationIds.toString()); + values.put(OutcomeEventsTable.COLUMN_NAME_IAM_IDS, iamIds.toString()); + // Save influence types + values.put(OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE, notificationInfluenceType.toString().toLowerCase()); + values.put(OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE, iamInfluenceType.toString().toLowerCase()); + // Save outcome data + values.put(OutcomeEventsTable.COLUMN_NAME_NAME, eventParams.getOutcomeId()); + values.put(OutcomeEventsTable.COLUMN_NAME_WEIGHT, eventParams.getWeight()); + values.put(OutcomeEventsTable.COLUMN_NAME_TIMESTAMP, eventParams.getTimestamp()); + + dbHelper.insert(OutcomeEventsTable.TABLE_NAME, null, values); } /** @@ -167,90 +136,84 @@ synchronized void saveOutcomeEvent(OSOutcomeEventParams eventParams) { @WorkerThread synchronized List getAllEventsToSend() { List events = new ArrayList<>(); - Cursor cursor = null; + Cursor + cursor = dbHelper.query(OutcomeEventsTable.TABLE_NAME, + null, + null, + null, + null, + null, + null + ); + + if (cursor.moveToFirst()) { + do { + // Retrieve influence types + String notificationInfluenceTypeString = cursor.getString(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE)); + OSInfluenceType notificationInfluenceType = OSInfluenceType.fromString(notificationInfluenceTypeString); + String iamInfluenceTypeString = cursor.getString(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE)); + OSInfluenceType iamInfluenceType = OSInfluenceType.fromString(iamInfluenceTypeString); + + // Retrieve influence ids + String notificationIds = cursor.getString(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS)); + notificationIds = notificationIds != null ? notificationIds : "[]"; + String iamIds = cursor.getString(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_IAM_IDS)); + iamIds = iamIds != null ? iamIds : "[]"; + + // Retrieve Outcome data + String name = cursor.getString(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_NAME)); + float weight = cursor.getFloat(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_WEIGHT)); + long timestamp = cursor.getLong(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_TIMESTAMP)); - try { - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); - cursor = readableDb.query( - OutcomeEventsTable.TABLE_NAME, - null, - null, - null, - null, - null, - null - ); - - if (cursor.moveToFirst()) { - do { - // Retrieve influence types - String notificationInfluenceTypeString = cursor.getString(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_INFLUENCE_TYPE)); - OSInfluenceType notificationInfluenceType = OSInfluenceType.fromString(notificationInfluenceTypeString); - String iamInfluenceTypeString = cursor.getString(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_IAM_INFLUENCE_TYPE)); - OSInfluenceType iamInfluenceType = OSInfluenceType.fromString(iamInfluenceTypeString); - - // Retrieve influence ids - String notificationIds = cursor.getString(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_NOTIFICATION_IDS)); - notificationIds = notificationIds != null ? notificationIds : "[]"; - String iamIds = cursor.getString(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_IAM_IDS)); - iamIds = iamIds != null ? iamIds : "[]"; - - // Retrieve Outcome data - String name = cursor.getString(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_NAME)); - float weight = cursor.getFloat(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_WEIGHT)); - long timestamp = cursor.getLong(cursor.getColumnIndex(OutcomeEventsTable.COLUMN_NAME_TIMESTAMP)); - - try { - OSOutcomeSourceBody directSourceBody = new OSOutcomeSourceBody(); - OSOutcomeSourceBody indirectSourceBody = new OSOutcomeSourceBody(); - OSOutcomeSource source = null; - - switch (notificationInfluenceType) { - case DIRECT: - directSourceBody.setNotificationIds(new JSONArray(notificationIds)); - source = new OSOutcomeSource(directSourceBody, null); - break; - case INDIRECT: - indirectSourceBody.setNotificationIds(new JSONArray(notificationIds)); - source = new OSOutcomeSource(null, indirectSourceBody); - break; - case UNATTRIBUTED: - // Keep source as null, no source mean unattributed - break; - case DISABLED: - // We should not save disable - break; - } - - switch (iamInfluenceType) { - case DIRECT: - directSourceBody.setInAppMessagesIds(new JSONArray(iamIds)); - source = source == null ? new OSOutcomeSource(directSourceBody, null) : source.setDirectBody(directSourceBody); - break; - case INDIRECT: - indirectSourceBody.setInAppMessagesIds(new JSONArray(iamIds)); - source = source == null ? new OSOutcomeSource(null, indirectSourceBody) : source.setIndirectBody(indirectSourceBody); - break; - case UNATTRIBUTED: - // Keep source as null, no source mean unattributed - break; - case DISABLED: - // We should not save disable - break; - } - - OSOutcomeEventParams eventParams = new OSOutcomeEventParams(name, source, weight, timestamp); - events.add(eventParams); - } catch (JSONException e) { - logger.error("Generating JSONArray from notifications ids outcome:JSON Failed.", e); + try { + OSOutcomeSourceBody directSourceBody = new OSOutcomeSourceBody(); + OSOutcomeSourceBody indirectSourceBody = new OSOutcomeSourceBody(); + OSOutcomeSource source = null; + + switch (notificationInfluenceType) { + case DIRECT: + directSourceBody.setNotificationIds(new JSONArray(notificationIds)); + source = new OSOutcomeSource(directSourceBody, null); + break; + case INDIRECT: + indirectSourceBody.setNotificationIds(new JSONArray(notificationIds)); + source = new OSOutcomeSource(null, indirectSourceBody); + break; + case UNATTRIBUTED: + // Keep source as null, no source mean unattributed + break; + case DISABLED: + // We should not save disable + break; } - } while (cursor.moveToNext()); - } - } finally { - if (cursor != null && !cursor.isClosed()) - cursor.close(); + + switch (iamInfluenceType) { + case DIRECT: + directSourceBody.setInAppMessagesIds(new JSONArray(iamIds)); + source = source == null ? new OSOutcomeSource(directSourceBody, null) : source.setDirectBody(directSourceBody); + break; + case INDIRECT: + indirectSourceBody.setInAppMessagesIds(new JSONArray(iamIds)); + source = source == null ? new OSOutcomeSource(null, indirectSourceBody) : source.setIndirectBody(indirectSourceBody); + break; + case UNATTRIBUTED: + // Keep source as null, no source mean unattributed + break; + case DISABLED: + // We should not save disable + break; + } + + OSOutcomeEventParams eventParams = new OSOutcomeEventParams(name, source, weight, timestamp); + events.add(eventParams); + } catch (JSONException e) { + logger.error("Generating JSONArray from notifications ids outcome:JSON Failed.", e); + } + } while (cursor.moveToNext()); } + cursor.close(); + return events; } @@ -295,25 +258,14 @@ synchronized void saveUniqueOutcomeEventParams(@NonNull OSOutcomeEventParams eve addIdsToListFromSource(cachedUniqueOutcomes, directBody); addIdsToListFromSource(cachedUniqueOutcomes, indirectBody); - SQLiteDatabase writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - writableDb.beginTransaction(); - try { - for (OSCachedUniqueOutcome uniqueOutcome : cachedUniqueOutcomes) { - ContentValues values = new ContentValues(); + for (OSCachedUniqueOutcome uniqueOutcome : cachedUniqueOutcomes) { + ContentValues values = new ContentValues(); - values.put(CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID, uniqueOutcome.getInfluenceId()); - values.put(CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE, String.valueOf(uniqueOutcome.getChannel())); - values.put(CachedUniqueOutcomeTable.COLUMN_NAME_NAME, outcomeName); + values.put(CachedUniqueOutcomeTable.COLUMN_CHANNEL_INFLUENCE_ID, uniqueOutcome.getInfluenceId()); + values.put(CachedUniqueOutcomeTable.COLUMN_CHANNEL_TYPE, String.valueOf(uniqueOutcome.getChannel())); + values.put(CachedUniqueOutcomeTable.COLUMN_NAME_NAME, outcomeName); - writableDb.insert(CachedUniqueOutcomeTable.TABLE_NAME, null, values); - } - writableDb.setTransactionSuccessful(); - } finally { - try { - writableDb.endTransaction(); // May throw if transaction was never opened or DB is full. - } catch (SQLException e) { - logger.error("Error closing transaction! ", e); - } + dbHelper.insert(CachedUniqueOutcomeTable.TABLE_NAME, null, values); } } @@ -323,7 +275,6 @@ synchronized void saveUniqueOutcomeEventParams(@NonNull OSOutcomeEventParams eve @WorkerThread synchronized List getNotCachedUniqueInfluencesForOutcome(String name, List influences) { List uniqueInfluences = new ArrayList<>(); - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); Cursor cursor = null; try { @@ -346,7 +297,7 @@ synchronized List getNotCachedUniqueInfluencesForOutcome(String nam String[] args = new String[]{channelInfluenceId, String.valueOf(channel), name}; - cursor = readableDb.query( + cursor = dbHelper.query( CachedUniqueOutcomeTable.TABLE_NAME, columns, where, diff --git a/OneSignalSDK/unittest/src/test/java/com/onesignal/InAppMessagingHelpers.java b/OneSignalSDK/unittest/src/test/java/com/onesignal/InAppMessagingHelpers.java index 83eefe335e..c5f9b57169 100644 --- a/OneSignalSDK/unittest/src/test/java/com/onesignal/InAppMessagingHelpers.java +++ b/OneSignalSDK/unittest/src/test/java/com/onesignal/InAppMessagingHelpers.java @@ -19,20 +19,20 @@ public class InAppMessagingHelpers { public static final String IAM_CLICK_ID = "12345678-1234-1234-1234-123456789012"; public static boolean evaluateMessage(OSInAppMessage message) { - return OSInAppMessageController.getController().triggerController.evaluateMessageTriggers(message); + return OneSignal.getInAppMessageController().triggerController.evaluateMessageTriggers(message); } public static boolean dynamicTriggerShouldFire(OSTrigger trigger) { - return OSInAppMessageController.getController().triggerController.dynamicTriggerController.dynamicTriggerShouldFire(trigger); + return OneSignal.getInAppMessageController().triggerController.dynamicTriggerController.dynamicTriggerShouldFire(trigger); } public static void resetSessionLaunchTime() { - OSDynamicTriggerController.sessionLaunchTime = new Date(); + OSDynamicTriggerController.resetSessionLaunchTime(); } public static void clearTestState() { OneSignal.pauseInAppMessages(false); - OSInAppMessageController.getController().getInAppMessageDisplayQueue().clear(); + OneSignal.getInAppMessageController().getInAppMessageDisplayQueue().clear(); } // Convenience method that wraps an object in a JSON Array diff --git a/OneSignalSDK/unittest/src/test/java/com/onesignal/MockOneSignalDBHelper.java b/OneSignalSDK/unittest/src/test/java/com/onesignal/MockOneSignalDBHelper.java index 889eda03cd..c05b1302c1 100644 --- a/OneSignalSDK/unittest/src/test/java/com/onesignal/MockOneSignalDBHelper.java +++ b/OneSignalSDK/unittest/src/test/java/com/onesignal/MockOneSignalDBHelper.java @@ -1,11 +1,19 @@ package com.onesignal; +import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteCantOpenDatabaseException; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabaseLockedException; +import android.os.SystemClock; import com.onesignal.outcomes.OSOutcomeTableProvider; public class MockOneSignalDBHelper implements OneSignalDb { + private static final int DB_OPEN_RETRY_MAX = 5; + private static final int DB_OPEN_RETRY_BACKOFF = 400; private Context context; @@ -13,13 +21,50 @@ public MockOneSignalDBHelper(Context context) { this.context = context; } - @Override + public void close() { + OneSignalDbHelper.getInstance(context).close(); + } + public SQLiteDatabase getSQLiteDatabaseWithRetries() { - return OneSignalDbHelper.getInstance(context).getSQLiteDatabaseWithRetries(); + int count = 0; + while (true) { + try { + return OneSignalDbHelper.getInstance(context).getWritableDatabase(); + } catch (SQLiteCantOpenDatabaseException | SQLiteDatabaseLockedException e) { + if (++count >= DB_OPEN_RETRY_MAX) + throw e; + SystemClock.sleep(count * DB_OPEN_RETRY_BACKOFF); + } + } } - public void close() { - OneSignalDbHelper.getInstance(context).close(); + @Override + public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) { + return OneSignalDbHelper.getInstance(context).query(table, columns, selection, selectionArgs, groupBy, having, orderBy); + } + + @Override + public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) { + return OneSignalDbHelper.getInstance(context).query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit); } + @Override + public void insert(String table, String nullColumnHack, ContentValues values) { + OneSignalDbHelper.getInstance(context).insert(table, nullColumnHack, values); + } + + @Override + public void insertOrThrow(String table, String nullColumnHack, ContentValues values) throws SQLException { + OneSignalDbHelper.getInstance(context).insertOrThrow(table, nullColumnHack, values); + } + + @Override + public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { + return OneSignalDbHelper.getInstance(context).update(table, values, whereClause, whereArgs); + } + + @Override + public void delete(String table, String whereClause, String[] whereArgs) { + OneSignalDbHelper.getInstance(context).delete(table, whereClause, whereArgs); + } } diff --git a/OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalNotificationManagerPackageHelper.java b/OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalNotificationManagerPackageHelper.java index 30b7772c2e..ef0559c967 100644 --- a/OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalNotificationManagerPackageHelper.java +++ b/OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalNotificationManagerPackageHelper.java @@ -39,7 +39,7 @@ public static StatusBarNotification[] getActiveNotifications(Context context) { return getNotificationManager(context).getActiveNotifications(); } - public static Integer getMostRecentNotifIdFromGroup(SQLiteDatabase db, String group, boolean isGroupless) { + public static Integer getMostRecentNotifIdFromGroup(OneSignalDbHelper db, String group, boolean isGroupless) { return OneSignalNotificationManager.getMostRecentNotifIdFromGroup(db, group, isGroupless); } diff --git a/OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalPackagePrivateHelper.java b/OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalPackagePrivateHelper.java index bddaf23e91..2bc32b1b44 100644 --- a/OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalPackagePrivateHelper.java +++ b/OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalPackagePrivateHelper.java @@ -203,8 +203,8 @@ public static void NotificationOpenedProcessor_processFromContext(Context contex NotificationOpenedProcessor.processFromContext(context, intent); } - public static void NotificationSummaryManager_updateSummaryNotificationAfterChildRemoved(Context context, SQLiteDatabase writableDb, String group, boolean dismissed) { - NotificationSummaryManager.updateSummaryNotificationAfterChildRemoved(context, writableDb, group, dismissed); + public static void NotificationSummaryManager_updateSummaryNotificationAfterChildRemoved(Context context, OneSignalDb db, String group, boolean dismissed) { + NotificationSummaryManager.updateSummaryNotificationAfterChildRemoved(context, db, group, dismissed); } public class TestOneSignalPrefs extends com.onesignal.OneSignalPrefs {} @@ -244,18 +244,9 @@ public RemoteOutcomeParams(boolean direct, boolean indirect, boolean unattribute } } - public static SQLiteDatabase OneSignal_getSQLiteDatabase(Context context) { - return OneSignalDbHelper.getInstance(context).getSQLiteDatabase(); - } - - public static void OneSignal_cleanOutcomeDatabaseTable(Context context) { - OneSignalDbHelper.cleanOutcomeDatabaseTable( - OneSignal_getSQLiteDatabase(context)); - } - public static class BadgeCountUpdater extends com.onesignal.BadgeCountUpdater { - public static void update(SQLiteDatabase readableDb, Context context) { - com.onesignal.BadgeCountUpdater.update(readableDb, context); + public static void update(OneSignalDb db, Context context) { + com.onesignal.BadgeCountUpdater.update(db, context); } } @@ -450,33 +441,33 @@ public OSTestInAppMessageAction(JSONObject json) throws JSONException { } public static void dismissCurrentMessage() { - com.onesignal.OSInAppMessage message = com.onesignal.OSInAppMessageController.getController().getCurrentDisplayedInAppMessage(); + com.onesignal.OSInAppMessage message = OneSignal.getInAppMessageController().getCurrentDisplayedInAppMessage(); if (message != null) - com.onesignal.OSInAppMessageController.getController().messageWasDismissed(message); + OneSignal.getInAppMessageController().messageWasDismissed(message); } public static boolean isInAppMessageShowing() { - return com.onesignal.OSInAppMessageController.getController().isInAppMessageShowing(); + return OneSignal.getInAppMessageController().isInAppMessageShowing(); } public static String getShowingInAppMessageId() { - return com.onesignal.OSInAppMessageController.getController().getCurrentDisplayedInAppMessage().messageId; + return OneSignal.getInAppMessageController().getCurrentDisplayedInAppMessage().messageId; } public static ArrayList getInAppMessageDisplayQueue() { - return com.onesignal.OSInAppMessageController.getController().getInAppMessageDisplayQueue(); + return OneSignal.getInAppMessageController().getInAppMessageDisplayQueue(); } public static void onMessageActionOccurredOnMessage(@NonNull final com.onesignal.OSInAppMessage message, @NonNull final JSONObject actionJson) throws JSONException { - com.onesignal.OSInAppMessageController.getController().onMessageActionOccurredOnMessage(message, actionJson); + OneSignal.getInAppMessageController().onMessageActionOccurredOnMessage(message, actionJson); } public static void onMessageWasShown(@NonNull com.onesignal.OSInAppMessage message) { - com.onesignal.OSInAppMessageController.getController().onMessageWasShown(message); + OneSignal.getInAppMessageController().onMessageWasShown(message); } public static List getRedisplayInAppMessages() { - List messages = com.onesignal.OSInAppMessageController.getController().getRedisplayedInAppMessages(); + List messages = OneSignal.getInAppMessageController().getRedisplayedInAppMessages(); List testMessages = new ArrayList<>(); for (OSInAppMessage message : messages) { diff --git a/OneSignalSDK/unittest/src/test/java/com/onesignal/StaticResetHelper.java b/OneSignalSDK/unittest/src/test/java/com/onesignal/StaticResetHelper.java index cd68f6dcb5..7ede5ef8b4 100644 --- a/OneSignalSDK/unittest/src/test/java/com/onesignal/StaticResetHelper.java +++ b/OneSignalSDK/unittest/src/test/java/com/onesignal/StaticResetHelper.java @@ -23,6 +23,9 @@ public boolean onOtherField(Field field) throws Exception { if (field.getName().equals("unprocessedOpenedNotifis")) { field.set(null, new ArrayList()); return true; + } else if (field.getName().equals("inAppMessageControllerFactory")) { + field.set(null, new OSInAppMessageControllerFactory()); + return true; } return false; } diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/DatabaseRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/DatabaseRunner.java index 0daab7b47f..b1b9228ee9 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/DatabaseRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/DatabaseRunner.java @@ -630,7 +630,7 @@ public void shouldUpgradeDbFromV4ToV8OutcomeTable() { outcomeValues.put(MockOSOutcomeEventsTable.COLUMN_NAME_WEIGHT, outcomeEventDB.getWeight()); outcomeValues.put(MockOSOutcomeEventsTable.COLUMN_NAME_TIMESTAMP, outcomeEventDB.getTimestamp()); - writableDatabase.insert(MockOSOutcomeEventsTable.TABLE_NAME, null, outcomeValues); + dbHelper.insert(MockOSOutcomeEventsTable.TABLE_NAME, null, outcomeValues); List outcomesSaved = getAllOutcomesRecords(dbHelper); assertEquals(1, outcomesSaved.size()); diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/DeviceTypeTestsRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/DeviceTypeTestsRunner.java index f51fc58928..fc08302c6b 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/DeviceTypeTestsRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/DeviceTypeTestsRunner.java @@ -14,10 +14,9 @@ import org.robolectric.shadows.ShadowLog; import static com.onesignal.OneSignalPackagePrivateHelper.UserState.DEVICE_TYPE_ANDROID; -import static com.onesignal.OneSignalPackagePrivateHelper.UserState.DEVICE_TYPE_HUAWEI; import static com.onesignal.OneSignalPackagePrivateHelper.UserState.DEVICE_TYPE_FIREOS; +import static com.onesignal.OneSignalPackagePrivateHelper.UserState.DEVICE_TYPE_HUAWEI; import static com.onesignal.OneSignalPackagePrivateHelper.getDeviceType; - import static org.junit.Assert.assertEquals; @Config( diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java index ea620a3243..afcb5b127b 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java @@ -144,7 +144,6 @@ }, sdk = 21 ) - @RunWith(RobolectricTestRunner.class) public class GenerateNotificationRunner { @@ -250,8 +249,8 @@ public void shouldNotRestoreActiveNotifs() throws Exception { JobScheduler scheduler = (JobScheduler)blankActivity.getSystemService(Context.JOB_SCHEDULER_SERVICE); assertTrue(scheduler.getAllPendingJobs().isEmpty()); } - - + + private static OSNotificationOpenResult lastOpenResult; @Test @@ -880,8 +879,7 @@ public void badgeCountShouldNotIncludeOldNotifications() { advanceSystemTimeBy(604_801); // Should not count as a badge - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); - OneSignalPackagePrivateHelper.BadgeCountUpdater.update(readableDb, blankActivity); + OneSignalPackagePrivateHelper.BadgeCountUpdater.update(dbHelper, blankActivity); assertEquals(0, ShadowBadgeCountUpdater.lastCount); } @@ -974,8 +972,7 @@ public void shouldGenerate2BasicGroupNotifications() { @Test public void shouldHandleOpeningInAppAlertWithGroupKeySet() { - SQLiteDatabase writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - NotificationSummaryManager_updateSummaryNotificationAfterChildRemoved(blankActivity, writableDb, "some_group", false); + NotificationSummaryManager_updateSummaryNotificationAfterChildRemoved(blankActivity, dbHelper, "some_group", false); } @Test @@ -991,8 +988,7 @@ public void shouldNotDisplaySummaryWhenDismissingAnInAppAlertIfOneDidntAlreadyEx NotificationBundleProcessor_ProcessFromGCMIntentService(blankActivity, bundle, null); // Test1 - Manually trigger a refresh on grouped notification. - SQLiteDatabase writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - NotificationSummaryManager_updateSummaryNotificationAfterChildRemoved(blankActivity, writableDb, "test1", false); + NotificationSummaryManager_updateSummaryNotificationAfterChildRemoved(blankActivity, dbHelper, "test1", false); assertEquals(0, ShadowRoboNotificationManager.notifications.size()); @@ -1002,8 +998,7 @@ public void shouldNotDisplaySummaryWhenDismissingAnInAppAlertIfOneDidntAlreadyEx NotificationBundleProcessor_ProcessFromGCMIntentService(blankActivity, bundle, null); // Test2 - Manually trigger a refresh on grouped notification. - writableDb = dbHelper.getSQLiteDatabaseWithRetries(); - NotificationSummaryManager_updateSummaryNotificationAfterChildRemoved(blankActivity, writableDb, "test1", false); + NotificationSummaryManager_updateSummaryNotificationAfterChildRemoved(blankActivity, dbHelper, "test1", false); assertEquals(0, ShadowRoboNotificationManager.notifications.size()); } diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/InAppMessageIntegrationTests.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/InAppMessageIntegrationTests.java index 5f9ce7864b..af71213f4d 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/InAppMessageIntegrationTests.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/InAppMessageIntegrationTests.java @@ -18,8 +18,8 @@ import com.onesignal.ShadowCustomTabsClient; import com.onesignal.ShadowCustomTabsSession; import com.onesignal.ShadowDynamicTimer; -import com.onesignal.ShadowJobService; import com.onesignal.ShadowGMSLocationController; +import com.onesignal.ShadowJobService; import com.onesignal.ShadowNotificationManagerCompat; import com.onesignal.ShadowOSUtils; import com.onesignal.ShadowOSViewUtils; @@ -242,6 +242,26 @@ public void testMessageDismissingWhileDeviceIsRotating() throws Exception { assertTrue(OneSignalPackagePrivateHelper.isInAppMessageShowing()); } + // This test reproduces https://github.com/OneSignal/OneSignal-Android-SDK/issues/1000 + @Test + public void testMessageDismissingWhileNewActivityIsBeingStarted() throws Exception { + initializeSdkWithMultiplePendingMessages(); + + // 1. Add trigger to show IAM + OneSignal.addTriggers(new HashMap() {{ + put("test_2", 2); + }}); + + // 2. Activity IAM is displaying over is dismissed + blankActivityController.stop(); + + // 3. IAM is dismissed before a new Activity is shown + OneSignalPackagePrivateHelper.WebViewManager.callDismissAndAwaitNextMessage(); + + // 4. An Activity put in to focus, successful we don't throw. + blankActivityController.resume(); + } + private void nextResponseMultiplePendingMessages() throws JSONException { final OSTestInAppMessage testFirstMessage = InAppMessagingHelpers.buildTestMessageWithSingleTrigger(OSTriggerKind.CUSTOM, "test_1", OSTestTrigger.OSTriggerOperator.EQUAL_TO.toString(), 3); @@ -445,6 +465,25 @@ public void doNotReshowInAppIfDismissed_evenAfterColdRestart() throws Exception assertEquals(0, OneSignalPackagePrivateHelper.getInAppMessageDisplayQueue().size()); } + @Test + public void doNotReshowInAppUntilTriggerIfAppBackPressed() throws Exception { + // 1. Start app + initializeSdkWithMultiplePendingMessages(); + // 2. Trigger showing In App and dismiss it + OneSignal.addTrigger("test_2", 2); + assertEquals(1, OneSignalPackagePrivateHelper.getInAppMessageDisplayQueue().size()); + assertTrue(OneSignalPackagePrivateHelper.isInAppMessageShowing()); + // 3. Emulate back pressing + blankActivityController.destroy(); + threadAndTaskWait(); + // 4. Put activity back to foreground + blankActivityController = Robolectric.buildActivity(BlankActivity.class).create(); + OneSignalInit(); + threadAndTaskWait(); + assertEquals(1, OneSignalPackagePrivateHelper.getInAppMessageDisplayQueue().size()); + assertFalse(OneSignalPackagePrivateHelper.isInAppMessageShowing()); + } + @Test public void reshowInAppIfDisplayedButNeverDismissedAfterColdRestart() throws Exception { // 1. Start app @@ -1163,6 +1202,65 @@ public void testInAppMessageDisplayMultipleTimes() throws Exception { assertTrue( OneSignalPackagePrivateHelper.getRedisplayInAppMessages().get(0).getRedisplayStats().getLastDisplayTime() - lastDisplayTime >= DELAY); } + @Test + public void testInAppMessageDisplayMultipleTimes_sessionDurationTrigger() throws Exception { + final OSTestInAppMessage message = InAppMessagingHelpers.buildTestMessageWithSingleTriggerAndRedisplay(OSTriggerKind.SESSION_TIME, "", + OSTestTrigger.OSTriggerOperator.GREATER_THAN.toString(), 0.05, LIMIT, DELAY); + + setMockRegistrationResponseWithMessages(new ArrayList() {{ + add(message); + }}); + + // Init OneSignal IAM with redisplay + OneSignalInit(); + threadAndTaskWait(); + + // No schedule should happen, IAM should evaluate to true + assertEquals(1, OneSignalPackagePrivateHelper.getInAppMessageDisplayQueue().size()); + + // Dismiss IAM will make display quantity increase and last display time to change + OneSignalPackagePrivateHelper.dismissCurrentMessage(); + // Check IAMs was removed from queue + assertEquals(0, OneSignalPackagePrivateHelper.getInAppMessageDisplayQueue().size()); + // Check if data after dismiss is set correctly + assertEquals(1, OneSignalPackagePrivateHelper.getRedisplayInAppMessages().size()); + assertEquals(1, OneSignalPackagePrivateHelper.getRedisplayInAppMessages().get(0).getRedisplayStats().getDisplayQuantity()); + long lastDisplayTime = OneSignalPackagePrivateHelper.getRedisplayInAppMessages().get(0).getRedisplayStats().getLastDisplayTime(); + assertTrue(lastDisplayTime > 0); + + // Change time for delay to be covered + advanceSystemTimeBy(DELAY); + fastColdRestartApp(); + + setMockRegistrationResponseWithMessages(new ArrayList() {{ + add(message); + }}); + + // Init OneSignal IAM with redisplay + OneSignalInit(); + threadAndTaskWait(); + + // No schedule should happen since session time period is very small, should evaluate to true on first run + // Wait for redisplay logic + Awaitility.await() + .atMost(new Duration(150, TimeUnit.MILLISECONDS)) + .pollInterval(new Duration(10, TimeUnit.MILLISECONDS)) + .untilAsserted(new ThrowingRunnable() { + @Override + public void run() { + assertEquals(1, OneSignalPackagePrivateHelper.getInAppMessageDisplayQueue().size()); + } + }); + + OneSignalPackagePrivateHelper.dismissCurrentMessage(); + // Check IAMs was removed from queue + assertEquals(0, OneSignalPackagePrivateHelper.getInAppMessageDisplayQueue().size()); + // Check if data after dismiss is set correctly + assertEquals(1, OneSignalPackagePrivateHelper.getRedisplayInAppMessages().size()); + assertEquals(2, OneSignalPackagePrivateHelper.getRedisplayInAppMessages().get(0).getRedisplayStats().getDisplayQuantity()); + assertTrue( OneSignalPackagePrivateHelper.getRedisplayInAppMessages().get(0).getRedisplayStats().getLastDisplayTime() - lastDisplayTime >= DELAY); + } + @Test public void testInAppMessageDisplayMultipleTimes_NoTriggers() throws Exception { final long currentTimeInSeconds = System.currentTimeMillis() / 1000; diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java index 324aed4618..066f8c111a 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java @@ -36,7 +36,6 @@ import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.location.Location; import android.net.ConnectivityManager; import android.os.Bundle; @@ -70,15 +69,15 @@ import com.onesignal.ShadowCustomTabsSession; import com.onesignal.ShadowFirebaseAnalytics; import com.onesignal.ShadowFusedLocationApiWrapper; -import com.onesignal.ShadowHMSFusedLocationProviderClient; +import com.onesignal.ShadowGMSLocationController; +import com.onesignal.ShadowGMSLocationUpdateListener; import com.onesignal.ShadowGoogleApiClientBuilder; import com.onesignal.ShadowGoogleApiClientCompatProxy; +import com.onesignal.ShadowHMSFusedLocationProviderClient; import com.onesignal.ShadowHMSLocationUpdateListener; import com.onesignal.ShadowHmsInstanceId; import com.onesignal.ShadowHuaweiTask; import com.onesignal.ShadowJobService; -import com.onesignal.ShadowGMSLocationController; -import com.onesignal.ShadowGMSLocationUpdateListener; import com.onesignal.ShadowNotificationManagerCompat; import com.onesignal.ShadowOSUtils; import com.onesignal.ShadowOneSignal; @@ -238,7 +237,7 @@ private static void cleanUp() throws Exception { notificationOpenedMessage = null; lastGetTags = null; - lastExternalUserIdResponse = null; + lastExternalUserIdResponse = null;; ShadowGMSLocationController.reset(); TestHelpers.beforeTestInitAndCleanup(); @@ -3653,8 +3652,7 @@ public void shouldCancelAndClearNotifications() throws Exception { assertEquals(0, ShadowRoboNotificationManager.notifications.size()); // Make sure they are marked dismissed. - SQLiteDatabase readableDb = dbHelper.getSQLiteDatabaseWithRetries(); - Cursor cursor = readableDb.query(OneSignalPackagePrivateHelper.NotificationTable.TABLE_NAME, new String[] { "created_time" }, + Cursor cursor = dbHelper.query(OneSignalPackagePrivateHelper.NotificationTable.TABLE_NAME, new String[] { "created_time" }, OneSignalPackagePrivateHelper.NotificationTable.COLUMN_NAME_DISMISSED + " = 1", null, null, null, null); assertEquals(2, cursor.getCount()); cursor.close(); @@ -4126,6 +4124,125 @@ public void shouldSendExternalUserIdBeforeRegistration() throws Exception { assertEquals(testExternalId, registrationRequest.payload.getString("external_user_id")); } + @Test + public void shouldSetExternalIdWithAuthHash() throws Exception { + ShadowOneSignalRestClient.paramExtras = new JSONObject().put("require_user_id_auth", true); + + OneSignalInit(); + threadAndTaskWait(); + + String testExternalId = "test_ext_id"; + + OneSignal.setExternalUserId(testExternalId, getExternalUserIdUpdateCompletionHandler()); + threadAndTaskWait(); + } + + @Test + public void shouldSetExternalIdWithAuthHashAfterRegistration() throws Exception { + OneSignalInit(); + threadAndTaskWait(); + + String testExternalId = "test_ext_id"; + String mockExternalIdHash = new String(new char[64]).replace('\0', '0'); + + OneSignal.setExternalUserId(testExternalId, mockExternalIdHash, null); + threadAndTaskWait(); + + assertEquals(3, ShadowOneSignalRestClient.networkCallCount); + + ShadowOneSignalRestClient.Request externalIdRequest = ShadowOneSignalRestClient.requests.get(2); + assertEquals(ShadowOneSignalRestClient.REST_METHOD.PUT, externalIdRequest.method); + assertEquals(testExternalId, externalIdRequest.payload.getString("external_user_id")); + assertEquals(mockExternalIdHash, externalIdRequest.payload.getString("external_user_id_auth_hash")); + } + + @Test + public void shouldSetExternalIdWithAuthHashBeforeRegistration() throws Exception { + String testExternalId = "test_ext_id"; + String mockExternalIdHash = new String(new char[64]).replace('\0', '0'); + + OneSignal.setExternalUserId(testExternalId, mockExternalIdHash, null); + + OneSignalInit(); + threadAndTaskWait(); + + assertEquals(2, ShadowOneSignalRestClient.networkCallCount); + + ShadowOneSignalRestClient.Request registrationRequest = ShadowOneSignalRestClient.requests.get(1); + assertEquals(ShadowOneSignalRestClient.REST_METHOD.POST, registrationRequest.method); + assertEquals(testExternalId, registrationRequest.payload.getString("external_user_id")); + assertEquals(mockExternalIdHash, registrationRequest.payload.getString("external_user_id_auth_hash")); + } + + @Test + public void shouldAlwaysSetExternalIdWithAuthHashAAfterRegistration() throws Exception { + OneSignalInit(); + threadAndTaskWait(); + + String testExternalId = "test_ext_id"; + String mockExternalIdHash = new String(new char[64]).replace('\0', '0'); + + OneSignal.setExternalUserId(testExternalId, mockExternalIdHash); + threadAndTaskWait(); + + ShadowOneSignalRestClient.Request registrationRequest = ShadowOneSignalRestClient.requests.get(2); + assertEquals(ShadowOneSignalRestClient.REST_METHOD.PUT, registrationRequest.method); + assertEquals(testExternalId, registrationRequest.payload.getString("external_user_id")); + assertEquals(mockExternalIdHash, registrationRequest.payload.getString("external_user_id_auth_hash")); + + fastColdRestartApp(); + + advanceSystemTimeBy(60); + OneSignalInit(); + threadAndTaskWait(); + + ShadowOneSignalRestClient.Request registrationRequestAfterColdStart = ShadowOneSignalRestClient.requests.get(4); + assertEquals(REST_METHOD.POST, registrationRequestAfterColdStart.method); + assertEquals(mockExternalIdHash, registrationRequestAfterColdStart.payload.getString("external_user_id_auth_hash")); + } + + @Test + public void shouldAlwaysSetExternalIdAndEmailWithAuthHashAAfterRegistration() throws Exception { + OneSignalInit(); + threadAndTaskWait(); + + String testExternalId = "test_ext_id"; + String mockExternalIdHash = new String(new char[64]).replace('\0', '0'); + + String email = "josh@onesignal.com"; + String mockEmailHash = new String(new char[64]).replace('\0', '0'); + + OneSignal.setExternalUserId(testExternalId, mockExternalIdHash); + OneSignal.setEmail(email, mockEmailHash); + threadAndTaskWait(); + + ShadowOneSignalRestClient.Request registrationRequest = ShadowOneSignalRestClient.requests.get(2); + assertEquals(ShadowOneSignalRestClient.REST_METHOD.PUT, registrationRequest.method); + assertEquals(testExternalId, registrationRequest.payload.getString("external_user_id")); + assertEquals(mockExternalIdHash, registrationRequest.payload.getString("external_user_id_auth_hash")); + + ShadowOneSignalRestClient.Request emailPost = ShadowOneSignalRestClient.requests.get(3); + assertEquals(REST_METHOD.POST, emailPost.method); + assertEquals(email, emailPost.payload.getString("identifier")); + assertEquals(11, emailPost.payload.getInt("device_type")); + assertEquals(mockEmailHash, emailPost.payload.getString("email_auth_hash")); + + fastColdRestartApp(); + + advanceSystemTimeBy(60); + OneSignalInit(); + threadAndTaskWait(); + + ShadowOneSignalRestClient.Request registrationRequestAfterColdStart = ShadowOneSignalRestClient.requests.get(6); + assertEquals(REST_METHOD.POST, registrationRequestAfterColdStart.method); + assertEquals(mockExternalIdHash, registrationRequestAfterColdStart.payload.getString("external_user_id_auth_hash")); + + ShadowOneSignalRestClient.Request emailPostAfterColdStart = ShadowOneSignalRestClient.requests.get(7); + assertEquals(REST_METHOD.POST, emailPostAfterColdStart.method); + assertEquals(11, emailPostAfterColdStart.payload.getInt("device_type")); + assertEquals(mockEmailHash, emailPostAfterColdStart.payload.getString("email_auth_hash")); + } + @Test public void shouldRemoveExternalUserId() throws Exception { OneSignal.setExternalUserId("test_ext_id"); diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/NotificationOpenedActivityHMSIntegrationTestsRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/NotificationOpenedActivityHMSIntegrationTestsRunner.java index 9958bc9f4e..84d1d812cc 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/NotificationOpenedActivityHMSIntegrationTestsRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/NotificationOpenedActivityHMSIntegrationTestsRunner.java @@ -32,11 +32,11 @@ import java.util.UUID; +import static com.onesignal.InAppMessagingHelpers.ONESIGNAL_APP_ID; +import static com.onesignal.OneSignalPackagePrivateHelper.GenerateNotification.BUNDLE_KEY_ACTION_ID; import static com.onesignal.OneSignalPackagePrivateHelper.NotificationBundleProcessor.PUSH_ADDITIONAL_DATA_KEY; -import static com.onesignal.OneSignalPackagePrivateHelper.OSNotificationFormatHelper.PAYLOAD_OS_ROOT_CUSTOM; import static com.onesignal.OneSignalPackagePrivateHelper.OSNotificationFormatHelper.PAYLOAD_OS_NOTIFICATION_ID; -import static com.onesignal.OneSignalPackagePrivateHelper.GenerateNotification.BUNDLE_KEY_ACTION_ID; -import static com.onesignal.InAppMessagingHelpers.ONESIGNAL_APP_ID; +import static com.onesignal.OneSignalPackagePrivateHelper.OSNotificationFormatHelper.PAYLOAD_OS_ROOT_CUSTOM; import static com.test.onesignal.RestClientAsserts.assertNotificationOpenAtIndex; import static com.test.onesignal.TestHelpers.fastColdRestartApp; import static com.test.onesignal.TestHelpers.threadAndTaskWait; diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/OutcomeEventIntegrationTests.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/OutcomeEventIntegrationTests.java index ce015fd463..53abaf2f2f 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/OutcomeEventIntegrationTests.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/OutcomeEventIntegrationTests.java @@ -16,8 +16,8 @@ import com.onesignal.ShadowAdvertisingIdProviderGPS; import com.onesignal.ShadowCustomTabsClient; import com.onesignal.ShadowCustomTabsSession; -import com.onesignal.ShadowJobService; import com.onesignal.ShadowGMSLocationController; +import com.onesignal.ShadowJobService; import com.onesignal.ShadowNotificationManagerCompat; import com.onesignal.ShadowOSUtils; import com.onesignal.ShadowOneSignalRestClient; diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/PushRegistratorHMSIntegrationTestsRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/PushRegistratorHMSIntegrationTestsRunner.java index 8e4430075f..1fc904d0bd 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/PushRegistratorHMSIntegrationTestsRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/PushRegistratorHMSIntegrationTestsRunner.java @@ -7,8 +7,6 @@ import com.huawei.hms.support.api.client.Status; import com.onesignal.InAppMessagingHelpers; import com.onesignal.OneSignal; -import static com.onesignal.OneSignalPackagePrivateHelper.UserState.PUSH_STATUS_HMS_TOKEN_TIMEOUT; -import static com.onesignal.OneSignalPackagePrivateHelper.UserState.PUSH_STATUS_HMS_API_EXCEPTION_OTHER; import com.onesignal.ShadowCustomTabsClient; import com.onesignal.ShadowCustomTabsSession; import com.onesignal.ShadowHmsInstanceId; @@ -29,6 +27,8 @@ import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLog; +import static com.onesignal.OneSignalPackagePrivateHelper.UserState.PUSH_STATUS_HMS_API_EXCEPTION_OTHER; +import static com.onesignal.OneSignalPackagePrivateHelper.UserState.PUSH_STATUS_HMS_TOKEN_TIMEOUT; import static com.test.onesignal.RestClientAsserts.assertHuaweiPlayerCreateAtIndex; import static com.test.onesignal.RestClientAsserts.assertPlayerCreateNotSubscribedAtIndex; import static com.test.onesignal.RestClientAsserts.assertPlayerCreateSubscribedAtIndex; diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/RESTClientRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/RESTClientRunner.java index 179c28a1c2..e4045dbf65 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/RESTClientRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/RESTClientRunner.java @@ -57,7 +57,7 @@ @RunWith(RobolectricTestRunner.class) public class RESTClientRunner { - + @BeforeClass // Runs only once, before any tests public static void setUpClass() throws Exception { ShadowLog.stream = System.out; @@ -82,7 +82,7 @@ public void testRESTClientFallbackTimeout() throws Exception { mockThreadHang = true; }}; - OneSignalRestClient.getSync("URL", null,""); + OneSignalRestClient.get("URL", null, ""); threadAndTaskWait(); assertTrue(ShadowOneSignalRestClientWithMockConnection.lastConnection.getDidInterruptMockHang()); @@ -92,7 +92,7 @@ public void testRESTClientFallbackTimeout() throws Exception { @Test public void SDKHeaderIsIncludedInGetCalls() throws Exception { - OneSignalRestClient.getSync("URL", null, null); + OneSignalRestClient.get("URL", null, null); threadAndTaskWait(); assertEquals(SDK_VERSION_HTTP_HEADER, getLastHTTPHeaderProp("SDK-Version")); @@ -100,7 +100,7 @@ public void SDKHeaderIsIncludedInGetCalls() throws Exception { @Test public void SDKHeaderIsIncludedInPostCalls() throws Exception { - OneSignalRestClient.postSync("URL", null,null); + OneSignalRestClient.post("URL", null, null); threadAndTaskWait(); assertEquals(SDK_VERSION_HTTP_HEADER, getLastHTTPHeaderProp("SDK-Version")); @@ -108,7 +108,7 @@ public void SDKHeaderIsIncludedInPostCalls() throws Exception { @Test public void SDKHeaderIsIncludedInPutCalls() throws Exception { - OneSignalRestClient.putSync("URL", null,null); + OneSignalRestClient.put("URL", null, null); threadAndTaskWait(); assertEquals(SDK_VERSION_HTTP_HEADER, getLastHTTPHeaderProp("SDK-Version")); @@ -131,7 +131,7 @@ public void testReusesCache() throws Exception { responseBody = "{\"key1\": \"value1\"}"; mockProps.put("etag", MOCK_ETAG_VALUE); }}; - OneSignalRestClient.getSync("URL", new OneSignalRestClient.ResponseHandler() { + OneSignalRestClient.get("URL", new OneSignalRestClient.ResponseHandler() { @Override public void onSuccess(String response) { firstResponse = response; @@ -145,7 +145,7 @@ public void onSuccess(String response) { status = 304; }}; - OneSignalRestClient.getSync("URL", new OneSignalRestClient.ResponseHandler() { + OneSignalRestClient.get("URL", new OneSignalRestClient.ResponseHandler() { @Override public void onSuccess(String response) { secondResponse = response; @@ -171,7 +171,7 @@ public void testReplacesCacheOn200() throws Exception { responseBody = newMockResponse; mockProps.put("etag", "MOCK_ETAG_VALUE2"); }}; - OneSignalRestClient.getSync("URL", null, MOCK_CACHE_KEY); + OneSignalRestClient.get("URL", null, MOCK_CACHE_KEY); threadAndTaskWait(); Thread.sleep(200); @@ -179,7 +179,7 @@ public void testReplacesCacheOn200() throws Exception { ShadowOneSignalRestClientWithMockConnection.mockResponse = new MockHttpURLConnection.MockResponse() {{ status = 304; }}; - OneSignalRestClient.getSync("URL", new OneSignalRestClient.ResponseHandler() { + OneSignalRestClient.get("URL", new OneSignalRestClient.ResponseHandler() { @Override public void onSuccess(String response) { secondResponse = response.replace("\u0000", ""); @@ -203,7 +203,7 @@ public void testApiCall400Response() throws Exception { final String[] failResponse = {null}; final int[] statusCodeResponse = {0}; - OneSignalRestClient.postSync("URL", null, new OneSignalRestClient.ResponseHandler() { + OneSignalRestClient.post("URL", null, new OneSignalRestClient.ResponseHandler() { @Override public void onSuccess(String response) { super.onSuccess(response); @@ -234,7 +234,7 @@ public void testApiCall400EmptyResponse() throws Exception { final String[] failResponse = {null}; final int[] statusCodeResponse = {0}; - OneSignalRestClient.postSync("URL", null, new OneSignalRestClient.ResponseHandler() { + OneSignalRestClient.post("URL", null, new OneSignalRestClient.ResponseHandler() { @Override public void onSuccess(String response) { super.onSuccess(response); diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/SessionManagerUnitTests.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/SessionManagerUnitTests.java index 0e359f85de..d017402b4e 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/SessionManagerUnitTests.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/SessionManagerUnitTests.java @@ -339,7 +339,7 @@ public void testDirectWithEmptyNotification() { } @Test - public void testSessionUpgradeFromAppClosed() { + public void testSessionUpgradeFromAppClosed() throws Exception { trackerFactory.saveInfluenceParams(new OneSignalPackagePrivateHelper.RemoteOutcomeParams()); sessionManager.initSessionFromCache(); @@ -369,6 +369,7 @@ public void testSessionUpgradeFromAppClosed() { } sessionManager.attemptSessionUpgrade(OneSignal.AppEntryAction.APP_CLOSE); + threadAndTaskWait(); influences = sessionManager.getInfluences(); @@ -390,7 +391,7 @@ public void testSessionUpgradeFromAppClosed() { } @Test - public void testSessionUpgradeFromUnattributedToIndirect() throws JSONException { + public void testSessionUpgradeFromUnattributedToIndirect() throws Exception { trackerFactory.saveInfluenceParams(new OneSignalPackagePrivateHelper.RemoteOutcomeParams()); sessionManager.initSessionFromCache(); @@ -420,6 +421,7 @@ public void testSessionUpgradeFromUnattributedToIndirect() throws JSONException } sessionManager.attemptSessionUpgrade(OneSignal.AppEntryAction.APP_OPEN); + threadAndTaskWait(); influences = sessionManager.getInfluences(); @@ -439,7 +441,7 @@ public void testSessionUpgradeFromUnattributedToIndirect() throws JSONException } @Test - public void testSessionUpgradeFromUnattributedToDirectNotification() throws JSONException { + public void testSessionUpgradeFromUnattributedToDirectNotification() throws Exception { trackerFactory.saveInfluenceParams(new OneSignalPackagePrivateHelper.RemoteOutcomeParams()); sessionManager.initSessionFromCache(); @@ -452,6 +454,7 @@ public void testSessionUpgradeFromUnattributedToDirectNotification() throws JSON sessionManager.onNotificationReceived(GENERIC_ID); sessionManager.onInAppMessageReceived(GENERIC_ID); sessionManager.onDirectInfluenceFromNotificationOpen(GENERIC_ID); + threadAndTaskWait(); iamInfluences = trackerFactory.getIAMChannelTracker().getCurrentSessionInfluence(); notificationInfluences = trackerFactory.getNotificationChannelTracker().getCurrentSessionInfluence(); @@ -474,7 +477,7 @@ public void testSessionUpgradeFromUnattributedToDirectNotification() throws JSON } @Test - public void testSessionUpgradeFromIndirectToDirect() throws JSONException { + public void testSessionUpgradeFromIndirectToDirect() throws Exception { trackerFactory.saveInfluenceParams(new OneSignalPackagePrivateHelper.RemoteOutcomeParams()); sessionManager.initSessionFromCache(); @@ -490,6 +493,7 @@ public void testSessionUpgradeFromIndirectToDirect() throws JSONException { assertEquals(GENERIC_ID, notificationInfluences.getIds().get(0)); sessionManager.onDirectInfluenceFromNotificationOpen(NOTIFICATION_ID); + threadAndTaskWait(); iamInfluences = trackerFactory.getIAMChannelTracker().getCurrentSessionInfluence(); notificationInfluences = trackerFactory.getNotificationChannelTracker().getCurrentSessionInfluence(); @@ -513,7 +517,7 @@ public void testSessionUpgradeFromIndirectToDirect() throws JSONException { } @Test - public void testSessionUpgradeFromDirectToDirectDifferentID() throws JSONException { + public void testSessionUpgradeFromDirectToDirectDifferentID() throws Exception { trackerFactory.saveInfluenceParams(new OneSignalPackagePrivateHelper.RemoteOutcomeParams()); sessionManager.initSessionFromCache(); @@ -527,6 +531,7 @@ public void testSessionUpgradeFromDirectToDirectDifferentID() throws JSONExcepti sessionManager.onNotificationReceived(NOTIFICATION_ID); sessionManager.onDirectInfluenceFromNotificationOpen(NOTIFICATION_ID); + threadAndTaskWait(); notificationInfluences = trackerFactory.getNotificationChannelTracker().getCurrentSessionInfluence(); @@ -543,7 +548,7 @@ public void testSessionUpgradeFromDirectToDirectDifferentID() throws JSONExcepti } @Test - public void testSessionUpgradeFromDirectToDirectSameID() throws JSONException { + public void testSessionUpgradeFromDirectToDirectSameID() throws Exception { trackerFactory.saveInfluenceParams(new OneSignalPackagePrivateHelper.RemoteOutcomeParams()); sessionManager.initSessionFromCache(); @@ -556,6 +561,7 @@ public void testSessionUpgradeFromDirectToDirectSameID() throws JSONException { assertEquals(GENERIC_ID, notificationInfluences.getIds().get(0)); sessionManager.attemptSessionUpgrade(OneSignal.AppEntryAction.NOTIFICATION_CLICK); + threadAndTaskWait(); notificationInfluences = trackerFactory.getNotificationChannelTracker().getCurrentSessionInfluence(); @@ -571,7 +577,7 @@ public void testSessionUpgradeFromDirectToDirectSameID() throws JSONException { } @Test - public void testSessionUpgradeFromDirectToDirectEndChannelsDirect() throws JSONException { + public void testSessionUpgradeFromDirectToDirectEndChannelsDirect() throws Exception { trackerFactory.saveInfluenceParams(new OneSignalPackagePrivateHelper.RemoteOutcomeParams()); sessionManager.initSessionFromCache(); @@ -579,6 +585,7 @@ public void testSessionUpgradeFromDirectToDirectEndChannelsDirect() throws JSONE sessionManager.onDirectInfluenceFromNotificationOpen(GENERIC_ID); sessionManager.onInAppMessageReceived(IAM_ID); sessionManager.onDirectInfluenceFromIAMClick(IAM_ID); + threadAndTaskWait(); OSInfluence iamInfluences = trackerFactory.getIAMChannelTracker().getCurrentSessionInfluence(); OSInfluence notificationInfluences = trackerFactory.getNotificationChannelTracker().getCurrentSessionInfluence(); @@ -589,6 +596,7 @@ public void testSessionUpgradeFromDirectToDirectEndChannelsDirect() throws JSONE assertEquals(GENERIC_ID, notificationInfluences.getIds().get(0)); sessionManager.onDirectInfluenceFromNotificationOpen(NOTIFICATION_ID); + threadAndTaskWait(); iamInfluences = trackerFactory.getIAMChannelTracker().getCurrentSessionInfluence(); notificationInfluences = trackerFactory.getNotificationChannelTracker().getCurrentSessionInfluence(); diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/TestHelpers.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/TestHelpers.java index 8767e3278f..ddc436761a 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/TestHelpers.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/TestHelpers.java @@ -6,7 +6,6 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.os.Looper; import android.os.SystemClock; import android.support.annotation.Nullable; @@ -22,9 +21,9 @@ import com.onesignal.ShadowDynamicTimer; import com.onesignal.ShadowFirebaseAnalytics; import com.onesignal.ShadowFusedLocationApiWrapper; -import com.onesignal.ShadowHMSFusedLocationProviderClient; import com.onesignal.ShadowGcmBroadcastReceiver; import com.onesignal.ShadowGoogleApiClientCompatProxy; +import com.onesignal.ShadowHMSFusedLocationProviderClient; import com.onesignal.ShadowHmsInstanceId; import com.onesignal.ShadowNotificationManagerCompat; import com.onesignal.ShadowOSUtils; @@ -240,8 +239,7 @@ static void restartAppAndElapseTimeToNextSession() throws Exception { } static ArrayList> getAllNotificationRecords(OneSignalDb db) { - SQLiteDatabase readableDatabase = db.getSQLiteDatabaseWithRetries(); - Cursor cursor = readableDatabase.query( + Cursor cursor = db.query( OneSignalPackagePrivateHelper.NotificationTable.TABLE_NAME, null, null, @@ -274,9 +272,8 @@ else if (type == Cursor.FIELD_TYPE_FLOAT) return mapList; } - static List getAllOutcomesRecordsDBv5(OneSignalDb db) { - SQLiteDatabase readableDatabase = db.getSQLiteDatabaseWithRetries(); - Cursor cursor = readableDatabase.query( + static List getAllOutcomesRecordsDBv5(OneSignalDb db) { ; + Cursor cursor = db.query( MockOSOutcomeEventsTable.TABLE_NAME, null, null, @@ -308,14 +305,12 @@ static List getAllOutcomesRecordsDBv5(OneSignalDb db) { } cursor.close(); - readableDatabase.close(); return events; } static List getAllOutcomesRecords(OneSignalDb db) { - SQLiteDatabase readableDatabase = db.getSQLiteDatabaseWithRetries(); - Cursor cursor = readableDatabase.query( + Cursor cursor = db.query( MockOSOutcomeEventsTable.TABLE_NAME, null, null, @@ -353,14 +348,12 @@ static List getAllOutcomesRecords(OneSignalDb db) { } cursor.close(); - readableDatabase.close(); return events; } static ArrayList getAllUniqueOutcomeNotificationRecordsDBv5(OneSignalDb db) { - SQLiteDatabase readableDatabase = db.getSQLiteDatabaseWithRetries(); - Cursor cursor = readableDatabase.query( + Cursor cursor = db.query( MockOSCachedUniqueOutcomeTable.TABLE_NAME_V1, null, null, @@ -384,14 +377,12 @@ static ArrayList getAllUniqueOutcomeNotificationRecor } cursor.close(); - readableDatabase.close(); return cachedUniqueOutcomes; } static ArrayList getAllUniqueOutcomeNotificationRecordsDB(OneSignalDb db) { - SQLiteDatabase readableDatabase = db.getSQLiteDatabaseWithRetries(); - Cursor cursor = readableDatabase.query( + Cursor cursor = db.query( MockOSCachedUniqueOutcomeTable.TABLE_NAME_V2, null, null, @@ -416,14 +407,11 @@ static ArrayList getAllUniqueOutcomeNotificationRecor } cursor.close(); - readableDatabase.close(); return cachedUniqueOutcomes; } synchronized static void saveIAM(OSTestInAppMessage inAppMessage, OneSignalDb db) { - SQLiteDatabase writableDatabase = db.getSQLiteDatabaseWithRetries(); - ContentValues values = new ContentValues(); values.put(OneSignalPackagePrivateHelper.InAppMessageTable.COLUMN_NAME_MESSAGE_ID, inAppMessage.messageId); values.put(OneSignalPackagePrivateHelper.InAppMessageTable.COLUMN_NAME_DISPLAY_QUANTITY, inAppMessage.getRedisplayStats().getDisplayQuantity()); @@ -431,13 +419,11 @@ synchronized static void saveIAM(OSTestInAppMessage inAppMessage, OneSignalDb db values.put(OneSignalPackagePrivateHelper.InAppMessageTable.COLUMN_CLICK_IDS, inAppMessage.getClickedClickIds().toString()); values.put(OneSignalPackagePrivateHelper.InAppMessageTable.COLUMN_DISPLAYED_IN_SESSION, inAppMessage.isDisplayedInSession()); - writableDatabase.insert(OneSignalPackagePrivateHelper.InAppMessageTable.TABLE_NAME, null, values); - writableDatabase.close(); + db.insert(OneSignalPackagePrivateHelper.InAppMessageTable.TABLE_NAME, null, values); } synchronized static List getAllInAppMessages(OneSignalDb db) throws JSONException { - SQLiteDatabase readableDatabase = db.getSQLiteDatabaseWithRetries(); - Cursor cursor = readableDatabase.query( + Cursor cursor = db.query( OneSignalPackagePrivateHelper.InAppMessageTable.TABLE_NAME, null, null,