Skip to content
Open
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Fixed
- Push token registration and disable operations, including their API requests and retries, now run on a dedicated SDK-owned serial executor instead of Android's process-wide `AsyncTask` queue. This prevents host app background work from delaying device registration, preserves operation order, and keeps client callbacks on the main thread.

## [3.11.0]
### Added
- `IterableConfig.Builder.setExpiringAuthTokenRefreshPeriod(double)` accepts fractional seconds, matching the iOS, React Native and Flutter SDKs. Previously Android only accepted whole seconds, so a value like `0.5` behaved differently here than on other platforms. The existing `Long` overload is deprecated but still works, so no code changes are required.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationManagerCompat;
import com.iterable.iterableapi.util.DeviceInfoUtils;
import org.json.JSONException;
Expand All @@ -18,6 +17,7 @@
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;

/**
* Created by David Truong dt@iterable.com
Expand Down Expand Up @@ -56,6 +56,7 @@ public class IterableApi {
private IterableAuthManager authManager;
private ConcurrentHashMap<String, String> deviceAttributes = new ConcurrentHashMap<>();
private IterableKeychain keychain;
private final IterablePushRegistration pushRegistration;

//region Background Initialization - Delegated to IterableBackgroundInitializer
//---------------------------------------------------------------------------------------
Expand Down Expand Up @@ -694,22 +695,26 @@ void setAuthToken(String authToken, boolean bypassAuth) {
}

protected void registerDeviceToken(final @Nullable String email, final @Nullable String userId, final @Nullable String authToken, final @NonNull String applicationName, final @NonNull String deviceToken, final Map<String, String> deviceAttributes) {
if (deviceToken != null) {
if (!checkSDKInitialization() && _userIdUnknown == null) {
if (sharedInstance.config.enableUnknownUserActivation) {
unknownUserManager.trackUnknownTokenRegistration(deviceToken);
}
return;
registerDeviceToken(email, userId, authToken, applicationName, deviceToken, deviceAttributes, IterableExecutors.push());
}

void registerDeviceToken(final @Nullable String email, final @Nullable String userId, final @Nullable String authToken, final @NonNull String applicationName, final @NonNull String deviceToken, final Map<String, String> deviceAttributes, Executor executor) {
if (shouldSubmitDeviceTokenRegistration(deviceToken)) {
executor.execute(() -> registerDeviceToken(email, userId, authToken, applicationName, deviceToken, null, deviceAttributes));
}
}
private boolean shouldSubmitDeviceTokenRegistration(@Nullable String deviceToken) {
if (deviceToken == null) {
return false;
}
if (!checkSDKInitialization() && _userIdUnknown == null) {
if (sharedInstance.config.enableUnknownUserActivation) {
unknownUserManager.trackUnknownTokenRegistration(deviceToken);
}
final Thread registrationThread = new Thread(new Runnable() {
public void run() {
registerDeviceToken(email, userId, authToken, applicationName, deviceToken, null, deviceAttributes);
}
});
registrationThread.start();
return false;
}
return true;
}

protected void disableToken(@Nullable String email, @Nullable String userId, @NonNull String token) {
disableToken(email, userId, null, token, null, null);
}
Expand Down Expand Up @@ -970,26 +975,22 @@ static void initializeForPush(@Nullable Context context) {

IterableApi() {
config = new IterableConfig.Builder().build();
pushRegistration = new IterablePushRegistration();
}

@VisibleForTesting
IterableApi(IterableInAppManager inAppManager) {
config = new IterableConfig.Builder().build();
this();
this.inAppManager = inAppManager;
}

@VisibleForTesting
IterableApi(IterableInAppManager inAppManager, IterableEmbeddedManager embeddedManager) {
config = new IterableConfig.Builder().build();
this.inAppManager = inAppManager;
this.embeddedManager = embeddedManager;
}

@VisibleForTesting
IterableApi(IterableApiClient apiClient, IterableInAppManager inAppManager) {
config = new IterableConfig.Builder().build();
this(inAppManager);
this.apiClient = apiClient;
}
IterableApi(IterableInAppManager inAppManager, IterableEmbeddedManager embeddedManager,
IterablePushRegistration pushRegistration) {
config = new IterableConfig.Builder().build();
this.inAppManager = inAppManager;
this.embeddedManager = embeddedManager;
this.pushRegistration = Objects.requireNonNull(pushRegistration);
}

//endregion
Expand Down Expand Up @@ -1673,7 +1674,7 @@ public void updateUser(@NonNull JSONObject dataFields, Boolean mergeNestedObject
public void registerForPush() {
if (checkSDKInitialization()) {
IterablePushRegistrationData data = new IterablePushRegistrationData(_email, _userId, _authToken, getPushIntegrationName(), IterablePushRegistrationData.PushRegistrationAction.ENABLE);
IterablePushRegistration.executePushRegistrationTask(data);
pushRegistration.executePushRegistrationTask(data);
}
}

Expand All @@ -1683,7 +1684,7 @@ public void registerForPush() {
public void disablePush() {
if (checkSDKInitialization()) {
IterablePushRegistrationData data = new IterablePushRegistrationData(_email, _userId, _authToken, getPushIntegrationName(), IterablePushRegistrationData.PushRegistrationAction.DISABLE);
IterablePushRegistration.executePushRegistrationTask(data);
pushRegistration.executePushRegistrationTask(data);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

class IterableApiClient {
private static final String TAG = "IterableApiClient";
private final @NonNull AuthProvider authProvider;
private final IterablePushRegistrationRequestProcessor pushRegistrationRequestProcessor;
// A newer push action invalidates retries from earlier registration or disable requests.
private final AtomicLong pushRegistrationRequestGeneration = new AtomicLong();
private RequestProcessor requestProcessor;

interface AuthProvider {
Expand All @@ -42,6 +46,8 @@ interface AuthProvider {

IterableApiClient(@NonNull AuthProvider authProvider) {
this.authProvider = authProvider;
pushRegistrationRequestProcessor =
new IterablePushRegistrationRequestProcessor();
}

private RequestProcessor getRequestProcessor() {
Expand Down Expand Up @@ -618,7 +624,13 @@ protected void disableToken(@Nullable String email, @Nullable String userId, @Nu
requestJSON.put(IterableConstants.KEY_USER_ID, userId);
}

sendPostRequest(IterableConstants.ENDPOINT_DISABLE_DEVICE, requestJSON, authToken, onSuccess, onFailure);
sendPushRegistrationPostRequest(
IterableConstants.ENDPOINT_DISABLE_DEVICE,
requestJSON,
authToken,
onSuccess,
onFailure
);
} catch (JSONException e) {
e.printStackTrace();
}
Expand Down Expand Up @@ -669,7 +681,13 @@ protected void registerDeviceToken(@Nullable String email, @Nullable String user
requestJSON.put(IterableConstants.KEY_PREFER_USER_ID, true);
}

sendPostRequest(IterableConstants.ENDPOINT_REGISTER_DEVICE_TOKEN, requestJSON, authToken, successHandler, failureHandler);
sendPushRegistrationPostRequest(
IterableConstants.ENDPOINT_REGISTER_DEVICE_TOKEN,
requestJSON,
authToken,
successHandler,
failureHandler
);
} catch (JSONException e) {
IterableLogger.e(TAG, "registerDeviceToken: exception", e);
}
Expand Down Expand Up @@ -768,6 +786,25 @@ void sendPostRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Nu
getRequestProcessor().processPostRequest(authProvider.getApiKey(), resourcePath, json, authToken, onSuccess, onFailure);
}

private void sendPushRegistrationPostRequest(
@NonNull String resourcePath,
@NonNull JSONObject json,
@Nullable String authToken,
@Nullable IterableHelper.SuccessHandler onSuccess,
@Nullable IterableHelper.FailureHandler onFailure
) {
long requestGeneration = pushRegistrationRequestGeneration.incrementAndGet();
pushRegistrationRequestProcessor.processPostRequest(
authProvider.getApiKey(),
resourcePath,
json,
authToken,
onSuccess,
onFailure,
() -> requestGeneration == pushRegistrationRequestGeneration.get()
);
}

/**
* Sends a GET request to Iterable.
* Performs network operations on an async thread instead of the main thread.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.iterable.iterableapi;

import android.os.Handler;
import android.os.Looper;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

final class IterableExecutors {
private static final Executor PUSH_EXECUTOR = Executors.newSingleThreadExecutor(runnable -> {
Thread thread = new Thread(runnable, "IterablePushExecutor");
thread.setDaemon(true);
thread.setPriority(Thread.NORM_PRIORITY);
return thread;
});
private static final Executor MAIN_EXECUTOR =
runnable -> new Handler(Looper.getMainLooper()).post(runnable);

private IterableExecutors() {
}

static Executor push() {
return PUSH_EXECUTOR;
}

static Executor main() {
return MAIN_EXECUTOR;
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package com.iterable.iterableapi;

import androidx.annotation.VisibleForTesting;
import java.util.concurrent.Executor;

class IterablePushRegistration {
final class IterablePushRegistration {
private final Executor executor;

@VisibleForTesting
static IterablePushRegistrationImpl instance = new IterablePushRegistrationImpl();
IterablePushRegistration() {
this(IterableExecutors.push());
}

static void executePushRegistrationTask(IterablePushRegistrationData data) {
instance.executePushRegistrationTask(data);
IterablePushRegistration(Executor executor) {
this.executor = executor;
}

static class IterablePushRegistrationImpl {
void executePushRegistrationTask(IterablePushRegistrationData data) {
new IterablePushRegistrationTask().execute(data);
}
void executePushRegistrationTask(IterablePushRegistrationData data) {
executor.execute(new IterablePushRegistrationTask(data));
Comment thread
franco-zalamena-iterable marked this conversation as resolved.
}
}
Loading
Loading