Created
          August 8, 2024 14:18 
        
      - 
      
 - 
        
Save pavloshargan/d052d3605f6c16098d142d4d38248575 to your computer and use it in GitHub Desktop.  
    Android Gopro MTP notebooks
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | 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