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