Skip to content

Commit a02618e

Browse files
authored
Merge branch 'dconeybe/dataconnect/MutableStateFlowUseUpdateInsteadOfCompareAndSet' into dconeybe/dataconnect/FirebaseDataConnectImplState
2 parents c762afd + 990b568 commit a02618e

File tree

24 files changed

+853
-415
lines changed

24 files changed

+853
-415
lines changed

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinator.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.google.firebase.crashlytics.internal.send.DataTransportCrashlyticsReportSender;
3737
import com.google.firebase.crashlytics.internal.settings.SettingsProvider;
3838
import com.google.firebase.crashlytics.internal.stacktrace.StackTraceTrimmingStrategy;
39+
import java.io.BufferedInputStream;
3940
import java.io.ByteArrayOutputStream;
4041
import java.io.File;
4142
import java.io.IOException;
@@ -427,13 +428,15 @@ private static CrashlyticsReport.ApplicationExitInfo convertApplicationExitInfo(
427428
@VisibleForTesting
428429
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
429430
public static String convertInputStreamToString(InputStream inputStream) throws IOException {
430-
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
431-
byte[] bytes = new byte[DEFAULT_BUFFER_SIZE];
432-
int length;
433-
while ((length = inputStream.read(bytes)) != -1) {
434-
byteArrayOutputStream.write(bytes, 0, length);
431+
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
432+
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
433+
byte[] bytes = new byte[DEFAULT_BUFFER_SIZE];
434+
int length;
435+
while ((length = bufferedInputStream.read(bytes)) != -1) {
436+
byteArrayOutputStream.write(bytes, 0, length);
437+
}
438+
return byteArrayOutputStream.toString(StandardCharsets.UTF_8.name());
435439
}
436-
return byteArrayOutputStream.toString(StandardCharsets.UTF_8.name());
437440
}
438441

439442
/** Finds the first ANR ApplicationExitInfo within the session. */

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/testutil/CompositeIndexTestHelper.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,11 @@ public void assertOnlineAndOfflineResultsMatch(
125125
@NonNull CollectionReference collection,
126126
@NonNull Query query,
127127
@NonNull String... expectedDocs) {
128-
checkOnlineAndOfflineResultsMatch(collection, query, toHashedIds(expectedDocs));
128+
// `checkOnlineAndOfflineResultsMatch` first makes sure all documents needed for
129+
// `query` are in the cache. It does so making a `get` on the first argument.
130+
// Since *all* composite index tests use the same collection, this is very inefficient to do.
131+
// Therefore, we should only do so for tests where `TEST_ID_FIELD` matches the current test.
132+
checkOnlineAndOfflineResultsMatch(this.query(collection), query, toHashedIds(expectedDocs));
129133
}
130134

131135
// Asserts that the IDs in the query snapshot matches the expected Ids. The expected document

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/testutil/IntegrationTestUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ public static List<Object> nullList() {
535535
* @param expectedDocs Ordered list of document keys that are expected to match the query
536536
*/
537537
public static void checkOnlineAndOfflineResultsMatch(
538-
CollectionReference collection, Query query, String... expectedDocs) {
538+
Query collection, Query query, String... expectedDocs) {
539539
// Note: Order matters. The following has to be done in the specific order:
540540

541541
// 1- Pre-populate the cache with the entire collection.

firebase-vertexai/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
# Unreleased
2+
* [changed] **Breaking Change**: `LiveModelFutures.connect` now returns `ListenableFuture<LiveSessionFutures>` instead of `ListenableFuture<LiveSession>`.
3+
* **Action Required:** Remove any transformations from LiveSession object to LiveSessionFutures object.
4+
* **Action Required:** Change type of variable handling `LiveModelFutures.connect` to `ListenableFuture<LiveSessionsFutures>`
5+
* [changed] **Breaking Change**: Removed `UNSPECIFIED` value for enum class `ResponseModality`
6+
* **Action Required:** Remove all references to `ResponseModality.UNSPECIFIED`
7+
* [changed] **Breaking Change**: Renamed `LiveGenerationConfig.setResponseModalities` to `LiveGenerationConfig.setResponseModality`
8+
* **Action Required:** Replace all references of `LiveGenerationConfig.setResponseModalities` with `LiveGenerationConfig.setResponseModality`
29
* [feature] Added support for `HarmBlockThreshold.OFF`. See the
310
[model documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/configure-safety-filters#how_to_configure_content_filters){: .external}
411
for more information.
12+
* [fixed] Improved thread usage when using a `LiveGenerativeModel`. (#6870)
13+
* [fixed] Fixed an issue with `LiveContentResponse` audio data not being present when the model was
14+
interrupted or the turn completed. (#6870)
15+
* [fixed] Fixed an issue with `LiveSession` not converting exceptions to `FirebaseVertexAIException`. (#6870)
16+
517

618
# 16.3.0
719
* [feature] Emits a warning when attempting to use an incompatible model with

firebase-vertexai/api.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ package com.google.firebase.vertexai.java {
115115
}
116116

117117
@com.google.firebase.vertexai.type.PublicPreviewAPI public abstract class LiveModelFutures {
118-
method public abstract com.google.common.util.concurrent.ListenableFuture<com.google.firebase.vertexai.type.LiveSession> connect();
118+
method public abstract com.google.common.util.concurrent.ListenableFuture<com.google.firebase.vertexai.java.LiveSessionFutures> connect();
119119
method public static final com.google.firebase.vertexai.java.LiveModelFutures from(com.google.firebase.vertexai.LiveGenerativeModel model);
120120
field public static final com.google.firebase.vertexai.java.LiveModelFutures.Companion Companion;
121121
}
@@ -132,6 +132,7 @@ package com.google.firebase.vertexai.java {
132132
method public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> send(String text);
133133
method public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> sendFunctionResponse(java.util.List<com.google.firebase.vertexai.type.FunctionResponsePart> functionList);
134134
method public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> sendMediaStream(java.util.List<com.google.firebase.vertexai.type.MediaData> mediaChunks);
135+
method public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> startAudioConversation();
135136
method public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> startAudioConversation(kotlin.jvm.functions.Function1<? super com.google.firebase.vertexai.type.FunctionCallPart,com.google.firebase.vertexai.type.FunctionResponsePart>? functionCallHandler);
136137
method public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> stopAudioConversation();
137138
method public abstract void stopReceiving();
@@ -597,7 +598,7 @@ package com.google.firebase.vertexai.type {
597598
method public com.google.firebase.vertexai.type.LiveGenerationConfig.Builder setFrequencyPenalty(Float? frequencyPenalty);
598599
method public com.google.firebase.vertexai.type.LiveGenerationConfig.Builder setMaxOutputTokens(Integer? maxOutputTokens);
599600
method public com.google.firebase.vertexai.type.LiveGenerationConfig.Builder setPresencePenalty(Float? presencePenalty);
600-
method public com.google.firebase.vertexai.type.LiveGenerationConfig.Builder setResponseModalities(com.google.firebase.vertexai.type.ResponseModality? responseModalities);
601+
method public com.google.firebase.vertexai.type.LiveGenerationConfig.Builder setResponseModality(com.google.firebase.vertexai.type.ResponseModality? responseModality);
601602
method public com.google.firebase.vertexai.type.LiveGenerationConfig.Builder setSpeechConfig(com.google.firebase.vertexai.type.SpeechConfig? speechConfig);
602603
method public com.google.firebase.vertexai.type.LiveGenerationConfig.Builder setTemperature(Float? temperature);
603604
method public com.google.firebase.vertexai.type.LiveGenerationConfig.Builder setTopK(Integer? topK);
@@ -628,7 +629,7 @@ package com.google.firebase.vertexai.type {
628629
method public suspend Object? send(String text, kotlin.coroutines.Continuation<? super kotlin.Unit>);
629630
method public suspend Object? sendFunctionResponse(java.util.List<com.google.firebase.vertexai.type.FunctionResponsePart> functionList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
630631
method public suspend Object? sendMediaStream(java.util.List<com.google.firebase.vertexai.type.MediaData> mediaChunks, kotlin.coroutines.Continuation<? super kotlin.Unit>);
631-
method public suspend Object? startAudioConversation(kotlin.jvm.functions.Function1<? super com.google.firebase.vertexai.type.FunctionCallPart,com.google.firebase.vertexai.type.FunctionResponsePart>? functionCallHandler = null, kotlin.coroutines.Continuation<? super kotlin.Unit>);
632+
method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public suspend Object? startAudioConversation(kotlin.jvm.functions.Function1<? super com.google.firebase.vertexai.type.FunctionCallPart,com.google.firebase.vertexai.type.FunctionResponsePart>? functionCallHandler = null, kotlin.coroutines.Continuation<? super kotlin.Unit>);
632633
method public void stopAudioConversation();
633634
method public void stopReceiving();
634635
}
@@ -696,7 +697,6 @@ package com.google.firebase.vertexai.type {
696697
field public static final com.google.firebase.vertexai.type.ResponseModality.Companion Companion;
697698
field public static final com.google.firebase.vertexai.type.ResponseModality IMAGE;
698699
field public static final com.google.firebase.vertexai.type.ResponseModality TEXT;
699-
field public static final com.google.firebase.vertexai.type.ResponseModality UNSPECIFIED;
700700
}
701701

702702
public static final class ResponseModality.Companion {

firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/FirebaseVertexAI.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package com.google.firebase.vertexai
1919
import android.util.Log
2020
import com.google.firebase.Firebase
2121
import com.google.firebase.FirebaseApp
22-
import com.google.firebase.annotations.concurrent.Background
22+
import com.google.firebase.annotations.concurrent.Blocking
2323
import com.google.firebase.app
2424
import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider
2525
import com.google.firebase.auth.internal.InternalAuthProvider
@@ -41,7 +41,7 @@ import kotlin.coroutines.CoroutineContext
4141
public class FirebaseVertexAI
4242
internal constructor(
4343
private val firebaseApp: FirebaseApp,
44-
@Background private val backgroundDispatcher: CoroutineContext,
44+
@Blocking private val blockingDispatcher: CoroutineContext,
4545
private val location: String,
4646
private val appCheckProvider: Provider<InteropAppCheckTokenProvider>,
4747
private val internalAuthProvider: Provider<InternalAuthProvider>,
@@ -133,7 +133,7 @@ internal constructor(
133133
"projects/${firebaseApp.options.projectId}/locations/${location}/publishers/google/models/${modelName}",
134134
firebaseApp.options.apiKey,
135135
firebaseApp,
136-
backgroundDispatcher,
136+
blockingDispatcher,
137137
generationConfig,
138138
tools,
139139
systemInstruction,

firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/FirebaseVertexAIMultiResourceComponent.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package com.google.firebase.vertexai
1818

1919
import androidx.annotation.GuardedBy
2020
import com.google.firebase.FirebaseApp
21-
import com.google.firebase.annotations.concurrent.Background
21+
import com.google.firebase.annotations.concurrent.Blocking
2222
import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider
2323
import com.google.firebase.auth.internal.InternalAuthProvider
2424
import com.google.firebase.inject.Provider
@@ -31,7 +31,7 @@ import kotlin.coroutines.CoroutineContext
3131
*/
3232
internal class FirebaseVertexAIMultiResourceComponent(
3333
private val app: FirebaseApp,
34-
@Background val backgroundDispatcher: CoroutineContext,
34+
@Blocking val blockingDispatcher: CoroutineContext,
3535
private val appCheckProvider: Provider<InteropAppCheckTokenProvider>,
3636
private val internalAuthProvider: Provider<InternalAuthProvider>,
3737
) {
@@ -43,7 +43,7 @@ internal class FirebaseVertexAIMultiResourceComponent(
4343
instances[location]
4444
?: FirebaseVertexAI(
4545
app,
46-
backgroundDispatcher,
46+
blockingDispatcher,
4747
location,
4848
appCheckProvider,
4949
internalAuthProvider

firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/FirebaseVertexAIRegistrar.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package com.google.firebase.vertexai
1818

1919
import androidx.annotation.Keep
2020
import com.google.firebase.FirebaseApp
21-
import com.google.firebase.annotations.concurrent.Background
21+
import com.google.firebase.annotations.concurrent.Blocking
2222
import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider
2323
import com.google.firebase.auth.internal.InternalAuthProvider
2424
import com.google.firebase.components.Component
@@ -41,13 +41,13 @@ internal class FirebaseVertexAIRegistrar : ComponentRegistrar {
4141
Component.builder(FirebaseVertexAIMultiResourceComponent::class.java)
4242
.name(LIBRARY_NAME)
4343
.add(Dependency.required(firebaseApp))
44-
.add(Dependency.required(backgroundDispatcher))
44+
.add(Dependency.required(blockingDispatcher))
4545
.add(Dependency.optionalProvider(appCheckInterop))
4646
.add(Dependency.optionalProvider(internalAuthProvider))
4747
.factory { container ->
4848
FirebaseVertexAIMultiResourceComponent(
4949
container[firebaseApp],
50-
container.get(backgroundDispatcher),
50+
container.get(blockingDispatcher),
5151
container.getProvider(appCheckInterop),
5252
container.getProvider(internalAuthProvider)
5353
)
@@ -62,7 +62,7 @@ internal class FirebaseVertexAIRegistrar : ComponentRegistrar {
6262
private val firebaseApp = unqualified(FirebaseApp::class.java)
6363
private val appCheckInterop = unqualified(InteropAppCheckTokenProvider::class.java)
6464
private val internalAuthProvider = unqualified(InternalAuthProvider::class.java)
65-
private val backgroundDispatcher =
66-
Qualified.qualified(Background::class.java, CoroutineDispatcher::class.java)
65+
private val blockingDispatcher =
66+
Qualified.qualified(Blocking::class.java, CoroutineDispatcher::class.java)
6767
}
6868
}

firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/LiveGenerativeModel.kt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
package com.google.firebase.vertexai
1818

1919
import com.google.firebase.FirebaseApp
20-
import com.google.firebase.annotations.concurrent.Background
20+
import com.google.firebase.annotations.concurrent.Blocking
2121
import com.google.firebase.appcheck.interop.InteropAppCheckTokenProvider
2222
import com.google.firebase.auth.internal.InternalAuthProvider
2323
import com.google.firebase.vertexai.common.APIController
2424
import com.google.firebase.vertexai.common.AppCheckHeaderProvider
25-
import com.google.firebase.vertexai.type.BidiGenerateContentClientMessage
25+
import com.google.firebase.vertexai.common.JSON
2626
import com.google.firebase.vertexai.type.Content
27+
import com.google.firebase.vertexai.type.LiveClientSetupMessage
2728
import com.google.firebase.vertexai.type.LiveGenerationConfig
2829
import com.google.firebase.vertexai.type.LiveSession
2930
import com.google.firebase.vertexai.type.PublicPreviewAPI
@@ -38,6 +39,7 @@ import kotlinx.coroutines.channels.ClosedReceiveChannelException
3839
import kotlinx.serialization.ExperimentalSerializationApi
3940
import kotlinx.serialization.encodeToString
4041
import kotlinx.serialization.json.Json
42+
import kotlinx.serialization.json.JsonObject
4143

4244
/**
4345
* Represents a multimodal model (like Gemini) capable of real-time content generation based on
@@ -47,7 +49,7 @@ import kotlinx.serialization.json.Json
4749
public class LiveGenerativeModel
4850
internal constructor(
4951
private val modelName: String,
50-
@Background private val backgroundDispatcher: CoroutineContext,
52+
@Blocking private val blockingDispatcher: CoroutineContext,
5153
private val config: LiveGenerationConfig? = null,
5254
private val tools: List<Tool>? = null,
5355
private val systemInstruction: Content? = null,
@@ -58,7 +60,7 @@ internal constructor(
5860
modelName: String,
5961
apiKey: String,
6062
firebaseApp: FirebaseApp,
61-
backgroundDispatcher: CoroutineContext,
63+
blockingDispatcher: CoroutineContext,
6264
config: LiveGenerationConfig? = null,
6365
tools: List<Tool>? = null,
6466
systemInstruction: Content? = null,
@@ -68,7 +70,7 @@ internal constructor(
6870
internalAuthProvider: InternalAuthProvider? = null,
6971
) : this(
7072
modelName,
71-
backgroundDispatcher,
73+
blockingDispatcher,
7274
config,
7375
tools,
7476
systemInstruction,
@@ -93,7 +95,7 @@ internal constructor(
9395
@OptIn(ExperimentalSerializationApi::class)
9496
public suspend fun connect(): LiveSession {
9597
val clientMessage =
96-
BidiGenerateContentClientMessage(
98+
LiveClientSetupMessage(
9799
modelName,
98100
config?.toInternal(),
99101
tools?.map { it.toInternal() },
@@ -104,10 +106,11 @@ internal constructor(
104106
try {
105107
val webSession = controller.getWebSocketSession(location)
106108
webSession.send(Frame.Text(data))
107-
val receivedJson = webSession.incoming.receive().readBytes().toString(Charsets.UTF_8)
108-
// TODO: Try to decode the json instead of string matching.
109-
return if (receivedJson.contains("setupComplete")) {
110-
LiveSession(session = webSession, backgroundDispatcher = backgroundDispatcher)
109+
val receivedJsonStr = webSession.incoming.receive().readBytes().toString(Charsets.UTF_8)
110+
val receivedJson = JSON.parseToJsonElement(receivedJsonStr)
111+
112+
return if (receivedJson is JsonObject && "setupComplete" in receivedJson) {
113+
LiveSession(session = webSession, blockingDispatcher = blockingDispatcher)
111114
} else {
112115
webSession.close()
113116
throw ServiceConnectionHandshakeFailedException("Unable to connect to the server")

firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/common/APIController.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ internal constructor(
165165

166166
suspend fun getWebSocketSession(location: String): ClientWebSocketSession =
167167
client.webSocketSession(getBidiEndpoint(location))
168+
168169
fun generateContentStream(
169170
request: GenerateContentRequest
170171
): Flow<GenerateContentResponse.Internal> =
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.vertexai.common.util
18+
19+
import android.media.AudioRecord
20+
import kotlinx.coroutines.flow.flow
21+
import kotlinx.coroutines.yield
22+
23+
/**
24+
* The minimum buffer size for this instance.
25+
*
26+
* The same as calling [AudioRecord.getMinBufferSize], except the params are pre-populated.
27+
*/
28+
internal val AudioRecord.minBufferSize: Int
29+
get() = AudioRecord.getMinBufferSize(sampleRate, channelConfiguration, audioFormat)
30+
31+
/**
32+
* Reads from this [AudioRecord] and returns the data in a flow.
33+
*
34+
* Will yield when this instance is not recording.
35+
*/
36+
internal fun AudioRecord.readAsFlow() = flow {
37+
val buffer = ByteArray(minBufferSize)
38+
39+
while (true) {
40+
if (recordingState != AudioRecord.RECORDSTATE_RECORDING) {
41+
yield()
42+
continue
43+
}
44+
45+
val bytesRead = read(buffer, 0, buffer.size)
46+
if (bytesRead > 0) {
47+
emit(buffer.copyOf(bytesRead))
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)