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.database.cts;
18 
19 
20 import android.content.Context;
21 import android.database.AbstractCursor;
22 import android.database.CharArrayBuffer;
23 import android.database.ContentObserver;
24 import android.database.CursorIndexOutOfBoundsException;
25 import android.database.CursorWindow;
26 import android.database.DataSetObserver;
27 import android.database.sqlite.SQLiteDatabase;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.test.InstrumentationTestCase;
31 
32 import java.io.File;
33 import java.util.ArrayList;
34 import java.util.Random;
35 
36 /**
37  * Test {@link AbstractCursor}.
38  */
39 public class AbstractCursorTest extends InstrumentationTestCase {
40     private static final int POSITION0 = 0;
41     private static final int POSITION1 = 1;
42     private  static final int ROW_MAX = 10;
43     private static final int DATA_COUNT = 10;
44     private static final String[] COLUMN_NAMES1 = new String[] {
45         "_id",             // 0
46         "number"           // 1
47     };
48     private static final String[] COLUMN_NAMES = new String[] { "name", "number", "profit" };
49     private TestAbstractCursor mTestAbstractCursor;
50     private Object mLockObj = new Object();
51 
52     private SQLiteDatabase mDatabase;
53     private File mDatabaseFile;
54     private AbstractCursor mDatabaseCursor;
55 
56     @Override
setUp()57     protected void setUp() throws Exception {
58         super.setUp();
59 
60         setupDatabase();
61         ArrayList<ArrayList> list = createTestList(ROW_MAX, COLUMN_NAMES.length);
62         mTestAbstractCursor = new TestAbstractCursor(COLUMN_NAMES, list);
63     }
64 
65     @Override
tearDown()66     protected void tearDown() throws Exception {
67         mDatabaseCursor.close();
68         mTestAbstractCursor.close();
69         mDatabase.close();
70         if (mDatabaseFile.exists()) {
71             mDatabaseFile.delete();
72         }
73         super.tearDown();
74     }
75 
testConstructor()76     public void testConstructor() {
77         TestAbstractCursor abstractCursor = new TestAbstractCursor();
78         assertEquals(-1, abstractCursor.getPosition());
79     }
80 
testGetBlob()81     public void testGetBlob() {
82         try {
83             mTestAbstractCursor.getBlob(0);
84             fail("getBlob should throws a UnsupportedOperationException here");
85         } catch (UnsupportedOperationException e) {
86             // expected
87         }
88     }
89 
testRegisterDataSetObserver()90     public void testRegisterDataSetObserver() {
91         MockDataSetObserver datasetObserver = new MockDataSetObserver();
92 
93         try {
94             mDatabaseCursor.unregisterDataSetObserver(datasetObserver);
95             fail("Can't unregister DataSetObserver before it is registered.");
96         } catch (IllegalStateException e) {
97             // expected
98         }
99 
100         mDatabaseCursor.registerDataSetObserver(datasetObserver);
101 
102         try {
103             mDatabaseCursor.registerDataSetObserver(datasetObserver);
104             fail("Can't register DataSetObserver twice before unregister it.");
105         } catch (IllegalStateException e) {
106             // expected
107         }
108 
109         mDatabaseCursor.unregisterDataSetObserver(datasetObserver);
110         mDatabaseCursor.registerDataSetObserver(datasetObserver);
111     }
112 
testRegisterContentObserver()113     public void testRegisterContentObserver() {
114         MockContentObserver contentObserver = new MockContentObserver();
115 
116         try {
117             mDatabaseCursor.unregisterContentObserver(contentObserver);
118             fail("Can't unregister ContentObserver before it is registered.");
119         } catch (IllegalStateException e) {
120             // expected
121         }
122 
123         mDatabaseCursor.registerContentObserver(contentObserver);
124 
125         try {
126             mDatabaseCursor.registerContentObserver(contentObserver);
127             fail("Can't register DataSetObserver twice before unregister it.");
128         } catch (IllegalStateException e) {
129             // expected
130         }
131 
132         mDatabaseCursor.unregisterContentObserver(contentObserver);
133         mDatabaseCursor.registerContentObserver(contentObserver);
134     }
135 
testSetNotificationUri()136     public void testSetNotificationUri() {
137         String MOCK_URI = "content://abstractrcursortest/testtable";
138         mDatabaseCursor.setNotificationUri(getInstrumentation().getContext().getContentResolver(),
139                 Uri.parse(MOCK_URI));
140     }
141 
testRespond()142     public void testRespond() {
143         Bundle b = new Bundle();
144         Bundle bundle = mDatabaseCursor.respond(b);
145         assertSame(Bundle.EMPTY, bundle);
146 
147         bundle = mDatabaseCursor.respond(null);
148         assertSame(Bundle.EMPTY, bundle);
149     }
150 
testRequery()151     public void testRequery() {
152         MockDataSetObserver mock = new MockDataSetObserver();
153         mDatabaseCursor.registerDataSetObserver(mock);
154         assertFalse(mock.hadCalledOnChanged());
155         mDatabaseCursor.requery();
156         assertTrue(mock.hadCalledOnChanged());
157     }
158 
testOnChange()159     public void testOnChange() throws InterruptedException {
160         MockContentObserver mock = new MockContentObserver();
161         mTestAbstractCursor.registerContentObserver(mock);
162         assertFalse(mock.hadCalledOnChange());
163         mTestAbstractCursor.onChange(true);
164         synchronized(mLockObj) {
165             if ( !mock.hadCalledOnChange() ) {
166                 mLockObj.wait(5000);
167             }
168         }
169         assertTrue(mock.hadCalledOnChange());
170     }
171 
testOnMove()172     public void testOnMove() {
173         mTestAbstractCursor.resetOnMoveRet();
174         assertFalse(mTestAbstractCursor.getOnMoveRet());
175         mTestAbstractCursor.moveToFirst();
176         mTestAbstractCursor.moveToPosition(5);
177         assertTrue(mTestAbstractCursor.getOnMoveRet());
178         assertEquals(0, mTestAbstractCursor.getOldPos());
179         assertEquals(5, mTestAbstractCursor.getNewPos());
180     }
181 
testMoveToPrevious()182     public void testMoveToPrevious() {
183         // Test moveToFirst, isFirst, moveToNext, getPosition
184         assertTrue(mDatabaseCursor.moveToFirst());
185         assertTrue(mDatabaseCursor.isFirst());
186         assertEquals(0, mDatabaseCursor.getPosition());
187         assertTrue(mDatabaseCursor.moveToNext());
188         assertEquals(1, mDatabaseCursor.getPosition());
189         assertFalse(mDatabaseCursor.isFirst());
190         assertTrue(mDatabaseCursor.moveToNext());
191         assertEquals(2, mDatabaseCursor.getPosition());
192 
193         // invoke moveToPosition with a number larger than row count.
194         assertFalse(mDatabaseCursor.moveToPosition(30000));
195         assertEquals(mDatabaseCursor.getCount(), mDatabaseCursor.getPosition());
196 
197         assertFalse(mDatabaseCursor.moveToPosition(-1));
198         assertEquals(-1, mDatabaseCursor.getPosition());
199         assertTrue(mDatabaseCursor.isBeforeFirst());
200 
201         mDatabaseCursor.moveToPosition(5);
202         assertEquals(5, mDatabaseCursor.getPosition());
203 
204         // Test moveToPrevious
205         assertTrue(mDatabaseCursor.moveToPrevious());
206         assertEquals(4, mDatabaseCursor.getPosition());
207         assertTrue(mDatabaseCursor.moveToPrevious());
208         assertEquals(3, mDatabaseCursor.getPosition());
209         assertTrue(mDatabaseCursor.moveToPrevious());
210         assertEquals(2, mDatabaseCursor.getPosition());
211 
212         // Test moveToLast, isLast, moveToPrevius, isAfterLast.
213         assertFalse(mDatabaseCursor.isLast());
214         assertTrue(mDatabaseCursor.moveToLast());
215         assertTrue(mDatabaseCursor.isLast());
216         assertFalse(mDatabaseCursor.isAfterLast());
217 
218         assertFalse(mDatabaseCursor.moveToNext());
219         assertTrue(mDatabaseCursor.isAfterLast());
220         assertFalse(mDatabaseCursor.moveToNext());
221         assertTrue(mDatabaseCursor.isAfterLast());
222         assertFalse(mDatabaseCursor.isLast());
223         assertTrue(mDatabaseCursor.moveToPrevious());
224         assertTrue(mDatabaseCursor.isLast());
225         assertTrue(mDatabaseCursor.moveToPrevious());
226         assertFalse(mDatabaseCursor.isLast());
227 
228         // Test move(int).
229         mDatabaseCursor.moveToFirst();
230         assertEquals(0, mDatabaseCursor.getPosition());
231         assertFalse(mDatabaseCursor.move(-1));
232         assertEquals(-1, mDatabaseCursor.getPosition());
233         assertTrue(mDatabaseCursor.move(1));
234         assertEquals(0, mDatabaseCursor.getPosition());
235 
236         assertTrue(mDatabaseCursor.move(5));
237         assertEquals(5, mDatabaseCursor.getPosition());
238         assertTrue(mDatabaseCursor.move(-1));
239         assertEquals(4, mDatabaseCursor.getPosition());
240 
241         mDatabaseCursor.moveToLast();
242         assertTrue(mDatabaseCursor.isLast());
243         assertFalse(mDatabaseCursor.isAfterLast());
244         assertFalse(mDatabaseCursor.move(1));
245         assertFalse(mDatabaseCursor.isLast());
246         assertTrue(mDatabaseCursor.isAfterLast());
247         assertTrue(mDatabaseCursor.move(-1));
248         assertTrue(mDatabaseCursor.isLast());
249         assertFalse(mDatabaseCursor.isAfterLast());
250     }
251 
testIsClosed()252     public void testIsClosed() {
253         assertFalse(mDatabaseCursor.isClosed());
254         mDatabaseCursor.close();
255         assertTrue(mDatabaseCursor.isClosed());
256     }
257 
testGetWindow()258     public void testGetWindow() {
259         CursorWindow window = new CursorWindow(false);
260         assertEquals(0, window.getNumRows());
261         // fill window from position 0
262         mDatabaseCursor.fillWindow(0, window);
263 
264         assertNotNull(mDatabaseCursor.getWindow());
265         assertEquals(mDatabaseCursor.getCount(), window.getNumRows());
266 
267         while (mDatabaseCursor.moveToNext()) {
268             assertEquals(mDatabaseCursor.getInt(POSITION1),
269                     window.getInt(mDatabaseCursor.getPosition(), POSITION1));
270         }
271         window.clear();
272     }
273 
testGetWantsAllOnMoveCalls()274     public void testGetWantsAllOnMoveCalls() {
275         assertFalse(mDatabaseCursor.getWantsAllOnMoveCalls());
276     }
277 
testIsFieldUpdated()278     public void testIsFieldUpdated() {
279         mTestAbstractCursor.moveToFirst();
280         assertFalse(mTestAbstractCursor.isFieldUpdated(0));
281     }
282 
testGetUpdatedField()283     public void testGetUpdatedField() {
284         mTestAbstractCursor.moveToFirst();
285         assertNull(mTestAbstractCursor.getUpdatedField(0));
286     }
287 
testGetExtras()288     public void testGetExtras() {
289         assertSame(Bundle.EMPTY, mDatabaseCursor.getExtras());
290     }
291 
testGetCount()292     public void testGetCount() {
293         assertEquals(DATA_COUNT, mDatabaseCursor.getCount());
294     }
295 
testGetColumnNames()296     public void testGetColumnNames() {
297         String[] names = mDatabaseCursor.getColumnNames();
298         assertEquals(COLUMN_NAMES1.length, names.length);
299 
300         for (int i = 0; i < COLUMN_NAMES1.length; i++) {
301             assertEquals(COLUMN_NAMES1[i], names[i]);
302         }
303     }
304 
testGetColumnName()305     public void testGetColumnName() {
306         assertEquals(COLUMN_NAMES1[0], mDatabaseCursor.getColumnName(0));
307         assertEquals(COLUMN_NAMES1[1], mDatabaseCursor.getColumnName(1));
308     }
309 
testGetColumnIndexOrThrow()310     public void testGetColumnIndexOrThrow() {
311         final String COLUMN_FAKE = "fake_name";
312         assertEquals(POSITION0, mDatabaseCursor.getColumnIndex(COLUMN_NAMES1[POSITION0]));
313         assertEquals(POSITION1, mDatabaseCursor.getColumnIndex(COLUMN_NAMES1[POSITION1]));
314         assertEquals(POSITION0, mDatabaseCursor.getColumnIndexOrThrow(COLUMN_NAMES1[POSITION0]));
315         assertEquals(POSITION1, mDatabaseCursor.getColumnIndexOrThrow(COLUMN_NAMES1[POSITION1]));
316 
317         try {
318             mDatabaseCursor.getColumnIndexOrThrow(COLUMN_FAKE);
319             fail("IllegalArgumentException expected, but not thrown");
320         } catch (IllegalArgumentException expected) {
321             // expected
322         }
323     }
324 
testGetColumnIndex()325     public void testGetColumnIndex() {
326         assertEquals(POSITION0, mDatabaseCursor.getColumnIndex(COLUMN_NAMES1[POSITION0]));
327         assertEquals(POSITION1, mDatabaseCursor.getColumnIndex(COLUMN_NAMES1[POSITION1]));
328     }
329 
testGetColumnCount()330     public void testGetColumnCount() {
331         assertEquals(COLUMN_NAMES1.length, mDatabaseCursor.getColumnCount());
332     }
333 
testDeactivate()334     public void testDeactivate() {
335         MockDataSetObserver mock = new MockDataSetObserver();
336         mDatabaseCursor.registerDataSetObserver(mock);
337         assertFalse(mock.hadCalledOnInvalid());
338         mDatabaseCursor.deactivate();
339         assertTrue(mock.hadCalledOnInvalid());
340     }
341 
testCopyStringToBuffer()342     public void testCopyStringToBuffer() {
343         CharArrayBuffer ca = new CharArrayBuffer(1000);
344         mTestAbstractCursor.moveToFirst();
345         mTestAbstractCursor.copyStringToBuffer(0, ca);
346         CursorWindow window = new CursorWindow(false);
347         mTestAbstractCursor.fillWindow(0, window);
348 
349         StringBuffer sb = new StringBuffer();
350         sb.append(window.getString(0, 0));
351         String str = mTestAbstractCursor.getString(0);
352         assertEquals(str.length(), ca.sizeCopied);
353         assertEquals(sb.toString(), new String(ca.data, 0, ca.sizeCopied));
354     }
355 
testCheckPosition()356     public void testCheckPosition() {
357         // Test with position = -1.
358         try {
359             mTestAbstractCursor.checkPosition();
360             fail("copyStringToBuffer() should throws CursorIndexOutOfBoundsException here.");
361         } catch (CursorIndexOutOfBoundsException e) {
362             // expected
363         }
364 
365         // Test with position = count.
366         assertTrue(mTestAbstractCursor.moveToPosition(mTestAbstractCursor.getCount() - 1));
367         mTestAbstractCursor.checkPosition();
368 
369         try {
370             assertFalse(mTestAbstractCursor.moveToPosition(mTestAbstractCursor.getCount()));
371             assertEquals(mTestAbstractCursor.getCount(), mTestAbstractCursor.getPosition());
372             mTestAbstractCursor.checkPosition();
373             fail("copyStringToBuffer() should throws CursorIndexOutOfBoundsException here.");
374         } catch (CursorIndexOutOfBoundsException e) {
375             // expected
376         }
377     }
378 
testSetExtras()379     public void testSetExtras() {
380         Bundle b = new Bundle();
381         mTestAbstractCursor.setExtras(b);
382         assertSame(b, mTestAbstractCursor.getExtras());
383     }
384 
385     @SuppressWarnings("unchecked")
createTestList(int rows, int cols)386     private static ArrayList<ArrayList> createTestList(int rows, int cols) {
387         ArrayList<ArrayList> list = new ArrayList<ArrayList>();
388         Random ran = new Random();
389 
390         for (int i = 0; i < rows; i++) {
391             ArrayList<Integer> col = new ArrayList<Integer>();
392             list.add(col);
393 
394             for (int j = 0; j < cols; j++) {
395                 // generate random number
396                 Integer r = ran.nextInt();
397                 col.add(r);
398             }
399         }
400 
401         return list;
402     }
403 
setupDatabase()404     private void setupDatabase() {
405         File dbDir = getInstrumentation().getTargetContext().getDir("tests",
406                 Context.MODE_WORLD_WRITEABLE);
407         mDatabaseFile = new File(dbDir, "database_test.db");
408         if (mDatabaseFile.exists()) {
409             mDatabaseFile.delete();
410         }
411         mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
412         assertNotNull(mDatabaseFile);
413         mDatabase.execSQL("CREATE TABLE test1 (_id INTEGER PRIMARY KEY, number TEXT);");
414         generateData();
415         mDatabaseCursor = (AbstractCursor) mDatabase.query("test1", null, null, null, null, null,
416                 null);
417     }
418 
generateData()419     private void generateData() {
420         for ( int i = 0; i < DATA_COUNT; i++) {
421             mDatabase.execSQL("INSERT INTO test1 (number) VALUES ('" + i + "');");
422         }
423     }
424 
425     private class TestAbstractCursor extends AbstractCursor {
426         private boolean mOnMoveReturnValue;
427         private int mOldPosition;
428         private int mNewPosition;
429         private String[] mColumnNames;
430         private ArrayList<Object>[] mRows;
431         private boolean mHadCalledOnChange = false;
432 
TestAbstractCursor()433         public TestAbstractCursor() {
434             super();
435         }
436         @SuppressWarnings("unchecked")
TestAbstractCursor(String[] columnNames, ArrayList<ArrayList> rows)437         public TestAbstractCursor(String[] columnNames, ArrayList<ArrayList> rows) {
438             int colCount = columnNames.length;
439             boolean foundID = false;
440 
441             // Add an _id column if not in columnNames
442             for (int i = 0; i < colCount; ++i) {
443                 if (columnNames[i].compareToIgnoreCase("_id") == 0) {
444                     mColumnNames = columnNames;
445                     foundID = true;
446                     break;
447                 }
448             }
449 
450             if (!foundID) {
451                 mColumnNames = new String[colCount + 1];
452                 System.arraycopy(columnNames, 0, mColumnNames, 0, columnNames.length);
453                 mColumnNames[colCount] = "_id";
454             }
455 
456             int rowCount = rows.size();
457             mRows = new ArrayList[rowCount];
458 
459             for (int i = 0; i < rowCount; ++i) {
460                 mRows[i] = rows.get(i);
461 
462                 if (!foundID) {
463                     mRows[i].add(Long.valueOf(i));
464                 }
465             }
466         }
467 
getOnMoveRet()468         public boolean getOnMoveRet() {
469             return mOnMoveReturnValue;
470         }
471 
resetOnMoveRet()472         public void resetOnMoveRet() {
473             mOnMoveReturnValue = false;
474         }
475 
getOldPos()476         public int getOldPos() {
477             return mOldPosition;
478         }
479 
getNewPos()480         public int getNewPos() {
481             return mNewPosition;
482         }
483 
484         @Override
onMove(int oldPosition, int newPosition)485         public boolean onMove(int oldPosition, int newPosition) {
486             mOnMoveReturnValue = super.onMove(oldPosition, newPosition);
487             mOldPosition = oldPosition;
488             mNewPosition = newPosition;
489             return mOnMoveReturnValue;
490         }
491 
492         @Override
getCount()493         public int getCount() {
494             return mRows.length;
495         }
496 
497         @Override
getColumnNames()498         public String[] getColumnNames() {
499             return mColumnNames;
500         }
501 
502         @Override
getString(int columnIndex)503         public String getString(int columnIndex) {
504             Object cell = mRows[mPos].get(columnIndex);
505             return (cell == null) ? null : cell.toString();
506         }
507 
508         @Override
getShort(int columnIndex)509         public short getShort(int columnIndex) {
510             Number num = (Number) mRows[mPos].get(columnIndex);
511             return num.shortValue();
512         }
513 
514         @Override
getInt(int columnIndex)515         public int getInt(int columnIndex) {
516             Number num = (Number) mRows[mPos].get(columnIndex);
517             return num.intValue();
518         }
519 
520         @Override
getLong(int columnIndex)521         public long getLong(int columnIndex) {
522             Number num = (Number) mRows[mPos].get(columnIndex);
523             return num.longValue();
524         }
525 
526         @Override
getFloat(int columnIndex)527         public float getFloat(int columnIndex) {
528             Number num = (Number) mRows[mPos].get(columnIndex);
529             return num.floatValue();
530         }
531 
532         @Override
getDouble(int columnIndex)533         public double getDouble(int columnIndex) {
534             Number num = (Number) mRows[mPos].get(columnIndex);
535             return num.doubleValue();
536         }
537 
538         @Override
isNull(int column)539         public boolean isNull(int column) {
540             return false;
541         }
542 
hadCalledOnChange()543         public boolean hadCalledOnChange() {
544             return mHadCalledOnChange;
545         }
546 
547         // the following are protected methods
548         @Override
checkPosition()549         protected void checkPosition() {
550             super.checkPosition();
551         }
552 
553         @Override
getUpdatedField(int columnIndex)554         protected Object getUpdatedField(int columnIndex) {
555             return super.getUpdatedField(columnIndex);
556         }
557 
558         @Override
isFieldUpdated(int columnIndex)559         protected boolean isFieldUpdated(int columnIndex) {
560             return super.isFieldUpdated(columnIndex);
561         }
562 
563         @Override
onChange(boolean selfChange)564         protected void onChange(boolean selfChange) {
565             super.onChange(selfChange);
566             mHadCalledOnChange = true;
567         }
568     }
569 
570     private class MockContentObserver extends ContentObserver {
571         public boolean mHadCalledOnChange;
572 
MockContentObserver()573         public MockContentObserver() {
574             super(null);
575         }
576 
577         @Override
onChange(boolean selfChange)578         public void onChange(boolean selfChange) {
579             super.onChange(selfChange);
580             mHadCalledOnChange = true;
581             synchronized(mLockObj) {
582                 mLockObj.notify();
583             }
584         }
585 
586         @Override
deliverSelfNotifications()587         public boolean deliverSelfNotifications() {
588             return true;
589         }
590 
hadCalledOnChange()591         public boolean hadCalledOnChange() {
592             return mHadCalledOnChange;
593         }
594     }
595 
596     private class MockDataSetObserver extends DataSetObserver {
597         private boolean mHadCalledOnChanged;
598         private boolean mHadCalledOnInvalid;
599 
600         @Override
onChanged()601         public void onChanged() {
602             super.onChanged();
603             mHadCalledOnChanged = true;
604         }
605 
606         @Override
onInvalidated()607         public void onInvalidated() {
608             super.onInvalidated();
609             mHadCalledOnInvalid = true;
610         }
611 
hadCalledOnChanged()612         public boolean hadCalledOnChanged() {
613             return mHadCalledOnChanged;
614         }
615 
hadCalledOnInvalid()616         public boolean hadCalledOnInvalid() {
617             return mHadCalledOnInvalid;
618         }
619     }
620 }
621