├── README-zh_rCN.md ├── README.md └── uhid-PureJava ├── app ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ ├── com │ │ └── android │ │ │ └── commands │ │ │ └── hid │ │ │ ├── Device.java │ │ │ └── Hid.java │ └── uhid │ │ └── purejava │ │ └── MainActivity.java │ └── res │ └── values │ └── strings.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /README-zh_rCN.md: -------------------------------------------------------------------------------- 1 | # AndroidUHidPureJava 2 | 纯Java实现的uhid模拟HID设备! 3 | 4 | # How it works 5 | 安卓6.0.1起,系统内置了hidcommand_jni库。加载这个库并使用库中的JNI函数即可。 6 | 7 | # How to to use 8 | 打开APP时会自动解压classes.dex至/sdcard/Android/data/uhid.purejava/files/classes.dex。用此命令即可启动uhid模拟: 9 | 10 | export CLASSPATH=/sdcard/Android/data/uhid.purejava/files/classes.dex;app_process / com.android.commands.hid.Hid 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidUHidPureJava 2 | 3 | A HID device simulation implemented purely in Java! 4 | 5 | # How it works 6 | 7 | Starting from Android 6.0.1, the "hidcommand_jni" library is built into the system. We can load this library and use the JNI functions provided in it. 8 | 9 | # How to use 10 | 11 | Once you open the app, it will automaticly unzip the classes.dex from its apk file to /sdcard/Android/data/uhid.purejava/files/classes.dex. Then you can use this command to launch uhid simulation: 12 | 13 | export CLASSPATH=/sdcard/Android/data/uhid/purejava/files/classes.dex;app_process / com.android.commands.hid.Hid 14 | 15 | Note: Adb or Root permission is required. 16 | 17 | Note: you can also manually unzip classes.dex from apk file. Just remember to change the filePath parameter in the launch command. 18 | 19 | # Support range 20 | 21 | Android 6.0.1 ~ Android 15 22 | -------------------------------------------------------------------------------- /uhid-PureJava/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'uhid.purejava' 7 | compileSdk 35 8 | 9 | defaultConfig { 10 | applicationId "uhid.purejava" 11 | minSdk 23 12 | targetSdk 35 13 | versionCode 1 14 | versionName "1.0" 15 | multiDexEnabled false 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | } 30 | 31 | dependencies { 32 | } -------------------------------------------------------------------------------- /uhid-PureJava/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /uhid-PureJava/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /uhid-PureJava/app/src/main/java/com/android/commands/hid/Device.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 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.commands.hid; 18 | 19 | import android.os.Build; 20 | import android.os.Bundle; 21 | import android.os.Handler; 22 | import android.os.HandlerThread; 23 | import android.os.Looper; 24 | import android.os.Message; 25 | import android.os.MessageQueue; 26 | import android.os.SystemClock; 27 | import android.util.Log; 28 | import android.util.SparseArray; 29 | 30 | import org.json.JSONArray; 31 | import org.json.JSONException; 32 | import org.json.JSONObject; 33 | 34 | import java.io.IOException; 35 | import java.io.OutputStream; 36 | import java.nio.ByteBuffer; 37 | import java.util.Arrays; 38 | import java.util.Map; 39 | 40 | public class Device { 41 | private static final String TAG = "HidDevice"; 42 | 43 | private static final int MSG_OPEN_DEVICE = 1; 44 | private static final int MSG_SEND_REPORT = 2; 45 | private static final int MSG_SEND_GET_FEATURE_REPORT_REPLY = 3; 46 | private static final int MSG_SEND_SET_REPORT_REPLY = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ? 4 : -1; 47 | private static final int MSG_CLOSE_DEVICE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ? 5 : 4; 48 | 49 | // Sync with linux uhid_event_type::UHID_OUTPUT 50 | private static final byte UHID_EVENT_TYPE_UHID_OUTPUT = 6; 51 | // Sync with linux uhid_event_type::UHID_SET_REPORT 52 | private static final byte UHID_EVENT_TYPE_SET_REPORT = 13; 53 | private final int mId; 54 | private final HandlerThread mThread; 55 | private final DeviceHandler mHandler; 56 | // mFeatureReports is limited to 256 entries, because the report number is 8-bit 57 | private final SparseArray mFeatureReports; 58 | private final Map mOutputs; 59 | private final OutputStream mOutputStream; 60 | private long mTimeToSend; 61 | 62 | private final Object mCond = new Object(); 63 | 64 | private int mResponseId; 65 | 66 | static { 67 | System.loadLibrary("hidcommand_jni"); 68 | } 69 | //since Android 6.0.1 70 | private static native long nativeOpenDevice(String name, int id, int vid, int pid, 71 | byte[] descriptor, MessageQueue queue, DeviceCallback callback); 72 | //since Android 8.1,API 27 73 | private static native long nativeOpenDevice(String name, int id, int vid, int pid, 74 | byte[] descriptor, DeviceCallback callback); 75 | //since Android 11,API 30 76 | private static native long nativeOpenDevice(String name, int id, int vid, int pid, int bus, 77 | byte[] descriptor, DeviceCallback callback); 78 | 79 | //since Android 15,API 35 80 | private static native long nativeOpenDevice(String name, String uniq, int id, int vid, int pid, int bus, 81 | byte[] descriptor, DeviceCallback callback); 82 | 83 | private static native void nativeSendReport(long ptr, byte[] data); 84 | 85 | private static native void nativeSendGetFeatureReportReply(long ptr, int id, byte[] data); 86 | 87 | //since Android 14,API 34 88 | private static native void nativeSendSetReportReply(long ptr, int id, boolean success); 89 | 90 | private static native void nativeCloseDevice(long ptr); 91 | 92 | public Device(int id, String name,String uniq, int vid, int pid, int bus, byte[] descriptor, 93 | byte[] report, SparseArray featureReports, Map outputs) { 94 | mId = id; 95 | mThread = new HandlerThread("HidDeviceHandler"); 96 | mThread.start(); 97 | mHandler = new DeviceHandler(mThread.getLooper()); 98 | mFeatureReports = featureReports; 99 | mOutputs = outputs; 100 | mOutputStream = System.out; 101 | Bundle args = new Bundle(); 102 | args.putInt("id", id); 103 | args.putInt("vid", vid); 104 | args.putInt("pid", pid); 105 | args.putInt("bus", bus); 106 | if (name != null) { 107 | args.putString("name", name); 108 | } else { 109 | args.putString("name", id + ":" + vid + ":" + pid); 110 | } 111 | args.putString("uniq", uniq); 112 | args.putByteArray("descriptor", descriptor); 113 | args.putByteArray("report", report); 114 | mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget(); 115 | mTimeToSend = SystemClock.uptimeMillis(); 116 | } 117 | 118 | public void sendReport(byte[] report) { 119 | Message msg = mHandler.obtainMessage(MSG_SEND_REPORT, report); 120 | // if two messages are sent at identical time, they will be processed in order received 121 | mHandler.sendMessageAtTime(msg, mTimeToSend); 122 | } 123 | 124 | public void setGetReportResponse(byte[] report) { 125 | mFeatureReports.put(report[0], report); 126 | } 127 | 128 | public void sendSetReportReply(boolean success) { 129 | Message msg = 130 | mHandler.obtainMessage(MSG_SEND_SET_REPORT_REPLY, mResponseId, success ? 1 : 0); 131 | 132 | mHandler.sendMessageAtTime(msg, mTimeToSend); 133 | } 134 | 135 | public void addDelay(int delay) { 136 | mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay; 137 | } 138 | 139 | public void close() { 140 | Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE); 141 | mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1); 142 | try { 143 | synchronized (mCond) { 144 | mCond.wait(); 145 | } 146 | } catch (InterruptedException ignore) { 147 | } 148 | } 149 | 150 | private class DeviceHandler extends Handler { 151 | private long mPtr; 152 | private boolean mBarrierToken; 153 | 154 | public DeviceHandler(Looper looper) { 155 | super(looper); 156 | } 157 | 158 | @Override 159 | public void handleMessage(Message msg) { 160 | 161 | if (msg.what == MSG_OPEN_DEVICE) { 162 | Bundle args = (Bundle) msg.obj; 163 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 164 | mPtr = nativeOpenDevice(args.getString("name"), args.getString("uniq"), args.getInt("id"), args.getInt("vid"), args.getInt("pid"), 165 | args.getInt("bus"), args.getByteArray("descriptor"), new DeviceCallback()); 166 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 167 | mPtr = nativeOpenDevice(args.getString("name"), args.getInt("id"), args.getInt("vid"), args.getInt("pid"), 168 | args.getInt("bus"), args.getByteArray("descriptor"), new DeviceCallback()); 169 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { 170 | mPtr = nativeOpenDevice(args.getString("name"), args.getInt("id"), args.getInt("vid"), args.getInt("pid"), 171 | args.getByteArray("descriptor"), new DeviceCallback()); 172 | } else { 173 | mPtr = nativeOpenDevice(args.getString("name"), args.getInt("id"), args.getInt("vid"), args.getInt("pid"), 174 | args.getByteArray("descriptor"), getLooper().myQueue(), new DeviceCallback()); 175 | } 176 | pauseEvents(); 177 | } else if (msg.what == MSG_SEND_REPORT) { 178 | if (mPtr != 0 && mBarrierToken) { 179 | nativeSendReport(mPtr, (byte[]) msg.obj); 180 | } else { 181 | Log.e(TAG, "Tried to send report to closed device."); 182 | } 183 | } else if (msg.what == MSG_SEND_GET_FEATURE_REPORT_REPLY) { 184 | if (mPtr != 0 && mBarrierToken) { 185 | nativeSendGetFeatureReportReply(mPtr, msg.arg1, (byte[]) msg.obj); 186 | } else { 187 | Log.e(TAG, "Tried to send feature report reply to closed device."); 188 | } 189 | } else if (msg.what == MSG_SEND_SET_REPORT_REPLY) { 190 | if (mPtr != 0) { 191 | final boolean success = msg.arg2 == 1; 192 | nativeSendSetReportReply(mPtr, msg.arg1, success); 193 | } else { 194 | Log.e(TAG, "Tried to send set report reply to closed device."); 195 | } 196 | } else if (msg.what == MSG_CLOSE_DEVICE) { 197 | if (mPtr != 0) { 198 | nativeCloseDevice(mPtr); 199 | getLooper().quitSafely(); 200 | mPtr = 0; 201 | } else { 202 | Log.e(TAG, "Tried to close already closed device."); 203 | } 204 | synchronized (mCond) { 205 | mCond.notify(); 206 | } 207 | } else { 208 | throw new IllegalArgumentException("Unknown device message"); 209 | } 210 | } 211 | 212 | public void pauseEvents() { 213 | mBarrierToken = false; 214 | } 215 | 216 | public void resumeEvents() { 217 | mBarrierToken = true; 218 | } 219 | } 220 | 221 | private class DeviceCallback { 222 | public void onDeviceOpen() { 223 | mHandler.resumeEvents(); 224 | } 225 | 226 | public void onDeviceGetReport(int requestId, int reportId) { 227 | if (mFeatureReports == null) { 228 | Log.e(TAG, "Received GET_REPORT request for reportId=" + reportId 229 | + ", but 'feature_reports' section is not found"); 230 | return; 231 | } 232 | byte[] report = mFeatureReports.get(reportId); 233 | 234 | if (report == null) { 235 | Log.e(TAG, "Requested feature report " + reportId + " is not specified"); 236 | } 237 | 238 | Message msg; 239 | msg = mHandler.obtainMessage(MSG_SEND_GET_FEATURE_REPORT_REPLY, requestId, 0, report); 240 | 241 | // Message is set to asynchronous so it won't be blocked by synchronization 242 | // barrier during UHID_OPEN. This is necessary for drivers that do 243 | // UHID_GET_REPORT requests during probe. 244 | msg.setAsynchronous(true); 245 | mHandler.sendMessageAtTime(msg, mTimeToSend); 246 | } 247 | 248 | // Send out the report to HID command output 249 | private void sendReportOutput(byte eventId, byte rtype, byte[] data) { 250 | JSONObject json = new JSONObject(); 251 | try { 252 | json.put("eventId", eventId); 253 | json.put("deviceId", mId); 254 | json.put("reportType", rtype); 255 | JSONArray dataArray = new JSONArray(); 256 | for (byte datum : data) { 257 | dataArray.put(datum & 0xFF); 258 | } 259 | json.put("reportData", dataArray); 260 | } catch (JSONException e) { 261 | throw new RuntimeException("Could not create JSON object ", e); 262 | } 263 | try { 264 | mOutputStream.write(json.toString().getBytes()); 265 | mOutputStream.flush(); 266 | } catch (IOException e) { 267 | throw new RuntimeException(e); 268 | } 269 | 270 | } 271 | 272 | // native callback 273 | public void onDeviceSetReport(byte rtype, byte[] data) { 274 | // We don't need to reply for the SET_REPORT but just send it to HID output for test 275 | // verification. 276 | sendReportOutput(UHID_EVENT_TYPE_SET_REPORT, rtype, data); 277 | } 278 | 279 | // native callback 280 | public void onDeviceSetReport(int id, byte rType, byte[] data) { 281 | // Used by sendSetReportReply() 282 | mResponseId = id; 283 | // We don't need to reply for the SET_REPORT but just send it to HID output for test 284 | // verification. 285 | sendReportOutput(UHID_EVENT_TYPE_SET_REPORT, rType, data); 286 | } 287 | 288 | // native callback 289 | public void onDeviceOutput(byte rtype, byte[] data) { 290 | sendReportOutput(UHID_EVENT_TYPE_UHID_OUTPUT, rtype, data); 291 | if (mOutputs == null) { 292 | Log.e(TAG, "Received OUTPUT request, but 'outputs' section is not found"); 293 | return; 294 | } 295 | byte[] response = mOutputs.get(ByteBuffer.wrap(data)); 296 | if (response == null) { 297 | Log.i(TAG, 298 | "Requested response for output " + Arrays.toString(data) + " is not found"); 299 | return; 300 | } 301 | 302 | Message msg; 303 | msg = mHandler.obtainMessage(MSG_SEND_REPORT, response); 304 | 305 | // Message is set to asynchronous so it won't be blocked by synchronization 306 | // barrier during UHID_OPEN. This is necessary for drivers that do 307 | // UHID_OUTPUT requests during probe, and expect a response right away. 308 | msg.setAsynchronous(true); 309 | mHandler.sendMessageAtTime(msg, mTimeToSend); 310 | } 311 | 312 | public void onDeviceError() { 313 | Log.e(TAG, "Device error occurred, closing /dev/uhid"); 314 | Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE); 315 | msg.setAsynchronous(true); 316 | msg.sendToTarget(); 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /uhid-PureJava/app/src/main/java/com/android/commands/hid/Hid.java: -------------------------------------------------------------------------------- 1 | package com.android.commands.hid; 2 | 3 | import android.os.SystemClock; 4 | 5 | import java.util.Random; 6 | import java.util.Scanner; 7 | 8 | //usage: export CLASSPATH=/sdcard/classes.dex;app_process / com.android.commands.hid.Hid 9 | 10 | public class Hid { 11 | 12 | //This is a normal hid keyboard's descriptor. 13 | private static final byte[] hidKeyboardDescriptor = new byte[]{ 14 | 15 | 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 16 | 0x09, 0x06, // Usage (Keyboard) 17 | (byte) 0xA1, 0x01, // Collection (Application) 18 | 0x05, 0x07, // Usage Page (Kbrd/Keypad) 19 | 0x19, (byte) 0xE0, // Usage Minimum (0xE0) 20 | 0x29, (byte) 0xE7, // Usage Maximum (0xE7) 21 | 0x15, 0x00, // Logical Minimum (0) 22 | 0x25, 0x01, // Logical Maximum (1) 23 | 0x75, 0x01, // Report Size (1) 24 | (byte) 0x95, 0x08, // Report Count (8) 25 | (byte) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 26 | (byte) 0x95, 0x01, // Report Count (1) 27 | 0x75, 0x08, // Report Size (8) 28 | (byte) 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 29 | (byte) 0x95, 0x05, // Report Count (5) 30 | 0x75, 0x01, // Report Size (1) 31 | 0x05, 0x08, // Usage Page (LEDs) 32 | 0x19, 0x01, // Usage Minimum (Num Lock) 33 | 0x29, 0x05, // Usage Maximum (Kana) 34 | (byte) 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 35 | (byte) 0x95, 0x01, // Report Count (1) 36 | 0x75, 0x03, // Report Size (3) 37 | (byte) 0x91, 0x03, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 38 | (byte) 0x95, 0x06, // Report Count (6) 39 | 0x75, 0x08, // Report Size (8) 40 | 0x15, 0x00, // Logical Minimum (0) 41 | 0x25, 0x65, // Logical Maximum (101) 42 | 0x05, 0x07, // Usage Page (Kbrd/Keypad) 43 | 0x19, 0x00, // Usage Minimum (0x00) 44 | 0x29, 0x65, // Usage Maximum (0x65) 45 | (byte) 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 46 | (byte) 0xC0, // End Collection 47 | }; 48 | 49 | 50 | //This is a normal hid mouse's descriptor. 51 | private static final byte[] hidMouseDescriptor = new byte[]{ 52 | 53 | 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 54 | 0x09, 0x02, // Usage (Mouse) 55 | (byte) 0xA1, 0x01, // Collection (Application) 56 | 0x09, 0x01, // Usage (Pointer) 57 | (byte) 0xA1, 0x00, // Collection (Physical) 58 | 0x05, 0x09, // Usage Page (Button) 59 | 0x19, 0x01, // Usage Minimum (0x01) 60 | 0x29, 0x05, // Usage Maximum (0x05) 61 | 0x15, 0x00, // Logical Minimum (0) 62 | 0x25, 0x01, // Logical Maximum (1) 63 | (byte) 0x95, 0x05, // Report Count (5) 64 | 0x75, 0x01, // Report Size (1) 65 | (byte) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 66 | (byte) 0x95, 0x01, // Report Count (1) 67 | 0x75, 0x03, // Report Size (3) 68 | (byte) 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 69 | 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 70 | 0x09, 0x30, // Usage (X) 71 | 0x09, 0x31, // Usage (Y) 72 | 0x09, 0x38, // Usage (Wheel) 73 | 0x15, (byte) 0x81, // Logical Minimum (-127) 74 | 0x25, 0x7F, // Logical Maximum (127) 75 | 0x75, 0x08, // Report Size (8) 76 | (byte) 0x95, 0x03, // Report Count (3) 77 | (byte) 0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) 78 | (byte) 0xC0, // End Collection 79 | (byte) 0xC0, // End Collection 80 | }; 81 | 82 | 83 | private static final byte[] hidGamePadDescriptor = new byte[]{ 84 | 0x05, 0x01, //Usage Page (Generic Desktop Ctrls) 85 | 0x09, 0x05, //Usage (Game Pad) 86 | (byte) 0xA1, 0x01, //Collection (Application) 87 | 0x05, 0x09, // Usage Page (Button) 88 | 0x19, 0x01, // Usage Minimum (Button 1) 89 | 0x29, 0x10, // Usage Maximum (Button 16) 90 | 0x15, 0x00, // Logical Minimum (0) 91 | 0x25, 0x01, // Logical Maximum (1) 92 | 0x75, 0x01, // Report Size (1) 93 | (byte) 0x95, 0x10, // Report Count (16) 94 | (byte) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 95 | 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 96 | 0x15, (byte) 0x81, // Logical Minimum (-127) 97 | 0x25, 0x7F, // Logical Maximum (127) 98 | 0x09, 0x30, // Usage (X) 99 | 0x09, 0x31, // Usage (Y) 100 | 0x09, 0x32, // Usage (Z) 101 | 0x09, 0x35, // Usage (Rz) 102 | 0x75, 0x08, // Report Size (8) 103 | (byte) 0x95, 0x04, // Report Count (4) 104 | (byte) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 105 | (byte) 0xC0, //End Col 106 | }; 107 | 108 | 109 | private static final byte[] hidTouchScreenDescriptor = new byte[]{ 110 | 0x05, 0x0D, // Usage Page (Digitizer) 111 | 0x09, 0x04, // Usage (Touch Screen) 112 | (byte) 0xA1, 0x01, // Collection (Application) 113 | 0x09, 0x22, // Usage (Finger) 114 | (byte) 0xA1, 0x00, // Collection (Physical) 115 | 0x09, 0x42, // Usage (Tip Switch) 116 | 0x15, 0x00, // Logical Minimum (0) 117 | 0x25, 0x01, // Logical Maximum (1) 118 | 0x75, 0x01, // Report Size (1) 119 | (byte) 0x95, 0x01, // Report Count (1) 120 | (byte) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 121 | 0x09, 0x32, // Usage (In Range) 122 | 0x15, 0x00, // Logical Minimum (0) 123 | 0x25, 0x01, // Logical Maximum (1) 124 | (byte) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 125 | 0x09, 0x51, // Usage (0x51) 126 | 0x75, 0x05, // Report Size (5) 127 | (byte) 0x95, 0x01, // Report Count (1) 128 | 0x16, 0x00, 0x00, // Logical Minimum (0) 129 | 0x26, 0x10, 0x00, // Logical Maximum (16) 130 | (byte) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 131 | 0x09, 0x47, // Usage (0x47) 132 | 0x75, 0x01, // Report Size (1) 133 | (byte) 0x95, 0x01, // Report Count (1) 134 | 0x15, 0x00, // Logical Minimum (0) 135 | 0x25, 0x01, // Logical Maximum (1) 136 | (byte) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 137 | 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 138 | 0x09, 0x30, // Usage (X) 139 | 0x75, 0x10, // Report Size (16) 140 | (byte) 0x95, 0x01, // Report Count (1) 141 | 0x55, 0x0D, // Unit Exponent (-3) 142 | 0x65, 0x33, // Unit (System: English Linear, Length: Inch) 143 | 0x15, 0x00, // Logical Minimum (0) 144 | 0x26, (byte) 0xFF, 0x0F, // Logical Maximum (4095) 145 | (byte) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 146 | 0x09, 0x31, // Usage (Y) 147 | 0x75, 0x10, // Report Size (16) 148 | (byte) 0x95, 0x01, // Report Count (1) 149 | 0x55, 0x0D, // Unit Exponent (-3) 150 | 0x65, 0x33, // Unit (System: English Linear, Length: Inch) 151 | 0x15, 0x00, // Logical Minimum (0) 152 | 0x26, (byte) 0xFF, 0x0F, // Logical Maximum (4095) 153 | (byte) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 154 | 0x05, 0x0D, // Usage Page (Digitizer) 155 | 0x09, 0x55, // Usage (0x55) 156 | 0x25, 0x08, // Logical Maximum (8) 157 | 0x75, 0x08, // Report Size (8) 158 | (byte) 0x95, 0x01, // Report Count (1) 159 | (byte) 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 160 | (byte) 0xC0, // End Collection 161 | (byte) 0xC0, // End Collection 162 | 163 | }; 164 | 165 | // The keyboardCode array is 8 bytes long and represents the report data for a hid keyboard. 166 | // keyboardCode[0] represents the MetaKeys' states of the keyboard (Such as "Win", "Ctrl", "Shift", "Alt"). Assigned by using the formula: keyboardCode[0] |= 1; keyboardCode[0] &= ~1; 167 | // keyboardCode[1] represents nothing and is not used. 168 | // keyboardCode[2] to keyboardCode[7] each represent a normal key pressed on the keyboard. 169 | public static byte[] keyboardCode = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 170 | 171 | 172 | // The mouseCode array is 4 bytes long and represents the report data for a hid mouse. 173 | // mouseCode[0] represents the buttons' states of the mouse. Assigned by using the formula: mouseCode[0] |= 1; mouseCode[0] &= ~1; 174 | // mouseCode[1] represents the X axis value of the mouse. Assigned normally. 175 | // mouseCode[2] represents the Y axis value of the mouse. Assigned normally. 176 | // mouseCode[3] represents the SCROLL axis value of the mouse. Assigned by using the formula: mouseCode[3] = (byte) (~scrollValue + 1); 177 | public static byte[] mouseCode = {0x00, 0x00, 0x00, 0x00}; 178 | 179 | 180 | // The gamePadCode array is 6 bytes long and represents the report data for a hid gamePad. 181 | // gamePadCode[0] and gamePadCode[1] represents the buttons' states of the gamePad. Assigned by using the formula: gamePadCode[0] |= 1; gamePadCode[0] &= ~1; 182 | // gamePadCode[2] represents the X axis value of the gamePad. Assigned normally. 183 | // gamePadCode[3] represents the Y axis value of the gamePad. Assigned normally. 184 | // gamePadCode[4] represents the Z axis value of the gamePad. Assigned normally. 185 | // gamePadCode[5] represents the RZ axis value of the gamePad. Assigned normally. 186 | public static byte[] gamePadCode = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 187 | 188 | 189 | // The touchScreenCode array is 5 bytes long and represents the report data for a single-touch hid touchScreen. 190 | // touchScreenCode[0]'s first bit represents whether a finger is touching the screen. 191 | // touchScreenCode[1] and touchScreenCode[2] represents the X value of the finger. Assigned by using the formula: touchScreenCode[1] = (byte) (x % 256); touchScreenCode[2] = (byte) (x / 256); 192 | // touchScreenCode[3] and touchScreenCode[4] represents the Y value of the finger. Assigned by using the formula: touchScreenCode[3] = (byte) (y % 256); touchScreenCode[4] = (byte) (y / 256); 193 | public static byte[] touchScreenCode = {0x00, 0x00, 0x00, 0x00, 0x00}; 194 | 195 | public static void main(String[] args) { 196 | 197 | //Check permission. 198 | int uid = android.os.Process.myUid(); 199 | if (uid != 0 && uid != 2000) { 200 | System.err.printf("Insufficient permission! Need to be launched by adb (uid 2000) or root (uid 0), but your uid is %d \n", uid); 201 | System.exit(255); 202 | return; 203 | } 204 | 205 | 206 | // We create four uHid devices. 207 | // The id, name, vid, pid, and bus parameters do not matter. Only the descriptor parameter matters. 208 | 209 | Device mouse = new Device(1, "uHidMouse", "1",1234, 5678, 0x03, hidMouseDescriptor, mouseCode, null, null); 210 | Device keyboard = new Device(2, "uHidKeyboard","2", 8765, 4321, 0x03, hidKeyboardDescriptor, keyboardCode, null, null); 211 | Device gamePad = new Device(3, "uHidGamePad", "3",2345, 6789, 0x03, hidGamePadDescriptor, gamePadCode, null, null); 212 | Device touchScreen = new Device(4, "uHidTouchScreen", "4",6789, 2345, 0x03, hidTouchScreenDescriptor, touchScreenCode, null, null); 213 | 214 | 215 | System.out.println(""); 216 | System.out.println("Create uhid mouse, keyboard, gamePad, touchScreen successfully."); 217 | System.out.println("Enter \"m\" to send random mouse move;"); 218 | System.out.println("Enter \"k\" to send random keyboard keyPress or keyRelease;"); 219 | System.out.println("Enter \"g\" to send random gamePad axis moves or axis reset;"); 220 | System.out.println("Enter \"t\" to send random touchScreen move;"); 221 | System.out.println("Enter \"exit\" to exit."); 222 | System.out.println(""); 223 | 224 | 225 | // The Scanner is used here only for testing purposes, it allows easy user input to simulate mouse and keyboard events. 226 | // A Random object is created to generate random codes for the mouse and keyboard events. 227 | // The program enters a label loop that reads input from the Scanner until the user types "exit". 228 | // Depending on the user input, the program generates and sends a report for the mouse or keyboard event using the corresponding sendReport() method. 229 | 230 | Scanner scanner = new Scanner(System.in); 231 | String inline; 232 | Random random = new Random(); 233 | label: 234 | while ((inline = scanner.nextLine()) != null) { 235 | switch (inline) { 236 | 237 | case "exit": 238 | break label; 239 | 240 | case "m": 241 | mouseCode[1] = (byte) random.nextInt(); 242 | mouseCode[2] = (byte) random.nextInt(); 243 | mouse.sendReport(mouseCode);//Here we send mouse report data. 244 | break; 245 | 246 | case "k": 247 | keyboardCode[2] = random.nextInt() % 2 == 0 ? (byte) random.nextInt() : 0; 248 | keyboard.sendReport(keyboardCode);//Here we send keyboard report data. 249 | break; 250 | 251 | case "g": 252 | boolean reset = random.nextInt() % 2 == 0; 253 | gamePadCode[2] = reset ? (byte) random.nextInt() : 0; 254 | gamePadCode[3] = reset ? (byte) random.nextInt() : 0; 255 | gamePadCode[4] = reset ? (byte) random.nextInt() : 0; 256 | gamePadCode[5] = reset ? (byte) random.nextInt() : 0; 257 | gamePad.sendReport(gamePadCode);//Here we send gamePad report data. 258 | break; 259 | 260 | case "t": 261 | 262 | int x1 = 4096 / 2 + random.nextInt() % 1024; 263 | int y1 = 4096 / 2 + random.nextInt() % 1024; 264 | touchScreenCode[0] = 0x01; 265 | //Square gesture 266 | for (int i = 0; i < 200; i++) { 267 | x1 += 2; 268 | touchScreenCode[1] = (byte) (x1 % 256); 269 | touchScreenCode[2] = (byte) (x1 / 256); 270 | touchScreenCode[3] = (byte) (y1 % 256); 271 | touchScreenCode[4] = (byte) (y1 / 256); 272 | touchScreen.sendReport(touchScreenCode);//Here we send touchScreen report data. 273 | SystemClock.sleep(3); 274 | } 275 | for (int i = 0; i < 200; i++) { 276 | y1 += 2; 277 | touchScreenCode[1] = (byte) (x1 % 256); 278 | touchScreenCode[2] = (byte) (x1 / 256); 279 | touchScreenCode[3] = (byte) (y1 % 256); 280 | touchScreenCode[4] = (byte) (y1 / 256); 281 | touchScreen.sendReport(touchScreenCode);//Here we send touchScreen report data. 282 | SystemClock.sleep(3); 283 | } 284 | for (int i = 0; i < 200; i++) { 285 | x1 -= 2; 286 | touchScreenCode[1] = (byte) (x1 % 256); 287 | touchScreenCode[2] = (byte) (x1 / 256); 288 | touchScreenCode[3] = (byte) (y1 % 256); 289 | touchScreenCode[4] = (byte) (y1 / 256); 290 | touchScreen.sendReport(touchScreenCode);//Here we send touchScreen report data. 291 | SystemClock.sleep(3); 292 | } 293 | for (int i = 0; i < 200; i++) { 294 | y1 -= 2; 295 | touchScreenCode[1] = (byte) (x1 % 256); 296 | touchScreenCode[2] = (byte) (x1 / 256); 297 | touchScreenCode[3] = (byte) (y1 % 256); 298 | touchScreenCode[4] = (byte) (y1 / 256); 299 | touchScreen.sendReport(touchScreenCode);//Here we send touchScreen report data. 300 | SystemClock.sleep(3); 301 | } 302 | touchScreenCode[0] = 0x00; 303 | touchScreen.sendReport(touchScreenCode);//Here we send touchScreen report data. 304 | break; 305 | } 306 | } 307 | scanner.close(); 308 | System.out.println("Bye!"); 309 | 310 | // Close the uhid devices. 311 | mouse.close(); 312 | keyboard.close(); 313 | gamePad.close(); 314 | touchScreen.close(); 315 | } 316 | 317 | 318 | } 319 | -------------------------------------------------------------------------------- /uhid-PureJava/app/src/main/java/uhid/purejava/MainActivity.java: -------------------------------------------------------------------------------- 1 | package uhid.purejava; 2 | 3 | import android.app.Activity; 4 | import android.content.ClipData; 5 | import android.content.ClipboardManager; 6 | import android.content.Context; 7 | import android.graphics.Color; 8 | import android.os.Bundle; 9 | import android.view.Gravity; 10 | import android.view.ViewGroup; 11 | import android.widget.FrameLayout; 12 | import android.widget.TextView; 13 | import android.widget.Toast; 14 | 15 | import com.android.commands.hid.Hid; 16 | 17 | import java.io.File; 18 | import java.io.FileInputStream; 19 | import java.io.FileOutputStream; 20 | import java.io.IOException; 21 | import java.io.OutputStream; 22 | import java.util.zip.ZipEntry; 23 | import java.util.zip.ZipInputStream; 24 | 25 | 26 | public class MainActivity extends Activity { 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | 31 | super.onCreate(savedInstanceState); 32 | 33 | TextView textView = new TextView(this); 34 | textView.setText("Uhid simulation command has been copied. Run it by adb or root."); 35 | textView.setTextColor(Color.WHITE); 36 | textView.setGravity(Gravity.CENTER); 37 | textView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 38 | setContentView(textView); 39 | 40 | 41 | File outputDir = getExternalFilesDir(null); 42 | boolean success = extractDexFile(outputDir); 43 | if (success) { 44 | ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); 45 | String cmd = "export CLASSPATH=" + outputDir.getAbsolutePath() + "/classes.dex;app_process /system/bin " + Hid.class.getName(); 46 | // 创建剪切板内容 47 | ClipData clip = ClipData.newPlainText("label", cmd); 48 | 49 | // 将内容设置到剪切板 50 | clipboard.setPrimaryClip(clip); 51 | Toast.makeText(this, "command copied to clipboard:\n"+cmd, Toast.LENGTH_SHORT).show(); 52 | } 53 | } 54 | 55 | public boolean extractDexFile(File outputDir) { 56 | String apkPath = getPackageCodePath(); // 获取自身 APK 文件路径 57 | File dexFile = new File(outputDir, "classes.dex"); 58 | 59 | // 确保输出目录存在 60 | if (!outputDir.exists() && !outputDir.mkdirs()) { 61 | return false; 62 | } 63 | 64 | try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(apkPath))) { 65 | ZipEntry zipEntry; 66 | // 遍历 ZIP 文件(即 APK),寻找 classes.dex 文件 67 | while ((zipEntry = zipInputStream.getNextEntry()) != null) { 68 | if ("classes.dex".equals(zipEntry.getName())) { 69 | // 创建目标文件输出流 70 | try (OutputStream outputStream = new FileOutputStream(dexFile)) { 71 | byte[] buffer = new byte[1024]; 72 | int len; 73 | while ((len = zipInputStream.read(buffer)) > 0) { 74 | outputStream.write(buffer, 0, len); 75 | } 76 | zipInputStream.closeEntry(); 77 | return true; // 成功解压 78 | } 79 | } 80 | } 81 | } catch (IOException e) { 82 | e.printStackTrace(); 83 | } 84 | 85 | return false; // 解压失败 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /uhid-PureJava/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /uhid-PureJava/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id 'com.android.application' version '8.7.3' apply false 4 | id 'com.android.library' version '8.7.3' apply false 5 | } -------------------------------------------------------------------------------- /uhid-PureJava/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true 22 | android.defaults.buildfeatures.buildconfig=true 23 | android.nonFinalResIds=false -------------------------------------------------------------------------------- /uhid-PureJava/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuDi-ZhanShen/AndroidUHidPureJava/14033fc8601cfb2d1a41381adb2f77693313a8e1/uhid-PureJava/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /uhid-PureJava/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 09 22:14:37 CST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /uhid-PureJava/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /uhid-PureJava/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /uhid-PureJava/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "uhid-PureJava" 16 | include ':app' 17 | --------------------------------------------------------------------------------