/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.providers.contacts;

import static com.android.providers.contacts.TestUtils.dumpTable;
import static com.android.providers.contacts.TestUtils.dumpUri;

import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.provider.Contacts;
import android.provider.Contacts.ContactMethods;
import android.provider.Contacts.Extensions;
import android.provider.Contacts.GroupMembership;
import android.provider.Contacts.Groups;
import android.provider.Contacts.Organizations;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
import android.provider.Contacts.Photos;
import android.provider.Contacts.Presence;
import android.provider.Contacts.Settings;
import android.provider.ContactsContract;
import android.test.suitebuilder.annotation.MediumTest;

import java.io.IOException;

/**
 * Tests for legacy contacts APIs.
 *
 * Run the test like this:
 * <code>
 * adb shell am instrument -e class com.android.providers.contacts.LegacyContactsProviderTest -w \
 *         com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
 * </code>
 *
 * Note that this SHOULD be a large test, but had to be bumped down to medium due to a bug in the
 * SQLite cleanup code.
 */
@MediumTest
@SuppressWarnings("deprecation")
public class LegacyContactsProviderTest extends BaseContactsProvider2Test {

    @Override
    protected Class<? extends ContentProvider> getProviderClass() {
       return SynchronousContactsProvider2.class;
    }

    @Override
    protected String getAuthority() {
        return Contacts.AUTHORITY + ";" + ContactsContract.AUTHORITY;
    }

    private static ContentValues noStats(ContentValues v) {
        final ContentValues ret = new ContentValues(v);
        ret.put(People.TIMES_CONTACTED, 0);
        ret.put(People.LAST_TIME_CONTACTED, 0);
        return ret;
    }

    public void testPeopleInsert() {
        ContentValues values = new ContentValues();
        putContactValues(values);

        Uri uri = mResolver.insert(People.CONTENT_URI, values);

        values = noStats(values);

        assertStoredValues(uri, values);

        assertSelection(People.CONTENT_URI, values, "people", People._ID, ContentUris.parseId(uri));
    }

    public void testPeopleUpdate() {
        ContentValues values = new ContentValues();
        putContactValues(values);

        Uri uri = mResolver.insert(People.CONTENT_URI, values);
        values = noStats(values);
        long personId = ContentUris.parseId(uri);
        assertStoredValues(uri, values);
        assertSelection(People.CONTENT_URI, values, "people", People._ID, personId);

        values.clear();
        putContactValues2(values);
        mResolver.update(uri, values, null, null);
        values = noStats(values);
        assertStoredValues(uri, values);

        values.clear();
        putContactValues(values);
        mResolver.update(People.CONTENT_URI, values, People._ID + "=" + personId, null);
        values = noStats(values);
        assertStoredValues(uri, values);
    }

    public void testPeopleDelete() {
        ContentValues values = new ContentValues();
        values.put(People.NAME, "John Doe");
        Uri personId = mResolver.insert(People.CONTENT_URI, values);
        mResolver.delete(personId, null, null);

        Cursor c = mResolver.query(personId, null, People.NAME + "='John Doe'" , null, null);
        assertEquals("Record count after deletion", 0, c.getCount());
        c.close();

        try {
            mResolver.query(People.DELETED_CONTENT_URI, null, null, null, null);
        } catch (UnsupportedOperationException e) {
            // Expected exception
        }
    }

    public void testPeopleFilter() {
        ContentValues values = new ContentValues();
        values.put(People.NAME, "Deer Doe");
        mResolver.insert(People.CONTENT_URI, values);

        values.clear();
        values.put(People.NAME, "Dear Dough");
        mResolver.insert(People.CONTENT_URI, values);

        values.clear();
        values.put(People.NAME, "D.R. Dauwe");
        mResolver.insert(People.CONTENT_URI, values);

        assertFilteredContacts("d", "Deer Doe", "Dear Dough", "D.R. Dauwe");
        assertFilteredContacts("de", "Deer Doe", "Dear Dough");
        assertFilteredContacts("dee", "Deer Doe");
        assertFilteredContacts("der");
    }

    public void testDefaultDisplayName() {
        ContentValues values = new ContentValues();
        values.put(People.NAME, "John Doe");
        Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        assertStoredValue(personUri, People.DISPLAY_NAME, "John Doe");
    }

