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.ui.widget; 18 19 import com.android.contacts.R; 20 import com.android.contacts.model.ContactsSource; 21 import com.android.contacts.model.EntityDelta; 22 import com.android.contacts.model.EntityModifier; 23 import com.android.contacts.model.ContactsSource.DataKind; 24 import com.android.contacts.model.ContactsSource.EditType; 25 import com.android.contacts.model.Editor.EditorListener; 26 import com.android.contacts.model.EntityDelta.ValuesDelta; 27 import com.android.contacts.ui.ViewIdGenerator; 28 29 import android.content.Context; 30 import android.content.Entity; 31 import android.content.res.Resources; 32 import android.graphics.drawable.Drawable; 33 import android.os.Parcel; 34 import android.os.Parcelable; 35 import android.provider.ContactsContract.Contacts; 36 import android.provider.ContactsContract.Data; 37 import android.provider.ContactsContract.RawContacts; 38 import android.provider.ContactsContract.CommonDataKinds.Photo; 39 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 40 import android.text.TextUtils; 41 import android.util.AttributeSet; 42 import android.view.LayoutInflater; 43 import android.view.View; 44 import android.view.ViewGroup; 45 import android.view.View.OnClickListener; 46 import android.widget.ImageView; 47 import android.widget.TextView; 48 49 /** 50 * Custom view that provides all the editor interaction for a specific 51 * {@link Contacts} represented through an {@link EntityDelta}. Callers can 52 * reuse this view and quickly rebuild its contents through 53 * {@link #setState(EntityDelta, ContactsSource)}. 54 * <p> 55 * Internal updates are performed against {@link ValuesDelta} so that the 56 * source {@link Entity} can be swapped out. Any state-based changes, such as 57 * adding {@link Data} rows or changing {@link EditType}, are performed through 58 * {@link EntityModifier} to ensure that {@link ContactsSource} are enforced. 59 */ 60 public class ContactEditorView extends BaseContactEditorView implements OnClickListener { 61 private TextView mReadOnly; 62 private TextView mReadOnlyName; 63 64 private View mPhotoStub; 65 private GenericEditorView mName; 66 67 private boolean mIsSourceReadOnly; 68 private ViewGroup mGeneral; 69 private ViewGroup mSecondary; 70 private boolean mSecondaryVisible; 71 72 private TextView mSecondaryHeader; 73 74 private Drawable mSecondaryOpen; 75 private Drawable mSecondaryClosed; 76 77 private View mHeaderColorBar; 78 private View mSideBar; 79 private ImageView mHeaderIcon; 80 private TextView mHeaderAccountType; 81 private TextView mHeaderAccountName; 82 83 private long mRawContactId = -1; 84 85 public ContactEditorView(Context context) { 86 super(context); 87 } 88 89 public ContactEditorView(Context context, AttributeSet attrs) { 90 super(context, attrs); 91 } 92 93 /** {@inheritDoc} */ 94 @Override 95 protected void onFinishInflate() { 96 super.onFinishInflate(); 97 98 mInflater = (LayoutInflater)getContext().getSystemService( 99 Context.LAYOUT_INFLATER_SERVICE); 100 101 mPhoto = (PhotoEditorView)findViewById(R.id.edit_photo); 102 mPhotoStub = findViewById(R.id.stub_photo); 103 104 final int photoSize = getResources().getDimensionPixelSize(R.dimen.edit_photo_size); 105 106 mReadOnly = (TextView)findViewById(R.id.edit_read_only); 107 108 mName = (GenericEditorView)findViewById(R.id.edit_name); 109 mName.setMinimumHeight(photoSize); 110 mName.setDeletable(false); 111 112 mReadOnlyName = (TextView) findViewById(R.id.read_only_name); 113 114 mGeneral = (ViewGroup)findViewById(R.id.sect_general); 115 mSecondary = (ViewGroup)findViewById(R.id.sect_secondary); 116 117 mHeaderColorBar = findViewById(R.id.header_color_bar); 118 mSideBar = findViewById(R.id.color_bar); 119 mHeaderIcon = (ImageView) findViewById(R.id.header_icon); 120 mHeaderAccountType = (TextView) findViewById(R.id.header_account_type); 121 mHeaderAccountName = (TextView) findViewById(R.id.header_account_name); 122 123 mSecondaryHeader = (TextView)findViewById(R.id.head_secondary); 124 mSecondaryHeader.setOnClickListener(this); 125 126 final Resources res = getResources(); 127 mSecondaryOpen = res.getDrawable(com.android.internal.R.drawable.expander_ic_maximized); 128 mSecondaryClosed = res.getDrawable(com.android.internal.R.drawable.expander_ic_minimized); 129 130 this.setSecondaryVisible(false); 131 } 132 133 /** {@inheritDoc} */ 134 public void onClick(View v) { 135 // Toggle visibility of secondary kinds 136 final boolean makeVisible = mSecondary.getVisibility() != View.VISIBLE; 137 this.setSecondaryVisible(makeVisible); 138 } 139 140 /** 141 * Set the visibility of secondary sections, along with header icon. 142 * 143 * <p>If the source is read-only and there's no secondary fields, the entire secondary section 144 * will be hidden. 145 */ 146 private void setSecondaryVisible(boolean makeVisible) { 147 mSecondaryVisible = makeVisible; 148 149 if (!mIsSourceReadOnly && mSecondary.getChildCount() > 0) { 150 mSecondaryHeader.setVisibility(View.VISIBLE); 151 mSecondaryHeader.setCompoundDrawablesWithIntrinsicBounds( 152 makeVisible ? mSecondaryOpen : mSecondaryClosed, null, null, null); 153 mSecondary.setVisibility(makeVisible ? View.VISIBLE : View.GONE); 154 } else { 155 mSecondaryHeader.setVisibility(View.GONE); 156 mSecondary.setVisibility(View.GONE); 157 } 158 } 159 160 /** 161 * Set the internal state for this view, given a current 162 * {@link EntityDelta} state and the {@link ContactsSource} that 163 * apply to that state. 164 */ 165 @Override 166 public void setState(EntityDelta state, ContactsSource source, ViewIdGenerator vig) { 167 // Remove any existing sections 168 mGeneral.removeAllViews(); 169 mSecondary.removeAllViews(); 170 171 // Bail if invalid state or source 172 if (state == null || source == null) return; 173 174 setId(vig.getId(state, null, null, ViewIdGenerator.NO_VIEW_INDEX)); 175 176 mIsSourceReadOnly = source.readOnly; 177 178 // Make sure we have StructuredName 179 EntityModifier.ensureKindExists(state, source, StructuredName.CONTENT_ITEM_TYPE); 180 181 // Fill in the header info 182 ValuesDelta values = state.getValues(); 183 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME); 184 CharSequence accountType = source.getDisplayLabel(mContext); 185 if (TextUtils.isEmpty(accountType)) { 186 accountType = mContext.getString(R.string.account_phone); 187 } 188 if (!TextUtils.isEmpty(accountName)) { 189 mHeaderAccountName.setText( 190 mContext.getString(R.string.from_account_format, accountName)); 191 } 192 mHeaderAccountType.setText(mContext.getString(R.string.account_type_format, accountType)); 193 mHeaderIcon.setImageDrawable(source.getDisplayIcon(mContext)); 194 195 mRawContactId = values.getAsLong(RawContacts._ID); 196 197 // Show photo editor when supported 198 EntityModifier.ensureKindExists(state, source, Photo.CONTENT_ITEM_TYPE); 199 mHasPhotoEditor = (source.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null); 200 mPhoto.setVisibility(mHasPhotoEditor ? View.VISIBLE : View.GONE); 201 mPhoto.setEnabled(!mIsSourceReadOnly); 202 mName.setEnabled(!mIsSourceReadOnly); 203 204 // Show and hide the appropriate views 205 if (mIsSourceReadOnly) { 206 mGeneral.setVisibility(View.GONE); 207 mName.setVisibility(View.GONE); 208 mReadOnly.setVisibility(View.VISIBLE); 209 mReadOnly.setText(mContext.getString(R.string.contact_read_only, accountType)); 210 mReadOnlyName.setVisibility(View.VISIBLE); 211 } else { 212 mGeneral.setVisibility(View.VISIBLE); 213 mName.setVisibility(View.VISIBLE); 214 mReadOnly.setVisibility(View.GONE); 215 mReadOnlyName.setVisibility(View.GONE); 216 } 217 218 boolean anySecondaryFieldFilled = false; 219 // Create editor sections for each possible data kind 220 for (DataKind kind : source.getSortedDataKinds()) { 221 // Skip kind of not editable 222 if (!kind.editable) continue; 223 224 final String mimeType = kind.mimeType; 225 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) { 226 // Handle special case editor for structured name 227 final ValuesDelta primary = state.getPrimaryEntry(mimeType); 228 if (!mIsSourceReadOnly) { 229 mName.setValues(kind, primary, state, mIsSourceReadOnly, vig); 230 } else { 231 String displayName = primary.getAsString(StructuredName.DISPLAY_NAME); 232 mReadOnlyName.setText(displayName); 233 } 234 } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) { 235 // Handle special case editor for photos 236 final ValuesDelta primary = state.getPrimaryEntry(mimeType); 237 mPhoto.setValues(kind, primary, state, mIsSourceReadOnly, vig); 238 if (mIsSourceReadOnly && !mPhoto.hasSetPhoto()) { 239 mPhotoStub.setVisibility(View.GONE); 240 } else { 241 mPhotoStub.setVisibility(View.VISIBLE); 242 } 243 } else if (!mIsSourceReadOnly) { 244 // Otherwise use generic section-based editors 245 if (kind.fieldList == null) continue; 246 final ViewGroup parent = kind.secondary ? mSecondary : mGeneral; 247 final KindSectionView section = (KindSectionView)mInflater.inflate( 248 R.layout.item_kind_section, parent, false); 249 section.setState(kind, state, mIsSourceReadOnly, vig); 250 if (kind.secondary && section.isAnyEditorFilledOut()) { 251 anySecondaryFieldFilled = true; 252 } 253 parent.addView(section); 254 } 255 } 256 257 setSecondaryVisible(anySecondaryFieldFilled); 258 } 259 260 /** 261 * Sets the {@link EditorListener} on the name field 262 */ 263 @Override 264 public void setNameEditorListener(EditorListener listener) { 265 mName.setEditorListener(listener); 266 } 267 268 @Override 269 public long getRawContactId() { 270 return mRawContactId; 271 } 272 273 private static class SavedState extends BaseSavedState { 274 public boolean mSecondaryVisible; 275 276 SavedState(Parcelable superState) { 277 super(superState); 278 } 279 280 private SavedState(Parcel in) { 281 super(in); 282 mSecondaryVisible = (in.readInt() == 0 ? false : true); 283 } 284 285 @Override 286 public void writeToParcel(Parcel out, int flags) { 287 super.writeToParcel(out, flags); 288 out.writeInt(mSecondaryVisible ? 1 : 0); 289 } 290 291 public static final Parcelable.Creator<SavedState> CREATOR 292 = new Parcelable.Creator<SavedState>() { 293 public SavedState createFromParcel(Parcel in) { 294 return new SavedState(in); 295 } 296 297 public SavedState[] newArray(int size) { 298 return new SavedState[size]; 299 } 300 }; 301 } 302 303 /** 304 * Saves the visibility of the secondary field. 305 */ 306 @Override 307 protected Parcelable onSaveInstanceState() { 308 Parcelable superState = super.onSaveInstanceState(); 309 SavedState ss = new SavedState(superState); 310 311 ss.mSecondaryVisible = mSecondaryVisible; 312 return ss; 313 } 314 315 /** 316 * Restores the visibility of the secondary field. 317 */ 318 @Override 319 protected void onRestoreInstanceState(Parcelable state) { 320 SavedState ss = (SavedState) state; 321 super.onRestoreInstanceState(ss.getSuperState()); 322 323 setSecondaryVisible(ss.mSecondaryVisible); 324 } 325 } 326