1 /*
2  * Copyright (C) 2014 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.cts.verifier.sensors.helpers;
18 
19 import com.android.cts.verifier.sensors.reporting.SensorTestDetails;
20 
21 import android.content.Context;
22 import android.hardware.Sensor;
23 import android.hardware.SensorEvent;
24 import android.hardware.SensorEventListener;
25 import android.hardware.SensorManager;
26 import android.hardware.cts.helpers.SensorCtsHelper;
27 import android.net.LocalServerSocket;
28 import android.net.LocalSocket;
29 import android.util.Log;
30 
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.util.List;
35 import java.util.StringTokenizer;
36 
37 /**
38  * This class handles communication with the host to respond to commands.
39  * The command/response link is through a TCP socket on the host side, forwarded via adb to a local
40  * socket on the device. The system uses a standard "accept-read_command-send_response-close" to
41  * execute commands sent from the host.
42  *
43  * CAUTION: The local socket name (SOCKET_NAME below) must match that used by the host to set up
44  * the adb-forwarding.
45  */
46 public class PowerTestHostLink {
47     private static final String TAG = "PowerTestHostLink";
48 
49     /**
50      * Host-to-device bridge will use a Listener instance to drive the test via the CtsVerifier
51      * running on the device.
52      */
53     public interface HostToDeviceInterface {
logTestResult(SensorTestDetails testDetails)54         void logTestResult(SensorTestDetails testDetails);
raiseError(String testName, String message)55         void raiseError(String testName, String message) throws Exception;
waitForUserAcknowledgement(String message)56         void waitForUserAcknowledgement(String message) throws InterruptedException;
logText(String text)57         void logText(String text);
turnScreenOff()58         void turnScreenOff();
59     }
60 
61     /** This is a data-only message to communicate result of a power test */
62     public class PowerTestResult{
63         public int passedCount = 0;
64         public int skippedCount = 0;
65         public int failedCount = 0;
66     }
67 
68     /**
69      * Standard response types back to host. Host-side code must match these definitions.
70      */
71     private final static String RESPONSE_OK = "OK";
72     private final static String RESPONSE_ERR = "ERR";
73     private final static String RESPONSE_UNAVAILABLE = "UNAVAILABLE";
74 
75     /**
76      * Socket name for host adb forwarded communications. Must match naem in host-side code.
77      */
78     public final static String SOCKET_NAME = "/android/cts/powertest";
79 
80     private volatile boolean mStopThread;
81     private final SensorManager mSensorManager;
82     private final HostToDeviceInterface mHostToDeviceExecutor;
83     private final PowerTestResult mTestResult = new PowerTestResult();
84 
PowerTestHostLink(Context context, HostToDeviceInterface listener)85     public PowerTestHostLink(Context context, HostToDeviceInterface listener) {
86         Log.d(TAG, " +++ Begin of localSocketServer() +++ ");
87         mHostToDeviceExecutor = listener;
88         mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
89     }
90 
91     /**
92      * Ensure connection to host is closed; stop accepting requests.
93      **/
close()94     public void close() {
95         mStopThread = true;
96     }
97 
98     /**
99      * Run the suite of tests via the host, responding to host requests.
100      *
101      * @return number of failed test cases
102      * @throws Exception
103      */
run()104     public PowerTestResult run() throws Exception {
105         // define buffer to receive data from host
106         final int BUFFER_SIZE = 4096;
107         byte[] buffer = new byte[BUFFER_SIZE];
108 
109         LocalServerSocket serverSocket = createSocket();
110         if (null == serverSocket) {
111             mStopThread = true;
112         }
113 
114         InputStream streamIn;
115         OutputStream streamOut;
116         LocalSocket receiverSocket;
117         while (!mStopThread) {
118 
119             try {
120                 Log.d(TAG, "localSocketServer accept...");
121                 receiverSocket = serverSocket.accept();
122                 Log.d(TAG, "Got new connection");
123             } catch (IOException e) {
124                 Log.d(TAG, "localSocketServer accept() failed !!!", e);
125                 continue;
126             }
127 
128             try {
129                 streamIn = receiverSocket.getInputStream();
130             } catch (IOException e) {
131                 Log.d(TAG, "getInputStream() failed !!!", e);
132                 continue;
133             }
134 
135             try {
136                 streamOut = receiverSocket.getOutputStream();
137             } catch (IOException e) {
138                 Log.e(TAG, "getOutputStream() failed", e);
139                 continue;
140             }
141 
142             Log.d(TAG, "The client connected to LocalServerSocket");
143 
144             try {
145                 int total = 0;
146                 // command and response handshake, so read all data
147                 while (streamIn.available() > 0 || total == 0) {
148                     if (total < BUFFER_SIZE) {
149                         int bytesRead = streamIn.read(buffer, total,
150                                 (BUFFER_SIZE - total));
151                         if (bytesRead > 0) {
152                             total += bytesRead;
153                         }
154                     } else {
155                         Log.e(TAG, "Message too long: truncating");
156                     }
157                 }
158                 String clientRequest = new String(buffer);
159                 clientRequest = clientRequest.substring(0, total);
160                 if (clientRequest.length() > 0) {
161 
162                     Log.d(TAG, "Client requested: " + clientRequest);
163                     try {
164                         String response = processClientRequest(clientRequest);
165                         if (response != null) {
166                             Log.d(TAG, "Sending response " + response);
167                             streamOut.write(response.getBytes(), 0, response.length());
168                         }
169                         // null response means response is deferred awaiting user response
170                     } catch (Exception e) {
171                         Log.e(TAG, "Error executing " + clientRequest, e);
172                         streamOut.write(RESPONSE_ERR.getBytes(), 0, RESPONSE_ERR.length());
173                     }
174                 }
175                 receiverSocket.close();
176             } catch (IOException e) {
177                 Log.e(TAG, "There is an exception when reading from or writing to socket", e);
178                 break;
179             }
180         }
181         Log.d(TAG, "The LocalSocketServer thread is going to stop !!!");
182 
183         if (serverSocket != null) {
184             try {
185                 serverSocket.close();
186             } catch (IOException e) {
187                 Log.d(TAG, "Exception on close of server socket", e);
188             }
189         }
190         mHostToDeviceExecutor.logText("Device disconnected.");
191         Log.d(TAG, "Returning " + mTestResult.passedCount + "passed " + mTestResult.skippedCount +
192                 "skipped " + mTestResult.failedCount + "failed.");
193         return mTestResult;
194     }
195 
processClientRequest(String request)196     private  String processClientRequest(String request) throws Exception {
197         // the following constants need to match the definitions in execute_power_tests.py
198         final String REQUEST_EXTERNAL_STORAGE = "EXTERNAL STORAGE?";
199         final String REQUEST_EXIT = "EXIT";
200         final String REQUEST_RAISE = "RAISE ";
201         final String REQUEST_USER_RESPONSE = "USER RESPONSE ";
202         final String REQUEST_SET_TEST_RESULT = "SET TEST RESULT ";
203         final String REQUEST_SENSOR_ON = "SENSOR ON ";
204         final String REQUEST_SENSOR_OFF = "SENSOR OFF";
205         final String REQUEST_SENSOR_AVAILABILITY = "SENSOR? ";
206         final String REQUEST_SCREEN_OFF = "SCREEN OFF";
207         final String REQUEST_SHOW_MESSAGE = "MESSAGE ";
208 
209         String response = RESPONSE_ERR;
210         // Queries must appear first and then commands to direct actions after in these statements
211         if (request.startsWith(REQUEST_SENSOR_AVAILABILITY)) {
212             final String sensor = request.substring(REQUEST_SENSOR_AVAILABILITY.length());
213             final int sensorId = getSensorId(sensor);
214             if (mSensorManager.getDefaultSensor(sensorId) == null) {
215                 response = RESPONSE_UNAVAILABLE;
216             } else {
217                 response = RESPONSE_OK;
218             }
219         } else if (request.startsWith(REQUEST_EXTERNAL_STORAGE)){
220             response = SensorCtsHelper.getSensorTestDataDirectory("power/").getAbsolutePath();
221             Log.d(TAG,"External storage is " + response);
222         } else if (request.startsWith(REQUEST_SCREEN_OFF)) {
223             try {
224                 mHostToDeviceExecutor.turnScreenOff();
225                 response = RESPONSE_OK;
226             } catch (SecurityException e) {
227                 Log.e(TAG, "Error Turning screen off", e);
228                 response = RESPONSE_ERR;
229             }
230         } else if (request.startsWith(REQUEST_SENSOR_ON)) {
231             String sensorList = request.substring(REQUEST_SENSOR_ON.length()).trim();
232             response = handleSensorSwitchCommand(sensorList, true);
233         } else if (request.startsWith(REQUEST_SENSOR_OFF)) {
234             String sensorList = request.substring(REQUEST_SENSOR_ON.length()).trim();
235             response = handleSensorSwitchCommand(sensorList, false);
236         } else if (request.startsWith(REQUEST_SHOW_MESSAGE)) {
237             final String message = request.substring(REQUEST_SHOW_MESSAGE.length());
238             mHostToDeviceExecutor.logText(message);
239             response = RESPONSE_OK;
240         } else if (request.startsWith(REQUEST_USER_RESPONSE)) {
241             String message = request.substring(REQUEST_USER_RESPONSE.length());
242             mHostToDeviceExecutor.waitForUserAcknowledgement(message);
243             response = RESPONSE_OK;
244         } else if (request.startsWith(REQUEST_SET_TEST_RESULT)) {
245             String testResult = request.substring(REQUEST_SET_TEST_RESULT.length());
246             response = handleSetTestResultCmd(testResult);
247         } else if (request.startsWith(REQUEST_RAISE)) {
248             String command = request.substring(REQUEST_RAISE.length());
249             StringTokenizer tokenizer = new StringTokenizer(command);
250             try {
251                 final String testName = tokenizer.nextToken();
252                 final String message = request.substring(7 + testName.length());
253                 mHostToDeviceExecutor.raiseError(testName, message);
254                 response = RESPONSE_OK;
255             } catch (Exception e) {
256                 Log.e(TAG, "Invalid RAISE command received (bad arguments): " + request);
257                 response = RESPONSE_ERR;
258             }
259         } else if (request.startsWith(REQUEST_EXIT)) {
260             mStopThread = true;
261             response = RESPONSE_OK;
262         } else {
263             Log.e(TAG, "Unknown request: " + request);
264         }
265         return response;
266     }
267 
handleSetTestResultCmd(final String request)268     private String handleSetTestResultCmd(final String request) {
269         String response;
270         StringTokenizer tokenizer = new StringTokenizer(request, " ");
271         String testName = "";
272         SensorTestDetails.ResultCode resultCode = SensorTestDetails.ResultCode.FAIL;
273         String message = "";
274 
275         try {
276             testName = tokenizer.nextToken();
277             final String resultToken = tokenizer.nextToken();
278 
279             if (resultToken.equals("PASS")) {
280                 resultCode = SensorTestDetails.ResultCode.PASS;
281                 ++mTestResult.passedCount;
282                 message = "PASSED: ";
283                 response = RESPONSE_OK;
284             } else if (resultToken.equals("FAIL")) {
285                 resultCode = SensorTestDetails.ResultCode.FAIL;
286                 ++mTestResult.failedCount;
287                 message = "FAILED: ";
288                 response = RESPONSE_OK;
289             } else if (resultToken.equals("SKIPPED")) {
290                 resultCode = SensorTestDetails.ResultCode.SKIPPED;
291                 ++mTestResult.skippedCount;
292                 message = "SKIPPED: ";
293                 response = RESPONSE_OK;
294             } else {
295                 response = RESPONSE_ERR;
296             }
297             while (tokenizer.hasMoreTokens()) {
298                 message += tokenizer.nextToken() + " ";
299             }
300             Log.i(TAG, message);
301         } catch (Exception e) {
302             Log.e(TAG, "Improperly formatted command received: " + request, e);
303             response = RESPONSE_ERR;
304         }
305         String fullMessage = testName + " " + message;
306         mHostToDeviceExecutor.logTestResult(
307                 new SensorTestDetails(testName, resultCode, fullMessage));
308         return response;
309     }
310 
handleSensorSwitchCommand(String sensorList, boolean switchOn)311     private String handleSensorSwitchCommand(String sensorList, boolean switchOn) {
312         String response;
313         try {
314             StringTokenizer tokenizer = new StringTokenizer(sensorList, " ");
315             int n = tokenizer.countTokens();
316             if (n == 0) {
317                 response = switchAllSensors(switchOn);
318             } else {
319                 String sensorName = tokenizer.nextToken();
320                 String requestRate = "";
321                 if (n > 1) {
322                     requestRate = tokenizer.nextToken();
323                 }
324                 if (sensorName.equals("ALL")) {
325                     response = switchAllSensors(switchOn);
326                 } else {
327                     int sensorId = getSensorId(sensorName);
328                     if (sensorId >= 0) {
329                         response = switchSensor(sensorId, switchOn, requestRate);
330                     } else {
331                         Log.e(TAG, "Unknown sensor in request: " + sensorName);
332                         response = RESPONSE_UNAVAILABLE;
333                     }
334                 }
335             }
336         } catch (Exception e) {
337             Log.e(TAG, "Improperly formatted command received on setting sensor state");
338             response = RESPONSE_ERR;
339         }
340         return response;
341     }
342 
getSensorId(String sensorName)343     private static int getSensorId(String sensorName) {
344         int sensorId = -1;
345 
346         if (sensorName.compareToIgnoreCase("ACCELEROMETER") == 0) {
347             sensorId = Sensor.TYPE_ACCELEROMETER;
348         } else if (sensorName.compareToIgnoreCase("AMBIENT_TEMPERATURE") == 0) {
349             sensorId = Sensor.TYPE_AMBIENT_TEMPERATURE;
350         } else if (sensorName.compareToIgnoreCase("GAME_ROTATION_VECTOR") == 0) {
351             sensorId = Sensor.TYPE_GAME_ROTATION_VECTOR;
352         } else if (sensorName.compareToIgnoreCase("GEOMAGNETIC_ROTATION_VECTOR") == 0) {
353             sensorId = Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR;
354         } else if (sensorName.compareToIgnoreCase("GRAVITY") == 0) {
355             sensorId = Sensor.TYPE_GRAVITY;
356         } else if (sensorName.compareToIgnoreCase("GYROSCOPE") == 0) {
357             sensorId = Sensor.TYPE_GYROSCOPE;
358         } else if (sensorName.compareToIgnoreCase("LIGHT") == 0) {
359             sensorId = Sensor.TYPE_LIGHT;
360         } else if (sensorName.compareToIgnoreCase("MAGNETIC_FIELD") == 0) {
361             sensorId = Sensor.TYPE_MAGNETIC_FIELD;
362         } else if (sensorName.compareToIgnoreCase("PRESSURE") == 0) {
363             sensorId = Sensor.TYPE_PRESSURE;
364         } else if (sensorName.compareToIgnoreCase("PROXIMITY") == 0) {
365             sensorId = Sensor.TYPE_PROXIMITY;
366         } else if (sensorName.compareToIgnoreCase("RELATIVE_HUMIDITY") == 0) {
367             sensorId = Sensor.TYPE_RELATIVE_HUMIDITY;
368         } else if (sensorName.compareToIgnoreCase("ROTATION_VECTOR") == 0) {
369             sensorId = Sensor.TYPE_ROTATION_VECTOR;
370         } else if (sensorName.compareToIgnoreCase("SIGNIFICANT_MOTION") == 0) {
371             sensorId = Sensor.TYPE_SIGNIFICANT_MOTION;
372         } else if (sensorName.compareToIgnoreCase("STEP_COUNTER") == 0) {
373             sensorId = Sensor.TYPE_STEP_COUNTER;
374         } else if (sensorName.compareToIgnoreCase("STEP_DETECTOR") == 0) {
375             sensorId = Sensor.TYPE_STEP_DETECTOR;
376         }
377 
378         return sensorId;
379     }
380 
switchSensor(int sensorId, boolean switchOn)381     private String switchSensor(int sensorId, boolean switchOn) {
382         return switchSensor(sensorId, switchOn, "SENSOR_DELAY_NORMAL");
383     }
384 
switchSensor(int sensorId, boolean switchOn, String requestFrequency)385     private String switchSensor(int sensorId, boolean switchOn, String requestFrequency) {
386         String response;
387         int rateUs = SensorManager.SENSOR_DELAY_NORMAL;
388 
389         if (requestFrequency.compareToIgnoreCase("SENSOR_DELAY_FASTEST") == 0) {
390             rateUs = SensorManager.SENSOR_DELAY_FASTEST;
391         } else if (requestFrequency.compareToIgnoreCase("SENSOR_DELAY_GAME") == 0) {
392             rateUs = SensorManager.SENSOR_DELAY_GAME;
393         } else if (requestFrequency.compareToIgnoreCase("SENSOR_DELAY_UI") == 0) {
394             rateUs = SensorManager.SENSOR_DELAY_UI;
395         }
396 
397         if (switchOn) {
398             mSensorManager.registerListener(mSensorEventListener,
399                     mSensorManager.getDefaultSensor(sensorId), rateUs);
400             response = RESPONSE_OK;
401             Log.v(TAG, "Switching ON " + String.valueOf(sensorId) + " | " + String.valueOf(rateUs));
402         } else {
403             mSensorManager.unregisterListener(mSensorEventListener,
404                     mSensorManager.getDefaultSensor(sensorId));
405             response = RESPONSE_OK;
406             Log.v(TAG, "Switching  OFF " + String.valueOf(sensorId));
407         }
408 
409         return response;
410     }
411 
switchAllSensors(boolean on)412     private String switchAllSensors(boolean on) {
413         List<Sensor> allSensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
414         String response = RESPONSE_OK;
415         for (Sensor sensor : allSensors) {
416             if (sensor.getType() >= Sensor.TYPE_DEVICE_PRIVATE_BASE) {
417                 continue;
418             }
419             response = switchSensor(sensor.getType(), on);
420             if (response == null) {
421                 response = RESPONSE_ERR;
422             }
423         }
424         return response;
425     }
426 
createSocket()427     private LocalServerSocket createSocket() {
428         try {
429             return new LocalServerSocket(SOCKET_NAME);
430         } catch (IOException e) {
431             Log.e(TAG, "LocalSocketServer creation failure.", e);
432             return null;
433         }
434     }
435 
436     private SensorEventListener mSensorEventListener = new SensorEventListener() {
437         @Override
438         public void onAccuracyChanged(Sensor sensor, int accuracy) {}
439 
440         @Override
441         public void onSensorChanged(SensorEvent event) {}
442     };
443 }
444