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