Skip to content

Fix FIAM ANR Issue 1430 #1488

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,7 @@ TestDeviceHelper providesTestDeviceHelper() {
@Provides
@FirebaseAppScope
ApiClient providesApiClient(
Lazy<GrpcClient> grpcClient,
Application application,
DataCollectionHelper dataCollectionHelper,
ProviderInstaller providerInstaller) {
return new ApiClient(
grpcClient,
firebaseApp,
application,
firebaseInstanceId,
dataCollectionHelper,
clock,
providerInstaller);
Lazy<GrpcClient> grpcClient, Application application, ProviderInstaller providerInstaller) {
return new ApiClient(grpcClient, firebaseApp, application, clock, providerInstaller);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ public void triggerEvent(String eventName) {

private void triggerInAppMessage(TriggeredInAppMessage inAppMessage) {
if (this.fiamDisplay != null) {
// The APIs that control the UI are going to be called on the main thread. Yay!
fiamDisplay.displayMessage(
inAppMessage.getInAppMessage(),
displayCallbacksFactory.generateDisplayCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,8 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build.VERSION;
import android.text.TextUtils;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import com.google.common.annotations.VisibleForTesting;
import com.google.developers.mobile.targeting.proto.ClientSignalsProto.ClientSignals;
import com.google.firebase.FirebaseApp;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;
import com.google.firebase.inappmessaging.internal.injection.scopes.FirebaseAppScope;
import com.google.firebase.inappmessaging.internal.time.Clock;
Expand All @@ -46,71 +42,43 @@
@FirebaseAppScope
public class ApiClient {

private static final String DATA_COLLECTION_DISABLED_ERROR =
"Automatic data collection is disabled, not attempting campaign fetch from service.";
private static final String FETCHING_CAMPAIGN_MESSAGE = "Fetching campaigns from service.";

private final Lazy<GrpcClient> grpcClient;
private final FirebaseApp firebaseApp;
private final Application application;
private final FirebaseInstanceId firebaseInstanceId;
private final DataCollectionHelper dataCollectionHelper;
private final Clock clock;
private final ProviderInstaller providerInstaller;

public ApiClient(
Lazy<GrpcClient> grpcClient,
FirebaseApp firebaseApp,
Application application,
FirebaseInstanceId firebaseInstanceId,
DataCollectionHelper dataCollectionHelper,
Clock clock,
ProviderInstaller providerInstaller) {
this.grpcClient = grpcClient;
this.firebaseApp = firebaseApp;
this.application = application;
this.firebaseInstanceId = firebaseInstanceId;
this.dataCollectionHelper = dataCollectionHelper;
this.clock = clock;
this.providerInstaller = providerInstaller;
}

@VisibleForTesting
static FetchEligibleCampaignsResponse createCacheExpiringResponse() {
// Within the cache, we use '0' as a special case to 'never' expire. '1' is used when we want to
// retry the getFiams call on subsequent event triggers, and force the cache to always expire
return FetchEligibleCampaignsResponse.newBuilder().setExpirationEpochTimestampMillis(1).build();
}

Task<FetchEligibleCampaignsResponse> getFiams(CampaignImpressionList impressionList) {
if (!dataCollectionHelper.isAutomaticDataCollectionEnabled()) {
Logging.logi(DATA_COLLECTION_DISABLED_ERROR);
return Tasks.forResult(createCacheExpiringResponse());
}
FetchEligibleCampaignsResponse getFiams(
InstanceIdResult instanceIdResult, CampaignImpressionList impressionList) {
Logging.logi(FETCHING_CAMPAIGN_MESSAGE);
providerInstaller.install();
return firebaseInstanceId
.getInstanceId()
.continueWith(
instanceIdResultTask -> {
InstanceIdResult instanceIdResult = instanceIdResultTask.getResult();
if (instanceIdResult == null) {
Logging.logw("InstanceID is null, not calling backend");
return createCacheExpiringResponse();
}
return withCacheExpirationSafeguards(
grpcClient
.get()
.fetchEligibleCampaigns(
FetchEligibleCampaignsRequest.newBuilder()
// The project Id we expect is the gcm sender id
.setProjectNumber(firebaseApp.getOptions().getGcmSenderId())
.addAllAlreadySeenCampaigns(
impressionList.getAlreadySeenCampaignsList())
.setClientSignals(getClientSignals())
.setRequestingClientApp(getClientAppInfo(instanceIdResult))
.build()));
});

return withCacheExpirationSafeguards(
grpcClient
.get()
.fetchEligibleCampaigns(
FetchEligibleCampaignsRequest.newBuilder()
// The project Id we expect is the gcm sender id
.setProjectNumber(firebaseApp.getOptions().getGcmSenderId())
.addAllAlreadySeenCampaigns(impressionList.getAlreadySeenCampaignsList())
.setClientSignals(getClientSignals())
.setRequestingClientApp(getClientAppInfo(instanceIdResult))
.build()));
}

private FetchEligibleCampaignsResponse withCacheExpirationSafeguards(
Expand Down Expand Up @@ -143,17 +111,11 @@ private ClientSignals getClientSignals() {
}

private ClientAppInfo getClientAppInfo(InstanceIdResult instanceIdResult) {
ClientAppInfo.Builder builder =
ClientAppInfo.newBuilder().setGmpAppId(firebaseApp.getOptions().getApplicationId());
String instanceId = instanceIdResult.getId();
String instanceToken = instanceIdResult.getToken();
if (!TextUtils.isEmpty(instanceId) && !TextUtils.isEmpty(instanceToken)) {
builder.setAppInstanceId(instanceId);
builder.setAppInstanceIdToken(instanceToken);
} else {
Logging.logw("Empty instance ID or instance token");
}
return builder.build();
return ClientAppInfo.newBuilder()
.setGmpAppId(firebaseApp.getOptions().getApplicationId())
.setAppInstanceId(instanceIdResult.getId())
.setAppInstanceIdToken(instanceIdResult.getToken())
.build();
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.google.internal.firebase.inappmessaging.v1.sdkserving.FetchEligibleCampaignsRequest;
import com.google.internal.firebase.inappmessaging.v1.sdkserving.FetchEligibleCampaignsResponse;
import com.google.internal.firebase.inappmessaging.v1.sdkserving.InAppMessagingSdkServingGrpc.InAppMessagingSdkServingBlockingStub;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;

/**
Expand All @@ -35,6 +36,6 @@ public class GrpcClient {
}

public FetchEligibleCampaignsResponse fetchEligibleCampaigns(FetchEligibleCampaignsRequest req) {
return stub.fetchEligibleCampaigns(req);
return stub.withDeadlineAfter(30000, TimeUnit.MILLISECONDS).fetchEligibleCampaigns(req);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@

package com.google.firebase.inappmessaging.internal;

import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import com.google.android.gms.tasks.Task;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;
import com.google.firebase.inappmessaging.CommonTypesProto.TriggeringCondition;
import com.google.firebase.inappmessaging.internal.injection.qualifiers.AppForeground;
import com.google.firebase.inappmessaging.internal.injection.qualifiers.ProgrammaticTrigger;
Expand All @@ -36,6 +40,7 @@
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import java.util.Locale;
import javax.annotation.Nullable;
import javax.inject.Inject;

/**
Expand All @@ -59,6 +64,8 @@ public class InAppMessageStreamManager {
private final AnalyticsEventsManager analyticsEventsManager;
private final TestDeviceHelper testDeviceHelper;
private final AbtIntegrationHelper abtIntegrationHelper;
private final FirebaseInstanceId firebaseInstanceId;
private final DataCollectionHelper dataCollectionHelper;

@Inject
public InAppMessageStreamManager(
Expand All @@ -73,6 +80,8 @@ public InAppMessageStreamManager(
RateLimiterClient rateLimiterClient,
@AppForeground RateLimit appForegroundRateLimit,
TestDeviceHelper testDeviceHelper,
FirebaseInstanceId firebaseInstanceId,
DataCollectionHelper dataCollectionHelper,
AbtIntegrationHelper abtIntegrationHelper) {
this.appForegroundEventFlowable = appForegroundEventFlowable;
this.programmaticTriggerEventFlowable = programmaticTriggerEventFlowable;
Expand All @@ -85,6 +94,8 @@ public InAppMessageStreamManager(
this.rateLimiterClient = rateLimiterClient;
this.appForegroundRateLimit = appForegroundRateLimit;
this.testDeviceHelper = testDeviceHelper;
this.dataCollectionHelper = dataCollectionHelper;
this.firebaseInstanceId = firebaseInstanceId;
this.abtIntegrationHelper = abtIntegrationHelper;
}

Expand Down Expand Up @@ -232,22 +243,35 @@ public Flowable<TriggeredInAppMessage> createFirebaseInAppMessageStream() {
.defaultIfEmpty(CampaignImpressionList.getDefaultInstance())
.onErrorResumeNext(Maybe.just(CampaignImpressionList.getDefaultInstance()));

Maybe<InstanceIdResult> getIID = taskToMaybe(firebaseInstanceId.getInstanceId());

Function<CampaignImpressionList, Maybe<FetchEligibleCampaignsResponse>> serviceFetch =
impressions ->
taskToMaybe(apiClient.getFiams(impressions))
.doOnSuccess(
resp ->
Logging.logi(
String.format(
Locale.US,
"Successfully fetched %d messages from backend",
resp.getMessagesList().size())))
.doOnSuccess(
resp -> impressionStorageClient.clearImpressions(resp).subscribe())
.doOnSuccess(analyticsEventsManager::updateContextualTriggers)
.doOnSuccess(testDeviceHelper::processCampaignFetch)
.doOnError(e -> Logging.logw("Service fetch error: " + e.getMessage()))
.onErrorResumeNext(Maybe.empty()); // Absorb service failures
campaignImpressionList -> {
if (!dataCollectionHelper.isAutomaticDataCollectionEnabled()) {
Logging.logi(
"Automatic data collection is disabled, not attempting campaign fetch from service.");
return Maybe.just(cacheExpiringResponse());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking out loud: Can this be modelled as an empty as well, similar to IID?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm maybe but I think this is pretty clear

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like it could map to empty or something like that but I think it's not worth it

}

// blocking get occurs on the IO thread because that's what we observeOn above
return Maybe.fromCallable(getIID::blockingGet)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to just return getIID here, which is already a Maybe

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah but I need to do blocking get which turns it into a not maybe, if I don't do blocking get here the waiting ends up hitting the main thread below

.filter(InAppMessageStreamManager::validIID)
.map(iid -> apiClient.getFiams(iid, campaignImpressionList))
.switchIfEmpty(Maybe.just(cacheExpiringResponse()))
.doOnSuccess(
resp ->
Logging.logi(
String.format(
Locale.US,
"Successfully fetched %d messages from backend",
resp.getMessagesList().size())))
.doOnSuccess(
resp -> impressionStorageClient.clearImpressions(resp).subscribe())
.doOnSuccess(analyticsEventsManager::updateContextualTriggers)
.doOnSuccess(testDeviceHelper::processCampaignFetch)
.doOnError(e -> Logging.logw("Service fetch error: " + e.getMessage()))
.onErrorResumeNext(Maybe.empty()); // Absorb service failures
};

if (shouldIgnoreCache(event)) {
Logging.logi(
Expand Down Expand Up @@ -349,6 +373,17 @@ private Maybe<TriggeredInAppMessage> triggeredInAppMessage(ThickContent content,
return Maybe.just(new TriggeredInAppMessage(inAppMessage, event));
}

private static boolean validIID(@Nullable InstanceIdResult iid) {
return iid != null && !TextUtils.isEmpty(iid.getId()) && !TextUtils.isEmpty(iid.getToken());
}

@VisibleForTesting
static FetchEligibleCampaignsResponse cacheExpiringResponse() {
// Within the cache, we use '0' as a special case to 'never' expire. '1' is used when we want to
// retry the getFiams call on subsequent event triggers, and force the cache to always expire
return FetchEligibleCampaignsResponse.newBuilder().setExpirationEpochTimestampMillis(1).build();
}

private static <T> Maybe<T> taskToMaybe(Task<T> task) {
return Maybe.create(
emitter -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,7 @@ TestDeviceHelper providesTestDeviceHelper(SharedPreferencesUtils sharedPreferenc
@Provides
@FirebaseAppScope
ApiClient providesApiClient(
Lazy<GrpcClient> grpcClient,
Application application,
DataCollectionHelper dataCollectionHelper,
ProviderInstaller providerInstaller) {
return new ApiClient(
grpcClient,
firebaseApp,
application,
firebaseInstanceId,
dataCollectionHelper,
clock,
providerInstaller);
Lazy<GrpcClient> grpcClient, Application application, ProviderInstaller providerInstaller) {
return new ApiClient(grpcClient, firebaseApp, application, clock, providerInstaller);
}
}
Loading