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