1 /*
<lambda>null2  * 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.scenecore
18 
19 import androidx.annotation.IntDef
20 import androidx.annotation.RestrictTo
21 import androidx.xr.arcore.Anchor
22 import androidx.xr.runtime.PlaneTrackingMode
23 import androidx.xr.runtime.Session
24 import androidx.xr.runtime.internal.AnchorEntity as RtAnchorEntity
25 import androidx.xr.runtime.internal.JxrPlatformAdapter
26 import java.time.Duration
27 import java.util.UUID
28 import java.util.concurrent.Executor
29 import java.util.concurrent.atomic.AtomicReference
30 
31 /**
32  * An AnchorEntity is created to track a Pose relative to some position or surface in the "Real
33  * World." Children of this Entity will remain positioned relative to that location in the real
34  * world, for the purposes of creating Augmented Reality experiences.
35  *
36  * Note that Anchors are only relative to the "real world", and not virtual environments. Also,
37  * calling setParent() on an AnchorEntity has no effect, as the parenting of an Anchor is controlled
38  * by the system.
39  */
40 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
41 public class AnchorEntity
42 private constructor(rtEntity: RtAnchorEntity, entityManager: EntityManager) :
43     BaseEntity<RtAnchorEntity>(rtEntity, entityManager) {
44     private val state = AtomicReference(rtEntity.state.fromRtState())
45 
46     private var onStateChangedListener: OnStateChangedListener? = null
47 
48     /** Specifies the current tracking state of the Anchor. */
49     @Target(AnnotationTarget.TYPE)
50     @IntDef(State.ANCHORED, State.UNANCHORED, State.TIMEDOUT, State.ERROR)
51     @Retention(AnnotationRetention.SOURCE)
52     internal annotation class StateValue
53 
54     public object State {
55         /**
56          * The ANCHORED state means that this Anchor is being actively tracked and updated by the
57          * perception stack. The application should expect children to maintain their relative
58          * positioning to the system's best understanding of a pose in the real world.
59          */
60         public const val ANCHORED: Int = 0
61         /**
62          * An UNANCHORED state could mean that the perception stack hasn't found an anchor for this
63          * Space, that it has lost tracking.
64          */
65         public const val UNANCHORED: Int = 1
66         /**
67          * The AnchorEntity timed out while searching for an underlying anchor. This it is not
68          * possible to recover the AnchorEntity.
69          */
70         public const val TIMEDOUT: Int = 2
71         /**
72          * The ERROR state means that something has gone wrong and this AnchorEntity is invalid
73          * without the possibility of recovery.
74          */
75         public const val ERROR: Int = 3
76     }
77 
78     /** Specifies the current persist state of the Anchor. */
79     public enum class PersistState {
80         /** The anchor hasn't been requested to persist. */
81         PERSIST_NOT_REQUESTED,
82         /** The anchor is requested to persist but hasn't been persisted yet. */
83         PERSIST_PENDING,
84         /** The anchor is persisted successfully. */
85         PERSISTED,
86     }
87 
88     /** Returns the current tracking state for this AnchorEntity. */
89     public fun getState(): @StateValue Int = state.get()
90 
91     /** Registers a listener callback to be issued when an anchor's state changes. */
92     @Suppress("ExecutorRegistration")
93     public fun setOnStateChangedListener(onStateChangedListener: OnStateChangedListener?) {
94         this.onStateChangedListener = onStateChangedListener
95         onStateChangedListener?.onStateChanged(state.get())
96     }
97 
98     /** Updates the current state. */
99     private fun setState(newState: @StateValue Int) {
100         state.set(newState)
101         onStateChangedListener?.onStateChanged(newState)
102     }
103 
104     /**
105      * Loads the ARCore for XR Anchor using a Jetpack XR Runtime session.
106      *
107      * @param session the Jetpack XR Runtime session to load the Anchor from.
108      * @return the ARCore for XR Anchor corresponding to the native pointer.
109      */
110     // TODO(b/373711152) : Remove this method once the ARCore for XR API migration is done.
111     public fun getAnchor(session: Session): Anchor {
112         return Anchor.loadFromNativePointer(session, rtEntity.nativePointer)
113     }
114 
115     public companion object {
116         private const val TAG = "AnchorEntity"
117 
118         /**
119          * Factory method for AnchorEntity.
120          *
121          * @param adapter JxrPlatformAdapter to use.
122          * @param bounds Bounds for this Anchor Entity.
123          * @param planeType Orientation for the plane to which this Anchor should attach.
124          * @param planeSemantic Semantics for the plane to which this Anchor should attach.
125          * @param timeout Maximum time to search for the anchor, if a suitable plane is not found
126          *   within the timeout time the AnchorEntity state will be set to TIMED_OUT.
127          */
128         internal fun create(
129             adapter: JxrPlatformAdapter,
130             entityManager: EntityManager,
131             bounds: Dimensions,
132             planeType: @PlaneTypeValue Int,
133             planeSemantic: @PlaneSemanticValue Int,
134             timeout: Duration = Duration.ZERO,
135         ): AnchorEntity {
136             val rtAnchorEntity =
137                 adapter.createAnchorEntity(
138                     bounds.toRtDimensions(),
139                     planeType.toRtPlaneType(),
140                     planeSemantic.toRtPlaneSemantic(),
141                     timeout,
142                 )
143             return create(rtAnchorEntity, entityManager)
144         }
145 
146         /**
147          * Factory method for AnchorEntity.
148          *
149          * @param anchor Anchor to create an AnchorEntity for.
150          */
151         internal fun create(
152             adapter: JxrPlatformAdapter,
153             entityManager: EntityManager,
154             anchor: Anchor,
155         ): AnchorEntity =
156             AnchorEntity.create(adapter.createAnchorEntity(anchor.runtimeAnchor), entityManager)
157 
158         /**
159          * Factory method for AnchorEntity.
160          *
161          * @param rtAnchorEntity Runtime AnchorEntity instance.
162          */
163         internal fun create(
164             rtAnchorEntity: RtAnchorEntity,
165             entityManager: EntityManager,
166         ): AnchorEntity {
167             val anchorEntity = AnchorEntity(rtAnchorEntity, entityManager)
168             rtAnchorEntity.setOnStateChangedListener { newRtState ->
169                 when (newRtState) {
170                     RtAnchorEntity.State.UNANCHORED -> anchorEntity.setState(State.UNANCHORED)
171                     RtAnchorEntity.State.ANCHORED -> anchorEntity.setState(State.ANCHORED)
172                     RtAnchorEntity.State.TIMED_OUT -> anchorEntity.setState(State.TIMEDOUT)
173                     RtAnchorEntity.State.ERROR -> anchorEntity.setState(State.ERROR)
174                 }
175             }
176             return anchorEntity
177         }
178 
179         /**
180          * Factory method for AnchorEntity.
181          *
182          * @param adapter JxrPlatformAdapter to use.
183          * @param uuid UUID of the persisted Anchor Entity to create.
184          * @param timeout Maximum time to search for the anchor, if a persisted anchor isn't located
185          *   within the timeout time the AnchorEntity state will be set to TIMED_OUT.
186          */
187         internal fun create(
188             adapter: JxrPlatformAdapter,
189             entityManager: EntityManager,
190             uuid: UUID,
191             timeout: Duration = Duration.ZERO,
192         ): AnchorEntity {
193             val rtAnchorEntity = adapter.createPersistedAnchorEntity(uuid, timeout)
194             val anchorEntity = AnchorEntity(rtAnchorEntity, entityManager)
195             rtAnchorEntity.setOnStateChangedListener { newRtState ->
196                 when (newRtState) {
197                     RtAnchorEntity.State.UNANCHORED -> anchorEntity.setState(State.UNANCHORED)
198                     RtAnchorEntity.State.ANCHORED -> anchorEntity.setState(State.ANCHORED)
199                     RtAnchorEntity.State.TIMED_OUT -> anchorEntity.setState(State.TIMEDOUT)
200                     RtAnchorEntity.State.ERROR -> anchorEntity.setState(State.ERROR)
201                 }
202             }
203             return anchorEntity
204         }
205 
206         /**
207          * Public factory function for an AnchorEntity which searches for a location to create an
208          * Anchor among the tracked planes available to the perception system.
209          *
210          * @param session Session to create the AnchorEntity in.
211          * @param bounds Bounds for this AnchorEntity.
212          * @param planeType Orientation of plane to which this Anchor should attach.
213          * @param planeSemantic Semantics of the plane to which this Anchor should attach.
214          * @param timeout The amount of time as a [Duration] to search for the a suitable plane to
215          *   attach to. If a plane is not found within the timeout, the returned AnchorEntity state
216          *   will be set to AnchorEntity.State.TIMEDOUT. It may take longer than the timeout period
217          *   before the anchor state is updated. If the timeout duration is zero it will search for
218          *   the anchor indefinitely.
219          * @throws [IllegalStateException] if [session.config.planeTracking] is set to
220          *   [PlaneTrackingMode.Disabled].
221          */
222         @JvmStatic
223         @JvmOverloads
224         public fun create(
225             session: Session,
226             bounds: Dimensions,
227             planeType: @PlaneTypeValue Int,
228             planeSemantic: @PlaneSemanticValue Int,
229             timeout: Duration = Duration.ZERO,
230         ): AnchorEntity {
231             check(session.config.planeTracking != PlaneTrackingMode.Disabled) {
232                 "Config.PlaneTrackingMode is set to Disabled."
233             }
234 
235             return AnchorEntity.create(
236                 session.platformAdapter,
237                 session.scene.entityManager,
238                 bounds,
239                 planeType,
240                 planeSemantic,
241                 timeout,
242             )
243         }
244 
245         /**
246          * Public factory function for an AnchorEntity which uses an Anchor from ARCore for XR.
247          *
248          * @param session Session to create the AnchorEntity in.
249          * @param anchor The PerceptionAnchor to use for this AnchorEntity.
250          */
251         @JvmStatic
252         public fun create(session: Session, anchor: Anchor): AnchorEntity {
253             return AnchorEntity.create(session.platformAdapter, session.scene.entityManager, anchor)
254         }
255     }
256 
257     /** Extension function that converts [RtAnchorEntity.State] to [AnchorEntity.State]. */
258     private fun Int.fromRtState() =
259         when (this) {
260             RtAnchorEntity.State.UNANCHORED -> State.UNANCHORED
261             RtAnchorEntity.State.ANCHORED -> State.ANCHORED
262             RtAnchorEntity.State.TIMED_OUT -> State.TIMEDOUT
263             RtAnchorEntity.State.ERROR -> State.ERROR
264             else -> throw IllegalArgumentException("Unknown state: $this")
265         }
266 
267     /**
268      * Registers a listener to be called when the Anchor moves relative to its underlying space.
269      *
270      * <p> The callback is triggered by any anchor movements such as those made by the underlying
271      * perception stack to maintain the anchor's position relative to the real world. Any cached
272      * data relative to the activity space or any other "space" should be updated when this callback
273      * is triggered.
274      *
275      * @param listener The listener to register if non-null, else stops listening if null.
276      * @param executor The executor to run the listener on. Defaults to SceneCore executor if null.
277      */
278     @JvmOverloads
279     @Suppress("ExecutorRegistration")
280     public fun setOnSpaceUpdatedListener(
281         listener: OnSpaceUpdatedListener?,
282         executor: Executor? = null,
283     ) {
284         rtEntity.setOnSpaceUpdatedListener(listener?.let { { it.onSpaceUpdated() } }, executor)
285     }
286 }
287 
288 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
289 public fun interface OnStateChangedListener {
onStateChangednull290     public fun onStateChanged(newState: @AnchorEntity.StateValue Int)
291 }
292