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