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