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        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            augmented.putAll(values);
80            String name = augmented.getAsString(StructuredName.DISPLAY_NAME);
81            mDbHelper.deleteNameLookup(dataId);
82            Integer fullNameStyle = augmented.getAsInteger(StructuredName.FULL_NAME_STYLE);
83            mNameLookupBuilder.insertNameLookup(rawContactId, dataId, name,
84                    fullNameStyle != null
85                            ? mSplitter.getAdjustedFullNameStyle(fullNameStyle)
86                            : FullNameStyle.UNDEFINED);
87        }
88        fixRawContactDisplayName(db, txContext, rawContactId);
89        triggerAggregation(txContext, rawContactId);
90        return true;
91    }
92
93    @Override
94    public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) {
95        long dataId = c.getLong(DataDeleteQuery._ID);
96        long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
97
98        int count = super.delete(db, txContext, c);
99
100        mDbHelper.deleteNameLookup(dataId);
101        fixRawContactDisplayName(db, txContext, rawContactId);
102        triggerAggregation(txContext, rawContactId);
103        return count;
104    }
105
106    /**
107     * Specific list of structured fields.
108     */
109    private final String[] STRUCTURED_FIELDS = new String[] {
110            StructuredName.PREFIX, StructuredName.GIVEN_NAME, StructuredName.MIDDLE_NAME,
111            StructuredName.FAMILY_NAME, StructuredName.SUFFIX
112    };
113
114    /**
115     * Parses the supplied display name, but only if the incoming values do
116     * not already contain structured name parts. Also, if the display name
117     * is not provided, generate one by concatenating first name and last
118     * name.
119     */
120    public void fixStructuredNameComponents(ContentValues augmented, ContentValues update) {
121        final String unstruct = update.getAsString(StructuredName.DISPLAY_NAME);
122
123        final boolean touchedUnstruct = !TextUtils.isEmpty(unstruct);
124        final boolean touchedStruct = !areAllEmpty(update, STRUCTURED_FIELDS);
125
126        if (touchedUnstruct && !touchedStruct) {
127            NameSplitter.Name name = new NameSplitter.Name();
128            mSplitter.split(name, unstruct);
129            name.toValues(update);
130        } else if (!touchedUnstruct
131                && (touchedStruct || areAnySpecified(update, STRUCTURED_FIELDS))) {
132            // We need to update the display name when any structured components
133            // are specified, even when they are null, which is why we are checking
134            // areAnySpecified.  The touchedStruct in the condition is an optimization:
135            // if there are non-null values, we know for a fact that some values are present.
136            NameSplitter.Name name = new NameSplitter.Name();
137            name.fromValues(augmented);
138            // As the name could be changed, let's guess the name style again.
139            name.fullNameStyle = FullNameStyle.UNDEFINED;
140            name.phoneticNameStyle = PhoneticNameStyle.UNDEFINED;
141            mSplitter.guessNameStyle(name);
142            int unadjustedFullNameStyle = name.fullNameStyle;
143            name.fullNameStyle = mSplitter.getAdjustedFullNameStyle(name.fullNameStyle);
144            final String joined = mSplitter.join(name, true, true);
145            update.put(StructuredName.DISPLAY_NAME, joined);
146
147            update.put(StructuredName.FULL_NAME_STYLE, unadjustedFullNameStyle);
148            update.put(StructuredName.PHONETIC_NAME_STYLE, name.phoneticNameStyle);
149        } else if (touchedUnstruct && touchedStruct){
150            if (!update.containsKey(StructuredName.FULL_NAME_STYLE)) {
151                update.put(StructuredName.FULL_NAME_STYLE,
152                        mSplitter.guessFullNameStyle(unstruct));
153            }
154            if (!update.containsKey(StructuredName.PHONETIC_NAME_STYLE)) {
155                NameSplitter.Name name = new NameSplitter.Name();
156                name.fromValues(update);
157                name.phoneticNameStyle = PhoneticNameStyle.UNDEFINED;
158                mSplitter.guessNameStyle(name);
159                update.put(StructuredName.PHONETIC_NAME_STYLE, name.phoneticNameStyle);
160            }
161        }
162    }
163
164    @Override
165    public boolean hasSearchableData() {
166        return true;
167    }
168
169    @Override
170    public boolean containsSearchableColumns(ContentValues values) {
171        return values.containsKey(StructuredName.FAMILY_NAME)
172                || values.containsKey(StructuredName.GIVEN_NAME)
173                || values.containsKey(StructuredName.MIDDLE_NAME)
174                || values.containsKey(StructuredName.PHONETIC_FAMILY_NAME)
175                || values.containsKey(StructuredName.PHONETIC_GIVEN_NAME)
176                || values.containsKey(StructuredName.PHONETIC_MIDDLE_NAME)
177                || values.containsKey(StructuredName.PREFIX)
178                || values.containsKey(StructuredName.SUFFIX);
179    }
180
181    @Override
182    public void appendSearchableData(IndexBuilder builder) {
183        String name = builder.getString(StructuredName.DISPLAY_NAME);
184        Integer fullNameStyle = builder.getInt(StructuredName.FULL_NAME_STYLE);
185
186        mNameLookupBuilder.appendToSearchIndex(builder, name, fullNameStyle != null
187                        ? mSplitter.getAdjustedFullNameStyle(fullNameStyle)
188                        : FullNameStyle.UNDEFINED);
189
190        String phoneticFamily = builder.getString(StructuredName.PHONETIC_FAMILY_NAME);
191        String phoneticMiddle = builder.getString(StructuredName.PHONETIC_MIDDLE_NAME);
192        String phoneticGiven = builder.getString(StructuredName.PHONETIC_GIVEN_NAME);
193
194        // Phonetic name is often spelled without spaces
195        if (!TextUtils.isEmpty(phoneticFamily) || !TextUtils.isEmpty(phoneticMiddle)
196                || !TextUtils.isEmpty(phoneticGiven)) {
197            mSb.setLength(0);
198            if (!TextUtils.isEmpty(phoneticFamily)) {
199                builder.appendName(phoneticFamily);
200                mSb.append(phoneticFamily);
201            }
202            if (!TextUtils.isEmpty(phoneticMiddle)) {
203                builder.appendName(phoneticMiddle);
204                mSb.append(phoneticMiddle);
205            }
206            if (!TextUtils.isEmpty(phoneticGiven)) {
207                builder.appendName(phoneticGiven);
208                mSb.append(phoneticGiven);
209            }
210            final String phoneticName = mSb.toString().trim();
211            int phoneticNameStyle = builder.getInt(StructuredName.PHONETIC_NAME_STYLE);
212            if (phoneticNameStyle == PhoneticNameStyle.UNDEFINED) {
213                phoneticNameStyle = mSplitter.guessPhoneticNameStyle(phoneticName);
214            }
215            builder.appendName(phoneticName);
216            mNameLookupBuilder.appendNameShorthandLookup(builder, phoneticName,
217                    phoneticNameStyle);
218        }
219    }
220}
221