337 lines
10 KiB
Java
337 lines
10 KiB
Java
/*
|
|
* 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.compat.annotation.UnsupportedAppUsage;
|
|
import android.system.ErrnoException;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.nio.ByteBuffer;
|
|
|
|
|
|
/**
|
|
* MemoryFile is a wrapper for {@link SharedMemory} which can optionally be set to purgeable.
|
|
*
|
|
* Applications should generally prefer to use {@link SharedMemory} which offers more flexible
|
|
* access & control over the shared memory region than MemoryFile does.
|
|
*
|
|
* 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 'true' if purged, 'false' otherwise
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
private static native boolean native_pin(FileDescriptor fd, boolean pin) throws IOException;
|
|
@UnsupportedAppUsage
|
|
private static native int native_get_size(FileDescriptor fd) throws IOException;
|
|
|
|
private SharedMemory mSharedMemory;
|
|
private ByteBuffer mMapping;
|
|
private boolean mAllowPurging = false; // true if our ashmem region is unpinned
|
|
|
|
/**
|
|
* Allocates a new ashmem region. The region is initially not purgable.
|
|
*
|
|
* @param name optional name for the file (can be null).
|
|
* @param length of the memory file in bytes, must be positive.
|
|
* @throws IOException if the memory file could not be created.
|
|
*/
|
|
public MemoryFile(String name, int length) throws IOException {
|
|
try {
|
|
mSharedMemory = SharedMemory.create(name, length);
|
|
mMapping = mSharedMemory.mapReadWrite();
|
|
} catch (ErrnoException ex) {
|
|
ex.rethrowAsIOException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Closes the memory file. If there are no other open references to the memory
|
|
* file, it will be deleted.
|
|
*/
|
|
public void close() {
|
|
deactivate();
|
|
mSharedMemory.close();
|
|
}
|
|
|
|
/**
|
|
* Unmaps the memory file from the process's memory space, but does not close it.
|
|
* After this method has been called, read and write operations through this object
|
|
* will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor.
|
|
*
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
void deactivate() {
|
|
if (mMapping != null) {
|
|
SharedMemory.unmap(mMapping);
|
|
mMapping = null;
|
|
}
|
|
}
|
|
|
|
private void checkActive() throws IOException {
|
|
if (mMapping == null) {
|
|
throw new IOException("MemoryFile has been deactivated");
|
|
}
|
|
}
|
|
|
|
private void beginAccess() throws IOException {
|
|
checkActive();
|
|
if (mAllowPurging) {
|
|
if (native_pin(mSharedMemory.getFileDescriptor(), true)) {
|
|
throw new IOException("MemoryFile has been purged");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void endAccess() throws IOException {
|
|
if (mAllowPurging) {
|
|
native_pin(mSharedMemory.getFileDescriptor(), false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the length of the memory file.
|
|
*
|
|
* @return file length.
|
|
*/
|
|
public int length() {
|
|
return mSharedMemory.getSize();
|
|
}
|
|
|
|
/**
|
|
* Is memory file purging enabled?
|
|
*
|
|
* @return true if the file may be purged.
|
|
*
|
|
* @deprecated Purgable is considered generally fragile and hard to use safely. Applications
|
|
* are recommend to instead use {@link android.content.ComponentCallbacks2#onTrimMemory(int)}
|
|
* to react to memory events and release shared memory regions as appropriate.
|
|
*/
|
|
@Deprecated
|
|
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
|
|
*
|
|
* @deprecated Purgable is considered generally fragile and hard to use safely. Applications
|
|
* are recommend to instead use {@link android.content.ComponentCallbacks2#onTrimMemory(int)}
|
|
* to react to memory events and release shared memory regions as appropriate.
|
|
*/
|
|
@Deprecated
|
|
synchronized public boolean allowPurging(boolean allowPurging) throws IOException {
|
|
boolean oldValue = mAllowPurging;
|
|
if (oldValue != allowPurging) {
|
|
native_pin(mSharedMemory.getFileDescriptor(), !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.
|
|
* @throws IOException if the memory file has been purged or deactivated.
|
|
*/
|
|
public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
|
|
throws IOException {
|
|
beginAccess();
|
|
try {
|
|
mMapping.position(srcOffset);
|
|
mMapping.get(buffer, destOffset, count);
|
|
} finally {
|
|
endAccess();
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @throws IOException if the memory file has been purged or deactivated.
|
|
*/
|
|
public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
|
|
throws IOException {
|
|
beginAccess();
|
|
try {
|
|
mMapping.position(destOffset);
|
|
mMapping.put(buffer, srcOffset, count);
|
|
} finally {
|
|
endAccess();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a FileDescriptor for the memory file.
|
|
*
|
|
* The returned file descriptor is not duplicated.
|
|
*
|
|
* @throws IOException If the memory file has been closed.
|
|
*
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public FileDescriptor getFileDescriptor() throws IOException {
|
|
return mSharedMemory.getFileDescriptor();
|
|
}
|
|
|
|
/**
|
|
* Returns the size of the memory file that the file descriptor refers to,
|
|
* or -1 if the file descriptor does not refer to a memory file.
|
|
*
|
|
* @throws IOException If <code>fd</code> is not a valid file descriptor.
|
|
*
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public static int getSize(FileDescriptor fd) throws IOException {
|
|
return native_get_size(fd);
|
|
}
|
|
|
|
private class MemoryInputStream extends InputStream {
|
|
|
|
private int mMark = 0;
|
|
private int mOffset = 0;
|
|
private byte[] mSingleByte;
|
|
|
|
@Override
|
|
public int available() throws IOException {
|
|
if (mOffset >= mSharedMemory.getSize()) {
|
|
return 0;
|
|
}
|
|
return mSharedMemory.getSize() - 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) {
|
|
return -1;
|
|
}
|
|
return mSingleByte[0];
|
|
}
|
|
|
|
@Override
|
|
public int read(byte buffer[], int offset, int count) throws IOException {
|
|
if (offset < 0 || count < 0 || offset + count > buffer.length) {
|
|
// readBytes() also does this check, but we need to do it before
|
|
// changing count.
|
|
throw new IndexOutOfBoundsException();
|
|
}
|
|
count = Math.min(count, available());
|
|
if (count < 1) {
|
|
return -1;
|
|
}
|
|
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 > mSharedMemory.getSize()) {
|
|
n = mSharedMemory.getSize() - 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);
|
|
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);
|
|
}
|
|
}
|
|
}
|