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