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.widget.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertSame;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.reset;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.verifyZeroInteractions;
31 
32 import android.content.Context;
33 import android.database.Cursor;
34 import android.database.DataSetObserver;
35 import android.database.sqlite.SQLiteDatabase;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.widget.CursorTreeAdapter;
40 import android.widget.Filter;
41 import android.widget.FilterQueryProvider;
42 import android.widget.TextView;
43 
44 import androidx.test.InstrumentationRegistry;
45 import androidx.test.annotation.UiThreadTest;
46 import androidx.test.filters.MediumTest;
47 import androidx.test.runner.AndroidJUnit4;
48 
49 import org.junit.After;
50 import org.junit.Before;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 
54 import java.io.File;
55 
56 /**
57  * Test {@link CursorTreeAdapter}.
58  */
59 @MediumTest
60 @RunWith(AndroidJUnit4.class)
61 public class CursorTreeAdapterTest {
62     private static final int NAME_INDEX = 1;
63     private static final int VALUE_INDEX = 1;
64     private static final String GROUP_ONE         = "group_one";
65     private static final String GROUP_TWO         = "group_two";
66     private static final String CHILD_VALUE_ONE   = "child_value_one";
67     private static final String CHILD_VALUE_TWO   = "child_value_two";
68     private static final String CHILD_VALUE_THREE = "child_value_three";
69     private static final String[] NAME_PROJECTION = new String[] {
70         "_id",           // 0
71         "name"           // 1
72     };
73 
74     private static final String[] VALUE_PROJECTION = new String[] {
75         "_id",            // 0
76         "value"           // 1
77     };
78 
79     private SQLiteDatabase mDatabase;
80     private File mDatabaseFile;
81     private Cursor mGroupCursor;
82     private Cursor mChildCursor1;
83     private Cursor mChildCursor2;
84     private ViewGroup mParent;
85     private Context mContext;
86 
createGroupCursor()87     private Cursor createGroupCursor() {
88         mDatabase.execSQL("CREATE TABLE group_table (_id INTEGER PRIMARY KEY, name TEXT);");
89         mDatabase.execSQL("INSERT INTO group_table (name) VALUES ('" + GROUP_ONE + "');");
90         mDatabase.execSQL("INSERT INTO group_table (name) VALUES ('" + GROUP_TWO + "');");
91         return mDatabase.query("group_table", NAME_PROJECTION, null, null, null, null, null);
92     }
93 
createChild1Cursor()94     private Cursor createChild1Cursor() {
95         mDatabase.execSQL("CREATE TABLE child1 (_id INTEGER PRIMARY KEY, value TEXT);");
96         mDatabase.execSQL("INSERT INTO child1 (value) VALUES ('" + CHILD_VALUE_ONE + "');");
97         mDatabase.execSQL("INSERT INTO child1 (value) VALUES ('" + CHILD_VALUE_TWO + "');");
98         return getChild1Cursor();
99     }
100 
getChild1Cursor()101     private Cursor getChild1Cursor() {
102         return mDatabase.query("child1", VALUE_PROJECTION, null, null, null, null, null);
103     }
104 
createChild2Cursor()105     private Cursor createChild2Cursor() {
106         mDatabase.execSQL("CREATE TABLE child2 (_id INTEGER PRIMARY KEY, value TEXT);");
107         mDatabase.execSQL("INSERT INTO child2 (value) VALUES ('" + CHILD_VALUE_THREE + "');");
108         return getChild2Cursor();
109     }
110 
getChild2Cursor()111     private Cursor getChild2Cursor() {
112         return mDatabase.query("child2", VALUE_PROJECTION, null, null, null, null, null);
113     }
114 
115     @Before
setup()116     public void setup() {
117         mContext = InstrumentationRegistry.getTargetContext();
118         File dbDir = mContext.getDir("tests", Context.MODE_PRIVATE);
119         mDatabaseFile = new File(dbDir, "database_test.db");
120         if (mDatabaseFile.exists()) {
121             mDatabaseFile.delete();
122         }
123         mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
124         assertNotNull(mDatabase);
125 
126         mGroupCursor = createGroupCursor();
127         assertNotNull(mGroupCursor);
128 
129         mChildCursor1 = createChild1Cursor();
130         assertNotNull(mChildCursor1);
131 
132         mChildCursor2 = createChild2Cursor();
133         assertNotNull(mChildCursor2);
134 
135         final LayoutInflater inflater = LayoutInflater.from(mContext);
136         mParent = (ViewGroup)inflater.inflate(R.layout.cursoradapter_host, null);
137         assertNotNull(mParent);
138     }
139 
140     @After
teardown()141     public void teardown() {
142         if (null != mGroupCursor) {
143             mGroupCursor.close();
144             mGroupCursor = null;
145         }
146 
147         if (null != mChildCursor1) {
148             mChildCursor1.close();
149             mChildCursor1 = null;
150         }
151 
152         if (null != mChildCursor2) {
153             mChildCursor2.close();
154             mChildCursor2 = null;
155         }
156         mDatabase.close();
157         mDatabaseFile.delete();
158     }
159 
160     @UiThreadTest
161     @Test
testConstructor()162     public void testConstructor() {
163         new MockCursorTreeAdapter(mGroupCursor, mContext);
164 
165         new MockCursorTreeAdapter(null, null);
166 
167         new MockCursorTreeAdapter(mGroupCursor, mContext, true);
168 
169         new MockCursorTreeAdapter(null, null, false);
170     }
171 
172     @UiThreadTest
173     @Test
testGetCursor()174     public void testGetCursor() {
175         CursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
176         assertSame(mGroupCursor, adapter.getCursor());
177 
178         adapter.changeCursor(null);
179         assertNull(adapter.getCursor());
180 
181         adapter.setGroupCursor(mGroupCursor);
182         assertSame(mGroupCursor, adapter.getCursor());
183     }
184 
185     @UiThreadTest
186     @Test
testSetGroupCursor()187     public void testSetGroupCursor() {
188         CursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
189         assertSame(mGroupCursor, adapter.getCursor());
190 
191         adapter.setGroupCursor(null);
192         assertNull(adapter.getCursor());
193 
194         adapter.setGroupCursor(mGroupCursor);
195         assertSame(mGroupCursor, adapter.getCursor());
196     }
197 
198     @UiThreadTest
199     @Test
testSetChildrenCursor()200     public void testSetChildrenCursor() {
201         MockCursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
202         assertTrue(mGroupCursor.moveToFirst());
203 
204         assertSame(mChildCursor1, adapter.getChild(0, 0));
205         adapter.setChildrenCursor(0, mChildCursor2);
206         assertSame(mChildCursor2, adapter.getChild(0, 0));
207     }
208 
209     @UiThreadTest
210     @Test
testChangeCursor()211     public void testChangeCursor() {
212         CursorTreeAdapter adapter = new MockCursorTreeAdapter(null, mContext);
213         assertNull(adapter.getCursor());
214 
215         adapter.changeCursor(mGroupCursor);
216         assertSame(mGroupCursor, adapter.getCursor());
217 
218         adapter.changeCursor(null);
219         assertNull(adapter.getCursor());
220     }
221 
222     @UiThreadTest
223     @Test
testNotifyDataSetChangedBoolean()224     public void testNotifyDataSetChangedBoolean() {
225         MockCursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
226         DataSetObserver mockDataSetObserver = mock(DataSetObserver.class);
227         adapter.registerDataSetObserver(mockDataSetObserver);
228 
229         // mChildrenCursorHelpers is empty
230         verifyZeroInteractions(mockDataSetObserver);
231         adapter.notifyDataSetChanged(false);
232         verify(mockDataSetObserver, times(1)).onChanged();
233 
234         // add group 0 into mChildrenCursorHelpers
235         adapter.getChild(0, 0);
236         // add group 1 into mChildrenCursorHelpers
237         adapter.getChild(1, 0);
238         reset(mockDataSetObserver);
239         verifyZeroInteractions(mockDataSetObserver);
240 
241         adapter.notifyDataSetChanged(true);
242         verify(mockDataSetObserver, times(1)).onChanged();
243         adapter.reset();
244         adapter.getChild(0, 0);
245         assertTrue(adapter.hasAddedChild1IntoCache());
246         adapter.getChild(1, 0);
247         assertTrue(adapter.hasAddedChild2IntoCache());
248 
249         // both group 0 and group 1 are in mChildrenCursorHelpers
250         reset(mockDataSetObserver);
251         verifyZeroInteractions(mockDataSetObserver);
252         // does not release cursors
253         adapter.notifyDataSetChanged(false);
254         verify(mockDataSetObserver, times(1)).onChanged();
255         adapter.reset();
256         adapter.getChild(0, 0);
257         assertFalse(adapter.hasAddedChild1IntoCache());
258         adapter.getChild(1, 0);
259         assertFalse(adapter.hasAddedChild2IntoCache());
260     }
261 
262     @UiThreadTest
263     @Test
testNotifyDataSetChanged()264     public void testNotifyDataSetChanged() {
265         MockCursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
266         DataSetObserver mockDataSetObserver = mock(DataSetObserver.class);
267         adapter.registerDataSetObserver(mockDataSetObserver);
268 
269         // mChildrenCursorHelpers is empty
270         verifyZeroInteractions(mockDataSetObserver);
271         adapter.notifyDataSetChanged();
272         verify(mockDataSetObserver, times(1)).onChanged();
273 
274         // add group 0 into mChildrenCursorHelpers
275         adapter.getChild(0, 0);
276         // add group 1 into mChildrenCursorHelpers
277         adapter.getChild(1, 0);
278         reset(mockDataSetObserver);
279         verifyZeroInteractions(mockDataSetObserver);
280         adapter.notifyDataSetChanged();
281         verify(mockDataSetObserver, times(1)).onChanged();
282         adapter.reset();
283         adapter.getChild(0, 0);
284         assertTrue(adapter.hasAddedChild1IntoCache());
285         adapter.getChild(1, 0);
286         assertTrue(adapter.hasAddedChild2IntoCache());
287     }
288 
289     @UiThreadTest
290     @Test
testNotifyDataSetInvalidated()291     public void testNotifyDataSetInvalidated() {
292         MockCursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
293         DataSetObserver mockDataSetObserver = mock(DataSetObserver.class);
294         adapter.registerDataSetObserver(mockDataSetObserver);
295 
296         verifyZeroInteractions(mockDataSetObserver);
297         // mChildrenCursorHelpers is empty
298         adapter.notifyDataSetInvalidated();
299         verify(mockDataSetObserver, times(1)).onInvalidated();
300 
301         // add group 0 into mChildrenCursorHelpers
302         adapter.getChild(0, 0);
303         // add group 1 into mChildrenCursorHelpers
304         adapter.getChild(1, 0);
305         reset(mockDataSetObserver);
306         verifyZeroInteractions(mockDataSetObserver);
307         adapter.notifyDataSetInvalidated();
308         verify(mockDataSetObserver, times(1)).onInvalidated();
309         adapter.reset();
310         adapter.getChild(0, 0);
311         assertTrue(adapter.hasAddedChild1IntoCache());
312         adapter.getChild(1, 0);
313         assertTrue(adapter.hasAddedChild2IntoCache());
314     }
315 
316     @UiThreadTest
317     @Test
testOnGroupCollapsed()318     public void testOnGroupCollapsed() {
319         MockCursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
320 
321         // mChildrenCursorHelpers is empty
322         adapter.onGroupCollapsed(0);
323 
324         // add group 0 into mChildrenCursorHelpers
325         adapter.getChild(0, 0);
326         // add group 1 into mChildrenCursorHelpers
327         adapter.getChild(1, 0);
328         adapter.reset();
329 
330         // remove group 0 from mChildrenCursorHelpers
331         adapter.onGroupCollapsed(0);
332         // add group 0 into mChildrenCursorHelpers again
333         adapter.getChild(0, 0);
334         assertTrue(adapter.hasAddedChild1IntoCache());
335         // should not add group 1 into mChildrenCursorHelpers again
336         adapter.getChild(1, 0);
337         assertFalse(adapter.hasAddedChild2IntoCache());
338 
339         adapter.reset();
340         adapter.onGroupCollapsed(1);
341         adapter.getChild(0, 0);
342         assertFalse(adapter.hasAddedChild1IntoCache());
343         adapter.getChild(1, 0);
344         assertTrue(adapter.hasAddedChild2IntoCache());
345 
346         // groupPosition is out of bound
347         assertEquals(2, adapter.getGroupCount());
348         try {
349             adapter.onGroupCollapsed(2);
350             fail("should throw NullPointerException");
351         } catch (NullPointerException e) {
352         }
353     }
354 
355     @UiThreadTest
356     @Test
testHasStableIds()357     public void testHasStableIds() {
358         CursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
359         assertTrue(adapter.hasStableIds());
360 
361         adapter  = new MockCursorTreeAdapter(null, null);
362         assertTrue(adapter.hasStableIds());
363     }
364 
365     @UiThreadTest
366     @Test
testIsChildSelectable()367     public void testIsChildSelectable() {
368         CursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
369         assertTrue(adapter.isChildSelectable(0, 0));
370         assertTrue(adapter.isChildSelectable(100, 100));
371 
372         adapter  = new MockCursorTreeAdapter(null, null);
373         assertTrue(adapter.isChildSelectable(0, 0));
374     }
375 
376     @UiThreadTest
377     @Test
testConvertToString()378     public void testConvertToString() {
379         CursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
380         assertEquals("", adapter.convertToString(null));
381 
382         assertEquals(mGroupCursor.toString(), adapter.convertToString(mGroupCursor));
383     }
384 
385     @UiThreadTest
386     @Test
testGetFilter()387     public void testGetFilter() {
388         MockCursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
389         Filter filter = adapter.getFilter();
390         assertNotNull(filter);
391         // to prove filter instanceof CursorFilter
392         assertFalse(adapter.hasCalledConvertToString());
393         assertEquals(adapter.convertToString(mGroupCursor),
394                 filter.convertResultToString(mGroupCursor));
395         assertTrue(adapter.hasCalledConvertToString());
396     }
397 
398     @UiThreadTest
399     @Test
testAccessQueryProvider()400     public void testAccessQueryProvider() {
401         CursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
402         FilterQueryProvider filterProvider = new MockFilterQueryProvider();
403         assertNotNull(filterProvider);
404 
405         assertNull(adapter.getFilterQueryProvider());
406 
407         adapter.setFilterQueryProvider(filterProvider);
408         assertSame(filterProvider, adapter.getFilterQueryProvider());
409     }
410 
411     @UiThreadTest
412     @Test
testRunQueryOnBackgroundThread()413     public void testRunQueryOnBackgroundThread() {
414         CursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
415         final String constraint = "constraint";
416 
417         // FilterQueryProvider is null, return mGroupCursor
418         assertSame(mGroupCursor, adapter.runQueryOnBackgroundThread(constraint));
419 
420         FilterQueryProvider filterProvider = new MockFilterQueryProvider();
421         assertNotNull(filterProvider);
422         adapter.setFilterQueryProvider(filterProvider);
423         assertNull(adapter.runQueryOnBackgroundThread(constraint));
424     }
425 
426     @UiThreadTest
427     @Test
testGetGroup()428     public void testGetGroup() {
429         CursorTreeAdapter adapter = new MockCursorTreeAdapter(null, mContext);
430 
431         // null group cursor
432         assertNull(adapter.getGroup(0));
433 
434         adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
435         Cursor retCursor = adapter.getGroup(0);
436         assertEquals(2, retCursor.getCount());
437         assertEquals(GROUP_ONE, retCursor.getString(NAME_INDEX));
438 
439         retCursor = adapter.getGroup(1);
440         assertEquals(GROUP_TWO, retCursor.getString(NAME_INDEX));
441 
442         // groupPosition is out of bound
443         assertEquals(2, adapter.getGroupCount());
444         assertNull(adapter.getGroup(2));
445     }
446 
447     @UiThreadTest
448     @Test
testGetGroupCount()449     public void testGetGroupCount() {
450         CursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
451         assertEquals(mGroupCursor.getCount(), adapter.getGroupCount());
452 
453         adapter.setGroupCursor(null);
454         assertEquals(0, adapter.getGroupCount());
455     }
456 
457     @UiThreadTest
458     @Test
testGetGroupId()459     public void testGetGroupId() {
460         CursorTreeAdapter adapter = new MockCursorTreeAdapter(null, mContext);
461 
462         // null group cursor
463         assertEquals(0, adapter.getGroupId(0));
464 
465         adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
466         assertEquals(1, adapter.getGroupId(0));
467         assertEquals(2, adapter.getGroupId(1));
468 
469         // groupPosition is out of bound
470         assertEquals(2, adapter.getGroupCount());
471         assertEquals(0, adapter.getGroupId(2));
472     }
473 
474     @UiThreadTest
475     @Test
testGetGroupView()476     public void testGetGroupView() {
477         final String expectedStr = "getGroupView test";
478         TextView retView;
479         TextView textView = new TextView(mContext);
480         textView.setText(expectedStr);
481         CursorTreeAdapter adapter = new MockCursorTreeAdapter(null, mContext);
482 
483         // null cursor
484         try {
485             assertNull(adapter.getCursor());
486             adapter.getGroupView(0, true, textView, mParent);
487             fail("does not throw IllegalStateException when cursor is invalid");
488         } catch (IllegalStateException e) {
489         }
490 
491         // groupPosition is out of bound
492         adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
493         try {
494             assertEquals(2, adapter.getGroupCount());
495             adapter.getGroupView(10, true, textView, mParent);
496             fail("does not throw IllegalStateException when position is out of bound");
497         } catch (IllegalStateException e) {
498         }
499 
500         // null convertView
501         retView = (TextView) adapter.getGroupView(1, true, null, mParent);
502         assertNotNull(retView);
503         assertEquals(GROUP_TWO, retView.getText().toString());
504 
505         // convertView is not null
506         retView = (TextView) adapter.getGroupView(0, true, textView, mParent);
507         assertNotNull(retView);
508         assertEquals(GROUP_ONE, retView.getText().toString());
509     }
510 
511     @UiThreadTest
512     @Test
testGetChild()513     public void testGetChild() {
514         CursorTreeAdapter adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
515         assertEquals(2, adapter.getGroupCount());
516         // groupPosition is out of bound.
517         try {
518             adapter.getChild(3, 1);
519             fail("should throw NullPointerException");
520         } catch (NullPointerException e) {
521         }
522 
523         Cursor retCursor = adapter.getChild(0, 0);
524         assertSame(mChildCursor1, retCursor);
525         assertEquals(CHILD_VALUE_ONE, retCursor.getString(VALUE_INDEX));
526 
527         retCursor = adapter.getChild(0, 1);
528         assertSame(mChildCursor1, retCursor);
529         assertEquals(CHILD_VALUE_TWO, retCursor.getString(VALUE_INDEX));
530 
531         // childPosition is out of bound
532         assertEquals(2, adapter.getChildrenCount(0));
533         assertNull(adapter.getChild(0, 2));
534 
535         retCursor = adapter.getChild(1, 0);
536         assertSame(mChildCursor2, retCursor);
537         assertEquals(CHILD_VALUE_THREE, retCursor.getString(VALUE_INDEX));
538     }
539 
540     @UiThreadTest
541     @Test
testGetChildId()542     public void testGetChildId() {
543         CursorTreeAdapter adapter = new MockCursorTreeAdapter(null, mContext);
544 
545         // null group cursor
546         try {
547             adapter.getChildId(0, 0);
548             fail("should throw NullPointerException");
549         } catch (NullPointerException e) {
550         }
551 
552         adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
553         assertEquals(1, adapter.getChildId(0, 0));
554         assertEquals(2, adapter.getChildId(0, 1));
555         assertEquals(1, adapter.getChildId(1, 0));
556 
557         // groupPosition is out of bound
558         assertEquals(2, adapter.getGroupCount());
559         try {
560             adapter.getChildId(3, 0);
561             fail("should throw NullPointerException");
562         } catch (NullPointerException e) {
563         }
564 
565         // childPosition is out of bound
566         assertEquals(2, adapter.getChildrenCount(0));
567         assertEquals(0, adapter.getChildId(0, 2));
568     }
569 
570     @UiThreadTest
571     @Test
testGetChildrenCount()572     public void testGetChildrenCount() {
573         CursorTreeAdapter adapter = new MockCursorTreeAdapter(null, mContext);
574 
575         // null group cursor
576         assertEquals(0, adapter.getChildrenCount(0));
577 
578         adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
579         assertEquals(2, adapter.getChildrenCount(0));
580         assertEquals(1, adapter.getChildrenCount(1));
581 
582         // groupPosition is out of bound
583         assertEquals(2, adapter.getGroupCount());
584         assertEquals(0, adapter.getChildrenCount(2));
585     }
586 
587     @UiThreadTest
588     @Test
testGetChildView()589     public void testGetChildView() {
590         final String expectedStr = "getChildView test";
591         TextView retView;
592         TextView textView = new TextView(mContext);
593         textView.setText(expectedStr);
594         CursorTreeAdapter adapter = new MockCursorTreeAdapter(null, mContext);
595 
596         // null cursor
597         assertNull(adapter.getCursor());
598         try {
599             adapter.getChildView(0, 0, true, textView, mParent);
600             fail("should throw NullPointerException");
601         } catch (NullPointerException e) {
602         }
603 
604         // groupPosition is out of bound
605         adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
606         try {
607             adapter.getChildView(10, 0, true, textView, mParent);
608             fail("should throw NullPointerException");
609         } catch (NullPointerException e) {
610         }
611 
612         // childPosition is out of bound
613         adapter = new MockCursorTreeAdapter(mGroupCursor, mContext);
614         try {
615             assertEquals(2, adapter.getChildrenCount(0));
616             adapter.getChildView(0, 2, true, textView, mParent);
617             fail("does not throw IllegalStateException when position is out of bound");
618         } catch (IllegalStateException e) {
619         }
620 
621         // null convertView
622         retView = (TextView) adapter.getChildView(1, 0, true, null, mParent);
623         assertNotNull(retView);
624         assertEquals(CHILD_VALUE_THREE, retView.getText().toString());
625 
626         // convertView is not null
627         retView = (TextView) adapter.getChildView(0, 1, true, textView, mParent);
628         assertNotNull(retView);
629         assertEquals(CHILD_VALUE_TWO, retView.getText().toString());
630     }
631 
632     private final class MockCursorTreeAdapter extends CursorTreeAdapter {
633         private boolean mHasAddedChild1IntoCache = false;
634         private boolean mHasAddedChild2IntoCache = false;
635         private boolean mHasCalledConvertToString = false;
636 
MockCursorTreeAdapter(Cursor cursor, Context context)637         public MockCursorTreeAdapter(Cursor cursor, Context context) {
638             super(cursor, context);
639         }
640 
MockCursorTreeAdapter(Cursor cursor, Context context, boolean autoRequery)641         public MockCursorTreeAdapter(Cursor cursor, Context context, boolean autoRequery) {
642             super(cursor, context, autoRequery);
643         }
644 
hasCalledConvertToString()645         public boolean hasCalledConvertToString() {
646             return mHasCalledConvertToString;
647         }
648 
649         @Override
convertToString(Cursor cursor)650         public String convertToString(Cursor cursor) {
651             mHasCalledConvertToString = true;
652             return super.convertToString(cursor);
653         }
654 
655         @Override
bindChildView(View view, Context context, Cursor cursor, boolean isLastChild)656         public void bindChildView(View view, Context context,
657                 Cursor cursor, boolean isLastChild) {
658             if (null == context || null == cursor || null == view) {
659                 return;
660             }
661 
662             if (view instanceof TextView) {
663                 String name = cursor.getString(VALUE_INDEX);
664                 TextView textView = (TextView) view;
665                 textView.setText(name);
666             }
667         }
668 
669         @Override
bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded)670         public void bindGroupView(View view, Context context,
671                 Cursor cursor, boolean isExpanded) {
672             if (null == context || null == cursor || null == view) {
673                 return;
674             }
675 
676             if (view instanceof TextView) {
677                 String name = cursor.getString(NAME_INDEX);
678                 TextView textView = (TextView) view;
679                 textView.setText(name);
680             }
681         }
682 
hasAddedChild1IntoCache()683         public boolean hasAddedChild1IntoCache() {
684             return mHasAddedChild1IntoCache;
685         }
686 
hasAddedChild2IntoCache()687         public boolean hasAddedChild2IntoCache() {
688             return mHasAddedChild2IntoCache;
689         }
690 
691         @Override
getChildrenCursor(Cursor groupCursor)692         protected Cursor getChildrenCursor(Cursor groupCursor) {
693             if (null == groupCursor) {
694                 return null;
695             }
696 
697             if (0 == groupCursor.getPosition()) {
698                 mHasAddedChild1IntoCache = true;
699                 if (mChildCursor1.isClosed()) {
700                     mChildCursor1 = getChild1Cursor();
701                 }
702                 return mChildCursor1;
703             } else if (1 == groupCursor.getPosition()) {
704                 mHasAddedChild2IntoCache = true;
705                 if (mChildCursor2.isClosed()) {
706                     mChildCursor2 = getChild2Cursor();
707                 }
708                 return mChildCursor2;
709             }
710             return null;
711         }
712 
713         @Override
newChildView(Context context, Cursor cursor, boolean isLastChild, ViewGroup parent)714         public View newChildView(Context context, Cursor cursor,
715                 boolean isLastChild, ViewGroup parent) {
716             if (null == context || null == cursor || null == parent) {
717                 return null;
718             }
719 
720             String childValue = cursor.getString(VALUE_INDEX);
721             TextView textView = new TextView(context);
722             textView.setText(childValue);
723             return textView;
724         }
725 
726         @Override
newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent)727         public View newGroupView(Context context, Cursor cursor,
728                 boolean isExpanded, ViewGroup parent) {
729             if (null == context || null == cursor || null == parent) {
730                 return null;
731             }
732 
733             String groupName = cursor.getString(NAME_INDEX);
734             TextView textView = new TextView(context);
735             textView.setText(groupName);
736             return textView;
737         }
738 
reset()739         public void reset() {
740             mHasAddedChild1IntoCache = false;
741             mHasAddedChild2IntoCache = false;
742             mHasCalledConvertToString = false;
743         }
744     }
745 
746     private final class MockFilterQueryProvider implements FilterQueryProvider {
runQuery(CharSequence constraint)747         public Cursor runQuery(CharSequence constraint) {
748             return null;
749         }
750     }
751 }
752