    public void testPrimaryOrganization() {
        ContentValues values = new ContentValues();
        final Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        // Primary
        values.clear();
        values.put(Organizations.ISPRIMARY, 1);
        values.put(Organizations.COMPANY, "Google");
        values.put(Organizations.TYPE, Organizations.TYPE_WORK);
        values.put(Organizations.PERSON_ID, personId);
        Uri orgUri1 = mResolver.insert(Organizations.CONTENT_URI, values);

        // Non-primary
        values.clear();
        values.put(Organizations.COMPANY, "Acme");
        values.put(Organizations.TYPE, Organizations.TYPE_WORK);
        values.put(Organizations.PERSON_ID, personId);
        Uri orgUri2 = mResolver.insert(Organizations.CONTENT_URI, values);

        values.clear();
        values.put(People.PRIMARY_ORGANIZATION_ID, ContentUris.parseId(orgUri1));
        values.put(People.DISPLAY_NAME, "Google");
        assertStoredValues(personUri, values);

        // Remove the original primary organization
        mResolver.delete(orgUri1, null, null);

        values.clear();
        values.put(People.PRIMARY_ORGANIZATION_ID, ContentUris.parseId(orgUri2));
        values.put(People.DISPLAY_NAME, "Acme");
        assertStoredValues(personUri, values);

        // Remove the remaining organization
        mResolver.delete(orgUri2, null, null);

        values.clear();
        values.putNull(People.PRIMARY_ORGANIZATION_ID);
        values.putNull(People.DISPLAY_NAME);
        assertStoredValues(personUri, values);
    }

    public void testPrimaryPhone() {
        ContentValues values = new ContentValues();
        putContactValuesExceptName(values);

        Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        // Primary
        values.clear();
        values.put(Phones.ISPRIMARY, 1);
        values.put(Phones.TYPE, Phones.TYPE_WORK);
        values.put(Phones.PERSON_ID, personId);
        values.put(Phones.NUMBER, "12345");
        Uri phoneUri1 = mResolver.insert(Phones.CONTENT_URI, values);

        // Non-primary
        values.clear();
        values.put(Phones.TYPE, Phones.TYPE_WORK);
        values.put(Phones.PERSON_ID, personId);
        values.put(Phones.NUMBER, "67890");
        Uri phoneUri2 = mResolver.insert(Phones.CONTENT_URI, values);

        values.clear();
        values.put(People.PRIMARY_PHONE_ID, ContentUris.parseId(phoneUri1));
        values.put(People.DISPLAY_NAME, "12345");
        assertStoredValues(personUri, values);

        values.clear();
        putContactValuesExceptName(values);
        values = noStats(values);
        values.put(People.PRIMARY_PHONE_ID, ContentUris.parseId(phoneUri1));
        assertStoredValues(phoneUri2, values);

        // Remove the primary phone number
        mResolver.delete(phoneUri1, null, null);

        values.clear();
        values.put(People.PRIMARY_PHONE_ID, ContentUris.parseId(phoneUri2));
        values.put(People.DISPLAY_NAME, "67890");
        assertStoredValues(personUri, values);

        // Remove the remaining phone number
        mResolver.delete(phoneUri2, null, null);

        values.clear();
        values.putNull(People.PRIMARY_PHONE_ID);
        values.putNull(People.DISPLAY_NAME);
        assertStoredValues(personUri, values);
    }

    public void testEmailCrud() {
        ContentValues values = new ContentValues();
        Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        // Primary
        values.clear();
        values.put(ContactMethods.PERSON_ID, personId);
        values.put(ContactMethods.KIND, Contacts.KIND_EMAIL);
        values.put(ContactMethods.TYPE, ContactMethods.TYPE_HOME);
        values.put(ContactMethods.DATA, "foo@acme.com");
        values.put(ContactMethods.ISPRIMARY, 1);
        Uri emailUri1 = mResolver.insert(ContactMethods.CONTENT_URI, values);

        assertEquals(ContactMethods.CONTENT_EMAIL_ITEM_TYPE, mResolver.getType(emailUri1));

        assertStoredValues(ContactMethods.CONTENT_URI,
                ContactMethods._ID + "=" + ContentUris.parseId(emailUri1), null, values);
        assertStoredValues(ContactMethods.CONTENT_EMAIL_URI,
                ContactMethods._ID + "=" + ContentUris.parseId(emailUri1), null, values);
    }

