diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 39a34593d..2e47eecb5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -94,6 +94,7 @@ mockitoKotlin = "5.3.1" okHttp = "5.4.0" playServicesFitness = "21.3.0" playServicesLocation = "21.4.0" +playServicesMediaEffectEnhancement = "16.0.0-beta04" playServicesWearable = "20.0.1" playbilling = "9.1.0" protobuf = "4.35.1" @@ -334,6 +335,7 @@ mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = " okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okHttp" } play-services-fitness = { module = "com.google.android.gms:play-services-fitness", version.ref = "playServicesFitness" } play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" } +play-services-media-effect-enhancement = { module = "com.google.android.gms:play-services-media-effect-enhancement", version.ref = "playServicesMediaEffectEnhancement" } play-services-wearable = { module = "com.google.android.gms:play-services-wearable", version.ref = "playServicesWearable" } reactive-streams = { module = "org.reactivestreams:reactive-streams", version.ref = "reactive-streams" } robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } diff --git a/media/build.gradle.kts b/media/build.gradle.kts index 7d3545b0d..6b12b3daa 100644 --- a/media/build.gradle.kts +++ b/media/build.gradle.kts @@ -23,8 +23,19 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + + buildFeatures { + viewBinding = true + } } dependencies { + implementation(libs.androidx.activity.ktx) + implementation(libs.androidx.fragment.ktx) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.media3.common) implementation(libs.androidx.media3.exoplayer) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.kotlinx.coroutines.play.services) + implementation(libs.play.services.media.effect.enhancement) } diff --git a/media/src/main/java/com/example/media/BitmapModeLifecycle.kt b/media/src/main/java/com/example/media/BitmapModeLifecycle.kt new file mode 100644 index 000000000..6b7255b86 --- /dev/null +++ b/media/src/main/java/com/example/media/BitmapModeLifecycle.kt @@ -0,0 +1,218 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.media + +import android.app.Application +import android.graphics.Bitmap +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.google.android.gms.common.api.Status +import com.google.android.gms.media.effect.enhancement.Enhancement +import com.google.android.gms.media.effect.enhancement.EnhancementCallback +import com.google.android.gms.media.effect.enhancement.EnhancementClient +import com.google.android.gms.media.effect.enhancement.EnhancementMode +import com.google.android.gms.media.effect.enhancement.EnhancementOptions +import com.google.android.gms.media.effect.enhancement.EnhancementSession +import com.google.android.gms.media.effect.enhancement.EnhancementSessionCallback +import java.util.concurrent.Executor +import java.util.concurrent.Executors +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.tasks.await +import kotlinx.coroutines.withContext + +// [START android_media_ai_enhancement_bitmap_initialize_engine] +class MediaSetupViewModel(application: Application) : AndroidViewModel(application) { + private val enhancementClient = Enhancement.getClient(application) + fun initializeEnhancementEngine() { + viewModelScope.launch { + try { + // 1. Verify hardware capability. + val isSupported = enhancementClient.isDeviceSupportedAsync() + if (!isSupported) { + notifyUiDeviceIncompatible() + return@launch + } + // 2. Verify and download the Google Play services ML modules. + val isInstalled = enhancementClient.isModuleInstalledAsync() + if (!isInstalled) { + notifyUiDownloadingModels() + enhancementClient.installModule(installStatusCallback).await() + } + notifyUiEngineReady() + } catch (e: Exception) { + + // Handle potential errors during session creation or image processing. + handleInitializationError(e) + } + } + } +} +// [END android_media_ai_enhancement_bitmap_initialize_engine] + +// [START android_media_ai_enhancement_bitmap_wrappers] +// Wraps the task-based createSession callback into a suspending function. +suspend fun EnhancementClient.createSessionAsync( + options: EnhancementOptions, + executor: Executor +): EnhancementSession = withContext(Dispatchers.Main) { + suspendCancellableCoroutine { continuation -> + + // EnhancementSessionCallback handles session success or failure. + val callback = object : EnhancementSessionCallback { + override fun onSessionCreated(session: EnhancementSession) { + continuation.resume(session) + } + override fun onSessionCreationFailed(status: Status) { + continuation.resumeWithException( + Exception("Session creation failed: ${status.statusMessage} (${status.statusCode})") + ) + } + override fun onSessionDestroyed() {} + override fun onSessionDisconnected(status: Status) {} + } + + // Handles errors during the initial request trigger. + createSession(options, callback).addOnFailureListener(executor) { e -> + if (continuation.isActive) { + continuation.resumeWithException(e) + } + } + } +} + +// Wraps this process in a suspending function for cleaner execution. +suspend fun EnhancementSession.processBitmapAsync( + bitmap: Bitmap, + options: EnhancementOptions +): Bitmap = suspendCancellableCoroutine { continuation -> + + // EnhancementCallback returns the processed bitmap or an error code. + val callback = object : EnhancementCallback { + override fun onBitmapProcessed(enhancedBitmap: Bitmap) { + continuation.resume(enhancedBitmap) + } + override fun onError(statusCode: Int) { + continuation.resumeWithException( + Exception("Bitmap processing failed with status code: $statusCode") + ) + } + override fun onSurfaceProcessed(timestamp: Long) {} + } + this.process(bitmap, options, callback) +} +// [END android_media_ai_enhancement_bitmap_wrappers] + +// [START android_media_ai_enhancement_bitmap_viewmodel] +// Define a data class to hold image information. +data class ImageInfo(val bitmap: Bitmap) +// Define a UI state class to hold loading status, errors, and enhanced image. +data class EnhancementUiState( + val isLoading: Boolean = false, + val enhancementError: String? = null, + val enhancedImage: ImageInfo? = null +) + +class EnhancementViewModel(application: Application) : AndroidViewModel(application) { + + // Backing field for UI state, initialized with default values. + private val _uiState = MutableStateFlow(EnhancementUiState()) + // Publicly exposed UI state flow for observation. + val uiState: StateFlow = _uiState.asStateFlow() + + // Initialize client to interact with the Media Enhancement service. + private val enhancementClient: EnhancementClient = Enhancement.getClient(application) + + // Single-thread executor for processing background enhancement tasks. + private val enhancementExecutor = Executors.newSingleThreadExecutor() + + // Track session state to enable reuse across multiple processing calls. + private var enhancementSession: EnhancementSession? = null + + // Primary function to trigger the enhancement workflow for a provided bitmap. + fun enhanceImage(bitmap: Bitmap) { + viewModelScope.launch(Dispatchers.IO) { + _uiState.update { it.copy(isLoading = true, enhancementError = null) } + try { + // 1. Establish the session lazily on demand. + + // Define enhancement options (for example, enable upscale, tonemapping) based + // on bitmap dimensions. + val options = EnhancementOptions( + bitmap.width, + bitmap.height, + EnhancementMode.BITMAP, + isTonemappingEnabled = true, + isDeblurAndDenoisePhotoEnabled = true, + isDeblurAndDenoiseVideoEnabled = false, + isUpscalePhotoEnabled = false, + isUpscaleVideoEnabled = false, + ) + if (enhancementSession == null) { + enhancementSession = enhancementClient.createSessionAsync(options, enhancementExecutor) + } + val session = enhancementSession ?: throw IllegalStateException("Session unavailable.") + // 2. Dispatch image through the neural pipeline. + val enhancedBitmap = session.processBitmapAsync(bitmap, options) + // 3. Render output to UI. + _uiState.update { + it.copy(enhancedImage = ImageInfo(bitmap = enhancedBitmap)) + } + } catch (e: Exception) { + _uiState.update { it.copy(enhancementError = e.message) } + } finally { + + // Ensure loading state is reset regardless of the outcome. + _uiState.update { it.copy(isLoading = false) } + } + } + } + override fun onCleared() { + // 4. Critical: Release native GPU hardware resources. + enhancementSession?.release() + enhancementSession = null + enhancementExecutor.shutdown() + super.onCleared() + } +} +// [END android_media_ai_enhancement_bitmap_viewmodel] + +private val installStatusCallback = object : EnhancementClient.InstallStatusCallback { + override fun onError(description: String) {} + override fun onCancelled() {} + override fun onDownloadProgressUpdate(progress: Int) {} + override fun onDownloadPending() {} + override fun onDownloadStart() {} + override fun onDownloadPaused() {} + override fun onDownloadComplete() {} + override fun onInstalled() {} +} + +private fun notifyUiDeviceIncompatible() {} + +private fun notifyUiDownloadingModels() {} + +private fun notifyUiEngineReady() {} + +private fun handleInitializationError(e: Exception) {} diff --git a/media/src/main/java/com/example/media/GetStartedEnhancement.kt b/media/src/main/java/com/example/media/GetStartedEnhancement.kt new file mode 100644 index 000000000..95b5cc880 --- /dev/null +++ b/media/src/main/java/com/example/media/GetStartedEnhancement.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.media + +import com.google.android.gms.media.effect.enhancement.EnhancementClient +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlinx.coroutines.suspendCancellableCoroutine + +// [START android_media_ai_enhancement_get_started_checks] +// Verifies if host hardware supports NPU/GPU acceleration. +suspend fun EnhancementClient.isDeviceSupportedAsync(): Boolean = suspendCancellableCoroutine { continuation -> + this.isDeviceSupported() + .addOnSuccessListener { result -> continuation.resume(result) } + .addOnFailureListener { exception -> continuation.resumeWithException(exception) } +} +// Verifies the presence of required neural network models. +suspend fun EnhancementClient.isModuleInstalledAsync(): Boolean = suspendCancellableCoroutine { continuation -> + this.isModuleInstalled() + .addOnSuccessListener { result -> continuation.resume(result) } + .addOnFailureListener { exception -> continuation.resumeWithException(exception) } +} +// [END android_media_ai_enhancement_get_started_checks] diff --git a/media/src/main/java/com/example/media/HdrLut.kt b/media/src/main/java/com/example/media/HdrLut.kt new file mode 100644 index 000000000..78e339f00 --- /dev/null +++ b/media/src/main/java/com/example/media/HdrLut.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.media + +import android.hardware.DisplayLuts +import android.hardware.LutProperties +import android.view.SurfaceControl + +private fun applyLutExample() { + // [START android_media_hdr_lut_apply] + val sc = SurfaceControl.Builder().build() + val luts = DisplayLuts() + val entry = DisplayLuts.Entry( + floatArrayOf(0.5f, 0.5f, 0.5f, 0.5f), + LutProperties.ONE_DIMENSION, + LutProperties.SAMPLING_KEY_MAX_RGB + ) + luts.set(entry) + SurfaceControl.Transaction().setLuts(sc, luts).apply() + // [END android_media_hdr_lut_apply] +} diff --git a/media/src/main/java/com/example/media/HdrPlayback.kt b/media/src/main/java/com/example/media/HdrPlayback.kt new file mode 100644 index 000000000..76ab33c45 --- /dev/null +++ b/media/src/main/java/com/example/media/HdrPlayback.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.media + +import android.media.MediaCodec +import android.media.MediaCodecInfo +import android.media.MediaCodecList +import android.media.MediaCrypto +import android.media.MediaFormat +import android.view.Display +import android.view.Display.HdrCapabilities.HDR_TYPE_HLG +import android.view.Surface +import android.view.SurfaceView +import java.util.Queue + +private fun checkHdrSupport(display: Display?) { + // [START android_media_hdr_playback_check_support] + // Check if display supports the HDR type. + val capabilities = display?.hdrCapabilities?.supportedHdrTypes ?: intArrayOf() + if (!capabilities.contains(HDR_TYPE_HLG)) { + throw RuntimeException("Display does not support desired HDR type") + } + // [END android_media_hdr_playback_check_support] +} + +private fun mediaCodecHdrFlow( + surfaceView: SurfaceView, + queue: Queue, + timestamp: Long, + crypto: MediaCrypto?, + offset: Int, + size: Int, + flags: Int, + isStreaming: Boolean, + bitstream: ByteArray +) { + // [START android_media_hdr_playback_mediacodec] + // Check if there's a codec that supports the specific HDR profile. + val list = MediaCodecList(MediaCodecList.REGULAR_CODECS) + var format = MediaFormat() /* media format from the container */ + format.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10) + val codecName = list.findDecoderForFormat(format) ?: throw RuntimeException("No codec supports the format") + + // Here is a standard MediaCodec playback flow. + val codec: MediaCodec = MediaCodec.createByCodecName(codecName) + val surface: Surface = surfaceView.holder.surface + val callback: MediaCodec.Callback = ( + object : MediaCodec.Callback() { + override fun onInputBufferAvailable(codec: MediaCodec, index: Int) { + queue.offer(index) + } + + override fun onOutputBufferAvailable( + codec: MediaCodec, + index: Int, + info: MediaCodec.BufferInfo + ) { + codec.releaseOutputBuffer(index, timestamp) + } + + override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) { + // handle error. + } + + override fun onOutputFormatChanged( + codec: MediaCodec, + format: MediaFormat + ) { + // handle format change. + } + } + ) + + codec.setCallback(callback) + codec.configure(format, surface, crypto, 0 /* flags */) + codec.start() + // [START_EXCLUDE silent] + /* + // [END_EXCLUDE] + while (/* until EOS */) { + // [START_EXCLUDE silent] + */ + while (isStreaming) { + // [END_EXCLUDE] + val index = queue.poll() + val buffer = codec.getInputBuffer(index) + buffer?.put(bitstream) + codec.queueInputBuffer(index, offset, size, timestamp, flags) + } + codec.stop() + codec.release() + // [END android_media_hdr_playback_mediacodec] +} diff --git a/media/src/main/java/com/example/media/MediaProjection.kt b/media/src/main/java/com/example/media/MediaProjection.kt new file mode 100644 index 000000000..6a2ee26ec --- /dev/null +++ b/media/src/main/java/com/example/media/MediaProjection.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.media + +import android.content.Context +import android.hardware.display.DisplayManager +import android.hardware.display.VirtualDisplay +import android.media.projection.MediaProjection +import android.media.projection.MediaProjectionManager +import android.view.Surface +import android.view.WindowManager +import androidx.activity.ComponentActivity +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult + +private class MediaProjectionActivity : ComponentActivity() { + + private var virtualDisplay: VirtualDisplay? = null + + fun startProjection() { + // [START android_media_projection_start] + val mediaProjectionManager = getSystemService(MediaProjectionManager::class.java) + var mediaProjection: MediaProjection? + + val startMediaProjection = registerForActivityResult( + StartActivityForResult() + ) { result -> + if (result.resultCode == RESULT_OK) { + mediaProjection = mediaProjectionManager + .getMediaProjection(result.resultCode, result.data!!) + } + } + + startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent()) + // [END android_media_projection_start] + } + + fun createVirtualDisplayExample( + mediaProjection: MediaProjection, + width: Int, + height: Int, + screenDensity: Int, + surface: Surface + ) { + // [START android_media_projection_virtual_display] + virtualDisplay = mediaProjection.createVirtualDisplay( + "ScreenCapture", + width, + height, + screenDensity, + DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, + surface, + null, null + ) + // [END android_media_projection_virtual_display] + } + + fun getWindowMetricsExample(context: Context) { + // [START android_media_projection_window_context_metrics] + val windowContext = context.createWindowContext( + context.display!!, + WindowManager.LayoutParams.TYPE_APPLICATION, null + ) + val projectionMetrics = windowContext.getSystemService(WindowManager::class.java) + .maximumWindowMetrics + // [END android_media_projection_window_context_metrics] + } +} diff --git a/media/src/main/java/com/example/media/SpatialAudio.kt b/media/src/main/java/com/example/media/SpatialAudio.kt new file mode 100644 index 000000000..cb5761392 --- /dev/null +++ b/media/src/main/java/com/example/media/SpatialAudio.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.media + +import android.content.Context +import android.media.AudioFormat +import android.media.AudioManager +import android.media.MediaFormat +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.trackselection.DefaultTrackSelector + +private fun querySpatializer(audioManager: AudioManager) { + // [START android_media_spatial_audio_get_spatializer] + val spatializer = audioManager.spatializer + // [END android_media_spatial_audio_get_spatializer] +} + +@OptIn(UnstableApi::class) +private fun disableChannelConstraintsPlayer(context: Context, exoPlayer: ExoPlayer) { + // [START android_media_spatial_audio_disable_channel_constraints_player] + exoPlayer.trackSelectionParameters = DefaultTrackSelector.Parameters.Builder(context) + .setConstrainAudioChannelCountToDeviceCapabilities(false) + .build() + // [END android_media_spatial_audio_disable_channel_constraints_player] +} + +@OptIn(UnstableApi::class) +private fun disableChannelConstraintsSelector(context: Context) { + // [START android_media_spatial_audio_disable_channel_constraints_selector] + val trackSelector = DefaultTrackSelector(context) + // ... + trackSelector.parameters = trackSelector.buildUponParameters() + .setConstrainAudioChannelCountToDeviceCapabilities(false) + .build() + // [END android_media_spatial_audio_disable_channel_constraints_selector] +} + +private fun setMaxAudioChannels(exoPlayer: ExoPlayer) { + // [START android_media_spatial_audio_set_max_channels] + exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters + .buildUpon() + .setMaxAudioChannelCount(2) + .build() + // [END android_media_spatial_audio_set_max_channels] +} + +private fun setMaxOutputChannels() { + // [START android_media_spatial_audio_max_output_channels] + val mediaFormat = MediaFormat() + mediaFormat.setInteger(MediaFormat.KEY_MAX_OUTPUT_CHANNEL_COUNT, 99) + // [END android_media_spatial_audio_max_output_channels] +} + +private fun buildSpatialAudioFormat() { + // [START android_media_spatial_audio_audio_format] + val audioFormat = AudioFormat.Builder() + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) + .build() + // [END android_media_spatial_audio_audio_format] +} diff --git a/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt b/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt new file mode 100644 index 000000000..4f501a9cd --- /dev/null +++ b/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.media + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.google.android.gms.media.effect.enhancement.Enhancement +import com.google.android.gms.media.effect.enhancement.EnhancementClient +import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await + +private object SurfaceModeLifecycleSnippet { + // [START android_media_ai_enhancement_surface_initialize_engine] + class MediaSetupViewModel(application: Application) : AndroidViewModel(application) { + private val enhancementClient = Enhancement.getClient(application) + fun initializeEnhancementEngine() { + viewModelScope.launch { + try { + // 1. Verify hardware capability. + val isSupported = enhancementClient.isDeviceSupportedAsync() + if (!isSupported) { + notifyUiDeviceIncompatible() + return@launch + } + // 2. Verify and download the Google Play services ML modules. + val isInstalled = enhancementClient.isModuleInstalledAsync() + if (!isInstalled) { + notifyUiDownloadingModels() + enhancementClient.installModule(installStatusCallback).await() + } + notifyUiEngineReady() + } catch (e: Exception) { + // Handle potential errors during session creation or image + // processing. + handleInitializationError(e) + } + } + } + } + // [END android_media_ai_enhancement_surface_initialize_engine] +} + +private val installStatusCallback = object : EnhancementClient.InstallStatusCallback { + override fun onError(description: String) {} + override fun onCancelled() {} + override fun onDownloadProgressUpdate(progress: Int) {} + override fun onDownloadPending() {} + override fun onDownloadStart() {} + override fun onDownloadPaused() {} + override fun onDownloadComplete() {} + override fun onInstalled() {} +} + +private fun notifyUiDeviceIncompatible() {} + +private fun notifyUiDownloadingModels() {} + +private fun notifyUiEngineReady() {} + +private fun handleInitializationError(e: Exception) {} diff --git a/media/src/main/java/com/example/media/UltraHdrDisplay.kt b/media/src/main/java/com/example/media/UltraHdrDisplay.kt new file mode 100644 index 000000000..1c314cd09 --- /dev/null +++ b/media/src/main/java/com/example/media/UltraHdrDisplay.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.media + +import android.content.pm.ActivityInfo +import android.graphics.BitmapFactory +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.example.media.databinding.FragmentUltraHdrDisplayBinding + +private class UltraHdrDisplayFragment : Fragment() { + + private lateinit var binding: FragmentUltraHdrDisplayBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + binding = FragmentUltraHdrDisplayBinding.inflate(inflater, container, false) + return binding.root + } + + fun displayUltraHdr(imageResId: Int) { + // [START android_media_ultra_hdr_display_window_color_mode] + val bitmap = BitmapFactory.decodeResource(resources, imageResId) + binding.imageContainer.setImageBitmap(bitmap) + + // Set color mode of the activity to the correct color mode. + requireActivity().window.colorMode = + if (bitmap.hasGainmap()) ActivityInfo.COLOR_MODE_HDR else ActivityInfo.COLOR_MODE_DEFAULT + // [END android_media_ultra_hdr_display_window_color_mode] + } +} diff --git a/media/src/main/res/layout/fragment_ultra_hdr_display.xml b/media/src/main/res/layout/fragment_ultra_hdr_display.xml new file mode 100644 index 000000000..6bce8bb59 --- /dev/null +++ b/media/src/main/res/layout/fragment_ultra_hdr_display.xml @@ -0,0 +1,21 @@ + + +