1/*
2 * Copyright (C) 2010 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 */
16package com.android.providers.contacts;
17
18import android.content.ContentValues;
19import android.content.Context;
20import android.database.Cursor;
21import android.database.sqlite.SQLiteDatabase;
22import android.provider.ContactsContract.CommonDataKinds.StructuredName;
23import android.provider.ContactsContract.FullNameStyle;
24import android.provider.ContactsContract.PhoneticNameStyle;
25import android.text.TextUtils;
26
27import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
28import com.android.providers.contacts.aggregation.ContactAggregator;
29
30/**
31 * Handler for email address data rows.
32 */
33public class DataRowHandlerForStructuredName extends DataRowHandler {
34    private final NameSplitter mSplitter;
35    private final NameLookupBuilder mNameLookupBuilder;
36    private final StringBuilder mSb = new StringBuilder();
37
38    public DataRowHandlerForStructuredName(Context context, ContactsDatabaseHelper dbHelper,
39            ContactAggregator aggregator, NameSplitter splitter,
40            NameLookupBuilder nameLookupBuilder) {
41        super(context, dbHelper, aggregator, StructuredName.CONTENT_ITEM_TYPE);
42        mSplitter = splitter;
43        mNameLookupBuilder = nameLookupBuilder;
44    }
45
46    @Override
47    public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId,
48            ContentValues values) {
49        fixStructuredNameComponents(values, values);
50
51        long dataId = super.insert(db, txContext, rawContactId, values);
52
53        String name = values.getAsString(StructuredName.DISPLAY_NAME);
54        Integer fullNameStyle = values.getAsInteger(StructuredName.FULL_NAME_STYLE);
55        mNameLookupBuilder.insertNameLookup(rawContactId, dataId, name,
56                fullNameStyle != null
57                        ? mSplitter.getAdjustedFullNameStyle(fullNameStyle)
58                        : FullNameStyle.UNDEFINED);
59        insertNameLookupForPhoneticName(rawContactId, dataId, values);
60        fixRawContactDisplayName(db, txContext, rawContactId);
61        triggerAggregation(txContext, rawContactId);
62        return dataId;
63    }
64
65    @Override
66    public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
67            Cursor c, boolean callerIsSyncAdapter) {
68        final long dataId = c.getLong(DataUpdateQuery._ID);
69        final long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
70
71        final ContentValues augmented = getAugmentedValues(db, dataId, values);
72        if (augmented == null) {  // No change
73            return false;
74        }
75
76        fixStructuredNameComponents(augmented, values);
77
78        super.update(db, txContext, values, c, callerIsSyncAdapter);
79        if (values.containsKey(StructuredName.DISPLAY_NAME) ||
80                values.containsKey(StructuredName.PHONETIC_FAMILY_NAME) ||
81                values.containsKey(StructuredName.PHONETIC_MIDDLE_NAME) ||
82                values.containsKey(StructuredName.PHONETIC_GIVEN_NAME)) {
83            augmented.putAll(values);
84            String name = augmented.getAsString(StructuredName.DISPLAY_NAME);
85            mDbHelper.deleteNameLookup(dataId);
86            Integer fullNameStyle = augmented.getAsInteger(StructuredName.FULL_NAME_STYLE);
87            mNameLookupBuilder.insertNameLookup(rawContactId, dataId, name,
88                    fullNameStyle != null
89                            ? mSplitter.getAdjustedFullNameStyle(fullNameStyle)
90                            : FullNameStyle.UNDEFINED);
91            insertNameLookupForPhoneticName(rawContactId, dataId, augmented);
92        }
93        fixRawContactDisplayName(db, txContext, rawContactId);
94        triggerAggregation(txContext, rawContactId);
95        return true;
96    }
97
98    @Override
99    public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) {
100        long dataId = c.getLong(DataDeleteQuery._ID);
101        long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
102
103        int count = super.delete(db, txContext, c);
104
105        mDbHelper.deleteNameLookup(dataId);
106        fixRawContactDisplayName(db, txContext, rawContactId);
107        triggerAggregation(txContext, rawContactId);
108        return count;
109    }
110
111    /**
112     * Specific list of structured fields.
113     */
114    private final String[] STRUCTURED_FIELDS = new String[] {
115            StructuredName.PREFIX, StructuredName.GIVEN_NAME, StructuredName.MIDDLE_NAME,
116            StructuredName.FAMILY_NAME, StructuredName.SUFFIX
117    };
118
119    /**
120     * Parses the supplied display name, but only if the incoming values do
121     * not already contain structured name parts. Also, if the display name
122     * is not provided, generate one by concatenating first name and last
123     * name.
124     */
125    public void fixStructuredNameComponents(ContentValues augmented, ContentValues update) {
126        final String unstruct = update.getAsString(StructuredName.DISPLAY_NAME);
127
128        final boolean touchedUnstruct = !TextUtils.isEmpty(unstruct);
129        final boolean touchedStruct = !areAllEmpty(update, STRUCTURED_FIELDS);
130
131        if (touchedUnstruct && !touchedStruct) {
132            NameSplitter.Name name = new NameSplitter.Name();
133            mSplitter.split(name, unstruct);
134            name.toValues(update);
135        } else if (!touchedUnstruct
136                && (touchedStruct || areAnySpecified(update, STRUCTURED_FIELDS))) {
137            // We need to update the display name when any structured components
138            // are specified, even when they are null, which is why we are checking
139            // areAnySpecified.  The touchedStruct in the condition is an optimization:
140            // if there are non-null values, we know for a fact that some values are present.
141            NameSplitter.Name name = new NameSplitter.Name();
142            name.fromValues(augmented);
143            // As the name could be changed, let's guess the name style again.
144            name.fullNameStyle = FullNameStyle.UNDEFINED;
145            name.phoneticNameStyle = PhoneticNameStyle.UNDEFINED;
146            mSplitter.guessNameStyle(name);
147            int unadjustedFullNameStyle = name.fullNameStyle;
148            name.fullNameStyle = mSplitter.getAdjustedFullNameStyle(name.fullNameStyle);
149            final String joined = mSplitter.join(name, true, true);
150            update.put(StructuredName.DISPLAY_NAME, joined);
151
152            update.put(StructuredName.FULL_NAME_STYLE, unadjustedFullNameStyle);
153            update.put(StructuredName.PHONETIC_NAME_STYLE, name.phoneticNameStyle);
154        } else if (touchedUnstruct && touchedStruct){
155            if (!update.containsKey(StructuredName.FULL_NAME_STYLE)) {
156                update.put(StructuredName.FULL_NAME_STYLE,
157                        mSplitter.guessFullNameStyle(unstruct));
158            }
159            if (!update.containsKey(StructuredName.PHONETIC_NAME_STYLE)) {
160                NameSplitter.Name name = new NameSplitter.Name();
161                name.fromValues(update);
162                name.phoneticNameStyle = PhoneticNameStyle.UNDEFINED;
163                mSplitter.guessNameStyle(name);
164                update.put(StructuredName.PHONETIC_NAME_STYLE, name.phoneticNameStyle);
165            }
166        }
167    }
168
169    public void insertNameLookupForPhoneticName(long rawContactId, long dataId,
170            ContentValues values) {
171        if (values.containsKey(StructuredName.PHONETIC_FAMILY_NAME)
172                || values.containsKey(StructuredName.PHONETIC_GIVEN_NAME)
173                || values.containsKey(StructuredName.PHONETIC_MIDDLE_NAME)) {
174            mDbHelper.insertNameLookupForPhoneticName(rawContactId, dataId,
175                    values.getAsString(StructuredName.PHONETIC_FAMILY_NAME),
176                    values.getAsString(StructuredName.PHONETIC_MIDDLE_NAME),
177                    values.getAsString(StructuredName.PHONETIC_GIVEN_NAME));
178        }
179    }
180
181    @Override
182    public boolean hasSearchableData() {
183        return true;
184    }
185
186    @Override
187    public boolean containsSearchableColumns(ContentValues values) {
188        return values.containsKey(StructuredName.FAMILY_NAME)
189                || values.containsKey(StructuredName.GIVEN_NAME)
190                || values.containsKey(StructuredName.MIDDLE_NAME)
191                || values.containsKey(StructuredName.PHONETIC_FAMILY_NAME)
192                || values.containsKey(StructuredName.PHONETIC_GIVEN_NAME)
193                || values.containsKey(StructuredName.PHONETIC_MIDDLE_NAME)
194                || values.containsKey(StructuredName.PREFIX)
195                || values.containsKey(StructuredName.SUFFIX);
196    }
197
198    @Override
199    public void appendSearchableData(IndexBuilder builder) {
200        String name = builder.getString(StructuredName.DISPLAY_NAME);
201        Integer fullNameStyle = builder.getInt(StructuredName.FULL_NAME_STYLE);
202
203        mNameLookupBuilder.appendToSearchIndex(builder, name, fullNameStyle != null
204                        ? mSplitter.getAdjustedFullNameStyle(fullNameStyle)
205                        : FullNameStyle.UNDEFINED);
206
207        String phoneticFamily = builder.getString(StructuredName.PHONETIC_FAMILY_NAME);
208        String phoneticMiddle = builder.getString(StructuredName.PHONETIC_MIDDLE_NAME);
209        String phoneticGiven = builder.getString(StructuredName.PHONETIC_GIVEN_NAME);
210
211        // Phonetic name is often spelled without spaces
212        if (!TextUtils.isEmpty(phoneticFamily) || !TextUtils.isEmpty(phoneticMiddle)
213                || !TextUtils.isEmpty(phoneticGiven)) {
214            mSb.setLength(0);
215            if (!TextUtils.isEmpty(phoneticFamily)) {
216                builder.appendName(phoneticFamily);
217                mSb.append(phoneticFamily);
218            }
219            if (!TextUtils.isEmpty(phoneticMiddle)) {
220                builder.appendName(phoneticMiddle);
221                mSb.append(phoneticMiddle);
222            }
223            if (!TextUtils.isEmpty(phoneticGiven)) {
224                builder.appendName(phoneticGiven);
225                mSb.append(phoneticGiven);
226            }
227            final String phoneticName = mSb.toString().trim();
228            int phoneticNameStyle = builder.getInt(StructuredName.PHONETIC_NAME_STYLE);
229            if (phoneticNameStyle == PhoneticNameStyle.UNDEFINED) {
230                phoneticNameStyle = mSplitter.guessPhoneticNameStyle(phoneticName);
231            }
232            builder.appendName(phoneticName);
233            mNameLookupBuilder.appendNameShorthandLookup(builder, phoneticName,
234                    phoneticNameStyle);
235        }
236    }
237}
238