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 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