RawContactEditorView.java revision 1044645b9c2050436f4f0e0c7e5b8da2931879ba
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.contacts.editor; 18 19import android.content.Context; 20import android.database.Cursor; 21import android.os.Bundle; 22import android.os.Parcelable; 23import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 24import android.provider.ContactsContract.CommonDataKinds.Nickname; 25import android.provider.ContactsContract.CommonDataKinds.Photo; 26import android.provider.ContactsContract.CommonDataKinds.StructuredName; 27import android.provider.ContactsContract.Contacts; 28import android.provider.ContactsContract.Data; 29import android.text.TextUtils; 30import android.util.AttributeSet; 31import android.view.LayoutInflater; 32import android.view.View; 33import android.view.ViewGroup; 34import android.widget.TextView; 35 36import com.android.contacts.GroupMetaDataLoader; 37import com.android.contacts.R; 38import com.android.contacts.common.model.account.AccountType; 39import com.android.contacts.common.model.account.AccountType.EditType; 40import com.android.contacts.common.model.dataitem.DataKind; 41import com.android.contacts.common.model.RawContactDelta; 42import com.android.contacts.common.model.ValuesDelta; 43import com.android.contacts.common.model.RawContactModifier; 44 45import com.google.common.base.Objects; 46 47import java.util.ArrayList; 48 49/** 50 * Custom view that provides all the editor interaction for a specific 51 * {@link Contacts} represented through an {@link RawContactDelta}. Callers can 52 * reuse this view and quickly rebuild its contents through 53 * {@link #setState(RawContactDelta, AccountType, ViewIdGenerator)}. 54 * <p> 55 * Internal updates are performed against {@link ValuesDelta} so that the 56 * source {@link RawContact} can be swapped out. Any state-based changes, such as 57 * adding {@link Data} rows or changing {@link EditType}, are performed through 58 * {@link RawContactModifier} to ensure that {@link AccountType} are enforced. 59 */ 60public class RawContactEditorView extends BaseRawContactEditorView { 61 private static final String KEY_SUPER_INSTANCE_STATE = "superInstanceState"; 62 63 private LayoutInflater mInflater; 64 65 private StructuredNameEditorView mName; 66 private PhoneticNameEditorView mPhoneticName; 67 private TextFieldsEditorView mNickName; 68 69 private GroupMembershipView mGroupMembershipView; 70 71 private ViewGroup mFields; 72 73 private View mAccountSelector; 74 private TextView mAccountSelectorTypeTextView; 75 private TextView mAccountSelectorNameTextView; 76 77 private View mAccountHeader; 78 private TextView mAccountHeaderTypeTextView; 79 private TextView mAccountHeaderNameTextView; 80 81 private long mRawContactId = -1; 82 private boolean mAutoAddToDefaultGroup = true; 83 private Cursor mGroupMetaData; 84 private DataKind mGroupMembershipKind; 85 private RawContactDelta mState; 86 87 public RawContactEditorView(Context context) { 88 super(context); 89 } 90 91 public RawContactEditorView(Context context, AttributeSet attrs) { 92 super(context, attrs); 93 } 94 95 @Override 96 public void setEnabled(boolean enabled) { 97 super.setEnabled(enabled); 98 99 View view = getPhotoEditor(); 100 if (view != null) { 101 view.setEnabled(enabled); 102 } 103 104 if (mName != null) { 105 mName.setEnabled(enabled); 106 } 107 108 if (mPhoneticName != null) { 109 mPhoneticName.setEnabled(enabled); 110 } 111 112 if (mFields != null) { 113 int count = mFields.getChildCount(); 114 for (int i = 0; i < count; i++) { 115 mFields.getChildAt(i).setEnabled(enabled); 116 } 117 } 118 119 if (mGroupMembershipView != null) { 120 mGroupMembershipView.setEnabled(enabled); 121 } 122 } 123 124 @Override 125 protected void onFinishInflate() { 126 super.onFinishInflate(); 127 128 mInflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 129 130 mName = (StructuredNameEditorView)findViewById(R.id.edit_name); 131 mName.setDeletable(false); 132 133 mPhoneticName = (PhoneticNameEditorView)findViewById(R.id.edit_phonetic_name); 134 mPhoneticName.setDeletable(false); 135 136 mNickName = (TextFieldsEditorView)findViewById(R.id.edit_nick_name); 137 138 mFields = (ViewGroup)findViewById(R.id.sect_fields); 139 140 mAccountHeader = findViewById(R.id.account_header_container); 141 mAccountHeaderTypeTextView = (TextView) findViewById(R.id.account_type); 142 mAccountHeaderNameTextView = (TextView) findViewById(R.id.account_name); 143 144 mAccountSelector = findViewById(R.id.account_selector_container); 145 mAccountSelectorTypeTextView = (TextView) findViewById(R.id.account_type_selector); 146 mAccountSelectorNameTextView = (TextView) findViewById(R.id.account_name_selector); 147 } 148 149 @Override 150 protected Parcelable onSaveInstanceState() { 151 Bundle bundle = new Bundle(); 152 // super implementation of onSaveInstanceState returns null 153 bundle.putParcelable(KEY_SUPER_INSTANCE_STATE, super.onSaveInstanceState()); 154 return bundle; 155 } 156 157 @Override 158 protected void onRestoreInstanceState(Parcelable state) { 159 if (state instanceof Bundle) { 160 Bundle bundle = (Bundle) state; 161 super.onRestoreInstanceState(bundle.getParcelable(KEY_SUPER_INSTANCE_STATE)); 162 return; 163 } 164 super.onRestoreInstanceState(state); 165 } 166 167 /** 168 * Set the internal state for this view, given a current 169 * {@link RawContactDelta} state and the {@link AccountType} that 170 * apply to that state. 171 */ 172 @Override 173 public void setState(RawContactDelta state, AccountType type, ViewIdGenerator vig, 174 boolean isProfile) { 175 176 mState = state; 177 178 // Remove any existing sections 179 mFields.removeAllViews(); 180 181 // Bail if invalid state or account type 182 if (state == null || type == null) return; 183 184 setId(vig.getId(state, null, null, ViewIdGenerator.NO_VIEW_INDEX)); 185 186 // Make sure we have a StructuredName 187 RawContactModifier.ensureKindExists(state, type, StructuredName.CONTENT_ITEM_TYPE); 188 189 mRawContactId = state.getRawContactId(); 190 191 // Fill in the account info 192 if (isProfile) { 193 String accountName = state.getAccountName(); 194 if (TextUtils.isEmpty(accountName)) { 195 mAccountHeaderNameTextView.setVisibility(View.GONE); 196 mAccountHeaderTypeTextView.setText(R.string.local_profile_title); 197 } else { 198 CharSequence accountType = type.getDisplayLabel(getContext()); 199 mAccountHeaderTypeTextView.setText(getContext().getString( 200 R.string.external_profile_title, 201 accountType)); 202 mAccountHeaderNameTextView.setText(accountName); 203 } 204 } else { 205 String accountName = state.getAccountName(); 206 CharSequence accountType = type.getDisplayLabel(getContext()); 207 if (TextUtils.isEmpty(accountType)) { 208 accountType = getContext().getString(R.string.account_phone); 209 } 210 if (!TextUtils.isEmpty(accountName)) { 211 mAccountHeaderNameTextView.setVisibility(View.VISIBLE); 212 mAccountHeaderNameTextView.setText( 213 getContext().getString(R.string.from_account_format, accountName)); 214 } else { 215 // Hide this view so the other text view will be centered vertically 216 mAccountHeaderNameTextView.setVisibility(View.GONE); 217 } 218 mAccountHeaderTypeTextView.setText( 219 getContext().getString(R.string.account_type_format, accountType)); 220 } 221 updateAccountHeaderContentDescription(); 222 223 // The account selector and header are both used to display the same information. 224 mAccountSelectorTypeTextView.setText(mAccountHeaderTypeTextView.getText()); 225 mAccountSelectorTypeTextView.setVisibility(mAccountHeaderTypeTextView.getVisibility()); 226 mAccountSelectorNameTextView.setText(mAccountHeaderNameTextView.getText()); 227 mAccountSelectorNameTextView.setVisibility(mAccountHeaderNameTextView.getVisibility()); 228 // Showing the account header at the same time as the account selector drop down is 229 // confusing. They should be mutually exclusive. 230 mAccountHeader.setVisibility(mAccountSelector.getVisibility() == View.GONE 231 ? View.VISIBLE : View.GONE); 232 233 // Show photo editor when supported 234 RawContactModifier.ensureKindExists(state, type, Photo.CONTENT_ITEM_TYPE); 235 setHasPhotoEditor((type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null)); 236 getPhotoEditor().setEnabled(isEnabled()); 237 mName.setEnabled(isEnabled()); 238 239 mPhoneticName.setEnabled(isEnabled()); 240 241 // Show and hide the appropriate views 242 mFields.setVisibility(View.VISIBLE); 243 mName.setVisibility(View.VISIBLE); 244 mPhoneticName.setVisibility(View.VISIBLE); 245 246 mGroupMembershipKind = type.getKindForMimetype(GroupMembership.CONTENT_ITEM_TYPE); 247 if (mGroupMembershipKind != null) { 248 mGroupMembershipView = (GroupMembershipView)mInflater.inflate( 249 R.layout.item_group_membership, mFields, false); 250 mGroupMembershipView.setKind(mGroupMembershipKind); 251 mGroupMembershipView.setEnabled(isEnabled()); 252 } 253 254 // Create editor sections for each possible data kind 255 for (DataKind kind : type.getSortedDataKinds()) { 256 // Skip kind of not editable 257 if (!kind.editable) continue; 258 259 final String mimeType = kind.mimeType; 260 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) { 261 // Handle special case editor for structured name 262 final ValuesDelta primary = state.getPrimaryEntry(mimeType); 263 mName.setValues( 264 type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME), 265 primary, state, false, vig); 266 mPhoneticName.setValues( 267 type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME), 268 primary, state, false, vig); 269 // It is useful to use Nickname outside of a KindSectionView so that we can treat it 270 // as a part of StructuredName's fake KindSectionView, even though it uses a 271 // different CP2 mime-type. We do a bit of extra work below to make this possible. 272 final DataKind nickNameKind = type.getKindForMimetype(Nickname.CONTENT_ITEM_TYPE); 273 if (nickNameKind != null) { 274 ValuesDelta primaryNickNameEntry = state.getPrimaryEntry(nickNameKind.mimeType); 275 if (primaryNickNameEntry == null) { 276 primaryNickNameEntry = RawContactModifier.insertChild(state, nickNameKind); 277 } 278 mNickName.setValues(nickNameKind, primaryNickNameEntry, state, false, vig); 279 mNickName.setDeletable(false); 280 } else { 281 mPhoneticName.setPadding(0, 0, 0, (int) getResources().getDimension( 282 R.dimen.editor_padding_between_editor_views)); 283 mNickName.setVisibility(View.GONE); 284 } 285 } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) { 286 // Handle special case editor for photos 287 final ValuesDelta primary = state.getPrimaryEntry(mimeType); 288 getPhotoEditor().setValues(kind, primary, state, false, vig); 289 } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) { 290 if (mGroupMembershipView != null) { 291 mGroupMembershipView.setState(state); 292 mFields.addView(mGroupMembershipView); 293 } 294 } else if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType) 295 || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType) 296 || Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) { 297 // Don't create fields for each of these mime-types. They are handled specially. 298 continue; 299 } else { 300 // Otherwise use generic section-based editors 301 if (kind.fieldList == null) continue; 302 final KindSectionView section = (KindSectionView)mInflater.inflate( 303 R.layout.item_kind_section, mFields, false); 304 section.setEnabled(isEnabled()); 305 section.setState(kind, state, /* readOnly =*/ false, 306 /* showOneEmptyEditor =*/ true, vig); 307 mFields.addView(section); 308 } 309 } 310 311 addToDefaultGroupIfNeeded(); 312 } 313 314 @Override 315 public void setGroupMetaData(Cursor groupMetaData) { 316 mGroupMetaData = groupMetaData; 317 addToDefaultGroupIfNeeded(); 318 if (mGroupMembershipView != null) { 319 mGroupMembershipView.setGroupMetaData(groupMetaData); 320 } 321 } 322 323 public void setAutoAddToDefaultGroup(boolean flag) { 324 this.mAutoAddToDefaultGroup = flag; 325 } 326 327 /** 328 * If automatic addition to the default group was requested (see 329 * {@link #setAutoAddToDefaultGroup}, checks if the raw contact is in any 330 * group and if it is not adds it to the default group (in case of Google 331 * contacts that's "My Contacts"). 332 */ 333 private void addToDefaultGroupIfNeeded() { 334 if (!mAutoAddToDefaultGroup || mGroupMetaData == null || mGroupMetaData.isClosed() 335 || mState == null) { 336 return; 337 } 338 339 boolean hasGroupMembership = false; 340 ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE); 341 if (entries != null) { 342 for (ValuesDelta values : entries) { 343 Long id = values.getGroupRowId(); 344 if (id != null && id.longValue() != 0) { 345 hasGroupMembership = true; 346 break; 347 } 348 } 349 } 350 351 if (!hasGroupMembership) { 352 long defaultGroupId = getDefaultGroupId(); 353 if (defaultGroupId != -1) { 354 ValuesDelta entry = RawContactModifier.insertChild(mState, mGroupMembershipKind); 355 if (entry != null) { 356 entry.setGroupRowId(defaultGroupId); 357 } 358 } 359 } 360 } 361 362 /** 363 * Returns the default group (e.g. "My Contacts") for the current raw contact's 364 * account. Returns -1 if there is no such group. 365 */ 366 private long getDefaultGroupId() { 367 String accountType = mState.getAccountType(); 368 String accountName = mState.getAccountName(); 369 String accountDataSet = mState.getDataSet(); 370 mGroupMetaData.moveToPosition(-1); 371 while (mGroupMetaData.moveToNext()) { 372 String name = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME); 373 String type = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE); 374 String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET); 375 if (name.equals(accountName) && type.equals(accountType) 376 && Objects.equal(dataSet, accountDataSet)) { 377 long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID); 378 if (!mGroupMetaData.isNull(GroupMetaDataLoader.AUTO_ADD) 379 && mGroupMetaData.getInt(GroupMetaDataLoader.AUTO_ADD) != 0) { 380 return groupId; 381 } 382 } 383 } 384 return -1; 385 } 386 387 public StructuredNameEditorView getNameEditor() { 388 return mName; 389 } 390 391 public TextFieldsEditorView getPhoneticNameEditor() { 392 return mPhoneticName; 393 } 394 395 public TextFieldsEditorView getNickNameEditor() { 396 return mNickName; 397 } 398 399 @Override 400 public long getRawContactId() { 401 return mRawContactId; 402 } 403} 404