Merge branch 'google-ai-edge:main' into main

This commit is contained in:
Fattire 2025-05-26 17:11:31 -07:00 committed by GitHub
commit e897b15f26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 117 additions and 64 deletions

View file

@ -31,7 +31,7 @@ android {
minSdk = 26
targetSdk = 36
versionCode = 1
versionName = "1.0.2"
versionName = "1.0.3"
// Needed for HuggingFace auth workflows.
manifestPlaceholders["appAuthRedirectScheme"] = "com.google.ai.edge.gallery.oauth"

View file

@ -23,6 +23,8 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.net.Uri
import android.util.Log
import android.util.Size
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
@ -31,6 +33,9 @@ import androidx.camera.core.CameraControl
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageProxy
import androidx.camera.core.resolutionselector.AspectRatioStrategy
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.core.resolutionselector.ResolutionStrategy
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.lifecycle.awaitInstance
import androidx.camera.view.PreviewView
@ -75,9 +80,11 @@ import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@ -104,6 +111,8 @@ import com.google.ai.edge.gallery.ui.theme.GalleryTheme
import kotlinx.coroutines.launch
import java.util.concurrent.Executors
private const val TAG = "AGMessageInputText"
/**
* Composable function to display a text input field for composing chat messages.
*
@ -135,7 +144,7 @@ fun MessageInputText(
var showAddContentMenu by remember { mutableStateOf(false) }
var showTextInputHistorySheet by remember { mutableStateOf(false) }
var showCameraCaptureBottomSheet by remember { mutableStateOf(false) }
var cameraCaptureSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val cameraCaptureSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
var tempPhotoUri by remember { mutableStateOf(value = Uri.EMPTY) }
var pickedImages by remember { mutableStateOf<List<Bitmap>>(listOf()) }
val updatePickedImages: (Bitmap) -> Unit = { bitmap ->
@ -150,21 +159,6 @@ fun MessageInputText(
checkFrontCamera(context = context, callback = { hasFrontCamera = it })
}
// launches camera
val cameraLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { isImageSaved ->
if (isImageSaved) {
handleImageSelected(
context = context,
uri = tempPhotoUri,
onImageSelected = { bitmap ->
updatePickedImages(bitmap)
},
rotateForPortrait = true,
)
}
}
// Permission request when taking picture.
val takePicturePermissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
@ -173,7 +167,6 @@ fun MessageInputText(
showAddContentMenu = false
tempPhotoUri = context.createTempPictureUri()
showCameraCaptureBottomSheet = true
// cameraLauncher.launch(tempPhotoUri)
}
}
@ -270,7 +263,6 @@ fun MessageInputText(
showAddContentMenu = false
tempPhotoUri = context.createTempPictureUri()
showCameraCaptureBottomSheet = true
// cameraLauncher.launch(tempPhotoUri)
}
// Otherwise, ask for permission
@ -416,28 +408,43 @@ fun MessageInputText(
val lifecycleOwner = LocalLifecycleOwner.current
val previewUseCase = remember { androidx.camera.core.Preview.Builder().build() }
val imageCaptureUseCase = remember { ImageCapture.Builder().build() }
val imageCaptureUseCase = remember {
// Try to limit the image size.
val preferredSize = Size(512, 512)
val resolutionStrategy = ResolutionStrategy(
preferredSize,
ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER
)
val resolutionSelector = ResolutionSelector.Builder()
.setResolutionStrategy(resolutionStrategy)
.setAspectRatioStrategy(AspectRatioStrategy.RATIO_4_3_FALLBACK_AUTO_STRATEGY)
.build()
ImageCapture.Builder().setResolutionSelector(resolutionSelector).build()
}
var cameraProvider by remember { mutableStateOf<ProcessCameraProvider?>(null) }
var cameraControl by remember { mutableStateOf<CameraControl?>(null) }
val localContext = LocalContext.current
var cameraSide by remember { mutableStateOf(CameraSelector.LENS_FACING_BACK) }
var cameraSide by remember { mutableIntStateOf(CameraSelector.LENS_FACING_BACK) }
val executor = remember { Executors.newSingleThreadExecutor() }
val capturedImageUri = remember { mutableStateOf<Uri?>(null) }
fun rebindCameraProvider() {
cameraProvider?.let { cameraProvider ->
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(cameraSide)
.build()
cameraProvider.unbindAll()
val camera = cameraProvider.bindToLifecycle(
lifecycleOwner = lifecycleOwner,
cameraSelector = cameraSelector,
previewUseCase,
imageCaptureUseCase
)
cameraControl = camera.cameraControl
try {
cameraProvider.unbindAll()
val camera = cameraProvider.bindToLifecycle(
lifecycleOwner = lifecycleOwner,
cameraSelector = cameraSelector,
previewUseCase,
imageCaptureUseCase
)
cameraControl = camera.cameraControl
} catch (e: Exception) {
Log.d(TAG, "Failed to bind camera", e)
}
}
}
@ -450,31 +457,25 @@ fun MessageInputText(
rebindCameraProvider()
}
// val cameraController = remember {
// LifecycleCameraController(context).apply {
// bindToLifecycle(lifecycleOwner)
// }
// }
DisposableEffect(Unit) { // Or key on lifecycleOwner if it makes more sense
onDispose {
cameraProvider?.unbindAll() // Unbind all use cases from the camera provider
if (!executor.isShutdown) {
executor.shutdown() // Shut down the executor service
}
}
}
Box(modifier = Modifier.fillMaxSize()) {
// PreviewView for the camera feed.
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { ctx ->
PreviewView(context).also {
PreviewView(ctx).also {
previewUseCase.surfaceProvider = it.surfaceProvider
rebindCameraProvider()
}
// PreviewView(ctx).apply {
// scaleType = PreviewView.ScaleType.FILL_START
// implementationMode = PreviewView.ImplementationMode.COMPATIBLE
// controller = cameraController // Attach the lifecycle-aware camera controller.
// }
},
// onRelease = {
// // Called when the PreviewView is removed from the composable hierarchy
// cameraController.unbind() // Unbinds the camera to free up resources
// }
)
// Close button.
@ -508,25 +509,31 @@ fun MessageInputText(
.size(64.dp)
.border(2.dp, MaterialTheme.colorScheme.onPrimary, CircleShape),
onClick = {
scope.launch {
val callback = object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
val callback = object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
try {
var bitmap = image.toBitmap()
val rotation = image.imageInfo.rotationDegrees
bitmap = if (rotation != 0) {
val matrix = Matrix().apply {
postRotate(rotation.toFloat())
}
Log.d(TAG, "image size: ${bitmap.width}, ${bitmap.height}")
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
} else bitmap
updatePickedImages(bitmap)
} catch (e: Exception) {
Log.e(TAG, "Failed to process image", e)
} finally {
image.close()
scope.launch {
cameraCaptureSheetState.hide()
showCameraCaptureBottomSheet = false
}
}
}
imageCaptureUseCase.takePicture(executor, callback)
cameraCaptureSheetState.hide()
showCameraCaptureBottomSheet = false
}
imageCaptureUseCase.takePicture(executor, callback)
},
) {
Icon(
@ -601,7 +608,7 @@ private fun rotateImageIfNecessary(bitmap: Bitmap, rotateForPortrait: Boolean =
private fun checkFrontCamera(context: Context, callback: (Boolean) -> Unit) {
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener(Runnable {
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
try {
// Attempt to select the default front camera