1 /*
2  * Copyright (C) 2023 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.virtualdevice.cts.sensor;
18 
19 import static android.hardware.Sensor.TYPE_ACCELEROMETER;
20 import static android.hardware.Sensor.TYPE_GYROSCOPE;
21 import static android.hardware.SensorDirectChannel.RATE_NORMAL;
22 import static android.hardware.SensorDirectChannel.RATE_STOP;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import static org.junit.Assert.assertThrows;
27 
28 import android.companion.virtual.sensor.VirtualSensor;
29 import android.companion.virtual.sensor.VirtualSensorDirectChannelWriter;
30 import android.companion.virtual.sensor.VirtualSensorEvent;
31 import android.os.SharedMemory;
32 import android.platform.test.annotations.AppModeFull;
33 
34 import androidx.test.ext.junit.runners.AndroidJUnit4;
35 
36 import org.junit.After;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 
40 import java.nio.ByteBuffer;
41 import java.nio.ByteOrder;
42 import java.util.List;
43 
44 
45 @RunWith(AndroidJUnit4.class)
46 @AppModeFull(reason = "VirtualDeviceManager cannot be accessed by instant apps")
47 public class VirtualSensorDirectChannelWriterTest {
48 
49     private static final int CHANNEL_HANDLE = 7;
50     private static final int SENSOR_HANDLE = 42;
51     private static final int REPORT_TOKEN = 15;
52     private static final String ACCELEROMETER_SENSOR_NAME = "VirtualAccelerometer";
53     private static final String GYROSCOPE_SENSOR_NAME = "VirtualGyroscope";
54     private static final String SHARED_MEMORY_NAME = "VirtualSensorSharedMemory";
55     private static final int SENSOR_EVENT_SIZE = 104;
56     private static final int SHARED_MEMORY_SIZE = SENSOR_EVENT_SIZE * 3;
57 
58     private final VirtualSensor mAccelerometer = new VirtualSensor(
59             SENSOR_HANDLE, TYPE_ACCELEROMETER, ACCELEROMETER_SENSOR_NAME);
60     private final VirtualSensor mGyroscope = new VirtualSensor(
61             SENSOR_HANDLE + 1, TYPE_GYROSCOPE, GYROSCOPE_SENSOR_NAME);
62 
63     private final VirtualSensorDirectChannelWriter mWriter = new VirtualSensorDirectChannelWriter();
64 
65     @After
tearDown()66     public void tearDown() {
67         mWriter.close();
68     }
69 
70     @Test
configureChannel_rateNormal_noChannelAdded_returnsError()71     public void configureChannel_rateNormal_noChannelAdded_returnsError() {
72         assertThat(
73                 mWriter.configureChannel(CHANNEL_HANDLE, mAccelerometer, RATE_NORMAL, REPORT_TOKEN))
74                 .isFalse();
75     }
76 
77     @Test
configureChannel_rateStop_noChannelAdded_returnsError()78     public void configureChannel_rateStop_noChannelAdded_returnsError() {
79         assertThat(
80                 mWriter.configureChannel(CHANNEL_HANDLE, mAccelerometer, RATE_STOP, REPORT_TOKEN))
81                 .isFalse();
82     }
83 
84     @Test
addChannel_nullSharedMemory_throwsException()85     public void addChannel_nullSharedMemory_throwsException() {
86         assertThrows(
87                 NullPointerException.class,
88                 () -> mWriter.addChannel(CHANNEL_HANDLE, /*sharedMemory=*/null));
89     }
90 
91     @Test
removeChannel_closesSharedMemory()92     public void removeChannel_closesSharedMemory() throws Exception {
93         SharedMemory sharedMemory = SharedMemory.create(SHARED_MEMORY_NAME, SHARED_MEMORY_SIZE);
94         assertThat(sharedMemory.getSize()).isGreaterThan(0);
95 
96         mWriter.addChannel(CHANNEL_HANDLE, sharedMemory);
97         mWriter.removeChannel(CHANNEL_HANDLE);
98 
99         // The shared memory's file descriptor is closed.
100         assertThrows(IllegalStateException.class, sharedMemory::getSize);
101     }
102 
103     @Test
writeEvent_nullEvent_throwsException()104     public void writeEvent_nullEvent_throwsException() {
105         assertThrows(
106                 NullPointerException.class,
107                 () -> mWriter.writeSensorEvent(mAccelerometer, /*event=*/null));
108     }
109 
110     @Test
writeEvent_nullSensor_throwsException()111     public void writeEvent_nullSensor_throwsException() {
112         assertThrows(
113                 NullPointerException.class,
114                 () -> mWriter.writeSensorEvent(/*sensor=*/null,
115                         new VirtualSensorEvent.Builder(new float[]{1, 2, 3}).build()));
116     }
117 
118     @Test
writeEvent_noConfiguredChannel_returnsError()119     public void writeEvent_noConfiguredChannel_returnsError() {
120         assertThat(mWriter.writeSensorEvent(
121                 mAccelerometer, new VirtualSensorEvent.Builder(new float[]{1, 2, 3}).build()))
122                 .isFalse();
123     }
124 
125     @Test
writeEvent_channelStopped_returnsError()126     public void writeEvent_channelStopped_returnsError() throws Exception {
127         SharedMemory sharedMemory = SharedMemory.create(SHARED_MEMORY_NAME, SHARED_MEMORY_SIZE);
128         mWriter.addChannel(CHANNEL_HANDLE, sharedMemory);
129 
130         assertThat(
131                 mWriter.configureChannel(CHANNEL_HANDLE, mAccelerometer, RATE_NORMAL, REPORT_TOKEN))
132                 .isTrue();
133         assertThat(
134                 mWriter.configureChannel(CHANNEL_HANDLE, mAccelerometer, RATE_STOP, REPORT_TOKEN))
135                 .isTrue();
136 
137         assertThat(mWriter.writeSensorEvent(
138                 mAccelerometer, new VirtualSensorEvent.Builder(new float[]{1, 2, 3}).build()))
139                 .isFalse();
140     }
141 
142     @Test
writeEvent_channelRemoved_returnsError()143     public void writeEvent_channelRemoved_returnsError() throws Exception {
144         SharedMemory sharedMemory = SharedMemory.create(SHARED_MEMORY_NAME, SHARED_MEMORY_SIZE);
145         mWriter.addChannel(CHANNEL_HANDLE, sharedMemory);
146         assertThat(
147                 mWriter.configureChannel(CHANNEL_HANDLE, mAccelerometer, RATE_NORMAL, REPORT_TOKEN))
148                 .isTrue();
149         mWriter.removeChannel(CHANNEL_HANDLE);
150 
151         assertThat(mWriter.writeSensorEvent(
152                 mAccelerometer, new VirtualSensorEvent.Builder(new float[]{1, 2, 3}).build()))
153                 .isFalse();
154     }
155 
156     @Test
writeEvent_singleChannel()157     public void writeEvent_singleChannel() throws Exception {
158         testWriteSensorEvents(
159                 List.of(new SharedMemoryWrapper(CHANNEL_HANDLE, SHARED_MEMORY_SIZE)),
160                 List.of(mAccelerometer));
161     }
162 
163     @Test
writeEvent_multipleChannels()164     public void writeEvent_multipleChannels() throws Exception {
165         testWriteSensorEvents(
166                 List.of(
167                         new SharedMemoryWrapper(CHANNEL_HANDLE, SHARED_MEMORY_SIZE),
168                         new SharedMemoryWrapper(CHANNEL_HANDLE + 1, SHARED_MEMORY_SIZE * 2)),
169                 List.of(mAccelerometer));
170     }
171 
172     @Test
writeEvent_multipleSensors_singleChannel()173     public void writeEvent_multipleSensors_singleChannel() throws Exception {
174         testWriteSensorEvents(
175                 List.of(new SharedMemoryWrapper(CHANNEL_HANDLE, SHARED_MEMORY_SIZE)),
176                 List.of(mAccelerometer, mGyroscope));
177     }
178 
179     @Test
writeEvent_multipleSensors_multipleChannels()180     public void writeEvent_multipleSensors_multipleChannels() throws Exception {
181         testWriteSensorEvents(
182                 List.of(
183                         new SharedMemoryWrapper(CHANNEL_HANDLE, SHARED_MEMORY_SIZE),
184                         new SharedMemoryWrapper(CHANNEL_HANDLE + 1, SHARED_MEMORY_SIZE * 2)),
185                 List.of(mAccelerometer, mGyroscope));
186     }
187 
testWriteSensorEvents( List<SharedMemoryWrapper> channels, List<VirtualSensor> sensors)188     private void testWriteSensorEvents(
189             List<SharedMemoryWrapper> channels, List<VirtualSensor> sensors) {
190         for (SharedMemoryWrapper channel : channels) {
191             for (VirtualSensor sensor : sensors) {
192                 assertThat(
193                         mWriter.configureChannel(
194                                 channel.mChannelHandle, sensor, RATE_NORMAL,
195                                 /*reportToken=*/sensor.getHandle()))
196                         .isTrue();
197             }
198         }
199 
200         for (int i = 0; i < 8; ++i) {
201             VirtualSensorEvent event = new VirtualSensorEvent.Builder(new float[]{i, i + 1, i + 2})
202                     .setTimestampNanos(System.nanoTime())
203                     .build();
204 
205             for (VirtualSensor sensor : sensors) {
206                 int sensorIndex = sensors.indexOf(sensor) + 1;
207                 if (i % sensorIndex == 0) {
208                     assertThat(mWriter.writeSensorEvent(sensor, event)).isTrue();
209 
210                     for (SharedMemoryWrapper channel : channels) {
211                         channel.verifyEvent(sensor, event, /*eventCount=*/i / sensorIndex + 1);
212                     }
213                 }
214             }
215         }
216     }
217 
218     private final class SharedMemoryWrapper {
219         private final int mChannelHandle;
220         private final SharedMemory mSharedMemory;
221         private final ByteBuffer mByteBuffer;
222         private int mReadOffset = 0;
223 
SharedMemoryWrapper(int channelHandle, int size)224         SharedMemoryWrapper(int channelHandle, int size) throws Exception {
225             mChannelHandle = channelHandle;
226             mSharedMemory = SharedMemory.create(SHARED_MEMORY_NAME, size);
227             mByteBuffer = mSharedMemory.mapReadOnly();
228             mByteBuffer.order(ByteOrder.nativeOrder());
229 
230             mWriter.addChannel(mChannelHandle, mSharedMemory);
231         }
232 
verifyEvent(VirtualSensor sensor, VirtualSensorEvent event, int eventCount)233         void verifyEvent(VirtualSensor sensor, VirtualSensorEvent event, int eventCount) {
234             mByteBuffer.position(mReadOffset);
235             assertThat(mByteBuffer.getInt()).isEqualTo(SENSOR_EVENT_SIZE);
236             assertThat(mByteBuffer.getInt()).isEqualTo(/*reportToken=*/sensor.getHandle());
237             assertThat(mByteBuffer.getInt()).isEqualTo(sensor.getType());
238             assertThat(mByteBuffer.getInt()).isEqualTo(eventCount);
239             assertThat(mByteBuffer.getLong()).isEqualTo(event.getTimestampNanos());
240             for (int i = 0; i < 16; ++i) {
241                 if (i < event.getValues().length) {
242                     assertThat(mByteBuffer.getFloat()).isEqualTo(event.getValues()[i]);
243                 } else {
244                     assertThat(mByteBuffer.getFloat()).isEqualTo(0);
245                 }
246             }
247             assertThat(mByteBuffer.getInt()).isEqualTo(0);
248 
249             mReadOffset += SENSOR_EVENT_SIZE;
250             if (mReadOffset + SENSOR_EVENT_SIZE >= mSharedMemory.getSize()) {
251                 mReadOffset = 0;
252             }
253         }
254 
255     }
256 }
257