1/*
2 * Copyright (C) 2011 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
17package com.android.providers.contacts;
18
19import android.database.sqlite.SQLiteDatabase;
20import android.test.MoreAsserts;
21import android.test.suitebuilder.annotation.LargeTest;
22import android.test.suitebuilder.annotation.SmallTest;
23
24import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
25import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
26import com.google.android.collect.Sets;
27
28import java.util.HashSet;
29import java.util.Set;
30
31@SmallTest
32public class ContactsDatabaseHelperTest extends BaseContactsProvider2Test {
33    private ContactsDatabaseHelper mDbHelper;
34    private SQLiteDatabase mDb;
35
36    @Override
37    protected void setUp() throws Exception {
38        super.setUp();
39        mDbHelper = getContactsProvider().getDatabaseHelper(getContext());
40        mDb = mDbHelper.getWritableDatabase();
41    }
42
43    public void testGetOrCreateAccountId() {
44        final AccountWithDataSet a1 = null;
45        final AccountWithDataSet a2 = new AccountWithDataSet("a", null, null);
46        final AccountWithDataSet a3 = new AccountWithDataSet(null, "b", null);
47        final AccountWithDataSet a4 = new AccountWithDataSet(null, null, "c");
48        final AccountWithDataSet a5 = new AccountWithDataSet("a", "b", "c");
49
50        // First, there's no accounts.  getAccountIdOrNull() always returns null.
51        assertNull(mDbHelper.getAccountIdOrNull(a1));
52        assertNull(mDbHelper.getAccountIdOrNull(a2));
53        assertNull(mDbHelper.getAccountIdOrNull(a3));
54        assertNull(mDbHelper.getAccountIdOrNull(a4));
55        assertNull(mDbHelper.getAccountIdOrNull(a5));
56
57        // getOrCreateAccountId should create accounts.
58        final long a1id = mDbHelper.getOrCreateAccountIdInTransaction(a1);
59        final long a2id = mDbHelper.getOrCreateAccountIdInTransaction(a2);
60        final long a3id = mDbHelper.getOrCreateAccountIdInTransaction(a3);
61        final long a4id = mDbHelper.getOrCreateAccountIdInTransaction(a4);
62        final long a5id = mDbHelper.getOrCreateAccountIdInTransaction(a5);
63
64        // The IDs should be all positive and unique.
65        assertTrue(a1id > 0);
66        assertTrue(a2id > 0);
67        assertTrue(a3id > 0);
68        assertTrue(a4id > 0);
69        assertTrue(a5id > 0);
70
71        final Set<Long> ids = Sets.newHashSet();
72        ids.add(a1id);
73        ids.add(a2id);
74        ids.add(a3id);
75        ids.add(a4id);
76        ids.add(a5id);
77        assertEquals(5, ids.size());
78
79        // Second call: This time getOrCreateAccountId will return the existing IDs.
80        assertEquals(a1id, mDbHelper.getOrCreateAccountIdInTransaction(a1));
81        assertEquals(a2id, mDbHelper.getOrCreateAccountIdInTransaction(a2));
82        assertEquals(a3id, mDbHelper.getOrCreateAccountIdInTransaction(a3));
83        assertEquals(a4id, mDbHelper.getOrCreateAccountIdInTransaction(a4));
84        assertEquals(a5id, mDbHelper.getOrCreateAccountIdInTransaction(a5));
85
86        // Now getAccountIdOrNull() returns IDs too.
87        assertEquals((Long) a1id, mDbHelper.getAccountIdOrNull(a1));
88        assertEquals((Long) a2id, mDbHelper.getAccountIdOrNull(a2));
89        assertEquals((Long) a3id, mDbHelper.getAccountIdOrNull(a3));
90        assertEquals((Long) a4id, mDbHelper.getAccountIdOrNull(a4));
91        assertEquals((Long) a5id, mDbHelper.getAccountIdOrNull(a5));
92
93        // null and AccountWithDataSet.NULL should be treated as the same thing.
94        assertEquals(a1id, mDbHelper.getOrCreateAccountIdInTransaction(AccountWithDataSet.LOCAL));
95        assertEquals((Long) a1id, mDbHelper.getAccountIdOrNull(AccountWithDataSet.LOCAL));
96
97        // Remove all accounts.
98        mDbHelper.getWritableDatabase().execSQL("delete from " + Tables.ACCOUNTS);
99
100        assertNull(mDbHelper.getAccountIdOrNull(AccountWithDataSet.LOCAL));
101        assertNull(mDbHelper.getAccountIdOrNull(a1));
102        assertNull(mDbHelper.getAccountIdOrNull(a2));
103        assertNull(mDbHelper.getAccountIdOrNull(a3));
104        assertNull(mDbHelper.getAccountIdOrNull(a4));
105        assertNull(mDbHelper.getAccountIdOrNull(a5));
106
107        // Logically same as a5, but physically different object.
108        final AccountWithDataSet a5b = new AccountWithDataSet("a", "b", "c");
109        // a5 and a5b should have the same ID.
110        assertEquals(
111                mDbHelper.getOrCreateAccountIdInTransaction(a5),
112                mDbHelper.getOrCreateAccountIdInTransaction(a5b));
113    }
114
115    /**
116     * Test for {@link ContactsDatabaseHelper#queryIdWithOneArg} and
117     * {@link ContactsDatabaseHelper#insertWithOneArgAndReturnId}.
118     */
119    public void testQueryIdWithOneArg_insertWithOneArgAndReturnId() {
120        final String query =
121                "SELECT " + MimetypesColumns._ID +
122                        " FROM " + Tables.MIMETYPES +
123                        " WHERE " + MimetypesColumns.MIMETYPE + "=?";
124
125        final String insert =
126                "INSERT INTO " + Tables.MIMETYPES + "("
127                        + MimetypesColumns.MIMETYPE +
128                        ") VALUES (?)";
129
130        // First, the table is empty.
131        assertEquals(-1, ContactsDatabaseHelper.queryIdWithOneArg(mDb, query, "value1"));
132        assertEquals(-1, ContactsDatabaseHelper.queryIdWithOneArg(mDb, query, "value2"));
133
134        // Insert one value.
135        final long id1 = ContactsDatabaseHelper.insertWithOneArgAndReturnId(mDb, insert, "value1");
136        MoreAsserts.assertNotEqual(-1, id1);
137
138        assertEquals(id1, ContactsDatabaseHelper.queryIdWithOneArg(mDb, query, "value1"));
139        assertEquals(-1, ContactsDatabaseHelper.queryIdWithOneArg(mDb, query, "value2"));
140
141
142        // Insert one value.
143        final long id2 = ContactsDatabaseHelper.insertWithOneArgAndReturnId(mDb, insert, "value2");
144        MoreAsserts.assertNotEqual(-1, id2);
145
146        assertEquals(id1, ContactsDatabaseHelper.queryIdWithOneArg(mDb, query, "value1"));
147        assertEquals(id2, ContactsDatabaseHelper.queryIdWithOneArg(mDb, query, "value2"));
148
149        // Insert the same value and cause a conflict.
150        assertEquals(-1, ContactsDatabaseHelper.insertWithOneArgAndReturnId(mDb, insert, "value2"));
151    }
152
153    /**
154     * Test for {@link ContactsDatabaseHelper#getPackageId(String)} and
155     * {@link ContactsDatabaseHelper#getMimeTypeId(String)}.
156     *
157     * We test them at the same time here, to make sure they're not mixing up the caches.
158     */
159    public void testGetPackageId_getMimeTypeId() {
160
161        // Test for getPackageId.
162        final long packageId1 = mDbHelper.getPackageId("value1");
163        final long packageId2 = mDbHelper.getPackageId("value2");
164        final long packageId3 = mDbHelper.getPackageId("value3");
165
166        // Make sure they're all different.
167        final HashSet<Long> set = new HashSet<>();
168        set.add(packageId1);
169        set.add(packageId2);
170        set.add(packageId3);
171
172        assertEquals(3, set.size());
173
174        // Test for getMimeTypeId.
175        final long mimetypeId1 = mDbHelper.getMimeTypeId("value1");
176        final long mimetypeId2 = mDbHelper.getMimeTypeId("value2");
177        final long mimetypeId3 = mDbHelper.getMimeTypeId("value3");
178
179        // Make sure they're all different.
180        set.clear();
181        set.add(mimetypeId1);
182        set.add(mimetypeId2);
183        set.add(mimetypeId3);
184
185        assertEquals(3, set.size());
186
187        // Call with the same values and make sure they return the cached value.
188        final long packageId1b = mDbHelper.getPackageId("value1");
189        final long mimetypeId1b = mDbHelper.getMimeTypeId("value1");
190
191        assertEquals(packageId1, packageId1b);
192        assertEquals(mimetypeId1, mimetypeId1b);
193
194        // Make sure the caches are also updated.
195        assertEquals(packageId2, (long) mDbHelper.mPackageCache.get("value2"));
196        assertEquals(mimetypeId2, (long) mDbHelper.mMimetypeCache.get("value2"));
197
198        // Clear the cache, but they should still return the values, selecting from the database.
199        mDbHelper.mPackageCache.clear();
200        mDbHelper.mMimetypeCache.clear();
201        assertEquals(packageId1, mDbHelper.getPackageId("value1"));
202        assertEquals(mimetypeId1, mDbHelper.getMimeTypeId("value1"));
203
204        // Empty the table
205        mDb.execSQL("DELETE FROM " + Tables.MIMETYPES);
206
207        // We should still have the cached value.
208        assertEquals(mimetypeId1, mDbHelper.getMimeTypeId("value1"));
209    }
210
211    /**
212     * Try to cause conflicts in getMimeTypeId() by calling it from multiple threads with
213     * the current time as the argument and make sure it won't crash.
214     *
215     * We don't know from the test if there have actually been conflits, but if you look at
216     * logcat you'll see a lot of conflict warnings.
217     */
218    @LargeTest
219    public void testGetMimeTypeId_conflict() {
220
221        final int NUM_THREADS = 4;
222        final int DURATION_SECONDS = 5;
223
224        final long finishTime = System.currentTimeMillis() + DURATION_SECONDS * 1000;
225
226        final Runnable r = new Runnable() {
227            @Override
228            public void run() {
229                for (;;) {
230                    final long now = System.currentTimeMillis();
231                    if (now >= finishTime) {
232                        return;
233                    }
234                    assertTrue(mDbHelper.getMimeTypeId(String.valueOf(now)) > 0);
235                }
236            }
237        };
238        final Thread[] threads = new Thread[NUM_THREADS];
239        for (int i = 0; i < threads.length; i++) {
240            threads[i] = new Thread(r);
241            threads[i].setDaemon(true);
242        }
243        for (int i = 0; i < threads.length; i++) {
244            threads[i].start();
245        }
246        for (int i = 0; i < threads.length; i++) {
247            try {
248                threads[i].join();
249            } catch (InterruptedException ignore) {
250            }
251        }
252    }
253}
254