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