• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 com.android.car;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertTrue;
23 
24 import android.car.Car;
25 import android.car.hardware.CarSensorEvent;
26 import android.car.hardware.CarSensorManager;
27 import android.hardware.automotive.vehicle.V2_0.VehicleGear;
28 import android.hardware.automotive.vehicle.V2_0.VehicleIgnitionState;
29 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
30 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
31 import android.os.SystemClock;
32 import android.test.suitebuilder.annotation.MediumTest;
33 import android.util.Log;
34 
35 import com.android.car.vehiclehal.VehiclePropValueBuilder;
36 
37 import org.junit.Test;
38 
39 /**
40  * Test the public entry points for the CarSensorManager
41  */
42 @MediumTest
43 public class CarSensorManagerTest extends MockedCarTestBase {
44     private static final String TAG = CarSensorManagerTest.class.getSimpleName();
45 
46     private CarSensorManager mCarSensorManager;
47 
48     @Override
configureMockedHal()49     protected synchronized void configureMockedHal() {
50         addProperty(VehicleProperty.NIGHT_MODE,
51                 VehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
52                         .addIntValue(0)
53                         .build());
54         addProperty(VehicleProperty.PERF_VEHICLE_SPEED,
55                 VehiclePropValueBuilder.newBuilder(VehicleProperty.PERF_VEHICLE_SPEED)
56                         .addFloatValue(0f)
57                         .build());
58         addProperty(VehicleProperty.FUEL_LEVEL,
59                 VehiclePropValueBuilder.newBuilder(VehicleProperty.FUEL_LEVEL)
60                         .addFloatValue(20000)  // ml
61                         .build());
62         addProperty(VehicleProperty.PARKING_BRAKE_ON,
63                 VehiclePropValueBuilder.newBuilder(VehicleProperty.PARKING_BRAKE_ON)
64                         .setBooleanValue(true)
65                         .build());
66         addProperty(VehicleProperty.CURRENT_GEAR,
67                 VehiclePropValueBuilder.newBuilder(VehicleProperty.CURRENT_GEAR)
68                         .addIntValue(0)
69                         .build());
70         addProperty(VehicleProperty.GEAR_SELECTION,
71                 VehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
72                         .addIntValue(0)
73                         .build());
74         addProperty(VehicleProperty.IGNITION_STATE,
75                 VehiclePropValueBuilder.newBuilder(VehicleProperty.IGNITION_STATE)
76                         .addIntValue(CarSensorEvent.IGNITION_STATE_ACC)
77                         .build());
78     }
79 
80     @Override
setUp()81     public void setUp() throws Exception {
82         super.setUp();
83         // Start the HAL layer and set up the sensor manager service
84         mCarSensorManager = (CarSensorManager) getCar().getCarManager(Car.SENSOR_SERVICE);
85     }
86 
87     /**
88      * Test single sensor availability entry point
89      * @throws Exception
90      */
91     @Test
testSensorAvailability()92     public void testSensorAvailability() throws Exception {
93         // NOTE:  Update this test if/when the reserved values put into use.  For now, we
94         //        expect them to never be supported.
95         assertFalse(mCarSensorManager.isSensorSupported(CarSensorManager.SENSOR_TYPE_RESERVED1));
96         assertFalse(mCarSensorManager.isSensorSupported(CarSensorManager.SENSOR_TYPE_RESERVED13));
97         assertFalse(mCarSensorManager.isSensorSupported(CarSensorManager.SENSOR_TYPE_RESERVED21));
98 
99         // We expect these sensors to always be available
100         assertTrue(mCarSensorManager.isSensorSupported(CarSensorManager.SENSOR_TYPE_CAR_SPEED));
101         assertTrue(mCarSensorManager.isSensorSupported(CarSensorManager.SENSOR_TYPE_FUEL_LEVEL));
102         assertTrue(mCarSensorManager.isSensorSupported(CarSensorManager.SENSOR_TYPE_PARKING_BRAKE));
103         assertTrue(mCarSensorManager.isSensorSupported(CarSensorManager.SENSOR_TYPE_GEAR));
104         assertTrue(mCarSensorManager.isSensorSupported(CarSensorManager.SENSOR_TYPE_NIGHT));
105         assertTrue(mCarSensorManager.isSensorSupported(
106                 CarSensorManager.SENSOR_TYPE_IGNITION_STATE));
107     }
108 
109     /**
110      * Test sensor enumeration entry point
111      * @throws Exception
112      */
113     @Test
testSensorEnumeration()114     public void testSensorEnumeration() throws Exception {
115         int[] supportedSensors = mCarSensorManager.getSupportedSensors();
116         assertNotNull(supportedSensors);
117 
118         Log.i(TAG, "Found " + supportedSensors.length + " supported sensors.");
119 
120         // Unfortunately, we don't have a definitive range for legal sensor values,
121         // so we have set a "reasonable" range here.  The ending value, in particular,
122         // will need to be updated if/when new sensor types are allowed.
123         // Here we are ensuring that all the enumerated sensors also return supported.
124         for (int candidate = 0; candidate <= CarSensorManager.SENSOR_TYPE_RESERVED21; ++candidate) {
125             boolean supported = mCarSensorManager.isSensorSupported(candidate);
126             boolean found = false;
127             for (int sensor : supportedSensors) {
128                 if (candidate == sensor) {
129                     found = true;
130                     Log.i(TAG, "Sensor type " + sensor + " is supported.");
131                     break;
132                 }
133             }
134 
135             // Make sure the individual query on a sensor type is consistent
136             assertEquals(found, supported);
137         }
138     }
139 
140     /**
141      * Test sensor notification registration, delivery, and unregistration
142      * @throws Exception
143      */
144     @Test
testEvents()145     public void testEvents() throws Exception {
146         // Set up our listener callback
147         SensorListener listener = new SensorListener();
148         mCarSensorManager.registerListener(listener,
149                 CarSensorManager.SENSOR_TYPE_NIGHT,
150                 CarSensorManager.SENSOR_RATE_FASTEST);
151 
152         VehiclePropValue value;
153         CarSensorEvent event;
154         CarSensorEvent.NightData data = null;
155 
156         // Clear event generated by registerCallback()
157         listener.waitForSensorChange();
158         listener.reset();
159 
160         // Set the value TRUE and wait for the event to arrive
161         getMockedVehicleHal().injectEvent(
162                 VehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
163                         .setBooleanValue(true)
164                         .setTimestamp(51L)
165                         .build(), true);
166         assertTrue(listener.waitForSensorChange(51L));
167 
168         // Ensure we got the expected event
169         assertEquals(listener.getLastEvent().sensorType, CarSensorManager.SENSOR_TYPE_NIGHT);
170 
171         // Ensure we got the expected value in our callback
172         data = listener.getLastEvent().getNightData(data);
173         Log.d(TAG, "NightMode " + data.isNightMode + " at " + data.timestamp);
174         assertTrue(data.isNightMode);
175 
176         // Ensure we have the expected value in the sensor manager's cache
177         event = mCarSensorManager.getLatestSensorEvent(CarSensorManager.SENSOR_TYPE_NIGHT);
178         assertNotNull(event);
179         data = event.getNightData(data);
180         assertEquals("Unexpected event timestamp", data.timestamp, 51);
181         assertTrue("Unexpected value", data.isNightMode);
182 
183         listener.reset();
184         // Set the value FALSE
185         getMockedVehicleHal().injectEvent(
186                 VehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
187                         .setTimestamp(1001)
188                         .setBooleanValue(false)
189                         .build(), true);
190         assertTrue(listener.waitForSensorChange(1001));
191 
192         // Ensure we got the expected event
193         assertEquals(listener.getLastEvent().sensorType, CarSensorManager.SENSOR_TYPE_NIGHT);
194 
195         // Ensure we got the expected value in our callback
196         data = listener.getLastEvent().getNightData(data);
197         assertEquals("Unexpected event timestamp", 1001, data.timestamp);
198         assertFalse("Unexpected value", data.isNightMode);
199 
200         // Ensure we have the expected value in the sensor manager's cache
201         event = mCarSensorManager.getLatestSensorEvent(CarSensorManager.SENSOR_TYPE_NIGHT);
202         assertNotNull(event);
203         data = event.getNightData(data);
204         assertFalse(data.isNightMode);
205 
206         // Unregister our handler (from all sensor types)
207         mCarSensorManager.unregisterListener(listener);
208 
209         listener.reset();
210         // Set the value TRUE again
211         value = VehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
212                 .setTimestamp(2001)
213                 .setBooleanValue(true)
214                 .build();
215         getMockedVehicleHal().injectEvent(value, true);
216 
217         // Ensure we did not get a callback (should timeout)
218         Log.i(TAG, "waiting for unexpected callback -- should timeout.");
219         assertFalse(listener.waitForSensorChange(2001));
220 
221         // Despite us not having a callback registered, the Sensor Manager should see the update
222         event = mCarSensorManager.getLatestSensorEvent(CarSensorManager.SENSOR_TYPE_NIGHT);
223         assertNotNull(event);
224         data = event.getNightData(data);
225         assertEquals("Unexpected event timestamp", data.timestamp, 2001);
226         assertTrue("Unexpected value", data.isNightMode);
227     }
228 
229     @Test
testIgnitionState()230     public void testIgnitionState() {
231         CarSensorEvent event = mCarSensorManager.getLatestSensorEvent(
232                 CarSensorManager.SENSOR_TYPE_IGNITION_STATE);
233         assertNotNull(event);
234         assertEquals(CarSensorEvent.IGNITION_STATE_ACC, event.intValues[0]);
235     }
236 
237     @Test
testIgnitionEvents()238     public void testIgnitionEvents() throws Exception {
239         SensorListener listener = new SensorListener();
240         mCarSensorManager.registerListener(listener, CarSensorManager.SENSOR_TYPE_IGNITION_STATE,
241                 CarSensorManager.SENSOR_RATE_NORMAL);
242         // Clear event generated by registerCallback()
243         listener.waitForSensorChange();
244 
245         // Mapping of HAL -> Manager ignition states.
246         int[] ignitionStates = new int[] {
247                 VehicleIgnitionState.UNDEFINED, CarSensorEvent.IGNITION_STATE_UNDEFINED,
248                 VehicleIgnitionState.LOCK, CarSensorEvent.IGNITION_STATE_LOCK,
249                 VehicleIgnitionState.OFF, CarSensorEvent.IGNITION_STATE_OFF,
250                 VehicleIgnitionState.ACC, CarSensorEvent.IGNITION_STATE_ACC,
251                 VehicleIgnitionState.ON, CarSensorEvent.IGNITION_STATE_ON,
252                 VehicleIgnitionState.START, CarSensorEvent.IGNITION_STATE_START,
253                 VehicleIgnitionState.ON, CarSensorEvent.IGNITION_STATE_ON,
254                 VehicleIgnitionState.LOCK, CarSensorEvent.IGNITION_STATE_LOCK,
255         };
256 
257         for (int i = 0; i < ignitionStates.length; i += 2) {
258             injectIgnitionStateAndAssert(listener, ignitionStates[i], ignitionStates[i + 1]);
259         }
260     }
261 
injectIgnitionStateAndAssert(SensorListener listener, int halIgnitionState, int mgrIgnitionState)262     private void injectIgnitionStateAndAssert(SensorListener listener, int halIgnitionState,
263             int mgrIgnitionState) throws Exception{
264         listener.reset();
265         long time = SystemClock.elapsedRealtimeNanos();
266         getMockedVehicleHal().injectEvent(
267                 VehiclePropValueBuilder.newBuilder(VehicleProperty.IGNITION_STATE)
268                         .addIntValue(halIgnitionState)
269                         .setTimestamp(time)
270                         .build(), true);
271         assertTrue(listener.waitForSensorChange(time));
272 
273         CarSensorEvent eventReceived = listener.getLastEvent();
274         assertEquals(CarSensorManager.SENSOR_TYPE_IGNITION_STATE, eventReceived.sensorType);
275         assertEquals(mgrIgnitionState, eventReceived.intValues[0]);
276     }
277 
278     @Test
testGear()279     public void testGear() throws Exception {
280         SensorListener listener = new SensorListener();
281 
282         mCarSensorManager.registerListener(listener, CarSensorManager.SENSOR_TYPE_GEAR,
283                 CarSensorManager.SENSOR_RATE_NORMAL);
284 
285         // Clear event generated by registerCallback()
286         listener.waitForSensorChange();
287 
288         // Mapping of HAL -> Manager gear selection states.
289         int[] gears = new int[] {
290                 VehicleGear.GEAR_PARK, CarSensorEvent.GEAR_PARK,
291                 VehicleGear.GEAR_DRIVE, CarSensorEvent.GEAR_DRIVE,
292                 VehicleGear.GEAR_NEUTRAL, CarSensorEvent.GEAR_NEUTRAL,
293                 VehicleGear.GEAR_REVERSE, CarSensorEvent.GEAR_REVERSE,
294                 VehicleGear.GEAR_1, CarSensorEvent.GEAR_FIRST,
295                 VehicleGear.GEAR_2, CarSensorEvent.GEAR_SECOND,
296                 VehicleGear.GEAR_3, CarSensorEvent.GEAR_THIRD,
297                 VehicleGear.GEAR_4, CarSensorEvent.GEAR_FOURTH,
298                 VehicleGear.GEAR_5, CarSensorEvent.GEAR_FIFTH,
299                 VehicleGear.GEAR_6, CarSensorEvent.GEAR_SIXTH,
300                 VehicleGear.GEAR_7, CarSensorEvent.GEAR_SEVENTH,
301                 VehicleGear.GEAR_8, CarSensorEvent.GEAR_EIGHTH,
302                 VehicleGear.GEAR_9, CarSensorEvent.GEAR_NINTH,
303         };
304 
305         for (int i = 0; i < gears.length; i += 2) {
306             injectGearEventAndAssert(listener, gears[i], gears[i + 1]);
307         }
308     }
309 
injectGearEventAndAssert(SensorListener listener, int halValue, int carSensorValue)310     private void injectGearEventAndAssert(SensorListener listener, int halValue,
311             int carSensorValue) throws Exception {
312         listener.reset();
313         long time = SystemClock.elapsedRealtimeNanos();
314         getMockedVehicleHal().injectEvent(
315                 VehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
316                         .addIntValue(halValue)
317                         .setTimestamp(time)
318                         .build(), true);
319         assertTrue(listener.waitForSensorChange(time));
320         CarSensorEvent event = mCarSensorManager.getLatestSensorEvent(
321                 CarSensorManager.SENSOR_TYPE_GEAR);
322         assertNotNull(event);
323         assertEquals(carSensorValue, event.intValues[0]);
324     }
325 
326     /**
327      * Test sensor multiple liseners notification registration, delivery and unregistration.
328      * @throws Exception
329      */
330     @Test
testEventsWithMultipleListeners()331     public void testEventsWithMultipleListeners() throws Exception {
332         // Set up our listeners callback
333         SensorListener listener1 = new SensorListener();
334         SensorListener listener2 = new SensorListener();
335         SensorListener listener3 = new SensorListener();
336 
337         mCarSensorManager.registerListener(listener1,
338                 CarSensorManager.SENSOR_TYPE_NIGHT,
339                 CarSensorManager.SENSOR_RATE_NORMAL);
340 
341         mCarSensorManager.registerListener(listener2,
342                 CarSensorManager.SENSOR_TYPE_NIGHT,
343                 CarSensorManager.SENSOR_RATE_NORMAL);
344 
345         mCarSensorManager.registerListener(listener3,
346                 CarSensorManager.SENSOR_TYPE_NIGHT,
347                 CarSensorManager.SENSOR_RATE_FASTEST);
348 
349         CarSensorEvent.NightData data = null;
350         VehiclePropValue value;
351         CarSensorEvent event;
352 
353         // Clear event generated by registerCallback()
354         listener1.waitForSensorChange();
355         listener2.waitForSensorChange();
356         listener3.waitForSensorChange();
357         listener1.reset();
358         listener2.reset();
359         listener3.reset();
360 
361         // Set the value TRUE and wait for the event to arrive
362         value = VehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
363                 .setTimestamp(1001L)
364                 .setBooleanValue(true)
365                 .build();
366         getMockedVehicleHal().injectEvent(value, true);
367 
368         assertTrue(listener1.waitForSensorChange(1001L));
369         assertTrue(listener2.waitForSensorChange(1001L));
370         assertTrue(listener3.waitForSensorChange(1001L));
371 
372         // Ensure we got the expected event
373         assertEquals(listener1.getLastEvent().sensorType, CarSensorManager.SENSOR_TYPE_NIGHT);
374         assertEquals(listener2.getLastEvent().sensorType, CarSensorManager.SENSOR_TYPE_NIGHT);
375         assertEquals(listener3.getLastEvent().sensorType, CarSensorManager.SENSOR_TYPE_NIGHT);
376 
377         // Ensure we got the expected value in our callback
378         data = listener1.getLastEvent().getNightData(data);
379         Log.d(TAG, "NightMode " + data.isNightMode + " at " + data.timestamp);
380         assertTrue(data.isNightMode);
381 
382         data = listener2.getLastEvent().getNightData(data);
383         Log.d(TAG, "NightMode " + data.isNightMode + " at " + data.timestamp);
384         assertTrue(data.isNightMode);
385 
386         data = listener3.getLastEvent().getNightData(data);
387         Log.d(TAG, "NightMode " + data.isNightMode + " at " + data.timestamp);
388         assertTrue(data.isNightMode);
389 
390         // Ensure we have the expected value in the sensor manager's cache
391         event = mCarSensorManager.getLatestSensorEvent(CarSensorManager.SENSOR_TYPE_NIGHT);
392         data = event.getNightData(data);
393         assertEquals("Unexpected event timestamp", 1001, data.timestamp);
394         assertTrue("Unexpected value", data.isNightMode);
395 
396         listener1.reset();
397         listener2.reset();
398         listener3.reset();
399         // Set the value FALSE
400         value = VehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
401                 .setTimestamp(2001)
402                 .setBooleanValue(false)
403                 .build();
404         getMockedVehicleHal().injectEvent(value, true);
405         assertTrue(listener1.waitForSensorChange(2001));
406         assertTrue(listener2.waitForSensorChange(2001));
407         assertTrue(listener3.waitForSensorChange(2001));
408 
409         // Ensure we got the expected event
410         assertEquals(listener1.getLastEvent().sensorType, CarSensorManager.SENSOR_TYPE_NIGHT);
411         assertEquals(listener2.getLastEvent().sensorType, CarSensorManager.SENSOR_TYPE_NIGHT);
412         assertEquals(listener3.getLastEvent().sensorType, CarSensorManager.SENSOR_TYPE_NIGHT);
413 
414         // Ensure we got the expected value in our callback
415         data = listener1.getLastEvent().getNightData(data);
416         assertEquals("Unexpected event timestamp", 2001, data.timestamp);
417         assertFalse("Unexpected value", data.isNightMode);
418 
419         data = listener2.getLastEvent().getNightData(data);
420         assertEquals("Unexpected event timestamp", 2001, data.timestamp);
421         assertFalse("Unexpected value", data.isNightMode);
422 
423         data = listener3.getLastEvent().getNightData(data);
424         assertEquals("Unexpected event timestamp", 2001, data.timestamp);
425         assertFalse("Unexpected value", data.isNightMode);
426 
427         // Ensure we have the expected value in the sensor manager's cache
428         event = mCarSensorManager.getLatestSensorEvent(CarSensorManager.SENSOR_TYPE_NIGHT);
429         data = event.getNightData(data);
430         assertFalse(data.isNightMode);
431 
432         Log.d(TAG, "Unregistering listener3");
433         listener1.reset();
434         listener2.reset();
435         listener3.reset();
436         mCarSensorManager.unregisterListener(listener3);
437         Log.d(TAG, "Rate changed - expect sensor restart and change event sent.");
438         value = VehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
439                 .setTimestamp(3002)
440                 .setBooleanValue(false)
441                 .build();
442         getMockedVehicleHal().injectEvent(value, true);
443         assertTrue(listener1.waitForSensorChange());
444         assertTrue(listener2.waitForSensorChange());
445         assertFalse(listener3.waitForSensorChange());
446         listener1.reset();
447         listener2.reset();
448         listener3.reset();
449         // Set the value TRUE again
450         value = VehiclePropValueBuilder.newBuilder(VehicleProperty.NIGHT_MODE)
451                 .setTimestamp()
452                 .setBooleanValue(true)
453                 .build();
454         getMockedVehicleHal().injectEvent(value, true);
455 
456         assertTrue(listener1.waitForSensorChange());
457         assertTrue(listener2.waitForSensorChange());
458         listener1.reset();
459         listener2.reset();
460 
461         // Ensure we did not get a callback (should timeout)
462         Log.i(TAG, "waiting for unexpected callback -- should timeout.");
463         assertFalse(listener3.waitForSensorChange());
464 
465         Log.d(TAG, "Unregistering listener2");
466         mCarSensorManager.unregisterListener(listener2);
467 
468         Log.d(TAG, "Rate did nor change - dont expect sensor restart and change event sent.");
469         assertFalse(listener1.waitForSensorChange());
470         assertFalse(listener2.waitForSensorChange());
471         assertFalse(listener3.waitForSensorChange());
472     }
473 
474 
475     /**
476      * Callback function we register for sensor update notifications.
477      * This tracks the number of times it has been called via the mAvailable semaphore,
478      * and keeps a reference to the most recent event delivered.
479      */
480     class SensorListener implements CarSensorManager.OnSensorChangedListener {
481         private final Object mSync = new Object();
482 
483         private CarSensorEvent mLastEvent = null;
484 
getLastEvent()485         CarSensorEvent getLastEvent() {
486             return mLastEvent;
487         }
488 
reset()489         void reset() {
490             synchronized (mSync) {
491                 mLastEvent = null;
492             }
493         }
494 
waitForSensorChange()495         boolean waitForSensorChange() throws InterruptedException {
496             return waitForSensorChange(0);
497         }
498 
499         // Returns True to indicate receipt of a sensor event.  False indicates a timeout.
waitForSensorChange(long eventTimeStamp)500         boolean waitForSensorChange(long eventTimeStamp) throws InterruptedException {
501             long start = SystemClock.elapsedRealtime();
502             boolean matchTimeStamp = eventTimeStamp != 0;
503             synchronized (mSync) {
504                 Log.d(TAG, "waitForSensorChange, mLastEvent: " + mLastEvent);
505                 while ((mLastEvent == null
506                         || (matchTimeStamp && mLastEvent.timestamp != eventTimeStamp))
507                         && (start + SHORT_WAIT_TIMEOUT_MS > SystemClock.elapsedRealtime())) {
508                     mSync.wait(10L);
509                 }
510                 return mLastEvent != null &&
511                         (!matchTimeStamp || mLastEvent.timestamp == eventTimeStamp);
512             }
513         }
514 
515         @Override
onSensorChanged(CarSensorEvent event)516         public void onSensorChanged(CarSensorEvent event) {
517             Log.d(TAG, "onSensorChanged, event: " + event);
518             synchronized (mSync) {
519                 // We're going to hold a reference to this object
520                 mLastEvent = event;
521                 mSync.notify();
522             }
523         }
524     }
525 
526 }
527