Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save pavloshargan/d052d3605f6c16098d142d4d38248575 to your computer and use it in GitHub Desktop.
Save pavloshargan/d052d3605f6c16098d142d4d38248575 to your computer and use it in GitHub Desktop.
Android Gopro MTP notebooks
Manifest:
add this to permissions:
<uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-feature android:name="android.hardware.usb.host" />
add this to <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" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
object DataStore {
var usbConnection: UsbDeviceConnection? = null
var uris = mutableListOf<Uri>()
var mtpDevice: MtpDevice? = null
}
private const val ACTION_USB_PERMISSION = "com.example.myapp.USB_PERMISSION"
private fun requestUsbPermission(context: Context, device: UsbDevice) {
val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
val permissionIntent = PendingIntent.getBroadcast(
context,
0,
Intent(ACTION_USB_PERMISSION),
PendingIntent.FLAG_IMMUTABLE
)
val filter = IntentFilter(ACTION_USB_PERMISSION)
context.registerReceiver(permissionReceiver, filter, Context.RECEIVER_EXPORTED)
usbManager.requestPermission(device, permissionIntent)
}
private val permissionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let {
if (ACTION_USB_PERMISSION == it.action) {
synchronized(this) {
val device: UsbDevice? = it.getParcelableExtra(UsbManager.EXTRA_DEVICE)
if (it.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
device?.let {
val serialNumber = it.serialNumber
Toast.makeText(context, "Serial Number2: $serialNumber", Toast.LENGTH_SHORT).show()
}
} else {
println("Permission denied for device $device")
}
context?.unregisterReceiver(this) // Unregister the receiver after handling
}
}
}
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
println("OnNewIntent")
if (intent?.action == UsbManager.ACTION_USB_DEVICE_ATTACHED) {
val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
device?.let {
DatadogLogger.d("Requesting permission for device: ${device.deviceName}")
checkAndRequestUsbPermission(this, it)
}
}
}
fun checkAndRequestUsbPermission(context: Context, device: UsbDevice) {
val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
if (device.manufacturerName == "GoPro") {
if (!usbManager.hasPermission(device)) {
requestUsbPermission(context, device)
} else {
// Handle the case where permission is already granted
val serialNumber = device.serialNumber
DatadogLogger.d("device details ${device}")
DatadogLogger.d("GoPro $serialNumber connected")
Toast.makeText(context, "GoPro $serialNumber connected", Toast.LENGTH_SHORT).show()
accessGoProMtp(context, usbManager, device)
}
} else {
println("Device is not a GoPro: ${device.manufacturerName}")
}
}
fun accessGoProMtp(context: Context, usbManager: UsbManager, device: UsbDevice) {
DatadogLogger.d("Access gopro mtp")
val connection = usbManager.openDevice(device)
DataStore.usbConnection = connection
DatadogLogger.d("Access gopro mtp connection $connection")
if (connection != null) {
val mtpDevice = MtpDevice(device)
DataStore.mtpDevice = mtpDevice
val opened = mtpDevice.open(connection)
DatadogLogger.d("MTP Device opened: $opened")
if (opened) {
val maxWaitTime = 15000 // 15 seconds
val pollInterval = 500 // 0.5 seconds
var totalWaitTime = 0
var storageIds: IntArray? = null
while (totalWaitTime < maxWaitTime) {
storageIds = mtpDevice.storageIds
if (storageIds != null && storageIds.isNotEmpty()) {
break
} else {
DatadogLogger.d("No storage IDs found, retrying in $pollInterval ms")
Thread.sleep(pollInterval.toLong())
totalWaitTime += pollInterval
}
}
DatadogLogger.d("Access gopro mtp storageIds ${storageIds?.contentToString()}")
if (storageIds != null && storageIds.isNotEmpty()) {
val uris = mutableListOf<Uri>()
for (storageId in storageIds) {
DatadogLogger.d("Access gopro mtp storageId $storageId")
val storageInfo = mtpDevice.getStorageInfo(storageId)
DatadogLogger.d("Storage Info: $storageInfo")
// Start the recursive iteration from the root directory
retrieveMp4Files(mtpDevice, storageId, 0, uris)
// Optionally add a small delay between operations to avoid overwhelming the device
Thread.sleep(100) // 100ms delay
}
DataStore.uris = uris // Save URIs to Datastore
DatadogLogger.d("Saved URIs to Datastore: ${uris.size} files found")
} else {
DatadogLogger.d("No storage IDs found or storageIds is null after waiting")
}
// Optionally, you can close the device after operations
// mtpDevice.close()
} else {
DatadogLogger.d("Failed to open MTP Device")
Handler(Looper.getMainLooper()).post {
Toast.makeText(context, "Unable to open GoPro device connection", Toast.LENGTH_SHORT).show()
}
}
} else {
DatadogLogger.d("Connection is null, unable to open device")
Handler(Looper.getMainLooper()).post {
Toast.makeText(context, "Unable to open GoPro device connection", Toast.LENGTH_SHORT).show()
}
}
}
fun retrieveMp4Files(mtpDevice: MtpDevice, storageId: Int, directoryHandle: Int, uris: MutableList<Uri>) {
val files = mtpDevice.getObjectHandles(storageId, 0, directoryHandle) ?: return
for (fileHandle in files) {
val fileInfo = mtpDevice.getObjectInfo(fileHandle)
if (fileInfo?.format == MtpConstants.FORMAT_ASSOCIATION) {
// Recursively search subdirectories
retrieveMp4Files(mtpDevice, storageId, fileHandle, uris)
} else if (fileInfo?.name?.endsWith(".MP4", ignoreCase = true) == true) {
val fileUri = Uri.parse("mtp://${mtpDevice.deviceId}/$storageId/$fileHandle")
if (!uris.contains(fileUri)) { // Check if the URI is already in the list
println("mp4 filename ${fileInfo.name}")
uris.add(fileUri)
}
}
}
}
// logic to download mtp uri to local storage:
private fun processMtpUri(uri: Uri): Uri? {
val uriString = uri.toString()
DatadogLogger.d("Processing URI: $uriString")
// Split the URI and filter out empty segments to handle double slashes correctly
val segments = uriString.split("/").filter { it.isNotEmpty() }
if (segments.size < 4) { // We expect at least 4 parts: "mtp:", "<device-id>", "<storage-id>", "<file-handle>"
DatadogLogger.w("Invalid MTP URI format: $uri")
return null
}
val storageId = segments[2].toIntOrNull() // Adjusted to get the correct index
val fileHandle = segments[3].toIntOrNull()
if (storageId == null || fileHandle == null) {
DatadogLogger.w("Failed to parse storageId or fileHandle from URI: $uri")
return null
}
// Ensure that the MTP device and connection are properly initialized
var mtpDevice = DataStore.mtpDevice
var connection = DataStore.usbConnection
if (mtpDevice == null || connection == null) {
val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
val device = usbManager.deviceList.values.firstOrNull() // Adjust this logic based on your needs
if (device != null) {
connection = usbManager.openDevice(device)
DatadogLogger.d("Opened connection for MTP device: $connection")
if (connection != null) {
mtpDevice = MtpDevice(device)
DataStore.mtpDevice = mtpDevice
DataStore.usbConnection = connection
val opened = mtpDevice.open(connection)
if (!opened) {
DatadogLogger.w("Failed to open MTP device connection")
return null
}
} else {
DatadogLogger.w("Failed to open connection to USB device")
return null
}
} else {
DatadogLogger.w("No USB device found")
return null
}
}
// Copy the MTP file to the local storage
return copyMtpFileToLocal(context, mtpDevice, storageId, fileHandle)
}
fun copyMtpFileToLocal(context: Context, mtpDevice: MtpDevice, storageId: Int, fileHandle: Int): Uri? {
val fileInfo = mtpDevice.getObjectInfo(fileHandle)
val fileName = fileInfo?.name ?: return null
// Define your local storage path and create the output file
val localFile = File(context.getExternalFilesDir(null), fileName)
// Using importFile with the destination path
val success = mtpDevice.importFile(fileHandle, localFile.absolutePath)
return if (success) {
Uri.fromFile(localFile)
} else {
DatadogLogger.d("Failed to import file from MTP device")
null
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment