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