diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/Utils.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/Utils.java index e54542f9ad0..9c752158cdf 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/Utils.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/Utils.java @@ -134,6 +134,29 @@ public static T awaitEvenIfOnMainThread(Task task) } } + /** Invokes latch.await(timeout, unit) uninterruptibly. */ + public static boolean awaitUninterruptibly(CountDownLatch latch, long timeout, TimeUnit unit) { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + + while (true) { + try { + // CountDownLatch treats negative timeouts just like zero. + return latch.await(remainingNanos, TimeUnit.NANOSECONDS); + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + /** * ExecutorService that is used exclusively by the awaitEvenIfOnMainThread function. If the * Continuation which counts down the latch is called on the same thread which is waiting on the diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/send/ReportQueue.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/send/ReportQueue.java index 404cc77d9af..d25c86c0eff 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/send/ReportQueue.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/send/ReportQueue.java @@ -14,17 +14,22 @@ package com.google.firebase.crashlytics.internal.send; +import android.annotation.SuppressLint; import com.google.android.datatransport.Event; +import com.google.android.datatransport.Priority; import com.google.android.datatransport.Transport; +import com.google.android.datatransport.runtime.ForcedSender; import com.google.android.gms.tasks.TaskCompletionSource; import com.google.firebase.crashlytics.internal.Logger; import com.google.firebase.crashlytics.internal.common.CrashlyticsReportWithSessionId; import com.google.firebase.crashlytics.internal.common.OnDemandCounter; +import com.google.firebase.crashlytics.internal.common.Utils; import com.google.firebase.crashlytics.internal.model.CrashlyticsReport; import com.google.firebase.crashlytics.internal.settings.Settings; import java.util.Locale; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -115,6 +120,18 @@ TaskCompletionSource enqueueReport( } } + @SuppressLint("DiscouragedApi") // best effort only + public void flushScheduledReportsIfAble() { + CountDownLatch latch = new CountDownLatch(1); + new Thread( + () -> { + ForcedSender.sendBlocking(transport, Priority.HIGHEST); + latch.countDown(); + }) + .start(); + Utils.awaitUninterruptibly(latch, 2, TimeUnit.SECONDS); + } + /** Send the report to Crashlytics through Google DataTransport. */ private void sendReport( CrashlyticsReportWithSessionId reportWithSessionId, @@ -128,6 +145,7 @@ private void sendReport( tcs.trySetException(error); return; } + flushScheduledReportsIfAble(); tcs.trySetResult(reportWithSessionId); }); } diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/ForcedSender.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/ForcedSender.java new file mode 100644 index 00000000000..d0a9ef2b8cc --- /dev/null +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/ForcedSender.java @@ -0,0 +1,42 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.android.datatransport.runtime; + +import android.annotation.SuppressLint; +import androidx.annotation.Discouraged; +import androidx.annotation.WorkerThread; +import com.google.android.datatransport.Priority; +import com.google.android.datatransport.Transport; + +@Discouraged( + message = + "TransportRuntime is not a realtime delivery system, don't use unless you absolutely must.") +public final class ForcedSender { + @WorkerThread + public static void sendBlocking(Transport transport, Priority priority) { + @SuppressLint("DiscouragedApi") + TransportContext context = getTransportContextOrThrow(transport).withPriority(priority); + TransportRuntime.getInstance().getUploader().logAndUpdateState(context, 1); + } + + private static TransportContext getTransportContextOrThrow(Transport transport) { + if (transport instanceof TransportImpl) { + return ((TransportImpl) transport).getTransportContext(); + } + throw new IllegalArgumentException("Expected instance of TransportImpl."); + } + + private ForcedSender() {} +} diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/TransportImpl.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/TransportImpl.java index 621363126ce..96d0fb83ca8 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/TransportImpl.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/TransportImpl.java @@ -57,4 +57,8 @@ public void schedule(Event event, TransportScheduleCallback callback) { .build(), callback); } + + TransportContext getTransportContext() { + return transportContext; + } } diff --git a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/Uploader.java b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/Uploader.java index ba59ef1eb1a..0ac9017e7f2 100644 --- a/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/Uploader.java +++ b/transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/Uploader.java @@ -17,6 +17,7 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import androidx.annotation.RestrictTo; import androidx.annotation.VisibleForTesting; import com.google.android.datatransport.Encoding; import com.google.android.datatransport.runtime.EncodedPayload; @@ -111,7 +112,8 @@ public void upload(TransportContext transportContext, int attemptNumber, Runnabl }); } - BackendResponse logAndUpdateState(TransportContext transportContext, int attemptNumber) { + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + public BackendResponse logAndUpdateState(TransportContext transportContext, int attemptNumber) { TransportBackend backend = backendRegistry.get(transportContext.getBackendName()); long maxNextRequestWaitMillis = 0; diff --git a/transport/transport-runtime/transport-runtime.gradle b/transport/transport-runtime/transport-runtime.gradle index 240c6720375..2272e16ba95 100644 --- a/transport/transport-runtime/transport-runtime.gradle +++ b/transport/transport-runtime/transport-runtime.gradle @@ -101,7 +101,7 @@ thirdPartyLicenses { dependencies { implementation project(':transport:transport-api') - implementation 'androidx.annotation:annotation:1.1.0' + implementation 'androidx.annotation:annotation:1.3.0' implementation 'javax.inject:javax.inject:1' implementation project(":encoders:firebase-encoders") implementation project(":encoders:firebase-encoders-proto") @@ -132,4 +132,4 @@ dependencies { androidTestImplementation 'org.mockito:mockito-android:2.25.0' androidTestAnnotationProcessor 'com.google.dagger:dagger-compiler:2.27' -} \ No newline at end of file +}