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