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 17 package com.android.contacts.editor; 18 19 import android.content.Context; 20 import android.database.Cursor; 21 import android.os.Bundle; 22 import android.os.Parcelable; 23 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 24 import android.provider.ContactsContract.CommonDataKinds.Nickname; 25 import android.provider.ContactsContract.CommonDataKinds.Photo; 26 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 27 import android.provider.ContactsContract.Contacts; 28 import android.provider.ContactsContract.Data; 29 import android.text.TextUtils; 30 import android.util.AttributeSet; 31 import android.view.LayoutInflater; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.widget.TextView; 35 36 import com.android.contacts.GroupMetaDataLoader; 37 import com.android.contacts.R; 38 import com.android.contacts.common.model.account.AccountType; 39 import com.android.contacts.common.model.account.AccountType.EditType; 40 import com.android.contacts.common.model.dataitem.DataKind; 41 import com.android.contacts.common.model.RawContactDelta; 42 import com.android.contacts.common.model.ValuesDelta; 43 import com.android.contacts.common.model.RawContactModifier; 44 45 import com.google.common.base.Objects; 46 47 import 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 */ 60 public 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 RawContactEditorView(Context context)87 public RawContactEditorView(Context context) { 88 super(context); 89 } 90 RawContactEditorView(Context context, AttributeSet attrs)91 public RawContactEditorView(Context context, AttributeSet attrs) { 92 super(context, attrs); 93 } 94 95 @Override setEnabled(boolean enabled)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 onFinishInflate()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 onSaveInstanceState()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 onRestoreInstanceState(Parcelable state)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 setState(RawContactDelta state, AccountType type, ViewIdGenerator vig, boolean isProfile)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(mContext); 199 mAccountHeaderTypeTextView.setText(mContext.getString(R.string.external_profile_title, 200 accountType)); 201 mAccountHeaderNameTextView.setText(accountName); 202 } 203 } else { 204 String accountName = state.getAccountName(); 205 CharSequence accountType = type.getDisplayLabel(mContext); 206 if (TextUtils.isEmpty(accountType)) { 207 accountType = mContext.getString(R.string.account_phone); 208 } 209 if (!TextUtils.isEmpty(accountName)) { 210 mAccountHeaderNameTextView.setVisibility(View.VISIBLE); 211 mAccountHeaderNameTextView.setText( 212 mContext.getString(R.string.from_account_format, accountName)); 213 } else { 214 // Hide this view so the other text view will be centered vertically 215 mAccountHeaderNameTextView.setVisibility(View.GONE); 216 } 217 mAccountHeaderTypeTextView.setText( 218 mContext.getString(R.string.account_type_format, accountType)); 219 } 220 updateAccountHeaderContentDescription(); 221 222 // The account selector and header are both used to display the same information. 223 mAccountSelectorTypeTextView.setText(mAccountHeaderTypeTextView.getText()); 224 mAccountSelectorTypeTextView.setVisibility(mAccountHeaderTypeTextView.getVisibility()); 225 mAccountSelectorNameTextView.setText(mAccountHeaderNameTextView.getText()); 226 mAccountSelectorNameTextView.setVisibility(mAccountHeaderNameTextView.getVisibility()); 227 // Showing the account header at the same time as the account selector drop down is 228 // confusing. They should be mutually exclusive. 229 mAccountHeader.setVisibility(mAccountSelector.getVisibility() == View.GONE 230 ? View.VISIBLE : View.GONE); 231 232 // Show photo editor when supported 233 RawContactModifier.ensureKindExists(state, type, Photo.CONTENT_ITEM_TYPE); 234 setHasPhotoEditor((type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null)); 235 getPhotoEditor().setEnabled(isEnabled()); 236 mName.setEnabled(isEnabled()); 237 238 mPhoneticName.setEnabled(isEnabled()); 239 240 // Show and hide the appropriate views 241 mFields.setVisibility(View.VISIBLE); 242 mName.setVisibility(View.VISIBLE); 243 mPhoneticName.setVisibility(View.VISIBLE); 244 245 mGroupMembershipKind = type.getKindForMimetype(GroupMembership.CONTENT_ITEM_TYPE); 246 if (mGroupMembershipKind != null) { 247 mGroupMembershipView = (GroupMembershipView)mInflater.inflate( 248 R.layout.item_group_membership, mFields, false); 249 mGroupMembershipView.setKind(mGroupMembershipKind); 250 mGroupMembershipView.setEnabled(isEnabled()); 251 } 252 253 // Create editor sections for each possible data kind 254 for (DataKind kind : type.getSortedDataKinds()) { 255 // Skip kind of not editable 256 if (!kind.editable) continue; 257 258 final String mimeType = kind.mimeType; 259 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) { 260 // Handle special case editor for structured name 261 final ValuesDelta primary = state.getPrimaryEntry(mimeType); 262 mName.setValues( 263 type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME), 264 primary, state, false, vig); 265 mPhoneticName.setValues( 266 type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME), 267 primary, state, false, vig); 268 // It is useful to use Nickname outside of a KindSectionView so that we can treat it 269 // as a part of StructuredName's fake KindSectionView, even though it uses a 270 // different CP2 mime-type. We do a bit of extra work below to make this possible. 271 final DataKind nickNameKind = type.getKindForMimetype(Nickname.CONTENT_ITEM_TYPE); 272 if (nickNameKind != null) { 273 ValuesDelta primaryNickNameEntry = state.getPrimaryEntry(nickNameKind.mimeType); 274 if (primaryNickNameEntry == null) { 275 primaryNickNameEntry = RawContactModifier.insertChild(state, nickNameKind); 276 } 277 mNickName.setValues(nickNameKind, primaryNickNameEntry, state, false, vig); 278 mNickName.setDeletable(false); 279 } else { 280 mPhoneticName.setPadding(0, 0, 0, (int) getResources().getDimension( 281 R.dimen.editor_padding_between_editor_views)); 282 mNickName.setVisibility(View.GONE); 283 } 284 } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) { 285 // Handle special case editor for photos 286 final ValuesDelta primary = state.getPrimaryEntry(mimeType); 287 getPhotoEditor().setValues(kind, primary, state, false, vig); 288 } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) { 289 if (mGroupMembershipView != null) { 290 mGroupMembershipView.setState(state); 291 mFields.addView(mGroupMembershipView); 292 } 293 } else if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType) 294 || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType) 295 || Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) { 296 // Don't create fields for each of these mime-types. They are handled specially. 297 continue; 298 } else { 299 // Otherwise use generic section-based editors 300 if (kind.fieldList == null) continue; 301 final KindSectionView section = (KindSectionView)mInflater.inflate( 302 R.layout.item_kind_section, mFields, false); 303 section.setEnabled(isEnabled()); 304 section.setState(kind, state, false, vig); 305 mFields.addView(section); 306 } 307 } 308 309 addToDefaultGroupIfNeeded(); 310 } 311 312 @Override setGroupMetaData(Cursor groupMetaData)313 public void setGroupMetaData(Cursor groupMetaData) { 314 mGroupMetaData = groupMetaData; 315 addToDefaultGroupIfNeeded(); 316 if (mGroupMembershipView != null) { 317 mGroupMembershipView.setGroupMetaData(groupMetaData); 318 } 319 } 320 setAutoAddToDefaultGroup(boolean flag)321 public void setAutoAddToDefaultGroup(boolean flag) { 322 this.mAutoAddToDefaultGroup = flag; 323 } 324 325 /** 326 * If automatic addition to the default group was requested (see 327 * {@link #setAutoAddToDefaultGroup}, checks if the raw contact is in any 328 * group and if it is not adds it to the default group (in case of Google 329 * contacts that's "My Contacts"). 330 */ addToDefaultGroupIfNeeded()331 private void addToDefaultGroupIfNeeded() { 332 if (!mAutoAddToDefaultGroup || mGroupMetaData == null || mGroupMetaData.isClosed() 333 || mState == null) { 334 return; 335 } 336 337 boolean hasGroupMembership = false; 338 ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE); 339 if (entries != null) { 340 for (ValuesDelta values : entries) { 341 Long id = values.getGroupRowId(); 342 if (id != null && id.longValue() != 0) { 343 hasGroupMembership = true; 344 break; 345 } 346 } 347 } 348 349 if (!hasGroupMembership) { 350 long defaultGroupId = getDefaultGroupId(); 351 if (defaultGroupId != -1) { 352 ValuesDelta entry = RawContactModifier.insertChild(mState, mGroupMembershipKind); 353 if (entry != null) { 354 entry.setGroupRowId(defaultGroupId); 355 } 356 } 357 } 358 } 359 360 /** 361 * Returns the default group (e.g. "My Contacts") for the current raw contact's 362 * account. Returns -1 if there is no such group. 363 */ getDefaultGroupId()364 private long getDefaultGroupId() { 365 String accountType = mState.getAccountType(); 366 String accountName = mState.getAccountName(); 367 String accountDataSet = mState.getDataSet(); 368 mGroupMetaData.moveToPosition(-1); 369 while (mGroupMetaData.moveToNext()) { 370 String name = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME); 371 String type = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_TYPE); 372 String dataSet = mGroupMetaData.getString(GroupMetaDataLoader.DATA_SET); 373 if (name.equals(accountName) && type.equals(accountType) 374 && Objects.equal(dataSet, accountDataSet)) { 375 long groupId = mGroupMetaData.getLong(GroupMetaDataLoader.GROUP_ID); 376 if (!mGroupMetaData.isNull(GroupMetaDataLoader.AUTO_ADD) 377 && mGroupMetaData.getInt(GroupMetaDataLoader.AUTO_ADD) != 0) { 378 return groupId; 379 } 380 } 381 } 382 return -1; 383 } 384 getNameEditor()385 public StructuredNameEditorView getNameEditor() { 386 return mName; 387 } 388 getPhoneticNameEditor()389 public TextFieldsEditorView getPhoneticNameEditor() { 390 return mPhoneticName; 391 } 392 getNickNameEditor()393 public TextFieldsEditorView getNickNameEditor() { 394 return mNickName; 395 } 396 397 @Override getRawContactId()398 public long getRawContactId() { 399 return mRawContactId; 400 } 401 } 402