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