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