From b61f2f102912199b3c50bda03c93fe76ac4478a5 Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:46:06 +0200 Subject: [PATCH 1/5] Stop reusing initial screenshot prompt and disable debuggable for non-debug builds --- app/build.gradle.kts | 7 ++++ .../ai/sample/GenerativeAiViewModelFactory.kt | 1 + .../kotlin/com/google/ai/sample/MenuScreen.kt | 8 +++-- .../multimodal/PhotoReasoningViewModel.kt | 36 ++----------------- 4 files changed, 17 insertions(+), 35 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4df88cd..8e1528c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -35,8 +35,15 @@ android { } buildTypes { + getByName("debug") { + isDebuggable = true + } + getByName("release") { + isDebuggable = false + } create("samples") { initWith(getByName("debug")) + isDebuggable = false } } diff --git a/app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt b/app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt index 3801cc6..106cafa 100644 --- a/app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt +++ b/app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt @@ -35,6 +35,7 @@ enum class ModelOption( val additionalDownloadUrls: List = emptyList(), val requiresVisionBackend: Boolean = false ) { + PUTER_GPT_5_4_NANO("GPT-5.4 Nano (Puter)", "openai/gpt-5.4-nano", ApiProvider.PUTER, supportsScreenshot = true), PUTER_GLM5("GLM-5V Turbo (Puter)", "openrouter:z-ai/glm-5v-turbo", ApiProvider.PUTER, supportsScreenshot = true), MISTRAL_LARGE_3("Mistral Large 3", "mistral-large-latest", ApiProvider.MISTRAL), MISTRAL_MEDIUM_3_1("Mistral Medium 3.1", "mistral-medium-latest", ApiProvider.MISTRAL), diff --git a/app/src/main/kotlin/com/google/ai/sample/MenuScreen.kt b/app/src/main/kotlin/com/google/ai/sample/MenuScreen.kt index 8abd5fb..f189279 100644 --- a/app/src/main/kotlin/com/google/ai/sample/MenuScreen.kt +++ b/app/src/main/kotlin/com/google/ai/sample/MenuScreen.kt @@ -215,10 +215,14 @@ fun MenuScreen( } val normalModels = allModels.filter { it != ModelOption.MISTRAL_MEDIUM_3_1 && + it != ModelOption.PUTER_GPT_5_4_NANO && it.apiProvider != ApiProvider.VERCEL && !STRIKETHROUGH_MODELS.contains(it) } - val orderedModels = listOf(ModelOption.MISTRAL_MEDIUM_3_1) + + val orderedModels = listOf( + ModelOption.PUTER_GPT_5_4_NANO, + ModelOption.MISTRAL_MEDIUM_3_1 + ) + normalModels + vercelModels + STRIKETHROUGH_MODELS @@ -288,7 +292,7 @@ fun MenuScreen( ModelOption.GPT_OSS_120B -> "This is a pure text model\nCerebras sometimes discontinues free access in the Free Tier, displaying an \"Error 404: gpt-oss-120b does not exist or you do not have access to it\" message, or changes the rate limits." ModelOption.MISTRAL_LARGE_3 -> "Mistral AI rejects requests containing non-black images with a 429 Error: Rate limit exceeded response" ModelOption.GEMINI_3_FLASH -> "Google often rejects requests to this model with a 503 Model is exhausted error" - ModelOption.PUTER_GLM5 -> "This model is expensive and uses up the free quota quickly. Consider GPT 5.4 nano" + ModelOption.PUTER_GLM5 -> "This model is expensive and uses up the free quota quickly. Consider GPT-5.4 Nano." ModelOption.GPT_5_1_CODEX_MAX, ModelOption.GPT_5_1_CODEX_MINI, ModelOption.GPT_5_NANO -> "Vercel requires a credit card" diff --git a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt index b22e1ae..4a33a5a 100644 --- a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt +++ b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt @@ -134,7 +134,6 @@ class PhotoReasoningViewModel( // Keep track of the current user input private var currentUserInput: String = "" - private var latestUserTaskInput: String = "" // Observable state for the input field to persist across configuration changes private val _userInput = MutableStateFlow("") @@ -791,10 +790,6 @@ class PhotoReasoningViewModel( imageUrisForChat: List? = null ) { val currentModel = com.google.ai.sample.GenerativeAiViewModelFactory.getCurrentModel() - if (userInput.isNotBlank() && screenInfoForPrompt.isNullOrBlank()) { - latestUserTaskInput = userInput.trim() - } - clearStaleErrorState() stopExecutionFlag.set(false) @@ -1191,7 +1186,8 @@ class PhotoReasoningViewModel( } // CerebrasRequest braucht stream-Feld — inline als JSON-String um Datenklasse nicht zu ändern - val streamingBody = """{"model":"$modelName","messages":${Json.encodeToString(apiMessages)},"max_completion_tokens":1024,"temperature":0.2,"top_p":1.0,"stream":true}""" + val selectedModelName = com.google.ai.sample.GenerativeAiViewModelFactory.getCurrentModel().modelName + val streamingBody = """{"model":"$selectedModelName","messages":${Json.encodeToString(apiMessages)},"max_completion_tokens":1024,"temperature":0.2,"top_p":1.0,"stream":true}""" val mediaType = "application/json".toMediaType() val client = OkHttpClient() @@ -1859,7 +1855,7 @@ class PhotoReasoningViewModel( context = context, inputContentJson = inputContentJson, chatHistoryJson = chatHistoryJson, - modelName = generativeModel.modelName, + modelName = currentModel.modelName, apiKey = apiKey, apiProvider = currentModel.apiProvider, tempFilePaths = tempFilePaths @@ -2166,32 +2162,6 @@ class PhotoReasoningViewModel( } private fun createGenericScreenshotPrompt(): String { - val latestTask = latestUserTaskInput.trim() - if (latestTask.isNotBlank()) { - return latestTask - } - - val lastUserMessage = _chatState.getAllMessages() - .asReversed() - .firstOrNull { it.participant == PhotoParticipant.USER && it.text.isNotBlank() } - ?.text - ?.trim() - - if (!lastUserMessage.isNullOrBlank()) { - val screenInfoMarker = "\n\nScreen elements:\n" - return lastUserMessage.substringBefore(screenInfoMarker).trim() - } - - val persistedInput = _userInput.value.trim() - if (persistedInput.isNotBlank()) { - return persistedInput - } - - val lastKnownInput = currentUserInput.trim() - if (lastKnownInput.isNotBlank()) { - return lastKnownInput - } - return "" } From 61e4a5985d2eccc4abdb2d3a4231cac96193b33e Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:59:37 +0200 Subject: [PATCH 2/5] Set debug buildType to non-debuggable --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8e1528c..7b8438f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -36,7 +36,7 @@ android { buildTypes { getByName("debug") { - isDebuggable = true + isDebuggable = false } getByName("release") { isDebuggable = false From 34aa032058fe83c34f4a99d3fa8bc569b20f86f3 Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:09:04 +0200 Subject: [PATCH 3/5] Fix invalid backup exclusion paths in data extraction rules --- app/src/main/res/xml/data_extraction_rules.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml index d0212fd..422378a 100644 --- a/app/src/main/res/xml/data_extraction_rules.xml +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -21,12 +21,8 @@ - - - - From db93284d7989c6640bd388566af724b7e2900bc4 Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:34:18 +0200 Subject: [PATCH 4/5] Restore debug debuggable and keep release artifacts non-debuggable --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7b8438f..8e1528c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -36,7 +36,7 @@ android { buildTypes { getByName("debug") { - isDebuggable = false + isDebuggable = true } getByName("release") { isDebuggable = false From e64a630c6f92404e2e27bab862a57df2b61a5c07 Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Tue, 21 Apr 2026 00:31:53 +0200 Subject: [PATCH 5/5] Build release APKs in manual workflow instead of debug --- .github/workflows/manual.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml index b9e1003..7d455ec 100644 --- a/.github/workflows/manual.yml +++ b/.github/workflows/manual.yml @@ -144,27 +144,27 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Build app module (debug) + - name: Build app module (release) if: env.BUILD_APP == 'true' - run: ./gradlew :app:assembleDebug + run: ./gradlew :app:assembleRelease - - name: Build humanoperator module (debug) + - name: Build humanoperator module (release) if: env.BUILD_HUMANOPERATOR == 'true' - run: ./gradlew :humanoperator:assembleDebug + run: ./gradlew :humanoperator:assembleRelease - name: Upload app APK if: env.BUILD_APP == 'true' uses: actions/upload-artifact@v4 with: - name: app-debug - path: app/build/outputs/apk/debug/app-debug.apk + name: app-release-unsigned + path: app/build/outputs/apk/release/app-release-unsigned.apk - name: Upload humanoperator APK if: env.BUILD_HUMANOPERATOR == 'true' uses: actions/upload-artifact@v4 with: - name: humanoperator-debug - path: humanoperator/build/outputs/apk/debug/humanoperator-debug.apk + name: humanoperator-release-unsigned + path: humanoperator/build/outputs/apk/release/humanoperator-release-unsigned.apk - name: Build summary run: |