Skip to content

Commit 5ff5871

Browse files
Request Termux permission directly before sending
1 parent 0b9e6cb commit 5ff5871

2 files changed

Lines changed: 65 additions & 62 deletions

File tree

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import androidx.compose.ui.platform.LocalContext
6666
import androidx.compose.ui.unit.dp
6767
import androidx.compose.ui.window.Dialog
6868
import androidx.compose.ui.window.DialogProperties
69+
import androidx.core.app.ActivityCompat
6970
import androidx.core.content.ContextCompat
7071
import androidx.lifecycle.lifecycleScope
7172
import androidx.navigation.NavHostController
@@ -129,6 +130,7 @@ class MainActivity : ComponentActivity() {
129130
private var isProcessingExplicitScreenshotRequest: Boolean = false
130131
private var onMediaProjectionPermissionGranted: (() -> Unit)? = null
131132
private var onWebRtcMediaProjectionResult: ((Int, Intent) -> Unit)? = null
133+
private var onTermuxRunCommandPermissionResult: ((Boolean) -> Unit)? = null
132134
private val mediaProjectionServiceStarter by lazy { MediaProjectionServiceStarter(this) }
133135

134136
// Payment dialog state
@@ -157,6 +159,33 @@ class MainActivity : ComponentActivity() {
157159
private lateinit var requestForegroundServicePermissionLauncher: ActivityResultLauncher<String>
158160
private val foregroundMediaProjectionPermission = android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION
159161

162+
163+
fun requestTermuxRunCommandPermission(onResult: (Boolean) -> Unit) {
164+
Log.d(TAG, "Requesting Termux RUN_COMMAND permission without pre-check")
165+
onTermuxRunCommandPermissionResult = onResult
166+
ActivityCompat.requestPermissions(
167+
this,
168+
arrayOf(TERMUX_RUN_COMMAND_PERMISSION),
169+
REQUEST_CODE_TERMUX_RUN_COMMAND_PERMISSION
170+
)
171+
}
172+
173+
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
174+
override fun onRequestPermissionsResult(
175+
requestCode: Int,
176+
permissions: Array<out String>,
177+
grantResults: IntArray
178+
) {
179+
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
180+
181+
if (requestCode == REQUEST_CODE_TERMUX_RUN_COMMAND_PERMISSION) {
182+
val isGranted = grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED
183+
Log.d(TAG, "Termux RUN_COMMAND permission result: $isGranted")
184+
onTermuxRunCommandPermissionResult?.invoke(isGranted)
185+
onTermuxRunCommandPermissionResult = null
186+
}
187+
}
188+
160189
fun requestMediaProjectionPermission(onGranted: (() -> Unit)? = null) {
161190
Log.d(TAG, "Requesting MediaProjection permission")
162191
onMediaProjectionPermissionGranted = onGranted
@@ -1136,6 +1165,8 @@ class MainActivity : ComponentActivity() {
11361165
}
11371166
private const val PREFS_NAME = "AppPrefs"
11381167
private const val PREF_KEY_FIRST_LAUNCH_INFO_SHOWN = "firstLaunchInfoShown"
1168+
private const val TERMUX_RUN_COMMAND_PERMISSION = "com.termux.permission.RUN_COMMAND"
1169+
private const val REQUEST_CODE_TERMUX_RUN_COMMAND_PERMISSION = 1001
11391170

11401171
// New Broadcast Actions for MediaProjection Screenshot Flow
11411172
const val ACTION_REQUEST_MEDIAPROJECTION_SCREENSHOT = "com.google.ai.sample.REQUEST_MEDIAPROJECTION_SCREENSHOT"

app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningScreen.kt

Lines changed: 34 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ import android.content.Intent
55
import android.net.Uri
66
import android.graphics.drawable.BitmapDrawable
77
import android.provider.Settings
8-
import android.os.Build
9-
import android.content.pm.PackageManager
10-
import android.content.pm.PackageInfo
118
import android.widget.Toast
12-
import androidx.core.content.ContextCompat
139
import androidx.activity.compose.BackHandler
1410
import androidx.activity.compose.rememberLauncherForActivityResult
1511
import androidx.activity.result.PickVisualMediaRequest
@@ -125,7 +121,6 @@ import kotlinx.coroutines.launch
125121
import kotlinx.coroutines.withContext
126122
import kotlinx.serialization.builtins.ListSerializer
127123
import kotlinx.serialization.json.Json
128-
import android.util.Log
129124
import kotlinx.serialization.SerializationException
130125

131126
@Composable
@@ -163,6 +158,7 @@ fun PhotoReasoningScreen(
163158
var systemMessageEntries by rememberSaveable { mutableStateOf(emptyList<SystemMessageEntry>()) }
164159
val focusManager = LocalFocusManager.current
165160
val messages by chatMessages.collectAsState()
161+
var isTermuxPermissionRequestPending by rememberSaveable { mutableStateOf(false) }
166162

167163
LaunchedEffect(Unit) {
168164
systemMessageEntries = SystemMessageEntryPreferences.loadEntries(context)
@@ -184,43 +180,27 @@ fun PhotoReasoningScreen(
184180
uri?.let { imageUris.add(it) }
185181
}
186182

187-
fun hasTermuxRunCommandPermission(): Boolean {
188-
val runtimeGranted = ContextCompat.checkSelfPermission(
189-
context,
190-
"com.termux.permission.RUN_COMMAND"
191-
) == PackageManager.PERMISSION_GRANTED
192-
if (!runtimeGranted) return false
193-
194-
return runCatching {
195-
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
196-
context.packageManager.getPackageInfo(
197-
context.packageName,
198-
PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong())
199-
)
200-
} else {
201-
@Suppress("DEPRECATION")
202-
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)
203-
}
204-
val permissions = packageInfo.requestedPermissions ?: return@runCatching runtimeGranted
205-
val flags = packageInfo.requestedPermissionsFlags ?: return@runCatching runtimeGranted
206-
val index = permissions.indexOf("com.termux.permission.RUN_COMMAND")
207-
if (index == -1 || index >= flags.size) runtimeGranted
208-
else (flags[index] and PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0
209-
}.getOrElse {
210-
Log.w("PhotoReasoningScreen", "Unable to verify requestedPermissionsFlags for Termux permission", it)
211-
runtimeGranted
183+
fun sendCurrentQuestion() {
184+
if (userQuestion.isNotBlank()) {
185+
onReasonClicked(userQuestion, imageUris.toList())
186+
onUserQuestionChanged("")
187+
imageUris.clear()
212188
}
213189
}
214-
val termuxPermissionLauncher = rememberLauncherForActivityResult(
215-
ActivityResultContracts.RequestPermission()
216-
) { isGranted ->
217-
if (isGranted) {
218-
TermuxFeedbackPreferences.resetPermissionDenialCount(context)
219-
if (userQuestion.isNotBlank()) {
220-
onReasonClicked(userQuestion, imageUris.toList())
221-
onUserQuestionChanged("")
222-
imageUris.clear()
223-
}
190+
191+
fun handleTermuxRunCommandPermissionDenied() {
192+
val denialCount = TermuxFeedbackPreferences.incrementPermissionDenialCount(context)
193+
if (denialCount >= 3) {
194+
Toast.makeText(
195+
context,
196+
"Enable Termux permissions in the Android settings",
197+
Toast.LENGTH_LONG
198+
).show()
199+
val appInfoIntent = Intent(
200+
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
201+
Uri.fromParts("package", context.packageName, null)
202+
)
203+
context.startActivity(appInfoIntent)
224204
}
225205
}
226206

@@ -475,38 +455,30 @@ fun PhotoReasoningScreen(
475455
}
476456

477457
if (userQuestion.isNotBlank()) {
478-
val hasTermuxRunCommandPermission = hasTermuxRunCommandPermission()
479-
if (!hasTermuxRunCommandPermission) {
480-
val denialCount = TermuxFeedbackPreferences.incrementPermissionDenialCount(context)
481-
if (denialCount >= 3) {
482-
Toast.makeText(
483-
context,
484-
"Enable Termux permissions in the Android settings",
485-
Toast.LENGTH_LONG
486-
).show()
487-
val appInfoIntent = Intent(
488-
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
489-
Uri.fromParts("package", context.packageName, null)
490-
)
491-
context.startActivity(appInfoIntent)
458+
if (mainActivity == null) {
459+
handleTermuxRunCommandPermissionDenied()
460+
return@IconButton
461+
}
462+
463+
isTermuxPermissionRequestPending = true
464+
mainActivity.requestTermuxRunCommandPermission { isGranted ->
465+
isTermuxPermissionRequestPending = false
466+
if (isGranted) {
467+
TermuxFeedbackPreferences.resetPermissionDenialCount(context)
468+
sendCurrentQuestion()
492469
} else {
493-
termuxPermissionLauncher.launch("com.termux.permission.RUN_COMMAND")
470+
handleTermuxRunCommandPermissionDenied()
494471
}
495-
return@IconButton
496472
}
497-
TermuxFeedbackPreferences.resetPermissionDenialCount(context)
498-
onReasonClicked(userQuestion, imageUris.toList())
499-
onUserQuestionChanged("")
500-
imageUris.clear()
501473
}
502474
},
503-
enabled = isInitialized && userQuestion.isNotBlank(),
475+
enabled = isInitialized && userQuestion.isNotBlank() && !isTermuxPermissionRequestPending,
504476
modifier = Modifier.padding(all = 4.dp).align(Alignment.CenterVertically)
505477
) {
506478
Icon(
507479
Icons.AutoMirrored.Filled.Send,
508480
stringResource(R.string.action_go),
509-
tint = if (isInitialized && userQuestion.isNotBlank())
481+
tint = if (isInitialized && userQuestion.isNotBlank() && !isTermuxPermissionRequestPending)
510482
MaterialTheme.colorScheme.primary else Color.Gray,
511483
)
512484
}

0 commit comments

Comments
 (0)