    public void testPrimaryEmail() {
        ContentValues values = new ContentValues();
        putContactValuesExceptName(values);
        Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        // Primary
        values.clear();
        values.put(ContactMethods.PERSON_ID, personId);
        values.put(ContactMethods.KIND, Contacts.KIND_EMAIL);
        values.put(ContactMethods.TYPE, ContactMethods.TYPE_HOME);
        values.put(ContactMethods.DATA, "foo@acme.com");
        values.put(ContactMethods.ISPRIMARY, 1);
        Uri emailUri1 = mResolver.insert(ContactMethods.CONTENT_URI, values);

        // Non-primary
        values.clear();
        values.put(ContactMethods.PERSON_ID, personId);
        values.put(ContactMethods.KIND, Contacts.KIND_EMAIL);
        values.put(ContactMethods.TYPE, ContactMethods.TYPE_WORK);
        values.put(ContactMethods.DATA, "bar@acme.com");
        Uri emailUri2 = mResolver.insert(ContactMethods.CONTENT_URI, values);

        values.clear();
        values.put(People.PRIMARY_EMAIL_ID, ContentUris.parseId(emailUri1));
        values.put(People.DISPLAY_NAME, "foo@acme.com");
        assertStoredValues(personUri, values);

        values.clear();
        putContactValuesExceptName(values);
        values = noStats(values);
        values.put(People.PRIMARY_EMAIL_ID, ContentUris.parseId(emailUri1));
        assertStoredValues(emailUri2, values);

        // Remove the primary email
        mResolver.delete(emailUri1, null, null);

        values.clear();
        values.put(People.PRIMARY_EMAIL_ID, ContentUris.parseId(emailUri2));
        values.put(People.DISPLAY_NAME, "bar@acme.com");
        assertStoredValues(personUri, values);

        // Remove the remaining email
        mResolver.delete(emailUri2, null, null);

        values.clear();
        values.putNull(People.PRIMARY_EMAIL_ID);
        values.putNull(People.DISPLAY_NAME);
        assertStoredValues(personUri, values);
    }

    public void testMarkAsContacted() {
        ContentValues values = new ContentValues();
        Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        int timesContactedBefore =
                Integer.parseInt(getStoredValue(personUri, People.TIMES_CONTACTED));
        long timeBefore = System.currentTimeMillis();
        People.markAsContacted(mResolver, personId);
        long timeAfter = System.currentTimeMillis();

        long lastContacted = Long.parseLong(getStoredValue(personUri, People.LAST_TIME_CONTACTED));
        int timesContactedAfter =
            Integer.parseInt(getStoredValue(personUri, People.TIMES_CONTACTED));

        // No longer supported as of O.
        assertEquals(0, lastContacted);
        assertEquals(0, timesContactedAfter);
    }

    public void testOrganizationsInsert() {
        ContentValues values = new ContentValues();
        Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        values.clear();
        values.put(Organizations.COMPANY, "Sierra");
        values.put(Organizations.PERSON_ID, personId);
        values.put(Organizations.TYPE, Organizations.TYPE_CUSTOM);
        values.put(Organizations.LABEL, "Club");
        values.put(Organizations.TITLE, "Member");
        values.put(Organizations.ISPRIMARY, 1);

        Uri uri = mResolver.insert(Organizations.CONTENT_URI, values);
        assertStoredValues(uri, values);
        assertSelection(Organizations.CONTENT_URI, values,
                "organizations", Organizations._ID, ContentUris.parseId(uri));

        assertPersonIdConstraint(Organizations.CONTENT_URI, Organizations.TYPE,
                Organizations.TYPE_WORK);

        assertTypeAndLabelConstraints(Organizations.CONTENT_URI, Organizations.PERSON_ID, personId,
                Organizations.TYPE, Organizations.TYPE_CUSTOM, Organizations.TYPE_OTHER,
                Organizations.LABEL);


        Uri twigUri = Uri.withAppendedPath(personUri, Organizations.CONTENT_DIRECTORY);
        assertStoredValues(twigUri, values);

        Uri twigUriWithId = ContentUris.withAppendedId(twigUri, ContentUris.parseId(uri));
        assertStoredValues(twigUriWithId, values);
    }

    public void testOrganizationsUpdate() {
        ContentValues values = new ContentValues();
        Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        values.clear();
        values.put(Organizations.COMPANY, "Sierra");
        values.put(Organizations.PERSON_ID, personId);
        values.put(Organizations.TYPE, Organizations.TYPE_CUSTOM);
        values.put(Organizations.LABEL, "Club");
        values.put(Organizations.TITLE, "Member");
        values.put(Organizations.ISPRIMARY, 0);

        Uri uri = mResolver.insert(Organizations.CONTENT_URI, values);

        values.clear();
        values.put(Organizations.COMPANY, "Planetary");
        values.put(Organizations.PERSON_ID, personId);
        values.put(Organizations.TYPE, Organizations.TYPE_CUSTOM);
        values.put(Organizations.LABEL, "Society");
        values.put(Organizations.TITLE, "Chair");
        values.put(Organizations.ISPRIMARY, 1);

        mResolver.update(uri, values, null, null);

        assertStoredValues(uri, values);
    }

