From 1e3761baa08ab4d97d25dd03c324925866b91ec9 Mon Sep 17 00:00:00 2001 From: Katherine Kuan <843428+kkuan2011@users.noreply.github.com> Date: Thu, 27 Aug 2026 05:37:15 +0000 Subject: [PATCH 1/6] Migrate Media II hardcoded snippets to media module --- media/build.gradle.kts | 8 + .../com/example/media/BitmapModeLifecycle.kt | 268 ++++++++++++++++++ .../example/media/GetStartedEnhancement.kt | 36 +++ .../src/main/java/com/example/media/HdrLut.kt | 57 ++++ .../java/com/example/media/HdrPlayback.kt | 114 ++++++++ .../example/media/MediaProjectionSnippets.kt | 93 ++++++ .../java/com/example/media/SpatialAudio.kt | 83 ++++++ .../com/example/media/SurfaceModeLifecycle.kt | 94 ++++++ .../java/com/example/media/UltraHdrDisplay.kt | 53 ++++ 9 files changed, 806 insertions(+) create mode 100644 media/src/main/java/com/example/media/BitmapModeLifecycle.kt create mode 100644 media/src/main/java/com/example/media/GetStartedEnhancement.kt create mode 100644 media/src/main/java/com/example/media/HdrLut.kt create mode 100644 media/src/main/java/com/example/media/HdrPlayback.kt create mode 100644 media/src/main/java/com/example/media/MediaProjectionSnippets.kt create mode 100644 media/src/main/java/com/example/media/SpatialAudio.kt create mode 100644 media/src/main/java/com/example/media/SurfaceModeLifecycle.kt create mode 100644 media/src/main/java/com/example/media/UltraHdrDisplay.kt diff --git a/media/build.gradle.kts b/media/build.gradle.kts index 7d3545b0d..1eb4af135 100644 --- a/media/build.gradle.kts +++ b/media/build.gradle.kts @@ -27,4 +27,12 @@ android { dependencies { implementation(libs.androidx.media3.exoplayer) + implementation(libs.androidx.media3.session) + implementation(libs.androidx.media3.common) + implementation(libs.androidx.media3.ui) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.activity.ktx) + implementation(libs.androidx.fragment.ktx) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.kotlinx.coroutines.play.services) } 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..8dead6a1c --- /dev/null +++ b/media/src/main/java/com/example/media/BitmapModeLifecycle.kt @@ -0,0 +1,268 @@ +/* + * 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.content.Context +import android.graphics.Bitmap +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.google.android.gms.tasks.Task +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().await() + } + notifyUiEngineReady() + } catch (e: Exception) { + + // Handle potential errors during session creation or image processing. + handleInitializationError(e) + } + } + } + // [START_EXCLUDE silent] + private fun notifyUiDeviceIncompatible() {} + private fun notifyUiDownloadingModels() {} + private fun notifyUiEngineReady() {} + private fun handleInitializationError(e: Exception) {} + // [END_EXCLUDE] +} +// [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. + // [START_EXCLUDE silent] + /* + // [END_EXCLUDE] + this.createSession(options, callback).addOnFailureListener(executor) { e -> + if (continuation.isActive) { + continuation.resumeWithException(e) + } + } + // [START_EXCLUDE silent] + */ + this@createSessionAsync.createSession(options, callback).addOnFailureListener(executor) { e -> + if (continuation.isActive) { + continuation.resumeWithException(e) + } + } + // [END_EXCLUDE] + } +} + +// 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. + if (enhancementSession == null) { + val options = EnhancementOptions( + bitmap.width, + bitmap.height, + EnhancementMode.BITMAP, + enableTonemap = true, + enableDeblurDenoise = true, + enableDenoiseOnly = false, + enableUpscale = false, + ) + enhancementSession = enhancementClient.createSessionAsync(options, enhancementExecutor) + } + val session = enhancementSession ?: throw IllegalStateException("Session unavailable.") + // 2. Dispatch image through the neural pipeline + val enhancedBitmap = session.processBitmapAsync(bitmap, session.defaultOptions) + // 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] + +// Shims for Media Enhancement API types if not provided by standalone SDK +object Enhancement { + fun getClient(context: Context): EnhancementClient = object : EnhancementClient { + override fun isDeviceSupported(): Task = TODO() + override fun isModuleInstalled(): Task = TODO() + override fun installModule(): Task = TODO() + override fun createSession(options: EnhancementOptions, callback: EnhancementSessionCallback): Task = TODO() + } +} + +interface EnhancementClient { + fun isDeviceSupported(): Task + fun isModuleInstalled(): Task + fun installModule(): Task + fun createSession(options: EnhancementOptions, callback: EnhancementSessionCallback): Task + suspend fun isDeviceSupportedAsync(): Boolean = false + suspend fun isModuleInstalledAsync(): Boolean = false +} + +interface EnhancementSession { + val defaultOptions: EnhancementOptions + fun process(bitmap: Bitmap, options: EnhancementOptions, callback: EnhancementCallback) + fun release() +} + +interface EnhancementSessionCallback { + fun onSessionCreated(session: EnhancementSession) + fun onSessionCreationFailed(status: Status) + fun onSessionDestroyed() + fun onSessionDisconnected(status: Status) +} + +interface EnhancementCallback { + fun onBitmapProcessed(enhancedBitmap: Bitmap) + fun onError(statusCode: Int) + fun onSurfaceProcessed(timestamp: Long) +} + +enum class EnhancementMode { + BITMAP, + SURFACE +} + +data class EnhancementOptions( + val width: Int, + val height: Int, + val enhancementMode: EnhancementMode, + val enableTonemap: Boolean = false, + val enableDeblurDenoise: Boolean = false, + val enableDenoiseOnly: Boolean = false, + val enableUpscale: Boolean = false, + val enableFaceDetection: Boolean = false +) { + fun setInputSurface(surface: android.view.Surface) {} + fun setOutputSurface(surface: android.view.Surface) {} +} + +data class Status(val statusCode: Int, val statusMessage: String) 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..9e210c057 --- /dev/null +++ b/media/src/main/java/com/example/media/GetStartedEnhancement.kt @@ -0,0 +1,36 @@ +/* + * 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 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..16e513de7 --- /dev/null +++ b/media/src/main/java/com/example/media/HdrLut.kt @@ -0,0 +1,57 @@ +/* + * 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.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) + // [START_EXCLUDE silent] + /* + // [END_EXCLUDE] + SurfaceControl.Transaction().setLuts(sc, luts).apply() + // [START_EXCLUDE silent] + */ + // [END_EXCLUDE] + // [END android_media_hdr_lut_apply] +} + +// Platform API shims for API 36 DisplayLuts +class DisplayLuts { + class Entry( + val data: FloatArray, + val dimension: Int, + val samplingKey: Int + ) + + fun set(entry: Entry) {} + fun set(first: Entry, second: Entry) {} +} + +object LutProperties { + const val ONE_DIMENSION = 1 + const val THREE_DIMENSIONS = 3 + const val SAMPLING_KEY_MAX_RGB = 1 +} 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..edfd6f274 --- /dev/null +++ b/media/src/main/java/com/example/media/HdrPlayback.kt @@ -0,0 +1,114 @@ +/* + * 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.Surface +import android.view.SurfaceView +import java.nio.ByteBuffer +import java.util.ArrayDeque +import java.util.Queue + +// Constant for HDR_TYPE_HLG (Display.HdrCapabilities.HDR_TYPE_HLG) +private const val HDR_TYPE_HLG = 2 + +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 +) { + // [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) + // [START_EXCLUDE silent] + /* + // [END_EXCLUDE] + buffer?.put(/* write bitstream */) + // [START_EXCLUDE silent] + */ + buffer?.put(byteArrayOf()) + // [END_EXCLUDE] + 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/MediaProjectionSnippets.kt b/media/src/main/java/com/example/media/MediaProjectionSnippets.kt new file mode 100644 index 000000000..d8f80c24a --- /dev/null +++ b/media/src/main/java/com/example/media/MediaProjectionSnippets.kt @@ -0,0 +1,93 @@ +/* + * 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.Activity +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 + +class MediaProjectionActivity : ComponentActivity() { + + private var mediaProjection: MediaProjection? = null + private var virtualDisplay: VirtualDisplay? = null + + fun startProjection() { + // [START android_media_projection_start] + val mediaProjectionManager = getSystemService(MediaProjectionManager::class.java) + var mediaProjection : MediaProjection + // [START_EXCLUDE silent] + /* + // [END_EXCLUDE] + val startMediaProjection = registerForActivityResult( + StartActivityForResult() + ) { result -> + if (result.resultCode == RESULT_OK) { + mediaProjection = mediaProjectionManager + .getMediaProjection(result.resultCode, result.data!!) + } + } + // [START_EXCLUDE silent] + */ + val startMediaProjection = registerForActivityResult( + StartActivityForResult() + ) { result -> + if (result.resultCode == Activity.RESULT_OK) { + mediaProjection = mediaProjectionManager + .getMediaProjection(result.resultCode, result.data!!)!! + } + } + // [END_EXCLUDE] + + startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent()) + // [END android_media_projection_start] + } + + fun createVirtualDisplayExample( + width: Int, + height: Int, + screenDensity: Int, + surface: Surface + ) { + val proj = mediaProjection ?: return + // [START android_media_projection_virtual_display] + virtualDisplay = proj.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..0eebdc8bf --- /dev/null +++ b/media/src/main/java/com/example/media/SpatialAudio.kt @@ -0,0 +1,83 @@ +/* + * 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) + // [START_EXCLUDE silent] + /* + // [END_EXCLUDE] + ... + // [START_EXCLUDE silent] + */ + // [END_EXCLUDE] + 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..459d0cc0a --- /dev/null +++ b/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt @@ -0,0 +1,94 @@ +/* + * 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.media.ImageReader +import android.view.Surface +import android.view.SurfaceView +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import java.util.concurrent.Executor +import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await + +// [START android_media_ai_enhancement_surface_initialize_engine] +class SurfaceMediaSetupViewModel(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().await() + } + notifyUiEngineReady() + } catch (e: Exception) { + // Handle potential errors during session creation or image + // processing. + handleInitializationError(e) + } + } + } + // [START_EXCLUDE silent] + private fun notifyUiDeviceIncompatible() {} + private fun notifyUiDownloadingModels() {} + private fun notifyUiEngineReady() {} + private fun handleInitializationError(e: Exception) {} + // [END_EXCLUDE] +} +// [END android_media_ai_enhancement_surface_initialize_engine] + +private suspend fun singleFrameSnapshot( + imageReader: ImageReader, + surfaceView: SurfaceView, + enhancementClient: EnhancementClient, + executor: Executor +) { + // [START android_media_ai_enhancement_surface_snapshot] + // Provisions input Surface (for example, ImageReader) and output Surface (for + // example, SurfaceView) + val inputSurface: Surface = imageReader.surface + val outputSurface: Surface = surfaceView.holder.surface + // 1. Configure parameters for SURFACE mode + val surfaceOptions = EnhancementOptions( + imageReader.width, + imageReader.height, + EnhancementMode.SURFACE, + enableTonemap = true, + enableDeblurDenoise = true, + enableFaceDetection = false + ).also { + // 2. Bind hardware surfaces + it.setInputSurface(inputSurface) + it.setOutputSurface(outputSurface) + } + + // 3. Create the session to process the hardware frame + val singleFrameSession = enhancementClient.createSessionAsync(surfaceOptions, executor) + // The API processes the single frame. Upon completion, release the session. + singleFrameSession.release() + // [END android_media_ai_enhancement_surface_snapshot] +} 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..e7c9f4b95 --- /dev/null +++ b/media/src/main/java/com/example/media/UltraHdrDisplay.kt @@ -0,0 +1,53 @@ +/* + * 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.Activity +import android.content.pm.ActivityInfo +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.fragment.app.Fragment + +class UltraHdrDisplayFragment : Fragment() { + + private val binding = FragmentBinding() + + fun displayUltraHdr(bitmap: Bitmap) { + // [START android_media_ultra_hdr_display_window_color_mode] + val bitmap = /* Get Bitmap from Image Resource */ + // [START_EXCLUDE silent] + /* + // [END_EXCLUDE] + 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 + // [START_EXCLUDE silent] + */ + bitmap + binding.imageContainer.setImageBitmap(bitmap) + requireActivity().window.colorMode = + if (bitmap.hasGainmap()) ActivityInfo.COLOR_MODE_HDR else ActivityInfo.COLOR_MODE_DEFAULT + // [END_EXCLUDE] + // [END android_media_ultra_hdr_display_window_color_mode] + } + + private class FragmentBinding { + val imageContainer = ImageView(null) + } +} From b3a2a5a92ff63e882f9614a3a929fe5c0dfd9470 Mon Sep 17 00:00:00 2001 From: Barbara Law Date: Fri, 4 Sep 2026 08:19:38 -0700 Subject: [PATCH 2/6] migrate media2 snippets --- .../com/example/media/BitmapModeLifecycle.kt | 13 ---- .../src/main/java/com/example/media/HdrLut.kt | 26 +------ .../java/com/example/media/HdrPlayback.kt | 69 +++++++++---------- .../example/media/MediaProjectionSnippets.kt | 49 ++++++------- .../java/com/example/media/SpatialAudio.kt | 16 ++--- .../com/example/media/SurfaceModeLifecycle.kt | 60 ++++++++-------- .../java/com/example/media/UltraHdrDisplay.kt | 18 ++--- 7 files changed, 100 insertions(+), 151 deletions(-) diff --git a/media/src/main/java/com/example/media/BitmapModeLifecycle.kt b/media/src/main/java/com/example/media/BitmapModeLifecycle.kt index 8dead6a1c..4cd2cef9c 100644 --- a/media/src/main/java/com/example/media/BitmapModeLifecycle.kt +++ b/media/src/main/java/com/example/media/BitmapModeLifecycle.kt @@ -94,22 +94,11 @@ suspend fun EnhancementClient.createSessionAsync( } // Handles errors during the initial request trigger. - // [START_EXCLUDE silent] - /* - // [END_EXCLUDE] - this.createSession(options, callback).addOnFailureListener(executor) { e -> - if (continuation.isActive) { - continuation.resumeWithException(e) - } - } - // [START_EXCLUDE silent] - */ this@createSessionAsync.createSession(options, callback).addOnFailureListener(executor) { e -> if (continuation.isActive) { continuation.resumeWithException(e) } } - // [END_EXCLUDE] } } @@ -223,8 +212,6 @@ interface EnhancementClient { fun isModuleInstalled(): Task fun installModule(): Task fun createSession(options: EnhancementOptions, callback: EnhancementSessionCallback): Task - suspend fun isDeviceSupportedAsync(): Boolean = false - suspend fun isModuleInstalledAsync(): Boolean = false } interface EnhancementSession { diff --git a/media/src/main/java/com/example/media/HdrLut.kt b/media/src/main/java/com/example/media/HdrLut.kt index 16e513de7..78e339f00 100644 --- a/media/src/main/java/com/example/media/HdrLut.kt +++ b/media/src/main/java/com/example/media/HdrLut.kt @@ -16,6 +16,8 @@ package com.example.media +import android.hardware.DisplayLuts +import android.hardware.LutProperties import android.view.SurfaceControl private fun applyLutExample() { @@ -28,30 +30,6 @@ private fun applyLutExample() { LutProperties.SAMPLING_KEY_MAX_RGB ) luts.set(entry) - // [START_EXCLUDE silent] - /* - // [END_EXCLUDE] SurfaceControl.Transaction().setLuts(sc, luts).apply() - // [START_EXCLUDE silent] - */ - // [END_EXCLUDE] // [END android_media_hdr_lut_apply] } - -// Platform API shims for API 36 DisplayLuts -class DisplayLuts { - class Entry( - val data: FloatArray, - val dimension: Int, - val samplingKey: Int - ) - - fun set(entry: Entry) {} - fun set(first: Entry, second: Entry) {} -} - -object LutProperties { - const val ONE_DIMENSION = 1 - const val THREE_DIMENSIONS = 3 - const val SAMPLING_KEY_MAX_RGB = 1 -} diff --git a/media/src/main/java/com/example/media/HdrPlayback.kt b/media/src/main/java/com/example/media/HdrPlayback.kt index edfd6f274..e4ed79095 100644 --- a/media/src/main/java/com/example/media/HdrPlayback.kt +++ b/media/src/main/java/com/example/media/HdrPlayback.kt @@ -22,21 +22,17 @@ 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.nio.ByteBuffer -import java.util.ArrayDeque import java.util.Queue -// Constant for HDR_TYPE_HLG (Display.HdrCapabilities.HDR_TYPE_HLG) -private const val HDR_TYPE_HLG = 2 - 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") + throw RuntimeException("Display does not support desired HDR type") } // [END android_media_hdr_playback_check_support] } @@ -61,29 +57,32 @@ private fun mediaCodecHdrFlow( // 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) - } + 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 onOutputBufferAvailable( + codec: MediaCodec, + index: Int, + info: MediaCodec.BufferInfo + ) { + codec.releaseOutputBuffer(index, timestamp) + } - override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) { - // handle error - } + override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) { + // handle error + } - override fun onOutputFormatChanged( - codec: MediaCodec, format: MediaFormat - ) { - // handle format change - } - }) + override fun onOutputFormatChanged( + codec: MediaCodec, + format: MediaFormat + ) { + // handle format change + } + } + ) codec.setCallback(callback) codec.configure(format, surface, crypto, 0 /* flags */) @@ -93,20 +92,20 @@ private fun mediaCodecHdrFlow( // [END_EXCLUDE] while (/* until EOS */) { // [START_EXCLUDE silent] - */ + */ while (isStreaming) { - // [END_EXCLUDE] - val index = queue.poll() - val buffer = codec.getInputBuffer(index) - // [START_EXCLUDE silent] + // [END_EXCLUDE] + val index = queue.poll() + val buffer = codec.getInputBuffer(index) + // [START_EXCLUDE silent] /* // [END_EXCLUDE] buffer?.put(/* write bitstream */) // [START_EXCLUDE silent] - */ - buffer?.put(byteArrayOf()) - // [END_EXCLUDE] - codec.queueInputBuffer(index, offset, size, timestamp, flags) + */ + buffer?.put(byteArrayOf()) + // [END_EXCLUDE] + codec.queueInputBuffer(index, offset, size, timestamp, flags) } codec.stop() codec.release() diff --git a/media/src/main/java/com/example/media/MediaProjectionSnippets.kt b/media/src/main/java/com/example/media/MediaProjectionSnippets.kt index d8f80c24a..a64115854 100644 --- a/media/src/main/java/com/example/media/MediaProjectionSnippets.kt +++ b/media/src/main/java/com/example/media/MediaProjectionSnippets.kt @@ -16,7 +16,6 @@ package com.example.media -import android.app.Activity import android.content.Context import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay @@ -29,65 +28,57 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo class MediaProjectionActivity : ComponentActivity() { - private var mediaProjection: MediaProjection? = null private var virtualDisplay: VirtualDisplay? = null fun startProjection() { // [START android_media_projection_start] val mediaProjectionManager = getSystemService(MediaProjectionManager::class.java) - var mediaProjection : MediaProjection - // [START_EXCLUDE silent] - /* - // [END_EXCLUDE] + var mediaProjection: MediaProjection + val startMediaProjection = registerForActivityResult( StartActivityForResult() ) { result -> if (result.resultCode == RESULT_OK) { mediaProjection = mediaProjectionManager .getMediaProjection(result.resultCode, result.data!!) + // [START_EXCLUDE silent] + ?: error("MediaProjection token missing") + // [END_EXCLUDE] } } - // [START_EXCLUDE silent] - */ - val startMediaProjection = registerForActivityResult( - StartActivityForResult() - ) { result -> - if (result.resultCode == Activity.RESULT_OK) { - mediaProjection = mediaProjectionManager - .getMediaProjection(result.resultCode, result.data!!)!! - } - } - // [END_EXCLUDE] startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent()) // [END android_media_projection_start] } fun createVirtualDisplayExample( + mediaProjection: MediaProjection, width: Int, height: Int, screenDensity: Int, surface: Surface ) { - val proj = mediaProjection ?: return // [START android_media_projection_virtual_display] - virtualDisplay = proj.createVirtualDisplay( - "ScreenCapture", - width, - height, - screenDensity, - DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, - surface, - null, null) + 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 windowContext = context.createWindowContext( + context.display!!, + WindowManager.LayoutParams.TYPE_APPLICATION, null + ) val projectionMetrics = windowContext.getSystemService(WindowManager::class.java) - .maximumWindowMetrics + .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 index 0eebdc8bf..58fa1bf78 100644 --- a/media/src/main/java/com/example/media/SpatialAudio.kt +++ b/media/src/main/java/com/example/media/SpatialAudio.kt @@ -35,8 +35,8 @@ private fun querySpatializer(audioManager: AudioManager) { 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() + .setConstrainAudioChannelCountToDeviceCapabilities(false) + .build() // [END android_media_spatial_audio_disable_channel_constraints_player] } @@ -49,20 +49,20 @@ private fun disableChannelConstraintsSelector(context: Context) { // [END_EXCLUDE] ... // [START_EXCLUDE silent] - */ + */ // [END_EXCLUDE] trackSelector.parameters = trackSelector.buildUponParameters() - .setConstrainAudioChannelCountToDeviceCapabilities(false) - .build() + .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() + .buildUpon() + .setMaxAudioChannelCount(2) + .build() // [END android_media_spatial_audio_set_max_channels] } diff --git a/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt b/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt index 459d0cc0a..ad53af311 100644 --- a/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt +++ b/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt @@ -26,40 +26,42 @@ import java.util.concurrent.Executor import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await -// [START android_media_ai_enhancement_surface_initialize_engine] -class SurfaceMediaSetupViewModel(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 +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().await() + } + notifyUiEngineReady() + } catch (e: Exception) { + // Handle potential errors during session creation or image + // processing. + handleInitializationError(e) } - // 2. Verify and download the Google Play services ML modules - val isInstalled = enhancementClient.isModuleInstalledAsync() - if (!isInstalled) { - notifyUiDownloadingModels() - enhancementClient.installModule().await() - } - notifyUiEngineReady() - } catch (e: Exception) { - // Handle potential errors during session creation or image - // processing. - handleInitializationError(e) } } + // [START_EXCLUDE silent] + private fun notifyUiDeviceIncompatible() {} + private fun notifyUiDownloadingModels() {} + private fun notifyUiEngineReady() {} + private fun handleInitializationError(e: Exception) {} + // [END_EXCLUDE] } - // [START_EXCLUDE silent] - private fun notifyUiDeviceIncompatible() {} - private fun notifyUiDownloadingModels() {} - private fun notifyUiEngineReady() {} - private fun handleInitializationError(e: Exception) {} - // [END_EXCLUDE] + // [END android_media_ai_enhancement_surface_initialize_engine] } -// [END android_media_ai_enhancement_surface_initialize_engine] private suspend fun singleFrameSnapshot( imageReader: ImageReader, diff --git a/media/src/main/java/com/example/media/UltraHdrDisplay.kt b/media/src/main/java/com/example/media/UltraHdrDisplay.kt index e7c9f4b95..723a27c78 100644 --- a/media/src/main/java/com/example/media/UltraHdrDisplay.kt +++ b/media/src/main/java/com/example/media/UltraHdrDisplay.kt @@ -16,7 +16,6 @@ package com.example.media -import android.app.Activity import android.content.pm.ActivityInfo import android.graphics.Bitmap import android.widget.ImageView @@ -24,30 +23,23 @@ import androidx.fragment.app.Fragment class UltraHdrDisplayFragment : Fragment() { - private val binding = FragmentBinding() + private lateinit var binding: FragmentBinding fun displayUltraHdr(bitmap: Bitmap) { // [START android_media_ultra_hdr_display_window_color_mode] val bitmap = /* Get Bitmap from Image Resource */ - // [START_EXCLUDE silent] - /* + // [START_EXCLUDE silent] + bitmap // [END_EXCLUDE] 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 - // [START_EXCLUDE silent] - */ - bitmap - binding.imageContainer.setImageBitmap(bitmap) - requireActivity().window.colorMode = - if (bitmap.hasGainmap()) ActivityInfo.COLOR_MODE_HDR else ActivityInfo.COLOR_MODE_DEFAULT - // [END_EXCLUDE] + if (bitmap.hasGainmap()) ActivityInfo.COLOR_MODE_HDR else ActivityInfo.COLOR_MODE_DEFAULT // [END android_media_ultra_hdr_display_window_color_mode] } private class FragmentBinding { - val imageContainer = ImageView(null) + lateinit var imageContainer: ImageView } } From ef6f42cb1b5b3422d6fdfc9ce59e24c3f0024efe Mon Sep 17 00:00:00 2001 From: Barbara Law Date: Thu, 17 Sep 2026 15:15:34 -0700 Subject: [PATCH 3/6] Address review nits in Media snippets. Alphabetize :media dependencies, rename MediaProjection.kt, replace the spatial-audio ellipsis sandwich with // ..., and fix ViewModel comment / HDR buffer indent plus Surface imports. --- media/build.gradle.kts | 8 ++++---- .../com/example/media/BitmapModeLifecycle.kt | 19 ++++++++++--------- .../java/com/example/media/HdrPlayback.kt | 10 +++++----- ...ojectionSnippets.kt => MediaProjection.kt} | 0 .../java/com/example/media/SpatialAudio.kt | 8 +------- 5 files changed, 20 insertions(+), 25 deletions(-) rename media/src/main/java/com/example/media/{MediaProjectionSnippets.kt => MediaProjection.kt} (100%) diff --git a/media/build.gradle.kts b/media/build.gradle.kts index 1eb4af135..e0bdd9155 100644 --- a/media/build.gradle.kts +++ b/media/build.gradle.kts @@ -26,13 +26,13 @@ android { } 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.androidx.media3.session) - implementation(libs.androidx.media3.common) implementation(libs.androidx.media3.ui) - implementation(libs.androidx.lifecycle.viewmodel.ktx) - implementation(libs.androidx.activity.ktx) - implementation(libs.androidx.fragment.ktx) implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.coroutines.play.services) } diff --git a/media/src/main/java/com/example/media/BitmapModeLifecycle.kt b/media/src/main/java/com/example/media/BitmapModeLifecycle.kt index 4cd2cef9c..cacd8f67d 100644 --- a/media/src/main/java/com/example/media/BitmapModeLifecycle.kt +++ b/media/src/main/java/com/example/media/BitmapModeLifecycle.kt @@ -19,6 +19,7 @@ package com.example.media import android.app.Application import android.content.Context import android.graphics.Bitmap +import android.view.Surface import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.google.android.gms.tasks.Task @@ -141,24 +142,24 @@ class EnhancementViewModel(application: Application) : AndroidViewModel(applicat // Publicly exposed UI state flow for observation. val uiState: StateFlow = _uiState.asStateFlow() -// Initialize client to interact with the Media Enhancement service. + // Initialize client to interact with the Media Enhancement service. private val enhancementClient: EnhancementClient = Enhancement.getClient(application) -// Single-thread executor for processing background enhancement tasks. + // Single-thread executor for processing background enhancement tasks. private val enhancementExecutor = Executors.newSingleThreadExecutor() -// Track session state to enable reuse across multiple processing calls. + // 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. + // 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. + // Define enhancement options (for example, enable upscale, tonemapping) based + // on bitmap dimensions. if (enhancementSession == null) { val options = EnhancementOptions( bitmap.width, @@ -182,7 +183,7 @@ class EnhancementViewModel(application: Application) : AndroidViewModel(applicat _uiState.update { it.copy(enhancementError = e.message) } } finally { -// Ensure loading state is reset regardless of the outcome. + // Ensure loading state is reset regardless of the outcome. _uiState.update { it.copy(isLoading = false) } } } @@ -248,8 +249,8 @@ data class EnhancementOptions( val enableUpscale: Boolean = false, val enableFaceDetection: Boolean = false ) { - fun setInputSurface(surface: android.view.Surface) {} - fun setOutputSurface(surface: android.view.Surface) {} + fun setInputSurface(surface: Surface) {} + fun setOutputSurface(surface: Surface) {} } data class Status(val statusCode: Int, val statusMessage: String) diff --git a/media/src/main/java/com/example/media/HdrPlayback.kt b/media/src/main/java/com/example/media/HdrPlayback.kt index e4ed79095..1ca3deee8 100644 --- a/media/src/main/java/com/example/media/HdrPlayback.kt +++ b/media/src/main/java/com/example/media/HdrPlayback.kt @@ -98,11 +98,11 @@ private fun mediaCodecHdrFlow( val index = queue.poll() val buffer = codec.getInputBuffer(index) // [START_EXCLUDE silent] - /* - // [END_EXCLUDE] - buffer?.put(/* write bitstream */) - // [START_EXCLUDE silent] - */ + /* + // [END_EXCLUDE] + buffer?.put(/* write bitstream */) + // [START_EXCLUDE silent] + */ buffer?.put(byteArrayOf()) // [END_EXCLUDE] codec.queueInputBuffer(index, offset, size, timestamp, flags) diff --git a/media/src/main/java/com/example/media/MediaProjectionSnippets.kt b/media/src/main/java/com/example/media/MediaProjection.kt similarity index 100% rename from media/src/main/java/com/example/media/MediaProjectionSnippets.kt rename to media/src/main/java/com/example/media/MediaProjection.kt diff --git a/media/src/main/java/com/example/media/SpatialAudio.kt b/media/src/main/java/com/example/media/SpatialAudio.kt index 58fa1bf78..cb5761392 100644 --- a/media/src/main/java/com/example/media/SpatialAudio.kt +++ b/media/src/main/java/com/example/media/SpatialAudio.kt @@ -44,13 +44,7 @@ private fun disableChannelConstraintsPlayer(context: Context, exoPlayer: ExoPlay private fun disableChannelConstraintsSelector(context: Context) { // [START android_media_spatial_audio_disable_channel_constraints_selector] val trackSelector = DefaultTrackSelector(context) - // [START_EXCLUDE silent] - /* - // [END_EXCLUDE] - ... - // [START_EXCLUDE silent] - */ - // [END_EXCLUDE] + // ... trackSelector.parameters = trackSelector.buildUponParameters() .setConstrainAudioChannelCountToDeviceCapabilities(false) .build() From 710dee4baf208a86611317dfffb974a7d4af1175 Mon Sep 17 00:00:00 2001 From: Barbara Law Date: Wed, 30 Sep 2026 21:05:25 +0000 Subject: [PATCH 4/6] Use the real media enhancement library and address review feedback - Replace the compile-only shims with the published com.google.android.gms:play-services-media-effect-enhancement library, pinned to 16.0.0-beta04 (the version the get-started page documents). Where page code doesn't compile against the real API, use the real API with the smallest visible change (review feedback). - Remove the Surface-mode snapshot region. The page describes an API the library doesn't have, so that block stays hardcoded on DAC and is being reported to the page owner. - Ultra HDR: make the host Fragment private, replace the stand-in binding class with a real generated ViewBinding (new layout, viewBinding enabled in :media) and use a hidden twin for the placeholder. Rendered output is unchanged. - Make MediaProjectionActivity private. - End natural-language comments inside regions with a full stop. --- gradle/libs.versions.toml | 2 + media/build.gradle.kts | 5 + .../com/example/media/BitmapModeLifecycle.kt | 113 ++++++------------ .../example/media/GetStartedEnhancement.kt | 5 +- .../java/com/example/media/HdrPlayback.kt | 10 +- .../java/com/example/media/MediaProjection.kt | 2 +- .../com/example/media/SurfaceModeLifecycle.kt | 54 +++------ .../java/com/example/media/UltraHdrDisplay.kt | 33 +++-- .../res/layout/fragment_ultra_hdr_display.xml | 21 ++++ 9 files changed, 111 insertions(+), 134 deletions(-) create mode 100644 media/src/main/res/layout/fragment_ultra_hdr_display.xml 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 e0bdd9155..4dee1c0a2 100644 --- a/media/build.gradle.kts +++ b/media/build.gradle.kts @@ -23,6 +23,10 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + + buildFeatures { + viewBinding = true + } } dependencies { @@ -35,4 +39,5 @@ dependencies { implementation(libs.androidx.media3.ui) 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 index cacd8f67d..49012b116 100644 --- a/media/src/main/java/com/example/media/BitmapModeLifecycle.kt +++ b/media/src/main/java/com/example/media/BitmapModeLifecycle.kt @@ -17,12 +17,17 @@ package com.example.media import android.app.Application -import android.content.Context import android.graphics.Bitmap -import android.view.Surface import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import com.google.android.gms.tasks.Task +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 @@ -43,17 +48,17 @@ class MediaSetupViewModel(application: Application) : AndroidViewModel(applicati fun initializeEnhancementEngine() { viewModelScope.launch { try { - // 1. Verify hardware capability + // 1. Verify hardware capability. val isSupported = enhancementClient.isDeviceSupportedAsync() if (!isSupported) { notifyUiDeviceIncompatible() return@launch } - // 2. Verify and download the Google Play services ML modules + // 2. Verify and download the Google Play services ML modules. val isInstalled = enhancementClient.isModuleInstalledAsync() if (!isInstalled) { notifyUiDownloadingModels() - enhancementClient.installModule().await() + enhancementClient.installModule(installStatusCallback).await() } notifyUiEngineReady() } catch (e: Exception) { @@ -64,6 +69,16 @@ class MediaSetupViewModel(application: Application) : AndroidViewModel(applicati } } // [START_EXCLUDE silent] + 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() {} @@ -156,26 +171,27 @@ class EnhancementViewModel(application: Application) : AndroidViewModel(applicat viewModelScope.launch(Dispatchers.IO) { _uiState.update { it.copy(isLoading = true, enhancementError = null) } try { - // 1. Establish the session lazily on demand + // 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) { - val options = EnhancementOptions( - bitmap.width, - bitmap.height, - EnhancementMode.BITMAP, - enableTonemap = true, - enableDeblurDenoise = true, - enableDenoiseOnly = false, - enableUpscale = false, - ) enhancementSession = enhancementClient.createSessionAsync(options, enhancementExecutor) } val session = enhancementSession ?: throw IllegalStateException("Session unavailable.") - // 2. Dispatch image through the neural pipeline - val enhancedBitmap = session.processBitmapAsync(bitmap, session.defaultOptions) - // 3. Render output to UI + // 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)) } @@ -189,7 +205,7 @@ class EnhancementViewModel(application: Application) : AndroidViewModel(applicat } } override fun onCleared() { - // 4. Critical: Release native GPU hardware resources + // 4. Critical: Release native GPU hardware resources. enhancementSession?.release() enhancementSession = null enhancementExecutor.shutdown() @@ -197,60 +213,3 @@ class EnhancementViewModel(application: Application) : AndroidViewModel(applicat } } // [END android_media_ai_enhancement_bitmap_viewmodel] - -// Shims for Media Enhancement API types if not provided by standalone SDK -object Enhancement { - fun getClient(context: Context): EnhancementClient = object : EnhancementClient { - override fun isDeviceSupported(): Task = TODO() - override fun isModuleInstalled(): Task = TODO() - override fun installModule(): Task = TODO() - override fun createSession(options: EnhancementOptions, callback: EnhancementSessionCallback): Task = TODO() - } -} - -interface EnhancementClient { - fun isDeviceSupported(): Task - fun isModuleInstalled(): Task - fun installModule(): Task - fun createSession(options: EnhancementOptions, callback: EnhancementSessionCallback): Task -} - -interface EnhancementSession { - val defaultOptions: EnhancementOptions - fun process(bitmap: Bitmap, options: EnhancementOptions, callback: EnhancementCallback) - fun release() -} - -interface EnhancementSessionCallback { - fun onSessionCreated(session: EnhancementSession) - fun onSessionCreationFailed(status: Status) - fun onSessionDestroyed() - fun onSessionDisconnected(status: Status) -} - -interface EnhancementCallback { - fun onBitmapProcessed(enhancedBitmap: Bitmap) - fun onError(statusCode: Int) - fun onSurfaceProcessed(timestamp: Long) -} - -enum class EnhancementMode { - BITMAP, - SURFACE -} - -data class EnhancementOptions( - val width: Int, - val height: Int, - val enhancementMode: EnhancementMode, - val enableTonemap: Boolean = false, - val enableDeblurDenoise: Boolean = false, - val enableDenoiseOnly: Boolean = false, - val enableUpscale: Boolean = false, - val enableFaceDetection: Boolean = false -) { - fun setInputSurface(surface: Surface) {} - fun setOutputSurface(surface: Surface) {} -} - -data class Status(val statusCode: Int, val statusMessage: String) diff --git a/media/src/main/java/com/example/media/GetStartedEnhancement.kt b/media/src/main/java/com/example/media/GetStartedEnhancement.kt index 9e210c057..95b5cc880 100644 --- a/media/src/main/java/com/example/media/GetStartedEnhancement.kt +++ b/media/src/main/java/com/example/media/GetStartedEnhancement.kt @@ -16,18 +16,19 @@ 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 +// 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 +// Verifies the presence of required neural network models. suspend fun EnhancementClient.isModuleInstalledAsync(): Boolean = suspendCancellableCoroutine { continuation -> this.isModuleInstalled() .addOnSuccessListener { result -> continuation.resume(result) } diff --git a/media/src/main/java/com/example/media/HdrPlayback.kt b/media/src/main/java/com/example/media/HdrPlayback.kt index 1ca3deee8..5fb0beeff 100644 --- a/media/src/main/java/com/example/media/HdrPlayback.kt +++ b/media/src/main/java/com/example/media/HdrPlayback.kt @@ -29,7 +29,7 @@ import java.util.Queue private fun checkHdrSupport(display: Display?) { // [START android_media_hdr_playback_check_support] - // Check if display supports the HDR type + // 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") @@ -48,13 +48,13 @@ private fun mediaCodecHdrFlow( isStreaming: Boolean ) { // [START android_media_hdr_playback_mediacodec] - // Check if there's a codec that supports the specific HDR profile + // 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 + // Here is a standard MediaCodec playback flow. val codec: MediaCodec = MediaCodec.createByCodecName(codecName) val surface: Surface = surfaceView.holder.surface val callback: MediaCodec.Callback = ( @@ -72,14 +72,14 @@ private fun mediaCodecHdrFlow( } override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) { - // handle error + // handle error. } override fun onOutputFormatChanged( codec: MediaCodec, format: MediaFormat ) { - // handle format change + // handle format change. } } ) diff --git a/media/src/main/java/com/example/media/MediaProjection.kt b/media/src/main/java/com/example/media/MediaProjection.kt index a64115854..3586b6ebf 100644 --- a/media/src/main/java/com/example/media/MediaProjection.kt +++ b/media/src/main/java/com/example/media/MediaProjection.kt @@ -26,7 +26,7 @@ import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult -class MediaProjectionActivity : ComponentActivity() { +private class MediaProjectionActivity : ComponentActivity() { private var virtualDisplay: VirtualDisplay? = null diff --git a/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt b/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt index ad53af311..02d3fc11c 100644 --- a/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt +++ b/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt @@ -17,12 +17,10 @@ package com.example.media import android.app.Application -import android.media.ImageReader -import android.view.Surface -import android.view.SurfaceView import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import java.util.concurrent.Executor +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 @@ -33,17 +31,17 @@ private object SurfaceModeLifecycleSnippet { fun initializeEnhancementEngine() { viewModelScope.launch { try { - // 1. Verify hardware capability + // 1. Verify hardware capability. val isSupported = enhancementClient.isDeviceSupportedAsync() if (!isSupported) { notifyUiDeviceIncompatible() return@launch } - // 2. Verify and download the Google Play services ML modules + // 2. Verify and download the Google Play services ML modules. val isInstalled = enhancementClient.isModuleInstalledAsync() if (!isInstalled) { notifyUiDownloadingModels() - enhancementClient.installModule().await() + enhancementClient.installModule(installStatusCallback).await() } notifyUiEngineReady() } catch (e: Exception) { @@ -54,6 +52,16 @@ private object SurfaceModeLifecycleSnippet { } } // [START_EXCLUDE silent] + 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() {} @@ -62,35 +70,3 @@ private object SurfaceModeLifecycleSnippet { } // [END android_media_ai_enhancement_surface_initialize_engine] } - -private suspend fun singleFrameSnapshot( - imageReader: ImageReader, - surfaceView: SurfaceView, - enhancementClient: EnhancementClient, - executor: Executor -) { - // [START android_media_ai_enhancement_surface_snapshot] - // Provisions input Surface (for example, ImageReader) and output Surface (for - // example, SurfaceView) - val inputSurface: Surface = imageReader.surface - val outputSurface: Surface = surfaceView.holder.surface - // 1. Configure parameters for SURFACE mode - val surfaceOptions = EnhancementOptions( - imageReader.width, - imageReader.height, - EnhancementMode.SURFACE, - enableTonemap = true, - enableDeblurDenoise = true, - enableFaceDetection = false - ).also { - // 2. Bind hardware surfaces - it.setInputSurface(inputSurface) - it.setOutputSurface(outputSurface) - } - - // 3. Create the session to process the hardware frame - val singleFrameSession = enhancementClient.createSessionAsync(surfaceOptions, executor) - // The API processes the single frame. Upon completion, release the session. - singleFrameSession.release() - // [END android_media_ai_enhancement_surface_snapshot] -} diff --git a/media/src/main/java/com/example/media/UltraHdrDisplay.kt b/media/src/main/java/com/example/media/UltraHdrDisplay.kt index 723a27c78..29af1f048 100644 --- a/media/src/main/java/com/example/media/UltraHdrDisplay.kt +++ b/media/src/main/java/com/example/media/UltraHdrDisplay.kt @@ -18,18 +18,35 @@ package com.example.media import android.content.pm.ActivityInfo import android.graphics.Bitmap -import android.widget.ImageView +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 -class UltraHdrDisplayFragment : Fragment() { +private class UltraHdrDisplayFragment : Fragment() { - private lateinit var binding: FragmentBinding + private lateinit var binding: FragmentUltraHdrDisplayBinding - fun displayUltraHdr(bitmap: Bitmap) { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + binding = FragmentUltraHdrDisplayBinding.inflate(inflater, container, false) + return binding.root + } + + fun displayUltraHdr(loadedBitmap: Bitmap) { // [START android_media_ultra_hdr_display_window_color_mode] + // [START_EXCLUDE silent] + /* + // [END_EXCLUDE] val bitmap = /* Get Bitmap from Image Resource */ - // [START_EXCLUDE silent] - bitmap + // [START_EXCLUDE silent] + */ + val bitmap = loadedBitmap // [END_EXCLUDE] binding.imageContainer.setImageBitmap(bitmap) @@ -38,8 +55,4 @@ class UltraHdrDisplayFragment : Fragment() { if (bitmap.hasGainmap()) ActivityInfo.COLOR_MODE_HDR else ActivityInfo.COLOR_MODE_DEFAULT // [END android_media_ultra_hdr_display_window_color_mode] } - - private class FragmentBinding { - lateinit var imageContainer: ImageView - } } 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 @@ + + + From 7d9151a86d09d649a9774d8890680581856abc7c Mon Sep 17 00:00:00 2001 From: Barbara Law Date: Fri, 2 Oct 2026 20:48:42 +0000 Subject: [PATCH 5/6] Address internal review feedback on Media II snippets Make the projection_start nullability fix visible, simplify createSession, move compile-only helpers out of the regions to the bottom of their files, and drop the unused media3 session and ui dependencies. --- media/build.gradle.kts | 2 - .../com/example/media/BitmapModeLifecycle.kt | 37 ++++++++++--------- .../java/com/example/media/MediaProjection.kt | 5 +-- .../com/example/media/SurfaceModeLifecycle.kt | 35 ++++++++++-------- 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/media/build.gradle.kts b/media/build.gradle.kts index 4dee1c0a2..6b12b3daa 100644 --- a/media/build.gradle.kts +++ b/media/build.gradle.kts @@ -35,8 +35,6 @@ dependencies { implementation(libs.androidx.lifecycle.viewmodel.ktx) implementation(libs.androidx.media3.common) implementation(libs.androidx.media3.exoplayer) - implementation(libs.androidx.media3.session) - implementation(libs.androidx.media3.ui) 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 index 49012b116..6b7255b86 100644 --- a/media/src/main/java/com/example/media/BitmapModeLifecycle.kt +++ b/media/src/main/java/com/example/media/BitmapModeLifecycle.kt @@ -68,22 +68,6 @@ class MediaSetupViewModel(application: Application) : AndroidViewModel(applicati } } } - // [START_EXCLUDE silent] - 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) {} - // [END_EXCLUDE] } // [END android_media_ai_enhancement_bitmap_initialize_engine] @@ -110,7 +94,7 @@ suspend fun EnhancementClient.createSessionAsync( } // Handles errors during the initial request trigger. - this@createSessionAsync.createSession(options, callback).addOnFailureListener(executor) { e -> + createSession(options, callback).addOnFailureListener(executor) { e -> if (continuation.isActive) { continuation.resumeWithException(e) } @@ -213,3 +197,22 @@ class EnhancementViewModel(application: Application) : AndroidViewModel(applicat } } // [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/MediaProjection.kt b/media/src/main/java/com/example/media/MediaProjection.kt index 3586b6ebf..6a2ee26ec 100644 --- a/media/src/main/java/com/example/media/MediaProjection.kt +++ b/media/src/main/java/com/example/media/MediaProjection.kt @@ -33,7 +33,7 @@ private class MediaProjectionActivity : ComponentActivity() { fun startProjection() { // [START android_media_projection_start] val mediaProjectionManager = getSystemService(MediaProjectionManager::class.java) - var mediaProjection: MediaProjection + var mediaProjection: MediaProjection? val startMediaProjection = registerForActivityResult( StartActivityForResult() @@ -41,9 +41,6 @@ private class MediaProjectionActivity : ComponentActivity() { if (result.resultCode == RESULT_OK) { mediaProjection = mediaProjectionManager .getMediaProjection(result.resultCode, result.data!!) - // [START_EXCLUDE silent] - ?: error("MediaProjection token missing") - // [END_EXCLUDE] } } diff --git a/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt b/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt index 02d3fc11c..4f501a9cd 100644 --- a/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt +++ b/media/src/main/java/com/example/media/SurfaceModeLifecycle.kt @@ -51,22 +51,25 @@ private object SurfaceModeLifecycleSnippet { } } } - // [START_EXCLUDE silent] - 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) {} - // [END_EXCLUDE] } // [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) {} From dbb14b4d3e33633b4fa028a590627c17a1c1bc6e Mon Sep 17 00:00:00 2001 From: Barbara Law Date: Fri, 9 Oct 2026 17:31:16 +0000 Subject: [PATCH 6/6] Replace HDR and Ultra HDR placeholder twins with visible code --- .../src/main/java/com/example/media/HdrPlayback.kt | 12 +++--------- .../main/java/com/example/media/UltraHdrDisplay.kt | 13 +++---------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/media/src/main/java/com/example/media/HdrPlayback.kt b/media/src/main/java/com/example/media/HdrPlayback.kt index 5fb0beeff..76ab33c45 100644 --- a/media/src/main/java/com/example/media/HdrPlayback.kt +++ b/media/src/main/java/com/example/media/HdrPlayback.kt @@ -45,7 +45,8 @@ private fun mediaCodecHdrFlow( offset: Int, size: Int, flags: Int, - isStreaming: Boolean + isStreaming: Boolean, + bitstream: ByteArray ) { // [START android_media_hdr_playback_mediacodec] // Check if there's a codec that supports the specific HDR profile. @@ -97,14 +98,7 @@ private fun mediaCodecHdrFlow( // [END_EXCLUDE] val index = queue.poll() val buffer = codec.getInputBuffer(index) - // [START_EXCLUDE silent] - /* - // [END_EXCLUDE] - buffer?.put(/* write bitstream */) - // [START_EXCLUDE silent] - */ - buffer?.put(byteArrayOf()) - // [END_EXCLUDE] + buffer?.put(bitstream) codec.queueInputBuffer(index, offset, size, timestamp, flags) } codec.stop() diff --git a/media/src/main/java/com/example/media/UltraHdrDisplay.kt b/media/src/main/java/com/example/media/UltraHdrDisplay.kt index 29af1f048..1c314cd09 100644 --- a/media/src/main/java/com/example/media/UltraHdrDisplay.kt +++ b/media/src/main/java/com/example/media/UltraHdrDisplay.kt @@ -17,7 +17,7 @@ package com.example.media import android.content.pm.ActivityInfo -import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -38,16 +38,9 @@ private class UltraHdrDisplayFragment : Fragment() { return binding.root } - fun displayUltraHdr(loadedBitmap: Bitmap) { + fun displayUltraHdr(imageResId: Int) { // [START android_media_ultra_hdr_display_window_color_mode] - // [START_EXCLUDE silent] - /* - // [END_EXCLUDE] - val bitmap = /* Get Bitmap from Image Resource */ - // [START_EXCLUDE silent] - */ - val bitmap = loadedBitmap - // [END_EXCLUDE] + val bitmap = BitmapFactory.decodeResource(resources, imageResId) binding.imageContainer.setImageBitmap(bitmap) // Set color mode of the activity to the correct color mode.