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 android.content.ContentResolver
20 import android.provider.Settings.System
21 import androidx.annotation.RestrictTo
22 import androidx.xr.runtime.HandTrackingMode
23 import androidx.xr.runtime.Session
24 import androidx.xr.runtime.internal.Hand as RuntimeHand
25 import androidx.xr.runtime.math.Pose
26 import androidx.xr.runtime.math.Quaternion
27 import androidx.xr.runtime.math.Vector3
28 import java.nio.ByteBuffer
29 import java.nio.ByteOrder
30 import kotlinx.coroutines.flow.MutableStateFlow
31 import kotlinx.coroutines.flow.StateFlow
32 import kotlinx.coroutines.flow.asStateFlow
33 
34 /** Contains the tracking information of one of the user's hands. */
35 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
36 public class Hand internal constructor(internal val runtimeHand: RuntimeHand) : Updatable {
37     /** * Companion object holding info to the left and right hands. */
38     public companion object {
39 
40         internal const val PRIMARY_HAND_SETTING_NAME = "primary_hand"
41 
42         /**
43          * Returns the Hand object that corresponds to the user's left hand when available.
44          *
45          * @param session the currently active [Session].
46          * @throws [IllegalStateException] if [HandTrackingMode] is set to Disabled.
47          */
48         @JvmStatic
leftnull49         public fun left(session: Session): Hand? {
50             val perceptionStateExtender = getPerceptionStateExtender(session)
51             val config = perceptionStateExtender.xrResourcesManager.lifecycleManager.config
52             check(config.handTracking != HandTrackingMode.Disabled) {
53                 "Config.HandTrackingMode is set to Disabled."
54             }
55             return perceptionStateExtender.xrResourcesManager.leftHand
56         }
57 
58         /**
59          * Returns the Hand object that corresponds to the user's right hand when available.
60          *
61          * @param session the currently active [Session].
62          * @throws [IllegalStateException] if [HandTrackingMode] is set to Disabled.
63          */
64         @JvmStatic
rightnull65         public fun right(session: Session): Hand? {
66             val perceptionStateExtender = getPerceptionStateExtender(session)
67             val config = perceptionStateExtender.xrResourcesManager.lifecycleManager.config
68             check(config.handTracking != HandTrackingMode.Disabled) {
69                 "Config.HandTrackingMode is set to Disabled."
70             }
71             return perceptionStateExtender.xrResourcesManager.rightHand
72         }
73 
74         /**
75          * Returns the handedness of the user's primary hand.
76          *
77          * @param resolver the [ContentResolver] to use to retrieve the setting.
78          * @return the [Handedness] of the user's primary hand. If the setting is not configured,
79          *   returns [Handedness.UNKNOWN].
80          */
getHandednessnull81         public fun getHandedness(resolver: ContentResolver): Handedness =
82             Handedness.values()[
83                     System.getInt(resolver, PRIMARY_HAND_SETTING_NAME, Handedness.UNKNOWN.ordinal)]
84 
85         private fun getPerceptionStateExtender(session: Session): PerceptionStateExtender {
86             val perceptionStateExtender: PerceptionStateExtender? =
87                 session.stateExtenders.filterIsInstance<PerceptionStateExtender>().first()
88             check(perceptionStateExtender != null) { "PerceptionStateExtender is not available." }
89             return perceptionStateExtender
90         }
91     }
92 
93     /** The handedness of the user's hand. */
94     public enum class Handedness {
95         LEFT,
96         RIGHT,
97         /** The handedness is not available if it is not explicitly set. */
98         UNKNOWN,
99     }
100 
101     /**
102      * The representation of the current state of [Hand].
103      *
104      * @param trackingState the current [TrackingState] of the hand.
105      * @param handJointsBuffer the [ByteBuffer] containing the pose of each joint in the hand.
106      */
107     public class State(
108         public val trackingState: TrackingState,
109         public val handJointsBuffer: ByteBuffer,
110     ) {
111 
112         private class JointsMap(
113             val trackingState: TrackingState,
114             val handJointsBuffer: ByteBuffer
115         ) : Map<HandJointType, Pose> {
116             override val entries: Set<Map.Entry<HandJointType, Pose>>
117                 get() =
118                     if (trackingState == TrackingState.Tracking) {
119                         RuntimeHand.parseHandJoint(trackingState, handJointsBuffer).entries.toSet()
120                     } else {
121                         emptySet()
122                     }
123 
124             override val keys: Set<HandJointType>
125                 get() =
126                     if (trackingState == TrackingState.Tracking) HandJointType.values().toSet()
127                     else emptySet()
128 
129             override val size: Int
130                 get() = keys.size
131 
132             override val values: Collection<Pose>
<lambda>null133                 get() = entries.map { it.value }
134 
containsKeynull135             override fun containsKey(key: HandJointType): Boolean {
136                 return keys.contains(key)
137             }
138 
containsValuenull139             override fun containsValue(value: Pose): Boolean {
140                 return values.contains(value)
141             }
142 
getnull143             override fun get(key: HandJointType): Pose? =
144                 if (trackingState == TrackingState.Tracking) locateHandJointFromBuffer(key)
145                 else null
146 
147             override fun isEmpty(): Boolean {
148                 return trackingState != TrackingState.Tracking
149             }
150 
locateHandJointFromBuffernull151             private fun locateHandJointFromBuffer(handJointType: HandJointType): Pose {
152                 val buffer = handJointsBuffer.duplicate().order(ByteOrder.nativeOrder())
153                 val bytePerPose = 7 * 4
154                 val byteOffset = handJointType.ordinal * bytePerPose
155                 buffer.position(byteOffset)
156                 val qx = buffer.float
157                 val qy = buffer.float
158                 val qz = buffer.float
159                 val qw = buffer.float
160                 val px = buffer.float
161                 val py = buffer.float
162                 val pz = buffer.float
163                 return Pose(Vector3(px, py, pz), Quaternion(qx, qy, qz, qw))
164             }
165         }
166 
167         /**
168          * Returns the current pose of each joint in the hand.
169          *
170          * @return a map of [HandJointType] to [Pose] representing the current pose of each joint in
171          *   the hand.
172          */
173         public val handJoints: Map<HandJointType, Pose> = JointsMap(trackingState, handJointsBuffer)
174 
equalsnull175         override fun equals(other: Any?): Boolean {
176             if (this === other) return true
177             if (other !is State) return false
178             return trackingState == other.trackingState &&
179                 handJointsBuffer == other.handJointsBuffer
180         }
181 
hashCodenull182         override fun hashCode(): Int {
183             var result = trackingState.hashCode()
184             result = 31 * result + handJointsBuffer.hashCode()
185             return result
186         }
187     }
188 
189     private val _state =
190         MutableStateFlow<State>(State(TrackingState.Paused, ByteBuffer.allocate(0)))
191     /** The current [State] of this hand. */
192     public val state: StateFlow<State> = _state.asStateFlow()
193 
updatenull194     override suspend fun update() {
195         _state.emit(State(runtimeHand.trackingState, runtimeHand.handJointsBuffer))
196     }
197 
equalsnull198     override fun equals(other: Any?): Boolean {
199         if (this === other) return true
200         if (other !is Hand) return false
201         return runtimeHand == other.runtimeHand
202     }
203 
hashCodenull204     override fun hashCode(): Int = runtimeHand.hashCode()
205 }
206