1/*
2 * Copyright (C) 2013 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 */
16package com.android.mail.content;
17
18import android.database.CharArrayBuffer;
19import android.database.Cursor;
20import android.database.CursorWrapper;
21import android.os.Bundle;
22
23import com.android.mail.providers.UIProvider;
24import com.android.mail.utils.LogTag;
25import com.android.mail.utils.LogUtils;
26
27public class ThreadSafeCursorWrapper extends CursorWrapper {
28    private static final String LOG_TAG = LogTag.getLogTag();
29
30    private final ThreadLocal<Integer> mPosition;
31    private final Object mLock = new Object();
32
33    public ThreadSafeCursorWrapper(Cursor cursor) {
34        super(cursor);
35
36        mPosition = new ThreadLocal<Integer>() {
37            @Override
38            protected Integer initialValue() {
39                return Integer.valueOf(-1);
40            }
41        };
42    }
43
44    @Override
45    public String getString(int column) {
46        synchronized (mLock) {
47            moveToCurrent();
48            return super.getString(column);
49        }
50    }
51
52    @Override
53    public short getShort(int column) {
54        synchronized (mLock) {
55            moveToCurrent();
56            return super.getShort(column);
57        }
58    }
59
60    @Override
61    public int getInt(int column) {
62        synchronized (mLock) {
63            moveToCurrent();
64            return super.getInt(column);
65        }
66    }
67
68    @Override
69    public long getLong(int column) {
70        synchronized (mLock) {
71            moveToCurrent();
72            return super.getLong(column);
73        }
74    }
75
76    @Override
77    public float getFloat(int column) {
78        synchronized (mLock) {
79            moveToCurrent();
80            return super.getFloat(column);
81        }
82    }
83
84    @Override
85    public double getDouble(int column) {
86        synchronized (mLock) {
87            moveToCurrent();
88            return super.getDouble(column);
89        }
90    }
91
92    @Override
93    public byte[] getBlob(int column) {
94        synchronized (mLock) {
95            moveToCurrent();
96            return super.getBlob(column);
97        }
98    }
99
100    @Override
101    public Bundle respond(Bundle extras) {
102        final int opts = extras.getInt(UIProvider.ConversationCursorCommand.COMMAND_KEY_OPTIONS);
103        if ((opts & UIProvider.ConversationCursorCommand.OPTION_MOVE_POSITION) != 0) {
104            synchronized (mLock) {
105                moveToCurrent();
106                return super.respond(extras);
107            }
108        } else {
109            return super.respond(extras);
110        }
111    }
112
113    @Override
114    public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
115        synchronized (mLock) {
116            moveToCurrent();
117            super.copyStringToBuffer(columnIndex, buffer);
118        }
119    }
120
121    @Override
122    public boolean isNull(int column){
123        synchronized (mLock) {
124            moveToCurrent();
125            return super.isNull(column);
126        }
127    }
128
129    private void moveToCurrent() {
130        final int pos = mPosition.get();
131        final boolean result = super.moveToPosition(pos);
132
133        // AbstractCursor returns false on negative positions, although Cursor documentation
134        // states that -1 is a valid input. Let's just log positive values as failures.
135        if (!result && pos >= 0) {
136            LogUtils.e(LOG_TAG, "Unexpected failure to move to current position, pos=%d", pos);
137        }
138    }
139
140    @Override
141    public boolean move(int offset) {
142        final int curPos = mPosition.get();
143        return moveToPosition(curPos + offset);
144    }
145
146    @Override
147    public boolean moveToFirst() {
148        return moveToPosition(0);
149    }
150
151    @Override
152    public boolean moveToLast() {
153        return moveToPosition(getCount() - 1);
154    }
155
156    @Override
157    public boolean moveToNext() {
158        final int curPos = mPosition.get();
159        return moveToPosition(curPos + 1);
160    }
161
162    @Override
163    public boolean moveToPosition(int position) {
164        // Make sure position isn't past the end of the cursor
165        final int count = getCount();
166        if (position >= count) {
167            mPosition.set(count);
168            return false;
169        }
170
171        // Make sure position isn't before the beginning of the cursor
172        if (position < 0) {
173            mPosition.set(-1);
174            return false;
175        }
176
177        final int curPos = mPosition.get();
178        // Check for no-op moves, and skip the rest of the work for them
179        if (position == curPos) {
180            return true;
181        }
182
183        // Save this thread's current position.
184        mPosition.set(position);
185        return true;
186    }
187
188    @Override
189    public boolean moveToPrevious() {
190        final int curPos = mPosition.get();
191        return moveToPosition(curPos - 1);
192    }
193
194    @Override
195    public int getPosition() {
196        return mPosition.get();
197    }
198}
199