diff --git a/android-core/proguard.pro b/android-core/proguard.pro
index f55779de1..6651d38c2 100644
--- a/android-core/proguard.pro
+++ b/android-core/proguard.pro
@@ -94,6 +94,7 @@
-keep class com.mparticle.MParticle$UserAttributes { *; }
-keep class com.mparticle.MParticle$ResetListener { *; }
-keep class com.mparticle.MParticle$OperatingSystem { *; }
+-keep class com.mparticle.uploadbatching.UploadBatchReceiver
-keep class com.mparticle.Session { *; }
diff --git a/android-core/src/main/AndroidManifest.xml b/android-core/src/main/AndroidManifest.xml
index 9733c3881..8c3b9358b 100644
--- a/android-core/src/main/AndroidManifest.xml
+++ b/android-core/src/main/AndroidManifest.xml
@@ -1,4 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/android-core/src/main/java/com/mparticle/MParticle.java b/android-core/src/main/java/com/mparticle/MParticle.java
index 726c852d4..53ab9ce23 100644
--- a/android-core/src/main/java/com/mparticle/MParticle.java
+++ b/android-core/src/main/java/com/mparticle/MParticle.java
@@ -5,6 +5,7 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationManager;
@@ -51,6 +52,7 @@
import com.mparticle.messaging.MPMessagingAPI;
import com.mparticle.messaging.ProviderCloudMessage;
import com.mparticle.segmentation.SegmentListener;
+import com.mparticle.uploadbatching.UploadBatchReceiver;
import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;
@@ -136,6 +138,13 @@ private MParticle(MParticleOptions options) {
mMessageManager = new MessageManager(configManager, appStateManager, mKitManager, sDevicePerformanceMetricsDisabled, mDatabaseManager, options);
mConfigManager.setNetworkOptions(options.getNetworkOptions());
mPreferences = options.getContext().getSharedPreferences(Constants.PREFS_FILE, Context.MODE_PRIVATE);
+ UploadBatchReceiver receiver = new UploadBatchReceiver();
+ IntentFilter intentFilter = UploadBatchReceiver.Companion.getIntentFilter();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ mAppContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED);
+ } else {
+ mAppContext.registerReceiver(receiver, intentFilter);
+ }
}
/**
diff --git a/android-core/src/main/java/com/mparticle/internal/AppStateManager.java b/android-core/src/main/java/com/mparticle/internal/AppStateManager.java
index 9d76d16a8..c565f1b0d 100644
--- a/android-core/src/main/java/com/mparticle/internal/AppStateManager.java
+++ b/android-core/src/main/java/com/mparticle/internal/AppStateManager.java
@@ -20,6 +20,7 @@
import com.mparticle.identity.IdentityApiRequest;
import com.mparticle.identity.MParticleUser;
import com.mparticle.internal.listeners.InternalListenerManager;
+import com.mparticle.uploadbatching.AlarmSchedulingUtilsKt;
import org.json.JSONObject;
@@ -364,6 +365,9 @@ public void onActivityStopped(Activity activity) {
private void logBackgrounded() {
MParticle instance = MParticle.getInstance();
if (instance != null) {
+ if (mConfigManager.isBackgroundBatchUploadingEnabled()) {
+ AlarmSchedulingUtilsKt.scheduleUploadBatchAlarm(mContext, mConfigManager.getUploadInterval());
+ }
logStateTransition(Constants.StateTransitionType.STATE_TRANS_BG, mCurrentActivityName);
instance.Internal().getKitManager().onApplicationBackground();
mCurrentActivityName = null;
diff --git a/android-core/src/main/java/com/mparticle/internal/ConfigManager.java b/android-core/src/main/java/com/mparticle/internal/ConfigManager.java
index cf225f843..b2b7c0f7a 100644
--- a/android-core/src/main/java/com/mparticle/internal/ConfigManager.java
+++ b/android-core/src/main/java/com/mparticle/internal/ConfigManager.java
@@ -58,6 +58,7 @@ public class ConfigManager {
public static final String VALUE_CUE_CATCH = "forcecatch";
public static final String PREFERENCES_FILE = "mp_preferences";
public static final String KEY_INCLUDE_SESSION_HISTORY = "inhd";
+ public static final String ENABLE_BACKGROUND_BATCHING = "ebb";
private static final String KEY_DEVICE_PERFORMANCE_METRICS_DISABLED = "dpmd";
public static final String WORKSPACE_TOKEN = "wst";
static final String ALIAS_MAX_WINDOW = "alias_max_window";
@@ -107,6 +108,7 @@ public class ConfigManager {
public static final int DEFAULT_UPLOAD_INTERVAL = 600;
private List configUpdatedListeners = new ArrayList<>();
private List sideloadedKits = new ArrayList<>();
+ private boolean enableBackgroundBatchingUpload = false;
private ConfigManager() {
super();
@@ -401,13 +403,15 @@ private synchronized void updateCoreConfig(JSONObject responseJSON, boolean newC
mLogUnhandledExceptions = responseJSON.getString(KEY_UNHANDLED_EXCEPTIONS);
}
+ //TODO Read from backgroundEventBatching feature flag
+ editor.putBoolean(ENABLE_BACKGROUND_BATCHING, enableBackgroundBatchingUpload);
+
if (responseJSON.has(KEY_PUSH_MESSAGES) && newConfig) {
sPushKeys = responseJSON.getJSONArray(KEY_PUSH_MESSAGES);
editor.putString(KEY_PUSH_MESSAGES, sPushKeys.toString());
}
mRampValue = responseJSON.optInt(KEY_RAMP, -1);
-
if (responseJSON.has(KEY_OPT_OUT)) {
mSendOoEvents = responseJSON.getBoolean(KEY_OPT_OUT);
} else {
@@ -913,6 +917,10 @@ public JSONArray getTriggerMessageHashes() {
return mTriggerMessageHashes;
}
+ public boolean isBackgroundBatchUploadingEnabled() {
+ return enableBackgroundBatchingUpload;
+ }
+
public boolean shouldTrigger(BaseMPMessage message) {
JSONArray messageMatches = getTriggerMessageMatches();
JSONArray triggerHashes = getTriggerMessageHashes();
@@ -922,9 +930,11 @@ public boolean shouldTrigger(BaseMPMessage message) {
isBackgroundAst = (message.getMessageType().equals(Constants.MessageType.APP_STATE_TRANSITION) && message.get(Constants.MessageKey.STATE_TRANSITION_TYPE).equals(Constants.StateTransitionType.STATE_TRANS_BG));
} catch (JSONException ex) {
}
+ if (enableBackgroundBatchingUpload && isBackgroundAst) {
+ return false;
+ }
boolean shouldTrigger = message.getMessageType().equals(Constants.MessageType.PUSH_RECEIVED)
- || message.getMessageType().equals(Constants.MessageType.COMMERCE_EVENT)
- || isBackgroundAst;
+ || message.getMessageType().equals(Constants.MessageType.COMMERCE_EVENT) || isBackgroundAst;
if (!shouldTrigger && messageMatches != null && messageMatches.length() > 0) {
shouldTrigger = true;
diff --git a/android-core/src/main/java/com/mparticle/internal/UploadHandler.java b/android-core/src/main/java/com/mparticle/internal/UploadHandler.java
index 790c8def4..6f3798de2 100644
--- a/android-core/src/main/java/com/mparticle/internal/UploadHandler.java
+++ b/android-core/src/main/java/com/mparticle/internal/UploadHandler.java
@@ -145,7 +145,8 @@ public void handleMessageImpl(Message msg) {
}
}
}
- if (mAppStateManager.getSession().isActive() && uploadInterval > 0 && msg.arg1 == 0) {
+ if ((mAppStateManager.getSession().isActive() && uploadInterval > 0 && msg.arg1 == 0) ||
+ (mParticleDBManager.hasMessagesForUpload() && mAppStateManager.isBackgrounded())) {
this.sendEmptyDelayed(UPLOAD_MESSAGES, uploadInterval);
}
break;
diff --git a/android-core/src/main/java/com/mparticle/media/MPMediaAPI.java b/android-core/src/main/java/com/mparticle/media/MPMediaAPI.java
index fdbb0617c..cef2b98bf 100644
--- a/android-core/src/main/java/com/mparticle/media/MPMediaAPI.java
+++ b/android-core/src/main/java/com/mparticle/media/MPMediaAPI.java
@@ -36,6 +36,7 @@ public MPMediaAPI(@Nullable Context context, @NonNull MediaCallbacks callbacks)
*
* @param playing Is your app currently playing music for the user.
*/
+ @Deprecated
public void setAudioPlaying(boolean playing) {
mAudioPlaying.set(playing);
if (playing) {
@@ -45,6 +46,7 @@ public void setAudioPlaying(boolean playing) {
}
}
+ @Deprecated
public boolean getAudioPlaying() {
return mAudioPlaying.get();
}
diff --git a/android-core/src/main/kotlin/com.mparticle/TimeUtils.kt b/android-core/src/main/kotlin/com.mparticle/TimeUtils.kt
new file mode 100644
index 000000000..c45933612
--- /dev/null
+++ b/android-core/src/main/kotlin/com.mparticle/TimeUtils.kt
@@ -0,0 +1,8 @@
+package com.mparticle
+
+import java.text.SimpleDateFormat
+import java.util.Date
+
+fun millisToLoggingDate(millis: Long): String {
+ return SimpleDateFormat("MM/dd/yyyy HH:mm:ss").format(Date(millis))
+}
\ No newline at end of file
diff --git a/android-core/src/main/kotlin/com/mparticle/uploadbatching/AlarmSchedulingUtils.kt b/android-core/src/main/kotlin/com/mparticle/uploadbatching/AlarmSchedulingUtils.kt
new file mode 100644
index 000000000..537c35ef5
--- /dev/null
+++ b/android-core/src/main/kotlin/com/mparticle/uploadbatching/AlarmSchedulingUtils.kt
@@ -0,0 +1,47 @@
+package com.mparticle.uploadbatching
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import com.mparticle.internal.Logger
+import com.mparticle.millisToLoggingDate
+
+fun scheduleUploadBatchAlarm(context: Context, delay: Long) {
+ val intent = Intent(context, UploadBatchReceiver::class.java).apply {
+ action = UploadBatchReceiver.ACTION_UPLOAD_BATCH
+ }
+ val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+ var alarmDelay = delay
+ //Setting alarm delay to 2min MINIMUM to prevent collision with end session message triggers
+ if (delay < 120000) {
+ alarmDelay = 120000L
+ }
+ val time = System.currentTimeMillis() + alarmDelay
+ val alarmType = AlarmManager.RTC_WAKEUP
+ (context.getSystemService(Context.ALARM_SERVICE) as AlarmManager?)?.let { manager ->
+
+ PendingIntent.getService(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE
+ )?.let {
+ manager.cancel(it)
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ manager.setAndAllowWhileIdle(alarmType, time, pendingIntent)
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ manager.setAndAllowWhileIdle(alarmType, time, pendingIntent)
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ manager.set(alarmType, time, pendingIntent)
+ } else {
+ manager.set(alarmType, time, pendingIntent)
+ }
+ }
+ Logger.debug("Upload batch alarm set at ${millisToLoggingDate(time)}")
+}
+
+
+
diff --git a/android-core/src/main/kotlin/com/mparticle/uploadbatching/UploadBatchReceiver.kt b/android-core/src/main/kotlin/com/mparticle/uploadbatching/UploadBatchReceiver.kt
new file mode 100644
index 000000000..972a08691
--- /dev/null
+++ b/android-core/src/main/kotlin/com/mparticle/uploadbatching/UploadBatchReceiver.kt
@@ -0,0 +1,34 @@
+package com.mparticle.uploadbatching
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import com.mparticle.MParticle
+import com.mparticle.internal.Logger
+
+internal class UploadBatchReceiver : BroadcastReceiver() {
+
+ companion object {
+ const val ACTION_UPLOAD_BATCH = "ACTION_UPLOAD_BATCH"
+ fun getIntentFilter(): IntentFilter {
+ return IntentFilter().apply { addAction(ACTION_UPLOAD_BATCH) }
+ }
+ }
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ Logger.debug("Received broadcast ${this::class.java.name}")
+ intent?.let {
+ if (it.action == ACTION_UPLOAD_BATCH) {
+ try {
+ MParticle.getInstance()?.let {
+ //Do if there is a non-null mParticle instance, force upload messages
+ it.upload()
+ Logger.debug("Uploading events in upload batch receiver")
+ }
+ } catch (e: Exception) {
+ }
+ }
+ }
+ }
+}
\ No newline at end of file