blob: 8f268b42de0aa816fc15eb18a87461305668805c [file] [log] [blame]
Walter Jangcab3dce2015-02-09 17:48:03 -08001/*
2 * Copyright (C) 2015 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 com.android.contacts.R;
20import com.android.contacts.common.model.AccountTypeManager;
21import com.android.contacts.common.model.RawContactDelta;
22import com.android.contacts.common.model.RawContactDeltaList;
Walter Jange720fde2015-02-17 10:54:14 -080023import com.android.contacts.common.model.RawContactModifier;
Walter Jangcab3dce2015-02-09 17:48:03 -080024import com.android.contacts.common.model.ValuesDelta;
25import com.android.contacts.common.model.account.AccountType;
Walter Jang2d3f31c2015-06-18 23:15:31 -070026import com.android.contacts.common.model.account.AccountWithDataSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080027import com.android.contacts.common.model.dataitem.DataKind;
Walter Jang708ea9e2015-09-10 15:42:05 -070028import com.android.contacts.common.util.AccountsListAdapter;
Walter Jangf46abd82015-02-20 16:52:04 -080029import com.android.contacts.common.util.MaterialColorMapUtils;
Walter Jang3efae4a2015-02-18 11:12:00 -080030import com.android.contacts.editor.CompactContactEditorFragment.PhotoHandler;
Walter Jang708ea9e2015-09-10 15:42:05 -070031import com.android.contacts.util.UiClosables;
Walter Jangcab3dce2015-02-09 17:48:03 -080032
33import android.content.Context;
Walter Jang3efae4a2015-02-18 11:12:00 -080034import android.graphics.Bitmap;
Walter Jang41b3ea12015-03-09 17:30:06 -070035import android.net.Uri;
Walter Jangcab3dce2015-02-09 17:48:03 -080036import android.provider.ContactsContract.CommonDataKinds.Email;
Walter Jangf5dfea42015-09-16 12:30:36 -070037import android.provider.ContactsContract.CommonDataKinds.Event;
Walter Jangcab3dce2015-02-09 17:48:03 -080038import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Walter Jangf5dfea42015-09-16 12:30:36 -070039import android.provider.ContactsContract.CommonDataKinds.Im;
Walter Jang3efae4a2015-02-18 11:12:00 -080040import android.provider.ContactsContract.CommonDataKinds.Nickname;
Walter Jangf5dfea42015-09-16 12:30:36 -070041import android.provider.ContactsContract.CommonDataKinds.Note;
42import android.provider.ContactsContract.CommonDataKinds.Organization;
Walter Jangcab3dce2015-02-09 17:48:03 -080043import android.provider.ContactsContract.CommonDataKinds.Phone;
44import android.provider.ContactsContract.CommonDataKinds.Photo;
Walter Jangf5dfea42015-09-16 12:30:36 -070045import android.provider.ContactsContract.CommonDataKinds.Relation;
46import android.provider.ContactsContract.CommonDataKinds.SipAddress;
Walter Jangcab3dce2015-02-09 17:48:03 -080047import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Walter Jangf5dfea42015-09-16 12:30:36 -070048import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
49import android.provider.ContactsContract.CommonDataKinds.Website;
Walter Jangcab3dce2015-02-09 17:48:03 -080050import android.text.TextUtils;
51import android.util.AttributeSet;
52import android.util.Log;
Walter Jang2d3f31c2015-06-18 23:15:31 -070053import android.util.Pair;
Walter Jangcab3dce2015-02-09 17:48:03 -080054import android.view.LayoutInflater;
Walter Jangb1c87622015-02-13 17:51:38 -080055import android.view.View;
Walter Jangcab3dce2015-02-09 17:48:03 -080056import android.view.ViewGroup;
Walter Jang708ea9e2015-09-10 15:42:05 -070057import android.widget.AdapterView;
Walter Jangcab3dce2015-02-09 17:48:03 -080058import android.widget.LinearLayout;
Walter Jang708ea9e2015-09-10 15:42:05 -070059import android.widget.ListPopupWindow;
Walter Jang2d3f31c2015-06-18 23:15:31 -070060import android.widget.TextView;
Walter Jangcab3dce2015-02-09 17:48:03 -080061
62import java.util.ArrayList;
Walter Jangf5dfea42015-09-16 12:30:36 -070063import java.util.Arrays;
64import java.util.Comparator;
Walter Jang19d985f2015-07-01 13:36:27 -070065import java.util.HashMap;
Walter Jangcab3dce2015-02-09 17:48:03 -080066import java.util.List;
Walter Jang19d985f2015-07-01 13:36:27 -070067import java.util.Map;
Walter Jang708ea9e2015-09-10 15:42:05 -070068import java.util.Objects;
Walter Jangf5dfea42015-09-16 12:30:36 -070069import java.util.TreeSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080070
71/**
Walter Jangf5dfea42015-09-16 12:30:36 -070072 * View to display information from multiple {@link RawContactDelta}s grouped together.
Walter Jangcab3dce2015-02-09 17:48:03 -080073 */
Walter Jangb6ca2722015-02-20 11:10:25 -080074public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
Walter Jangcab3dce2015-02-09 17:48:03 -080075
76 private static final String TAG = "CompactEditorView";
77
Walter Jangf5dfea42015-09-16 12:30:36 -070078 private static final MimeTypeComparator MIME_TYPE_COMPARATOR = new MimeTypeComparator();
79
Walter Jangb6ca2722015-02-20 11:10:25 -080080 /**
81 * Callbacks for hosts of {@link CompactRawContactsEditorView}s.
82 */
83 public interface Listener {
84
85 /**
Walter Jang151f3e62015-02-26 15:29:40 -080086 * Invoked when the structured name editor field has changed.
87 *
88 * @param rawContactId The raw contact ID from the underlying {@link RawContactDelta}.
89 * @param valuesDelta The values from the underlying {@link RawContactDelta}.
90 */
91 public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta);
Walter Jang708ea9e2015-09-10 15:42:05 -070092
93 /**
94 * Invoked when the compact editor should rebind editors for a new account.
95 *
96 * @param oldState Old data being edited.
97 * @param oldAccount Old account associated with oldState.
98 * @param newAccount New account to be used.
99 */
100 public void onRebindEditorsForNewContact(RawContactDelta oldState,
101 AccountWithDataSet oldAccount, AccountWithDataSet newAccount);
Walter Jang151f3e62015-02-26 15:29:40 -0800102 }
103
Walter Jangf5dfea42015-09-16 12:30:36 -0700104 /** Used to sort kind sections. */
105 private static final class MimeTypeComparator implements
106 Comparator<Map.Entry<String,List<KindSectionData>>> {
107
108 // Order of kinds roughly matches quick contacts; we diverge in the following ways:
109 // 1) all names are together at the top (in quick contacts the nickname and phonetic name
110 // are in the about card)
111 // 2) IM is moved up after addresses
112 // 3) SIP addresses are moved to below phone numbers
113 private static final List<String> MIME_TYPE_ORDER = Arrays.asList(new String[] {
114 StructuredName.CONTENT_ITEM_TYPE,
115 DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
116 DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
117 Nickname.CONTENT_ITEM_TYPE,
118 Phone.CONTENT_ITEM_TYPE,
119 SipAddress.CONTENT_ITEM_TYPE,
120 Email.CONTENT_ITEM_TYPE,
121 StructuredPostal.CONTENT_ITEM_TYPE,
122 Im.CONTENT_ITEM_TYPE,
123 Website.CONTENT_ITEM_TYPE,
124 Organization.CONTENT_ITEM_TYPE,
125 Event.CONTENT_ITEM_TYPE,
126 Relation.CONTENT_ITEM_TYPE,
127 Note.CONTENT_ITEM_TYPE
128 });
129
130 @Override
131 public int compare(Map.Entry<String, List<KindSectionData>> entry1,
132 Map.Entry<String, List<KindSectionData>> entry2) {
133 if (entry1 == entry2) return 0;
134 if (entry1 == null) return -1;
135 if (entry2 == null) return 1;
136
137 final String mimeType1 = entry1.getKey();
138 final String mimeType2 = entry2.getKey();
139
140 int index1 = MIME_TYPE_ORDER.indexOf(mimeType1);
141 int index2 = MIME_TYPE_ORDER.indexOf(mimeType2);
142
143 // Fallback to alphabetical ordering of the mime type if both are not found
144 if (index1 < 0 && index2 < 0) return mimeType1.compareTo(mimeType2);
145 if (index1 < 0) return 1;
146 if (index2 < 0) return -1;
147
148 return index1 < index2 ? -1 : 1;
149 }
150 }
151
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700152 private CompactRawContactsEditorView.Listener mListener;
Walter Jangb6ca2722015-02-20 11:10:25 -0800153
Walter Jangcab3dce2015-02-09 17:48:03 -0800154 private AccountTypeManager mAccountTypeManager;
155 private LayoutInflater mLayoutInflater;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800156
Walter Jangcab3dce2015-02-09 17:48:03 -0800157 private ViewIdGenerator mViewIdGenerator;
Walter Jangf46abd82015-02-20 16:52:04 -0800158 private MaterialColorMapUtils.MaterialPalette mMaterialPalette;
Walter Jang708ea9e2015-09-10 15:42:05 -0700159 private long mPhotoId;
Walter Jang708ea9e2015-09-10 15:42:05 -0700160 private boolean mHasNewContact;
161 private boolean mIsUserProfile;
162 private AccountWithDataSet mPrimaryAccount;
163 private RawContactDelta mPrimaryRawContactDelta;
Walter Jang363d3fd2015-09-16 10:29:07 -0700164 private Map<String,List<KindSectionData>> mKindSectionDataMap = new HashMap<>();
Walter Jangcab3dce2015-02-09 17:48:03 -0800165
Walter Jang708ea9e2015-09-10 15:42:05 -0700166 // Account header
167 private View mAccountHeaderContainer;
168 private TextView mAccountHeaderType;
169 private TextView mAccountHeaderName;
170
171 // Account selector
172 private View mAccountSelectorContainer;
173 private View mAccountSelector;
174 private TextView mAccountSelectorType;
175 private TextView mAccountSelectorName;
176
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700177 private CompactPhotoEditorView mPhotoView;
Walter Jangf5dfea42015-09-16 12:30:36 -0700178 private ViewGroup mKindSectionViews;
Walter Jangb6ca2722015-02-20 11:10:25 -0800179 private View mMoreFields;
Walter Jangcab3dce2015-02-09 17:48:03 -0800180
Walter Jang3efae4a2015-02-18 11:12:00 -0800181 private long mPhotoRawContactId;
182
Walter Jangcab3dce2015-02-09 17:48:03 -0800183 public CompactRawContactsEditorView(Context context) {
184 super(context);
185 }
186
187 public CompactRawContactsEditorView(Context context, AttributeSet attrs) {
188 super(context, attrs);
189 }
190
Walter Jangb6ca2722015-02-20 11:10:25 -0800191 /**
192 * Sets the receiver for {@link CompactRawContactsEditorView} callbacks.
193 */
194 public void setListener(Listener listener) {
195 mListener = listener;
196 }
197
Walter Jangcab3dce2015-02-09 17:48:03 -0800198 @Override
199 protected void onFinishInflate() {
200 super.onFinishInflate();
201
202 mAccountTypeManager = AccountTypeManager.getInstance(getContext());
203 mLayoutInflater = (LayoutInflater)
204 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
205
Walter Jang708ea9e2015-09-10 15:42:05 -0700206 // Account header
207 mAccountHeaderContainer = findViewById(R.id.account_container);
208 mAccountHeaderType = (TextView) findViewById(R.id.account_type);
209 mAccountHeaderName = (TextView) findViewById(R.id.account_name);
210
211 // Account selector
212 mAccountSelectorContainer = findViewById(R.id.account_selector_container);
213 mAccountSelector = findViewById(R.id.account);
214 mAccountSelectorType = (TextView) findViewById(R.id.account_type_selector);
215 mAccountSelectorName = (TextView) findViewById(R.id.account_name_selector);
216
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700217 mPhotoView = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
Walter Jangf5dfea42015-09-16 12:30:36 -0700218 mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
Walter Jangb6ca2722015-02-20 11:10:25 -0800219 mMoreFields = findViewById(R.id.more_fields);
220 mMoreFields.setOnClickListener(this);
221 }
222
Walter Jangb6ca2722015-02-20 11:10:25 -0800223 @Override
224 public void onClick(View view) {
Walter Jangf5dfea42015-09-16 12:30:36 -0700225 if (view.getId() == R.id.more_fields) {
226 // Stop hiding empty editors and allow the user to enter values for all kinds now
227 for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
228 final CompactKindSectionView kindSectionView =
229 (CompactKindSectionView) mKindSectionViews.getChildAt(i);
230 kindSectionView.setHideWhenEmpty(false);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700231 // Prevent the user from adding new names
232 if (!StructuredName.CONTENT_ITEM_TYPE.equals(kindSectionView.getMimeType())) {
233 kindSectionView.setShowOneEmptyEditor(true);
234 }
Walter Jangf5dfea42015-09-16 12:30:36 -0700235 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ false);
236 }
237
238 updateMoreFieldsButton();
Walter Jangb6ca2722015-02-20 11:10:25 -0800239 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800240 }
241
242 @Override
243 public void setEnabled(boolean enabled) {
244 super.setEnabled(enabled);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700245 final int childCount = mKindSectionViews.getChildCount();
246 for (int i = 0; i < childCount; i++) {
247 mKindSectionViews.getChildAt(i).setEnabled(enabled);
Walter Jangcab3dce2015-02-09 17:48:03 -0800248 }
249 }
250
Walter Jang3efae4a2015-02-18 11:12:00 -0800251 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800252 * Pass through to {@link CompactPhotoEditorView#setPhotoHandler}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800253 */
254 public void setPhotoHandler(PhotoHandler photoHandler) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700255 mPhotoView.setPhotoHandler(photoHandler);
Walter Jang3efae4a2015-02-18 11:12:00 -0800256 }
257
258 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800259 * Pass through to {@link CompactPhotoEditorView#setPhoto}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800260 */
261 public void setPhoto(Bitmap bitmap) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700262 mPhotoView.setPhoto(bitmap);
Walter Jang3efae4a2015-02-18 11:12:00 -0800263 }
264
265 /**
Walter Jang41b3ea12015-03-09 17:30:06 -0700266 * Pass through to {@link CompactPhotoEditorView#setFullSizedPhoto(Uri)}.
267 */
268 public void setFullSizePhoto(Uri photoUri) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700269 mPhotoView.setFullSizedPhoto(photoUri);
Walter Jang41b3ea12015-03-09 17:30:06 -0700270 }
271
272 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800273 * Pass through to {@link CompactPhotoEditorView#isWritablePhotoSet}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800274 */
275 public boolean isWritablePhotoSet() {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700276 return mPhotoView.isWritablePhotoSet();
Walter Jang3efae4a2015-02-18 11:12:00 -0800277 }
278
279 /**
Walter Jang3efae4a2015-02-18 11:12:00 -0800280 * Get the raw contact ID for the CompactHeaderView photo.
281 */
Walter Jang3efae4a2015-02-18 11:12:00 -0800282 public long getPhotoRawContactId() {
283 return mPhotoRawContactId;
284 }
285
Walter Jangd35e5ef2015-02-24 09:18:16 -0800286 public View getAggregationAnchorView() {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700287 // TODO: since there may be more than one structured name now we should return the one
288 // being edited instead of just the first one.
289 for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
290 final CompactKindSectionView kindSectionView =
291 (CompactKindSectionView) mKindSectionViews.getChildAt(i);
292 if (!StructuredName.CONTENT_ITEM_TYPE.equals(kindSectionView.getMimeType())) {
293 return kindSectionView.findViewById(R.id.anchor_view);
294 }
Walter Jangd35e5ef2015-02-24 09:18:16 -0800295 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700296 return null;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800297 }
298
Walter Jangf46abd82015-02-20 16:52:04 -0800299 public void setState(RawContactDeltaList rawContactDeltas,
Walter Jang06f73a12015-06-17 11:15:48 -0700300 MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700301 long photoId, boolean hasNewContact, boolean isUserProfile,
302 AccountWithDataSet primaryAccount) {
303 // Clear previous state and reset views
Walter Jang363d3fd2015-09-16 10:29:07 -0700304 mKindSectionDataMap.clear();
Walter Jangf5dfea42015-09-16 12:30:36 -0700305 mKindSectionViews.removeAllViews();
Walter Jang363d3fd2015-09-16 10:29:07 -0700306 mMoreFields.setVisibility(View.VISIBLE);
Walter Jangcab3dce2015-02-09 17:48:03 -0800307
Walter Jangf46abd82015-02-20 16:52:04 -0800308 mMaterialPalette = materialPalette;
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700309 mViewIdGenerator = viewIdGenerator;
Walter Jang708ea9e2015-09-10 15:42:05 -0700310 mPhotoId = photoId;
Walter Jang708ea9e2015-09-10 15:42:05 -0700311 mHasNewContact = hasNewContact;
312 mIsUserProfile = isUserProfile;
313 mPrimaryAccount = primaryAccount;
314 if (mPrimaryAccount == null) {
315 mPrimaryAccount = ContactEditorUtils.getInstance(getContext()).getDefaultAccount();
316 }
317 vlog("state: primary " + mPrimaryAccount);
Walter Jangcab3dce2015-02-09 17:48:03 -0800318
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700319 // Parse the given raw contact deltas
320 if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
321 elog("No raw contact deltas");
Walter Jangf5dfea42015-09-16 12:30:36 -0700322 return;
323 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700324 vlog("state: setting state from " + rawContactDeltas.size() + " RawContactDelta(s)");
325 parseRawContactDeltas(rawContactDeltas);
326 if (mKindSectionDataMap == null || mKindSectionDataMap.isEmpty()) {
327 elog("No kind section data parsed from RawContactDelta(s)");
328 return;
329 }
330
331 // Setup the view
332 setId(mViewIdGenerator.getId(rawContactDeltas.get(0), /* dataKind =*/ null,
333 /* valuesDelta =*/ null, ViewIdGenerator.NO_VIEW_INDEX));
Walter Jang708ea9e2015-09-10 15:42:05 -0700334 addAccountInfo();
Walter Jang363d3fd2015-09-16 10:29:07 -0700335 addPhotoView();
Walter Jangf5dfea42015-09-16 12:30:36 -0700336 addKindSectionViews();
Walter Jangf5dfea42015-09-16 12:30:36 -0700337 updateMoreFieldsButton();
Walter Jangcab3dce2015-02-09 17:48:03 -0800338 }
339
Walter Jang708ea9e2015-09-10 15:42:05 -0700340 private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700341 if (mPrimaryAccount != null) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700342 // Use the first writable contact that matches the primary account
343 for (RawContactDelta rawContactDelta : rawContactDeltas) {
344 if (!rawContactDelta.isVisible()) continue;
345 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700346 if (accountType == null || !accountType.areContactsWritable()) continue;
347 if (matchesPrimaryAccount(rawContactDelta)) {
348 vlog("parse: matched primary account raw contact");
Walter Jang708ea9e2015-09-10 15:42:05 -0700349 mPrimaryRawContactDelta = rawContactDelta;
350 break;
351 }
Walter Jang2d3f31c2015-06-18 23:15:31 -0700352 }
353 }
Walter Jang708ea9e2015-09-10 15:42:05 -0700354 if (mPrimaryRawContactDelta == null) {
355 // Fall back to the first writable raw contact
356 for (RawContactDelta rawContactDelta : rawContactDeltas) {
357 if (!rawContactDelta.isVisible()) continue;
358 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
359 if (accountType != null && accountType.areContactsWritable()) {
360 vlog("parse: falling back to the first writable raw contact as primary");
361 mPrimaryRawContactDelta = rawContactDelta;
362 break;
363 }
364 }
Walter Jang2d3f31c2015-06-18 23:15:31 -0700365 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700366 if (mPrimaryRawContactDelta != null) {
367 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
368 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
369 StructuredName.CONTENT_ITEM_TYPE);
Walter Jang2d3f31c2015-06-18 23:15:31 -0700370
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700371 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
372 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
373 Photo.CONTENT_ITEM_TYPE);
374 }
Walter Jangfa127a12015-06-18 09:48:18 -0700375
Walter Jang363d3fd2015-09-16 10:29:07 -0700376 // Build the kind section data list map
Walter Jangcab3dce2015-02-09 17:48:03 -0800377 for (RawContactDelta rawContactDelta : rawContactDeltas) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700378 if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700379 vlog("parse: " + rawContactDelta);
Walter Jangcab3dce2015-02-09 17:48:03 -0800380 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jang363d3fd2015-09-16 10:29:07 -0700381 if (accountType == null) continue;
382 final List<DataKind> dataKinds = accountType.getSortedDataKinds();
383 final int dataKindSize = dataKinds == null ? 0 : dataKinds.size();
384 vlog("parse: " + dataKindSize + " dataKinds(s)");
385 for (int i = 0; i < dataKindSize; i++) {
386 final DataKind dataKind = dataKinds.get(i);
Walter Jang363d3fd2015-09-16 10:29:07 -0700387 if (dataKind == null || !dataKind.editable) continue;
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700388 final List<KindSectionData> kindSectionDataList =
389 getKindSectionDataList(dataKind.mimeType);
Walter Jang363d3fd2015-09-16 10:29:07 -0700390 final KindSectionData kindSectionData =
391 new KindSectionData(accountType, dataKind, rawContactDelta);
392 kindSectionDataList.add(kindSectionData);
393 vlog("parse: " + i + " " + dataKind.mimeType + " " +
394 (kindSectionData.hasValuesDeltas()
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700395 ? kindSectionData.getValuesDeltas().size() : 0) + " value(s)");
Walter Jangac679af2015-06-01 12:17:06 -0700396 }
397 }
Walter Jang3efae4a2015-02-18 11:12:00 -0800398 }
399
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700400 private List<KindSectionData> getKindSectionDataList(String mimeType) {
401 List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
402 if (kindSectionDataList == null) {
403 kindSectionDataList = new ArrayList<>();
404 mKindSectionDataMap.put(mimeType, kindSectionDataList);
405 }
406 return kindSectionDataList;
407 }
408
409 /** Whether the given RawContactDelta is from the primary account. */
410 private boolean matchesPrimaryAccount(RawContactDelta rawContactDelta) {
411 return Objects.equals(mPrimaryAccount.name, rawContactDelta.getAccountName())
412 && Objects.equals(mPrimaryAccount.type, rawContactDelta.getAccountType())
413 && Objects.equals(mPrimaryAccount.dataSet, rawContactDelta.getDataSet());
414 }
415
Walter Jang708ea9e2015-09-10 15:42:05 -0700416 private void addAccountInfo() {
417 if (mPrimaryRawContactDelta == null) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700418 mAccountHeaderContainer.setVisibility(View.GONE);
419 mAccountSelectorContainer.setVisibility(View.GONE);
420 return;
421 }
422
423 // Get the account information for the primary raw contact delta
424 final Pair<String,String> accountInfo = EditorUiUtils.getAccountInfo(getContext(),
425 mIsUserProfile, mPrimaryRawContactDelta.getAccountName(),
426 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager));
427
428 // The account header and selector show the same information so both shouldn't be visible
429 // at the same time
430 final List<AccountWithDataSet> accounts =
431 AccountTypeManager.getInstance(getContext()).getAccounts(true);
432 if (mHasNewContact && !mIsUserProfile && accounts.size() > 1) {
433 mAccountHeaderContainer.setVisibility(View.GONE);
434 addAccountSelector(accountInfo);
435 } else {
436 addAccountHeader(accountInfo);
437 mAccountSelectorContainer.setVisibility(View.GONE);
438 }
439 }
440
441 private void addAccountHeader(Pair<String,String> accountInfo) {
Walter Jang03cea2e2015-09-18 17:04:21 -0700442 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700443 // Hide this view so the other text view will be centered vertically
444 mAccountHeaderName.setVisibility(View.GONE);
445 } else {
446 mAccountHeaderName.setVisibility(View.VISIBLE);
447 mAccountHeaderName.setText(accountInfo.first);
448 }
449 mAccountHeaderType.setText(accountInfo.second);
450
451 mAccountHeaderContainer.setContentDescription(
452 EditorUiUtils.getAccountInfoContentDescription(
453 accountInfo.first, accountInfo.second));
454 }
455
456 private void addAccountSelector(Pair<String,String> accountInfo) {
457 mAccountSelectorContainer.setVisibility(View.VISIBLE);
458
Walter Jang03cea2e2015-09-18 17:04:21 -0700459 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700460 // Hide this view so the other text view will be centered vertically
461 mAccountSelectorName.setVisibility(View.GONE);
462 } else {
463 mAccountSelectorName.setVisibility(View.VISIBLE);
464 mAccountSelectorName.setText(accountInfo.first);
465 }
466 mAccountSelectorType.setText(accountInfo.second);
467
468 mAccountSelectorContainer.setContentDescription(
469 EditorUiUtils.getAccountInfoContentDescription(
470 accountInfo.first, accountInfo.second));
471
472 mAccountSelector.setOnClickListener(new View.OnClickListener() {
473 @Override
474 public void onClick(View v) {
475 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
476 final AccountsListAdapter adapter =
477 new AccountsListAdapter(getContext(),
478 AccountsListAdapter.AccountListFilter.ACCOUNTS_CONTACT_WRITABLE,
479 mPrimaryAccount);
480 popup.setWidth(mAccountSelectorContainer.getWidth());
481 popup.setAnchorView(mAccountSelectorContainer);
482 popup.setAdapter(adapter);
483 popup.setModal(true);
484 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
485 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
486 @Override
487 public void onItemClick(AdapterView<?> parent, View view, int position,
488 long id) {
489 UiClosables.closeQuietly(popup);
490 final AccountWithDataSet newAccount = adapter.getItem(position);
491 if (mListener != null && !mPrimaryAccount.equals(newAccount)) {
492 mListener.onRebindEditorsForNewContact(
493 mPrimaryRawContactDelta,
494 mPrimaryAccount,
495 newAccount);
496 }
497 }
498 });
499 popup.show();
500 }
501 });
502 }
503
Walter Jang363d3fd2015-09-16 10:29:07 -0700504 private void addPhotoView() {
505 // Get the kind section data and values delta that will back the photo view
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700506 Pair<KindSectionData,ValuesDelta> pair = getPrimaryKindSectionData(mPhotoId);
Walter Jang363d3fd2015-09-16 10:29:07 -0700507 if (pair == null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700508 wlog("photo: no kind section data parsed");
Walter Jang363d3fd2015-09-16 10:29:07 -0700509 return;
510 }
511 final KindSectionData kindSectionData = pair.first;
512 final ValuesDelta valuesDelta = pair.second;
Walter Jang06f73a12015-06-17 11:15:48 -0700513
Walter Jang363d3fd2015-09-16 10:29:07 -0700514 // If we're editing a read-only contact we want to display the photo from the
515 // read-only contact in a photo editor backed by the new raw contact
516 // that was created. The new raw contact is the first writable one.
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700517 if (mHasNewContact) {
518 mPhotoRawContactId = mPrimaryRawContactDelta == null
519 ? null : mPrimaryRawContactDelta.getRawContactId();
Walter Jang363d3fd2015-09-16 10:29:07 -0700520 }
Walter Jang06f73a12015-06-17 11:15:48 -0700521
Walter Jang363d3fd2015-09-16 10:29:07 -0700522 mPhotoRawContactId = kindSectionData.getRawContactDelta().getRawContactId();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700523 mPhotoView.setValues(kindSectionData.getDataKind(), valuesDelta,
Walter Jang363d3fd2015-09-16 10:29:07 -0700524 kindSectionData.getRawContactDelta(),
525 !kindSectionData.getAccountType().areContactsWritable(), mMaterialPalette,
526 mViewIdGenerator);
527 }
528
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700529 private Pair<KindSectionData,ValuesDelta> getPrimaryKindSectionData(long id) {
530 final String mimeType = Photo.CONTENT_ITEM_TYPE;
Walter Jang363d3fd2015-09-16 10:29:07 -0700531 final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
532 if (kindSectionDataList == null || kindSectionDataList.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700533 wlog("photo: no kind section data parsed");
Walter Jang363d3fd2015-09-16 10:29:07 -0700534 return null;
535 }
Walter Jang151f3e62015-02-26 15:29:40 -0800536
Walter Jang363d3fd2015-09-16 10:29:07 -0700537 KindSectionData resultKindSectionData = null;
538 ValuesDelta resultValuesDelta = null;
539 if (id > 0) {
540 // Look for a match for the ID that was passed in
541 for (KindSectionData kindSectionData : kindSectionDataList) {
542 resultValuesDelta = kindSectionData.getValuesDeltaById(id);
543 if (resultValuesDelta != null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700544 vlog("photo: matched kind section data by ID");
Walter Jang363d3fd2015-09-16 10:29:07 -0700545 resultKindSectionData = kindSectionData;
546 break;
Walter Jang398cd4b2015-06-16 11:17:53 -0700547 }
548 }
549 }
Walter Jang363d3fd2015-09-16 10:29:07 -0700550 if (resultKindSectionData == null) {
551 // Look for a super primary photo
552 for (KindSectionData kindSectionData : kindSectionDataList) {
553 resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
554 if (resultValuesDelta != null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700555 wlog("photo: matched super primary kind section data");
Walter Jang363d3fd2015-09-16 10:29:07 -0700556 resultKindSectionData = kindSectionData;
557 break;
558 }
Walter Jang151f3e62015-02-26 15:29:40 -0800559 }
560 }
Walter Jang363d3fd2015-09-16 10:29:07 -0700561 if (resultKindSectionData == null) {
562 // Fall back to the first non-empty value
563 for (KindSectionData kindSectionData : kindSectionDataList) {
564 resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
565 if (resultValuesDelta != null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700566 vlog("photo: using first non empty value");
Walter Jang363d3fd2015-09-16 10:29:07 -0700567 resultKindSectionData = kindSectionData;
568 break;
569 }
Walter Jang151f3e62015-02-26 15:29:40 -0800570 }
571 }
Walter Jang363d3fd2015-09-16 10:29:07 -0700572 if (resultKindSectionData == null || resultValuesDelta == null) {
573 final List<ValuesDelta> valuesDeltaList = kindSectionDataList.get(0).getValuesDeltas();
574 if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700575 vlog("photo: falling back to first empty entry");
Walter Jang363d3fd2015-09-16 10:29:07 -0700576 resultValuesDelta = valuesDeltaList.get(0);
577 resultKindSectionData = kindSectionDataList.get(0);
Walter Jang10446452015-02-20 13:51:16 -0800578 }
579 }
Walter Jang363d3fd2015-09-16 10:29:07 -0700580 return resultKindSectionData != null && resultValuesDelta != null
581 ? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
Walter Jang10446452015-02-20 13:51:16 -0800582 }
583
Walter Jangf5dfea42015-09-16 12:30:36 -0700584 private void addKindSectionViews() {
585 // Sort the kinds
586 final TreeSet<Map.Entry<String,List<KindSectionData>>> entries =
587 new TreeSet<>(MIME_TYPE_COMPARATOR);
588 entries.addAll(mKindSectionDataMap.entrySet());
Walter Jang3efae4a2015-02-18 11:12:00 -0800589
Walter Jangf5dfea42015-09-16 12:30:36 -0700590 vlog("kind: " + entries.size() + " kindSection(s)");
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700591 int i = -1;
Walter Jangf5dfea42015-09-16 12:30:36 -0700592 for (Map.Entry<String, List<KindSectionData>> entry : entries) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700593 i++;
594
Walter Jangf5dfea42015-09-16 12:30:36 -0700595 final String mimeType = entry.getKey();
596 final List<KindSectionData> kindSectionDataList = entry.getValue();
Walter Jangab50e6f2015-06-15 08:57:22 -0700597
Walter Jangf5dfea42015-09-16 12:30:36 -0700598 // Ignore mime types that we've already handled
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700599 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
600 vlog("kind: " + i + " " + mimeType + " dropped");
601 continue;
602 }
Walter Jangf5dfea42015-09-16 12:30:36 -0700603
604 // Ignore mime types that we don't handle
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700605 if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)
606 || DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)) {
607 vlog("kind: " + i + " " + mimeType + " dropped");
608 continue;
609 }
Walter Jangf5dfea42015-09-16 12:30:36 -0700610
611 if (kindSectionDataList != null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700612 vlog("kind: " + i + " " + mimeType + ": " + kindSectionDataList.size() +
613 " kindSectionData(s)");
614
Walter Jangf5dfea42015-09-16 12:30:36 -0700615 final CompactKindSectionView kindSectionView = inflateKindSectionView(
616 mKindSectionViews, kindSectionDataList, mimeType);
Walter Jangf5dfea42015-09-16 12:30:36 -0700617 mKindSectionViews.addView(kindSectionView);
Walter Jangcab3dce2015-02-09 17:48:03 -0800618 }
619 }
620 }
621
Walter Jangf5dfea42015-09-16 12:30:36 -0700622 private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
623 List<KindSectionData> kindSectionDataList, String mimeType) {
624 final CompactKindSectionView kindSectionView = (CompactKindSectionView)
625 mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
626 /* attachToRoot =*/ false);
627
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700628 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)
629 || Phone.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jangf5dfea42015-09-16 12:30:36 -0700630 || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700631 // Names, phone numbers, and email addresses are always displayed.
632 // Phone numbers and email addresses are the only types you add new values
633 // to initially.
Walter Jangf5dfea42015-09-16 12:30:36 -0700634 kindSectionView.setHideWhenEmpty(false);
635 }
Walter Jangf5dfea42015-09-16 12:30:36 -0700636
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700637 // Prevent the user from adding any new names
638 if (!StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
639 kindSectionView.setShowOneEmptyEditor(true);
640 }
641
642 kindSectionView.setState(kindSectionDataList, /* readOnly =*/ false, mViewIdGenerator,
643 mListener);
Walter Jangf5dfea42015-09-16 12:30:36 -0700644
645 return kindSectionView;
Walter Jangcab3dce2015-02-09 17:48:03 -0800646 }
647
Walter Jangf5dfea42015-09-16 12:30:36 -0700648 private void updateMoreFieldsButton() {
649 // If any kind section views are hidden then show the link
650 for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
651 final CompactKindSectionView kindSectionView =
652 (CompactKindSectionView) mKindSectionViews.getChildAt(i);
653 if (kindSectionView.getVisibility() == View.GONE) {
654 // Show the more fields button
655 mMoreFields.setVisibility(View.VISIBLE);
656 return;
657 }
658 }
659 // Hide the more fields button
660 mMoreFields.setVisibility(View.GONE);
Walter Jangcab3dce2015-02-09 17:48:03 -0800661 }
662
Walter Jangbf63a6d2015-05-05 09:14:35 -0700663 private static void vlog(String message) {
664 if (Log.isLoggable(TAG, Log.VERBOSE)) {
665 Log.v(TAG, message);
Walter Jangcab3dce2015-02-09 17:48:03 -0800666 }
667 }
Walter Jang363d3fd2015-09-16 10:29:07 -0700668
669 private static void wlog(String message) {
670 if (Log.isLoggable(TAG, Log.WARN)) {
671 Log.w(TAG, message);
672 }
673 }
674
675 private static void elog(String message) {
676 Log.e(TAG, message);
677 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800678}