    public void testPhonesInsert() {
        ContentValues values = new ContentValues();
        putContactValues(values);
        Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        values.clear();
        values.put(Phones.PERSON_ID, personId);
        values.put(Phones.TYPE, Phones.TYPE_CUSTOM);
        values.put(Phones.LABEL, "Directory");
        values.put(Phones.NUMBER, "1-800-4664-411");
        values.put(Phones.ISPRIMARY, 1);

        Uri uri = mResolver.insert(Phones.CONTENT_URI, values);
        ContentValues expectedResults[] = new ContentValues[1];
        // Adding another value to assert
        expectedResults[0] = new ContentValues(values);
        expectedResults[0].put(Phones.NUMBER_KEY, "11446640081");

        // The result is joined with People
        putContactValues(expectedResults[0]);
        expectedResults[0] = noStats(expectedResults[0]);
        assertStoredValues(uri, expectedResults);
        assertSelection(Phones.CONTENT_URI, values, "phones",
                Phones._ID, ContentUris.parseId(uri));

        // Access the phone through People
        Uri twigUri = Uri.withAppendedPath(personUri, People.Phones.CONTENT_DIRECTORY);
        assertStoredValues(twigUri, expectedResults);

        // Now the person should be joined with Phone
        values.clear();
        putContactValues(values);
        values = noStats(values);
        values.put(People.TYPE, Phones.TYPE_CUSTOM);
        values.put(People.LABEL, "Directory");
        values.put(People.NUMBER, "1-800-4664-411");
        assertStoredValues(personUri, values);

        assertPersonIdConstraint(Phones.CONTENT_URI, Phones.TYPE, Phones.TYPE_WORK);

        assertTypeAndLabelConstraints(Phones.CONTENT_URI, Phones.PERSON_ID, personId, Phones.TYPE,
                Phones.TYPE_CUSTOM, Phones.TYPE_OTHER, Phones.LABEL);
    }

    public void testPhonesUpdate() {
        ContentValues values = new ContentValues();
        Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        values.clear();
        values.put(Phones.PERSON_ID, personId);
        values.put(Phones.TYPE, Phones.TYPE_CUSTOM);
        values.put(Phones.LABEL, "Directory");
        values.put(Phones.NUMBER, "1-800-4664-411");
        values.put(Phones.ISPRIMARY, 0);

        Uri uri = mResolver.insert(Phones.CONTENT_URI, values);

        values.clear();
        values.put(Phones.PERSON_ID, personId);
        values.put(Phones.TYPE, Phones.TYPE_FAX_HOME);
        values.putNull(Phones.LABEL);
        values.put(Phones.NUMBER, "1-800-555-4663");
        values.put(Phones.ISPRIMARY, 1);

        mResolver.update(uri, values, null, null);

        ContentValues[] expectedValues = new ContentValues[1];
        expectedValues[0] = values;
        expectedValues[0].put(Phones.NUMBER_KEY, "36645550081");
        assertStoredValues(uri, expectedValues);
    }

    public void testPhonesFilterQuery() {
        ContentValues values = new ContentValues();
        putContactValues(values);
        Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        values.clear();
        values.put(Phones.PERSON_ID, personId);
        values.put(Phones.TYPE, Phones.TYPE_CUSTOM);
        values.put(Phones.LABEL, "Directory");
        values.put(Phones.NUMBER, "800-4664-411");
        values.put(Phones.ISPRIMARY, 1);

        Uri uri = mResolver.insert(Phones.CONTENT_URI, values);

        Uri filterUri1 = Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, "18004664411");
        ContentValues[] expectedValues = new ContentValues[1];
        expectedValues[0] = values;
        expectedValues[0].put(Phones.NUMBER_KEY, "1144664008");
        assertStoredValues(filterUri1, expectedValues);

