Skip to content

Commit dd9d485

Browse files
committed
release: SDK 3.0.1
# Conflicts: # Sources/settings.gradle.kts
1 parent e926e76 commit dd9d485

File tree

18 files changed

+13387
-80
lines changed

18 files changed

+13387
-80
lines changed

Sources/gradle/libs.versions.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ androidXTestCoreKtx = "1.6.1"
1111
androidXTestRunner = "1.6.2"
1212
androidXTestTruth = "1.6.0"
1313
assertjCore = "3.24.2"
14-
batchSdk = "3.0.0"
15-
batchApiLevel = "300"
14+
batchSdk = "3.0.1"
15+
batchApiLevel = "301"
1616
batchMessagingApiLevel = "30"
1717
batchResourcePrefix = "com_batchsdk_"
1818
batchNamespace = "com.batch.android"

Sources/sdk/src/main/java/com/batch/android/Batch.java

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import android.app.Application;
55
import android.app.PendingIntent;
66
import android.app.Service;
7+
import android.app.job.JobInfo;
8+
import android.app.job.JobScheduler;
79
import android.content.BroadcastReceiver;
810
import android.content.Context;
911
import android.content.Intent;
@@ -31,6 +33,7 @@
3133
import com.batch.android.core.ParameterKeys;
3234
import com.batch.android.core.Parameters;
3335
import com.batch.android.core.TaskExecutor;
36+
import com.batch.android.core.VersionHelper;
3437
import com.batch.android.debug.BatchDebugActivity;
3538
import com.batch.android.debug.FindMyInstallationHelper;
3639
import com.batch.android.di.providers.ActionModuleProvider;
@@ -2035,7 +2038,7 @@ else if (lastStop == null && state == State.READY) {
20352038
/*
20362039
* Check for update migration stuff
20372040
*/
2038-
updateVersionManagement();
2041+
updateVersionManagement(applicationContext);
20392042

20402043
/*
20412044
* Check that we have mandatory permissions
@@ -2386,29 +2389,59 @@ static Install getInstall() {
23862389
/**
23872390
* Method call at every start to check if the lib has been updated
23882391
*/
2389-
private static void updateVersionManagement() {
2392+
private static void updateVersionManagement(@NonNull Context context) {
23902393
try {
23912394
String currentVersion = Parameters.SDK_VERSION;
2392-
String savedVersion = ParametersProvider
2393-
.get(RuntimeManagerProvider.get().getContext())
2394-
.get(ParameterKeys.LIB_CURRENTVERSION_KEY);
2395+
String savedVersion = ParametersProvider.get(context).get(ParameterKeys.LIB_CURRENTVERSION_KEY);
23952396
if (savedVersion == null) { // First launch case
2396-
ParametersProvider
2397-
.get(RuntimeManagerProvider.get().getContext())
2398-
.set(ParameterKeys.LIB_CURRENTVERSION_KEY, currentVersion, true);
2397+
ParametersProvider.get(context).set(ParameterKeys.LIB_CURRENTVERSION_KEY, currentVersion, true);
23992398
} else if (!savedVersion.equals(currentVersion)) { // new version
2400-
ParametersProvider
2401-
.get(RuntimeManagerProvider.get().getContext())
2402-
.set(ParameterKeys.LIB_CURRENTVERSION_KEY, currentVersion, true);
2403-
ParametersProvider
2404-
.get(RuntimeManagerProvider.get().getContext())
2405-
.set(ParameterKeys.LIB_PREVIOUSVERSION_KEY, savedVersion, true);
2399+
handleNewVersionMigration(context, savedVersion, currentVersion);
2400+
ParametersProvider.get(context).set(ParameterKeys.LIB_CURRENTVERSION_KEY, currentVersion, true);
2401+
ParametersProvider.get(context).set(ParameterKeys.LIB_PREVIOUSVERSION_KEY, savedVersion, true);
24062402
}
24072403
} catch (Exception e) {
24082404
Logger.internal("Error on updateVersionManagement", e);
24092405
}
24102406
}
24112407

2408+
private static void handleNewVersionMigration(Context context, String savedVersion, String currentVersion) {
2409+
Logger.internal("New version detected : " + savedVersion + " -> " + currentVersion);
2410+
2411+
int savedMajorVersion = VersionHelper.getMajor(savedVersion);
2412+
int currentMajorVersion = VersionHelper.getMajor(currentVersion);
2413+
2414+
if (savedMajorVersion <= 2 && currentMajorVersion == 3) {
2415+
removeOldPendingDisplayReceiptJobs(context);
2416+
}
2417+
}
2418+
2419+
/**
2420+
* Removes all pending display receipt jobs from the JobScheduler.
2421+
* <p>
2422+
* This method is used to clean up any leftover display receipt jobs that might have been scheduled
2423+
* by older versions of the SDK, which could potentially cause issues with newer versions.
2424+
*
2425+
* @param context The application context.
2426+
*/
2427+
private static void removeOldPendingDisplayReceiptJobs(Context context) {
2428+
JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
2429+
2430+
if (scheduler == null) {
2431+
Logger.internal("JobScheduler service is unavailable. Skipping job cancellation.");
2432+
return;
2433+
}
2434+
2435+
for (JobInfo pendingJob : scheduler.getAllPendingJobs()) {
2436+
if (pendingJob.getService().getClassName().equals("com.batch.android.BatchDisplayReceiptJobService")) {
2437+
scheduler.cancel(pendingJob.getId());
2438+
Logger.internal(
2439+
"Scheduled display receipt job service with id " + pendingJob.getId() + " was canceled"
2440+
);
2441+
}
2442+
}
2443+
}
2444+
24122445
private static class InternalBroadcastReceiver extends BroadcastReceiver {
24132446

24142447
@Override
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.batch.android.core;
2+
3+
public class VersionHelper {
4+
5+
/**
6+
* Extracts the major version number from a version string.
7+
*
8+
* <p>The version string is expected to be in the format "major.minor.patch" (e.g., "1.2.3").
9+
* If the string cannot be parsed or is in an unexpected format, this method logs an error
10+
* and returns 0.
11+
*
12+
* @param version The version string to parse.
13+
* @return The major version number as an integer, or 0 if parsing fails.
14+
*/
15+
public static int getMajor(String version) {
16+
try {
17+
return Integer.parseInt(version.split("\\.")[0]);
18+
} catch (Exception e) {
19+
Logger.internal("Error parsing version strings: " + version, e);
20+
21+
return 0;
22+
}
23+
}
24+
}

Sources/sdk/src/main/java/com/batch/android/eventdispatcher/MessagingEventPayload.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,13 @@ public MessagingEventPayload(BatchMessage message, JSONObject payload, JSONObjec
4848
@Override
4949
public String getTrackingId() {
5050
if (payload != null) {
51-
return payload.reallyOptString("did", null);
51+
// MEP TrackingId key
52+
String trackingId = payload.reallyOptString("did", null);
53+
if (trackingId != null) {
54+
return trackingId;
55+
}
56+
// CEP TrackingId key
57+
return payload.reallyOptString("trackingId", null);
5258
}
5359
return null;
5460
}

Sources/sdk/src/main/java/com/batch/android/localcampaigns/CampaignManager.java

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.util.Collections;
4040
import java.util.HashMap;
4141
import java.util.HashSet;
42+
import java.util.Iterator;
4243
import java.util.List;
4344
import java.util.Locale;
4445
import java.util.Map;
@@ -121,7 +122,7 @@ public class CampaignManager {
121122
/**
122123
* Cached list of synced JIT campaigns
123124
*/
124-
private final Map<String, SyncedJITResult> syncedJITCampaigns = new HashMap<>();
125+
private final Map<String, SyncedJITResult> syncedJITCampaignsCached = new HashMap<>();
125126

126127
public CampaignManager(@NonNull LocalCampaignsTracker viewTracker) {
127128
this.viewTracker = viewTracker;
@@ -137,11 +138,21 @@ public static CampaignManager provide() {
137138
* Calling this will triggers campaigns that have seen their conditions met.
138139
*
139140
* @param updatedCampaignList Updated campaign list. Can't be null
141+
* @param upToDate Whether the campaigns are up-to-date (meaning just been sync from server) or not
140142
*/
141-
public void updateCampaignList(@NonNull List<LocalCampaign> updatedCampaignList) {
143+
public void updateCampaignList(@NonNull List<LocalCampaign> updatedCampaignList, boolean upToDate) {
142144
synchronized (this.campaignListLock) {
143145
this.campaignList.clear();
144146
this.campaignList.addAll(cleanCampaignList(updatedCampaignList));
147+
148+
if (upToDate) {
149+
List<String> ids = new ArrayList<>();
150+
for (LocalCampaign campaign : this.campaignList) {
151+
ids.add(campaign.id);
152+
}
153+
updateSyncedJITCampaignsCached(this.campaignList, ids, false);
154+
}
155+
145156
campaignsLoaded.set(true);
146157

147158
updateWatchedEventNames();
@@ -306,18 +317,7 @@ public void onSuccess(List<String> eligibleCampaignIds) {
306317
if (eligibleCampaignIds.isEmpty()) {
307318
listener.onCampaignElected(null);
308319
} else {
309-
for (LocalCampaign campaign : eligibleCampaignsRequiringSync) {
310-
SyncedJITResult syncedJITCampaignState = new SyncedJITResult(
311-
dateProvider.getCurrentDate().getTime()
312-
);
313-
if (!eligibleCampaignIds.contains(campaign.id)) {
314-
eligibleCampaignsRequiringSync.remove(campaign);
315-
syncedJITCampaignState.eligible = false;
316-
} else {
317-
syncedJITCampaignState.eligible = true;
318-
}
319-
syncedJITCampaigns.put(campaign.id, syncedJITCampaignState);
320-
}
320+
updateSyncedJITCampaignsCached(eligibleCampaignsRequiringSync, eligibleCampaignIds, true);
321321
if (eligibleCampaignsRequiringSync.isEmpty()) {
322322
// Should not happen
323323
listener.onCampaignElected(null);
@@ -337,6 +337,35 @@ public void onFailure(Webservice.WebserviceError error) {
337337
);
338338
}
339339

340+
/**
341+
* Update the synced JIT campaigns cache after a LocalCampaign WS Sync or a JIT sync
342+
*
343+
* @param syncedCampaigns The synced JIT campaigns
344+
* @param eligibleCampaignIds The eligible campaign ids
345+
* @param removeNonEligibleCampaigns Whether to remove non-eligible campaigns
346+
*/
347+
@VisibleForTesting
348+
protected void updateSyncedJITCampaignsCached(
349+
@NonNull List<LocalCampaign> syncedCampaigns,
350+
@NonNull List<String> eligibleCampaignIds,
351+
boolean removeNonEligibleCampaigns
352+
) {
353+
synchronized (syncedJITCampaignsCached) {
354+
Iterator<LocalCampaign> iterator = syncedCampaigns.iterator();
355+
long now = dateProvider.getCurrentDate().getTime();
356+
while (iterator.hasNext()) {
357+
LocalCampaign campaign = iterator.next();
358+
if (campaign.requiresJustInTimeSync) {
359+
boolean eligible = eligibleCampaignIds.contains(campaign.id);
360+
if (!eligible && removeNonEligibleCampaigns) {
361+
iterator.remove();
362+
}
363+
syncedJITCampaignsCached.put(campaign.id, new SyncedJITResult(now, eligible));
364+
}
365+
}
366+
}
367+
}
368+
340369
/**
341370
* Check if JIT sync is available
342371
*
@@ -353,24 +382,26 @@ public synchronized boolean isJITServiceAvailable() {
353382
* @return a {@link SyncedJITResult.State}
354383
*/
355384
public SyncedJITResult.State getSyncedJITCampaignState(LocalCampaign campaign) {
356-
if (!campaign.requiresJustInTimeSync) {
357-
//Should not happen but ensure we do not sync for a non-jit campaign
358-
return SyncedJITResult.State.ELIGIBLE;
359-
}
385+
synchronized (syncedJITCampaignsCached) {
386+
if (!campaign.requiresJustInTimeSync) {
387+
//Should not happen but ensure we do not sync for a non-jit campaign
388+
return SyncedJITResult.State.ELIGIBLE;
389+
}
360390

361-
if (!syncedJITCampaigns.containsKey(campaign.id)) {
362-
return SyncedJITResult.State.REQUIRES_SYNC;
363-
}
391+
if (!syncedJITCampaignsCached.containsKey(campaign.id)) {
392+
return SyncedJITResult.State.REQUIRES_SYNC;
393+
}
364394

365-
SyncedJITResult syncedJITResult = syncedJITCampaigns.get(campaign.id);
366-
if (syncedJITResult == null) {
367-
return SyncedJITResult.State.REQUIRES_SYNC;
368-
}
395+
SyncedJITResult syncedJITResult = syncedJITCampaignsCached.get(campaign.id);
396+
if (syncedJITResult == null) {
397+
return SyncedJITResult.State.REQUIRES_SYNC;
398+
}
369399

370-
if (dateProvider.getCurrentDate().getTime() >= (syncedJITResult.timestamp + JIT_CAMPAIGN_CACHE_PERIOD)) {
371-
return SyncedJITResult.State.REQUIRES_SYNC;
400+
if (dateProvider.getCurrentDate().getTime() >= (syncedJITResult.timestamp + JIT_CAMPAIGN_CACHE_PERIOD)) {
401+
return SyncedJITResult.State.REQUIRES_SYNC;
402+
}
403+
return syncedJITResult.eligible ? SyncedJITResult.State.ELIGIBLE : SyncedJITResult.State.NOT_ELIGIBLE;
372404
}
373-
return syncedJITResult.eligible ? SyncedJITResult.State.ELIGIBLE : SyncedJITResult.State.NOT_ELIGIBLE;
374405
}
375406

376407
/**
@@ -411,6 +442,7 @@ public void setCappings(LocalCampaignsResponse.GlobalCappings cappings) {
411442
* - Campaigns that have a max api level too low (min api level doesn't not mean that it is busted forever)
412443
*/
413444
@VisibleForTesting
445+
@NonNull
414446
public List<LocalCampaign> cleanCampaignList(@NonNull List<LocalCampaign> campaignsToClean) {
415447
final BatchDate currentDate = dateProvider.getCurrentDate();
416448

@@ -652,7 +684,7 @@ public boolean loadSavedCampaignResponse(@NonNull final Context context) {
652684
try {
653685
List<LocalCampaign> campaigns = localCampaignResponseDeserializer.deserializeCampaigns();
654686
cappings = localCampaignResponseDeserializer.deserializeCappings();
655-
updateCampaignList(campaigns);
687+
updateCampaignList(campaigns, false);
656688
} catch (Exception ex) {
657689
Logger.internal(TAG, "Can't convert json to LocalCampaignsResponse : " + ex.toString());
658690
return false;

Sources/sdk/src/main/java/com/batch/android/localcampaigns/model/LocalCampaign.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,5 +209,10 @@ public enum State {
209209
public SyncedJITResult(long timestamp) {
210210
this.timestamp = timestamp;
211211
}
212+
213+
public SyncedJITResult(long timestamp, boolean eligible) {
214+
this.timestamp = timestamp;
215+
this.eligible = eligible;
216+
}
212217
}
213218
}

Sources/sdk/src/main/java/com/batch/android/messaging/view/PannableBannerFrameLayout.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
import android.animation.ValueAnimator;
66
import android.annotation.SuppressLint;
77
import android.content.Context;
8-
import android.os.Build;
98
import android.util.AttributeSet;
109
import android.view.GestureDetector;
1110
import android.view.MotionEvent;
1211
import android.view.ViewConfiguration;
1312
import android.view.animation.LinearInterpolator;
1413
import android.view.animation.OvershootInterpolator;
1514
import android.widget.FrameLayout;
15+
import android.widget.ScrollView;
1616
import androidx.annotation.Nullable;
1717
import androidx.dynamicanimation.animation.DynamicAnimation;
1818
import androidx.dynamicanimation.animation.SpringAnimation;
@@ -40,6 +40,9 @@ public class PannableBannerFrameLayout extends FrameLayout implements GestureDet
4040

4141
private OnDismissListener dismissListener;
4242

43+
@Nullable
44+
private ScrollView nestedScrollView;
45+
4346
private boolean isPannable;
4447

4548
/**
@@ -128,13 +131,16 @@ public void setPannable(boolean pannable) {
128131
isPannable = pannable;
129132
}
130133

134+
public void setNestedScrollView(@Nullable ScrollView nestedScrollView) {
135+
this.nestedScrollView = nestedScrollView;
136+
}
137+
131138
@Override
132139
public boolean onInterceptTouchEvent(MotionEvent ev) {
133140
if (!isPannable) {
134141
return super.onInterceptTouchEvent(ev);
135142
}
136143
// Detect if the user is panning the view, so that it's possible even when trying to do so on a button
137-
138144
final int action = ev.getAction();
139145

140146
// Touch complete: give control back
@@ -151,6 +157,14 @@ public boolean onInterceptTouchEvent(MotionEvent ev) {
151157
if (isPanning) {
152158
return true;
153159
}
160+
if (nestedScrollView != null) {
161+
float yDelta = ev.getY() - initialInterceptYOffset;
162+
if (nestedScrollView.canScrollVertically(1) && yDelta < 0) {
163+
return super.onInterceptTouchEvent(ev);
164+
} else if (nestedScrollView.canScrollVertically(-1) && yDelta > 0) {
165+
return super.onInterceptTouchEvent(ev);
166+
}
167+
}
154168

155169
if (hasYPassedTouchSlop(ev.getY(), initialInterceptYOffset)) {
156170
// Use the initial offset, so that we also reach the touch slop in the standard
@@ -212,7 +226,7 @@ public boolean onTouchEvent(MotionEvent event) {
212226
(dismissDirection == DismissDirection.BOTTOM && translationY < 0) ||
213227
(dismissDirection == DismissDirection.TOP && translationY > 0)
214228
) {
215-
translationY *= 0.25;
229+
translationY *= 0.25F;
216230
}
217231
setTranslationY(translationY);
218232
break;

0 commit comments

Comments
 (0)