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.StructuredName;
23 import android.provider.ContactsContract.FullNameStyle;
24 import android.provider.ContactsContract.PhoneticNameStyle;
25 import android.text.TextUtils;
26 
27 import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
28 import com.android.providers.contacts.aggregation.ContactAggregator;
29 
30 /**
31  * Handler for email address data rows.
32  */
33 public class DataRowHandlerForStructuredName extends DataRowHandler {
34     private final NameSplitter mSplitter;
35     private final NameLookupBuilder mNameLookupBuilder;
36     private final StringBuilder mSb = new StringBuilder();
37 
DataRowHandlerForStructuredName(Context context, ContactsDatabaseHelper dbHelper, ContactAggregator aggregator, NameSplitter splitter, NameLookupBuilder nameLookupBuilder)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
insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, ContentValues values)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
update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, Cursor c, boolean callerIsSyncAdapter)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
delete(SQLiteDatabase db, TransactionContext txContext, Cursor c)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      */
fixStructuredNameComponents(ContentValues augmented, ContentValues update)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
hasSearchableData()165     public boolean hasSearchableData() {
166         return true;
167     }
168 
169     @Override
containsSearchableColumns(ContentValues values)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
appendSearchableData(IndexBuilder builder)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