        Uri filterUri2 = Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, "7773334444");
        assertEquals(0, getCount(filterUri2, null, null));
    }

    public void testEmailInsert() {
        assertContactMethodInsert(Contacts.KIND_EMAIL,
                ContactMethods.TYPE_CUSTOM, "Some other way", "foo@acme.com", null, true);
    }

    public void testEmailUpdate() {
        assertContactMethodUpdate(Contacts.KIND_EMAIL,
                ContactMethods.TYPE_CUSTOM, "Some other way", "foo@acme.com", null, false,
                ContactMethods.TYPE_HOME, null, "bar@acme.com", "aux", true);
    }

    public void testImInsertStandard() {
        assertContactMethodInsert(Contacts.KIND_IM,
                ContactMethods.TYPE_CUSTOM, "Some other way", "pre:3", "Bar", true);
    }

    public void testImUpdateStandard() {
        assertContactMethodUpdate(Contacts.KIND_IM,
                ContactMethods.TYPE_CUSTOM, "Some other way", "pre:3", "Bar", false,
                ContactMethods.TYPE_WORK, null, "pre:4", "Buz", true);
    }

    public void testImInsertCustom() {
        assertContactMethodInsert(Contacts.KIND_IM,
                ContactMethods.TYPE_CUSTOM, "Some other way", "custom:my_proto", "Bar", true);
    }

    public void testImUpdateCustom() {
        assertContactMethodUpdate(Contacts.KIND_IM,
                ContactMethods.TYPE_CUSTOM, "Some other way", "custom:my_proto", "Bar", false,
                ContactMethods.TYPE_HOME, null, "custom:my_other_proto", "Buz", true);
    }

    public void testPostalInsert() {
        assertContactMethodInsert(Contacts.KIND_POSTAL,
                ContactMethods.TYPE_CUSTOM, "Some other way", "Foo", "Bar", true);
    }

    public void testPostalUpdate() {
        assertContactMethodUpdate(Contacts.KIND_POSTAL,
                ContactMethods.TYPE_CUSTOM, "Some other way", "Foo", "Bar", false,
                ContactMethods.TYPE_WORK, null, "Biz", "Baz", true);
    }

    private void assertContactMethodInsert(int kind, int type, String label, String data,
            String auxData, boolean primary) {
        ContentValues values = new ContentValues();
        putContactValues(values);
        final Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        values.clear();
        values.put(ContactMethods.PERSON_ID, personId);
        values.put(ContactMethods.KIND, kind);
        values.put(ContactMethods.TYPE, type);
        values.put(ContactMethods.LABEL, label);
        values.put(ContactMethods.DATA, data);
        values.put(ContactMethods.AUX_DATA, auxData);
        values.put(ContactMethods.ISPRIMARY, primary ? 1 : 0);

        Uri uri = mResolver.insert(ContactMethods.CONTENT_URI, values);

        // The result is joined with People
        putContactValues(values);

        values = noStats(values);

        assertStoredValues(uri, values);
        assertSelection(ContactMethods.CONTENT_URI, values, "contact_methods",
                ContactMethods._ID, ContentUris.parseId(uri));

        // Access the contact method through People
        Uri twigUri = Uri.withAppendedPath(personUri, People.ContactMethods.CONTENT_DIRECTORY);
        assertStoredValues(twigUri, values);

        assertPersonIdConstraint(ContactMethods.CONTENT_URI, ContactMethods.TYPE,
                ContactMethods.TYPE_WORK);

        assertTypeAndLabelConstraints(ContactMethods.CONTENT_URI, ContactMethods.PERSON_ID,
                personId, ContactMethods.TYPE, ContactMethods.TYPE_CUSTOM,
                ContactMethods.TYPE_OTHER, ContactMethods.LABEL);
    }

    private void assertContactMethodUpdate(int kind,
            int type1, String label1, String data1, String auxData1, boolean primary1,
            int type2, String label2, String data2, String auxData2, boolean primary2) {
        ContentValues values = new ContentValues();
        putContactValues(values);
        final Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        values.clear();
        values.put(ContactMethods.PERSON_ID, personId);
        values.put(ContactMethods.KIND, kind);
        values.put(ContactMethods.TYPE, type1);
        values.put(ContactMethods.LABEL, label1);
        values.put(ContactMethods.DATA, data1);
        values.put(ContactMethods.AUX_DATA, auxData1);
        values.put(ContactMethods.ISPRIMARY, primary1 ? 1 : 0);

        Uri uri = mResolver.insert(ContactMethods.CONTENT_URI, values);

        values.clear();
        values.put(ContactMethods.TYPE, type2);
        values.put(ContactMethods.LABEL, label2);
        values.put(ContactMethods.DATA, data2);
        values.put(ContactMethods.AUX_DATA, auxData2);
        values.put(ContactMethods.ISPRIMARY, primary2 ? 1 : 0);
        mResolver.update(uri, values, null, null);

        assertStoredValues(uri, values);
    }

    public void testExtensionsInsert() {
        ContentValues values = new ContentValues();
        final Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        values.clear();
        values.put(Extensions.PERSON_ID, personId);
        values.put(Extensions.NAME, "Foo");
        values.put(Extensions.VALUE, "Bar");

        Uri uri = mResolver.insert(Extensions.CONTENT_URI, values);
        assertStoredValues(uri, values);
        assertSelection(Extensions.CONTENT_URI, values, "extensions",
                Extensions._ID, ContentUris.parseId(uri));

        // Access the extensions through People
        Uri twigUri = Uri.withAppendedPath(personUri, People.Extensions.CONTENT_DIRECTORY);
        assertStoredValues(twigUri, values);
    }

    public void testExtensionsUpdate() {
        ContentValues values = new ContentValues();
        final Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        values.clear();
        values.put(Extensions.PERSON_ID, personId);
        values.put(Extensions.NAME, "Foo");
        values.put(Extensions.VALUE, "Bar");

        Uri uri = mResolver.insert(Extensions.CONTENT_URI, values);

        values.clear();
        values.put(Extensions.NAME, "Biz");
        values.put(Extensions.VALUE, "Baz");
        mResolver.update(uri, values, null, null);

        assertStoredValues(uri, values);
    }

    public void testGroupsInsert() {
        ContentValues values = new ContentValues();
        values.put(Groups.NAME, "Galois");
        values.put(Groups.NOTES, "Abel");
        values.put(Groups.SYSTEM_ID, "12345");

        Uri groupUri = mResolver.insert(Groups.CONTENT_URI, values);
        assertStoredValues(groupUri, values);
    }

    public void testGroupsUpdate() {
        ContentValues values = new ContentValues();
        values.put(Groups.NAME, "Galois");
        values.put(Groups.NOTES, "Abel");
        values.put(Groups.SYSTEM_ID, "12345");

        Uri groupUri = mResolver.insert(Groups.CONTENT_URI, values);

        values.clear();
        values.put(Groups.NAME, "Klein");
        values.put(Groups.NOTES, "Vierergruppe");
        values.put(Groups.SYSTEM_ID, "1234");
        mResolver.update(groupUri, values, null, null);
        assertStoredValues(groupUri, values);
    }

    public void testGroupMembershipsInsert() {
        ContentValues values = new ContentValues();
        values.put(Groups.NOTES, "Abel");
        Uri groupUri = insertLegacyGroup("Galois", values);
        Uri personUri = insertPerson("Klein", values);
        Uri membershipUri = insertLegacyGroupMembership(groupUri, personUri, values);
        values.put(Groups.NAME, "Galois");
        values.put(Groups.NOTES, "Abel");

        assertStoredValues(membershipUri, values);
        assertSelection(GroupMembership.CONTENT_URI, values, "groupmembership",
                GroupMembership._ID, ContentUris.parseId(membershipUri));

        Uri personsGroupsUri = Uri.withAppendedPath(personUri, GroupMembership.CONTENT_DIRECTORY);
        assertStoredValues(personsGroupsUri, values);
    }

    public void testAddToGroup() {
        ContentValues values = new ContentValues();
        Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        values.clear();
        values.put(Groups.NAME, "Galois");
        Uri groupUri = mResolver.insert(Groups.CONTENT_URI, values);

        People.addToGroup(mResolver, personId, "Galois");

        values.clear();
        values.put(GroupMembership.GROUP_ID, ContentUris.parseId(groupUri));
        values.put(GroupMembership.PERSON_ID, personId);

        Uri personsGroupsUri = Uri.withAppendedPath(personUri, GroupMembership.CONTENT_DIRECTORY);
        assertStoredValues(personsGroupsUri, values);
    }

    public void testGroupMembersByGroupName() {
        ContentValues values = new ContentValues();
        Uri groupUri1 = insertLegacyGroup("Galois", values);
        Uri personUri1 = insertPerson("Klein", values);
        insertLegacyGroupMembership(groupUri1, personUri1, values);

        Uri groupUri2 = insertLegacyGroup("Euler", values);
        Uri personUri2 = insertPerson("Lagrange", values);
        insertLegacyGroupMembership(groupUri2, personUri2, values);

        // NOTE: testing non-public API support
        assertStoredValue(Uri.parse("content://contacts/groups/name/Galois/members"),
                People.NAME, "Klein");
        assertStoredValue(Uri.parse("content://contacts/groups/name/Euler/members"),
                People.NAME, "Lagrange");
    }

    public void testPhotoUpdate() throws Exception {
        byte[] photo = loadPhotoFromResource(
                com.android.providers.contacts.tests.R.drawable.earth_small, PhotoSize.ORIGINAL);
        byte[] thumbnailedPhoto = loadPhotoFromResource(
                com.android.providers.contacts.tests.R.drawable.earth_small, PhotoSize.THUMBNAIL);

        ContentValues values = new ContentValues();
        Uri personUri = mResolver.insert(People.CONTENT_URI, values);

        values.clear();
        values.put(Photos.DATA, photo);
        values.put(Photos.LOCAL_VERSION, "10");
        // FIXME this column was unavailable for update in legacy ContactsProvider
        // values.put(Photos.DOWNLOAD_REQUIRED, 1);
        values.put(Photos.EXISTS_ON_SERVER, 1);
        values.put(Photos.SYNC_ERROR, "404 does not exist");

        Uri photoUri = Uri.withAppendedPath(personUri, Photos.CONTENT_DIRECTORY);
        mResolver.update(photoUri, values, null, null);
        values.put(Photos.DATA, thumbnailedPhoto);
        assertStoredValues(photoUri, values);

        long photoId = Long.parseLong(getStoredValue(photoUri, Photos._ID));

        values.put(Photos.LOCAL_VERSION, "11");
        values.put(Photos.DATA, photo);
        Uri contentUri = ContentUris.withAppendedId(Photos.CONTENT_URI, photoId);
        mResolver.update(contentUri, values, null, null);
        values.put(Photos.DATA, thumbnailedPhoto);
        assertStoredValues(contentUri, values);
        assertStoredValues(photoUri, values);
    }

    /**
     * Capturing the search suggestion requirements in test cases as a reference.
     */
    public void testSearchSuggestionsNotInMyContacts() throws Exception {
        // We don't provide compatibility for search suggestions
        return;
    }

    /**
     * Capturing the search suggestion requirements in test cases as a reference.
     */
    public void testSearchSuggestionsByName() throws Exception {

        // We don't provide compatibility for search suggestions
        return;
    }

    private void assertSearchSuggestion(boolean name, boolean photo, boolean organization,
            boolean phone, boolean email, String query, boolean expectIcon1Uri, String expectedIcon2,
            String expectedText1, String expectedText2) throws IOException {
        ContentValues values = new ContentValues();

        if (name) {
            values.put(People.NAME, "Deer Dough");
        }

        final Uri personUri = mResolver.insert(People.CONTENT_URI, values);
        long personId = ContentUris.parseId(personUri);

        People.addToMyContactsGroup(mResolver, personId);

        if (photo) {
            values.clear();
            byte[] photoData = loadTestPhoto();
            values.put(Photos.DATA, photoData);
            values.put(Photos.LOCAL_VERSION, "1");
            values.put(Photos.EXISTS_ON_SERVER, 0);
            Uri photoUri = Uri.withAppendedPath(personUri, Photos.CONTENT_DIRECTORY);
            mResolver.update(photoUri, values, null, null);
        }

        if (organization) {
            values.clear();
            values.put(Organizations.ISPRIMARY, 1);
            values.put(Organizations.COMPANY, "Google");
            values.put(Organizations.TYPE, Organizations.TYPE_WORK);
            values.put(Organizations.PERSON_ID, personId);
            mResolver.insert(Organizations.CONTENT_URI, values);
        }

        if (email) {
            values.clear();
            values.put(ContactMethods.PERSON_ID, personId);
            values.put(ContactMethods.KIND, Contacts.KIND_EMAIL);
            values.put(ContactMethods.TYPE, ContactMethods.TYPE_HOME);
            values.put(ContactMethods.DATA, "foo@acme.com");
            values.put(ContactMethods.ISPRIMARY, 1);
            mResolver.insert(ContactMethods.CONTENT_URI, values);


            String protocol = ContactMethods
                    .encodePredefinedImProtocol(ContactMethods.PROTOCOL_GOOGLE_TALK);
            values.clear();
            values.put(Presence.IM_PROTOCOL, protocol);
            values.put(Presence.IM_HANDLE, "foo@acme.com");
            values.put(Presence.IM_ACCOUNT, "foo");
            values.put(Presence.PRESENCE_STATUS, Presence.OFFLINE);
            values.put(Presence.PRESENCE_CUSTOM_STATUS, "Coding for Android");
            mResolver.insert(Presence.CONTENT_URI, values);
        }

        if (phone) {
            values.clear();
            values.put(Phones.PERSON_ID, personId);
            values.put(Phones.TYPE, Phones.TYPE_HOME);
            values.put(Phones.NUMBER, "1-800-4664-411");
            values.put(Phones.ISPRIMARY, 1);
            mResolver.insert(Phones.CONTENT_URI, values);
        }

        Uri searchUri = new Uri.Builder().scheme("content").authority(Contacts.AUTHORITY)
                .appendPath(SearchManager.SUGGEST_URI_PATH_QUERY).appendPath(query).build();

        Cursor c = mResolver.query(searchUri, null, null, null, null);
        assertEquals(1, c.getCount());
        c.moveToFirst();
        values.clear();

        String icon1 = c.getString(c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1));
        if (expectIcon1Uri) {
            assertTrue(icon1.startsWith("content:"));
        } else {
            assertEquals(String.valueOf(com.android.internal.R.drawable.ic_contact_picture), icon1);
        }

        // SearchManager does not declare a constant for _id
        values.put("_id", personId);
        values.put(SearchManager.SUGGEST_COLUMN_ICON_2, expectedIcon2);
        values.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, personId);
        values.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, personId);
        values.put(SearchManager.SUGGEST_COLUMN_TEXT_1, expectedText1);
        values.put(SearchManager.SUGGEST_COLUMN_TEXT_2, expectedText2);
        assertCursorValues(c, values);
        c.close();

        // Cleanup
        mResolver.delete(personUri, null, null);
    }

    /**
     * Capturing the search suggestion requirements in test cases as a reference.
     */
    public void testSearchSuggestionsByPhoneNumber() throws Exception {
        // We don't provide compatibility for search suggestions
        return;
    }

    public void testSettings() throws Exception {
        ContentValues values = new ContentValues();
        values.put(Settings._SYNC_ACCOUNT, "foo");
        values.put(Settings._SYNC_ACCOUNT_TYPE, "bar");
        values.put(Settings.KEY, Settings.SYNC_EVERYTHING);
        values.put(Settings.VALUE, 7);
        mResolver.update(Settings.CONTENT_URI, values, null, null);

        assertStoredValue(Settings.CONTENT_URI, Settings._SYNC_ACCOUNT + "='foo' AND "
                + Settings.KEY + "='" + Settings.SYNC_EVERYTHING + "'", null, Settings.VALUE, "7");

        assertStoredValue(ContactsContract.Settings.CONTENT_URI,
                ContactsContract.Settings.ACCOUNT_NAME + "='foo'",
                null, ContactsContract.Settings.SHOULD_SYNC, "7");

        values.clear();
        values.put(ContactsContract.Settings.SHOULD_SYNC, 8);
        mResolver.update(ContactsContract.Settings.CONTENT_URI, values,
                ContactsContract.Settings.ACCOUNT_NAME + "='foo'", null);

        assertStoredValue(Settings.CONTENT_URI, Settings._SYNC_ACCOUNT + "='foo' AND "
                + Settings.KEY + "='" + Settings.SYNC_EVERYTHING + "'", null, Settings.VALUE, "8");
    }

    private Uri insertPerson(String name, ContentValues values) {
        values.put(People.NAME, name);
        return mResolver.insert(People.CONTENT_URI, values);
    }

    private Uri insertLegacyGroup(String name, ContentValues values) {
        values.put(Groups.NAME, name);
        return mResolver.insert(Groups.CONTENT_URI, values);
    }

    private Uri insertLegacyGroupMembership(Uri groupUri, Uri personUri, ContentValues values) {
        long groupId = ContentUris.parseId(groupUri);
        long personId = ContentUris.parseId(personUri);

        values.clear();
        values.put(GroupMembership.GROUP_ID, groupId);
        values.put(GroupMembership.PERSON_ID, personId);
        Uri membershipUri = mResolver.insert(GroupMembership.CONTENT_URI, values);
        return membershipUri;
    }

    private void putContactValues(ContentValues values) {
        putContactValuesExceptName(values);
        values.put(People.NAME, "Deer Dough");
    }

    private void putContactValuesExceptName(ContentValues values) {
        // Populating only unhidden columns
        values.put(People.NOTES, "Cash Cow");
        values.put(People.TIMES_CONTACTED, 3);
        values.put(People.LAST_TIME_CONTACTED, 10);
        values.put(People.CUSTOM_RINGTONE, "ringtone");
        values.put(People.SEND_TO_VOICEMAIL, 1);
        values.put(People.STARRED, 1);
    }

    private void putContactValues2(ContentValues values) {
        // Populating only unhidden columns
        values.put(People.NAME, "John Doe");
        values.put(People.PHONETIC_NAME, "jon dawe");
        values.put(People.NOTES, "Poor Johnny");
        values.put(People.TIMES_CONTACTED, 4);
        values.put(People.LAST_TIME_CONTACTED, 11);
        values.put(People.CUSTOM_RINGTONE, "rangtone");
        values.put(People.SEND_TO_VOICEMAIL, 0);
        values.put(People.STARRED, 0);
    }

    private void assertFilteredContacts(String filter, String... expectedNames) {
        Uri filterUri = Uri.withAppendedPath(People.CONTENT_FILTER_URI, filter);
        Cursor c = mResolver.query(filterUri, null, null, null, null);
        try {
            assertEquals("Record count", expectedNames.length, c.getCount());
            int column = c.getColumnIndex(People.NAME);
            for (int i = 0; i < expectedNames.length; i++) {
                c.moveToNext();
                assertEquals(expectedNames[i], c.getString(column));
            }
        } finally {
            c.close();
        }
    }

    private void assertPersonIdConstraint(Uri uri, String typeColumn, int defaultType) {
        ContentValues values = new ContentValues();
        values.put(typeColumn, defaultType);
        try {
            mResolver.insert(uri, values);
            fail("Inserted row without person ID");
        } catch (Exception e) {
            // Exception expected
        }
    }

    private void assertTypeAndLabelConstraints(Uri uri, String personIdColumn, long personId,
            String typeColumn, int defaultType, int otherType, String labelColumn) {
        ContentValues values = new ContentValues();
        values.put(personIdColumn, personId);
        values.put(labelColumn, "Foo");

        try {
            mResolver.insert(uri, values);
            fail("Inserted row with label without defining type");
        } catch (Exception e) {
            // Exception expected
        }
    }

    protected void assertSelection(Uri uri, ContentValues values, String legacyTable,
            String idColumn, long id) {
        values.put(idColumn, id);
        String qualified = legacyTable + "." + idColumn;
        super.assertSelection(uri, values, qualified, id);
    }
}
