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