blob: 2bdd212657c4761559f8b40de0717c6b5150cdf3 [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 Jang708ea9e2015-09-10 15:42:05 -070030import com.android.contacts.util.UiClosables;
Walter Jangcab3dce2015-02-09 17:48:03 -080031
Tingting Wang655ad1a2015-10-05 17:51:14 -070032import android.content.ContentUris;
Walter Jangcab3dce2015-02-09 17:48:03 -080033import android.content.Context;
Walter Jangf10ca152015-09-22 15:23:55 -070034import android.database.Cursor;
Walter Jang3efae4a2015-02-18 11:12:00 -080035import android.graphics.Bitmap;
Walter Jang41b3ea12015-03-09 17:30:06 -070036import android.net.Uri;
Walter Jang3f18d612015-10-07 16:01:05 -070037import android.os.Bundle;
Walter Jang79658e12015-09-24 10:36:26 -070038import android.os.Parcel;
39import android.os.Parcelable;
Tingting Wang655ad1a2015-10-05 17:51:14 -070040import android.provider.ContactsContract;
Walter Jangcab3dce2015-02-09 17:48:03 -080041import android.provider.ContactsContract.CommonDataKinds.Email;
Walter Jangf5dfea42015-09-16 12:30:36 -070042import android.provider.ContactsContract.CommonDataKinds.Event;
Walter Jangcab3dce2015-02-09 17:48:03 -080043import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Walter Jangf5dfea42015-09-16 12:30:36 -070044import android.provider.ContactsContract.CommonDataKinds.Im;
Walter Jang3efae4a2015-02-18 11:12:00 -080045import android.provider.ContactsContract.CommonDataKinds.Nickname;
Walter Jangf5dfea42015-09-16 12:30:36 -070046import android.provider.ContactsContract.CommonDataKinds.Note;
47import android.provider.ContactsContract.CommonDataKinds.Organization;
Walter Jangcab3dce2015-02-09 17:48:03 -080048import android.provider.ContactsContract.CommonDataKinds.Phone;
49import android.provider.ContactsContract.CommonDataKinds.Photo;
Walter Jangf5dfea42015-09-16 12:30:36 -070050import android.provider.ContactsContract.CommonDataKinds.Relation;
51import android.provider.ContactsContract.CommonDataKinds.SipAddress;
Walter Jangcab3dce2015-02-09 17:48:03 -080052import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Walter Jangf5dfea42015-09-16 12:30:36 -070053import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
54import android.provider.ContactsContract.CommonDataKinds.Website;
Walter Jangcab3dce2015-02-09 17:48:03 -080055import android.text.TextUtils;
56import android.util.AttributeSet;
57import android.util.Log;
Walter Jang2d3f31c2015-06-18 23:15:31 -070058import android.util.Pair;
Walter Jangcab3dce2015-02-09 17:48:03 -080059import android.view.LayoutInflater;
Walter Jangb1c87622015-02-13 17:51:38 -080060import android.view.View;
Walter Jangcab3dce2015-02-09 17:48:03 -080061import android.view.ViewGroup;
Walter Jang708ea9e2015-09-10 15:42:05 -070062import android.widget.AdapterView;
Tingting Wang655ad1a2015-10-05 17:51:14 -070063import android.widget.BaseAdapter;
64import android.widget.ImageView;
Walter Jangcab3dce2015-02-09 17:48:03 -080065import android.widget.LinearLayout;
Walter Jang708ea9e2015-09-10 15:42:05 -070066import android.widget.ListPopupWindow;
Walter Jang2d3f31c2015-06-18 23:15:31 -070067import android.widget.TextView;
Walter Jangcab3dce2015-02-09 17:48:03 -080068
Walter Jang708774a2015-10-16 09:32:06 -070069import java.io.FileNotFoundException;
Walter Jangcab3dce2015-02-09 17:48:03 -080070import java.util.ArrayList;
Walter Jangf5dfea42015-09-16 12:30:36 -070071import java.util.Arrays;
Walter Jang192a01c2015-09-22 15:23:55 -070072import java.util.Collections;
Walter Jangf5dfea42015-09-16 12:30:36 -070073import java.util.Comparator;
Walter Jang19d985f2015-07-01 13:36:27 -070074import java.util.HashMap;
Tingting Wanga0b69402015-10-13 14:27:58 -070075import java.util.LinkedHashMap;
76import java.util.LinkedHashSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080077import java.util.List;
Walter Jang19d985f2015-07-01 13:36:27 -070078import java.util.Map;
Walter Jangf5dfea42015-09-16 12:30:36 -070079import java.util.TreeSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080080
81/**
Walter Jangf5dfea42015-09-16 12:30:36 -070082 * View to display information from multiple {@link RawContactDelta}s grouped together.
Walter Jangcab3dce2015-02-09 17:48:03 -080083 */
Walter Jangb6ca2722015-02-20 11:10:25 -080084public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
Walter Jangcab3dce2015-02-09 17:48:03 -080085
Walter Jang4f5594a2015-10-06 18:40:31 -070086 static final String TAG = "CompactEditorView";
Walter Jangcab3dce2015-02-09 17:48:03 -080087
Walter Jang192a01c2015-09-22 15:23:55 -070088 private static final KindSectionDataMapEntryComparator
89 KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR = new KindSectionDataMapEntryComparator();
Walter Jangf5dfea42015-09-16 12:30:36 -070090
Walter Jangb6ca2722015-02-20 11:10:25 -080091 /**
92 * Callbacks for hosts of {@link CompactRawContactsEditorView}s.
93 */
94 public interface Listener {
95
96 /**
Walter Jang151f3e62015-02-26 15:29:40 -080097 * Invoked when the structured name editor field has changed.
98 *
99 * @param rawContactId The raw contact ID from the underlying {@link RawContactDelta}.
100 * @param valuesDelta The values from the underlying {@link RawContactDelta}.
101 */
102 public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta);
Walter Jang708ea9e2015-09-10 15:42:05 -0700103
104 /**
105 * Invoked when the compact editor should rebind editors for a new account.
106 *
107 * @param oldState Old data being edited.
108 * @param oldAccount Old account associated with oldState.
109 * @param newAccount New account to be used.
110 */
111 public void onRebindEditorsForNewContact(RawContactDelta oldState,
112 AccountWithDataSet oldAccount, AccountWithDataSet newAccount);
Walter Jang79658e12015-09-24 10:36:26 -0700113
114 /**
115 * Invoked when no editors could be bound for the contact.
116 */
117 public void onBindEditorsFailed();
Walter Jangd6753152015-10-02 09:23:13 -0700118
119 /**
120 * Invoked after editors have been bound for the contact.
121 */
122 public void onEditorsBound();
Tingting Wang655ad1a2015-10-05 17:51:14 -0700123
124 /**
Tingting Wang5585c6e2015-10-14 11:19:58 -0700125 * Invoked when a rawcontact from linked contacts is selected in editor.
Tingting Wang655ad1a2015-10-05 17:51:14 -0700126 */
Tingting Wang91cee282015-10-07 13:48:17 -0700127 public void onRawContactSelected(Uri uri, long rawContactId, boolean isReadOnly);
Walter Jang3f18d612015-10-07 16:01:05 -0700128
129 /**
130 * Returns the map of raw contact IDs to newly taken or selected photos that have not
131 * yet been saved to CP2.
132 */
133 public Bundle getUpdatedPhotos();
Tingting Wang655ad1a2015-10-05 17:51:14 -0700134 }
135
136 /**
137 * Used to list the account info for the given raw contacts list.
138 */
139 private static final class RawContactAccountListAdapter extends BaseAdapter {
140 private final LayoutInflater mInflater;
141 private final Context mContext;
142 private final RawContactDeltaList mRawContactDeltas;
143
144 public RawContactAccountListAdapter(Context context, RawContactDeltaList rawContactDeltas) {
145 mContext = context;
146 mRawContactDeltas = new RawContactDeltaList();
147 for (RawContactDelta rawContactDelta : rawContactDeltas) {
148 if (rawContactDelta.isVisible()) {
149 mRawContactDeltas.add(rawContactDelta);
150 }
151 }
152 mInflater = LayoutInflater.from(context);
153 }
154
155 @Override
156 public View getView(int position, View convertView, ViewGroup parent) {
157 final View resultView = convertView != null ? convertView
158 : mInflater.inflate(R.layout.account_selector_list_item, parent, false);
159
160 final RawContactDelta rawContactDelta = mRawContactDeltas.get(position);
161 final String accountName = rawContactDelta.getAccountName();
162 final AccountType accountType = rawContactDelta.getRawContactAccountType(mContext);
163
164 final TextView text1 = (TextView) resultView.findViewById(android.R.id.text1);
165 text1.setText(accountType.getDisplayLabel(mContext));
166
167 // For email addresses, we don't want to truncate at end, which might cut off the domain
168 // name.
169 final TextView text2 = (TextView) resultView.findViewById(android.R.id.text2);
Tingting Wanga0b69402015-10-13 14:27:58 -0700170 if (TextUtils.isEmpty(accountName)) {
171 text2.setVisibility(View.GONE);
172 } else {
173 text2.setText(accountName);
174 text2.setEllipsize(TextUtils.TruncateAt.MIDDLE);
175 }
Tingting Wang655ad1a2015-10-05 17:51:14 -0700176
177 final ImageView icon = (ImageView) resultView.findViewById(android.R.id.icon);
178 icon.setImageDrawable(accountType.getDisplayIcon(mContext));
179
180 return resultView;
181 }
182
183 @Override
184 public int getCount() {
185 return mRawContactDeltas.size();
186 }
187
188 @Override
189 public RawContactDelta getItem(int position) {
190 return mRawContactDeltas.get(position);
191 }
192
193 @Override
194 public long getItemId(int position) {
195 return getItem(position).getRawContactId();
196 }
Walter Jang151f3e62015-02-26 15:29:40 -0800197 }
198
Walter Jang192a01c2015-09-22 15:23:55 -0700199 /** Used to sort entire kind sections. */
200 private static final class KindSectionDataMapEntryComparator implements
Walter Jangcbd431d2015-10-06 13:07:30 -0700201 Comparator<Map.Entry<String,KindSectionDataList>> {
Walter Jangf5dfea42015-09-16 12:30:36 -0700202
Walter Jang192a01c2015-09-22 15:23:55 -0700203 final MimeTypeComparator mMimeTypeComparator = new MimeTypeComparator();
204
205 @Override
Walter Jangcbd431d2015-10-06 13:07:30 -0700206 public int compare(Map.Entry<String, KindSectionDataList> entry1,
207 Map.Entry<String, KindSectionDataList> entry2) {
Walter Jang192a01c2015-09-22 15:23:55 -0700208 if (entry1 == entry2) return 0;
209 if (entry1 == null) return -1;
210 if (entry2 == null) return 1;
211
212 final String mimeType1 = entry1.getKey();
213 final String mimeType2 = entry2.getKey();
214
215 return mMimeTypeComparator.compare(mimeType1, mimeType2);
216 }
217 }
218
219 /**
220 * Sorts kinds roughly the same as quick contacts; we diverge in the following ways:
221 * <ol>
222 * <li>All names are together at the top.</li>
223 * <li>IM is moved up after addresses</li>
224 * <li>SIP addresses are moved to below phone numbers</li>
Walter Jang79658e12015-09-24 10:36:26 -0700225 * <li>Group membership is placed at the end</li>
Walter Jang192a01c2015-09-22 15:23:55 -0700226 * </ol>
227 */
228 private static final class MimeTypeComparator implements Comparator<String> {
229
Walter Jangf5dfea42015-09-16 12:30:36 -0700230 private static final List<String> MIME_TYPE_ORDER = Arrays.asList(new String[] {
231 StructuredName.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700232 Nickname.CONTENT_ITEM_TYPE,
Walter Jang2ae21752015-09-25 11:25:38 -0700233 Organization.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700234 Phone.CONTENT_ITEM_TYPE,
235 SipAddress.CONTENT_ITEM_TYPE,
236 Email.CONTENT_ITEM_TYPE,
237 StructuredPostal.CONTENT_ITEM_TYPE,
238 Im.CONTENT_ITEM_TYPE,
239 Website.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700240 Event.CONTENT_ITEM_TYPE,
241 Relation.CONTENT_ITEM_TYPE,
Walter Jangf10ca152015-09-22 15:23:55 -0700242 Note.CONTENT_ITEM_TYPE,
243 GroupMembership.CONTENT_ITEM_TYPE
Walter Jangf5dfea42015-09-16 12:30:36 -0700244 });
245
246 @Override
Walter Jang192a01c2015-09-22 15:23:55 -0700247 public int compare(String mimeType1, String mimeType2) {
248 if (mimeType1 == mimeType2) return 0;
249 if (mimeType1 == null) return -1;
250 if (mimeType2 == null) return 1;
Walter Jangf5dfea42015-09-16 12:30:36 -0700251
252 int index1 = MIME_TYPE_ORDER.indexOf(mimeType1);
253 int index2 = MIME_TYPE_ORDER.indexOf(mimeType2);
254
255 // Fallback to alphabetical ordering of the mime type if both are not found
256 if (index1 < 0 && index2 < 0) return mimeType1.compareTo(mimeType2);
257 if (index1 < 0) return 1;
258 if (index2 < 0) return -1;
259
260 return index1 < index2 ? -1 : 1;
261 }
262 }
263
Walter Jang192a01c2015-09-22 15:23:55 -0700264 /**
265 * Sorts primary accounts and google account types before others.
266 */
267 private static final class EditorComparator implements Comparator<KindSectionData> {
268
269 private RawContactDeltaComparator mRawContactDeltaComparator;
270
271 private EditorComparator(Context context) {
272 mRawContactDeltaComparator = new RawContactDeltaComparator(context);
273 }
274
275 @Override
276 public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
277 if (kindSectionData1 == kindSectionData2) return 0;
278 if (kindSectionData1 == null) return -1;
279 if (kindSectionData2 == null) return 1;
280
281 final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
282 final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
283
284 if (rawContactDelta1 == rawContactDelta2) return 0;
285 if (rawContactDelta1 == null) return -1;
286 if (rawContactDelta2 == null) return 1;
287
288 return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
289 }
290 }
291
292 /**
293 * Sorts primary account names first, followed by google account types, and other account
294 * types last. For names from the same account we order structured names before nicknames,
295 * but still keep names from the same account together.
296 */
297 private static final class NameEditorComparator implements Comparator<KindSectionData> {
298
Walter Jang4f5594a2015-10-06 18:40:31 -0700299 private final RawContactDeltaComparator mRawContactDeltaComparator;
300 private final MimeTypeComparator mMimeTypeComparator;
301 private final RawContactDelta mPrimaryRawContactDelta;
Walter Jang192a01c2015-09-22 15:23:55 -0700302
303 private NameEditorComparator(Context context, RawContactDelta primaryRawContactDelta) {
304 mRawContactDeltaComparator = new RawContactDeltaComparator(context);
305 mMimeTypeComparator = new MimeTypeComparator();
306 mPrimaryRawContactDelta = primaryRawContactDelta;
307 }
308
309 @Override
310 public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
311 if (kindSectionData1 == kindSectionData2) return 0;
312 if (kindSectionData1 == null) return -1;
313 if (kindSectionData2 == null) return 1;
314
315 final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
316 final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
317
318 if (rawContactDelta1 == rawContactDelta2) return 0;
319 if (rawContactDelta1 == null) return -1;
320 if (rawContactDelta2 == null) return 1;
321
322 final boolean isRawContactDelta1Primary =
323 mPrimaryRawContactDelta.equals(rawContactDelta1);
324 final boolean isRawContactDelta2Primary =
325 mPrimaryRawContactDelta.equals(rawContactDelta2);
326
327 // If both names are from the primary account, sort my by mime type
328 if (isRawContactDelta1Primary && isRawContactDelta2Primary) {
329 final String mimeType1 = kindSectionData1.getDataKind().mimeType;
330 final String mimeType2 = kindSectionData2.getDataKind().mimeType;
331 return mMimeTypeComparator.compare(mimeType1, mimeType2);
332 }
333
334 // The primary account name should be before all others
Walter Jang79658e12015-09-24 10:36:26 -0700335 if (isRawContactDelta1Primary) return -1;
336 if (isRawContactDelta2Primary) return 1;
Walter Jang192a01c2015-09-22 15:23:55 -0700337
338 return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
339 }
340 }
341
Walter Jang79658e12015-09-24 10:36:26 -0700342 public static class SavedState extends BaseSavedState {
343
344 public static final Parcelable.Creator<SavedState> CREATOR =
345 new Parcelable.Creator<SavedState>() {
346 public SavedState createFromParcel(Parcel in) {
347 return new SavedState(in);
348 }
349 public SavedState[] newArray(int size) {
350 return new SavedState[size];
351 }
352 };
353
354 private boolean mIsExpanded;
355
356 public SavedState(Parcelable superState) {
357 super(superState);
358 }
359
360 private SavedState(Parcel in) {
361 super(in);
362 mIsExpanded = in.readInt() != 0;
363 }
364
365 @Override
366 public void writeToParcel(Parcel out, int flags) {
367 super.writeToParcel(out, flags);
368 out.writeInt(mIsExpanded ? 1 : 0);
369 }
370 }
371
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700372 private CompactRawContactsEditorView.Listener mListener;
Walter Jangb6ca2722015-02-20 11:10:25 -0800373
Walter Jangcab3dce2015-02-09 17:48:03 -0800374 private AccountTypeManager mAccountTypeManager;
375 private LayoutInflater mLayoutInflater;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800376
Walter Jangcab3dce2015-02-09 17:48:03 -0800377 private ViewIdGenerator mViewIdGenerator;
Walter Jangf46abd82015-02-20 16:52:04 -0800378 private MaterialColorMapUtils.MaterialPalette mMaterialPalette;
Walter Jang708ea9e2015-09-10 15:42:05 -0700379 private long mPhotoId;
Walter Jang4f5594a2015-10-06 18:40:31 -0700380 private String mReadOnlyDisplayName;
Walter Jang708ea9e2015-09-10 15:42:05 -0700381 private boolean mHasNewContact;
382 private boolean mIsUserProfile;
383 private AccountWithDataSet mPrimaryAccount;
384 private RawContactDelta mPrimaryRawContactDelta;
Walter Jangcbd431d2015-10-06 13:07:30 -0700385 private Map<String,KindSectionDataList> mKindSectionDataMap = new HashMap<>();
Walter Jangcab3dce2015-02-09 17:48:03 -0800386
Walter Jang708ea9e2015-09-10 15:42:05 -0700387 // Account header
388 private View mAccountHeaderContainer;
389 private TextView mAccountHeaderType;
390 private TextView mAccountHeaderName;
Tingting Wang91cee282015-10-07 13:48:17 -0700391 private ImageView mAccountHeaderIcon;
Walter Jang708ea9e2015-09-10 15:42:05 -0700392
393 // Account selector
394 private View mAccountSelectorContainer;
395 private View mAccountSelector;
396 private TextView mAccountSelectorType;
397 private TextView mAccountSelectorName;
398
Tingting Wang655ad1a2015-10-05 17:51:14 -0700399 // Raw contacts selector
400 private View mRawContactContainer;
401 private TextView mRawContactSummary;
402 private ImageView mPrimaryAccountIcon;
403
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700404 private CompactPhotoEditorView mPhotoView;
Walter Jangf5dfea42015-09-16 12:30:36 -0700405 private ViewGroup mKindSectionViews;
Walter Jangf10ca152015-09-22 15:23:55 -0700406 private Map<String,List<CompactKindSectionView>> mKindSectionViewsMap = new HashMap<>();
Walter Jangb6ca2722015-02-20 11:10:25 -0800407 private View mMoreFields;
Walter Jangcab3dce2015-02-09 17:48:03 -0800408
Walter Jang79658e12015-09-24 10:36:26 -0700409 private boolean mIsExpanded;
Walter Jangcbd431d2015-10-06 13:07:30 -0700410
Walter Jang3efae4a2015-02-18 11:12:00 -0800411 private long mPhotoRawContactId;
Walter Jang31a74ad2015-10-02 19:17:39 -0700412 private ValuesDelta mPhotoValuesDelta;
Walter Jang4f5594a2015-10-06 18:40:31 -0700413 private StructuredNameEditorView mPrimaryNameEditorView;
Walter Jang3efae4a2015-02-18 11:12:00 -0800414
Walter Jangcab3dce2015-02-09 17:48:03 -0800415 public CompactRawContactsEditorView(Context context) {
416 super(context);
417 }
418
419 public CompactRawContactsEditorView(Context context, AttributeSet attrs) {
420 super(context, attrs);
421 }
422
Walter Jangb6ca2722015-02-20 11:10:25 -0800423 /**
424 * Sets the receiver for {@link CompactRawContactsEditorView} callbacks.
425 */
426 public void setListener(Listener listener) {
427 mListener = listener;
428 }
429
Walter Jangcab3dce2015-02-09 17:48:03 -0800430 @Override
431 protected void onFinishInflate() {
432 super.onFinishInflate();
433
434 mAccountTypeManager = AccountTypeManager.getInstance(getContext());
435 mLayoutInflater = (LayoutInflater)
436 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
437
Walter Jang708ea9e2015-09-10 15:42:05 -0700438 // Account header
439 mAccountHeaderContainer = findViewById(R.id.account_container);
440 mAccountHeaderType = (TextView) findViewById(R.id.account_type);
441 mAccountHeaderName = (TextView) findViewById(R.id.account_name);
Tingting Wang91cee282015-10-07 13:48:17 -0700442 mAccountHeaderIcon = (ImageView) findViewById(R.id.account_type_icon);
Walter Jang708ea9e2015-09-10 15:42:05 -0700443
444 // Account selector
445 mAccountSelectorContainer = findViewById(R.id.account_selector_container);
446 mAccountSelector = findViewById(R.id.account);
447 mAccountSelectorType = (TextView) findViewById(R.id.account_type_selector);
448 mAccountSelectorName = (TextView) findViewById(R.id.account_name_selector);
449
Tingting Wang655ad1a2015-10-05 17:51:14 -0700450 // Raw contacts selector
451 mRawContactContainer = findViewById(R.id.all_rawcontacts_accounts_container);
452 mRawContactSummary = (TextView) findViewById(R.id.rawcontacts_accounts_summary);
453 mPrimaryAccountIcon = (ImageView) findViewById(R.id.primary_account_icon);
454
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700455 mPhotoView = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
Walter Jangf5dfea42015-09-16 12:30:36 -0700456 mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
Walter Jangb6ca2722015-02-20 11:10:25 -0800457 mMoreFields = findViewById(R.id.more_fields);
458 mMoreFields.setOnClickListener(this);
459 }
460
Walter Jangb6ca2722015-02-20 11:10:25 -0800461 @Override
462 public void onClick(View view) {
Walter Jangf5dfea42015-09-16 12:30:36 -0700463 if (view.getId() == R.id.more_fields) {
Walter Jangd6753152015-10-02 09:23:13 -0700464 showAllFields();
Walter Jangb6ca2722015-02-20 11:10:25 -0800465 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800466 }
467
468 @Override
469 public void setEnabled(boolean enabled) {
470 super.setEnabled(enabled);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700471 final int childCount = mKindSectionViews.getChildCount();
472 for (int i = 0; i < childCount; i++) {
473 mKindSectionViews.getChildAt(i).setEnabled(enabled);
Walter Jangcab3dce2015-02-09 17:48:03 -0800474 }
475 }
476
Walter Jang79658e12015-09-24 10:36:26 -0700477 @Override
478 public Parcelable onSaveInstanceState() {
479 final Parcelable superState = super.onSaveInstanceState();
480 final SavedState savedState = new SavedState(superState);
481 savedState.mIsExpanded = mIsExpanded;
482 return savedState;
483 }
484
485 @Override
486 public void onRestoreInstanceState(Parcelable state) {
487 if(!(state instanceof SavedState)) {
488 super.onRestoreInstanceState(state);
489 return;
490 }
491 final SavedState savedState = (SavedState) state;
492 super.onRestoreInstanceState(savedState.getSuperState());
493 mIsExpanded = savedState.mIsExpanded;
Walter Jangd6753152015-10-02 09:23:13 -0700494 if (mIsExpanded) {
495 showAllFields();
Walter Jang79658e12015-09-24 10:36:26 -0700496 }
497 }
498
Walter Jang3efae4a2015-02-18 11:12:00 -0800499 /**
Walter Jang31a74ad2015-10-02 19:17:39 -0700500 * Pass through to {@link CompactPhotoEditorView#setListener}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800501 */
Walter Jang31a74ad2015-10-02 19:17:39 -0700502 public void setPhotoListener(CompactPhotoEditorView.Listener listener) {
503 mPhotoView.setListener(listener);
Walter Jang3efae4a2015-02-18 11:12:00 -0800504 }
505
Walter Jang31a74ad2015-10-02 19:17:39 -0700506 public void removePhoto() {
Walter Jang45b86d52015-10-15 15:23:16 -0700507 mPhotoValuesDelta.setFromTemplate(true);
Walter Jang31a74ad2015-10-02 19:17:39 -0700508 mPhotoValuesDelta.put(Photo.PHOTO, (byte[]) null);
509
510 mPhotoView.removePhoto();
Walter Jang3efae4a2015-02-18 11:12:00 -0800511 }
512
513 /**
Walter Jang41b3ea12015-03-09 17:30:06 -0700514 * Pass through to {@link CompactPhotoEditorView#setFullSizedPhoto(Uri)}.
515 */
516 public void setFullSizePhoto(Uri photoUri) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700517 mPhotoView.setFullSizedPhoto(photoUri);
Walter Jang41b3ea12015-03-09 17:30:06 -0700518 }
519
Walter Jang31a74ad2015-10-02 19:17:39 -0700520 public void updatePhoto(Uri photoUri) {
Walter Jang45b86d52015-10-15 15:23:16 -0700521 mPhotoValuesDelta.setFromTemplate(false);
Walter Jang3f18d612015-10-07 16:01:05 -0700522
Walter Jang45b86d52015-10-15 15:23:16 -0700523 if (!mIsUserProfile) {
524 // Unset primary for all photos
525 unsetSuperPrimary();
526
527 // Mark the currently displayed photo as primary
528 mPhotoValuesDelta.setSuperPrimary(true);
529 }
Walter Jang31a74ad2015-10-02 19:17:39 -0700530
Wenyi Wang4c9cf3a2015-10-15 17:58:23 -0700531 // Even though high-res photos cannot be saved by passing them via
532 // an EntityDeltaList (since they cause the Bundle size limit to be
533 // exceeded), we still pass a low-res thumbnail. This simplifies
534 // code all over the place, because we don't have to test whether
535 // there is a change in EITHER the delta-list OR a changed photo...
536 // this way, there is always a change in the delta-list.
537 try {
538 final byte[] bytes = EditorUiUtils.getCompressedThumbnailBitmapBytes(
539 getContext(), photoUri);
540 if (bytes != null) {
541 mPhotoValuesDelta.setPhoto(bytes);
542 }
543 } catch (FileNotFoundException e) {
544 elog("Failed to get bitmap from photo Uri");
545 }
546
Walter Jang31a74ad2015-10-02 19:17:39 -0700547 mPhotoView.setFullSizedPhoto(photoUri);
548 }
549
Walter Jang3f18d612015-10-07 16:01:05 -0700550 private void unsetSuperPrimary() {
551 final List<KindSectionData> kindSectionDataList =
552 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
553 for (KindSectionData kindSectionData : kindSectionDataList) {
554 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
555 for (ValuesDelta valuesDelta : valuesDeltaList) {
556 valuesDelta.setSuperPrimary(false);
557 }
558 }
559 }
560
Walter Jang41b3ea12015-03-09 17:30:06 -0700561 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800562 * Pass through to {@link CompactPhotoEditorView#isWritablePhotoSet}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800563 */
564 public boolean isWritablePhotoSet() {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700565 return mPhotoView.isWritablePhotoSet();
Walter Jang3efae4a2015-02-18 11:12:00 -0800566 }
567
568 /**
Walter Jang3efae4a2015-02-18 11:12:00 -0800569 * Get the raw contact ID for the CompactHeaderView photo.
570 */
Walter Jang3efae4a2015-02-18 11:12:00 -0800571 public long getPhotoRawContactId() {
572 return mPhotoRawContactId;
573 }
574
Walter Jang4f5594a2015-10-06 18:40:31 -0700575 public StructuredNameEditorView getPrimaryNameEditorView() {
576 return mPrimaryNameEditorView;
577 }
578
Walter Jang31a74ad2015-10-02 19:17:39 -0700579 /**
580 * Returns a data holder for every non-default/non-empty photo from each raw contact, whether
581 * the raw contact is writable or not.
582 */
583 public ArrayList<CompactPhotoSelectionFragment.Photo> getPhotos() {
584 final ArrayList<CompactPhotoSelectionFragment.Photo> photos = new ArrayList<>();
585
Walter Jang3f18d612015-10-07 16:01:05 -0700586 final Bundle updatedPhotos = mListener == null ? null : mListener.getUpdatedPhotos();
587
Walter Jang31a74ad2015-10-02 19:17:39 -0700588 final List<KindSectionData> kindSectionDataList =
589 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
590 for (int i = 0; i < kindSectionDataList.size(); i++) {
591 final KindSectionData kindSectionData = kindSectionDataList.get(i);
592 final AccountType accountType = kindSectionData.getAccountType();
593 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
594 if (valuesDeltaList == null || valuesDeltaList.isEmpty()) continue;
595 for (int j = 0; j < valuesDeltaList.size(); j++) {
596 final ValuesDelta valuesDelta = valuesDeltaList.get(j);
597 final Bitmap bitmap = EditorUiUtils.getPhotoBitmap(valuesDelta);
598 if (bitmap == null) continue;
599
600 final CompactPhotoSelectionFragment.Photo photo =
601 new CompactPhotoSelectionFragment.Photo();
602 photo.titleRes = accountType.titleRes;
603 photo.iconRes = accountType.iconRes;
604 photo.syncAdapterPackageName = accountType.syncAdapterPackageName;
605 photo.valuesDelta = valuesDelta;
606 photo.primary = valuesDelta.isSuperPrimary();
607 photo.kindSectionDataListIndex = i;
608 photo.valuesDeltaListIndex = j;
Walter Jang3f18d612015-10-07 16:01:05 -0700609
610 if (updatedPhotos != null) {
611 photo.updatedPhotoUri = (Uri) updatedPhotos.get(String.valueOf(
612 kindSectionData.getRawContactDelta().getRawContactId()));
613 }
614
Walter Jang31a74ad2015-10-02 19:17:39 -0700615 photos.add(photo);
616 }
617 }
618
619 return photos;
620 }
621
622 /**
623 * Marks the raw contact photo given as primary for the aggregate contact and updates the
624 * UI.
625 */
626 public void setPrimaryPhoto(CompactPhotoSelectionFragment.Photo photo) {
Walter Jang31a74ad2015-10-02 19:17:39 -0700627 // Find the values delta to mark as primary
Walter Jang3f18d612015-10-07 16:01:05 -0700628 final KindSectionDataList kindSectionDataList =
629 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
Walter Jang31a74ad2015-10-02 19:17:39 -0700630 if (photo.kindSectionDataListIndex < 0
631 || photo.kindSectionDataListIndex >= kindSectionDataList.size()) {
632 wlog("Invalid kind section data list index");
633 return;
634 }
635 final KindSectionData kindSectionData =
636 kindSectionDataList.get(photo.kindSectionDataListIndex);
637 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
638 if (photo.valuesDeltaListIndex >= valuesDeltaList.size()) {
639 wlog("Invalid values delta list index");
640 return;
641 }
642 final ValuesDelta valuesDelta = valuesDeltaList.get(photo.valuesDeltaListIndex);
643 valuesDelta.setFromTemplate(false);
Walter Jang45b86d52015-10-15 15:23:16 -0700644
645 if (!mIsUserProfile) {
646 // Unset primary for all other photos
647 unsetSuperPrimary();
648
649 valuesDelta.setSuperPrimary(true);
650 }
Walter Jang31a74ad2015-10-02 19:17:39 -0700651
652 // Update the UI
653 mPhotoView.setPhoto(valuesDelta, mMaterialPalette);
654 }
655
Walter Jangd35e5ef2015-02-24 09:18:16 -0800656 public View getAggregationAnchorView() {
Walter Jangf10ca152015-09-22 15:23:55 -0700657 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
Walter Jang192a01c2015-09-22 15:23:55 -0700658 StructuredName.CONTENT_ITEM_TYPE);
Walter Jangf10ca152015-09-22 15:23:55 -0700659 if (!kindSectionViews.isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700660 return mKindSectionViews.getChildAt(0).findViewById(R.id.anchor_view);
Walter Jangd35e5ef2015-02-24 09:18:16 -0800661 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700662 return null;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800663 }
664
Walter Jangf10ca152015-09-22 15:23:55 -0700665 public void setGroupMetaData(Cursor groupMetaData) {
666 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
667 GroupMembership.CONTENT_ITEM_TYPE);
668 for (CompactKindSectionView kindSectionView : kindSectionViews) {
669 kindSectionView.setGroupMetaData(groupMetaData);
Walter Jangd6753152015-10-02 09:23:13 -0700670 if (mIsExpanded) {
671 kindSectionView.setHideWhenEmpty(false);
672 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
673 }
Walter Jangf10ca152015-09-22 15:23:55 -0700674 }
675 }
676
Walter Jangf46abd82015-02-20 16:52:04 -0800677 public void setState(RawContactDeltaList rawContactDeltas,
Walter Jang06f73a12015-06-17 11:15:48 -0700678 MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
Walter Jang4f5594a2015-10-06 18:40:31 -0700679 long photoId, String readOnlyDisplayName, boolean hasNewContact,
680 boolean isUserProfile, AccountWithDataSet primaryAccount) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700681 mKindSectionDataMap.clear();
Walter Jangf5dfea42015-09-16 12:30:36 -0700682 mKindSectionViews.removeAllViews();
Walter Jang363d3fd2015-09-16 10:29:07 -0700683 mMoreFields.setVisibility(View.VISIBLE);
Walter Jangcab3dce2015-02-09 17:48:03 -0800684
Walter Jangf46abd82015-02-20 16:52:04 -0800685 mMaterialPalette = materialPalette;
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700686 mViewIdGenerator = viewIdGenerator;
Walter Jang708ea9e2015-09-10 15:42:05 -0700687 mPhotoId = photoId;
Walter Jang4f5594a2015-10-06 18:40:31 -0700688 mReadOnlyDisplayName = readOnlyDisplayName;
Walter Jang708ea9e2015-09-10 15:42:05 -0700689 mHasNewContact = hasNewContact;
690 mIsUserProfile = isUserProfile;
691 mPrimaryAccount = primaryAccount;
692 if (mPrimaryAccount == null) {
693 mPrimaryAccount = ContactEditorUtils.getInstance(getContext()).getDefaultAccount();
694 }
695 vlog("state: primary " + mPrimaryAccount);
Walter Jangcab3dce2015-02-09 17:48:03 -0800696
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700697 // Parse the given raw contact deltas
698 if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
699 elog("No raw contact deltas");
Walter Jang79658e12015-09-24 10:36:26 -0700700 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jangf5dfea42015-09-16 12:30:36 -0700701 return;
702 }
Walter Jangcbd431d2015-10-06 13:07:30 -0700703 parseRawContactDeltas(rawContactDeltas);
Walter Jang79658e12015-09-24 10:36:26 -0700704 if (mKindSectionDataMap.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700705 elog("No kind section data parsed from RawContactDelta(s)");
Walter Jang79658e12015-09-24 10:36:26 -0700706 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700707 return;
708 }
Walter Jangcbd431d2015-10-06 13:07:30 -0700709 mPrimaryRawContactDelta = mKindSectionDataMap.get(StructuredName.CONTENT_ITEM_TYPE)
710 .getEntryToWrite(mPrimaryAccount, mHasNewContact).first.getRawContactDelta();
711 if (mPrimaryRawContactDelta != null) {
712 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
713 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
714 StructuredName.CONTENT_ITEM_TYPE);
715 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
716 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
717 Photo.CONTENT_ITEM_TYPE);
718 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700719
720 // Setup the view
Tingting Wang655ad1a2015-10-05 17:51:14 -0700721 addAccountInfo(rawContactDeltas);
Walter Jang363d3fd2015-09-16 10:29:07 -0700722 addPhotoView();
Walter Jangf5dfea42015-09-16 12:30:36 -0700723 addKindSectionViews();
Walter Jang4f5594a2015-10-06 18:40:31 -0700724 if (mHasNewContact) {
725 maybeCopyPrimaryDisplayName();
Walter Jangd6753152015-10-02 09:23:13 -0700726 }
Walter Jang4f5594a2015-10-06 18:40:31 -0700727 if (mIsExpanded) showAllFields();
Walter Jangd6753152015-10-02 09:23:13 -0700728
729 if (mListener != null) mListener.onEditorsBound();
Walter Jangcab3dce2015-02-09 17:48:03 -0800730 }
731
Walter Jangcbd431d2015-10-06 13:07:30 -0700732 private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700733 // Build the kind section data list map
Walter Jang192a01c2015-09-22 15:23:55 -0700734 vlog("parse: " + rawContactDeltas.size() + " rawContactDelta(s)");
735 for (int j = 0; j < rawContactDeltas.size(); j++) {
736 final RawContactDelta rawContactDelta = rawContactDeltas.get(j);
737 vlog("parse: " + j + " rawContactDelta" + rawContactDelta);
Walter Jang363d3fd2015-09-16 10:29:07 -0700738 if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
Walter Jangcab3dce2015-02-09 17:48:03 -0800739 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jang363d3fd2015-09-16 10:29:07 -0700740 if (accountType == null) continue;
741 final List<DataKind> dataKinds = accountType.getSortedDataKinds();
742 final int dataKindSize = dataKinds == null ? 0 : dataKinds.size();
743 vlog("parse: " + dataKindSize + " dataKinds(s)");
744 for (int i = 0; i < dataKindSize; i++) {
745 final DataKind dataKind = dataKinds.get(i);
Walter Jang192a01c2015-09-22 15:23:55 -0700746 if (dataKind == null || !dataKind.editable) {
747 vlog("parse: " + i + " " + dataKind.mimeType + " dropped read-only");
748 continue;
749 }
750 final String mimeType = dataKind.mimeType;
751
752 // Skip psuedo mime types
753 if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)
754 || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
755 vlog("parse: " + i + " " + dataKind.mimeType + " dropped pseudo type");
756 continue;
757 }
758
Walter Jang3f18d612015-10-07 16:01:05 -0700759 final KindSectionDataList kindSectionDataList =
Walter Jangcbd431d2015-10-06 13:07:30 -0700760 getOrCreateKindSectionDataList(mimeType);
Walter Jang363d3fd2015-09-16 10:29:07 -0700761 final KindSectionData kindSectionData =
762 new KindSectionData(accountType, dataKind, rawContactDelta);
763 kindSectionDataList.add(kindSectionData);
Walter Jang192a01c2015-09-22 15:23:55 -0700764
Walter Jang79658e12015-09-24 10:36:26 -0700765 // Note we must create nickname entries
Walter Jang192a01c2015-09-22 15:23:55 -0700766 if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jang79658e12015-09-24 10:36:26 -0700767 && kindSectionData.getValuesDeltas().isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700768 RawContactModifier.insertChild(rawContactDelta, dataKind);
769 }
770
Walter Jang363d3fd2015-09-16 10:29:07 -0700771 vlog("parse: " + i + " " + dataKind.mimeType + " " +
Walter Jang192a01c2015-09-22 15:23:55 -0700772 kindSectionData.getValuesDeltas().size() + " value(s)");
Walter Jangac679af2015-06-01 12:17:06 -0700773 }
774 }
Walter Jang3efae4a2015-02-18 11:12:00 -0800775 }
776
Walter Jang3f18d612015-10-07 16:01:05 -0700777 private KindSectionDataList getOrCreateKindSectionDataList(String mimeType) {
Walter Jang192a01c2015-09-22 15:23:55 -0700778 // Put structured names and nicknames together
779 mimeType = Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
780 ? StructuredName.CONTENT_ITEM_TYPE : mimeType;
Walter Jangcbd431d2015-10-06 13:07:30 -0700781 KindSectionDataList kindSectionDataList = mKindSectionDataMap.get(mimeType);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700782 if (kindSectionDataList == null) {
Walter Jangcbd431d2015-10-06 13:07:30 -0700783 kindSectionDataList = new KindSectionDataList();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700784 mKindSectionDataMap.put(mimeType, kindSectionDataList);
785 }
786 return kindSectionDataList;
787 }
788
Tingting Wang655ad1a2015-10-05 17:51:14 -0700789 private void addAccountInfo(RawContactDeltaList rawContactDeltas) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700790 if (mPrimaryRawContactDelta == null) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700791 mAccountHeaderContainer.setVisibility(View.GONE);
792 mAccountSelectorContainer.setVisibility(View.GONE);
793 return;
794 }
795
796 // Get the account information for the primary raw contact delta
797 final Pair<String,String> accountInfo = EditorUiUtils.getAccountInfo(getContext(),
798 mIsUserProfile, mPrimaryRawContactDelta.getAccountName(),
799 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager));
800
801 // The account header and selector show the same information so both shouldn't be visible
802 // at the same time
803 final List<AccountWithDataSet> accounts =
804 AccountTypeManager.getInstance(getContext()).getAccounts(true);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700805 mRawContactContainer.setVisibility(View.GONE);
Walter Jang708ea9e2015-09-10 15:42:05 -0700806 if (mHasNewContact && !mIsUserProfile && accounts.size() > 1) {
807 mAccountHeaderContainer.setVisibility(View.GONE);
808 addAccountSelector(accountInfo);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700809 } else if (mHasNewContact && !mIsUserProfile) {
810 addAccountHeader(accountInfo);
811 mAccountSelectorContainer.setVisibility(View.GONE);
812 } else if (rawContactDeltas.size() > 1) {
813 mAccountHeaderContainer.setVisibility(View.GONE);
814 mAccountSelectorContainer.setVisibility(View.GONE);
815 addRawContactAccountSelector(rawContactDeltas);
Walter Jang708ea9e2015-09-10 15:42:05 -0700816 } else {
817 addAccountHeader(accountInfo);
818 mAccountSelectorContainer.setVisibility(View.GONE);
819 }
820 }
821
822 private void addAccountHeader(Pair<String,String> accountInfo) {
Walter Jang03cea2e2015-09-18 17:04:21 -0700823 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700824 // Hide this view so the other text view will be centered vertically
825 mAccountHeaderName.setVisibility(View.GONE);
826 } else {
827 mAccountHeaderName.setVisibility(View.VISIBLE);
828 mAccountHeaderName.setText(accountInfo.first);
829 }
830 mAccountHeaderType.setText(accountInfo.second);
831
Tingting Wang91cee282015-10-07 13:48:17 -0700832 final AccountType primaryAccountType = mPrimaryRawContactDelta.getRawContactAccountType(
833 getContext());
834 mAccountHeaderIcon.setImageDrawable(primaryAccountType.getDisplayIcon(getContext()));
835
Walter Jang708ea9e2015-09-10 15:42:05 -0700836 mAccountHeaderContainer.setContentDescription(
837 EditorUiUtils.getAccountInfoContentDescription(
838 accountInfo.first, accountInfo.second));
839 }
840
841 private void addAccountSelector(Pair<String,String> accountInfo) {
842 mAccountSelectorContainer.setVisibility(View.VISIBLE);
843
Walter Jang03cea2e2015-09-18 17:04:21 -0700844 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700845 // Hide this view so the other text view will be centered vertically
846 mAccountSelectorName.setVisibility(View.GONE);
847 } else {
848 mAccountSelectorName.setVisibility(View.VISIBLE);
849 mAccountSelectorName.setText(accountInfo.first);
850 }
851 mAccountSelectorType.setText(accountInfo.second);
852
853 mAccountSelectorContainer.setContentDescription(
854 EditorUiUtils.getAccountInfoContentDescription(
855 accountInfo.first, accountInfo.second));
856
857 mAccountSelector.setOnClickListener(new View.OnClickListener() {
858 @Override
859 public void onClick(View v) {
860 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
861 final AccountsListAdapter adapter =
862 new AccountsListAdapter(getContext(),
863 AccountsListAdapter.AccountListFilter.ACCOUNTS_CONTACT_WRITABLE,
864 mPrimaryAccount);
865 popup.setWidth(mAccountSelectorContainer.getWidth());
866 popup.setAnchorView(mAccountSelectorContainer);
867 popup.setAdapter(adapter);
868 popup.setModal(true);
869 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
870 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
871 @Override
872 public void onItemClick(AdapterView<?> parent, View view, int position,
873 long id) {
874 UiClosables.closeQuietly(popup);
875 final AccountWithDataSet newAccount = adapter.getItem(position);
876 if (mListener != null && !mPrimaryAccount.equals(newAccount)) {
877 mListener.onRebindEditorsForNewContact(
878 mPrimaryRawContactDelta,
879 mPrimaryAccount,
880 newAccount);
881 }
882 }
883 });
884 popup.show();
885 }
886 });
887 }
888
Tingting Wang655ad1a2015-10-05 17:51:14 -0700889 private void addRawContactAccountSelector(final RawContactDeltaList rawContactDeltas) {
890 mRawContactContainer.setVisibility(View.VISIBLE);
891
Tingting Wanga0b69402015-10-13 14:27:58 -0700892 Collections.sort(rawContactDeltas, new RawContactDeltaComparator(getContext()));
893
Tingting Wang655ad1a2015-10-05 17:51:14 -0700894 final String accountsSummary = getRawContactsAccountsSummary(
895 getContext(), rawContactDeltas);
896 mRawContactSummary.setText(accountsSummary);
897 mRawContactContainer.setContentDescription(accountsSummary);
898 if (mPrimaryRawContactDelta != null) {
899 final AccountType primaryAccountType = mPrimaryRawContactDelta.getRawContactAccountType(
900 getContext());
901 mPrimaryAccountIcon.setImageDrawable(primaryAccountType.getDisplayIcon(getContext()));
902 }
903
904 mRawContactContainer.setOnClickListener(new View.OnClickListener() {
905 @Override
906 public void onClick(View v) {
907 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
908 final RawContactAccountListAdapter adapter =
909 new RawContactAccountListAdapter(getContext(), rawContactDeltas);
910 popup.setWidth(mRawContactContainer.getWidth());
911 popup.setAnchorView(mRawContactContainer);
912 popup.setAdapter(adapter);
913 popup.setModal(true);
914 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
915 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
916 @Override
917 public void onItemClick(AdapterView<?> parent, View view, int position,
918 long id) {
919 UiClosables.closeQuietly(popup);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700920
921 if (mListener != null) {
Tingting Wang91cee282015-10-07 13:48:17 -0700922 final long rawContactId = adapter.getItemId(position);
923 final Uri rawContactUri = ContentUris.withAppendedId(
924 ContactsContract.RawContacts.CONTENT_URI, rawContactId);
925 final RawContactDelta rawContactDelta = adapter.getItem(position);
926 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(
927 getContext());
928 final AccountType accountType = rawContactDelta.getAccountType(
929 accountTypes);
930 final boolean isReadOnly = !accountType.areContactsWritable();
931
932 mListener.onRawContactSelected(rawContactUri, rawContactId, isReadOnly);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700933 }
934 }
935 });
936 popup.show();
937 }
938 });
939 }
940
941 private static String getRawContactsAccountsSummary(
942 Context context, RawContactDeltaList rawContactDeltas) {
Tingting Wanga0b69402015-10-13 14:27:58 -0700943 final LinkedHashMap<String, Integer> accountTypeNumber = new LinkedHashMap<>();
Tingting Wang655ad1a2015-10-05 17:51:14 -0700944 for (RawContactDelta rawContactDelta : rawContactDeltas) {
945 if (rawContactDelta.isVisible()) {
946 final AccountType accountType = rawContactDelta.getRawContactAccountType(context);
947 final String accountTypeLabel = accountType.getDisplayLabel(context).toString();
948 if (accountTypeNumber.containsKey(accountTypeLabel)) {
949 int number = accountTypeNumber.get(accountTypeLabel);
950 number++;
951 accountTypeNumber.put(accountTypeLabel, number);
952 } else {
953 accountTypeNumber.put(accountTypeLabel, 1);
954 }
955 }
956 }
957
Tingting Wanga0b69402015-10-13 14:27:58 -0700958 final LinkedHashSet<String> linkedAccounts = new LinkedHashSet<>();
Tingting Wang655ad1a2015-10-05 17:51:14 -0700959 for (String accountTypeLabel : accountTypeNumber.keySet()) {
960 final String number = context.getResources().getQuantityString(
961 R.plurals.quickcontact_suggestion_account_type_number,
962 accountTypeNumber.get(accountTypeLabel),
963 accountTypeNumber.get(accountTypeLabel));
964 final String accountWithNumber = context.getResources().getString(
965 R.string.quickcontact_suggestion_account_type,
966 accountTypeLabel,
967 number);
968 linkedAccounts.add(accountWithNumber);
969 }
970 return TextUtils.join(",", linkedAccounts);
971 }
972
Walter Jang363d3fd2015-09-16 10:29:07 -0700973 private void addPhotoView() {
Walter Jang31a74ad2015-10-02 19:17:39 -0700974 // Get the kind section data and values delta that we will display in the photo view
Walter Jangcbd431d2015-10-06 13:07:30 -0700975 final KindSectionDataList kindSectionDataList =
976 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
977 final Pair<KindSectionData,ValuesDelta> photoToDisplay =
978 kindSectionDataList.getEntryToDisplay(mPhotoId);
979 if (photoToDisplay == null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700980 wlog("photo: no kind section data parsed");
Walter Jangcbd431d2015-10-06 13:07:30 -0700981 mPhotoView.setVisibility(View.GONE);
Walter Jang363d3fd2015-09-16 10:29:07 -0700982 return;
983 }
Walter Jang06f73a12015-06-17 11:15:48 -0700984
Walter Jang31a74ad2015-10-02 19:17:39 -0700985 // Set the photo view
Walter Jangcbd431d2015-10-06 13:07:30 -0700986 mPhotoView.setPhoto(photoToDisplay.second, mMaterialPalette);
Walter Jang31a74ad2015-10-02 19:17:39 -0700987
988 // Find the raw contact ID and values delta that will be written when the photo is edited
Walter Jangcbd431d2015-10-06 13:07:30 -0700989 final Pair<KindSectionData,ValuesDelta> photoToWrite = kindSectionDataList.getEntryToWrite(
990 mPrimaryAccount, mHasNewContact);
991 if (photoToWrite == null) {
Walter Jang31a74ad2015-10-02 19:17:39 -0700992 mPhotoView.setReadOnly(true);
993 return;
994 }
Walter Jang31a74ad2015-10-02 19:17:39 -0700995 mPhotoView.setReadOnly(false);
Walter Jangcbd431d2015-10-06 13:07:30 -0700996 mPhotoRawContactId = photoToWrite.first.getRawContactDelta().getRawContactId();
997 mPhotoValuesDelta = photoToWrite.second;
Walter Jang31a74ad2015-10-02 19:17:39 -0700998 }
999
Walter Jangf5dfea42015-09-16 12:30:36 -07001000 private void addKindSectionViews() {
1001 // Sort the kinds
Walter Jangcbd431d2015-10-06 13:07:30 -07001002 final TreeSet<Map.Entry<String,KindSectionDataList>> entries =
Walter Jang192a01c2015-09-22 15:23:55 -07001003 new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
Walter Jangf5dfea42015-09-16 12:30:36 -07001004 entries.addAll(mKindSectionDataMap.entrySet());
Walter Jang3efae4a2015-02-18 11:12:00 -08001005
Walter Jangf5dfea42015-09-16 12:30:36 -07001006 vlog("kind: " + entries.size() + " kindSection(s)");
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001007 int i = -1;
Walter Jangcbd431d2015-10-06 13:07:30 -07001008 for (Map.Entry<String, KindSectionDataList> entry : entries) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001009 i++;
1010
Walter Jangf5dfea42015-09-16 12:30:36 -07001011 final String mimeType = entry.getKey();
Walter Jangcbd431d2015-10-06 13:07:30 -07001012 final KindSectionDataList kindSectionDataList = entry.getValue();
Walter Jangab50e6f2015-06-15 08:57:22 -07001013
Walter Jangf5dfea42015-09-16 12:30:36 -07001014 // Ignore mime types that we've already handled
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001015 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
1016 vlog("kind: " + i + " " + mimeType + " dropped");
1017 continue;
1018 }
Walter Jangf5dfea42015-09-16 12:30:36 -07001019
Walter Jange5e7aef2015-10-15 14:52:42 -07001020 // Don't show more than one group editor on the compact editor.
1021 // Groups will still be editable for each raw contact individually on the full editor.
1022 if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)
1023 && kindSectionDataList.size() > 1) {
1024 vlog("kind: " + i + " " + mimeType + " dropped");
1025 continue;
1026 }
1027
Walter Jang192a01c2015-09-22 15:23:55 -07001028 if (kindSectionDataList != null && !kindSectionDataList.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001029 vlog("kind: " + i + " " + mimeType + ": " + kindSectionDataList.size() +
1030 " kindSectionData(s)");
1031
Walter Jangf5dfea42015-09-16 12:30:36 -07001032 final CompactKindSectionView kindSectionView = inflateKindSectionView(
1033 mKindSectionViews, kindSectionDataList, mimeType);
Walter Jangf5dfea42015-09-16 12:30:36 -07001034 mKindSectionViews.addView(kindSectionView);
Walter Jangf10ca152015-09-22 15:23:55 -07001035
1036 // Keep a pointer to all the KindSectionsViews for each mimeType
1037 getKindSectionViews(mimeType).add(kindSectionView);
Walter Jangcab3dce2015-02-09 17:48:03 -08001038 }
1039 }
1040 }
1041
Walter Jangf10ca152015-09-22 15:23:55 -07001042 private List<CompactKindSectionView> getKindSectionViews(String mimeType) {
1043 List<CompactKindSectionView> kindSectionViews = mKindSectionViewsMap.get(mimeType);
1044 if (kindSectionViews == null) {
1045 kindSectionViews = new ArrayList<>();
1046 mKindSectionViewsMap.put(mimeType, kindSectionViews);
1047 }
1048 return kindSectionViews;
1049 }
1050
Walter Jangf5dfea42015-09-16 12:30:36 -07001051 private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
Walter Jangcbd431d2015-10-06 13:07:30 -07001052 KindSectionDataList kindSectionDataList, String mimeType) {
Walter Jangf5dfea42015-09-16 12:30:36 -07001053 final CompactKindSectionView kindSectionView = (CompactKindSectionView)
1054 mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
1055 /* attachToRoot =*/ false);
Walter Jang45b86d52015-10-15 15:23:16 -07001056 kindSectionView.setIsUserProfile(mIsUserProfile);
Walter Jangf5dfea42015-09-16 12:30:36 -07001057
Walter Jang192a01c2015-09-22 15:23:55 -07001058 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jangf5dfea42015-09-16 12:30:36 -07001059 || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang192a01c2015-09-22 15:23:55 -07001060 // Phone numbers and email addresses are always displayed,
1061 // even if they are empty
Walter Jangf5dfea42015-09-16 12:30:36 -07001062 kindSectionView.setHideWhenEmpty(false);
1063 }
Walter Jangf5dfea42015-09-16 12:30:36 -07001064
Walter Jang192a01c2015-09-22 15:23:55 -07001065 // Since phone numbers and email addresses displayed even if they are empty,
1066 // they will be the only types you add new values to initially for new contacts
1067 kindSectionView.setShowOneEmptyEditor(true);
1068
1069 // Sort so the editors wind up in the order we want
1070 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
1071 Collections.sort(kindSectionDataList, new NameEditorComparator(getContext(),
1072 mPrimaryRawContactDelta));
1073 } else {
1074 Collections.sort(kindSectionDataList, new EditorComparator(getContext()));
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001075 }
1076
Walter Jang79658e12015-09-24 10:36:26 -07001077 kindSectionView.setState(kindSectionDataList, mViewIdGenerator, mListener);
Walter Jangf5dfea42015-09-16 12:30:36 -07001078
1079 return kindSectionView;
Walter Jangcab3dce2015-02-09 17:48:03 -08001080 }
1081
Walter Jang4f5594a2015-10-06 18:40:31 -07001082 private void maybeCopyPrimaryDisplayName() {
1083 if (TextUtils.isEmpty(mReadOnlyDisplayName)) return;
1084 final List<CompactKindSectionView> kindSectionViews
1085 = mKindSectionViewsMap.get(StructuredName.CONTENT_ITEM_TYPE);
1086 if (kindSectionViews.isEmpty()) return;
1087 final CompactKindSectionView primaryNameKindSectionView = kindSectionViews.get(0);
1088 if (primaryNameKindSectionView.isEmptyName()) {
1089 vlog("name: using read only display name as primary name");
1090 primaryNameKindSectionView.setName(mReadOnlyDisplayName);
1091 mPrimaryNameEditorView = primaryNameKindSectionView.getPrimaryNameEditorView();
1092 }
1093 }
1094
Walter Jangd6753152015-10-02 09:23:13 -07001095 private void showAllFields() {
Walter Jang79658e12015-09-24 10:36:26 -07001096 // Stop hiding empty editors and allow the user to enter values for all kinds now
1097 for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
1098 final CompactKindSectionView kindSectionView =
1099 (CompactKindSectionView) mKindSectionViews.getChildAt(i);
1100 kindSectionView.setHideWhenEmpty(false);
1101 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
1102 }
1103 mIsExpanded = true;
Walter Jang79658e12015-09-24 10:36:26 -07001104
Walter Jangf5dfea42015-09-16 12:30:36 -07001105 // Hide the more fields button
1106 mMoreFields.setVisibility(View.GONE);
Walter Jangcab3dce2015-02-09 17:48:03 -08001107 }
1108
Walter Jangbf63a6d2015-05-05 09:14:35 -07001109 private static void vlog(String message) {
1110 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1111 Log.v(TAG, message);
Walter Jangcab3dce2015-02-09 17:48:03 -08001112 }
1113 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001114
1115 private static void wlog(String message) {
1116 if (Log.isLoggable(TAG, Log.WARN)) {
1117 Log.w(TAG, message);
1118 }
1119 }
1120
1121 private static void elog(String message) {
1122 Log.e(TAG, message);
1123 }
Walter Jangcab3dce2015-02-09 17:48:03 -08001124}