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 */ 16 package com.android.mail.content; 17 18 import android.database.CharArrayBuffer; 19 import android.database.Cursor; 20 import android.database.CursorWrapper; 21 import android.os.Bundle; 22 23 import com.android.mail.providers.UIProvider; 24 import com.android.mail.utils.LogTag; 25 import com.android.mail.utils.LogUtils; 26 27 public 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 ThreadSafeCursorWrapper(Cursor cursor)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 getString(int column)45 public String getString(int column) { 46 synchronized (mLock) { 47 moveToCurrent(); 48 return super.getString(column); 49 } 50 } 51 52 @Override getShort(int column)53 public short getShort(int column) { 54 synchronized (mLock) { 55 moveToCurrent(); 56 return super.getShort(column); 57 } 58 } 59 60 @Override getInt(int column)61 public int getInt(int column) { 62 synchronized (mLock) { 63 moveToCurrent(); 64 return super.getInt(column); 65 } 66 } 67 68 @Override getLong(int column)69 public long getLong(int column) { 70 synchronized (mLock) { 71 moveToCurrent(); 72 return super.getLong(column); 73 } 74 } 75 76 @Override getFloat(int column)77 public float getFloat(int column) { 78 synchronized (mLock) { 79 moveToCurrent(); 80 return super.getFloat(column); 81 } 82 } 83 84 @Override getDouble(int column)85 public double getDouble(int column) { 86 synchronized (mLock) { 87 moveToCurrent(); 88 return super.getDouble(column); 89 } 90 } 91 92 @Override getBlob(int column)93 public byte[] getBlob(int column) { 94 synchronized (mLock) { 95 moveToCurrent(); 96 return super.getBlob(column); 97 } 98 } 99 100 @Override respond(Bundle extras)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 copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)114 public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { 115 synchronized (mLock) { 116 moveToCurrent(); 117 super.copyStringToBuffer(columnIndex, buffer); 118 } 119 } 120 121 @Override isNull(int column)122 public boolean isNull(int column){ 123 synchronized (mLock) { 124 moveToCurrent(); 125 return super.isNull(column); 126 } 127 } 128 moveToCurrent()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 move(int offset)141 public boolean move(int offset) { 142 final int curPos = mPosition.get(); 143 return moveToPosition(curPos + offset); 144 } 145 146 @Override moveToFirst()147 public boolean moveToFirst() { 148 return moveToPosition(0); 149 } 150 151 @Override moveToLast()152 public boolean moveToLast() { 153 return moveToPosition(getCount() - 1); 154 } 155 156 @Override moveToNext()157 public boolean moveToNext() { 158 final int curPos = mPosition.get(); 159 return moveToPosition(curPos + 1); 160 } 161 162 @Override moveToPosition(int position)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 moveToPrevious()189 public boolean moveToPrevious() { 190 final int curPos = mPosition.get(); 191 return moveToPosition(curPos - 1); 192 } 193 194 @Override getPosition()195 public int getPosition() { 196 return mPosition.get(); 197 } 198 } 199