1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package android.content.cts;
18 
19 
20 import android.content.ContentQueryMap;
21 import android.content.ContentResolver;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.database.Cursor;
25 import android.os.Handler;
26 import android.os.HandlerThread;
27 import android.os.Looper;
28 import android.platform.test.annotations.AppModeSdkSandbox;
29 import android.test.InstrumentationTestCase;
30 import android.test.UiThreadTest;
31 
32 import java.util.Map;
33 import java.util.Observable;
34 import java.util.Observer;
35 
36 /**
37  * Test {@link ContentQueryMap}.
38  */
39 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
40 public class ContentQueryMapTest extends InstrumentationTestCase {
41     private static final int TEST_TIME_OUT = 5000;
42 
43     private static final String NAME0  = "name0";
44     private static final String VALUE0 = "value0";
45     private static final String NAME1  = "name1";
46     private static final String VALUE1 = "value1";
47     private static final String NAME2  = "name2";
48     private static final String VALUE2 = "value2";
49     private static final String NAME3  = "name3";
50     private static final String VALUE3 = "value3";
51 
52     private static final String[] PROJECTIONS = new String[] {
53         DummyProvider.NAME, DummyProvider.VALUE};
54 
55     private static final int ORIGINAL_ROW_COUNT = 2;
56     private ContentResolver mResolver;
57     private Cursor mCursor;
58     private ContentQueryMap mContentQueryMap;
59     private Context mContext;
60 
61     @Override
setUp()62     protected void setUp() throws Exception {
63         super.setUp();
64         mContext = getInstrumentation().getTargetContext();
65         mResolver = mContext.getContentResolver();
66 
67         ContentValues values0 = new ContentValues();
68         values0.put(DummyProvider.NAME, NAME0);
69         values0.put(DummyProvider.VALUE, VALUE0);
70         mResolver.insert(DummyProvider.CONTENT_URI, values0);
71 
72         ContentValues values1 = new ContentValues();
73         values1.put(DummyProvider.NAME, NAME1);
74         values1.put(DummyProvider.VALUE, VALUE1);
75         mResolver.insert(DummyProvider.CONTENT_URI, values1);
76 
77         mCursor = mResolver.query(DummyProvider.CONTENT_URI, PROJECTIONS, null, null, null);
78         assertNotNull(mCursor);
79     }
80 
81     @Override
tearDown()82     protected void tearDown() throws Exception {
83         if (mContentQueryMap != null) {
84             mContentQueryMap.close();
85             mContentQueryMap = null;
86         }
87         if (mCursor != null) {
88             mCursor.close();
89             mCursor = null;
90         }
91 
92         mResolver.delete(DummyProvider.CONTENT_URI, null, null);
93 
94         super.tearDown();
95     }
96 
97     @UiThreadTest
testConstructor()98     public void testConstructor() {
99         new ContentQueryMap(mCursor, DummyProvider.NAME, true, null);
100 
101         new ContentQueryMap(mCursor, DummyProvider.VALUE, false, new Handler());
102 
103         try {
104             new ContentQueryMap(mCursor, null, false, new Handler());
105             fail("Should throw NullPointerException if param columnNameOfKey is null");
106         } catch (NullPointerException e) {
107         }
108 
109         try {
110             new ContentQueryMap(null, DummyProvider.NAME, false, new Handler());
111             fail("Should throw NullPointerException if param cursor is null");
112         } catch (NullPointerException e) {
113         }
114     }
115 
116     @UiThreadTest
testGetRows()117     public void testGetRows() {
118         // handler can be null
119         mContentQueryMap = new ContentQueryMap(mCursor, DummyProvider.NAME, true, null);
120         Map<String, ContentValues> rows = mContentQueryMap.getRows();
121         assertEquals(ORIGINAL_ROW_COUNT, rows.size());
122         assertTrue(rows.containsKey(NAME0));
123         assertEquals(VALUE0, rows.get(NAME0).getAsString(DummyProvider.VALUE));
124         assertTrue(rows.containsKey(NAME1));
125         assertEquals(VALUE1, rows.get(NAME1).getAsString(DummyProvider.VALUE));
126         mContentQueryMap.close();
127 
128         // the mCursor has been close
129         mContentQueryMap = new ContentQueryMap(mCursor, DummyProvider.NAME, false, new Handler());
130         rows = mContentQueryMap.getRows();
131         assertFalse(rows.containsKey(NAME0));
132         mContentQueryMap.requery();
133         rows = mContentQueryMap.getRows();
134         assertFalse(rows.containsKey(NAME0));
135     }
136 
testRequery()137     public void testRequery() {
138         // Disable the keepUpdated to make sure requery() will not be called
139         // from somewhere else
140         mContentQueryMap = new ContentQueryMap(mCursor, DummyProvider.NAME, false, null);
141         ContentValues contentValues = mContentQueryMap.getValues(NAME0);
142         assertNotNull(contentValues);
143         assertEquals(VALUE0, contentValues.getAsString(DummyProvider.VALUE));
144 
145         contentValues = mContentQueryMap.getValues(NAME1);
146         assertNotNull(contentValues);
147         assertEquals(VALUE1, contentValues.getAsString(DummyProvider.VALUE));
148 
149         contentValues = mContentQueryMap.getValues(NAME2);
150         assertNull(contentValues);
151 
152         // update NAME0 and VALUE0
153         ContentValues values = new ContentValues();
154         values.put(DummyProvider.NAME, NAME2);
155         values.put(DummyProvider.VALUE, VALUE2);
156         mResolver.update(DummyProvider.CONTENT_URI, values,
157                 DummyProvider.NAME + " = '" + NAME0 + "'", null);
158         mContentQueryMap.requery();
159 
160         contentValues = mContentQueryMap.getValues(NAME0);
161         assertNull(contentValues);
162 
163         contentValues = mContentQueryMap.getValues(NAME1);
164         assertNotNull(contentValues);
165         assertEquals(VALUE1, contentValues.getAsString(DummyProvider.VALUE));
166 
167         contentValues = mContentQueryMap.getValues(NAME2);
168         assertNotNull(contentValues);
169         assertEquals(VALUE2, contentValues.getAsString(DummyProvider.VALUE));
170     }
171 
testSetKeepUpdated()172     public void testSetKeepUpdated() throws InterruptedException {
173         MockObserver observer = new MockObserver();
174 
175         // keepUpdated is false
176         mContentQueryMap = new ContentQueryMap(mCursor, DummyProvider.NAME, false, null);
177         mContentQueryMap.addObserver(observer);
178         assertFalse(observer.hadUpdated(0));
179 
180         ContentValues contentValues = mContentQueryMap.getValues(NAME0);
181         assertNotNull(contentValues);
182         assertEquals(VALUE0, contentValues.getAsString(DummyProvider.VALUE));
183         contentValues = mContentQueryMap.getValues(NAME2);
184         assertNull(contentValues);
185 
186         // update NAME0 and VALUE0
187         ContentValues values = new ContentValues();
188         values.put(DummyProvider.NAME, NAME2);
189         values.put(DummyProvider.VALUE, VALUE2);
190         mResolver.update(DummyProvider.CONTENT_URI, values,
191                 DummyProvider.NAME + " = '" + NAME0 + "'", null);
192 
193         // values have not been updated
194         assertFalse(observer.hadUpdated(0));
195         contentValues = mContentQueryMap.getValues(NAME0);
196         assertNotNull(contentValues);
197         assertEquals(VALUE0, contentValues.getAsString(DummyProvider.VALUE));
198         contentValues = mContentQueryMap.getValues(NAME2);
199         assertNull(contentValues);
200 
201         // have to update manually
202         mContentQueryMap.requery();
203         assertTrue(observer.hadUpdated(0));
204         assertSame(mContentQueryMap, observer.getObservable());
205 
206         contentValues = mContentQueryMap.getValues(NAME0);
207         assertNull(contentValues);
208         contentValues = mContentQueryMap.getValues(NAME2);
209         assertNotNull(contentValues);
210         assertEquals(VALUE2, contentValues.getAsString(DummyProvider.VALUE));
211 
212         observer.reset();
213         contentValues = mContentQueryMap.getValues(NAME3);
214         assertNull(contentValues);
215         new Thread(new Runnable() {
216             public void run() {
217                 Looper.prepare();
218                 mContentQueryMap.setKeepUpdated(true);
219                 synchronized (ContentQueryMapTest.this) {
220                     //listener is ready, release the sender thread
221                     ContentQueryMapTest.this.notify();
222                 }
223                 Looper.loop();
224             }
225         }).start();
226         synchronized (this) {
227             wait(TEST_TIME_OUT);
228         }//wait the listener to be ready before launching onChange event
229 
230         // insert NAME3 and VALUE3
231         values = new ContentValues();
232         values.put(DummyProvider.NAME, NAME3);
233         values.put(DummyProvider.VALUE, VALUE3);
234         mResolver.insert(DummyProvider.CONTENT_URI, values);
235 
236         // should be updated automatically
237         assertTrue(observer.hadUpdated(TEST_TIME_OUT));
238         assertSame(mContentQueryMap, observer.getObservable());
239         contentValues = mContentQueryMap.getValues(NAME3);
240         assertNotNull(contentValues);
241         assertEquals(VALUE3, contentValues.getAsString(DummyProvider.VALUE));
242 
243         observer.reset();
244         new Thread(new Runnable() {
245             public void run() {
246                 Looper.prepare();
247                 mContentQueryMap.setKeepUpdated(false);
248                 synchronized (ContentQueryMapTest.this) {
249                     //listener is ready, release the sender thread
250                     ContentQueryMapTest.this.notify();
251                 }
252                 Looper.loop();
253             }
254         }).start();
255         synchronized (this) {
256             wait(TEST_TIME_OUT);
257         }//wait the listener to be ready before launching onChange event
258         // update NAME3 and VALUE3
259         values = new ContentValues();
260         values.put(DummyProvider.NAME, NAME0);
261         values.put(DummyProvider.VALUE, VALUE0);
262         mResolver.update(DummyProvider.CONTENT_URI, values,
263                 DummyProvider.NAME + " = '" + NAME3 + "'", null);
264 
265         // values have not been updated
266         assertFalse(observer.hadUpdated(TEST_TIME_OUT));
267         contentValues = mContentQueryMap.getValues(NAME3);
268         assertNotNull(contentValues);
269         assertEquals(VALUE3, contentValues.getAsString(DummyProvider.VALUE));
270     }
271 
testSetKeepUpdatedWithHandler()272     public void testSetKeepUpdatedWithHandler() throws InterruptedException {
273         MockObserver observer = new MockObserver();
274         HandlerThread thread = new HandlerThread("testSetKeepUpdatedWithHandler");
275         thread.start();
276         Handler handler = new Handler(thread.getLooper());
277         // keepUpdated is false
278         mContentQueryMap = new ContentQueryMap(mCursor, DummyProvider.NAME, false, handler);
279         mContentQueryMap.addObserver(observer);
280         assertFalse(observer.hadUpdated(0));
281 
282         ContentValues contentValues = mContentQueryMap.getValues(NAME0);
283         assertNotNull(contentValues);
284         assertEquals(VALUE0, contentValues.getAsString(DummyProvider.VALUE));
285         contentValues = mContentQueryMap.getValues(NAME2);
286         assertNull(contentValues);
287 
288         // update NAME0 and VALUE0
289         ContentValues values = new ContentValues();
290         values.put(DummyProvider.NAME, NAME2);
291         values.put(DummyProvider.VALUE, VALUE2);
292         mResolver.update(DummyProvider.CONTENT_URI, values,
293                 DummyProvider.NAME + " = '" + NAME0 + "'", null);
294 
295         // values have not been updated
296         assertFalse(observer.hadUpdated(TEST_TIME_OUT));
297         contentValues = mContentQueryMap.getValues(NAME0);
298         assertNotNull(contentValues);
299         assertEquals(VALUE0, contentValues.getAsString(DummyProvider.VALUE));
300         contentValues = mContentQueryMap.getValues(NAME2);
301         assertNull(contentValues);
302 
303         // have to update manually
304         mContentQueryMap.requery();
305         assertTrue(observer.hadUpdated(0));
306         assertSame(mContentQueryMap, observer.getObservable());
307 
308         contentValues = mContentQueryMap.getValues(NAME0);
309         assertNull(contentValues);
310         contentValues = mContentQueryMap.getValues(NAME2);
311         assertNotNull(contentValues);
312         assertEquals(VALUE2, contentValues.getAsString(DummyProvider.VALUE));
313 
314         observer.reset();
315         contentValues = mContentQueryMap.getValues(NAME3);
316         assertNull(contentValues);
317         mContentQueryMap.setKeepUpdated(true);
318 
319         // insert NAME3 and VALUE3
320         values = new ContentValues();
321         values.put(DummyProvider.NAME, NAME3);
322         values.put(DummyProvider.VALUE, VALUE3);
323         mResolver.insert(DummyProvider.CONTENT_URI, values);
324 
325         // should be updated automatically
326         assertTrue(observer.hadUpdated(TEST_TIME_OUT));
327         assertSame(mContentQueryMap, observer.getObservable());
328         contentValues = mContentQueryMap.getValues(NAME3);
329         assertNotNull(contentValues);
330         assertEquals(VALUE3, contentValues.getAsString(DummyProvider.VALUE));
331 
332         observer.reset();
333         mContentQueryMap.setKeepUpdated(false);
334         // update NAME3 and VALUE3
335         values = new ContentValues();
336         values.put(DummyProvider.NAME, NAME0);
337         values.put(DummyProvider.VALUE, VALUE0);
338         mResolver.update(DummyProvider.CONTENT_URI, values,
339                 DummyProvider.NAME + " = '" + NAME3 + "'", null);
340 
341         // values have not been updated
342         assertFalse(observer.hadUpdated(TEST_TIME_OUT));
343         contentValues = mContentQueryMap.getValues(NAME3);
344         assertNotNull(contentValues);
345         assertEquals(VALUE3, contentValues.getAsString(DummyProvider.VALUE));
346     }
347 
testGetValuesBoundary()348     public void testGetValuesBoundary() {
349         mContentQueryMap = new ContentQueryMap(mCursor, DummyProvider.NAME, false, null);
350         assertNull(mContentQueryMap.getValues(null));
351         assertNull(mContentQueryMap.getValues(""));
352     }
353 
354     private static final class MockObserver implements Observer {
355         private boolean mHadUpdated = false;
356         private Observable mObservable;
357 
reset()358         public void reset() {
359             mHadUpdated = false;
360             mObservable = null;
361         }
362 
update(Observable observable, Object data)363         public synchronized void update(Observable observable, Object data) {
364             mObservable = observable;
365             mHadUpdated = true;
366             notify();
367         }
368 
getObservable()369         public Observable getObservable() {
370             return mObservable;
371         }
372 
hadUpdated(long timeout)373         public synchronized boolean hadUpdated(long timeout) throws InterruptedException {
374             // do not wait if timeout is 0
375             if (timeout > 0 && !mHadUpdated) {
376                 wait(timeout);
377             }
378             return mHadUpdated;
379         }
380     }
381 }
382