diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f7e2b5470..351974d06 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,12 +28,9 @@ androidx-test-junit = "1.2.1" androidx-window = "1.5.0-alpha02" androidx-window-core = "1.5.0-alpha02" androidx-window-java = "1.5.0-alpha02" -# @keep -androidx-xr = "1.0.0-alpha03" -# @keep -androidx-xr-arcore = "1.0.0-alpha04" -androidx-xr-compose = "1.0.0-alpha04" -androidx-xr-scenecore = "1.0.0-alpha04" +androidx-xr-arcore = "1.0.0-alpha05" +androidx-xr-scenecore = "1.0.0-alpha05" +androidx-xr-compose = "1.0.0-alpha05" androidxHiltNavigationCompose = "1.2.0" appcompat = "1.7.1" coil = "2.7.0" diff --git a/xr/build.gradle.kts b/xr/build.gradle.kts index 74ad5bfe9..138383c8d 100644 --- a/xr/build.gradle.kts +++ b/xr/build.gradle.kts @@ -25,6 +25,9 @@ android { buildFeatures { compose = true } + lint { + disable += "RestrictedApi" + } } dependencies { @@ -33,8 +36,6 @@ dependencies { implementation(libs.androidx.xr.compose) implementation(libs.androidx.activity.ktx) - implementation(libs.guava) - implementation(libs.kotlinx.coroutines.guava) implementation(libs.androidx.media3.exoplayer) diff --git a/xr/src/main/java/com/example/xr/arcore/Anchors.kt b/xr/src/main/java/com/example/xr/arcore/Anchors.kt index cb4992095..56c7cb8d7 100644 --- a/xr/src/main/java/com/example/xr/arcore/Anchors.kt +++ b/xr/src/main/java/com/example/xr/arcore/Anchors.kt @@ -22,25 +22,23 @@ import androidx.xr.arcore.Trackable import androidx.xr.runtime.Config import androidx.xr.runtime.Session import androidx.xr.runtime.SessionConfigureConfigurationNotSupported -import androidx.xr.runtime.SessionConfigurePermissionsNotGranted import androidx.xr.runtime.SessionConfigureSuccess import androidx.xr.runtime.math.Pose import androidx.xr.scenecore.AnchorEntity import androidx.xr.scenecore.Entity import androidx.xr.scenecore.scene -@Suppress("RestrictedApi") // b/416288516 - session.config and session.configure() are incorrectly restricted fun configureAnchoring(session: Session) { // [START androidxr_arcore_anchoring_configure] val newConfig = session.config.copy( - anchorPersistence = Config.AnchorPersistenceMode.Enabled, + anchorPersistence = Config.AnchorPersistenceMode.LOCAL, ) when (val result = session.configure(newConfig)) { is SessionConfigureConfigurationNotSupported -> TODO(/* Some combinations of configurations are not valid. Handle this failure case. */) - is SessionConfigurePermissionsNotGranted -> - TODO(/* The required permissions in result.permissions have not been granted. */) is SessionConfigureSuccess -> TODO(/* Success! */) + else -> + TODO(/* A different unhandled exception was thrown. */) } // [END androidxr_arcore_anchoring_configure] } @@ -72,7 +70,7 @@ private fun attachEntityToAnchor( ) { // [START androidxr_arcore_entity_tracks_anchor] AnchorEntity.create(session, anchor).apply { - setParent(session.scene.activitySpace) + parent = session.scene.activitySpace addChild(entity) } // [END androidxr_arcore_entity_tracks_anchor] diff --git a/xr/src/main/java/com/example/xr/arcore/Hands.kt b/xr/src/main/java/com/example/xr/arcore/Hands.kt index 13346b202..1507bed7c 100644 --- a/xr/src/main/java/com/example/xr/arcore/Hands.kt +++ b/xr/src/main/java/com/example/xr/arcore/Hands.kt @@ -24,7 +24,6 @@ import androidx.xr.runtime.Config import androidx.xr.runtime.HandJointType import androidx.xr.runtime.Session import androidx.xr.runtime.SessionConfigureConfigurationNotSupported -import androidx.xr.runtime.SessionConfigurePermissionsNotGranted import androidx.xr.runtime.SessionConfigureSuccess import androidx.xr.runtime.math.Pose import androidx.xr.runtime.math.Quaternion @@ -35,18 +34,17 @@ import androidx.xr.scenecore.scene import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch -@Suppress("RestrictedApi") // b/416288516 - session.config and session.configure() are incorrectly restricted fun ComponentActivity.configureSession(session: Session) { // [START androidxr_arcore_hand_configure] val newConfig = session.config.copy( - handTracking = Config.HandTrackingMode.Enabled + handTracking = Config.HandTrackingMode.BOTH ) when (val result = session.configure(newConfig)) { is SessionConfigureConfigurationNotSupported -> TODO(/* Some combinations of configurations are not valid. Handle this failure case. */) - is SessionConfigurePermissionsNotGranted -> - TODO(/* The required permissions in result.permissions have not been granted. */) is SessionConfigureSuccess -> TODO(/* Success! */) + else -> + TODO(/* A different unhandled exception was thrown. */) } // [END androidxr_arcore_hand_configure] } @@ -71,8 +69,8 @@ fun ComponentActivity.collectHands(session: Session) { fun secondaryHandDetection(activity: Activity, session: Session) { fun detectGesture(handState: Flow) {} // [START androidxr_arcore_hand_handedness] - val handedness = Hand.getHandedness(activity.contentResolver) - val secondaryHand = if (handedness == Hand.Handedness.LEFT) Hand.right(session) else Hand.left(session) + val handedness = Hand.getPrimaryHandSide(activity.contentResolver) + val secondaryHand = if (handedness == Hand.HandSide.LEFT) Hand.right(session) else Hand.left(session) val handState = secondaryHand?.state ?: return detectGesture(handState) // [END androidxr_arcore_hand_handedness] @@ -86,7 +84,7 @@ fun ComponentActivity.renderPlanetAtHandPalm(leftHandState: Hand.State) { // the down direction points in the same direction as the palm val angle = Vector3.angleBetween(palmPose.rotation * Vector3.Down, Vector3.Up) - palmEntity.setHidden(angle > Math.toRadians(40.0)) + palmEntity.setEnabled(angle > Math.toRadians(40.0)) val transformedPose = session.scene.perceptionSpace.transformPoseTo( @@ -107,7 +105,7 @@ fun ComponentActivity.renderPlanetAtFingerTip(rightHandState: Hand.State) { // the forward direction points towards the finger tip. val angle = Vector3.angleBetween(tipPose.rotation * Vector3.Forward, Vector3.Up) - indexFingerEntity.setHidden(angle > Math.toRadians(40.0)) + indexFingerEntity.setEnabled(angle > Math.toRadians(40.0)) val transformedPose = session.scene.perceptionSpace.transformPoseTo( diff --git a/xr/src/main/java/com/example/xr/arcore/Planes.kt b/xr/src/main/java/com/example/xr/arcore/Planes.kt index fd5e02c11..b5017dcb7 100644 --- a/xr/src/main/java/com/example/xr/arcore/Planes.kt +++ b/xr/src/main/java/com/example/xr/arcore/Planes.kt @@ -20,24 +20,22 @@ import androidx.xr.arcore.Plane import androidx.xr.runtime.Config import androidx.xr.runtime.Session import androidx.xr.runtime.SessionConfigureConfigurationNotSupported -import androidx.xr.runtime.SessionConfigurePermissionsNotGranted import androidx.xr.runtime.SessionConfigureSuccess import androidx.xr.runtime.math.Pose import androidx.xr.runtime.math.Ray import androidx.xr.scenecore.scene -@Suppress("RestrictedApi") // b/416288516 - session.config and session.configure() are incorrectly restricted fun configurePlaneTracking(session: Session) { // [START androidxr_arcore_planetracking_configure] val newConfig = session.config.copy( - planeTracking = Config.PlaneTrackingMode.HorizontalAndVertical, + planeTracking = Config.PlaneTrackingMode.HORIZONTAL_AND_VERTICAL, ) when (val result = session.configure(newConfig)) { is SessionConfigureConfigurationNotSupported -> TODO(/* Some combinations of configurations are not valid. Handle this failure case. */) - is SessionConfigurePermissionsNotGranted -> - TODO(/* The required permissions in result.permissions have not been granted. */) is SessionConfigureSuccess -> TODO(/* Success! */) + else -> + TODO(/* A different unhandled exception was thrown. */) } // [END androidxr_arcore_planetracking_configure] } @@ -58,7 +56,7 @@ private fun hitTestTable(session: Session) { // When interested in the first Table hit: val tableHit = results.firstOrNull { val trackable = it.trackable - trackable is Plane && trackable.state.value.label == Plane.Label.Table + trackable is Plane && trackable.state.value.label == Plane.Label.TABLE } // [END androidxr_arcore_hitTest] } diff --git a/xr/src/main/java/com/example/xr/compose/Orbiter.kt b/xr/src/main/java/com/example/xr/compose/Orbiter.kt index 364709c83..f01c4cd1f 100644 --- a/xr/src/main/java/com/example/xr/compose/Orbiter.kt +++ b/xr/src/main/java/com/example/xr/compose/Orbiter.kt @@ -25,8 +25,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CornerSize -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -36,9 +36,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.xr.compose.spatial.EdgeOffset +import androidx.xr.compose.spatial.ContentEdge import androidx.xr.compose.spatial.Orbiter -import androidx.xr.compose.spatial.OrbiterEdge +import androidx.xr.compose.spatial.OrbiterOffsetType import androidx.xr.compose.spatial.Subspace import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.SpatialRow @@ -72,7 +72,7 @@ private fun OrbiterExampleSubspace() { @Composable fun OrbiterExample() { Orbiter( - position = OrbiterEdge.Bottom, + position = ContentEdge.Bottom, offset = 96.dp, alignment = Alignment.CenterHorizontally ) { @@ -102,13 +102,14 @@ fun OrbiterAnchoringExample() { Subspace { SpatialRow { Orbiter( - position = OrbiterEdge.Top, - offset = EdgeOffset.inner(8.dp), + position = ContentEdge.Top, + offset = 8.dp, + offsetType = OrbiterOffsetType.InnerEdge, shape = SpatialRoundedCornerShape(size = CornerSize(50)) ) { Text( "Hello World!", - style = MaterialTheme.typography.h2, + style = MaterialTheme.typography.titleMedium, modifier = Modifier .background(Color.White) .padding(16.dp) @@ -150,7 +151,7 @@ private fun Ui2DToOribiter() { // New XR differentiated approach Orbiter( - position = OrbiterEdge.Start, + position = ContentEdge.Start, offset = dimensionResource(R.dimen.start_orbiter_padding), alignment = Alignment.Top ) { diff --git a/xr/src/main/java/com/example/xr/compose/SpatialElevation.kt b/xr/src/main/java/com/example/xr/compose/SpatialElevation.kt index 3ab8f3f54..68fedfe3f 100644 --- a/xr/src/main/java/com/example/xr/compose/SpatialElevation.kt +++ b/xr/src/main/java/com/example/xr/compose/SpatialElevation.kt @@ -27,7 +27,7 @@ private fun ComposableThatShouldElevateInXr() {} private fun SpatialElevationExample() { // [START androidxr_compose_spatialelevation] // Elevate an otherwise 2D Composable (signified here by ComposableThatShouldElevateInXr). - SpatialElevation(spatialElevationLevel = SpatialElevationLevel.Level4) { + SpatialElevation(elevation = SpatialElevationLevel.Level4) { ComposableThatShouldElevateInXr() } // [END androidxr_compose_spatialelevation] diff --git a/xr/src/main/java/com/example/xr/compose/SpatialExternalSurface.kt b/xr/src/main/java/com/example/xr/compose/SpatialExternalSurface.kt index 1736bc909..c42730758 100644 --- a/xr/src/main/java/com/example/xr/compose/SpatialExternalSurface.kt +++ b/xr/src/main/java/com/example/xr/compose/SpatialExternalSurface.kt @@ -19,19 +19,23 @@ package com.example.xr.compose import android.content.ContentResolver import android.net.Uri import androidx.compose.runtime.Composable +import androidx.compose.runtime.ExperimentalComposeApi import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import androidx.media3.common.C import androidx.media3.common.MediaItem import androidx.media3.exoplayer.ExoPlayer import androidx.xr.compose.spatial.Subspace import androidx.xr.compose.subspace.SpatialExternalSurface import androidx.xr.compose.subspace.StereoMode +import androidx.xr.compose.subspace.SurfaceProtection import androidx.xr.compose.subspace.layout.SubspaceModifier import androidx.xr.compose.subspace.layout.height import androidx.xr.compose.subspace.layout.width // [START androidxr_compose_SpatialExternalSurfaceStereo] +@OptIn(ExperimentalComposeApi::class) @Composable fun SpatialExternalSurfaceContent() { val context = LocalContext.current @@ -69,3 +73,46 @@ fun SpatialExternalSurfaceContent() { } } // [END androidxr_compose_SpatialExternalSurfaceStereo] + +// [START androidxr_compose_SpatialExternalSurfaceDRM] +@OptIn(ExperimentalComposeApi::class) +@Composable +fun DrmSpatialVideoPlayer() { + val context = LocalContext.current + Subspace { + SpatialExternalSurface( + modifier = SubspaceModifier + .width(1200.dp) + .height(676.dp), + stereoMode = StereoMode.SideBySide, + surfaceProtection = SurfaceProtection.Protected + ) { + val exoPlayer = remember { ExoPlayer.Builder(context).build() } + + // Define the URI for your DRM-protected content and license server. + val videoUri = "https://your-content-provider.com/video.mpd" + val drmLicenseUrl = "https://your-license-server.com/license" + + // Build a MediaItem with the necessary DRM configuration. + val mediaItem = MediaItem.Builder() + .setUri(videoUri) + .setDrmConfiguration( + MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID) + .setLicenseUri(drmLicenseUrl) + .build() + ) + .build() + + onSurfaceCreated { surface -> + // The created surface is secure and can be used by the player. + exoPlayer.setVideoSurface(surface) + exoPlayer.setMediaItem(mediaItem) + exoPlayer.prepare() + exoPlayer.play() + } + + onSurfaceDestroyed { exoPlayer.release() } + } + } +} +// [END androidxr_compose_SpatialExternalSurfaceDRM] diff --git a/xr/src/main/java/com/example/xr/compose/Views.kt b/xr/src/main/java/com/example/xr/compose/Views.kt index 4fc693828..b567b2f09 100644 --- a/xr/src/main/java/com/example/xr/compose/Views.kt +++ b/xr/src/main/java/com/example/xr/compose/Views.kt @@ -35,8 +35,8 @@ import androidx.xr.compose.subspace.layout.depth import androidx.xr.compose.subspace.layout.height import androidx.xr.compose.subspace.layout.width import androidx.xr.runtime.Session +import androidx.xr.runtime.math.IntSize2d import androidx.xr.scenecore.PanelEntity -import androidx.xr.scenecore.PixelDimensions import com.example.xr.R private class MyCustomView(context: Context) : View(context) @@ -86,7 +86,7 @@ fun ComponentActivity.PanelEntityWithView(xrSession: Session) { val panelEntity = PanelEntity.create( session = xrSession, view = panelContent, - pixelDimensions = PixelDimensions(500, 500), + pixelDimensions = IntSize2d(500, 500), name = "panel entity" ) // [END androidxr_compose_PanelEntityWithView] diff --git a/xr/src/main/java/com/example/xr/compose/Volume.kt b/xr/src/main/java/com/example/xr/compose/Volume.kt index 83073c224..cce37ca83 100644 --- a/xr/src/main/java/com/example/xr/compose/Volume.kt +++ b/xr/src/main/java/com/example/xr/compose/Volume.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.xr.compose.platform.LocalSession import androidx.xr.compose.spatial.Subspace +import androidx.xr.compose.subspace.ExperimentalSubspaceVolumeApi import androidx.xr.compose.subspace.SpatialPanel import androidx.xr.compose.subspace.Volume import androidx.xr.compose.subspace.layout.SubspaceModifier @@ -62,6 +63,7 @@ private fun VolumeExample() { } // [START androidxr_compose_ObjectInAVolume] +@OptIn(ExperimentalSubspaceVolumeApi::class) @Composable fun ObjectInAVolume(show3DObject: Boolean) { // [START_EXCLUDE silent] diff --git a/xr/src/main/java/com/example/xr/misc/ModeTransition.kt b/xr/src/main/java/com/example/xr/misc/ModeTransition.kt index dca0ddbfb..ea13d5868 100644 --- a/xr/src/main/java/com/example/xr/misc/ModeTransition.kt +++ b/xr/src/main/java/com/example/xr/misc/ModeTransition.kt @@ -32,6 +32,6 @@ fun modeTransitionCompose() { fun modeTransitionScenecore(xrSession: Session) { // [START androidxr_misc_modeTransitionScenecore] - xrSession.scene.spatialEnvironment.requestHomeSpaceMode() + xrSession.scene.requestHomeSpaceMode() // [END androidxr_misc_modeTransitionScenecore] } diff --git a/xr/src/main/java/com/example/xr/runtime/Session.kt b/xr/src/main/java/com/example/xr/runtime/Session.kt index f2fd85a2a..2a9f84ff1 100644 --- a/xr/src/main/java/com/example/xr/runtime/Session.kt +++ b/xr/src/main/java/com/example/xr/runtime/Session.kt @@ -16,14 +16,11 @@ package com.example.xr.runtime -import android.app.Activity +import androidx.activity.ComponentActivity import androidx.compose.runtime.Composable import androidx.xr.compose.platform.LocalSession import androidx.xr.runtime.Session -import androidx.xr.runtime.SessionCreatePermissionsNotGranted import androidx.xr.runtime.SessionCreateSuccess -import androidx.xr.runtime.SessionResumePermissionsNotGranted -import androidx.xr.runtime.SessionResumeSuccess // [START androidxr_localsession] @Composable @@ -32,30 +29,15 @@ fun ComposableUsingSession() { } // [END androidxr_localsession] -fun Activity.createSession() { +fun ComponentActivity.createSession() { // [START androidxr_session_create] when (val result = Session.create(this)) { is SessionCreateSuccess -> { val xrSession = result.session // ... } - is SessionCreatePermissionsNotGranted -> - TODO(/* The required permissions in result.permissions have not been granted. */) + else -> + TODO(/* A different unhandled exception was thrown. */) } // [END androidxr_session_create] } - -fun sessionResume(session: Session) { - // [START androidxr_session_resume] - when (val result = session.resume()) { - is SessionResumeSuccess -> { - // Session has been created successfully. - // Attach any successful handlers here. - } - - is SessionResumePermissionsNotGranted -> { - // Request permissions in `result.permissions`. - } - } - // [END androidxr_session_resume] -} diff --git a/xr/src/main/java/com/example/xr/scenecore/Entities.kt b/xr/src/main/java/com/example/xr/scenecore/Entities.kt index d4c723604..cf16f266e 100644 --- a/xr/src/main/java/com/example/xr/scenecore/Entities.kt +++ b/xr/src/main/java/com/example/xr/scenecore/Entities.kt @@ -16,23 +16,10 @@ package com.example.xr.scenecore -import androidx.xr.runtime.Session import androidx.xr.runtime.math.Pose import androidx.xr.runtime.math.Quaternion import androidx.xr.runtime.math.Vector3 -import androidx.xr.scenecore.AnchorPlacement -import androidx.xr.scenecore.Dimensions import androidx.xr.scenecore.Entity -import androidx.xr.scenecore.InputEvent -import androidx.xr.scenecore.InteractableComponent -import androidx.xr.scenecore.MovableComponent -import androidx.xr.scenecore.PlaneSemantic -import androidx.xr.scenecore.PlaneType -import androidx.xr.scenecore.ResizableComponent -import androidx.xr.scenecore.ResizeListener -import androidx.xr.scenecore.SurfaceEntity -import java.util.concurrent.Executor -import java.util.concurrent.Executors private fun setPoseExample(entity: Entity) { // [START androidxr_scenecore_entity_setPoseExample] @@ -45,11 +32,11 @@ private fun setPoseExample(entity: Entity) { // [END androidxr_scenecore_entity_setPoseExample] } -private fun hideEntity(entity: Entity) { - // [START androidxr_scenecore_entity_hideEntity] - // Hide the entity - entity.setHidden(true) - // [END androidxr_scenecore_entity_hideEntity] +private fun disableEntity(entity: Entity) { + // [START androidxr_scenecore_entity_setEnabled] + // Disable the entity. + entity.setEnabled(false) + // [END androidxr_scenecore_entity_setEnabled] } private fun entitySetScale(entity: Entity) { @@ -58,62 +45,3 @@ private fun entitySetScale(entity: Entity) { entity.setScale(2f) // [END androidxr_scenecore_entity_entitySetScale] } - -private fun moveableComponentExample(session: Session, entity: Entity) { - // [START androidxr_scenecore_moveableComponentExample] - val anchorPlacement = AnchorPlacement.createForPlanes( - planeTypeFilter = setOf(PlaneSemantic.FLOOR, PlaneSemantic.TABLE), - planeSemanticFilter = setOf(PlaneType.VERTICAL) - ) - - val movableComponent = MovableComponent.create( - session = session, - systemMovable = false, - scaleInZ = false, - anchorPlacement = setOf(anchorPlacement) - ) - entity.addComponent(movableComponent) - // [END androidxr_scenecore_moveableComponentExample] -} - -private fun resizableComponentExample(session: Session, entity: Entity, executor: Executor) { - // [START androidxr_scenecore_resizableComponentExample] - val resizableComponent = ResizableComponent.create(session) - resizableComponent.minimumSize = Dimensions(177f, 100f, 1f) - resizableComponent.fixedAspectRatio = 16f / 9f // Specify a 16:9 aspect ratio - - resizableComponent.addResizeListener( - executor, - object : ResizeListener { - override fun onResizeEnd(entity: Entity, finalSize: Dimensions) { - - // update the size in the component - resizableComponent.size = finalSize - - // update the Entity to reflect the new size - (entity as SurfaceEntity).canvasShape = SurfaceEntity.CanvasShape.Quad(finalSize.width, finalSize.height) - } - }, - ) - - entity.addComponent(resizableComponent) - // [END androidxr_scenecore_resizableComponentExample] -} - -private fun interactableComponentExample(session: Session, entity: Entity) { - // [START androidxr_scenecore_interactableComponentExample] - val executor = Executors.newSingleThreadExecutor() - val interactableComponent = InteractableComponent.create(session, executor) { - // when the user disengages with the entity with their hands - if (it.source == InputEvent.SOURCE_HANDS && it.action == InputEvent.ACTION_UP) { - // increase size with right hand and decrease with left - if (it.pointerType == InputEvent.POINTER_TYPE_RIGHT) { - entity.setScale(1.5f) - } else if (it.pointerType == InputEvent.POINTER_TYPE_LEFT) { - entity.setScale(0.5f) - } - } - } - entity.addComponent(interactableComponent) - // [END androidxr_scenecore_interactableComponentExample] -} diff --git a/xr/src/main/java/com/example/xr/scenecore/Environments.kt b/xr/src/main/java/com/example/xr/scenecore/Environments.kt index 35f753569..5ed80d36f 100644 --- a/xr/src/main/java/com/example/xr/scenecore/Environments.kt +++ b/xr/src/main/java/com/example/xr/scenecore/Environments.kt @@ -16,24 +16,24 @@ package com.example.xr.scenecore +import android.content.Context import androidx.xr.runtime.Session import androidx.xr.scenecore.ExrImage import androidx.xr.scenecore.GltfModel import androidx.xr.scenecore.SpatialEnvironment import androidx.xr.scenecore.scene -import kotlinx.coroutines.guava.await +import java.nio.file.Paths private class Environments(val session: Session) { - suspend fun loadEnvironmentGeometry() { + suspend fun loadEnvironmentGeometry(context: Context) { // [START androidxr_scenecore_environment_loadEnvironmentGeometry] - val environmentGeometryFuture = GltfModel.create(session, "DayGeometry.glb") - val environmentGeometry = environmentGeometryFuture.await() + val environmentGeometry = GltfModel.create(session, Paths.get("DayGeometry.glb")) // [END androidxr_scenecore_environment_loadEnvironmentGeometry] } - fun loadEnvironmentSkybox() { + suspend fun loadEnvironmentSkybox() { // [START androidxr_scenecore_environment_loadEnvironmentSkybox] - val lightingForSkybox = ExrImage.create(session, "BlueSkyboxLighting.zip") + val lightingForSkybox = ExrImage.createFromZip(session, Paths.get("BlueSkyboxLighting.zip")) // [END androidxr_scenecore_environment_loadEnvironmentSkybox] } @@ -41,38 +41,40 @@ private class Environments(val session: Session) { // [START androidxr_scenecore_environment_setEnvironmentPreference] val spatialEnvironmentPreference = SpatialEnvironment.SpatialEnvironmentPreference(lightingForSkybox, environmentGeometry) - val preferenceResult = - session.scene.spatialEnvironment.setSpatialEnvironmentPreference(spatialEnvironmentPreference) - if (preferenceResult == SpatialEnvironment.SetSpatialEnvironmentPreferenceChangeApplied()) { + session.scene.spatialEnvironment.preferredSpatialEnvironment = spatialEnvironmentPreference + if (session.scene.spatialEnvironment.isPreferredSpatialEnvironmentActive) { // The environment was successfully updated and is now visible, and any listeners // specified using addOnSpatialEnvironmentChangedListener will be notified. - } else if (preferenceResult == SpatialEnvironment.SetSpatialEnvironmentPreferenceChangePending()) { - // The environment is in the process of being updated. Once visible, any listeners - // specified using addOnSpatialEnvironmentChangedListener will be notified. + } else { + // The passthrough opacity preference was successfully set, but not + // immediately visible. The passthrough opacity change will be applied + // when the activity has the SPATIAL_CAPABILITY_APP_ENVIRONMENT capability. + // Then, any listeners specified using addOnSpatialEnvironmentChangedListener + // will be notified. } // [END androidxr_scenecore_environment_setEnvironmentPreference] } fun setPassthroughOpacityPreference() { // [START androidxr_scenecore_environment_setPassthroughOpacityPreference] - val preferenceResult = session.scene.spatialEnvironment.setPassthroughOpacityPreference(1.0f) - if (preferenceResult == SpatialEnvironment.SetPassthroughOpacityPreferenceChangeApplied()) { + session.scene.spatialEnvironment.preferredPassthroughOpacity = 1.0f + if (session.scene.spatialEnvironment.currentPassthroughOpacity == 1.0f) { // The passthrough opacity request succeeded and should be visible now, and any listeners - // specified using addOnPassthroughOpacityChangedListener will be notified - } else if (preferenceResult == SpatialEnvironment.SetPassthroughOpacityPreferenceChangePending()) { + // specified using addOnPassthroughOpacityChangedListener will be notified. + } else { // The passthrough opacity preference was successfully set, but not // immediately visible. The passthrough opacity change will be applied // when the activity has the // SpatialCapabilities.SPATIAL_CAPABILITY_PASSTHROUGH_CONTROL capability. // Then, any listeners specified using addOnPassthroughOpacityChangedListener - // will be notified + // will be notified. } // [END androidxr_scenecore_environment_setPassthroughOpacityPreference] } fun getCurrentPassthroughOpacity() { // [START androidxr_scenecore_environment_getCurrentPassthroughOpacity] - val currentPassthroughOpacity = session.scene.spatialEnvironment.getCurrentPassthroughOpacity() + val currentPassthroughOpacity = session.scene.spatialEnvironment.currentPassthroughOpacity // [END androidxr_scenecore_environment_getCurrentPassthroughOpacity] } } diff --git a/xr/src/main/java/com/example/xr/scenecore/GltfEntity.kt b/xr/src/main/java/com/example/xr/scenecore/GltfEntity.kt index c7181e2f3..997436ddf 100644 --- a/xr/src/main/java/com/example/xr/scenecore/GltfEntity.kt +++ b/xr/src/main/java/com/example/xr/scenecore/GltfEntity.kt @@ -25,11 +25,11 @@ import androidx.xr.scenecore.GltfModel import androidx.xr.scenecore.GltfModelEntity import androidx.xr.scenecore.SpatialCapabilities import androidx.xr.scenecore.scene -import kotlinx.coroutines.guava.await +import java.nio.file.Paths private suspend fun loadGltfFile(session: Session) { // [START androidxr_scenecore_gltfmodel_create] - val gltfModel = GltfModel.create(session, "models/saturn_rings.glb").await() + val gltfModel = GltfModel.create(session, Paths.get("models", "saturn_rings.glb")) // [END androidxr_scenecore_gltfmodel_create] } diff --git a/xr/src/main/java/com/example/xr/scenecore/InteractableComponent.kt b/xr/src/main/java/com/example/xr/scenecore/InteractableComponent.kt new file mode 100644 index 000000000..a712d0dfe --- /dev/null +++ b/xr/src/main/java/com/example/xr/scenecore/InteractableComponent.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2025 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.xr.scenecore + +import androidx.xr.runtime.Session +import androidx.xr.scenecore.Entity +import androidx.xr.scenecore.InputEvent +import androidx.xr.scenecore.InteractableComponent +import java.util.concurrent.Executors + +private fun interactableComponentExample(session: Session, entity: Entity) { + // [START androidxr_scenecore_interactableComponentExample] + val executor = Executors.newSingleThreadExecutor() + val interactableComponent = InteractableComponent.create(session, executor) { + // when the user disengages with the entity with their hands + if (it.source == InputEvent.Source.SOURCE_HANDS && it.action == InputEvent.Action.ACTION_UP) { + // increase size with right hand and decrease with left + if (it.pointerType == InputEvent.Pointer.POINTER_TYPE_RIGHT) { + entity.setScale(1.5f) + } else if (it.pointerType == InputEvent.Pointer.POINTER_TYPE_LEFT) { + entity.setScale(0.5f) + } + } + } + entity.addComponent(interactableComponent) + // [END androidxr_scenecore_interactableComponentExample] +} diff --git a/xr/src/main/java/com/example/xr/scenecore/MovableComponent.kt b/xr/src/main/java/com/example/xr/scenecore/MovableComponent.kt new file mode 100644 index 000000000..3c254e433 --- /dev/null +++ b/xr/src/main/java/com/example/xr/scenecore/MovableComponent.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2025 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.xr.scenecore + +import androidx.xr.runtime.Session +import androidx.xr.scenecore.AnchorPlacement +import androidx.xr.scenecore.Entity +import androidx.xr.scenecore.MovableComponent +import androidx.xr.scenecore.PlaneOrientation +import androidx.xr.scenecore.PlaneSemanticType + +private fun createSystemMovable(session: Session, entity: Entity) { + // [START androidxr_scenecore_movableComponent_createSystemMovable] + val movableComponent = MovableComponent.createSystemMovable(session) + entity.addComponent(movableComponent) + // [END androidxr_scenecore_movableComponent_createSystemMovable] +} + +private fun movableComponentAnchorExample(session: Session, entity: Entity) { + // [START androidxr_scenecore_movableComponent_anchorable] + val anchorPlacement = AnchorPlacement.createForPlanes( + anchorablePlaneOrientations = setOf(PlaneOrientation.VERTICAL), + anchorablePlaneSemanticTypes = setOf(PlaneSemanticType.FLOOR, PlaneSemanticType.TABLE) + ) + + val movableComponent = MovableComponent.createAnchorable( + session = session, + anchorPlacement = setOf(anchorPlacement) + ) + entity.addComponent(movableComponent) + // [END androidxr_scenecore_movableComponent_anchorable] +} diff --git a/xr/src/main/java/com/example/xr/scenecore/ResizableComponent.kt b/xr/src/main/java/com/example/xr/scenecore/ResizableComponent.kt new file mode 100644 index 000000000..28b70027d --- /dev/null +++ b/xr/src/main/java/com/example/xr/scenecore/ResizableComponent.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2025 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.xr.scenecore + +import androidx.xr.runtime.Session +import androidx.xr.runtime.math.FloatSize3d +import androidx.xr.scenecore.ResizableComponent +import androidx.xr.scenecore.ResizeEvent +import androidx.xr.scenecore.SurfaceEntity +import java.util.concurrent.Executor + +private fun resizableComponentExample( + session: Session, + surfaceEntity: SurfaceEntity, + executor: Executor +) { + // [START androidxr_scenecore_resizableComponentExample] + val resizableComponent = ResizableComponent.create(session) { event -> + if (event.resizeState == ResizeEvent.ResizeState.RESIZE_STATE_END) { + // update the Entity to reflect the new size + surfaceEntity.canvasShape = SurfaceEntity.CanvasShape.Quad(event.newSize.width, event.newSize.height) + } + } + resizableComponent.minimumEntitySize = FloatSize3d(177f, 100f, 1f) + resizableComponent.fixedAspectRatio = 16f / 9f // Specify a 16:9 aspect ratio + + surfaceEntity.addComponent(resizableComponent) + // [END androidxr_scenecore_resizableComponentExample] +} diff --git a/xr/src/main/java/com/example/xr/scenecore/SpatialAudio.kt b/xr/src/main/java/com/example/xr/scenecore/SpatialAudio.kt index 1d1eac1ae..b68e67713 100644 --- a/xr/src/main/java/com/example/xr/scenecore/SpatialAudio.kt +++ b/xr/src/main/java/com/example/xr/scenecore/SpatialAudio.kt @@ -22,6 +22,10 @@ import android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION import android.media.AudioAttributes.USAGE_ASSISTANCE_SONIFICATION import android.media.MediaPlayer import android.media.SoundPool +import androidx.annotation.OptIn +import androidx.media3.common.C +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.audio.AudioCapabilities import androidx.xr.runtime.Session import androidx.xr.scenecore.Entity import androidx.xr.scenecore.PointSourceParams @@ -149,3 +153,19 @@ private fun playSpatialAudioAtEntityAmbionics(session: Session, appContext: Cont } // [END androidxr_scenecore_playSpatialAudioAmbionics] } + +@OptIn(UnstableApi::class) +private fun detectSupport(context: Context) { + // [START androidxr_scenecore_dolby_detect_support] + val audioCapabilities = AudioCapabilities.getCapabilities(context, androidx.media3.common.AudioAttributes.DEFAULT, null) + if (audioCapabilities.supportsEncoding(C.ENCODING_AC3)) { + // Device supports playback of the Dolby Digital media format. + } + if (audioCapabilities.supportsEncoding(C.ENCODING_E_AC3)) { + // Device supports playback of the Dolby Digital Plus media format. + } + if (audioCapabilities.supportsEncoding(C.ENCODING_E_AC3_JOC)) { + // Device supports playback of the Dolby Digital Plus with Dolby Atmos media format. + } + // [END androidxr_scenecore_dolby_detect_support] +} diff --git a/xr/src/main/java/com/example/xr/scenecore/SpatialCapabilities.kt b/xr/src/main/java/com/example/xr/scenecore/SpatialCapabilities.kt index fcfcdf5a8..7fb784080 100644 --- a/xr/src/main/java/com/example/xr/scenecore/SpatialCapabilities.kt +++ b/xr/src/main/java/com/example/xr/scenecore/SpatialCapabilities.kt @@ -27,7 +27,7 @@ fun checkMultipleCapabilities(xrSession: Session) { SpatialCapabilities.SPATIAL_CAPABILITY_PASSTHROUGH_CONTROL ) ) { - xrSession.scene.spatialEnvironment.setPassthroughOpacityPreference(0f) + xrSession.scene.spatialEnvironment.preferredPassthroughOpacity = 1f } // Example 2: multiple capability flags can be checked simultaneously: if (xrSession.scene.spatialCapabilities.hasCapability( diff --git a/xr/src/main/java/com/example/xr/scenecore/SpatialVideo.kt b/xr/src/main/java/com/example/xr/scenecore/SpatialVideo.kt index 460d35db2..ec733bb61 100644 --- a/xr/src/main/java/com/example/xr/scenecore/SpatialVideo.kt +++ b/xr/src/main/java/com/example/xr/scenecore/SpatialVideo.kt @@ -19,13 +19,19 @@ package com.example.xr.scenecore import android.content.ContentResolver import android.net.Uri import androidx.activity.ComponentActivity +import androidx.lifecycle.lifecycleScope +import androidx.media3.common.C import androidx.media3.common.MediaItem import androidx.media3.exoplayer.ExoPlayer import androidx.xr.runtime.Session import androidx.xr.runtime.math.Pose import androidx.xr.runtime.math.Vector3 import androidx.xr.scenecore.SurfaceEntity +import androidx.xr.scenecore.Texture +import androidx.xr.scenecore.TextureSampler import androidx.xr.scenecore.scene +import java.nio.file.Paths +import kotlinx.coroutines.launch private fun ComponentActivity.surfaceEntityCreate(xrSession: Session) { // [START androidxr_scenecore_surfaceEntityCreate] @@ -105,3 +111,117 @@ private fun ComponentActivity.surfaceEntityCreateMVHEVC(xrSession: Session) { exoPlayer.play() // [END androidxr_scenecore_surfaceEntityCreateMVHEVC] } + +private fun ComponentActivity.surfaceEntityCreateDRM(xrSession: Session) { + // [START androidxr_scenecore_surfaceEntityCreateDRM] + // Create a SurfaceEntity with DRM content + + // Define the URI for your DRM-protected content and license server. + val videoUri = "https://your-content-provider.com/video.mpd" + val drmLicenseUrl = "https://your-license-server.com/license" + + // Create the SurfaceEntity with the PROTECTED content security level. + val protectedSurfaceEntity = SurfaceEntity.create( + session = xrSession, + stereoMode = SurfaceEntity.StereoMode.SIDE_BY_SIDE, + pose = Pose(Vector3(0.0f, 0.0f, -1.5f)), + canvasShape = SurfaceEntity.CanvasShape.Quad(1.0f, 1.0f), + contentSecurityLevel = SurfaceEntity.ContentSecurityLevel.PROTECTED + ) + + // Build a MediaItem with the necessary DRM configuration. + val mediaItem = MediaItem.Builder() + .setUri(videoUri) + .setDrmConfiguration( + MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID) + .setLicenseUri(drmLicenseUrl) + .build() + ) + .build() + + // Initialize ExoPlayer and set the protected surface. + val exoPlayer = ExoPlayer.Builder(this).build() + exoPlayer.setVideoSurface(protectedSurfaceEntity.getSurface()) + + // Set the media item and start playback. + exoPlayer.setMediaItem(mediaItem) + exoPlayer.prepare() + exoPlayer.play() + + // [END androidxr_scenecore_surfaceEntityCreateDRM] +} + +private fun ComponentActivity.surfaceEntityHDR(xrSession: Session) { + // [START androidxr_scenecore_surfaceEntityHDR] + // Define the color properties for your HDR video. These values should be specific + // to your content. + val hdrMetadata = SurfaceEntity.ContentColorMetadata( + colorSpace = SurfaceEntity.ContentColorMetadata.ColorSpace.BT2020, + colorTransfer = SurfaceEntity.ContentColorMetadata.ColorTransfer.ST2084, // PQ + colorRange = SurfaceEntity.ContentColorMetadata.ColorRange.LIMITED, + maxCLL = 1000 // Example: 1000 nits + ) + + // Create a SurfaceEntity, passing the HDR metadata at creation time. + val hdrSurfaceEntity = SurfaceEntity.create( + session = xrSession, + stereoMode = SurfaceEntity.StereoMode.MONO, + pose = Pose(Vector3(0.0f, 0.0f, -1.5f)), + canvasShape = SurfaceEntity.CanvasShape.Quad(1.0f, 1.0f), + contentColorMetadata = hdrMetadata + ) + + // Initialize ExoPlayer and set the surface. + val exoPlayer = ExoPlayer.Builder(this).build() + exoPlayer.setVideoSurface(hdrSurfaceEntity.getSurface()) + + // Define the URI for your HDR content. + val videoUri = "https://your-content-provider.com/hdr_video.mp4" + val mediaItem = MediaItem.fromUri(videoUri) + + // Set the media item and start playback. + exoPlayer.setMediaItem(mediaItem) + exoPlayer.prepare() + exoPlayer.play() + // [END androidxr_scenecore_surfaceEntityHDR] +} + +private fun surfaceEntityEdgeFeathering(xrSession: Session) { + // [START androidxr_scenecore_surfaceEntityEdgeFeathering] + // Create a SurfaceEntity. + val surfaceEntity = SurfaceEntity.create( + session = xrSession, + pose = Pose(Vector3(0.0f, 0.0f, -1.5f)) + ) + + // Feather the edges of the surface. + surfaceEntity.edgeFeather = + SurfaceEntity.EdgeFeatheringParams.SmoothFeather(0.1f, 0.1f) + // [END androidxr_scenecore_surfaceEntityEdgeFeathering] +} + +private fun surfaceEntityAlphaMasking(xrSession: Session, activity: ComponentActivity) { + // [START androidxr_scenecore_surfaceEntityAlphaMasking] + // Create a SurfaceEntity. + val surfaceEntity = SurfaceEntity.create( + session = xrSession, + pose = Pose(Vector3(0.0f, 0.0f, -1.5f)) + ) + + // Load the texture in a coroutine scope. + activity.lifecycleScope.launch { + val alphaMaskTexture = + Texture.create( + xrSession, + Paths.get("textures", "alpha_mask.png"), + TextureSampler.create() + ) + + // Apply the alpha mask. + surfaceEntity.primaryAlphaMaskTexture = alphaMaskTexture + + // To remove the mask, set the property to null. + surfaceEntity.primaryAlphaMaskTexture = null + } + // [END androidxr_scenecore_surfaceEntityAlphaMasking] +}