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