1 /* 2 * Copyright (C) 2018 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.input; 18 19 import static org.junit.Assert.assertEquals; 20 21 import android.content.Context; 22 import android.hardware.lights.Light; 23 import android.util.ArrayMap; 24 import android.util.SparseArray; 25 import android.view.InputDevice; 26 import android.view.InputEvent; 27 import android.view.KeyEvent; 28 import android.view.MotionEvent; 29 30 import org.json.JSONArray; 31 import org.json.JSONException; 32 import org.json.JSONObject; 33 34 import java.io.ByteArrayOutputStream; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.io.OutputStream; 38 import java.util.ArrayList; 39 import java.util.Iterator; 40 import java.util.List; 41 42 43 /** 44 * Parse json resource file that contains the test commands for HidDevice 45 * 46 * For files containing reports and input events, each entry should be in the following format: 47 * <code> 48 * {"name": "test case name", 49 * "reports": reports, 50 * "events": input_events 51 * } 52 * </code> 53 * 54 * {@code reports} - an array of strings that contain hex arrays. 55 * {@code input_events} - an array of dicts in the following format: 56 * <code> 57 * {"action": "down|move|up", "axes": {"axis_x": x, "axis_y": y}, "keycode": "button_a"} 58 * </code> 59 * {@code "axes"} should only be defined for motion events, and {@code "keycode"} for key events. 60 * Timestamps will not be checked. 61 62 * Example: 63 * <code> 64 * [{ "name": "press button A", 65 * "reports": ["report1", 66 * "report2", 67 * "report3" 68 * ], 69 * "events": [{"action": "down", "axes": {"axis_y": 0.5, "axis_x": 0.1}}, 70 * {"action": "move", "axes": {"axis_y": 0.0, "axis_x": 0.0}} 71 * ] 72 * }, 73 * ... more tests like that 74 * ] 75 * </code> 76 */ 77 public class InputJsonParser { 78 private static final String TAG = "InputJsonParser"; 79 80 private Context mContext; 81 InputJsonParser(Context context)82 public InputJsonParser(Context context) { 83 mContext = context; 84 } 85 86 /** 87 * Convenience function to create JSONArray from resource. 88 * The resource specified should contain JSON array as the top-level structure. 89 * 90 * @param resourceId The resourceId that contains the json data (typically inside R.raw) 91 */ getJsonArrayFromResource(int resourceId)92 private JSONArray getJsonArrayFromResource(int resourceId) { 93 String data = readRawResource(resourceId); 94 try { 95 return new JSONArray(data); 96 } catch (JSONException e) { 97 throw new RuntimeException( 98 "Could not parse resource " + resourceId + ", received: " + data, 99 e); 100 } 101 } 102 103 /** 104 * Convenience function to read in an entire file as a String. 105 * 106 * @param id resourceId of the file 107 * @return contents of the raw resource file as a String 108 */ readRawResource(int id)109 private String readRawResource(int id) { 110 InputStream inputStream = mContext.getResources().openRawResource(id); 111 try { 112 return readFully(inputStream); 113 } catch (IOException e) { 114 throw new RuntimeException("Could not read resource id " + id); 115 } 116 } 117 118 /** 119 * Read register command from raw resource. 120 * 121 * @param resourceId the raw resource id that contains the command 122 * @return the command to register device that can be passed to HidDevice constructor 123 */ readRegisterCommand(int resourceId)124 public String readRegisterCommand(int resourceId) { 125 return readRawResource(resourceId); 126 } 127 128 /** 129 * Read entire input stream until no data remains. 130 * 131 * @param inputStream 132 * @return content of the input stream 133 * @throws IOException 134 */ readFully(InputStream inputStream)135 private String readFully(InputStream inputStream) throws IOException { 136 OutputStream baos = new ByteArrayOutputStream(); 137 byte[] buffer = new byte[1024]; 138 int read = inputStream.read(buffer); 139 while (read >= 0) { 140 baos.write(buffer, 0, read); 141 read = inputStream.read(buffer); 142 } 143 return baos.toString(); 144 } 145 146 /** 147 * Extract the device id from the raw resource file. This is needed in order to register 148 * a HidDevice. 149 * 150 * @param resourceId resource file that contains the register command. 151 * @return hid device id 152 */ readDeviceId(int resourceId)153 public int readDeviceId(int resourceId) { 154 try { 155 JSONObject json = new JSONObject(readRawResource(resourceId)); 156 return json.getInt("id"); 157 } catch (JSONException e) { 158 throw new RuntimeException("Could not read device id from resource " + resourceId); 159 } 160 } 161 162 /** 163 * Extract the Vendor id from the raw resource file. 164 * 165 * @param resourceId resource file that contains the register command. 166 * @return device vendor id 167 */ readVendorId(int resourceId)168 public int readVendorId(int resourceId) { 169 try { 170 JSONObject json = new JSONObject(readRawResource(resourceId)); 171 return json.getInt("vid"); 172 } catch (JSONException e) { 173 throw new RuntimeException("Could not read vendor id from resource " + resourceId); 174 } 175 } 176 177 /** 178 * Extract the input sources from the raw resource file. 179 * 180 * @param resourceId resource file that contains the register command. 181 * @return device sources 182 */ readSources(int resourceId)183 public int readSources(int resourceId) { 184 try { 185 JSONObject json = new JSONObject(readRawResource(resourceId)); 186 return sourceFromString(json.optString("source")); 187 } catch (JSONException e) { 188 throw new RuntimeException("Could not read resource from resource " + resourceId); 189 } 190 } 191 192 /** 193 * Extract the Product id from the raw resource file. 194 * 195 * @param resourceId resource file that contains the register command. 196 * @return device product id 197 */ readProductId(int resourceId)198 public int readProductId(int resourceId) { 199 try { 200 JSONObject json = new JSONObject(readRawResource(resourceId)); 201 return json.getInt("pid"); 202 } catch (JSONException e) { 203 throw new RuntimeException("Could not read prduct id from resource " + resourceId); 204 } 205 } 206 getLongList(JSONArray array)207 private List<Long> getLongList(JSONArray array) { 208 List<Long> data = new ArrayList<Long>(); 209 for (int i = 0; i < array.length(); i++) { 210 try { 211 data.add(array.getLong(i)); 212 } catch (JSONException e) { 213 throw new RuntimeException("Could not read array index " + i); 214 } 215 } 216 return data; 217 } 218 getIntList(JSONArray array)219 private List<Integer> getIntList(JSONArray array) { 220 List<Integer> data = new ArrayList<Integer>(); 221 for (int i = 0; i < array.length(); i++) { 222 try { 223 data.add(array.getInt(i)); 224 } catch (JSONException e) { 225 throw new RuntimeException("Could not read array index " + i); 226 } 227 } 228 return data; 229 } 230 parseInputEvent(int testCaseNumber, int source, JSONObject entry)231 private InputEvent parseInputEvent(int testCaseNumber, int source, JSONObject entry) { 232 try { 233 InputEvent event; 234 if (entry.has("keycode")) { 235 event = parseKeyEvent(source, entry); 236 } else if (entry.has("axes")) { 237 event = parseMotionEvent(source, entry); 238 } else { 239 throw new RuntimeException( 240 "Input event is not specified correctly. Received: " + entry); 241 } 242 return event; 243 } catch (JSONException e) { 244 throw new RuntimeException("Could not process entry " + testCaseNumber + " : " + entry); 245 } 246 } 247 248 /** 249 * Read json resource, and return a {@code List} of HidTestData, which contains 250 * the name of each test, along with the HID reports and the expected input events. 251 */ getHidTestData(int resourceId)252 public List<HidTestData> getHidTestData(int resourceId) { 253 JSONArray json = getJsonArrayFromResource(resourceId); 254 List<HidTestData> tests = new ArrayList<HidTestData>(); 255 for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) { 256 HidTestData testData = new HidTestData(); 257 258 try { 259 JSONObject testcaseEntry = json.getJSONObject(testCaseNumber); 260 testData.name = testcaseEntry.getString("name"); 261 JSONArray reports = testcaseEntry.getJSONArray("reports"); 262 263 for (int i = 0; i < reports.length(); i++) { 264 String report = reports.getString(i); 265 testData.reports.add(report); 266 } 267 268 final int source = sourceFromString(testcaseEntry.optString("source")); 269 JSONArray events = testcaseEntry.getJSONArray("events"); 270 for (int i = 0; i < events.length(); i++) { 271 testData.events.add(parseInputEvent(i, source, events.getJSONObject(i))); 272 } 273 tests.add(testData); 274 } catch (JSONException e) { 275 throw new RuntimeException("Could not process entry " + testCaseNumber); 276 } 277 } 278 return tests; 279 } 280 281 /** 282 * Read json resource, and return a {@code List} of HidVibratorTestData, which contains 283 * the vibrator FF effect strength data index, and the hid output verification data. 284 */ getHidVibratorTestData(int resourceId)285 public List<HidVibratorTestData> getHidVibratorTestData(int resourceId) { 286 JSONArray json = getJsonArrayFromResource(resourceId); 287 List<HidVibratorTestData> tests = new ArrayList<HidVibratorTestData>(); 288 for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) { 289 HidVibratorTestData testData = new HidVibratorTestData(); 290 try { 291 JSONObject testcaseEntry = json.getJSONObject(testCaseNumber); 292 testData.leftFfIndex = testcaseEntry.getInt("leftFfIndex"); 293 testData.rightFfIndex = testcaseEntry.getInt("rightFfIndex"); 294 295 JSONArray durationsArray = testcaseEntry.getJSONArray("durations"); 296 JSONArray amplitudesArray = testcaseEntry.getJSONArray("amplitudes"); 297 assertEquals(durationsArray.length(), amplitudesArray.length()); 298 testData.durations = new ArrayList<Long>(); 299 testData.amplitudes = new ArrayList<Integer>(); 300 for (int i = 0; i < durationsArray.length(); i++) { 301 testData.durations.add(durationsArray.getLong(i)); 302 testData.amplitudes.add(amplitudesArray.getInt(i)); 303 } 304 305 JSONArray outputArray = testcaseEntry.getJSONArray("output"); 306 testData.verifyMap = new ArrayMap<Integer, Integer>(); 307 for (int i = 0; i < outputArray.length(); i++) { 308 JSONObject item = outputArray.getJSONObject(i); 309 int index = item.getInt("index"); 310 int data = item.getInt("data"); 311 testData.verifyMap.put(index, data); 312 } 313 tests.add(testData); 314 } catch (JSONException e) { 315 throw new RuntimeException("Could not process entry " + testCaseNumber); 316 } 317 } 318 return tests; 319 } 320 321 /** 322 * Read json resource, and return a {@code List} of HidBatteryTestData, which contains 323 * the name of each test, along with the HID reports and the expected batttery status. 324 */ getHidBatteryTestData(int resourceId)325 public List<HidBatteryTestData> getHidBatteryTestData(int resourceId) { 326 JSONArray json = getJsonArrayFromResource(resourceId); 327 List<HidBatteryTestData> tests = new ArrayList<HidBatteryTestData>(); 328 for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) { 329 HidBatteryTestData testData = new HidBatteryTestData(); 330 try { 331 JSONObject testcaseEntry = json.getJSONObject(testCaseNumber); 332 testData.name = testcaseEntry.getString("name"); 333 JSONArray reports = testcaseEntry.getJSONArray("reports"); 334 335 for (int i = 0; i < reports.length(); i++) { 336 String report = reports.getString(i); 337 testData.reports.add(report); 338 } 339 340 JSONArray capacitiesArray = testcaseEntry.getJSONArray("capacities"); 341 testData.capacities = new float[capacitiesArray.length()]; 342 for (int i = 0; i < capacitiesArray.length(); i++) { 343 testData.capacities[i] = Float.valueOf(capacitiesArray.getString(i)); 344 } 345 testData.status = testcaseEntry.getInt("status"); 346 tests.add(testData); 347 } catch (JSONException e) { 348 throw new RuntimeException("Could not process entry " + testCaseNumber + " " + e); 349 } 350 } 351 return tests; 352 } 353 354 /** 355 * Read json resource, and return a {@code List} of HidLightTestData, which contains 356 * the light type and light state request, and the hid output verification data. 357 */ getHidLightTestData(int resourceId)358 public List<HidLightTestData> getHidLightTestData(int resourceId) { 359 JSONArray json = getJsonArrayFromResource(resourceId); 360 List<HidLightTestData> tests = new ArrayList<HidLightTestData>(); 361 for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) { 362 HidLightTestData testData = new HidLightTestData(); 363 try { 364 JSONObject testcaseEntry = json.getJSONObject(testCaseNumber); 365 testData.lightType = lightTypeFromString( 366 testcaseEntry.getString("type")); 367 testData.lightCapabilities = lightCapabilityFromString( 368 testcaseEntry.getString("capabilities")); 369 testData.lightName = testcaseEntry.getString("name"); 370 testData.lightColor = testcaseEntry.getInt("color"); 371 testData.lightPlayerId = testcaseEntry.getInt("playerId"); 372 testData.hidEventType = uhidEventFromString( 373 testcaseEntry.getString("hidEventType")); 374 testData.report = testcaseEntry.getString("report"); 375 376 JSONArray outputArray = testcaseEntry.getJSONArray("ledsHidOutput"); 377 testData.expectedHidData = new ArrayMap<Integer, Integer>(); 378 for (int i = 0; i < outputArray.length(); i++) { 379 JSONObject item = outputArray.getJSONObject(i); 380 int index = item.getInt("index"); 381 int data = item.getInt("data"); 382 testData.expectedHidData.put(index, data); 383 } 384 tests.add(testData); 385 } catch (JSONException e) { 386 throw new RuntimeException("Could not process entry " + testCaseNumber + " : " + e); 387 } 388 } 389 return tests; 390 } 391 392 /** 393 * Read json resource, and return a {@code List} of UinputVibratorTestData, which contains 394 * the vibrator FF effect of durations and amplitudes. 395 */ getUinputVibratorTestData(int resourceId)396 public List<UinputVibratorTestData> getUinputVibratorTestData(int resourceId) { 397 JSONArray json = getJsonArrayFromResource(resourceId); 398 List<UinputVibratorTestData> tests = new ArrayList<UinputVibratorTestData>(); 399 for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) { 400 UinputVibratorTestData testData = new UinputVibratorTestData(); 401 try { 402 JSONObject testcaseEntry = json.getJSONObject(testCaseNumber); 403 JSONArray durationsArray = testcaseEntry.getJSONArray("durations"); 404 JSONArray amplitudesArray = testcaseEntry.getJSONArray("amplitudes"); 405 assertEquals("Duration array length not equal to amplitude array length", 406 durationsArray.length(), amplitudesArray.length()); 407 testData.durations = getLongList(durationsArray); 408 testData.amplitudes = getIntList(amplitudesArray); 409 tests.add(testData); 410 } catch (JSONException e) { 411 throw new RuntimeException("Could not process entry " + testCaseNumber); 412 } 413 } 414 return tests; 415 } 416 417 /** 418 * Read json resource, and return a {@code List} of UinputVibratorManagerTestData, which 419 * contains the vibrator Ids and FF effect of durations and amplitudes. 420 */ getUinputVibratorManagerTestData(int resourceId)421 public List<UinputVibratorManagerTestData> getUinputVibratorManagerTestData(int resourceId) { 422 JSONArray json = getJsonArrayFromResource(resourceId); 423 List<UinputVibratorManagerTestData> tests = new ArrayList<UinputVibratorManagerTestData>(); 424 for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) { 425 UinputVibratorManagerTestData testData = new UinputVibratorManagerTestData(); 426 try { 427 JSONObject testcaseEntry = json.getJSONObject(testCaseNumber); 428 JSONArray durationsArray = testcaseEntry.getJSONArray("durations"); 429 testData.durations = getLongList(durationsArray); 430 testData.amplitudes = new SparseArray<>(); 431 JSONObject amplitudesObj = testcaseEntry.getJSONObject("amplitudes"); 432 for (int i = 0; i < amplitudesObj.names().length(); i++) { 433 String vibratorId = amplitudesObj.names().getString(i); 434 JSONArray amplitudesArray = amplitudesObj.getJSONArray(vibratorId); 435 testData.amplitudes.append(Integer.valueOf(vibratorId), 436 getIntList(amplitudesArray)); 437 assertEquals("Duration array length not equal to amplitude array length", 438 durationsArray.length(), amplitudesArray.length()); 439 } 440 tests.add(testData); 441 } catch (JSONException e) { 442 throw new RuntimeException("Could not process entry " + testCaseNumber); 443 } 444 } 445 return tests; 446 } 447 448 /** 449 * Read json resource, and return a {@code List} of UinputTestData, which contains 450 * the name of each test, along with the uinput injections and the expected input events. 451 */ getUinputTestData(int resourceId)452 public List<UinputTestData> getUinputTestData(int resourceId) { 453 JSONArray json = getJsonArrayFromResource(resourceId); 454 List<UinputTestData> tests = new ArrayList<UinputTestData>(); 455 for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) { 456 UinputTestData testData = new UinputTestData(); 457 458 try { 459 JSONObject testcaseEntry = json.getJSONObject(testCaseNumber); 460 testData.name = testcaseEntry.getString("name"); 461 JSONArray reports = testcaseEntry.getJSONArray("injections"); 462 for (int i = 0; i < reports.length(); i++) { 463 String injections = reports.getString(i); 464 testData.evdevEvents.add(injections); 465 } 466 467 final int source = sourceFromString(testcaseEntry.optString("source")); 468 469 JSONArray events = testcaseEntry.getJSONArray("events"); 470 for (int i = 0; i < events.length(); i++) { 471 testData.events.add(parseInputEvent(i, source, events.getJSONObject(i))); 472 } 473 tests.add(testData); 474 } catch (JSONException e) { 475 throw new RuntimeException("Could not process entry " + testCaseNumber); 476 } 477 } 478 return tests; 479 } 480 parseKeyEvent(int source, JSONObject entry)481 private KeyEvent parseKeyEvent(int source, JSONObject entry) throws JSONException { 482 int action = keyActionFromString(entry.getString("action")); 483 int keyCode = KeyEvent.keyCodeFromString(entry.getString("keycode")); 484 int metaState = metaStateFromString(entry.optString("metaState")); 485 // We will only check select fields of the KeyEvent. Times are not checked. 486 return new KeyEvent(/* downTime */ 0, /* eventTime */ 0, action, keyCode, 487 /* repeat */ 0, metaState, /* deviceId */ 0, /* scanCode */ 0, 488 /* flags */ 0, source); 489 } 490 parseMotionEvent(int source, JSONObject entry)491 private MotionEvent parseMotionEvent(int source, JSONObject entry) throws JSONException { 492 JSONArray pointers = entry.optJSONArray("axes"); 493 int pointerCount = pointers == null ? 1 : pointers.length(); 494 495 MotionEvent.PointerProperties[] properties = 496 new MotionEvent.PointerProperties[pointerCount]; 497 for (int i = 0; i < pointerCount; i++) { 498 properties[i] = new MotionEvent.PointerProperties(); 499 properties[i].id = i; 500 properties[i].toolType = MotionEvent.TOOL_TYPE_UNKNOWN; 501 } 502 503 MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount]; 504 for (int i = 0; i < pointerCount; i++) { 505 coords[i] = new MotionEvent.PointerCoords(); 506 } 507 508 int action = motionActionFromString(entry); 509 510 int buttonState = 0; 511 JSONArray buttons = entry.optJSONArray("buttonState"); 512 if (buttons != null) { 513 for (int i = 0; i < buttons.length(); i++) { 514 String buttonStr = buttons.getString(i); 515 buttonState |= motionButtonFromString(buttonStr); 516 } 517 } 518 519 // "axes" field should be an array if there are multiple pointers 520 for (int i = 0; i < pointerCount; i++) { 521 JSONObject axes; 522 if (pointers == null) { 523 axes = entry.getJSONObject("axes"); 524 } else { 525 axes = pointers.getJSONObject(i); 526 } 527 Iterator<String> keys = axes.keys(); 528 while (keys.hasNext()) { 529 String axis = keys.next(); 530 float value = (float) axes.getDouble(axis); 531 coords[i].setAxisValue(MotionEvent.axisFromString(axis), value); 532 } 533 } 534 535 // Times are not checked 536 return MotionEvent.obtain(/* downTime */ 0, /* eventTime */ 0, action, 537 pointerCount, properties, coords, 0, buttonState, 0f, 0f, 538 0, 0, source, 0); 539 } 540 keyActionFromString(String action)541 private static int keyActionFromString(String action) { 542 switch (action.toUpperCase()) { 543 case "DOWN": 544 return KeyEvent.ACTION_DOWN; 545 case "UP": 546 return KeyEvent.ACTION_UP; 547 } 548 throw new RuntimeException("Unknown action specified: " + action); 549 } 550 metaStateFromString(String metaStateString)551 private static int metaStateFromString(String metaStateString) { 552 int metaState = 0; 553 if (metaStateString.isEmpty()) { 554 return metaState; 555 } 556 final String[] metaKeys = metaStateString.split("\\|"); 557 for (final String metaKeyString : metaKeys) { 558 final String trimmedKeyString = metaKeyString.trim(); 559 switch (trimmedKeyString.toUpperCase()) { 560 case "SHIFT_LEFT": 561 metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON; 562 break; 563 case "SHIFT_RIGHT": 564 metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_RIGHT_ON; 565 break; 566 case "CTRL_LEFT": 567 metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON; 568 break; 569 case "CTRL_RIGHT": 570 metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_RIGHT_ON; 571 break; 572 case "ALT_LEFT": 573 metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON; 574 break; 575 case "ALT_RIGHT": 576 metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON; 577 break; 578 case "META_LEFT": 579 metaState |= KeyEvent.META_META_ON | KeyEvent.META_META_LEFT_ON; 580 break; 581 case "META_RIGHT": 582 metaState |= KeyEvent.META_META_ON | KeyEvent.META_META_RIGHT_ON; 583 break; 584 case "CAPS_LOCK": 585 metaState |= KeyEvent.META_CAPS_LOCK_ON; 586 break; 587 case "NUM_LOCK": 588 metaState |= KeyEvent.META_NUM_LOCK_ON; 589 break; 590 case "SCROLL_LOCK": 591 metaState |= KeyEvent.META_SCROLL_LOCK_ON; 592 break; 593 default: 594 throw new RuntimeException("Unknown meta state chunk: " + trimmedKeyString 595 + " in meta state string: " + metaStateString); 596 } 597 } 598 return metaState; 599 } 600 motionActionFromString(JSONObject entry)601 private static int motionActionFromString(JSONObject entry) { 602 String action; 603 int motionAction = 0; 604 605 try { 606 action = entry.getString("action").toUpperCase(); 607 } catch (JSONException e) { 608 throw new RuntimeException("Action not specified. "); 609 } 610 611 switch (action) { 612 case "DOWN": 613 motionAction = MotionEvent.ACTION_DOWN; 614 break; 615 case "MOVE": 616 motionAction = MotionEvent.ACTION_MOVE; 617 break; 618 case "UP": 619 motionAction = MotionEvent.ACTION_UP; 620 break; 621 case "BUTTON_PRESS": 622 motionAction = MotionEvent.ACTION_BUTTON_PRESS; 623 break; 624 case "BUTTON_RELEASE": 625 motionAction = MotionEvent.ACTION_BUTTON_RELEASE; 626 break; 627 case "HOVER_ENTER": 628 motionAction = MotionEvent.ACTION_HOVER_ENTER; 629 break; 630 case "HOVER_MOVE": 631 motionAction = MotionEvent.ACTION_HOVER_MOVE; 632 break; 633 case "HOVER_EXIT": 634 motionAction = MotionEvent.ACTION_HOVER_EXIT; 635 break; 636 case "POINTER_DOWN": 637 motionAction = MotionEvent.ACTION_POINTER_DOWN; 638 break; 639 case "POINTER_UP": 640 motionAction = MotionEvent.ACTION_POINTER_UP; 641 break; 642 case "CANCEL": 643 motionAction = MotionEvent.ACTION_CANCEL; 644 break; 645 default: 646 throw new RuntimeException("Unknown action specified: " + action); 647 } 648 int pointerId; 649 try { 650 if (motionAction == MotionEvent.ACTION_POINTER_UP 651 || motionAction == MotionEvent.ACTION_POINTER_DOWN) { 652 pointerId = entry.getInt("pointerId"); 653 } else { 654 pointerId = entry.optInt("pointerId", 0); 655 } 656 } catch (JSONException e) { 657 throw new RuntimeException("PointerId not specified: " + action); 658 } 659 return motionAction | (pointerId << MotionEvent.ACTION_POINTER_INDEX_SHIFT); 660 } 661 sourceFromString(String sourceString)662 private static int sourceFromString(String sourceString) { 663 if (sourceString.isEmpty()) { 664 return InputDevice.SOURCE_UNKNOWN; 665 } 666 int source = 0; 667 final String[] sourceEntries = sourceString.split("\\|"); 668 for (final String sourceEntry : sourceEntries) { 669 final String trimmedSourceEntry = sourceEntry.trim(); 670 switch (trimmedSourceEntry.toUpperCase()) { 671 case "MOUSE": 672 source |= InputDevice.SOURCE_MOUSE; 673 break; 674 case "MOUSE_RELATIVE": 675 source |= InputDevice.SOURCE_MOUSE_RELATIVE; 676 break; 677 case "JOYSTICK": 678 source |= InputDevice.SOURCE_JOYSTICK; 679 break; 680 case "KEYBOARD": 681 source |= InputDevice.SOURCE_KEYBOARD; 682 break; 683 case "GAMEPAD": 684 source |= InputDevice.SOURCE_GAMEPAD; 685 break; 686 case "DPAD": 687 source |= InputDevice.SOURCE_DPAD; 688 break; 689 case "TOUCHPAD": 690 source |= InputDevice.SOURCE_TOUCHPAD; 691 break; 692 case "SENSOR": 693 source |= InputDevice.SOURCE_SENSOR; 694 break; 695 default: 696 throw new RuntimeException("Unknown source chunk: " + trimmedSourceEntry 697 + " in source string: " + sourceString); 698 } 699 } 700 return source; 701 } 702 motionButtonFromString(String button)703 private static int motionButtonFromString(String button) { 704 switch (button.toUpperCase()) { 705 case "BACK": 706 return MotionEvent.BUTTON_BACK; 707 case "FORWARD": 708 return MotionEvent.BUTTON_FORWARD; 709 case "PRIMARY": 710 return MotionEvent.BUTTON_PRIMARY; 711 case "SECONDARY": 712 return MotionEvent.BUTTON_SECONDARY; 713 case "STYLUS_PRIMARY": 714 return MotionEvent.BUTTON_STYLUS_PRIMARY; 715 case "STYLUS_SECONDARY": 716 return MotionEvent.BUTTON_STYLUS_SECONDARY; 717 case "TERTIARY": 718 return MotionEvent.BUTTON_TERTIARY; 719 } 720 throw new RuntimeException("Unknown button specified: " + button); 721 } 722 lightTypeFromString(String typeString)723 private static int lightTypeFromString(String typeString) { 724 switch (typeString.toUpperCase()) { 725 case "INPUT": 726 return Light.LIGHT_TYPE_INPUT; 727 case "PLAYER_ID": 728 return Light.LIGHT_TYPE_PLAYER_ID; 729 default: 730 throw new RuntimeException("Unknown light type specified: " + typeString); 731 } 732 } 733 lightCapabilityFromString(String capString)734 private static int lightCapabilityFromString(String capString) { 735 int capabilities = 0; 736 final String[] entries = capString.split("\\|"); 737 for (final String entry : entries) { 738 final String trimmedEntry = entry.trim(); 739 switch (trimmedEntry.toUpperCase()) { 740 case "BRIGHTNESS": 741 capabilities |= Light.LIGHT_CAPABILITY_BRIGHTNESS; 742 break; 743 case "RGB": 744 capabilities |= Light.LIGHT_CAPABILITY_RGB; 745 break; 746 case "NONE": 747 break; 748 default: 749 throw new RuntimeException("Unknown capability specified: " + capString); 750 } 751 } 752 return capabilities; 753 } 754 755 // Return the enum uhid_event_type in kernel linux/uhid.h. uhidEventFromString(String eventString)756 private static byte uhidEventFromString(String eventString) { 757 switch (eventString.toUpperCase()) { 758 case "UHID_OUTPUT": 759 return 6; 760 case "UHID_GET_REPORT": 761 return 9; 762 case "UHID_SET_REPORT": 763 return 13; 764 } 765 throw new RuntimeException("Unknown uhid event type specified: " + eventString); 766 } 767 } 768