package com.felhr.usbmassstorageforandroid.filesystems.fat32; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import com.felhr.usbmassstorageforandroid.filesystems.MasterBootRecord; import com.felhr.usbmassstorageforandroid.filesystems.Partition; import com.felhr.usbmassstorageforandroid.scsi.SCSICommunicator; import com.felhr.usbmassstorageforandroid.scsi.SCSIInterface; import com.felhr.usbmassstorageforandroid.scsi.SCSIRead10Response; import com.felhr.usbmassstorageforandroid.scsi.SCSIResponse; import com.felhr.usbmassstorageforandroid.utilities.UnsignedUtil; import java.io.DataInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.concurrent.atomic.AtomicBoolean; /** * Created by Felipe Herranz([email protected]) on 20/2/15. */ public class FATHandler { private static final int LOAD_CACHE = 0; private static final int FIND_EMPTY_CLUSTERCHAIN = 1; private SCSICommunicator comm; private final Object monitor; private final Object cacheMonitor; private SCSIResponse currentResponse; private boolean currentStatus; private AtomicBoolean waiting; private MasterBootRecord mbr; //Mounted Partition private Partition partition; private ReservedRegion reservedRegion; private Path path; //CacheThread vars private Handler wHandler; private FAT32Cache cache; private CacheThread cacheThread; public FATHandler(UsbDevice mDevice, UsbDeviceConnection mConnection) { this.comm = new SCSICommunicator(mDevice, mConnection); this.monitor = new Object(); this.cacheMonitor = new Object(); this.cache = new FAT32Cache(); this.path = new Path(); this.waiting = new AtomicBoolean(true); this.cacheThread = new CacheThread(); } public boolean mount(int partitionIndex, int cacheMode) { boolean isOpen = comm.openSCSICommunicator(scsiInterface); if(!isOpen) return false; testUnitReady(); if(currentStatus) mbr = getMbr(); else return false; if(mbr.getPartitions().length >= partitionIndex + 1) { partition = mbr.getPartitions()[partitionIndex]; if(!partition.isFAT32()) return false; reservedRegion = getReservedRegion(); List<Long> clustersRoot = getClusterChain(2); byte[] data = readClusters(clustersRoot); path.setDirectoryContent(getFileEntries(data)); if(cacheMode != 0) { cacheThread.start(); while(wHandler == null){} wHandler.obtainMessage(LOAD_CACHE, cacheMode, 0).sendToTarget(); cacheThread.waitTillCaching(); } return true; }else { return false; } } public boolean unMount() { return preventAllowRemoval(false); } public List<FileEntry> list() { return path.getDirectoryContent(); } public List<FileEntry> getPath() { return path.getAbsolutePath(); } public boolean changeDir(String directoryName) { Iterator<FileEntry> e = path.getDirectoryContent().iterator(); while(e.hasNext()) { FileEntry entry = e.next(); String name; if(!entry.getLongName().equals("")) name = entry.getLongName(); else name = entry.getShortName(); if(name.equalsIgnoreCase(directoryName) && entry.isDirectory()) { path.addDirectory(entry); long firstCluster = entry.getFirstCluster(); List<Long> clusterChain = getClusterChain(firstCluster); byte[] data = readClusters(clusterChain); path.clearDirectoryContent(); path.setDirectoryContent(getFileEntries(data)); return true; } } return false; } public boolean changeDirBack() { FileEntry currentEntry = path.getCurrentDirectory(); if(currentEntry != null) { path.clearDirectoryContent(); if(path.deleteLastDir()) { if(!path.isRoot()) { FileEntry backEntry = path.getCurrentDirectory(); long firstCluster = backEntry.getFirstCluster(); List<Long> clusterChain = getClusterChain(firstCluster); byte[] data = readClusters(clusterChain); path.clearDirectoryContent(); path.setDirectoryContent(getFileEntries(data)); return true; }else { List<Long> clustersRoot = getClusterChain(2); byte[] data = readClusters(clustersRoot); path.clearDirectoryContent(); path.setDirectoryContent(getFileEntries(data)); return true; } }else { //You are in root directory, no back dir to go!! return false; } }else { return false; } } /* Filename: fileName should be a LFN or a short file name + extension */ public byte[] readFile(String fileName) { Iterator<FileEntry> e = path.getDirectoryContent().iterator(); while(e.hasNext()) { FileEntry entry = e.next(); String name; if(!entry.getLongName().equals("")) name = entry.getLongName(); else if(!entry.getFileExtension().equals("")) name = entry.getShortName() + "." + entry.getFileExtension(); else name = entry.getShortName(); if(name.equalsIgnoreCase(fileName) && !entry.isDirectory()) { long firstCluster = entry.getFirstCluster(); if(firstCluster != 0) // File size is not 0 { List<Long> clusterChain = getClusterChain(firstCluster); byte[] data = readClusters(clusterChain); return Arrays.copyOf(data, (int) entry.getSize()); }else { return new byte[0]; } } } return null; } public boolean writeNewFile(java.io.File file) { String fileName = file.getName(); boolean isReadOnly = !file.canWrite(); boolean isHidden = file.isHidden(); boolean isDirectory = file.isDirectory(); long lastModified = file.lastModified(); if(!file.isDirectory()) { byte[] fileData = new byte[(int) file.length()]; try { DataInputStream dis = new DataInputStream(new FileInputStream(file)); try { dis.readFully(fileData); } catch (IOException e) { e.printStackTrace(); return false; } }catch(FileNotFoundException e) { e.printStackTrace(); return false; } return writeNewFile(fileName, fileData, isReadOnly, isHidden, isDirectory, lastModified); }else { boolean result = writeNewFile(fileName, null, isReadOnly, isHidden, isDirectory, lastModified); changeDir(fileName); if(!result) return false; java.io.File[] files = file.listFiles(); for(int i=0;i<=files.length-1;i++) { java.io.File subFile = files[i]; writeNewFile(subFile); } changeDirBack(); } return true; } /* Write a file in the current Path */ public boolean writeNewFile(String fileName, byte[] data, boolean isReadOnly, boolean isHidden, boolean isDirectory, long lastModified) { // Get clusterchain of the current folder List<Long> clusterChain; if(!path.isRoot()) { FileEntry dir = path.getCurrentDirectory(); clusterChain = getClusterChain(dir.getFirstCluster()); }else { clusterChain = getClusterChain(2); } // LFN entries required + 1 fileEntry + 1 more if fileName.length() % 11 != 0 int fileEntriesRequired = fileName.length() / 11 + 1; if(fileName.length() % 11 != 0) fileEntriesRequired += 1; // There is no space for a new entry. resize the folder. if(path.getFreeEntries() < fileEntriesRequired) { long lastCluster = clusterChain.get(clusterChain.size()-1); long newLastCluster = resizeClusterChain(lastCluster); int freeEntriesNewCluster = (int) (reservedRegion.getSectorsPerCluster() * reservedRegion.getBytesPerSector()) / 32; path.setFreeEntries(freeEntriesNewCluster + path.getFreeEntries()); if(newLastCluster != 0) clusterChain.add(newLastCluster); else return false; } // get dir fileEntries and obtain a valid cluster chain for the new file byte[] dirData = readClusters(clusterChain); List<Long> fileClusterChain = new ArrayList<Long>(); if(!isDirectory) { int clusters; if(data.length != 0) { clusters = (int) (data.length / (reservedRegion.getSectorsPerCluster() * reservedRegion.getBytesPerSector())); if(data.length % (reservedRegion.getSectorsPerCluster() * reservedRegion.getBytesPerSector()) != 0) clusters += 1; }else { clusters = 1; } fileClusterChain = setClusterChain(clusters, true); if(fileClusterChain == null) // It was no possible to get a clusterchain return false; }else { // It is a dir, it just needs one cluster at least at this moment fileClusterChain = setClusterChain(1, true); if(fileClusterChain == null) // It was no possible to get a clusterchain return false; } // get a raw FileEntry long size; if(!isDirectory) size = data.length; else size = 0; FileEntry newEntry = FileEntry.getEntry( fileName, fileClusterChain.get(0), size, path.getDirectoryContent() , isReadOnly, isHidden, isDirectory, lastModified); byte[] rawFileEntry = newEntry.getRawFileEntry(); // Write fileEntry in dir clusters int index = getFirstFileEntryIndex(dirData); System.arraycopy(rawFileEntry, 0, dirData, index, rawFileEntry.length); // Write file entry writeClusters(clusterChain, dirData); // update free entries path.setFreeEntries(path.getFreeEntries() - fileEntriesRequired); // Write file only if file entry is not a directory and the size is not 0 if(!isDirectory && size != 0) { boolean result = writeClusters(fileClusterChain, data); if(result) { path.addFileEntry(newEntry); } return result; }else if(isDirectory) { // Add . and .. entries FileEntry dotEntry = FileEntry.getEntry( ".", fileClusterChain.get(0), 0, null , false, false, true, lastModified); FileEntry dotDotEntry = FileEntry.getEntry( "..", clusterChain.get(0), 0, null , false, false, true, lastModified); byte[] dotEntryRaw = dotEntry.getRawFileEntry(); byte[] dotDotEntryRaw = dotDotEntry.getRawFileEntry(); byte[] dotEntriesRaw = new byte[64]; System.arraycopy(dotEntryRaw, 0, dotEntriesRaw, 0, 32); System.arraycopy(dotDotEntryRaw, 0, dotEntriesRaw, 32, 32); writeClusters(fileClusterChain, dotEntriesRaw); } path.addFileEntry(newEntry); return true; } public boolean deleteFile(String fileName) { Iterator<FileEntry> e = path.getDirectoryContent().iterator(); int i = 0; while(e.hasNext()) { FileEntry entry = e.next(); String name; if(!entry.getLongName().equals("")) name = entry.getLongName(); else name = entry.getShortName(); if(name.equalsIgnoreCase(fileName)) { long firstCluster; if(!path.isRoot()) firstCluster = path.getCurrentDirectory().getFirstCluster(); else firstCluster = 2; List<Long> clusterChainFolder = getClusterChain(firstCluster); List<Long> clusterChainFile = getClusterChain(entry.getFirstCluster()); // if no elements in clusterchain get out byte[] data = readClusters(clusterChainFolder); boolean result = setEntryToErased(data, i, entry.getLongName()); if(result) writeClusters(clusterChainFolder, data); else return false; // Delete the FileEntry object path.deleteFileEntry(i); return deleteClusterChain(clusterChainFile); } i++; } return false; } private void testUnitReady() { comm.testUnitReady(); waitTillNotification(); } private MasterBootRecord getMbr() { byte[] data = readBytes(0, 1); if(data != null) return MasterBootRecord.parseMbr(data); else return null; } /* Optimization required: if next cluster pointer is the next sector there is no need to use readBytes again. */ private List<Long> getClusterChain(long cluster) { boolean keepSearching = true; List<Long> clusterChain = new ArrayList<Long>(); clusterChain.add(cluster); while(keepSearching) { long lbaCluster = getEntryLBA(cluster); byte[] sector = readBytes(lbaCluster, 1); int entrySectorIndex = getEntrySectorIndex(cluster); int[] indexes = getRealIndexes(entrySectorIndex); cluster = UnsignedUtil.convertBytes2Long(sector[indexes[3]], sector[indexes[2]], sector[indexes[1]], sector[indexes[0]]); if(cluster != 0xfffffff) { clusterChain.add(cluster); }else { keepSearching = false; } } return clusterChain; } /* Set a clusterchain on the FAT Return null if is not possible to get clusterchain */ private List<Long> setClusterChain(int clusters, boolean forceCache) { List<Long> clusterChainList = new ArrayList<Long>(); long[] lbaChain = new long[clusters]; int[] entries = new int[clusters]; // 0-127 range int i = 0; // index for clusterchain long lbaFatStart = getEntryLBA(0); long lbaIndex; if(!forceCache) lbaIndex = lbaFatStart; else { lbaIndex = cache.getCluster(); if(lbaIndex == 0) // No sectors in the cache lbaIndex = lbaFatStart; } long lbaFatEnd = lbaFatStart + reservedRegion.getNumberSectorsPerFat(); boolean keep = true; while(keep) { byte[] data = readBytes(lbaIndex, 1); for(int indexEntry=0;indexEntry<=127;indexEntry++) { int[] indexes = getRealIndexes(indexEntry); long value = UnsignedUtil.convertBytes2Long(data[indexes[3]], data[indexes[2]], data[indexes[1]], data[indexes[0]]); if(value == 0x0000000) { long clusterEntry = getFatEntryFromLBA(lbaIndex, indexes[0]); clusterChainList.add(clusterEntry); lbaChain[i] = lbaIndex; entries[i] = indexEntry; if(++i == clusters) // All empty clusters has been located. Set the clusterchain { for(int j=0;j<=clusters-1;j++) { long lba = lbaChain[j]; byte[] data2 = readBytes(lba, 1); long nextCluster; if(j < clusters-1) nextCluster = clusterChainList.get(j+1); else nextCluster = 0xfffffff; int fatEntry = entries[j]; // 0-127 range int[] currentIndexes = getRealIndexes(fatEntry); byte[] nextClusterRaw = UnsignedUtil.convertULong2Bytes(nextCluster); data2[currentIndexes[0]] = nextClusterRaw[3]; data2[currentIndexes[1]] = nextClusterRaw[2]; data2[currentIndexes[2]] = nextClusterRaw[1]; data2[currentIndexes[3]] = nextClusterRaw[0]; writeBytes(lba, data2); } keep = false; break; } } } if(!forceCache) lbaIndex++; else { if(keep) { cache.deleteCluster(); lbaIndex = cache.getCluster(); if (lbaIndex == 0) lbaIndex += 2; // No more clusters in cache. } } if(lbaIndex > lbaFatEnd) return null; } return clusterChainList; } /* Resize a clusterchain. Returns the new last cluster or 0 if it was not possible to resize */ private long resizeClusterChain(long lastCluster) { long lbaFATLastCluster = getEntryLBA(lastCluster); byte[] data = readBytes(lbaFATLastCluster, 1); int sectorIndex = getEntrySectorIndex(lastCluster); // 0-127 if(sectorIndex < 127) sectorIndex++; long indexFat = lbaFATLastCluster; long lbaFatEnd = getEntryLBA(0) + reservedRegion.getNumberSectorsPerFat(); boolean keep = true; while(keep) { for(int i=sectorIndex; i<=127;i++) { int[] indexes = getRealIndexes(i); long value = UnsignedUtil.convertBytes2Long(data[indexes[3]], data[indexes[2]], data[indexes[1]], data[indexes[0]]); if(value == 0x0000000) { long clusterEntry = getFatEntryFromLBA(indexFat, indexes[0]); List<Long> singleList = new ArrayList<Long>(); singleList.add(clusterEntry); byte[] zeroedCluster = new byte[(int) (reservedRegion.getBytesPerSector() * reservedRegion.getSectorsPerCluster())]; writeClusters(singleList, zeroedCluster); // Set the referred cluster to 0x00 (whole cluster is empty) // Previous last cluster FAT entry now points to the new last cluster byte[] dataPrevLBA; if(lbaFATLastCluster == indexFat) { dataPrevLBA = data; }else { dataPrevLBA = readBytes(lbaFATLastCluster, 1); } sectorIndex = getEntrySectorIndex(lastCluster); // 0-127 int[] prevIndexes = getRealIndexes(sectorIndex); byte[] lastClusterRaw = UnsignedUtil.convertULong2Bytes(clusterEntry); dataPrevLBA[prevIndexes[0]] = lastClusterRaw[3]; dataPrevLBA[prevIndexes[1]] = lastClusterRaw[2]; dataPrevLBA[prevIndexes[2]] = lastClusterRaw[1]; dataPrevLBA[prevIndexes[3]] = lastClusterRaw[0]; if(!writeBytes(lbaFATLastCluster, dataPrevLBA)) return 0; // Current last cluster FAT entry points to NUL (0xfffffff) lastClusterRaw = UnsignedUtil.convertULong2Bytes(0xfffffff); data[indexes[0]] = lastClusterRaw[3]; data[indexes[1]] = lastClusterRaw[2]; data[indexes[2]] = lastClusterRaw[1]; data[indexes[3]] = lastClusterRaw[0]; if(!writeBytes(indexFat, data)) return 0; return clusterEntry; } } indexFat++; sectorIndex = 0; if(indexFat > lbaFatEnd) return 0; // 0 is not a valid cluster data = readBytes(indexFat, 1); } return 0; } private boolean deleteClusterChain(List<Long> clusterChain) { Iterator<Long> e = clusterChain.iterator(); while(e.hasNext()) { long cluster = e.next(); long lbaCluster = getEntryLBA(cluster); int sectorIndex = getEntrySectorIndex(cluster); // 0-127 int realIndexes[] = getRealIndexes(sectorIndex); byte[] data = readBytes(lbaCluster, 1); data[realIndexes[0]] = 0x00; data[realIndexes[1]] = 0x00; data[realIndexes[2]] = 0x00; data[realIndexes[3]] = 0x00; if(!writeBytes(lbaCluster, data)) return false; } return true; } private boolean writeClusters(List<Long> clusters, byte[] data) { int maxLength = 16384; // Linux/libusb internally can only handle a buffer of 16834 for bulk transfers int maxClusters = (int) (maxLength / (reservedRegion.getBytesPerSector() * reservedRegion.getSectorsPerCluster())); long firstClusterLba = partition.getLbaStart() + reservedRegion.getNumberReservedSectors() + (reservedRegion.getFatCopies() * reservedRegion.getNumberSectorsPerFat()); ListIterator<Long> e = clusters.listIterator(); int pointer = 0; while(e.hasNext()) { long cluster = e.next(); int i = 1; boolean keep = true; while(i < maxClusters && keep) { if(e.hasNext()) { if(cluster == e.next() - i) i++; else { if(e.hasPrevious()) e.previous(); keep = false; } }else { keep = false; } } long lbaCluster = firstClusterLba + (cluster - 2) * reservedRegion.getSectorsPerCluster(); int bufferLength = (int) (reservedRegion.getBytesPerSector() * reservedRegion.getSectorsPerCluster() * i); byte[] buffer = new byte[bufferLength]; if(pointer + bufferLength <= data.length) System.arraycopy(data, pointer, buffer, 0, bufferLength); else System.arraycopy(data, pointer, buffer, 0, data.length - pointer); boolean result = writeBytes(lbaCluster, buffer); if(!result) return false; pointer += bufferLength; } return true; } private byte[] readClusters(List<Long> clusters) { int maxLength = 16384; // Linux/libusb internally can only handle a buffer of 16834 for bulk transfers int maxClusters = (int) (maxLength / (reservedRegion.getBytesPerSector() * reservedRegion.getSectorsPerCluster())); long firstClusterLba = partition.getLbaStart() + reservedRegion.getNumberReservedSectors() + (reservedRegion.getFatCopies() * reservedRegion.getNumberSectorsPerFat()); int lengthData = clusters.size() * ((int) (reservedRegion.getSectorsPerCluster() * reservedRegion.getBytesPerSector())); byte[] data = new byte[lengthData]; ListIterator<Long> e = clusters.listIterator(); int pointer = 0; while(e.hasNext()) { long cluster = e.next(); int i = 1; boolean keep = true; while(i < maxClusters && keep) { if(e.hasNext()) { if(cluster == e.next() - i) i++; else { if(e.hasPrevious()) e.previous(); keep = false; } }else { keep = false; } } long lbaCluster = firstClusterLba + (cluster - 2) * reservedRegion.getSectorsPerCluster(); int clustersLength = i * (int) (reservedRegion.getSectorsPerCluster()); int bufferLength = clustersLength * ((int) reservedRegion.getBytesPerSector()); byte[] rawClusters = readBytes(lbaCluster, clustersLength); if(rawClusters == null) return null; System.arraycopy(rawClusters, 0, data, pointer, bufferLength); pointer += bufferLength; } return data; } private ReservedRegion getReservedRegion() { long lbaPartitionStart = partition.getLbaStart(); byte[] data = readBytes(lbaPartitionStart, 1); if(data != null) return ReservedRegion.getReservedRegion(data); else return null; } private byte[] readBytes(long lba, int length) { comm.read10(0, false, false, false, UnsignedUtil.ulongToInt(lba), 0, length); waitTillNotification(); if(currentStatus) { return ((SCSIRead10Response) currentResponse).getBuffer(); }else { return null; } } private boolean writeBytes(long lba, byte[] data) { int length = data.length / 512; if(data.length % 512 != 0) length += 1; comm.write10(0, false, false, false, UnsignedUtil.ulongToInt(lba), 0, length, data); waitTillNotification(); return currentStatus; } private boolean preventAllowRemoval(boolean prevent) { comm.preventAllowRemoval(0, prevent); waitTillNotification(); return currentStatus; } private int getFirstFileEntryIndex(byte[] data) { int k = 0; boolean keep = true; while(keep) { if(data[k * 32] == 0x00) keep = false; else k++; } return k * 32; } private List<FileEntry> getFileEntries(byte[] data) { int freeEntries = 0; List<FileEntry> entries = new ArrayList<FileEntry>(); List<String> longFileEntryNames = new ArrayList<String>(); int entrySize = 32; byte[] bufferEntry = new byte[entrySize]; int i = 0; int index1 = entrySize * i; while(index1 < data.length) { System.arraycopy(data, index1, bufferEntry, 0, entrySize); if((bufferEntry[0] != 0x00 && bufferEntry[0] != (byte) 0xe5) && (bufferEntry[11] == 0x0f || bufferEntry[11] == 0x1f || bufferEntry[11] == 0x2f || bufferEntry[11] == 0x3f)) // LFN Entry { longFileEntryNames.add(LFNHandler.parseLFNEntry(bufferEntry)); }else if((bufferEntry[0] != 0x00 && bufferEntry[0] != (byte) 0xe5)) // Normal entry { if(longFileEntryNames != null) // LFN is present { String lfn = ""; int index2 = longFileEntryNames.size() - 1; while(index2 >= 0) { lfn += longFileEntryNames.get(index2); index2--; } entries.add(FileEntry.getEntry(lfn, bufferEntry)); longFileEntryNames.clear(); }else // No LFN { entries.add(FileEntry.getEntry(null, bufferEntry)); } }else if(bufferEntry[0] == 0x00) // Free entries batch started. Calculate free entries and break { int freeBytes = data.length - index1; freeEntries = freeBytes / 32; break; } i++; index1 = entrySize * i; } path.setFreeEntries(freeEntries); return entries; } private boolean setEntryToErased(byte[] data, int indexEntry, String longName) { int counterEntries = 0; int lfnEntries; if(!longName.equals("")) { lfnEntries = longName.length() / 11; if(longName.length() % 11 != 0) lfnEntries += 1; }else { lfnEntries = 0; } int i = 0; while(i < data.length-1) { byte firstByte = data[i * 32]; byte attr = data[i * 32 + 11]; if(firstByte != 0x00 && firstByte != (byte) 0xe5 && attr != 0x0f && attr != 0x1f && attr != 0x2f && attr != 0x3f) { if(counterEntries == indexEntry) // given entry has been found { data[i * 32] = (byte) 0xe5; // Mark entry as delete for (int j = 1; j <= lfnEntries; j++) // Mark all lfn entries as deleted { int k = i * 32; data[k - (j * 32)] = (byte) 0xe5; } return true; }else { counterEntries++; } } i++; } return false; } private long getEntryLBA(long entry) { long fatLBA = partition.getLbaStart() + reservedRegion.getNumberReservedSectors(); return fatLBA + (entry / 128); } private int getEntrySectorIndex(long entry) // range of returned value: [0-127] { return ((int) (entry - ((entry / 128) * 128))); } private int[] getRealIndexes(int entryBlock) { int[] indexes = new int[4]; int value = 4 * entryBlock; indexes[0] = value; indexes[1] = value + 1; indexes[2] = value + 2; indexes[3] = value + 3; return indexes; } private long getFatEntryFromLBA(long lba, int index) { return (lba - getEntryLBA(0)) * 128 + (index / 4); } private void waitTillNotification() { synchronized(monitor) { while(waiting.get()) { try { monitor.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } waiting.set(true); } } private void scsiSuccessNotification() { synchronized(monitor) { waiting.set(false); monitor.notify(); } } private SCSIInterface scsiInterface = new SCSIInterface() { @Override public void onSCSIOperationCompleted(int status, int dataResidue) { if(status == 0) { currentStatus = true; scsiSuccessNotification(); }else { currentStatus = false; scsiSuccessNotification(); } } @Override public void onSCSIDataReceived(SCSIResponse response) { currentResponse = response; } @Override public void onSCSIOperationStarted(boolean status) { } }; private class CacheThread extends Thread { private AtomicBoolean keep; private AtomicBoolean cacheReading; private int maxElements; public CacheThread() { keep = new AtomicBoolean(false); cacheReading = new AtomicBoolean(true); } @Override public void run() { Looper.prepare(); wHandler = new Handler() { @Override public void handleMessage(Message msg) { switch(msg.what) { case LOAD_CACHE: populateCache(msg.arg1); break; case FIND_EMPTY_CLUSTERCHAIN: break; } } }; Looper.loop(); } public boolean waitTillCaching() { synchronized(cacheMonitor) { while(cacheReading.get()) { try { cacheMonitor.wait(); }catch (InterruptedException e) { e.printStackTrace(); } } return true; } } public void notifyCacheRead() { synchronized(cacheMonitor) { cacheReading.set(false); cacheMonitor.notify(); } } private void populateCache(int cacheMode) { int j = 0; long lbaFATStart = getEntryLBA(0); long lbaFATEnd = lbaFATStart + reservedRegion.getNumberSectorsPerFat(); if(cacheMode == 1) // Low Cache maxElements = 200; else if(cacheMode == 2) // Medium Cache maxElements = (int) (reservedRegion.getNumberSectorsPerFat() / 2); else if(cacheMode == 3) // High Cache maxElements = (int) reservedRegion.getNumberSectorsPerFat(); for(long i=lbaFATStart;i<=lbaFATEnd-1;i++) { byte[] rawFATLba = readBytes(i, 1); if(isCacheable(rawFATLba)) { cache.addCluster(i); if(++j == maxElements) { notifyCacheRead(); return; } } } notifyCacheRead(); } /* Consider the sector cacheable if at least 1/4 is available */ private boolean isCacheable(byte[] rawSector) { int counter = 0; for(int j=0;j<=rawSector.length-1;j+=4) { long entry = UnsignedUtil.convertBytes2Long(rawSector[j+3], rawSector[j+2], rawSector[j+1], rawSector[j]); if(entry == 0) counter++; if(counter > 100) return true; } return false; } } }