Skip to content

Commit 9966f80

Browse files
committed
Fix: don't request MediaProjection permission for text-only WebView models
sendMessageFromWebView checked GenerativeAiViewModelFactory.getCurrentModel() .supportsScreenshot directly, which always reports true for ONLINE_MODEL (the placeholder used for every JS-only model selected from the WebView dropdown). This caused a MediaProjection permission prompt before sending a message even when a text-only model (e.g. a Groq/Cerebras text-only model) was selected. Extracted the existing, correct capability-resolution logic from ScreenOperatorAccessibilityService.currentModelSupportsScreenshot() into a shared util/ActiveModelCapabilities.kt so both the accessibility service and MainActivity.sendMessageFromWebView resolve the model's real screenshot support the same way (custom JSON model -> JS-only WebView model via the persisted js_only_supports_screenshot flag -> native model enum).
1 parent da93462 commit 9966f80

3 files changed

Lines changed: 62 additions & 28 deletions

File tree

app/src/main/kotlin/com/google/ai/sample/MainActivity.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import com.android.billingclient.api.PendingPurchasesParams
6969
import com.google.ai.sample.feature.multimodal.PhotoReasoningViewModel
7070
import com.google.ai.sample.GenerativeAiViewModelFactory
7171
import com.google.ai.sample.ui.theme.GenerativeAISample
72+
import com.google.ai.sample.util.ActiveModelCapabilities
7273
import com.google.ai.sample.util.BroadcastReceiverCompat
7374
import com.google.ai.sample.util.NotificationUtil
7475
import com.google.ai.sample.util.TermuxExecutionModePreferences
@@ -1396,9 +1397,17 @@ class MainActivity : ComponentActivity() {
13961397
// Mirror the native send-button logic: ask for MediaProjection permission before sending
13971398
// when the active model supports screenshots, unless it is the Human Expert model (which
13981399
// manages its own WebRTC-based projection separately).
1400+
// Use ActiveModelCapabilities instead of GenerativeAiViewModelFactory.getCurrentModel()
1401+
// .supportsScreenshot directly: for JS-only online models selected from the WebView
1402+
// dropdown (the normal case here), currentModel stays ONLINE_MODEL, which always
1403+
// reports supportsScreenshot=true - so a text-only WebView model (e.g. a Groq/Cerebras
1404+
// text-only model) would otherwise incorrectly trigger a MediaProjection permission
1405+
// request before every send. ActiveModelCapabilities resolves the model's real
1406+
// capability from the WebView-persisted "js_only_supports_screenshot" flag instead.
13991407
val currentModel = GenerativeAiViewModelFactory.getCurrentModel()
14001408
val modelName = currentModel.name
1401-
val requiresScreenCapturePermission = currentModel.supportsScreenshot && modelName != "HUMAN_EXPERT"
1409+
val requiresScreenCapturePermission =
1410+
ActiveModelCapabilities.currentModelSupportsScreenshot(this) && modelName != "HUMAN_EXPERT"
14021411
if (!_isMediaProjectionPermissionGranted.value && requiresScreenCapturePermission) {
14031412
Log.d(TAG, "sendMessageFromWebView: MediaProjection not yet granted. Requesting permission first.")
14041413
requestMediaProjectionPermission {

app/src/main/kotlin/com/google/ai/sample/ScreenOperatorAccessibilityService.kt

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import android.view.KeyEvent
2525
import android.view.accessibility.AccessibilityEvent
2626
import android.view.accessibility.AccessibilityNodeInfo
2727
import android.widget.Toast
28+
import com.google.ai.sample.util.ActiveModelCapabilities
2829
import com.google.ai.sample.util.AppNamePackageMapper
2930
import com.google.ai.sample.util.AppOpenFeedbackPreferences
3031
import com.google.ai.sample.util.Command
@@ -515,33 +516,8 @@ class ScreenOperatorAccessibilityService : AccessibilityService() {
515516
* for a text-only model: Command.Wait's toast, the delayed-screenshot toast, and the
516517
* actual capture/MediaProjection-request decision in executeTakeScreenshotCommand.
517518
*/
518-
private fun currentModelSupportsScreenshot(): Boolean {
519-
com.google.ai.sample.util.CustomModelRegistry.getActiveModel()?.let { customModel ->
520-
return customModel.supportsScreenshot
521-
}
522-
523-
val currentModel = GenerativeAiViewModelFactory.getCurrentModel()
524-
val jsModelPrefs = applicationContext.getSharedPreferences("js_model_prefs", android.content.Context.MODE_PRIVATE)
525-
val jsOnlyModelId = jsModelPrefs.getString("js_only_model_id", null)
526-
527-
// JS-only online models (the normal WebView model dropdown) are a third, separate
528-
// case: they are not in CustomModelRegistry, and ModelOption.ONLINE_MODEL always
529-
// reports supportsScreenshot=true. Their real capability is persisted by the WebView
530-
// on model selection (dispatch("setJsOnlyModelSupportsScreenshot", ...)) - the same
531-
// flag PhotoReasoningViewModel already reads to decide whether to attach the image to
532-
// the outgoing payload. jsOnlyModelId being non-null is itself the reliable signal
533-
// that a JS-only model is active (it is cleared whenever a custom or native built-in
534-
// model is selected instead - see WebViewBridge.setSelectedModel) - it is checked on
535-
// its own, without also requiring currentModel == ONLINE_MODEL, because currentModel
536-
// is only updated by native built-in model selections and can otherwise still hold a
537-
// stale value (e.g. a previously selected offline model) while a JS-only model is the
538-
// one actually active.
539-
if (jsOnlyModelId != null) {
540-
return jsModelPrefs.getBoolean("js_only_supports_screenshot", true)
541-
}
542-
543-
return currentModel.supportsScreenshot
544-
}
519+
private fun currentModelSupportsScreenshot(): Boolean =
520+
ActiveModelCapabilities.currentModelSupportsScreenshot(applicationContext)
545521

546522
private fun executeTakeScreenshotCommand(): Boolean {
547523
val delayMillis = pendingScreenshotDelayMillis
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.google.ai.sample.util
2+
3+
import android.content.Context
4+
import com.google.ai.sample.GenerativeAiViewModelFactory
5+
6+
/**
7+
* Resolves whether the currently active model (whichever of the three model categories -
8+
* custom JSON-defined model, JS-only online model, or native built-in model/offline model -
9+
* is active) actually supports screenshots/vision input.
10+
*
11+
* This is the single source of truth used everywhere a screenshot-related decision must
12+
* reflect the model's *real* capability rather than the native [GenerativeAiViewModelFactory]
13+
* model enum, which always reports `supportsScreenshot = true` for `ONLINE_MODEL` (the
14+
* placeholder used for every JS-only model selected from the WebView dropdown). Used for:
15+
* the actual capture/MediaProjection-request decision in
16+
* `ScreenOperatorAccessibilityService.executeTakeScreenshotCommand`, its related toasts, and
17+
* the pre-send MediaProjection permission check in `MainActivity.sendMessageFromWebView`.
18+
*/
19+
object ActiveModelCapabilities {
20+
21+
fun currentModelSupportsScreenshot(context: Context): Boolean {
22+
CustomModelRegistry.getActiveModel()?.let { customModel ->
23+
return customModel.supportsScreenshot
24+
}
25+
26+
val currentModel = GenerativeAiViewModelFactory.getCurrentModel()
27+
val jsModelPrefs = context.applicationContext
28+
.getSharedPreferences("js_model_prefs", Context.MODE_PRIVATE)
29+
val jsOnlyModelId = jsModelPrefs.getString("js_only_model_id", null)
30+
31+
// JS-only online models (the normal WebView model dropdown) are a third, separate
32+
// case: they are not in CustomModelRegistry, and ModelOption.ONLINE_MODEL always
33+
// reports supportsScreenshot=true. Their real capability is persisted by the WebView
34+
// on model selection (dispatch("setJsOnlyModelSupportsScreenshot", ...)) - the same
35+
// flag PhotoReasoningViewModel already reads to decide whether to attach the image to
36+
// the outgoing payload. jsOnlyModelId being non-null is itself the reliable signal
37+
// that a JS-only model is active (it is cleared whenever a custom or native built-in
38+
// model is selected instead - see WebViewBridge.setSelectedModel) - it is checked on
39+
// its own, without also requiring currentModel == ONLINE_MODEL, because currentModel
40+
// is only updated by native built-in model selections and can otherwise still hold a
41+
// stale value (e.g. a previously selected offline model) while a JS-only model is the
42+
// one actually active.
43+
if (jsOnlyModelId != null) {
44+
return jsModelPrefs.getBoolean("js_only_supports_screenshot", true)
45+
}
46+
47+
return currentModel.supportsScreenshot
48+
}
49+
}

0 commit comments

Comments
 (0)