Skip to content

Instantly share code, notes, and snippets.

@sr105
Created March 6, 2015 15:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sr105/345e2096f158e3195c0f to your computer and use it in GitHub Desktop.
Save sr105/345e2096f158e3195c0f to your computer and use it in GitHub Desktop.
Class that discovers USB mass storage devices on Android. See getAll() as a starting point.
package com.rdm.storagetests;
import com.rdm.storagetests.StorageHelper.StorageVolume;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/*
* Create a list of USBDevice objects
* - sysfs path
* - device number (e.g. 8:13)
* - size (in Bytes)
* - readonly (get from parent)
* - removable (get from parent)
*
* optional:
* - manufacturer
* - device name
* - file system label
*
*/
/*
* - How will we match these to StorageVolumes?
* - volume size
* - vold device path (in /proc/mounts, device name is /dev/block/vold/8:13)
* The 8:13 is the device number.
*
* - might be other ways using /proc/diskstats or some other /proc file
*/
public class UsbDevice
{
private static List<UsbDevice> sDeviceCache;
public final File mPath;
public String mDeviceNumber;
public long mSize;
public boolean mReadonly;
public boolean mRemovable;
public String mVendor;
public String mModel;
public StorageVolume mVolume;
public UsbDevice(File sysfsPath)
{
this(sysfsPath, null);
}
public UsbDevice(File sysfsPath, UsbDevice parent)
{
mPath = sysfsPath;
if (parent == null)
{
mReadonly = "1".equals(readFirstLineFromFile(new File(mPath, "ro")));
mRemovable = "1".equals(readFirstLineFromFile(new File(mPath, "removable")));
mVendor = readFirstLineFromFile(new File(mPath, "../../vendor"));
mModel = readFirstLineFromFile(new File(mPath, "../../model"));
}
else
{
mReadonly = parent.mReadonly;
mRemovable = parent.mRemovable;
mVendor = parent.mVendor;
mModel = parent.mModel;
}
mDeviceNumber = readFirstLineFromFile(new File(mPath, "dev"));
try
{
mSize = Long.valueOf(readFirstLineFromFile(new File(mPath, "size"))) * 512;
}
catch (NumberFormatException e)
{
// should never happen
mSize = 0;
}
}
public String toString()
{
return "[UsbDevice "
+ mPath.getName()
+ " " + mDeviceNumber
+ " " + mVendor
+ " " + mModel
+ (mReadonly ? " ro" : " rw")
+ (mRemovable ? " removable" : " not_removable")
+ " " + StorageUnit.humanReadable(mSize)
+ " " + (mVolume == null ? "not_mounted" : mVolume.getPath())
+ "]";
}
@SuppressWarnings("unused")
public static List<UsbDevice> getAll()
{
boolean reload;
return getAll(reload = false);
}
public static List<UsbDevice> getAll(boolean reload)
{
if (!reload && sDeviceCache != null)
{
return sDeviceCache;
}
// patterns must match the entire string, so adding ^ or $ is meaningless
String[] patterns = new String[] {"usb[-_]storage", "[0-9].*", "host.*", "target.*", "[0-9][0-9:]+", "block", ".*"};
List<File> dirs = new ArrayList<>();
dirs.add(new File("/sys/bus/usb/drivers"));
for (String pattern : patterns)
{
dirs = findNamedDirs(pattern, dirs);
}
List<UsbDevice> usbBlockDevices = new ArrayList<>();
for (File disk : dirs)
{
UsbDevice parent = new UsbDevice(disk);
usbBlockDevices.add(parent);
for (File part : disk.listFiles(dirNameFilter("^" + disk.getName() + "[0-9]+")))
{
usbBlockDevices.add(new UsbDevice(part, parent));
}
}
synchronizeWithStorageVolumes(usbBlockDevices);
sDeviceCache = usbBlockDevices;
return sDeviceCache;
}
private static void synchronizeWithStorageVolumes(List<UsbDevice> usbDevices)
{
List<StorageVolume> volumes = StorageHelper.getStorages(true);
for (UsbDevice u : usbDevices)
{
for (StorageVolume v : volumes)
{
// Device Numbers are very reliable, use them first
if (v.device.equals("/dev/block/vold/" + u.mDeviceNumber))
{
v.setUsbDevice(u);
u.mVolume = v;
break;
}
// Next, try looking for the device name as a component in the volume paths
for (String path : v.getPaths())
{
if (StorageHelper.pathContainsDir(path, new String[] {u.mPath.getName()}))
{
v.setUsbDevice(u);
u.mVolume = v;
break;
}
}
if (v.getUsbDevice() == u)
{
break;
}
/*
* TODO: this is not reliable. What if I insert two identically sized USB drives?
* TODO: need a better way to sync up when vold is not in use
* Ideas:
* - Quickly check to see if all of the volume sizes are unique?
* - /proc/diskstats?
* - we could perform a *lot* of I/O and watch the stats :)
* - busybox stat command to get the device number? (not always available)
*
*/
if (v.file.getTotalSpace() == u.mSize)
{
v.setUsbDevice(u);
u.mVolume = v;
break;
}
}
}
}
/**
* ******* utility methods *********
*/
private String readFirstLineFromFile(File file)
{
try
{
return new BufferedReader(new FileReader(file)).readLine();
}
catch (IOException e)
{
// If the file is missing or unavailable, we return an empty string
}
return "";
}
private static FileFilter dirNameFilter(final String pattern)
{
return new FileFilter()
{
@Override
public boolean accept(File pathname)
{
return pathname.isDirectory() && pathname.getName().matches(pattern);
}
};
}
private static List<File> findNamedDirs(final String pattern, List<File> dirs)
{
List<File> matchingDirs = new ArrayList<>();
for (File d : dirs)
{
matchingDirs.addAll(Arrays.asList(d.listFiles(dirNameFilter(pattern))));
}
return matchingDirs;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment