diff --git a/.gitignore b/.gitignore index e511255..c737d54 100644 --- a/.gitignore +++ b/.gitignore @@ -41,17 +41,7 @@ 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 +.idea/ # Keystore files # Uncomment the following lines if you do not want to check your keystore files in. diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index e0e4c70..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -USB Gadget Tool \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 681f41a..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - -
- - - - 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/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index fb7f4a8..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index a5f05cd..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 860da66..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index e497da9..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 910f1c5..16fccf7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' android { compileSdkVersion 33 @@ -23,6 +24,11 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } + + buildFeatures { + viewBinding true + } + } dependencies { @@ -34,6 +40,10 @@ dependencies { implementation 'androidx.viewpager2:viewpager2:1.0.0' implementation 'com.google.android.material:material:1.6.1' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' + testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85cd48e..7a9a710 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + - + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/usbFunctionProfiles/CCID b/app/src/main/assets/usbFunctionProfiles/CCID index bf18547..da0d9f5 100644 --- a/app/src/main/assets/usbFunctionProfiles/CCID +++ b/app/src/main/assets/usbFunctionProfiles/CCID @@ -1,6 +1,5 @@ #!/bin/sh -GADGET="ccid" GADGET_PATH="____gadgetPath____" cd $GADGET_PATH/configs/ diff --git a/app/src/main/assets/usbFunctionProfiles/CTAP b/app/src/main/assets/usbFunctionProfiles/CTAP index e931146..18f1f8c 100644 --- a/app/src/main/assets/usbFunctionProfiles/CTAP +++ b/app/src/main/assets/usbFunctionProfiles/CTAP @@ -1,6 +1,5 @@ #!/bin/sh -GADGET="ctap" GADGET_PATH="____gadgetPath____" cd $GADGET_PATH/configs/ diff --git a/app/src/main/assets/usbFunctionProfiles/Keyboard b/app/src/main/assets/usbFunctionProfiles/Keyboard index 6283238..18cf54e 100644 --- a/app/src/main/assets/usbFunctionProfiles/Keyboard +++ b/app/src/main/assets/usbFunctionProfiles/Keyboard @@ -1,6 +1,5 @@ #!/bin/sh -GADGET="keyboard" GADGET_PATH="____gadgetPath____" cd $GADGET_PATH/configs/ diff --git a/app/src/main/assets/usbFunctionProfiles/Mouse b/app/src/main/assets/usbFunctionProfiles/Mouse index 736976b..627a1ad 100644 --- a/app/src/main/assets/usbFunctionProfiles/Mouse +++ b/app/src/main/assets/usbFunctionProfiles/Mouse @@ -1,6 +1,5 @@ #!/bin/sh -GADGET="mouse" GADGET_PATH="____gadgetPath____" cd $GADGET_PATH/configs/ diff --git a/app/src/main/assets/usbFunctionProfiles/UVC b/app/src/main/assets/usbFunctionProfiles/UVC index 6b60f42..9557d17 100644 --- a/app/src/main/assets/usbFunctionProfiles/UVC +++ b/app/src/main/assets/usbFunctionProfiles/UVC @@ -1,6 +1,5 @@ #!/bin/sh -GADGET="camera" GADGET_PATH="____gadgetPath____" cd $GADGET_PATH/configs/ diff --git a/app/src/main/assets/usbGadgetProfiles/CCID b/app/src/main/assets/usbGadgetProfiles/CCID index d0f5ce6..1066cd7 100644 --- a/app/src/main/assets/usbGadgetProfiles/CCID +++ b/app/src/main/assets/usbGadgetProfiles/CCID @@ -3,7 +3,7 @@ CONFIGFS_DIR="/config" GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget" -GADGET="ccid3" +GADGET="____gadgetName____" GADGET_PATH=${GADGETS_PATH}/${GADGET} CONFIG_PATH="$GADGET_PATH/configs/c.1/" @@ -27,6 +27,7 @@ echo "42" > serialnumber cd $CONFIG_PATH echo 30 > MaxPower +mkdir -p strings/0x409 echo "Configuration" > strings/0x409/configuration ln -s ${GADGET_PATH}/functions/ccid.usb0 $CONFIG_PATH/ccid.usb0 diff --git a/app/src/main/assets/usbGadgetProfiles/CTAP b/app/src/main/assets/usbGadgetProfiles/CTAP index ac5278a..5582648 100644 --- a/app/src/main/assets/usbGadgetProfiles/CTAP +++ b/app/src/main/assets/usbGadgetProfiles/CTAP @@ -3,7 +3,7 @@ CONFIGFS_DIR="/config" GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget" -GADGET="ctap3" +GADGET="____gadgetName____" GADGET_PATH=${GADGETS_PATH}/${GADGET} CONFIG_PATH="$GADGET_PATH/configs/c.1/" @@ -42,6 +42,7 @@ echo "42" > serialnumber cd $CONFIG_PATH echo 30 > MaxPower +mkdir -p strings/0x409 echo "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/Empty b/app/src/main/assets/usbGadgetProfiles/Empty index b3be8b2..96c133c 100644 --- a/app/src/main/assets/usbGadgetProfiles/Empty +++ b/app/src/main/assets/usbGadgetProfiles/Empty @@ -3,7 +3,7 @@ CONFIGFS_DIR="/config" GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget" -GADGET="gadget_${RANDOM}" +GADGET="____gadgetName____" GADGET_PATH=${GADGETS_PATH}/${GADGET} CONFIG_PATH="$GADGET_PATH/configs/c.1/" @@ -24,4 +24,5 @@ echo "42" > serialnumber cd $CONFIG_PATH echo 120 > MaxPower +mkdir -p strings/0x409 echo "Configuration" > strings/0x409/configuration diff --git a/app/src/main/assets/usbGadgetProfiles/Mouse & Keyboard b/app/src/main/assets/usbGadgetProfiles/MouseKeyboard similarity index 97% rename from app/src/main/assets/usbGadgetProfiles/Mouse & Keyboard rename to app/src/main/assets/usbGadgetProfiles/MouseKeyboard index e47b1d8..2d9d3b2 100644 --- a/app/src/main/assets/usbGadgetProfiles/Mouse & Keyboard +++ b/app/src/main/assets/usbGadgetProfiles/MouseKeyboard @@ -3,7 +3,7 @@ CONFIGFS_DIR="/config" GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget" -GADGET="keyboard" +GADGET="____gadgetName____" GADGET_PATH=${GADGETS_PATH}/${GADGET} CONFIG_PATH="$GADGET_PATH/configs/c.1/" @@ -50,6 +50,7 @@ echo "42" > serialnumber cd $CONFIG_PATH echo 120 > MaxPower +mkdir -p strings/0x409 echo "Configuration" > strings/0x409/configuration ln -s ${GADGET_PATH}/functions/hid.keyboard $CONFIG_PATH/hid.keyboard diff --git a/app/src/main/assets/usbGadgetProfiles/UVC b/app/src/main/assets/usbGadgetProfiles/UVC index c20ce7b..7bc7574 100644 --- a/app/src/main/assets/usbGadgetProfiles/UVC +++ b/app/src/main/assets/usbGadgetProfiles/UVC @@ -3,7 +3,7 @@ CONFIGFS_DIR="/config" GADGETS_PATH="${CONFIGFS_DIR}/usb_gadget" -GADGET="uvc" +GADGET="____gadgetName____" GADGET_PATH=${GADGETS_PATH}/${GADGET} CONFIG_PATH="$GADGET_PATH/configs/c.1/" diff --git a/app/src/main/java/net/tjado/usbgadget/BootConfiguration.kt b/app/src/main/java/net/tjado/usbgadget/BootConfiguration.kt new file mode 100644 index 0000000..6daa34f --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/BootConfiguration.kt @@ -0,0 +1,44 @@ +package net.tjado.usbgadget + +import android.app.Application +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +class BootConfiguration : BroadcastReceiver() { + private val TAG = "DEVICE_BOOT" + + override fun onReceive(context: Context, intent: Intent) { + if (Intent.ACTION_BOOT_COMPLETED == intent.action) { + Log.i(TAG, "Intent ACTION_LOCKED_BOOT_COMPLETED") + val gmPreferences = GadgetManagerPreferences(context) + + val shellApi = GadgetShellApi() + if(!shellApi.hasRootSync()) { + Log.e(TAG, "No root permissions... aborting") + return + } + Log.i(TAG, "Seems root is available... proceeding...") + + val assets = GadgetAssetProfiles(context.applicationContext as Application) + val activeGadget = gmPreferences.getActiveBootGadget() + + for(profile in assets.allGadgetProfiles) { + if( gmPreferences.isAddedDuringBoot(profile) ) { + Log.i(TAG, "Adding gadget $profile") + + shellApi.exec(assets.getProfileGadget(profile)!!) { response -> + if (response != null && response.first == true && profile == activeGadget) { + Log.i(TAG, "Activating gadget $profile") + shellApi.activate("/config/usb_gadget/${profile}", null) + } else if (response != null && response.first == false) { + Log.e(TAG, "Activation failed: ${response.second}") + } + } + } + } + + Log.i(TAG, "Synchronous processing finished. ") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/CoroutineTask.kt b/app/src/main/java/net/tjado/usbgadget/CoroutineTask.kt new file mode 100644 index 0000000..772c362 --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/CoroutineTask.kt @@ -0,0 +1,39 @@ +package net.tjado.usbgadget + +import kotlinx.coroutines.* + +// gist by Shanmuga Santhosh A +// https://gist.github.com/shanmugasanthosh7/417f413f359d10c8f4581542a11b3f91/ +abstract class CoroutineTask { + + protected open fun onPreExecute() {} + + protected abstract fun doInBackground(vararg params: Params): APair + + protected open fun onPostExecute(result: APair) {} + + private val mUiScope by lazy { CoroutineScope(Dispatchers.Main) } + + private lateinit var mJob: Job + + fun execute(vararg params: Params) { + mJob = mUiScope.launch { + // run on UI/Main thread + onPreExecute() + + // execute on worker thread + val result = async(Dispatchers.IO) { doInBackground(*params) } + + // return result on UI/Main thread + onPostExecute(result.await()) + } + } + + fun cancel() { + if (::mJob.isInitialized && mJob.isActive) mJob.cancel() + } + + fun cancelAll() { + if (mUiScope.isActive) mUiScope.cancel() + } +} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/DeviceInfoFragment.java b/app/src/main/java/net/tjado/usbgadget/DeviceInfoFragment.java deleted file mode 100644 index c0d7076..0000000 --- a/app/src/main/java/net/tjado/usbgadget/DeviceInfoFragment.java +++ /dev/null @@ -1,94 +0,0 @@ -package net.tjado.usbgadget; - -import android.graphics.Typeface; -import android.os.Bundle; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.fragment.app.Fragment; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; - -import java.io.BufferedReader; -import java.io.StringReader; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; - -public class DeviceInfoFragment extends Fragment { - - private MutableLiveData> deviceData; - private View v; - - public DeviceInfoFragment() {} - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - this.v = inflater.inflate(R.layout.fragment_device_info, container, false); - - deviceData = new MutableLiveData<>(); - deviceData.setValue(new TreeMap<>(new DeviceInfoMapComparator())); - - deviceData.observe(getViewLifecycleOwner(), item -> { - this.loadData(item); - }); - - GadgetShellApi gsa = new GadgetShellApi(); - gsa.updateDeviceInfo(deviceData); - - return v; - } - - private void loadData(Map deviceData) { - - LinearLayout list = this.v.findViewById(R.id.list_device_data); - list.removeAllViews(); - - View viHead = getLayoutInflater().inflate(R.layout.row_device_info, null); - - TextView tvHeadName = viHead.findViewById(R.id.name); - tvHeadName.setText("Kernel Config Parameter"); - tvHeadName.setTypeface(null, Typeface.BOLD); - - TextView tvHeadValue = viHead.findViewById(R.id.value); - tvHeadValue.setText("Value"); - tvHeadValue.setTypeface(null, Typeface.BOLD); - list.addView(viHead); - - for (Map.Entry entry : deviceData.entrySet()) { - View vi = getLayoutInflater().inflate(R.layout.row_device_info, null); - - TextView tvName = vi.findViewById(R.id.name); - tvName.setText(entry.getKey().toUpperCase()); - - TextView tvValue = vi.findViewById(R.id.value); - String value = entry.getValue(); - - String color; - switch (value) { - case "y": - value = "Yes"; - color = "#008000"; - break; - case "n": - value = "No"; - color = "#ff0000"; - break; - case "NOT_SET": - value = "Not set"; - color = "#ff0000"; - break; - default: - color = "#000000"; - } - - tvValue.setText(Html.fromHtml(String.format("%s", color, value), Html.FROM_HTML_MODE_LEGACY)); - - list.addView(vi); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/DeviceInfoFragment.kt b/app/src/main/java/net/tjado/usbgadget/DeviceInfoFragment.kt new file mode 100644 index 0000000..fd307cd --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/DeviceInfoFragment.kt @@ -0,0 +1,79 @@ +package net.tjado.usbgadget + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.os.Bundle +import android.graphics.Typeface +import android.text.Html +import android.view.View +import androidx.fragment.app.Fragment +import androidx.lifecycle.MutableLiveData +import net.tjado.usbgadget.databinding.FragmentDeviceInfoBinding +import net.tjado.usbgadget.databinding.RowDeviceInfoBinding +import java.util.* + + +class DeviceInfoFragment : Fragment() { + private var _binding: FragmentDeviceInfoBinding? = null + private val binding get() = _binding!! + + private var deviceData: MutableLiveData?>? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentDeviceInfoBinding.inflate(inflater, container, false) + val view = binding.root + + deviceData = MutableLiveData() + deviceData!!.value = TreeMap(DeviceInfoMapComparator()) + deviceData!!.observe(viewLifecycleOwner) { item: TreeMap? -> loadData(item) } + val shellAPi = GadgetShellApi() + shellAPi.updateDeviceInfo(deviceData!!) + + return view + } + + private fun loadData(deviceData: Map?) { + binding.listDeviceData.removeAllViews() + + val rowHead = RowDeviceInfoBinding.inflate(layoutInflater) + rowHead.name.text = "Kernel Config Parameter" + rowHead.name.setTypeface(null, Typeface.BOLD) + rowHead.value.text = "Value" + rowHead.value.setTypeface(null, Typeface.BOLD) + + binding.listDeviceData.addView(rowHead.root) + + for (entry in deviceData!!.entries) { + val rowEntry = RowDeviceInfoBinding.inflate(layoutInflater) + rowEntry.name.text = entry.key.uppercase(Locale.getDefault()) + + var value = entry.value + var color: String + when (value) { + "y" -> { + value = "Yes" + color = "#008000" + } + "n" -> { + value = "No" + color = "#ff0000" + } + "NOT_SET" -> { + value = "Not set" + color = "#ff0000" + } + else -> color = "#000000" + } + rowEntry.value.text = Html.fromHtml( + String.format("%s", color, value), + Html.FROM_HTML_MODE_LEGACY + ) + + binding.listDeviceData.addView(rowEntry.root) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/DeviceInfoMapComparator.java b/app/src/main/java/net/tjado/usbgadget/DeviceInfoMapComparator.java deleted file mode 100644 index e6aeae9..0000000 --- a/app/src/main/java/net/tjado/usbgadget/DeviceInfoMapComparator.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.tjado.usbgadget; - -import java.util.Comparator; -import java.util.TreeMap; - -public class DeviceInfoMapComparator implements Comparator { - - @Override - public int compare(String s1, String s2) { - if (s1.equalsIgnoreCase("KERNEL_VERSION")) { - return -1; - } else if (s2.equalsIgnoreCase("KERNEL_VERSION")) { - return 1; - } else { - return s1.compareTo(s2); - } - } -} diff --git a/app/src/main/java/net/tjado/usbgadget/DeviceInfoMapComparator.kt b/app/src/main/java/net/tjado/usbgadget/DeviceInfoMapComparator.kt new file mode 100644 index 0000000..4dada15 --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/DeviceInfoMapComparator.kt @@ -0,0 +1,15 @@ +package net.tjado.usbgadget + +import java.util.Comparator + +class DeviceInfoMapComparator : Comparator { + override fun compare(s1: String, s2: String): Int { + return if (s1.equals("KERNEL_VERSION", ignoreCase = true)) { + -1 + } else if (s2.equals("KERNEL_VERSION", ignoreCase = true)) { + 1 + } else { + s1.compareTo(s2) + } + } +} \ 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 deleted file mode 100644 index b389d49..0000000 --- a/app/src/main/java/net/tjado/usbgadget/ExecuteAsRootUtil.java +++ /dev/null @@ -1,154 +0,0 @@ -/** - * 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.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 (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 Pair execute(String command) { - return execute(new String[]{ command }); - } - - public static 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 sbOut = new StringBuffer(); - Scanner scanner = new Scanner(stdout); - while (scanner.hasNext()) { - sbOut.append(scanner.nextLine()); - sbOut.append(System.lineSeparator()); - } - - StringBuffer sbErr = new StringBuffer(); - Scanner scannerErr = new Scanner(stderr); - while (scannerErr.hasNext()) { - sbErr.append(scannerErr.nextLine()); - if (scannerErr.hasNext()) - sbErr.append(System.lineSeparator()); - } - - output = sbOut.toString(); - Log.d("ROOT (stdout)", output); - - String error = sbErr.toString(); - Log.d("ROOT (stderr)", error); - - retval = ! error.equals("Permission denied"); - } - } - catch (IOException | 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/ExecuteAsRootUtil.kt b/app/src/main/java/net/tjado/usbgadget/ExecuteAsRootUtil.kt new file mode 100644 index 0000000..efae1c2 --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/ExecuteAsRootUtil.kt @@ -0,0 +1,120 @@ +/** + * Authorizer + * + * Copyright 2016 by Tjado Mäcke @maecke.de> + * Licensed under GNU General Public License 3.0. + * + * @license GPL-3.0 //opensource.org/licenses/GPL-3.0> + */ +package net.tjado.usbgadget + +import androidx.core.util.Pair +import java.io.DataInputStream +import java.io.DataOutputStream +import java.io.IOException +import java.lang.Exception +import java.util.* + +/* + * Class by muzikant + * + * Reference: + * http://muzikant-android.blogspot.com/2011/02/how-to-get-root-access-and-execute.html + * http://stackoverflow.com/a/7102780 + */ +object ExecuteAsRootUtil { + fun canRunRootCommands(): Boolean { + var retval = false + val suProcess: Process + try { + suProcess = Runtime.getRuntime().exec("su") + val os = DataOutputStream(suProcess.outputStream) + val osRes = DataInputStream(suProcess.inputStream) + + if (null != os && null != osRes) { + // Getting the id of the current user to check if this is root + os.writeBytes("id\n") + os.flush() + val currUid = osRes.readLine() + val exitSu: Boolean + + if (null == currUid) { + retval = false + exitSu = false + Log.d("ROOT", "Can't get root access or denied by user") + } else if (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("\nexit\n") + os.flush() + } + } + } catch (e: Exception) { + // 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.javaClass.name + "] : " + e.message) + } + + return retval + } + + fun execute(command: String): Pair<*, *> { + return execute(arrayOf(command)) + } + + fun execute(commands: Array?): Pair<*, *> { + var retval = false + var output: String? = null + + try { + if (commands != null && commands.isNotEmpty()) { + val suProcess = Runtime.getRuntime().exec("su -") + val stdin = DataOutputStream(suProcess.outputStream) + val stdout = suProcess.inputStream + val stderr = suProcess.errorStream + for (cmd in commands) { + Log.d("ROOT", String.format("Execute command: %s", cmd)) + stdin.writeBytes(cmd) + stdin.flush() + } + stdin.writeBytes("\nexit\n") + stdin.flush() + stdin.close() + val sbOut = StringBuffer() + val scanner = Scanner(stdout) + while (scanner.hasNext()) { + sbOut.append(scanner.nextLine()) + sbOut.append(System.lineSeparator()) + } + val sbErr = StringBuffer() + val scannerErr = Scanner(stderr) + while (scannerErr.hasNext()) { + sbErr.append(scannerErr.nextLine()) + if (scannerErr.hasNext()) sbErr.append(System.lineSeparator()) + } + output = sbOut.toString() + Log.d("ROOT (stdout)", output) + val error = sbErr.toString() + Log.d("ROOT (stderr)", error) + retval = error != "Permission denied" + } + } catch (ex: IOException) { + Log.w("ROOT", "Can't get root access", ex) + } catch (ex: SecurityException) { + Log.w("ROOT", "Can't get root access", ex) + } catch (ex: Exception) { + Log.w("ROOT", "Error executing internal operation", ex) + } + + return 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 deleted file mode 100644 index 0da62ca..0000000 --- a/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.java +++ /dev/null @@ -1,108 +0,0 @@ -package net.tjado.usbgadget; - -import android.app.Activity; -import android.graphics.Color; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Switch; -import android.widget.TextView; - -import androidx.cardview.widget.CardView; -import androidx.recyclerview.widget.RecyclerView; - -import java.lang.ref.WeakReference; -import java.util.List; - -public class GadgetAdapter extends RecyclerView.Adapter { - private Activity context; - private LayoutInflater inflater; - private final GadgetAdapterClickInterface listener; - List data; - - public GadgetAdapter(Activity context, List data, GadgetAdapterClickInterface clickListener) { - this.context = context; - this.data = data; - listener = clickListener; - } - - // 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, listener); - } - - @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)); - - holder.status.setChecked(holder.gadget.isActivated()); - - String udc = holder.gadget.getValue("udc"); - holder.udc.setText(udc); - if (holder.gadget.isActivated()) { - holder.card.setCardBackgroundColor(Color.WHITE); - } else { - holder.card.setCardBackgroundColor(Color.LTGRAY); - } - - } - - // 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; - Switch status; - - private WeakReference listenerRef; - - public DefaultViewHolder(View itemView, GadgetAdapterClickInterface clickInterface) { - super(itemView); - card = itemView.findViewById(R.id.cv_gadget); - manufacturer = itemView.findViewById(R.id.tv_gadget_manufacturer); - product = itemView.findViewById(R.id.tv_gadget_product); - serialnumber = itemView.findViewById(R.id.tv_gadget_sn); - udc = itemView.findViewById(R.id.tv_gadget_udc); - path = itemView.findViewById(R.id.tv_gadget_path); - functions = itemView.findViewById(R.id.tv_gadget_functions); - status = itemView.findViewById(R.id.tv_gadget_status); - - status.setOnClickListener((buttonView) -> { - listenerRef.get().onStatusChange(gadget, status.isChecked()); - }); - - listenerRef = new WeakReference<>(clickInterface); - - itemView.setOnClickListener(view -> { - listenerRef.get().onGadgetClicked( gadget ); - }); - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.kt b/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.kt new file mode 100644 index 0000000..f8a37ca --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/GadgetAdapter.kt @@ -0,0 +1,76 @@ +package net.tjado.usbgadget + +import android.app.Activity +import android.graphics.Color +import android.text.Html +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import net.tjado.usbgadget.databinding.CardviewGadgetBinding +import java.lang.ref.WeakReference + + +class GadgetAdapter( + private val context: Activity, + private var data: List, + private val listener: GadgetAdapterClickInterface +) : RecyclerView.Adapter() { + private val inflater: LayoutInflater? = null + + private var _binding: CardviewGadgetBinding? = null + private val binding get() = _binding!! + + interface GadgetAdapterClickInterface { + fun onGadgetClicked(gadget: GadgetObject) + fun onStatusChange(gadget: GadgetObject, newStatus: Boolean) + } + + // Inflate the layout when viewholder created + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DefaultViewHolder { + _binding = CardviewGadgetBinding.inflate(LayoutInflater.from(parent.context), parent, false) + + return DefaultViewHolder(binding, listener) + } + + override fun onBindViewHolder(holder: DefaultViewHolder, position: Int) { + with(holder) { + gadget = data[position] + binding.tvGadgetManufacturer.text = gadget?.getValue("manufacturer") + binding.tvGadgetProduct.text = gadget?.getValue("product") + binding.tvGadgetSn.text = gadget?.getValue("serialnumber") + binding.tvGadgetPath.text = gadget?.getValue("gadget_path") + binding.tvGadgetFunctions.text = Html.fromHtml(holder.gadget?.formattedFunctions, Html.FROM_HTML_MODE_LEGACY) + binding.tvGadgetStatus.isChecked = gadget?.isActivated == true + + val udc = gadget!!.getValue("udc") + binding.tvGadgetUdc.text = udc + if (gadget?.isActivated == true) { + binding.cvGadget.setCardBackgroundColor(Color.WHITE) + } else { + binding.cvGadget.setCardBackgroundColor(Color.LTGRAY) + } + } + } + + override fun getItemCount(): Int { + return data.size + } + + // Static inner class to initialize the views of rows + inner class DefaultViewHolder(val binding: CardviewGadgetBinding, clickInterface: GadgetAdapterClickInterface) : RecyclerView.ViewHolder(binding.root) { + var gadget: GadgetObject? = null + private val listenerRef: WeakReference + + init { + listenerRef = WeakReference(clickInterface) + + binding.tvGadgetStatus.setOnClickListener { + gadget?.let { listenerRef.get()?.onStatusChange(it, binding.tvGadgetStatus.isChecked) } + } + + itemView.setOnClickListener { + gadget?.let { listenerRef.get()?.onGadgetClicked(it) } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetAdapterClickInterface.java b/app/src/main/java/net/tjado/usbgadget/GadgetAdapterClickInterface.java deleted file mode 100644 index 4141292..0000000 --- a/app/src/main/java/net/tjado/usbgadget/GadgetAdapterClickInterface.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.tjado.usbgadget; - -public interface GadgetAdapterClickInterface { - - void onGadgetClicked(GadgetObject gadget); - void onStatusChange( GadgetObject gadget, Boolean newStatus); -} diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetAssetProfiles.java b/app/src/main/java/net/tjado/usbgadget/GadgetAssetProfiles.java deleted file mode 100644 index 9f7699c..0000000 --- a/app/src/main/java/net/tjado/usbgadget/GadgetAssetProfiles.java +++ /dev/null @@ -1,88 +0,0 @@ -package net.tjado.usbgadget; - -import android.app.Application; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; -import java.util.Scanner; - -public class GadgetAssetProfiles { - - private Application app; - - public GadgetAssetProfiles(Application app) { - this.app = app; - } - - public String[] getAllGadgetProfiles() { - String [] gadgetProfileList; - - try { - gadgetProfileList = this.app.getAssets().list("usbGadgetProfiles/"); - } catch (IOException e) { - gadgetProfileList = null; - } - - return gadgetProfileList; - } - - public String[] getAllFunctionProfiles() { - String [] gadgetProfileList; - - try { - gadgetProfileList = this.app.getAssets().list("usbFunctionProfiles/"); - } catch (IOException e) { - gadgetProfileList = null; - } - - return gadgetProfileList; - } - - public String getProfileFromAsset(String profileFolder, String assetFile) { - try { - InputStream is = this.app.getAssets().open(String.format("%s/%s", profileFolder, assetFile)); - Scanner scanner = new Scanner(is); - - StringBuffer sb = new StringBuffer(); - while (scanner.hasNext()) { - sb.append(scanner.nextLine()).append("\n"); - } - - return sb.toString(); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - - public String getProfileGadget(String assetFile) { - return getProfileFromAsset("usbGadgetProfiles", assetFile); - } - - public String getProfileFunction(String assetFile, String gadgetPath) { - if (!GadgetShellApi.isValidGadgetPath(gadgetPath)) { - return null; - } - - Map tokens = new HashMap<>(); - tokens.put("gadgetPath", gadgetPath); - String profile = getProfileFromAsset("usbFunctionProfiles", assetFile); - - return parseAsset(profile, tokens); - } - - public String parseAsset(String profile, Map tokens) { - if (profile == null || profile.equals("")) { - return ""; - } - - if (tokens != null && tokens.size() > 0) { - for (Map.Entry token : tokens.entrySet()) { - profile = profile.replace("____" + token.getKey() + "____", token.getValue()); - } - } - - return profile; - } -} diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetAssetProfiles.kt b/app/src/main/java/net/tjado/usbgadget/GadgetAssetProfiles.kt new file mode 100644 index 0000000..c7af33c --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/GadgetAssetProfiles.kt @@ -0,0 +1,64 @@ +package net.tjado.usbgadget + +import android.app.Application +import android.text.TextUtils + + +class GadgetAssetProfiles(private val app: Application) { + + val allGadgetProfiles: Array + get() { + return app.assets.list("usbGadgetProfiles/") as Array + } + + val allFunctionProfiles: Array + get() { + return app.assets.list("usbFunctionProfiles/") as Array + } + + fun getProfileFromAsset(profileFolder: String, assetFile: String): String { + val inputStream = app.assets.open("$profileFolder/$assetFile") + return inputStream.bufferedReader().use { it.readText() } + } + + fun getProfileGadget(assetFile: String): String? { + return getProfileGadget(assetFile, assetFile) + } + + fun getProfileGadget(assetFile: String, gadgetName: String): String? { + if (TextUtils.isEmpty(gadgetName)) { + return null + } + + val tokens: MutableMap = HashMap() + tokens["gadgetName"] = gadgetName + val profile = getProfileFromAsset("usbGadgetProfiles", assetFile) + return parseAsset(profile, tokens) + } + + fun getProfileFunction(assetFile: String, gadgetPath: String): String? { + if (!GadgetShellApi.isValidGadgetPath(gadgetPath)) { + return null + } + + val tokens: MutableMap = HashMap() + tokens["gadgetPath"] = gadgetPath + val profile = getProfileFromAsset("usbFunctionProfiles", assetFile) + return parseAsset(profile, tokens) + } + + fun parseAsset(profile: String, tokens: Map): String { + if (TextUtils.isEmpty(profile)) { + return "" + } + + var parsedProfile = profile + if (tokens.isNotEmpty()) { + for ((key, value) in tokens) { + parsedProfile = parsedProfile.replace("____" + key + "____", value) + } + } + + return parsedProfile + } +} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetFragment.java b/app/src/main/java/net/tjado/usbgadget/GadgetFragment.java deleted file mode 100644 index 9e9c813..0000000 --- a/app/src/main/java/net/tjado/usbgadget/GadgetFragment.java +++ /dev/null @@ -1,125 +0,0 @@ -package net.tjado.usbgadget; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.Switch; -import android.widget.TextView; -import android.widget.ViewFlipper; - -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; - -import java.util.ArrayList; - -public class GadgetFragment extends Fragment { - - private GadgetViewModel gadgetViewModel; - private ViewFlipper rootFlipper; - private GadgetObject gadget; - private String[] functionProfileList; - private View view; - - public GadgetFragment(GadgetObject g) { - this.gadget = g; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - view = inflater.inflate(R.layout.fragment_gadget, container, false); - gadgetViewModel = new ViewModelProvider(getActivity()).get(GadgetViewModel.class); - - rootFlipper = view.findViewById(R.id.flipper); - - gadgetViewModel.getGadgets().observe(getViewLifecycleOwner(), item -> { - for (GadgetObject g : item) { - if (g.getValue("gadget_path").equals(this.gadget.getValue("gadget_path"))) { - this.gadget = g; - refreshData(); - } - } - }); - - gadgetViewModel.hasRootPermissions().observe(getViewLifecycleOwner(), item -> { - rootFlipper.setDisplayedChild(item ? 0 : 1); - }); - - GadgetAssetProfiles gap = new GadgetAssetProfiles(getActivity().getApplication()); - functionProfileList = gap.getAllFunctionProfiles(); - - Button addFunction = view.findViewById(R.id.btn_function_add); - addFunction.setOnClickListener(v -> { - final AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getContext()); - alertBuilder.setTitle("Add Function to Gadget"); - alertBuilder.setCancelable(true); - - alertBuilder.setItems(functionProfileList, (dialog, which) -> { - gadgetViewModel.loadFunctionProfile(gadget, functionProfileList[which]); - }); - - alertBuilder.show(); - }); - - - refreshData(); - - return view; - } - - private void refreshData() { - View v = view; - TextView manufacturer = v.findViewById(R.id.tv_gadget_manufacturer); - TextView product = v.findViewById(R.id.tv_gadget_product); - TextView serialnumber = v.findViewById(R.id.tv_gadget_sn); - TextView udc = v.findViewById(R.id.tv_gadget_udc); - TextView path = v.findViewById(R.id.tv_gadget_path); - Switch status = v.findViewById(R.id.tv_gadget_status); - - manufacturer.setText(gadget.getValue("manufacturer")); - product.setText(gadget.getValue("product")); - serialnumber.setText(gadget.getValue("serialnumber")); - path.setText(gadget.getValue("gadget_path")); - udc.setText(gadget.getValue("udc")); - - status.setChecked(gadget.isActivated()); - status.setOnClickListener((buttonView) -> { - if(status.isChecked()) { - gadget.activate(response -> { - gadgetViewModel.updateGadgetData(); - }); - } else { - gadget.deactivate(response -> { - gadgetViewModel.updateGadgetData(); - }); - } - }); - - LinearLayout list = v.findViewById(R.id.list_functons); - list.removeAllViews(); - - ArrayList functions = gadget.getFunctions(); - for(String functionName : functions) { - View vi = getLayoutInflater().inflate(R.layout.row_function, null); - - TextView tv = vi.findViewById(R.id.name); - tv.setText(functionName); - - Switch f_status = vi.findViewById(R.id.status); - f_status.setChecked( gadget.getActiveFunctions().contains(functionName) ); - f_status.setOnClickListener((buttonView) -> { - if (f_status.isChecked()){ - gadget.activateFunction(functionName, false); - } else { - gadget.deactivateFunction(functionName, true); - } - gadgetViewModel.reloadGadgetData(); - }); - - list.addView(vi); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/net/tjado/usbgadget/GadgetFragment.kt b/app/src/main/java/net/tjado/usbgadget/GadgetFragment.kt new file mode 100644 index 0000000..98be221 --- /dev/null +++ b/app/src/main/java/net/tjado/usbgadget/GadgetFragment.kt @@ -0,0 +1,101 @@ +package net.tjado.usbgadget + + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.os.Bundle + +import android.view.View +import android.widget.* +import androidx.appcompat.app.AlertDialog +import androidx.lifecycle.ViewModelProvider +import androidx.fragment.app.Fragment +import net.tjado.usbgadget.databinding.FragmentGadgetBinding +import net.tjado.usbgadget.databinding.RowFunctionBinding + + +class GadgetFragment(private var gadget: GadgetObject) : Fragment() { + private var _binding: FragmentGadgetBinding? = null + private val binding get() = _binding!! + + private lateinit var gadgetViewModel: GadgetViewModel + private lateinit var functionProfileList: Array + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentGadgetBinding.inflate(inflater, container, false) + val view = binding.root + + gadgetViewModel = ViewModelProvider(requireActivity()).get(GadgetViewModel::class.java) + gadgetViewModel.gadgets.observe(viewLifecycleOwner) { item: List -> + for (g in item) { + if (g.getValue("gadget_path") == gadget.getValue("gadget_path")) { + gadget = g + refreshData() + } + } + } + + gadgetViewModel.hasRootPermissions().observe(viewLifecycleOwner) { + item -> binding.flipper.displayedChild = if (item) 0 else 1 + } + + val assets = GadgetAssetProfiles(requireActivity().application) + functionProfileList = assets.allFunctionProfiles + + val addFunction = view.findViewById