Loading core/java/android/content/res/AssetFileDescriptor.java +88 −0 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.content.res; package android.content.res; import android.os.MemoryFile; import android.os.Parcel; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.Parcelable; Loading @@ -24,6 +25,8 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.IOException; import java.io.InputStream; import java.nio.channels.FileChannel; /** /** * File descriptor of an entry in the AssetManager. This provides your own * File descriptor of an entry in the AssetManager. This provides your own Loading Loading @@ -123,6 +126,13 @@ public class AssetFileDescriptor implements Parcelable { mFd.close(); mFd.close(); } } /** * Checks whether this file descriptor is for a memory file. */ private boolean isMemoryFile() throws IOException { return MemoryFile.isMemoryFile(mFd.getFileDescriptor()); } /** /** * Create and return a new auto-close input stream for this asset. This * Create and return a new auto-close input stream for this asset. This * will either return a full asset {@link AutoCloseInputStream}, or * will either return a full asset {@link AutoCloseInputStream}, or Loading @@ -132,6 +142,12 @@ public class AssetFileDescriptor implements Parcelable { * should only call this once for a particular asset. * should only call this once for a particular asset. */ */ public FileInputStream createInputStream() throws IOException { public FileInputStream createInputStream() throws IOException { if (isMemoryFile()) { if (mLength > Integer.MAX_VALUE) { throw new IOException("File length too large for a memory file: " + mLength); } return new AutoCloseMemoryFileInputStream(mFd, (int)mLength); } if (mLength < 0) { if (mLength < 0) { return new ParcelFileDescriptor.AutoCloseInputStream(mFd); return new ParcelFileDescriptor.AutoCloseInputStream(mFd); } } Loading Loading @@ -261,6 +277,66 @@ public class AssetFileDescriptor implements Parcelable { } } } } /** * An input stream that reads from a MemoryFile and closes it when the stream is closed. * This extends FileInputStream just because {@link #createInputStream} returns * a FileInputStream. All the FileInputStream methods are * overridden to use the MemoryFile instead. */ private static class AutoCloseMemoryFileInputStream extends FileInputStream { private ParcelFileDescriptor mParcelFd; private MemoryFile mFile; private InputStream mStream; public AutoCloseMemoryFileInputStream(ParcelFileDescriptor fd, int length) throws IOException { super(fd.getFileDescriptor()); mParcelFd = fd; mFile = new MemoryFile(fd.getFileDescriptor(), length, "r"); mStream = mFile.getInputStream(); } @Override public int available() throws IOException { return mStream.available(); } @Override public void close() throws IOException { mParcelFd.close(); // must close ParcelFileDescriptor, not just the file descriptor, // since it could be a subclass of ParcelFileDescriptor. // E.g. ContentResolver.ParcelFileDescriptorInner.close() releases // a content provider mFile.close(); // to unmap the memory file from the address space. mStream.close(); // doesn't actually do anything } @Override public FileChannel getChannel() { return null; } @Override public int read() throws IOException { return mStream.read(); } @Override public int read(byte[] buffer, int offset, int count) throws IOException { return mStream.read(buffer, offset, count); } @Override public int read(byte[] buffer) throws IOException { return mStream.read(buffer); } @Override public long skip(long count) throws IOException { return mStream.skip(count); } } /** /** * An OutputStream you can create on a ParcelFileDescriptor, which will * An OutputStream you can create on a ParcelFileDescriptor, which will * take care of calling {@link ParcelFileDescriptor#close * take care of calling {@link ParcelFileDescriptor#close Loading Loading @@ -345,4 +421,16 @@ public class AssetFileDescriptor implements Parcelable { return new AssetFileDescriptor[size]; return new AssetFileDescriptor[size]; } } }; }; /** * Creates an AssetFileDescriptor from a memory file. * * @hide */ public static AssetFileDescriptor fromMemoryFile(MemoryFile memoryFile) throws IOException { ParcelFileDescriptor fd = memoryFile.getParcelFileDescriptor(); return new AssetFileDescriptor(fd, 0, memoryFile.length()); } } } core/java/android/os/MemoryFile.java +112 −5 Original line number Original line Diff line number Diff line Loading @@ -37,9 +37,14 @@ public class MemoryFile { { private static String TAG = "MemoryFile"; private static String TAG = "MemoryFile"; // mmap(2) protection flags from <sys/mman.h> private static final int PROT_READ = 0x1; private static final int PROT_WRITE = 0x2; private static native FileDescriptor native_open(String name, int length) throws IOException; private static native FileDescriptor native_open(String name, int length) throws IOException; // returns memory address for ashmem region // returns memory address for ashmem region private static native int native_mmap(FileDescriptor fd, int length) throws IOException; private static native int native_mmap(FileDescriptor fd, int length, int mode) throws IOException; private static native void native_munmap(int addr, int length) throws IOException; private static native void native_munmap(int addr, int length) throws IOException; private static native void native_close(FileDescriptor fd); private static native void native_close(FileDescriptor fd); private static native int native_read(FileDescriptor fd, int address, byte[] buffer, private static native int native_read(FileDescriptor fd, int address, byte[] buffer, Loading @@ -47,14 +52,16 @@ public class MemoryFile private static native void native_write(FileDescriptor fd, int address, byte[] buffer, private static native void native_write(FileDescriptor fd, int address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException; private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException; private static native boolean native_is_ashmem_region(FileDescriptor fd) throws IOException; private FileDescriptor mFD; // ashmem file descriptor private FileDescriptor mFD; // ashmem file descriptor private int mAddress; // address of ashmem memory private int mAddress; // address of ashmem memory private int mLength; // total length of our ashmem region private int mLength; // total length of our ashmem region private boolean mAllowPurging = false; // true if our ashmem region is unpinned private boolean mAllowPurging = false; // true if our ashmem region is unpinned private final boolean mOwnsRegion; // false if this is a ref to an existing ashmem region /** /** * MemoryFile constructor. * Allocates a new ashmem region. The region is initially not purgable. * * * @param name optional name for the file (can be null). * @param name optional name for the file (can be null). * @param length of the memory file in bytes. * @param length of the memory file in bytes. Loading @@ -63,11 +70,43 @@ public class MemoryFile public MemoryFile(String name, int length) throws IOException { public MemoryFile(String name, int length) throws IOException { mLength = length; mLength = length; mFD = native_open(name, length); mFD = native_open(name, length); mAddress = native_mmap(mFD, length); mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE); mOwnsRegion = true; } /** * Creates a reference to an existing memory file. Changes to the original file * will be available through this reference. * Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail. * * @param fd File descriptor for an existing memory file, as returned by * {@link #getFileDescriptor()}. This file descriptor will be closed * by {@link #close()}. * @param length Length of the memory file in bytes. * @param mode File mode. Currently only "r" for read-only access is supported. * @throws NullPointerException if <code>fd</code> is null. * @throws IOException If <code>fd</code> does not refer to an existing memory file, * or if the file mode of the existing memory file is more restrictive * than <code>mode</code>. * * @hide */ public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException { if (fd == null) { throw new NullPointerException("File descriptor is null."); } if (!isMemoryFile(fd)) { throw new IllegalArgumentException("Not a memory file."); } mLength = length; mFD = fd; mAddress = native_mmap(mFD, length, modeToProt(mode)); mOwnsRegion = false; } } /** /** * Closes and releases all resources for the memory file. * Closes the memory file. If there are no other open references to the memory * file, it will be deleted. */ */ public void close() { public void close() { deactivate(); deactivate(); Loading @@ -76,7 +115,14 @@ public class MemoryFile } } } } private void deactivate() { /** * 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 */ public void deactivate() { if (!isDeactivated()) { if (!isDeactivated()) { try { try { native_munmap(mAddress, mLength); native_munmap(mAddress, mLength); Loading Loading @@ -135,6 +181,9 @@ public class MemoryFile * @return previous value of allowPurging * @return previous value of allowPurging */ */ synchronized public boolean allowPurging(boolean allowPurging) throws IOException { synchronized public boolean allowPurging(boolean allowPurging) throws IOException { if (!mOwnsRegion) { throw new IOException("Only the owner can make ashmem regions purgable."); } boolean oldValue = mAllowPurging; boolean oldValue = mAllowPurging; if (oldValue != allowPurging) { if (oldValue != allowPurging) { native_pin(mFD, !allowPurging); native_pin(mFD, !allowPurging); Loading Loading @@ -210,6 +259,64 @@ public class MemoryFile native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); } } /** * Gets a ParcelFileDescriptor for the memory file. See {@link #getFileDescriptor()} * for caveats. This must be here to allow classes outside <code>android.os</code< to * make ParcelFileDescriptors from MemoryFiles, as * {@link ParcelFileDescriptor#ParcelFileDescriptor(FileDescriptor)} is package private. * * * @return The file descriptor owned by this memory file object. * The file descriptor is not duplicated. * @throws IOException If the memory file has been closed. * * @hide */ public ParcelFileDescriptor getParcelFileDescriptor() throws IOException { return new ParcelFileDescriptor(getFileDescriptor()); } /** * Gets a FileDescriptor for the memory file. Note that this file descriptor * is only safe to pass to {@link #MemoryFile(FileDescriptor,int)}). It * should not be used with file descriptor operations that expect a file descriptor * for a normal file. * * The returned file descriptor is not duplicated. * * @throws IOException If the memory file has been closed. * * @hide */ public FileDescriptor getFileDescriptor() throws IOException { return mFD; } /** * Checks whether the given file descriptor refers to a memory file. * * @throws IOException If <code>fd</code> is not a valid file descriptor. * * @hide */ public static boolean isMemoryFile(FileDescriptor fd) throws IOException { return native_is_ashmem_region(fd); } /** * Converts a file mode string to a <code>prot</code> value as expected by * native_mmap(). * * @throws IllegalArgumentException if the file mode is invalid. */ private static int modeToProt(String mode) { if ("r".equals(mode)) { return PROT_READ; } else { throw new IllegalArgumentException("Unsupported file mode: '" + mode + "'"); } } private class MemoryInputStream extends InputStream { private class MemoryInputStream extends InputStream { private int mMark = 0; private int mMark = 0; Loading core/jni/android_os_MemoryFile.cpp +26 −4 Original line number Original line Diff line number Diff line Loading @@ -46,10 +46,10 @@ static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring na } } static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor, static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor, jint length) jint length, jint prot) { { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); jint result = (jint)mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); jint result = (jint)mmap(NULL, length, prot, MAP_SHARED, fd, 0); if (!result) if (!result) jniThrowException(env, "java/io/IOException", "mmap failed"); jniThrowException(env, "java/io/IOException", "mmap failed"); return result; return result; Loading Loading @@ -118,14 +118,36 @@ static void android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDe } } } } static jboolean android_os_MemoryFile_is_ashmem_region(JNIEnv* env, jobject clazz, jobject fileDescriptor) { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); // Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region. // ASHMEM_GET_SIZE should succeed for all ashmem regions, and the kernel // should return ENOTTY for all other valid file descriptors int result = ashmem_get_size_region(fd); if (result < 0) { if (errno == ENOTTY) { // ENOTTY means that the ioctl does not apply to this object, // i.e., it is not an ashmem region. return JNI_FALSE; } // Some other error, throw exception jniThrowIOException(env, errno); return JNI_FALSE; } return JNI_TRUE; } static const JNINativeMethod methods[] = { static const JNINativeMethod methods[] = { {"native_open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open}, {"native_open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open}, {"native_mmap", "(Ljava/io/FileDescriptor;I)I", (void*)android_os_MemoryFile_mmap}, {"native_mmap", "(Ljava/io/FileDescriptor;II)I", (void*)android_os_MemoryFile_mmap}, {"native_munmap", "(II)V", (void*)android_os_MemoryFile_munmap}, {"native_munmap", "(II)V", (void*)android_os_MemoryFile_munmap}, {"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close}, {"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close}, {"native_read", "(Ljava/io/FileDescriptor;I[BIIIZ)I", (void*)android_os_MemoryFile_read}, {"native_read", "(Ljava/io/FileDescriptor;I[BIIIZ)I", (void*)android_os_MemoryFile_read}, {"native_write", "(Ljava/io/FileDescriptor;I[BIIIZ)V", (void*)android_os_MemoryFile_write}, {"native_write", "(Ljava/io/FileDescriptor;I[BIIIZ)V", (void*)android_os_MemoryFile_write}, {"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin}, {"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin}, {"native_is_ashmem_region", "(Ljava/io/FileDescriptor;)Z", (void*)android_os_MemoryFile_is_ashmem_region} }; }; static const char* const kClassPathName = "android/os/MemoryFile"; static const char* const kClassPathName = "android/os/MemoryFile"; Loading tests/AndroidTests/AndroidManifest.xml +6 −0 Original line number Original line Diff line number Diff line Loading @@ -206,6 +206,12 @@ <meta-data android:name="com.android.unit_tests.reference" android:resource="@xml/metadata" /> <meta-data android:name="com.android.unit_tests.reference" android:resource="@xml/metadata" /> </provider> </provider> <!-- Application components used for content tests --> <provider android:name=".content.MemoryFileProvider" android:authorities="com.android.unit_tests.content.MemoryFileProvider" android:process=":MemoryFileProvider"> </provider> <!-- Application components used for os tests --> <!-- Application components used for os tests --> <service android:name=".os.MessengerService" <service android:name=".os.MessengerService" Loading tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProvider.java 0 → 100644 +211 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2009 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 com.android.unit_tests.content; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.os.MemoryFile; import android.os.ParcelFileDescriptor; import android.util.Log; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; /** Simple test provider that runs in the local process. */ public class MemoryFileProvider extends ContentProvider { private static final String TAG = "MemoryFileProvider"; private static final String DATA_FILE = "data.bin"; // some random data public static final byte[] TEST_BLOB = new byte[] { -12, 127, 0, 3, 1, 2, 3, 4, 5, 6, 1, -128, -1, -54, -65, 35, -53, -96, -74, -74, -55, -43, -69, 3, 52, -58, -121, 127, 87, -73, 16, -13, -103, -65, -128, -36, 107, 24, 118, -17, 97, 97, -88, 19, -94, -54, 53, 43, 44, -27, -124, 28, -74, 26, 35, -36, 16, -124, -31, -31, -128, -79, 108, 116, 43, -17 }; private SQLiteOpenHelper mOpenHelper; private static final int DATA_ID_BLOB = 1; private static final int HUGE = 2; private static final int FILE = 3; private static final UriMatcher sURLMatcher = new UriMatcher( UriMatcher.NO_MATCH); static { sURLMatcher.addURI("*", "data/#/blob", DATA_ID_BLOB); sURLMatcher.addURI("*", "huge", HUGE); sURLMatcher.addURI("*", "file", FILE); } private static class DatabaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "local.db"; private static final int DATABASE_VERSION = 1; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE data (" + "_id INTEGER PRIMARY KEY," + "_blob TEXT, " + "integer INTEGER);"); // insert alarms ContentValues values = new ContentValues(); values.put("_id", 1); values.put("_blob", TEST_BLOB); values.put("integer", 100); db.insert("data", null, values); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { Log.w(TAG, "Upgrading test database from version " + oldVersion + " to " + currentVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS data"); onCreate(db); } } public MemoryFileProvider() { } @Override public boolean onCreate() { mOpenHelper = new DatabaseHelper(getContext()); try { OutputStream out = getContext().openFileOutput(DATA_FILE, Context.MODE_PRIVATE); out.write(TEST_BLOB); out.close(); } catch (IOException ex) { ex.printStackTrace(); } return true; } @Override public Cursor query(Uri url, String[] projectionIn, String selection, String[] selectionArgs, String sort) { throw new UnsupportedOperationException("query not supported"); } @Override public String getType(Uri url) { int match = sURLMatcher.match(url); switch (match) { case DATA_ID_BLOB: return "application/octet-stream"; case FILE: return "application/octet-stream"; default: throw new IllegalArgumentException("Unknown URL"); } } @Override public AssetFileDescriptor openAssetFile(Uri url, String mode) throws FileNotFoundException { int match = sURLMatcher.match(url); switch (match) { case DATA_ID_BLOB: String sql = "SELECT _blob FROM data WHERE _id=" + url.getPathSegments().get(1); return getBlobColumnAsAssetFile(url, mode, sql); case HUGE: try { MemoryFile memoryFile = new MemoryFile(null, 5000000); memoryFile.writeBytes(TEST_BLOB, 0, 1000000, TEST_BLOB.length); memoryFile.deactivate(); return AssetFileDescriptor.fromMemoryFile(memoryFile); } catch (IOException ex) { throw new FileNotFoundException("Error reading " + url + ":" + ex.toString()); } case FILE: File file = getContext().getFileStreamPath(DATA_FILE); ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); return new AssetFileDescriptor(fd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); default: throw new FileNotFoundException("No files supported by provider at " + url); } } private AssetFileDescriptor getBlobColumnAsAssetFile(Uri url, String mode, String sql) throws FileNotFoundException { if (!"r".equals(mode)) { throw new FileNotFoundException("Mode " + mode + " not supported for " + url); } try { SQLiteDatabase db = mOpenHelper.getReadableDatabase(); MemoryFile file = simpleQueryForBlobMemoryFile(db, sql); if (file == null) throw new FileNotFoundException("No such entry: " + url); AssetFileDescriptor afd = AssetFileDescriptor.fromMemoryFile(file); file.deactivate(); // need to dup and then close? openFileHelper() doesn't do that though return afd; } catch (IOException ex) { throw new FileNotFoundException("Error reading " + url + ":" + ex.toString()); } } private MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql) throws IOException { Cursor cursor = db.rawQuery(sql, null); try { if (!cursor.moveToFirst()) { return null; } byte[] bytes = cursor.getBlob(0); MemoryFile file = new MemoryFile(null, bytes.length); file.writeBytes(bytes, 0, 0, bytes.length); return file; } finally { if (cursor != null) { cursor.close(); } } } @Override public int update(Uri url, ContentValues values, String where, String[] whereArgs) { throw new UnsupportedOperationException("update not supported"); } @Override public Uri insert(Uri url, ContentValues initialValues) { throw new UnsupportedOperationException("insert not supported"); } @Override public int delete(Uri url, String where, String[] whereArgs) { throw new UnsupportedOperationException("delete not supported"); } } Loading
core/java/android/content/res/AssetFileDescriptor.java +88 −0 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.content.res; package android.content.res; import android.os.MemoryFile; import android.os.Parcel; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.Parcelable; Loading @@ -24,6 +25,8 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.IOException; import java.io.InputStream; import java.nio.channels.FileChannel; /** /** * File descriptor of an entry in the AssetManager. This provides your own * File descriptor of an entry in the AssetManager. This provides your own Loading Loading @@ -123,6 +126,13 @@ public class AssetFileDescriptor implements Parcelable { mFd.close(); mFd.close(); } } /** * Checks whether this file descriptor is for a memory file. */ private boolean isMemoryFile() throws IOException { return MemoryFile.isMemoryFile(mFd.getFileDescriptor()); } /** /** * Create and return a new auto-close input stream for this asset. This * Create and return a new auto-close input stream for this asset. This * will either return a full asset {@link AutoCloseInputStream}, or * will either return a full asset {@link AutoCloseInputStream}, or Loading @@ -132,6 +142,12 @@ public class AssetFileDescriptor implements Parcelable { * should only call this once for a particular asset. * should only call this once for a particular asset. */ */ public FileInputStream createInputStream() throws IOException { public FileInputStream createInputStream() throws IOException { if (isMemoryFile()) { if (mLength > Integer.MAX_VALUE) { throw new IOException("File length too large for a memory file: " + mLength); } return new AutoCloseMemoryFileInputStream(mFd, (int)mLength); } if (mLength < 0) { if (mLength < 0) { return new ParcelFileDescriptor.AutoCloseInputStream(mFd); return new ParcelFileDescriptor.AutoCloseInputStream(mFd); } } Loading Loading @@ -261,6 +277,66 @@ public class AssetFileDescriptor implements Parcelable { } } } } /** * An input stream that reads from a MemoryFile and closes it when the stream is closed. * This extends FileInputStream just because {@link #createInputStream} returns * a FileInputStream. All the FileInputStream methods are * overridden to use the MemoryFile instead. */ private static class AutoCloseMemoryFileInputStream extends FileInputStream { private ParcelFileDescriptor mParcelFd; private MemoryFile mFile; private InputStream mStream; public AutoCloseMemoryFileInputStream(ParcelFileDescriptor fd, int length) throws IOException { super(fd.getFileDescriptor()); mParcelFd = fd; mFile = new MemoryFile(fd.getFileDescriptor(), length, "r"); mStream = mFile.getInputStream(); } @Override public int available() throws IOException { return mStream.available(); } @Override public void close() throws IOException { mParcelFd.close(); // must close ParcelFileDescriptor, not just the file descriptor, // since it could be a subclass of ParcelFileDescriptor. // E.g. ContentResolver.ParcelFileDescriptorInner.close() releases // a content provider mFile.close(); // to unmap the memory file from the address space. mStream.close(); // doesn't actually do anything } @Override public FileChannel getChannel() { return null; } @Override public int read() throws IOException { return mStream.read(); } @Override public int read(byte[] buffer, int offset, int count) throws IOException { return mStream.read(buffer, offset, count); } @Override public int read(byte[] buffer) throws IOException { return mStream.read(buffer); } @Override public long skip(long count) throws IOException { return mStream.skip(count); } } /** /** * An OutputStream you can create on a ParcelFileDescriptor, which will * An OutputStream you can create on a ParcelFileDescriptor, which will * take care of calling {@link ParcelFileDescriptor#close * take care of calling {@link ParcelFileDescriptor#close Loading Loading @@ -345,4 +421,16 @@ public class AssetFileDescriptor implements Parcelable { return new AssetFileDescriptor[size]; return new AssetFileDescriptor[size]; } } }; }; /** * Creates an AssetFileDescriptor from a memory file. * * @hide */ public static AssetFileDescriptor fromMemoryFile(MemoryFile memoryFile) throws IOException { ParcelFileDescriptor fd = memoryFile.getParcelFileDescriptor(); return new AssetFileDescriptor(fd, 0, memoryFile.length()); } } }
core/java/android/os/MemoryFile.java +112 −5 Original line number Original line Diff line number Diff line Loading @@ -37,9 +37,14 @@ public class MemoryFile { { private static String TAG = "MemoryFile"; private static String TAG = "MemoryFile"; // mmap(2) protection flags from <sys/mman.h> private static final int PROT_READ = 0x1; private static final int PROT_WRITE = 0x2; private static native FileDescriptor native_open(String name, int length) throws IOException; private static native FileDescriptor native_open(String name, int length) throws IOException; // returns memory address for ashmem region // returns memory address for ashmem region private static native int native_mmap(FileDescriptor fd, int length) throws IOException; private static native int native_mmap(FileDescriptor fd, int length, int mode) throws IOException; private static native void native_munmap(int addr, int length) throws IOException; private static native void native_munmap(int addr, int length) throws IOException; private static native void native_close(FileDescriptor fd); private static native void native_close(FileDescriptor fd); private static native int native_read(FileDescriptor fd, int address, byte[] buffer, private static native int native_read(FileDescriptor fd, int address, byte[] buffer, Loading @@ -47,14 +52,16 @@ public class MemoryFile private static native void native_write(FileDescriptor fd, int address, byte[] buffer, private static native void native_write(FileDescriptor fd, int address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException; private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException; private static native boolean native_is_ashmem_region(FileDescriptor fd) throws IOException; private FileDescriptor mFD; // ashmem file descriptor private FileDescriptor mFD; // ashmem file descriptor private int mAddress; // address of ashmem memory private int mAddress; // address of ashmem memory private int mLength; // total length of our ashmem region private int mLength; // total length of our ashmem region private boolean mAllowPurging = false; // true if our ashmem region is unpinned private boolean mAllowPurging = false; // true if our ashmem region is unpinned private final boolean mOwnsRegion; // false if this is a ref to an existing ashmem region /** /** * MemoryFile constructor. * Allocates a new ashmem region. The region is initially not purgable. * * * @param name optional name for the file (can be null). * @param name optional name for the file (can be null). * @param length of the memory file in bytes. * @param length of the memory file in bytes. Loading @@ -63,11 +70,43 @@ public class MemoryFile public MemoryFile(String name, int length) throws IOException { public MemoryFile(String name, int length) throws IOException { mLength = length; mLength = length; mFD = native_open(name, length); mFD = native_open(name, length); mAddress = native_mmap(mFD, length); mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE); mOwnsRegion = true; } /** * Creates a reference to an existing memory file. Changes to the original file * will be available through this reference. * Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail. * * @param fd File descriptor for an existing memory file, as returned by * {@link #getFileDescriptor()}. This file descriptor will be closed * by {@link #close()}. * @param length Length of the memory file in bytes. * @param mode File mode. Currently only "r" for read-only access is supported. * @throws NullPointerException if <code>fd</code> is null. * @throws IOException If <code>fd</code> does not refer to an existing memory file, * or if the file mode of the existing memory file is more restrictive * than <code>mode</code>. * * @hide */ public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException { if (fd == null) { throw new NullPointerException("File descriptor is null."); } if (!isMemoryFile(fd)) { throw new IllegalArgumentException("Not a memory file."); } mLength = length; mFD = fd; mAddress = native_mmap(mFD, length, modeToProt(mode)); mOwnsRegion = false; } } /** /** * Closes and releases all resources for the memory file. * Closes the memory file. If there are no other open references to the memory * file, it will be deleted. */ */ public void close() { public void close() { deactivate(); deactivate(); Loading @@ -76,7 +115,14 @@ public class MemoryFile } } } } private void deactivate() { /** * 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 */ public void deactivate() { if (!isDeactivated()) { if (!isDeactivated()) { try { try { native_munmap(mAddress, mLength); native_munmap(mAddress, mLength); Loading Loading @@ -135,6 +181,9 @@ public class MemoryFile * @return previous value of allowPurging * @return previous value of allowPurging */ */ synchronized public boolean allowPurging(boolean allowPurging) throws IOException { synchronized public boolean allowPurging(boolean allowPurging) throws IOException { if (!mOwnsRegion) { throw new IOException("Only the owner can make ashmem regions purgable."); } boolean oldValue = mAllowPurging; boolean oldValue = mAllowPurging; if (oldValue != allowPurging) { if (oldValue != allowPurging) { native_pin(mFD, !allowPurging); native_pin(mFD, !allowPurging); Loading Loading @@ -210,6 +259,64 @@ public class MemoryFile native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); } } /** * Gets a ParcelFileDescriptor for the memory file. See {@link #getFileDescriptor()} * for caveats. This must be here to allow classes outside <code>android.os</code< to * make ParcelFileDescriptors from MemoryFiles, as * {@link ParcelFileDescriptor#ParcelFileDescriptor(FileDescriptor)} is package private. * * * @return The file descriptor owned by this memory file object. * The file descriptor is not duplicated. * @throws IOException If the memory file has been closed. * * @hide */ public ParcelFileDescriptor getParcelFileDescriptor() throws IOException { return new ParcelFileDescriptor(getFileDescriptor()); } /** * Gets a FileDescriptor for the memory file. Note that this file descriptor * is only safe to pass to {@link #MemoryFile(FileDescriptor,int)}). It * should not be used with file descriptor operations that expect a file descriptor * for a normal file. * * The returned file descriptor is not duplicated. * * @throws IOException If the memory file has been closed. * * @hide */ public FileDescriptor getFileDescriptor() throws IOException { return mFD; } /** * Checks whether the given file descriptor refers to a memory file. * * @throws IOException If <code>fd</code> is not a valid file descriptor. * * @hide */ public static boolean isMemoryFile(FileDescriptor fd) throws IOException { return native_is_ashmem_region(fd); } /** * Converts a file mode string to a <code>prot</code> value as expected by * native_mmap(). * * @throws IllegalArgumentException if the file mode is invalid. */ private static int modeToProt(String mode) { if ("r".equals(mode)) { return PROT_READ; } else { throw new IllegalArgumentException("Unsupported file mode: '" + mode + "'"); } } private class MemoryInputStream extends InputStream { private class MemoryInputStream extends InputStream { private int mMark = 0; private int mMark = 0; Loading
core/jni/android_os_MemoryFile.cpp +26 −4 Original line number Original line Diff line number Diff line Loading @@ -46,10 +46,10 @@ static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring na } } static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor, static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor, jint length) jint length, jint prot) { { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); jint result = (jint)mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); jint result = (jint)mmap(NULL, length, prot, MAP_SHARED, fd, 0); if (!result) if (!result) jniThrowException(env, "java/io/IOException", "mmap failed"); jniThrowException(env, "java/io/IOException", "mmap failed"); return result; return result; Loading Loading @@ -118,14 +118,36 @@ static void android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDe } } } } static jboolean android_os_MemoryFile_is_ashmem_region(JNIEnv* env, jobject clazz, jobject fileDescriptor) { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); // Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region. // ASHMEM_GET_SIZE should succeed for all ashmem regions, and the kernel // should return ENOTTY for all other valid file descriptors int result = ashmem_get_size_region(fd); if (result < 0) { if (errno == ENOTTY) { // ENOTTY means that the ioctl does not apply to this object, // i.e., it is not an ashmem region. return JNI_FALSE; } // Some other error, throw exception jniThrowIOException(env, errno); return JNI_FALSE; } return JNI_TRUE; } static const JNINativeMethod methods[] = { static const JNINativeMethod methods[] = { {"native_open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open}, {"native_open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open}, {"native_mmap", "(Ljava/io/FileDescriptor;I)I", (void*)android_os_MemoryFile_mmap}, {"native_mmap", "(Ljava/io/FileDescriptor;II)I", (void*)android_os_MemoryFile_mmap}, {"native_munmap", "(II)V", (void*)android_os_MemoryFile_munmap}, {"native_munmap", "(II)V", (void*)android_os_MemoryFile_munmap}, {"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close}, {"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close}, {"native_read", "(Ljava/io/FileDescriptor;I[BIIIZ)I", (void*)android_os_MemoryFile_read}, {"native_read", "(Ljava/io/FileDescriptor;I[BIIIZ)I", (void*)android_os_MemoryFile_read}, {"native_write", "(Ljava/io/FileDescriptor;I[BIIIZ)V", (void*)android_os_MemoryFile_write}, {"native_write", "(Ljava/io/FileDescriptor;I[BIIIZ)V", (void*)android_os_MemoryFile_write}, {"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin}, {"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin}, {"native_is_ashmem_region", "(Ljava/io/FileDescriptor;)Z", (void*)android_os_MemoryFile_is_ashmem_region} }; }; static const char* const kClassPathName = "android/os/MemoryFile"; static const char* const kClassPathName = "android/os/MemoryFile"; Loading
tests/AndroidTests/AndroidManifest.xml +6 −0 Original line number Original line Diff line number Diff line Loading @@ -206,6 +206,12 @@ <meta-data android:name="com.android.unit_tests.reference" android:resource="@xml/metadata" /> <meta-data android:name="com.android.unit_tests.reference" android:resource="@xml/metadata" /> </provider> </provider> <!-- Application components used for content tests --> <provider android:name=".content.MemoryFileProvider" android:authorities="com.android.unit_tests.content.MemoryFileProvider" android:process=":MemoryFileProvider"> </provider> <!-- Application components used for os tests --> <!-- Application components used for os tests --> <service android:name=".os.MessengerService" <service android:name=".os.MessengerService" Loading
tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProvider.java 0 → 100644 +211 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2009 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 com.android.unit_tests.content; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.os.MemoryFile; import android.os.ParcelFileDescriptor; import android.util.Log; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; /** Simple test provider that runs in the local process. */ public class MemoryFileProvider extends ContentProvider { private static final String TAG = "MemoryFileProvider"; private static final String DATA_FILE = "data.bin"; // some random data public static final byte[] TEST_BLOB = new byte[] { -12, 127, 0, 3, 1, 2, 3, 4, 5, 6, 1, -128, -1, -54, -65, 35, -53, -96, -74, -74, -55, -43, -69, 3, 52, -58, -121, 127, 87, -73, 16, -13, -103, -65, -128, -36, 107, 24, 118, -17, 97, 97, -88, 19, -94, -54, 53, 43, 44, -27, -124, 28, -74, 26, 35, -36, 16, -124, -31, -31, -128, -79, 108, 116, 43, -17 }; private SQLiteOpenHelper mOpenHelper; private static final int DATA_ID_BLOB = 1; private static final int HUGE = 2; private static final int FILE = 3; private static final UriMatcher sURLMatcher = new UriMatcher( UriMatcher.NO_MATCH); static { sURLMatcher.addURI("*", "data/#/blob", DATA_ID_BLOB); sURLMatcher.addURI("*", "huge", HUGE); sURLMatcher.addURI("*", "file", FILE); } private static class DatabaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "local.db"; private static final int DATABASE_VERSION = 1; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE data (" + "_id INTEGER PRIMARY KEY," + "_blob TEXT, " + "integer INTEGER);"); // insert alarms ContentValues values = new ContentValues(); values.put("_id", 1); values.put("_blob", TEST_BLOB); values.put("integer", 100); db.insert("data", null, values); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { Log.w(TAG, "Upgrading test database from version " + oldVersion + " to " + currentVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS data"); onCreate(db); } } public MemoryFileProvider() { } @Override public boolean onCreate() { mOpenHelper = new DatabaseHelper(getContext()); try { OutputStream out = getContext().openFileOutput(DATA_FILE, Context.MODE_PRIVATE); out.write(TEST_BLOB); out.close(); } catch (IOException ex) { ex.printStackTrace(); } return true; } @Override public Cursor query(Uri url, String[] projectionIn, String selection, String[] selectionArgs, String sort) { throw new UnsupportedOperationException("query not supported"); } @Override public String getType(Uri url) { int match = sURLMatcher.match(url); switch (match) { case DATA_ID_BLOB: return "application/octet-stream"; case FILE: return "application/octet-stream"; default: throw new IllegalArgumentException("Unknown URL"); } } @Override public AssetFileDescriptor openAssetFile(Uri url, String mode) throws FileNotFoundException { int match = sURLMatcher.match(url); switch (match) { case DATA_ID_BLOB: String sql = "SELECT _blob FROM data WHERE _id=" + url.getPathSegments().get(1); return getBlobColumnAsAssetFile(url, mode, sql); case HUGE: try { MemoryFile memoryFile = new MemoryFile(null, 5000000); memoryFile.writeBytes(TEST_BLOB, 0, 1000000, TEST_BLOB.length); memoryFile.deactivate(); return AssetFileDescriptor.fromMemoryFile(memoryFile); } catch (IOException ex) { throw new FileNotFoundException("Error reading " + url + ":" + ex.toString()); } case FILE: File file = getContext().getFileStreamPath(DATA_FILE); ParcelFileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); return new AssetFileDescriptor(fd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); default: throw new FileNotFoundException("No files supported by provider at " + url); } } private AssetFileDescriptor getBlobColumnAsAssetFile(Uri url, String mode, String sql) throws FileNotFoundException { if (!"r".equals(mode)) { throw new FileNotFoundException("Mode " + mode + " not supported for " + url); } try { SQLiteDatabase db = mOpenHelper.getReadableDatabase(); MemoryFile file = simpleQueryForBlobMemoryFile(db, sql); if (file == null) throw new FileNotFoundException("No such entry: " + url); AssetFileDescriptor afd = AssetFileDescriptor.fromMemoryFile(file); file.deactivate(); // need to dup and then close? openFileHelper() doesn't do that though return afd; } catch (IOException ex) { throw new FileNotFoundException("Error reading " + url + ":" + ex.toString()); } } private MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql) throws IOException { Cursor cursor = db.rawQuery(sql, null); try { if (!cursor.moveToFirst()) { return null; } byte[] bytes = cursor.getBlob(0); MemoryFile file = new MemoryFile(null, bytes.length); file.writeBytes(bytes, 0, 0, bytes.length); return file; } finally { if (cursor != null) { cursor.close(); } } } @Override public int update(Uri url, ContentValues values, String where, String[] whereArgs) { throw new UnsupportedOperationException("update not supported"); } @Override public Uri insert(Uri url, ContentValues initialValues) { throw new UnsupportedOperationException("insert not supported"); } @Override public int delete(Uri url, String where, String[] whereArgs) { throw new UnsupportedOperationException("delete not supported"); } }