1 package org.robolectric.shadows; 2 3 import android.os.Build; 4 import android.os.SharedMemory; 5 import android.system.ErrnoException; 6 import android.system.OsConstants; 7 import java.io.File; 8 import java.io.FileDescriptor; 9 import java.io.IOException; 10 import java.io.RandomAccessFile; 11 import java.nio.ByteBuffer; 12 import java.nio.MappedByteBuffer; 13 import java.nio.channels.FileChannel.MapMode; 14 import java.nio.file.Files; 15 import java.util.Map; 16 import java.util.concurrent.ConcurrentHashMap; 17 import java.util.concurrent.atomic.AtomicReference; 18 import org.robolectric.RuntimeEnvironment; 19 import org.robolectric.annotation.Implementation; 20 import org.robolectric.annotation.Implements; 21 import org.robolectric.annotation.RealObject; 22 import org.robolectric.annotation.Resetter; 23 import org.robolectric.util.ReflectionHelpers; 24 import org.robolectric.util.TempDirectory; 25 26 /** 27 * A {@link SharedMemory} fake that uses a private temporary disk file for storage and Java's {@link 28 * MappedByteBuffer} for the memory mappings. 29 */ 30 @Implements( 31 value = SharedMemory.class, 32 minSdk = Build.VERSION_CODES.O_MR1, 33 /* not quite true, but this prevents a useless `shadowOf()` accessor showing 34 * up, which would break people compiling against API 26 and earlier. 35 */ 36 isInAndroidSdk = false) 37 public class ShadowSharedMemory { 38 private static final Map<Integer, File> filesByFd = new ConcurrentHashMap<>(); 39 40 private static final AtomicReference<ErrnoException> fakeCreateException = 41 new AtomicReference<>(); 42 43 @RealObject private SharedMemory realObject; 44 45 @Resetter reset()46 public static void reset() { 47 filesByFd.clear(); 48 } 49 50 /** 51 * Only works on {@link SharedMemory} instances from {@link SharedMemory#create}. 52 * 53 * <p>"prot" is ignored -- all mappings are read/write. 54 */ 55 @Implementation map(int prot, int offset, int length)56 protected ByteBuffer map(int prot, int offset, int length) throws ErrnoException { 57 ReflectionHelpers.callInstanceMethod(realObject, "checkOpen"); 58 FileDescriptor fileDescriptor = getRealFileDescriptor(); 59 int fd = ReflectionHelpers.getField(fileDescriptor, "fd"); 60 File file = filesByFd.get(fd); 61 if (file == null) { 62 throw new IllegalStateException("Cannot find the backing file from fd"); 63 } 64 65 try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw")) { 66 // It would be easy to support a MapMode.READ_ONLY type mapping as well except none of the 67 // OsConstants fields are even initialized by robolectric and so "prot" is always zero! 68 return randomAccessFile.getChannel().map(MapMode.READ_WRITE, offset, length); 69 } catch (IOException e) { 70 throw new ErrnoException(e.getMessage(), OsConstants.EIO, e); 71 } 72 } 73 74 @Implementation unmap(ByteBuffer mappedBuf)75 protected static void unmap(ByteBuffer mappedBuf) throws ErrnoException { 76 if (mappedBuf instanceof MappedByteBuffer) { 77 // There's no API to clean up a MappedByteBuffer other than GC so we've got nothing to do! 78 } else { 79 throw new IllegalArgumentException( 80 "ByteBuffer wasn't created by #map(int, int, int); can't unmap"); 81 } 82 } 83 84 @Implementation nCreate(String name, int size)85 protected static FileDescriptor nCreate(String name, int size) throws ErrnoException { 86 maybeThrow(fakeCreateException); 87 88 TempDirectory tempDirectory = RuntimeEnvironment.getTempDirectory(); 89 90 try { 91 // Give each instance its own private file. 92 File sharedMemoryFile = 93 Files.createTempFile( 94 tempDirectory.createIfNotExists("SharedMemory"), "shmem-" + name, ".tmp") 95 .toFile(); 96 RandomAccessFile randomAccessFile = new RandomAccessFile(sharedMemoryFile, "rw"); 97 randomAccessFile.setLength(0); 98 randomAccessFile.setLength(size); 99 100 FileDescriptor fileDescriptor = randomAccessFile.getFD(); 101 int fd = ReflectionHelpers.getField(fileDescriptor, "fd"); 102 filesByFd.put(fd, sharedMemoryFile); 103 return fileDescriptor; 104 } catch (IOException e) { 105 throw new RuntimeException("Unable to create file descriptior", e); 106 } 107 } 108 109 @Implementation nGetSize(FileDescriptor fd)110 protected static int nGetSize(FileDescriptor fd) { 111 int internalFd = ReflectionHelpers.getField(fd, "fd"); 112 return (int) filesByFd.get(internalFd).length(); 113 } 114 getRealFileDescriptor()115 private FileDescriptor getRealFileDescriptor() { 116 return ReflectionHelpers.getField(realObject, "mFileDescriptor"); 117 } 118 119 /** 120 * Causes subsequent calls to {@link SharedMemory#create)} to throw the specified exception, if 121 * non-null. Pass null to restore create to normal operation. 122 */ setCreateShouldThrow(ErrnoException e)123 public static void setCreateShouldThrow(ErrnoException e) { 124 fakeCreateException.set(e); 125 } 126 maybeThrow(AtomicReference<ErrnoException> exceptionRef)127 private static void maybeThrow(AtomicReference<ErrnoException> exceptionRef) 128 throws ErrnoException { 129 ErrnoException exception = exceptionRef.get(); 130 if (exception != null) { 131 throw exception; 132 } 133 } 134 } 135