diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..87bee47
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,13 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(./gradlew -h)",
+ "Bash(chmod +x ./gradlew)",
+ "Bash(./gradlew tasks *)",
+ "Bash(./gradlew assembleDebug)",
+ "Bash(./gradlew clean *)",
+ "Bash(./gradlew assembleRelease)",
+ "Bash(awk '{print $NF}')"
+ ]
+ }
+}
diff --git a/README.md b/README.md
index 8a881bd..c1315c2 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,7 @@
Simple application to set the USB DAC volume on UNROOTED Android Devices
## This is a fork of the original app
-I improved it so it detects when you connect a usb dac, automatically unlocks the volume and exits. You need to enable recording permission (is necessary on latest android versions) and enable auto exiting.
-Works flawlessly with the Apple USB to Jack adapter :)
-Go to releases for the latest alpha version.
+I improved it so it only open the app when apple dongle detected and have options for auto exit the app (thanks to [polkaulfield](https://github.com/polkaulfield/usbDacVolumeAndroid))
# Why
The vast majority of high end android smartphones sold today do not contain a 3.5mm headphone jack. To remedy this, most people will use a USB-C to 3.5mm DAC. However, there are certian DACs (namely the Apple USB- C DAC) that do not default to their highest output setting. On most platforms (Windows, Linux, macOS, iOS), this isn't an issue because they either force the highest DAC volume and adjust their own mixer volume, or they control the DAC volume explicitly. Android does neither, so as a result, some DACs are quieter than they possibly can be.
@@ -40,3 +38,4 @@ You will temporarily lose sound during the setting of volume, however you should
# Special thanks to:
[ibaiGorordo](https://github.com/ibaiGorordo/libusbAndroidTest) for most of the code :>
+[polkaulfield](https://github.com/polkaulfield/usbDacVolumeAndroid) for auto exit code :>
diff --git a/app/build.gradle b/app/build.gradle
index 14d651c..8808c67 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -3,26 +3,30 @@ plugins {
}
android {
- compileSdk 30
+ compileSdk 36
+ ndkVersion "28.2.13676358"
defaultConfig {
applicationId 'com.example.libusbAndroidTest'
minSdk 28
- targetSdk 30
- versionCode 1
- versionName "1.0"
+ targetSdk 36
+ versionCode 2
+ versionName "1.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags '-std=c++17'
+ arguments '-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON', '-DCMAKE_ANDROID_NATIVE_SUPPORT_FLEXIBLE_PAGE_SIZES=ON'
}
}
}
buildTypes {
release {
- minifyEnabled false
+ minifyEnabled true
+ shrinkResources true
+ signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
@@ -39,10 +43,15 @@ android {
viewBinding true
}
namespace 'com.example.libusbAndroidTest'
+ packagingOptions {
+ jniLibs {
+ useLegacyPackaging = false
+ }
+ }
}
dependencies {
-
+ implementation 'androidx.core:core:1.9.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 802f2f0..b902803 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,7 +3,7 @@
-
+
-
diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt
index 8f5bae7..0b6a2f9 100644
--- a/app/src/main/cpp/CMakeLists.txt
+++ b/app/src/main/cpp/CMakeLists.txt
@@ -9,7 +9,6 @@ cmake_minimum_required(VERSION 3.10.2)
project("libusbAndroidTest")
-
###### libusb ######
set(LIBUSB_DIR ${CMAKE_SOURCE_DIR}/libusb)
set(LIBUSB_SOURCES ${LIBUSB_DIR}/libusb/core.c
diff --git a/app/src/main/cpp/libusb b/app/src/main/cpp/libusb
index 1a90627..b54814c 160000
--- a/app/src/main/cpp/libusb
+++ b/app/src/main/cpp/libusb
@@ -1 +1 @@
-Subproject commit 1a906274a66dd58bf81836db1306902d4a7dc185
+Subproject commit b54814c141f6fb68183a38641506e0ec51693124
diff --git a/app/src/main/java/com/example/libusbAndroidTest/MainActivity.java b/app/src/main/java/com/example/libusbAndroidTest/MainActivity.java
index ed3ef9f..3a18ccb 100644
--- a/app/src/main/java/com/example/libusbAndroidTest/MainActivity.java
+++ b/app/src/main/java/com/example/libusbAndroidTest/MainActivity.java
@@ -1,17 +1,11 @@
package com.example.libusbAndroidTest;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-
-import android.Manifest;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
import android.graphics.Color;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
@@ -21,12 +15,15 @@
import android.os.Bundle;
import android.util.Log;
import android.view.View;
-import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
import com.example.libusbAndroidTest.databinding.ActivityMainBinding;
import java.util.HashMap;
@@ -41,20 +38,26 @@ public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private UsbManager usbManager;
- private TextView tv;
+ private TextView tvDeviceName;
private EditText volInput;
private CheckBox autoApply;
private CheckBox quitAfterApply;
- private Button recordPermissionButton;
-
private int deviceDescriptor = -1;
private static String deviceName;
+ // in Hex 0x5AC is apple vendor id
+ private static final String APPLE_VENDOR_ID = "1452";
+
+ // in Hex 0x110A is apple dongle product id
+ private static final String APPLE_DONGLE_PRODUCT_ID = "4362";
+
+
private static final String TAG = "USB DAC Volume Adjustment" ;
private static final String ACTION_USB_PERMISSION =
"com.android.example.USB_PERMISSION";
+ private static final int RECORD_AUDIO_PERMISSION_CODE = 1;
private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
@@ -77,8 +80,22 @@ public void onReceive(Context context, Intent intent) {
}
};
+ private boolean isAppleDongle(UsbDevice device){
+ return device.getVendorId() == Integer.parseInt(APPLE_VENDOR_ID) && device.getProductId() == Integer.parseInt(APPLE_DONGLE_PRODUCT_ID);
+ }
+
protected void connectDevice(UsbDevice device)
{
+ Log.d("UsbDevice", "device: " + device.getDeviceName() + " " + device.getVendorId() + " " + device.getProductId());
+ Log.d("UsbDevice",
+ "class: " + device.getDeviceClass() + " " + device.getDeviceSubclass() + " " + device.getDeviceProtocol());
+ boolean isAppleDongle = isAppleDongle(device);
+
+ String vendorId = "0x" + Integer.toHexString(device.getVendorId()).toUpperCase();
+ String productId = "0x" + Integer.toHexString(device.getProductId()).toUpperCase();
+ String deviceVendorIdAndProductId = isAppleDongle ? "Apple Dongle Detected!" :
+ "vendorId: " + vendorId + " productId:" + productId;
+ tvDeviceName.setText(deviceVendorIdAndProductId);
UsbInterface intf = device.getInterface(0);
UsbEndpoint endpoint = intf.getEndpoint(0);
UsbDeviceConnection connection = usbManager.openDevice(device);
@@ -88,17 +105,20 @@ protected void connectDevice(UsbDevice device)
deviceName = initializeNativeDevice(fileDescriptor);
deviceDescriptor = fileDescriptor;
- if(autoApply.isChecked() && quitAfterApply.isChecked()){
+ if(autoApply.isChecked()){
setDeviceVolume(fileDescriptor);
- finishAndRemoveTask();
+ if(quitAfterApply.isChecked()){
+ finishAndRemoveTask();
+ }
}
}
protected void checkUsbDevices()
{
- PendingIntent permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
+ PendingIntent permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);
HashMap deviceList = usbManager.getDeviceList();
+
for (UsbDevice device : deviceList.values()) {
if(usbManager.hasPermission(device))
{
@@ -116,38 +136,54 @@ protected void onCreate(Bundle savedInstanceState) {
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
- tv = binding.sampleText;
+ tvDeviceName = binding.deviceName;
volInput = binding.volume;
autoApply = binding.autoApply;
quitAfterApply = binding.quitAfterApply;
- recordPermissionButton = binding.recordPermissionButton;
SharedPreferences settings = getApplicationContext().getSharedPreferences("myPrefs", 0);
- volInput.setText(settings.getString("volume", "0000"));
+ volInput.setText(settings.getString("volume", "007f"));
autoApply.setChecked(settings.getBoolean("autoApply", false));
quitAfterApply.setChecked(settings.getBoolean("quitAfterApply", false));
- if (ContextCompat.checkSelfPermission(this,
- Manifest.permission.RECORD_AUDIO)
- == PackageManager.PERMISSION_GRANTED) {
- recordPermissionButton.setEnabled(false);
- }
-
// Initialize UsbManager
usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
// Initialize the receiver for getting the device permission
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
- registerReceiver(usbReceiver, filter);
+ ContextCompat.registerReceiver(this, usbReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);
checkUsbDevices();
+
+ requestRecordAudioPermission();
+ }
+
+ private void requestRecordAudioPermission() {
+ if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO)
+ != android.content.pm.PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this,
+ new String[]{android.Manifest.permission.RECORD_AUDIO},
+ RECORD_AUDIO_PERMISSION_CODE);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == RECORD_AUDIO_PERMISSION_CODE) {
+ if (grantResults.length > 0 && grantResults[0] == android.content.pm.PackageManager.PERMISSION_GRANTED) {
+ Log.d(TAG, "RECORD_AUDIO permission granted");
+ } else {
+ Log.d(TAG, "RECORD_AUDIO permission denied");
+ }
+ }
}
public void applyButtonPressed(View view){
String volume = volInput.getText().toString();
if(deviceDescriptor < 0){
- tv.setBackgroundColor(Color.RED);
+ tvDeviceName.setBackgroundColor(Color.RED);
return;
}
@@ -208,23 +244,4 @@ public void setDeviceVolume(int fileDescriptor){
setDeviceVolume(fileDescriptor, hexStringToByteArray(volume));
Toast.makeText(getApplicationContext(), "Volume set for DAC!", Toast.LENGTH_LONG).show();
}
-
- // Trigger the permission popup
-
- private final int CALLBACK_PERMISSION = 1;
- public void recordPermissionButtonPressed(View view) {
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, CALLBACK_PERMISSION);
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- if (requestCode == CALLBACK_PERMISSION) {
- if (grantResults.length > 0
- && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- recordPermissionButton.setEnabled(false);
- return;
- }
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 14ee2dc..9f58b81 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -6,57 +6,15 @@
android:layout_height="match_parent"
tools:context=".MainActivity">
-
-
-
-
-
-
@@ -64,27 +22,46 @@
android:id="@+id/auto_apply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="129dp"
- android:layout_marginTop="24dp"
- android:layout_marginEnd="129dp"
+ android:layout_marginTop="@dimen/dp_8"
android:onClick="autoApplyCheckboxPressed"
- android:text="Auto Apply on Start"
+ android:text="@string/auto_apply_on_start"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.0"
- app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintStart_toStartOf="@id/volume"
app:layout_constraintTop_toBottomOf="@+id/volume" />
+
+
+ app:layout_constraintTop_toBottomOf="@+id/quit_after_apply" />
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..bc05d68
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 8dp
+ 16dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0d9846d..a5954f6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,9 @@
- usbDacVolumeAndroid
+ Apple Dongle Volume Fixer
+ Auto apply on start
+ Quit app after applying volume
+ No USB device
+ Request record permission
+ Apply
+ VOLUME (HEX)
\ No newline at end of file
diff --git a/app/src/main/res/xml/device_filter.xml b/app/src/main/res/xml/device_filter.xml
index a0087c5..81abba9 100644
--- a/app/src/main/res/xml/device_filter.xml
+++ b/app/src/main/res/xml/device_filter.xml
@@ -1,5 +1,4 @@
-
-
+
diff --git a/build.gradle b/build.gradle
index aac62e9..24cfa52 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,10 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.7.3'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
+ classpath 'com.android.tools.build:gradle:8.11.0'
}
}
diff --git a/gradle.properties b/gradle.properties
index 193a5b2..bfeae71 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -17,6 +17,5 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
-android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 19cfad9..a5826ea 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Wed Jul 02 10:30:55 SGT 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755