diff --git a/firebase-annotations/firebase-annotations.gradle b/firebase-annotations/firebase-annotations.gradle
index e77114c5afb..b1762d0a129 100644
--- a/firebase-annotations/firebase-annotations.gradle
+++ b/firebase-annotations/firebase-annotations.gradle
@@ -29,3 +29,7 @@ java {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Werror"
}
+
+dependencies {
+ implementation 'javax.inject:javax.inject:1'
+}
diff --git a/firebase-annotations/src/main/java/com/google/firebase/annotations/concurrent/Background.java b/firebase-annotations/src/main/java/com/google/firebase/annotations/concurrent/Background.java
new file mode 100644
index 00000000000..5626ea94a78
--- /dev/null
+++ b/firebase-annotations/src/main/java/com/google/firebase/annotations/concurrent/Background.java
@@ -0,0 +1,30 @@
+// 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.firebase.annotations.concurrent;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import javax.inject.Qualifier;
+
+/**
+ * An executor/coroutine dispatcher for long running tasks including disk IO, heavy CPU
+ * computations.
+ *
+ *
For operations that can block for long periods of time, like network requests, use the {@link
+ * Blocking} executor.
+ */
+@Qualifier
+@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
+public @interface Background {}
diff --git a/firebase-annotations/src/main/java/com/google/firebase/annotations/concurrent/Blocking.java b/firebase-annotations/src/main/java/com/google/firebase/annotations/concurrent/Blocking.java
new file mode 100644
index 00000000000..d57513b57f1
--- /dev/null
+++ b/firebase-annotations/src/main/java/com/google/firebase/annotations/concurrent/Blocking.java
@@ -0,0 +1,27 @@
+// 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.firebase.annotations.concurrent;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import javax.inject.Qualifier;
+
+/**
+ * An executor/coroutine dispatcher for tasks that can block for long periods of time, e.g network
+ * IO.
+ */
+@Qualifier
+@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
+public @interface Blocking {}
diff --git a/firebase-annotations/src/main/java/com/google/firebase/annotations/concurrent/Lightweight.java b/firebase-annotations/src/main/java/com/google/firebase/annotations/concurrent/Lightweight.java
new file mode 100644
index 00000000000..4d3b0828954
--- /dev/null
+++ b/firebase-annotations/src/main/java/com/google/firebase/annotations/concurrent/Lightweight.java
@@ -0,0 +1,26 @@
+// 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.firebase.annotations.concurrent;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import javax.inject.Qualifier;
+
+/**
+ * An executor/coroutine dispatcher for lightweight tasks that never block (on IO or other tasks).
+ */
+@Qualifier
+@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
+public @interface Lightweight {}
diff --git a/firebase-annotations/src/main/java/com/google/firebase/annotations/concurrent/UiThread.java b/firebase-annotations/src/main/java/com/google/firebase/annotations/concurrent/UiThread.java
new file mode 100644
index 00000000000..bad10c164b8
--- /dev/null
+++ b/firebase-annotations/src/main/java/com/google/firebase/annotations/concurrent/UiThread.java
@@ -0,0 +1,24 @@
+// 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.firebase.annotations.concurrent;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import javax.inject.Qualifier;
+
+/** An executor/coroutine dispatcher for work that must run on the UI thread. */
+@Qualifier
+@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
+public @interface UiThread {}
diff --git a/firebase-common/firebase-common.gradle b/firebase-common/firebase-common.gradle
index 47726702701..16c38ff1db6 100644
--- a/firebase-common/firebase-common.gradle
+++ b/firebase-common/firebase-common.gradle
@@ -66,6 +66,7 @@ dependencies {
implementation project(':firebase-components')
implementation 'com.google.android.gms:play-services-basement:18.1.0'
implementation "com.google.android.gms:play-services-tasks:18.0.1"
+ implementation 'androidx.concurrent:concurrent-futures:1.1.0'
// FirebaseApp references storage, so storage needs to be on classpath when dokka runs.
javadocClasspath project(path: ':firebase-storage')
diff --git a/firebase-common/ktx/ktx.gradle b/firebase-common/ktx/ktx.gradle
index 2d15573ab83..f86c14a5e16 100644
--- a/firebase-common/ktx/ktx.gradle
+++ b/firebase-common/ktx/ktx.gradle
@@ -38,6 +38,7 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+ implementation project(':firebase-annotations')
implementation project(':firebase-common')
implementation project(':firebase-components')
implementation 'androidx.annotation:annotation:1.1.0'
diff --git a/firebase-common/ktx/src/main/kotlin/com/google/firebase/ktx/Firebase.kt b/firebase-common/ktx/src/main/kotlin/com/google/firebase/ktx/Firebase.kt
index f939f218f0f..64ed1fa31d2 100644
--- a/firebase-common/ktx/src/main/kotlin/com/google/firebase/ktx/Firebase.kt
+++ b/firebase-common/ktx/src/main/kotlin/com/google/firebase/ktx/Firebase.kt
@@ -17,9 +17,18 @@ import android.content.Context
import androidx.annotation.Keep
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
+import com.google.firebase.annotations.concurrent.Background
+import com.google.firebase.annotations.concurrent.Blocking
+import com.google.firebase.annotations.concurrent.Lightweight
+import com.google.firebase.annotations.concurrent.UiThread
import com.google.firebase.components.Component
import com.google.firebase.components.ComponentRegistrar
+import com.google.firebase.components.Dependency
+import com.google.firebase.components.Qualified
import com.google.firebase.platforminfo.LibraryVersionComponent
+import java.util.concurrent.Executor
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.asCoroutineDispatcher
/**
* Single access point to all firebase SDKs from Kotlin.
@@ -40,11 +49,11 @@ fun Firebase.initialize(context: Context): FirebaseApp? = FirebaseApp.initialize
/** Initializes and returns a FirebaseApp. */
fun Firebase.initialize(context: Context, options: FirebaseOptions): FirebaseApp =
- FirebaseApp.initializeApp(context, options)
+ FirebaseApp.initializeApp(context, options)
/** Initializes and returns a FirebaseApp. */
fun Firebase.initialize(context: Context, options: FirebaseOptions, name: String): FirebaseApp =
- FirebaseApp.initializeApp(context, options, name)
+ FirebaseApp.initializeApp(context, options, name)
/** Returns options of default FirebaseApp */
val Firebase.options: FirebaseOptions
@@ -57,6 +66,27 @@ internal const val LIBRARY_NAME: String = "fire-core-ktx"
class FirebaseCommonKtxRegistrar : ComponentRegistrar {
override fun getComponents(): List> {
return listOf(
- LibraryVersionComponent.create(LIBRARY_NAME, BuildConfig.VERSION_NAME))
+ LibraryVersionComponent.create(LIBRARY_NAME, BuildConfig.VERSION_NAME),
+ coroutineDispatcher(),
+ coroutineDispatcher(),
+ coroutineDispatcher(),
+ coroutineDispatcher()
+ )
}
}
+
+private inline fun coroutineDispatcher(): Component =
+ Component.builder(
+ Qualified.qualified(T::class.java, CoroutineDispatcher::class.java)
+ ).add(
+ Dependency.required(
+ Qualified.qualified(
+ T::class.java,
+ Executor::class.java
+ )
+ )
+ ).factory { c ->
+ c.get(
+ Qualified.qualified(T::class.java, Executor::class.java)
+ ).asCoroutineDispatcher()
+ }.build()
diff --git a/firebase-common/src/main/java/com/google/firebase/FirebaseApp.java b/firebase-common/src/main/java/com/google/firebase/FirebaseApp.java
index efc6c01d8ce..8fc7767388e 100644
--- a/firebase-common/src/main/java/com/google/firebase/FirebaseApp.java
+++ b/firebase-common/src/main/java/com/google/firebase/FirebaseApp.java
@@ -23,8 +23,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.GuardedBy;
@@ -47,6 +45,7 @@
import com.google.firebase.components.ComponentRegistrar;
import com.google.firebase.components.ComponentRuntime;
import com.google.firebase.components.Lazy;
+import com.google.firebase.concurrent.ExecutorsRegistrar;
import com.google.firebase.events.Publisher;
import com.google.firebase.heartbeatinfo.DefaultHeartBeatController;
import com.google.firebase.inject.Provider;
@@ -59,7 +58,6 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -97,16 +95,10 @@ public class FirebaseApp {
private static final Object LOCK = new Object();
- private static final Executor UI_EXECUTOR = new UiExecutor();
-
/** A map of (name, FirebaseApp) instances. */
@GuardedBy("LOCK")
static final Map INSTANCES = new ArrayMap<>();
- private static final String FIREBASE_ANDROID = "fire-android";
- private static final String FIREBASE_COMMON = "fire-core";
- private static final String KOTLIN = "kotlin";
-
private final Context applicationContext;
private final String name;
private final FirebaseOptions options;
@@ -427,9 +419,10 @@ protected FirebaseApp(Context applicationContext, String name, FirebaseOptions o
FirebaseTrace.pushTrace("Runtime");
componentRuntime =
- ComponentRuntime.builder(UI_EXECUTOR)
+ ComponentRuntime.builder(com.google.firebase.concurrent.UiExecutor.INSTANCE)
.addLazyComponentRegistrars(registrars)
.addComponentRegistrar(new FirebaseCommonRegistrar())
+ .addComponentRegistrar(new ExecutorsRegistrar())
.addComponent(Component.of(applicationContext, Context.class))
.addComponent(Component.of(this, FirebaseApp.class))
.addComponent(Component.of(options, FirebaseOptions.class))
@@ -712,14 +705,4 @@ public void onBackgroundStateChanged(boolean background) {
}
}
}
-
- private static class UiExecutor implements Executor {
-
- private static final Handler HANDLER = new Handler(Looper.getMainLooper());
-
- @Override
- public void execute(@NonNull Runnable command) {
- HANDLER.post(command);
- }
- }
}
diff --git a/firebase-common/src/main/java/com/google/firebase/concurrent/CustomThreadFactory.java b/firebase-common/src/main/java/com/google/firebase/concurrent/CustomThreadFactory.java
new file mode 100644
index 00000000000..b3079db1d20
--- /dev/null
+++ b/firebase-common/src/main/java/com/google/firebase/concurrent/CustomThreadFactory.java
@@ -0,0 +1,41 @@
+// 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.firebase.concurrent;
+
+import java.util.Locale;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicLong;
+
+class CustomThreadFactory implements ThreadFactory {
+ private static final ThreadFactory DEFAULT = Executors.defaultThreadFactory();
+ private final AtomicLong threadCount = new AtomicLong();
+ private final String namePrefix;
+ private final int priority;
+
+ CustomThreadFactory(String namePrefix, int priority) {
+ this.namePrefix = namePrefix;
+ this.priority = priority;
+ }
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread thread = DEFAULT.newThread(r);
+ thread.setPriority(priority);
+ thread.setName(
+ String.format(Locale.ROOT, "%s Thread #%d", namePrefix, threadCount.getAndIncrement()));
+ return thread;
+ }
+}
diff --git a/firebase-common/src/main/java/com/google/firebase/concurrent/DelegatingScheduledExecutorService.java b/firebase-common/src/main/java/com/google/firebase/concurrent/DelegatingScheduledExecutorService.java
new file mode 100644
index 00000000000..23b9385c284
--- /dev/null
+++ b/firebase-common/src/main/java/com/google/firebase/concurrent/DelegatingScheduledExecutorService.java
@@ -0,0 +1,185 @@
+// 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.firebase.concurrent;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+class DelegatingScheduledExecutorService implements ScheduledExecutorService {
+ private final ExecutorService delegate;
+ private final ScheduledExecutorService scheduler;
+
+ DelegatingScheduledExecutorService(ExecutorService delegate, ScheduledExecutorService scheduler) {
+ this.delegate = delegate;
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public void shutdown() {
+ throw new UnsupportedOperationException("Shutting down is not allowed.");
+ }
+
+ @Override
+ public List shutdownNow() {
+ throw new UnsupportedOperationException("Shutting down is not allowed.");
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return delegate.isShutdown();
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return delegate.isTerminated();
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ return delegate.awaitTermination(timeout, unit);
+ }
+
+ @Override
+ public Future submit(Callable task) {
+ return delegate.submit(task);
+ }
+
+ @Override
+ public Future submit(Runnable task, T result) {
+ return delegate.submit(task, result);
+ }
+
+ @Override
+ public Future> submit(Runnable task) {
+ return delegate.submit(task);
+ }
+
+ @Override
+ public List> invokeAll(Collection extends Callable> tasks)
+ throws InterruptedException {
+ return delegate.invokeAll(tasks);
+ }
+
+ @Override
+ public List> invokeAll(
+ Collection extends Callable> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return delegate.invokeAll(tasks, timeout, unit);
+ }
+
+ @Override
+ public T invokeAny(Collection extends Callable> tasks)
+ throws ExecutionException, InterruptedException {
+ return delegate.invokeAny(tasks);
+ }
+
+ @Override
+ public T invokeAny(Collection extends Callable> tasks, long timeout, TimeUnit unit)
+ throws ExecutionException, InterruptedException, TimeoutException {
+ return delegate.invokeAny(tasks, timeout, unit);
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ delegate.execute(command);
+ }
+
+ @Override
+ public ScheduledFuture> schedule(Runnable command, long delay, TimeUnit unit) {
+ return new DelegatingScheduledFuture(
+ completer ->
+ scheduler.schedule(
+ () ->
+ delegate.execute(
+ () -> {
+ try {
+ command.run();
+ completer.set(null);
+ } catch (Exception ex) {
+ completer.setException(ex);
+ }
+ }),
+ delay,
+ unit));
+ }
+
+ @Override
+ public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) {
+ return new DelegatingScheduledFuture<>(
+ completer ->
+ scheduler.schedule(
+ () ->
+ delegate.submit(
+ () -> {
+ try {
+ V result = callable.call();
+ completer.set(result);
+ } catch (Exception ex) {
+ completer.setException(ex);
+ }
+ }),
+ delay,
+ unit));
+ }
+
+ @Override
+ public ScheduledFuture> scheduleAtFixedRate(
+ Runnable command, long initialDelay, long period, TimeUnit unit) {
+ return new DelegatingScheduledFuture<>(
+ completer ->
+ scheduler.scheduleAtFixedRate(
+ () ->
+ delegate.execute(
+ () -> {
+ try {
+ command.run();
+ } catch (Exception ex) {
+ completer.setException(ex);
+ throw ex;
+ }
+ }),
+ initialDelay,
+ period,
+ unit));
+ }
+
+ @Override
+ public ScheduledFuture> scheduleWithFixedDelay(
+ Runnable command, long initialDelay, long delay, TimeUnit unit) {
+ return new DelegatingScheduledFuture<>(
+ completer ->
+ scheduler.scheduleWithFixedDelay(
+ () ->
+ delegate.execute(
+ () -> {
+ try {
+ command.run();
+ } catch (Exception ex) {
+ completer.setException(ex);
+ }
+ }),
+ initialDelay,
+ delay,
+ unit));
+ }
+}
diff --git a/firebase-common/src/main/java/com/google/firebase/concurrent/DelegatingScheduledFuture.java b/firebase-common/src/main/java/com/google/firebase/concurrent/DelegatingScheduledFuture.java
new file mode 100644
index 00000000000..26ef258136a
--- /dev/null
+++ b/firebase-common/src/main/java/com/google/firebase/concurrent/DelegatingScheduledFuture.java
@@ -0,0 +1,72 @@
+// 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.firebase.concurrent;
+
+import android.annotation.SuppressLint;
+import androidx.concurrent.futures.AbstractResolvableFuture;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+// While direct use of AbstractResolvableFuture is not encouraged, it's stable for use and is not
+// going to be removed. In this case it's required since we need to implement a ScheduledFuture so
+// we can't use CallbackToFutureAdapter.
+@SuppressLint("RestrictedApi")
+class DelegatingScheduledFuture extends AbstractResolvableFuture
+ implements ScheduledFuture {
+
+ interface Completer {
+ void set(T value);
+
+ void setException(Throwable ex);
+ }
+
+ interface Resolver {
+ ScheduledFuture> addCompleter(Completer completer);
+ }
+
+ DelegatingScheduledFuture(Resolver resolver) {
+ upstreamFuture =
+ resolver.addCompleter(
+ new Completer() {
+ @Override
+ public void set(V value) {
+ DelegatingScheduledFuture.this.set(value);
+ }
+
+ @Override
+ public void setException(Throwable ex) {
+ DelegatingScheduledFuture.this.setException(ex);
+ }
+ });
+ }
+
+ private final ScheduledFuture> upstreamFuture;
+
+ @Override
+ protected void afterDone() {
+ upstreamFuture.cancel(wasInterrupted());
+ }
+
+ @Override
+ public long getDelay(TimeUnit unit) {
+ return upstreamFuture.getDelay(unit);
+ }
+
+ @Override
+ public int compareTo(Delayed o) {
+ return upstreamFuture.compareTo(o);
+ }
+}
diff --git a/firebase-common/src/main/java/com/google/firebase/concurrent/ExecutorsRegistrar.java b/firebase-common/src/main/java/com/google/firebase/concurrent/ExecutorsRegistrar.java
new file mode 100644
index 00000000000..1b28eb8aec9
--- /dev/null
+++ b/firebase-common/src/main/java/com/google/firebase/concurrent/ExecutorsRegistrar.java
@@ -0,0 +1,102 @@
+// 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.firebase.concurrent;
+
+import android.annotation.SuppressLint;
+import android.os.Process;
+import com.google.firebase.annotations.concurrent.Background;
+import com.google.firebase.annotations.concurrent.Blocking;
+import com.google.firebase.annotations.concurrent.Lightweight;
+import com.google.firebase.annotations.concurrent.UiThread;
+import com.google.firebase.components.Component;
+import com.google.firebase.components.ComponentRegistrar;
+import com.google.firebase.components.Lazy;
+import com.google.firebase.components.Qualified;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+
+/** @hide */
+@SuppressLint("ThreadPoolCreation")
+public class ExecutorsRegistrar implements ComponentRegistrar {
+ private static final Lazy BG_EXECUTOR =
+ new Lazy<>(
+ () ->
+ scheduled(
+ Executors.newFixedThreadPool(
+ 4, factory("Firebase Background", Process.THREAD_PRIORITY_BACKGROUND))));
+
+ private static final Lazy LITE_EXECUTOR =
+ new Lazy<>(
+ () ->
+ scheduled(
+ Executors.newFixedThreadPool(
+ Math.max(2, Runtime.getRuntime().availableProcessors()),
+ factory("Firebase Lite", Process.THREAD_PRIORITY_DEFAULT))));
+
+ private static final Lazy BLOCKING_EXECUTOR =
+ new Lazy<>(
+ () ->
+ scheduled(
+ Executors.newCachedThreadPool(
+ factory(
+ "Firebase Blocking",
+ Process.THREAD_PRIORITY_BACKGROUND
+ + Process.THREAD_PRIORITY_LESS_FAVORABLE))));
+
+ private static final Lazy SCHEDULER =
+ new Lazy<>(
+ () ->
+ Executors.newSingleThreadScheduledExecutor(
+ factory("Firebase Scheduler", Process.THREAD_PRIORITY_DEFAULT)));
+
+ @Override
+ public List> getComponents() {
+ return Arrays.asList(
+ Component.builder(
+ Qualified.qualified(Background.class, ScheduledExecutorService.class),
+ Qualified.qualified(Background.class, ExecutorService.class),
+ Qualified.qualified(Background.class, Executor.class))
+ .factory(c -> BG_EXECUTOR.get())
+ .build(),
+ Component.builder(
+ Qualified.qualified(Blocking.class, ScheduledExecutorService.class),
+ Qualified.qualified(Blocking.class, ExecutorService.class),
+ Qualified.qualified(Blocking.class, Executor.class))
+ .factory(c -> BLOCKING_EXECUTOR.get())
+ .build(),
+ Component.builder(
+ Qualified.qualified(Lightweight.class, ScheduledExecutorService.class),
+ Qualified.qualified(Lightweight.class, ExecutorService.class),
+ Qualified.qualified(Lightweight.class, Executor.class))
+ .factory(c -> LITE_EXECUTOR.get())
+ .build(),
+ Component.builder(Qualified.qualified(UiThread.class, Executor.class))
+ .factory(c -> UiExecutor.INSTANCE)
+ .build());
+ }
+
+ private static ScheduledExecutorService scheduled(ExecutorService delegate) {
+ return new DelegatingScheduledExecutorService(delegate, SCHEDULER.get());
+ }
+
+ private static ThreadFactory factory(String threadPrefix, int priority) {
+ return new CustomThreadFactory(threadPrefix, priority);
+ }
+}
diff --git a/firebase-common/src/main/java/com/google/firebase/concurrent/UiExecutor.java b/firebase-common/src/main/java/com/google/firebase/concurrent/UiExecutor.java
new file mode 100644
index 00000000000..e06311c8a3a
--- /dev/null
+++ b/firebase-common/src/main/java/com/google/firebase/concurrent/UiExecutor.java
@@ -0,0 +1,31 @@
+// 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.firebase.concurrent;
+
+import android.os.Handler;
+import android.os.Looper;
+import java.util.concurrent.Executor;
+
+/** @hide */
+public enum UiExecutor implements Executor {
+ INSTANCE;
+
+ private static final Handler HANDLER = new Handler(Looper.getMainLooper());
+
+ @Override
+ public void execute(Runnable command) {
+ HANDLER.post(command);
+ }
+}
diff --git a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java
index cb7722c37bd..ff9ba5123fc 100644
--- a/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java
+++ b/firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatController.java
@@ -23,18 +23,16 @@
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.FirebaseApp;
+import com.google.firebase.annotations.concurrent.Background;
import com.google.firebase.components.Component;
import com.google.firebase.components.Dependency;
+import com.google.firebase.components.Qualified;
import com.google.firebase.inject.Provider;
import com.google.firebase.platforminfo.UserAgentPublisher;
import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPOutputStream;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -52,9 +50,6 @@ public class DefaultHeartBeatController implements HeartBeatController, HeartBea
private final Executor backgroundExecutor;
- private static final ThreadFactory THREAD_FACTORY =
- r -> new Thread(r, "heartbeat-information-executor");
-
public Task registerHeartBeat() {
if (consumers.size() <= 0) {
return Tasks.forResult(null);
@@ -118,12 +113,12 @@ private DefaultHeartBeatController(
Context context,
String persistenceKey,
Set consumers,
- Provider userAgentProvider) {
+ Provider userAgentProvider,
+ Executor backgroundExecutor) {
this(
() -> new HeartBeatInfoStorage(context, persistenceKey),
consumers,
- new ThreadPoolExecutor(
- 0, 1, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), THREAD_FACTORY),
+ backgroundExecutor,
userAgentProvider,
context);
}
@@ -143,19 +138,22 @@ private DefaultHeartBeatController(
}
public static @NonNull Component component() {
+ Qualified backgroundExecutor = Qualified.qualified(Background.class, Executor.class);
return Component.builder(
DefaultHeartBeatController.class, HeartBeatController.class, HeartBeatInfo.class)
.add(Dependency.required(Context.class))
.add(Dependency.required(FirebaseApp.class))
.add(Dependency.setOf(HeartBeatConsumer.class))
.add(Dependency.requiredProvider(UserAgentPublisher.class))
+ .add(Dependency.required(backgroundExecutor))
.factory(
c ->
new DefaultHeartBeatController(
c.get(Context.class),
c.get(FirebaseApp.class).getPersistenceKey(),
c.setOf(HeartBeatConsumer.class),
- c.getProvider(UserAgentPublisher.class)))
+ c.getProvider(UserAgentPublisher.class),
+ c.get(backgroundExecutor)))
.build();
}
diff --git a/firebase-common/src/test/AndroidManifest.xml b/firebase-common/src/test/AndroidManifest.xml
new file mode 100644
index 00000000000..b0baac40e37
--- /dev/null
+++ b/firebase-common/src/test/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/firebase-common/src/test/java/com/google/firebase/concurrent/DelegatingScheduledExecutorServiceTest.java b/firebase-common/src/test/java/com/google/firebase/concurrent/DelegatingScheduledExecutorServiceTest.java
new file mode 100644
index 00000000000..0717ddf4ee8
--- /dev/null
+++ b/firebase-common/src/test/java/com/google/firebase/concurrent/DelegatingScheduledExecutorServiceTest.java
@@ -0,0 +1,82 @@
+// 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.firebase.concurrent;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class DelegatingScheduledExecutorServiceTest {
+ private final DelegatingScheduledExecutorService service =
+ new DelegatingScheduledExecutorService(
+ Executors.newCachedThreadPool(), Executors.newSingleThreadScheduledExecutor());
+
+ @Test
+ public void schedule_whenCancelled_shouldCancelUnderlyingFuture() {
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicBoolean ran = new AtomicBoolean();
+ ScheduledFuture