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