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.AbstractContactAggregator; 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, AbstractContactAggregator aggregator, NameSplitter splitter, NameLookupBuilder nameLookupBuilder)38 public DataRowHandlerForStructuredName(Context context, ContactsDatabaseHelper dbHelper, 39 AbstractContactAggregator 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, boolean callerIsMetadataSyncAdapter)65 public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, 66 Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) { 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, callerIsMetadataSyncAdapter); 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