├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support_request.md └── workflows │ └── build_android.yaml ├── .gitignore ├── Android ├── .gitignore ├── README.md └── src │ ├── .gitignore │ ├── app │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── google │ │ │ │ └── ai │ │ │ │ └── edge │ │ │ │ └── gallery │ │ │ │ ├── Analytics.kt │ │ │ │ ├── GalleryApp.kt │ │ │ │ ├── GalleryAppTopBar.kt │ │ │ │ ├── GalleryApplication.kt │ │ │ │ ├── GalleryLifecycleProvider.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── SettingsSerializer.kt │ │ │ │ ├── common │ │ │ │ ├── Types.kt │ │ │ │ └── Utils.kt │ │ │ │ ├── data │ │ │ │ ├── AppBarAction.kt │ │ │ │ ├── AppContainer.kt │ │ │ │ ├── Config.kt │ │ │ │ ├── ConfigValue.kt │ │ │ │ ├── Consts.kt │ │ │ │ ├── DataStoreRepository.kt │ │ │ │ ├── DownloadRepository.kt │ │ │ │ ├── Model.kt │ │ │ │ ├── ModelAllowlist.kt │ │ │ │ ├── Tasks.kt │ │ │ │ └── Types.kt │ │ │ │ ├── di │ │ │ │ └── AppModule.kt │ │ │ │ ├── ui │ │ │ │ ├── common │ │ │ │ │ ├── AuthConfig.kt │ │ │ │ │ ├── ClickableLink.kt │ │ │ │ │ ├── ColorUtils.kt │ │ │ │ │ ├── ConfigDialog.kt │ │ │ │ │ ├── DownloadAndTryButton.kt │ │ │ │ │ ├── ErrorDialog.kt │ │ │ │ │ ├── MarkdownText.kt │ │ │ │ │ ├── ModelPageAppBar.kt │ │ │ │ │ ├── ModelPicker.kt │ │ │ │ │ ├── ModelPickerChipsPager.kt │ │ │ │ │ ├── TaskIcon.kt │ │ │ │ │ ├── TosSheet.kt │ │ │ │ │ ├── Utils.kt │ │ │ │ │ ├── chat │ │ │ │ │ │ ├── AudioPlaybackPanel.kt │ │ │ │ │ │ ├── AudioRecorderPanel.kt │ │ │ │ │ │ ├── BenchmarkConfigDialog.kt │ │ │ │ │ │ ├── ChatMessage.kt │ │ │ │ │ │ ├── ChatPanel.kt │ │ │ │ │ │ ├── ChatView.kt │ │ │ │ │ │ ├── ChatViewModel.kt │ │ │ │ │ │ ├── DataCard.kt │ │ │ │ │ │ ├── MessageActionButton.kt │ │ │ │ │ │ ├── MessageBodyAudioClip.kt │ │ │ │ │ │ ├── MessageBodyBenchmark.kt │ │ │ │ │ │ ├── MessageBodyBenchmarkLlm.kt │ │ │ │ │ │ ├── MessageBodyClassification.kt │ │ │ │ │ │ ├── MessageBodyConfigUpdate.kt │ │ │ │ │ │ ├── MessageBodyImage.kt │ │ │ │ │ │ ├── MessageBodyImageWithHistory.kt │ │ │ │ │ │ ├── MessageBodyInfo.kt │ │ │ │ │ │ ├── MessageBodyLoading.kt │ │ │ │ │ │ ├── MessageBodyPromptTemplates.kt │ │ │ │ │ │ ├── MessageBodyText.kt │ │ │ │ │ │ ├── MessageBodyWarning.kt │ │ │ │ │ │ ├── MessageBubbleShape.kt │ │ │ │ │ │ ├── MessageInputImage.kt │ │ │ │ │ │ ├── MessageInputText.kt │ │ │ │ │ │ ├── MessageLatency.kt │ │ │ │ │ │ ├── MessageSender.kt │ │ │ │ │ │ ├── ModelDownloadStatusInfoPanel.kt │ │ │ │ │ │ ├── ModelDownloadingAnimation.kt │ │ │ │ │ │ ├── ModelInitializationStatus.kt │ │ │ │ │ │ ├── ModelNotDownloaded.kt │ │ │ │ │ │ ├── ModelSelector.kt │ │ │ │ │ │ ├── TextInputHistorySheet.kt │ │ │ │ │ │ └── ZoomableBox.kt │ │ │ │ │ └── modelitem │ │ │ │ │ │ ├── ConfirmDeleteModelDialog.kt │ │ │ │ │ │ ├── ModelItem.kt │ │ │ │ │ │ ├── ModelItemActionButton.kt │ │ │ │ │ │ ├── ModelNameAndStatus.kt │ │ │ │ │ │ └── StatusIcon.kt │ │ │ │ ├── home │ │ │ │ │ ├── HomeScreen.kt │ │ │ │ │ ├── ModelImportDialog.kt │ │ │ │ │ ├── NewReleaseNotification.kt │ │ │ │ │ └── SettingsDialog.kt │ │ │ │ ├── icon │ │ │ │ │ └── Deploy.kt │ │ │ │ ├── llmchat │ │ │ │ │ ├── LlmChatModelHelper.kt │ │ │ │ │ ├── LlmChatScreen.kt │ │ │ │ │ └── LlmChatViewModel.kt │ │ │ │ ├── llmsingleturn │ │ │ │ │ ├── LlmSingleTurnScreen.kt │ │ │ │ │ ├── LlmSingleTurnViewModel.kt │ │ │ │ │ ├── PromptTemplateConfigs.kt │ │ │ │ │ ├── PromptTemplatesPanel.kt │ │ │ │ │ ├── ResponsePanel.kt │ │ │ │ │ ├── SingleSelectButton.kt │ │ │ │ │ └── VerticalSplitView.kt │ │ │ │ ├── modelmanager │ │ │ │ │ ├── ModelList.kt │ │ │ │ │ ├── ModelManager.kt │ │ │ │ │ └── ModelManagerViewModel.kt │ │ │ │ ├── navigation │ │ │ │ │ └── GalleryNavGraph.kt │ │ │ │ └── theme │ │ │ │ │ ├── Color.kt │ │ │ │ │ ├── Theme.kt │ │ │ │ │ ├── ThemeSettings.kt │ │ │ │ │ └── Type.kt │ │ │ │ └── worker │ │ │ │ └── DownloadWorker.kt │ │ ├── proto │ │ │ └── settings.proto │ │ └── res │ │ │ ├── drawable │ │ │ ├── chat_spark.xml │ │ │ ├── circle.xml │ │ │ ├── double_circle.xml │ │ │ ├── four_circle.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_launcher_foreground.xml │ │ │ ├── image_spark.xml │ │ │ ├── logo.xml │ │ │ ├── pantegon.xml │ │ │ └── text_spark.xml │ │ │ ├── font │ │ │ ├── nunito_black.ttf │ │ │ ├── nunito_bold.ttf │ │ │ ├── nunito_extrabold.ttf │ │ │ ├── nunito_extralight.ttf │ │ │ ├── nunito_light.ttf │ │ │ ├── nunito_medium.ttf │ │ │ ├── nunito_regular.ttf │ │ │ └── nunito_semibold.ttf │ │ │ ├── mipmap-anydpi-v26 │ │ │ └── ic_launcher.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ │ ├── values-night │ │ │ └── themes.xml │ │ │ ├── values │ │ │ ├── dimens.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ │ └── xml │ │ │ ├── backup_rules.xml │ │ │ ├── data_extraction_rules.xml │ │ │ └── file_paths.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── google │ │ └── ai │ │ └── edge │ │ └── gallery │ │ └── data │ │ └── ModelAllowlistTest.kt │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle.kts ├── CONTRIBUTING.md ├── LICENSE ├── README.md └── model_allowlist.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug:** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce:** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior:** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots:** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Device & App Information (Please complete the following):** 27 | - Device: [e.g., Samsung Galaxy S23, Google Pixel 7] 28 | - Android Version: [e.g., Android 12, Android 13] 29 | - App Version: [e.g., 1.0.1, v1.0.2] 30 | 31 | **Additional context:** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🆘 Support Request 3 | about: Ask a question or get help with usage. 4 | title: "[Support]: " 5 | labels: ["support", "question"] 6 | assignees: [] 7 | --- 8 | 9 | <!-- 10 | Thanks for reaching out for help! To assist you efficiently, please provide as much detail as possible. 11 | --> 12 | 13 | **What do you need help with?** 14 | 15 | Is this a question about how to do something, a configuration problem, or a general issue you can't solve? 16 | 17 | **Describe the issue/question:** 18 | 19 | Clearly describe what you are trying to achieve, what problem you are facing, or what question you have. 20 | 21 | **What have you tried so far? (Optional):** 22 | 23 | List any steps you've already taken to troubleshoot, find information, or attempt a solution. 24 | 25 | **Expected outcome (Optional):** 26 | 27 | If applicable, what did you hope would happen, or what solution are you looking for? 28 | 29 | **Screenshots/Videos (Optional):** 30 | 31 | If applicable, add screenshots or a short video that might help explain your situation. 32 | 33 | **Environment & Details:** 34 | 35 | Please provide details about your operating environment, relevant URLs, or any messages you see. 36 | 37 | - **Operating System:** 38 | - **Browser & Version (if applicable):** 39 | - **Any relevant messages (e.g., from UI, console):** 40 | ``` 41 | PASTE_ANY_MESSAGES_HERE 42 | ``` 43 | 44 | **Any additional context?:** 45 | 46 | Is there anything else that might be useful for us to know? 47 | -------------------------------------------------------------------------------- /.github/workflows/build_android.yaml: -------------------------------------------------------------------------------- 1 | name: Build Android APK 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ "main" ] 7 | paths: 8 | - 'Android/**' 9 | pull_request: 10 | branches: [ "main" ] 11 | paths: 12 | - 'Android/**' 13 | 14 | jobs: 15 | build_apk: 16 | name: Build Android APK 17 | runs-on: ubuntu-latest 18 | defaults: 19 | run: 20 | working-directory: ./Android/src 21 | steps: 22 | - name: Checkout the source code 23 | uses: actions/checkout@v3 24 | - uses: actions/setup-java@v4 25 | with: 26 | distribution: 'temurin' 27 | java-version: '21' 28 | - name: Build 29 | run: ./gradlew assembleRelease 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /Android/.gitignore: -------------------------------------------------------------------------------- 1 | # @license 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 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Log/OS Files 25 | *.log 26 | 27 | # Android Studio generated files and folders 28 | captures/ 29 | .externalNativeBuild/ 30 | .cxx/ 31 | *.apk 32 | output.json 33 | 34 | # IntelliJ 35 | *.iml 36 | .idea/ 37 | misc.xml 38 | deploymentTargetDropDown.xml 39 | render.experimental.xml 40 | 41 | # Keystore files 42 | *.jks 43 | *.keystore 44 | 45 | # Google Services (e.g. APIs or Firebase) 46 | google-services.json 47 | 48 | # Android Profiling 49 | *.hprof 50 | 51 | .DS_Store 52 | -------------------------------------------------------------------------------- /Android/README.md: -------------------------------------------------------------------------------- 1 | # Google AI Edge Gallery (Android) 2 | -------------------------------------------------------------------------------- /Android/src/.gitignore: -------------------------------------------------------------------------------- 1 | # @license 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 | *.iml 17 | 18 | .gradle 19 | /local.properties 20 | /.idea/caches 21 | /.idea/libraries 22 | /.idea/modules.xml 23 | /.idea/workspace.xml 24 | /.idea/navEditor.xml 25 | /.idea/assetWizardSettings.xml 26 | .DS_Store 27 | /build 28 | /captures 29 | .externalNativeBuild 30 | .cxx 31 | local.properties 32 | -------------------------------------------------------------------------------- /Android/src/app/.gitignore: -------------------------------------------------------------------------------- 1 | # @license 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 | /build 18 | /release -------------------------------------------------------------------------------- /Android/src/app/build.gradle.kts: -------------------------------------------------------------------------------- 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 | plugins { 18 | alias(libs.plugins.android.application) 19 | // Note: set apply to true to enable google-services (requires google-services.json). 20 | alias(libs.plugins.google.services) apply false 21 | alias(libs.plugins.kotlin.android) 22 | alias(libs.plugins.kotlin.compose) 23 | alias(libs.plugins.kotlin.serialization) 24 | alias(libs.plugins.protobuf) 25 | alias(libs.plugins.hilt.application) 26 | alias(libs.plugins.oss.licenses) 27 | kotlin("kapt") 28 | } 29 | 30 | android { 31 | namespace = "com.google.ai.edge.gallery" 32 | compileSdk = 35 33 | 34 | defaultConfig { 35 | applicationId = "com.google.aiedge.gallery" 36 | minSdk = 31 37 | targetSdk = 35 38 | versionCode = 1 39 | versionName = "1.0.4" 40 | 41 | // Needed for HuggingFace auth workflows. 42 | // Use the scheme of the "Redirect URLs" in HuggingFace app. 43 | manifestPlaceholders["appAuthRedirectScheme"] = 44 | "REPLACE_WITH_YOUR_REDIRECT_SCHEME_IN_HUGGINGFACE_APP" 45 | 46 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | isMinifyEnabled = false 52 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 53 | signingConfig = signingConfigs.getByName("debug") 54 | } 55 | } 56 | compileOptions { 57 | sourceCompatibility = JavaVersion.VERSION_11 58 | targetCompatibility = JavaVersion.VERSION_11 59 | } 60 | kotlinOptions { 61 | jvmTarget = "11" 62 | freeCompilerArgs += "-Xcontext-receivers" 63 | } 64 | buildFeatures { 65 | compose = true 66 | buildConfig = true 67 | } 68 | } 69 | 70 | dependencies { 71 | implementation(libs.androidx.core.ktx) 72 | implementation(libs.androidx.lifecycle.runtime.ktx) 73 | implementation(libs.androidx.activity.compose) 74 | implementation(platform(libs.androidx.compose.bom)) 75 | implementation(libs.androidx.ui) 76 | implementation(libs.androidx.ui.graphics) 77 | implementation(libs.androidx.ui.tooling.preview) 78 | implementation(libs.androidx.material3) 79 | implementation(libs.androidx.compose.navigation) 80 | implementation(libs.kotlinx.serialization.json) 81 | implementation(libs.material.icon.extended) 82 | implementation(libs.androidx.work.runtime) 83 | implementation(libs.androidx.datastore) 84 | implementation(libs.com.google.code.gson) 85 | implementation(libs.androidx.lifecycle.process) 86 | implementation(libs.mediapipe.tasks.text) 87 | implementation(libs.mediapipe.tasks.genai) 88 | implementation(libs.mediapipe.tasks.imagegen) 89 | implementation(libs.commonmark) 90 | implementation(libs.richtext) 91 | implementation(libs.tflite) 92 | implementation(libs.tflite.gpu) 93 | implementation(libs.tflite.support) 94 | implementation(libs.camerax.core) 95 | implementation(libs.camerax.camera2) 96 | implementation(libs.camerax.lifecycle) 97 | implementation(libs.camerax.view) 98 | implementation(libs.openid.appauth) 99 | implementation(libs.androidx.splashscreen) 100 | implementation(libs.protobuf.javalite) 101 | implementation(libs.hilt.android) 102 | implementation(libs.hilt.navigation.compose) 103 | implementation(libs.play.services.oss.licenses) 104 | implementation(platform(libs.firebase.bom)) 105 | implementation(libs.firebase.analytics) 106 | kapt(libs.hilt.android.compiler) 107 | testImplementation(libs.junit) 108 | androidTestImplementation(libs.androidx.junit) 109 | androidTestImplementation(libs.androidx.espresso.core) 110 | androidTestImplementation(platform(libs.androidx.compose.bom)) 111 | androidTestImplementation(libs.androidx.ui.test.junit4) 112 | androidTestImplementation(libs.hilt.android.testing) 113 | debugImplementation(libs.androidx.ui.tooling) 114 | debugImplementation(libs.androidx.ui.test.manifest) 115 | } 116 | 117 | protobuf { 118 | protoc { artifact = "com.google.protobuf:protoc:4.26.1" } 119 | generateProtoTasks { all().forEach { it.plugins { create("java") { option("lite") } } } } 120 | } 121 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/Analytics.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery 18 | 19 | import android.util.Log 20 | import com.google.firebase.Firebase 21 | import com.google.firebase.analytics.FirebaseAnalytics 22 | import com.google.firebase.analytics.analytics 23 | 24 | private var hasLoggedAnalyticsWarning = false 25 | 26 | val firebaseAnalytics: FirebaseAnalytics? 27 | get() = 28 | runCatching { Firebase.analytics } 29 | .onFailure { exception -> 30 | // Firebase.analytics can throw an exception if goolgle-services is not set up, e.g., 31 | // missing google-services.json. 32 | if (!hasLoggedAnalyticsWarning) { 33 | Log.w("AGAnalyticsFirebase", "Firebase Analytics is not available", exception) 34 | } 35 | } 36 | .getOrNull() 37 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/GalleryApp.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery 18 | 19 | import androidx.compose.runtime.Composable 20 | import androidx.navigation.NavHostController 21 | import androidx.navigation.compose.rememberNavController 22 | import com.google.ai.edge.gallery.ui.navigation.GalleryNavHost 23 | 24 | /** Top level composable representing the main screen of the application. */ 25 | @Composable 26 | fun GalleryApp(navController: NavHostController = rememberNavController()) { 27 | GalleryNavHost(navController = navController) 28 | } 29 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/GalleryApplication.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery 18 | 19 | import android.app.Application 20 | import com.google.ai.edge.gallery.common.writeLaunchInfo 21 | import com.google.ai.edge.gallery.data.DataStoreRepository 22 | import com.google.ai.edge.gallery.ui.theme.ThemeSettings 23 | import com.google.firebase.FirebaseApp 24 | import dagger.hilt.android.HiltAndroidApp 25 | import javax.inject.Inject 26 | 27 | @HiltAndroidApp 28 | class GalleryApplication : Application() { 29 | 30 | @Inject lateinit var dataStoreRepository: DataStoreRepository 31 | 32 | override fun onCreate() { 33 | super.onCreate() 34 | 35 | writeLaunchInfo(context = this) 36 | 37 | // Load saved theme. 38 | ThemeSettings.themeOverride.value = dataStoreRepository.readTheme() 39 | 40 | FirebaseApp.initializeApp(this) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/GalleryLifecycleProvider.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery 18 | 19 | interface AppLifecycleProvider { 20 | var isAppInForeground: Boolean 21 | } 22 | 23 | class GalleryLifecycleProvider : AppLifecycleProvider { 24 | private var _isAppInForeground = false 25 | 26 | override var isAppInForeground: Boolean 27 | get() = _isAppInForeground 28 | set(value) { 29 | _isAppInForeground = value 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/MainActivity.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery 18 | 19 | import android.os.Build 20 | import android.os.Bundle 21 | import android.view.WindowManager 22 | import androidx.activity.ComponentActivity 23 | import androidx.activity.compose.setContent 24 | import androidx.activity.enableEdgeToEdge 25 | import androidx.compose.foundation.layout.fillMaxSize 26 | import androidx.compose.material3.Surface 27 | import androidx.compose.ui.Modifier 28 | import androidx.core.os.bundleOf 29 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen 30 | import com.google.ai.edge.gallery.ui.theme.GalleryTheme 31 | import com.google.firebase.analytics.FirebaseAnalytics 32 | import dagger.hilt.android.AndroidEntryPoint 33 | 34 | @AndroidEntryPoint 35 | class MainActivity : ComponentActivity() { 36 | 37 | override fun onCreate(savedInstanceState: Bundle?) { 38 | super.onCreate(savedInstanceState) 39 | 40 | installSplashScreen() 41 | 42 | enableEdgeToEdge() 43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 44 | // Fix for three-button nav not properly going edge-to-edge. 45 | // See: https://issuetracker.google.com/issues/298296168 46 | window.isNavigationBarContrastEnforced = false 47 | } 48 | setContent { GalleryTheme { Surface(modifier = Modifier.fillMaxSize()) { GalleryApp() } } } 49 | // Keep the screen on while the app is running for better demo experience. 50 | window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) 51 | } 52 | 53 | override fun onResume() { 54 | super.onResume() 55 | 56 | firebaseAnalytics?.logEvent( 57 | FirebaseAnalytics.Event.APP_OPEN, 58 | bundleOf( 59 | "app_version" to BuildConfig.VERSION_NAME, 60 | "os_version" to Build.VERSION.SDK_INT.toString(), 61 | "device_model" to Build.MODEL, 62 | ), 63 | ) 64 | } 65 | 66 | companion object { 67 | private const val TAG = "AGMainActivity" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/SettingsSerializer.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery 18 | 19 | import androidx.datastore.core.CorruptionException 20 | import androidx.datastore.core.Serializer 21 | import com.google.ai.edge.gallery.proto.Settings 22 | import com.google.protobuf.InvalidProtocolBufferException 23 | import java.io.InputStream 24 | import java.io.OutputStream 25 | 26 | object SettingsSerializer : Serializer<Settings> { 27 | override val defaultValue: Settings = Settings.getDefaultInstance() 28 | 29 | override suspend fun readFrom(input: InputStream): Settings { 30 | try { 31 | return Settings.parseFrom(input) 32 | } catch (exception: InvalidProtocolBufferException) { 33 | throw CorruptionException("Cannot read proto.", exception) 34 | } 35 | } 36 | 37 | override suspend fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output) 38 | } 39 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/common/Types.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.common 18 | 19 | import androidx.compose.ui.graphics.Color 20 | 21 | interface LatencyProvider { 22 | val latencyMs: Float 23 | } 24 | 25 | data class Classification(val label: String, val score: Float, val color: Color) 26 | 27 | data class JsonObjAndTextContent<T>(val jsonObj: T, val textContent: String) 28 | 29 | class AudioClip(val audioData: ByteArray, val sampleRate: Int) 30 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/data/AppBarAction.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.data 18 | 19 | /** Possible action for app bar. */ 20 | enum class AppBarActionType { 21 | NO_ACTION, 22 | APP_SETTING, 23 | DOWNLOAD_MANAGER, 24 | MODEL_SELECTOR, 25 | NAVIGATE_UP, 26 | REFRESH_MODELS, 27 | REFRESHING_MODELS, 28 | } 29 | 30 | class AppBarAction(val actionType: AppBarActionType, val actionFn: () -> Unit) 31 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/data/AppContainer.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.data 18 | 19 | import android.content.Context 20 | import androidx.datastore.core.DataStore 21 | import com.google.ai.edge.gallery.AppLifecycleProvider 22 | import com.google.ai.edge.gallery.GalleryLifecycleProvider 23 | import com.google.ai.edge.gallery.proto.Settings 24 | 25 | /** 26 | * App container for Dependency injection. 27 | * 28 | * This interface defines the dependencies required by the application. 29 | */ 30 | interface AppContainer { 31 | val context: Context 32 | val lifecycleProvider: AppLifecycleProvider 33 | val dataStoreRepository: DataStoreRepository 34 | val downloadRepository: DownloadRepository 35 | } 36 | 37 | /** 38 | * Default implementation of the AppContainer interface. 39 | * 40 | * This class provides concrete implementations for the application's dependencies, 41 | */ 42 | class DefaultAppContainer(ctx: Context, dataStore: DataStore<Settings>) : AppContainer { 43 | override val context = ctx 44 | override val lifecycleProvider = GalleryLifecycleProvider() 45 | override val dataStoreRepository = DefaultDataStoreRepository(dataStore) 46 | override val downloadRepository = DefaultDownloadRepository(ctx, lifecycleProvider) 47 | } 48 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/data/ConfigValue.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.data 18 | 19 | // @Serializable(with = ConfigValueSerializer::class) 20 | sealed class ConfigValue { 21 | // @Serializable 22 | data class IntValue(val value: Int) : ConfigValue() 23 | 24 | // @Serializable 25 | data class FloatValue(val value: Float) : ConfigValue() 26 | 27 | // @Serializable 28 | data class StringValue(val value: String) : ConfigValue() 29 | } 30 | 31 | // /** 32 | // * Custom serializer for the ConfigValue class. 33 | // * 34 | // * This object implements the KSerializer interface to provide custom serialization and 35 | // * deserialization logic for the ConfigValue class. It handles different types of ConfigValue 36 | // * (IntValue, FloatValue, StringValue) and supports JSON format. 37 | // */ 38 | // object ConfigValueSerializer : KSerializer<ConfigValue> { 39 | // override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ConfigValue") 40 | 41 | // override fun serialize(encoder: Encoder, value: ConfigValue) { 42 | // when (value) { 43 | // is ConfigValue.IntValue -> encoder.encodeInt(value.value) 44 | // is ConfigValue.FloatValue -> encoder.encodeFloat(value.value) 45 | // is ConfigValue.StringValue -> encoder.encodeString(value.value) 46 | // } 47 | // } 48 | 49 | // override fun deserialize(decoder: Decoder): ConfigValue { 50 | // val input = 51 | // decoder as? JsonDecoder 52 | // ?: throw SerializationException("This serializer only works with Json") 53 | // return when (val element = input.decodeJsonElement()) { 54 | // is JsonPrimitive -> { 55 | // if (element.isString) { 56 | // ConfigValue.StringValue(element.content) 57 | // } else if (element.content.contains('.')) { 58 | // ConfigValue.FloatValue(element.content.toFloat()) 59 | // } else { 60 | // ConfigValue.IntValue(element.content.toInt()) 61 | // } 62 | // } 63 | 64 | // else -> throw SerializationException("Expected JsonPrimitive") 65 | // } 66 | // } 67 | // } 68 | 69 | fun getIntConfigValue(configValue: ConfigValue?, default: Int): Int { 70 | if (configValue == null) { 71 | return default 72 | } 73 | return when (configValue) { 74 | is ConfigValue.IntValue -> configValue.value 75 | is ConfigValue.FloatValue -> configValue.value.toInt() 76 | is ConfigValue.StringValue -> 0 77 | } 78 | } 79 | 80 | fun getFloatConfigValue(configValue: ConfigValue?, default: Float): Float { 81 | if (configValue == null) { 82 | return default 83 | } 84 | return when (configValue) { 85 | is ConfigValue.IntValue -> configValue.value.toFloat() 86 | is ConfigValue.FloatValue -> configValue.value 87 | is ConfigValue.StringValue -> 0f 88 | } 89 | } 90 | 91 | fun getStringConfigValue(configValue: ConfigValue?, default: String): String { 92 | if (configValue == null) { 93 | return default 94 | } 95 | return when (configValue) { 96 | is ConfigValue.IntValue -> "${configValue.value}" 97 | is ConfigValue.FloatValue -> "${configValue.value}" 98 | is ConfigValue.StringValue -> configValue.value 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/data/Consts.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.data 18 | 19 | // Keys used to send/receive data to Work. 20 | const val KEY_MODEL_URL = "KEY_MODEL_URL" 21 | const val KEY_MODEL_NAME = "KEY_MODEL_NAME" 22 | const val KEY_MODEL_VERSION = "KEY_MODEL_VERSION" 23 | const val KEY_MODEL_DOWNLOAD_MODEL_DIR = "KEY_MODEL_DOWNLOAD_MODEL_DIR" 24 | const val KEY_MODEL_DOWNLOAD_FILE_NAME = "KEY_MODEL_DOWNLOAD_FILE_NAME" 25 | const val KEY_MODEL_TOTAL_BYTES = "KEY_MODEL_TOTAL_BYTES" 26 | const val KEY_MODEL_DOWNLOAD_RECEIVED_BYTES = "KEY_MODEL_DOWNLOAD_RECEIVED_BYTES" 27 | const val KEY_MODEL_DOWNLOAD_RATE = "KEY_MODEL_DOWNLOAD_RATE" 28 | const val KEY_MODEL_DOWNLOAD_REMAINING_MS = "KEY_MODEL_DOWNLOAD_REMAINING_SECONDS" 29 | const val KEY_MODEL_DOWNLOAD_ERROR_MESSAGE = "KEY_MODEL_DOWNLOAD_ERROR_MESSAGE" 30 | const val KEY_MODEL_DOWNLOAD_ACCESS_TOKEN = "KEY_MODEL_DOWNLOAD_ACCESS_TOKEN" 31 | const val KEY_MODEL_DOWNLOAD_APP_TS = "KEY_MODEL_DOWNLOAD_APP_TS" 32 | const val KEY_MODEL_EXTRA_DATA_URLS = "KEY_MODEL_EXTRA_DATA_URLS" 33 | const val KEY_MODEL_EXTRA_DATA_DOWNLOAD_FILE_NAMES = "KEY_MODEL_EXTRA_DATA_DOWNLOAD_FILE_NAMES" 34 | const val KEY_MODEL_IS_ZIP = "KEY_MODEL_IS_ZIP" 35 | const val KEY_MODEL_UNZIPPED_DIR = "KEY_MODEL_UNZIPPED_DIR" 36 | const val KEY_MODEL_START_UNZIPPING = "KEY_MODEL_START_UNZIPPING" 37 | 38 | // Default values for LLM models. 39 | const val DEFAULT_MAX_TOKEN = 1024 40 | const val DEFAULT_TOPK = 40 41 | const val DEFAULT_TOPP = 0.9f 42 | const val DEFAULT_TEMPERATURE = 1.0f 43 | val DEFAULT_ACCELERATORS = listOf(Accelerator.GPU) 44 | 45 | // Max number of images allowed in a "ask image" session. 46 | const val MAX_IMAGE_COUNT = 10 47 | 48 | // Max number of audio clip in an "ask audio" session. 49 | const val MAX_AUDIO_CLIP_COUNT = 1 50 | 51 | // Max audio clip duration in seconds. 52 | const val MAX_AUDIO_CLIP_DURATION_SEC = 30 53 | 54 | // Audio-recording related consts. 55 | const val SAMPLE_RATE = 16000 56 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/data/DataStoreRepository.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.data 18 | 19 | import androidx.datastore.core.DataStore 20 | import com.google.ai.edge.gallery.proto.AccessTokenData 21 | import com.google.ai.edge.gallery.proto.ImportedModel 22 | import com.google.ai.edge.gallery.proto.Settings 23 | import com.google.ai.edge.gallery.proto.Theme 24 | import kotlinx.coroutines.flow.first 25 | import kotlinx.coroutines.runBlocking 26 | 27 | // TODO(b/423700720): Change to async (suspend) functions 28 | interface DataStoreRepository { 29 | fun saveTextInputHistory(history: List<String>) 30 | 31 | fun readTextInputHistory(): List<String> 32 | 33 | fun saveTheme(theme: Theme) 34 | 35 | fun readTheme(): Theme 36 | 37 | fun saveAccessTokenData(accessToken: String, refreshToken: String, expiresAt: Long) 38 | 39 | fun clearAccessTokenData() 40 | 41 | fun readAccessTokenData(): AccessTokenData? 42 | 43 | fun saveImportedModels(importedModels: List<ImportedModel>) 44 | 45 | fun readImportedModels(): List<ImportedModel> 46 | 47 | fun isTosAccepted(): Boolean 48 | 49 | fun acceptTos() 50 | } 51 | 52 | /** Repository for managing data using Proto DataStore. */ 53 | class DefaultDataStoreRepository(private val dataStore: DataStore<Settings>) : DataStoreRepository { 54 | override fun saveTextInputHistory(history: List<String>) { 55 | runBlocking { 56 | dataStore.updateData { settings -> 57 | settings.toBuilder().clearTextInputHistory().addAllTextInputHistory(history).build() 58 | } 59 | } 60 | } 61 | 62 | override fun readTextInputHistory(): List<String> { 63 | return runBlocking { 64 | val settings = dataStore.data.first() 65 | settings.textInputHistoryList 66 | } 67 | } 68 | 69 | override fun saveTheme(theme: Theme) { 70 | runBlocking { 71 | dataStore.updateData { settings -> settings.toBuilder().setTheme(theme).build() } 72 | } 73 | } 74 | 75 | override fun readTheme(): Theme { 76 | return runBlocking { 77 | val settings = dataStore.data.first() 78 | val curTheme = settings.theme 79 | // Use "auto" as the default theme. 80 | if (curTheme == Theme.THEME_UNSPECIFIED) Theme.THEME_AUTO else curTheme 81 | } 82 | } 83 | 84 | override fun saveAccessTokenData(accessToken: String, refreshToken: String, expiresAt: Long) { 85 | runBlocking { 86 | dataStore.updateData { settings -> 87 | settings 88 | .toBuilder() 89 | .setAccessTokenData( 90 | AccessTokenData.newBuilder() 91 | .setAccessToken(accessToken) 92 | .setRefreshToken(refreshToken) 93 | .setExpiresAtMs(expiresAt) 94 | .build() 95 | ) 96 | .build() 97 | } 98 | } 99 | } 100 | 101 | override fun clearAccessTokenData() { 102 | runBlocking { 103 | dataStore.updateData { settings -> settings.toBuilder().clearAccessTokenData().build() } 104 | } 105 | } 106 | 107 | override fun readAccessTokenData(): AccessTokenData? { 108 | return runBlocking { 109 | val settings = dataStore.data.first() 110 | settings.accessTokenData 111 | } 112 | } 113 | 114 | override fun saveImportedModels(importedModels: List<ImportedModel>) { 115 | runBlocking { 116 | dataStore.updateData { settings -> 117 | settings.toBuilder().clearImportedModel().addAllImportedModel(importedModels).build() 118 | } 119 | } 120 | } 121 | 122 | override fun readImportedModels(): List<ImportedModel> { 123 | return runBlocking { 124 | val settings = dataStore.data.first() 125 | settings.importedModelList 126 | } 127 | } 128 | 129 | override fun isTosAccepted(): Boolean { 130 | return runBlocking { 131 | val settings = dataStore.data.first() 132 | settings.isTosAccepted 133 | } 134 | } 135 | 136 | override fun acceptTos() { 137 | runBlocking { 138 | dataStore.updateData { settings -> settings.toBuilder().setIsTosAccepted(true).build() } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/data/ModelAllowlist.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.data 18 | 19 | import com.google.gson.annotations.SerializedName 20 | 21 | data class DefaultConfig( 22 | @SerializedName("topK") val topK: Int?, 23 | @SerializedName("topP") val topP: Float?, 24 | @SerializedName("temperature") val temperature: Float?, 25 | @SerializedName("accelerators") val accelerators: String?, 26 | @SerializedName("maxTokens") val maxTokens: Int?, 27 | ) 28 | 29 | /** A model in the model allowlist. */ 30 | data class AllowedModel( 31 | val name: String, 32 | val modelId: String, 33 | val modelFile: String, 34 | val description: String, 35 | val sizeInBytes: Long, 36 | val version: String, 37 | val defaultConfig: DefaultConfig, 38 | val taskTypes: List<String>, 39 | val disabled: Boolean? = null, 40 | val llmSupportImage: Boolean? = null, 41 | val llmSupportAudio: Boolean? = null, 42 | val estimatedPeakMemoryInBytes: Long? = null, 43 | ) { 44 | fun toModel(): Model { 45 | // Construct HF download url. 46 | val downloadUrl = "https://huggingface.co/$modelId/resolve/main/$modelFile?download=true" 47 | 48 | // Config. 49 | val isLlmModel = 50 | taskTypes.contains(TASK_LLM_CHAT.type.id) || taskTypes.contains(TASK_LLM_PROMPT_LAB.type.id) 51 | var configs: List<Config> = listOf() 52 | if (isLlmModel) { 53 | val defaultTopK: Int = defaultConfig.topK ?: DEFAULT_TOPK 54 | val defaultTopP: Float = defaultConfig.topP ?: DEFAULT_TOPP 55 | val defaultTemperature: Float = defaultConfig.temperature ?: DEFAULT_TEMPERATURE 56 | val defaultMaxToken = defaultConfig.maxTokens ?: 1024 57 | var accelerators: List<Accelerator> = DEFAULT_ACCELERATORS 58 | if (defaultConfig.accelerators != null) { 59 | val items = defaultConfig.accelerators.split(",") 60 | accelerators = mutableListOf() 61 | for (item in items) { 62 | if (item == "cpu") { 63 | accelerators.add(Accelerator.CPU) 64 | } else if (item == "gpu") { 65 | accelerators.add(Accelerator.GPU) 66 | } 67 | } 68 | } 69 | configs = 70 | createLlmChatConfigs( 71 | defaultTopK = defaultTopK, 72 | defaultTopP = defaultTopP, 73 | defaultTemperature = defaultTemperature, 74 | defaultMaxToken = defaultMaxToken, 75 | accelerators = accelerators, 76 | ) 77 | } 78 | 79 | // Misc. 80 | var showBenchmarkButton = true 81 | var showRunAgainButton = true 82 | if (isLlmModel) { 83 | showBenchmarkButton = false 84 | showRunAgainButton = false 85 | } 86 | 87 | return Model( 88 | name = name, 89 | version = version, 90 | info = description, 91 | url = downloadUrl, 92 | sizeInBytes = sizeInBytes, 93 | estimatedPeakMemoryInBytes = estimatedPeakMemoryInBytes, 94 | configs = configs, 95 | downloadFileName = modelFile, 96 | showBenchmarkButton = showBenchmarkButton, 97 | showRunAgainButton = showRunAgainButton, 98 | learnMoreUrl = "https://huggingface.co/${modelId}", 99 | llmSupportImage = llmSupportImage == true, 100 | llmSupportAudio = llmSupportAudio == true, 101 | ) 102 | } 103 | 104 | override fun toString(): String { 105 | return "$modelId/$modelFile" 106 | } 107 | } 108 | 109 | /** The model allowlist. */ 110 | data class ModelAllowlist(val models: List<AllowedModel>) 111 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/data/Types.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.data 18 | 19 | enum class Accelerator(val label: String) { 20 | CPU(label = "CPU"), 21 | GPU(label = "GPU"), 22 | } 23 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/di/AppModule.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.di 18 | 19 | import android.content.Context 20 | import androidx.datastore.core.DataStore 21 | import androidx.datastore.core.DataStoreFactory 22 | import androidx.datastore.core.Serializer 23 | import androidx.datastore.dataStoreFile 24 | import com.google.ai.edge.gallery.AppLifecycleProvider 25 | import com.google.ai.edge.gallery.GalleryLifecycleProvider 26 | import com.google.ai.edge.gallery.SettingsSerializer 27 | import com.google.ai.edge.gallery.data.DataStoreRepository 28 | import com.google.ai.edge.gallery.data.DefaultDataStoreRepository 29 | import com.google.ai.edge.gallery.data.DefaultDownloadRepository 30 | import com.google.ai.edge.gallery.data.DownloadRepository 31 | import com.google.ai.edge.gallery.proto.Settings 32 | import dagger.Module 33 | import dagger.Provides 34 | import dagger.hilt.InstallIn 35 | import dagger.hilt.android.qualifiers.ApplicationContext 36 | import dagger.hilt.components.SingletonComponent 37 | import javax.inject.Singleton 38 | 39 | @Module 40 | @InstallIn(SingletonComponent::class) 41 | internal object AppModule { 42 | 43 | // Provides the SettingsSerializer 44 | @Provides 45 | @Singleton 46 | fun provideSettingsSerializer(): Serializer<Settings> { 47 | return SettingsSerializer 48 | } 49 | 50 | // Provides DataStore<Settings> 51 | @Provides 52 | @Singleton 53 | fun provideSettingsDataStore( 54 | @ApplicationContext context: Context, 55 | settingsSerializer: Serializer<Settings>, 56 | ): DataStore<Settings> { 57 | return DataStoreFactory.create( 58 | serializer = settingsSerializer, 59 | produceFile = { context.dataStoreFile("settings.pb") }, 60 | ) 61 | } 62 | 63 | // Provides AppLifecycleProvider 64 | @Provides 65 | @Singleton 66 | fun provideAppLifecycleProvider(): AppLifecycleProvider { 67 | return GalleryLifecycleProvider() 68 | } 69 | 70 | // Provides DataStoreRepository 71 | @Provides 72 | @Singleton 73 | fun provideDataStoreRepository(dataStore: DataStore<Settings>): DataStoreRepository { 74 | return DefaultDataStoreRepository(dataStore) 75 | } 76 | 77 | // Provides DownloadRepository 78 | @Provides 79 | @Singleton 80 | fun provideDownloadRepository( 81 | @ApplicationContext context: Context, 82 | lifecycleProvider: AppLifecycleProvider, 83 | ): DownloadRepository { 84 | return DefaultDownloadRepository(context, lifecycleProvider) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/AuthConfig.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common 18 | 19 | import androidx.core.net.toUri 20 | import net.openid.appauth.AuthorizationServiceConfiguration 21 | 22 | object AuthConfig { 23 | // Hugging Face Client ID. 24 | const val clientId = "REPLACE_WITH_YOUR_CLIENT_ID_IN_HUGGINGFACE_APP" 25 | 26 | // Registered redirect URI. 27 | // 28 | // The scheme needs to match the 29 | // "android.defaultConfig.manifestPlaceholders["appAuthRedirectScheme"]" field in 30 | // "build.gradle.kts". 31 | const val redirectUri = "REPLACE_WITH_YOUR_REDIRECT_URI_IN_HUGGINGFACE_APP" 32 | 33 | // OAuth 2.0 Endpoints (Authorization + Token Exchange) 34 | private const val authEndpoint = "https://huggingface.co/oauth/authorize" 35 | private const val tokenEndpoint = "https://huggingface.co/oauth/token" 36 | 37 | // OAuth service configuration (AppAuth library requires this) 38 | val authServiceConfig = 39 | AuthorizationServiceConfiguration( 40 | authEndpoint.toUri(), // Authorization endpoint 41 | tokenEndpoint.toUri(), // Token exchange endpoint 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ClickableLink.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common 18 | 19 | import androidx.compose.foundation.clickable 20 | import androidx.compose.foundation.layout.Arrangement 21 | import androidx.compose.foundation.layout.Row 22 | import androidx.compose.foundation.layout.padding 23 | import androidx.compose.foundation.layout.size 24 | import androidx.compose.material3.Icon 25 | import androidx.compose.material3.MaterialTheme 26 | import androidx.compose.material3.Text 27 | import androidx.compose.runtime.Composable 28 | import androidx.compose.ui.Alignment 29 | import androidx.compose.ui.Modifier 30 | import androidx.compose.ui.graphics.vector.ImageVector 31 | import androidx.compose.ui.platform.LocalUriHandler 32 | import androidx.compose.ui.text.AnnotatedString 33 | import androidx.compose.ui.text.SpanStyle 34 | import androidx.compose.ui.text.style.TextAlign 35 | import androidx.compose.ui.text.style.TextDecoration 36 | import androidx.compose.ui.unit.dp 37 | import androidx.core.os.bundleOf 38 | import com.google.ai.edge.gallery.firebaseAnalytics 39 | import com.google.ai.edge.gallery.ui.theme.customColors 40 | 41 | @Composable 42 | fun ClickableLink(url: String, linkText: String, icon: ImageVector) { 43 | val uriHandler = LocalUriHandler.current 44 | val annotatedText = 45 | AnnotatedString( 46 | text = linkText, 47 | spanStyles = 48 | listOf( 49 | AnnotatedString.Range( 50 | item = 51 | SpanStyle( 52 | color = MaterialTheme.customColors.linkColor, 53 | textDecoration = TextDecoration.Underline, 54 | ), 55 | start = 0, 56 | end = linkText.length, 57 | ) 58 | ), 59 | ) 60 | 61 | Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) { 62 | Icon(icon, contentDescription = "", modifier = Modifier.size(16.dp)) 63 | Text( 64 | text = annotatedText, 65 | textAlign = TextAlign.Center, 66 | style = MaterialTheme.typography.bodyLarge, 67 | modifier = 68 | Modifier.padding(start = 6.dp).clickable { 69 | uriHandler.openUri(url) 70 | firebaseAnalytics?.logEvent("resource_link_click", bundleOf("link_destination" to url)) 71 | }, 72 | ) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ColorUtils.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common 18 | 19 | import androidx.compose.material3.MaterialTheme 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.ui.graphics.Color 22 | import com.google.ai.edge.gallery.data.Task 23 | import com.google.ai.edge.gallery.ui.theme.customColors 24 | 25 | @Composable 26 | fun getTaskBgColor(task: Task): Color { 27 | val colorIndex: Int = task.index % MaterialTheme.customColors.taskBgColors.size 28 | return MaterialTheme.customColors.taskBgColors[colorIndex] 29 | } 30 | 31 | @Composable 32 | fun getTaskIconColor(task: Task): Color { 33 | val colorIndex: Int = task.index % MaterialTheme.customColors.taskIconColors.size 34 | return MaterialTheme.customColors.taskIconColors[colorIndex] 35 | } 36 | 37 | @Composable 38 | fun getTaskIconColor(index: Int): Color { 39 | val colorIndex: Int = index % MaterialTheme.customColors.taskIconColors.size 40 | return MaterialTheme.customColors.taskIconColors[colorIndex] 41 | } 42 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/ErrorDialog.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common 18 | 19 | import androidx.compose.foundation.layout.Arrangement 20 | import androidx.compose.foundation.layout.Column 21 | import androidx.compose.foundation.layout.Row 22 | import androidx.compose.foundation.layout.fillMaxWidth 23 | import androidx.compose.foundation.layout.padding 24 | import androidx.compose.foundation.shape.RoundedCornerShape 25 | import androidx.compose.material3.Button 26 | import androidx.compose.material3.Card 27 | import androidx.compose.material3.MaterialTheme 28 | import androidx.compose.material3.Text 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.ui.Modifier 31 | import androidx.compose.ui.unit.dp 32 | import androidx.compose.ui.window.Dialog 33 | 34 | @Composable 35 | fun ErrorDialog(error: String, onDismiss: () -> Unit) { 36 | Dialog(onDismissRequest = onDismiss) { 37 | Card(modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(16.dp)) { 38 | Column( 39 | modifier = Modifier.padding(20.dp), 40 | verticalArrangement = Arrangement.spacedBy(16.dp), 41 | ) { 42 | // Title 43 | Text( 44 | "Error", 45 | style = MaterialTheme.typography.titleLarge, 46 | modifier = Modifier.padding(bottom = 8.dp), 47 | ) 48 | 49 | // Error 50 | Text( 51 | error, 52 | style = MaterialTheme.typography.bodySmall, 53 | color = MaterialTheme.colorScheme.error, 54 | ) 55 | 56 | Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { 57 | Button(onClick = onDismiss) { Text("Close") } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/MarkdownText.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common 18 | 19 | import androidx.compose.material3.MaterialTheme 20 | import androidx.compose.material3.ProvideTextStyle 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.runtime.CompositionLocalProvider 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.text.SpanStyle 25 | import androidx.compose.ui.text.TextLinkStyles 26 | import androidx.compose.ui.text.TextStyle 27 | import androidx.compose.ui.text.font.FontFamily 28 | import com.google.ai.edge.gallery.ui.theme.customColors 29 | import com.halilibo.richtext.commonmark.Markdown 30 | import com.halilibo.richtext.ui.CodeBlockStyle 31 | import com.halilibo.richtext.ui.RichTextStyle 32 | import com.halilibo.richtext.ui.material3.RichText 33 | import com.halilibo.richtext.ui.string.RichTextStringStyle 34 | 35 | /** Composable function to display Markdown-formatted text. */ 36 | @Composable 37 | fun MarkdownText(text: String, modifier: Modifier = Modifier, smallFontSize: Boolean = false) { 38 | val fontSize = 39 | if (smallFontSize) MaterialTheme.typography.bodyMedium.fontSize 40 | else MaterialTheme.typography.bodyLarge.fontSize 41 | CompositionLocalProvider { 42 | ProvideTextStyle(value = TextStyle(fontSize = fontSize, lineHeight = fontSize * 1.3)) { 43 | RichText( 44 | modifier = modifier, 45 | style = 46 | RichTextStyle( 47 | codeBlockStyle = 48 | CodeBlockStyle( 49 | textStyle = 50 | TextStyle( 51 | fontSize = MaterialTheme.typography.bodySmall.fontSize, 52 | fontFamily = FontFamily.Monospace, 53 | ) 54 | ), 55 | stringStyle = 56 | RichTextStringStyle( 57 | linkStyle = 58 | TextLinkStyles(style = SpanStyle(color = MaterialTheme.customColors.linkColor)) 59 | ), 60 | ), 61 | ) { 62 | Markdown(content = text) 63 | } 64 | } 65 | } 66 | } 67 | 68 | // @Preview(showBackground = true) 69 | // @Composable 70 | // fun MarkdownTextPreview() { 71 | // GalleryTheme { 72 | // MarkdownText(text = "*Hello World*\n**Good morning!!**") 73 | // } 74 | // } 75 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/TaskIcon.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common 18 | 19 | import androidx.compose.foundation.Image 20 | import androidx.compose.foundation.background 21 | import androidx.compose.foundation.layout.Box 22 | import androidx.compose.foundation.layout.Column 23 | import androidx.compose.foundation.layout.aspectRatio 24 | import androidx.compose.foundation.layout.fillMaxSize 25 | import androidx.compose.foundation.layout.size 26 | import androidx.compose.foundation.layout.width 27 | import androidx.compose.material3.Icon 28 | import androidx.compose.material3.MaterialTheme 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.draw.alpha 33 | import androidx.compose.ui.graphics.BlendMode 34 | import androidx.compose.ui.graphics.Color 35 | import androidx.compose.ui.graphics.ColorFilter 36 | import androidx.compose.ui.graphics.painter.Painter 37 | import androidx.compose.ui.graphics.vector.ImageVector 38 | import androidx.compose.ui.layout.ContentScale 39 | import androidx.compose.ui.res.painterResource 40 | import androidx.compose.ui.res.vectorResource 41 | import androidx.compose.ui.tooling.preview.Preview 42 | import androidx.compose.ui.unit.Dp 43 | import androidx.compose.ui.unit.dp 44 | import com.google.ai.edge.gallery.R 45 | import com.google.ai.edge.gallery.data.TASKS 46 | import com.google.ai.edge.gallery.data.TASK_LLM_CHAT 47 | import com.google.ai.edge.gallery.data.Task 48 | import com.google.ai.edge.gallery.ui.theme.GalleryTheme 49 | import com.google.ai.edge.gallery.ui.theme.customColors 50 | 51 | private val SHAPES: List<Int> = 52 | listOf(R.drawable.pantegon, R.drawable.double_circle, R.drawable.circle, R.drawable.four_circle) 53 | 54 | /** 55 | * Composable that displays an icon representing a task. It consists of a background image and a 56 | * foreground icon, both centered within a square box. 57 | */ 58 | @Composable 59 | fun TaskIcon(task: Task, modifier: Modifier = Modifier, width: Dp = 56.dp) { 60 | Box(modifier = modifier.width(width).aspectRatio(1f), contentAlignment = Alignment.Center) { 61 | Image( 62 | painter = getTaskIconBgShape(task = task), 63 | contentDescription = "", 64 | modifier = Modifier.fillMaxSize().alpha(0.6f), 65 | contentScale = ContentScale.Fit, 66 | colorFilter = 67 | ColorFilter.tint( 68 | MaterialTheme.customColors.taskIconShapeBgColor, 69 | blendMode = BlendMode.SrcIn, 70 | ), 71 | ) 72 | Icon( 73 | task.icon ?: ImageVector.vectorResource(task.iconVectorResourceId!!), 74 | tint = getTaskIconColor(task = task), 75 | modifier = Modifier.size(width * 0.6f), 76 | contentDescription = "", 77 | ) 78 | } 79 | } 80 | 81 | @Composable 82 | private fun getTaskIconBgShape(task: Task): Painter { 83 | val colorIndex: Int = task.index % SHAPES.size 84 | return painterResource(SHAPES[colorIndex]) 85 | } 86 | 87 | @Preview(showBackground = true) 88 | @Composable 89 | fun TaskIconPreview() { 90 | for ((index, task) in TASKS.withIndex()) { 91 | task.index = index 92 | } 93 | 94 | GalleryTheme { 95 | Column(modifier = Modifier.background(Color.Gray)) { 96 | TaskIcon(task = TASK_LLM_CHAT, width = 80.dp) 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/Utils.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common 18 | 19 | import android.Manifest 20 | import android.content.Context 21 | import android.content.pm.PackageManager 22 | import android.net.Uri 23 | import android.os.Build 24 | import androidx.activity.compose.ManagedActivityResultLauncher 25 | import androidx.compose.ui.graphics.Color 26 | import androidx.core.content.ContextCompat 27 | import androidx.core.content.FileProvider 28 | import com.google.ai.edge.gallery.data.Model 29 | import com.google.ai.edge.gallery.data.Task 30 | import com.google.ai.edge.gallery.ui.modelmanager.ModelManagerViewModel 31 | import java.io.File 32 | import kotlin.math.ln 33 | import kotlin.math.pow 34 | 35 | private const val TAG = "AGUtils" 36 | 37 | /** Format the bytes into a human-readable format. */ 38 | fun Long.humanReadableSize(si: Boolean = true, extraDecimalForGbAndAbove: Boolean = false): String { 39 | val bytes = this 40 | 41 | val unit = if (si) 1000 else 1024 42 | if (bytes < unit) return "$bytes B" 43 | val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt() 44 | val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i" 45 | var formatString = "%.1f %sB" 46 | if (extraDecimalForGbAndAbove && pre.lowercase() != "k" && pre != "M") { 47 | formatString = "%.2f %sB" 48 | } 49 | return formatString.format(bytes / unit.toDouble().pow(exp.toDouble()), pre) 50 | } 51 | 52 | fun Float.humanReadableDuration(): String { 53 | val milliseconds = this 54 | if (milliseconds < 1000) { 55 | return "$milliseconds ms" 56 | } 57 | val seconds = milliseconds / 1000f 58 | if (seconds < 60) { 59 | return "%.1f s".format(seconds) 60 | } 61 | 62 | val minutes = seconds / 60f 63 | if (minutes < 60) { 64 | return "%.1f min".format(minutes) 65 | } 66 | 67 | val hours = minutes / 60f 68 | return "%.1f h".format(hours) 69 | } 70 | 71 | fun Long.formatToHourMinSecond(): String { 72 | val ms = this 73 | if (ms < 0) { 74 | return "-" 75 | } 76 | 77 | val seconds = ms / 1000 78 | val hours = seconds / 3600 79 | val minutes = (seconds % 3600) / 60 80 | val remainingSeconds = seconds % 60 81 | 82 | val parts = mutableListOf<String>() 83 | 84 | if (hours > 0) { 85 | parts.add("$hours h") 86 | } 87 | if (minutes > 0) { 88 | parts.add("$minutes min") 89 | } 90 | if (remainingSeconds > 0 || (hours == 0L && minutes == 0L)) { 91 | parts.add("$remainingSeconds sec") 92 | } 93 | 94 | return parts.joinToString(" ") 95 | } 96 | 97 | fun getDistinctiveColor(index: Int): Color { 98 | val colors = 99 | listOf( 100 | // Color(0xffe6194b), 101 | Color(0xff3cb44b), 102 | Color(0xffffe119), 103 | Color(0xff4363d8), 104 | Color(0xfff58231), 105 | Color(0xff911eb4), 106 | Color(0xff46f0f0), 107 | Color(0xfff032e6), 108 | Color(0xffbcf60c), 109 | Color(0xfffabebe), 110 | Color(0xff008080), 111 | Color(0xffe6beff), 112 | Color(0xff9a6324), 113 | Color(0xfffffac8), 114 | Color(0xff800000), 115 | Color(0xffaaffc3), 116 | Color(0xff808000), 117 | Color(0xffffd8b1), 118 | Color(0xff000075), 119 | ) 120 | return colors[index % colors.size] 121 | } 122 | 123 | fun Context.createTempPictureUri( 124 | fileName: String = "picture_${System.currentTimeMillis()}", 125 | fileExtension: String = ".png", 126 | ): Uri { 127 | val tempFile = File.createTempFile(fileName, fileExtension, cacheDir).apply { createNewFile() } 128 | 129 | return FileProvider.getUriForFile( 130 | applicationContext, 131 | "com.google.aiedge.gallery.provider" /* {applicationId}.provider */, 132 | tempFile, 133 | ) 134 | } 135 | 136 | fun checkNotificationPermissionAndStartDownload( 137 | context: Context, 138 | launcher: ManagedActivityResultLauncher<String, Boolean>, 139 | modelManagerViewModel: ModelManagerViewModel, 140 | task: Task, 141 | model: Model, 142 | ) { 143 | // Check permission 144 | when (PackageManager.PERMISSION_GRANTED) { 145 | // Already got permission. Call the lambda. 146 | ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) -> { 147 | modelManagerViewModel.downloadModel(task = task, model = model) 148 | } 149 | 150 | // Otherwise, ask for permission 151 | else -> { 152 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 153 | launcher.launch(Manifest.permission.POST_NOTIFICATIONS) 154 | } 155 | } 156 | } 157 | } 158 | 159 | fun ensureValidFileName(fileName: String): String { 160 | return fileName.replace(Regex("[^a-zA-Z0-9._-]"), "_") 161 | } 162 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/BenchmarkConfigDialog.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | import androidx.compose.runtime.Composable 22 | import com.google.ai.edge.gallery.data.Config 23 | import com.google.ai.edge.gallery.data.ConfigKey 24 | import com.google.ai.edge.gallery.data.NumberSliderConfig 25 | import com.google.ai.edge.gallery.data.ValueType 26 | import com.google.ai.edge.gallery.data.convertValueToTargetType 27 | import com.google.ai.edge.gallery.ui.common.ConfigDialog 28 | 29 | private const val DEFAULT_BENCHMARK_WARM_UP_ITERATIONS = 50f 30 | private const val DEFAULT_BENCHMARK_ITERATIONS = 200f 31 | 32 | private val BENCHMARK_CONFIGS: List<Config> = 33 | listOf( 34 | NumberSliderConfig( 35 | key = ConfigKey.WARM_UP_ITERATIONS, 36 | sliderMin = 10f, 37 | sliderMax = 200f, 38 | defaultValue = DEFAULT_BENCHMARK_WARM_UP_ITERATIONS, 39 | valueType = ValueType.INT, 40 | ), 41 | NumberSliderConfig( 42 | key = ConfigKey.BENCHMARK_ITERATIONS, 43 | sliderMin = 50f, 44 | sliderMax = 500f, 45 | defaultValue = DEFAULT_BENCHMARK_ITERATIONS, 46 | valueType = ValueType.INT, 47 | ), 48 | ) 49 | 50 | private val BENCHMARK_CONFIGS_INITIAL_VALUES = 51 | mapOf( 52 | ConfigKey.WARM_UP_ITERATIONS.label to DEFAULT_BENCHMARK_WARM_UP_ITERATIONS, 53 | ConfigKey.BENCHMARK_ITERATIONS.label to DEFAULT_BENCHMARK_ITERATIONS, 54 | ) 55 | 56 | /** 57 | * Composable function to display a configuration dialog for benchmarking a chat message. 58 | * 59 | * This function renders a configuration dialog specifically tailored for setting up benchmark 60 | * parameters. It allows users to specify warm-up and benchmark iterations before running a 61 | * benchmark test on a given chat message. 62 | */ 63 | @Composable 64 | fun BenchmarkConfigDialog( 65 | onDismissed: () -> Unit, 66 | messageToBenchmark: ChatMessage?, 67 | onBenchmarkClicked: (ChatMessage, warmUpIterations: Int, benchmarkIterations: Int) -> Unit, 68 | ) { 69 | ConfigDialog( 70 | title = "Benchmark configs", 71 | okBtnLabel = "Start", 72 | configs = BENCHMARK_CONFIGS, 73 | initialValues = BENCHMARK_CONFIGS_INITIAL_VALUES, 74 | onDismissed = onDismissed, 75 | onOk = { curConfigValues -> 76 | // Hide config dialog. 77 | onDismissed() 78 | 79 | // Start benchmark. 80 | messageToBenchmark?.let { message -> 81 | val warmUpIterations = 82 | convertValueToTargetType( 83 | value = curConfigValues.getValue(ConfigKey.WARM_UP_ITERATIONS.label), 84 | valueType = ValueType.INT, 85 | ) 86 | as Int 87 | val benchmarkIterations = 88 | convertValueToTargetType( 89 | value = curConfigValues.getValue(ConfigKey.BENCHMARK_ITERATIONS.label), 90 | valueType = ValueType.INT, 91 | ) 92 | as Int 93 | onBenchmarkClicked(message, warmUpIterations, benchmarkIterations) 94 | } 95 | }, 96 | ) 97 | } 98 | 99 | // @Preview(showBackground = true) 100 | // @Composable 101 | // fun BenchmarkConfigDialogPreview() { 102 | // GalleryTheme { 103 | // BenchmarkConfigDialog( 104 | // onDismissed = {}, 105 | // messageToBenchmark = null, 106 | // onBenchmarkClicked = { _, _, _ -> }, 107 | // ) 108 | // } 109 | // } 110 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/DataCard.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | import androidx.compose.foundation.layout.Arrangement 20 | import androidx.compose.foundation.layout.Column 21 | import androidx.compose.foundation.layout.Row 22 | import androidx.compose.foundation.layout.offset 23 | import androidx.compose.foundation.layout.padding 24 | import androidx.compose.material3.MaterialTheme 25 | import androidx.compose.material3.Text 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.draw.alpha 29 | import androidx.compose.ui.tooling.preview.Preview 30 | import androidx.compose.ui.unit.dp 31 | import com.google.ai.edge.gallery.ui.theme.GalleryTheme 32 | import com.google.ai.edge.gallery.ui.theme.bodySmallMediumNarrow 33 | import com.google.ai.edge.gallery.ui.theme.bodySmallMediumNarrowBold 34 | import com.google.ai.edge.gallery.ui.theme.labelSmallNarrow 35 | import com.google.ai.edge.gallery.ui.theme.labelSmallNarrowMedium 36 | 37 | /** 38 | * Composable function to display a data card with a label and a numeric value. 39 | * 40 | * This function renders a column containing a label and a formatted numeric value. It provides 41 | * options for highlighting the value and displaying a placeholder when the value is not available. 42 | */ 43 | @Composable 44 | fun DataCard( 45 | label: String, 46 | value: Float?, 47 | unit: String, 48 | highlight: Boolean = false, 49 | showPlaceholder: Boolean = false, 50 | ) { 51 | var strValue = "-" 52 | Column { 53 | Text(label, style = labelSmallNarrowMedium) 54 | if (showPlaceholder) { 55 | Text("-", style = bodySmallMediumNarrow) 56 | } else { 57 | strValue = if (value == null) "-" else "%.2f".format(value) 58 | if (highlight) { 59 | Text(strValue, style = bodySmallMediumNarrowBold, color = MaterialTheme.colorScheme.primary) 60 | } else { 61 | Text(strValue, style = bodySmallMediumNarrow) 62 | } 63 | } 64 | if (strValue != "-") { 65 | Text(unit, style = labelSmallNarrow, modifier = Modifier.alpha(0.5f).offset(y = (-1).dp)) 66 | } 67 | } 68 | } 69 | 70 | @Preview(showBackground = true) 71 | @Composable 72 | fun DataCardPreview() { 73 | GalleryTheme { 74 | Row(modifier = Modifier.padding(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp)) { 75 | DataCard( 76 | label = "sum", 77 | value = 123.45f, 78 | unit = "ms", 79 | highlight = true, 80 | showPlaceholder = false, 81 | ) 82 | DataCard( 83 | label = "average", 84 | value = 12.3f, 85 | unit = "ms", 86 | highlight = false, 87 | showPlaceholder = false, 88 | ) 89 | DataCard( 90 | label = "test", 91 | value = null, 92 | unit = "ms", 93 | highlight = false, 94 | showPlaceholder = false, 95 | ) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageActionButton.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | import androidx.compose.foundation.background 22 | import androidx.compose.foundation.clickable 23 | import androidx.compose.foundation.layout.Row 24 | import androidx.compose.foundation.layout.offset 25 | import androidx.compose.foundation.layout.padding 26 | import androidx.compose.foundation.layout.size 27 | import androidx.compose.foundation.shape.CircleShape 28 | import androidx.compose.material3.Icon 29 | import androidx.compose.material3.MaterialTheme 30 | import androidx.compose.material3.Text 31 | import androidx.compose.runtime.Composable 32 | import androidx.compose.ui.Alignment 33 | import androidx.compose.ui.Modifier 34 | import androidx.compose.ui.draw.alpha 35 | import androidx.compose.ui.draw.clip 36 | import androidx.compose.ui.graphics.vector.ImageVector 37 | import androidx.compose.ui.unit.dp 38 | import com.google.ai.edge.gallery.ui.theme.bodySmallNarrow 39 | 40 | /** Composable function to display an action button below a chat message. */ 41 | @Composable 42 | fun MessageActionButton( 43 | label: String, 44 | icon: ImageVector, 45 | onClick: () -> Unit, 46 | modifier: Modifier = Modifier, 47 | enabled: Boolean = true, 48 | ) { 49 | val curModifier = 50 | modifier 51 | .padding(top = 4.dp) 52 | .clip(CircleShape) 53 | .background( 54 | if (enabled) MaterialTheme.colorScheme.secondaryContainer 55 | else MaterialTheme.colorScheme.surfaceContainerHigh 56 | ) 57 | val alpha: Float = if (enabled) 1.0f else 0.3f 58 | Row( 59 | modifier = if (enabled) curModifier.clickable { onClick() } else modifier, 60 | verticalAlignment = Alignment.CenterVertically, 61 | ) { 62 | Icon( 63 | icon, 64 | contentDescription = "", 65 | modifier = Modifier.size(16.dp).offset(x = 6.dp).alpha(alpha), 66 | ) 67 | Text( 68 | label, 69 | color = MaterialTheme.colorScheme.onSecondaryContainer, 70 | style = bodySmallNarrow, 71 | modifier = Modifier.padding(start = 10.dp, end = 8.dp, top = 4.dp, bottom = 4.dp).alpha(alpha), 72 | ) 73 | } 74 | } 75 | 76 | // @Preview(showBackground = true) 77 | // @Composable 78 | // fun MessageActionButtonPreview() { 79 | // GalleryTheme { 80 | // Column { 81 | // MessageActionButton(label = "run", icon = Icons.Default.PlayArrow, onClick = {}) 82 | // MessageActionButton( 83 | // label = "run", 84 | // icon = Icons.Default.PlayArrow, 85 | // enabled = false, 86 | // onClick = {}) 87 | // } 88 | // } 89 | // } 90 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyAudioClip.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | import androidx.compose.foundation.layout.padding 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.unit.dp 23 | 24 | @Composable 25 | fun MessageBodyAudioClip(message: ChatMessageAudioClip, modifier: Modifier = Modifier) { 26 | AudioPlaybackPanel( 27 | audioData = message.audioData, 28 | sampleRate = message.sampleRate, 29 | isRecording = false, 30 | modifier = Modifier.padding(end = 16.dp), 31 | onDarkBg = true, 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyBenchmark.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | import androidx.compose.foundation.background 22 | import androidx.compose.foundation.layout.Arrangement 23 | import androidx.compose.foundation.layout.Box 24 | import androidx.compose.foundation.layout.Column 25 | import androidx.compose.foundation.layout.Row 26 | import androidx.compose.foundation.layout.fillMaxWidth 27 | import androidx.compose.foundation.layout.height 28 | import androidx.compose.foundation.layout.padding 29 | import androidx.compose.foundation.layout.width 30 | import androidx.compose.foundation.shape.RoundedCornerShape 31 | import androidx.compose.material3.MaterialTheme 32 | import androidx.compose.runtime.Composable 33 | import androidx.compose.ui.Modifier 34 | import androidx.compose.ui.draw.alpha 35 | import androidx.compose.ui.draw.clip 36 | import androidx.compose.ui.unit.dp 37 | import kotlin.math.max 38 | 39 | private const val DEFAULT_HISTOGRAM_BAR_HEIGHT = 50f 40 | 41 | /** 42 | * Composable function to display benchmark results within a chat message. 43 | * 44 | * This function renders benchmark statistics (e.g., average latency) in data cards and visualizes 45 | * the latency distribution using a histogram. 46 | */ 47 | @Composable 48 | fun MessageBodyBenchmark(message: ChatMessageBenchmarkResult) { 49 | Column( 50 | modifier = Modifier.padding(12.dp).fillMaxWidth(), 51 | verticalArrangement = Arrangement.spacedBy(8.dp), 52 | ) { 53 | // Data cards. 54 | Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { 55 | for (stat in message.orderedStats) { 56 | DataCard( 57 | label = stat.label, 58 | unit = stat.unit, 59 | value = message.statValues[stat.id], 60 | highlight = stat.id == message.highlightStat, 61 | showPlaceholder = message.isWarmingUp(), 62 | ) 63 | } 64 | } 65 | 66 | // Histogram 67 | if (message.histogram.buckets.isNotEmpty()) { 68 | Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) { 69 | for ((index, count) in message.histogram.buckets.withIndex()) { 70 | var barBgColor = MaterialTheme.colorScheme.onSurfaceVariant 71 | var alpha = 0.3f 72 | if (count != 0) { 73 | alpha = 0.5f 74 | } 75 | if (index == message.histogram.highlightBucketIndex) { 76 | barBgColor = MaterialTheme.colorScheme.primary 77 | alpha = 0.8f 78 | } 79 | // Bar container. 80 | Column( 81 | modifier = Modifier.height(DEFAULT_HISTOGRAM_BAR_HEIGHT.dp).width(4.dp), 82 | verticalArrangement = Arrangement.Bottom, 83 | ) { 84 | // Bar content. 85 | Box( 86 | modifier = 87 | Modifier.height( 88 | max( 89 | 1f, 90 | count.toFloat() / message.histogram.maxCount.toFloat() * 91 | DEFAULT_HISTOGRAM_BAR_HEIGHT, 92 | ) 93 | .dp 94 | ) 95 | .fillMaxWidth() 96 | .clip(RoundedCornerShape(20.dp, 20.dp, 0.dp, 0.dp)) 97 | .alpha(alpha) 98 | .background(barBgColor) 99 | ) 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | // @Preview(showBackground = true) 108 | // @Composable 109 | // fun MessageBodyBenchmarkPreview() { 110 | // GalleryTheme { 111 | // MessageBodyBenchmark( 112 | // message = ChatMessageBenchmarkResult( 113 | // orderedStats = listOf( 114 | // Stat(id = "stat1", label = "Stat1", unit = "ms"), 115 | // Stat(id = "stat2", label = "Stat2", unit = "ms"), 116 | // Stat(id = "stat3", label = "Stat3", unit = "ms"), 117 | // Stat(id = "stat4", label = "Stat4", unit = "ms") 118 | // ), 119 | // statValues = mutableMapOf( 120 | // "stat1" to 0.3f, 121 | // "stat2" to 0.4f, 122 | // "stat3" to 0.5f, 123 | // ), 124 | // values = listOf(), 125 | // histogram = Histogram(listOf(), 0), 126 | // warmupCurrent = 0, 127 | // warmupTotal = 0, 128 | // iterationCurrent = 0, 129 | // iterationTotal = 0, 130 | // highlightStat = "stat2" 131 | // ) 132 | // ) 133 | // } 134 | // } 135 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyBenchmarkLlm.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | import androidx.compose.foundation.layout.Arrangement 22 | import androidx.compose.foundation.layout.Column 23 | import androidx.compose.foundation.layout.Row 24 | import androidx.compose.foundation.layout.fillMaxWidth 25 | import androidx.compose.foundation.layout.padding 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.unit.dp 29 | 30 | /** 31 | * Composable function to display benchmark LLM results within a chat message. 32 | * 33 | * This function renders benchmark statistics (e.g., various token speed) in data cards 34 | */ 35 | @Composable 36 | fun MessageBodyBenchmarkLlm(message: ChatMessageBenchmarkLlmResult, modifier: Modifier = Modifier) { 37 | Column(modifier = modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { 38 | // Data cards. 39 | Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { 40 | for (stat in message.orderedStats) { 41 | DataCard(label = stat.label, unit = stat.unit, value = message.statValues[stat.id]) 42 | } 43 | } 44 | } 45 | } 46 | 47 | // @Preview(showBackground = true) 48 | // @Composable 49 | // fun MessageBodyBenchmarkLlmPreview() { 50 | // GalleryTheme { 51 | // MessageBodyBenchmarkLlm( 52 | // message = ChatMessageBenchmarkLlmResult( 53 | // orderedStats = listOf( 54 | // Stat(id = "stat1", label = "Stat1", unit = "tokens/s"), 55 | // Stat(id = "stat2", label = "Stat2", unit = "tokens/s") 56 | // ), 57 | // statValues = mutableMapOf( 58 | // "stat1" to 0.3f, 59 | // "stat2" to 0.4f, 60 | // ), 61 | // running = false, 62 | // ) 63 | // ) 64 | // } 65 | // } 66 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyClassification.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | import androidx.compose.foundation.background 22 | import androidx.compose.foundation.layout.Arrangement 23 | import androidx.compose.foundation.layout.Box 24 | import androidx.compose.foundation.layout.Column 25 | import androidx.compose.foundation.layout.Row 26 | import androidx.compose.foundation.layout.Spacer 27 | import androidx.compose.foundation.layout.fillMaxWidth 28 | import androidx.compose.foundation.layout.height 29 | import androidx.compose.foundation.layout.padding 30 | import androidx.compose.foundation.shape.CircleShape 31 | import androidx.compose.material3.MaterialTheme 32 | import androidx.compose.material3.Text 33 | import androidx.compose.runtime.Composable 34 | import androidx.compose.ui.Alignment 35 | import androidx.compose.ui.Modifier 36 | import androidx.compose.ui.draw.clip 37 | import androidx.compose.ui.text.style.TextOverflow 38 | import androidx.compose.ui.unit.dp 39 | 40 | val CLASSIFICATION_BAR_HEIGHT = 8.dp 41 | val CLASSIFICATION_BAR_MAX_WIDTH = 200.dp 42 | 43 | /** 44 | * Composable function to display classification results. 45 | * 46 | * This function renders a list of classifications, each with its label, score, and a visual score 47 | * bar. 48 | */ 49 | @Composable 50 | fun MessageBodyClassification( 51 | message: ChatMessageClassification, 52 | modifier: Modifier = Modifier, 53 | oneLineLabel: Boolean = false, 54 | ) { 55 | Column(modifier = modifier.padding(12.dp)) { 56 | for (classification in message.classifications) { 57 | Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { 58 | // Classification label. 59 | Text( 60 | classification.label, 61 | maxLines = if (oneLineLabel) 1 else Int.MAX_VALUE, 62 | overflow = TextOverflow.Ellipsis, 63 | style = MaterialTheme.typography.bodySmall, 64 | modifier = Modifier.weight(1f), 65 | ) 66 | // Classification score. 67 | Text( 68 | "%.2f".format(classification.score), 69 | style = MaterialTheme.typography.bodySmall, 70 | modifier = Modifier.align(Alignment.Bottom), 71 | ) 72 | } 73 | Spacer(modifier = Modifier.height(2.dp)) 74 | // Score bar. 75 | Box { 76 | Box( 77 | modifier = 78 | Modifier.fillMaxWidth() 79 | .height(CLASSIFICATION_BAR_HEIGHT) 80 | .clip(CircleShape) 81 | .background(MaterialTheme.colorScheme.surfaceDim) 82 | ) 83 | Box( 84 | modifier = 85 | Modifier.fillMaxWidth(classification.score) 86 | .height(CLASSIFICATION_BAR_HEIGHT) 87 | .clip(CircleShape) 88 | .background(classification.color) 89 | ) 90 | } 91 | Spacer(modifier = Modifier.height(6.dp)) 92 | } 93 | } 94 | } 95 | 96 | // @Preview(showBackground = true) 97 | // @Composable 98 | // fun MessageBodyClassificationPreview() { 99 | // GalleryTheme { 100 | // MessageBodyClassification( 101 | // message = 102 | // ChatMessageClassification( 103 | // classifications = 104 | // listOf( 105 | // Classification(label = "label1", score = 0.3f, color = Color.Red), 106 | // Classification(label = "label2", score = 0.7f, color = Color.Blue), 107 | // ), 108 | // latencyMs = 12345f, 109 | // ) 110 | // ) 111 | // } 112 | // } 113 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyImage.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | import androidx.compose.foundation.Image 20 | import androidx.compose.foundation.layout.height 21 | import androidx.compose.foundation.layout.width 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.layout.ContentScale 25 | import androidx.compose.ui.unit.dp 26 | 27 | @Composable 28 | fun MessageBodyImage(message: ChatMessageImage, modifier: Modifier = Modifier) { 29 | val bitmapWidth = message.bitmap.width 30 | val bitmapHeight = message.bitmap.height 31 | val imageWidth = 32 | if (bitmapWidth >= bitmapHeight) 200 else (200f / bitmapHeight * bitmapWidth).toInt() 33 | val imageHeight = 34 | if (bitmapHeight >= bitmapWidth) 200 else (200f / bitmapWidth * bitmapHeight).toInt() 35 | 36 | Image( 37 | bitmap = message.imageBitMap, 38 | contentDescription = "", 39 | modifier = modifier.height(imageHeight.dp).width(imageWidth.dp), 40 | contentScale = ContentScale.Fit, 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyImageWithHistory.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | import androidx.compose.foundation.Image 20 | import androidx.compose.foundation.gestures.detectHorizontalDragGestures 21 | import androidx.compose.foundation.layout.Column 22 | import androidx.compose.foundation.layout.height 23 | import androidx.compose.foundation.layout.width 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.runtime.LaunchedEffect 26 | import androidx.compose.runtime.MutableIntState 27 | import androidx.compose.runtime.MutableState 28 | import androidx.compose.runtime.getValue 29 | import androidx.compose.runtime.mutableFloatStateOf 30 | import androidx.compose.runtime.mutableIntStateOf 31 | import androidx.compose.runtime.mutableStateOf 32 | import androidx.compose.runtime.remember 33 | import androidx.compose.runtime.setValue 34 | import androidx.compose.ui.Modifier 35 | import androidx.compose.ui.input.pointer.pointerInput 36 | import androidx.compose.ui.layout.ContentScale 37 | import androidx.compose.ui.unit.dp 38 | 39 | /** 40 | * Composable function to display an image message with history, allowing users to navigate through 41 | * different versions by sliding on the image. 42 | */ 43 | @Composable 44 | fun MessageBodyImageWithHistory( 45 | message: ChatMessageImageWithHistory, 46 | imageHistoryCurIndex: MutableIntState, 47 | ) { 48 | val prevMessage: MutableState<ChatMessageImageWithHistory?> = remember { mutableStateOf(null) } 49 | 50 | LaunchedEffect(message) { 51 | imageHistoryCurIndex.intValue = message.bitmaps.size - 1 52 | prevMessage.value = message 53 | } 54 | 55 | Column { 56 | val curImage = message.bitmaps[imageHistoryCurIndex.intValue] 57 | val curImageBitmap = message.imageBitMaps[imageHistoryCurIndex.intValue] 58 | 59 | val bitmapWidth = curImage.width 60 | val bitmapHeight = curImage.height 61 | val imageWidth = 62 | if (bitmapWidth >= bitmapHeight) 200 else (200f / bitmapHeight * bitmapWidth).toInt() 63 | val imageHeight = 64 | if (bitmapHeight >= bitmapWidth) 200 else (200f / bitmapWidth * bitmapHeight).toInt() 65 | 66 | var value by remember { mutableFloatStateOf(0f) } 67 | var savedIndex by remember { mutableIntStateOf(0) } 68 | Image( 69 | bitmap = curImageBitmap, 70 | contentDescription = "", 71 | modifier = 72 | Modifier.height(imageHeight.dp).width(imageWidth.dp).pointerInput(Unit) { 73 | detectHorizontalDragGestures( 74 | onDragStart = { 75 | value = 0f 76 | savedIndex = imageHistoryCurIndex.intValue 77 | } 78 | ) { _, dragAmount -> 79 | value += (dragAmount / 20f) // Adjust sensitivity here 80 | imageHistoryCurIndex.intValue = (savedIndex + value).toInt() 81 | if (imageHistoryCurIndex.intValue < 0) { 82 | imageHistoryCurIndex.intValue = 0 83 | } else if (imageHistoryCurIndex.intValue > message.bitmaps.size - 1) { 84 | imageHistoryCurIndex.intValue = message.bitmaps.size - 1 85 | } 86 | } 87 | }, 88 | contentScale = ContentScale.Fit, 89 | ) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyInfo.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | import androidx.compose.foundation.background 22 | import androidx.compose.foundation.layout.Arrangement 23 | import androidx.compose.foundation.layout.Box 24 | import androidx.compose.foundation.layout.Row 25 | import androidx.compose.foundation.layout.fillMaxWidth 26 | import androidx.compose.foundation.layout.padding 27 | import androidx.compose.foundation.shape.RoundedCornerShape 28 | import androidx.compose.material3.MaterialTheme 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.ui.Modifier 31 | import androidx.compose.ui.draw.clip 32 | import androidx.compose.ui.unit.dp 33 | import com.google.ai.edge.gallery.ui.common.MarkdownText 34 | import com.google.ai.edge.gallery.ui.theme.customColors 35 | 36 | /** 37 | * Composable function to display informational message content within a chat. 38 | * 39 | * Supports markdown. 40 | */ 41 | @Composable 42 | fun MessageBodyInfo(message: ChatMessageInfo, smallFontSize: Boolean = true) { 43 | Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { 44 | Box( 45 | modifier = 46 | Modifier.clip(RoundedCornerShape(16.dp)) 47 | .background(MaterialTheme.customColors.agentBubbleBgColor) 48 | ) { 49 | MarkdownText( 50 | text = message.content, 51 | modifier = Modifier.padding(12.dp), 52 | smallFontSize = smallFontSize, 53 | ) 54 | } 55 | } 56 | } 57 | 58 | // @Preview(showBackground = true) 59 | // @Composable 60 | // fun MessageBodyInfoPreview() { 61 | // GalleryTheme { 62 | // Row(modifier = Modifier.padding(16.dp)) { 63 | // MessageBodyInfo(message = ChatMessageInfo(content = "This is a model")) 64 | // } 65 | // } 66 | // } 67 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyLoading.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | import androidx.compose.animation.core.Animatable 22 | import androidx.compose.animation.core.tween 23 | import androidx.compose.foundation.Image 24 | import androidx.compose.foundation.layout.Box 25 | import androidx.compose.foundation.layout.size 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.runtime.LaunchedEffect 28 | import androidx.compose.runtime.mutableIntStateOf 29 | import androidx.compose.runtime.remember 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.graphics.ColorFilter 33 | import androidx.compose.ui.graphics.graphicsLayer 34 | import androidx.compose.ui.layout.ContentScale 35 | import androidx.compose.ui.res.painterResource 36 | import androidx.compose.ui.unit.dp 37 | import com.google.ai.edge.gallery.R 38 | import com.google.ai.edge.gallery.ui.common.getTaskIconColor 39 | import kotlinx.coroutines.delay 40 | import kotlinx.coroutines.launch 41 | 42 | private val IMAGE_RESOURCES = 43 | listOf(R.drawable.pantegon, R.drawable.double_circle, R.drawable.circle, R.drawable.four_circle) 44 | 45 | private const val ANIMATION_DURATION = 300 46 | private const val ANIMATION_DURATION2 = 300 47 | private const val PAUSE_DURATION = 200 48 | private const val PAUSE_DURATION2 = 0 49 | 50 | /** Composable function to display a loading indicator. */ 51 | @Composable 52 | fun MessageBodyLoading() { 53 | val progress = remember { Animatable(0f) } 54 | val alphaAnim = remember { Animatable(0f) } 55 | val activeImageIndex = remember { mutableIntStateOf(0) } 56 | 57 | LaunchedEffect(Unit) { // Run this once 58 | while (true) { 59 | var progressJob = launch { 60 | progress.animateTo( 61 | targetValue = 1f, 62 | animationSpec = 63 | tween( 64 | durationMillis = ANIMATION_DURATION, 65 | easing = multiBounceEasing(bounces = 3, decay = 0.02f), 66 | ), 67 | ) 68 | } 69 | var alphaJob = launch { 70 | alphaAnim.animateTo( 71 | targetValue = 1f, 72 | animationSpec = tween(durationMillis = ANIMATION_DURATION / 2), 73 | ) 74 | } 75 | progressJob.join() 76 | alphaJob.join() 77 | delay((PAUSE_DURATION).toLong()) 78 | 79 | progressJob = launch { 80 | progress.animateTo( 81 | targetValue = 0f, 82 | animationSpec = 83 | tween( 84 | durationMillis = ANIMATION_DURATION2, 85 | easing = multiBounceEasing(bounces = 3, decay = 0.02f), 86 | ), 87 | ) 88 | } 89 | alphaJob = launch { 90 | alphaAnim.animateTo( 91 | targetValue = 0f, 92 | animationSpec = tween(durationMillis = ANIMATION_DURATION2 / 2), 93 | ) 94 | } 95 | 96 | progressJob.join() 97 | alphaJob.join() 98 | delay(PAUSE_DURATION2.toLong()) 99 | 100 | activeImageIndex.intValue = (activeImageIndex.intValue + 1) % IMAGE_RESOURCES.size 101 | } 102 | } 103 | 104 | Box(contentAlignment = Alignment.Center) { 105 | for ((index, imageResource) in IMAGE_RESOURCES.withIndex()) { 106 | Image( 107 | painter = painterResource(id = imageResource), 108 | contentDescription = "", 109 | contentScale = ContentScale.Fit, 110 | colorFilter = ColorFilter.tint(getTaskIconColor(index = index)), 111 | modifier = 112 | Modifier.graphicsLayer { 113 | scaleX = progress.value * 0.2f + 0.8f 114 | scaleY = progress.value * 0.2f + 0.8f 115 | rotationZ = progress.value * 100 116 | alpha = if (index != activeImageIndex.intValue) 0f else alphaAnim.value 117 | } 118 | .size(24.dp), 119 | ) 120 | } 121 | } 122 | } 123 | 124 | // @Preview(showBackground = true) 125 | // @Composable 126 | // fun MessageBodyLoadingPreview() { 127 | // GalleryTheme { Row(modifier = Modifier.padding(16.dp)) { MessageBodyLoading() } } 128 | // } 129 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyText.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 20 | // import androidx.compose.ui.tooling.preview.Preview 21 | 22 | import androidx.compose.foundation.layout.padding 23 | import androidx.compose.material3.MaterialTheme 24 | import androidx.compose.material3.Text 25 | import androidx.compose.runtime.Composable 26 | import androidx.compose.ui.Modifier 27 | import androidx.compose.ui.graphics.Color 28 | import androidx.compose.ui.text.font.FontWeight 29 | import androidx.compose.ui.unit.dp 30 | import com.google.ai.edge.gallery.ui.common.MarkdownText 31 | 32 | /** Composable function to display the text content of a ChatMessageText. */ 33 | @Composable 34 | fun MessageBodyText(message: ChatMessageText) { 35 | if (message.side == ChatSide.USER) { 36 | Text( 37 | message.content, 38 | style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Medium), 39 | color = Color.White, 40 | modifier = Modifier.padding(12.dp), 41 | ) 42 | } else if (message.side == ChatSide.AGENT) { 43 | if (message.isMarkdown) { 44 | MarkdownText(text = message.content, modifier = Modifier.padding(12.dp)) 45 | } else { 46 | Text( 47 | message.content, 48 | style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Medium), 49 | color = MaterialTheme.colorScheme.onSurface, 50 | modifier = Modifier.padding(12.dp), 51 | ) 52 | } 53 | } 54 | } 55 | 56 | // @Preview(showBackground = true) 57 | // @Composable 58 | // fun MessageBodyTextPreview() { 59 | // GalleryTheme { 60 | // Column { 61 | // Row(modifier = Modifier.padding(16.dp).background(MaterialTheme.colorScheme.primary)) { 62 | // MessageBodyText(ChatMessageText(content = "Hello world", side = ChatSide.USER)) 63 | // } 64 | // Row( 65 | // modifier = Modifier.padding(16.dp).background(MaterialTheme.colorScheme.surfaceContainer) 66 | // ) { 67 | // MessageBodyText(ChatMessageText(content = "yes hello world", side = ChatSide.AGENT)) 68 | // } 69 | // } 70 | // } 71 | // } 72 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBodyWarning.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | import androidx.compose.foundation.background 22 | import androidx.compose.foundation.layout.Arrangement 23 | import androidx.compose.foundation.layout.Box 24 | import androidx.compose.foundation.layout.Row 25 | import androidx.compose.foundation.layout.fillMaxWidth 26 | import androidx.compose.foundation.layout.padding 27 | import androidx.compose.foundation.shape.RoundedCornerShape 28 | import androidx.compose.material3.MaterialTheme 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.ui.Modifier 31 | import androidx.compose.ui.draw.clip 32 | import androidx.compose.ui.unit.dp 33 | import com.google.ai.edge.gallery.ui.common.MarkdownText 34 | 35 | /** 36 | * Composable function to display warning message content within a chat. 37 | * 38 | * Supports markdown. 39 | */ 40 | @Composable 41 | fun MessageBodyWarning(message: ChatMessageWarning) { 42 | Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { 43 | Box( 44 | modifier = 45 | Modifier.clip(RoundedCornerShape(16.dp)) 46 | .background(MaterialTheme.colorScheme.tertiaryContainer) 47 | ) { 48 | MarkdownText( 49 | text = message.content, 50 | modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp), 51 | smallFontSize = true, 52 | ) 53 | } 54 | } 55 | } 56 | 57 | // @Preview(showBackground = true) 58 | // @Composable 59 | // fun MessageBodyWarningPreview() { 60 | // GalleryTheme { 61 | // Row(modifier = Modifier.padding(16.dp)) { 62 | // MessageBodyWarning(message = ChatMessageWarning(content = "This is a warning")) 63 | // } 64 | // } 65 | // } 66 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageBubbleShape.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | import androidx.compose.ui.geometry.CornerRadius 20 | import androidx.compose.ui.geometry.RoundRect 21 | import androidx.compose.ui.geometry.Size 22 | import androidx.compose.ui.graphics.Outline 23 | import androidx.compose.ui.graphics.Path 24 | import androidx.compose.ui.graphics.Shape 25 | import androidx.compose.ui.unit.Density 26 | import androidx.compose.ui.unit.Dp 27 | import androidx.compose.ui.unit.LayoutDirection 28 | 29 | /** 30 | * Custom Shape for creating message bubble outlines with configurable corner radii. 31 | * 32 | * This class defines a custom Shape that generates a rounded rectangle outline, suitable for 33 | * message bubbles. It allows specifying a uniform corner radius for most corners, but also provides 34 | * the option to have a hard (non-rounded) corner on either the left or right side. 35 | */ 36 | class MessageBubbleShape( 37 | private val radius: Dp, 38 | private val hardCornerAtLeftOrRight: Boolean = false, 39 | ) : Shape { 40 | override fun createOutline( 41 | size: Size, 42 | layoutDirection: LayoutDirection, 43 | density: Density, 44 | ): Outline { 45 | val radiusPx = with(density) { radius.toPx() } 46 | val path = 47 | Path().apply { 48 | addRoundRect( 49 | RoundRect( 50 | left = 0f, 51 | top = 0f, 52 | right = size.width, 53 | bottom = size.height, 54 | topLeftCornerRadius = 55 | if (hardCornerAtLeftOrRight) CornerRadius(0f, 0f) 56 | else CornerRadius(radiusPx, radiusPx), 57 | topRightCornerRadius = 58 | if (hardCornerAtLeftOrRight) CornerRadius(radiusPx, radiusPx) 59 | else CornerRadius(0f, 0f), // No rounding here 60 | bottomLeftCornerRadius = CornerRadius(radiusPx, radiusPx), 61 | bottomRightCornerRadius = CornerRadius(radiusPx, radiusPx), 62 | ) 63 | ) 64 | } 65 | return Outline.Generic(path) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/MessageLatency.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | 22 | import androidx.compose.material3.MaterialTheme 23 | import androidx.compose.material3.Text 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.draw.alpha 27 | import com.google.ai.edge.gallery.ui.common.humanReadableDuration 28 | 29 | /** Composable function to display the latency of a chat message, if available. */ 30 | @Composable 31 | fun LatencyText(message: ChatMessage) { 32 | if (message.latencyMs >= 0) { 33 | Text( 34 | message.latencyMs.humanReadableDuration(), 35 | modifier = Modifier.alpha(0.5f), 36 | style = MaterialTheme.typography.labelSmall, 37 | ) 38 | } 39 | } 40 | 41 | // @Preview(showBackground = true) 42 | // @Composable 43 | // fun LatencyTextPreview() { 44 | // GalleryTheme { 45 | // Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) 46 | // { 47 | // for (latencyMs in listOf(123f, 1234f, 123456f, 7234567f)) { 48 | // LatencyText( 49 | // message = 50 | // ChatMessage(latencyMs = latencyMs, type = ChatMessageType.TEXT, side = 51 | // ChatSide.AGENT) 52 | // ) 53 | // } 54 | // } 55 | // } 56 | // } 57 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ModelDownloadStatusInfoPanel.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | import androidx.compose.animation.AnimatedVisibility 20 | import androidx.compose.animation.fadeIn 21 | import androidx.compose.animation.fadeOut 22 | import androidx.compose.animation.scaleIn 23 | import androidx.compose.animation.scaleOut 24 | import androidx.compose.foundation.layout.Arrangement 25 | import androidx.compose.foundation.layout.Box 26 | import androidx.compose.foundation.layout.Column 27 | import androidx.compose.foundation.layout.fillMaxSize 28 | import androidx.compose.runtime.Composable 29 | import androidx.compose.runtime.LaunchedEffect 30 | import androidx.compose.runtime.collectAsState 31 | import androidx.compose.runtime.getValue 32 | import androidx.compose.runtime.mutableStateOf 33 | import androidx.compose.runtime.remember 34 | import androidx.compose.runtime.setValue 35 | import androidx.compose.ui.Alignment 36 | import androidx.compose.ui.Modifier 37 | import com.google.ai.edge.gallery.data.Model 38 | import com.google.ai.edge.gallery.data.ModelDownloadStatusType 39 | import com.google.ai.edge.gallery.data.Task 40 | import com.google.ai.edge.gallery.ui.common.DownloadAndTryButton 41 | import com.google.ai.edge.gallery.ui.modelmanager.ModelManagerViewModel 42 | import kotlinx.coroutines.delay 43 | 44 | @Composable 45 | fun ModelDownloadStatusInfoPanel( 46 | model: Model, 47 | task: Task, 48 | modelManagerViewModel: ModelManagerViewModel, 49 | ) { 50 | val modelManagerUiState by modelManagerViewModel.uiState.collectAsState() 51 | 52 | // Manages the conditional display of UI elements (download model button and downloading 53 | // animation) based on the corresponding download status. 54 | // 55 | // It uses delayed visibility ensuring they are shown only after a short delay if their 56 | // respective conditions remain true. This prevents UI flickering and provides a smoother 57 | // user experience. 58 | val curStatus = modelManagerUiState.modelDownloadStatus[model.name] 59 | var shouldShowDownloadingAnimation by remember { mutableStateOf(false) } 60 | var downloadingAnimationConditionMet by remember { mutableStateOf(false) } 61 | var shouldShowDownloadModelButton by remember { mutableStateOf(false) } 62 | var downloadModelButtonConditionMet by remember { mutableStateOf(false) } 63 | 64 | downloadingAnimationConditionMet = 65 | curStatus?.status == ModelDownloadStatusType.IN_PROGRESS || 66 | curStatus?.status == ModelDownloadStatusType.PARTIALLY_DOWNLOADED || 67 | curStatus?.status == ModelDownloadStatusType.UNZIPPING 68 | downloadModelButtonConditionMet = 69 | curStatus?.status == ModelDownloadStatusType.FAILED || 70 | curStatus?.status == ModelDownloadStatusType.NOT_DOWNLOADED 71 | 72 | LaunchedEffect(downloadingAnimationConditionMet) { 73 | if (downloadingAnimationConditionMet) { 74 | delay(100) 75 | shouldShowDownloadingAnimation = true 76 | } else { 77 | shouldShowDownloadingAnimation = false 78 | } 79 | } 80 | 81 | LaunchedEffect(downloadModelButtonConditionMet) { 82 | if (downloadModelButtonConditionMet) { 83 | delay(700) 84 | shouldShowDownloadModelButton = true 85 | } else { 86 | shouldShowDownloadModelButton = false 87 | } 88 | } 89 | 90 | AnimatedVisibility( 91 | visible = shouldShowDownloadingAnimation, 92 | enter = scaleIn(initialScale = 0.9f) + fadeIn(), 93 | exit = scaleOut(targetScale = 0.9f) + fadeOut(), 94 | ) { 95 | Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { 96 | ModelDownloadingAnimation( 97 | model = model, 98 | task = task, 99 | modelManagerViewModel = modelManagerViewModel, 100 | ) 101 | } 102 | } 103 | 104 | AnimatedVisibility(visible = shouldShowDownloadModelButton, enter = fadeIn(), exit = fadeOut()) { 105 | Column( 106 | modifier = Modifier.fillMaxSize(), 107 | verticalArrangement = Arrangement.Center, 108 | horizontalAlignment = Alignment.CenterHorizontally, 109 | ) { 110 | DownloadAndTryButton( 111 | task = task, 112 | model = model, 113 | enabled = true, 114 | needToDownloadFirst = true, 115 | modelManagerViewModel = modelManagerViewModel, 116 | onClicked = {}, 117 | ) 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ModelInitializationStatus.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | 22 | import androidx.compose.foundation.background 23 | import androidx.compose.foundation.layout.Arrangement 24 | import androidx.compose.foundation.layout.Box 25 | import androidx.compose.foundation.layout.Row 26 | import androidx.compose.foundation.layout.Spacer 27 | import androidx.compose.foundation.layout.fillMaxWidth 28 | import androidx.compose.foundation.layout.padding 29 | import androidx.compose.foundation.layout.size 30 | import androidx.compose.foundation.layout.width 31 | import androidx.compose.foundation.shape.CircleShape 32 | import androidx.compose.material3.CircularProgressIndicator 33 | import androidx.compose.material3.MaterialTheme 34 | import androidx.compose.material3.Text 35 | import androidx.compose.runtime.Composable 36 | import androidx.compose.ui.Alignment 37 | import androidx.compose.ui.Modifier 38 | import androidx.compose.ui.draw.clip 39 | import androidx.compose.ui.res.stringResource 40 | import androidx.compose.ui.unit.dp 41 | import com.google.ai.edge.gallery.R 42 | 43 | /** 44 | * Composable function to display a visual indicator for model initialization status. 45 | * 46 | * This function renders a row containing a circular progress indicator and a message indicating 47 | * that the model is currently initializing. It provides a visual cue to the user that the model is 48 | * in a loading state. 49 | */ 50 | @Composable 51 | fun ModelInitializationStatusChip() { 52 | Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { 53 | Box( 54 | modifier = 55 | Modifier.padding(8.dp) 56 | .clip(CircleShape) 57 | .background(MaterialTheme.colorScheme.secondaryContainer) 58 | ) { 59 | Row( 60 | modifier = Modifier.padding(top = 4.dp, bottom = 4.dp, start = 8.dp, end = 8.dp), 61 | horizontalArrangement = Arrangement.Center, 62 | verticalAlignment = Alignment.CenterVertically, 63 | ) { 64 | // Circular progress indicator. 65 | CircularProgressIndicator( 66 | modifier = Modifier.size(14.dp), 67 | strokeWidth = 2.dp, 68 | color = MaterialTheme.colorScheme.onSecondaryContainer, 69 | ) 70 | 71 | Spacer(modifier = Modifier.width(8.dp)) 72 | 73 | // Text message. 74 | Text( 75 | stringResource(R.string.model_is_initializing_msg), 76 | style = MaterialTheme.typography.bodySmall, 77 | color = MaterialTheme.colorScheme.onSecondaryContainer, 78 | ) 79 | } 80 | } 81 | } 82 | } 83 | 84 | // @Preview(showBackground = true) 85 | // @Composable 86 | // fun ModelInitializationStatusPreview() { 87 | // GalleryTheme { ModelInitializationStatusChip() } 88 | // } 89 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ModelNotDownloaded.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | 22 | import androidx.compose.foundation.layout.Arrangement 23 | import androidx.compose.foundation.layout.Column 24 | import androidx.compose.foundation.layout.fillMaxSize 25 | import androidx.compose.material3.Button 26 | import androidx.compose.material3.Text 27 | import androidx.compose.runtime.Composable 28 | import androidx.compose.ui.Alignment 29 | import androidx.compose.ui.Modifier 30 | 31 | /** 32 | * Composable function to display a button to download model if the model has not been downloaded. 33 | */ 34 | @Composable 35 | fun ModelNotDownloaded(modifier: Modifier = Modifier, onClicked: () -> Unit) { 36 | Column( 37 | modifier = modifier.fillMaxSize(), 38 | verticalArrangement = Arrangement.Center, 39 | horizontalAlignment = Alignment.CenterHorizontally, 40 | ) { 41 | Button(onClick = onClicked) { Text("Download & Try it", maxLines = 1) } 42 | } 43 | } 44 | 45 | // @Preview(showBackground = true) 46 | // @Composable 47 | // fun Preview() { 48 | // GalleryTheme { ModelNotDownloaded(onClicked = {}) } 49 | // } 50 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ZoomableBox.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.chat 18 | 19 | import androidx.compose.foundation.gestures.detectTransformGestures 20 | import androidx.compose.foundation.layout.Box 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.runtime.getValue 23 | import androidx.compose.runtime.mutableFloatStateOf 24 | import androidx.compose.runtime.mutableStateOf 25 | import androidx.compose.runtime.remember 26 | import androidx.compose.runtime.setValue 27 | import androidx.compose.ui.Alignment 28 | import androidx.compose.ui.Modifier 29 | import androidx.compose.ui.input.pointer.pointerInput 30 | import androidx.compose.ui.layout.onSizeChanged 31 | import androidx.compose.ui.unit.IntSize 32 | 33 | @Composable 34 | fun ZoomableBox( 35 | modifier: Modifier = Modifier, 36 | minScale: Float = 1f, 37 | maxScale: Float = 5f, 38 | content: @Composable ZoomableBoxScope.() -> Unit, 39 | ) { 40 | var scale by remember { mutableFloatStateOf(1f) } 41 | var offsetX by remember { mutableFloatStateOf(0f) } 42 | var offsetY by remember { mutableFloatStateOf(0f) } 43 | var size by remember { mutableStateOf(IntSize.Zero) } 44 | Box( 45 | modifier = 46 | modifier 47 | .onSizeChanged { size = it } 48 | .pointerInput(Unit) { 49 | detectTransformGestures { _, pan, zoom, _ -> 50 | scale = maxOf(minScale, minOf(scale * zoom, maxScale)) 51 | val maxX = (size.width * (scale - 1)) / 2 52 | val minX = -maxX 53 | offsetX = maxOf(minX, minOf(maxX, offsetX + pan.x)) 54 | val maxY = (size.height * (scale - 1)) / 2 55 | val minY = -maxY 56 | offsetY = maxOf(minY, minOf(maxY, offsetY + pan.y)) 57 | } 58 | }, 59 | contentAlignment = Alignment.TopEnd, 60 | ) { 61 | val scope = ZoomableBoxScopeImpl(scale, offsetX, offsetY) 62 | scope.content() 63 | } 64 | } 65 | 66 | interface ZoomableBoxScope { 67 | val scale: Float 68 | val offsetX: Float 69 | val offsetY: Float 70 | } 71 | 72 | private data class ZoomableBoxScopeImpl( 73 | override val scale: Float, 74 | override val offsetX: Float, 75 | override val offsetY: Float, 76 | ) : ZoomableBoxScope 77 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/modelitem/ConfirmDeleteModelDialog.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.modelitem 18 | 19 | import androidx.compose.material3.AlertDialog 20 | import androidx.compose.material3.Button 21 | import androidx.compose.material3.Text 22 | import androidx.compose.material3.TextButton 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.ui.res.stringResource 25 | import com.google.ai.edge.gallery.R 26 | import com.google.ai.edge.gallery.data.Model 27 | 28 | /** Composable function to display a confirmation dialog for deleting a model. */ 29 | @Composable 30 | fun ConfirmDeleteModelDialog(model: Model, onConfirm: () -> Unit, onDismiss: () -> Unit) { 31 | AlertDialog( 32 | onDismissRequest = onDismiss, 33 | title = { Text(stringResource(R.string.confirm_delete_model_dialog_title)) }, 34 | text = { 35 | Text(stringResource(R.string.confirm_delete_model_dialog_content).format(model.name)) 36 | }, 37 | confirmButton = { Button(onClick = onConfirm) { Text(stringResource(R.string.ok)) } }, 38 | dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } }, 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/modelitem/ModelItemActionButton.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.modelitem 18 | 19 | import android.content.Context 20 | import androidx.compose.foundation.layout.Row 21 | import androidx.compose.foundation.layout.padding 22 | import androidx.compose.foundation.layout.size 23 | import androidx.compose.material.icons.Icons 24 | import androidx.compose.material.icons.rounded.Cancel 25 | import androidx.compose.material.icons.rounded.Delete 26 | import androidx.compose.material.icons.rounded.FileDownload 27 | import androidx.compose.material3.CircularProgressIndicator 28 | import androidx.compose.material3.Icon 29 | import androidx.compose.material3.IconButton 30 | import androidx.compose.runtime.Composable 31 | import androidx.compose.runtime.getValue 32 | import androidx.compose.runtime.mutableStateOf 33 | import androidx.compose.runtime.remember 34 | import androidx.compose.runtime.setValue 35 | import androidx.compose.ui.Alignment 36 | import androidx.compose.ui.Modifier 37 | import androidx.compose.ui.unit.dp 38 | import com.google.ai.edge.gallery.data.Model 39 | import com.google.ai.edge.gallery.data.ModelDownloadStatus 40 | import com.google.ai.edge.gallery.data.ModelDownloadStatusType 41 | import com.google.ai.edge.gallery.data.Task 42 | import com.google.ai.edge.gallery.ui.common.getTaskIconColor 43 | import com.google.ai.edge.gallery.ui.modelmanager.ModelManagerViewModel 44 | 45 | /** 46 | * Composable function to display action buttons for a model item, based on its download status. 47 | * 48 | * This function renders the appropriate action button (download, delete, cancel) based on the 49 | * provided ModelDownloadStatus. It also handles notification permission requests for downloading 50 | * and displays a confirmation dialog for deleting models. 51 | */ 52 | @Composable 53 | fun ModelItemActionButton( 54 | context: Context, 55 | model: Model, 56 | task: Task, 57 | modelManagerViewModel: ModelManagerViewModel, 58 | downloadStatus: ModelDownloadStatus?, 59 | onDownloadClicked: (Model) -> Unit, 60 | modifier: Modifier = Modifier, 61 | showDeleteButton: Boolean = true, 62 | showDownloadButton: Boolean = true, 63 | ) { 64 | var showConfirmDeleteDialog by remember { mutableStateOf(false) } 65 | 66 | Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) { 67 | when (downloadStatus?.status) { 68 | // Button to start the download. 69 | ModelDownloadStatusType.NOT_DOWNLOADED, 70 | ModelDownloadStatusType.FAILED -> 71 | if (showDownloadButton) { 72 | IconButton(onClick = { onDownloadClicked(model) }) { 73 | Icon(Icons.Rounded.FileDownload, contentDescription = "", tint = getTaskIconColor(task)) 74 | } 75 | } 76 | 77 | // Button to delete the download. 78 | ModelDownloadStatusType.SUCCEEDED -> { 79 | if (showDeleteButton) { 80 | IconButton(onClick = { showConfirmDeleteDialog = true }) { 81 | Icon(Icons.Rounded.Delete, contentDescription = "", tint = getTaskIconColor(task)) 82 | } 83 | } 84 | } 85 | 86 | // Show spinner when the model is partially downloaded because it might some time for 87 | // background task to be started by Android. 88 | ModelDownloadStatusType.PARTIALLY_DOWNLOADED -> { 89 | CircularProgressIndicator(modifier = Modifier.padding(end = 12.dp).size(24.dp)) 90 | } 91 | 92 | // Button to cancel the download when it is in progress. 93 | ModelDownloadStatusType.IN_PROGRESS, 94 | ModelDownloadStatusType.UNZIPPING -> 95 | IconButton( 96 | onClick = { modelManagerViewModel.cancelDownloadModel(task = task, model = model) } 97 | ) { 98 | Icon(Icons.Rounded.Cancel, contentDescription = "", tint = getTaskIconColor(task)) 99 | } 100 | 101 | else -> {} 102 | } 103 | } 104 | 105 | if (showConfirmDeleteDialog) { 106 | ConfirmDeleteModelDialog( 107 | model = model, 108 | onConfirm = { 109 | modelManagerViewModel.deleteModel(task = task, model = model) 110 | showConfirmDeleteDialog = false 111 | }, 112 | onDismiss = { showConfirmDeleteDialog = false }, 113 | ) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/modelitem/StatusIcon.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.common.modelitem 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | import androidx.compose.foundation.layout.Arrangement 22 | import androidx.compose.foundation.layout.Row 23 | import androidx.compose.foundation.layout.size 24 | import androidx.compose.material.icons.Icons 25 | import androidx.compose.material.icons.automirrored.outlined.HelpOutline 26 | import androidx.compose.material.icons.filled.DownloadForOffline 27 | import androidx.compose.material.icons.rounded.Downloading 28 | import androidx.compose.material.icons.rounded.Error 29 | import androidx.compose.material3.Icon 30 | import androidx.compose.material3.MaterialTheme 31 | import androidx.compose.runtime.Composable 32 | import androidx.compose.ui.Alignment 33 | import androidx.compose.ui.Modifier 34 | import androidx.compose.ui.graphics.Color 35 | import androidx.compose.ui.unit.dp 36 | import com.google.ai.edge.gallery.data.ModelDownloadStatus 37 | import com.google.ai.edge.gallery.data.ModelDownloadStatusType 38 | import com.google.ai.edge.gallery.ui.theme.customColors 39 | 40 | private val SIZE = 18.dp 41 | 42 | /** Composable function to display an icon representing the download status of a model. */ 43 | @Composable 44 | fun StatusIcon(downloadStatus: ModelDownloadStatus?, modifier: Modifier = Modifier) { 45 | Row( 46 | verticalAlignment = Alignment.CenterVertically, 47 | horizontalArrangement = Arrangement.Center, 48 | modifier = modifier, 49 | ) { 50 | when (downloadStatus?.status) { 51 | ModelDownloadStatusType.NOT_DOWNLOADED -> 52 | Icon( 53 | Icons.AutoMirrored.Outlined.HelpOutline, 54 | tint = Color(0xFFCCCCCC), 55 | contentDescription = "", 56 | modifier = Modifier.size(SIZE), 57 | ) 58 | 59 | ModelDownloadStatusType.SUCCEEDED -> { 60 | Icon( 61 | Icons.Filled.DownloadForOffline, 62 | tint = MaterialTheme.customColors.successColor, 63 | contentDescription = "", 64 | modifier = Modifier.size(SIZE), 65 | ) 66 | } 67 | 68 | ModelDownloadStatusType.FAILED -> 69 | Icon( 70 | Icons.Rounded.Error, 71 | tint = Color(0xFFAA0000), 72 | contentDescription = "", 73 | modifier = Modifier.size(SIZE), 74 | ) 75 | 76 | ModelDownloadStatusType.IN_PROGRESS -> 77 | Icon(Icons.Rounded.Downloading, contentDescription = "", modifier = Modifier.size(SIZE)) 78 | 79 | else -> {} 80 | } 81 | } 82 | } 83 | 84 | // @Preview(showBackground = true) 85 | // @Composable 86 | // fun StatusIconPreview() { 87 | // GalleryTheme { 88 | // Column { 89 | // for (downloadStatus in 90 | // listOf( 91 | // ModelDownloadStatus(status = ModelDownloadStatusType.NOT_DOWNLOADED), 92 | // ModelDownloadStatus(status = ModelDownloadStatusType.IN_PROGRESS), 93 | // ModelDownloadStatus(status = ModelDownloadStatusType.SUCCEEDED), 94 | // ModelDownloadStatus(status = ModelDownloadStatusType.FAILED), 95 | // ModelDownloadStatus(status = ModelDownloadStatusType.UNZIPPING), 96 | // ModelDownloadStatus(status = ModelDownloadStatusType.PARTIALLY_DOWNLOADED), 97 | // )) { 98 | // StatusIcon(downloadStatus = downloadStatus) 99 | // } 100 | // } 101 | // } 102 | // } 103 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/icon/Deploy.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.icon 18 | 19 | import androidx.compose.ui.graphics.Color 20 | import androidx.compose.ui.graphics.PathFillType 21 | import androidx.compose.ui.graphics.SolidColor 22 | import androidx.compose.ui.graphics.StrokeCap 23 | import androidx.compose.ui.graphics.StrokeJoin 24 | import androidx.compose.ui.graphics.vector.ImageVector 25 | import androidx.compose.ui.graphics.vector.path 26 | import androidx.compose.ui.unit.dp 27 | 28 | val Deployed_code: ImageVector 29 | get() { 30 | if (internal_Deployed_code != null) { 31 | return internal_Deployed_code!! 32 | } 33 | internal_Deployed_code = 34 | ImageVector.Builder( 35 | name = "Deployed_code", 36 | defaultWidth = 24.dp, 37 | defaultHeight = 24.dp, 38 | viewportWidth = 960f, 39 | viewportHeight = 960f, 40 | ) 41 | .apply { 42 | path( 43 | fill = SolidColor(Color.Black), 44 | fillAlpha = 1.0f, 45 | stroke = null, 46 | strokeAlpha = 1.0f, 47 | strokeLineWidth = 1.0f, 48 | strokeLineCap = StrokeCap.Butt, 49 | strokeLineJoin = StrokeJoin.Miter, 50 | strokeLineMiter = 1.0f, 51 | pathFillType = PathFillType.NonZero, 52 | ) { 53 | moveTo(440f, 777f) 54 | verticalLineToRelative(-274f) 55 | lineTo(200f, 364f) 56 | verticalLineToRelative(274f) 57 | close() 58 | moveToRelative(80f, 0f) 59 | lineToRelative(240f, -139f) 60 | verticalLineToRelative(-274f) 61 | lineTo(520f, 503f) 62 | close() 63 | moveToRelative(-40f, -343f) 64 | lineToRelative(237f, -137f) 65 | lineToRelative(-237f, -137f) 66 | lineToRelative(-237f, 137f) 67 | close() 68 | moveTo(160f, 708f) 69 | quadToRelative(-19f, -11f, -29.5f, -29f) 70 | reflectiveQuadTo(120f, 639f) 71 | verticalLineToRelative(-318f) 72 | quadToRelative(0f, -22f, 10.5f, -40f) 73 | reflectiveQuadToRelative(29.5f, -29f) 74 | lineToRelative(280f, -161f) 75 | quadToRelative(19f, -11f, 40f, -11f) 76 | reflectiveQuadToRelative(40f, 11f) 77 | lineToRelative(280f, 161f) 78 | quadToRelative(19f, 11f, 29.5f, 29f) 79 | reflectiveQuadToRelative(10.5f, 40f) 80 | verticalLineToRelative(318f) 81 | quadToRelative(0f, 22f, -10.5f, 40f) 82 | reflectiveQuadTo(800f, 708f) 83 | lineTo(520f, 869f) 84 | quadToRelative(-19f, 11f, -40f, 11f) 85 | reflectiveQuadToRelative(-40f, -11f) 86 | close() 87 | moveToRelative(320f, -228f) 88 | } 89 | } 90 | .build() 91 | return internal_Deployed_code!! 92 | } 93 | 94 | private var internal_Deployed_code: ImageVector? = null 95 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/SingleSelectButton.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.llmsingleturn 18 | 19 | import androidx.compose.foundation.background 20 | import androidx.compose.foundation.clickable 21 | import androidx.compose.foundation.layout.Arrangement 22 | import androidx.compose.foundation.layout.Box 23 | import androidx.compose.foundation.layout.Row 24 | import androidx.compose.foundation.layout.padding 25 | import androidx.compose.foundation.shape.RoundedCornerShape 26 | import androidx.compose.material.icons.Icons 27 | import androidx.compose.material.icons.rounded.ArrowDropDown 28 | import androidx.compose.material3.DropdownMenu 29 | import androidx.compose.material3.DropdownMenuItem 30 | import androidx.compose.material3.Icon 31 | import androidx.compose.material3.MaterialTheme 32 | import androidx.compose.material3.Text 33 | import androidx.compose.runtime.Composable 34 | import androidx.compose.runtime.LaunchedEffect 35 | import androidx.compose.runtime.getValue 36 | import androidx.compose.runtime.mutableStateOf 37 | import androidx.compose.runtime.remember 38 | import androidx.compose.runtime.setValue 39 | import androidx.compose.ui.Alignment 40 | import androidx.compose.ui.Modifier 41 | import androidx.compose.ui.draw.clip 42 | import androidx.compose.ui.unit.dp 43 | 44 | @Composable 45 | fun SingleSelectButton( 46 | config: PromptTemplateSingleSelectInputEditor, 47 | onSelected: (String) -> Unit, 48 | ) { 49 | var showMenu by remember { mutableStateOf(false) } 50 | var selectedOption by remember { mutableStateOf(config.defaultOption) } 51 | 52 | LaunchedEffect(config) { selectedOption = config.defaultOption } 53 | 54 | Box { 55 | Row( 56 | verticalAlignment = Alignment.CenterVertically, 57 | horizontalArrangement = Arrangement.spacedBy(2.dp), 58 | modifier = 59 | Modifier.clip(RoundedCornerShape(8.dp)) 60 | .background(MaterialTheme.colorScheme.secondaryContainer) 61 | .clickable { showMenu = true } 62 | .padding(vertical = 4.dp, horizontal = 6.dp) 63 | .padding(start = 8.dp), 64 | ) { 65 | Text("${config.label}: $selectedOption", style = MaterialTheme.typography.labelLarge) 66 | Icon(Icons.Rounded.ArrowDropDown, contentDescription = "") 67 | } 68 | 69 | DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { 70 | // Options 71 | for (option in config.options) { 72 | DropdownMenuItem( 73 | text = { Text(option) }, 74 | onClick = { 75 | selectedOption = option 76 | showMenu = false 77 | onSelected(option) 78 | }, 79 | ) 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmsingleturn/VerticalSplitView.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.llmsingleturn 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 21 | import androidx.compose.foundation.background 22 | import androidx.compose.foundation.gestures.detectDragGestures 23 | import androidx.compose.foundation.layout.Box 24 | import androidx.compose.foundation.layout.Column 25 | import androidx.compose.foundation.layout.fillMaxSize 26 | import androidx.compose.foundation.layout.fillMaxWidth 27 | import androidx.compose.foundation.layout.height 28 | import androidx.compose.foundation.layout.width 29 | import androidx.compose.foundation.shape.CircleShape 30 | import androidx.compose.material3.MaterialTheme 31 | import androidx.compose.runtime.Composable 32 | import androidx.compose.runtime.getValue 33 | import androidx.compose.runtime.mutableFloatStateOf 34 | import androidx.compose.runtime.mutableStateOf 35 | import androidx.compose.runtime.remember 36 | import androidx.compose.runtime.setValue 37 | import androidx.compose.ui.Alignment 38 | import androidx.compose.ui.Modifier 39 | import androidx.compose.ui.draw.clip 40 | import androidx.compose.ui.input.pointer.pointerInput 41 | import androidx.compose.ui.layout.onGloballyPositioned 42 | import androidx.compose.ui.platform.LocalDensity 43 | import androidx.compose.ui.unit.Dp 44 | import androidx.compose.ui.unit.dp 45 | import com.google.ai.edge.gallery.ui.theme.customColors 46 | 47 | @Composable 48 | fun VerticalSplitView( 49 | topView: @Composable () -> Unit, 50 | bottomView: @Composable () -> Unit, 51 | modifier: Modifier = Modifier, 52 | initialRatio: Float = 0.5f, 53 | minTopHeight: Dp = 250.dp, 54 | minBottomHeight: Dp = 200.dp, 55 | handleThickness: Dp = 20.dp, 56 | ) { 57 | var splitRatio by remember { mutableFloatStateOf(initialRatio) } 58 | var columnHeightPx by remember { mutableFloatStateOf(0f) } 59 | var columnHeightDp by remember { mutableStateOf(0.dp) } 60 | val localDensity = LocalDensity.current 61 | 62 | Column( 63 | modifier = 64 | modifier.fillMaxSize().onGloballyPositioned { coordinates -> 65 | // Set column height using the LayoutCoordinates 66 | columnHeightPx = coordinates.size.height.toFloat() 67 | columnHeightDp = with(localDensity) { coordinates.size.height.toDp() } 68 | } 69 | ) { 70 | Box(modifier = Modifier.fillMaxWidth().weight(splitRatio)) { topView() } 71 | 72 | Box( 73 | modifier = 74 | Modifier.fillMaxWidth() 75 | .height(handleThickness) 76 | .background(MaterialTheme.customColors.agentBubbleBgColor) 77 | .pointerInput(Unit) { 78 | detectDragGestures { change, dragAmount -> 79 | val newTopHeightPx = columnHeightPx * splitRatio + dragAmount.y 80 | var newTopHeightDp = with(localDensity) { newTopHeightPx.toDp() } 81 | if (newTopHeightDp < minTopHeight) { 82 | newTopHeightDp = minTopHeight 83 | } 84 | if (columnHeightDp - newTopHeightDp < minBottomHeight) { 85 | newTopHeightDp = columnHeightDp - minBottomHeight 86 | } 87 | splitRatio = newTopHeightDp / columnHeightDp 88 | change.consume() 89 | } 90 | }, 91 | contentAlignment = Alignment.Center, 92 | ) { 93 | Box( 94 | modifier = 95 | Modifier.width(32.dp) 96 | .height(4.dp) 97 | .clip(CircleShape) 98 | .background(MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f)) 99 | ) 100 | } 101 | 102 | Box(modifier = Modifier.fillMaxWidth().weight(1f - splitRatio)) { bottomView() } 103 | } 104 | } 105 | 106 | // @Preview(showBackground = true) 107 | // @Composable 108 | // fun VerticalSplitViewPreview() { 109 | // GalleryTheme { VerticalSplitView(topView = { Text("top") }, bottomView = { Text("bottom") }) } 110 | // } 111 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/ModelManager.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.modelmanager 18 | 19 | // import androidx.compose.ui.tooling.preview.Preview 20 | // import com.google.ai.edge.gallery.ui.preview.PreviewModelManagerViewModel 21 | // import com.google.ai.edge.gallery.ui.preview.TASK_TEST1 22 | // import com.google.ai.edge.gallery.ui.theme.GalleryTheme 23 | 24 | import androidx.activity.compose.BackHandler 25 | import androidx.compose.foundation.layout.fillMaxSize 26 | import androidx.compose.material3.ExperimentalMaterial3Api 27 | import androidx.compose.material3.Scaffold 28 | import androidx.compose.runtime.Composable 29 | import androidx.compose.runtime.LaunchedEffect 30 | import androidx.compose.runtime.derivedStateOf 31 | import androidx.compose.runtime.getValue 32 | import androidx.compose.runtime.remember 33 | import androidx.compose.ui.Modifier 34 | import com.google.ai.edge.gallery.GalleryTopAppBar 35 | import com.google.ai.edge.gallery.data.AppBarAction 36 | import com.google.ai.edge.gallery.data.AppBarActionType 37 | import com.google.ai.edge.gallery.data.Model 38 | import com.google.ai.edge.gallery.data.Task 39 | 40 | /** A screen to manage models. */ 41 | @OptIn(ExperimentalMaterial3Api::class) 42 | @Composable 43 | fun ModelManager( 44 | task: Task, 45 | viewModel: ModelManagerViewModel, 46 | navigateUp: () -> Unit, 47 | onModelClicked: (Model) -> Unit, 48 | modifier: Modifier = Modifier, 49 | ) { 50 | // Set title based on the task. 51 | var title = "${task.type.label} model" 52 | if (task.models.size != 1) { 53 | title += "s" 54 | } 55 | // Model count. 56 | val modelCount by remember { 57 | derivedStateOf { 58 | val trigger = task.updateTrigger.value 59 | if (trigger >= 0) { 60 | task.models.size 61 | } else { 62 | -1 63 | } 64 | } 65 | } 66 | 67 | // Navigate up when there are no models left. 68 | LaunchedEffect(modelCount) { 69 | if (modelCount == 0) { 70 | navigateUp() 71 | } 72 | } 73 | 74 | // Handle system's edge swipe. 75 | BackHandler { navigateUp() } 76 | 77 | Scaffold( 78 | modifier = modifier, 79 | topBar = { 80 | GalleryTopAppBar( 81 | title = title, 82 | leftAction = AppBarAction(actionType = AppBarActionType.NAVIGATE_UP, actionFn = navigateUp), 83 | ) 84 | }, 85 | ) { innerPadding -> 86 | ModelList( 87 | task = task, 88 | modelManagerViewModel = viewModel, 89 | contentPadding = innerPadding, 90 | onModelClicked = onModelClicked, 91 | modifier = Modifier.fillMaxSize(), 92 | ) 93 | } 94 | } 95 | 96 | // @Preview 97 | // @Composable 98 | // fun ModelManagerPreview() { 99 | // val context = LocalContext.current 100 | 101 | // GalleryTheme { 102 | // ModelManager( 103 | // viewModel = PreviewModelManagerViewModel(context = context), 104 | // onModelClicked = {}, 105 | // task = TASK_TEST1, 106 | // navigateUp = {}, 107 | // ) 108 | // } 109 | // } 110 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/theme/Color.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.theme 18 | 19 | import androidx.compose.ui.graphics.Color 20 | 21 | // val primaryLight = Color(0xFF32628D) 22 | val primaryLight = Color(0xFF1F1F1F) 23 | val onPrimaryLight = Color(0xFFFFFFFF) 24 | val primaryContainerLight = Color(0xFFD0E4FF) 25 | val onPrimaryContainerLight = Color(0xFF144A74) 26 | val secondaryLight = Color(0xFF526070) 27 | val onSecondaryLight = Color(0xFFFFFFFF) 28 | // val secondaryContainerLight = Color(0xFFD6E4F7) 29 | val secondaryContainerLight = Color(0xFFC2E7FF) 30 | val onSecondaryContainerLight = Color(0xFF3B4857) 31 | val tertiaryLight = Color(0xFF775A0B) 32 | val onTertiaryLight = Color(0xFFFFFFFF) 33 | val tertiaryContainerLight = Color(0xFFFFDF9B) 34 | val onTertiaryContainerLight = Color(0xFF5B4300) 35 | val errorLight = Color(0xFF904A43) 36 | val onErrorLight = Color(0xFFFFFFFF) 37 | val errorContainerLight = Color(0xFFFFDAD5) 38 | val onErrorContainerLight = Color(0xFF73342D) 39 | val backgroundLight = Color(0xFFF8F9FF) 40 | val onBackgroundLight = Color(0xFF191C20) 41 | val surfaceLight = Color(0xFFF8F9FF) 42 | val onSurfaceLight = Color(0xFF191C20) 43 | val surfaceVariantLight = Color(0xFFDEE3EB) 44 | val onSurfaceVariantLight = Color(0xFF42474E) 45 | val outlineLight = Color(0xFF73777F) 46 | val outlineVariantLight = Color(0xFFC2C7CF) 47 | val scrimLight = Color(0xFF000000) 48 | val inverseSurfaceLight = Color(0xFF2D3135) 49 | val inverseOnSurfaceLight = Color(0xFFEFF1F6) 50 | val inversePrimaryLight = Color(0xFF9DCAFC) 51 | val surfaceDimLight = Color(0xFFD8DAE0) 52 | val surfaceBrightLight = Color(0xFFF8F9FF) 53 | val surfaceContainerLowestLight = Color(0xFFFFFFFF) 54 | val surfaceContainerLowLight = Color(0xFFF2F3F9) 55 | val surfaceContainerLight = Color(0xFFECEEF4) 56 | val surfaceContainerHighLight = Color(0xFFE6E8EE) 57 | val surfaceContainerHighestLight = Color(0xFFE0E2E8) 58 | 59 | val primaryDark = Color(0xFF9DCAFC) 60 | val onPrimaryDark = Color(0xFF003355) 61 | val primaryContainerDark = Color(0xFF144A74) 62 | val onPrimaryContainerDark = Color(0xFFD0E4FF) 63 | val secondaryDark = Color(0xFFBAC8DA) 64 | val onSecondaryDark = Color(0xFF243240) 65 | val secondaryContainerDark = Color(0xFF3B4857) 66 | val onSecondaryContainerDark = Color(0xFFD6E4F7) 67 | val tertiaryDark = Color(0xFFE8C26C) 68 | val onTertiaryDark = Color(0xFF3F2E00) 69 | val tertiaryContainerDark = Color(0xFF5B4300) 70 | val onTertiaryContainerDark = Color(0xFFFFDF9B) 71 | val errorDark = Color(0xFFFFB4AB) 72 | val onErrorDark = Color(0xFF561E19) 73 | val errorContainerDark = Color(0xFF73342D) 74 | val onErrorContainerDark = Color(0xFFFFDAD5) 75 | val backgroundDark = Color(0xFF101418) 76 | val onBackgroundDark = Color(0xFFE0E2E8) 77 | val surfaceDark = Color(0xFF101418) 78 | val onSurfaceDark = Color(0xFFE0E2E8) 79 | val surfaceVariantDark = Color(0xFF42474E) 80 | val onSurfaceVariantDark = Color(0xFFC2C7CF) 81 | val outlineDark = Color(0xFF8C9199) 82 | val outlineVariantDark = Color(0xFF42474E) 83 | val scrimDark = Color(0xFF000000) 84 | val inverseSurfaceDark = Color(0xFFE0E2E8) 85 | val inverseOnSurfaceDark = Color(0xFF2D3135) 86 | val inversePrimaryDark = Color(0xFF32628D) 87 | val surfaceDimDark = Color(0xFF101418) 88 | val surfaceBrightDark = Color(0xFF36393E) 89 | val surfaceContainerLowestDark = Color(0xFF0B0E12) 90 | val surfaceContainerLowDark = Color(0xFF191C20) 91 | val surfaceContainerDark = Color(0xFF1D2024) 92 | val surfaceContainerHighDark = Color(0xFF272A2F) 93 | val surfaceContainerHighestDark = Color(0xFF32353A) 94 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/theme/ThemeSettings.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.theme 18 | 19 | import androidx.compose.runtime.mutableStateOf 20 | import com.google.ai.edge.gallery.proto.Theme 21 | 22 | object ThemeSettings { 23 | val themeOverride = mutableStateOf<Theme>(Theme.THEME_AUTO) 24 | } 25 | -------------------------------------------------------------------------------- /Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/theme/Type.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.ui.theme 18 | 19 | import androidx.compose.material3.Typography 20 | import androidx.compose.ui.text.font.Font 21 | import androidx.compose.ui.text.font.FontFamily 22 | import androidx.compose.ui.text.font.FontWeight 23 | import androidx.compose.ui.unit.sp 24 | import com.google.ai.edge.gallery.R 25 | 26 | val nunitoFontFamily = 27 | FontFamily( 28 | Font(R.font.nunito_regular, FontWeight.Normal), 29 | Font(R.font.nunito_extralight, FontWeight.ExtraLight), 30 | Font(R.font.nunito_light, FontWeight.Light), 31 | Font(R.font.nunito_medium, FontWeight.Medium), 32 | Font(R.font.nunito_semibold, FontWeight.SemiBold), 33 | Font(R.font.nunito_bold, FontWeight.Bold), 34 | Font(R.font.nunito_extrabold, FontWeight.ExtraBold), 35 | Font(R.font.nunito_black, FontWeight.Black), 36 | ) 37 | 38 | val baseline = Typography() 39 | 40 | val AppTypography = 41 | Typography( 42 | displayLarge = baseline.displayLarge.copy(fontFamily = nunitoFontFamily), 43 | displayMedium = baseline.displayMedium.copy(fontFamily = nunitoFontFamily), 44 | displaySmall = baseline.displaySmall.copy(fontFamily = nunitoFontFamily), 45 | headlineLarge = baseline.headlineLarge.copy(fontFamily = nunitoFontFamily), 46 | headlineMedium = baseline.headlineMedium.copy(fontFamily = nunitoFontFamily), 47 | headlineSmall = baseline.headlineSmall.copy(fontFamily = nunitoFontFamily), 48 | titleLarge = baseline.titleLarge.copy(fontFamily = nunitoFontFamily), 49 | titleMedium = baseline.titleMedium.copy(fontFamily = nunitoFontFamily), 50 | titleSmall = baseline.titleSmall.copy(fontFamily = nunitoFontFamily), 51 | bodyLarge = baseline.bodyLarge.copy(fontFamily = nunitoFontFamily), 52 | bodyMedium = baseline.bodyMedium.copy(fontFamily = nunitoFontFamily), 53 | bodySmall = baseline.bodySmall.copy(fontFamily = nunitoFontFamily), 54 | labelLarge = baseline.labelLarge.copy(fontFamily = nunitoFontFamily), 55 | labelMedium = baseline.labelMedium.copy(fontFamily = nunitoFontFamily), 56 | labelSmall = baseline.labelSmall.copy(fontFamily = nunitoFontFamily), 57 | ) 58 | 59 | val titleMediumNarrow = 60 | baseline.titleMedium.copy(fontFamily = nunitoFontFamily, letterSpacing = 0.0.sp) 61 | 62 | val titleSmaller = 63 | baseline.titleSmall.copy( 64 | fontFamily = nunitoFontFamily, 65 | fontSize = 12.sp, 66 | fontWeight = FontWeight.Bold, 67 | ) 68 | 69 | val labelSmallNarrow = 70 | baseline.labelSmall.copy(fontFamily = nunitoFontFamily, letterSpacing = 0.0.sp) 71 | 72 | val labelSmallNarrowMedium = 73 | baseline.labelSmall.copy( 74 | fontFamily = nunitoFontFamily, 75 | fontWeight = FontWeight.Medium, 76 | letterSpacing = 0.0.sp, 77 | ) 78 | 79 | val bodySmallNarrow = baseline.bodySmall.copy(fontFamily = nunitoFontFamily, letterSpacing = 0.0.sp) 80 | 81 | val bodySmallSemiBold = 82 | baseline.bodySmall.copy(fontFamily = nunitoFontFamily, fontWeight = FontWeight.SemiBold) 83 | 84 | val bodyMediumSemiBold = 85 | baseline.bodyMedium.copy(fontFamily = nunitoFontFamily, fontWeight = FontWeight.SemiBold) 86 | 87 | val bodySmallMediumNarrow = 88 | baseline.bodySmall.copy(fontFamily = nunitoFontFamily, letterSpacing = 0.0.sp, fontSize = 14.sp) 89 | 90 | val bodySmallMediumNarrowBold = 91 | baseline.bodySmall.copy( 92 | fontFamily = nunitoFontFamily, 93 | letterSpacing = 0.0.sp, 94 | fontSize = 14.sp, 95 | fontWeight = FontWeight.Bold, 96 | ) 97 | -------------------------------------------------------------------------------- /Android/src/app/src/main/proto/settings.proto: -------------------------------------------------------------------------------- 1 | /* Copyright 2025 Google LLC 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | ==============================================================================*/ 15 | 16 | syntax = "proto3"; 17 | 18 | package com.google.ai.edge.gallery.proto; 19 | 20 | option java_package = "com.google.ai.edge.gallery.proto"; 21 | option java_multiple_files = true; 22 | 23 | enum Theme { 24 | THEME_UNSPECIFIED = 0; 25 | 26 | // Force to use light theme. 27 | THEME_LIGHT = 1; 28 | 29 | // Force to use dark theme. 30 | THEME_DARK = 2; 31 | 32 | // Use the system them setting on user's phone. 33 | THEME_AUTO = 3; 34 | } 35 | 36 | message AccessTokenData { 37 | string access_token = 1; 38 | string refresh_token = 2; 39 | int64 expires_at_ms = 3; 40 | } 41 | 42 | message ImportedModel { 43 | string file_name = 1; 44 | int64 file_size = 2; 45 | 46 | oneof config { 47 | LlmConfig llm_config = 3; 48 | } 49 | } 50 | 51 | message LlmConfig { 52 | repeated string compatible_accelerators = 1; 53 | int32 default_max_tokens = 2; 54 | int32 default_topk = 3; 55 | float default_topp = 4; 56 | float default_temperature = 5; 57 | bool support_image = 6; 58 | bool support_audio = 7; 59 | } 60 | 61 | message Settings { 62 | Theme theme = 1; 63 | AccessTokenData access_token_data = 2; 64 | repeated string text_input_history = 3; 65 | repeated ImportedModel imported_model = 4; 66 | bool is_tos_accepted = 5; 67 | } 68 | -------------------------------------------------------------------------------- /Android/src/app/src/main/res/drawable/chat_spark.xml: -------------------------------------------------------------------------------- 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 18 | android:width="38dp" 19 | android:height="38dp" 20 | android:viewportWidth="38" 21 | android:viewportHeight="38"> 22 | <group> 23 | <path 24 | android:fillColor="#FF1967D2" 25 | android:pathData="M9.32 21.5h15.3v-1.7c-0.44-0.1-0.87-0.24-1.28-0.4-0.41-0.17-0.82-0.37-1.2-0.57H9.32v2.67Zm0-4.89h10.1c-0.34-0.39-0.65-0.8-0.93-1.24-0.29-0.44-0.54-0.92-0.74-1.44H9.32v2.68Zm0-4.9h7.53V9.09H9.32v2.64Zm17.86 6.42c0-2.3-0.81-4.26-2.44-5.87-1.6-1.63-3.56-2.44-5.87-2.44 2.3 0 4.26-0.8 5.87-2.41 1.63-1.63 2.44-3.6 2.44-5.9 0 2.3 0.8 4.27 2.41 5.9 1.63 1.6 3.6 2.4 5.9 2.4-2.3 0-4.27 0.82-5.9 2.45-1.6 1.6-2.4 3.56-2.4 5.87Zm-21.4 1.04V25v2.49V5.5v2.1 2.22 7.34 2.99-0.24-0.74ZM3.12 33.9V5.5c0-0.72 0.26-1.34 0.77-1.86 0.55-0.54 1.18-0.81 1.9-0.81h13.83c-0.36 0.38-0.7 0.8-1 1.24-0.32 0.44-0.58 0.92-0.78 1.43H5.79V27.5L8.16 25H31.5v-5.83c0.51-0.2 1-0.46 1.43-0.77 0.44-0.31 0.86-0.65 1.24-1.01V25c0 0.72-0.27 1.36-0.81 1.9-0.52 0.52-1.14 0.78-1.86 0.78H9.32l-6.21 6.21Z"/> 26 | </group> 27 | </vector> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/drawable/circle.xml: -------------------------------------------------------------------------------- 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" 18 | android:viewportWidth="63" 19 | android:viewportHeight="63" 20 | android:width="63dp" 21 | android:height="63dp"> 22 | <path 23 | android:pathData="M63 31.5C63 14.103 48.897 0 31.5 0C14.103 0 0 14.103 0 31.5C0 48.897 14.103 63 31.5 63C48.897 63 63 48.897 63 31.5Z" 24 | android:fillColor="#FFFFFF" 25 | android:fillAlpha="1" /> 26 | </vector> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/drawable/double_circle.xml: -------------------------------------------------------------------------------- 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" 18 | android:viewportWidth="62" 19 | android:viewportHeight="62" 20 | android:width="62dp" 21 | android:height="62dp"> 22 | <path 23 | android:pathData="M7.78628 54.2125C-2.59542 43.8291 -2.59542 26.9941 7.78628 16.6107L16.6114 7.78754C26.9931 -2.59584 43.8287 -2.59584 54.2137 7.78754C64.5954 18.1709 64.5954 35.0059 54.2137 45.3893L45.3886 54.2125C35.0069 64.5958 18.1713 64.5958 7.78628 54.2125Z" 24 | android:fillColor="#FFFFFF" 25 | android:fillAlpha="1" /> 26 | </vector> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/drawable/four_circle.xml: -------------------------------------------------------------------------------- 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" 18 | android:viewportWidth="59" 19 | android:viewportHeight="59" 20 | android:width="59dp" 21 | android:height="59dp"> 22 | <path 23 | android:pathData="M1.3077 21.3393C-4.19593 8.66644 8.66702 -4.19605 21.3396 1.30784L23.4364 2.21791C27.3032 3.89848 31.6966 3.89848 35.5666 2.21791L37.6602 1.30784C50.3359 -4.19605 63.1957 8.66644 57.6921 21.3393L56.7817 23.4344C55.1036 27.3037 55.1036 31.6964 56.7817 35.5654L57.6921 37.6609C63.1957 50.3337 50.3359 63.1962 37.6602 57.692L35.5666 56.782C31.6966 55.1017 27.3032 55.1017 23.4364 56.782L21.3396 57.692C8.66702 63.1962 -4.19593 50.3337 1.3077 37.6609L2.21809 35.5654C3.89932 31.6964 3.89932 27.3037 2.21809 23.4344L1.3077 21.3393Z" 24 | android:fillColor="#FFFFFF" 25 | android:fillAlpha="1" /> 26 | </vector> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 18 | xmlns:aapt="http://schemas.android.com/aapt" 19 | android:width="108dp" 20 | android:height="108dp" 21 | android:viewportWidth="108" 22 | android:viewportHeight="108"> 23 | <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> 24 | <aapt:attr name="android:fillColor"> 25 | <gradient 26 | android:endX="85.84757" 27 | android:endY="92.4963" 28 | android:startX="42.9492" 29 | android:startY="49.59793" 30 | android:type="linear"> 31 | <item 32 | android:color="#44000000" 33 | android:offset="0.0" /> 34 | <item 35 | android:color="#00000000" 36 | android:offset="1.0" /> 37 | </gradient> 38 | </aapt:attr> 39 | </path> 40 | <path 41 | android:fillColor="#FFFFFF" 42 | android:fillType="nonZero" 43 | android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" 44 | android:strokeWidth="1" 45 | android:strokeColor="#00000000" /> 46 | </vector> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/drawable/image_spark.xml: -------------------------------------------------------------------------------- 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 18 | android:width="38dp" 19 | android:height="38dp" 20 | android:viewportWidth="38" 21 | android:viewportHeight="38"> 22 | <group> 23 | <path 24 | android:fillColor="#FF34A853" 25 | android:pathData="M9.24 26.2h10.1l5-5-2.05-2.69-4.9 6.45-3.56-4.81-4.59 6.05Zm-1.9 6.14c-0.73 0-1.36-0.26-1.9-0.78-0.52-0.54-0.78-1.17-0.78-1.9V16.53h1.28 1.4v13.13h11.73v2.68H7.34Zm22.6-16.7V7.06H16.82v-1.4-1.28h13.12c0.73 0 1.35 0.27 1.87 0.81 0.54 0.52 0.81 1.14 0.81 1.87v8.58h-1.36-1.32Zm-8.2 16.7v-4.78l8.59-8.54c0.23-0.23 0.5-0.4 0.78-0.5 0.28-0.1 0.57-0.16 0.85-0.16 0.31 0 0.61 0.06 0.9 0.2 0.28 0.1 0.54 0.27 0.77 0.5l1.44 1.44c0.23 0.23 0.4 0.49 0.5 0.77 0.1 0.29 0.16 0.57 0.16 0.86 0 0.28-0.07 0.58-0.2 0.89-0.1 0.28-0.27 0.54-0.5 0.78l-8.5 8.54h-4.78ZM33.4 22.13l-1.44-1.44 1.44 1.44ZM24.08 30h1.47l4.7-4.74-1.43-1.43-4.74 4.7V30Zm5.47-5.48l-0.73-0.7 1.43 1.44-0.7-0.74Zm-20.97-9.4c0-1.88-0.66-3.49-1.98-4.81C5.28 8.97 3.68 8.3 1.8 8.3c1.89 0 3.5-0.66 4.81-1.98C7.92 5 8.58 3.39 8.58 1.5c0 1.9 0.66 3.5 1.98 4.82 1.35 1.32 2.97 1.98 4.86 1.98-1.9 0-3.51 0.67-4.86 2.02-1.32 1.32-1.98 2.93-1.98 4.82Z"/> 26 | </group> 27 | </vector> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/drawable/logo.xml: -------------------------------------------------------------------------------- 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 18 | android:width="196dp" 19 | android:height="199dp" 20 | android:viewportWidth="196" 21 | android:viewportHeight="199"> 22 | <path 23 | android:fillColor="#FFF6B704" 24 | android:pathData="M37.46 2.58c4.46-3.44 10.66-3.44 15.12 0l11.2 8.64c0.87 0.66 1.82 1.21 2.82 1.63l13.06 5.43c5.2 2.15 8.3 7.55 7.56 13.14L85.37 45.5c-0.14 1.08-0.14 2.18 0 3.26l1.85 14.07c0.74 5.59-2.36 10.98-7.56 13.15L66.6 81.39c-1 0.42-1.95 0.96-2.81 1.63l-11.2 8.64c-4.47 3.44-10.67 3.44-15.13 0l-11.2-8.64c-0.87-0.67-1.82-1.21-2.82-1.63l-13.06-5.42c-5.2-2.17-8.3-7.56-7.56-13.15l1.85-14.07c0.14-1.08 0.14-2.18 0-3.26L2.82 31.42c-0.74-5.6 2.37-10.99 7.56-13.14l13.06-5.43c1-0.42 1.95-0.97 2.81-1.63l11.21-8.64Z"/> 25 | <path 26 | android:fillColor="#FFE54335" 27 | android:pathData="M118.52 77.54c-14.84-14.85-14.84-38.93 0-53.78l12.6-12.62c14.85-14.85 38.9-14.85 53.75 0 14.84 14.85 14.84 38.93 0 53.78l-12.61 12.62c-14.84 14.85-38.9 14.85-53.74 0Z"/> 28 | <path 29 | android:fillColor="#FF34A353" 30 | android:pathData="M90.04 153.95c0-24.89-20.15-45.06-45.02-45.06C20.16 108.9 0 129.06 0 153.95 0 178.83 20.16 199 45.02 199c24.87 0 45.02-20.17 45.02-45.05Z"/> 31 | <path 32 | android:fillColor="#FF4280EF" 33 | android:pathData="M109.25 145.13c-7.86-18.12 10.52-36.52 28.64-28.64l3 1.3c5.52 2.4 11.8 2.4 17.33 0l3-1.3c18.1-7.88 36.49 10.52 28.62 28.64l-1.3 3c-2.4 5.54-2.4 11.82 0 17.35l1.3 3c7.87 18.13-10.51 36.52-28.63 28.65l-2.99-1.3c-5.53-2.4-11.81-2.4-17.34 0l-3 1.3c-18.1 7.87-36.5-10.53-28.63-28.65l1.3-3c2.41-5.53 2.41-11.81 0-17.35l-1.3-3Z"/> 34 | </vector> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/drawable/pantegon.xml: -------------------------------------------------------------------------------- 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" 18 | android:viewportWidth="59" 19 | android:viewportHeight="63" 20 | android:width="59dp" 21 | android:height="63dp"> 22 | <path 23 | android:pathData="M24.2282 1.72389C27.3373 -0.574629 31.6627 -0.574629 34.7718 1.72389L42.5861 7.50056C43.188 7.94527 43.8479 8.31221 44.5486 8.59132L53.6533 12.2177C57.2755 13.6601 59.4383 17.266 58.9251 21.007L57.6351 30.41C57.5359 31.1342 57.5359 31.8674 57.6351 32.5916L58.9251 41.9953C59.4383 45.7342 57.2755 49.3412 53.6533 50.7853L44.5486 54.4096C43.8479 54.6908 43.188 55.0553 42.5861 55.4997L34.7718 61.2764C31.6627 63.5745 27.3373 63.5745 24.2282 61.2764L16.4139 55.4997C15.812 55.0553 15.1521 54.6908 14.4513 54.4096L5.34671 50.7853C1.72447 49.3412 -0.438263 45.7342 0.0749229 41.9953L1.36492 32.5916C1.46446 31.8674 1.46446 31.1342 1.36492 30.41L0.0749229 21.007C-0.438263 17.266 1.72447 13.6601 5.34671 12.2177L14.4513 8.59132C15.1521 8.31221 15.812 7.94527 16.4139 7.50056L24.2282 1.72389Z" 24 | android:fillColor="#FFFFFF" 25 | android:fillAlpha="1" /> 26 | </vector> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/drawable/text_spark.xml: -------------------------------------------------------------------------------- 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 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 18 | android:width="37dp" 19 | android:height="36dp" 20 | android:viewportWidth="37" 21 | android:viewportHeight="36"> 22 | <group> 23 | <path 24 | android:fillColor="#FFE37400" 25 | android:pathData="M19.8 33.25l-6.34-6.37 1.87-1.86 4.46 4.47 9.13-9.1 1.86 1.91L19.8 33.25ZM2.7 24.1l7.7-20.31h3.03l7.68 20.3h-2.99l-1.98-5.35H7.6L5.66 24.1H2.7Zm5.8-7.89h6.75L11.95 7h-0.08L8.5 16.2Zm18.28 1.29c0-2.3-0.81-4.26-2.44-5.87-1.6-1.63-3.56-2.44-5.87-2.44 2.3 0 4.26-0.8 5.87-2.41 1.63-1.63 2.44-3.6 2.44-5.9 0 2.3 0.8 4.27 2.41 5.9 1.63 1.6 3.6 2.4 5.9 2.4-2.3 0-4.27 0.82-5.9 2.45-1.6 1.6-2.4 3.56-2.4 5.87Z"/> 26 | </group> 27 | </vector> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/font/nunito_black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_black.ttf -------------------------------------------------------------------------------- /Android/src/app/src/main/res/font/nunito_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_bold.ttf -------------------------------------------------------------------------------- /Android/src/app/src/main/res/font/nunito_extrabold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_extrabold.ttf -------------------------------------------------------------------------------- /Android/src/app/src/main/res/font/nunito_extralight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_extralight.ttf -------------------------------------------------------------------------------- /Android/src/app/src/main/res/font/nunito_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_light.ttf -------------------------------------------------------------------------------- /Android/src/app/src/main/res/font/nunito_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_medium.ttf -------------------------------------------------------------------------------- /Android/src/app/src/main/res/font/nunito_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_regular.ttf -------------------------------------------------------------------------------- /Android/src/app/src/main/res/font/nunito_semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/font/nunito_semibold.ttf -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | Copyright 2025 Google LLC 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | --> 17 | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> 18 | <background android:drawable="@mipmap/ic_launcher_background"/> 19 | <foreground android:drawable="@mipmap/ic_launcher_foreground"/> 20 | <monochrome android:drawable="@mipmap/ic_launcher_monochrome"/> 21 | </adaptive-icon> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-hdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-hdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-mdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-mdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /Android/src/app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | Copyright 2025 Google LLC 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | --> 17 | 18 | <resources xmlns:tools="http://schemas.android.com/tools"> 19 | <style name="Theme.Gallery.OssLicenses" parent="Theme.AppCompat"> 20 | <item name="android:windowOptOutEdgeToEdgeEnforcement" tools:targetApi="35">true</item> 21 | </style> 22 | </resources> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | Copyright 2025 Google LLC 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | --> 17 | 18 | 19 | <resources> 20 | <dimen name="model_selector_height">54dp</dimen> 21 | <dimen name="chat_bubble_corner_radius">24dp</dimen> 22 | </resources> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | Copyright 2025 Google LLC 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | --> 17 | 18 | <resources> 19 | <color name="ic_launcher_background">#ffffff</color> 20 | </resources> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 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 | <resources> 18 | <!-- TODO(jingjin): Add translation support when i18n is ready. --> 19 | <string name="app_name" translatable="false">Google AI Edge Gallery</string> 20 | <string name="model_manager" translatable="false">Model Manager</string> 21 | <string name="downloaded_size" translatable="false">%1$s downloaded</string> 22 | <string name="cancel" translatable="false">Cancel</string> 23 | <string name="ok" translatable="false">OK</string> 24 | <string name="close" translatable="false">Close</string> 25 | <string name="confirm_delete_model_dialog_title" translatable="false">Delete download</string> 26 | <string name="confirm_delete_model_dialog_content" translatable="false">Are you sure you want to delete the downloaded model \"%s\"?</string> 27 | <string name="notification_title_success" translatable="false">Model download succeeded</string> 28 | <string name="notification_content_success" translatable="false">Model \"%s\" has been downloaded</string> 29 | <string name="notification_title_fail" translatable="false">Model download failed</string> 30 | <string name="notification_content_fail" translatable="false">Failed to download model \"%s\"</string> 31 | <string name="chat_textinput_placeholder" translatable="false">Type message…</string> 32 | <string name="chat_you" translatable="false">You</string> 33 | <string name="chat_llm_agent_name" translatable="false">LLM</string> 34 | <string name="chat_generic_agent_name" translatable="false">Model</string> 35 | <string name="chat_generic_result_name" translatable="false">Result</string> 36 | <string name="model_not_downloaded_msg" translatable="false">Model not downloaded yet</string> 37 | <string name="model_is_initializing_msg" translatable="false">Initializing model…</string> 38 | <string name="settings_dialog_tos_title" translatable="false">Terms of services</string> 39 | <string name="text_input_placeholder_text_classification" translatable="false">Type movie review to classify…</string> 40 | <string name="text_image_generation_text_field_placeholder" translatable="false">Type prompt…</string> 41 | <string name="text_input_placeholder_llm_chat" translatable="false">Type prompt…</string> 42 | <string name="tos_sheet_title_app_name" translatable="false">Google AI Edge Gallery App</string> 43 | <string name="tos_sheet_title_tos" translatable="false">Terms of Service</string> 44 | <string name="tos_sheet_view_full_tos" translatable="false">View the full Terms of Service</string> 45 | <string name="tos_sheet_view_accept_button_label" translatable="false">Accept and Continue</string> 46 | <string name="run_again" translatable="false">Run again</string> 47 | <string name="benchmark" translatable="false">Run benchmark</string> 48 | <string name="warming_up" translatable="false">warming up…</string> 49 | <string name="running" translatable="false">running</string> 50 | </resources> 51 | -------------------------------------------------------------------------------- /Android/src/app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | Copyright 2025 Google LLC 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | --> 17 | 18 | <resources xmlns:tools="http://schemas.android.com/tools"> 19 | <style name="Theme.Gallery" parent="android:Theme.Material.Light.NoActionBar" /> 20 | <style name="Theme.Gallery.SplashScreen" parent="Theme.SplashScreen"> 21 | <item name="windowSplashScreenBackground">#2A2A34</item> 22 | <item name="postSplashScreenTheme">@style/Theme.Gallery</item> 23 | </style> 24 | <style name="Theme.Gallery.OssLicenses" parent="Theme.AppCompat.Light.DarkActionBar"> 25 | <item name="android:windowOptOutEdgeToEdgeEnforcement" tools:targetApi="35">true</item> 26 | </style> 27 | </resources> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?><!-- 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 | Sample backup rules file; uncomment and customize as necessary. 17 | See https://developer.android.com/guide/topics/data/autobackup 18 | for details. 19 | Note: This file is ignored for devices older that API 31 20 | See https://developer.android.com/about/versions/12/backup-restore 21 | --> 22 | <full-backup-content> 23 | <!-- 24 | <include domain="sharedpref" path="."/> 25 | <exclude domain="sharedpref" path="device.xml"/> 26 | --> 27 | </full-backup-content> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?><!-- 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 | Sample data extraction rules file; uncomment and customize as necessary. 17 | See https://developer.android.com/about/versions/12/backup-restore#xml-changes 18 | for details. 19 | --> 20 | <data-extraction-rules> 21 | <cloud-backup> 22 | <!-- TODO: Use <include> and <exclude> to control what is backed up. 23 | <include .../> 24 | <exclude .../> 25 | --> 26 | </cloud-backup> 27 | <!-- 28 | <device-transfer> 29 | <include .../> 30 | <exclude .../> 31 | </device-transfer> 32 | --> 33 | </data-extraction-rules> -------------------------------------------------------------------------------- /Android/src/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | Copyright 2025 Google LLC 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | --> 17 | 18 | <paths> 19 | <cache-path 20 | name="cache_pictures" 21 | path="/" /> 22 | </paths> -------------------------------------------------------------------------------- /Android/src/app/src/test/java/com/google/ai/edge/gallery/data/ModelAllowlistTest.kt: -------------------------------------------------------------------------------- 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.ai.edge.gallery.data 18 | 19 | import org.junit.Assert.assertEquals 20 | import org.junit.Assert.assertFalse 21 | import org.junit.Assert.assertTrue 22 | import org.junit.Test 23 | import org.junit.runner.RunWith 24 | import org.junit.runners.JUnit4 25 | 26 | @RunWith(JUnit4::class) 27 | class ModelAllowlistTest { 28 | @Test 29 | fun toModel_success() { 30 | val modelName = "test_model" 31 | val modelId = "test_model_id" 32 | val modelFile = "test_model_file" 33 | val description = "test description" 34 | val sizeInBytes = 100L 35 | val version = "20250623" 36 | val topK = 10 37 | val topP = 0.5f 38 | val temperature = 0.1f 39 | val maxTokens = 1000 40 | val accelerators = "gpu,cpu" 41 | val taskTypes = listOf("llm_chat", "ask_image") 42 | val estimatedPeakMemoryInBytes = 300L 43 | 44 | val allowedModel = 45 | AllowedModel( 46 | name = modelName, 47 | modelId = modelId, 48 | modelFile = modelFile, 49 | description = description, 50 | sizeInBytes = sizeInBytes, 51 | version = version, 52 | defaultConfig = 53 | DefaultConfig( 54 | topK = topK, 55 | topP = topP, 56 | temperature = temperature, 57 | maxTokens = maxTokens, 58 | accelerators = accelerators, 59 | ), 60 | taskTypes = taskTypes, 61 | llmSupportImage = true, 62 | llmSupportAudio = true, 63 | estimatedPeakMemoryInBytes = estimatedPeakMemoryInBytes, 64 | ) 65 | val model = allowedModel.toModel() 66 | 67 | // Check that basic fields are set correctly. 68 | assertEquals(model.name, modelName) 69 | assertEquals(model.version, version) 70 | assertEquals(model.info, description) 71 | assertEquals( 72 | model.url, 73 | "https://huggingface.co/test_model_id/resolve/main/test_model_file?download=true", 74 | ) 75 | assertEquals(model.sizeInBytes, sizeInBytes) 76 | assertEquals(model.estimatedPeakMemoryInBytes, estimatedPeakMemoryInBytes) 77 | assertEquals(model.downloadFileName, modelFile) 78 | assertFalse(model.showBenchmarkButton) 79 | assertFalse(model.showRunAgainButton) 80 | assertTrue(model.llmSupportImage) 81 | assertTrue(model.llmSupportAudio) 82 | 83 | // Check that configs are set correctly. 84 | assertEquals(model.configs.size, 5) 85 | 86 | // A label for showing max tokens (non-changeable). 87 | assertTrue(model.configs[0] is LabelConfig) 88 | assertEquals((model.configs[0] as LabelConfig).defaultValue, "$maxTokens") 89 | 90 | // A slider for topK. 91 | assertTrue(model.configs[1] is NumberSliderConfig) 92 | assertEquals((model.configs[1] as NumberSliderConfig).defaultValue, topK.toFloat()) 93 | 94 | // A slider for topP. 95 | assertTrue(model.configs[2] is NumberSliderConfig) 96 | assertEquals((model.configs[2] as NumberSliderConfig).defaultValue, topP) 97 | 98 | // A slider for temperature. 99 | assertTrue(model.configs[3] is NumberSliderConfig) 100 | assertEquals((model.configs[3] as NumberSliderConfig).defaultValue, temperature) 101 | 102 | // A segmented button for accelerators. 103 | assertTrue(model.configs[4] is SegmentedButtonConfig) 104 | assertEquals((model.configs[4] as SegmentedButtonConfig).defaultValue, "GPU") 105 | assertEquals((model.configs[4] as SegmentedButtonConfig).options, listOf("GPU", "CPU")) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Android/src/build.gradle.kts: -------------------------------------------------------------------------------- 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 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 18 | plugins { 19 | alias(libs.plugins.android.application) apply false 20 | alias(libs.plugins.google.services) apply false 21 | alias(libs.plugins.kotlin.android) apply false 22 | alias(libs.plugins.kotlin.compose) apply false 23 | alias(libs.plugins.hilt.application) apply false 24 | } 25 | -------------------------------------------------------------------------------- /Android/src/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. For more details, visit 12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /Android/src/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-ai-edge/gallery/6cc45c3600b7d969114248bc8a990ed711382ecd/Android/src/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Android/src/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Mar 02 09:29:13 PST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /Android/src/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /Android/src/settings.gradle.kts: -------------------------------------------------------------------------------- 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 | pluginManagement { 18 | repositories { 19 | google { 20 | content { 21 | includeGroupByRegex("com\\.android.*") 22 | includeGroupByRegex("com\\.google.*") 23 | includeGroupByRegex("androidx.*") 24 | } 25 | } 26 | mavenCentral() 27 | gradlePluginPortal() 28 | } 29 | resolutionStrategy { 30 | eachPlugin { 31 | if (requested.id.id == "com.google.android.gms.oss-licenses-plugin") { 32 | useModule("com.google.android.gms:oss-licenses-plugin:0.10.6") 33 | } 34 | } 35 | } 36 | } 37 | 38 | dependencyResolutionManagement { 39 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 40 | repositories { 41 | // mavenLocal() 42 | google() 43 | mavenCentral() 44 | } 45 | } 46 | 47 | rootProject.name = "AI Edge Gallery" 48 | 49 | include(":app") 50 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | The repository is not currently ready for code contributions. We will 2 | make a separate announcement when we are ready for OSS users to make 3 | contributions to it. 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google AI Edge Gallery ✨ 2 | 3 | [](LICENSE) 4 | [](https://github.com/google-ai-edge/gallery/releases) 5 | 6 | **Explore, Experience, and Evaluate the Future of On-Device Generative AI with Google AI Edge.** 7 | 8 | The Google AI Edge Gallery is an experimental app that puts the power of cutting-edge Generative AI models directly into your hands, running entirely on your Android *(available now)* and iOS *(coming soon)* devices. Dive into a world of creative and practical AI use cases, all running locally, without needing an internet connection once the model is loaded. Experiment with different models, chat, ask questions with images, explore prompts, and more! 9 | 10 | **Overview** 11 | <img width="1532" alt="Overview" src="https://github.com/user-attachments/assets/4f2702d7-91a0-4eb3-aa76-58bc8e7089c6" /> 12 | 13 | **Ask Image** 14 | <img width="1532" alt="Ask Image" src="https://github.com/user-attachments/assets/e2b5b41b-fed0-4a7c-9547-2abb1c10962c" /> 15 | 16 | **Prompt Lab** 17 | <img width="1532" alt="Prompt Lab" src="https://github.com/user-attachments/assets/22e459d0-0365-4a92-8570-fb59d4d1e320" /> 18 | 19 | **AI Chat** 20 | <img width="1532" alt="AI Chat" src="https://github.com/user-attachments/assets/edaa4f89-237a-4b84-b647-b3c4631f09dc" /> 21 | 22 | ## ✨ Core Features 23 | 24 | * **📱 Run Locally, Fully Offline:** Experience the magic of GenAI without an internet connection. All processing happens directly on your device. 25 | * **🤖 Choose Your Model:** Easily switch between different models from Hugging Face and compare their performance. 26 | * **🖼️ Ask Image:** Upload an image and ask questions about it. Get descriptions, solve problems, or identify objects. 27 | * **✍️ Prompt Lab:** Summarize, rewrite, generate code, or use freeform prompts to explore single-turn LLM use cases. 28 | * **💬 AI Chat:** Engage in multi-turn conversations. 29 | * **📊 Performance Insights:** Real-time benchmarks (TTFT, decode speed, latency). 30 | * **🧩 Bring Your Own Model:** Test your local LiteRT `.task` models. 31 | * **🔗 Developer Resources:** Quick links to model cards and source code. 32 | 33 | ## 🏁 Get Started in Minutes! 34 | 35 | 1. **Download the App:** Grab the [**latest APK**](https://github.com/google-ai-edge/gallery/releases/latest/download/ai-edge-gallery.apk). 36 | 2. **Install & Explore:** For detailed installation instructions (including for corporate devices) and a full user guide, head over to our [**Project Wiki**](https://github.com/google-ai-edge/gallery/wiki)! 37 | 38 | ## 🛠️ Technology Highlights 39 | 40 | * **Google AI Edge:** Core APIs and tools for on-device ML. 41 | * **LiteRT:** Lightweight runtime for optimized model execution. 42 | * **LLM Inference API:** Powering on-device Large Language Models. 43 | * **Hugging Face Integration:** For model discovery and download. 44 | 45 | ## 🤝 Feedback 46 | 47 | This is an **experimental Alpha release**, and your input is crucial! 48 | 49 | * 🐞 **Found a bug?** [Report it here!](https://github.com/google-ai-edge/gallery/issues/new?assignees=&labels=bug&template=bug_report.md&title=%5BBUG%5D) 50 | * 💡 **Have an idea?** [Suggest a feature!](https://github.com/google-ai-edge/gallery/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=%5BFEATURE%5D) 51 | 52 | ## 📄 License 53 | 54 | Licensed under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for details. 55 | 56 | ## 🔗 Useful Links 57 | 58 | * [**Project Wiki (Detailed Guides)**](https://github.com/google-ai-edge/gallery/wiki) 59 | * [Hugging Face LiteRT Community](https://huggingface.co/litert-community) 60 | * [LLM Inference guide for Android](https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference/android) 61 | * [Google AI Edge Documentation](https://ai.google.dev/edge) 62 | -------------------------------------------------------------------------------- /model_allowlist.json: -------------------------------------------------------------------------------- 1 | { 2 | "models": [ 3 | { 4 | "name": "Gemma-3n-E2B-it-int4", 5 | "modelId": "google/gemma-3n-E2B-it-litert-preview", 6 | "modelFile": "gemma-3n-E2B-it-int4.task", 7 | "description": "Preview version of [Gemma 3n E2B](https://ai.google.dev/gemma/docs/gemma-3n) ready for deployment on Android using the [MediaPipe LLM Inference API](https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference). The current checkpoint only supports text and vision input, with 4096 context length.", 8 | "sizeInBytes": 3136226711, 9 | "estimatedPeakMemoryInBytes": 5905580032, 10 | "version": "20250520", 11 | "llmSupportImage": true, 12 | "defaultConfig": { 13 | "topK": 64, 14 | "topP": 0.95, 15 | "temperature": 1.0, 16 | "maxTokens": 4096, 17 | "accelerators": "cpu,gpu" 18 | }, 19 | "taskTypes": ["llm_chat", "llm_prompt_lab", "llm_ask_image"] 20 | }, 21 | { 22 | "name": "Gemma-3n-E4B-it-int4", 23 | "modelId": "google/gemma-3n-E4B-it-litert-preview", 24 | "modelFile": "gemma-3n-E4B-it-int4.task", 25 | "description": "Preview version of [Gemma 3n E4B](https://ai.google.dev/gemma/docs/gemma-3n) ready for deployment on Android using the [MediaPipe LLM Inference API](https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference). The current checkpoint only supports text and vision input, with 4096 context length.", 26 | "sizeInBytes": 4405655031, 27 | "estimatedPeakMemoryInBytes": 6979321856, 28 | "version": "20250520", 29 | "llmSupportImage": true, 30 | "defaultConfig": { 31 | "topK": 64, 32 | "topP": 0.95, 33 | "temperature": 1.0, 34 | "maxTokens": 4096, 35 | "accelerators": "cpu,gpu" 36 | }, 37 | "taskTypes": ["llm_chat", "llm_prompt_lab", "llm_ask_image"] 38 | }, 39 | { 40 | "name": "Gemma3-1B-IT q4", 41 | "modelId": "litert-community/Gemma3-1B-IT", 42 | "modelFile": "Gemma3-1B-IT_multi-prefill-seq_q4_ekv2048.task", 43 | "description": "A variant of [google/Gemma-3-1B-IT](https://huggingface.co/google/Gemma-3-1B-IT) with 4-bit quantization ready for deployment on Android using the [MediaPipe LLM Inference API](https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference)", 44 | "sizeInBytes": 554661246, 45 | "estimatedPeakMemoryInBytes": 2147483648, 46 | "version": "20250514", 47 | "defaultConfig": { 48 | "topK": 64, 49 | "topP": 0.95, 50 | "temperature": 1.0, 51 | "maxTokens": 1024, 52 | "accelerators": "gpu,cpu" 53 | }, 54 | "taskTypes": ["llm_chat", "llm_prompt_lab"] 55 | }, 56 | { 57 | "name": "Qwen2.5-1.5B-Instruct q8", 58 | "modelId": "litert-community/Qwen2.5-1.5B-Instruct", 59 | "modelFile": "Qwen2.5-1.5B-Instruct_multi-prefill-seq_q8_ekv1280.task", 60 | "description": "A variant of [Qwen/Qwen2.5-1.5B-Instruct](https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct) with 8-bit quantization ready for deployment on Android using the [MediaPipe LLM Inference API](https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference)", 61 | "sizeInBytes": 1625493432, 62 | "estimatedPeakMemoryInBytes": 2684354560, 63 | "version": "20250514", 64 | "defaultConfig": { 65 | "topK": 40, 66 | "topP": 0.95, 67 | "temperature": 1.0, 68 | "maxTokens": 1024, 69 | "accelerators": "cpu" 70 | }, 71 | "taskTypes": ["llm_chat", "llm_prompt_lab"] 72 | } 73 | ] 74 | } 75 | --------------------------------------------------------------------------------