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