1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.xr.arcore 18 19 import androidx.annotation.RestrictTo 20 import androidx.xr.runtime.AnchorPersistenceMode 21 import androidx.xr.runtime.Session 22 import androidx.xr.runtime.internal.Anchor as RuntimeAnchor 23 import androidx.xr.runtime.internal.AnchorNotTrackingException 24 import androidx.xr.runtime.internal.AnchorResourcesExhaustedException 25 import androidx.xr.runtime.math.Pose 26 import java.util.UUID 27 import kotlin.coroutines.Continuation 28 import kotlin.coroutines.resume 29 import kotlin.coroutines.resumeWithException 30 import kotlinx.coroutines.flow.MutableStateFlow 31 import kotlinx.coroutines.flow.StateFlow 32 import kotlinx.coroutines.flow.asStateFlow 33 import kotlinx.coroutines.suspendCancellableCoroutine 34 35 /** 36 * An anchor describes a fixed location and orientation in the real world. To stay at a fixed 37 * location in physical space, the numerical description of this position may update as ARCore for 38 * XR updates its understanding of the physical world. 39 */ 40 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) 41 public class Anchor 42 internal constructor( 43 public val runtimeAnchor: RuntimeAnchor, 44 private val xrResourceManager: XrResourcesManager, 45 ) : Updatable { 46 public companion object { 47 /** 48 * Creates and attaches an [Anchor] at the given [pose]. 49 * 50 * @param session the [Session] that is used to create the anchor. 51 * @param pose the [Pose] that describes the location and orientation of the anchor. 52 * @return the result of the operation. Can be [AnchorCreateSuccess] that contains the 53 * created [Anchor], or [AnchorCreateResourcesExhausted] if the resources allocated for 54 * anchors have been exhausted. 55 */ 56 @JvmStatic createnull57 public fun create(session: Session, pose: Pose): AnchorCreateResult { 58 val perceptionStateExtender = getPerceptionStateExtender(session) 59 val runtimeAnchor: RuntimeAnchor 60 try { 61 runtimeAnchor = session.runtime.perceptionManager.createAnchor(pose) 62 } catch (e: AnchorResourcesExhaustedException) { 63 return AnchorCreateResourcesExhausted() 64 } catch (e: AnchorNotTrackingException) { 65 return AnchorCreateNotTracking() 66 } 67 return generateCreateResult(runtimeAnchor, perceptionStateExtender.xrResourcesManager) 68 } 69 70 /** 71 * Retrieves all the [UUID] instances from [Anchor] objects that have been persisted by 72 * [persist] that are still present in the local storage. 73 * 74 * @throws [IllegalStateException] if [AnchorPersistenceMode] is set to Disabled. 75 */ 76 @JvmStatic getPersistedAnchorUuidsnull77 public fun getPersistedAnchorUuids(session: Session): List<UUID> { 78 check(session.config.anchorPersistence != AnchorPersistenceMode.Disabled) { 79 "Config.AnchorPersistenceMode is set to Disabled." 80 } 81 return session.runtime.perceptionManager.getPersistedAnchorUuids() 82 } 83 84 /** 85 * Loads an [Anchor] from local storage, using the given [uuid]. The anchor will attempt to 86 * be attached in the same physical location as the anchor that was previously persisted. 87 * The [uuid] should be the return value of a previous call to [persist]. 88 * 89 * @throws [IllegalStateException] if [AnchorPersistenceMode] is set to Disabled. 90 */ 91 @JvmStatic loadnull92 public fun load(session: Session, uuid: UUID): AnchorCreateResult { 93 check(session.config.anchorPersistence != AnchorPersistenceMode.Disabled) { 94 "Config.AnchorPersistenceMode is set to Disabled." 95 } 96 97 val perceptionStateExtender = getPerceptionStateExtender(session) 98 val runtimeAnchor: RuntimeAnchor 99 try { 100 runtimeAnchor = session.runtime.perceptionManager.loadAnchor(uuid) 101 } catch (e: AnchorResourcesExhaustedException) { 102 return AnchorCreateResourcesExhausted() 103 } 104 return generateCreateResult(runtimeAnchor, perceptionStateExtender.xrResourcesManager) 105 } 106 107 /** Loads an [Anchor] of the given native pointer. */ 108 // TODO(b/373711152) : Remove this method once the Jetpack XR Runtime API migration is done. 109 @JvmStatic loadFromNativePointernull110 public fun loadFromNativePointer(session: Session, nativePointer: Long): Anchor { 111 val perceptionStateExtender = getPerceptionStateExtender(session) 112 val runtimeAnchor = 113 session.runtime.perceptionManager.loadAnchorFromNativePointer(nativePointer) 114 return Anchor(runtimeAnchor, perceptionStateExtender.xrResourcesManager) 115 } 116 117 /** 118 * Deletes a persisted Anchor denoted by [uuid] from local storage. 119 * 120 * @throws [IllegalStateException] if [AnchorPersistenceMode] is set to Disabled. 121 */ 122 @JvmStatic unpersistnull123 public fun unpersist(session: Session, uuid: UUID) { 124 check(session.config.anchorPersistence != AnchorPersistenceMode.Disabled) { 125 "Config.AnchorPersistenceMode is set to Disabled." 126 } 127 session.runtime.perceptionManager.unpersistAnchor(uuid) 128 } 129 getPerceptionStateExtendernull130 private fun getPerceptionStateExtender(session: Session): PerceptionStateExtender { 131 val perceptionStateExtender: PerceptionStateExtender? = 132 session.stateExtenders.filterIsInstance<PerceptionStateExtender>().first() 133 check(perceptionStateExtender != null) { "PerceptionStateExtender is not available." } 134 return perceptionStateExtender 135 } 136 generateCreateResultnull137 private fun generateCreateResult( 138 runtimeAnchor: RuntimeAnchor, 139 xrResourceManager: XrResourcesManager, 140 ): AnchorCreateResult { 141 val anchor = Anchor(runtimeAnchor, xrResourceManager) 142 xrResourceManager.addUpdatable(anchor) 143 return AnchorCreateSuccess(anchor) 144 } 145 } 146 147 // TODO(b/372049781): This constructor is only used for testing. Remove it once cl/683360061 is 148 // submitted. 149 public constructor(runtimeAnchor: RuntimeAnchor) : this(runtimeAnchor, XrResourcesManager()) 150 151 /** 152 * The representation of the current state of an [Anchor]. 153 * 154 * @property trackingState the current [TrackingState] of the anchor. 155 * @property pose the location of the anchor in the world coordinate space. 156 */ 157 public class State(public val trackingState: TrackingState, public val pose: Pose) { 158 equalsnull159 override fun equals(other: Any?): Boolean { 160 if (this === other) return true 161 if (other !is State) return false 162 return pose == other.pose && trackingState == other.trackingState 163 } 164 hashCodenull165 override fun hashCode(): Int { 166 var result = pose.hashCode() 167 result = 31 * result + trackingState.hashCode() 168 return result 169 } 170 } 171 172 private val _state: MutableStateFlow<State> = 173 MutableStateFlow<State>(State(runtimeAnchor.trackingState, runtimeAnchor.pose)) 174 /** The current [State] of this anchor. */ 175 public val state: StateFlow<State> = _state.asStateFlow() 176 177 private var persistContinuation: Continuation<UUID>? = null 178 179 /** 180 * Stores this anchor in the application's local storage so that it can be shared across 181 * sessions. 182 * 183 * @return the [UUID] that uniquely identifies this anchor. 184 * @throws [IllegalStateException] if [AnchorPersistenceMode] is set to Disabled. 185 */ persistnull186 public suspend fun persist(): UUID { 187 val config = xrResourceManager.lifecycleManager.config 188 check(config.anchorPersistence != AnchorPersistenceMode.Disabled) { 189 "Config.AnchorPersistenceMode is set to Disabled." 190 } 191 runtimeAnchor.persist() 192 // Suspend the coroutine until the anchor is persisted. 193 return suspendCancellableCoroutine { persistContinuation = it } 194 } 195 196 /** Detaches this anchor. This anchor will no longer be updated or tracked. */ detachnull197 public fun detach() { 198 xrResourceManager.removeUpdatable(this) 199 xrResourceManager.queueAnchorToDetach(this) 200 } 201 equalsnull202 override fun equals(other: Any?): Boolean { 203 if (this === other) return true 204 if (other !is Anchor) return false 205 return runtimeAnchor == other.runtimeAnchor 206 } 207 hashCodenull208 override fun hashCode(): Int = runtimeAnchor.hashCode() 209 210 override suspend fun update() { 211 _state.emit(State(runtimeAnchor.trackingState, runtimeAnchor.pose)) 212 if (persistContinuation == null) { 213 return 214 } 215 when (runtimeAnchor.persistenceState) { 216 RuntimeAnchor.PersistenceState.Pending -> { 217 // Do nothing while we wait for the anchor to be persisted. 218 } 219 RuntimeAnchor.PersistenceState.Persisted -> { 220 persistContinuation?.resume(runtimeAnchor.uuid!!) 221 persistContinuation = null 222 } 223 RuntimeAnchor.PersistenceState.NotPersisted -> { 224 persistContinuation?.resumeWithException( 225 RuntimeException("Anchor was not persisted.") 226 ) 227 persistContinuation = null 228 } 229 } 230 } 231 } 232