The most reliable way to connect an Arduino to an Android app built in Android Studio is via a USB OTG (On-The-Go) cable paired with the usb-serial-for-android library. While older tutorials rely on Bluetooth (HC-05), Android 12 and later severely restrict background Bluetooth serial access, making USB OTG the definitive standard for low-latency, rock-solid hardware communication. This guide walks through a complete bidirectional serial build, targeting the exact CH340G driver hurdles that stall most developers.
Time to Build: 45 minutes.
Project Spec Sheet & Hardware Requirements
Before opening Android Studio, verify your physical layer. The most common point of failure in Arduino-to-Android projects is using a charge-only USB cable that lacks the D+ and D- data lines.
| Component | Exact Variant / Specification | Notes & Pricing (2026) |
|---|---|---|
| Microcontroller | Arduino Nano V3 (ATmega328P) | Must have the CH340G USB-UART chip (not FTDI). ~$6.00. |
| OTG Adapter | USB-C to USB-A OTG | Must support data transfer. UGREEN or Anker branded. ~$7.00. |
| Cable | Mini-USB to USB-A Data Cable | Verify data capability by plugging into a PC first. ~$4.00. |
| Sensor | 10kΩ Linear Potentiometer | For analog input testing. ~$1.00. |
| Actuator | 5mm Red LED + 220Ω Resistor | For digital output testing. ~$0.50. |
Pin Mapping & Physical Wiring
This build uses a simple bidirectional loop: the phone reads the potentiometer and writes to the LED. Wire the Nano V3 exactly as mapped below.
| Arduino Nano V3 Pin | Component | Wire Color (Suggested) | Function |
|---|---|---|---|
| D2 | 220Ω Resistor → LED Anode | Orange | Digital Output (LED Control) |
| GND | LED Cathode & Pot Pin 3 | Black | Circuit Ground |
| 5V | Pot Pin 1 | Red | VCC for Potentiometer |
| A0 | Pot Wiper (Pin 2) | Blue | Analog Input (Sensor Data) |
Android Studio Configuration & The CH340G Filter
The Arduino Nano V3 uses the CH340G USB-to-serial chip. Android does not natively map this chip to a standard serial port. You must use the usb-serial-for-android library and explicitly declare the CH340 Vendor ID (VID) and Product ID (PID) in your manifest.
Step 1: Add the library to your build.gradle.kts (Module level):
dependencies {
implementation("com.github.mik3y:usb-serial-for-android:3.8.0")
}
Step 2: Create res/xml/device_filter.xml. The CH340G VID is 0x1A86 and PID is 0x7523. If you omit this, Android will silently ignore the Arduino when it is plugged in.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="6790" product-id="29987" /> <!-- 1A86:7523 in decimal -->
</resources>
Step 3: Add the intent filter to your AndroidManifest.xml inside your main <activity> tag:
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
Complete Firmware & Android Studio Code
Below is the complete, compilable code for both sides of the bridge. The Arduino firmware targets the Nano V3 (ATmega328P) at 9600 baud.
Arduino Firmware (C++)
// Target: Arduino Nano V3 (ATmega328P)
// Baud: 9600
const int LED_PIN = 2;
const int POT_PIN = A0;
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
pinMode(POT_PIN, INPUT);
}
void loop() {
// Read commands from Android
if (Serial.available() > 0) {
char cmd = Serial.read();
if (cmd == '1') digitalWrite(LED_PIN, HIGH);
else if (cmd == '0') digitalWrite(LED_PIN, LOW);
}
// Send sensor data to Android
int sensorVal = analogRead(POT_PIN);
Serial.println(sensorVal);
delay(100); // 10Hz update rate
}
Android Studio Kotlin Implementation
This snippet handles USB permission requests, port initialization, and bidirectional I/O with proper error handling. For full Android USB Host API context, refer to the official documentation.
import android.content.Context
import android.hardware.usb.UsbManager
import com.hoho.android.usbserial.driver.UsbSerialPort
import com.hoho.android.usbserial.driver.UsbSerialProber
import java.io.IOException
fun connectToArduino(context: Context): UsbSerialPort? {
val manager = context.getSystemService(Context.USB_SERVICE) as UsbManager
val availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager)
if (availableDrivers.isEmpty()) {
// Error: device_filter.xml is missing VID/PID or cable is charge-only
return null
}
val driver = availableDrivers[0]
val connection = manager.openDevice(driver.device)
if (connection == null) {
// Error: User denied USB permission prompt
return null
}
val port = driver.ports[0] // Most Arduinos only have 1 port
try {
port.open(connection)
port.setParameters(9600, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
// Turn on LED (Send '1')
port.write(byteArrayOf('1'.code.toByte()), 1000)
// Read Potentiometer (Blocking read for example)
val buffer = ByteArray(8)
val bytesRead = port.read(buffer, 1000)
if (bytesRead > 0) {
val sensorData = String(buffer, 0, bytesRead).trim()
// Process sensorData
}
} catch (e: IOException) {
// Handle physical disconnect during I/O
port.close()
return null
}
return port
}
Debugging: First Three Things to Check When It Fails
If your app crashes or fails to connect, look for this exact error string in your Android Studio Logcat:
java.lang.SecurityException: User has not given permission to device UsbDevice [name=/dev/bus/usb/001/002, vendor=6790, product=29987]
If you see that, or if findAllDrivers() returns an empty list, execute these three checks in order:
- Verify the Data Lines (Physical Layer): Swap the Mini-USB cable. 60% of OTG failures are caused by charge-only cables. Plug the Arduino into a PC; if the PC doesn't chime and assign a COM port, the cable lacks data wires.
- Check the Decimal VID/PID (Manifest Layer): The CH340G VID is
1A86(Hex) which is6790(Decimal). Android XML requires decimal values. If you put1A86in thevendor-idtag, Android parses it as decimal 1710, and the filter fails silently. - Clear App USB Permissions (OS Layer): If you previously clicked "Cancel" on the Android USB permission prompt and checked "Always open with this app", Android remembers the denial. Go to Android Settings → Apps → Your App → Set as Default → Clear Defaults, then force-stop the app and replug the OTG cable.
Extending and Simplifying the Build
How to Simplify: If dealing with CH340G drivers and XML filters is frustrating, switch your hardware to an Arduino Leonardo or Pro Micro (ATmega32U4). These boards feature native USB HID (Human Interface Device) capabilities. Android recognizes them natively as keyboards or gamepads without requiring the usb-serial-for-android library or custom VID/PID filters. You can read more about Arduino native USB in the official docs.
How to Extend: To add wireless telemetry without violating Android 12+ Bluetooth restrictions, replace the Nano with an ESP32-WROOM-32. Use the ESP32's BLE (Bluetooth Low Energy) GATT server. Android Studio's BLE API is fully supported in the background, allowing you to read the potentiometer via BLE characteristics while the phone screen is off.
Frequently Asked Questions
Can I use Bluetooth instead of USB OTG for Arduino Android Studio projects?
You can, but it is no longer recommended for new builds. Classic Bluetooth (using the HC-05/HC-06 modules) relies on the BluetoothSocket API, which Android 12 and later heavily restrict for background operation to prevent tracking. If you must go wireless, use an ESP32 with BLE (Bluetooth Low Energy), which is fully supported for background sensor polling in modern Android Studio environments.
Why does Android Studio not recognize my Arduino Uno R3?
The official Arduino Uno R3 uses the ATmega16U2 USB-to-Serial chip, which has a different VID/PID (VID: 0x2341, PID: 0x0043) than the CH340G found on clone Nanos. If your Uno isn't recognized, you must add the Uno's decimal VID (9025) and PID (67) to your device_filter.xml file alongside the CH340G values.
How do I keep the USB serial connection alive when the Android screen turns off?
Android aggressively kills background USB connections to save battery. To maintain the link, you must run your serial read/write loop inside an Android Foreground Service with a persistent notification. Furthermore, you need to acquire a PARTIAL_WAKE_LOCK via the PowerManager to prevent the CPU from sleeping, which would halt the USB host controller.






