blob: c4ba05054eda44f32a0aad56909f6e3eeee34f68 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.commands.uinput;
import android.util.JsonReader;
import android.util.JsonToken;
import android.util.Log;
import android.util.SparseArray;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import src.com.android.commands.uinput.InputAbsInfo;
/**
* An event is a JSON file defined action event to instruct uinput to perform a command like
* device registration or uinput events injection.
*/
public class Event {
private static final String TAG = "UinputEvent";
public static final String COMMAND_REGISTER = "register";
public static final String COMMAND_DELAY = "delay";
public static final String COMMAND_INJECT = "inject";
private static final int ABS_CNT = 64;
// These constants come from "include/uapi/linux/input.h" in the kernel
enum Bus {
USB(0x03), BLUETOOTH(0x05);
private final int mValue;
Bus(int value) {
mValue = value;
}
int getValue() {
return mValue;
}
}
private int mId;
private String mCommand;
private String mName;
private int mVid;
private int mPid;
private Bus mBus;
private int[] mInjections;
private SparseArray<int[]> mConfiguration;
private int mDuration;
private int mFfEffectsMax = 0;
private SparseArray<InputAbsInfo> mAbsInfo;
public int getId() {
return mId;
}
public String getCommand() {
return mCommand;
}
public String getName() {
return mName;
}
public int getVendorId() {
return mVid;
}
public int getProductId() {
return mPid;
}
public int getBus() {
return mBus.getValue();
}
public int[] getInjections() {
return mInjections;
}
public SparseArray<int[]> getConfiguration() {
return mConfiguration;
}
public int getDuration() {
return mDuration;
}
public int getFfEffectsMax() {
return mFfEffectsMax;
}
public SparseArray<InputAbsInfo> getAbsInfo() {
return mAbsInfo;
}
/**
* Convert an event to String.
*/
public String toString() {
return "Event{id=" + mId
+ ", command=" + mCommand
+ ", name=" + mName
+ ", vid=" + mVid
+ ", pid=" + mPid
+ ", bus=" + mBus
+ ", events=" + Arrays.toString(mInjections)
+ ", configuration=" + mConfiguration
+ ", duration=" + mDuration
+ ", ff_effects_max=" + mFfEffectsMax
+ "}";
}
private static class Builder {
private Event mEvent;
Builder() {
mEvent = new Event();
}
public void setId(int id) {
mEvent.mId = id;
}
private void setCommand(String command) {
mEvent.mCommand = command;
}
public void setName(String name) {
mEvent.mName = name;
}
public void setInjections(int[] events) {
mEvent.mInjections = events;
}
public void setConfiguration(SparseArray<int[]> configuration) {
mEvent.mConfiguration = configuration;
}
public void setVid(int vid) {
mEvent.mVid = vid;
}
public void setPid(int pid) {
mEvent.mPid = pid;
}
public void setBus(Bus bus) {
mEvent.mBus = bus;
}
public void setDuration(int duration) {
mEvent.mDuration = duration;
}
public void setFfEffectsMax(int ffEffectsMax) {
mEvent.mFfEffectsMax = ffEffectsMax;
}
public void setAbsInfo(SparseArray<InputAbsInfo> absInfo) {
mEvent.mAbsInfo = absInfo;
}
public Event build() {
if (mEvent.mId == -1) {
throw new IllegalStateException("No event id");
} else if (mEvent.mCommand == null) {
throw new IllegalStateException("Event does not contain a command");
}
if (COMMAND_REGISTER.equals(mEvent.mCommand)) {
if (mEvent.mConfiguration == null) {
throw new IllegalStateException(
"Device registration is missing configuration");
}
} else if (COMMAND_DELAY.equals(mEvent.mCommand)) {
if (mEvent.mDuration <= 0) {
throw new IllegalStateException("Delay has missing or invalid duration");
}
} else if (COMMAND_INJECT.equals(mEvent.mCommand)) {
if (mEvent.mInjections == null) {
throw new IllegalStateException("Inject command is missing injection data");
}
} else {
throw new IllegalStateException("Unknown command " + mEvent.mCommand);
}
return mEvent;
}
}
/**
* A class that parses the JSON event format from an input stream to build device events.
*/
public static class Reader {
private JsonReader mReader;
public Reader(InputStreamReader in) {
mReader = new JsonReader(in);
mReader.setLenient(true);
}
/**
* Get next event entry from JSON file reader.
*/
public Event getNextEvent() throws IOException {
Event e = null;
while (e == null && mReader.peek() != JsonToken.END_DOCUMENT) {
Event.Builder eb = new Event.Builder();
try {
mReader.beginObject();
while (mReader.hasNext()) {
String name = mReader.nextName();
switch (name) {
case "id":
eb.setId(readInt());
break;
case "command":
eb.setCommand(mReader.nextString());
break;
case "name":
eb.setName(mReader.nextString());
break;
case "vid":
eb.setVid(readInt());
break;
case "pid":
eb.setPid(readInt());
break;
case "bus":
eb.setBus(readBus());
break;
case "events":
int[] injections = readIntList().stream()
.mapToInt(Integer::intValue).toArray();
eb.setInjections(injections);
break;
case "configuration":
eb.setConfiguration(readConfiguration());
break;
case "ff_effects_max":
eb.setFfEffectsMax(readInt());
break;
case "abs_info":
eb.setAbsInfo(readAbsInfoArray());
break;
case "duration":
eb.setDuration(readInt());
break;
default:
mReader.skipValue();
}
}
mReader.endObject();
} catch (IllegalStateException ex) {
error("Error reading in object, ignoring.", ex);
consumeRemainingElements();
mReader.endObject();
continue;
}
e = eb.build();
}
return e;
}
private ArrayList<Integer> readIntList() throws IOException {
ArrayList<Integer> data = new ArrayList<Integer>();
try {
mReader.beginArray();
while (mReader.hasNext()) {
data.add(Integer.decode(mReader.nextString()));
}
mReader.endArray();
} catch (IllegalStateException | NumberFormatException e) {
consumeRemainingElements();
mReader.endArray();
throw new IllegalStateException("Encountered malformed data.", e);
}
return data;
}
private byte[] readData() throws IOException {
ArrayList<Integer> data = readIntList();
byte[] rawData = new byte[data.size()];
for (int i = 0; i < data.size(); i++) {
int d = data.get(i);
if ((d & 0xFF) != d) {
throw new IllegalStateException("Invalid data, all values must be byte-sized");
}
rawData[i] = (byte) d;
}
return rawData;
}
private int readInt() throws IOException {
String val = mReader.nextString();
return Integer.decode(val);
}
private Bus readBus() throws IOException {
String val = mReader.nextString();
return Bus.valueOf(val.toUpperCase());
}
private SparseArray<int[]> readConfiguration()
throws IllegalStateException, IOException {
SparseArray<int[]> configuration = new SparseArray<>();
try {
mReader.beginArray();
while (mReader.hasNext()) {
int type = 0;
int[] data = null;
mReader.beginObject();
while (mReader.hasNext()) {
String name = mReader.nextName();
switch (name) {
case "type":
type = readInt();
break;
case "data":
data = readIntList().stream()
.mapToInt(Integer::intValue).toArray();
break;
default:
consumeRemainingElements();
mReader.endObject();
throw new IllegalStateException(
"Invalid key in device configuration: " + name);
}
}
mReader.endObject();
if (data != null) {
configuration.put(type, data);
}
}
mReader.endArray();
} catch (IllegalStateException | NumberFormatException e) {
consumeRemainingElements();
mReader.endArray();
throw new IllegalStateException("Encountered malformed data.", e);
}
return configuration;
}
private InputAbsInfo readAbsInfo() throws IllegalStateException, IOException {
InputAbsInfo absInfo = new InputAbsInfo();
try {
mReader.beginObject();
while (mReader.hasNext()) {
String name = mReader.nextName();
switch (name) {
case "value":
absInfo.value = readInt();
break;
case "minimum":
absInfo.minimum = readInt();
break;
case "maximum":
absInfo.maximum = readInt();
break;
case "fuzz":
absInfo.fuzz = readInt();
break;
case "flat":
absInfo.flat = readInt();
break;
case "resolution":
absInfo.resolution = readInt();
break;
default:
consumeRemainingElements();
mReader.endObject();
throw new IllegalStateException("Invalid key in abs info: " + name);
}
}
mReader.endObject();
} catch (IllegalStateException | NumberFormatException e) {
consumeRemainingElements();
mReader.endObject();
throw new IllegalStateException("Encountered malformed data.", e);
}
return absInfo;
}
private SparseArray<InputAbsInfo> readAbsInfoArray()
throws IllegalStateException, IOException {
SparseArray<InputAbsInfo> infoArray = new SparseArray<>();
try {
mReader.beginArray();
while (mReader.hasNext()) {
int type = 0;
InputAbsInfo absInfo = null;
mReader.beginObject();
while (mReader.hasNext()) {
String name = mReader.nextName();
switch (name) {
case "code":
type = readInt();
break;
case "info":
absInfo = readAbsInfo();
break;
default:
consumeRemainingElements();
mReader.endObject();
throw new IllegalStateException("Invalid key in abs info array: "
+ name);
}
}
mReader.endObject();
if (absInfo != null) {
infoArray.put(type, absInfo);
}
}
mReader.endArray();
} catch (IllegalStateException | NumberFormatException e) {
consumeRemainingElements();
mReader.endArray();
throw new IllegalStateException("Encountered malformed data.", e);
}
return infoArray;
}
private void consumeRemainingElements() throws IOException {
while (mReader.hasNext()) {
mReader.skipValue();
}
}
}
private static void error(String msg, Exception e) {
System.out.println(msg);
Log.e(TAG, msg);
if (e != null) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
}