Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions firebase-ai/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Unreleased

- [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.

# 17.2.0

- [feature] Added support for returning thought summaries, which are synthesized versions of a
Expand Down
6 changes: 6 additions & 0 deletions firebase-ai/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,8 @@ 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<com.google.firebase.ai.type.LiveServerMessage> 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>);
Expand Down Expand Up @@ -918,6 +920,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;
Expand Down
2 changes: 1 addition & 1 deletion firebase-ai/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.

version=17.2.1
version=17.3.0
latestReleasedVersion=17.2.0
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ internal constructor(
private val tools: List<Tool>? = null,
private val systemInstruction: Content? = null,
private val location: String,
private val controller: APIController,
private val firebaseApp: FirebaseApp,
private val controller: APIController
) {
internal constructor(
modelName: String,
Expand All @@ -78,6 +79,7 @@ internal constructor(
tools,
systemInstruction,
location,
firebaseApp,
APIController(
apiKey,
modelName,
Expand Down Expand Up @@ -119,7 +121,11 @@ internal constructor(
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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ public class AudioRecordInitializationFailedException(message: String) :
public class ServiceConnectionHandshakeFailedException(message: String, cause: Throwable? = null) :
FirebaseAIException(message, cause)

/** The request is missing a permission that is required to perform the requested operation. */
public class PermissionMissingException(message: String, cause: Throwable? = null) :
FirebaseAIException(message, cause)

/** Catch all case for exceptions not explicitly expected. */
public class UnknownException internal constructor(message: String, cause: Throwable? = null) :
FirebaseAIException(message, cause)
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
package com.google.firebase.ai.type

import android.Manifest.permission.RECORD_AUDIO
import android.content.pm.PackageManager
import android.media.AudioFormat
import android.media.AudioTrack
import android.util.Log
import androidx.annotation.RequiresPermission
import androidx.core.content.ContextCompat
import com.google.firebase.FirebaseApp
import com.google.firebase.ai.common.JSON
import com.google.firebase.ai.common.util.CancelledCoroutineScope
import com.google.firebase.ai.common.util.accumulateUntil
Expand Down Expand Up @@ -58,7 +61,8 @@ public class LiveSession
internal constructor(
private val session: ClientWebSocketSession,
@Blocking private val blockingDispatcher: CoroutineContext,
private var audioHelper: AudioHelper? = null
private var audioHelper: AudioHelper? = null,
private val firebaseApp: FirebaseApp,
) {
/**
* Coroutine scope that we batch data on for [startAudioConversation].
Expand Down Expand Up @@ -93,6 +97,14 @@ internal constructor(
public suspend fun startAudioConversation(
functionCallHandler: ((FunctionCallPart) -> FunctionResponsePart)? = null
) {

val context = firebaseApp.applicationContext
if (
ContextCompat.checkSelfPermission(context, RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED
) {
throw PermissionMissingException("Audio access not provided by the user")
}

FirebaseAIException.catchAsync {
if (scope.isActive) {
Log.w(
Expand Down Expand Up @@ -131,6 +143,12 @@ internal constructor(
}
}

/** Indicates whether the underlying websocket connection is active. */
public fun isClosed(): Boolean = !(session.isActive && !session.incoming.tryReceive().isClosed)

/** Indicates whether an audio conversation is being used for this session object. */
public fun isAudioConversationActive(): Boolean = (audioHelper != null)

/**
* Receives responses from the model for both streaming and standard requests.
*
Expand Down