From 48362192425b04242e4bed837414333db6096cc8 Mon Sep 17 00:00:00 2001 From: cartland Date: Tue, 7 Oct 2025 16:39:19 -0700 Subject: [PATCH 1/3] Add Gemini Developer API code snippets This commit introduces a collection of code snippets demonstrating the usage of the Gemini Developer API. It includes examples for model configuration, text-only and multimodal inputs (image, audio, video), multi-turn chat, and image generation/editing. The necessary dependencies for Firebase AI, Guava, and Reactive Streams have been added to gradle/libs.versions.toml and misc/build.gradle.kts. Region-Tag: android_gemini_developer_api_gemini_25_flash_model Region-Tag: android_gemini_developer_api_gemini_25_flash_image_model Region-Tag: android_gemini_developer_api_text_only_input Region-Tag: android_gemini_developer_api_multimodal_input Region-Tag: android_gemini_developer_api_multimodal_audio_input Region-Tag: android_gemini_developer_api_multimodal_video_input Region-Tag: android_gemini_developer_api_multiturn_chat Region-Tag: android_gemini_developer_api_generate_image_from_text Region-Tag: android_gemini_developer_api_edit_image Region-Tag: android_gemini_developer_api_edit_image_chat Region-Tag: android_gemini_developer_api_gemini_25_flash_model_java Region-Tag: android_gemini_developer_api_gemini_25_flash_image_model_java Region-Tag: android_gemini_developer_api_text_only_input_java Region-Tag: android_gemini_developer_api_multimodal_input_java Region-Tag: android_gemini_developer_api_multimodal_audio_input_java Region-Tag: android_gemini_developer_api_multimodal_video_input_java Region-Tag: android_gemini_developer_api_multiturn_chat_java Region-Tag: android_gemini_developer_api_generate_image_from_text_java Region-Tag: android_gemini_developer_api_edit_image_java Region-Tag: android_gemini_developer_api_edit_image_chat_java --- gradle/libs.versions.toml | 7 + misc/build.gradle.kts | 5 + .../snippets/ai/GeminiDeveloperApiSnippets.kt | 196 ++++++++++ .../ai/GeminiDeveloperApiSnippetsJava.java | 370 ++++++++++++++++++ misc/src/main/res/drawable/scones.xml | 19 + 5 files changed, 597 insertions(+) create mode 100644 misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippets.kt create mode 100644 misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippetsJava.java create mode 100644 misc/src/main/res/drawable/scones.xml diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f330b1f06..b54e695f1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,10 +40,13 @@ compose-latest = "1.9.3" composeUiTooling = "1.5.3" coreSplashscreen = "1.0.1" coroutines = "1.10.2" +firebase-bom = "34.3.0" glide = "1.0.0-beta08" google-maps = "19.2.0" gradle-versions = "0.53.0" guava = "33.5.0-jre" +guava-android = "31.0.1-android" +reactive-streams = "1.0.4" hilt = "2.57.2" horologist = "0.8.2-alpha" junit = "4.13.2" @@ -175,11 +178,15 @@ appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" coil-kt-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "wearComposeFoundation" } compose-ui-tooling = { module = "androidx.wear.compose:compose-ui-tooling", version.ref = "composeUiTooling" } +firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" } +firebase-ai = { module = "com.google.firebase:firebase-ai" } glide-compose = { module = "com.github.bumptech.glide:compose", version.ref = "glide" } google-android-material = { module = "com.google.android.material:material", version.ref = "material" } googlemaps-compose = { module = "com.google.maps.android:maps-compose", version.ref = "maps-compose" } googlemaps-maps = { module = "com.google.android.gms:play-services-maps", version.ref = "google-maps" } guava = { module = "com.google.guava:guava", version.ref = "guava" } +guava-android = { module = "com.google.guava:guava", version.ref = "guava-android" } +reactive-streams = { module = "org.reactivestreams:reactive-streams", version.ref = "reactive-streams" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } horologist-compose-layout = { module = "com.google.android.horologist:horologist-compose-layout", version.ref = "horologist" } diff --git a/misc/build.gradle.kts b/misc/build.gradle.kts index cb0905420..6a2dc60d1 100644 --- a/misc/build.gradle.kts +++ b/misc/build.gradle.kts @@ -1,3 +1,4 @@ + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -72,6 +73,10 @@ dependencies { implementation(libs.androidx.startup.runtime) implementation(libs.androidx.window.java) implementation(libs.appcompat) + implementation(platform(libs.firebase.bom)) + implementation(libs.firebase.ai) + implementation(libs.guava.android) + implementation(libs.reactive.streams) testImplementation(libs.junit) testImplementation(kotlin("test")) androidTestImplementation(libs.androidx.test.ext.junit) diff --git a/misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippets.kt b/misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippets.kt new file mode 100644 index 000000000..c7eacd686 --- /dev/null +++ b/misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippets.kt @@ -0,0 +1,196 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * 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 + * + * https://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.example.snippets.ai + +import android.content.ContentResolver +import android.graphics.Bitmap +import android.net.Uri +import com.google.firebase.Firebase +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.ImagePart +import com.google.firebase.ai.type.ResponseModality +import com.google.firebase.ai.type.content +import com.google.firebase.ai.type.generationConfig +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +object GeminiDeveloperApi25FlashModelConfiguration { + // [START android_gemini_developer_api_gemini_25_flash_model] + // Start by instantiating a GenerativeModel and specifying the model name: + val model = Firebase.ai(backend = GenerativeBackend.googleAI()) + .generativeModel("gemini-2.5-flash") + // [END android_gemini_developer_api_gemini_25_flash_model] +} + +object Gemini25FlashImagePreviewModelConfiguration { + // [START android_gemini_developer_api_gemini_25_flash_image_model] + val model = Firebase.ai(backend = GenerativeBackend.googleAI()).generativeModel( + modelName = "gemini-2.5-flash-image-preview", + // Configure the model to respond with text and images (required) + generationConfig = generationConfig { + responseModalities = listOf( + ResponseModality.TEXT, + ResponseModality.IMAGE + ) + } + ) + // [END android_gemini_developer_api_gemini_25_flash_image_model] +} + +fun textOnlyInput(scope: CoroutineScope) { + val model = GeminiDeveloperApi25FlashModelConfiguration.model + // [START android_gemini_developer_api_text_only_input] + scope.launch { + val response = model.generateContent("Write a story about a magic backpack.") + } + // [END android_gemini_developer_api_text_only_input] +} + +fun textAndImageInput(scope: CoroutineScope, bitmap: Bitmap) { + val model = GeminiDeveloperApi25FlashModelConfiguration.model + // [START android_gemini_developer_api_multimodal_input] + scope.launch { + val response = model.generateContent( + content { + image(bitmap) + text("what is the object in the picture?") + } + ) + } + // [END android_gemini_developer_api_multimodal_input] +} + +fun textAndAudioInput( + scope: CoroutineScope, + contentResolver: ContentResolver, + audioUri: Uri +) { + val model = GeminiDeveloperApi25FlashModelConfiguration.model + // [START android_gemini_developer_api_multimodal_audio_input] + scope.launch { + contentResolver.openInputStream(audioUri).use { stream -> + stream?.let { + val bytes = it.readBytes() + + val prompt = content { + inlineData(bytes, "audio/mpeg") // Specify the appropriate audio MIME type + text("Transcribe this audio recording.") + } + + val response = model.generateContent(prompt) + } + } + } + // [END android_gemini_developer_api_multimodal_audio_input] +} + +fun textAndVideoInput( + scope: CoroutineScope, + contentResolver: ContentResolver, + videoUri: Uri +) { + val model = GeminiDeveloperApi25FlashModelConfiguration.model + // [START android_gemini_developer_api_multimodal_video_input] + scope.launch { + contentResolver.openInputStream(videoUri).use { stream -> + stream?.let { + val bytes = it.readBytes() + + val prompt = content { + inlineData(bytes, "video/mp4") // Specify the appropriate video MIME type + text("Describe the content of this video") + } + + val response = model.generateContent(prompt) + } + } + } + // [END android_gemini_developer_api_multimodal_video_input] +} + +fun multiTurnChat(scope: CoroutineScope) { + val model = GeminiDeveloperApi25FlashModelConfiguration.model + // [START android_gemini_developer_api_multiturn_chat] + val chat = model.startChat( + history = listOf( + content(role = "user") { text("Hello, I have 2 dogs in my house.") }, + content(role = "model") { text("Great to meet you. What would you like to know?") } + ) + ) + + scope.launch { + val response = chat.sendMessage("How many paws are in my house?") + } + // [END android_gemini_developer_api_multiturn_chat] +} + +fun generateImageFromText(scope: CoroutineScope) { + val model = Gemini25FlashImagePreviewModelConfiguration.model + // [START android_gemini_developer_api_generate_image_from_text] + scope.launch { + // Provide a text prompt instructing the model to generate an image + val prompt = + "A hyper realistic picture of a t-rex with a blue bag pack roaming a pre-historic forest." + // To generate image output, call `generateContent` with the text input + val generatedImageAsBitmap: Bitmap? = model.generateContent(prompt) + .candidates.first().content.parts.filterIsInstance() + .firstOrNull()?.image + } + // [END android_gemini_developer_api_generate_image_from_text] +} + +fun editImage(scope: CoroutineScope, bitmap: Bitmap) { + val model = Gemini25FlashImagePreviewModelConfiguration.model + // [START android_gemini_developer_api_edit_image] + scope.launch { + // Provide a text prompt instructing the model to edit the image + val prompt = content { + image(bitmap) + text("Edit this image to make it look like a cartoon") + } + // To edit the image, call `generateContent` with the prompt (image and text input) + val generatedImageAsBitmap: Bitmap? = model.generateContent(prompt) + .candidates.first().content.parts.filterIsInstance().firstOrNull()?.image + // Handle the generated text and image + } + // [END android_gemini_developer_api_edit_image] +} + +fun editImageWithChat(scope: CoroutineScope, bitmap: Bitmap) { + val model = Gemini25FlashImagePreviewModelConfiguration.model + // [START android_gemini_developer_api_edit_image_chat] + scope.launch { + // Create the initial prompt instructing the model to edit the image + val prompt = content { + image(bitmap) + text("Edit this image to make it look like a cartoon") + } + // Initialize the chat + val chat = model.startChat() + // To generate an initial response, send a user message with the image and text prompt + var response = chat.sendMessage(prompt) + // Inspect the returned image + var generatedImageAsBitmap: Bitmap? = response + .candidates.first().content.parts.filterIsInstance().firstOrNull()?.image + // Follow up requests do not need to specify the image again + response = chat.sendMessage("But make it old-school line drawing style") + generatedImageAsBitmap = response + .candidates.first().content.parts.filterIsInstance().firstOrNull()?.image + } + // [END android_gemini_developer_api_edit_image_chat] +} diff --git a/misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippetsJava.java b/misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippetsJava.java new file mode 100644 index 000000000..1f7c4c33b --- /dev/null +++ b/misc/src/main/java/com/example/snippets/ai/GeminiDeveloperApiSnippetsJava.java @@ -0,0 +1,370 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * 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 + * + * https://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.example.snippets.ai; + +import android.app.Application; +import android.content.ContentResolver; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.util.Log; + +import com.example.snippets.R; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.firebase.ai.FirebaseAI; +import com.google.firebase.ai.GenerativeModel; +import com.google.firebase.ai.java.ChatFutures; +import com.google.firebase.ai.java.GenerativeModelFutures; +import com.google.firebase.ai.type.Content; +import com.google.firebase.ai.type.GenerateContentResponse; +import com.google.firebase.ai.type.GenerationConfig; +import com.google.firebase.ai.type.GenerativeBackend; +import com.google.firebase.ai.type.ImagePart; +import com.google.firebase.ai.type.Part; +import com.google.firebase.ai.type.ResponseModality; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Executor; + +final class GeminiDeveloperApiSnippetsJava { + + private static final String TAG = "GeminiDeveloperApiSnippetsJava"; + + private GeminiDeveloperApiSnippetsJava() {} + + static final class GeminiDeveloperApi25FlashModelConfigurationJava { + // [START android_gemini_developer_api_gemini_25_flash_model_java] + public static GenerativeModel firebaseAI = FirebaseAI.getInstance(GenerativeBackend.googleAI()) + .generativeModel("gemini-2.5-flash"); + + public static GenerativeModelFutures model = GenerativeModelFutures.from(firebaseAI); + // [END android_gemini_developer_api_gemini_25_flash_model_java] + + } + + static final class Gemini25FlashImagePreviewModelConfigurationJava { + // [START android_gemini_developer_api_gemini_25_flash_image_model_java] + public static GenerativeModel ai = FirebaseAI.getInstance(GenerativeBackend.googleAI()).generativeModel( + "gemini-2.5-flash-image-preview", + // Configure the model to respond with text and images (required) + new GenerationConfig.Builder() + .setResponseModalities(Arrays.asList(ResponseModality.TEXT, ResponseModality.IMAGE)) + .build() + ); + public static GenerativeModelFutures model = GenerativeModelFutures.from(ai); + // [END android_gemini_developer_api_gemini_25_flash_image_model_java] + } + + public static void textOnlyInput(Executor executor) { + GenerativeModelFutures model = GeminiDeveloperApi25FlashModelConfigurationJava.model; + // [START android_gemini_developer_api_text_only_input_java] + Content prompt = new Content.Builder() + .addText("Write a story about a magic backpack.") + .build(); + + ListenableFuture response = model.generateContent(prompt); + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + String resultText = result.getText(); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END android_gemini_developer_api_text_only_input_java] + } + + public static void textAndImageInput(Executor executor, Bitmap bitmap) { + GenerativeModelFutures model = GeminiDeveloperApi25FlashModelConfigurationJava.model; + // [START android_gemini_developer_api_multimodal_input_java] + Content content = new Content.Builder() + .addImage(bitmap) + .addText("what is the object in the picture?") + .build(); + + ListenableFuture response = model.generateContent(content); + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + String resultText = result.getText(); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END android_gemini_developer_api_multimodal_input_java] + } + + public static void textAndAudioInput(Executor executor, Application applicationContext, Uri audioUri) { + GenerativeModelFutures model = GeminiDeveloperApi25FlashModelConfigurationJava.model; + // [START android_gemini_developer_api_multimodal_audio_input_java] + ContentResolver resolver = applicationContext.getContentResolver(); + + try (InputStream stream = resolver.openInputStream(audioUri)) { + File audioFile = new File(new URI(audioUri.toString())); + int audioSize = (int) audioFile.length(); + byte[] audioBytes = new byte[audioSize]; + if (stream != null) { + stream.read(audioBytes, 0, audioBytes.length); + stream.close(); + + // Provide a prompt that includes audio specified earlier and text + Content prompt = new Content.Builder() + .addInlineData(audioBytes, "audio/mpeg") // Specify the appropriate audio MIME type + .addText("Transcribe what's said in this audio recording.") + .build(); + + // To generate text output, call `generateContent` with the prompt + ListenableFuture response = model.generateContent(prompt); + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + String text = result.getText(); + Log.d(TAG, (text == null) ? "" : text); + } + @Override + public void onFailure(Throwable t) { + Log.e(TAG, "Failed to generate a response", t); + } + }, executor); + } else { + Log.e(TAG, "Error getting input stream for file."); + // Handle the error appropriately + } + } catch (IOException e) { + Log.e(TAG, "Failed to read the audio file", e); + } catch (URISyntaxException e) { + Log.e(TAG, "Invalid audio file", e); + } + // [END android_gemini_developer_api_multimodal_audio_input_java] + } + + public static void textAndVideoInput(Executor executor, Application applicationContext, Uri videoUri) { + GenerativeModelFutures model = GeminiDeveloperApi25FlashModelConfigurationJava.model; + // [START android_gemini_developer_api_multimodal_video_input_java] + ContentResolver resolver = applicationContext.getContentResolver(); + + try (InputStream stream = resolver.openInputStream(videoUri)) { + File videoFile = new File(new URI(videoUri.toString())); + int videoSize = (int) videoFile.length(); + byte[] videoBytes = new byte[videoSize]; + if (stream != null) { + stream.read(videoBytes, 0, videoBytes.length); + stream.close(); + + // Provide a prompt that includes video specified earlier and text + Content prompt = new Content.Builder() + .addInlineData(videoBytes, "video/mp4") + .addText("Describe the content of this video") + .build(); + + // To generate text output, call generateContent with the prompt + ListenableFuture response = model.generateContent(prompt); + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + String resultText = result.getText(); + System.out.println(resultText); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + } + } catch (IOException e) { + e.printStackTrace(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + // [END android_gemini_developer_api_multimodal_video_input_java] + } + + public static void multiTurnChat(Executor executor) { + GenerativeModelFutures model = GeminiDeveloperApi25FlashModelConfigurationJava.model; + // [START android_gemini_developer_api_multiturn_chat_java] + Content.Builder userContentBuilder = new Content.Builder(); + userContentBuilder.setRole("user"); + userContentBuilder.addText("Hello, I have 2 dogs in my house."); + Content userContent = userContentBuilder.build(); + + Content.Builder modelContentBuilder = new Content.Builder(); + modelContentBuilder.setRole("model"); + modelContentBuilder.addText("Great to meet you. What would you like to know?"); + Content modelContent = modelContentBuilder.build(); + + List history = Arrays.asList(userContent, modelContent); + + // Initialize the chat + ChatFutures chat = model.startChat(history); + + // Create a new user message + Content.Builder messageBuilder = new Content.Builder(); + messageBuilder.setRole("user"); + messageBuilder.addText("How many paws are in my house?"); + + Content message = messageBuilder.build(); + + // Send the message + ListenableFuture response = chat.sendMessage(message); + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + String resultText = result.getText(); + System.out.println(resultText); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END android_gemini_developer_api_multiturn_chat_java] + } + + public static void generateImageFromText(Executor executor) { + GenerativeModelFutures model = Gemini25FlashImagePreviewModelConfigurationJava.model; + // [START android_gemini_developer_api_generate_image_from_text_java] + // Provide a text prompt instructing the model to generate an image + Content prompt = new Content.Builder() + .addText("Generate an image of the Eiffel Tower with fireworks in the background.") + .build(); + // To generate an image, call `generateContent` with the text input + ListenableFuture response = model.generateContent(prompt); + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + // iterate over all the parts in the first candidate in the result object + for (Part part : result.getCandidates().get(0).getContent().getParts()) { + if (part instanceof ImagePart) { + ImagePart imagePart = (ImagePart) part; + // The returned image as a bitmap + Bitmap generatedImageAsBitmap = imagePart.getImage(); + break; + } + } + } + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END android_gemini_developer_api_generate_image_from_text_java] + } + + public static void editImage(Executor executor, Resources resources) { + GenerativeModelFutures model = Gemini25FlashImagePreviewModelConfigurationJava.model; + // [START android_gemini_developer_api_edit_image_java] + // Provide an image for the model to edit + Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.scones); + // Provide a text prompt instructing the model to edit the image + Content promptcontent = new Content.Builder() + .addImage(bitmap) + .addText("Edit this image to make it look like a cartoon") + .build(); + // To edit the image, call `generateContent` with the prompt (image and text input) + ListenableFuture response = model.generateContent(promptcontent); + Futures.addCallback(response, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + // iterate over all the parts in the first candidate in the result object + for (Part part : result.getCandidates().get(0).getContent().getParts()) { + if (part instanceof ImagePart) { + ImagePart imagePart = (ImagePart) part; + Bitmap generatedImageAsBitmap = imagePart.getImage(); + break; + } + } + } + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END android_gemini_developer_api_edit_image_java] + } + + public static void editImageWithChat(Executor executor, Resources resources) { + GenerativeModelFutures model = Gemini25FlashImagePreviewModelConfigurationJava.model; + // [START android_gemini_developer_api_edit_image_chat_java] + // Provide an image for the model to edit + Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.scones); + // Initialize the chat + ChatFutures chat = model.startChat(); + // Create the initial prompt instructing the model to edit the image + Content prompt = new Content.Builder() + .setRole("user") + .addImage(bitmap) + .addText("Edit this image to make it look like a cartoon") + .build(); + // To generate an initial response, send a user message with the image and text prompt + ListenableFuture response = chat.sendMessage(prompt); + // Extract the image from the initial response + ListenableFuture initialRequest = Futures.transform(response, + result -> { + for (Part part : result.getCandidates().get(0).getContent().getParts()) { + if (part instanceof ImagePart) { + ImagePart imagePart = (ImagePart) part; + return imagePart.getImage(); + } + } + return null; + }, executor); + // Follow up requests do not need to specify the image again + ListenableFuture modelResponseFuture = Futures.transformAsync( + initialRequest, + generatedImage -> { + Content followUpPrompt = new Content.Builder() + .addText("But make it old-school line drawing style") + .build(); + return chat.sendMessage(followUpPrompt); + }, executor); + // Add a final callback to check the reworked image + Futures.addCallback(modelResponseFuture, new FutureCallback() { + @Override + public void onSuccess(GenerateContentResponse result) { + for (Part part : result.getCandidates().get(0).getContent().getParts()) { + if (part instanceof ImagePart) { + ImagePart imagePart = (ImagePart) part; + Bitmap generatedImageAsBitmap = imagePart.getImage(); + break; + } + } + } + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, executor); + // [END android_gemini_developer_api_edit_image_chat_java] + } +} diff --git a/misc/src/main/res/drawable/scones.xml b/misc/src/main/res/drawable/scones.xml new file mode 100644 index 000000000..2eafb40af --- /dev/null +++ b/misc/src/main/res/drawable/scones.xml @@ -0,0 +1,19 @@ + + + + + From 4a8782f5ccaa897bd63a098eeaf0f11822ad0ea3 Mon Sep 17 00:00:00 2001 From: cartland Date: Thu, 9 Oct 2025 01:28:38 -0700 Subject: [PATCH 2/3] feat(ai): Add Imagen image generation snippets This commit adds new Kotlin snippets for the Firebase AI Imagen API. The snippets demonstrate how to: - Configure the Imagen model with options like image count, aspect ratio, and safety settings. - Generate an image from a text prompt. Region-Tags: - `android_imagen_model_configuration` - `android_imagen_generate_images` --- .../com/example/snippets/ai/ImagenSnippets.kt | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 misc/src/main/java/com/example/snippets/ai/ImagenSnippets.kt diff --git a/misc/src/main/java/com/example/snippets/ai/ImagenSnippets.kt b/misc/src/main/java/com/example/snippets/ai/ImagenSnippets.kt new file mode 100644 index 000000000..c24eb73c7 --- /dev/null +++ b/misc/src/main/java/com/example/snippets/ai/ImagenSnippets.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * 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 + * + * https://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. + */ +@file:OptIn(PublicPreviewAPI::class) + +package com.example.snippets.ai + +import com.google.firebase.Firebase +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.ImagenAspectRatio +import com.google.firebase.ai.type.ImagenGenerationConfig +import com.google.firebase.ai.type.ImagenImageFormat +import com.google.firebase.ai.type.ImagenPersonFilterLevel +import com.google.firebase.ai.type.ImagenSafetyFilterLevel +import com.google.firebase.ai.type.ImagenSafetySettings +import com.google.firebase.ai.type.PublicPreviewAPI +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +private object ImagenModelConfiguration { + // [START android_imagen_model_configuration] + val config = ImagenGenerationConfig( + numberOfImages = 2, + aspectRatio = ImagenAspectRatio.LANDSCAPE_16x9, + imageFormat = ImagenImageFormat.jpeg(compressionQuality = 100), + addWatermark = false, + ) + + // Initialize the Gemini Developer API backend service + // For Vertex AI use Firebase.ai(backend = GenerativeBackend.vertexAI()) + val model = Firebase.ai(backend = GenerativeBackend.googleAI()).imagenModel( + modelName = "imagen-4.0-generate-001", + generationConfig = config, + safetySettings = ImagenSafetySettings( + safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, + personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL + ), + ) + // [END android_imagen_model_configuration] +} + +private fun generateImagesWithImagen(scope: CoroutineScope) { + val model = ImagenModelConfiguration.model + scope.launch { + // [START android_imagen_generate_images] + val imageResponse = model.generateImages( + prompt = "A hyper realistic picture of a t-rex with a blue bagpack in a prehistoric forest", + ) + val image = imageResponse.images.first() + val bitmapImage = image.asBitmap() + // [END android_imagen_generate_images] + } +} From e25629f46e3f86d82d54fa84145c12861574ca4b Mon Sep 17 00:00:00 2001 From: cartland Date: Thu, 9 Oct 2025 02:05:29 -0700 Subject: [PATCH 3/3] feat(ai): Add Imagen image generation snippets This commit introduces snippets for Imagen 4.0, a text-to-image generation model. It adds new files, `ImagenSnippets.kt` and `ImagenSnippetsJava.java`, which demonstrate how to configure the model and generate images from a text prompt. The commit also includes the following changes: - Adds the `@PublicPreviewAPI` annotation to the new Imagen snippets. - Refactors existing Gemini snippets to initialize models within a static block for consistency. - Adds `@SuppressWarnings("unused")` to several functions and classes to handle linter warnings. - Updates the `build.gradle.kts` file to exclude the `.idea` directory from Spotless XML formatting. --- build.gradle.kts | 9 +- .../snippets/ai/GeminiDeveloperApiSnippets.kt | 8 ++ .../ai/GeminiDeveloperApiSnippetsJava.java | 41 ++++---- .../com/example/snippets/ai/ImagenSnippets.kt | 1 + .../snippets/ai/ImagenSnippetsJava.java | 94 +++++++++++++++++++ 5 files changed, 131 insertions(+), 22 deletions(-) create mode 100644 misc/src/main/java/com/example/snippets/ai/ImagenSnippetsJava.java diff --git a/build.gradle.kts b/build.gradle.kts index 08912624e..d4ea18313 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,3 @@ -import org.codehaus.groovy.runtime.DefaultGroovyMethods.step -import org.jetbrains.kotlin.gradle.internal.builtins.StandardNames.FqNames.target - // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { alias(libs.plugins.gradle.versions) @@ -94,7 +91,11 @@ allprojects { } format("xml") { target("**/*.xml") - targetExclude("**/build/**/*.xml", "spotless/**/*.xml") + targetExclude( + "**/build/**/*.xml", + "spotless/**/*.xml", + ".idea/**", + ) // Look for the root tag or a tag that is a snippet licenseHeaderFile(rootProject.file("spotless/copyright.xml"), "(<[a-zA-Z])|(