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