instances =
+ new ConcurrentHashMap<>();
+
+ @Inject
+ ProviderMultiResourceComponent(RecaptchaEnterpriseAppCheckProviderFactory providerFactory) {
+ this.providerFactory = providerFactory;
+ }
+
+ @NonNull
+ public RecaptchaEnterpriseAppCheckProvider get(@NonNull String siteKey) {
+ RecaptchaEnterpriseAppCheckProvider provider = instances.get(siteKey);
+ if (provider == null) {
+ synchronized (instances) {
+ provider = instances.get(siteKey);
+ if (provider == null) {
+ provider = providerFactory.create(siteKey);
+ instances.put(siteKey, provider);
+ }
+ }
+ }
+ return provider;
+ }
+
+ @AssistedFactory
+ interface RecaptchaEnterpriseAppCheckProviderFactory {
+ RecaptchaEnterpriseAppCheckProvider create(@Assisted String siteKey);
+ }
+}
diff --git a/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/internal/RecaptchaEnterpriseAppCheckProvider.java b/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/internal/RecaptchaEnterpriseAppCheckProvider.java
new file mode 100644
index 00000000000..961ab7defa8
--- /dev/null
+++ b/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/internal/RecaptchaEnterpriseAppCheckProvider.java
@@ -0,0 +1,142 @@
+// Copyright 2025 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.appcheck.recaptchaenterprise.internal;
+
+import android.app.Application;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import com.google.android.gms.tasks.Task;
+import com.google.android.gms.tasks.Tasks;
+import com.google.android.recaptcha.Recaptcha;
+import com.google.android.recaptcha.RecaptchaAction;
+import com.google.android.recaptcha.RecaptchaTasksClient;
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.annotations.concurrent.Blocking;
+import com.google.firebase.annotations.concurrent.Lightweight;
+import com.google.firebase.appcheck.AppCheckProvider;
+import com.google.firebase.appcheck.AppCheckToken;
+import com.google.firebase.appcheck.internal.DefaultAppCheckToken;
+import com.google.firebase.appcheck.internal.NetworkClient;
+import com.google.firebase.appcheck.internal.RetryManager;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedInject;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * An implementation of {@link AppCheckProvider} that uses reCAPTCHA Enterprise for device
+ * attestation.
+ *
+ * This class orchestrates the flow:
+ *
+ *
+ * - Obtain a reCAPTCHA token via {@code RecaptchaTasksClient}.
+ *
- Exchange the reCAPTCHA token with the Firebase App Check backend to receive a Firebase App
+ * Check token.
+ *
+ */
+public class RecaptchaEnterpriseAppCheckProvider implements AppCheckProvider {
+
+ private final RecaptchaAction recaptchaAction = RecaptchaAction.custom("fire_app_check");
+ private volatile Task recaptchaTasksClientTask;
+ private final Executor liteExecutor;
+ private final Executor blockingExecutor;
+ private final RetryManager retryManager;
+ private final NetworkClient networkClient;
+ private String siteKey;
+ private Application application;
+ private static final String TAG = "rCEAppCheckProvider";
+
+ @AssistedInject
+ public RecaptchaEnterpriseAppCheckProvider(
+ @NonNull FirebaseApp firebaseApp,
+ @Assisted @NonNull String siteKey,
+ @Lightweight Executor liteExecutor,
+ @Blocking Executor blockingExecutor) {
+ this.application = (Application) firebaseApp.getApplicationContext();
+ this.siteKey = siteKey;
+ this.liteExecutor = liteExecutor;
+ this.blockingExecutor = blockingExecutor;
+ this.retryManager = new RetryManager();
+ this.networkClient = new NetworkClient(firebaseApp);
+ }
+
+ @VisibleForTesting
+ RecaptchaEnterpriseAppCheckProvider(
+ @Lightweight Executor liteExecutor,
+ @Blocking Executor blockingExecutor,
+ @NonNull RetryManager retryManager,
+ @NonNull NetworkClient networkClient,
+ @NonNull RecaptchaTasksClient recaptchaTasksClient) {
+ this.liteExecutor = liteExecutor;
+ this.blockingExecutor = blockingExecutor;
+ this.retryManager = retryManager;
+ this.networkClient = networkClient;
+ this.recaptchaTasksClientTask = Tasks.forResult(recaptchaTasksClient);
+ }
+
+ public void initializeRecaptchaClient() {
+ if (recaptchaTasksClientTask == null) {
+ synchronized (this) {
+ if (recaptchaTasksClientTask == null) {
+ Log.d(TAG, "Initializing RecaptchaTasksClient for siteKey: " + siteKey);
+ recaptchaTasksClientTask = Recaptcha.fetchTaskClient(application, siteKey);
+ }
+ }
+ }
+ }
+
+ @NonNull
+ @Override
+ public Task getToken() {
+ return getRecaptchaEnterpriseAttestation()
+ .onSuccessTask(
+ liteExecutor,
+ recaptchaEnterpriseToken -> {
+ ExchangeRecaptchaEnterpriseTokenRequest request =
+ new ExchangeRecaptchaEnterpriseTokenRequest(recaptchaEnterpriseToken);
+ return Tasks.call(
+ blockingExecutor,
+ () ->
+ networkClient.exchangeAttestationForAppCheckToken(
+ request.toJsonString().getBytes(StandardCharsets.UTF_8),
+ NetworkClient.RECAPTCHA_ENTERPRISE,
+ retryManager));
+ })
+ .onSuccessTask(
+ liteExecutor,
+ appCheckTokenResponse ->
+ Tasks.forResult(
+ DefaultAppCheckToken.constructFromAppCheckTokenResponse(
+ appCheckTokenResponse)));
+ }
+
+ @NonNull
+ private Task getRecaptchaEnterpriseAttestation() {
+ return recaptchaTasksClientTask.continueWithTask(
+ blockingExecutor,
+ task -> {
+ if (task.isSuccessful()) {
+ RecaptchaTasksClient client = task.getResult();
+ return client.executeTask(recaptchaAction);
+ } else {
+ Log.w(TAG, "Recaptcha task failed", task.getException());
+ return Tasks.forException((Objects.requireNonNull(task.getException())));
+ }
+ });
+ }
+}
diff --git a/firebase-dynamic-links/src/main/java/com/google/firebase/dynamiclinks/internal/FirebaseDynamicLinksImplConstants.java b/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/internal/package-info.java
similarity index 67%
rename from firebase-dynamic-links/src/main/java/com/google/firebase/dynamiclinks/internal/FirebaseDynamicLinksImplConstants.java
rename to appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/internal/package-info.java
index c9da60e96b8..baae7a935b7 100644
--- a/firebase-dynamic-links/src/main/java/com/google/firebase/dynamiclinks/internal/FirebaseDynamicLinksImplConstants.java
+++ b/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/internal/package-info.java
@@ -1,4 +1,4 @@
-// Copyright 2021 Google LLC
+// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,10 +12,5 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-/// THIS FILE IS AUTO GENERATED. Do not modify.
-package com.google.firebase.dynamiclinks.internal;
-
-interface FirebaseDynamicLinksImplConstants {
- int GET_DYNAMIC_LINK_METHOD_KEY = 13201;
- int CREATE_SHORT_DYNAMIC_LINK_METHOD_KEY = 13202;
-}
+/** @hide */
+package com.google.firebase.appcheck.recaptchaenterprise.internal;
diff --git a/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/FirebaseAppCheckRecaptchaEnterpriseRegistrarTest.java b/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/FirebaseAppCheckRecaptchaEnterpriseRegistrarTest.java
new file mode 100644
index 00000000000..1d3ddadca85
--- /dev/null
+++ b/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/FirebaseAppCheckRecaptchaEnterpriseRegistrarTest.java
@@ -0,0 +1,38 @@
+// Copyright 2025 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.appcheck.recaptchaenterprise;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.firebase.components.Component;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link FirebaseAppCheckRecaptchaEnterpriseRegistrar}. */
+@RunWith(RobolectricTestRunner.class)
+public class FirebaseAppCheckRecaptchaEnterpriseRegistrarTest {
+ @Test
+ public void testGetComponents() {
+ FirebaseAppCheckRecaptchaEnterpriseRegistrar registrar =
+ new FirebaseAppCheckRecaptchaEnterpriseRegistrar();
+ List> components = registrar.getComponents();
+ assertThat(components).isNotEmpty();
+ assertThat(components).hasSize(2);
+ Component> firebaseExecutorsComponent = components.get(0);
+ assertThat(firebaseExecutorsComponent.isLazy()).isTrue();
+ }
+}
diff --git a/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactoryTest.java b/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactoryTest.java
new file mode 100644
index 00000000000..865b69e409c
--- /dev/null
+++ b/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactoryTest.java
@@ -0,0 +1,84 @@
+// Copyright 2025 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.appcheck.recaptchaenterprise;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.appcheck.AppCheckProvider;
+import com.google.firebase.appcheck.recaptchaenterprise.internal.ProviderMultiResourceComponent;
+import com.google.firebase.appcheck.recaptchaenterprise.internal.RecaptchaEnterpriseAppCheckProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/** Tests for {@link RecaptchaEnterpriseAppCheckProviderFactory}. */
+@RunWith(MockitoJUnitRunner.class)
+public class RecaptchaEnterpriseAppCheckProviderFactoryTest {
+ static final String SITE_KEY_1 = "siteKey1";
+
+ @Mock private FirebaseApp mockFirebaseApp;
+ @Mock private ProviderMultiResourceComponent mockComponent;
+ @Mock private RecaptchaEnterpriseAppCheckProvider mockProvider;
+
+ @Before
+ public void setUp() {
+ when(mockFirebaseApp.get(eq(ProviderMultiResourceComponent.class))).thenReturn(mockComponent);
+ when(mockComponent.get(anyString())).thenReturn(mockProvider);
+ }
+
+ @Test
+ public void getInstance_nonNullSiteKey_returnsNonNullInstance() {
+ RecaptchaEnterpriseAppCheckProviderFactory factory =
+ RecaptchaEnterpriseAppCheckProviderFactory.getInstance(SITE_KEY_1);
+ assertNotNull(factory);
+ }
+
+ @Test
+ public void getInstance_nullSiteKey_expectThrows() {
+ assertThrows(
+ NullPointerException.class,
+ () -> RecaptchaEnterpriseAppCheckProviderFactory.getInstance(null));
+ }
+
+ @Test
+ public void create_nonNullFirebaseApp_returnsRecaptchaEnterpriseAppCheckProvider() {
+ RecaptchaEnterpriseAppCheckProviderFactory factory =
+ RecaptchaEnterpriseAppCheckProviderFactory.getInstance(SITE_KEY_1);
+ AppCheckProvider provider = factory.create(mockFirebaseApp);
+ assertNotNull(provider);
+ assertEquals(RecaptchaEnterpriseAppCheckProvider.class, provider.getClass());
+ }
+
+ @Test
+ public void create_callMultipleTimes_providerIsInitializedOnlyOnce() {
+ RecaptchaEnterpriseAppCheckProviderFactory factory =
+ RecaptchaEnterpriseAppCheckProviderFactory.getInstance(SITE_KEY_1);
+
+ factory.create(mockFirebaseApp);
+ factory.create(mockFirebaseApp);
+ factory.create(mockFirebaseApp);
+ verify(mockProvider, times(1)).initializeRecaptchaClient();
+ }
+}
diff --git a/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/internal/ExchangeRecaptchaEnterpriseTokenRequestTest.java b/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/internal/ExchangeRecaptchaEnterpriseTokenRequestTest.java
new file mode 100644
index 00000000000..20e48b7eb46
--- /dev/null
+++ b/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/internal/ExchangeRecaptchaEnterpriseTokenRequestTest.java
@@ -0,0 +1,39 @@
+// Copyright 2025 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.appcheck.recaptchaenterprise.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.json.JSONObject;
+import org.junit.Test;
+
+/** Tests for {@link ExchangeRecaptchaEnterpriseTokenRequest}. */
+public class ExchangeRecaptchaEnterpriseTokenRequestTest {
+ private static final String RECAPTCHA_ENTERPRISE_TOKEN = "recaptchaEnterpriseToken";
+
+ @Test
+ public void toJsonString_expectSerialized() throws Exception {
+ ExchangeRecaptchaEnterpriseTokenRequest exchangeRecaptchaEnterpriseTokenRequest =
+ new ExchangeRecaptchaEnterpriseTokenRequest(RECAPTCHA_ENTERPRISE_TOKEN);
+
+ String jsonString = exchangeRecaptchaEnterpriseTokenRequest.toJsonString();
+ JSONObject jsonObject = new JSONObject(jsonString);
+
+ assertThat(
+ jsonObject.getString(
+ ExchangeRecaptchaEnterpriseTokenRequest.RECAPTCHA_ENTERPRISE_TOKEN_KEY))
+ .isEqualTo(RECAPTCHA_ENTERPRISE_TOKEN);
+ }
+}
diff --git a/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/internal/RecaptchaEnterpriseAppCheckProviderTest.java b/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/internal/RecaptchaEnterpriseAppCheckProviderTest.java
new file mode 100644
index 00000000000..19cbdc4957b
--- /dev/null
+++ b/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/internal/RecaptchaEnterpriseAppCheckProviderTest.java
@@ -0,0 +1,164 @@
+// Copyright 2025 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.appcheck.recaptchaenterprise.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.android.gms.tasks.Task;
+import com.google.android.gms.tasks.Tasks;
+import com.google.android.recaptcha.RecaptchaAction;
+import com.google.android.recaptcha.RecaptchaTasksClient;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.FirebaseException;
+import com.google.firebase.appcheck.AppCheckToken;
+import com.google.firebase.appcheck.internal.AppCheckTokenResponse;
+import com.google.firebase.appcheck.internal.DefaultAppCheckToken;
+import com.google.firebase.appcheck.internal.NetworkClient;
+import com.google.firebase.appcheck.internal.RetryManager;
+import com.google.firebase.concurrent.TestOnlyExecutors;
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.LooperMode;
+
+/** Tests for {@link RecaptchaEnterpriseAppCheckProvider}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+@LooperMode(LooperMode.Mode.LEGACY)
+public class RecaptchaEnterpriseAppCheckProviderTest {
+ private static final String APP_CHECK_TOKEN = "appCheckToken";
+ private static final String RECAPTCHA_ENTERPRISE_TOKEN = "recaptchaEnterpriseToken";
+ private final Executor liteExecutor = MoreExecutors.directExecutor();
+ private final Executor blockingExecutor = MoreExecutors.directExecutor();
+ private final String siteKey = "siteKey";
+
+ @Mock private NetworkClient mockNetworkClient;
+ @Mock FirebaseApp mockFirebaseApp;
+ @Mock RecaptchaTasksClient mockRecaptchaTasksClient;
+ @Mock RetryManager mockRetryManager;
+
+ @Captor private ArgumentCaptor recaptchaActionCaptor;
+ @Captor private ArgumentCaptor requestCaptor;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ public void testPublicConstructor_nullFirebaseApp_expectThrows() {
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ new RecaptchaEnterpriseAppCheckProvider(
+ null, siteKey, TestOnlyExecutors.lite(), TestOnlyExecutors.blocking()));
+ }
+
+ @Test
+ public void testPublicConstructor_nullSiteKey_expectThrows() {
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ new RecaptchaEnterpriseAppCheckProvider(
+ mockFirebaseApp, null, TestOnlyExecutors.lite(), TestOnlyExecutors.blocking()));
+ }
+
+ @Test
+ public void getToken_onSuccess_setsTaskResult() throws Exception {
+ when(mockRecaptchaTasksClient.executeTask(any(RecaptchaAction.class)))
+ .thenReturn(Tasks.forResult(RECAPTCHA_ENTERPRISE_TOKEN));
+ String jsonResponse =
+ new JSONObject().put("token", APP_CHECK_TOKEN).put("ttl", 3600).toString();
+ when(mockNetworkClient.exchangeAttestationForAppCheckToken(
+ any(byte[].class), eq(NetworkClient.RECAPTCHA_ENTERPRISE), eq(mockRetryManager)))
+ .thenReturn(AppCheckTokenResponse.fromJsonString(jsonResponse));
+
+ RecaptchaEnterpriseAppCheckProvider provider =
+ new RecaptchaEnterpriseAppCheckProvider(
+ liteExecutor,
+ blockingExecutor,
+ mockRetryManager,
+ mockNetworkClient,
+ mockRecaptchaTasksClient);
+ Task task = provider.getToken();
+
+ assertThat(task.isSuccessful()).isTrue();
+ AppCheckToken token = task.getResult();
+ assertThat(token).isInstanceOf(DefaultAppCheckToken.class);
+ assertThat(token.getToken()).isEqualTo(APP_CHECK_TOKEN);
+
+ verify(mockRecaptchaTasksClient).executeTask(recaptchaActionCaptor.capture());
+ assertThat(recaptchaActionCaptor.getValue().getAction()).isEqualTo("fire_app_check");
+ verify(mockNetworkClient)
+ .exchangeAttestationForAppCheckToken(
+ requestCaptor.capture(), eq(NetworkClient.RECAPTCHA_ENTERPRISE), eq(mockRetryManager));
+ }
+
+ @Test
+ public void getToken_recaptchaFails_returnException() {
+ Exception exception = new Exception("Recaptcha error");
+ when(mockRecaptchaTasksClient.executeTask(any(RecaptchaAction.class)))
+ .thenReturn(Tasks.forException(exception));
+
+ RecaptchaEnterpriseAppCheckProvider provider =
+ new RecaptchaEnterpriseAppCheckProvider(
+ liteExecutor,
+ blockingExecutor,
+ mockRetryManager,
+ mockNetworkClient,
+ mockRecaptchaTasksClient);
+ Task task = provider.getToken();
+ assertThat(task.isSuccessful()).isFalse();
+ assertThat(task.getException()).isEqualTo(exception);
+ }
+
+ @Test
+ public void getToken_networkFails_returnException()
+ throws FirebaseException, JSONException, IOException {
+ when(mockRecaptchaTasksClient.executeTask(any(RecaptchaAction.class)))
+ .thenReturn(Tasks.forResult(RECAPTCHA_ENTERPRISE_TOKEN));
+ Exception exception = new IOException("Network error");
+ when(mockNetworkClient.exchangeAttestationForAppCheckToken(
+ any(byte[].class), eq(NetworkClient.RECAPTCHA_ENTERPRISE), eq(mockRetryManager)))
+ .thenThrow(exception);
+
+ RecaptchaEnterpriseAppCheckProvider provider =
+ new RecaptchaEnterpriseAppCheckProvider(
+ liteExecutor,
+ blockingExecutor,
+ mockRetryManager,
+ mockNetworkClient,
+ mockRecaptchaTasksClient);
+ Task task = provider.getToken();
+ assertThat(task.isSuccessful()).isFalse();
+ assertThat(task.getException()).isEqualTo(exception);
+ }
+}
diff --git a/appcheck/firebase-appcheck/CHANGELOG.md b/appcheck/firebase-appcheck/CHANGELOG.md
index 19e29699175..88d12d6afc3 100644
--- a/appcheck/firebase-appcheck/CHANGELOG.md
+++ b/appcheck/firebase-appcheck/CHANGELOG.md
@@ -1,141 +1,154 @@
# Unreleased
+# 19.0.1
+
+- [changed] Bumped internal dependencies.
+
+# 19.0.0
+
+- [changed] **Breaking Change**: Updated minSdkVersion to API level 23 or higher.
+- [removed] **Breaking Change**: Stopped releasing the deprecated Kotlin extensions (KTX) module and
+ removed it from the Firebase Android BoM. Instead, use the KTX APIs from the main module. For
+ details, see the
+ [FAQ about this initiative](https://firebase.google.com/docs/android/kotlin-migration).
# 18.0.0
-* [changed] Bump internal dependencies
-* [changed] Internal support for `SafetyNet` has been dropped, as the [SafetyNet Attestation API
-has been deprecated.](https://developer.android.com/privacy-and-security/safetynet/deprecation-timeline#safetynet_attestation_deprecation_timeline)
+- [changed] Bump internal dependencies
+- [changed] Internal support for `SafetyNet` has been dropped, as the
+ [SafetyNet Attestation API has been deprecated.](https://developer.android.com/privacy-and-security/safetynet/deprecation-timeline#safetynet_attestation_deprecation_timeline)
## Kotlin
-The Kotlin extensions library transitively includes the updated
-`firebase-appcheck` library. The Kotlin extensions library has no additional
-updates.
+
+The Kotlin extensions library transitively includes the updated `firebase-appcheck` library. The
+Kotlin extensions library has no additional updates.
# 17.1.2
-* [changed] Bump internal dependencies.
+- [changed] Bump internal dependencies.
## Kotlin
-The Kotlin extensions library transitively includes the updated
-`firebase-appcheck` library. The Kotlin extensions library has no additional
-updates.
+
+The Kotlin extensions library transitively includes the updated `firebase-appcheck` library. The
+Kotlin extensions library has no additional updates.
# 17.1.1
-* [fixed] Fixed a bug causing internal tests to depend directly on `firebase-common`.
-* [fixed] Fixed client-side throttling in Play Integrity flows.
+- [fixed] Fixed a bug causing internal tests to depend directly on `firebase-common`.
+- [fixed] Fixed client-side throttling in Play Integrity flows.
## Kotlin
-The Kotlin extensions library transitively includes the updated
-`firebase-appcheck` library. The Kotlin extensions library has no additional
-updates.
+
+The Kotlin extensions library transitively includes the updated `firebase-appcheck` library. The
+Kotlin extensions library has no additional updates.
# 17.1.0
-* [changed] Added Kotlin extensions (KTX) APIs from `com.google.firebase:firebase-appcheck-ktx`
- to `com.google.firebase:firebase-appcheck` under the `com.google.firebase.appcheck` package.
- For details, see the
+
+- [changed] Added Kotlin extensions (KTX) APIs from `com.google.firebase:firebase-appcheck-ktx` to
+ `com.google.firebase:firebase-appcheck` under the `com.google.firebase.appcheck` package. For
+ details, see the
[FAQ about this initiative](https://firebase.google.com/docs/android/kotlin-migration)
-* [deprecated] All the APIs from `com.google.firebase:firebase-appcheck-ktx` have been added to
- `com.google.firebase:firebase-appcheck` under the `com.google.firebase.appcheck` package,
- and all the Kotlin extensions (KTX) APIs in `com.google.firebase:firebase-appcheck-ktx` are
- now deprecated. As early as April 2024, we'll no longer release KTX modules. For details, see the
+- [deprecated] All the APIs from `com.google.firebase:firebase-appcheck-ktx` have been added to
+ `com.google.firebase:firebase-appcheck` under the `com.google.firebase.appcheck` package, and all
+ the Kotlin extensions (KTX) APIs in `com.google.firebase:firebase-appcheck-ktx` are now
+ deprecated. As early as April 2024, we'll no longer release KTX modules. For details, see the
[FAQ about this initiative](https://firebase.google.com/docs/android/kotlin-migration)
-
## Kotlin
-The Kotlin extensions library transitively includes the updated
-`firebase-appcheck` library. The Kotlin extensions library has no additional
-updates.
+
+The Kotlin extensions library transitively includes the updated `firebase-appcheck` library. The
+Kotlin extensions library has no additional updates.
# 17.0.1
-* [changed] Internal updates to allow Firebase SDKs to obtain limited-use tokens.
+
+- [changed] Internal updates to allow Firebase SDKs to obtain limited-use tokens.
# 17.0.0
-* [feature] Added [`getLimitedUseAppCheckToken()`](/docs/reference/android/com/google/firebase/appcheck/FirebaseAppCheck#getLimitedUseAppCheckToken())
- for obtaining limited-use tokens for protecting non-Firebase backends.
+- [feature] Added
+ [`getLimitedUseAppCheckToken()`]()
+ for obtaining limited-use tokens for protecting non-Firebase backends.
## Kotlin
-The Kotlin extensions library transitively includes the updated
-`firebase-appcheck` library. The Kotlin extensions library has no additional
-updates.
+
+The Kotlin extensions library transitively includes the updated `firebase-appcheck` library. The
+Kotlin extensions library has no additional updates.
# 16.1.2
-* [unchanged] Updated to keep [app_check] SDK versions aligned.
+- [unchanged] Updated to keep [app_check] SDK versions aligned.
## Kotlin
-The Kotlin extensions library transitively includes the updated
-`firebase-appcheck` library. The Kotlin extensions library has no additional
-updates.
+
+The Kotlin extensions library transitively includes the updated `firebase-appcheck` library. The
+Kotlin extensions library has no additional updates.
# 16.1.1
-* [changed] Migrated [app_check] SDKs to use standard Firebase executors.
- (GitHub [#4431](//github.com/firebase/firebase-android-sdk/issues/4431){: .external}
- and
- [#4449](//github.com/firebase/firebase-android-sdk/issues/4449){: .external})
-* [changed] Moved Task continuations off the main thread.
- (GitHub [#4453](//github.com/firebase/firebase-android-sdk/issues/4453){: .external})
+- [changed] Migrated [app_check] SDKs to use standard Firebase executors. (GitHub
+ [#4431](//github.com/firebase/firebase-android-sdk/issues/4431){: .external} and
+ [#4449](//github.com/firebase/firebase-android-sdk/issues/4449){: .external})
+- [changed] Moved Task continuations off the main thread. (GitHub
+ [#4453](//github.com/firebase/firebase-android-sdk/issues/4453){: .external})
## Kotlin
-The Kotlin extensions library transitively includes the updated
-`firebase-appcheck` library. The Kotlin extensions library has no additional
-updates.
+
+The Kotlin extensions library transitively includes the updated `firebase-appcheck` library. The
+Kotlin extensions library has no additional updates.
# 16.1.0
-* [unchanged] Updated to accommodate the release of the updated
- [app_check] Kotlin extensions library.
+- [unchanged] Updated to accommodate the release of the updated [app_check] Kotlin extensions
+ library.
## Kotlin
-The Kotlin extensions library transitively includes the updated
-`firebase-appcheck` library. The Kotlin extensions library has the following
-additional updates:
-
-* [feature] Firebase now supports Kotlin coroutines.
- With this release, we added
- [`kotlinx-coroutines-play-services`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-play-services/){: .external}
- to `firebase-appcheck-ktx` as a transitive dependency, which exposes the
+
+The Kotlin extensions library transitively includes the updated `firebase-appcheck` library. The
+Kotlin extensions library has the following additional updates:
+
+- [feature] Firebase now supports Kotlin coroutines. With this release, we added
+ [`kotlinx-coroutines-play-services`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-play-services/){:
+ .external} to `firebase-appcheck-ktx` as a transitive dependency, which exposes the
`Task.await()` suspend function to convert a
- [`Task`](https://developers.google.com/android/guides/tasks) into a Kotlin
- coroutine.
+ [`Task`](https://developers.google.com/android/guides/tasks) into a Kotlin coroutine.
# 16.0.1
-* [changed] Updated dependency of `play-services-basement` to its latest
- version (v18.1.0).
+
+- [changed] Updated dependency of `play-services-basement` to its latest version (v18.1.0).
# 16.0.0
-* [changed] [app_check] has exited beta and is now generally available for
- use.
-* [feature] Added support for
- [Play Integrity](https://developer.android.com/google/play/integrity) as an
- attestation provider.
+
+- [changed] [app_check] has exited beta and is now generally available for use.
+- [feature] Added support for [Play Integrity](https://developer.android.com/google/play/integrity)
+ as an attestation provider.
# 16.0.0-beta06
-* [fixed] Fixed a bug in the [app_check] token refresh flow when using a
- custom provider.
+
+- [fixed] Fixed a bug in the [app_check] token refresh flow when using a custom provider.
# 16.0.0-beta05
-* [changed] Internal improvements.
+
+- [changed] Internal improvements.
# 16.0.0-beta04
-* [changed] Improved error handling logic by minimizing the amount of requests
- that are unlikely to succeed.
-* [fixed] Fixed heartbeat reporting.
+
+- [changed] Improved error handling logic by minimizing the amount of requests that are unlikely to
+ succeed.
+- [fixed] Fixed heartbeat reporting.
# 16.0.0-beta03
-* [changed] Added `X-Android-Package` and `X-Android-Cert` request headers to
- [app_check] network calls.
+
+- [changed] Added `X-Android-Package` and `X-Android-Cert` request headers to [app_check] network
+ calls.
# 16.0.0-beta02
-* [feature] Added [`getAppCheckToken()`](/docs/reference/android/com/google/firebase/appcheck/FirebaseAppCheck#getAppCheckToken(boolean)),
+
+- [feature] Added
+ [`getAppCheckToken()`](),
[`AppCheckTokenListener`](/docs/reference/android/com/google/firebase/appcheck/FirebaseAppCheck.AppCheckListener),
- and associated setters and removers for developers to request and observe
- changes to the [app_check] token.
+ and associated setters and removers for developers to request and observe changes to the
+ [app_check] token.
# 16.0.0-beta01
-* [feature] Initial beta release of the [app_check] SDK with abuse reduction
- features.
+- [feature] Initial beta release of the [app_check] SDK with abuse reduction features.
diff --git a/appcheck/firebase-appcheck/api.txt b/appcheck/firebase-appcheck/api.txt
index fe214cd0b66..557c950dd0e 100644
--- a/appcheck/firebase-appcheck/api.txt
+++ b/appcheck/firebase-appcheck/api.txt
@@ -41,14 +41,3 @@ package com.google.firebase.appcheck {
}
-package com.google.firebase.appcheck.ktx {
-
- public final class FirebaseAppCheckKt {
- method @Deprecated public static com.google.firebase.appcheck.FirebaseAppCheck appCheck(com.google.firebase.ktx.Firebase, com.google.firebase.FirebaseApp app);
- method @Deprecated public static operator String component1(com.google.firebase.appcheck.AppCheckToken);
- method @Deprecated public static operator long component2(com.google.firebase.appcheck.AppCheckToken);
- method @Deprecated public static com.google.firebase.appcheck.FirebaseAppCheck getAppCheck(com.google.firebase.ktx.Firebase);
- }
-
-}
-
diff --git a/appcheck/firebase-appcheck/firebase-appcheck.gradle b/appcheck/firebase-appcheck/firebase-appcheck.gradle
index 8bb131495f3..c43505f766a 100644
--- a/appcheck/firebase-appcheck/firebase-appcheck.gradle
+++ b/appcheck/firebase-appcheck/firebase-appcheck.gradle
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
plugins {
id 'firebase-library'
id("kotlin-android")
@@ -43,18 +45,19 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
- kotlinOptions { jvmTarget = "1.8" }
testOptions.unitTests.includeAndroidResources = true
}
+kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_1_8 } }
+
dependencies {
javadocClasspath libs.autovalue.annotations
api libs.playservices.tasks
- api 'com.google.firebase:firebase-annotations:16.2.0'
+ api libs.firebase.annotations
api "com.google.firebase:firebase-appcheck-interop:17.1.0"
- api("com.google.firebase:firebase-common:21.0.0")
- api("com.google.firebase:firebase-components:18.0.0")
+ api(libs.firebase.common)
+ api(libs.firebase.components)
implementation libs.androidx.annotation
implementation libs.playservices.base
@@ -70,7 +73,6 @@ dependencies {
testImplementation libs.junit
testImplementation libs.junit
testImplementation libs.mockito.core
- testImplementation libs.mockito.mockito.inline
testImplementation libs.robolectric
androidTestImplementation project(':appcheck:firebase-appcheck')
@@ -85,5 +87,4 @@ dependencies {
androidTestImplementation libs.truth
androidTestImplementation libs.junit
androidTestImplementation libs.mockito.core
- androidTestImplementation libs.mockito.mockito.inline
}
diff --git a/appcheck/firebase-appcheck/gradle.properties b/appcheck/firebase-appcheck/gradle.properties
index 9b7be4891d1..04d7bc444aa 100644
--- a/appcheck/firebase-appcheck/gradle.properties
+++ b/appcheck/firebase-appcheck/gradle.properties
@@ -1,2 +1,2 @@
-version=18.0.1
-latestReleasedVersion=18.0.0
+version=19.0.2
+latestReleasedVersion=19.0.1
diff --git a/appcheck/firebase-appcheck/ktx/api.txt b/appcheck/firebase-appcheck/ktx/api.txt
deleted file mode 100644
index da4f6cc18fe..00000000000
--- a/appcheck/firebase-appcheck/ktx/api.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 3.0
diff --git a/appcheck/firebase-appcheck/ktx/gradle.properties b/appcheck/firebase-appcheck/ktx/gradle.properties
deleted file mode 100644
index 9eff84e6c72..00000000000
--- a/appcheck/firebase-appcheck/ktx/gradle.properties
+++ /dev/null
@@ -1 +0,0 @@
-android.enableUnitTestBinaryResources=true
diff --git a/appcheck/firebase-appcheck/ktx/src/androidTest/AndroidManifest.xml b/appcheck/firebase-appcheck/ktx/src/androidTest/AndroidManifest.xml
deleted file mode 100644
index 1b0d1dff1e4..00000000000
--- a/appcheck/firebase-appcheck/ktx/src/androidTest/AndroidManifest.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/appcheck/firebase-appcheck/ktx/src/androidTest/kotlin/com/google/firebase/appcheck/ktx/FirebaseAppCheckTests.kt b/appcheck/firebase-appcheck/ktx/src/androidTest/kotlin/com/google/firebase/appcheck/ktx/FirebaseAppCheckTests.kt
deleted file mode 100644
index 2989924a2c5..00000000000
--- a/appcheck/firebase-appcheck/ktx/src/androidTest/kotlin/com/google/firebase/appcheck/ktx/FirebaseAppCheckTests.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.appcheck.ktx
-
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
-import com.google.common.truth.Truth.assertThat
-import com.google.firebase.FirebaseApp
-import com.google.firebase.FirebaseOptions
-import com.google.firebase.appcheck.AppCheckToken
-import com.google.firebase.appcheck.FirebaseAppCheck
-import com.google.firebase.ktx.Firebase
-import com.google.firebase.ktx.app
-import com.google.firebase.ktx.initialize
-import com.google.firebase.platforminfo.UserAgentPublisher
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-const val APP_ID = "APP_ID"
-const val API_KEY = "API_KEY"
-
-const val EXISTING_APP = "existing"
-
-@RunWith(AndroidJUnit4ClassRunner::class)
-abstract class BaseTestCase {
- @Before
- fun setUp() {
- Firebase.initialize(
- ApplicationProvider.getApplicationContext(),
- FirebaseOptions.Builder()
- .setApplicationId(APP_ID)
- .setApiKey(API_KEY)
- .setProjectId("123")
- .build()
- )
-
- Firebase.initialize(
- ApplicationProvider.getApplicationContext(),
- FirebaseOptions.Builder()
- .setApplicationId(APP_ID)
- .setApiKey(API_KEY)
- .setProjectId("123")
- .build(),
- EXISTING_APP
- )
- }
-
- @After
- fun cleanUp() {
- FirebaseApp.clearInstancesForTest()
- }
-}
-
-@RunWith(AndroidJUnit4ClassRunner::class)
-class FirebaseAppCheckTests : BaseTestCase() {
- @Test
- fun appCheck_default_callsDefaultGetInstance() {
- assertThat(Firebase.appCheck).isSameInstanceAs(FirebaseAppCheck.getInstance())
- }
-
- @Test
- fun appCheck_with_custom_firebaseapp_calls_GetInstance() {
- val app = Firebase.app(EXISTING_APP)
- assertThat(Firebase.appCheck(app)).isSameInstanceAs(FirebaseAppCheck.getInstance(app))
- }
-
- @Test
- fun appCheckToken_destructuring_declaration_works() {
- val mockAppCheckToken =
- object : AppCheckToken() {
- override fun getToken(): String = "randomToken"
-
- override fun getExpireTimeMillis(): Long = 23121997
- }
-
- val (token, expiration) = mockAppCheckToken
-
- assertThat(token).isEqualTo(mockAppCheckToken.token)
- assertThat(expiration).isEqualTo(mockAppCheckToken.expireTimeMillis)
- }
-}
-
-internal const val LIBRARY_NAME: String = "fire-app-check-ktx"
-
-@RunWith(AndroidJUnit4ClassRunner::class)
-class LibraryVersionTest : BaseTestCase() {
- @Test
- fun libraryRegistrationAtRuntime() {
- val publisher = Firebase.app.get(UserAgentPublisher::class.java)
- assertThat(publisher.userAgent).contains(LIBRARY_NAME)
- }
-}
diff --git a/appcheck/firebase-appcheck/ktx/src/main/AndroidManifest.xml b/appcheck/firebase-appcheck/ktx/src/main/AndroidManifest.xml
deleted file mode 100644
index e2af9507263..00000000000
--- a/appcheck/firebase-appcheck/ktx/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/appcheck/firebase-appcheck/ktx/src/main/kotlin/com/google/firebase/appcheck/ktx/Logging.kt b/appcheck/firebase-appcheck/ktx/src/main/kotlin/com/google/firebase/appcheck/ktx/Logging.kt
deleted file mode 100644
index 56050655f1f..00000000000
--- a/appcheck/firebase-appcheck/ktx/src/main/kotlin/com/google/firebase/appcheck/ktx/Logging.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2023 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.appcheck.ktx
-
-import androidx.annotation.Keep
-import com.google.firebase.appcheck.BuildConfig
-import com.google.firebase.components.Component
-import com.google.firebase.components.ComponentRegistrar
-import com.google.firebase.platforminfo.LibraryVersionComponent
-
-internal const val LIBRARY_NAME: String = "fire-app-check-ktx"
-
-/** @suppress */
-@Keep
-class FirebaseAppcheckLegacyRegistrar : ComponentRegistrar {
- override fun getComponents(): List> {
- return listOf(LibraryVersionComponent.create(LIBRARY_NAME, BuildConfig.VERSION_NAME))
- }
-}
diff --git a/appcheck/firebase-appcheck/src/androidTest/java/com/google/firebase/appcheck/ktx/FirebaseAppCheckTests.kt b/appcheck/firebase-appcheck/src/androidTest/java/com/google/firebase/appcheck/ktx/FirebaseAppCheckTests.kt
deleted file mode 100644
index 969b0230ed5..00000000000
--- a/appcheck/firebase-appcheck/src/androidTest/java/com/google/firebase/appcheck/ktx/FirebaseAppCheckTests.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.appcheck.ktx
-
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
-import com.google.common.truth.Truth.assertThat
-import com.google.firebase.FirebaseApp
-import com.google.firebase.FirebaseOptions
-import com.google.firebase.appcheck.AppCheckToken
-import com.google.firebase.appcheck.FirebaseAppCheck
-import com.google.firebase.ktx.Firebase
-import com.google.firebase.ktx.app
-import com.google.firebase.ktx.initialize
-import com.google.firebase.platforminfo.UserAgentPublisher
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-const val APP_ID = "APP_ID"
-const val API_KEY = "API_KEY"
-
-const val EXISTING_APP = "existing"
-
-@RunWith(AndroidJUnit4ClassRunner::class)
-abstract class BaseTestCase {
- @Before
- fun setUp() {
- Firebase.initialize(
- ApplicationProvider.getApplicationContext(),
- FirebaseOptions.Builder()
- .setApplicationId(APP_ID)
- .setApiKey(API_KEY)
- .setProjectId("123")
- .build()
- )
-
- Firebase.initialize(
- ApplicationProvider.getApplicationContext(),
- FirebaseOptions.Builder()
- .setApplicationId(APP_ID)
- .setApiKey(API_KEY)
- .setProjectId("123")
- .build(),
- EXISTING_APP
- )
- }
-
- @After
- fun cleanUp() {
- FirebaseApp.clearInstancesForTest()
- }
-}
-
-@RunWith(AndroidJUnit4ClassRunner::class)
-class FirebaseAppCheckTests : BaseTestCase() {
- @Test
- fun appCheck_default_callsDefaultGetInstance() {
- assertThat(Firebase.appCheck).isSameInstanceAs(FirebaseAppCheck.getInstance())
- }
-
- @Test
- fun appCheck_with_custom_firebaseapp_calls_GetInstance() {
- val app = Firebase.app(EXISTING_APP)
- assertThat(Firebase.appCheck(app)).isSameInstanceAs(FirebaseAppCheck.getInstance(app))
- }
-
- @Test
- fun appCheckToken_destructuring_declaration_works() {
- val mockAppCheckToken =
- object : AppCheckToken() {
- override fun getToken(): String = "randomToken"
-
- override fun getExpireTimeMillis(): Long = 23121997
- }
-
- val (token, expiration) = mockAppCheckToken
-
- assertThat(token).isEqualTo(mockAppCheckToken.token)
- assertThat(expiration).isEqualTo(mockAppCheckToken.expireTimeMillis)
- }
-}
-
-@RunWith(AndroidJUnit4ClassRunner::class)
-class LibraryVersionTest : BaseTestCase() {
- @Test
- fun libraryRegistrationAtRuntime() {
- val publisher = Firebase.app.get(UserAgentPublisher::class.java)
- }
-}
diff --git a/appcheck/firebase-appcheck/src/main/java/com/google/firebase/appcheck/internal/NetworkClient.java b/appcheck/firebase-appcheck/src/main/java/com/google/firebase/appcheck/internal/NetworkClient.java
index 410f59290cb..fa91e6f8323 100644
--- a/appcheck/firebase-appcheck/src/main/java/com/google/firebase/appcheck/internal/NetworkClient.java
+++ b/appcheck/firebase-appcheck/src/main/java/com/google/firebase/appcheck/internal/NetworkClient.java
@@ -41,6 +41,7 @@
import java.lang.annotation.RetentionPolicy;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import org.json.JSONException;
/**
@@ -57,9 +58,10 @@ public class NetworkClient {
"https://firebaseappcheck.googleapis.com/v1/projects/%s/apps/%s:exchangePlayIntegrityToken?key=%s";
private static final String PLAY_INTEGRITY_CHALLENGE_URL_TEMPLATE =
"https://firebaseappcheck.googleapis.com/v1/projects/%s/apps/%s:generatePlayIntegrityChallenge?key=%s";
+ private static final String RECAPTCHA_ENTERPRISE_URL_TEMPLATE =
+ "https://firebaseappcheck.googleapis.com/v1/projects/%s/apps/%s:exchangeRecaptchaEnterpriseToken?key=%s";
private static final String CONTENT_TYPE = "Content-Type";
private static final String APPLICATION_JSON = "application/json";
- private static final String UTF_8 = "UTF-8";
@VisibleForTesting static final String X_FIREBASE_CLIENT = "X-Firebase-Client";
@VisibleForTesting static final String X_ANDROID_PACKAGE = "X-Android-Package";
@VisibleForTesting static final String X_ANDROID_CERT = "X-Android-Cert";
@@ -71,12 +73,13 @@ public class NetworkClient {
private final Provider heartBeatControllerProvider;
@Retention(RetentionPolicy.SOURCE)
- @IntDef({UNKNOWN, DEBUG, PLAY_INTEGRITY})
+ @IntDef({UNKNOWN, DEBUG, PLAY_INTEGRITY, RECAPTCHA_ENTERPRISE})
public @interface AttestationTokenType {}
public static final int UNKNOWN = 0;
public static final int DEBUG = 2;
public static final int PLAY_INTEGRITY = 3;
+ public static final int RECAPTCHA_ENTERPRISE = 4;
public NetworkClient(@NonNull FirebaseApp firebaseApp) {
this(
@@ -172,7 +175,8 @@ private String makeNetworkRequest(
? urlConnection.getInputStream()
: urlConnection.getErrorStream();
StringBuilder response = new StringBuilder();
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, UTF_8))) {
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
@@ -236,6 +240,8 @@ private static String getUrlTemplate(@AttestationTokenType int tokenType) {
return DEBUG_EXCHANGE_URL_TEMPLATE;
case PLAY_INTEGRITY:
return PLAY_INTEGRITY_EXCHANGE_URL_TEMPLATE;
+ case RECAPTCHA_ENTERPRISE:
+ return RECAPTCHA_ENTERPRISE_URL_TEMPLATE;
default:
throw new IllegalArgumentException("Unknown token type.");
}
@@ -246,7 +252,7 @@ HttpURLConnection createHttpUrlConnection(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
- private static final boolean isResponseSuccess(int responseCode) {
+ private static boolean isResponseSuccess(int responseCode) {
return responseCode >= 200 && responseCode < 300;
}
}
diff --git a/appcheck/firebase-appcheck/src/main/java/com/google/firebase/appcheck/ktx/FirebaseAppCheck.kt b/appcheck/firebase-appcheck/src/main/java/com/google/firebase/appcheck/ktx/FirebaseAppCheck.kt
deleted file mode 100644
index 15e1f5b2189..00000000000
--- a/appcheck/firebase-appcheck/src/main/java/com/google/firebase/appcheck/ktx/FirebaseAppCheck.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.appcheck.ktx
-
-import com.google.firebase.FirebaseApp
-import com.google.firebase.appcheck.AppCheckToken
-import com.google.firebase.appcheck.FirebaseAppCheck
-import com.google.firebase.components.Component
-import com.google.firebase.components.ComponentRegistrar
-import com.google.firebase.ktx.Firebase
-import com.google.firebase.ktx.app
-
-/**
- * Accessing this object for Kotlin apps has changed; see the
- * [migration guide](https://firebase.google.com/docs/android/kotlin-migration).
- *
- * Returns the [FirebaseAppCheck] instance of the default [FirebaseApp].
- * @deprecated **Deprecation Notice:** The Kotlin extensions (KTX) APIs have been added to their
- * respective main modules, and the Kotlin extension (KTX) APIs in
- * `com.google.firebase.firebase-appcheck-ktx` are now deprecated. As early as April 2024, we'll no
- * longer release KTX modules. For details, see the
- * [FAQ about this initiative.](https://firebase.google.com/docs/android/kotlin-migration)
- */
-val Firebase.appCheck: FirebaseAppCheck
- get() = FirebaseAppCheck.getInstance()
-
-/**
- * Accessing this object for Kotlin apps has changed; see the
- * [migration guide](https://firebase.google.com/docs/android/kotlin-migration).
- *
- * Returns the [FirebaseAppCheck] instance of a given [FirebaseApp].
- * @deprecated **Deprecation Notice:** The Kotlin extensions (KTX) APIs have been added to their
- * respective main modules, and the Kotlin extension (KTX) APIs in
- * `com.google.firebase.firebase-appcheck-ktx` are now deprecated. As early as April 2024, we'll no
- * longer release KTX modules. For details, see the
- * [FAQ about this initiative.](https://firebase.google.com/docs/android/kotlin-migration)
- */
-fun Firebase.appCheck(app: FirebaseApp) = FirebaseAppCheck.getInstance(app)
-
-/**
- * Destructuring declaration for [AppCheckToken] to provide token.
- *
- * @return the token of the [AppCheckToken]
- * @deprecated **Deprecation Notice:** The Kotlin extensions (KTX) APIs have been added to their
- * respective main modules, and the Kotlin extension (KTX) APIs in
- * `com.google.firebase.firebase-appcheck-ktx` are now deprecated. As early as April 2024, we'll no
- * longer release KTX modules. For details, see the
- * [FAQ about this initiative.](https://firebase.google.com/docs/android/kotlin-migration)
- */
-@Deprecated(
- "Migrate to use the KTX API from the main module: https://firebase.google.com/docs/android/kotlin-migration.",
- ReplaceWith("")
-)
-operator fun AppCheckToken.component1() = token
-
-/**
- * Destructuring declaration for [AppCheckToken] to provide expireTimeMillis.
- *
- * @return the expireTimeMillis of the [AppCheckToken]
- * @deprecated **Deprecation Notice:** The Kotlin extensions (KTX) APIs have been added to their
- * respective main modules, and the Kotlin extension (KTX) APIs in
- * `com.google.firebase.firebase-appcheck-ktx` are now deprecated. As early as April 2024, we'll no
- * longer release KTX modules. For details, see the
- * [FAQ about this initiative.](https://firebase.google.com/docs/android/kotlin-migration)
- */
-@Deprecated(
- "Migrate to use the KTX API from the main module: https://firebase.google.com/docs/android/kotlin-migration.",
- ReplaceWith("")
-)
-operator fun AppCheckToken.component2() = expireTimeMillis
-
-/**
- * @suppress
- * @deprecated **Deprecation Notice:** The Kotlin extensions (KTX) APIs have been added to their
- * respective main modules, and the Kotlin extension (KTX) APIs in
- * `com.google.firebase.firebase-appcheck-ktx` are now deprecated. As early as April 2024, we'll no
- * longer release KTX modules. For details, see the
- * [FAQ about this initiative.](https://firebase.google.com/docs/android/kotlin-migration)
- */
-@Deprecated(
- "Migrate to use the KTX API from the main module: https://firebase.google.com/docs/android/kotlin-migration.",
- ReplaceWith("")
-)
-class FirebaseAppCheckKtxRegistrar : ComponentRegistrar {
- override fun getComponents(): List> = listOf()
-}
diff --git a/build.gradle.kts b/build.gradle.kts
index a10ac0119ea..dab824bdf4b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -33,7 +33,7 @@ extra["targetSdkVersion"] = 34
extra["compileSdkVersion"] = 34
-extra["minSdkVersion"] = 21
+extra["minSdkVersion"] = 23
firebaseContinuousIntegration {
ignorePaths =
@@ -60,6 +60,11 @@ fun Project.applySpotless() {
target("*.gradle.kts") // default target for kotlinGradle
ktfmt("0.41").googleStyle()
}
+ format("styling") {
+ target("src/**/*.md", "*.md", "docs/**/*.md")
+ targetExclude("**/third_party/**", "src/test/resources/**", "release_report.md")
+ prettier().config(mapOf("printWidth" to 100, "proseWrap" to "always"))
+ }
}
}
diff --git a/ci/README.md b/ci/README.md
index a4b4eb4799b..1948ff3b7e3 100644
--- a/ci/README.md
+++ b/ci/README.md
@@ -14,6 +14,7 @@ This directory contains tooling used to run Continuous Integration tasks.
source ~/.venvs/fireci/bin/activate
```
- At the root of the firebase sdk repo, run
+
```
pip3 install -e ./ci/fireci/
```
@@ -25,8 +26,8 @@ This directory contains tooling used to run Continuous Integration tasks.
## Uninstall
-If you run into any issues and need to re-install, or uninstall the package, you can do so
-by uninstalling the `fireci` package.
+If you run into any issues and need to re-install, or uninstall the package, you can do so by
+uninstalling the `fireci` package.
```shell
pip3 uninstall fireci -y
@@ -34,8 +35,8 @@ pip3 uninstall fireci -y
## Debug
-By default, if you're not running `fireci` within the context of CI, the minimum log level is set
-to `INFO`.
+By default, if you're not running `fireci` within the context of CI, the minimum log level is set to
+`INFO`.
To manually set the level to `DEBUG`, you can use the `--debug` flag.
@@ -43,5 +44,4 @@ To manually set the level to `DEBUG`, you can use the `--debug` flag.
fireci --debug clean
```
-> ![NOTE]
-> The `--debug` flag must come _before_ the command.
+> ![NOTE] The `--debug` flag must come _before_ the command.
diff --git a/ci/danger/Gemfile b/ci/danger/Gemfile
index 6c0467dd241..b6c6c859b2f 100644
--- a/ci/danger/Gemfile
+++ b/ci/danger/Gemfile
@@ -2,4 +2,4 @@
# commit Gemfile and Gemfile.lock.
source 'https://rubygems.org'
-gem 'danger', '8.4.5'
+gem 'danger', '9.5.3'
diff --git a/ci/danger/Gemfile.lock b/ci/danger/Gemfile.lock
index 783002346b0..42d08f0a17a 100644
--- a/ci/danger/Gemfile.lock
+++ b/ci/danger/Gemfile.lock
@@ -1,86 +1,102 @@
GEM
remote: https://rubygems.org/
specs:
- addressable (2.8.1)
- public_suffix (>= 2.0.2, < 6.0)
+ activesupport (8.0.3)
+ base64
+ benchmark (>= 0.3)
+ bigdecimal
+ concurrent-ruby (~> 1.0, >= 1.3.1)
+ connection_pool (>= 2.2.5)
+ drb
+ i18n (>= 1.6, < 2)
+ logger (>= 1.4.2)
+ minitest (>= 5.1)
+ securerandom (>= 0.3)
+ tzinfo (~> 2.0, >= 2.0.5)
+ uri (>= 0.13.1)
+ addressable (2.8.7)
+ public_suffix (>= 2.0.2, < 7.0)
+ base64 (0.3.0)
+ benchmark (0.4.1)
+ bigdecimal (3.3.0)
claide (1.1.0)
claide-plugins (0.9.2)
cork
nap
open4 (~> 1.3)
colored2 (3.1.2)
+ concurrent-ruby (1.3.5)
+ connection_pool (2.5.4)
cork (0.3.0)
colored2 (~> 3.1)
- danger (8.4.5)
+ danger (9.5.3)
+ base64 (~> 0.2)
claide (~> 1.0)
claide-plugins (>= 0.9.2)
- colored2 (~> 3.1)
+ colored2 (>= 3.1, < 5)
cork (~> 0.1)
- faraday (>= 0.9.0, < 2.0)
+ faraday (>= 0.9.0, < 3.0)
faraday-http-cache (~> 2.0)
- git (~> 1.7)
- kramdown (~> 2.3)
+ git (>= 1.13, < 3.0)
+ kramdown (>= 2.5.1, < 3.0)
kramdown-parser-gfm (~> 1.0)
- no_proxy_fix
- octokit (~> 4.7)
- terminal-table (>= 1, < 4)
- faraday (1.10.1)
- faraday-em_http (~> 1.0)
- faraday-em_synchrony (~> 1.0)
- faraday-excon (~> 1.1)
- faraday-httpclient (~> 1.0)
- faraday-multipart (~> 1.0)
- faraday-net_http (~> 1.0)
- faraday-net_http_persistent (~> 1.0)
- faraday-patron (~> 1.0)
- faraday-rack (~> 1.0)
- faraday-retry (~> 1.0)
- ruby2_keywords (>= 0.0.4)
- faraday-em_http (1.0.0)
- faraday-em_synchrony (1.0.0)
- faraday-excon (1.1.0)
- faraday-http-cache (2.4.1)
+ octokit (>= 4.0)
+ pstore (~> 0.1)
+ terminal-table (>= 1, < 5)
+ drb (2.2.3)
+ faraday (2.14.0)
+ faraday-net_http (>= 2.0, < 3.5)
+ json
+ logger
+ faraday-http-cache (2.5.1)
faraday (>= 0.8)
- faraday-httpclient (1.0.1)
- faraday-multipart (1.0.4)
- multipart-post (~> 2)
- faraday-net_http (1.0.1)
- faraday-net_http_persistent (1.2.0)
- faraday-patron (1.0.0)
- faraday-rack (1.0.0)
- faraday-retry (1.0.3)
- git (1.13.1)
+ faraday-net_http (3.4.1)
+ net-http (>= 0.5.0)
+ git (2.3.3)
+ activesupport (>= 5.0)
addressable (~> 2.8)
+ process_executer (~> 1.1)
rchardet (~> 1.8)
- kramdown (2.4.0)
- rexml
+ i18n (1.14.7)
+ concurrent-ruby (~> 1.0)
+ json (2.15.1)
+ kramdown (2.5.1)
+ rexml (>= 3.3.9)
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
- multipart-post (2.2.3)
+ logger (1.7.0)
+ minitest (5.25.5)
nap (1.1.0)
- no_proxy_fix (0.1.2)
- octokit (4.25.1)
+ net-http (0.6.0)
+ uri
+ octokit (10.0.0)
faraday (>= 1, < 3)
sawyer (~> 0.9)
open4 (1.3.4)
- public_suffix (5.0.1)
- rchardet (1.8.0)
- rexml (3.2.8)
- strscan (>= 3.0.9)
- ruby2_keywords (0.0.5)
+ process_executer (1.3.0)
+ pstore (0.2.0)
+ public_suffix (6.0.2)
+ rchardet (1.10.0)
+ rexml (3.4.4)
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
- strscan (3.1.0)
- terminal-table (3.0.2)
- unicode-display_width (>= 1.1.1, < 3)
- unicode-display_width (2.2.0)
+ securerandom (0.4.1)
+ terminal-table (4.0.0)
+ unicode-display_width (>= 1.1.1, < 4)
+ tzinfo (2.0.6)
+ concurrent-ruby (~> 1.0)
+ unicode-display_width (3.2.0)
+ unicode-emoji (~> 4.1)
+ unicode-emoji (4.1.0)
+ uri (1.0.4)
PLATFORMS
+ arm64-darwin-24
ruby
DEPENDENCIES
- danger (= 8.4.5)
+ danger (= 9.5.3)
BUNDLED WITH
- 1.17.2
+ 2.7.2
diff --git a/ci/fireci/fireciplugins/binary_size.py b/ci/fireci/fireciplugins/binary_size.py
index 6f966a3bfd7..ddb97c3f939 100644
--- a/ci/fireci/fireciplugins/binary_size.py
+++ b/ci/fireci/fireciplugins/binary_size.py
@@ -55,6 +55,13 @@ def binary_size(pull_request, log, metrics_service_url, access_token):
affected_artifacts, all_artifacts = _parse_artifacts()
artifacts = affected_artifacts if pull_request else all_artifacts
sdks = ','.join(artifacts)
+ if not sdks:
+ _logger.info(
+ "No sdks found whose binary size to measure ("
+ "pull_request=%s affected_artifacts=%s all_artifacts=%s)",
+ pull_request, affected_artifacts, all_artifacts
+ )
+ return
workdir = 'health-metrics/apk-size'
process = gradle.run('assemble', '--continue', gradle.P('sdks', sdks), workdir=workdir, check=False)
diff --git a/ci/fireci/fireciplugins/clean.py b/ci/fireci/fireciplugins/clean.py
index 9f2cd6af9a5..2238cccab35 100644
--- a/ci/fireci/fireciplugins/clean.py
+++ b/ci/fireci/fireciplugins/clean.py
@@ -40,7 +40,6 @@
\b
$ fireci clean firebase-common
- $ fireci clean firebase-common firebase-vertexai
Clean all projects:
diff --git a/ci/fireci/fireciplugins/macrobenchmark/run/runner.py b/ci/fireci/fireciplugins/macrobenchmark/run/runner.py
index a997e66cb49..8cff62ee954 100644
--- a/ci/fireci/fireciplugins/macrobenchmark/run/runner.py
+++ b/ci/fireci/fireciplugins/macrobenchmark/run/runner.py
@@ -119,7 +119,6 @@ def _process_changed_modules(path: Path) -> List[str]:
":firebase-components": ["Firebase", "ComponentDiscovery", "Runtime"],
":firebase-database": ["fire-rtdb"],
":firebase-datatransport": ["fire-transport"],
- ":firebase-dynamic-links": ["fire-dl"],
":firebase-crashlytics": ["fire-cls"],
":firebase-crashlytics-ndk": ["fire-cls"],
":firebase-firestore": ["fire-fst"],
diff --git a/ci/fireci/pyproject.toml b/ci/fireci/pyproject.toml
index 8fd3b462353..41f77c976fb 100644
--- a/ci/fireci/pyproject.toml
+++ b/ci/fireci/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
name = "fireci"
version = "0.1"
dependencies = [
- "protobuf==3.20.3",
+ "protobuf==4.25.8",
"click==8.1.7",
"google-cloud-storage==2.18.2",
"mypy==1.6.0",
@@ -14,7 +14,7 @@ dependencies = [
"pandas==1.5.3",
"PyGithub==1.58.2",
"pystache==0.6.0",
- "requests==2.32.2",
+ "requests==2.32.4",
"seaborn==0.12.2",
"PyYAML==6.0.1",
"termcolor==2.4.0",
diff --git a/ci/workflow_summary/README.md b/ci/workflow_summary/README.md
index c60558db9d2..d6318224ea2 100644
--- a/ci/workflow_summary/README.md
+++ b/ci/workflow_summary/README.md
@@ -1,176 +1,190 @@
# `workflow_information.py` Script
## Prerequisites
-- [Python](https://www.python.org/) and required packages.
- ```
- pip install requests argparse
- ```
+
+- [Python](https://www.python.org/) and required packages.
+ ```
+ pip install requests argparse
+ ```
## Usage
-- Collect last `90` days' `Postsubmit` `ci_workflow.yml` workflow runs:
- ```
- python workflow_information.py --token ${your_github_toke} --branch master --event push --d 90
- ```
-- Collect last `30` days' `Presubmit` `ci_workflow.yml` workflow runs:
- ```
- python workflow_information.py --token ${your_github_toke} --event pull_request --d 30
- ```
+- Collect last `90` days' `Postsubmit` `ci_workflow.yml` workflow runs:
+
+ ```
+ python workflow_information.py --token ${your_github_toke} --branch master --event push --d 90
+ ```
+
+- Collect last `30` days' `Presubmit` `ci_workflow.yml` workflow runs:
-- Please refer to `Inputs` section for more use cases, and `Outputs` section for the workflow summary report format.
+ ```
+ python workflow_information.py --token ${your_github_toke} --event pull_request --d 30
+ ```
+
+- Please refer to `Inputs` section for more use cases, and `Outputs` section for the workflow
+ summary report format.
## Inputs
-- `-o, --repo_owner`: **[Required]** GitHub repo owner, default value is `firebase`.
-- `-n, --repo_name`: **[Required]** GitHub repo name, default value is `firebase-android-sdk`.
+- `-o, --repo_owner`: **[Required]** GitHub repo owner, default value is `firebase`.
-- `-t, --token`: **[Required]** GitHub access token. See [Creating a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token).
+- `-n, --repo_name`: **[Required]** GitHub repo name, default value is `firebase-android-sdk`.
-- `-w, --workflow_name`: **[Required]** Workflow filename, default value is `ci_tests.yml`.
+- `-t, --token`: **[Required]** GitHub access token. See
+ [Creating a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token).
-- `-d, --days`: Filter workflows that running in past -d days, default value is `90`. See [retention period for GitHub Actions artifacts and logs](https://docs.github.com/en/organizations/managing-organization-settings/configuring-the-retention-period-for-github-actions-artifacts-and-logs-in-your-organization).
+- `-w, --workflow_name`: **[Required]** Workflow filename, default value is `ci_tests.yml`.
-- `-b, --branch`: Filter branch name that workflows run against.
+- `-d, --days`: Filter workflows that running in past -d days, default value is `90`. See
+ [retention period for GitHub Actions artifacts and logs](https://docs.github.com/en/organizations/managing-organization-settings/configuring-the-retention-period-for-github-actions-artifacts-and-logs-in-your-organization).
-- `-a, --actor`: Filter the actor who triggers the workflow runs.
+- `-b, --branch`: Filter branch name that workflows run against.
-- `-e, --event`: Filter workflows trigger event, could be one of the following values `['push', 'pull_request', 'issue']`.
+- `-a, --actor`: Filter the actor who triggers the workflow runs.
-- `-j, --jobs`: Filter workflows jobs, default is `all` (including rerun jobs), could be one of the following values `['latest', 'all']`.
+- `-e, --event`: Filter workflows trigger event, could be one of the following values
+ `['push', 'pull_request', 'issue']`.
-- `-f, --folder`: Workflow and job information will be store here, default value is the current datatime.
+- `-j, --jobs`: Filter workflows jobs, default is `all` (including rerun jobs), could be one of the
+ following values `['latest', 'all']`.
+- `-f, --folder`: Workflow and job information will be store here, default value is the current
+ datatime.
## Outputs
-- `workflow_summary_report.txt`: a general report contains workflow pass/failure count, running time, etc.
-
- ```
- 2023-03-03 01:37:07.114500
- Namespace(actor=None, branch=None, days=30, event='pull_request', folder='presubmit_30', jobs='all', repo_name='firebase-android-sdk', repo_owner='firebase', token=${your_github_token}, workflow_name='ci_tests.yml')
-
- Workflow 'ci_tests.yml' Report:
- Workflow Failure Rate:64.77%
- Workflow Total Count: 193 (success: 68, failure: 125)
-
- Workflow Runtime Report:
- 161 workflow runs finished without rerun, the average running time: 0:27:24.745342
- Including:
- 56 passed workflow runs, with average running time: 0:17:29.214286
- 105 failed workflow runs, with average running time: 0:32:42.361905
-
- 32 runs finished with rerun, the average running time: 1 day, 3:57:53.937500
- The running time for each workflow reruns are:
- ['1 day, 2:24:32', '3:35:54', '3:19:14', '4 days, 6:10:50', '15:33:39', '1:57:21', '1:13:12', '1:55:18', '12 days, 21:51:29', '0:48:48', '0:45:28', '1:40:21', '2 days, 1:46:35', '19:47:16', '0:45:49', '2:22:36', '0:25:22', '0:55:30', '1:40:32', '1:10:05', '20:08:38', '0:31:03', '5 days, 9:19:25', '5:10:44', '1:20:57', '0:28:47', '1:52:44', '20:19:17', '0:35:15', '21:31:07', '3 days, 1:06:44', '3 days, 2:18:14']
-
- Job Failure Report:
- Unit Tests (:firebase-storage):
- Failure Rate:54.61%
- Total Count: 152 (success: 69, failure: 83)
- Unit Tests (:firebase-messaging):
- Failure Rate:35.37%
- Total Count: 147 (success: 95, failure: 52)
- ```
-
-
-- Intermediate file `workflow_summary.json`: contains all the workflow runs and job information attached to each workflow.
-
- ```
- {
- 'workflow_name':'ci_tests.yml',
- 'total_count':81,
- 'success_count':32,
- 'failure_count':49,
- 'created':'>2022-11-30T23:15:04Z',
- 'workflow_runs':[
+- `workflow_summary_report.txt`: a general report contains workflow pass/failure count, running
+ time, etc.
+
+ ```
+ 2023-03-03 01:37:07.114500
+ Namespace(actor=None, branch=None, days=30, event='pull_request', folder='presubmit_30', jobs='all', repo_name='firebase-android-sdk', repo_owner='firebase', token=${your_github_token}, workflow_name='ci_tests.yml')
+
+ Workflow 'ci_tests.yml' Report:
+ Workflow Failure Rate:64.77%
+ Workflow Total Count: 193 (success: 68, failure: 125)
+
+ Workflow Runtime Report:
+ 161 workflow runs finished without rerun, the average running time: 0:27:24.745342
+ Including:
+ 56 passed workflow runs, with average running time: 0:17:29.214286
+ 105 failed workflow runs, with average running time: 0:32:42.361905
+
+ 32 runs finished with rerun, the average running time: 1 day, 3:57:53.937500
+ The running time for each workflow reruns are:
+ ['1 day, 2:24:32', '3:35:54', '3:19:14', '4 days, 6:10:50', '15:33:39', '1:57:21', '1:13:12', '1:55:18', '12 days, 21:51:29', '0:48:48', '0:45:28', '1:40:21', '2 days, 1:46:35', '19:47:16', '0:45:49', '2:22:36', '0:25:22', '0:55:30', '1:40:32', '1:10:05', '20:08:38', '0:31:03', '5 days, 9:19:25', '5:10:44', '1:20:57', '0:28:47', '1:52:44', '20:19:17', '0:35:15', '21:31:07', '3 days, 1:06:44', '3 days, 2:18:14']
+
+ Job Failure Report:
+ Unit Tests (:firebase-storage):
+ Failure Rate:54.61%
+ Total Count: 152 (success: 69, failure: 83)
+ Unit Tests (:firebase-messaging):
+ Failure Rate:35.37%
+ Total Count: 147 (success: 95, failure: 52)
+ ```
+
+- Intermediate file `workflow_summary.json`: contains all the workflow runs and job information
+ attached to each workflow.
+
+ ```
+ {
+ 'workflow_name':'ci_tests.yml',
+ 'total_count':81,
+ 'success_count':32,
+ 'failure_count':49,
+ 'created':'>2022-11-30T23:15:04Z',
+ 'workflow_runs':[
+ {
+ 'workflow_id':4296343867,
+ 'conclusion':'failure',
+ 'head_branch':'master',
+ 'actor':'vkryachko',
+ 'created_at':'2023-02-28T18:47:40Z',
+ 'updated_at':'2023-02-28T19:20:16Z',
+ 'run_started_at':'2023-02-28T18:47:40Z',
+ 'run_attempt':1,
+ 'html_url':'https://github.com/firebase/firebase-android-sdk/actions/runs/4296343867',
+ 'jobs_url':'https://api.github.com/repos/firebase/firebase-android-sdk/actions/runs/4296343867/jobs',
+ 'jobs':{
+ 'total_count':95,
+ 'success_count':92,
+ 'failure_count':3,
+ 'job_runs':[
+ {
+ 'job_id':11664775180,
+ 'job_name':'Determine changed modules',
+ 'conclusion':'success',
+ 'created_at':'2023-02-28T18:47:42Z',
+ 'started_at':'2023-02-28T18:47:50Z',
+ 'completed_at':'2023-02-28T18:50:11Z',
+ 'run_attempt': 1,
+ 'html_url':'https://github.com/firebase/firebase-android-sdk/actions/runs/4296343867/jobs/7487936863',
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ```
+
+- Intermediate file `job_summary.json`: contains all the job runs organized by job name.
+ ```
+ {
+ 'Unit Test Results':{ # job name
+ 'total_count':17,
+ 'success_count':7,
+ 'failure_count':10,
+ 'failure_jobs':[ # data structure is the same as same as workflow_summary['workflow_runs']['job_runs']
{
- 'workflow_id':4296343867,
+ 'job_id':11372664143,
+ 'job_name':'Unit Test Results',
'conclusion':'failure',
- 'head_branch':'master',
- 'actor':'vkryachko',
- 'created_at':'2023-02-28T18:47:40Z',
- 'updated_at':'2023-02-28T19:20:16Z',
- 'run_started_at':'2023-02-28T18:47:40Z',
- 'run_attempt':1,
- 'html_url':'https://github.com/firebase/firebase-android-sdk/actions/runs/4296343867',
- 'jobs_url':'https://api.github.com/repos/firebase/firebase-android-sdk/actions/runs/4296343867/jobs',
- 'jobs':{
- 'total_count':95,
- 'success_count':92,
- 'failure_count':3,
- 'job_runs':[
- {
- 'job_id':11664775180,
- 'job_name':'Determine changed modules',
- 'conclusion':'success',
- 'created_at':'2023-02-28T18:47:42Z',
- 'started_at':'2023-02-28T18:47:50Z',
- 'completed_at':'2023-02-28T18:50:11Z',
- 'run_attempt': 1,
- 'html_url':'https://github.com/firebase/firebase-android-sdk/actions/runs/4296343867/jobs/7487936863',
- }
- ]
- }
+ 'created_at':'2023-02-15T22:02:06Z',
+ 'started_at':'2023-02-15T22:02:06Z',
+ 'completed_at':'2023-02-15T22:02:06Z',
+ 'run_attempt': 1,
+ 'html_url':'https://github.com/firebase/firebase-android-sdk/runs/11372664143',
}
]
}
- ```
-
-- Intermediate file `job_summary.json`: contains all the job runs organized by job name.
- ```
- {
- 'Unit Test Results':{ # job name
- 'total_count':17,
- 'success_count':7,
- 'failure_count':10,
- 'failure_jobs':[ # data structure is the same as same as workflow_summary['workflow_runs']['job_runs']
- {
- 'job_id':11372664143,
- 'job_name':'Unit Test Results',
- 'conclusion':'failure',
- 'created_at':'2023-02-15T22:02:06Z',
- 'started_at':'2023-02-15T22:02:06Z',
- 'completed_at':'2023-02-15T22:02:06Z',
- 'run_attempt': 1,
- 'html_url':'https://github.com/firebase/firebase-android-sdk/runs/11372664143',
- }
- ]
- }
- }
- ```
-
+ }
+ ```
# `collect_ci_test_logs.py` Script
## Usage
-- Collect `ci_test.yml` job failure logs from `workflow_information.py` script's intermediate file:
- ```
- python collect_ci_test_logs.py --token ${github_toke} --folder ${folder}
- ```
+
+- Collect `ci_test.yml` job failure logs from `workflow_information.py` script's intermediate file:
+ ```
+ python collect_ci_test_logs.py --token ${github_toke} --folder ${folder}
+ ```
## Inputs
-- `-t, --token`: **[Required]** GitHub access token. See [Creating a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token).
+- `-t, --token`: **[Required]** GitHub access token. See
+ [Creating a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token).
-- `-f, --folder`: **[Required]** Folder that store intermediate files generated by `workflow_information.py`. `ci_workflow.yml` job failure logs will also be stored here.
+- `-f, --folder`: **[Required]** Folder that store intermediate files generated by
+ `workflow_information.py`. `ci_workflow.yml` job failure logs will also be stored here.
## Outputs
-- `${job name}.log`: contains job failure rate, list all failed job links and failure logs.
- ```
- Unit Tests (:firebase-storage):
- Failure rate:40.00%
- Total count: 20 (success: 12, failure: 8)
- Failed jobs:
-
- https://github.com/firebase/firebase-android-sdk/actions/runs/4296343867/jobs/7487989874
- firebase-storage:testDebugUnitTest
- Task :firebase-storage:testDebugUnitTest
- 2023-02-28T18:54:38.1333725Z
- 2023-02-28T18:54:38.1334278Z com.google.firebase.storage.DownloadTest > streamDownloadWithResumeAndCancel FAILED
- 2023-02-28T18:54:38.1334918Z org.junit.ComparisonFailure at DownloadTest.java:190
- 2023-02-28T18:57:20.3329130Z
- 2023-02-28T18:57:20.3330165Z 112 tests completed, 1 failed
- 2023-02-28T18:57:20.5329189Z
- 2023-02-28T18:57:20.5330505Z > Task :firebase-storage:testDebugUnitTest FAILED
- ```
+- `${job name}.log`: contains job failure rate, list all failed job links and failure logs.
+
+ ```
+ Unit Tests (:firebase-storage):
+ Failure rate:40.00%
+ Total count: 20 (success: 12, failure: 8)
+ Failed jobs:
+
+ https://github.com/firebase/firebase-android-sdk/actions/runs/4296343867/jobs/7487989874
+ firebase-storage:testDebugUnitTest
+ Task :firebase-storage:testDebugUnitTest
+ 2023-02-28T18:54:38.1333725Z
+ 2023-02-28T18:54:38.1334278Z com.google.firebase.storage.DownloadTest > streamDownloadWithResumeAndCancel FAILED
+ 2023-02-28T18:54:38.1334918Z org.junit.ComparisonFailure at DownloadTest.java:190
+ 2023-02-28T18:57:20.3329130Z
+ 2023-02-28T18:57:20.3330165Z 112 tests completed, 1 failed
+ 2023-02-28T18:57:20.5329189Z
+ 2023-02-28T18:57:20.5330505Z > Task :firebase-storage:testDebugUnitTest FAILED
+ ```
diff --git a/contributor-docs/README.md b/contributor-docs/README.md
index c7b37f2b6f3..414b545bf72 100644
--- a/contributor-docs/README.md
+++ b/contributor-docs/README.md
@@ -5,8 +5,9 @@ permalink: /
# Contributor documentation
-This site is a collection of docs and best practices for contributors to Firebase Android SDKs.
-It describes how Firebase works on Android and provides guidance on how to build/maintain a Firebase SDK.
+This site is a collection of docs and best practices for contributors to Firebase Android SDKs. It
+describes how Firebase works on Android and provides guidance on how to build/maintain a Firebase
+SDK.
## New to Firebase?
diff --git a/contributor-docs/best_practices/dependency_injection.md b/contributor-docs/best_practices/dependency_injection.md
index 3b5de828998..1b900899bdb 100644
--- a/contributor-docs/best_practices/dependency_injection.md
+++ b/contributor-docs/best_practices/dependency_injection.md
@@ -5,38 +5,43 @@ parent: Best Practices
# Dependency Injection
While [Firebase Components]({{ site.baseurl }}{% link components/components.md %}) provides basic
-Dependency Injection capabilities for interop between Firebase SDKs, it's not ideal as a general purpose
-DI framework for a few reasons, to name some:
+Dependency Injection capabilities for interop between Firebase SDKs, it's not ideal as a general
+purpose DI framework for a few reasons, to name some:
-* It's verbose, i.e. requires manually specifying dependencies and constructing instances of components in Component
- definitions.
-* It has a runtime cost, i.e. initialization time is linear in the number of Components present in the graph
+- It's verbose, i.e. requires manually specifying dependencies and constructing instances of
+ components in Component definitions.
+- It has a runtime cost, i.e. initialization time is linear in the number of Components present in
+ the graph
-As a result using [Firebase Components]({{ site.baseurl }}{% link components/components.md %}) is appropriate only
-for inter-SDK injection and scoping instances per `FirebaseApp`.
+As a result using [Firebase Components]({{ site.baseurl }}{% link components/components.md %}) is
+appropriate only for inter-SDK injection and scoping instances per `FirebaseApp`.
-On the other hand, manually instantiating SDKs is often tedious, errorprone, and leads to code smells
-that make code less testable and couples it to the implementation rather than the interface. For more context see
-[Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) and [Motivation](https://github.com/google/guice/wiki/Motivation).
+On the other hand, manually instantiating SDKs is often tedious, errorprone, and leads to code
+smells that make code less testable and couples it to the implementation rather than the interface.
+For more context see [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) and
+[Motivation](https://github.com/google/guice/wiki/Motivation).
-{: .important }
-It's recommended to use [Dagger](https://dagger.dev) for internal dependency injection within the SDKs and
-[Components]({{ site.baseurl }}{% link components/components.md %}) to inject inter-sdk dependencies that are available only at
-runtime into the [Dagger Graph](https://dagger.dev/dev-guide/#building-the-graph) via
-[builder setters](https://dagger.dev/dev-guide/#binding-instances) or [factory arguments](https://dagger.dev/api/latest/dagger/Component.Factory.html).
+{: .important } It's recommended to use [Dagger](https://dagger.dev) for internal dependency
+injection within the SDKs and [Components]({{ site.baseurl }}{% link components/components.md %}) to
+inject inter-sdk dependencies that are available only at runtime into the
+[Dagger Graph](https://dagger.dev/dev-guide/#building-the-graph) via
+[builder setters](https://dagger.dev/dev-guide/#binding-instances) or
+[factory arguments](https://dagger.dev/api/latest/dagger/Component.Factory.html).
-See: [Dagger docs](https://dagger.dev)
-See: [Dagger tutorial](https://dagger.dev/tutorial/)
+See: [Dagger docs](https://dagger.dev) See: [Dagger tutorial](https://dagger.dev/tutorial/)
-{: .highlight }
-While Hilt is the recommended way to use dagger in Android applications, it's not suitable for SDK/library development.
+{: .highlight } While Hilt is the recommended way to use dagger in Android applications, it's not
+suitable for SDK/library development.
## How to get started
-Since [Dagger](https://dagger.dev) does not strictly follow semver and requires the dagger-compiler version to match the dagger library version,
-it's not safe to depend on it via a pom level dependency, see [This comment](https://github.com/firebase/firebase-android-sdk/issues/1677#issuecomment-645669608) for context. For this reason in Firebase SDKs we "vendor/repackage" Dagger into the SDK itself under
-`com.google.firebase.{sdkname}.dagger`. While it incurs in a size increase, it's usually on the order of a couple of KB and is considered
-negligible.
+Since [Dagger](https://dagger.dev) does not strictly follow semver and requires the dagger-compiler
+version to match the dagger library version, it's not safe to depend on it via a pom level
+dependency, see
+[This comment](https://github.com/firebase/firebase-android-sdk/issues/1677#issuecomment-645669608)
+for context. For this reason in Firebase SDKs we "vendor/repackage" Dagger into the SDK itself under
+`com.google.firebase.{sdkname}.dagger`. While it incurs in a size increase, it's usually on the
+order of a couple of KB and is considered negligible.
To use Dagger in your SDK use the following in your Gradle build file:
@@ -56,10 +61,12 @@ dependencies {
## General Dagger setup
-As mentioned in [Firebase Components]({{ site.baseurl }}{% link components/components.md %}), all components are scoped per `FirebaseApp`
-meaning there is a single instance of the component within a given `FirebaseApp`.
+As mentioned in [Firebase Components]({{ site.baseurl }}{% link components/components.md %}), all
+components are scoped per `FirebaseApp` meaning there is a single instance of the component within a
+given `FirebaseApp`.
-This makes it a natural fit to get all inter-sdk dependencies and instatiate the Dagger component inside the `ComponentRegistrar`.
+This makes it a natural fit to get all inter-sdk dependencies and instatiate the Dagger component
+inside the `ComponentRegistrar`.
```kotlin
class MyRegistrar : ComponentRegistrar {
@@ -122,15 +129,17 @@ class MySdkInteropAdapter @Inject constructor(private val interop: com.google.fi
## Scope
-Unlike Component, Dagger does not use singleton scope by default and instead injects a new instance of a type at each injection point,
-in the example above we want `MySdk` and `MySdkInteropAdapter` to be singletons so they are are annotated with `@Singleton`.
+Unlike Component, Dagger does not use singleton scope by default and instead injects a new instance
+of a type at each injection point, in the example above we want `MySdk` and `MySdkInteropAdapter` to
+be singletons so they are are annotated with `@Singleton`.
-See [Scoped bindings](https://dagger.dev/dev-guide/#singletons-and-scoped-bindings) for more details.
+See [Scoped bindings](https://dagger.dev/dev-guide/#singletons-and-scoped-bindings) for more
+details.
### Support multiple instances of the SDK per `FirebaseApp`(multi-resource)
-As mentioned in [Firebase Components]({{ site.baseurl }}{% link components/components.md %}), some SDKs support multi-resource mode,
-which effectively means that there are 2 scopes at play:
+As mentioned in [Firebase Components]({{ site.baseurl }}{% link components/components.md %}), some
+SDKs support multi-resource mode, which effectively means that there are 2 scopes at play:
1. `@Singleton` scope that the main `MultiResourceComponent` has.
2. Each instance of the sdk will have its own scope.
@@ -143,7 +152,7 @@ flowchart LR
direction BT
subgraph GlobalComponents[Outside of SDK]
direction LR
-
+
FirebaseOptions
SomeInterop
Executor["@Background Executor"]
@@ -155,7 +164,7 @@ flowchart LR
SomeImpl -.-> SomeInterop
SomeImpl -.-> Executor
end
-
+
subgraph Default["@DbScope SDK(default)"]
MainClassDefault[FirebaseDatabase] --> SomeImpl
SomeOtherImplDefault[SomeOtherImpl] -.-> FirebaseOptions
@@ -169,7 +178,7 @@ flowchart LR
end
end
end
-
+
classDef green fill:#4db6ac
classDef blue fill:#1a73e8
class GlobalComponents green
@@ -178,9 +187,11 @@ flowchart LR
class MyDbName blue
```
-As you can see above, `DatabaseMultiDb` and `SomeImpl` are singletons, while `FirebaseDatabase` and `SomeOtherImpl` are scoped per `database name`.
+As you can see above, `DatabaseMultiDb` and `SomeImpl` are singletons, while `FirebaseDatabase` and
+`SomeOtherImpl` are scoped per `database name`.
-It can be easily achieved with the help of [Dagger's subcomponents](https://dagger.dev/dev-guide/subcomponents).
+It can be easily achieved with the help of
+[Dagger's subcomponents](https://dagger.dev/dev-guide/subcomponents).
For example:
@@ -235,7 +246,7 @@ Implementing `DatabaseMultiDb`:
@Singleton
class DatabaseMultiDb @Inject constructor(private val factory: DbInstanceComponent.Factory) {
private val instances = mutableMapOf()
-
+
@Synchronized
fun get(dbName: String) : FirebaseDatabase {
if (!instances.containsKey(dbName)) {
diff --git a/contributor-docs/components/components.md b/contributor-docs/components/components.md
index de624274aab..816d71c7842 100644
--- a/contributor-docs/components/components.md
+++ b/contributor-docs/components/components.md
@@ -5,21 +5,22 @@ nav_order: 4
---
# Firebase Components
+
{: .no_toc}
-1. TOC
-{:toc}
+1. TOC {:toc}
-Firebase is known for being easy to use and requiring no/minimal configuration at runtime.
-Just adding SDKs to the app makes them discover each other to provide additional functionality,
-e.g. `Firestore` automatically integrates with `Auth` if present in the app.
+Firebase is known for being easy to use and requiring no/minimal configuration at runtime. Just
+adding SDKs to the app makes them discover each other to provide additional functionality, e.g.
+`Firestore` automatically integrates with `Auth` if present in the app.
-* Firebase SDKs have required and optional dependencies on other Firebase SDKs
-* SDKs have different initialization requirements, e.g. `Analytics` and `Crashlytics` must be
+- Firebase SDKs have required and optional dependencies on other Firebase SDKs
+- SDKs have different initialization requirements, e.g. `Analytics` and `Crashlytics` must be
initialized upon application startup, while some are initialized on demand only.
-To accommodate these requirements Firebase uses a component model that discovers SDKs present in the app,
-determines their dependencies and provides them to dependent SDKs via a `Dependency Injection` mechanism.
+To accommodate these requirements Firebase uses a component model that discovers SDKs present in the
+app, determines their dependencies and provides them to dependent SDKs via a `Dependency Injection`
+mechanism.
This page describes the aforementioned Component Model, how it works and why it's needed.
@@ -27,17 +28,19 @@ This page describes the aforementioned Component Model, how it works and why it'
### Transparent/invisible to 3p Developers
-To provide good developer experience, we don't want developers to think about how SDKs work and interoperate internally.
-Instead we want our SDKs to have a simple API surface that hides all of the internal details.
-Most products have an API surface that allows developers to get aninstance of a given SDK via `FirebaseFoo.getInstance()`
-and start using it right away.
+To provide good developer experience, we don't want developers to think about how SDKs work and
+interoperate internally. Instead we want our SDKs to have a simple API surface that hides all of the
+internal details. Most products have an API surface that allows developers to get aninstance of a
+given SDK via `FirebaseFoo.getInstance()` and start using it right away.
### Simple to use and integrate with for component developers
-* The component model is lightweight in terms of integration effort. It is not opinionated on how components are structured.
-* The component model should require as little cooperation from components runtime as possible.
-* It provides component developers with an API that is easy to use correctly, and hard to use incorrectly.
-* Does not sacrifice testability of individual components in isolation
+- The component model is lightweight in terms of integration effort. It is not opinionated on how
+ components are structured.
+- The component model should require as little cooperation from components runtime as possible.
+- It provides component developers with an API that is easy to use correctly, and hard to use
+ incorrectly.
+- Does not sacrifice testability of individual components in isolation
### Performant at startup and initialization
@@ -47,11 +50,12 @@ The runtime does as little work as possible during initialization.
A Firebase Component is an entity that:
-* Implements one or more interfaces
-* Has a list of dependencies(required or optional). See [Dependencies]({{ site.baseurl }}{% link components/dependencies.md %})
-* Has initialization requirements(e.g. eager in default app)
-* Defines a factory creates an instance of the component’s interface given it's dependencies.
- (In other words describes how to create the given component.)
+- Implements one or more interfaces
+- Has a list of dependencies(required or optional). See
+ [Dependencies]({{ site.baseurl }}{% link components/dependencies.md %})
+- Has initialization requirements(e.g. eager in default app)
+- Defines a factory creates an instance of the component’s interface given it's dependencies. (In
+ other words describes how to create the given component.)
Example:
@@ -66,11 +70,11 @@ Component auth = Component.builder(FirebaseAuth.class, InternalAut
.build()
```
-All components are singletons within a Component Container(e.g. one instance per FirebaseApp).
-There are however SDKs that need the ability to expose multiple objects per FirebaseApp,
-for example RTBD(as well as Storage and Firestore) has multidb support which allows developers
-to access one or more databases within one FirebaseApp. To address this requirement,
-SDKs have to register their components in the following form(or similar):
+All components are singletons within a Component Container(e.g. one instance per FirebaseApp). There
+are however SDKs that need the ability to expose multiple objects per FirebaseApp, for example
+RTBD(as well as Storage and Firestore) has multidb support which allows developers to access one or
+more databases within one FirebaseApp. To address this requirement, SDKs have to register their
+components in the following form(or similar):
```java
// This is the singleton holder of different instances of FirebaseDatabase.
@@ -80,18 +84,20 @@ interface RtdbComponent {
}
```
-As you can see in the previous section, components are just values and don't have any behavior per se,
-essentially they are just blueprints of how to create them and what dependencies they need.
+As you can see in the previous section, components are just values and don't have any behavior per
+se, essentially they are just blueprints of how to create them and what dependencies they need.
-So there needs to be some ComponentRuntime that can discover and wire them together into a dependency graph,
-in order to do that, there needs to be an agreed upon location where SDKs can register the components they provide.
+So there needs to be some ComponentRuntime that can discover and wire them together into a
+dependency graph, in order to do that, there needs to be an agreed upon location where SDKs can
+register the components they provide.
The next 2 sections describe how it's done.
## Component Registration
-In order to define the `Components` an SDK provides, it needs to define a class that implements `ComponentRegistrar`,
-this class contains all component definitions the SDK wants to register with the runtime:
+In order to define the `Components` an SDK provides, it needs to define a class that implements
+`ComponentRegistrar`, this class contains all component definitions the SDK wants to register with
+the runtime:
```java
public class MyRegistrar implements ComponentRegistrar {
@@ -108,7 +114,8 @@ public class MyRegistrar implements ComponentRegistrar {
## Component Discovery
-In addition to creating the `ComponentRegistrar` class, SDKs also need to add them to their `AndroidManifest.xml` under `ComponentDiscoveryService`:
+In addition to creating the `ComponentRegistrar` class, SDKs also need to add them to their
+`AndroidManifest.xml` under `ComponentDiscoveryService`:
```xml
@@ -123,28 +130,32 @@ In addition to creating the `ComponentRegistrar` class, SDKs also need to add th
```
-When the final app is built, manifest registrar entries will all end up inside the above `service` as metadata key- value pairs.
-At this point `FirebaseApp` will instantiate them and use the `ComponentRuntime` to construct the component graph.
+When the final app is built, manifest registrar entries will all end up inside the above `service`
+as metadata key- value pairs. At this point `FirebaseApp` will instantiate them and use the
+`ComponentRuntime` to construct the component graph.
## Dependency resolution and initialization
### Definitions and constraints
-* **Component A depends on Component B** if `B` depends on an `interface` that `A` implements.
-* **For any Interface I, only one component is allowed to implement I**(with the exception of
- [Set Dependencies]({{ site.baseurl }}{% link components/dependencies.md %}#set-dependencies)). If this invariant is violated, the container will
- fail to start at runtime.
-* **There must not be any dependency cycles** among components. See Dependency Cycle Resolution on how this limitation can
- be mitigated
-* **Components are initialized lazily by default**(unless a component is declared eager) and are initialized when requested
- by an application either directly or transitively.
+- **Component A depends on Component B** if `B` depends on an `interface` that `A` implements.
+- **For any Interface I, only one component is allowed to implement I**(with the exception of [Set
+ Dependencies]({{ site.baseurl }}{% link components/dependencies.md %}#set-dependencies)). If this
+ invariant is violated, the container will fail to start at runtime.
+- **There must not be any dependency cycles** among components. See Dependency Cycle Resolution on
+ how this limitation can be mitigated
+- **Components are initialized lazily by default**(unless a component is declared eager) and are
+ initialized when requested by an application either directly or transitively.
The initialization phase of the FirebaseApp will consist of the following steps:
1. Get a list of available FirebaseComponents that were discovered by the Discovery mechanism
-2. Topologically sort components based on their declared dependencies - failing if a dependency cycle is detected or multiple implementations are registered for any interface.
-3. Store a map of {iface -> ComponentFactory} so that components can be instantiated on demand(Note that component instantiation does not yet happen)
-4. Initialize EAGER components or schedule them to initialize on device unlock, if in direct boot mode.
+2. Topologically sort components based on their declared dependencies - failing if a dependency
+ cycle is detected or multiple implementations are registered for any interface.
+3. Store a map of {iface -> ComponentFactory} so that components can be instantiated on demand(Note
+ that component instantiation does not yet happen)
+4. Initialize EAGER components or schedule them to initialize on device unlock, if in direct boot
+ mode.
### Initialization example
@@ -172,12 +183,12 @@ flowchart TD
RemoteConfig --> FirebaseApp
RemoteConfig --> Context
RemoteConfig --> Installations
-
-
+
+
classDef eager fill:#4db66e,stroke:#4db6ac,color:#000;
classDef transitive fill:#4db6ac,stroke:#4db6ac,color:#000;
classDef always fill:#1a73e8,stroke:#7baaf7,color:#fff;
-
+
class Analytics eager
class Crashlytics eager
class Context always
@@ -186,33 +197,37 @@ flowchart TD
class Installations transitive
```
-There are **2 explicitly eager** components in this example: `Crashlytics` and `Analytics`.
-These components are initialized when `FirebaseApp` is initialized. `Installations` is initialized eagerly because
-eager components depends on it(see Prefer Lazy dependencies to avoid this as mush as possible).
-`FirebaseApp`, `FirebaseOptions` and `Android Context` are always present in the Component Container and are considered initialized as well.
+There are **2 explicitly eager** components in this example: `Crashlytics` and `Analytics`. These
+components are initialized when `FirebaseApp` is initialized. `Installations` is initialized eagerly
+because eager components depends on it(see Prefer Lazy dependencies to avoid this as mush as
+possible). `FirebaseApp`, `FirebaseOptions` and `Android Context` are always present in the
+Component Container and are considered initialized as well.
-*The rest of the components are left uninitialized and will remain so until the client application requests them or an eager
-component initializes them by using a Lazy dependency.*
-For example, if the application calls `FirebaseDatabase.getInstance()`, the container will initialize `Auth` and `Database`
-and will return `Database` to the user.
+_The rest of the components are left uninitialized and will remain so until the client application
+requests them or an eager component initializes them by using a Lazy dependency._ For example, if
+the application calls `FirebaseDatabase.getInstance()`, the container will initialize `Auth` and
+`Database` and will return `Database` to the user.
### Support multiple instances of the SDK per `FirebaseApp`(multi-resource)
-Some SDKs support multi-resource mode of operation, where it's possible to create more than one instance per `FirebaseApp`.
+Some SDKs support multi-resource mode of operation, where it's possible to create more than one
+instance per `FirebaseApp`.
Examples:
-* RTDB allows more than one database in a single Firebase project, so it's possible to instantiate one instance of the sdk per datbase
+- RTDB allows more than one database in a single Firebase project, so it's possible to instantiate
+ one instance of the sdk per datbase
```kotlin
val rtdbOne = Firebase.database(app) // uses default database
val rtdbTwo = Firebase.database(app, "dbName")
```
-* Firestore, functions, and others support the same usage pattern
+- Firestore, functions, and others support the same usage pattern
-To allow for that, such SDKs register a singleton "MultiResource" [Firebase component]({{ site.baseurl }}{% link components/components.md %}),
-which creates instances per resource(e.g. db name).
+To allow for that, such SDKs register a singleton "MultiResource" [Firebase
+component]({{ site.baseurl }}{% link components/components.md %}), which creates instances per
+resource(e.g. db name).
Example
@@ -236,7 +251,7 @@ class FirebaseDatabase(
companion object {
fun getInstance(app : FirebaseApp) = getInstance("default")
- fun getInstance(app : FirebaseApp, dbName: String) =
+ fun getInstance(app : FirebaseApp, dbName: String) =
app.get(DatabaseComponent::class.java).get("default")
}
diff --git a/contributor-docs/components/dependencies.md b/contributor-docs/components/dependencies.md
index 587fe109ab1..b77fb34ab64 100644
--- a/contributor-docs/components/dependencies.md
+++ b/contributor-docs/components/dependencies.md
@@ -3,18 +3,18 @@ parent: Firebase Components
---
# Dependencies
+
{: .no_toc}
-1. TOC
-{:toc}
+1. TOC {:toc}
This page gives an overview of the different dependency types supported by the Components Framework.
## Background
-As discussed in [Firebase Components]({{ site.baseurl }}{% link components/components.md %}), in order
-for a `Component` to be injected with the things it needs to function, it has to declare its dependencies.
-These dependencies are then made available and injected into `Components` at runtime.
+As discussed in [Firebase Components]({{ site.baseurl }}{% link components/components.md %}), in
+order for a `Component` to be injected with the things it needs to function, it has to declare its
+dependencies. These dependencies are then made available and injected into `Components` at runtime.
Firebase Components provide different types of dependencies.
@@ -34,15 +34,14 @@ class MyComponent(private val dep : MyDep) {
}
```
-As you can see above the component's dependency is passed by value directly,
-which means that the dependency needs to be fully initialized before
-it's handed off to the requesting component. As a result `MyComponent` may have to pay the cost
-of initializing `MyDep` just to be created.
+As you can see above the component's dependency is passed by value directly, which means that the
+dependency needs to be fully initialized before it's handed off to the requesting component. As a
+result `MyComponent` may have to pay the cost of initializing `MyDep` just to be created.
### Lazy/Provider Injection
-With this type of injection, instead of getting an instance of the dependency directly, the dependency
-is passed into the `Component` with the help of a `com.google.firebase.inject.Provider`
+With this type of injection, instead of getting an instance of the dependency directly, the
+dependency is passed into the `Component` with the help of a `com.google.firebase.inject.Provider`
```java
public interface Provider { T get(); }
@@ -58,19 +57,22 @@ class MyComponent(private val dep : Provider) {
}
```
-On the surface this does not look like a big change, but it has an important side effect. In order to create
-an instance of `MyComponent`, we don't need to initialize `MyDep` anymore. Instead, initialization can be
-delayed until `MyDep` is actually used.
+On the surface this does not look like a big change, but it has an important side effect. In order
+to create an instance of `MyComponent`, we don't need to initialize `MyDep` anymore. Instead,
+initialization can be delayed until `MyDep` is actually used.
-It is also benefitial to use a `Provider` in the context of [Play's dynamic feature delivery](https://developer.android.com/guide/playcore/feature-delivery).
-See [Dynamic Module Support]({{ site.baseurl }}{% link components/dynamic_modules.md %}) for more details.
+It is also benefitial to use a `Provider` in the context of
+[Play's dynamic feature delivery](https://developer.android.com/guide/playcore/feature-delivery).
+See [Dynamic Module Support]({{ site.baseurl }}{% link components/dynamic_modules.md %}) for more
+details.
## Required dependencies
-This type of dependency informs the `ComponentRuntime` that a given `Component` cannot function without a dependency.
-When the dependency is missing during initialization, `ComponentRuntime` will throw a `MissingDependencyException`.
-This type of dependency is useful for built-in components that are always present like `Context`, `FirebaseApp`,
-`FirebaseOptions`, [Executors]({{ site.baseurl }}{% link components/executors.md %}).
+This type of dependency informs the `ComponentRuntime` that a given `Component` cannot function
+without a dependency. When the dependency is missing during initialization, `ComponentRuntime` will
+throw a `MissingDependencyException`. This type of dependency is useful for built-in components that
+are always present like `Context`, `FirebaseApp`, `FirebaseOptions`,
+[Executors]({{ site.baseurl }}{% link components/executors.md %}).
To declare a required dependency use one of the following in your `ComponentRegistrar`:
@@ -85,9 +87,9 @@ To declare a required dependency use one of the following in your `ComponentRegi
## Optional Dependencies
-This type of dependencies is useful when your `Component` can operate normally when the dependency is not
-available, but can have enhanced functionality when present. e.g. `Firestore` can work without `Auth` but
-provides secure database access when `Auth` is present.
+This type of dependencies is useful when your `Component` can operate normally when the dependency
+is not available, but can have enhanced functionality when present. e.g. `Firestore` can work
+without `Auth` but provides secure database access when `Auth` is present.
To declare an optional dependency use the following in your `ComponentRegistrar`:
@@ -99,24 +101,26 @@ To declare an optional dependency use the following in your `ComponentRegistrar`
The provider will return `null` if the dependency is not present in the app.
-{: .warning }
-When the app uses [Play's dynamic feature delivery](https://developer.android.com/guide/playcore/feature-delivery),
-`provider.get()` will return your dependency when it becomes available. To support this use case, don't store references to the result of `provider.get()` calls.
+{: .warning } When the app uses
+[Play's dynamic feature delivery](https://developer.android.com/guide/playcore/feature-delivery),
+`provider.get()` will return your dependency when it becomes available. To support this use case,
+don't store references to the result of `provider.get()` calls.
See [Dynamic Module Support]({{ site.baseurl }}{% link components/dynamic_modules.md %}) for details
-{: .warning }
-See Deferred dependencies if you your dependency has a callback based API
+{: .warning } See Deferred dependencies if you your dependency has a callback based API
## Deferred Dependencies
-Useful for optional dependencies which have a listener-style API, i.e. the dependent component registers a
-listener with the dependency and never calls it again (instead the dependency will call the registered listener).
-A good example is `Firestore`'s use of `Auth`, where `Firestore` registers a token change listener to get
-notified when a new token is available. The problem is that when `Firestore` initializes, `Auth` may not be
-present in the app, and is instead part of a dynamic module that can be loaded at runtime on demand.
+Useful for optional dependencies which have a listener-style API, i.e. the dependent component
+registers a listener with the dependency and never calls it again (instead the dependency will call
+the registered listener). A good example is `Firestore`'s use of `Auth`, where `Firestore` registers
+a token change listener to get notified when a new token is available. The problem is that when
+`Firestore` initializes, `Auth` may not be present in the app, and is instead part of a dynamic
+module that can be loaded at runtime on demand.
-To solve this problem, Components have a notion of a `Deferred` dependency. A deferred is defined as follows:
+To solve this problem, Components have a notion of a `Deferred` dependency. A deferred is defined as
+follows:
```java
public interface Deferred {
@@ -145,7 +149,8 @@ See [Dynamic Module Support]({{ site.baseurl }}{% link components/dynamic_module
## Set Dependencies
-The Components Framework allows registering components to be part of a set, such components are registered explicitly to be a part of a `Set` as opposed to be a unique value of `T`:
+The Components Framework allows registering components to be part of a set, such components are
+registered explicitly to be a part of a `Set` as opposed to be a unique value of `T`:
```java
// Sdk 1
@@ -157,21 +162,22 @@ Component.intoSetBuilder(SomeType.class)
.build();
```
-With the above setup each SDK contributes a value of `SomeType` into a `Set` which becomes available as a
-`Set` dependency.
+With the above setup each SDK contributes a value of `SomeType` into a `Set` which becomes
+available as a `Set` dependency.
-To consume such a set the interested `Component` needs to declare a special kind of dependency in one of 2 ways:
+To consume such a set the interested `Component` needs to declare a special kind of dependency in
+one of 2 ways:
-* `Dependency.setOf(SomeType.class)`, a dependency of type `Set`.
-* `Dependency.setOfProvider(SomeType.class)`, a dependency of type `Provider>`. The advantage of this
- is that the `Set` is not initialized until the first call to `provider.get()` at which point all elements of the
- set will get initialized.
+- `Dependency.setOf(SomeType.class)`, a dependency of type `Set`.
+- `Dependency.setOfProvider(SomeType.class)`, a dependency of type `Provider>`. The
+ advantage of this is that the `Set` is not initialized until the first call to `provider.get()` at
+ which point all elements of the set will get initialized.
-{: .warning }
-Similar to optional `Provider` dependencies, where an optional dependency can become available at runtime due to
+{: .warning } Similar to optional `Provider` dependencies, where an optional dependency can become
+available at runtime due to
[Play's dynamic feature delivery](https://developer.android.com/guide/playcore/feature-delivery),
-`Set` dependencies can change at runtime by new elements getting added to the set.
-So make sure to hold on to the original `Set` to be able to observe new values in it as they are added.
+`Set` dependencies can change at runtime by new elements getting added to the set. So make sure to
+hold on to the original `Set` to be able to observe new values in it as they are added.
Example:
diff --git a/contributor-docs/components/executors.md b/contributor-docs/components/executors.md
index f7fdc7c3a7b..f8aea35a476 100644
--- a/contributor-docs/components/executors.md
+++ b/contributor-docs/components/executors.md
@@ -3,20 +3,22 @@ parent: Firebase Components
---
# Executors
+
{: .no_toc}
-1. TOC
-{:toc}
+1. TOC {:toc}
## Intro
-OS threads are a limited resource that needs to be used with care. In order to minimize the number of threads used by Firebase
-as a whole and to increase resource sharing Firebase Common provides a set of standard
-[executors](https://developer.android.com/reference/java/util/concurrent/Executor)
-and [coroutine dispatchers](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/)
+OS threads are a limited resource that needs to be used with care. In order to minimize the number
+of threads used by Firebase as a whole and to increase resource sharing Firebase Common provides a
+set of standard [executors](https://developer.android.com/reference/java/util/concurrent/Executor)
+and
+[coroutine dispatchers](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/)
for use by all Firebase SDKs.
-These executors are available as components and can be requested by product SDKs as component dependencies.
+These executors are available as components and can be requested by product SDKs as component
+dependencies.
Example:
@@ -25,7 +27,7 @@ public class MyRegistrar implements ComponentRegistrar {
public List> getComponents() {
Qualified backgroundExecutor = Qualified.qualified(Background.class, Executor.class);
Qualified liteExecutorService = Qualified.qualified(Lightweight.class, ExecutorService.class);
-
+
return Collections.singletonList(
Component.builder(MyComponent.class)
.add(Dependency.required(backgroundExecutor))
@@ -38,17 +40,17 @@ public class MyRegistrar implements ComponentRegistrar {
All executors(with the exception of `@UiThread`) are available as the following interfaces:
-* `Executor`
-* `ExecutorService`
-* `ScheduledExecutorService`
-* `CoroutineDispatcher`
+- `Executor`
+- `ExecutorService`
+- `ScheduledExecutorService`
+- `CoroutineDispatcher`
`@UiThread` is provided only as a plain `Executor`.
### Validation
-All SDKs have a custom linter check that detects creation of thread pools and threads,
-this is to ensure SDKs use the above executors instead of creating their own.
+All SDKs have a custom linter check that detects creation of thread pools and threads, this is to
+ensure SDKs use the above executors instead of creating their own.
## Choose the right executor
@@ -65,17 +67,17 @@ flowchart TD
DoesBlock --> |Yes| DiskIO{Does it block only\n on disk IO?}
DiskIO --> |Yes| BgExecutor
DiskIO --> |No| BlockExecutor[[Blocking Executor]]
-
-
+
+
classDef start fill:#4db6ac,stroke:#4db6ac,color:#000;
class Start start
-
+
classDef condition fill:#f8f9fa,stroke:#bdc1c6,color:#000;
class DoesBlock condition;
class NeedUi condition;
class TakesLong condition;
class DiskIO condition;
-
+
classDef executor fill:#1a73e8,stroke:#7baaf7,color:#fff;
class UiExecutor executor;
class LiteExecutor executor;
@@ -85,7 +87,8 @@ flowchart TD
### UiThread
-Used to schedule tasks on application's UI thread, internally it uses a Handler to post runnables onto the main looper.
+Used to schedule tasks on application's UI thread, internally it uses a Handler to post runnables
+onto the main looper.
Example:
@@ -101,8 +104,8 @@ Qualified dispatcher = qualified(UiThread::class.java, Coro
### Lightweight
-Use for tasks that never block and don't take to long to execute. Backed by a thread pool of N threads
-where N is the amount of parallelism available on the device(number of CPU cores)
+Use for tasks that never block and don't take to long to execute. Backed by a thread pool of N
+threads where N is the amount of parallelism available on the device(number of CPU cores)
Example:
@@ -118,8 +121,8 @@ Qualified dispatcher = qualified(Lightweight::class.java, C
### Background
-Use for tasks that may block on disk IO(use `@Blocking` for network IO or blocking on other threads).
-Backed by 4 threads.
+Use for tasks that may block on disk IO(use `@Blocking` for network IO or blocking on other
+threads). Backed by 4 threads.
Example:
@@ -153,8 +156,8 @@ Qualified dispatcher = qualified(Blocking::class.java, Coro
#### Direct executor
-{: .warning }
-Prefer `@Lightweight` instead of using direct executor as it could cause dead locks and stack overflows.
+{: .warning } Prefer `@Lightweight` instead of using direct executor as it could cause dead locks
+and stack overflows.
For any trivial tasks that don't need to run asynchronously
@@ -166,7 +169,9 @@ FirebaseExecutors.directExecutor()
#### Sequential Executor
-When you need an executor that runs tasks sequentially and guarantees any memory access is synchronized prefer to use a sequential executor instead of creating a `newSingleThreadedExecutor()`.
+When you need an executor that runs tasks sequentially and guarantees any memory access is
+synchronized prefer to use a sequential executor instead of creating a
+`newSingleThreadedExecutor()`.
Example:
@@ -179,13 +184,13 @@ Executor sequentialExecutor = FirebaseExecutors.newSequentialExecutor(c.get(bgEx
## Proper Kotlin usage
-A `CoroutineContext` should be preferred when possible over an explicit `Executor`
-or `CoroutineDispatcher`. You should only use an `Executor` at the highest
-(or inversely the lowest) level of your implementations. Most classes should not
-be concerned with the existence of an `Executor`.
+A `CoroutineContext` should be preferred when possible over an explicit `Executor` or
+`CoroutineDispatcher`. You should only use an `Executor` at the highest (or inversely the lowest)
+level of your implementations. Most classes should not be concerned with the existence of an
+`Executor`.
-Keep in mind that you can combine `CoroutineContext` with other `CoroutineScope`
-or `CoroutineContext`. And that all `suspend` functions inherent their `coroutineContext`:
+Keep in mind that you can combine `CoroutineContext` with other `CoroutineScope` or
+`CoroutineContext`. And that all `suspend` functions inherent their `coroutineContext`:
```kotlin
suspend fun createSession(): Session {
@@ -202,20 +207,19 @@ To learn more, you should give the following Kotlin wiki page a read:
### Using Executors in tests
-`@Lightweight` and `@Background` executors have StrictMode enabled and throw exceptions on violations.
-For example trying to do Network IO on either of them will throw.
-With that in mind, when it comes to writing tests, prefer to use the common executors as opposed to creating
-your own thread pools. This will ensure that your code uses the appropriate executor and does not slow down
+`@Lightweight` and `@Background` executors have StrictMode enabled and throw exceptions on
+violations. For example trying to do Network IO on either of them will throw. With that in mind,
+when it comes to writing tests, prefer to use the common executors as opposed to creating your own
+thread pools. This will ensure that your code uses the appropriate executor and does not slow down
all of Firebase by using the wrong one.
-To do that, you should prefer relying on Components to inject the right executor even in tests.
-This will ensure your tests are always using the executor that is actually used in your SDK build.
-If your SDK uses Dagger, see [Dependency Injection]({{ site.baseurl }}{% link
-best_practices/dependency_injection.md %})
-and [Dagger's testing guide](https://dagger.dev/dev-guide/testing).
+To do that, you should prefer relying on Components to inject the right executor even in tests. This
+will ensure your tests are always using the executor that is actually used in your SDK build. If
+your SDK uses Dagger, see [Dependency Injection]({{ site.baseurl }}{% link
+best_practices/dependency_injection.md %}) and [Dagger's testing guide](https://dagger.dev/dev-guide/testing).
-When the above is not an option, you can use `TestOnlyExecutors`, but make sure you're testing your code with
-the same executor that is used in production code:
+When the above is not an option, you can use `TestOnlyExecutors`, but make sure you're testing your
+code with the same executor that is used in production code:
```kotlin
dependencies {
@@ -237,35 +241,34 @@ TestOnlyExecutors.lite();
### Policy violations in tests
-Unit tests require [Robolectric](https://github.com/robolectric/robolectric) to
-function correctly, and this comes with a major drawback; no policy validation.
+Unit tests require [Robolectric](https://github.com/robolectric/robolectric) to function correctly,
+and this comes with a major drawback; no policy validation.
-Robolectric supports `StrictMode`- but does not provide the backing for its
-policy mechanisms to fire on violations. As such, you'll be able to do things
-like using `TestOnlyExecutors.background()` to execute blocking actions; usage
-that would have otherwise crashed in a real application.
+Robolectric supports `StrictMode`- but does not provide the backing for its policy mechanisms to
+fire on violations. As such, you'll be able to do things like using `TestOnlyExecutors.background()`
+to execute blocking actions; usage that would have otherwise crashed in a real application.
-Unfortunately, there is no easy way to fix this for unit tests. You can get
-around the issue by moving the tests to an emulator (integration tests)- but
-those can be more expensive than your standard unit test, so you may want to
-take that into consideration when planning your testing strategy.
+Unfortunately, there is no easy way to fix this for unit tests. You can get around the issue by
+moving the tests to an emulator (integration tests)- but those can be more expensive than your
+standard unit test, so you may want to take that into consideration when planning your testing
+strategy.
### StandardTestDispatcher support
The [kotlin.coroutines.test](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/)
-library provides support for a number of different mechanisms in tests. Some of the more
-famous features include:
+library provides support for a number of different mechanisms in tests. Some of the more famous
+features include:
- [advanceUntilIdle](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scheduler/advance-until-idle.html)
- [advanceTimeBy](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scheduler/advance-time-by.html)
- [runCurrent](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scheduler/run-current.html)
-These features are all backed by `StandardTestDispatcher`, or more appropriately,
-the `TestScope` provided in a `runTest` block.
+These features are all backed by `StandardTestDispatcher`, or more appropriately, the `TestScope`
+provided in a `runTest` block.
-Unfortunately, `TestOnlyExecutors` does not natively bind with `TestScope`.
-Meaning, should you use `TestOnlyExecutors` in your tests- you won't be able to utilize
-the features provided by `TestScope`:
+Unfortunately, `TestOnlyExecutors` does not natively bind with `TestScope`. Meaning, should you use
+`TestOnlyExecutors` in your tests- you won't be able to utilize the features provided by
+`TestScope`:
```kotlin
@Test
@@ -279,9 +282,8 @@ fun doesStuff() = runTest {
}
```
-To help fix this, we provide an extension method on `TestScope` called
-`firebaseExecutors`. It facilitates the binding of `TestOnlyExecutors` with the
-current `TestScope`.
+To help fix this, we provide an extension method on `TestScope` called `firebaseExecutors`. It
+facilitates the binding of `TestOnlyExecutors` with the current `TestScope`.
For example, here's how you could use this extension method in a test:
@@ -295,4 +297,4 @@ fun doesStuff() = runTest {
runCurrent()
}
-```
\ No newline at end of file
+```
diff --git a/contributor-docs/how_firebase_works.md b/contributor-docs/how_firebase_works.md
index 3d20eeb2374..bbfba97012e 100644
--- a/contributor-docs/how_firebase_works.md
+++ b/contributor-docs/how_firebase_works.md
@@ -8,54 +8,74 @@ nav_order: 3
### Eager Initialization
-One of the biggest strengths for Firebase clients is the ease of integration. In a common case, a developer has very few things to do to integrate with Firebase. There is no need to initialize/configure Firebase at runtime. Firebase automatically initializes at application start and begins providing value to developers. A few notable examples:
+One of the biggest strengths for Firebase clients is the ease of integration. In a common case, a
+developer has very few things to do to integrate with Firebase. There is no need to
+initialize/configure Firebase at runtime. Firebase automatically initializes at application start
+and begins providing value to developers. A few notable examples:
-* `Analytics` automatically tracks app events
-* `Firebase Performance` automatically tracks app startup time, all network requests and screen performance
-* `Crashlytics` automatically captures all application crashes, ANRs and non-fatals
+- `Analytics` automatically tracks app events
+- `Firebase Performance` automatically tracks app startup time, all network requests and screen
+ performance
+- `Crashlytics` automatically captures all application crashes, ANRs and non-fatals
-This feature makes onboarding and adoption very simple. However, comes with the great responsibility of keeping the application snappy. We shouldn't slow down application startup for 3p developers as it can stand in the way of user adoption of their application.
+This feature makes onboarding and adoption very simple. However, comes with the great responsibility
+of keeping the application snappy. We shouldn't slow down application startup for 3p developers as
+it can stand in the way of user adoption of their application.
### Automatic Inter-Product Discovery
-When present together in an application, Firebase products can detect each other and automatically provide additional functionality to the developer, e.g.:
+When present together in an application, Firebase products can detect each other and automatically
+provide additional functionality to the developer, e.g.:
-* `Firestore` automatically detects `Auth` and `AppCheck` to protect read/write access to the database
-* `Crashlytics` integrates with `Analytics`, when available, to provide additional insights into the application behavior and enables safe app rollouts
+- `Firestore` automatically detects `Auth` and `AppCheck` to protect read/write access to the
+ database
+- `Crashlytics` integrates with `Analytics`, when available, to provide additional insights into the
+ application behavior and enables safe app rollouts
## FirebaseApp at the Core of Firebase
-Regardless of what Firebase SDKs are present in the app, the main initialization point of Firebase is `FirebaseApp`. It acts as a container for all SDKs, manages their configuration, initialization and lifecycle.
+Regardless of what Firebase SDKs are present in the app, the main initialization point of Firebase
+is `FirebaseApp`. It acts as a container for all SDKs, manages their configuration, initialization
+and lifecycle.
### Initialization
-`FirebaseApp` gets initialized with the help of `FirebaseApp#initializeApp()`. This happens [automatically at app startup](https://firebase.blog/posts/2016/12/how-does-firebase-initialize-on-android) or manually by the developer.
+`FirebaseApp` gets initialized with the help of `FirebaseApp#initializeApp()`. This happens
+[automatically at app startup](https://firebase.blog/posts/2016/12/how-does-firebase-initialize-on-android)
+or manually by the developer.
-During initialization, `FirebaseApp` discovers all Firebase SDKs present in the app, determines the dependency graph between products(for inter-product functionality) and initializes `eager` products that need to start immediately, e.g. `Crashlytics` and `FirebasePerformance`.
+During initialization, `FirebaseApp` discovers all Firebase SDKs present in the app, determines the
+dependency graph between products(for inter-product functionality) and initializes `eager` products
+that need to start immediately, e.g. `Crashlytics` and `FirebasePerformance`.
### Firebase Configuration
-`FirebaseApp` contains Firebase configuration for all products to use, namely `FirebaseOptions`, which tells Firebase which `Firebase` project to talk to, which real-time database to use, etc.
+`FirebaseApp` contains Firebase configuration for all products to use, namely `FirebaseOptions`,
+which tells Firebase which `Firebase` project to talk to, which real-time database to use, etc.
### Additional Services/Components
-In addition to `FirebaseOptions`, `FirebaseApp` registers additional components that product SDKs can request via dependency injection. To name a few:
+In addition to `FirebaseOptions`, `FirebaseApp` registers additional components that product SDKs
+can request via dependency injection. To name a few:
-* `android.content.Context`(Application context)
-* [Common Executors]({{ site.baseurl }}{% link components/executors.md %})
-* `FirebaseOptions`
-* Various internal components
+- `android.content.Context`(Application context)
+- [Common Executors]({{ site.baseurl }}{% link components/executors.md %})
+- `FirebaseOptions`
+- Various internal components
## Discovery and Dependency Injection
There are multiple considerations that lead to the current design of how Firebase SDKs initialize.
1. Certain SDKs need to initialize at app startup.
-2. SDKs have optional dependencies on other products that get enabled when the developer adds the dependency to their app.
+2. SDKs have optional dependencies on other products that get enabled when the developer adds the
+ dependency to their app.
-To enable this functionality, Firebase uses a runtime discovery and dependency injection framework [firebase-components](https://github.com/firebase/firebase-android-sdk/tree/main/firebase-components).
+To enable this functionality, Firebase uses a runtime discovery and dependency injection framework
+[firebase-components](https://github.com/firebase/firebase-android-sdk/tree/main/firebase-components).
-To integrate with this framework SDKs register the components they provide via a `ComponentRegistrar` and declare any dependencies they need to initialize, e.g.
+To integrate with this framework SDKs register the components they provide via a
+`ComponentRegistrar` and declare any dependencies they need to initialize, e.g.
```java
public class MyRegistrar implements ComponentRegistrar {
@@ -80,6 +100,7 @@ public class MyRegistrar implements ComponentRegistrar {
}
```
-This registrar is then registered in `AndroidManifest.xml` of the SDK and is used by `FirebaseApp` to discover all components and construct the dependency graph.
+This registrar is then registered in `AndroidManifest.xml` of the SDK and is used by `FirebaseApp`
+to discover all components and construct the dependency graph.
More details in [Firebase Components]({{ site.baseurl }}{% link components/components.md %}).
diff --git a/contributor-docs/onboarding/env_setup.md b/contributor-docs/onboarding/env_setup.md
index 95427f8a66a..871cb910293 100644
--- a/contributor-docs/onboarding/env_setup.md
+++ b/contributor-docs/onboarding/env_setup.md
@@ -5,36 +5,33 @@ parent: Onboarding
# Development Environment Setup
This page describes software and configuration required to work on code in the
-[Firebase/firebase-android-sdk](https://github.com/firebase/firebase-android-sdk)
-repository.
+[Firebase/firebase-android-sdk](https://github.com/firebase/firebase-android-sdk) repository.
{:toc}
## JDK
-The currently required version of the JDK is `11`. Any other versions are
-unsupported and using them could result in build failures.
+The currently required version of the JDK is `11`. Any other versions are unsupported and using them
+could result in build failures.
## Android Studio
-In general, the most recent version of Android Studio should work. The version
-that is tested at the time of this writing is `Dolphin | 2021.3.1`.
+In general, the most recent version of Android Studio should work. The version that is tested at the
+time of this writing is `Dolphin | 2021.3.1`.
-Download it here:
-[Download Android Studio](https://developer.android.com/studio)
+Download it here: [Download Android Studio](https://developer.android.com/studio)
## Emulators
-If you plan to run tests on emulators(you should), you should be able to install
-them directly from Android Studio's AVD manager.
+If you plan to run tests on emulators(you should), you should be able to install them directly from
+Android Studio's AVD manager.
## Github (Googlers Only)
-To onboard and get write access to the github repository you need to have a
-github account fully linked with [go/github](http://go/github).
+To onboard and get write access to the github repository you need to have a github account fully
+linked with [go/github](http://go/github).
-File a bug using this
-[bug template](http://b/issues/new?component=312729&template=1016566) and wait
+File a bug using this [bug template](http://b/issues/new?component=312729&template=1016566) and wait
for access to be granted.
After that configure github keys as usual using this
@@ -42,9 +39,9 @@ After that configure github keys as usual using this
## Importing the repository
-1. Clone the repository with `git clone --recurse-submodules
- git@github.com:firebase/firebase-android-sdk.git`.
+1. Clone the repository with
+ `git clone --recurse-submodules git@github.com:firebase/firebase-android-sdk.git`.
1. Open Android Studio and click "Open an existing project".
- 
+ 
1. Find the `firebase-android-sdk` directory and open.
1. To run integration/device tests you will need a `google-services.json` file.
diff --git a/contributor-docs/onboarding/new_sdk.md b/contributor-docs/onboarding/new_sdk.md
index 2d39b001d62..6dd619bb6e8 100644
--- a/contributor-docs/onboarding/new_sdk.md
+++ b/contributor-docs/onboarding/new_sdk.md
@@ -3,23 +3,22 @@ parent: Onboarding
---
# Creating a new Firebase SDK
+
{: .no_toc}
-1. TOC
-{:toc}
+1. TOC {:toc}
Want to create a new SDK in
-[firebase/firebase-android-sdk](https://github.com/firebase/firebase-android-sdk)?
-Read on.
+[firebase/firebase-android-sdk](https://github.com/firebase/firebase-android-sdk)? Read on.
{:toc}
## Repository layout and Gradle
-[firebase/firebase-android-sdk](https://github.com/firebase/firebase-android-sdk)
-uses a multi-project Gradle build to organize the different libraries it hosts.
-As a consequence, each project/product within this repo is hosted under its own
-subdirectory with its respective build file(s).
+[firebase/firebase-android-sdk](https://github.com/firebase/firebase-android-sdk) uses a
+multi-project Gradle build to organize the different libraries it hosts. As a consequence, each
+project/product within this repo is hosted under its own subdirectory with its respective build
+file(s).
```bash
firebase-android-sdk
@@ -35,21 +34,17 @@ firebase-android-sdk
└── build.gradle # root project build file.
```
-Most commonly, SDKs are located as immediate child directories of the root
-directory, with the directory name being the exact name of the Maven artifact ID
-the library will have once released. e.g. `firebase-common` directory
-hosts code for the `com.google.firebase:firebase-common` SDK.
+Most commonly, SDKs are located as immediate child directories of the root directory, with the
+directory name being the exact name of the Maven artifact ID the library will have once released.
+e.g. `firebase-common` directory hosts code for the `com.google.firebase:firebase-common` SDK.
-{: .warning }
-Note that the build file name for any given SDK is not `build.gradle` or `build.gradle.kts`
-but rather mirrors the name of the sdk, e.g.
+{: .warning } Note that the build file name for any given SDK is not `build.gradle` or
+`build.gradle.kts` but rather mirrors the name of the sdk, e.g.
`firebase-common/firebase-common.gradle` or `firebase-common/firebase-common.gradle.kts`.
-All of the core Gradle build logic lives in `plugins` and is used by all
-SDKs.
+All of the core Gradle build logic lives in `plugins` and is used by all SDKs.
-SDKs can be grouped together for convenience by placing them in a directory of
-choice.
+SDKs can be grouped together for convenience by placing them in a directory of choice.
## Creating an SDK
@@ -72,34 +67,21 @@ plugins {
// id("kotlin-android")
}
-firebaseLibrary {
- // enable this only if you have tests in `androidTest`.
- testLab.enabled = true
- publishJavadoc = true
-}
+firebaseLibrary { // enable this only if you have tests in `androidTest`. testLab.enabled = true
+publishJavadoc = true }
-android {
- val targetSdkVersion : Int by rootProject
- val minSdkVersion : Int by rootProject
-
- compileSdk = targetSdkVersion
- defaultConfig {
- namespace = "com.google.firebase.foo"
- // change this if you have custom needs.
- minSdk = minSdkVersion
- targetSdk = targetSdkVersion
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- }
+android { val targetSdkVersion : Int by rootProject val minSdkVersion : Int by rootProject
- testOptions.unitTests.isIncludeAndroidResources = true
-}
+compileSdk = targetSdkVersion defaultConfig { namespace = "com.google.firebase.foo" // change this
+if you have custom needs. minSdk = minSdkVersion targetSdk = targetSdkVersion
+testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" }
-dependencies {
- implementation("com.google.firebase:firebase-common:21.0.0")
- implementation("com.google.firebase:firebase-components:18.0.0")
-}
+testOptions.unitTests.isIncludeAndroidResources = true }
-```
+dependencies { implementation("com.google.firebase:firebase-common:21.0.0")
+implementation("com.google.firebase:firebase-components:18.0.0") }
+
+````
### Create `src/main/AndroidManifest.xml` with the following content:
@@ -134,13 +116,14 @@ dependencies {
-```
+````
### Create `com.google.firebase.foo.FirebaseFoo`
For Kotlin
+
src/main/kotlin/com/google/firebase/foo/FirebaseFoo.kt
@@ -161,6 +144,7 @@ class FirebaseFoo {
For Java
+
src/main/java/com/google/firebase/foo/FirebaseFoo.java
@@ -182,14 +166,15 @@ public class FirebaseFoo {
### Create `com.google.firebase.foo.FirebaseFooRegistrar`
For Kotlin
+
src/main/kotlin/com/google/firebase/foo/FirebaseFooRegistrar.kt
-{: .warning }
-You should strongly consider using [Dependency Injection]({{ site.baseurl }}{% link best_practices/dependency_injection.md %})
-to instantiate your sdk instead of manually constructing its instance in the `factory()` below.
+{: .warning } You should strongly consider using [Dependency
+Injection]({{ site.baseurl }}{% link best_practices/dependency_injection.md %}) to instantiate your
+sdk instead of manually constructing its instance in the `factory()` below.
```kotlin
class FirebaseFooRegistrar : ComponentRegistrar {
@@ -204,6 +189,7 @@ class FirebaseFooRegistrar : ComponentRegistrar {
For Java
+
src/main/java/com/google/firebase/foo/FirebaseFooRegistrar.java
diff --git a/docs/README.md b/docs/README.md
index 7d764ead5f2..dfedd7abcf4 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,39 +1,37 @@
# Firebase Android SDK
-The Firebase SDK for Android is the official way to add Firebase to your
-Android app. To get started, visit the [setup instructions][android-setup].
+The Firebase SDK for Android is the official way to add Firebase to your Android app. To get
+started, visit the [setup instructions][android-setup].
## Open Source
This repository includes the following Firebase SDKs for Android:
- * `firebase-common`
- * `firebase-database`
- * `firebase-firestore`
- * `firebase-functions`
- * `firebase-inappmessaging-display`
- * `firebase-perf`
- * `firebase-storage`
+- `firebase-common`
+- `firebase-database`
+- `firebase-firestore`
+- `firebase-functions`
+- `firebase-inappmessaging-display`
+- `firebase-perf`
+- `firebase-storage`
-For more information on building the SDKs from source or contributing,
-visit the [main README][main-readme].
+For more information on building the SDKs from source or contributing, visit the [main
+README][main-readme].
## Kotlin Extensions
-The following Firebase SDKs for Android have Kotlin extension libraries
-that allow you to write more idiomatic Kotlin code when using Firebase
-in your app:
-
- * [`firebase-common`](ktx/common.md)
- * [`firebase-crashlytics`](ktx/crashlytics.md)
- * [`firebase-dynamic-links`](ktx/dynamic-links.md)
- * [`firebase-firestore`](ktx/firestore.md)
- * [`firebase-functions`](ktx/functions.md)
- * [`firebase-inappmessaging`](ktx/inappmessaging.md)
- * [`firebase-inappmessaging-display`](ktx/inappmessaging-display.md)
- * [`firebase-remote-config`](ktx/remote-config.md)
- * [`firebase-storage`](ktx/storage.md)
- * [`firebase-database`](ktx/database.md)
+The following Firebase SDKs for Android have Kotlin extension libraries that allow you to write more
+idiomatic Kotlin code when using Firebase in your app:
+
+- [`firebase-common`](ktx/common.md)
+- [`firebase-crashlytics`](ktx/crashlytics.md)
+- [`firebase-firestore`](ktx/firestore.md)
+- [`firebase-functions`](ktx/functions.md)
+- [`firebase-inappmessaging`](ktx/inappmessaging.md)
+- [`firebase-inappmessaging-display`](ktx/inappmessaging-display.md)
+- [`firebase-remote-config`](ktx/remote-config.md)
+- [`firebase-storage`](ktx/storage.md)
+- [`firebase-database`](ktx/database.md)
[android-setup]: https://firebase.google.com/docs/android/setup
[main-readme]: https://github.com/firebase/firebase-android-sdk/blob/main/README.md
diff --git a/docs/ktx/common.md b/docs/ktx/common.md
deleted file mode 100644
index 3935c9e1b9c..00000000000
--- a/docs/ktx/common.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# Firebase Common Kotlin Extensions
-
-## Getting Started
-
-To use the Firebase Common Android SDK with Kotlin Extensions, add the following
-to your app's `build.gradle` file:
-
-```groovy
-// See maven.google.com for the latest versions
-// This library transitively includes the firebase-common library
-implementation 'com.google.firebase:firebase-common-ktx:$VERSION'
-```
-
-## Features
-
-### Get the default FirebaseApp and FirebaseOptions
-
-**Kotlin**
-```kotlin
-val defaultApp = FirebaseApp.getInstance()
-val defaultOptions = defaultApp.options
-```
-
-**Kotlin + KTX**
-```kotlin
-val defaultApp = Firebase.app
-val defaultOptions = Firebase.options
-```
-
-### Initialize a FirebaseApp
-
-**Kotlin**
-```kotlin
-val options = FirebaseApp.getInstance().options
-val anotherApp = FirebaseApp.initializeApp(context, options, "myApp")
-```
-
-**Kotlin + KTX**
-```kotlin
-var anotherApp = Firebase.initialize(context, Firebase.options, "myApp")
-```
-
diff --git a/docs/ktx/crashlytics.md b/docs/ktx/crashlytics.md
deleted file mode 100644
index 687944ba2e4..00000000000
--- a/docs/ktx/crashlytics.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# Crashlytics Kotlin Extensions
-
-## Getting Started
-
-To use the Firebase Crashlytics Android SDK with Kotlin Extensions, add the following
-to your app's `build.gradle` file:
-
-```groovy
-// See maven.google.com for the latest versions
-// This library transitively includes the firebase-crashlytics library
-implementation 'com.google.firebase:firebase-crashlytics-ktx:$VERSION'
-```
-
-## Features
-
-### Get an instance of FirebaseCrashlytics
-
-**Kotlin**
-```kotlin
-val crashlytics = FirebaseCrashlytics.getInstance()
-```
-
-**Kotlin + KTX**
-```kotlin
-val crashlytics = Firebase.crashlytics
-```
-
-### Set custom keys
-
-**Kotlin**
-```kotlin
-crashlytics.setCustomKey("str_key", "hello")
-crashlytics.setCustomKey("bool_key", true)
-crashlytics.setCustomKey("int_key", 1)
-crashlytics.setCustomKey("long_key", 1L)
-crashlytics.setCustomKey("float_key", 1.0f)
-crashlytics.setCustomKey("double_key", 1.0)
-```
-
-**Kotlin + KTX**
-```kotlin
-crashlytics.setCustomKeys {
- key("str_key", "hello")
- key("bool_key", true)
- key("int_key", 1)
- key("long_key", 1L)
- key("float_key", 1.0f)
- key("double_key", 1.0)
-}
-```
diff --git a/docs/ktx/database.md b/docs/ktx/database.md
deleted file mode 100644
index d54d3497c84..00000000000
--- a/docs/ktx/database.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# Realtime Database Kotlin Extensions
-
-## Getting Started
-
-To use the Firebase Realtime Database Android SDK with Kotlin Extensions, add the following
-to your app's `build.gradle` file:
-
-```groovy
-// See maven.google.com for the latest versions
-// This library transitively includes the firebase-database library
-implementation 'com.google.firebase:firebase-database-ktx:$VERSION'
-```
-
-## Features
-
-### Get an instance of FirebaseDatabase
-
-**Kotlin**
-```kotlin
-val database = FirebaseDatabase.getInstance()
-val anotherDatabase = FirebaseDatabase.getInstance(FirebaseApp.getInstance("myApp"))
-```
-
-**Kotlin + KTX**
-```kotlin
-val database = Firebase.database
-val anotherDatabase = Firebase.database(Firebase.app("myApp"))
-```
-
-### Get the FirebaseDatabase for the specified url
-
-**Kotlin**
-```kotlin
-val database = FirebaseDatabase.getInstance(url)
-```
-
-**Kotlin + KTX**
-```kotlin
-val database = Firebase.database(url)
-```
-
-
-### Get the FirebaseDatabase of the given FirebaseApp and url
-
-**Kotlin**
-```kotlin
-val database = FirebaseDatabase.getInstance(app, url)
-```
-
-**Kotlin + KTX**
-```kotlin
-val database = Firebase.database(app, url)
-```
-
-### Convert a DataSnapshot to a POJO
-
-**Kotlin**
-```kotlin
-val snapshot: DataSnapshot = ...
-val myObject = snapshot.getValue(MyClass::class.java)
-```
-
-**Kotlin + KTX**
-```kotlin
-val snapshot: DocumentSnapshot = ...
-val myObject = snapshot.getValue()
-```
-
-### Convert a DataSnapshot to generic types such as List or Map
-
-**Kotlin**
-```kotlin
-val snapshot: DataSnapshot = ...
-val typeIndicator = object : GenericTypeIndicator>() {}
-val messages: List = snapshot.getValue(typeIndicator)
-```
-
-**Kotlin + KTX**
-```kotlin
-val snapshot: DocumentSnapshot = ...
-val messages: List = snapshot.getValue>()
-```
-
-### Convert a MutableData to a POJO in a Transaction
-
-**Kotlin**
-```kotlin
-override fun doTransaction(mutableData: MutableData): Transaction.Result {
- val post = mutableData.getValue(Post::class.java)
- // ...
-}
-```
-
-**Kotlin + KTX**
-```kotlin
-override fun doTransaction(mutableData: MutableData): Transaction.Result {
- val post = mutableData.getValue()
- // ...
-}
-```
diff --git a/docs/ktx/dynamic-links.md b/docs/ktx/dynamic-links.md
deleted file mode 100644
index 187dade8b41..00000000000
--- a/docs/ktx/dynamic-links.md
+++ /dev/null
@@ -1,170 +0,0 @@
-# Dynamic Links Kotlin Extensions
-
-## Getting Started
-
-To use the Dynamic Links Android SDK with Kotlin Extensions, add the following
-to your app's `build.gradle` file:
-
-```groovy
-// See maven.google.com for the latest versions
-// This library transitively includes the firebase-dynamic-links library
-implementation 'com.google.firebase:firebase-dynamic-links-ktx:$VERSION'
-```
-
-## Features
-
-### Get an instance of FirebaseDynamicLinks
-
-**Kotlin**
-```kotlin
-val dynamicLinks = FirebaseDynamicLinks.getInstance()
-val anotherDynamicLinks = FirebaseDynamicLinks.getInstance(FirebaseApp.getInstance("myApp"))
-```
-
-**Kotlin + KTX**
-```kotlin
-val dynamicLinks = Firebase.dynamicLinks
-val anotherDynamicLinks = Firebase.dynamicLinks(Firebase.app("myApp"))
-```
-
-### Create a Dynamic Link from parameters
-
-**Kotlin**
-```kotlin
-val dynamicLink = FirebaseDynamicLinks.getInstance().createDynamicLink()
- .setLink(Uri.parse("https://www.example.com/"))
- .setDomainUriPrefix("https://example.page.link")
- .setAndroidParameters(
- DynamicLink.AndroidParameters.Builder("com.example.android")
- .setMinimumVersion(16)
- .build())
- .setIosParameters(
- DynamicLink.IosParameters.Builder("com.example.ios")
- .setAppStoreId("123456789")
- .setMinimumVersion("1.0.1")
- .build())
- .setGoogleAnalyticsParameters(
- DynamicLink.GoogleAnalyticsParameters.Builder()
- .setSource("orkut")
- .setMedium("social")
- .setCampaign("example-promo")
- .build())
- .setItunesConnectAnalyticsParameters(
- DynamicLink.ItunesConnectAnalyticsParameters.Builder()
- .setProviderToken("123456")
- .setCampaignToken("example-promo")
- .build())
- .setSocialMetaTagParameters(
- DynamicLink.SocialMetaTagParameters.Builder()
- .setTitle("Example of a Dynamic Link")
- .setDescription("This link works whether the app is installed or not!")
- .build())
- .buildDynamicLink()
-```
-
-**Kotlin + KTX**
-```kotlin
-val dynamicLink = Firebase.dynamicLinks.dynamicLink {
- link = Uri.parse("https://www.example.com/")
- domainUriPrefix = "https://example.page.link"
- androidParameters("com.example.android") {
- minimumVersion = 16
- }
- iosParameters("com.example.ios") {
- appStoreId = "123456789"
- minimumVersion = "1.0.1"
- }
- googleAnalyticsParameters {
- source = "orkut"
- medium = "social"
- campaign = "example-promo"
- }
- itunesConnectAnalyticsParameters {
- providerToken = "123456"
- campaignToken = "example-promo"
- }
- socialMetaTagParameters {
- title = "Example of a Dynamic Link"
- description = "This link works whether the app is installed or not!"
- }
-}
-```
-
-### Shorten a long Dynamic Link
-
-**Kotlin**
-```kotlin
-FirebaseDynamicLinks.getInstance().createDynamicLink()
- .setLongLink(Uri.parse("https://example.page.link/?link=" +
- "https://www.example.com/&apn=com.example.android&ibn=com.example.ios"))
- .buildShortDynamicLink()
- .addOnSuccessListener { result ->
- // Short link created
- val shortLink = result.shortLink
- val flowchartLink = result.previewLink
- val warnings = result.warnings
-
- // do something with the links and warnings
- showLinks(shortLink, flowchartLink)
- displayWarnings(warnings)
- }
- .addOnFailureListener {
- // Error
- // ...
- }
-```
-
-**Kotlin + KTX**
-```kotlin
-Firebase.dynamicLinks.shortLinkAsync {
- longLink = Uri.parse("https://example.page.link/?link=" +
- "https://www.example.com/&apn=com.example.android&ibn=com.example.ios")
-}.addOnSuccessListener { (shortLink, flowchartLink, warnings) ->
- // do something with the links and warnings
- showLinks(shortLink, flowchartLink)
- displayWarnings(warnings)
-}.addOnFailureListener {
- // Error
- // ...
-}
-```
-
-### Create a Dynamic Link with a shorter link suffix
-
-**Kotlin**
-```kotlin
-val shortLinkTask = FirebaseDynamicLinks.getInstance().createDynamicLink()
- // ...
- .buildShortDynamicLink(ShortDynamicLink.Suffix.SHORT)
-```
-
-**Kotlin + KTX**
-```kotlin
-val shortLinkTask = Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) {
- // ...
-}
-```
-
-### Receive deep links
-
-**Kotlin**
-```kotlin
-Firebase.dynamicLinks
- .getDynamicLink(intent)
- .addOnSuccessListener(this) { pendingDynamicLinkData ->
- val deepLink = pendingDynamicLinkData.link
- val minAppVersion = pendingDynamicLinkData.minimumAppVersion
- val clickTimestamp = pendingDynamicLinkData.clickTimestamp
-
- // TODO(developer): handle the deepLink
- }.addOnFailureListener { /* ... */ }
-```
-
-**Kotlin + KTX**
-```kotlin
-Firebase.dynamicLinks
- .getDynamicLink(intent)
- .addOnSuccessListener(this) { (deepLink, minAppVersion, clickTimestamp) ->
- // TODO(developer): handle the deepLink
- }.addOnFailureListener { /* ... */ }
-```
diff --git a/docs/ktx/firestore.md b/docs/ktx/firestore.md
deleted file mode 100644
index 276f6bc0820..00000000000
--- a/docs/ktx/firestore.md
+++ /dev/null
@@ -1,150 +0,0 @@
-# Firestore Kotlin Extensions
-
-## Getting Started
-
-To use the Cloud Firestore Android SDK with Kotlin Extensions, add the following
-to your app's `build.gradle` file:
-
-```groovy
-// See maven.google.com for the latest versions
-// This library transitively includes the firebase-firestore library
-implementation 'com.google.firebase:firebase-firestore-ktx:$VERSION'
-```
-
-## Features
-
-### Get an instance of FirebaseFirestore
-
-**Kotlin**
-```kotlin
-val firestore = FirebaseFirestore.getInstance()
-val anotherFirestore = FirebaseFirestore.getInstance(FirebaseApp.getInstance("myApp"))
-```
-
-**Kotlin + KTX**
-```kotlin
-val firestore = Firebase.firestore
-val anotherFirestore = Firebase.firestore(Firebase.app("myApp"))
-```
-
-### Get a document
-
-**Kotlin**
-```kotlin
-firestore.collection("cities")
- .document("LON")
- .addSnapshotListener { document: DocumentSnapshot?, error: ->
- if (error != null) {
- // Handle error
- return@addSnapshotListener
- }
- if (document != null) {
- // Use document
- }
- }
-```
-
-**Kotlin + KTX**
-```kotlin
-firestore.collection("cities")
- .document("LON")
- .snapshots()
- .collect { document: DocumentSnapshot ->
- // Use document
- }
-```
-
-### Query documents
-
-**Kotlin**
-```kotlin
-firestore.collection("cities")
- .whereEqualTo("capital", true)
- .addSnapshotListener { documents: QuerySnapshot?, error ->
- if (error != null) {
- // Handle error
- return@addSnapshotListener
- }
- if (documents != null) {
- for (document in documents) {
- // Use document
- }
- }
- }
-```
-
-**Kotlin + KTX**
-```kotlin
-firestore.collection("cities")
- .whereEqualTo("capital", true)
- .snapshots()
- .collect { documents: QuerySnapshot ->
- for (document in documents) {
- // Use document
- }
- }
-```
-
-### Convert a DocumentSnapshot field to a POJO
-
-**Kotlin**
-```kotlin
-val snapshot: DocumentSnapshot = ...
-val myObject = snapshot.get("fieldPath", MyClass::class.java)
-```
-
-**Kotlin + KTX**
-```kotlin
-val snapshot: DocumentSnapshot = ...
-val myObject = snapshot.get("fieldPath")
-```
-
-### Convert a DocumentSnapshot to a POJO
-
-**Kotlin**
-```kotlin
-val snapshot: DocumentSnapshot = ...
-val myObject = snapshot.toObject(MyClass::class.java)
-```
-
-**Kotlin + KTX**
-```kotlin
-val snapshot: DocumentSnapshot = ...
-val myObject = snapshot.toObject()
-```
-
-### Convert a QuerySnapshot to a list of POJOs
-
-**Kotlin**
-```kotlin
-val snapshot: QuerySnapshot = ...
-val objectList = snapshot.toObjects(MyClass::class.java)
-```
-
-**Kotlin + KTX**
-```kotlin
-val snapshot: QuerySnapshot = ...
-val objectList = snapshot.toObjects()
-```
-
-### Setup Firestore with a local emulator
-
-**Kotlin**
-```kotlin
-val settings = FirebaseFirestoreSettings.Builder()
- .setHost("10.0.2.2:8080")
- .setSslEnabled(false)
- .setPersistenceEnabled(false)
- .build()
-
-firestore.setFirestoreSettings(settings)
-```
-
-**Kotlin + KTX**
-```kotlin
-firestore.firestoreSettings = firestoreSettings {
- host = "http://10.0.2.2:8080"
- isSslEnabled = false
- isPersistenceEnabled = false
-}
-```
diff --git a/docs/ktx/functions.md b/docs/ktx/functions.md
deleted file mode 100644
index 6de19ce53ed..00000000000
--- a/docs/ktx/functions.md
+++ /dev/null
@@ -1,62 +0,0 @@
-# Cloud Functions Kotlin Extensions
-
-## Getting Started
-
-To use the Cloud Functions Android SDK with Kotlin Extensions, add the following
-to your app's `build.gradle` file:
-
-```groovy
-// See maven.google.com for the latest versions
-// This library transitively includes the firebase-functions library
-implementation 'com.google.firebase:firebase-functions-ktx:$VERSION'
-```
-
-## Features
-
-### Get the FirebaseFunctions instance of the default app
-
-**Kotlin**
-```kotlin
-val functions = FirebaseFunctions.getInstance()
-```
-
-**Kotlin + KTX**
-```kotlin
-val functions = Firebase.functions
-```
-
-### Get the FirebaseFunctions of a given region
-
-**Kotlin**
-```kotlin
-val functions = FirebaseFunctions.getInstance(region)
-```
-
-**Kotlin + KTX**
-```kotlin
-val functions = Firebase.functions(region)
-```
-
-### Get the FirebaseFunctions of a given FirebaseApp
-
-**Kotlin**
-```kotlin
-val functions = FirebaseFunctions.getInstance(app)
-```
-
-**Kotlin + KTX**
-```kotlin
-val functions = Firebase.functions(app)
-```
-
-### Get the FirebaseFunctions of a given region and FirebaseApp
-
-**Kotlin**
-```kotlin
-val functions = FirebaseFunctions.getInstance(app, region)
-```
-
-**Kotlin + KTX**
-```kotlin
-val functions = Firebase.functions(app, region)
-```
diff --git a/docs/ktx/inappmessaging-display.md b/docs/ktx/inappmessaging-display.md
deleted file mode 100644
index 82a6d3702cc..00000000000
--- a/docs/ktx/inappmessaging-display.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# In-App Messaging Display Kotlin Extensions
-
-## Getting Started
-
-To use the Firebase In-App Messaging Display Android SDK with Kotlin Extensions, add the following
-to your app's `build.gradle` file:
-
-```groovy
-// See maven.google.com for the latest versions
-// This library transitively includes the firebase-inappmessaging-display library
-implementation 'com.google.firebase:firebase-inappmessaging-display-ktx:$VERSION'
-```
-
-## Features
-
-### Get an instance of FirebaseInAppMessagingDisplay
-
-**Kotlin**
-```kotlin
-val fiamUI = FirebaseInAppMessagingDisplay.getInstance()
-```
-
-**Kotlin + KTX**
-```kotlin
-val fiamUI = Firebase.inAppMessagingDisplay
-```
diff --git a/docs/ktx/inappmessaging.md b/docs/ktx/inappmessaging.md
deleted file mode 100644
index 13f90d7d84f..00000000000
--- a/docs/ktx/inappmessaging.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# In-App Messaging Kotlin Extensions
-
-## Getting Started
-
-To use the Firebase In-App Messaging Android SDK with Kotlin Extensions, add the following
-to your app's `build.gradle` file:
-
-```groovy
-// See maven.google.com for the latest versions
-// This library transitively includes the firebase-inappmessaging library
-implementation 'com.google.firebase:firebase-inappmessaging-ktx:$VERSION'
-```
-
-## Features
-
-### Get an instance of FirebaseInAppMessaging
-
-**Kotlin**
-```kotlin
-val fiamUI = FirebaseInAppMessaging.getInstance()
-```
-
-**Kotlin + KTX**
-```kotlin
-val fiamUI = Firebase.inAppMessaging
-```
diff --git a/docs/ktx/remote-config.md b/docs/ktx/remote-config.md
deleted file mode 100644
index 4f685cea793..00000000000
--- a/docs/ktx/remote-config.md
+++ /dev/null
@@ -1,86 +0,0 @@
-# Remote Config Kotlin Extensions
-
-## Getting Started
-
-To use the Firebase Remote Config Android SDK with Kotlin Extensions, add the following
-to your app's `build.gradle` file:
-
-```groovy
-// See maven.google.com for the latest versions
-// This library transitively includes the firebase-config library
-implementation 'com.google.firebase:firebase-config-ktx:$VERSION'
-```
-
-## Features
-
-### Get the FirebaseRemoteConfig instance of the default app
-
-**Kotlin**
-```kotlin
-val remoteConfig = FirebaseRemoteConfig.getInstance()
-```
-
-**Kotlin + KTX**
-```kotlin
-val remoteConfig = Firebase.remoteConfig
-```
-
-### Get the FirebaseRemoteConfig of a given FirebaseApp
-
-**Kotlin**
-```kotlin
-val remoteConfig = FirebaseRemoteConfig.getInstance(app)
-```
-
-**Kotlin + KTX**
-```kotlin
-val remoteConfig = Firebase.remoteConfig(app)
-```
-
-### Get parameter values from FirebaseRemoteConfig
-
-**Kotlin**
-```kotlin
-val isEnabled = remoteConfig.getBoolean("is_enabled")
-
-val fileBytes = remoteConfig.getByteArray("file_bytes")
-
-val audioVolume = remoteConfig.getDouble("audio_volume")
-
-val maxCharacters = remoteConfig.getLong("max_characters")
-
-val accessKey = remoteConfig.getString("access_key")
-```
-
-**Kotlin + KTX**
-```kotlin
-val isEnabled = remoteConfig["is_enabled"].asBoolean()
-
-val fileBytes = remoteConfig["file_bytes"].asByteArray()
-
-val audioVolume = remoteConfig["audio_volume"].asDouble()
-
-val maxCharacters = remoteConfig["max_characters"].asLong()
-
-val accessKey = remoteConfig["access_key"].asString()
-```
-
-### Set Remote Config Settings
-
-**Kotlin**
-```kotlin
-val configSettings = FirebaseRemoteConfigSettings.Builder()
- .setMinimumFetchIntervalInSeconds(3600)
- .setFetchTimeoutInSeconds(60)
- .build()
-remoteConfig.setConfigSettingsAsync(configSettings)
-```
-
-**Kotlin + KTX**
-```kotlin
-val configSettings = remoteConfigSettings {
- minimumFetchIntervalInSeconds = 3600
- fetchTimeoutInSeconds = 60
-}
-remoteConfig.setConfigSettingsAsync(configSettings)
-```
\ No newline at end of file
diff --git a/docs/ktx/storage.md b/docs/ktx/storage.md
deleted file mode 100644
index 5854723fe6e..00000000000
--- a/docs/ktx/storage.md
+++ /dev/null
@@ -1,62 +0,0 @@
-# Storage Kotlin Extensions
-
-## Getting Started
-
-To use the Cloud Storage Android SDK with Kotlin Extensions, add the following
-to your app's `build.gradle` file:
-
-```groovy
-// See maven.google.com for the latest versions
-// This library transitively includes the firebase-storage library
-implementation 'com.google.firebase:firebase-storage-ktx:$VERSION'
-```
-
-## Features
-
-### Get an instance of FirebaseStorage
-
-**Kotlin**
-```kotlin
-val storage = FirebaseStorage.getInstance()
-val anotherStorage = FirebaseStorage.getInstance(FirebaseApp.getInstance("myApp"))
-```
-
-**Kotlin + KTX**
-```kotlin
-val storage = Firebase.storage
-val anotherStorage = Firebase.storage(Firebase.app("myApp"))
-```
-
-### Get the FirebaseStorage for a custom storage bucket url
-
-**Kotlin**
-```kotlin
-val storage = FirebaseStorage.getInstance("gs://my-custom-bucket")
-val anotherStorage = FirebaseStorage.getInstance(FirebaseApp.getInstance("myApp"), "gs://my-custom-bucket")
-```
-
-**Kotlin + KTX**
-```kotlin
-val storage = Firebase.storage("gs://my-custom-bucket")
-val anotherStorage = Firebase.storage(Firebase.app("myApp"), "gs://my-custom-bucket")
-```
-
-### Create file metadata
-
-**Kotlin**
-```kotlin
-val metadata = StorageMetadata.Builder()
- .setContentType("image/jpg")
- .setContentDisposition("attachment")
- .setCustomMetadata("location", "Maputo, MOZ")
- .build()
-```
-
-**Kotlin + KTX**
-```kotlin
-val metadata = storageMetadata {
- contentType = "image/jpg"
- contentDisposition = "attachment"
- setCustomMetadata("location", "Maputo, MOZ")
-}
-```
diff --git a/encoders/README.md b/encoders/README.md
index 6a33fcb1c81..afc897087d2 100644
--- a/encoders/README.md
+++ b/encoders/README.md
@@ -1,27 +1,24 @@
# Firebase Encoders
-This project provides libraries and code generation infrastructure that allows
-encoding java classes into various target serialization formats(currently
-supported: **json** and **proto**).
+This project provides libraries and code generation infrastructure that allows encoding java classes
+into various target serialization formats(currently supported: **json** and **proto**).
The project consists of multiple parts:
-* `firebase_encoders` - Core API and Annotations library.
-* `processor` - Java plugin that automatically generates encoders for
- `@Encodable` annotated POJOs.
-* `firebase_encoders_json` - JSON serialization support.
-* `firebase_encoders_proto` - Protobuf serialization support.
-* `protoc_gen` - Protobuf compiler plugin that generates encoder-compliant
- classes. Can be used with `firebase_encoders_proto` and
- `firebase_encoders_json`.
-* `reflective` - Can be used to encode any given class via Java
- reflection(**not recommented**).
+- `firebase_encoders` - Core API and Annotations library.
+- `processor` - Java plugin that automatically generates encoders for `@Encodable` annotated POJOs.
+- `firebase_encoders_json` - JSON serialization support.
+- `firebase_encoders_proto` - Protobuf serialization support.
+- `protoc_gen` - Protobuf compiler plugin that generates encoder-compliant classes. Can be used with
+ `firebase_encoders_proto` and `firebase_encoders_json`.
+- `reflective` - Can be used to encode any given class via Java reflection(**not recommented**).
### Protobuf gettings started
##### Step1. Place proto files into **src/main/proto/**
-*src/main/proto/my.proto*
+_src/main/proto/my.proto_
+
```proto
syntax = "proto3";
@@ -35,10 +32,10 @@ message SimpleProto {
}
```
-
##### Step2. Add the following configurations into gradle module build file.
-*example.gradle*
+_example.gradle_
+
```gradle
plugins {
id "java-library"
@@ -86,11 +83,14 @@ dependencies {
##### Step3. Create a code-gen-cfg.textproto file at the module root folder(same location as the gradle module build file).
-*code-gen-cfg.textproto*
+_code-gen-cfg.textproto_
Note:
+
- The filename must be the same as the filename determined in the gradle build file.
-- Only need to specify the "root" proto object, anything it references will automatically be included.
+- Only need to specify the "root" proto object, anything it references will automatically be
+ included.
+
```textproto
# code_gen_cfg.textproto
# proto-file: src/main/proto/my.proto
@@ -112,8 +112,7 @@ com.google.google.protobuf.Timestamp
com.google.google.protobuf.Timestamp$Builder
```
-Only `root` classes are "encodable" meaning that they have the following
-methods:
+Only `root` classes are "encodable" meaning that they have the following methods:
```java
public class SimpleProto {
@@ -124,7 +123,8 @@ public class SimpleProto {
### Annotation Processing on Kotlin
-The default gradle `annotationProcessor` import doesn't run the processor over kotlin code, so we need to use `kapt`
+The default gradle `annotationProcessor` import doesn't run the processor over kotlin code, so we
+need to use `kapt`
1. Add the plugin to your build
@@ -145,4 +145,4 @@ dependencies {
// annotationProcessor project(":encoders:firebase-encoders-processor")
kapt project(":encoders:firebase-encoders-processor")
}
-```
\ No newline at end of file
+```
diff --git a/encoders/firebase-decoders-json/CHANGELOG.md b/encoders/firebase-decoders-json/CHANGELOG.md
index f514bbb890e..79e701b844d 100644
--- a/encoders/firebase-decoders-json/CHANGELOG.md
+++ b/encoders/firebase-decoders-json/CHANGELOG.md
@@ -1,3 +1 @@
# Unreleased
-
-
diff --git a/encoders/firebase-encoders-json/CHANGELOG.md b/encoders/firebase-encoders-json/CHANGELOG.md
index f514bbb890e..79e701b844d 100644
--- a/encoders/firebase-encoders-json/CHANGELOG.md
+++ b/encoders/firebase-encoders-json/CHANGELOG.md
@@ -1,3 +1 @@
# Unreleased
-
-
diff --git a/encoders/firebase-encoders-json/firebase-encoders-json.gradle b/encoders/firebase-encoders-json/firebase-encoders-json.gradle
index d054982b2dd..beb4cb70a4b 100644
--- a/encoders/firebase-encoders-json/firebase-encoders-json.gradle
+++ b/encoders/firebase-encoders-json/firebase-encoders-json.gradle
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
plugins {
id 'firebase-library'
id 'kotlin-android'
@@ -19,7 +21,7 @@ plugins {
firebaseLibrary {
publishJavadoc = false
- releaseNotes {
+ releaseNotes {
enabled.set(false)
}
}
@@ -38,9 +40,6 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
- kotlinOptions {
- jvmTarget = '1.8'
- }
testOptions {
unitTests {
includeAndroidResources = true
@@ -48,6 +47,8 @@ android {
}
}
+kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_1_8 } }
+
dependencies {
api 'com.google.firebase:firebase-encoders:17.0.0'
diff --git a/encoders/firebase-encoders-processor/CHANGELOG.md b/encoders/firebase-encoders-processor/CHANGELOG.md
index f514bbb890e..79e701b844d 100644
--- a/encoders/firebase-encoders-processor/CHANGELOG.md
+++ b/encoders/firebase-encoders-processor/CHANGELOG.md
@@ -1,3 +1 @@
# Unreleased
-
-
diff --git a/encoders/firebase-encoders-proto/CHANGELOG.md b/encoders/firebase-encoders-proto/CHANGELOG.md
index f4e346bb920..d43e9b27315 100644
--- a/encoders/firebase-encoders-proto/CHANGELOG.md
+++ b/encoders/firebase-encoders-proto/CHANGELOG.md
@@ -1,4 +1,4 @@
# Unreleased
-* [changed] Updated protobuf dependency to `3.25.5` to fix
- [CVE-2024-7254](https://nvd.nist.gov/vuln/detail/CVE-2024-7254).
+- [changed] Updated protobuf dependency to `3.25.5` to fix
+ [CVE-2024-7254](https://nvd.nist.gov/vuln/detail/CVE-2024-7254).
diff --git a/encoders/firebase-encoders-reflective/CHANGELOG.md b/encoders/firebase-encoders-reflective/CHANGELOG.md
index f514bbb890e..79e701b844d 100644
--- a/encoders/firebase-encoders-reflective/CHANGELOG.md
+++ b/encoders/firebase-encoders-reflective/CHANGELOG.md
@@ -1,3 +1 @@
# Unreleased
-
-
diff --git a/encoders/firebase-encoders/CHANGELOG.md b/encoders/firebase-encoders/CHANGELOG.md
index f514bbb890e..79e701b844d 100644
--- a/encoders/firebase-encoders/CHANGELOG.md
+++ b/encoders/firebase-encoders/CHANGELOG.md
@@ -1,3 +1 @@
# Unreleased
-
-
diff --git a/encoders/protoc-gen-firebase-encoders/CHANGELOG.md b/encoders/protoc-gen-firebase-encoders/CHANGELOG.md
index f514bbb890e..79e701b844d 100644
--- a/encoders/protoc-gen-firebase-encoders/CHANGELOG.md
+++ b/encoders/protoc-gen-firebase-encoders/CHANGELOG.md
@@ -1,3 +1 @@
# Unreleased
-
-
diff --git a/firebase-abt/CHANGELOG.md b/firebase-abt/CHANGELOG.md
index d488ea79620..5d6730988ed 100644
--- a/firebase-abt/CHANGELOG.md
+++ b/firebase-abt/CHANGELOG.md
@@ -1,47 +1,64 @@
# Unreleased
+# 23.0.1
+
+- [changed] Bumped internal dependencies.
+
+# 23.0.0
+
+- [changed] **Breaking Change**: Updated minSdkVersion to API level 23 or higher.
+- [removed] **Breaking Change**: Stopped releasing the deprecated Kotlin extensions (KTX) module and
+ removed it from the Firebase Android BoM. Instead, use the KTX APIs from the main module. For
+ details, see the
+ [FAQ about this initiative](https://firebase.google.com/docs/android/kotlin-migration).
# 22.0.0
-* [changed] Bump internal dependencies
+
+- [changed] Bump internal dependencies
# 21.1.2
-* [changed] Bump internal dependencies.
+
+- [changed] Bump internal dependencies.
# 21.1.1
-* [changed] Internal changes to improve experiment reporting.
+
+- [changed] Internal changes to improve experiment reporting.
# 21.1.0
-* [changed] Internal changes to ensure functionality alignment with other
- SDK releases.
+
+- [changed] Internal changes to ensure functionality alignment with other SDK releases.
# 21.0.2
-* [changed] Updated dependency of `play-services-basement` to its latest
- version (v18.1.0).
+
+- [changed] Updated dependency of `play-services-basement` to its latest version (v18.1.0).
# 21.0.1
-* [changed] Updated dependencies of `play-services-basement`,
- `play-services-base`, and `play-services-tasks` to their latest versions
- (v18.0.0, v18.0.1, and v18.0.1, respectively). For more information, see the
- [note](#basement18-0-0_base18-0-1_tasks18-0-1) at the top of this release
- entry.
+
+- [changed] Updated dependencies of `play-services-basement`, `play-services-base`, and
+ `play-services-tasks` to their latest versions (v18.0.0, v18.0.1, and v18.0.1, respectively). For
+ more information, see the [note](#basement18-0-0_base18-0-1_tasks18-0-1) at the top of this
+ release entry.
# 21.0.0
-* [changed] Internal infrastructure improvements.
-* [changed] Internal changes to support dynamic feature modules.
+
+- [changed] Internal infrastructure improvements.
+- [changed] Internal changes to support dynamic feature modules.
# 20.0.0
-* [removed] Removed the protocol buffer dependency and moved relevant protocol
- buffer definitions to [inappmessaging_longer]. If you use [ab_testing]
- with [inappmessaging], you'll need to update to
+
+- [removed] Removed the protocol buffer dependency and moved relevant protocol buffer definitions to
+ [inappmessaging_longer]. If you use [ab_testing] with [inappmessaging], you'll need to update to
[inappmessaging] v19.1.2 or later.
# 19.0.1
-* [changed] Internal changes to ensure functionality alignment with other SDK releases.
+
+- [changed] Internal changes to ensure functionality alignment with other SDK releases.
# 17.1.1
-* [changed] Updated API to support the latest [remote_config] update.
-* [changed] Updated minSdkVersion to API level 16.
+
+- [changed] Updated API to support the latest [remote_config] update.
+- [changed] Updated minSdkVersion to API level 16.
# 17.1.0
-* [changed] Updated API to support the latest [remote_config] update.
+- [changed] Updated API to support the latest [remote_config] update.
diff --git a/firebase-abt/firebase-abt.gradle b/firebase-abt/firebase-abt.gradle
index 3743c3b9546..3d63dfb2cb2 100644
--- a/firebase-abt/firebase-abt.gradle
+++ b/firebase-abt/firebase-abt.gradle
@@ -22,7 +22,6 @@ firebaseLibrary {
releaseNotes {
name.set("{{ab_testing}}")
versionName.set("ab_testing")
- hasKTX.set(false)
}
}
@@ -52,8 +51,8 @@ android {
}
dependencies {
- api 'com.google.firebase:firebase-common:21.0.0'
- api 'com.google.firebase:firebase-components:18.0.0'
+ api libs.firebase.common
+ api libs.firebase.components
implementation libs.playservices.basement
implementation ('com.google.firebase:firebase-measurement-connector:18.0.0') {
diff --git a/firebase-abt/gradle.properties b/firebase-abt/gradle.properties
index 97257e8c980..250707abd72 100644
--- a/firebase-abt/gradle.properties
+++ b/firebase-abt/gradle.properties
@@ -1,2 +1,2 @@
-version=22.0.1
-latestReleasedVersion=22.0.0
+version=23.0.2
+latestReleasedVersion=23.0.1
diff --git a/firebase-ai/CHANGELOG.md b/firebase-ai/CHANGELOG.md
index dd818c29cec..abf0bf55c68 100644
--- a/firebase-ai/CHANGELOG.md
+++ b/firebase-ai/CHANGELOG.md
@@ -1,28 +1,108 @@
# Unreleased
-* [fixed] Fixed `FirebaseAI.getInstance` StackOverflowException (#6971)
-* [fixed] Fixed an issue that was causing the SDK to send empty `FunctionDeclaration` descriptions to the API.
-* [changed] Introduced the `Voice` class, which accepts a voice name, and deprecated the `Voices` class.
-* [changed] **Breaking Change**: Updated `SpeechConfig` to take in `Voice` class instead of `Voices` class.
- * **Action Required:** Update all references of `SpeechConfig` initialization to use `Voice` class.
-* [fixed] Fix incorrect model name in count token requests to the developer API backend
-
-
+- [changed] Added better scheduling and louder output for Live API.
+- [changed] Added support for input and output transcription. (#7482)
+- [feature] Added support for sending realtime audio and video in a `LiveSession`.
+- [changed] Removed redundant internal exception types. (#7475)
+
+# 17.4.0
+
+- [changed] **Breaking Change**: Removed the `candidateCount` option from `LiveGenerationConfig`
+- [changed] Added support for user interrupts for the `startAudioConversation` method in the
+ `LiveSession` class. (#7413)
+- [changed] Added support for the URL context tool, which allows the model to access content from
+ provided public web URLs to inform and enhance its responses. (#7382)
+- [changed] Added better error messages to `ServiceConnectionHandshakeFailedException` (#7412)
+- [changed] Marked the public constructor for `UsageMetadata` as deprecated (#7420)
+- [changed] Using Firebase AI Logic with the Gemini Developer API is now Generally Available (GA).
+- [changed] Using Firebase AI Logic with the Imagen generation APIs is now Generally Available (GA).
+
+# 17.3.0
+
+- [changed] Bumped internal dependencies.
+- [feature] Added support for code execution.
+- [changed] Marked the public constructors for `ExecutableCodePart` and `CodeExecutionResultPart` as
+ deprecated.
+- [feature] Introduced `MissingPermissionsException`, which is thrown when the necessary permissions
+ have not been granted by the user.
+- [feature] Added helper functions to `LiveSession` to allow developers to track the status of the
+ audio session and the underlying websocket connection.
+- [changed] Added new values to `HarmCategory` (#7324)
+- [fixed] Fixed an issue that caused unknown or empty `Part`s to throw an exception. Instead, we now
+ log them and filter them from the response (#7333)
+
+# 17.2.0
+
+- [feature] Added support for returning thought summaries, which are synthesized versions of a
+ model's internal reasoning process.
+- [fixed] Fixed an issue causing the accessor methods in `GenerateContentResponse` to throw an
+ exception when the response contained no candidates.
+- [changed] Added better description for requests which fail due to the Gemini API not being
+ configured.
+- [changed] Added a `dilation` parameter to `ImagenMaskReference.generateMaskAndPadForOutpainting`
+ (#7260)
+- [feature] Added support for limited-use tokens with Firebase App Check. These limited-use tokens
+ are required for an upcoming optional feature called _replay protection_. We recommend
+ [enabling the usage of limited-use tokens](https://firebase.google.com/docs/ai-logic/app-check)
+ now so that when replay protection becomes available, you can enable it sooner because more of
+ your users will be on versions of your app that send limited-use tokens. (#7285)
+
+# 17.1.0
+
+=======
+
+- [feature] added support for Imagen Editing, including inpainting, outpainting, control, style
+ transfer, and subject references (#7075)
+- [feature] **Preview:** Added support for bidirectional streaming in Gemini Developer Api
+
+# 17.0.0
+
+- [feature] Added support for configuring the "thinking" budget when using Gemini 2.5 series models.
+ (#6990)
+- [feature] **Breaking Change**: Add support for grounding with Google Search (#7042).
+ - **Action Required:** Update all references of `groundingAttributions`, `webSearchQueries`,
+ `retrievalQueries` in `GroundingMetadata` to be non-optional.
+- [changed] require at least one argument for `generateContent()`, `generateContentStream()` and
+ `countTokens()`.
+- [feature] Added new overloads for `generateContent()`, `generateContentStream()` and
+ `countTokens()` that take a `List` parameter.
+- [changed] **Breaking Change**: Updated minSdkVersion to API level 23 or higher.
+
+# 16.2.0
+
+- [changed] Deprecate the `totalBillableCharacters` field (only usable with pre-2.0 models). (#7042)
+- [feature] Added support for extra schema properties like `title`, `minItems`, `maxItems`,
+ `minimum` and `maximum`. As well as support for the `anyOf` schema. (#7013)
+
+# 16.1.0
+
+- [fixed] Fixed `FirebaseAI.getInstance` StackOverflowException (#6971)
+- [fixed] Fixed an issue that was causing the SDK to send empty `FunctionDeclaration` descriptions
+ to the API.
+- [changed] Introduced the `Voice` class, which accepts a voice name, and deprecated the `Voices`
+ class.
+- [changed] **Breaking Change**: Updated `SpeechConfig` to take in `Voice` class instead of `Voices`
+ class.
+ - **Action Required:** Update all references of `SpeechConfig` initialization to use `Voice`
+ class.
+- [fixed] Fix incorrect model name in count token requests to the developer API backend
+
# 16.0.0
-* [feature] Initial release of the Firebase AI SDK (`firebase-ai`). This SDK *replaces* the previous
- Vertex AI in Firebase SDK (`firebase-vertexai`) to accommodate the evolving set of supported
- features and services.
- * The new Firebase AI SDK provides **Preview** support for the Gemini Developer API, including its
- free tier offering.
- * Using the Firebase AI SDK with the Vertex AI Gemini API is still generally available (GA).
-
- If you're using the old `firebase-vertexai`, we recommend
- [migrating to `firebase-ai`](/docs/ai-logic/migrate-to-latest-sdk)
- because all new development and features will be in this new SDK.
-* [feature] **Preview:** Added support for specifying response modalities in `GenerationConfig`
- (only available in the new `firebase-ai` package). This includes support for image generation using
- [specific Gemini models](/docs/vertex-ai/models).
-
- Note: This feature is in Public Preview, which means that it is not subject to any SLA or
- deprecation policy and could change in backwards-incompatible ways.
+- [feature] Initial release of the Firebase AI SDK (`firebase-ai`). This SDK _replaces_ the previous
+ Vertex AI in Firebase SDK (`firebase-vertexai`) to accommodate the evolving set of supported
+ features and services.
+ - The new Firebase AI SDK provides **Preview** support for the Gemini Developer API, including its
+ free tier offering.
+ - Using the Firebase AI SDK with the Vertex AI Gemini API is still generally available (GA).
+
+If you're using the old `firebase-vertexai`, we recommend
+[migrating to `firebase-ai`](/docs/ai-logic/migrate-to-latest-sdk) because all new development and
+features will be in this new SDK.
+
+- [feature] **Preview:** Added support for specifying response modalities in `GenerationConfig`
+ (only available in the new `firebase-ai` package). This includes support for image generation
+ using [specific Gemini models](/docs/vertex-ai/models).
+
+Note: This feature is in Public Preview, which means that it is not subject to any SLA or
+deprecation policy and could change in backwards-incompatible ways.
diff --git a/firebase-ai/README.md b/firebase-ai/README.md
index e09f65c6092..2572fd8da18 100644
--- a/firebase-ai/README.md
+++ b/firebase-ai/README.md
@@ -1,7 +1,7 @@
# Firebase AI SDK
-For developer documentation, please visit https://firebase.google.com/docs/vertex-ai.
-This README is for contributors building and running tests for the SDK.
+For developer documentation, please visit https://firebase.google.com/docs/vertex-ai. This README is
+for contributors building and running tests for the SDK.
## Building
@@ -11,9 +11,8 @@ All Gradle commands should be run from the root of this repository.
## Running Tests
-> [!IMPORTANT]
-> These unit tests require mock response files, which can be downloaded by running
-`./firebase-ai/update_responses.sh` from the root of this repository.
+> [!IMPORTANT] These unit tests require mock response files, which can be downloaded by running
+> `./firebase-ai/update_responses.sh` from the root of this repository.
Unit tests:
@@ -25,8 +24,8 @@ Integration tests, requiring a running and connected device (emulator or real):
## Code Formatting
-Format Kotlin code in this SDK in Android Studio using
-the [spotless plugin]([https://plugins.jetbrains.com/plugin/14912-ktfmt](https://github.com/diffplug/spotless)
-by running:
+Format Kotlin code in this SDK in Android Studio using the [spotless
+plugin]([https://plugins.jetbrains.com/plugin/14912-ktfmt](https://github.com/diffplug/spotless) by
+running:
`./gradlew firebase-ai:spotlessApply`
diff --git a/firebase-ai/api.txt b/firebase-ai/api.txt
index 5645b466110..f73c51d7112 100644
--- a/firebase-ai/api.txt
+++ b/firebase-ai/api.txt
@@ -23,12 +23,14 @@ package com.google.firebase.ai {
method public com.google.firebase.ai.GenerativeModel generativeModel(String modelName, com.google.firebase.ai.type.GenerationConfig? generationConfig = null, java.util.List? safetySettings = null, java.util.List? tools = null, com.google.firebase.ai.type.ToolConfig? toolConfig = null, com.google.firebase.ai.type.Content? systemInstruction = null, com.google.firebase.ai.type.RequestOptions requestOptions = com.google.firebase.ai.type.RequestOptions());
method public static com.google.firebase.ai.FirebaseAI getInstance();
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.ai.type.GenerativeBackend backend);
+ method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.ai.type.GenerativeBackend backend, boolean useLimitedUseAppCheckTokens);
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app);
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend);
- method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName);
- method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null);
- method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null, com.google.firebase.ai.type.ImagenSafetySettings? safetySettings = null);
- method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null, com.google.firebase.ai.type.ImagenSafetySettings? safetySettings = null, com.google.firebase.ai.type.RequestOptions requestOptions = com.google.firebase.ai.type.RequestOptions());
+ method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend, boolean useLimitedUseAppCheckTokens);
+ method public com.google.firebase.ai.ImagenModel imagenModel(String modelName);
+ method public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null);
+ method public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null, com.google.firebase.ai.type.ImagenSafetySettings? safetySettings = null);
+ method public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null, com.google.firebase.ai.type.ImagenSafetySettings? safetySettings = null, com.google.firebase.ai.type.RequestOptions requestOptions = com.google.firebase.ai.type.RequestOptions());
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.LiveGenerativeModel liveModel(String modelName);
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.LiveGenerativeModel liveModel(String modelName, com.google.firebase.ai.type.LiveGenerationConfig? generationConfig = null);
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.LiveGenerativeModel liveModel(String modelName, com.google.firebase.ai.type.LiveGenerationConfig? generationConfig = null, java.util.List? tools = null);
@@ -41,31 +43,40 @@ package com.google.firebase.ai {
public static final class FirebaseAI.Companion {
method public com.google.firebase.ai.FirebaseAI getInstance();
method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.ai.type.GenerativeBackend backend);
+ method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.ai.type.GenerativeBackend backend, boolean useLimitedUseAppCheckTokens);
method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app);
method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend);
+ method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend, boolean useLimitedUseAppCheckTokens);
property public final com.google.firebase.ai.FirebaseAI instance;
}
public final class FirebaseAIKt {
method public static com.google.firebase.ai.FirebaseAI ai(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend = GenerativeBackend.googleAI());
+ method public static com.google.firebase.ai.FirebaseAI ai(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend = GenerativeBackend.googleAI(), boolean useLimitedUseAppCheckTokens);
method public static com.google.firebase.ai.FirebaseAI getAi(com.google.firebase.Firebase);
}
public final class GenerativeModel {
method public suspend Object? countTokens(android.graphics.Bitmap prompt, kotlin.coroutines.Continuation super com.google.firebase.ai.type.CountTokensResponse>);
- method public suspend Object? countTokens(com.google.firebase.ai.type.Content[] prompt, kotlin.coroutines.Continuation super com.google.firebase.ai.type.CountTokensResponse>);
+ method public suspend Object? countTokens(com.google.firebase.ai.type.Content prompt, com.google.firebase.ai.type.Content[] prompts, kotlin.coroutines.Continuation super com.google.firebase.ai.type.CountTokensResponse>);
method public suspend Object? countTokens(String prompt, kotlin.coroutines.Continuation super com.google.firebase.ai.type.CountTokensResponse>);
+ method public suspend Object? countTokens(java.util.List prompt, kotlin.coroutines.Continuation super com.google.firebase.ai.type.CountTokensResponse>);
method public suspend Object? generateContent(android.graphics.Bitmap prompt, kotlin.coroutines.Continuation super com.google.firebase.ai.type.GenerateContentResponse>);
- method public suspend Object? generateContent(com.google.firebase.ai.type.Content[] prompt, kotlin.coroutines.Continuation super com.google.firebase.ai.type.GenerateContentResponse>);
+ method public suspend Object? generateContent(com.google.firebase.ai.type.Content prompt, com.google.firebase.ai.type.Content[] prompts, kotlin.coroutines.Continuation super com.google.firebase.ai.type.GenerateContentResponse>);
method public suspend Object? generateContent(String prompt, kotlin.coroutines.Continuation super com.google.firebase.ai.type.GenerateContentResponse>);
+ method public suspend Object? generateContent(java.util.List prompt, kotlin.coroutines.Continuation super com.google.firebase.ai.type.GenerateContentResponse>);
method public kotlinx.coroutines.flow.Flow generateContentStream(android.graphics.Bitmap prompt);
- method public kotlinx.coroutines.flow.Flow generateContentStream(com.google.firebase.ai.type.Content... prompt);
+ method public kotlinx.coroutines.flow.Flow generateContentStream(com.google.firebase.ai.type.Content prompt, com.google.firebase.ai.type.Content... prompts);
method public kotlinx.coroutines.flow.Flow generateContentStream(String prompt);
+ method public kotlinx.coroutines.flow.Flow generateContentStream(java.util.List prompt);
method public com.google.firebase.ai.Chat startChat(java.util.List history = emptyList());
}
- @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenModel {
+ public final class ImagenModel {
+ method @com.google.firebase.ai.type.PublicPreviewAPI public suspend Object? editImage(java.util.List extends com.google.firebase.ai.type.ImagenReferenceImage> referenceImages, String prompt, com.google.firebase.ai.type.ImagenEditingConfig? config = null, kotlin.coroutines.Continuation super com.google.firebase.ai.type.ImagenGenerationResponse>);
method public suspend Object? generateImages(String prompt, kotlin.coroutines.Continuation super com.google.firebase.ai.type.ImagenGenerationResponse>);
+ method @com.google.firebase.ai.type.PublicPreviewAPI public suspend Object? inpaintImage(com.google.firebase.ai.type.ImagenInlineImage image, String prompt, com.google.firebase.ai.type.ImagenMaskReference mask, com.google.firebase.ai.type.ImagenEditingConfig config, kotlin.coroutines.Continuation super com.google.firebase.ai.type.ImagenGenerationResponse>);
+ method @com.google.firebase.ai.type.PublicPreviewAPI public suspend Object? outpaintImage(com.google.firebase.ai.type.ImagenInlineImage image, com.google.firebase.ai.type.Dimensions newDimensions, com.google.firebase.ai.type.ImagenImagePlacement newPosition = com.google.firebase.ai.type.ImagenImagePlacement.CENTER, String prompt = "", com.google.firebase.ai.type.ImagenEditingConfig? config = null, kotlin.coroutines.Continuation super com.google.firebase.ai.type.ImagenGenerationResponse>);
}
@com.google.firebase.ai.type.PublicPreviewAPI public final class LiveGenerativeModel {
@@ -89,10 +100,10 @@ package com.google.firebase.ai.java {
}
public abstract class GenerativeModelFutures {
- method public abstract com.google.common.util.concurrent.ListenableFuture countTokens(com.google.firebase.ai.type.Content... prompt);
+ method public abstract com.google.common.util.concurrent.ListenableFuture countTokens(com.google.firebase.ai.type.Content prompt, com.google.firebase.ai.type.Content... prompts);
method public static final com.google.firebase.ai.java.GenerativeModelFutures from(com.google.firebase.ai.GenerativeModel model);
- method public abstract com.google.common.util.concurrent.ListenableFuture generateContent(com.google.firebase.ai.type.Content... prompt);
- method public abstract org.reactivestreams.Publisher generateContentStream(com.google.firebase.ai.type.Content... prompt);
+ method public abstract com.google.common.util.concurrent.ListenableFuture generateContent(com.google.firebase.ai.type.Content prompt, com.google.firebase.ai.type.Content... prompts);
+ method public abstract org.reactivestreams.Publisher generateContentStream(com.google.firebase.ai.type.Content prompt, com.google.firebase.ai.type.Content... prompts);
method public abstract com.google.firebase.ai.GenerativeModel getGenerativeModel();
method public abstract com.google.firebase.ai.java.ChatFutures startChat();
method public abstract com.google.firebase.ai.java.ChatFutures startChat(java.util.List history);
@@ -104,9 +115,13 @@ package com.google.firebase.ai.java {
}
@com.google.firebase.ai.type.PublicPreviewAPI public abstract class ImagenModelFutures {
+ method public abstract com.google.common.util.concurrent.ListenableFuture> editImage(java.util.List extends com.google.firebase.ai.type.ImagenReferenceImage> referenceImages, String prompt);
+ method public abstract com.google.common.util.concurrent.ListenableFuture> editImage(java.util.List extends com.google.firebase.ai.type.ImagenReferenceImage> referenceImages, String prompt, com.google.firebase.ai.type.ImagenEditingConfig? config = null);
method public static final com.google.firebase.ai.java.ImagenModelFutures from(com.google.firebase.ai.ImagenModel model);
method public abstract com.google.common.util.concurrent.ListenableFuture> generateImages(String prompt);
method public abstract com.google.firebase.ai.ImagenModel getImageModel();
+ method public abstract com.google.common.util.concurrent.ListenableFuture> inpaintImage(com.google.firebase.ai.type.ImagenInlineImage image, String prompt, com.google.firebase.ai.type.ImagenMaskReference mask, com.google.firebase.ai.type.ImagenEditingConfig config);
+ method public abstract com.google.common.util.concurrent.ListenableFuture> outpaintImage(com.google.firebase.ai.type.ImagenInlineImage image, com.google.firebase.ai.type.Dimensions newDimensions, com.google.firebase.ai.type.ImagenImagePlacement newPosition = com.google.firebase.ai.type.ImagenImagePlacement.CENTER, String prompt = "", com.google.firebase.ai.type.ImagenEditingConfig? config = null);
field public static final com.google.firebase.ai.java.ImagenModelFutures.Companion Companion;
}
@@ -130,10 +145,18 @@ package com.google.firebase.ai.java {
method public abstract org.reactivestreams.Publisher receive();
method public abstract com.google.common.util.concurrent.ListenableFuture send(com.google.firebase.ai.type.Content content);
method public abstract com.google.common.util.concurrent.ListenableFuture send(String text);
+ method public abstract com.google.common.util.concurrent.ListenableFuture sendAudioRealtime(com.google.firebase.ai.type.InlineData audio);
method public abstract com.google.common.util.concurrent.ListenableFuture sendFunctionResponse(java.util.List functionList);
- method public abstract com.google.common.util.concurrent.ListenableFuture sendMediaStream(java.util.List mediaChunks);
+ method @Deprecated public abstract com.google.common.util.concurrent.ListenableFuture sendMediaStream(java.util.List mediaChunks);
+ method public abstract com.google.common.util.concurrent.ListenableFuture sendTextRealtime(String text);
+ method public abstract com.google.common.util.concurrent.ListenableFuture sendVideoRealtime(com.google.firebase.ai.type.InlineData video);
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture startAudioConversation();
- method public abstract com.google.common.util.concurrent.ListenableFuture startAudioConversation(kotlin.jvm.functions.Function1 super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler);
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture startAudioConversation(boolean enableInterruptions);
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture startAudioConversation(kotlin.jvm.functions.Function1 super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler);
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture startAudioConversation(kotlin.jvm.functions.Function1 super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler, boolean enableInterruptions);
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture startAudioConversation(kotlin.jvm.functions.Function1 super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler, kotlin.jvm.functions.Function2 super com.google.firebase.ai.type.Transcription?,? super com.google.firebase.ai.type.Transcription?,kotlin.Unit>? transcriptHandler, boolean enableInterruptions);
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture startAudioConversation(kotlin.jvm.functions.Function2 super com.google.firebase.ai.type.Transcription?,? super com.google.firebase.ai.type.Transcription?,kotlin.Unit>? transcriptHandler);
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture startAudioConversation(kotlin.jvm.functions.Function2 super com.google.firebase.ai.type.Transcription?,? super com.google.firebase.ai.type.Transcription?,kotlin.Unit>? transcriptHandler, boolean enableInterruptions);
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public abstract com.google.common.util.concurrent.ListenableFuture stopAudioConversation();
method public abstract void stopReceiving();
field public static final com.google.firebase.ai.java.LiveSessionFutures.Companion Companion;
@@ -147,10 +170,17 @@ package com.google.firebase.ai.java {
package com.google.firebase.ai.type {
+ public final class APINotConfiguredException extends com.google.firebase.ai.type.FirebaseAIException {
+ }
+
public final class AudioRecordInitializationFailedException extends com.google.firebase.ai.type.FirebaseAIException {
ctor public AudioRecordInitializationFailedException(String message);
}
+ public final class AudioTranscriptionConfig {
+ ctor public AudioTranscriptionConfig();
+ }
+
public final class BlockReason {
method public String getName();
method public int getOrdinal();
@@ -171,11 +201,15 @@ package com.google.firebase.ai.type {
method public com.google.firebase.ai.type.CitationMetadata? getCitationMetadata();
method public com.google.firebase.ai.type.Content getContent();
method public com.google.firebase.ai.type.FinishReason? getFinishReason();
+ method public com.google.firebase.ai.type.GroundingMetadata? getGroundingMetadata();
method public java.util.List getSafetyRatings();
+ method public com.google.firebase.ai.type.UrlContextMetadata? getUrlContextMetadata();
property public final com.google.firebase.ai.type.CitationMetadata? citationMetadata;
property public final com.google.firebase.ai.type.Content content;
property public final com.google.firebase.ai.type.FinishReason? finishReason;
+ property public final com.google.firebase.ai.type.GroundingMetadata? groundingMetadata;
property public final java.util.List safetyRatings;
+ property public final com.google.firebase.ai.type.UrlContextMetadata? urlContextMetadata;
}
public final class Citation {
@@ -198,6 +232,17 @@ package com.google.firebase.ai.type {
property public final java.util.List citations;
}
+ public final class CodeExecutionResultPart implements com.google.firebase.ai.type.Part {
+ ctor @Deprecated public CodeExecutionResultPart(String outcome, String output);
+ method public boolean executionSucceeded();
+ method public String getOutcome();
+ method public String getOutput();
+ method public boolean isThought();
+ property public boolean isThought;
+ property public final String outcome;
+ property public final String output;
+ }
+
public final class Content {
ctor public Content(String? role = "user", java.util.List extends com.google.firebase.ai.type.Part> parts);
ctor public Content(java.util.List extends com.google.firebase.ai.type.Part> parts);
@@ -245,22 +290,42 @@ package com.google.firebase.ai.type {
}
public final class CountTokensResponse {
- ctor public CountTokensResponse(int totalTokens, Integer? totalBillableCharacters = null, java.util.List promptTokensDetails = emptyList());
+ ctor public CountTokensResponse(int totalTokens, @Deprecated Integer? totalBillableCharacters = null, java.util.List promptTokensDetails = emptyList());
method public operator int component1();
method public operator Integer? component2();
method public operator java.util.List? component3();
method public java.util.List getPromptTokensDetails();
- method public Integer? getTotalBillableCharacters();
+ method @Deprecated public Integer? getTotalBillableCharacters();
method public int getTotalTokens();
property public final java.util.List promptTokensDetails;
- property public final Integer? totalBillableCharacters;
+ property @Deprecated public final Integer? totalBillableCharacters;
property public final int totalTokens;
}
+ public final class Dimensions {
+ ctor public Dimensions(int width, int height);
+ method public int getHeight();
+ method public int getWidth();
+ property public final int height;
+ property public final int width;
+ }
+
+ public final class ExecutableCodePart implements com.google.firebase.ai.type.Part {
+ ctor @Deprecated public ExecutableCodePart(String language, String code);
+ method public String getCode();
+ method public String getLanguage();
+ method public boolean isThought();
+ property public final String code;
+ property public boolean isThought;
+ property public final String language;
+ }
+
public final class FileDataPart implements com.google.firebase.ai.type.Part {
ctor public FileDataPart(String uri, String mimeType);
method public String getMimeType();
method public String getUri();
+ method public boolean isThought();
+ property public boolean isThought;
property public final String mimeType;
property public final String uri;
}
@@ -295,8 +360,10 @@ package com.google.firebase.ai.type {
method public java.util.Map getArgs();
method public String? getId();
method public String getName();
+ method public boolean isThought();
property public final java.util.Map args;
property public final String? id;
+ property public boolean isThought;
property public final String name;
}
@@ -325,7 +392,9 @@ package com.google.firebase.ai.type {
method public String? getId();
method public String getName();
method public kotlinx.serialization.json.JsonObject getResponse();
+ method public boolean isThought();
property public final String? id;
+ property public boolean isThought;
property public final String name;
property public final kotlinx.serialization.json.JsonObject response;
}
@@ -337,12 +406,14 @@ package com.google.firebase.ai.type {
method public java.util.List getInlineDataParts();
method public com.google.firebase.ai.type.PromptFeedback? getPromptFeedback();
method public String? getText();
+ method public String? getThoughtSummary();
method public com.google.firebase.ai.type.UsageMetadata? getUsageMetadata();
property public final java.util.List candidates;
property public final java.util.List functionCalls;
property public final java.util.List inlineDataParts;
property public final com.google.firebase.ai.type.PromptFeedback? promptFeedback;
property public final String? text;
+ property public final String? thoughtSummary;
property public final com.google.firebase.ai.type.UsageMetadata? usageMetadata;
}
@@ -362,6 +433,7 @@ package com.google.firebase.ai.type {
method public com.google.firebase.ai.type.GenerationConfig.Builder setResponseSchema(com.google.firebase.ai.type.Schema? responseSchema);
method public com.google.firebase.ai.type.GenerationConfig.Builder setStopSequences(java.util.List? stopSequences);
method public com.google.firebase.ai.type.GenerationConfig.Builder setTemperature(Float? temperature);
+ method public com.google.firebase.ai.type.GenerationConfig.Builder setThinkingConfig(com.google.firebase.ai.type.ThinkingConfig? thinkingConfig);
method public com.google.firebase.ai.type.GenerationConfig.Builder setTopK(Integer? topK);
method public com.google.firebase.ai.type.GenerationConfig.Builder setTopP(Float? topP);
field public Integer? candidateCount;
@@ -373,6 +445,7 @@ package com.google.firebase.ai.type {
field public com.google.firebase.ai.type.Schema? responseSchema;
field public java.util.List? stopSequences;
field public Float? temperature;
+ field public com.google.firebase.ai.type.ThinkingConfig? thinkingConfig;
field public Integer? topK;
field public Float? topP;
}
@@ -398,6 +471,48 @@ package com.google.firebase.ai.type {
method public com.google.firebase.ai.type.GenerativeBackend vertexAI(String location = "us-central1");
}
+ public final class GoogleSearch {
+ ctor public GoogleSearch();
+ }
+
+ @Deprecated public final class GroundingAttribution {
+ ctor @Deprecated public GroundingAttribution(com.google.firebase.ai.type.Segment segment, Float? confidenceScore);
+ method @Deprecated public Float? getConfidenceScore();
+ method @Deprecated public com.google.firebase.ai.type.Segment getSegment();
+ property @Deprecated public final Float? confidenceScore;
+ property @Deprecated public final com.google.firebase.ai.type.Segment segment;
+ }
+
+ public final class GroundingChunk {
+ ctor public GroundingChunk(com.google.firebase.ai.type.WebGroundingChunk? web);
+ method public com.google.firebase.ai.type.WebGroundingChunk? getWeb();
+ property public final com.google.firebase.ai.type.WebGroundingChunk? web;
+ }
+
+ public final class GroundingMetadata {
+ ctor public GroundingMetadata(java.util.List webSearchQueries, com.google.firebase.ai.type.SearchEntryPoint? searchEntryPoint, java.util.List retrievalQueries, @Deprecated java.util.List groundingAttribution, java.util.List groundingChunks, java.util.List groundingSupports);
+ method @Deprecated public java.util.List getGroundingAttribution();
+ method public java.util.List getGroundingChunks();
+ method public java.util.List getGroundingSupports();
+ method public java.util.List getRetrievalQueries();
+ method public com.google.firebase.ai.type.SearchEntryPoint? getSearchEntryPoint();
+ method public java.util.List getWebSearchQueries();
+ property @Deprecated public final java.util.List groundingAttribution;
+ property public final java.util.List groundingChunks;
+ property public final java.util.List groundingSupports;
+ property public final java.util.List retrievalQueries;
+ property public final com.google.firebase.ai.type.SearchEntryPoint? searchEntryPoint;
+ property public final java.util.List webSearchQueries;
+ }
+
+ public final class GroundingSupport {
+ ctor public GroundingSupport(com.google.firebase.ai.type.Segment segment, java.util.List groundingChunkIndices);
+ method public java.util.List getGroundingChunkIndices();
+ method public com.google.firebase.ai.type.Segment getSegment();
+ property public final java.util.List groundingChunkIndices;
+ property public final com.google.firebase.ai.type.Segment segment;
+ }
+
public final class HarmBlockMethod {
method public int getOrdinal();
property public final int ordinal;
@@ -431,6 +546,10 @@ package com.google.firebase.ai.type {
field public static final com.google.firebase.ai.type.HarmCategory DANGEROUS_CONTENT;
field public static final com.google.firebase.ai.type.HarmCategory HARASSMENT;
field public static final com.google.firebase.ai.type.HarmCategory HATE_SPEECH;
+ field public static final com.google.firebase.ai.type.HarmCategory IMAGE_DANGEROUS_CONTENT;
+ field public static final com.google.firebase.ai.type.HarmCategory IMAGE_HARASSMENT;
+ field public static final com.google.firebase.ai.type.HarmCategory IMAGE_HATE;
+ field public static final com.google.firebase.ai.type.HarmCategory IMAGE_SEXUALLY_EXPLICIT;
field public static final com.google.firebase.ai.type.HarmCategory SEXUALLY_EXPLICIT;
field public static final com.google.firebase.ai.type.HarmCategory UNKNOWN;
}
@@ -469,10 +588,12 @@ package com.google.firebase.ai.type {
public final class ImagePart implements com.google.firebase.ai.type.Part {
ctor public ImagePart(android.graphics.Bitmap image);
method public android.graphics.Bitmap getImage();
+ method public boolean isThought();
property public final android.graphics.Bitmap image;
+ property public boolean isThought;
}
- @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenAspectRatio {
+ public final class ImagenAspectRatio {
field public static final com.google.firebase.ai.type.ImagenAspectRatio.Companion Companion;
field public static final com.google.firebase.ai.type.ImagenAspectRatio LANDSCAPE_16x9;
field public static final com.google.firebase.ai.type.ImagenAspectRatio LANDSCAPE_4x3;
@@ -484,7 +605,44 @@ package com.google.firebase.ai.type {
public static final class ImagenAspectRatio.Companion {
}
- @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenGenerationConfig {
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenBackgroundMask extends com.google.firebase.ai.type.ImagenMaskReference {
+ ctor public ImagenBackgroundMask(Double? dilation = null);
+ }
+
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenControlReference extends com.google.firebase.ai.type.ImagenReferenceImage {
+ ctor public ImagenControlReference(com.google.firebase.ai.type.ImagenControlType type, com.google.firebase.ai.type.ImagenInlineImage? image = null, Integer? referenceId = null, Boolean? enableComputation = null, Integer? superpixelRegionSize = null, Integer? superpixelRuler = null);
+ }
+
+ public final class ImagenControlType {
+ field public static final com.google.firebase.ai.type.ImagenControlType CANNY;
+ field public static final com.google.firebase.ai.type.ImagenControlType COLOR_SUPERPIXEL;
+ field public static final com.google.firebase.ai.type.ImagenControlType.Companion Companion;
+ field public static final com.google.firebase.ai.type.ImagenControlType FACE_MESH;
+ field public static final com.google.firebase.ai.type.ImagenControlType SCRIBBLE;
+ }
+
+ public static final class ImagenControlType.Companion {
+ }
+
+ public final class ImagenEditMode {
+ field public static final com.google.firebase.ai.type.ImagenEditMode.Companion Companion;
+ field public static final com.google.firebase.ai.type.ImagenEditMode INPAINT_INSERTION;
+ field public static final com.google.firebase.ai.type.ImagenEditMode INPAINT_REMOVAL;
+ field public static final com.google.firebase.ai.type.ImagenEditMode OUTPAINT;
+ }
+
+ public static final class ImagenEditMode.Companion {
+ }
+
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenEditingConfig {
+ ctor public ImagenEditingConfig(com.google.firebase.ai.type.ImagenEditMode? editMode = null, Integer? editSteps = null);
+ }
+
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenForegroundMask extends com.google.firebase.ai.type.ImagenMaskReference {
+ ctor public ImagenForegroundMask(Double? dilation = null);
+ }
+
+ public final class ImagenGenerationConfig {
ctor public ImagenGenerationConfig(String? negativePrompt = null, Integer? numberOfImages = 1, com.google.firebase.ai.type.ImagenAspectRatio? aspectRatio = null, com.google.firebase.ai.type.ImagenImageFormat? imageFormat = null, Boolean? addWatermark = null);
method public Boolean? getAddWatermark();
method public com.google.firebase.ai.type.ImagenAspectRatio? getAspectRatio();
@@ -519,17 +677,17 @@ package com.google.firebase.ai.type {
}
public final class ImagenGenerationConfigKt {
- method @com.google.firebase.ai.type.PublicPreviewAPI public static com.google.firebase.ai.type.ImagenGenerationConfig imagenGenerationConfig(kotlin.jvm.functions.Function1 super com.google.firebase.ai.type.ImagenGenerationConfig.Builder,kotlin.Unit> init);
+ method public static com.google.firebase.ai.type.ImagenGenerationConfig imagenGenerationConfig(kotlin.jvm.functions.Function1 super com.google.firebase.ai.type.ImagenGenerationConfig.Builder,kotlin.Unit> init);
}
- @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenGenerationResponse {
+ public final class ImagenGenerationResponse {
method public String? getFilteredReason();
method public java.util.List getImages();
property public final String? filteredReason;
property public final java.util.List images;
}
- @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenImageFormat {
+ public final class ImagenImageFormat {
method public Integer? getCompressionQuality();
method public String getMimeType();
method public static com.google.firebase.ai.type.ImagenImageFormat jpeg(Integer? compressionQuality = null);
@@ -544,7 +702,29 @@ package com.google.firebase.ai.type {
method public com.google.firebase.ai.type.ImagenImageFormat png();
}
- @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenInlineImage {
+ public final class ImagenImagePlacement {
+ method public static com.google.firebase.ai.type.ImagenImagePlacement fromCoordinate(int x, int y);
+ method public Integer? getX();
+ method public Integer? getY();
+ property public final Integer? x;
+ property public final Integer? y;
+ field public static final com.google.firebase.ai.type.ImagenImagePlacement BOTTOM_CENTER;
+ field public static final com.google.firebase.ai.type.ImagenImagePlacement BOTTOM_LEFT;
+ field public static final com.google.firebase.ai.type.ImagenImagePlacement BOTTOM_RIGHT;
+ field public static final com.google.firebase.ai.type.ImagenImagePlacement CENTER;
+ field public static final com.google.firebase.ai.type.ImagenImagePlacement.Companion Companion;
+ field public static final com.google.firebase.ai.type.ImagenImagePlacement LEFT_CENTER;
+ field public static final com.google.firebase.ai.type.ImagenImagePlacement RIGHT_CENTER;
+ field public static final com.google.firebase.ai.type.ImagenImagePlacement TOP_CENTER;
+ field public static final com.google.firebase.ai.type.ImagenImagePlacement TOP_LEFT;
+ field public static final com.google.firebase.ai.type.ImagenImagePlacement TOP_RIGHT;
+ }
+
+ public static final class ImagenImagePlacement.Companion {
+ method public com.google.firebase.ai.type.ImagenImagePlacement fromCoordinate(int x, int y);
+ }
+
+ public final class ImagenInlineImage {
method public android.graphics.Bitmap asBitmap();
method public byte[] getData();
method public String getMimeType();
@@ -552,7 +732,24 @@ package com.google.firebase.ai.type {
property public final String mimeType;
}
- @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenPersonFilterLevel {
+ public final class ImagenInlineImageKt {
+ method @com.google.firebase.ai.type.PublicPreviewAPI public static com.google.firebase.ai.type.ImagenInlineImage toImagenInlineImage(android.graphics.Bitmap);
+ }
+
+ @com.google.firebase.ai.type.PublicPreviewAPI public abstract class ImagenMaskReference extends com.google.firebase.ai.type.ImagenReferenceImage {
+ method public static final java.util.List generateMaskAndPadForOutpainting(com.google.firebase.ai.type.ImagenInlineImage image, com.google.firebase.ai.type.Dimensions newDimensions);
+ method public static final java.util.List generateMaskAndPadForOutpainting(com.google.firebase.ai.type.ImagenInlineImage image, com.google.firebase.ai.type.Dimensions newDimensions, com.google.firebase.ai.type.ImagenImagePlacement newPosition = com.google.firebase.ai.type.ImagenImagePlacement.CENTER);
+ method public static final java.util.List generateMaskAndPadForOutpainting(com.google.firebase.ai.type.ImagenInlineImage image, com.google.firebase.ai.type.Dimensions newDimensions, com.google.firebase.ai.type.ImagenImagePlacement newPosition = com.google.firebase.ai.type.ImagenImagePlacement.CENTER, double dilation = 0.01);
+ field public static final com.google.firebase.ai.type.ImagenMaskReference.Companion Companion;
+ }
+
+ public static final class ImagenMaskReference.Companion {
+ method public java.util.List generateMaskAndPadForOutpainting(com.google.firebase.ai.type.ImagenInlineImage image, com.google.firebase.ai.type.Dimensions newDimensions);
+ method public java.util.List generateMaskAndPadForOutpainting(com.google.firebase.ai.type.ImagenInlineImage image, com.google.firebase.ai.type.Dimensions newDimensions, com.google.firebase.ai.type.ImagenImagePlacement newPosition = com.google.firebase.ai.type.ImagenImagePlacement.CENTER);
+ method public java.util.List generateMaskAndPadForOutpainting(com.google.firebase.ai.type.ImagenInlineImage image, com.google.firebase.ai.type.Dimensions newDimensions, com.google.firebase.ai.type.ImagenImagePlacement newPosition = com.google.firebase.ai.type.ImagenImagePlacement.CENTER, double dilation = 0.01);
+ }
+
+ public final class ImagenPersonFilterLevel {
field public static final com.google.firebase.ai.type.ImagenPersonFilterLevel ALLOW_ADULT;
field public static final com.google.firebase.ai.type.ImagenPersonFilterLevel ALLOW_ALL;
field public static final com.google.firebase.ai.type.ImagenPersonFilterLevel BLOCK_ALL;
@@ -562,7 +759,22 @@ package com.google.firebase.ai.type {
public static final class ImagenPersonFilterLevel.Companion {
}
- @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenSafetyFilterLevel {
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenRawImage extends com.google.firebase.ai.type.ImagenReferenceImage {
+ ctor public ImagenRawImage(com.google.firebase.ai.type.ImagenInlineImage image);
+ }
+
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenRawMask extends com.google.firebase.ai.type.ImagenMaskReference {
+ ctor public ImagenRawMask(com.google.firebase.ai.type.ImagenInlineImage mask, Double? dilation = null);
+ }
+
+ @com.google.firebase.ai.type.PublicPreviewAPI public abstract class ImagenReferenceImage {
+ method public final com.google.firebase.ai.type.ImagenInlineImage? getImage();
+ method public final Integer? getReferenceId();
+ property public final com.google.firebase.ai.type.ImagenInlineImage? image;
+ property public final Integer? referenceId;
+ }
+
+ public final class ImagenSafetyFilterLevel {
field public static final com.google.firebase.ai.type.ImagenSafetyFilterLevel BLOCK_LOW_AND_ABOVE;
field public static final com.google.firebase.ai.type.ImagenSafetyFilterLevel BLOCK_MEDIUM_AND_ABOVE;
field public static final com.google.firebase.ai.type.ImagenSafetyFilterLevel BLOCK_NONE;
@@ -573,15 +785,47 @@ package com.google.firebase.ai.type {
public static final class ImagenSafetyFilterLevel.Companion {
}
- @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenSafetySettings {
+ public final class ImagenSafetySettings {
ctor public ImagenSafetySettings(com.google.firebase.ai.type.ImagenSafetyFilterLevel safetyFilterLevel, com.google.firebase.ai.type.ImagenPersonFilterLevel personFilterLevel);
}
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenSemanticMask extends com.google.firebase.ai.type.ImagenMaskReference {
+ ctor public ImagenSemanticMask(java.util.List classes, Double? dilation = null);
+ }
+
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenStyleReference extends com.google.firebase.ai.type.ImagenReferenceImage {
+ ctor public ImagenStyleReference(com.google.firebase.ai.type.ImagenInlineImage image, Integer? referenceId = null, String? description = null);
+ }
+
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class ImagenSubjectReference extends com.google.firebase.ai.type.ImagenReferenceImage {
+ ctor public ImagenSubjectReference(com.google.firebase.ai.type.ImagenInlineImage image, Integer? referenceId = null, String? description = null, com.google.firebase.ai.type.ImagenSubjectReferenceType? subjectType = null);
+ }
+
+ public final class ImagenSubjectReferenceType {
+ field public static final com.google.firebase.ai.type.ImagenSubjectReferenceType ANIMAL;
+ field public static final com.google.firebase.ai.type.ImagenSubjectReferenceType.Companion Companion;
+ field public static final com.google.firebase.ai.type.ImagenSubjectReferenceType PERSON;
+ field public static final com.google.firebase.ai.type.ImagenSubjectReferenceType PRODUCT;
+ }
+
+ public static final class ImagenSubjectReferenceType.Companion {
+ }
+
+ public final class InlineData {
+ ctor public InlineData(byte[] data, String mimeType);
+ method public byte[] getData();
+ method public String getMimeType();
+ property public final byte[] data;
+ property public final String mimeType;
+ }
+
public final class InlineDataPart implements com.google.firebase.ai.type.Part {
ctor public InlineDataPart(byte[] inlineData, String mimeType);
method public byte[] getInlineData();
method public String getMimeType();
+ method public boolean isThought();
property public final byte[] inlineData;
+ property public boolean isThought;
property public final String mimeType;
}
@@ -601,18 +845,20 @@ package com.google.firebase.ai.type {
public static final class LiveGenerationConfig.Builder {
ctor public LiveGenerationConfig.Builder();
method public com.google.firebase.ai.type.LiveGenerationConfig build();
- method public com.google.firebase.ai.type.LiveGenerationConfig.Builder setCandidateCount(Integer? candidateCount);
method public com.google.firebase.ai.type.LiveGenerationConfig.Builder setFrequencyPenalty(Float? frequencyPenalty);
+ method public com.google.firebase.ai.type.LiveGenerationConfig.Builder setInputAudioTranscription(com.google.firebase.ai.type.AudioTranscriptionConfig? config);
method public com.google.firebase.ai.type.LiveGenerationConfig.Builder setMaxOutputTokens(Integer? maxOutputTokens);
+ method public com.google.firebase.ai.type.LiveGenerationConfig.Builder setOutputAudioTranscription(com.google.firebase.ai.type.AudioTranscriptionConfig? config);
method public com.google.firebase.ai.type.LiveGenerationConfig.Builder setPresencePenalty(Float? presencePenalty);
method public com.google.firebase.ai.type.LiveGenerationConfig.Builder setResponseModality(com.google.firebase.ai.type.ResponseModality? responseModality);
method public com.google.firebase.ai.type.LiveGenerationConfig.Builder setSpeechConfig(com.google.firebase.ai.type.SpeechConfig? speechConfig);
method public com.google.firebase.ai.type.LiveGenerationConfig.Builder setTemperature(Float? temperature);
method public com.google.firebase.ai.type.LiveGenerationConfig.Builder setTopK(Integer? topK);
method public com.google.firebase.ai.type.LiveGenerationConfig.Builder setTopP(Float? topP);
- field public Integer? candidateCount;
field public Float? frequencyPenalty;
+ field public com.google.firebase.ai.type.AudioTranscriptionConfig? inputAudioTranscription;
field public Integer? maxOutputTokens;
+ field public com.google.firebase.ai.type.AudioTranscriptionConfig? outputAudioTranscription;
field public Float? presencePenalty;
field public com.google.firebase.ai.type.ResponseModality? responseModality;
field public com.google.firebase.ai.type.SpeechConfig? speechConfig;
@@ -630,14 +876,18 @@ package com.google.firebase.ai.type {
}
@com.google.firebase.ai.type.PublicPreviewAPI public final class LiveServerContent implements com.google.firebase.ai.type.LiveServerMessage {
- ctor public LiveServerContent(com.google.firebase.ai.type.Content? content, boolean interrupted, boolean turnComplete, boolean generationComplete);
+ ctor @Deprecated public LiveServerContent(com.google.firebase.ai.type.Content? content, boolean interrupted, boolean turnComplete, boolean generationComplete, com.google.firebase.ai.type.Transcription? inputTranscription, com.google.firebase.ai.type.Transcription? outputTranscription);
method public com.google.firebase.ai.type.Content? getContent();
method public boolean getGenerationComplete();
+ method public com.google.firebase.ai.type.Transcription? getInputTranscription();
method public boolean getInterrupted();
+ method public com.google.firebase.ai.type.Transcription? getOutputTranscription();
method public boolean getTurnComplete();
property public final com.google.firebase.ai.type.Content? content;
property public final boolean generationComplete;
+ property public final com.google.firebase.ai.type.Transcription? inputTranscription;
property public final boolean interrupted;
+ property public final com.google.firebase.ai.type.Transcription? outputTranscription;
property public final boolean turnComplete;
}
@@ -662,22 +912,29 @@ package com.google.firebase.ai.type {
@com.google.firebase.ai.type.PublicPreviewAPI public final class LiveSession {
method public suspend Object? close(kotlin.coroutines.Continuation super kotlin.Unit>);
+ method public boolean isAudioConversationActive();
+ method public boolean isClosed();
method public kotlinx.coroutines.flow.Flow receive();
method public suspend Object? send(com.google.firebase.ai.type.Content content, kotlin.coroutines.Continuation super kotlin.Unit>);
method public suspend Object? send(String text, kotlin.coroutines.Continuation super kotlin.Unit>);
+ method public suspend Object? sendAudioRealtime(com.google.firebase.ai.type.InlineData audio, kotlin.coroutines.Continuation super kotlin.Unit>);
method public suspend Object? sendFunctionResponse(java.util.List functionList, kotlin.coroutines.Continuation super kotlin.Unit>);
- method public suspend Object? sendMediaStream(java.util.List mediaChunks, kotlin.coroutines.Continuation super kotlin.Unit>);
+ method @Deprecated public suspend Object? sendMediaStream(java.util.List mediaChunks, kotlin.coroutines.Continuation super kotlin.Unit>);
+ method public suspend Object? sendTextRealtime(String text, kotlin.coroutines.Continuation super kotlin.Unit>);
+ method public suspend Object? sendVideoRealtime(com.google.firebase.ai.type.InlineData video, kotlin.coroutines.Continuation super kotlin.Unit>);
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public suspend Object? startAudioConversation(kotlin.jvm.functions.Function1 super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler = null, boolean enableInterruptions = false, kotlin.coroutines.Continuation super kotlin.Unit>);
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public suspend Object? startAudioConversation(kotlin.jvm.functions.Function1 super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler = null, kotlin.coroutines.Continuation super kotlin.Unit>);
+ method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public suspend Object? startAudioConversation(kotlin.jvm.functions.Function1 super com.google.firebase.ai.type.FunctionCallPart,com.google.firebase.ai.type.FunctionResponsePart>? functionCallHandler = null, kotlin.jvm.functions.Function2 super com.google.firebase.ai.type.Transcription?,? super com.google.firebase.ai.type.Transcription?,kotlin.Unit>? transcriptHandler = null, boolean enableInterruptions = false, kotlin.coroutines.Continuation super kotlin.Unit>);
method public void stopAudioConversation();
method public void stopReceiving();
}
- @com.google.firebase.ai.type.PublicPreviewAPI public final class MediaData {
- ctor public MediaData(byte[] data, String mimeType);
- method public byte[] getData();
- method public String getMimeType();
- property public final byte[] data;
- property public final String mimeType;
+ @Deprecated @com.google.firebase.ai.type.PublicPreviewAPI public final class MediaData {
+ ctor @Deprecated public MediaData(byte[] data, String mimeType);
+ method @Deprecated public byte[] getData();
+ method @Deprecated public String getMimeType();
+ property @Deprecated public final byte[] data;
+ property @Deprecated public final String mimeType;
}
public final class ModalityTokenCount {
@@ -690,6 +947,8 @@ package com.google.firebase.ai.type {
}
public interface Part {
+ method public boolean isThought();
+ property public abstract boolean isThought;
}
public final class PartKt {
@@ -699,6 +958,10 @@ package com.google.firebase.ai.type {
method public static String? asTextOrNull(com.google.firebase.ai.type.Part);
}
+ public final class PermissionMissingException extends com.google.firebase.ai.type.FirebaseAIException {
+ ctor public PermissionMissingException(String message, Throwable? cause = null);
+ }
+
public final class PromptBlockedException extends com.google.firebase.ai.type.FirebaseAIException {
method public com.google.firebase.ai.type.GenerateContentResponse? getResponse();
property public final com.google.firebase.ai.type.GenerateContentResponse? response;
@@ -714,7 +977,7 @@ package com.google.firebase.ai.type {
property public final java.util.List safetyRatings;
}
- @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR, message="This API is part of an experimental public preview and may change in " + "backwards-incompatible ways without notice.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface PublicPreviewAPI {
+ @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR, message="This API is part of an experimental public preview and may change in " + "backwards-incompatible ways without notice.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface PublicPreviewAPI {
}
public final class QuotaExceededException extends com.google.firebase.ai.type.FirebaseAIException {
@@ -765,84 +1028,156 @@ package com.google.firebase.ai.type {
}
public final class Schema {
+ method public static com.google.firebase.ai.type.Schema anyOf(java.util.List schemas);
method public static com.google.firebase.ai.type.Schema array(com.google.firebase.ai.type.Schema items);
method public static com.google.firebase.ai.type.Schema array(com.google.firebase.ai.type.Schema items, String? description = null);
method public static com.google.firebase.ai.type.Schema array(com.google.firebase.ai.type.Schema items, String? description = null, boolean nullable = false);
+ method public static com.google.firebase.ai.type.Schema array(com.google.firebase.ai.type.Schema items, String? description = null, boolean nullable = false, String? title = null);
+ method public static com.google.firebase.ai.type.Schema array(com.google.firebase.ai.type.Schema items, String? description = null, boolean nullable = false, String? title = null, Integer? minItems = null);
+ method public static com.google.firebase.ai.type.Schema array(com.google.firebase.ai.type.Schema items, String? description = null, boolean nullable = false, String? title = null, Integer? minItems = null, Integer? maxItems = null);
method public static com.google.firebase.ai.type.Schema boolean();
method public static com.google.firebase.ai.type.Schema boolean(String? description = null);
method public static com.google.firebase.ai.type.Schema boolean(String? description = null, boolean nullable = false);
+ method public static com.google.firebase.ai.type.Schema boolean(String? description = null, boolean nullable = false, String? title = null);
method public static com.google.firebase.ai.type.Schema enumeration(java.util.List values);
method public static com.google.firebase.ai.type.Schema enumeration(java.util.List values, String? description = null);
method public static com.google.firebase.ai.type.Schema enumeration(java.util.List values, String? description = null, boolean nullable = false);
+ method public static com.google.firebase.ai.type.Schema enumeration(java.util.List values, String? description = null, boolean nullable = false, String? title = null);
+ method public java.util.List? getAnyOf();
method public String? getDescription();
method public java.util.List? getEnum();
method public String? getFormat();
method public com.google.firebase.ai.type.Schema? getItems();
+ method public Integer? getMaxItems();
+ method public Double? getMaximum();
+ method public Integer? getMinItems();
+ method public Double? getMinimum();
method public Boolean? getNullable();
method public java.util.Map? getProperties();
method public java.util.List? getRequired();
+ method public String? getTitle();
method public String getType();
method public static com.google.firebase.ai.type.Schema numDouble();
method public static com.google.firebase.ai.type.Schema numDouble(String? description = null);
method public static com.google.firebase.ai.type.Schema numDouble(String? description = null, boolean nullable = false);
+ method public static com.google.firebase.ai.type.Schema numDouble(String? description = null, boolean nullable = false, String? title = null);
+ method public static com.google.firebase.ai.type.Schema numDouble(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null);
+ method public static com.google.firebase.ai.type.Schema numDouble(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null, Double? maximum = null);
method public static com.google.firebase.ai.type.Schema numFloat();
method public static com.google.firebase.ai.type.Schema numFloat(String? description = null);
method public static com.google.firebase.ai.type.Schema numFloat(String? description = null, boolean nullable = false);
+ method public static com.google.firebase.ai.type.Schema numFloat(String? description = null, boolean nullable = false, String? title = null);
+ method public static com.google.firebase.ai.type.Schema numFloat(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null);
+ method public static com.google.firebase.ai.type.Schema numFloat(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null, Double? maximum = null);
method public static com.google.firebase.ai.type.Schema numInt();
method public static com.google.firebase.ai.type.Schema numInt(String? description = null);
method public static com.google.firebase.ai.type.Schema numInt(String? description = null, boolean nullable = false);
+ method public static com.google.firebase.ai.type.Schema numInt(String? description = null, boolean nullable = false, String? title = null);
+ method public static com.google.firebase.ai.type.Schema numInt(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null);
+ method public static com.google.firebase.ai.type.Schema numInt(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null, Double? maximum = null);
method public static com.google.firebase.ai.type.Schema numLong();
method public static com.google.firebase.ai.type.Schema numLong(String? description = null);
method public static com.google.firebase.ai.type.Schema numLong(String? description = null, boolean nullable = false);
+ method public static com.google.firebase.ai.type.Schema numLong(String? description = null, boolean nullable = false, String? title = null);
+ method public static com.google.firebase.ai.type.Schema numLong(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null);
+ method public static com.google.firebase.ai.type.Schema numLong(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null, Double? maximum = null);
method public static com.google.firebase.ai.type.Schema obj(java.util.Map properties);
method public static com.google.firebase.ai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList());
method public static com.google.firebase.ai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList(), String? description = null);
method public static com.google.firebase.ai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList(), String? description = null, boolean nullable = false);
+ method public static com.google.firebase.ai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList(), String? description = null, boolean nullable = false, String? title = null);
method public static com.google.firebase.ai.type.Schema str();
method public static com.google.firebase.ai.type.Schema str(String? description = null);
method public static com.google.firebase.ai.type.Schema str(String? description = null, boolean nullable = false);
method public static com.google.firebase.ai.type.Schema str(String? description = null, boolean nullable = false, com.google.firebase.ai.type.StringFormat? format = null);
+ method public static com.google.firebase.ai.type.Schema str(String? description = null, boolean nullable = false, com.google.firebase.ai.type.StringFormat? format = null, String? title = null);
+ property public final java.util.List? anyOf;
property public final String? description;
property public final java.util.List? enum;
property public final String? format;
property public final com.google.firebase.ai.type.Schema? items;
+ property public final Integer? maxItems;
+ property public final Double? maximum;
+ property public final Integer? minItems;
+ property public final Double? minimum;
property public final Boolean? nullable;
property public final java.util.Map? properties;
property public final java.util.List? required;
+ property public final String? title;
property public final String type;
field public static final com.google.firebase.ai.type.Schema.Companion Companion;
}
public static final class Schema.Companion {
+ method public com.google.firebase.ai.type.Schema anyOf(java.util.List schemas);
method public com.google.firebase.ai.type.Schema array(com.google.firebase.ai.type.Schema items);
method public com.google.firebase.ai.type.Schema array(com.google.firebase.ai.type.Schema items, String? description = null);
method public com.google.firebase.ai.type.Schema array(com.google.firebase.ai.type.Schema items, String? description = null, boolean nullable = false);
+ method public com.google.firebase.ai.type.Schema array(com.google.firebase.ai.type.Schema items, String? description = null, boolean nullable = false, String? title = null);
+ method public com.google.firebase.ai.type.Schema array(com.google.firebase.ai.type.Schema items, String? description = null, boolean nullable = false, String? title = null, Integer? minItems = null);
+ method public com.google.firebase.ai.type.Schema array(com.google.firebase.ai.type.Schema items, String? description = null, boolean nullable = false, String? title = null, Integer? minItems = null, Integer? maxItems = null);
method public com.google.firebase.ai.type.Schema boolean();
method public com.google.firebase.ai.type.Schema boolean(String? description = null);
method public com.google.firebase.ai.type.Schema boolean(String? description = null, boolean nullable = false);
+ method public com.google.firebase.ai.type.Schema boolean(String? description = null, boolean nullable = false, String? title = null);
method public com.google.firebase.ai.type.Schema enumeration(java.util.List values);
method public com.google.firebase.ai.type.Schema enumeration(java.util.List values, String? description = null);
method public com.google.firebase.ai.type.Schema enumeration(java.util.List values, String? description = null, boolean nullable = false);
+ method public com.google.firebase.ai.type.Schema enumeration(java.util.List values, String? description = null, boolean nullable = false, String? title = null);
method public com.google.firebase.ai.type.Schema numDouble();
method public com.google.firebase.ai.type.Schema numDouble(String? description = null);
method public com.google.firebase.ai.type.Schema numDouble(String? description = null, boolean nullable = false);
+ method public com.google.firebase.ai.type.Schema numDouble(String? description = null, boolean nullable = false, String? title = null);
+ method public com.google.firebase.ai.type.Schema numDouble(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null);
+ method public com.google.firebase.ai.type.Schema numDouble(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null, Double? maximum = null);
method public com.google.firebase.ai.type.Schema numFloat();
method public com.google.firebase.ai.type.Schema numFloat(String? description = null);
method public com.google.firebase.ai.type.Schema numFloat(String? description = null, boolean nullable = false);
+ method public com.google.firebase.ai.type.Schema numFloat(String? description = null, boolean nullable = false, String? title = null);
+ method public com.google.firebase.ai.type.Schema numFloat(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null);
+ method public com.google.firebase.ai.type.Schema numFloat(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null, Double? maximum = null);
method public com.google.firebase.ai.type.Schema numInt();
method public com.google.firebase.ai.type.Schema numInt(String? description = null);
method public com.google.firebase.ai.type.Schema numInt(String? description = null, boolean nullable = false);
+ method public com.google.firebase.ai.type.Schema numInt(String? description = null, boolean nullable = false, String? title = null);
+ method public com.google.firebase.ai.type.Schema numInt(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null);
+ method public com.google.firebase.ai.type.Schema numInt(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null, Double? maximum = null);
method public com.google.firebase.ai.type.Schema numLong();
method public com.google.firebase.ai.type.Schema numLong(String? description = null);
method public com.google.firebase.ai.type.Schema numLong(String? description = null, boolean nullable = false);
+ method public com.google.firebase.ai.type.Schema numLong(String? description = null, boolean nullable = false, String? title = null);
+ method public com.google.firebase.ai.type.Schema numLong(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null);
+ method public com.google.firebase.ai.type.Schema numLong(String? description = null, boolean nullable = false, String? title = null, Double? minimum = null, Double? maximum = null);
method public com.google.firebase.ai.type.Schema obj(java.util.Map properties);
method public com.google.firebase.ai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList());
method public com.google.firebase.ai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList(), String? description = null);
method public com.google.firebase.ai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList(), String? description = null, boolean nullable = false);
+ method public com.google.firebase.ai.type.Schema obj(java.util.Map properties, java.util.List optionalProperties = emptyList(), String? description = null, boolean nullable = false, String? title = null);
method public com.google.firebase.ai.type.Schema str();
method public com.google.firebase.ai.type.Schema str(String? description = null);
method public com.google.firebase.ai.type.Schema str(String? description = null, boolean nullable = false);
method public com.google.firebase.ai.type.Schema str(String? description = null, boolean nullable = false, com.google.firebase.ai.type.StringFormat? format = null);
+ method public com.google.firebase.ai.type.Schema str(String? description = null, boolean nullable = false, com.google.firebase.ai.type.StringFormat? format = null, String? title = null);
+ }
+
+ public final class SearchEntryPoint {
+ ctor public SearchEntryPoint(String renderedContent, String? sdkBlob);
+ method public String getRenderedContent();
+ method public String? getSdkBlob();
+ property public final String renderedContent;
+ property public final String? sdkBlob;
+ }
+
+ public final class Segment {
+ ctor public Segment(int startIndex, int endIndex, int partIndex, String text);
+ method public int getEndIndex();
+ method public int getPartIndex();
+ method public int getStartIndex();
+ method public String getText();
+ property public final int endIndex;
+ property public final int partIndex;
+ property public final int startIndex;
+ property public final String text;
}
public final class SerializationException extends com.google.firebase.ai.type.FirebaseAIException {
@@ -878,39 +1213,104 @@ package com.google.firebase.ai.type {
public final class TextPart implements com.google.firebase.ai.type.Part {
ctor public TextPart(String text);
method public String getText();
+ method public boolean isThought();
+ property public boolean isThought;
property public final String text;
}
+ public final class ThinkingConfig {
+ }
+
+ public static final class ThinkingConfig.Builder {
+ ctor public ThinkingConfig.Builder();
+ method public com.google.firebase.ai.type.ThinkingConfig build();
+ method public com.google.firebase.ai.type.ThinkingConfig.Builder setIncludeThoughts(boolean includeThoughts);
+ method public com.google.firebase.ai.type.ThinkingConfig.Builder setThinkingBudget(int thinkingBudget);
+ }
+
+ public final class ThinkingConfigKt {
+ method public static com.google.firebase.ai.type.ThinkingConfig thinkingConfig(kotlin.jvm.functions.Function1 super com.google.firebase.ai.type.ThinkingConfig.Builder,kotlin.Unit> init);
+ }
+
public final class Tool {
+ method public static com.google.firebase.ai.type.Tool codeExecution();
method public static com.google.firebase.ai.type.Tool functionDeclarations(java.util.List functionDeclarations);
+ method public static com.google.firebase.ai.type.Tool googleSearch(com.google.firebase.ai.type.GoogleSearch googleSearch = com.google.firebase.ai.type.GoogleSearch());
+ method @com.google.firebase.ai.type.PublicPreviewAPI public static com.google.firebase.ai.type.Tool urlContext(com.google.firebase.ai.type.UrlContext urlContext = com.google.firebase.ai.type.UrlContext());
field public static final com.google.firebase.ai.type.Tool.Companion Companion;
}
public static final class Tool.Companion {
+ method public com.google.firebase.ai.type.Tool codeExecution();
method public com.google.firebase.ai.type.Tool functionDeclarations(java.util.List functionDeclarations);
+ method public com.google.firebase.ai.type.Tool googleSearch(com.google.firebase.ai.type.GoogleSearch googleSearch = com.google.firebase.ai.type.GoogleSearch());
+ method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.type.Tool urlContext(com.google.firebase.ai.type.UrlContext urlContext = com.google.firebase.ai.type.UrlContext());
}
public final class ToolConfig {
ctor public ToolConfig(com.google.firebase.ai.type.FunctionCallingConfig? functionCallingConfig);
}
+ public final class Transcription {
+ method public String? getText();
+ property public final String? text;
+ }
+
public final class UnknownException extends com.google.firebase.ai.type.FirebaseAIException {
}
public final class UnsupportedUserLocationException extends com.google.firebase.ai.type.FirebaseAIException {
}
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class UrlContext {
+ ctor public UrlContext();
+ }
+
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class UrlContextMetadata {
+ method public java.util.List getUrlMetadata();
+ property public final java.util.List urlMetadata;
+ }
+
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class UrlMetadata {
+ method public String? getRetrievedUrl();
+ method public com.google.firebase.ai.type.UrlRetrievalStatus getUrlRetrievalStatus();
+ property public final String? retrievedUrl;
+ property public final com.google.firebase.ai.type.UrlRetrievalStatus urlRetrievalStatus;
+ }
+
+ @com.google.firebase.ai.type.PublicPreviewAPI public final class UrlRetrievalStatus {
+ method public String getName();
+ method public int getOrdinal();
+ property public final String name;
+ property public final int ordinal;
+ field public static final com.google.firebase.ai.type.UrlRetrievalStatus.Companion Companion;
+ field public static final com.google.firebase.ai.type.UrlRetrievalStatus ERROR;
+ field public static final com.google.firebase.ai.type.UrlRetrievalStatus PAYWALL;
+ field public static final com.google.firebase.ai.type.UrlRetrievalStatus SUCCESS;
+ field public static final com.google.firebase.ai.type.UrlRetrievalStatus UNSAFE;
+ field public static final com.google.firebase.ai.type.UrlRetrievalStatus UNSPECIFIED;
+ }
+
+ public static final class UrlRetrievalStatus.Companion {
+ }
+
public final class UsageMetadata {
- ctor public UsageMetadata(int promptTokenCount, Integer? candidatesTokenCount, int totalTokenCount, java.util.List promptTokensDetails, java.util.List candidatesTokensDetails);
+ ctor @Deprecated public UsageMetadata(int promptTokenCount, Integer? candidatesTokenCount, int totalTokenCount, java.util.List promptTokensDetails, java.util.List candidatesTokensDetails, int thoughtsTokenCount);
method public Integer? getCandidatesTokenCount();
method public java.util.List getCandidatesTokensDetails();
method public int getPromptTokenCount();
method public java.util.List getPromptTokensDetails();
+ method public int getThoughtsTokenCount();
+ method public int getToolUsePromptTokenCount();
+ method public java.util.List getToolUsePromptTokensDetails();
method public int getTotalTokenCount();
property public final Integer? candidatesTokenCount;
property public final java.util.List candidatesTokensDetails;
property public final int promptTokenCount;
property public final java.util.List promptTokensDetails;
+ property public final int thoughtsTokenCount;
+ property public final int toolUsePromptTokenCount;
+ property public final java.util.List toolUsePromptTokensDetails;
property public final int totalTokenCount;
}
@@ -935,5 +1335,15 @@ package com.google.firebase.ai.type {
@Deprecated public static final class Voices.Companion {
}
+ public final class WebGroundingChunk {
+ ctor public WebGroundingChunk(String? uri, String? title, String? domain);
+ method public String? getDomain();
+ method public String? getTitle();
+ method public String? getUri();
+ property public final String? domain;
+ property public final String? title;
+ property public final String? uri;
+ }
+
}
diff --git a/firebase-ai/firebase-ai.gradle.kts b/firebase-ai/firebase-ai.gradle.kts
index de29f44b0a1..2aa3a16b6fb 100644
--- a/firebase-ai/firebase-ai.gradle.kts
+++ b/firebase-ai/firebase-ai.gradle.kts
@@ -16,11 +16,12 @@
@file:Suppress("UnstableApiUsage")
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("firebase-library")
id("kotlin-android")
+ id("copy-google-services")
alias(libs.plugins.kotlinx.serialization)
}
@@ -28,9 +29,8 @@ firebaseLibrary {
testLab.enabled = false
publishJavadoc = true
releaseNotes {
- name.set("{{firebase_ai}}")
+ name.set("{{firebase_ai_logic}}")
versionName.set("ai")
- hasKTX.set(false)
}
}
@@ -40,7 +40,7 @@ android {
namespace = "com.google.firebase.ai"
compileSdk = 34
defaultConfig {
- minSdk = 21
+ minSdk = rootProject.extra["minSdkVersion"] as Int
consumerProguardFiles("consumer-rules.pro")
multiDexEnabled = true
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -55,7 +55,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
- kotlinOptions { jvmTarget = "1.8" }
testOptions {
targetSdk = targetSdkVersion
unitTests {
@@ -70,18 +69,9 @@ android {
sourceSets { getByName("test").java.srcDirs("src/testUtil") }
}
-// Enable Kotlin "Explicit API Mode". This causes the Kotlin compiler to fail if any
-// classes, methods, or properties have implicit `public` visibility. This check helps
-// avoid accidentally leaking elements into the public API, requiring that any public
-// element be explicitly declared as `public`.
-// https://github.com/Kotlin/KEEP/blob/master/proposals/explicit-api-mode.md
-// https://chao2zhang.medium.com/explicit-api-mode-for-kotlin-on-android-b8264fdd76d1
-tasks.withType().all {
- if (!name.contains("test", ignoreCase = true)) {
- if (!kotlinOptions.freeCompilerArgs.contains("-Xexplicit-api=strict")) {
- kotlinOptions.freeCompilerArgs += "-Xexplicit-api=strict"
- }
- }
+kotlin {
+ compilerOptions { jvmTarget = JvmTarget.JVM_1_8 }
+ explicitApi()
}
dependencies {
@@ -92,9 +82,9 @@ dependencies {
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.ktor.client.logging)
- api("com.google.firebase:firebase-common:21.0.0")
- implementation("com.google.firebase:firebase-components:18.0.0")
- implementation("com.google.firebase:firebase-annotations:16.2.0")
+ api(libs.firebase.common)
+ implementation(libs.firebase.components)
+ implementation(libs.firebase.annotations)
implementation("com.google.firebase:firebase-appcheck-interop:17.1.0")
implementation(libs.androidx.annotation)
implementation(libs.kotlinx.serialization.json)
diff --git a/firebase-ai/gradle.properties b/firebase-ai/gradle.properties
index 1c7c87996dd..c4acd5b3aae 100644
--- a/firebase-ai/gradle.properties
+++ b/firebase-ai/gradle.properties
@@ -12,5 +12,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-version=16.1.0
-latestReleasedVersion=16.0.0
+version=17.5.0
+latestReleasedVersion=17.4.0
diff --git a/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/AIModels.kt b/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/AIModels.kt
new file mode 100644
index 00000000000..5b51b47fc0c
--- /dev/null
+++ b/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/AIModels.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2025 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.ai
+
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.firebase.FirebaseApp
+import com.google.firebase.ai.type.GenerativeBackend
+
+class AIModels {
+
+ companion object {
+ private val API_KEY: String = ""
+ private val APP_ID: String = ""
+ private val PROJECT_ID: String = "fireescape-integ-tests"
+ // General purpose models
+ var app: FirebaseApp? = null
+ var flash2Model: GenerativeModel? = null
+ var flash2LiteModel: GenerativeModel? = null
+
+ /** Returns a list of general purpose models to test */
+ fun getModels(): List {
+ if (flash2Model == null) {
+ setup()
+ }
+ return listOf(flash2Model!!, flash2LiteModel!!)
+ }
+
+ fun app(): FirebaseApp {
+ if (app == null) {
+ setup()
+ }
+ return app!!
+ }
+
+ fun setup() {
+ val context = InstrumentationRegistry.getInstrumentation().context
+ app = FirebaseApp.initializeApp(context)
+ flash2Model =
+ FirebaseAI.getInstance(app!!, GenerativeBackend.vertexAI())
+ .generativeModel(
+ modelName = "gemini-2.5-flash",
+ )
+ flash2LiteModel =
+ FirebaseAI.getInstance(app!!, GenerativeBackend.vertexAI())
+ .generativeModel(
+ modelName = "gemini-2.5-flash-lite",
+ )
+ }
+ }
+}
diff --git a/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/CountTokensTests.kt b/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/CountTokensTests.kt
new file mode 100644
index 00000000000..04ff6262ee8
--- /dev/null
+++ b/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/CountTokensTests.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2025 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.ai
+
+import android.graphics.Bitmap
+import com.google.firebase.ai.AIModels.Companion.getModels
+import com.google.firebase.ai.type.Content
+import com.google.firebase.ai.type.ContentModality
+import com.google.firebase.ai.type.CountTokensResponse
+import java.io.ByteArrayOutputStream
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+
+class CountTokensTests {
+
+ /** Ensures that the token count is expected for simple words. */
+ @Test
+ fun testCountTokensAmount() {
+ for (model in getModels()) {
+ runBlocking {
+ val response = model.countTokens("this is five different words")
+ assert(response.totalTokens == 5)
+ assert(response.promptTokensDetails.size == 1)
+ assert(response.promptTokensDetails[0].modality == ContentModality.TEXT)
+ assert(response.promptTokensDetails[0].tokenCount == 5)
+ }
+ }
+ }
+
+ /** Ensures that the model returns token counts in the correct modality for text. */
+ @Test
+ fun testCountTokensTextModality() {
+ for (model in getModels()) {
+ runBlocking {
+ val response = model.countTokens("this is a text prompt")
+ checkTokenCountsMatch(response)
+ assert(response.promptTokensDetails.size == 1)
+ assert(containsModality(response, ContentModality.TEXT))
+ }
+ }
+ }
+
+ /** Ensures that the model returns token counts in the correct modality for bitmap images. */
+ @Test
+ fun testCountTokensImageModality() {
+ for (model in getModels()) {
+ runBlocking {
+ val bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val response = model.countTokens(bitmap)
+ checkTokenCountsMatch(response)
+ assert(response.promptTokensDetails.size == 1)
+ assert(containsModality(response, ContentModality.IMAGE))
+ }
+ }
+ }
+
+ /**
+ * Ensures the model can count tokens for multiple modalities at once, and return the
+ * corresponding token modalities correctly.
+ */
+ @Test
+ fun testCountTokensTextAndImageModality() {
+ for (model in getModels()) {
+ runBlocking {
+ val bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val response =
+ model.countTokens(
+ Content.Builder().text("this is text").build(),
+ Content.Builder().image(bitmap).build()
+ )
+ checkTokenCountsMatch(response)
+ assert(response.promptTokensDetails.size == 2)
+ assert(containsModality(response, ContentModality.TEXT))
+ assert(containsModality(response, ContentModality.IMAGE))
+ }
+ }
+ }
+
+ /**
+ * Ensures the model can count the tokens for a sent file. Additionally, ensures that the model
+ * treats this sent file as the modality of the mime type, in this case, a plaintext file has its
+ * tokens counted as `ContentModality.TEXT`.
+ */
+ @Test
+ fun testCountTokensTextFileModality() {
+ for (model in getModels()) {
+ runBlocking {
+ val response =
+ model.countTokens(
+ Content.Builder().inlineData("this is text".toByteArray(), "text/plain").build()
+ )
+ checkTokenCountsMatch(response)
+ assert(response.totalTokens == 3)
+ assert(response.promptTokensDetails.size == 1)
+ assert(containsModality(response, ContentModality.TEXT))
+ }
+ }
+ }
+
+ /**
+ * Ensures the model can count the tokens for a sent file. Additionally, ensures that the model
+ * treats this sent file as the modality of the mime type, in this case, a PNG encoded bitmap has
+ * its tokens counted as `ContentModality.IMAGE`.
+ */
+ @Test
+ fun testCountTokensImageFileModality() {
+ for (model in getModels()) {
+ runBlocking {
+ val bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val stream = ByteArrayOutputStream()
+ bitmap.compress(Bitmap.CompressFormat.PNG, 1, stream)
+ val array = stream.toByteArray()
+ val response = model.countTokens(Content.Builder().inlineData(array, "image/png").build())
+ checkTokenCountsMatch(response)
+ assert(response.promptTokensDetails.size == 1)
+ assert(containsModality(response, ContentModality.IMAGE))
+ }
+ }
+ }
+
+ /**
+ * Ensures that nothing is free, that is, empty content contains no tokens. For some reason, this
+ * is treated as `ContentModality.TEXT`.
+ */
+ @Test
+ fun testCountTokensNothingIsFree() {
+ for (model in getModels()) {
+ runBlocking {
+ val response = model.countTokens(Content.Builder().build())
+ checkTokenCountsMatch(response)
+ assert(response.totalTokens == 0)
+ assert(response.promptTokensDetails.size == 1)
+ assert(containsModality(response, ContentModality.TEXT))
+ }
+ }
+ }
+
+ /**
+ * Checks if the model can count the tokens for a sent file. Additionally, ensures that the model
+ * treats this sent file as the modality of the mime type, in this case, a JSON file is not
+ * recognized, and no tokens are counted. This ensures if/when the model can handle JSON, our
+ * testing makes us aware.
+ */
+ @Test
+ fun testCountTokensJsonFileModality() {
+ for (model in getModels()) {
+ runBlocking {
+ val json =
+ """
+ {
+ "foo": "bar",
+ "baz": 3,
+ "qux": [
+ {
+ "quux": [
+ 1,
+ 2
+ ]
+ }
+ ]
+ }
+ """
+ .trimIndent()
+ val response =
+ model.countTokens(
+ Content.Builder().inlineData(json.toByteArray(), "application/json").build()
+ )
+ checkTokenCountsMatch(response)
+ assert(response.promptTokensDetails.isEmpty())
+ assert(response.totalTokens == 0)
+ }
+ }
+ }
+
+ fun checkTokenCountsMatch(response: CountTokensResponse) {
+ assert(sumTokenCount(response) == response.totalTokens)
+ }
+
+ fun sumTokenCount(response: CountTokensResponse): Int {
+ return response.promptTokensDetails.sumOf { it.tokenCount }
+ }
+
+ fun containsModality(response: CountTokensResponse, modality: ContentModality): Boolean {
+ for (token in response.promptTokensDetails) {
+ if (token.modality == modality) {
+ return true
+ }
+ }
+ return false
+ }
+}
diff --git a/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/GenerateContentTests.kt b/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/GenerateContentTests.kt
new file mode 100644
index 00000000000..7de068311ef
--- /dev/null
+++ b/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/GenerateContentTests.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2025 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.ai
+
+import android.graphics.Bitmap
+import com.google.firebase.ai.AIModels.Companion.getModels
+import com.google.firebase.ai.type.Content
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+
+class GenerateContentTests {
+ private val validator = TypesValidator()
+
+ /**
+ * Ensures the model can response to prompts and that the structure of this response is expected.
+ */
+ @Test
+ fun testGenerateContent_BasicRequest() {
+ for (model in getModels()) {
+ runBlocking {
+ val response = model.generateContent("pick a random color")
+ validator.validateResponse(response)
+ }
+ }
+ }
+
+ /**
+ * Ensures that the model can answer very simple questions. Further testing the "logic" of the
+ * model and the content of the responses is prone to flaking, this test is also prone to that.
+ * This is probably the furthest we can consistently test for reasonable response structure, past
+ * sending the request and response back to the model and asking it if it fits our expectations.
+ */
+ @Test
+ fun testGenerateContent_ColorMixing() {
+ for (model in getModels()) {
+ runBlocking {
+ val response = model.generateContent("what color is created when red and yellow are mixed?")
+ validator.validateResponse(response)
+ assert(response.text!!.contains("orange", true))
+ }
+ }
+ }
+
+ /**
+ * Ensures that the model can answer very simple questions. Further testing the "logic" of the
+ * model and the content of the responses is prone to flaking, this test is also prone to that.
+ * This is probably the furthest we can consistently test for reasonable response structure, past
+ * sending the request and response back to the model and asking it if it fits our expectations.
+ */
+ @Test
+ fun testGenerateContent_CanSendImage() {
+ for (model in getModels()) {
+ runBlocking {
+ val bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val yellow = Integer.parseUnsignedInt("FFFFFF00", 16)
+ bitmap.setPixel(3, 3, yellow)
+ bitmap.setPixel(6, 3, yellow)
+ bitmap.setPixel(3, 6, yellow)
+ bitmap.setPixel(4, 7, yellow)
+ bitmap.setPixel(5, 7, yellow)
+ bitmap.setPixel(6, 6, yellow)
+ val response =
+ model.generateContent(
+ Content.Builder().text("here is a tiny smile").image(bitmap).build()
+ )
+ validator.validateResponse(response)
+ }
+ }
+ }
+}
diff --git a/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/ImagenTests.kt b/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/ImagenTests.kt
new file mode 100644
index 00000000000..218637d4b32
--- /dev/null
+++ b/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/ImagenTests.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2025 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.ai
+
+import com.google.firebase.ai.AIModels.Companion.app
+import com.google.firebase.ai.type.ImagenBackgroundMask
+import com.google.firebase.ai.type.ImagenEditMode
+import com.google.firebase.ai.type.ImagenEditingConfig
+import com.google.firebase.ai.type.ImagenRawImage
+import com.google.firebase.ai.type.PublicPreviewAPI
+import kotlinx.coroutines.runBlocking
+import org.junit.Ignore
+import org.junit.Test
+
+@OptIn(PublicPreviewAPI::class)
+class ImagenTests {
+
+ @Ignore("Currently not supported by backend model")
+ @Test
+ fun testGenerateAndEditImage() {
+ val imageGenerationModel = FirebaseAI.getInstance(app()).imagenModel("imagen-3.0-generate-002")
+ val imageEditingModel = FirebaseAI.getInstance(app()).imagenModel("imagen-3.0-capability-001")
+
+ runBlocking {
+ val catImage = imageGenerationModel.generateImages("A cat").images.first()
+ val editedCatImage =
+ imageEditingModel.editImage(
+ listOf(ImagenRawImage(catImage), ImagenBackgroundMask()),
+ "A cat flying through space",
+ ImagenEditingConfig(ImagenEditMode.INPAINT_INSERTION)
+ )
+ assert(editedCatImage.images.size == 1)
+ }
+ }
+}
diff --git a/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/ToolTests.kt b/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/ToolTests.kt
new file mode 100644
index 00000000000..856154c161f
--- /dev/null
+++ b/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/ToolTests.kt
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2025 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.ai
+
+import com.google.firebase.ai.AIModels.Companion.app
+import com.google.firebase.ai.type.Content
+import com.google.firebase.ai.type.FunctionCallingConfig
+import com.google.firebase.ai.type.FunctionDeclaration
+import com.google.firebase.ai.type.FunctionResponsePart
+import com.google.firebase.ai.type.GenerativeBackend
+import com.google.firebase.ai.type.Schema
+import com.google.firebase.ai.type.Tool
+import com.google.firebase.ai.type.ToolConfig
+import com.google.firebase.ai.type.content
+import io.ktor.util.toLowerCasePreservingASCIIRules
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.json.JsonArray
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonNull
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.booleanOrNull
+import kotlinx.serialization.json.doubleOrNull
+import kotlinx.serialization.json.float
+import kotlinx.serialization.json.intOrNull
+import kotlinx.serialization.json.jsonArray
+import kotlinx.serialization.json.jsonObject
+import kotlinx.serialization.json.jsonPrimitive
+import org.junit.Test
+
+class ToolTests {
+ val validator = TypesValidator()
+
+ @Test
+ fun testTools_functionCallStructuring() {
+ val schema =
+ mapOf(
+ Pair(
+ "character",
+ Schema.obj(
+ mapOf(
+ Pair("name", Schema.string("the character's full name")),
+ Pair("gender", Schema.string("the character's gender")),
+ Pair("weight", Schema.float("the character's weight, in kilograms")),
+ Pair("height", Schema.float("the character's height, in centimeters")),
+ Pair(
+ "favorite_foods",
+ Schema.array(
+ Schema.string("the name of a food"),
+ "a short list of the character's favorite foods"
+ )
+ ),
+ Pair(
+ "mother",
+ Schema.obj(
+ mapOf(Pair("name", Schema.string("the character's mother's name"))),
+ description = "information about the character's mother"
+ )
+ ),
+ )
+ )
+ ),
+ )
+ val model =
+ setupModel(
+ FunctionDeclaration(
+ name = "getFavoriteColor",
+ description =
+ "determines a video game character's favorite color based on their features",
+ parameters = schema
+ ),
+ )
+ runBlocking {
+ val response =
+ model.generateContent(
+ "I'm imagining a video game character whose name is sam, but I can't think of the rest of their traits, could you make them up for me and figure out the character's favorite color?"
+ )
+ validator.validateResponse((response))
+ assert(response.functionCalls.size == 1)
+ val call = response.functionCalls[0]
+ assert(call.name == "getFavoriteColor")
+ validateSchema(schema, call.args)
+ }
+ }
+
+ @Test
+ fun testTools_basicDecisionMaking() {
+ val schema =
+ mapOf(
+ Pair("character", Schema.string("the character whose favorite color should be obtained"))
+ )
+ val model =
+ setupModel(
+ FunctionDeclaration(
+ name = "getFavoriteColor",
+ description = "returns the favorite color from a provided character's name",
+ parameters = schema
+ ),
+ FunctionDeclaration(
+ name = "eatAllSnacks",
+ description =
+ "orders a robot to find the kitchen of the provided character by their name, then eat all of their snacks so they get really sad. returns how many snacks were eaten",
+ parameters = schema
+ )
+ )
+ runBlocking {
+ val response = model.generateContent("what is amy's favorite color?")
+ validator.validateResponse((response))
+ assert(response.functionCalls.size == 1)
+ val call = response.functionCalls[0]
+ assert(call.name == "getFavoriteColor")
+ validateSchema(schema, call.args)
+ }
+ }
+
+ /** Ensures the model is capable of a simple question, tool call, response workflow. */
+ @Test
+ fun testTools_BasicToolCall() {
+ val schema =
+ mapOf(
+ Pair("character", Schema.string("the character whose favorite color should be obtained"))
+ )
+ val model =
+ setupModel(
+ FunctionDeclaration(
+ name = "getFavoriteColor",
+ description = "returns the favorite color from a provided character's name",
+ parameters = schema
+ )
+ )
+ runBlocking {
+ val question = content { text("what's bob's favorite color?") }
+ val response = model.generateContent(question)
+ validator.validateResponse((response))
+ assert(response.functionCalls.size == 1)
+ for (call in response.functionCalls) {
+ assert(call.name == "getFavoriteColor")
+ validateSchema(schema, call.args)
+ assert(
+ call.args["character"]!!.jsonPrimitive.content.toLowerCasePreservingASCIIRules() == "bob"
+ )
+ model.generateContent(
+ question,
+ Content(
+ role = "model",
+ parts =
+ listOf(
+ call,
+ )
+ ),
+ Content(
+ parts =
+ listOf(
+ FunctionResponsePart(
+ id = call.id,
+ name = call.name,
+ response = JsonObject(mapOf(Pair("result", JsonPrimitive("green"))))
+ ),
+ )
+ )
+ )
+ }
+ }
+ }
+
+ /**
+ * Ensures the model can chain function calls together to reach trivial conclusions. In this case,
+ * the model needs to use the output of one function call as the input to another.
+ */
+ @Test
+ fun testTools_sequencingFunctionCalls() {
+ val nameSchema =
+ mapOf(
+ Pair("name", Schema.string("the name of the person whose birth month should be obtained"))
+ )
+ val monthSchema =
+ mapOf(Pair("month", Schema.string("the month whose color should be obtained")))
+ val model =
+ setupModel(
+ FunctionDeclaration(
+ name = "getBirthMonth",
+ description = "returns a person's birth month based on their name",
+ parameters = nameSchema
+ ),
+ FunctionDeclaration(
+ name = "getMonthColor",
+ description = "returns the color for a certain month",
+ parameters = monthSchema
+ )
+ )
+ runBlocking {
+ val question = content { text("what color is john's birth month") }
+ val response = model.generateContent(question)
+ assert(response.functionCalls.size == 1)
+ val call = response.functionCalls[0]
+ assert(call.name == "getBirthMonth")
+ assert(call.args["name"]!!.jsonPrimitive.content.toLowerCasePreservingASCIIRules() == "john")
+ validateSchema(nameSchema, call.args)
+ val response2 =
+ model.generateContent(
+ question,
+ Content(
+ role = "model",
+ parts =
+ listOf(
+ call,
+ )
+ ),
+ Content(
+ parts =
+ listOf(
+ FunctionResponsePart(
+ id = call.id,
+ name = call.name,
+ response = JsonObject(mapOf(Pair("result", JsonPrimitive("june"))))
+ ),
+ )
+ )
+ )
+ validator.validateResponse((response))
+ assert(response2.functionCalls.size == 1)
+ val call2 = response2.functionCalls[0]
+ assert(call2.name == "getMonthColor")
+ assert(
+ call2.args["month"]!!.jsonPrimitive.content.toLowerCasePreservingASCIIRules() == "june"
+ )
+ validateSchema(monthSchema, call2.args)
+ }
+ }
+
+ fun validateSchema(schema: Map, args: Map) {
+ // Model should not call the function with unspecified arguments
+ assert(schema.keys.containsAll(args.keys))
+ for (entry in schema) {
+ validateSchema(entry.value, args.get(entry.key))
+ }
+ }
+
+ /** Simple schema validation. Not comprehensive, but should detect notable inaccuracy. */
+ fun validateSchema(schema: Schema, json: JsonElement?) {
+ if (json == null) {
+ assert(schema.nullable == true)
+ return
+ }
+ when (json) {
+ is JsonNull -> {
+ assert(schema.nullable == true)
+ }
+ is JsonPrimitive -> {
+ if (schema.type == "INTEGER") {
+ assert(json.intOrNull != null)
+ } else if (schema.type == "NUMBER") {
+ assert(json.doubleOrNull != null)
+ } else if (schema.type == "BOOLEAN") {
+ assert(json.booleanOrNull != null)
+ } else if (schema.type == "STRING") {
+ assert(json.isString)
+ } else {
+ assert(false)
+ }
+ }
+ is JsonObject -> {
+ assert(schema.type == "OBJECT")
+ val required = schema.required ?: listOf()
+ val obj = json.jsonObject
+ for (entry in schema.properties!!) {
+ if (obj.containsKey(entry.key)) {
+ validateSchema(entry.value, obj.get(entry.key))
+ } else {
+ assert(!required.contains(entry.key))
+ }
+ }
+ }
+ is JsonArray -> {
+ assert(schema.type == "ARRAY")
+ for (e in json.jsonArray) {
+ validateSchema(schema.items!!, e)
+ }
+ }
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun setupModel(vararg functions: FunctionDeclaration): GenerativeModel {
+ val model =
+ FirebaseAI.getInstance(app(), GenerativeBackend.vertexAI())
+ .generativeModel(
+ modelName = "gemini-2.5-flash",
+ toolConfig =
+ ToolConfig(
+ functionCallingConfig = FunctionCallingConfig(FunctionCallingConfig.Mode.ANY)
+ ),
+ tools = listOf(Tool.functionDeclarations(functions.toList())),
+ )
+ return model
+ }
+ }
+}
diff --git a/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/TypesValidator.kt b/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/TypesValidator.kt
new file mode 100644
index 00000000000..768b9cf4eca
--- /dev/null
+++ b/firebase-ai/src/androidTest/kotlin/com/google/firebase/ai/TypesValidator.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2025 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.ai
+
+import com.google.firebase.ai.type.Candidate
+import com.google.firebase.ai.type.Content
+import com.google.firebase.ai.type.GenerateContentResponse
+import com.google.firebase.ai.type.TextPart
+
+/** Performs structural validation of various API types */
+class TypesValidator {
+
+ fun validateResponse(response: GenerateContentResponse) {
+ if (response.candidates.isNotEmpty() && hasText(response.candidates[0].content)) {
+ assert(response.text!!.isNotEmpty())
+ } else if (response.candidates.isNotEmpty()) {
+ assert(!hasText(response.candidates[0].content))
+ }
+ response.candidates.forEach { validateCandidate(it) }
+ }
+
+ fun validateCandidate(candidate: Candidate) {
+ validateContent(candidate.content)
+ }
+
+ fun validateContent(content: Content) {
+ assert(content.role != "user")
+ }
+
+ fun hasText(content: Content): Boolean {
+ return content.parts.filterIsInstance().isNotEmpty()
+ }
+}
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/Chat.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/Chat.kt
index 13599fb1c9a..73d304d3885 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/Chat.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/Chat.kt
@@ -66,7 +66,8 @@ public class Chat(
prompt.assertComesFromUser()
attemptLock()
try {
- val response = model.generateContent(*history.toTypedArray(), prompt)
+ val fullPrompt = history + prompt
+ val response = model.generateContent(fullPrompt.first(), *fullPrompt.drop(1).toTypedArray())
history.add(prompt)
history.add(response.candidates.first().content)
return response
@@ -127,7 +128,8 @@ public class Chat(
prompt.assertComesFromUser()
attemptLock()
- val flow = model.generateContentStream(*history.toTypedArray(), prompt)
+ val fullPrompt = history + prompt
+ val flow = model.generateContentStream(fullPrompt.first(), *fullPrompt.drop(1).toTypedArray())
val bitmaps = LinkedList()
val inlineDataParts = LinkedList()
val text = StringBuilder()
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAI.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAI.kt
index 86eb8057b1d..dd2309c984a 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAI.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAI.kt
@@ -25,7 +25,6 @@ import com.google.firebase.ai.type.GenerativeBackend
import com.google.firebase.ai.type.GenerativeBackendEnum
import com.google.firebase.ai.type.ImagenGenerationConfig
import com.google.firebase.ai.type.ImagenSafetySettings
-import com.google.firebase.ai.type.InvalidStateException
import com.google.firebase.ai.type.LiveGenerationConfig
import com.google.firebase.ai.type.PublicPreviewAPI
import com.google.firebase.ai.type.RequestOptions
@@ -47,12 +46,14 @@ internal constructor(
@Blocking private val blockingDispatcher: CoroutineContext,
private val appCheckProvider: Provider,
private val internalAuthProvider: Provider,
+ private val useLimitedUseAppCheckTokens: Boolean
) {
/**
* Instantiates a new [GenerativeModel] given the provided parameters.
*
- * @param modelName The name of the model to use, for example `"gemini-2.0-flash-exp"`.
+ * @param modelName The name of the model to use. See the documentation for a list of
+ * [supported models](https://firebase.google.com/docs/ai-logic/models).
* @param generationConfig The configuration parameters to use for content generation.
* @param safetySettings The safety bounds the model will abide to during content generation.
* @param tools A list of [Tool]s the model may use to generate content.
@@ -92,6 +93,7 @@ internal constructor(
modelUri,
firebaseApp.options.apiKey,
firebaseApp,
+ useLimitedUseAppCheckTokens,
generationConfig,
safetySettings,
tools,
@@ -107,7 +109,8 @@ internal constructor(
/**
* Instantiates a new [LiveGenerationConfig] given the provided parameters.
*
- * @param modelName The name of the model to use, for example `"gemini-2.0-flash-exp"`.
+ * @param modelName The name of the model to use. See the documentation for a list of
+ * [supported models](https://firebase.google.com/docs/ai-logic/models).
* @param generationConfig The configuration parameters to use for content generation.
* @param tools A list of [Tool]s the model may use to generate content.
* @param systemInstruction [Content] instructions that direct the model to behave a certain way.
@@ -124,6 +127,7 @@ internal constructor(
systemInstruction: Content? = null,
requestOptions: RequestOptions = RequestOptions(),
): LiveGenerativeModel {
+
if (!modelName.startsWith(GEMINI_MODEL_NAME_PREFIX)) {
Log.w(
TAG,
@@ -138,7 +142,7 @@ internal constructor(
GenerativeBackendEnum.VERTEX_AI ->
"projects/${firebaseApp.options.projectId}/locations/${backend.location}/publishers/google/models/${modelName}"
GenerativeBackendEnum.GOOGLE_AI ->
- throw InvalidStateException("Live Model is not yet available on the Google AI backend")
+ "projects/${firebaseApp.options.projectId}/models/${modelName}"
},
firebaseApp.options.apiKey,
firebaseApp,
@@ -150,20 +154,22 @@ internal constructor(
requestOptions,
appCheckProvider.get(),
internalAuthProvider.get(),
+ backend,
+ useLimitedUseAppCheckTokens,
)
}
/**
* Instantiates a new [ImagenModel] given the provided parameters.
*
- * @param modelName The name of the model to use, for example `"imagen-3.0-generate-001"`.
+ * @param modelName The name of the model to use. See the documentation for a list of
+ * [supported models](https://firebase.google.com/docs/ai-logic/models).
* @param generationConfig The configuration parameters to use for image generation.
* @param safetySettings The safety bounds the model will abide by during image generation.
* @param requestOptions Configuration options for sending requests to the backend.
* @return The initialized [ImagenModel] instance.
*/
@JvmOverloads
- @PublicPreviewAPI
public fun imagenModel(
modelName: String,
generationConfig: ImagenGenerationConfig? = null,
@@ -190,6 +196,7 @@ internal constructor(
modelUri,
firebaseApp.options.apiKey,
firebaseApp,
+ useLimitedUseAppCheckTokens,
generationConfig,
safetySettings,
requestOptions,
@@ -214,9 +221,30 @@ internal constructor(
public fun getInstance(
app: FirebaseApp = Firebase.app,
backend: GenerativeBackend
+ ): FirebaseAI {
+ return getInstance(app, backend, false)
+ }
+
+ /**
+ * Returns the [FirebaseAI] instance for the provided [FirebaseApp] and [backend].
+ *
+ * @param backend the backend reference to make generative AI requests to.
+ * @param useLimitedUseAppCheckTokens when sending tokens to the backend, this option enables
+ * the usage of App Check's limited-use tokens instead of the standard cached tokens. Learn more
+ * about [limited-use tokens](https://firebase.google.com/docs/ai-logic/app-check), including
+ * their nuances, when to use them, and best practices for integrating them into your app.
+ *
+ * _This flag is set to `false` by default._
+ */
+ @JvmStatic
+ @JvmOverloads
+ public fun getInstance(
+ app: FirebaseApp = Firebase.app,
+ backend: GenerativeBackend,
+ useLimitedUseAppCheckTokens: Boolean,
): FirebaseAI {
val multiResourceComponent = app[FirebaseAIMultiResourceComponent::class.java]
- return multiResourceComponent.get(backend)
+ return multiResourceComponent.get(InstanceKey(backend, useLimitedUseAppCheckTokens))
}
/** The [FirebaseAI] instance for the provided [FirebaseApp] using the Google AI Backend. */
@@ -245,3 +273,19 @@ public fun Firebase.ai(
app: FirebaseApp = Firebase.app,
backend: GenerativeBackend = GenerativeBackend.googleAI()
): FirebaseAI = FirebaseAI.getInstance(app, backend)
+
+/**
+ * Returns the [FirebaseAI] instance for the provided [FirebaseApp] and [backend].
+ *
+ * @param backend the backend reference to make generative AI requests to.
+ * @param useLimitedUseAppCheckTokens use App Check's limited-use tokens when sending requests to
+ * the backend. Learn more about
+ * [limited-use tokens](https://firebase.google.com/docs/ai-logic/app-check), including their
+ * nuances, when to use them, and best practices for integrating them into your app.
+ */
+// TODO(b/440356335): Update docs above when web page goes live in M170
+public fun Firebase.ai(
+ app: FirebaseApp = Firebase.app,
+ backend: GenerativeBackend = GenerativeBackend.googleAI(),
+ useLimitedUseAppCheckTokens: Boolean
+): FirebaseAI = FirebaseAI.getInstance(app, backend, useLimitedUseAppCheckTokens)
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAIMultiResourceComponent.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAIMultiResourceComponent.kt
index c0667b1685e..d39a93fa598 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAIMultiResourceComponent.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAIMultiResourceComponent.kt
@@ -37,18 +37,24 @@ internal class FirebaseAIMultiResourceComponent(
private val internalAuthProvider: Provider,
) {
- @GuardedBy("this") private val instances: MutableMap = mutableMapOf()
+ @GuardedBy("this") private val instances: MutableMap = mutableMapOf()
- fun get(backend: GenerativeBackend): FirebaseAI =
+ fun get(key: InstanceKey): FirebaseAI =
synchronized(this) {
- instances[backend.location]
- ?: FirebaseAI(
- app,
- backend,
- blockingDispatcher,
- appCheckProvider,
- internalAuthProvider,
- )
- .also { instances[backend.location] = it }
+ instances.getOrPut(key) {
+ FirebaseAI(
+ app,
+ key.backend,
+ blockingDispatcher,
+ appCheckProvider,
+ internalAuthProvider,
+ key.useLimitedUseAppCheckTokens
+ )
+ }
}
}
+
+internal data class InstanceKey(
+ val backend: GenerativeBackend,
+ val useLimitedUseAppCheckTokens: Boolean
+)
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/GenerativeModel.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/GenerativeModel.kt
index 1b36998f970..45aa1e567e3 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/GenerativeModel.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/GenerativeModel.kt
@@ -65,6 +65,7 @@ internal constructor(
modelName: String,
apiKey: String,
firebaseApp: FirebaseApp,
+ useLimitedUseAppCheckTokens: Boolean,
generationConfig: GenerationConfig? = null,
safetySettings: List? = null,
tools: List? = null,
@@ -73,7 +74,7 @@ internal constructor(
requestOptions: RequestOptions = RequestOptions(),
generativeBackend: GenerativeBackend,
appCheckTokenProvider: InteropAppCheckTokenProvider? = null,
- internalAuthProvider: InternalAuthProvider? = null,
+ internalAuthProvider: InternalAuthProvider? = null
) : this(
modelName,
generationConfig,
@@ -88,7 +89,12 @@ internal constructor(
requestOptions,
"gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}",
firebaseApp,
- AppCheckHeaderProvider(TAG, appCheckTokenProvider, internalAuthProvider),
+ AppCheckHeaderProvider(
+ TAG,
+ useLimitedUseAppCheckTokens,
+ appCheckTokenProvider,
+ internalAuthProvider
+ ),
),
)
@@ -100,13 +106,48 @@ internal constructor(
* @throws [FirebaseAIException] if the request failed.
* @see [FirebaseAIException] for types of errors.
*/
- public suspend fun generateContent(vararg prompt: Content): GenerateContentResponse =
+ public suspend fun generateContent(
+ prompt: Content,
+ vararg prompts: Content
+ ): GenerateContentResponse =
try {
- controller.generateContent(constructRequest(*prompt)).toPublic().validate()
+ controller.generateContent(constructRequest(prompt, *prompts)).toPublic().validate()
} catch (e: Throwable) {
throw FirebaseAIException.from(e)
}
+ /**
+ * Generates new content from the input [Content] given to the model as a prompt.
+ *
+ * @param prompt The input(s) given to the model as a prompt.
+ * @return The content generated by the model.
+ * @throws [FirebaseAIException] if the request failed.
+ * @see [FirebaseAIException] for types of errors.
+ */
+ public suspend fun generateContent(prompt: List): GenerateContentResponse =
+ try {
+ controller.generateContent(constructRequest(prompt)).toPublic().validate()
+ } catch (e: Throwable) {
+ throw FirebaseAIException.from(e)
+ }
+
+ /**
+ * Generates new content as a stream from the input [Content] given to the model as a prompt.
+ *
+ * @param prompt The input(s) given to the model as a prompt.
+ * @return A [Flow] which will emit responses as they are returned by the model.
+ * @throws [FirebaseAIException] if the request failed.
+ * @see [FirebaseAIException] for types of errors.
+ */
+ public fun generateContentStream(
+ prompt: Content,
+ vararg prompts: Content
+ ): Flow =
+ controller
+ .generateContentStream(constructRequest(prompt, *prompts))
+ .catch { throw FirebaseAIException.from(it) }
+ .map { it.toPublic().validate() }
+
/**
* Generates new content as a stream from the input [Content] given to the model as a prompt.
*
@@ -115,9 +156,9 @@ internal constructor(
* @throws [FirebaseAIException] if the request failed.
* @see [FirebaseAIException] for types of errors.
*/
- public fun generateContentStream(vararg prompt: Content): Flow =
+ public fun generateContentStream(prompt: List): Flow =
controller
- .generateContentStream(constructRequest(*prompt))
+ .generateContentStream(constructRequest(prompt))
.catch { throw FirebaseAIException.from(it) }
.map { it.toPublic().validate() }
@@ -177,9 +218,25 @@ internal constructor(
* @throws [FirebaseAIException] if the request failed.
* @see [FirebaseAIException] for types of errors.
*/
- public suspend fun countTokens(vararg prompt: Content): CountTokensResponse {
+ public suspend fun countTokens(prompt: Content, vararg prompts: Content): CountTokensResponse {
+ try {
+ return controller.countTokens(constructCountTokensRequest(prompt, *prompts)).toPublic()
+ } catch (e: Throwable) {
+ throw FirebaseAIException.from(e)
+ }
+ }
+
+ /**
+ * Counts the number of tokens in a prompt using the model's tokenizer.
+ *
+ * @param prompt The input(s) given to the model as a prompt.
+ * @return The [CountTokensResponse] of running the model's tokenizer on the input.
+ * @throws [FirebaseAIException] if the request failed.
+ * @see [FirebaseAIException] for types of errors.
+ */
+ public suspend fun countTokens(prompt: List): CountTokensResponse {
try {
- return controller.countTokens(constructCountTokensRequest(*prompt)).toPublic()
+ return controller.countTokens(constructCountTokensRequest(*prompt.toTypedArray())).toPublic()
} catch (e: Throwable) {
throw FirebaseAIException.from(e)
}
@@ -232,6 +289,8 @@ internal constructor(
systemInstruction?.copy(role = "system")?.toInternal(),
)
+ private fun constructRequest(prompt: List) = constructRequest(*prompt.toTypedArray())
+
private fun constructCountTokensRequest(vararg prompt: Content) =
when (generativeBackend.backend) {
GenerativeBackendEnum.GOOGLE_AI -> CountTokensRequest.forGoogleAI(constructRequest(*prompt))
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/ImagenModel.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/ImagenModel.kt
index 4d88d09b1e1..62f11319f68 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/ImagenModel.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/ImagenModel.kt
@@ -19,12 +19,19 @@ package com.google.firebase.ai
import com.google.firebase.FirebaseApp
import com.google.firebase.ai.common.APIController
import com.google.firebase.ai.common.AppCheckHeaderProvider
-import com.google.firebase.ai.common.ContentBlockedException
import com.google.firebase.ai.common.GenerateImageRequest
+import com.google.firebase.ai.type.ContentBlockedException
+import com.google.firebase.ai.type.Dimensions
import com.google.firebase.ai.type.FirebaseAIException
+import com.google.firebase.ai.type.ImagenEditMode
+import com.google.firebase.ai.type.ImagenEditingConfig
import com.google.firebase.ai.type.ImagenGenerationConfig
import com.google.firebase.ai.type.ImagenGenerationResponse
+import com.google.firebase.ai.type.ImagenImagePlacement
import com.google.firebase.ai.type.ImagenInlineImage
+import com.google.firebase.ai.type.ImagenMaskReference
+import com.google.firebase.ai.type.ImagenRawImage
+import com.google.firebase.ai.type.ImagenReferenceImage
import com.google.firebase.ai.type.ImagenSafetySettings
import com.google.firebase.ai.type.PublicPreviewAPI
import com.google.firebase.ai.type.RequestOptions
@@ -34,8 +41,10 @@ import com.google.firebase.auth.internal.InternalAuthProvider
/**
* Represents a generative model (like Imagen), capable of generating images based on various input
* types.
+ *
+ * See the documentation for a list of
+ * [supported models](https://firebase.google.com/docs/ai-logic/models).
*/
-@PublicPreviewAPI
public class ImagenModel
internal constructor(
private val modelName: String,
@@ -48,6 +57,7 @@ internal constructor(
modelName: String,
apiKey: String,
firebaseApp: FirebaseApp,
+ useLimitedUseAppCheckTokens: Boolean,
generationConfig: ImagenGenerationConfig? = null,
safetySettings: ImagenSafetySettings? = null,
requestOptions: RequestOptions = RequestOptions(),
@@ -63,7 +73,12 @@ internal constructor(
requestOptions,
"gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}",
firebaseApp,
- AppCheckHeaderProvider(TAG, appCheckTokenProvider, internalAuthProvider),
+ AppCheckHeaderProvider(
+ TAG,
+ useLimitedUseAppCheckTokens,
+ appCheckTokenProvider,
+ internalAuthProvider
+ ),
),
)
@@ -75,30 +90,137 @@ internal constructor(
public suspend fun generateImages(prompt: String): ImagenGenerationResponse =
try {
controller
- .generateImage(constructRequest(prompt, null, generationConfig))
+ .generateImage(constructGenerateImageRequest(prompt, generationConfig))
.validate()
.toPublicInline()
} catch (e: Throwable) {
throw FirebaseAIException.from(e)
}
- private fun constructRequest(
+ /**
+ * Generates an image from a single or set of base images, returning the result directly to the
+ * caller.
+ *
+ * @param referenceImages the image inputs given to the model as a prompt
+ * @param prompt the text input given to the model as a prompt
+ * @param config the editing configuration settings
+ */
+ @PublicPreviewAPI
+ public suspend fun editImage(
+ referenceImages: List,
prompt: String,
- gcsUri: String?,
- config: ImagenGenerationConfig?,
+ config: ImagenEditingConfig? = null,
+ ): ImagenGenerationResponse =
+ try {
+ controller
+ .generateImage(constructEditRequest(referenceImages, prompt, config))
+ .validate()
+ .toPublicInline()
+ } catch (e: Throwable) {
+ throw FirebaseAIException.from(e)
+ }
+
+ /**
+ * Generates an image by inpainting a masked off part of a base image. Inpainting is the process
+ * of filling in missing or masked off parts of the image using context from the original image
+ * and prompt.
+ *
+ * @param image the base image
+ * @param prompt the text input given to the model as a prompt
+ * @param mask the mask which defines where in the image can be painted by Imagen.
+ * @param config the editing configuration settings, it should include an [ImagenEditMode]
+ */
+ @PublicPreviewAPI
+ public suspend fun inpaintImage(
+ image: ImagenInlineImage,
+ prompt: String,
+ mask: ImagenMaskReference,
+ config: ImagenEditingConfig,
+ ): ImagenGenerationResponse {
+ return editImage(listOf(ImagenRawImage(image), mask), prompt, config)
+ }
+
+ /**
+ * Generates an image by outpainting the given image, extending its content beyond the original
+ * borders using context from the original image, and optionally, the prompt.
+ *
+ * @param image the base image
+ * @param newDimensions the new dimensions for the image, *must* be larger than the original
+ * image.
+ * @param newPosition the placement of the base image within the new image. This can either be
+ * coordinates (0,0 is the top left corner) or an alignment (ex:
+ * [ImagenImagePlacement.BOTTOM_CENTER])
+ * @param prompt optional, can be used to specify the background generated if context is
+ * insufficient
+ * @param config the editing configuration settings
+ * @see [ImagenMaskReference.generateMaskAndPadForOutpainting]
+ */
+ @PublicPreviewAPI
+ public suspend fun outpaintImage(
+ image: ImagenInlineImage,
+ newDimensions: Dimensions,
+ newPosition: ImagenImagePlacement = ImagenImagePlacement.CENTER,
+ prompt: String = "",
+ config: ImagenEditingConfig? = null,
+ ): ImagenGenerationResponse {
+ return editImage(
+ ImagenMaskReference.generateMaskAndPadForOutpainting(image, newDimensions, newPosition),
+ prompt,
+ ImagenEditingConfig(ImagenEditMode.OUTPAINT, config?.editSteps)
+ )
+ }
+
+ private fun constructGenerateImageRequest(
+ prompt: String,
+ generationConfig: ImagenGenerationConfig? = null,
+ ): GenerateImageRequest {
+ @OptIn(PublicPreviewAPI::class)
+ return GenerateImageRequest(
+ listOf(GenerateImageRequest.ImagenPrompt(prompt, null)),
+ GenerateImageRequest.ImagenParameters(
+ sampleCount = generationConfig?.numberOfImages ?: 1,
+ includeRaiReason = true,
+ includeSafetyAttributes = true,
+ addWatermark = generationConfig?.addWatermark,
+ personGeneration = safetySettings?.personFilterLevel?.internalVal,
+ negativePrompt = generationConfig?.negativePrompt,
+ safetySetting = safetySettings?.safetyFilterLevel?.internalVal,
+ storageUri = null,
+ aspectRatio = generationConfig?.aspectRatio?.internalVal,
+ imageOutputOptions = generationConfig?.imageFormat?.toInternal(),
+ editMode = null,
+ editConfig = null,
+ ),
+ )
+ }
+
+ @PublicPreviewAPI
+ private fun constructEditRequest(
+ referenceImages: List,
+ prompt: String,
+ editConfig: ImagenEditingConfig?,
): GenerateImageRequest {
+ var maxRefId = referenceImages.mapNotNull { it.referenceId }.maxOrNull() ?: 1
return GenerateImageRequest(
- listOf(GenerateImageRequest.ImagenPrompt(prompt)),
+ listOf(
+ GenerateImageRequest.ImagenPrompt(
+ prompt = prompt,
+ referenceImages = referenceImages.map { it.toInternal(++maxRefId) },
+ )
+ ),
GenerateImageRequest.ImagenParameters(
- sampleCount = config?.numberOfImages ?: 1,
+ sampleCount = generationConfig?.numberOfImages ?: 1,
includeRaiReason = true,
+ includeSafetyAttributes = true,
addWatermark = generationConfig?.addWatermark,
personGeneration = safetySettings?.personFilterLevel?.internalVal,
- negativePrompt = config?.negativePrompt,
+ negativePrompt = generationConfig?.negativePrompt,
safetySetting = safetySettings?.safetyFilterLevel?.internalVal,
- storageUri = gcsUri,
- aspectRatio = config?.aspectRatio?.internalVal,
+ storageUri = null,
+ aspectRatio = generationConfig?.aspectRatio?.internalVal,
imageOutputOptions = generationConfig?.imageFormat?.toInternal(),
+ editMode = editConfig?.editMode?.value,
+ editConfig = editConfig?.toInternal(),
),
)
}
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt
index fe4cae0d187..b0a1b541c6b 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt
@@ -21,6 +21,7 @@ import com.google.firebase.ai.common.APIController
import com.google.firebase.ai.common.AppCheckHeaderProvider
import com.google.firebase.ai.common.JSON
import com.google.firebase.ai.type.Content
+import com.google.firebase.ai.type.GenerativeBackend
import com.google.firebase.ai.type.LiveClientSetupMessage
import com.google.firebase.ai.type.LiveGenerationConfig
import com.google.firebase.ai.type.LiveSession
@@ -31,6 +32,7 @@ import com.google.firebase.ai.type.Tool
import com.google.firebase.annotations.concurrent.Blocking
import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider
import com.google.firebase.auth.internal.InternalAuthProvider
+import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession
import io.ktor.websocket.Frame
import io.ktor.websocket.close
import io.ktor.websocket.readBytes
@@ -54,6 +56,7 @@ internal constructor(
private val tools: List? = null,
private val systemInstruction: Content? = null,
private val location: String,
+ private val firebaseApp: FirebaseApp,
private val controller: APIController
) {
internal constructor(
@@ -68,6 +71,8 @@ internal constructor(
requestOptions: RequestOptions = RequestOptions(),
appCheckTokenProvider: InteropAppCheckTokenProvider? = null,
internalAuthProvider: InternalAuthProvider? = null,
+ generativeBackend: GenerativeBackend,
+ useLimitedUseAppCheckTokens: Boolean,
) : this(
modelName,
blockingDispatcher,
@@ -75,13 +80,20 @@ internal constructor(
tools,
systemInstruction,
location,
+ firebaseApp,
APIController(
apiKey,
modelName,
requestOptions,
"gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}",
firebaseApp,
- AppCheckHeaderProvider(TAG, appCheckTokenProvider, internalAuthProvider),
+ AppCheckHeaderProvider(
+ TAG,
+ useLimitedUseAppCheckTokens,
+ appCheckTokenProvider,
+ internalAuthProvider
+ ),
+ generativeBackend
),
)
@@ -99,24 +111,34 @@ internal constructor(
modelName,
config?.toInternal(),
tools?.map { it.toInternal() },
- systemInstruction?.toInternal()
+ systemInstruction?.toInternal(),
+ config?.inputAudioTranscription?.toInternal(),
+ config?.outputAudioTranscription?.toInternal()
)
.toInternal()
val data: String = Json.encodeToString(clientMessage)
+ var webSession: DefaultClientWebSocketSession? = null
try {
- val webSession = controller.getWebSocketSession(location)
+ webSession = controller.getWebSocketSession(location)
webSession.send(Frame.Text(data))
val receivedJsonStr = webSession.incoming.receive().readBytes().toString(Charsets.UTF_8)
val receivedJson = JSON.parseToJsonElement(receivedJsonStr)
return if (receivedJson is JsonObject && "setupComplete" in receivedJson) {
- LiveSession(session = webSession, blockingDispatcher = blockingDispatcher)
+ LiveSession(
+ session = webSession,
+ blockingDispatcher = blockingDispatcher,
+ firebaseApp = firebaseApp
+ )
} else {
webSession.close()
throw ServiceConnectionHandshakeFailedException("Unable to connect to the server")
}
} catch (e: ClosedReceiveChannelException) {
- throw ServiceConnectionHandshakeFailedException("Channel was closed by the server", e)
+ val reason = webSession?.closeReason?.await()
+ val message =
+ "Channel was closed by the server.${if (reason != null) " Details: ${reason.message}" else ""}"
+ throw ServiceConnectionHandshakeFailedException(message, e)
}
}
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/APIController.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/APIController.kt
index 34a4b96b7dd..220e5efedac 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/APIController.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/APIController.kt
@@ -21,14 +21,26 @@ import com.google.firebase.Firebase
import com.google.firebase.FirebaseApp
import com.google.firebase.ai.common.util.decodeToFlow
import com.google.firebase.ai.common.util.fullModelName
+import com.google.firebase.ai.type.APINotConfiguredException
import com.google.firebase.ai.type.CountTokensResponse
import com.google.firebase.ai.type.FinishReason
+import com.google.firebase.ai.type.FirebaseAIException
import com.google.firebase.ai.type.GRpcErrorResponse
import com.google.firebase.ai.type.GenerateContentResponse
+import com.google.firebase.ai.type.GenerativeBackend
+import com.google.firebase.ai.type.GenerativeBackendEnum
import com.google.firebase.ai.type.ImagenGenerationResponse
+import com.google.firebase.ai.type.InvalidAPIKeyException
+import com.google.firebase.ai.type.PromptBlockedException
import com.google.firebase.ai.type.PublicPreviewAPI
+import com.google.firebase.ai.type.QuotaExceededException
import com.google.firebase.ai.type.RequestOptions
import com.google.firebase.ai.type.Response
+import com.google.firebase.ai.type.ResponseStoppedException
+import com.google.firebase.ai.type.SerializationException
+import com.google.firebase.ai.type.ServerException
+import com.google.firebase.ai.type.ServiceDisabledException
+import com.google.firebase.ai.type.UnsupportedUserLocationException
import com.google.firebase.options
import io.ktor.client.HttpClient
import io.ktor.client.call.body
@@ -36,7 +48,7 @@ import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
-import io.ktor.client.plugins.websocket.ClientWebSocketSession
+import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.plugins.websocket.webSocketSession
import io.ktor.client.request.HttpRequestBuilder
@@ -65,6 +77,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.ClassDiscriminatorMode
import kotlinx.serialization.json.Json
@OptIn(ExperimentalSerializationApi::class)
@@ -73,6 +86,7 @@ internal val JSON = Json {
prettyPrint = false
isLenient = true
explicitNulls = false
+ classDiscriminatorMode = ClassDiscriminatorMode.NONE
}
/**
@@ -99,6 +113,7 @@ internal constructor(
private val appVersion: Int = 0,
private val googleAppId: String,
private val headerProvider: HeaderProvider?,
+ private val backend: GenerativeBackend? = null
) {
constructor(
@@ -108,6 +123,7 @@ internal constructor(
apiClient: String,
firebaseApp: FirebaseApp,
headerProvider: HeaderProvider? = null,
+ backend: GenerativeBackend? = null,
) : this(
key,
model,
@@ -117,7 +133,8 @@ internal constructor(
firebaseApp,
getVersionNumber(firebaseApp),
firebaseApp.options.applicationId,
- headerProvider
+ headerProvider,
+ backend
)
private val model = fullModelName(model)
@@ -144,7 +161,7 @@ internal constructor(
.body()
.validate()
} catch (e: Throwable) {
- throw FirebaseCommonAIException.from(e)
+ throw FirebaseAIException.from(e)
}
suspend fun generateImage(request: GenerateImageRequest): ImagenGenerationResponse.Internal =
@@ -157,13 +174,19 @@ internal constructor(
.also { validateResponse(it) }
.body()
} catch (e: Throwable) {
- throw FirebaseCommonAIException.from(e)
+ throw FirebaseAIException.from(e)
}
private fun getBidiEndpoint(location: String): String =
- "wss://firebasevertexai.googleapis.com/ws/google.firebase.vertexai.v1beta.LlmBidiService/BidiGenerateContent/locations/$location?key=$key"
+ when (backend?.backend) {
+ GenerativeBackendEnum.VERTEX_AI,
+ null ->
+ "wss://firebasevertexai.googleapis.com/ws/google.firebase.vertexai.v1beta.LlmBidiService/BidiGenerateContent/locations/$location?key=$key"
+ GenerativeBackendEnum.GOOGLE_AI ->
+ "wss://firebasevertexai.googleapis.com/ws/google.firebase.vertexai.v1beta.GenerativeService/BidiGenerateContent?key=$key"
+ }
- suspend fun getWebSocketSession(location: String): ClientWebSocketSession =
+ suspend fun getWebSocketSession(location: String): DefaultClientWebSocketSession =
client.webSocketSession(getBidiEndpoint(location)) { applyCommonHeaders() }
fun generateContentStream(
@@ -176,7 +199,7 @@ internal constructor(
applyCommonConfiguration(request)
}
.map { it.validate() }
- .catch { throw FirebaseCommonAIException.from(it) }
+ .catch { throw FirebaseAIException.from(it) }
suspend fun countTokens(request: CountTokensRequest): CountTokensResponse.Internal =
try {
@@ -188,7 +211,7 @@ internal constructor(
.also { validateResponse(it) }
.body()
} catch (e: Throwable) {
- throw FirebaseCommonAIException.from(e)
+ throw FirebaseAIException.from(e)
}
private fun HttpRequestBuilder.applyCommonHeaders() {
@@ -323,6 +346,11 @@ private suspend fun validateResponse(response: HttpResponse) {
if (message.contains("The prompt could not be submitted")) {
throw PromptBlockedException(message)
}
+ if (message.contains("genai config not found")) {
+ throw APINotConfiguredException(
+ "The Gemini Developer API is not enabled, to enable and configure, see https://firebase.google.com/docs/ai-logic/faq-and-troubleshooting?api=dev#error-genai-config-not-found"
+ )
+ }
getServiceDisabledErrorDetailsOrNull(error)?.let {
val errorMessage =
if (it.metadata?.get("service") == "firebasevertexai.googleapis.com") {
@@ -356,9 +384,9 @@ private fun GenerateContentResponse.Internal.validate() = apply {
if ((candidates?.isEmpty() != false) && promptFeedback == null) {
throw SerializationException("Error deserializing response, found no valid fields")
}
- promptFeedback?.blockReason?.let { throw PromptBlockedException(this) }
+ promptFeedback?.blockReason?.let { throw PromptBlockedException(this.toPublic(), null, null) }
candidates
?.mapNotNull { it.finishReason }
?.firstOrNull { it != FinishReason.Internal.STOP }
- ?.let { throw ResponseStoppedException(this) }
+ ?.let { throw ResponseStoppedException(this.toPublic()) }
}
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/AppCheckHeaderProvider.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/AppCheckHeaderProvider.kt
index d5a5ec32305..96214c98a2d 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/AppCheckHeaderProvider.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/AppCheckHeaderProvider.kt
@@ -25,6 +25,7 @@ import kotlinx.coroutines.tasks.await
internal class AppCheckHeaderProvider(
private val logTag: String,
+ private val useLimitedUseAppCheckTokens: Boolean,
private val appCheckTokenProvider: InteropAppCheckTokenProvider? = null,
private val internalAuthProvider: InternalAuthProvider? = null,
) : HeaderProvider {
@@ -36,7 +37,14 @@ internal class AppCheckHeaderProvider(
if (appCheckTokenProvider == null) {
Log.w(logTag, "AppCheck not registered, skipping")
} else {
- val token = appCheckTokenProvider.getToken(false).await()
+ val result =
+ if (useLimitedUseAppCheckTokens) {
+ appCheckTokenProvider.limitedUseToken
+ } else {
+ appCheckTokenProvider.getToken(false)
+ }
+
+ val token = result.await()
if (token.error != null) {
Log.w(logTag, "Error obtaining AppCheck token", token.error)
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/Exceptions.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/Exceptions.kt
deleted file mode 100644
index 6e2ff67ca4d..00000000000
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/Exceptions.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright 2024 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.ai.common
-
-import com.google.firebase.ai.type.GenerateContentResponse
-import io.ktor.serialization.JsonConvertException
-import kotlinx.coroutines.TimeoutCancellationException
-
-/** Parent class for any errors that occur. */
-internal sealed class FirebaseCommonAIException(message: String, cause: Throwable? = null) :
- RuntimeException(message, cause) {
- companion object {
-
- /**
- * Converts a [Throwable] to a [FirebaseCommonAIException].
- *
- * Will populate default messages as expected, and propagate the provided [cause] through the
- * resulting exception.
- */
- fun from(cause: Throwable): FirebaseCommonAIException =
- when (cause) {
- is FirebaseCommonAIException -> cause
- is JsonConvertException,
- is kotlinx.serialization.SerializationException ->
- SerializationException(
- "Something went wrong while trying to deserialize a response from the server.",
- cause,
- )
- is TimeoutCancellationException ->
- RequestTimeoutException("The request failed to complete in the allotted time.")
- else -> UnknownException("Something unexpected happened.", cause)
- }
- }
-}
-
-/** Something went wrong while trying to deserialize a response from the server. */
-internal class SerializationException(message: String, cause: Throwable? = null) :
- FirebaseCommonAIException(message, cause)
-
-/** The server responded with a non 200 response code. */
-internal class ServerException(message: String, cause: Throwable? = null) :
- FirebaseCommonAIException(message, cause)
-
-/** The server responded that the API Key is no valid. */
-internal class InvalidAPIKeyException(message: String, cause: Throwable? = null) :
- FirebaseCommonAIException(message, cause)
-
-/**
- * A request was blocked for some reason.
- *
- * See the [response's][response] `promptFeedback.blockReason` for more information.
- *
- * @property response the full server response for the request.
- */
-internal class PromptBlockedException
-internal constructor(
- val response: GenerateContentResponse.Internal?,
- cause: Throwable? = null,
- message: String? = null,
-) :
- FirebaseCommonAIException(
- "Prompt was blocked: ${response?.promptFeedback?.blockReason?.name?: message}",
- cause,
- ) {
- internal constructor(message: String, cause: Throwable? = null) : this(null, cause, message)
-}
-
-/**
- * The user's location (region) is not supported by the API.
- *
- * See the Google documentation for a
- * [list of regions](https://ai.google.dev/available_regions#available_regions) (countries and
- * territories) where the API is available.
- */
-internal class UnsupportedUserLocationException(cause: Throwable? = null) :
- FirebaseCommonAIException("User location is not supported for the API use.", cause)
-
-/**
- * Some form of state occurred that shouldn't have.
- *
- * Usually indicative of consumer error.
- */
-internal class InvalidStateException(message: String, cause: Throwable? = null) :
- FirebaseCommonAIException(message, cause)
-
-/**
- * A request was stopped during generation for some reason.
- *
- * @property response the full server response for the request
- */
-internal class ResponseStoppedException(
- val response: GenerateContentResponse.Internal,
- cause: Throwable? = null
-) :
- FirebaseCommonAIException(
- "Content generation stopped. Reason: ${response.candidates?.first()?.finishReason?.name}",
- cause,
- )
-
-/**
- * A request took too long to complete.
- *
- * Usually occurs due to a user specified [timeout][RequestOptions.timeout].
- */
-internal class RequestTimeoutException(message: String, cause: Throwable? = null) :
- FirebaseCommonAIException(message, cause)
-
-/** The quota for this API key is depleted, retry this request at a later time. */
-internal class QuotaExceededException(message: String, cause: Throwable? = null) :
- FirebaseCommonAIException(message, cause)
-
-/** The service is not enabled for this project. Visit the Firebase Console to enable it. */
-internal class ServiceDisabledException(message: String, cause: Throwable? = null) :
- FirebaseCommonAIException(message, cause)
-
-/** Catch all case for exceptions not explicitly expected. */
-internal class UnknownException(message: String, cause: Throwable? = null) :
- FirebaseCommonAIException(message, cause)
-
-internal class ContentBlockedException(message: String, cause: Throwable? = null) :
- FirebaseCommonAIException(message, cause)
-
-internal fun makeMissingCaseException(
- source: String,
- ordinal: Int
-): com.google.firebase.ai.type.SerializationException {
- return com.google.firebase.ai.type.SerializationException(
- """
- |Missing case for a $source: $ordinal
- |This error indicates that one of the `toInternal` conversions needs updating.
- |If you're a developer seeing this exception, please file an issue on our GitHub repo:
- |https://github.com/firebase/firebase-android-sdk
- """
- .trimMargin()
- )
-}
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/Request.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/Request.kt
index ebc3db7f282..bb6bf242bb0 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/Request.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/Request.kt
@@ -21,7 +21,9 @@ import com.google.firebase.ai.common.util.fullModelName
import com.google.firebase.ai.common.util.trimmedModelName
import com.google.firebase.ai.type.Content
import com.google.firebase.ai.type.GenerationConfig
+import com.google.firebase.ai.type.ImagenEditingConfig
import com.google.firebase.ai.type.ImagenImageFormat
+import com.google.firebase.ai.type.ImagenReferenceImage
import com.google.firebase.ai.type.PublicPreviewAPI
import com.google.firebase.ai.type.SafetySetting
import com.google.firebase.ai.type.Tool
@@ -75,17 +77,22 @@ internal data class CountTokensRequest(
}
@Serializable
+@OptIn(PublicPreviewAPI::class)
internal data class GenerateImageRequest(
val instances: List,
val parameters: ImagenParameters,
) : Request {
- @Serializable internal data class ImagenPrompt(val prompt: String)
+ @Serializable
+ internal data class ImagenPrompt(
+ val prompt: String?,
+ val referenceImages: List?
+ )
- @OptIn(PublicPreviewAPI::class)
@Serializable
internal data class ImagenParameters(
val sampleCount: Int,
val includeRaiReason: Boolean,
+ val includeSafetyAttributes: Boolean,
val storageUri: String?,
val negativePrompt: String?,
val aspectRatio: String?,
@@ -93,5 +100,19 @@ internal data class GenerateImageRequest(
val personGeneration: String?,
val addWatermark: Boolean?,
val imageOutputOptions: ImagenImageFormat.Internal?,
+ val editMode: String?,
+ @OptIn(PublicPreviewAPI::class) val editConfig: ImagenEditingConfig.Internal?,
)
+
+ @Serializable
+ internal enum class ReferenceType {
+ @SerialName("REFERENCE_TYPE_UNSPECIFIED") UNSPECIFIED,
+ @SerialName("REFERENCE_TYPE_RAW") RAW,
+ @SerialName("REFERENCE_TYPE_MASK") MASK,
+ @SerialName("REFERENCE_TYPE_CONTROL") CONTROL,
+ @SerialName("REFERENCE_TYPE_STYLE") STYLE,
+ @SerialName("REFERENCE_TYPE_SUBJECT") SUBJECT,
+ @SerialName("REFERENCE_TYPE_MASKED_SUBJECT") MASKED_SUBJECT,
+ @SerialName("REFERENCE_TYPE_PRODUCT") PRODUCT
+ }
}
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/util/android.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/util/android.kt
index 4d7a1e46097..9f1bbd37260 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/util/android.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/util/android.kt
@@ -17,8 +17,8 @@
package com.google.firebase.ai.common.util
import android.media.AudioRecord
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.yield
/**
* The minimum buffer size for this instance.
@@ -38,13 +38,17 @@ internal fun AudioRecord.readAsFlow() = flow {
while (true) {
if (recordingState != AudioRecord.RECORDSTATE_RECORDING) {
- yield()
+ // delay uses a different scheduler in the backend, so it's "stickier" in its enforcement when
+ // compared to yield.
+ delay(0)
continue
}
-
val bytesRead = read(buffer, 0, buffer.size)
if (bytesRead > 0) {
emit(buffer.copyOf(bytesRead))
}
+ // delay uses a different scheduler in the backend, so it's "stickier" in its enforcement when
+ // compared to yield.
+ delay(0)
}
}
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/util/serialization.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/util/serialization.kt
index 91490da4126..db0a3745785 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/util/serialization.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/common/util/serialization.kt
@@ -17,7 +17,7 @@
package com.google.firebase.ai.common.util
import android.util.Log
-import com.google.firebase.ai.common.SerializationException
+import com.google.firebase.ai.type.SerializationException
import kotlin.reflect.KClass
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/java/GenerativeModelFutures.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/java/GenerativeModelFutures.kt
index 57a531c1cd8..51a90135e12 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/java/GenerativeModelFutures.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/java/GenerativeModelFutures.kt
@@ -42,7 +42,8 @@ public abstract class GenerativeModelFutures internal constructor() {
* @throws [FirebaseAIException] if the request failed.
*/
public abstract fun generateContent(
- vararg prompt: Content
+ prompt: Content,
+ vararg prompts: Content
): ListenableFuture
/**
@@ -53,7 +54,8 @@ public abstract class GenerativeModelFutures internal constructor() {
* @throws [FirebaseAIException] if the request failed.
*/
public abstract fun generateContentStream(
- vararg prompt: Content
+ prompt: Content,
+ vararg prompts: Content
): Publisher
/**
@@ -63,7 +65,10 @@ public abstract class GenerativeModelFutures internal constructor() {
* @return The [CountTokensResponse] of running the model's tokenizer on the input.
* @throws [FirebaseAIException] if the request failed.
*/
- public abstract fun countTokens(vararg prompt: Content): ListenableFuture
+ public abstract fun countTokens(
+ prompt: Content,
+ vararg prompts: Content
+ ): ListenableFuture
/**
* Creates a [ChatFutures] instance which internally tracks the ongoing conversation with the
@@ -83,15 +88,22 @@ public abstract class GenerativeModelFutures internal constructor() {
private class FuturesImpl(private val model: GenerativeModel) : GenerativeModelFutures() {
override fun generateContent(
- vararg prompt: Content
+ prompt: Content,
+ vararg prompts: Content
): ListenableFuture =
- SuspendToFutureAdapter.launchFuture { model.generateContent(*prompt) }
-
- override fun generateContentStream(vararg prompt: Content): Publisher =
- model.generateContentStream(*prompt).asPublisher()
-
- override fun countTokens(vararg prompt: Content): ListenableFuture =
- SuspendToFutureAdapter.launchFuture { model.countTokens(*prompt) }
+ SuspendToFutureAdapter.launchFuture { model.generateContent(prompt, *prompts) }
+
+ override fun generateContentStream(
+ prompt: Content,
+ vararg prompts: Content
+ ): Publisher =
+ model.generateContentStream(prompt, *prompts).asPublisher()
+
+ override fun countTokens(
+ prompt: Content,
+ vararg prompts: Content
+ ): ListenableFuture =
+ SuspendToFutureAdapter.launchFuture { model.countTokens(prompt, *prompts) }
override fun startChat(): ChatFutures = startChat(emptyList())
diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/java/ImagenModelFutures.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/java/ImagenModelFutures.kt
index 99d42d32732..2f0299da406 100644
--- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/java/ImagenModelFutures.kt
+++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/java/ImagenModelFutures.kt
@@ -19,8 +19,14 @@ package com.google.firebase.ai.java
import androidx.concurrent.futures.SuspendToFutureAdapter
import com.google.common.util.concurrent.ListenableFuture
import com.google.firebase.ai.ImagenModel
+import com.google.firebase.ai.type.Dimensions
+import com.google.firebase.ai.type.ImagenEditMode
+import com.google.firebase.ai.type.ImagenEditingConfig
import com.google.firebase.ai.type.ImagenGenerationResponse
+import com.google.firebase.ai.type.ImagenImagePlacement
import com.google.firebase.ai.type.ImagenInlineImage
+import com.google.firebase.ai.type.ImagenMaskReference
+import com.google.firebase.ai.type.ImagenReferenceImage
import com.google.firebase.ai.type.PublicPreviewAPI
/**
@@ -39,6 +45,69 @@ public abstract class ImagenModelFutures internal constructor() {
prompt: String,
): ListenableFuture