diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..23de6e2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,88 @@
+# Built application files
+*.apk
+*.aar
+*.ap_
+*.aab
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+# Uncomment the following line in case you need and you don't have the release build type files in your app
+# release/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+# Android Studio 3 in .gitignore file.
+.idea/caches
+.idea/modules.xml
+# Comment next line if keeping position of elements in Navigation Editor is relevant for you
+.idea/navEditor.xml
+
+# Keystore files
+# Uncomment the following lines if you do not want to check your keystore files in.
+#*.jks
+#*.keystore
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+.cxx/
+
+# Google Services (e.g. APIs or Firebase)
+# google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+vcs.xml
+
+# lint
+lint/intermediates/
+lint/generated/
+lint/outputs/
+lint/tmp/
+# lint/reports/
+
+# Android Profiling
+*.hprof
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..e0e4c70
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+USB Gadget Tool
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..681f41a
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..a5f05cd
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..7bfef59
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..429f8ac
--- /dev/null
+++ b/README.md
@@ -0,0 +1,16 @@
+# USB Gadget Tool
+Convert your Android to any USB device you like!
+
+Following USB gadgets are integrated:
+* Keyboard & Mouse (/dev/hidg0 & /dev/hidg1)
+* FIDO CTAP (/dev/hidg0 - for WebAuthn)
+* CCID (Chip Card Interface Device)
+
+## Roadmap
+* root command logging in App
+* Creating & enabling USB Gadgets during boot
+* Import custom USB Gadget profiles
+
+## How does it work?
+USB Gadget Tool uses ConfigFS - an userspace API inside the Linux Kernel - for creation of arbitrary USB composite devices.
+https://www.kernel.org/doc/Documentation/filesystems/configfs/configfs.txt
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..3a1ea80
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,38 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 29
+
+ defaultConfig {
+ applicationId "net.tjado.usbgadget"
+ minSdkVersion 26
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: "libs", include: ["*.jar"])
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'androidx.recyclerview:recyclerview:1.1.0'
+ implementation 'androidx.cardview:cardview:1.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/net/tjado/usbgadget/ExampleInstrumentedTest.java b/app/src/androidTest/java/net/tjado/usbgadget/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..06740ab
--- /dev/null
+++ b/app/src/androidTest/java/net/tjado/usbgadget/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package net.tjado.usbgadget;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("net.tjado.usbgadget", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..85cd48e
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/assets/configfs.sh b/app/src/main/assets/configfs.sh
new file mode 100644
index 0000000..695a15f
--- /dev/null
+++ b/app/src/main/assets/configfs.sh
@@ -0,0 +1 @@
+find /config/usb_gadget/ -exec ls -la {} \; 2>&1
\ No newline at end of file
diff --git a/app/src/main/assets/usbGadgetProfiles/ccid.sh b/app/src/main/assets/usbGadgetProfiles/ccid.sh
new file mode 100644
index 0000000..d778005
--- /dev/null
+++ b/app/src/main/assets/usbGadgetProfiles/ccid.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+CONFIGFS_DIR="/config"
+GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget"
+
+GADGET="ccid3"
+GADGET_PATH=${GADGETS_PATH}/${GADGET}
+
+CONFIG_PATH="$GADGET_PATH/configs/c.1/"
+STRINGS_PATH="$GADGET_PATH/strings/0x409/"
+
+mkdir -p $CONFIG_PATH
+mkdir -p $STRINGS_PATH
+
+mkdir -p $GADGET_PATH/functions/ccid.usb0
+cd $GADGET_PATH/functions/ccid.usb0
+
+
+cd $GADGET_PATH
+echo 0xa4ac > idVendor
+echo 0x0525 > idProduct
+
+cd $STRINGS_PATH
+echo "tejado" > manufacturer
+echo "CCID" > product
+echo "42" > serialnumber
+
+cd $CONFIG_PATH
+echo 30 > MaxPower
+echo "CCID Configuration" > strings/0x409/configuration
+
+ln -s ${GADGET_PATH}/functions/ccid.usb0 $CONFIG_PATH/ccid.usb0
diff --git a/app/src/main/assets/usbGadgetProfiles/ctap.sh b/app/src/main/assets/usbGadgetProfiles/ctap.sh
new file mode 100644
index 0000000..e563bb7
--- /dev/null
+++ b/app/src/main/assets/usbGadgetProfiles/ctap.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+CONFIGFS_DIR="/config"
+GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget"
+
+GADGET="ctap3"
+GADGET_PATH=${GADGETS_PATH}/${GADGET}
+
+CONFIG_PATH="$GADGET_PATH/configs/c.1/"
+STRINGS_PATH="$GADGET_PATH/strings/0x409/"
+
+mkdir -p $CONFIG_PATH
+mkdir -p $STRINGS_PATH
+
+mkdir -p $GADGET_PATH/functions/hid.usb0
+cd $GADGET_PATH/functions/hid.usb0
+
+# HID protocol (according to USB spec: 1 for keyboard)
+echo 0 > protocol
+# device subclass
+echo 0 > subclass
+# number of bytes per record
+echo 64 > report_length
+
+# writing report descriptor
+echo -ne \\x06\\xD0\\xF1\\x09\\x01\\xA1\\x01\\x09\\x20\\x15\\x00\\x26\\xFF\\x00\\x75\\x08\\x95\\x40\\x81\\x02\\x09\\x21\\x15\\x00\\x26\\xFF\\x00\\x75\\x08\\x95\\x40\\x91\\x02\\xC0 > report_desc
+
+cd $GADGET_PATH
+echo '0xa4ac' > idVendor
+echo '0x0525' > idProduct
+
+echo '0x0512' > bcdDevice
+echo '0x0200' > bcdUSB
+echo 0 > bDeviceProtocol
+echo 0 > bDeviceSubClass
+echo 8 > bMaxPacketSize0
+
+cd $STRINGS_PATH
+echo "tejado" > manufacturer
+echo "CTAP" > product
+echo "42" > serialnumber
+
+cd $CONFIG_PATH
+echo 30 > MaxPower
+echo "HID Configuration" > strings/0x409/configuration
+
+ln -s ${GADGET_PATH}/functions/hid.usb0 $CONFIG_PATH/hid.usb0
\ No newline at end of file
diff --git a/app/src/main/assets/usbGadgetProfiles/mouse+keyboard.sh b/app/src/main/assets/usbGadgetProfiles/mouse+keyboard.sh
new file mode 100644
index 0000000..37d4792
--- /dev/null
+++ b/app/src/main/assets/usbGadgetProfiles/mouse+keyboard.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+CONFIGFS_DIR="/config"
+GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget"
+
+GADGET="keyboard"
+GADGET_PATH=${GADGETS_PATH}/${GADGET}
+
+CONFIG_PATH="$GADGET_PATH/configs/c.1/"
+STRINGS_PATH="$GADGET_PATH/strings/0x409/"
+
+mkdir -p $CONFIG_PATH
+mkdir -p $STRINGS_PATH
+
+mkdir -p $GADGET_PATH/functions/hid.usb0
+cd $GADGET_PATH/functions/hid.usb0
+
+# HID protocol (according to USB spec: 1 for keyboard)
+echo 1 > protocol
+# device subclass
+echo 1 > subclass
+# number of bytes per record
+echo 8 > report_length
+
+# writing report descriptor
+echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > report_desc
+
+mkdir -p $GADGET_PATH/functions/hid.usb1
+cd $GADGET_PATH/functions/hid.usb1
+
+# HID protocol (according to USB spec: 2 for mouse)
+echo 2 > protocol
+# device subclass
+echo 1 > subclass
+# number of bytes per record
+echo 4 > report_length
+
+# writing report descriptor
+echo -ne \\x05\\x01\\x09\\x02\\xa1\\x01\\x09\\x01\\xa1\\x00\\x05\\x09\\x19\\x01\\x29\\x05\\x15\\x00\\x25\\x01\\x95\\x05\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x03\\x81\\x01\\x05\\x01\\x09\\x30\\x09\\x31\\x09\\x38\\x15\\x81\\x25\\x7F\\x75\\x08\\x95\\x03\\x81\\x06\\xc0\\xc0 > report_desc
+
+
+cd $GADGET_PATH
+echo 0xa4ac > idVendor
+echo 0x0525 > idProduct
+
+cd $STRINGS_PATH
+echo "tejado" > manufacturer
+echo "HID" > product
+echo "42" > serialnumber
+
+cd $CONFIG_PATH
+echo 120 > MaxPower
+echo "HID Configuration" > strings/0x409/configuration
+
+ln -s ${GADGET_PATH}/functions/hid.usb0 $CONFIG_PATH/hid.usb0
+ln -s ${GADGET_PATH}/functions/hid.usb1 $CONFIG_PATH/hid.usb1
\ No newline at end of file
diff --git a/app/src/main/java/net/tjado/usbgadget/ExecuteAsRootUtil.java b/app/src/main/java/net/tjado/usbgadget/ExecuteAsRootUtil.java
new file mode 100644
index 0000000..b17f0a1
--- /dev/null
+++ b/app/src/main/java/net/tjado/usbgadget/ExecuteAsRootUtil.java
@@ -0,0 +1,242 @@
+/**
+ * Authorizer
+ *
+ * Copyright 2016 by Tjado Mäcke
+ * Licensed under GNU General Public License 3.0.
+ *
+ * @license GPL-3.0
+ */
+package net.tjado.usbgadget;
+
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Scanner;
+
+/*
+ * Class by muzikant
+ *
+ * Reference:
+ * http://muzikant-android.blogspot.com/2011/02/how-to-get-root-access-and-execute.html
+ * http://stackoverflow.com/a/7102780
+ */
+
+public class ExecuteAsRootUtil
+{
+
+ public static boolean canRunRootCommands()
+ {
+ boolean retval = false;
+ Process suProcess;
+
+ try
+ {
+ suProcess = Runtime.getRuntime().exec("su");
+
+ DataOutputStream os = new DataOutputStream(suProcess.getOutputStream());
+ DataInputStream osRes = new DataInputStream(suProcess.getInputStream());
+
+ if (null != os && null != osRes)
+ {
+ // Getting the id of the current user to check if this is root
+ os.writeBytes("id\n");
+ os.flush();
+
+ String currUid = osRes.readLine();
+ boolean exitSu = false;
+ if (null == currUid)
+ {
+ retval = false;
+ exitSu = false;
+ Log.d("ROOT", "Can't get root access or denied by user");
+ }
+ else if (true == currUid.contains("uid=0"))
+ {
+ retval = true;
+ exitSu = true;
+ Log.d("ROOT", "Root access granted");
+ }
+ else
+ {
+ retval = false;
+ exitSu = true;
+ Log.d("ROOT", "Root access rejected: " + currUid);
+ }
+
+ if (exitSu)
+ {
+ os.writeBytes("exit\n");
+ os.flush();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ // Can't get root !
+ // Probably broken pipe exception on trying to write to output stream (os) after su failed, meaning that the device is not rooted
+
+ retval = false;
+ Log.d("ROOT", "Root access rejected [" + e.getClass().getName() + "] : " + e.getMessage());
+ }
+
+ return retval;
+ }
+
+ public static final Pair execute(String command) {
+ return execute(new String[]{ command });
+ }
+
+ public static final Pair execute(String[] commands)
+ {
+ Boolean retval = false;
+ String output = null;
+
+ try
+ {
+ if (commands != null && commands.length > 0)
+ {
+ Process suProcess = Runtime.getRuntime().exec("su -");
+
+ DataOutputStream stdin = new DataOutputStream(suProcess.getOutputStream());
+ InputStream stdout = suProcess.getInputStream();
+ InputStream stderr = suProcess.getErrorStream();
+
+ for (String cmd: commands) {
+ Log.d("root", String.format("Execute command: %s", cmd));
+ stdin.writeBytes(cmd);
+ stdin.flush();
+ }
+
+ stdin.writeBytes("exit\n");
+ stdin.flush();
+
+ stdin.close();
+
+ StringBuffer sb = new StringBuffer();
+ Scanner scanner = new Scanner(stdout);
+ while (scanner.hasNext()) {
+ sb.append(scanner.nextLine());
+ sb.append(System.lineSeparator());
+ }
+
+ Scanner scannerErr = new Scanner(stderr);
+ while (scannerErr.hasNext()) {
+ Log.d("root", scannerErr.nextLine());
+ }
+
+ output = sb.toString();
+ Log.d("root", output);
+
+ try
+ {
+ int suProcessRetval = suProcess.waitFor();
+ if (255 != suProcessRetval)
+ {
+ // Root access granted
+ retval = true;
+ }
+ else
+ {
+ // Root access denied
+ retval = false;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.e("ROOT", "Error executing root action", ex);
+ }
+ }
+ }
+ catch (IOException ex)
+ {
+ Log.w("ROOT", "Can't get root access", ex);
+ }
+ catch (SecurityException ex)
+ {
+ Log.w("ROOT", "Can't get root access", ex);
+ }
+ catch (Exception ex)
+ {
+ Log.w("ROOT", "Error executing internal operation", ex);
+ }
+
+ return new Pair(retval, output);
+ }
+
+
+ public static final Pair executeBinaryOut(String[] commands)
+ {
+ Boolean retval = false;
+ byte[] output = new byte[64];;
+
+ try
+ {
+ if (commands != null && commands.length > 0)
+ {
+ int count = 64;
+ Process suProcess = Runtime.getRuntime().exec("su -");
+
+ DataOutputStream stdin = new DataOutputStream(suProcess.getOutputStream());
+ InputStream stdout = suProcess.getInputStream();
+ InputStream stderr = suProcess.getErrorStream();
+
+ for (String cmd: commands) {
+ Log.d("root", String.format("Execute command: %s", cmd));
+ stdin.writeBytes(cmd);
+ stdin.flush();
+ }
+
+ stdin.writeBytes("exit\n");
+ stdin.flush();
+ stdin.close();
+
+ int tmp = stdout.read(output, 0, 64);
+ Log.d("muh", String.format("read %d of data", tmp));
+ stdout.close();
+
+ Scanner scannerErr = new Scanner(stderr);
+ while (scannerErr.hasNext()) {
+ Log.d("root", scannerErr.nextLine());
+ }
+
+ try
+ {
+ int suProcessRetval = suProcess.waitFor();
+ if (255 != suProcessRetval)
+ {
+ // Root access granted
+ retval = true;
+ }
+ else
+ {
+ // Root access denied
+ retval = false;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.e("ROOT", "Error executing root action", ex);
+ }
+ }
+ }
+ catch (IOException ex)
+ {
+ Log.w("ROOT", "Can't get root access", ex);
+ }
+ catch (SecurityException ex)
+ {
+ Log.w("ROOT", "Can't get root access", ex);
+ }
+ catch (Exception ex)
+ {
+ Log.w("ROOT", "Error executing internal operation", ex);
+ }
+
+ return new Pair(retval, output);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.java b/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.java
new file mode 100644
index 0000000..89cb9e3
--- /dev/null
+++ b/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.java
@@ -0,0 +1,129 @@
+package net.tjado.usbgadget;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.text.Html;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.util.Collections;
+import java.util.List;
+
+import androidx.cardview.widget.CardView;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class GadgetAdapter extends RecyclerView.Adapter {
+ private Activity context;
+ private LayoutInflater inflater;
+ List data = Collections.emptyList();
+
+ public GadgetAdapter(Activity context, List data) {
+ this.context = context;
+ // inflater = LayoutInflater.from(context);
+ this.data = data;
+ }
+
+ // Inflate the layout when viewholder created
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+
+ View view = inflater.from(parent.getContext()).
+ inflate(R.layout.cardview_gadget, parent, false);
+ return new DefaultViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+
+ initLayoutDefault((DefaultViewHolder) holder, position);
+ }
+
+
+ @Override
+ public int getItemCount() {
+ return data.size();
+ }
+
+ private void initLayoutDefault(DefaultViewHolder holder, int pos) {
+ holder.gadget = data.get(pos);
+ holder.manufacturer.setText(holder.gadget.getValue("manufacturer"));
+ holder.product.setText(holder.gadget.getValue("product"));
+ holder.serialnumber.setText(holder.gadget.getValue("serialnumber"));
+ holder.path.setText(holder.gadget.getValue("gadget_path"));
+ holder.functions.setText(Html.fromHtml(holder.gadget.getFormattedFunctions(), Html.FROM_HTML_MODE_LEGACY));
+
+ String udc = holder.gadget.getValue("udc");
+ holder.udc.setText(udc);
+ if (holder.gadget.isActivated()) {
+ holder.card.setCardBackgroundColor(Color.GREEN);
+ holder.activate.setText(R.string.deactivate);
+ } else {
+ holder.activate.setText(R.string.activate);
+ holder.card.setCardBackgroundColor(Color.WHITE);
+ }
+
+ }
+
+ // Static inner class to initialize the views of rows
+ static class DefaultViewHolder extends RecyclerView.ViewHolder {
+ GadgetObject gadget;
+ CardView card;
+ TextView manufacturer;
+ TextView product;
+ TextView serialnumber;
+ TextView udc;
+ TextView functions;
+ TextView path;
+ Button activate;
+
+ public DefaultViewHolder(View itemView) {
+ super(itemView);
+ card = (CardView) itemView.findViewById(R.id.cv_gadget);
+ manufacturer = (TextView) itemView.findViewById(R.id.tv_gadget_manufacturer);
+ product = (TextView) itemView.findViewById(R.id.tv_gadget_product);
+ serialnumber = (TextView) itemView.findViewById(R.id.tv_gadget_sn);
+ udc = (TextView) itemView.findViewById(R.id.tv_gadget_udc);
+ path = (TextView) itemView.findViewById(R.id.tv_gadget_path);
+ functions = (TextView) itemView.findViewById(R.id.tv_gadget_functions);
+ activate = (Button) itemView.findViewById(R.id.activate);
+
+ activate.setOnClickListener(view -> {
+ String gadgetPath = gadget.getValue("gadget_path");
+ String[] commands = {};
+
+ if (gadgetPath == null || !gadgetPath.startsWith("/config/")) {
+ Log.d("root", String.format("gadgetPath errornous value: %s", gadgetPath));
+ return;
+ }
+
+ if (gadget.isActivated()) {
+ // deactivate specific gadget
+ String cmdDeactivateUsb = String.format("echo \"\" > %s/UDC\n", gadgetPath);
+
+ commands = new String[]{cmdDeactivateUsb};
+ } else {
+ // deactivate all
+ String cmdDeactivateUsbAll = "find /config/usb_gadget/ -name UDC -type f -exec sh -c 'echo \"\" > \"$@\"' _ {} \\;\n";
+ // activate clicked one
+ String cmdActivateUsb = String.format("getprop sys.usb.controller > %s/UDC\n", gadgetPath);
+
+ commands = new String[]{cmdDeactivateUsbAll, cmdActivateUsb};
+ }
+
+ RootTask mTask = new RootTask(itemView.getContext(), commands, response -> {
+ //mTextView.setText((String) response);
+ });
+ mTask.execute();
+
+ ((MainActivity) itemView.getContext()).getGadgetData();
+ return;
+ });
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetObject.java b/app/src/main/java/net/tjado/usbgadget/GadgetObject.java
new file mode 100644
index 0000000..b702ad0
--- /dev/null
+++ b/app/src/main/java/net/tjado/usbgadget/GadgetObject.java
@@ -0,0 +1,89 @@
+package net.tjado.usbgadget;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+public class GadgetObject {
+ protected String mText1;
+ protected HashMap values = new HashMap<>();
+ protected SortedSet functions = new TreeSet<>();
+ protected SortedSet activeFunctions = new TreeSet<>();
+
+ private boolean isSelected;
+
+ public GadgetObject() {
+ }
+
+ public GadgetObject(String text) {
+ this.setmText1(text);
+ }
+
+ public String getmText1() {
+ return mText1;
+ }
+
+ public void setmText1(String mText1) {
+ this.mText1 = mText1;
+ }
+
+ public void setValue(String key, String value) {
+ values.put(key, value);
+ }
+
+ public String getValue(String key) {
+ try {
+ return values.get(key);
+ }
+ catch (Exception e) {
+ return "not set";
+ }
+ }
+
+ public void addFunction(String f) {
+ functions.add(f);
+ }
+
+ public SortedSet getFunctions(String key) {
+ return functions;
+ }
+
+ public void addActiveFunction(String f) {
+ activeFunctions.add(f);
+ }
+
+ public SortedSet getActiveFunctions(String key) {
+ return activeFunctions;
+ }
+
+ public Boolean isActivated() {
+ String udc = getValue("udc");
+ if (udc.equals("") || udc.equals("not set")) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public String getFormattedFunctions() {
+ StringBuffer sb = new StringBuffer();
+ String color = "";
+
+ Iterator it = functions.iterator();
+ while( it.hasNext() ) {
+ String item = it.next();
+
+ if( activeFunctions.contains(item)) {
+ color = "#008000";
+ } else {
+ color = "#ff0000";
+ }
+
+ sb.append(String.format("%s
", color, item));
+ }
+
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/tjado/usbgadget/MainActivity.java b/app/src/main/java/net/tjado/usbgadget/MainActivity.java
new file mode 100644
index 0000000..a745021
--- /dev/null
+++ b/app/src/main/java/net/tjado/usbgadget/MainActivity.java
@@ -0,0 +1,193 @@
+package net.tjado.usbgadget;
+
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.ViewFlipper;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class MainActivity extends AppCompatActivity {
+
+ private GadgetAdapter gadgetAdapter;
+ private RecyclerView gadgetRecyclerView;
+
+ List data;
+ String [] gadgetProfileList;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ gadgetRecyclerView = (RecyclerView) findViewById(R.id.rv_gadgets);
+ data = new ArrayList<>();
+
+ if( ExecuteAsRootUtil.canRunRootCommands() ) {
+ getGadgetData();
+ } else {
+ ((ViewFlipper) findViewById(R.id.flipper)).showNext();
+ }
+
+
+ try {
+ gadgetProfileList = getAssets().list("usbGadgetProfiles/");
+ } catch (IOException e) {
+ gadgetProfileList = null;
+ }
+
+ gadgetAdapter = new GadgetAdapter(this, data);
+ gadgetRecyclerView.setAdapter(gadgetAdapter);
+ GridLayoutManager gridLayoutManager = new GridLayoutManager(this,1);
+ gadgetRecyclerView.setLayoutManager(gridLayoutManager);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+
+ switch (id) {
+ case R.id.action_add:
+ addGadget();
+ break;
+ case R.id.action_refresh:
+ ((ViewFlipper) findViewById(R.id.flipper)).setDisplayedChild(0);
+ if( ExecuteAsRootUtil.canRunRootCommands() ) {
+ getGadgetData();
+ } else {
+ ((ViewFlipper) findViewById(R.id.flipper)).showNext();
+ }
+ return true;
+ case R.id.info:
+ showInfo();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ public void getGadgetData() {
+ String cmd = "for dir in /config/usb_gadget/*/; do echo GADGET_PATH=$dir; cd $dir; if [ \"$?\" -ne \"0\" ]; then echo \"Error - not able to change dir to $dir... exit\"; exit 1; fi; echo UDC=$(cat UDC); find ./configs/ -type l -exec sh -c 'echo FUNCTIONS_ACTIVE=$(basename $(readlink \"$@\"))' _ {} \\;; for f in ./functions/*/; do echo FUNCTIONS=$(basename $f); done; cd ./strings/0x409/; for vars in *; do echo ${vars}=$(cat $vars); done; echo \"=============\"; done; \n";
+
+ RootTask mTask = new RootTask(this.getApplicationContext(), cmd, response -> {
+ //mTextView.setText((String) Response);
+ BufferedReader bufReader = new BufferedReader(new StringReader(response));
+ data.clear();
+ GadgetObject gadget = new GadgetObject();
+
+ try {
+ String line;
+ while ((line = bufReader.readLine()) != null) {
+ if(line.equals("=============")) {
+ data.add(gadget);
+ gadget = new GadgetObject();
+ continue;
+ }
+
+ String[] parts = line.split("=",2);
+
+ if( parts[0].equals("FUNCTIONS") ) {
+ gadget.addFunction(parts[1]);
+ } else if( parts[0].equals("FUNCTIONS_ACTIVE") ) {
+ gadget.addActiveFunction(parts[1]);
+ } else {
+ gadget.setValue(parts[0].toLowerCase(), parts[1]);
+ }
+ }
+ }
+ catch (Exception e) {
+ Log.d("root", e.getMessage());
+ e.printStackTrace();
+ }
+
+ gadgetAdapter.notifyDataSetChanged();
+
+ });
+ mTask.execute();
+ }
+
+ protected void showInfo() {
+ final AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
+ alertBuilder.setTitle(R.string.info_title);
+ alertBuilder.setCancelable(false);
+
+ alertBuilder.setMessage(R.string.info_message);
+ alertBuilder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialoginterface, int i) {
+ }});
+
+ alertBuilder.show();
+ }
+
+ protected void addGadget() {
+ final AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
+ alertBuilder.setTitle("Add Gadget");
+ alertBuilder.setCancelable(true);
+
+ alertBuilder.setItems(gadgetProfileList, (dialog, which) -> loadGadgetProfileFromAsset(gadgetProfileList[which]));
+
+ alertBuilder.show();
+ }
+
+ protected String getGadgetProfile(String assetFile) {
+ BufferedReader reader = null;
+ try {
+ InputStream is = getAssets().open(String.format("usbGadgetProfiles/%s", assetFile));
+ Scanner scanner = new Scanner(is);
+
+ StringBuffer sb = new StringBuffer();
+ while (scanner.hasNext()) {
+ sb.append(scanner.nextLine() + "\n");
+ }
+ return sb.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ protected Boolean loadGadgetProfileFromAsset(String assetFile) {
+ String profile = getGadgetProfile(assetFile);
+
+ if (profile == null || profile.equals("")) {
+ return false;
+ }
+
+ RootTask mTask = new RootTask(this.getApplicationContext(), profile, response -> {
+ getGadgetData();
+ });
+ mTask.execute();
+
+ return true;
+ }
+
+ protected void runShell() {
+ String cmd = "find /config/usb_gadget/ -exec ls -la {} \\; 2>&1\n";
+ RootTask mTask = new RootTask(this.getApplicationContext(), cmd, response -> {
+ //mTextView.setText((String) response);
+ });
+ mTask.execute();
+
+ //Pair cr = ExecuteAsRootUtil.execute("find /config/usb_gadget/ -exec ls -la {} \\; 2>&1\n");
+ //mTextView.setText((String) cr.second);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/tjado/usbgadget/RootTask.java b/app/src/main/java/net/tjado/usbgadget/RootTask.java
new file mode 100644
index 0000000..a702a04
--- /dev/null
+++ b/app/src/main/java/net/tjado/usbgadget/RootTask.java
@@ -0,0 +1,45 @@
+package net.tjado.usbgadget;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.util.Pair;
+
+public class RootTask extends AsyncTask {
+ OnRootTaskListener listener;
+ Context mContext;
+ String[] mCommands;
+
+ public interface OnRootTaskListener {
+ public void OnRootTaskFinish(String Response);
+ }
+
+ public RootTask(Context ctx, String command, OnRootTaskListener listener) {
+ mContext = ctx;
+ mCommands = new String[] {command};
+ this.listener = listener;
+ }
+
+ public RootTask(Context ctx, String[] commands, OnRootTaskListener listener) {
+ mContext = ctx;
+ mCommands = commands;
+ this.listener = listener;
+ }
+
+ @Override
+ protected String doInBackground(Void... params) {
+ Pair cr = ExecuteAsRootUtil.execute(mCommands);
+ return (String) cr.second;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ }
+
+ @Override
+ protected void onPostExecute(String result) {
+ if( listener != null ) {
+ listener.OnRootTaskFinish(result);
+ }
+ }
+}
diff --git a/app/src/main/res/drawable-v24/baseline_add_circle_outline_24.xml b/app/src/main/res/drawable-v24/baseline_add_circle_outline_24.xml
new file mode 100644
index 0000000..a1c721d
--- /dev/null
+++ b/app/src/main/res/drawable-v24/baseline_add_circle_outline_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable-v24/baseline_help_24.xml b/app/src/main/res/drawable-v24/baseline_help_24.xml
new file mode 100644
index 0000000..c0c9268
--- /dev/null
+++ b/app/src/main/res/drawable-v24/baseline_help_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable-v24/baseline_info_24.xml b/app/src/main/res/drawable-v24/baseline_info_24.xml
new file mode 100644
index 0000000..17255b7
--- /dev/null
+++ b/app/src/main/res/drawable-v24/baseline_info_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable-v24/baseline_loop_24.xml b/app/src/main/res/drawable-v24/baseline_loop_24.xml
new file mode 100644
index 0000000..c2f773a
--- /dev/null
+++ b/app/src/main/res/drawable-v24/baseline_loop_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..8f12af7
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/cardview_gadget.xml b/app/src/main/res/layout/cardview_gadget.xml
new file mode 100644
index 0000000..65b6c01
--- /dev/null
+++ b/app/src/main/res/layout/cardview_gadget.xml
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
new file mode 100644
index 0000000..535a8b2
--- /dev/null
+++ b/app/src/main/res/menu/main.xml
@@ -0,0 +1,21 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a571e60
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..61da551
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c41dd28
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..db5080a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..6dba46d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..da31a87
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..15ac681
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b216f2d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..f25a419
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..e96783c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..bb2c80a
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #33b5e5
+ #33b5e5
+ #33b5e5
+ #D1E3E9
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8abdfeb
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,16 @@
+
+ USB Gadget Tool
+ USB Gadget Tool requires "root" permissions.
+ Activate!
+ Deactivate!
+ Refresh
+ Add USB Gadget
+ Information
+ USB Gadget Tool v0.1 beta
+
+ USB Gadget Tool gives you full control over the USB Device services on your phone. For this ConfigFS on the OS Kernel is used.\n\n
+ By tejado (Tjado Mäcke)\n\n
+ Website:\n
+ https:\/\/github.com/tejado/android-usb-gadget
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..17ea046
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/net/tjado/usbgadget/ExampleUnitTest.java b/app/src/test/java/net/tjado/usbgadget/ExampleUnitTest.java
new file mode 100644
index 0000000..1da4201
--- /dev/null
+++ b/app/src/test/java/net/tjado/usbgadget/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package net.tjado.usbgadget;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..6754c23
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,24 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:4.0.1"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..c52ac9b
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..45dd23c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jul 24 10:04:38 CEST 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..eb84f94
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':app'
+rootProject.name = "USB Gadget Tool"
\ No newline at end of file