1/*
2 * Copyright (C) 2010 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.contacts.common.model;
18
19import android.content.ContentUris;
20import android.net.Uri;
21import android.provider.ContactsContract;
22import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
23import android.provider.ContactsContract.CommonDataKinds.StructuredName;
24import android.provider.ContactsContract.Contacts;
25import android.provider.ContactsContract.Data;
26import android.provider.ContactsContract.DisplayNameSources;
27import android.provider.ContactsContract.RawContacts;
28import android.provider.ContactsContract.StatusUpdates;
29import android.test.LoaderTestCase;
30import android.test.suitebuilder.annotation.LargeTest;
31
32import com.android.contacts.common.model.AccountTypeManager;
33import com.android.contacts.common.test.mocks.ContactsMockContext;
34import com.android.contacts.common.test.mocks.MockContentProvider;
35import com.android.contacts.common.model.account.AccountType;
36import com.android.contacts.common.model.account.AccountWithDataSet;
37import com.android.contacts.common.model.account.BaseAccountType;
38import com.android.contacts.common.testing.InjectedServices;
39import com.android.contacts.common.test.mocks.MockAccountTypeManager;
40import com.android.contacts.common.util.Constants;
41
42import org.json.JSONException;
43import org.json.JSONObject;
44
45/**
46 * Runs ContactLoader tests for the the contact-detail and editor view.
47 */
48@LargeTest
49public class ContactLoaderTest extends LoaderTestCase {
50    private static final long CONTACT_ID = 1;
51    private static final long RAW_CONTACT_ID = 11;
52    private static final long DATA_ID = 21;
53    private static final String LOOKUP_KEY = "aa%12%@!";
54
55    private ContactsMockContext mMockContext;
56    private MockContentProvider mContactsProvider;
57
58    @Override
59    protected void setUp() throws Exception {
60        super.setUp();
61        mMockContext = new ContactsMockContext(getContext());
62        mContactsProvider = mMockContext.getContactsProvider();
63
64        InjectedServices services = new InjectedServices();
65        AccountType accountType = new BaseAccountType() {
66            @Override
67            public boolean areContactsWritable() {
68                return false;
69            }
70        };
71        accountType.accountType = "mockAccountType";
72
73        AccountWithDataSet account =
74                new AccountWithDataSet("mockAccountName", "mockAccountType", null);
75
76        AccountTypeManager.setInstanceForTest(
77                new MockAccountTypeManager(
78                        new AccountType[]{accountType}, new AccountWithDataSet[]{account}));
79    }
80
81    @Override
82    protected void tearDown() throws Exception {
83        mMockContext = null;
84        mContactsProvider = null;
85        super.tearDown();
86    }
87
88    private Contact assertLoadContact(Uri uri) {
89        final ContactLoader loader = new ContactLoader(mMockContext, uri, true);
90        return getLoaderResultSynchronously(loader);
91    }
92
93    public void testNullUri() {
94        Contact result = assertLoadContact(null);
95        assertTrue(result.isError());
96    }
97
98    public void testEmptyUri() {
99        Contact result = assertLoadContact(Uri.EMPTY);
100        assertTrue(result.isError());
101    }
102
103    public void testInvalidUri() {
104        Contact result = assertLoadContact(Uri.parse("content://wtf"));
105        assertTrue(result.isError());
106    }
107
108    public void testLoadContactWithContactIdUri() {
109        // Use content Uris that only contain the ID
110        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID);
111        final Uri entityUri = Uri.withAppendedPath(baseUri, Contacts.Entity.CONTENT_DIRECTORY);
112        final Uri lookupUri = ContentUris.withAppendedId(
113                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY),
114                CONTACT_ID);
115
116        ContactQueries queries = new ContactQueries();
117        mContactsProvider.expectTypeQuery(baseUri, Contacts.CONTENT_ITEM_TYPE);
118        queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY);
119
120        Contact contact = assertLoadContact(baseUri);
121
122        assertEquals(CONTACT_ID, contact.getId());
123        assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId());
124        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
125        assertEquals(LOOKUP_KEY, contact.getLookupKey());
126        assertEquals(lookupUri, contact.getLookupUri());
127        assertEquals(1, contact.getRawContacts().size());
128        assertEquals(1, contact.getStatuses().size());
129        mContactsProvider.verify();
130    }
131
132    public void testLoadContactWithOldStyleUri() {
133        // Use content Uris that only contain the ID but use the format used in Donut
134        final Uri legacyUri = ContentUris.withAppendedId(
135                Uri.parse("content://contacts"), RAW_CONTACT_ID);
136        final Uri rawContactUri = ContentUris.withAppendedId(
137                RawContacts.CONTENT_URI, RAW_CONTACT_ID);
138        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID);
139        final Uri lookupUri = ContentUris.withAppendedId(
140                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY),
141                CONTACT_ID);
142        final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY);
143
144        ContactQueries queries = new ContactQueries();
145        queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, CONTACT_ID, LOOKUP_KEY);
146        queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY);
147
148        Contact contact = assertLoadContact(legacyUri);
149
150        assertEquals(CONTACT_ID, contact.getId());
151        assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId());
152        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
153        assertEquals(LOOKUP_KEY, contact.getLookupKey());
154        assertEquals(lookupUri, contact.getLookupUri());
155        assertEquals(1, contact.getRawContacts().size());
156        assertEquals(1, contact.getStatuses().size());
157        mContactsProvider.verify();
158    }
159
160    public void testLoadContactWithRawContactIdUri() {
161        // Use content Uris that only contain the ID but use the format used in Donut
162        final Uri rawContactUri = ContentUris.withAppendedId(
163                RawContacts.CONTENT_URI, RAW_CONTACT_ID);
164        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID);
165        final Uri lookupUri = ContentUris.withAppendedId(
166                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY),
167                CONTACT_ID);
168        final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY);
169
170        ContactQueries queries = new ContactQueries();
171        mContactsProvider.expectTypeQuery(rawContactUri, RawContacts.CONTENT_ITEM_TYPE);
172        queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, CONTACT_ID, LOOKUP_KEY);
173        queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY);
174
175        Contact contact = assertLoadContact(rawContactUri);
176
177        assertEquals(CONTACT_ID, contact.getId());
178        assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId());
179        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
180        assertEquals(LOOKUP_KEY, contact.getLookupKey());
181        assertEquals(lookupUri, contact.getLookupUri());
182        assertEquals(1, contact.getRawContacts().size());
183        assertEquals(1, contact.getStatuses().size());
184        mContactsProvider.verify();
185    }
186
187    public void testLoadContactWithContactLookupUri() {
188        // Use lookup-style Uris that do not contain the Contact-ID
189        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID);
190        final Uri lookupNoIdUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY);
191        final Uri lookupUri = ContentUris.withAppendedId(lookupNoIdUri, CONTACT_ID);
192        final Uri entityUri = Uri.withAppendedPath(
193                lookupNoIdUri, Contacts.Entity.CONTENT_DIRECTORY);
194
195        ContactQueries queries = new ContactQueries();
196        mContactsProvider.expectTypeQuery(lookupNoIdUri, Contacts.CONTENT_ITEM_TYPE);
197        queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY);
198
199        Contact contact = assertLoadContact(lookupNoIdUri);
200
201        assertEquals(CONTACT_ID, contact.getId());
202        assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId());
203        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
204        assertEquals(LOOKUP_KEY, contact.getLookupKey());
205        assertEquals(lookupUri, contact.getLookupUri());
206        assertEquals(1, contact.getRawContacts().size());
207        assertEquals(1, contact.getStatuses().size());
208        mContactsProvider.verify();
209    }
210
211    public void testLoadContactWithContactLookupAndIdUri() {
212        // Use lookup-style Uris that also contain the Contact-ID
213        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID);
214        final Uri lookupUri = ContentUris.withAppendedId(
215                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY),
216                CONTACT_ID);
217        final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY);
218
219        ContactQueries queries = new ContactQueries();
220        mContactsProvider.expectTypeQuery(lookupUri, Contacts.CONTENT_ITEM_TYPE);
221        queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY);
222
223        Contact contact = assertLoadContact(lookupUri);
224
225        assertEquals(CONTACT_ID, contact.getId());
226        assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId());
227        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
228        assertEquals(LOOKUP_KEY, contact.getLookupKey());
229        assertEquals(lookupUri, contact.getLookupUri());
230        assertEquals(1, contact.getRawContacts().size());
231        assertEquals(1, contact.getStatuses().size());
232        mContactsProvider.verify();
233    }
234
235    public void testLoadContactWithContactLookupWithIncorrectIdUri() {
236        // Use lookup-style Uris that contain incorrect Contact-ID
237        // (we want to ensure that still the correct contact is chosen)
238        final long wrongContactId = 2;
239        final long wrongRawContactId = 12;
240
241        final String wrongLookupKey = "ab%12%@!";
242        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID);
243        final Uri wrongBaseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, wrongContactId);
244        final Uri lookupUri = ContentUris.withAppendedId(
245                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY),
246                CONTACT_ID);
247        final Uri lookupWithWrongIdUri = ContentUris.withAppendedId(
248                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY),
249                wrongContactId);
250        final Uri entityUri = Uri.withAppendedPath(lookupWithWrongIdUri,
251                Contacts.Entity.CONTENT_DIRECTORY);
252
253        ContactQueries queries = new ContactQueries();
254        mContactsProvider.expectTypeQuery(lookupWithWrongIdUri, Contacts.CONTENT_ITEM_TYPE);
255        queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY);
256
257        Contact contact = assertLoadContact(lookupWithWrongIdUri);
258
259        assertEquals(CONTACT_ID, contact.getId());
260        assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId());
261        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
262        assertEquals(LOOKUP_KEY, contact.getLookupKey());
263        assertEquals(lookupUri, contact.getLookupUri());
264        assertEquals(1, contact.getRawContacts().size());
265        assertEquals(1, contact.getStatuses().size());
266
267        mContactsProvider.verify();
268    }
269
270    public void testLoadContactReturnDirectoryContactWithoutDisplayName() throws JSONException {
271        // Use lookup-style Uri that contains encoded json object which encapsulates the
272        // directory contact. The test json object is:
273        // {
274        //   display_name_source": 40,
275        //   "vnd.android.cursor.item\/contact":{"email":{"data1":"test@google.com" }}
276        // }
277        JSONObject itemJson = new JSONObject();
278        itemJson.put("email", new JSONObject().put("data1", "test@google.com"));
279        JSONObject json = new JSONObject();
280        json.put(Contacts.NAME_RAW_CONTACT_ID, CONTACT_ID);
281        json.put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME);
282        json.put(Contacts.CONTENT_ITEM_TYPE, itemJson);
283
284        final Uri lookupUri = Contacts.CONTENT_LOOKUP_URI.buildUpon()
285                .encodedFragment(json.toString())
286                .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, "1")
287                .appendPath(Constants.LOOKUP_URI_ENCODED).build();
288
289        mContactsProvider.expectTypeQuery(lookupUri, Contacts.CONTENT_ITEM_TYPE);
290        Contact contact = assertLoadContact(lookupUri);
291
292        assertEquals(-1, contact.getId());
293        assertEquals(-1, contact.getNameRawContactId());
294        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
295        assertEquals("", contact.getDisplayName());
296        assertEquals(lookupUri, contact.getLookupUri());
297        assertEquals(1, contact.getRawContacts().size());
298        mContactsProvider.verify();
299    }
300
301    class ContactQueries {
302        public void fetchAllData(
303                Uri baseUri, long contactId, long rawContactId, long dataId, String encodedLookup) {
304            mContactsProvider.expectQuery(baseUri)
305                    .withProjection(new String[] {
306                        Contacts.NAME_RAW_CONTACT_ID, Contacts.DISPLAY_NAME_SOURCE,
307                        Contacts.LOOKUP_KEY, Contacts.DISPLAY_NAME,
308                        Contacts.DISPLAY_NAME_ALTERNATIVE, Contacts.PHONETIC_NAME,
309                        Contacts.PHOTO_ID, Contacts.STARRED, Contacts.CONTACT_PRESENCE,
310                        Contacts.CONTACT_STATUS, Contacts.CONTACT_STATUS_TIMESTAMP,
311                        Contacts.CONTACT_STATUS_RES_PACKAGE, Contacts.CONTACT_STATUS_LABEL,
312
313                        Contacts.Entity.CONTACT_ID,
314                        Contacts.Entity.RAW_CONTACT_ID,
315
316                        RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE,
317                        RawContacts.DATA_SET,
318                        RawContacts.DIRTY, RawContacts.VERSION, RawContacts.SOURCE_ID,
319                        RawContacts.SYNC1, RawContacts.SYNC2, RawContacts.SYNC3, RawContacts.SYNC4,
320                        RawContacts.DELETED,
321
322                        Contacts.Entity.DATA_ID,
323
324                        Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5,
325                        Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10,
326                        Data.DATA11, Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15,
327                        Data.SYNC1, Data.SYNC2, Data.SYNC3, Data.SYNC4,
328                        Data.DATA_VERSION, Data.IS_PRIMARY,
329                        Data.IS_SUPER_PRIMARY, Data.MIMETYPE,
330
331                        GroupMembership.GROUP_SOURCE_ID,
332
333                        Data.PRESENCE, Data.CHAT_CAPABILITY,
334                        Data.STATUS, Data.STATUS_RES_PACKAGE, Data.STATUS_ICON,
335                        Data.STATUS_LABEL, Data.STATUS_TIMESTAMP,
336
337                        Contacts.PHOTO_URI,
338
339                        Contacts.SEND_TO_VOICEMAIL,
340                        Contacts.CUSTOM_RINGTONE,
341                        Contacts.IS_USER_PROFILE,
342
343                        Data.TIMES_USED,
344                        Data.LAST_TIME_USED,
345                    })
346                    .withSortOrder(Contacts.Entity.RAW_CONTACT_ID)
347                    .returnRow(
348                        rawContactId, 40,
349                        "aa%12%@!", "John Doe", "Doe, John", "jdo",
350                        0, 0, StatusUpdates.AVAILABLE,
351                        "Having lunch", 0,
352                        "mockPkg1", 10,
353
354                        contactId,
355                        rawContactId,
356
357                        "mockAccountName", "mockAccountType", null,
358                        0, 1, 0,
359                        "sync1", "sync2", "sync3", "sync4",
360                        0,
361
362                        dataId,
363
364                        "dat1", "dat2", "dat3", "dat4", "dat5",
365                        "dat6", "dat7", "dat8", "dat9", "dat10",
366                        "dat11", "dat12", "dat13", "dat14", "dat15",
367                        "syn1", "syn2", "syn3", "syn4",
368
369                        0, 0,
370                        0, StructuredName.CONTENT_ITEM_TYPE,
371
372                        "groupId",
373
374                        StatusUpdates.INVISIBLE, null,
375                        "Having dinner", "mockPkg3", 0,
376                        20, 0,
377
378                        "content:some.photo.uri",
379
380                        0,
381                        null,
382                        0,
383
384                        0,
385                        0
386                    );
387        }
388
389        void fetchLookupAndId(final Uri sourceUri, final long expectedContactId,
390                final String expectedEncodedLookup) {
391            mContactsProvider.expectQuery(sourceUri)
392                    .withProjection(Contacts.LOOKUP_KEY, Contacts._ID)
393                    .returnRow(expectedEncodedLookup, expectedContactId);
394        }
395
396        void fetchContactIdAndLookupFromRawContactUri(final Uri rawContactUri,
397                final long expectedContactId, final String expectedEncodedLookup) {
398            // TODO: use a lighter query by joining rawcontacts with contacts in provider
399            // (See ContactContracts.java)
400            final Uri dataUri = Uri.withAppendedPath(rawContactUri,
401                    RawContacts.Data.CONTENT_DIRECTORY);
402            mContactsProvider.expectQuery(dataUri)
403                    .withProjection(RawContacts.CONTACT_ID, Contacts.LOOKUP_KEY)
404                    .returnRow(expectedContactId, expectedEncodedLookup);
405        }
406    }
407}
408