1 /* 2 * Copyright (C) 2020 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 android.app.Instrumentation; 20 import android.util.Log; 21 22 import androidx.annotation.GuardedBy; 23 24 import org.json.JSONArray; 25 import org.json.JSONException; 26 import org.json.JSONObject; 27 28 import java.io.IOException; 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * Represents a virtual UINPUT device registered through /dev/uinput. 34 */ 35 public class UinputDevice extends VirtualInputDevice { 36 private static final String TAG = "UinputDevice"; 37 // uinput executable expects "-" argument to read from stdin instead of a file 38 private static final String UINPUT_COMMAND = "uinput -"; 39 40 @GuardedBy("mLock") 41 private List<UinputResultData> mResults = new ArrayList<UinputResultData>(); 42 43 @Override getShellCommand()44 protected String getShellCommand() { 45 return UINPUT_COMMAND; 46 } 47 48 @Override readResults()49 protected void readResults() { 50 try { 51 mReader.beginObject(); 52 UinputResultData result = new UinputResultData(); 53 while (mReader.hasNext()) { 54 String fieldName = mReader.nextName(); 55 if (fieldName.equals("reason")) { 56 result.reason = mReader.nextString(); 57 } 58 if (fieldName.equals("id")) { 59 result.deviceId = Integer.decode(mReader.nextString()); 60 } 61 if (fieldName.equals("status")) { 62 result.status = Integer.decode(mReader.nextString()); 63 } 64 } 65 mReader.endObject(); 66 addResult(result); 67 } catch (IOException ex) { 68 Log.w(TAG, "Exiting JSON Result reader. " + ex); 69 } 70 } 71 UinputDevice(Instrumentation instrumentation, int sources, UinputRegisterCommand cmd)72 public UinputDevice(Instrumentation instrumentation, int sources, UinputRegisterCommand cmd) { 73 super(instrumentation, cmd.getId(), cmd.getVid(), cmd.getPid(), sources, cmd); 74 } 75 76 /** 77 * Get uinput command return results as list of UinputResultData 78 * 79 * @return List of UinputResultData results 80 */ getResults(int deviceId, String reason)81 public synchronized List<UinputResultData> getResults(int deviceId, String reason) 82 throws IOException { 83 List<UinputResultData> results = new ArrayList<UinputResultData>(); 84 synchronized (mLock) { 85 for (UinputResultData result : mResults) { 86 if (deviceId == result.deviceId && reason.equals(result.reason)) { 87 results.add(result); 88 } 89 } 90 } 91 return results; 92 } 93 94 /** 95 * Add uinput command returned UinputResultData result 96 * 97 * @param result UinputResultData result 98 */ addResult(UinputResultData result)99 public synchronized void addResult(UinputResultData result) { 100 synchronized (mLock) { 101 if (mId == result.deviceId && mResults != null) { 102 mResults.add(result); 103 } 104 } 105 } 106 107 /** 108 * Inject array of uinput events to the device. The events array should follow the below 109 * format: 110 * 111 * String evdevEvents = "[1, 10, 1, 0, 0, 0]" 112 * The above string represents an event array of [EV_KEY, KEY_9, DOWN, EV_SYN, SYN_REPORT, 0] 113 * Hex strings ("0x01") are not supported inside the incoming string. 114 * The number of entries in the provided string has to be a multiple of 3. 115 * @param evdevEvents The uinput events to be injected. (a JSON-formatted array of numbers) 116 */ injectEvents(String evdevEvents)117 public void injectEvents(String evdevEvents) { 118 JSONObject json = new JSONObject(); 119 try { 120 json.put("command", "inject"); 121 json.put("id", mId); 122 json.put("events", new JSONArray(evdevEvents)); 123 } catch (JSONException e) { 124 throw new RuntimeException("Could not inject events: " + evdevEvents); 125 } 126 writeCommands(json.toString().getBytes()); 127 } 128 129 /** 130 * Inject a delay into the uinput process, guaranteeing that it will wait for at least the 131 * specified time before executing any more commands. 132 * 133 * @param delayMs The amount of time to delay, in milliseconds. 134 */ injectDelay(int delayMs)135 public void injectDelay(int delayMs) { 136 JSONObject json = new JSONObject(); 137 try { 138 json.put("command", "delay"); 139 json.put("id", mId); 140 json.put("duration", delayMs); 141 } catch (JSONException e) { 142 throw new RuntimeException("Could not inject delay of " + delayMs + "ms"); 143 } 144 writeCommands(json.toString().getBytes()); 145 } 146 } 147