| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.os; |
| |
| import android.util.Log; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| |
| |
| /** |
| * MemoryFile is a wrapper for the Linux ashmem driver. |
| * MemoryFiles are backed by shared memory, which can be optionally |
| * set to be purgeable. |
| * Purgeable files may have their contents reclaimed by the kernel |
| * in low memory conditions (only if allowPurging is set to true). |
| * After a file is purged, attempts to read or write the file will |
| * cause an IOException to be thrown. |
| */ |
| public class MemoryFile |
| { |
| private static String TAG = "MemoryFile"; |
| |
| // returns fd |
| private native int native_open(String name, int length); |
| // returns memory address for ashmem region |
| private native int native_mmap(int fd, int length); |
| private native void native_close(int fd); |
| private native int native_read(int fd, int address, byte[] buffer, |
| int srcOffset, int destOffset, int count, boolean isUnpinned); |
| private native void native_write(int fd, int address, byte[] buffer, |
| int srcOffset, int destOffset, int count, boolean isUnpinned); |
| private native void native_pin(int fd, boolean pin); |
| |
| private int mFD; // ashmem file descriptor |
| private int mAddress; // address of ashmem memory |
| private int mLength; // total length of our ashmem region |
| private boolean mAllowPurging = false; // true if our ashmem region is unpinned |
| |
| /** |
| * MemoryFile constructor. |
| * |
| * @param name optional name for the file (can be null). |
| * @param length of the memory file in bytes. |
| */ |
| public MemoryFile(String name, int length) { |
| mLength = length; |
| mFD = native_open(name, length); |
| mAddress = native_mmap(mFD, length); |
| } |
| |
| /** |
| * Closes and releases all resources for the memory file. |
| */ |
| public void close() { |
| if (mFD > 0) { |
| native_close(mFD); |
| mFD = 0; |
| } |
| } |
| |
| @Override |
| protected void finalize() { |
| if (mFD > 0) { |
| Log.e(TAG, "MemoryFile.finalize() called while ashmem still open"); |
| close(); |
| } |
| } |
| |
| /** |
| * Returns the length of the memory file. |
| * |
| * @return file length. |
| */ |
| public int length() { |
| return mLength; |
| } |
| |
| /** |
| * Is memory file purging enabled? |
| * |
| * @return true if the file may be purged. |
| */ |
| public boolean isPurgingAllowed() { |
| return mAllowPurging; |
| } |
| |
| /** |
| * Enables or disables purging of the memory file. |
| * |
| * @param allowPurging true if the operating system can purge the contents |
| * of the file in low memory situations |
| * @return previous value of allowPurging |
| */ |
| synchronized public boolean allowPurging(boolean allowPurging) throws IOException { |
| boolean oldValue = mAllowPurging; |
| if (oldValue != allowPurging) { |
| native_pin(mFD, !allowPurging); |
| mAllowPurging = allowPurging; |
| } |
| return oldValue; |
| } |
| |
| /** |
| * Creates a new InputStream for reading from the memory file. |
| * |
| @return InputStream |
| */ |
| public InputStream getInputStream() { |
| return new MemoryInputStream(); |
| } |
| |
| /** |
| * Creates a new OutputStream for writing to the memory file. |
| * |
| @return OutputStream |
| */ |
| public OutputStream getOutputStream() { |
| |
| return new MemoryOutputStream(); |
| } |
| |
| /** |
| * Reads bytes from the memory file. |
| * Will throw an IOException if the file has been purged. |
| * |
| * @param buffer byte array to read bytes into. |
| * @param srcOffset offset into the memory file to read from. |
| * @param destOffset offset into the byte array buffer to read into. |
| * @param count number of bytes to read. |
| * @return number of bytes read. |
| */ |
| public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count) |
| throws IOException { |
| if (destOffset < 0 || destOffset > buffer.length || count < 0 |
| || count > buffer.length - destOffset |
| || srcOffset < 0 || srcOffset > mLength |
| || count > mLength - srcOffset) { |
| throw new IndexOutOfBoundsException(); |
| } |
| return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); |
| } |
| |
| /** |
| * Write bytes to the memory file. |
| * Will throw an IOException if the file has been purged. |
| * |
| * @param buffer byte array to write bytes from. |
| * @param srcOffset offset into the byte array buffer to write from. |
| * @param destOffset offset into the memory file to write to. |
| * @param count number of bytes to write. |
| */ |
| public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count) |
| throws IOException { |
| if (srcOffset < 0 || srcOffset > buffer.length || count < 0 |
| || count > buffer.length - srcOffset |
| || destOffset < 0 || destOffset > mLength |
| || count > mLength - destOffset) { |
| throw new IndexOutOfBoundsException(); |
| } |
| native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); |
| } |
| |
| private class MemoryInputStream extends InputStream { |
| |
| private int mMark = 0; |
| private int mOffset = 0; |
| private byte[] mSingleByte; |
| |
| @Override |
| public int available() throws IOException { |
| if (mOffset >= mLength) { |
| return 0; |
| } |
| return mLength - mOffset; |
| } |
| |
| @Override |
| public boolean markSupported() { |
| return true; |
| } |
| |
| @Override |
| public void mark(int readlimit) { |
| mMark = mOffset; |
| } |
| |
| @Override |
| public void reset() throws IOException { |
| mOffset = mMark; |
| } |
| |
| @Override |
| public int read() throws IOException { |
| if (mSingleByte == null) { |
| mSingleByte = new byte[1]; |
| } |
| int result = read(mSingleByte, 0, 1); |
| if (result != 1) { |
| throw new IOException("read() failed"); |
| } |
| return mSingleByte[0]; |
| } |
| |
| @Override |
| public int read(byte buffer[], int offset, int count) throws IOException { |
| int result = readBytes(buffer, mOffset, offset, count); |
| if (result > 0) { |
| mOffset += result; |
| } |
| return result; |
| } |
| |
| @Override |
| public long skip(long n) throws IOException { |
| if (mOffset + n > mLength) { |
| n = mLength - mOffset; |
| } |
| mOffset += n; |
| return n; |
| } |
| } |
| |
| private class MemoryOutputStream extends OutputStream { |
| |
| private int mOffset = 0; |
| private byte[] mSingleByte; |
| |
| @Override |
| public void write(byte buffer[], int offset, int count) throws IOException { |
| writeBytes(buffer, offset, mOffset, count); |
| } |
| |
| @Override |
| public void write(int oneByte) throws IOException { |
| if (mSingleByte == null) { |
| mSingleByte = new byte[1]; |
| } |
| mSingleByte[0] = (byte)oneByte; |
| write(mSingleByte, 0, 1); |
| } |
| } |
| } |