1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.os; 18 19 import android.system.ErrnoException; 20 21 import java.io.FileDescriptor; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.io.OutputStream; 25 import java.nio.ByteBuffer; 26 27 28 /** 29 * MemoryFile is a wrapper for {@link SharedMemory} which can optionally be set to purgeable. 30 * 31 * Applications should generally prefer to use {@link SharedMemory} which offers more flexible 32 * access & control over the shared memory region than MemoryFile does. 33 * 34 * Purgeable files may have their contents reclaimed by the kernel 35 * in low memory conditions (only if allowPurging is set to true). 36 * After a file is purged, attempts to read or write the file will 37 * cause an IOException to be thrown. 38 */ 39 public class MemoryFile { 40 private static String TAG = "MemoryFile"; 41 42 // Returns 'true' if purged, 'false' otherwise native_pin(FileDescriptor fd, boolean pin)43 private static native boolean native_pin(FileDescriptor fd, boolean pin) throws IOException; native_get_size(FileDescriptor fd)44 private static native int native_get_size(FileDescriptor fd) throws IOException; 45 46 private SharedMemory mSharedMemory; 47 private ByteBuffer mMapping; 48 private boolean mAllowPurging = false; // true if our ashmem region is unpinned 49 50 /** 51 * Allocates a new ashmem region. The region is initially not purgable. 52 * 53 * @param name optional name for the file (can be null). 54 * @param length of the memory file in bytes, must be positive. 55 * @throws IOException if the memory file could not be created. 56 */ MemoryFile(String name, int length)57 public MemoryFile(String name, int length) throws IOException { 58 try { 59 mSharedMemory = SharedMemory.create(name, length); 60 mMapping = mSharedMemory.mapReadWrite(); 61 } catch (ErrnoException ex) { 62 ex.rethrowAsIOException(); 63 } 64 } 65 66 /** 67 * Closes the memory file. If there are no other open references to the memory 68 * file, it will be deleted. 69 */ close()70 public void close() { 71 deactivate(); 72 mSharedMemory.close(); 73 } 74 75 /** 76 * Unmaps the memory file from the process's memory space, but does not close it. 77 * After this method has been called, read and write operations through this object 78 * will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor. 79 * 80 * @hide 81 */ deactivate()82 void deactivate() { 83 if (mMapping != null) { 84 SharedMemory.unmap(mMapping); 85 mMapping = null; 86 } 87 } 88 checkActive()89 private void checkActive() throws IOException { 90 if (mMapping == null) { 91 throw new IOException("MemoryFile has been deactivated"); 92 } 93 } 94 beginAccess()95 private void beginAccess() throws IOException { 96 checkActive(); 97 if (mAllowPurging) { 98 if (native_pin(mSharedMemory.getFileDescriptor(), true)) { 99 throw new IOException("MemoryFile has been purged"); 100 } 101 } 102 } 103 endAccess()104 private void endAccess() throws IOException { 105 if (mAllowPurging) { 106 native_pin(mSharedMemory.getFileDescriptor(), false); 107 } 108 } 109 110 /** 111 * Returns the length of the memory file. 112 * 113 * @return file length. 114 */ length()115 public int length() { 116 return mSharedMemory.getSize(); 117 } 118 119 /** 120 * Is memory file purging enabled? 121 * 122 * @return true if the file may be purged. 123 * 124 * @deprecated Purgable is considered generally fragile and hard to use safely. Applications 125 * are recommend to instead use {@link android.content.ComponentCallbacks2#onTrimMemory(int)} 126 * to react to memory events and release shared memory regions as appropriate. 127 */ 128 @Deprecated isPurgingAllowed()129 public boolean isPurgingAllowed() { 130 return mAllowPurging; 131 } 132 133 /** 134 * Enables or disables purging of the memory file. 135 * 136 * @param allowPurging true if the operating system can purge the contents 137 * of the file in low memory situations 138 * @return previous value of allowPurging 139 * 140 * @deprecated Purgable is considered generally fragile and hard to use safely. Applications 141 * are recommend to instead use {@link android.content.ComponentCallbacks2#onTrimMemory(int)} 142 * to react to memory events and release shared memory regions as appropriate. 143 */ 144 @Deprecated allowPurging(boolean allowPurging)145 synchronized public boolean allowPurging(boolean allowPurging) throws IOException { 146 boolean oldValue = mAllowPurging; 147 if (oldValue != allowPurging) { 148 native_pin(mSharedMemory.getFileDescriptor(), !allowPurging); 149 mAllowPurging = allowPurging; 150 } 151 return oldValue; 152 } 153 154 /** 155 * Creates a new InputStream for reading from the memory file. 156 * 157 @return InputStream 158 */ getInputStream()159 public InputStream getInputStream() { 160 return new MemoryInputStream(); 161 } 162 163 /** 164 * Creates a new OutputStream for writing to the memory file. 165 * 166 @return OutputStream 167 */ getOutputStream()168 public OutputStream getOutputStream() { 169 return new MemoryOutputStream(); 170 } 171 172 /** 173 * Reads bytes from the memory file. 174 * Will throw an IOException if the file has been purged. 175 * 176 * @param buffer byte array to read bytes into. 177 * @param srcOffset offset into the memory file to read from. 178 * @param destOffset offset into the byte array buffer to read into. 179 * @param count number of bytes to read. 180 * @return number of bytes read. 181 * @throws IOException if the memory file has been purged or deactivated. 182 */ readBytes(byte[] buffer, int srcOffset, int destOffset, int count)183 public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count) 184 throws IOException { 185 beginAccess(); 186 try { 187 mMapping.position(srcOffset); 188 mMapping.get(buffer, destOffset, count); 189 } finally { 190 endAccess(); 191 } 192 return count; 193 } 194 195 /** 196 * Write bytes to the memory file. 197 * Will throw an IOException if the file has been purged. 198 * 199 * @param buffer byte array to write bytes from. 200 * @param srcOffset offset into the byte array buffer to write from. 201 * @param destOffset offset into the memory file to write to. 202 * @param count number of bytes to write. 203 * @throws IOException if the memory file has been purged or deactivated. 204 */ writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)205 public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count) 206 throws IOException { 207 beginAccess(); 208 try { 209 mMapping.position(destOffset); 210 mMapping.put(buffer, srcOffset, count); 211 } finally { 212 endAccess(); 213 } 214 } 215 216 /** 217 * Gets a FileDescriptor for the memory file. 218 * 219 * The returned file descriptor is not duplicated. 220 * 221 * @throws IOException If the memory file has been closed. 222 * 223 * @hide 224 */ getFileDescriptor()225 public FileDescriptor getFileDescriptor() throws IOException { 226 return mSharedMemory.getFileDescriptor(); 227 } 228 229 /** 230 * Returns the size of the memory file that the file descriptor refers to, 231 * or -1 if the file descriptor does not refer to a memory file. 232 * 233 * @throws IOException If <code>fd</code> is not a valid file descriptor. 234 * 235 * @hide 236 */ getSize(FileDescriptor fd)237 public static int getSize(FileDescriptor fd) throws IOException { 238 return native_get_size(fd); 239 } 240 241 private class MemoryInputStream extends InputStream { 242 243 private int mMark = 0; 244 private int mOffset = 0; 245 private byte[] mSingleByte; 246 247 @Override available()248 public int available() throws IOException { 249 if (mOffset >= mSharedMemory.getSize()) { 250 return 0; 251 } 252 return mSharedMemory.getSize() - mOffset; 253 } 254 255 @Override markSupported()256 public boolean markSupported() { 257 return true; 258 } 259 260 @Override mark(int readlimit)261 public void mark(int readlimit) { 262 mMark = mOffset; 263 } 264 265 @Override reset()266 public void reset() throws IOException { 267 mOffset = mMark; 268 } 269 270 @Override read()271 public int read() throws IOException { 272 if (mSingleByte == null) { 273 mSingleByte = new byte[1]; 274 } 275 int result = read(mSingleByte, 0, 1); 276 if (result != 1) { 277 return -1; 278 } 279 return mSingleByte[0]; 280 } 281 282 @Override read(byte buffer[], int offset, int count)283 public int read(byte buffer[], int offset, int count) throws IOException { 284 if (offset < 0 || count < 0 || offset + count > buffer.length) { 285 // readBytes() also does this check, but we need to do it before 286 // changing count. 287 throw new IndexOutOfBoundsException(); 288 } 289 count = Math.min(count, available()); 290 if (count < 1) { 291 return -1; 292 } 293 int result = readBytes(buffer, mOffset, offset, count); 294 if (result > 0) { 295 mOffset += result; 296 } 297 return result; 298 } 299 300 @Override skip(long n)301 public long skip(long n) throws IOException { 302 if (mOffset + n > mSharedMemory.getSize()) { 303 n = mSharedMemory.getSize() - mOffset; 304 } 305 mOffset += n; 306 return n; 307 } 308 } 309 310 private class MemoryOutputStream extends OutputStream { 311 312 private int mOffset = 0; 313 private byte[] mSingleByte; 314 315 @Override write(byte buffer[], int offset, int count)316 public void write(byte buffer[], int offset, int count) throws IOException { 317 writeBytes(buffer, offset, mOffset, count); 318 mOffset += count; 319 } 320 321 @Override write(int oneByte)322 public void write(int oneByte) throws IOException { 323 if (mSingleByte == null) { 324 mSingleByte = new byte[1]; 325 } 326 mSingleByte[0] = (byte)oneByte; 327 write(mSingleByte, 0, 1); 328 } 329 } 330 } 331