1 /* 2 * Copyright (C) 2022 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 android.hardware.cts.accessories; 18 19 import static org.junit.Assert.*; 20 21 import com.android.cts.input.HidResultData; 22 23 /** Declares virtual head trackers for SensorHeadTrackerTest registered through /dev/uhid/ */ 24 public class VirtualHeadTracker extends HidCommand { 25 private static final String TAG = "VirtualHeadTracker"; 26 private boolean mIsHeadTrackerEnabled = false; 27 private VirtualHtRunnable mVirtualHtRunnable = new VirtualHtRunnable(); 28 29 /* 30 The orientation, angular velocity and discontinuity 31 count for the virtual head tracker. 32 In C++ this would be: 33 struct __attribute__((packed)) { 34 int16_t orientation[3]; 35 int16_t angularVelocity[3]; 36 uint8_t discontinuityCount; 37 }; 38 */ 39 private static final float[] ORIENTATION = { 40 (float) Math.PI / 2, (float) -Math.PI / 3, (float) Math.PI / 4 41 }; 42 private static final float[] ANGULAR_VELOCITY = { 43 (float) Math.PI, (float) -Math.PI * 2, (float) Math.PI * 3 44 }; 45 private byte mDiscontinuityCount = 0x00; 46 public static final float[] HEAD_TRACKER_VALUES = { 47 ORIENTATION[0], 48 ORIENTATION[1], 49 ORIENTATION[2], 50 ANGULAR_VELOCITY[0], 51 ANGULAR_VELOCITY[1], 52 ANGULAR_VELOCITY[2] 53 }; 54 incDiscontinuityCount()55 public void incDiscontinuityCount() { 56 mDiscontinuityCount++; 57 } 58 generateReport()59 private String generateReport() { 60 byte report = 1; 61 62 // HEAD_TRACKER_VALUES is the end results when it goes through sendHidReport. 63 return "[" 64 + String.format("0x%02x,", report) 65 + orientationFloatToStr(ORIENTATION) 66 + angularVelocityFloatToStr(ANGULAR_VELOCITY) 67 + String.format("0x%02x", mDiscontinuityCount) 68 + "]"; 69 } 70 71 /** 72 * Convert an orientation array from float to the int16 HID format as a String of bytes. 73 * 74 * @param orientation a rotation vector represented as float[3] of x, y, z in radians with range 75 * [-pi, pi]; magnitude must be in range [0, pi] 76 */ orientationFloatToStr(float[] orientation)77 private static String orientationFloatToStr(float[] orientation) { 78 String stringOfBytes = new String(); 79 for (float orient : orientation) { 80 int hidValue = (int) Math.round(orient / Math.PI * Short.MAX_VALUE); 81 stringOfBytes += intToShortByteStr(hidValue); 82 } 83 return stringOfBytes; 84 } 85 86 /** 87 * Convert an orientation array from float to the int16 HID format as a String of bytes. 88 * 89 * @param angularVelocity is radians per second, in range [-32, 32] 90 */ angularVelocityFloatToStr(float[] angularVelocity)91 private static String angularVelocityFloatToStr(float[] angularVelocity) { 92 String stringOfBytes = new String(); 93 for (float angle : angularVelocity) { 94 int hidValue = Math.round(angle / 32 * Short.MAX_VALUE); 95 stringOfBytes += intToShortByteStr(hidValue); 96 } 97 return stringOfBytes; 98 } 99 100 /** Clamps an int to short range and returns it as a string in little endian order */ intToShortByteStr(int hidValue)101 private static String intToShortByteStr(int hidValue) { 102 hidValue = Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, hidValue)); 103 return String.format("0x%02x,0x%02x,", hidValue & 0xff, (hidValue & 0xff00) >> 8); 104 } 105 106 @Override processResults(HidResultData results)107 protected void processResults(HidResultData results) { 108 if (mIsHeadTrackerEnabled) { 109 mVirtualHtRunnable.stop(); 110 } 111 mIsHeadTrackerEnabled = (results.reportData[1] & 0x03) == 0x03; 112 setGetReportResponse(results); 113 sendSetReportReply(true); 114 if (mIsHeadTrackerEnabled) { 115 mVirtualHtRunnable.start(); 116 } 117 } 118 119 private class VirtualHtRunnable implements Runnable { 120 private Thread mVirtualHeadTrackerThread; 121 start()122 public void start() { 123 if (mVirtualHeadTrackerThread != null) { 124 fail("mVirtualHeadTrackerThread should be null"); 125 } else { 126 mVirtualHeadTrackerThread = new Thread(this); 127 } 128 mVirtualHeadTrackerThread.start(); 129 } 130 stop()131 public void stop() { 132 mVirtualHeadTrackerThread.interrupt(); 133 try { 134 mVirtualHeadTrackerThread.join(); 135 } catch (InterruptedException ex) { 136 137 } 138 mVirtualHeadTrackerThread = null; 139 } 140 run()141 public void run() { 142 int count = 0; 143 while (!mVirtualHeadTrackerThread.isInterrupted()) { 144 try { 145 Thread.sleep(10); 146 sendHidReport(generateReport()); 147 } catch (InterruptedException ex) { 148 return; 149 } 150 } 151 } 152 } 153 } 154