1 package org.robolectric.shadows; 2 3 import android.os.StatFs; 4 import java.io.File; 5 import java.util.Map; 6 import java.util.TreeMap; 7 import org.robolectric.annotation.Implementation; 8 import org.robolectric.annotation.Implements; 9 import org.robolectric.annotation.Resetter; 10 11 /** 12 * Robolectic doesn't provide actual filesystem stats; rather, it provides the ability to specify 13 * stats values in advance. 14 * 15 * @see #registerStats(File, int, int, int) 16 */ 17 @Implements(StatFs.class) 18 public class ShadowStatFs { 19 public static final int BLOCK_SIZE = 4096; 20 private static final Stats DEFAULT_STATS = new Stats(0, 0, 0); 21 private static TreeMap<String, Stats> stats = new TreeMap<>(); 22 private Stats stat; 23 24 @Implementation __constructor__(String path)25 protected void __constructor__(String path) { 26 restat(path); 27 } 28 29 @Implementation getBlockSize()30 protected int getBlockSize() { 31 return BLOCK_SIZE; 32 } 33 34 @Implementation getBlockCount()35 protected int getBlockCount() { 36 return stat.blockCount; 37 } 38 39 @Implementation getFreeBlocks()40 protected int getFreeBlocks() { 41 return stat.freeBlocks; 42 } 43 44 @Implementation getFreeBlocksLong()45 protected long getFreeBlocksLong() { 46 return stat.freeBlocks; 47 } 48 49 @Implementation getFreeBytes()50 protected long getFreeBytes() { 51 return getBlockSizeLong() * getFreeBlocksLong(); 52 } 53 54 @Implementation getAvailableBytes()55 protected long getAvailableBytes() { 56 return getBlockSizeLong() * getAvailableBlocksLong(); 57 } 58 59 @Implementation getTotalBytes()60 protected long getTotalBytes() { 61 return getBlockSizeLong() * getBlockCountLong(); 62 } 63 64 @Implementation getAvailableBlocks()65 protected int getAvailableBlocks() { 66 return stat.availableBlocks; 67 } 68 69 @Implementation restat(String path)70 protected void restat(String path) { 71 Map.Entry<String, Stats> mapEntry = stats.floorEntry(path); 72 for (; ; ) { 73 // We will hit all matching paths, longest one first. We may hit non-matching paths before we 74 // find the right one. 75 if (mapEntry == null) { 76 stat = DEFAULT_STATS; 77 return; 78 } 79 String key = mapEntry.getKey(); 80 if (path.startsWith(key)) { 81 stat = mapEntry.getValue(); 82 return; 83 } 84 mapEntry = stats.lowerEntry(key); 85 } 86 } 87 88 /** Robolectric always uses a block size of 4096. */ 89 @Implementation getBlockSizeLong()90 protected long getBlockSizeLong() { 91 return BLOCK_SIZE; 92 } 93 94 @Implementation getBlockCountLong()95 protected long getBlockCountLong() { 96 return stat.blockCount; 97 } 98 99 @Implementation getAvailableBlocksLong()100 protected long getAvailableBlocksLong() { 101 return stat.availableBlocks; 102 } 103 104 /** 105 * Register stats for a path, which will be used when a matching {@link StatFs} instance is 106 * created. 107 * 108 * @param path path to the file 109 * @param blockCount number of blocks 110 * @param freeBlocks number of free blocks 111 * @param availableBlocks number of available blocks 112 */ registerStats(File path, int blockCount, int freeBlocks, int availableBlocks)113 public static void registerStats(File path, int blockCount, int freeBlocks, int availableBlocks) { 114 registerStats(path.getAbsolutePath(), blockCount, freeBlocks, availableBlocks); 115 } 116 117 /** 118 * Register stats for a path, which will be used when a matching {@link StatFs} instance is 119 * created. A {@link StatFs} instance matches if it extends path. If several registered paths 120 * match, we pick the longest one. 121 * 122 * @param path path to the file 123 * @param blockCount number of blocks 124 * @param freeBlocks number of free blocks 125 * @param availableBlocks number of available blocks 126 */ registerStats( String path, int blockCount, int freeBlocks, int availableBlocks)127 public static void registerStats( 128 String path, int blockCount, int freeBlocks, int availableBlocks) { 129 stats.put(path, new Stats(blockCount, freeBlocks, availableBlocks)); 130 } 131 132 /** 133 * Unregister stats for a path. If the path is not registered, it will be a no-op. 134 * 135 * @param path path to the file 136 */ unregisterStats(File path)137 public static void unregisterStats(File path) { 138 unregisterStats(path.getAbsolutePath()); 139 } 140 141 /** 142 * Unregister stats for a path. If the path is not registered, it will be a no-op. 143 * 144 * @param path path to the file 145 */ unregisterStats(String path)146 public static void unregisterStats(String path) { 147 stats.remove(path); 148 } 149 150 @Resetter reset()151 public static void reset() { 152 stats.clear(); 153 } 154 155 private static class Stats { Stats(int blockCount, int freeBlocks, int availableBlocks)156 Stats(int blockCount, int freeBlocks, int availableBlocks) { 157 this.blockCount = blockCount; 158 this.freeBlocks = freeBlocks; 159 this.availableBlocks = availableBlocks; 160 } 161 162 int blockCount, freeBlocks, availableBlocks; 163 } 164 } 165