1/*
2 * Copyright (C) 2016 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
17package android.util;
18
19import android.os.Parcel;
20import android.os.ParcelFileDescriptor;
21import android.os.Parcelable;
22
23import libcore.io.IoUtils;
24import dalvik.system.CloseGuard;
25
26import java.io.Closeable;
27import java.io.IOException;
28import java.util.UUID;
29
30/**
31 * This class is an array of integers that is backed by shared memory.
32 * It is useful for efficiently sharing state between processes. The
33 * write and read operations are guaranteed to not result in read/
34 * write memory tear, i.e. they are atomic. However, multiple read/
35 * write operations are <strong>not</strong> synchronized between
36 * each other.
37 * <p>
38 * The data structure is designed to have one owner process that can
39 * read/write. There may be multiple client processes that can only read.
40 * The owner process is the process that created the array. The shared
41 * memory is pinned (not reclaimed by the system) until the owning process
42 * dies or the data structure is closed. This class is <strong>not</strong>
43 * thread safe. You should not interact with an instance of this class
44 * once it is closed. If you pass back to the owner process an instance
45 * it will be read only even in the owning process.
46 * </p>
47 *
48 * @hide
49 */
50public final class MemoryIntArray implements Parcelable, Closeable {
51    private static final String TAG = "MemoryIntArray";
52
53    private static final int MAX_SIZE = 1024;
54
55    private final CloseGuard mCloseGuard = CloseGuard.get();
56
57    private final boolean mIsOwner;
58    private final long mMemoryAddr;
59    private int mFd = -1;
60
61    /**
62     * Creates a new instance.
63     *
64     * @param size The size of the array in terms of integer slots. Cannot be
65     *     more than {@link #getMaxSize()}.
66     * @throws IOException If an error occurs while accessing the shared memory.
67     */
68    public MemoryIntArray(int size) throws IOException {
69        if (size > MAX_SIZE) {
70            throw new IllegalArgumentException("Max size is " + MAX_SIZE);
71        }
72        mIsOwner = true;
73        final String name = UUID.randomUUID().toString();
74        mFd = nativeCreate(name, size);
75        mMemoryAddr = nativeOpen(mFd, mIsOwner);
76        mCloseGuard.open("close");
77    }
78
79    private MemoryIntArray(Parcel parcel) throws IOException {
80        mIsOwner = false;
81        ParcelFileDescriptor pfd = parcel.readParcelable(null);
82        if (pfd == null) {
83            throw new IOException("No backing file descriptor");
84        }
85        mFd = pfd.detachFd();
86        mMemoryAddr = nativeOpen(mFd, mIsOwner);
87        mCloseGuard.open("close");
88    }
89
90    /**
91     * @return Gets whether this array is mutable.
92     */
93    public boolean isWritable() {
94        enforceNotClosed();
95        return mIsOwner;
96    }
97
98    /**
99     * Gets the value at a given index.
100     *
101     * @param index The index.
102     * @return The value at this index.
103     * @throws IOException If an error occurs while accessing the shared memory.
104     */
105    public int get(int index) throws IOException {
106        enforceNotClosed();
107        enforceValidIndex(index);
108        return nativeGet(mFd, mMemoryAddr, index);
109    }
110
111    /**
112     * Sets the value at a given index. This method can be called only if
113     * {@link #isWritable()} returns true which means your process is the
114     * owner.
115     *
116     * @param index The index.
117     * @param value The value to set.
118     * @throws IOException If an error occurs while accessing the shared memory.
119     */
120    public void set(int index, int value) throws IOException {
121        enforceNotClosed();
122        enforceWritable();
123        enforceValidIndex(index);
124        nativeSet(mFd, mMemoryAddr, index, value);
125    }
126
127    /**
128     * Gets the array size.
129     *
130     * @throws IOException If an error occurs while accessing the shared memory.
131     */
132    public int size() throws IOException {
133        enforceNotClosed();
134        return nativeSize(mFd);
135    }
136
137    /**
138     * Closes the array releasing resources.
139     *
140     * @throws IOException If an error occurs while accessing the shared memory.
141     */
142    @Override
143    public void close() throws IOException {
144        if (!isClosed()) {
145            nativeClose(mFd, mMemoryAddr, mIsOwner);
146            mFd = -1;
147            mCloseGuard.close();
148        }
149    }
150
151    /**
152     * @return Whether this array is closed and shouldn't be used.
153     */
154    public boolean isClosed() {
155        return mFd == -1;
156    }
157
158    @Override
159    protected void finalize() throws Throwable {
160        try {
161            if (mCloseGuard != null) {
162                mCloseGuard.warnIfOpen();
163            }
164
165            IoUtils.closeQuietly(this);
166        } finally {
167            super.finalize();
168        }
169    }
170
171    @Override
172    public int describeContents() {
173        return CONTENTS_FILE_DESCRIPTOR;
174    }
175
176    @Override
177    public void writeToParcel(Parcel parcel, int flags) {
178        ParcelFileDescriptor pfd = ParcelFileDescriptor.adoptFd(mFd);
179        try {
180            // Don't let writing to a parcel to close our fd - plz
181            parcel.writeParcelable(pfd, flags & ~Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
182        } finally {
183            pfd.detachFd();
184        }
185    }
186
187    @Override
188    public boolean equals(Object obj) {
189        if (obj == null) {
190            return false;
191        }
192        if (this == obj) {
193            return true;
194        }
195        if (getClass() != obj.getClass()) {
196            return false;
197        }
198        MemoryIntArray other = (MemoryIntArray) obj;
199        return mFd == other.mFd;
200    }
201
202    @Override
203    public int hashCode() {
204        return mFd;
205    }
206
207    private void enforceNotClosed() {
208        if (isClosed()) {
209            throw new IllegalStateException("cannot interact with a closed instance");
210        }
211    }
212
213    private void enforceValidIndex(int index) throws IOException {
214        final int size = size();
215        if (index < 0 || index > size - 1) {
216            throw new IndexOutOfBoundsException(
217                    index + " not between 0 and " + (size - 1));
218        }
219    }
220
221    private void enforceWritable() {
222        if (!isWritable()) {
223            throw new UnsupportedOperationException("array is not writable");
224        }
225    }
226
227    private native int nativeCreate(String name, int size);
228    private native long nativeOpen(int fd, boolean owner);
229    private native void nativeClose(int fd, long memoryAddr, boolean owner);
230    private native int nativeGet(int fd, long memoryAddr, int index);
231    private native void nativeSet(int fd, long memoryAddr, int index, int value);
232    private native int nativeSize(int fd);
233
234    /**
235     * @return The max array size.
236     */
237    public static int getMaxSize() {
238        return MAX_SIZE;
239    }
240
241    public static final Parcelable.Creator<MemoryIntArray> CREATOR =
242            new Parcelable.Creator<MemoryIntArray>() {
243        @Override
244        public MemoryIntArray createFromParcel(Parcel parcel) {
245            try {
246                return new MemoryIntArray(parcel);
247            } catch (IOException ioe) {
248                throw new IllegalArgumentException("Error unparceling MemoryIntArray");
249            }
250        }
251
252        @Override
253        public MemoryIntArray[] newArray(int size) {
254            return new MemoryIntArray[size];
255        }
256    };
257}
258