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.assertNotNull;
21 import static org.junit.Assert.assertNull;
22 import static org.junit.Assert.assertSame;
23 import static org.junit.Assert.fail;
24 import static org.mockito.Matchers.any;
25 import static org.mockito.Matchers.anyInt;
26 import static org.mockito.Matchers.eq;
27 import static org.mockito.Mockito.doReturn;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.reset;
30 import static org.mockito.Mockito.times;
31 import static org.mockito.Mockito.verify;
32 
33 import android.content.Context;
34 import android.database.Cursor;
35 import android.database.MatrixCursor;
36 import android.graphics.Bitmap;
37 import android.graphics.drawable.BitmapDrawable;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.widget.ImageView;
42 import android.widget.LinearLayout;
43 import android.widget.SimpleCursorAdapter;
44 import android.widget.TextView;
45 
46 import androidx.test.InstrumentationRegistry;
47 import androidx.test.annotation.UiThreadTest;
48 import androidx.test.filters.SmallTest;
49 import androidx.test.runner.AndroidJUnit4;
50 
51 import com.android.compatibility.common.util.WidgetTestUtils;
52 
53 import org.junit.Before;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 
57 import java.io.IOException;
58 import java.io.InputStream;
59 import java.io.OutputStream;
60 
61 /**
62  * Test {@link SimpleCursorAdapter}.
63  * The simple cursor adapter's cursor will be set to
64  * {@link SimpleCursorAdapterTest#mCursor} It will use internal
65  * R.layout.simple_list_item_1.
66  */
67 @SmallTest
68 @RunWith(AndroidJUnit4.class)
69 public class SimpleCursorAdapterTest {
70     private static final int ADAPTER_ROW_COUNT = 20;
71 
72     private static final int DEFAULT_COLUMN_COUNT = 2;
73 
74     private static final int[] VIEWS_TO = new int[] { R.id.cursorAdapter_item0 };
75 
76     private static final String[] COLUMNS_FROM = new String[] { "column1" };
77 
78     private static final String SAMPLE_IMAGE_NAME = "testimage.jpg";
79 
80     private Context mContext;
81 
82     /**
83      * The original cursor and its content will be set to:
84      * <TABLE>
85      * <TR>
86      * <TH>Column0</TH>
87      * <TH>Column1</TH>
88      * </TR>
89      * <TR>
90      * <TD>00</TD>
91      * <TD>01</TD>
92      * </TR>
93      * <TR>
94      * <TD>10</TD>
95      * <TD>11</TD>
96      * </TR>
97      * <TR>
98      * <TD>...</TD>
99      * <TD>...</TD>
100      * </TR>
101      * <TR>
102      * <TD>190</TD>
103      * <TD>191</TD>
104      * </TR>
105      * </TABLE>
106      * It has 2 columns and 20 rows
107      */
108     private Cursor mCursor;
109 
110     @Before
setup()111     public void setup() {
112         mContext = InstrumentationRegistry.getTargetContext();
113 
114         mCursor = createTestCursor(DEFAULT_COLUMN_COUNT, ADAPTER_ROW_COUNT);
115     }
116 
makeSimpleCursorAdapter()117     private SimpleCursorAdapter makeSimpleCursorAdapter() {
118         return new SimpleCursorAdapter(
119                 mContext, R.layout.cursoradapter_item0, mCursor, COLUMNS_FROM, VIEWS_TO);
120     }
121 
122     @UiThreadTest
123     @Test
testConstructor()124     public void testConstructor() {
125         new SimpleCursorAdapter(mContext, R.layout.cursoradapter_item0,
126                 createTestCursor(DEFAULT_COLUMN_COUNT, ADAPTER_ROW_COUNT),
127                 COLUMNS_FROM, VIEWS_TO);
128     }
129 
130     @UiThreadTest
131     @Test
testBindView()132     public void testBindView() {
133         SimpleCursorAdapter simpleCursorAdapter = makeSimpleCursorAdapter();
134         TextView listItem = (TextView) simpleCursorAdapter.newView(mContext, null, null);
135 
136         listItem.setText("");
137         mCursor.moveToFirst();
138         simpleCursorAdapter.bindView(listItem, null, mCursor);
139         assertEquals("01", listItem.getText().toString());
140 
141         mCursor.moveToLast();
142         simpleCursorAdapter.bindView(listItem, null, mCursor);
143         assertEquals("191", listItem.getText().toString());
144 
145         // the binder take care of binding
146         listItem.setText("");
147         SimpleCursorAdapter.ViewBinder binder = mock(SimpleCursorAdapter.ViewBinder.class);
148         doReturn(true).when(binder).setViewValue(any(View.class), any(Cursor.class), anyInt());
149         simpleCursorAdapter.setViewBinder(binder);
150         mCursor.moveToFirst();
151         simpleCursorAdapter.bindView(listItem, null, mCursor);
152         verify(binder, times(1)).setViewValue(any(View.class), eq(mCursor), eq(1));
153         assertEquals("", listItem.getText().toString());
154 
155         // the binder try to bind but fail
156         doReturn(false).when(binder).setViewValue(any(View.class), any(Cursor.class), anyInt());
157         reset(binder);
158         mCursor.moveToLast();
159         simpleCursorAdapter.bindView(listItem, null, mCursor);
160         verify(binder, times(1)).setViewValue(any(View.class), eq(mCursor), eq(1));
161         assertEquals("191", listItem.getText().toString());
162 
163         final int [] to = { R.id.cursorAdapter_host };
164         simpleCursorAdapter = new SimpleCursorAdapter(mContext, R.layout.cursoradapter_host,
165                 mCursor, COLUMNS_FROM, to);
166         LinearLayout illegalView = (LinearLayout)simpleCursorAdapter.newView(mContext, null, null);
167         try {
168             // The IllegalStateException already gets thrown in the line above.
169             simpleCursorAdapter.bindView(illegalView, null, mCursor);
170             fail("Should throw IllegalStateException if the view is not TextView or ImageView");
171         } catch (IllegalStateException e) {
172             // expected
173         }
174     }
175 
176     @UiThreadTest
177     @Test
testAccessViewBinder()178     public void testAccessViewBinder() {
179         SimpleCursorAdapter simpleCursorAdapter = makeSimpleCursorAdapter();
180         assertNull(simpleCursorAdapter.getViewBinder());
181 
182         SimpleCursorAdapter.ViewBinder binder = mock(SimpleCursorAdapter.ViewBinder.class);
183         doReturn(true).when(binder).setViewValue(any(View.class), any(Cursor.class), anyInt());
184         simpleCursorAdapter.setViewBinder(binder);
185         assertSame(binder, simpleCursorAdapter.getViewBinder());
186 
187         doReturn(false).when(binder).setViewValue(any(View.class), any(Cursor.class), anyInt());
188         simpleCursorAdapter.setViewBinder(binder);
189         assertSame(binder, simpleCursorAdapter.getViewBinder());
190 
191         simpleCursorAdapter.setViewBinder(null);
192         assertNull(simpleCursorAdapter.getViewBinder());
193     }
194 
195     @UiThreadTest
196     @Test
testSetViewText()197     public void testSetViewText() {
198         SimpleCursorAdapter simpleCursorAdapter = makeSimpleCursorAdapter();
199         TextView view = new TextView(mContext);
200         simpleCursorAdapter.setViewText(view, "expected");
201         assertEquals("expected", view.getText().toString());
202 
203         simpleCursorAdapter.setViewText(view, null);
204         assertEquals("", view.getText().toString());
205     }
206 
207     @UiThreadTest
208     @Test
testSetViewImage()209     public void testSetViewImage() {
210         SimpleCursorAdapter simpleCursorAdapter = makeSimpleCursorAdapter();
211         // resId
212         int sceneryImgResId = R.drawable.scenery;
213         ImageView view = new ImageView(mContext);
214         assertNull(view.getDrawable());
215         simpleCursorAdapter.setViewImage(view, String.valueOf(sceneryImgResId));
216         assertNotNull(view.getDrawable());
217         BitmapDrawable d = (BitmapDrawable) mContext.getResources().getDrawable(
218                 sceneryImgResId);
219         WidgetTestUtils.assertEquals(d.getBitmap(),
220                 ((BitmapDrawable) view.getDrawable()).getBitmap());
221 
222         // blank
223         view = new ImageView(mContext);
224         assertNull(view.getDrawable());
225         simpleCursorAdapter.setViewImage(view, "");
226         assertNull(view.getDrawable());
227 
228         // null
229         view = new ImageView(mContext);
230         assertNull(view.getDrawable());
231         try {
232             // Should declare NullPoinertException if the uri or value is null
233             simpleCursorAdapter.setViewImage(view, null);
234             fail("Should throw NullPointerException if the uri or value is null");
235         } catch (NullPointerException e) {
236             // expected
237         }
238 
239         // uri
240         view = new ImageView(mContext);
241         assertNull(view.getDrawable());
242         try {
243             int testimgRawId = R.raw.testimage;
244             simpleCursorAdapter.setViewImage(view,
245                     createTestImage(mContext, SAMPLE_IMAGE_NAME, testimgRawId));
246             assertNotNull(view.getDrawable());
247             Bitmap actualBitmap = ((BitmapDrawable) view.getDrawable()).getBitmap();
248             Bitmap testBitmap = WidgetTestUtils.getUnscaledAndDitheredBitmap(
249                     mContext.getResources(), testimgRawId, actualBitmap.getConfig());
250             WidgetTestUtils.assertEquals(testBitmap, actualBitmap);
251         } finally {
252             destroyTestImage(mContext, SAMPLE_IMAGE_NAME);
253         }
254     }
255 
256     @UiThreadTest
257     @Test
testAccessStringConversionColumn()258     public void testAccessStringConversionColumn() {
259         SimpleCursorAdapter simpleCursorAdapter = makeSimpleCursorAdapter();
260         // default is -1
261         assertEquals(-1, simpleCursorAdapter.getStringConversionColumn());
262 
263         simpleCursorAdapter.setStringConversionColumn(1);
264         assertEquals(1, simpleCursorAdapter.getStringConversionColumn());
265 
266         // Should check whether the column index is out of bounds
267         simpleCursorAdapter.setStringConversionColumn(Integer.MAX_VALUE);
268         assertEquals(Integer.MAX_VALUE, simpleCursorAdapter.getStringConversionColumn());
269 
270         // Should check whether the column index is out of bounds
271         simpleCursorAdapter.setStringConversionColumn(Integer.MIN_VALUE);
272         assertEquals(Integer.MIN_VALUE, simpleCursorAdapter.getStringConversionColumn());
273     }
274 
275     @UiThreadTest
276     @Test
testAccessCursorToStringConverter()277     public void testAccessCursorToStringConverter() {
278         SimpleCursorAdapter simpleCursorAdapter = makeSimpleCursorAdapter();
279         // default is null
280         assertNull(simpleCursorAdapter.getCursorToStringConverter());
281 
282         SimpleCursorAdapter.CursorToStringConverter converter =
283                 mock(SimpleCursorAdapter.CursorToStringConverter.class);
284         simpleCursorAdapter.setCursorToStringConverter(converter);
285         assertSame(converter, simpleCursorAdapter.getCursorToStringConverter());
286 
287         simpleCursorAdapter.setCursorToStringConverter(null);
288         assertNull(simpleCursorAdapter.getCursorToStringConverter());
289     }
290 
291     @UiThreadTest
292     @Test
testChangeCursor()293     public void testChangeCursor() {
294         SimpleCursorAdapter simpleCursorAdapter = makeSimpleCursorAdapter();
295         // have "column1"
296         Cursor curWith3Columns = createTestCursor(3, ADAPTER_ROW_COUNT);
297         simpleCursorAdapter.changeCursor(curWith3Columns);
298         assertSame(curWith3Columns, simpleCursorAdapter.getCursor());
299 
300         // does not have "column1"
301         Cursor curWith1Column = createTestCursor(1, ADAPTER_ROW_COUNT);
302         try {
303             simpleCursorAdapter.changeCursor(curWith1Column);
304             fail("Should throw exception if the cursor does not have the "
305                     + "original column passed in the constructor");
306         } catch (IllegalArgumentException e) {
307             // expected
308         }
309     }
310 
311     @UiThreadTest
312     @Test
testConvertToString()313     public void testConvertToString() {
314         SimpleCursorAdapter simpleCursorAdapter = makeSimpleCursorAdapter();
315         mCursor.moveToFirst();
316         assertEquals("", simpleCursorAdapter.convertToString(null));
317 
318         // converter is null, StringConversionColumn is set to negative
319         simpleCursorAdapter.setStringConversionColumn(Integer.MIN_VALUE);
320         assertEquals(mCursor.toString(), simpleCursorAdapter.convertToString(mCursor));
321 
322         // converter is null, StringConversionColumn is set to 1
323         simpleCursorAdapter.setStringConversionColumn(1);
324         assertEquals("01", simpleCursorAdapter.convertToString(mCursor));
325 
326         // converter is null, StringConversionColumn is set to 3 (larger than columns count)
327         // the cursor has 3 columns including column0, column1 and _id which is added automatically
328         simpleCursorAdapter.setStringConversionColumn(DEFAULT_COLUMN_COUNT + 1);
329         try {
330             simpleCursorAdapter.convertToString(mCursor);
331             fail("Should throw IndexOutOfBoundsException if index is beyond the columns count");
332         } catch (IndexOutOfBoundsException e) {
333             // expected
334         }
335 
336         Cursor curWith3Columns = createTestCursor(DEFAULT_COLUMN_COUNT + 1, ADAPTER_ROW_COUNT);
337         curWith3Columns.moveToFirst();
338 
339         // converter is null, StringConversionColumn is set to 3
340         // and covert with a cursor which has 4 columns
341         simpleCursorAdapter.setStringConversionColumn(2);
342         assertEquals("02", simpleCursorAdapter.convertToString(curWith3Columns));
343 
344         // converter is not null, StringConversionColumn is 1
345         SimpleCursorAdapter.CursorToStringConverter converter =
346                 mock(SimpleCursorAdapter.CursorToStringConverter.class);
347         simpleCursorAdapter.setCursorToStringConverter(converter);
348         simpleCursorAdapter.setStringConversionColumn(1);
349         simpleCursorAdapter.convertToString(curWith3Columns);
350         verify(converter, times(1)).convertToString(curWith3Columns);
351     }
352 
353     @UiThreadTest
354     @Test
testNewView()355     public void testNewView() {
356         SimpleCursorAdapter simpleCursorAdapter = makeSimpleCursorAdapter();
357         LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(
358                 Context.LAYOUT_INFLATER_SERVICE);
359         ViewGroup viewGroup = (ViewGroup) layoutInflater.inflate(
360                 R.layout.cursoradapter_host, null);
361         View result = simpleCursorAdapter.newView(mContext, null, viewGroup);
362         assertNotNull(result);
363         assertEquals(R.id.cursorAdapter_item0, result.getId());
364 
365         result = simpleCursorAdapter.newView(mContext, null, null);
366         assertNotNull(result);
367         assertEquals(R.id.cursorAdapter_item0, result.getId());
368     }
369 
370     @UiThreadTest
371     @Test
testNewDropDownView()372     public void testNewDropDownView() {
373         SimpleCursorAdapter simpleCursorAdapter = makeSimpleCursorAdapter();
374         LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(
375                 Context.LAYOUT_INFLATER_SERVICE);
376         ViewGroup viewGroup = (ViewGroup) layoutInflater.inflate(
377                 R.layout.cursoradapter_host, null);
378         View result = simpleCursorAdapter.newDropDownView(null, null, viewGroup);
379         assertNotNull(result);
380         assertEquals(R.id.cursorAdapter_item0, result.getId());
381     }
382 
383     @UiThreadTest
384     @Test
testChangeCursorAndColumns()385     public void testChangeCursorAndColumns() {
386         SimpleCursorAdapter simpleCursorAdapter = makeSimpleCursorAdapter();
387         assertSame(mCursor, simpleCursorAdapter.getCursor());
388 
389         TextView listItem = (TextView) simpleCursorAdapter.newView(mContext, null, null);
390 
391         mCursor.moveToFirst();
392         simpleCursorAdapter.bindView(listItem, null, mCursor);
393         assertEquals("01", listItem.getText().toString());
394 
395         mCursor.moveToLast();
396         simpleCursorAdapter.bindView(listItem, null, mCursor);
397         assertEquals("191", listItem.getText().toString());
398 
399         Cursor newCursor = createTestCursor(3, ADAPTER_ROW_COUNT);
400         final String[] from = new String[] { "column2" };
401         simpleCursorAdapter.changeCursorAndColumns(newCursor, from, VIEWS_TO);
402         assertSame(newCursor, simpleCursorAdapter.getCursor());
403         newCursor.moveToFirst();
404         simpleCursorAdapter.bindView(listItem, null, newCursor);
405         assertEquals("02", listItem.getText().toString());
406 
407         newCursor.moveToLast();
408         simpleCursorAdapter.bindView(listItem, null, newCursor);
409         assertEquals("192", listItem.getText().toString());
410 
411         simpleCursorAdapter.changeCursorAndColumns(null, null, null);
412         assertNull(simpleCursorAdapter.getCursor());
413     }
414 
415     /**
416      * Creates the test cursor.
417      *
418      * @param colCount the column count
419      * @param rowCount the row count
420      * @return the cursor
421      */
422     @SuppressWarnings("unchecked")
createTestCursor(int colCount, int rowCount)423     private Cursor createTestCursor(int colCount, int rowCount) {
424         String[] columns = new String[colCount + 1];
425         for (int i = 0; i < colCount; i++) {
426             columns[i] = "column" + i;
427         }
428         columns[colCount] = "_id";
429 
430         MatrixCursor cursor = new MatrixCursor(columns, rowCount);
431         Object[] row = new Object[colCount + 1];
432         for (int i = 0; i < rowCount; i++) {
433             for (int j = 0; j < colCount; j++) {
434                 row[j] = "" + i + "" + j;
435             }
436             row[colCount] = i;
437             cursor.addRow(row);
438         }
439         return cursor;
440     }
441 
createTestImage(Context context, String fileName, int resId)442     public static String createTestImage(Context context, String fileName, int resId) {
443         try (InputStream source = context.getResources().openRawResource(resId);
444              OutputStream target = context.openFileOutput(fileName, Context.MODE_PRIVATE)) {
445             byte[] buffer = new byte[1024];
446             for (int len = source.read(buffer); len > 0; len = source.read(buffer)) {
447                 target.write(buffer, 0, len);
448             }
449         } catch (IOException e) {
450             fail(e.getMessage());
451         }
452 
453         return context.getFileStreamPath(fileName).getAbsolutePath();
454     }
455 
destroyTestImage(Context context, String fileName)456     public static void destroyTestImage(Context context, String fileName) {
457         context.deleteFile(fileName);
458     }
459 }
460