blob: 091f1cbe69ac8d9f8d0be69be0fb02e9bf1a08b2 [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;
33import android.content.ContentValues;
Walter Jangcab3dce2015-02-09 17:48:03 -080034import android.content.Context;
Tingting Wang655ad1a2015-10-05 17:51:14 -070035import android.content.Intent;
Walter Jangf10ca152015-09-22 15:23:55 -070036import android.database.Cursor;
Walter Jang3efae4a2015-02-18 11:12:00 -080037import android.graphics.Bitmap;
Walter Jang41b3ea12015-03-09 17:30:06 -070038import android.net.Uri;
Walter Jang79658e12015-09-24 10:36:26 -070039import android.os.Parcel;
40import android.os.Parcelable;
Tingting Wang655ad1a2015-10-05 17:51:14 -070041import android.provider.ContactsContract;
Walter Jangcab3dce2015-02-09 17:48:03 -080042import android.provider.ContactsContract.CommonDataKinds.Email;
Walter Jangf5dfea42015-09-16 12:30:36 -070043import android.provider.ContactsContract.CommonDataKinds.Event;
Walter Jangcab3dce2015-02-09 17:48:03 -080044import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Walter Jangf5dfea42015-09-16 12:30:36 -070045import android.provider.ContactsContract.CommonDataKinds.Im;
Walter Jang3efae4a2015-02-18 11:12:00 -080046import android.provider.ContactsContract.CommonDataKinds.Nickname;
Walter Jangf5dfea42015-09-16 12:30:36 -070047import android.provider.ContactsContract.CommonDataKinds.Note;
48import android.provider.ContactsContract.CommonDataKinds.Organization;
Walter Jangcab3dce2015-02-09 17:48:03 -080049import android.provider.ContactsContract.CommonDataKinds.Phone;
50import android.provider.ContactsContract.CommonDataKinds.Photo;
Walter Jangf5dfea42015-09-16 12:30:36 -070051import android.provider.ContactsContract.CommonDataKinds.Relation;
52import android.provider.ContactsContract.CommonDataKinds.SipAddress;
Walter Jangcab3dce2015-02-09 17:48:03 -080053import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Walter Jangf5dfea42015-09-16 12:30:36 -070054import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
55import android.provider.ContactsContract.CommonDataKinds.Website;
Walter Jangcab3dce2015-02-09 17:48:03 -080056import android.text.TextUtils;
57import android.util.AttributeSet;
58import android.util.Log;
Walter Jang2d3f31c2015-06-18 23:15:31 -070059import android.util.Pair;
Walter Jangcab3dce2015-02-09 17:48:03 -080060import android.view.LayoutInflater;
Walter Jangb1c87622015-02-13 17:51:38 -080061import android.view.View;
Walter Jangcab3dce2015-02-09 17:48:03 -080062import android.view.ViewGroup;
Walter Jang708ea9e2015-09-10 15:42:05 -070063import android.widget.AdapterView;
Tingting Wang655ad1a2015-10-05 17:51:14 -070064import android.widget.BaseAdapter;
65import android.widget.ImageView;
Walter Jangcab3dce2015-02-09 17:48:03 -080066import android.widget.LinearLayout;
Walter Jang708ea9e2015-09-10 15:42:05 -070067import android.widget.ListPopupWindow;
Walter Jang2d3f31c2015-06-18 23:15:31 -070068import android.widget.TextView;
Walter Jangcab3dce2015-02-09 17:48:03 -080069
Walter Jang31a74ad2015-10-02 19:17:39 -070070import java.io.FileNotFoundException;
Walter Jangcab3dce2015-02-09 17:48:03 -080071import java.util.ArrayList;
Walter Jangf5dfea42015-09-16 12:30:36 -070072import java.util.Arrays;
Walter Jang192a01c2015-09-22 15:23:55 -070073import java.util.Collections;
Walter Jangf5dfea42015-09-16 12:30:36 -070074import java.util.Comparator;
Walter Jang19d985f2015-07-01 13:36:27 -070075import java.util.HashMap;
Tingting Wang655ad1a2015-10-05 17:51:14 -070076import java.util.HashSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080077import java.util.List;
Walter Jang19d985f2015-07-01 13:36:27 -070078import java.util.Map;
Tingting Wang655ad1a2015-10-05 17:51:14 -070079import java.util.Set;
Walter Jangf5dfea42015-09-16 12:30:36 -070080import java.util.TreeSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080081
82/**
Walter Jangf5dfea42015-09-16 12:30:36 -070083 * View to display information from multiple {@link RawContactDelta}s grouped together.
Walter Jangcab3dce2015-02-09 17:48:03 -080084 */
Walter Jangb6ca2722015-02-20 11:10:25 -080085public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
Walter Jangcab3dce2015-02-09 17:48:03 -080086
Walter Jang4f5594a2015-10-06 18:40:31 -070087 static final String TAG = "CompactEditorView";
Walter Jangcab3dce2015-02-09 17:48:03 -080088
Walter Jang192a01c2015-09-22 15:23:55 -070089 private static final KindSectionDataMapEntryComparator
90 KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR = new KindSectionDataMapEntryComparator();
Walter Jangf5dfea42015-09-16 12:30:36 -070091
Walter Jangb6ca2722015-02-20 11:10:25 -080092 /**
93 * Callbacks for hosts of {@link CompactRawContactsEditorView}s.
94 */
95 public interface Listener {
96
97 /**
Walter Jang151f3e62015-02-26 15:29:40 -080098 * Invoked when the structured name editor field has changed.
99 *
100 * @param rawContactId The raw contact ID from the underlying {@link RawContactDelta}.
101 * @param valuesDelta The values from the underlying {@link RawContactDelta}.
102 */
103 public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta);
Walter Jang708ea9e2015-09-10 15:42:05 -0700104
105 /**
106 * Invoked when the compact editor should rebind editors for a new account.
107 *
108 * @param oldState Old data being edited.
109 * @param oldAccount Old account associated with oldState.
110 * @param newAccount New account to be used.
111 */
112 public void onRebindEditorsForNewContact(RawContactDelta oldState,
113 AccountWithDataSet oldAccount, AccountWithDataSet newAccount);
Walter Jang79658e12015-09-24 10:36:26 -0700114
115 /**
116 * Invoked when no editors could be bound for the contact.
117 */
118 public void onBindEditorsFailed();
Walter Jangd6753152015-10-02 09:23:13 -0700119
120 /**
121 * Invoked after editors have been bound for the contact.
122 */
123 public void onEditorsBound();
Tingting Wang655ad1a2015-10-05 17:51:14 -0700124
125 /**
126 * Invoked when a rawcontact from merged contacts is selected in editor.
127 */
128 public void onRawContactSelected(Uri uri, long rawContactId);
129 }
130
131 /**
132 * Used to list the account info for the given raw contacts list.
133 */
134 private static final class RawContactAccountListAdapter extends BaseAdapter {
135 private final LayoutInflater mInflater;
136 private final Context mContext;
137 private final RawContactDeltaList mRawContactDeltas;
138
139 public RawContactAccountListAdapter(Context context, RawContactDeltaList rawContactDeltas) {
140 mContext = context;
141 mRawContactDeltas = new RawContactDeltaList();
142 for (RawContactDelta rawContactDelta : rawContactDeltas) {
143 if (rawContactDelta.isVisible()) {
144 mRawContactDeltas.add(rawContactDelta);
145 }
146 }
147 mInflater = LayoutInflater.from(context);
148 }
149
150 @Override
151 public View getView(int position, View convertView, ViewGroup parent) {
152 final View resultView = convertView != null ? convertView
153 : mInflater.inflate(R.layout.account_selector_list_item, parent, false);
154
155 final RawContactDelta rawContactDelta = mRawContactDeltas.get(position);
156 final String accountName = rawContactDelta.getAccountName();
157 final AccountType accountType = rawContactDelta.getRawContactAccountType(mContext);
158
159 final TextView text1 = (TextView) resultView.findViewById(android.R.id.text1);
160 text1.setText(accountType.getDisplayLabel(mContext));
161
162 // For email addresses, we don't want to truncate at end, which might cut off the domain
163 // name.
164 final TextView text2 = (TextView) resultView.findViewById(android.R.id.text2);
165 text2.setText(accountName);
166 text2.setEllipsize(TextUtils.TruncateAt.MIDDLE);
167
168 final ImageView icon = (ImageView) resultView.findViewById(android.R.id.icon);
169 icon.setImageDrawable(accountType.getDisplayIcon(mContext));
170
171 return resultView;
172 }
173
174 @Override
175 public int getCount() {
176 return mRawContactDeltas.size();
177 }
178
179 @Override
180 public RawContactDelta getItem(int position) {
181 return mRawContactDeltas.get(position);
182 }
183
184 @Override
185 public long getItemId(int position) {
186 return getItem(position).getRawContactId();
187 }
Walter Jang151f3e62015-02-26 15:29:40 -0800188 }
189
Walter Jang192a01c2015-09-22 15:23:55 -0700190 /** Used to sort entire kind sections. */
191 private static final class KindSectionDataMapEntryComparator implements
Walter Jangcbd431d2015-10-06 13:07:30 -0700192 Comparator<Map.Entry<String,KindSectionDataList>> {
Walter Jangf5dfea42015-09-16 12:30:36 -0700193
Walter Jang192a01c2015-09-22 15:23:55 -0700194 final MimeTypeComparator mMimeTypeComparator = new MimeTypeComparator();
195
196 @Override
Walter Jangcbd431d2015-10-06 13:07:30 -0700197 public int compare(Map.Entry<String, KindSectionDataList> entry1,
198 Map.Entry<String, KindSectionDataList> entry2) {
Walter Jang192a01c2015-09-22 15:23:55 -0700199 if (entry1 == entry2) return 0;
200 if (entry1 == null) return -1;
201 if (entry2 == null) return 1;
202
203 final String mimeType1 = entry1.getKey();
204 final String mimeType2 = entry2.getKey();
205
206 return mMimeTypeComparator.compare(mimeType1, mimeType2);
207 }
208 }
209
210 /**
211 * Sorts kinds roughly the same as quick contacts; we diverge in the following ways:
212 * <ol>
213 * <li>All names are together at the top.</li>
214 * <li>IM is moved up after addresses</li>
215 * <li>SIP addresses are moved to below phone numbers</li>
Walter Jang79658e12015-09-24 10:36:26 -0700216 * <li>Group membership is placed at the end</li>
Walter Jang192a01c2015-09-22 15:23:55 -0700217 * </ol>
218 */
219 private static final class MimeTypeComparator implements Comparator<String> {
220
Walter Jangf5dfea42015-09-16 12:30:36 -0700221 private static final List<String> MIME_TYPE_ORDER = Arrays.asList(new String[] {
222 StructuredName.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700223 Nickname.CONTENT_ITEM_TYPE,
Walter Jang2ae21752015-09-25 11:25:38 -0700224 Organization.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700225 Phone.CONTENT_ITEM_TYPE,
226 SipAddress.CONTENT_ITEM_TYPE,
227 Email.CONTENT_ITEM_TYPE,
228 StructuredPostal.CONTENT_ITEM_TYPE,
229 Im.CONTENT_ITEM_TYPE,
230 Website.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700231 Event.CONTENT_ITEM_TYPE,
232 Relation.CONTENT_ITEM_TYPE,
Walter Jangf10ca152015-09-22 15:23:55 -0700233 Note.CONTENT_ITEM_TYPE,
234 GroupMembership.CONTENT_ITEM_TYPE
Walter Jangf5dfea42015-09-16 12:30:36 -0700235 });
236
237 @Override
Walter Jang192a01c2015-09-22 15:23:55 -0700238 public int compare(String mimeType1, String mimeType2) {
239 if (mimeType1 == mimeType2) return 0;
240 if (mimeType1 == null) return -1;
241 if (mimeType2 == null) return 1;
Walter Jangf5dfea42015-09-16 12:30:36 -0700242
243 int index1 = MIME_TYPE_ORDER.indexOf(mimeType1);
244 int index2 = MIME_TYPE_ORDER.indexOf(mimeType2);
245
246 // Fallback to alphabetical ordering of the mime type if both are not found
247 if (index1 < 0 && index2 < 0) return mimeType1.compareTo(mimeType2);
248 if (index1 < 0) return 1;
249 if (index2 < 0) return -1;
250
251 return index1 < index2 ? -1 : 1;
252 }
253 }
254
Walter Jang192a01c2015-09-22 15:23:55 -0700255 /**
256 * Sorts primary accounts and google account types before others.
257 */
258 private static final class EditorComparator implements Comparator<KindSectionData> {
259
260 private RawContactDeltaComparator mRawContactDeltaComparator;
261
262 private EditorComparator(Context context) {
263 mRawContactDeltaComparator = new RawContactDeltaComparator(context);
264 }
265
266 @Override
267 public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
268 if (kindSectionData1 == kindSectionData2) return 0;
269 if (kindSectionData1 == null) return -1;
270 if (kindSectionData2 == null) return 1;
271
272 final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
273 final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
274
275 if (rawContactDelta1 == rawContactDelta2) return 0;
276 if (rawContactDelta1 == null) return -1;
277 if (rawContactDelta2 == null) return 1;
278
279 return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
280 }
281 }
282
283 /**
284 * Sorts primary account names first, followed by google account types, and other account
285 * types last. For names from the same account we order structured names before nicknames,
286 * but still keep names from the same account together.
287 */
288 private static final class NameEditorComparator implements Comparator<KindSectionData> {
289
Walter Jang4f5594a2015-10-06 18:40:31 -0700290 private final RawContactDeltaComparator mRawContactDeltaComparator;
291 private final MimeTypeComparator mMimeTypeComparator;
292 private final RawContactDelta mPrimaryRawContactDelta;
Walter Jang192a01c2015-09-22 15:23:55 -0700293
294 private NameEditorComparator(Context context, RawContactDelta primaryRawContactDelta) {
295 mRawContactDeltaComparator = new RawContactDeltaComparator(context);
296 mMimeTypeComparator = new MimeTypeComparator();
297 mPrimaryRawContactDelta = primaryRawContactDelta;
298 }
299
300 @Override
301 public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
302 if (kindSectionData1 == kindSectionData2) return 0;
303 if (kindSectionData1 == null) return -1;
304 if (kindSectionData2 == null) return 1;
305
306 final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
307 final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
308
309 if (rawContactDelta1 == rawContactDelta2) return 0;
310 if (rawContactDelta1 == null) return -1;
311 if (rawContactDelta2 == null) return 1;
312
313 final boolean isRawContactDelta1Primary =
314 mPrimaryRawContactDelta.equals(rawContactDelta1);
315 final boolean isRawContactDelta2Primary =
316 mPrimaryRawContactDelta.equals(rawContactDelta2);
317
318 // If both names are from the primary account, sort my by mime type
319 if (isRawContactDelta1Primary && isRawContactDelta2Primary) {
320 final String mimeType1 = kindSectionData1.getDataKind().mimeType;
321 final String mimeType2 = kindSectionData2.getDataKind().mimeType;
322 return mMimeTypeComparator.compare(mimeType1, mimeType2);
323 }
324
325 // The primary account name should be before all others
Walter Jang79658e12015-09-24 10:36:26 -0700326 if (isRawContactDelta1Primary) return -1;
327 if (isRawContactDelta2Primary) return 1;
Walter Jang192a01c2015-09-22 15:23:55 -0700328
329 return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
330 }
331 }
332
Walter Jang79658e12015-09-24 10:36:26 -0700333 public static class SavedState extends BaseSavedState {
334
335 public static final Parcelable.Creator<SavedState> CREATOR =
336 new Parcelable.Creator<SavedState>() {
337 public SavedState createFromParcel(Parcel in) {
338 return new SavedState(in);
339 }
340 public SavedState[] newArray(int size) {
341 return new SavedState[size];
342 }
343 };
344
345 private boolean mIsExpanded;
346
347 public SavedState(Parcelable superState) {
348 super(superState);
349 }
350
351 private SavedState(Parcel in) {
352 super(in);
353 mIsExpanded = in.readInt() != 0;
354 }
355
356 @Override
357 public void writeToParcel(Parcel out, int flags) {
358 super.writeToParcel(out, flags);
359 out.writeInt(mIsExpanded ? 1 : 0);
360 }
361 }
362
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700363 private CompactRawContactsEditorView.Listener mListener;
Walter Jangb6ca2722015-02-20 11:10:25 -0800364
Walter Jangcab3dce2015-02-09 17:48:03 -0800365 private AccountTypeManager mAccountTypeManager;
366 private LayoutInflater mLayoutInflater;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800367
Walter Jangcab3dce2015-02-09 17:48:03 -0800368 private ViewIdGenerator mViewIdGenerator;
Walter Jangf46abd82015-02-20 16:52:04 -0800369 private MaterialColorMapUtils.MaterialPalette mMaterialPalette;
Walter Jang708ea9e2015-09-10 15:42:05 -0700370 private long mPhotoId;
Walter Jang4f5594a2015-10-06 18:40:31 -0700371 private String mReadOnlyDisplayName;
Walter Jang708ea9e2015-09-10 15:42:05 -0700372 private boolean mHasNewContact;
373 private boolean mIsUserProfile;
374 private AccountWithDataSet mPrimaryAccount;
375 private RawContactDelta mPrimaryRawContactDelta;
Walter Jangcbd431d2015-10-06 13:07:30 -0700376 private Map<String,KindSectionDataList> mKindSectionDataMap = new HashMap<>();
Walter Jangcab3dce2015-02-09 17:48:03 -0800377
Walter Jang708ea9e2015-09-10 15:42:05 -0700378 // Account header
379 private View mAccountHeaderContainer;
380 private TextView mAccountHeaderType;
381 private TextView mAccountHeaderName;
382
383 // Account selector
384 private View mAccountSelectorContainer;
385 private View mAccountSelector;
386 private TextView mAccountSelectorType;
387 private TextView mAccountSelectorName;
388
Tingting Wang655ad1a2015-10-05 17:51:14 -0700389 // Raw contacts selector
390 private View mRawContactContainer;
391 private TextView mRawContactSummary;
392 private ImageView mPrimaryAccountIcon;
393
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700394 private CompactPhotoEditorView mPhotoView;
Walter Jangf5dfea42015-09-16 12:30:36 -0700395 private ViewGroup mKindSectionViews;
Walter Jangf10ca152015-09-22 15:23:55 -0700396 private Map<String,List<CompactKindSectionView>> mKindSectionViewsMap = new HashMap<>();
Walter Jangb6ca2722015-02-20 11:10:25 -0800397 private View mMoreFields;
Walter Jangcab3dce2015-02-09 17:48:03 -0800398
Walter Jang79658e12015-09-24 10:36:26 -0700399 private boolean mIsExpanded;
Walter Jangcbd431d2015-10-06 13:07:30 -0700400
Walter Jang3efae4a2015-02-18 11:12:00 -0800401 private long mPhotoRawContactId;
Walter Jang31a74ad2015-10-02 19:17:39 -0700402 private ValuesDelta mPhotoValuesDelta;
Walter Jang4f5594a2015-10-06 18:40:31 -0700403 private StructuredNameEditorView mPrimaryNameEditorView;
Walter Jang3efae4a2015-02-18 11:12:00 -0800404
Walter Jangcab3dce2015-02-09 17:48:03 -0800405 public CompactRawContactsEditorView(Context context) {
406 super(context);
407 }
408
409 public CompactRawContactsEditorView(Context context, AttributeSet attrs) {
410 super(context, attrs);
411 }
412
Walter Jangb6ca2722015-02-20 11:10:25 -0800413 /**
414 * Sets the receiver for {@link CompactRawContactsEditorView} callbacks.
415 */
416 public void setListener(Listener listener) {
417 mListener = listener;
418 }
419
Walter Jangcab3dce2015-02-09 17:48:03 -0800420 @Override
421 protected void onFinishInflate() {
422 super.onFinishInflate();
423
424 mAccountTypeManager = AccountTypeManager.getInstance(getContext());
425 mLayoutInflater = (LayoutInflater)
426 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
427
Walter Jang708ea9e2015-09-10 15:42:05 -0700428 // Account header
429 mAccountHeaderContainer = findViewById(R.id.account_container);
430 mAccountHeaderType = (TextView) findViewById(R.id.account_type);
431 mAccountHeaderName = (TextView) findViewById(R.id.account_name);
432
433 // Account selector
434 mAccountSelectorContainer = findViewById(R.id.account_selector_container);
435 mAccountSelector = findViewById(R.id.account);
436 mAccountSelectorType = (TextView) findViewById(R.id.account_type_selector);
437 mAccountSelectorName = (TextView) findViewById(R.id.account_name_selector);
438
Tingting Wang655ad1a2015-10-05 17:51:14 -0700439 // Raw contacts selector
440 mRawContactContainer = findViewById(R.id.all_rawcontacts_accounts_container);
441 mRawContactSummary = (TextView) findViewById(R.id.rawcontacts_accounts_summary);
442 mPrimaryAccountIcon = (ImageView) findViewById(R.id.primary_account_icon);
443
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700444 mPhotoView = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
Walter Jangf5dfea42015-09-16 12:30:36 -0700445 mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
Walter Jangb6ca2722015-02-20 11:10:25 -0800446 mMoreFields = findViewById(R.id.more_fields);
447 mMoreFields.setOnClickListener(this);
448 }
449
Walter Jangb6ca2722015-02-20 11:10:25 -0800450 @Override
451 public void onClick(View view) {
Walter Jangf5dfea42015-09-16 12:30:36 -0700452 if (view.getId() == R.id.more_fields) {
Walter Jangd6753152015-10-02 09:23:13 -0700453 showAllFields();
Walter Jangb6ca2722015-02-20 11:10:25 -0800454 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800455 }
456
457 @Override
458 public void setEnabled(boolean enabled) {
459 super.setEnabled(enabled);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700460 final int childCount = mKindSectionViews.getChildCount();
461 for (int i = 0; i < childCount; i++) {
462 mKindSectionViews.getChildAt(i).setEnabled(enabled);
Walter Jangcab3dce2015-02-09 17:48:03 -0800463 }
464 }
465
Walter Jang79658e12015-09-24 10:36:26 -0700466 @Override
467 public Parcelable onSaveInstanceState() {
468 final Parcelable superState = super.onSaveInstanceState();
469 final SavedState savedState = new SavedState(superState);
470 savedState.mIsExpanded = mIsExpanded;
471 return savedState;
472 }
473
474 @Override
475 public void onRestoreInstanceState(Parcelable state) {
476 if(!(state instanceof SavedState)) {
477 super.onRestoreInstanceState(state);
478 return;
479 }
480 final SavedState savedState = (SavedState) state;
481 super.onRestoreInstanceState(savedState.getSuperState());
482 mIsExpanded = savedState.mIsExpanded;
Walter Jangd6753152015-10-02 09:23:13 -0700483 if (mIsExpanded) {
484 showAllFields();
Walter Jang79658e12015-09-24 10:36:26 -0700485 }
486 }
487
Walter Jang3efae4a2015-02-18 11:12:00 -0800488 /**
Walter Jang31a74ad2015-10-02 19:17:39 -0700489 * Pass through to {@link CompactPhotoEditorView#setListener}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800490 */
Walter Jang31a74ad2015-10-02 19:17:39 -0700491 public void setPhotoListener(CompactPhotoEditorView.Listener listener) {
492 mPhotoView.setListener(listener);
Walter Jang3efae4a2015-02-18 11:12:00 -0800493 }
494
Walter Jang31a74ad2015-10-02 19:17:39 -0700495 public void removePhoto() {
496 mPhotoValuesDelta.setFromTemplate(false);
497 mPhotoValuesDelta.put(Photo.PHOTO, (byte[]) null);
498
499 mPhotoView.removePhoto();
Walter Jang3efae4a2015-02-18 11:12:00 -0800500 }
501
502 /**
Walter Jang41b3ea12015-03-09 17:30:06 -0700503 * Pass through to {@link CompactPhotoEditorView#setFullSizedPhoto(Uri)}.
504 */
505 public void setFullSizePhoto(Uri photoUri) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700506 mPhotoView.setFullSizedPhoto(photoUri);
Walter Jang41b3ea12015-03-09 17:30:06 -0700507 }
508
Walter Jang31a74ad2015-10-02 19:17:39 -0700509 public void updatePhoto(Uri photoUri) {
510 // Even though high-res photos cannot be saved by passing them via
511 // an EntityDeltaList (since they cause the Bundle size limit to be
512 // exceeded), we still pass a low-res thumbnail. This simplifies
513 // code all over the place, because we don't have to test whether
514 // there is a change in EITHER the delta-list OR a changed photo...
515 // this way, there is always a change in the delta-list.
516 mPhotoValuesDelta.setFromTemplate(false);
517 mPhotoValuesDelta.setSuperPrimary(true);
518 try {
519 final byte[] bytes = EditorUiUtils.getCompressedThumbnailBitmapBytes(
520 getContext(), photoUri);
521 if (bytes != null) {
522 mPhotoValuesDelta.setPhoto(bytes);
523 }
524 } catch (FileNotFoundException e) {
525 elog("Failed to get bitmap from photo Uri");
526 }
527
528 mPhotoView.setFullSizedPhoto(photoUri);
529 }
530
Walter Jang41b3ea12015-03-09 17:30:06 -0700531 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800532 * Pass through to {@link CompactPhotoEditorView#isWritablePhotoSet}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800533 */
534 public boolean isWritablePhotoSet() {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700535 return mPhotoView.isWritablePhotoSet();
Walter Jang3efae4a2015-02-18 11:12:00 -0800536 }
537
538 /**
Walter Jang3efae4a2015-02-18 11:12:00 -0800539 * Get the raw contact ID for the CompactHeaderView photo.
540 */
Walter Jang3efae4a2015-02-18 11:12:00 -0800541 public long getPhotoRawContactId() {
542 return mPhotoRawContactId;
543 }
544
Walter Jang4f5594a2015-10-06 18:40:31 -0700545 public StructuredNameEditorView getPrimaryNameEditorView() {
546 return mPrimaryNameEditorView;
547 }
548
Walter Jang31a74ad2015-10-02 19:17:39 -0700549 /**
550 * Returns a data holder for every non-default/non-empty photo from each raw contact, whether
551 * the raw contact is writable or not.
552 */
553 public ArrayList<CompactPhotoSelectionFragment.Photo> getPhotos() {
554 final ArrayList<CompactPhotoSelectionFragment.Photo> photos = new ArrayList<>();
555
556 final List<KindSectionData> kindSectionDataList =
557 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
558 for (int i = 0; i < kindSectionDataList.size(); i++) {
559 final KindSectionData kindSectionData = kindSectionDataList.get(i);
560 final AccountType accountType = kindSectionData.getAccountType();
561 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
562 if (valuesDeltaList == null || valuesDeltaList.isEmpty()) continue;
563 for (int j = 0; j < valuesDeltaList.size(); j++) {
564 final ValuesDelta valuesDelta = valuesDeltaList.get(j);
565 final Bitmap bitmap = EditorUiUtils.getPhotoBitmap(valuesDelta);
566 if (bitmap == null) continue;
567
568 final CompactPhotoSelectionFragment.Photo photo =
569 new CompactPhotoSelectionFragment.Photo();
570 photo.titleRes = accountType.titleRes;
571 photo.iconRes = accountType.iconRes;
572 photo.syncAdapterPackageName = accountType.syncAdapterPackageName;
573 photo.valuesDelta = valuesDelta;
574 photo.primary = valuesDelta.isSuperPrimary();
575 photo.kindSectionDataListIndex = i;
576 photo.valuesDeltaListIndex = j;
577 photos.add(photo);
578 }
579 }
580
581 return photos;
582 }
583
584 /**
585 * Marks the raw contact photo given as primary for the aggregate contact and updates the
586 * UI.
587 */
588 public void setPrimaryPhoto(CompactPhotoSelectionFragment.Photo photo) {
589 // Unset primary for all other photos
590 final List<KindSectionData> kindSectionDataList =
591 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
592 for (KindSectionData kindSectionData : kindSectionDataList) {
593 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
594 for (ValuesDelta valuesDelta : valuesDeltaList) {
595 valuesDelta.setSuperPrimary(false);
596 }
597 }
598
599 // Find the values delta to mark as primary
600 if (photo.kindSectionDataListIndex < 0
601 || photo.kindSectionDataListIndex >= kindSectionDataList.size()) {
602 wlog("Invalid kind section data list index");
603 return;
604 }
605 final KindSectionData kindSectionData =
606 kindSectionDataList.get(photo.kindSectionDataListIndex);
607 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
608 if (photo.valuesDeltaListIndex >= valuesDeltaList.size()) {
609 wlog("Invalid values delta list index");
610 return;
611 }
612 final ValuesDelta valuesDelta = valuesDeltaList.get(photo.valuesDeltaListIndex);
613 valuesDelta.setFromTemplate(false);
614 valuesDelta.setSuperPrimary(true);
615
616 // Update the UI
617 mPhotoView.setPhoto(valuesDelta, mMaterialPalette);
618 }
619
Walter Jangd35e5ef2015-02-24 09:18:16 -0800620 public View getAggregationAnchorView() {
Walter Jangf10ca152015-09-22 15:23:55 -0700621 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
Walter Jang192a01c2015-09-22 15:23:55 -0700622 StructuredName.CONTENT_ITEM_TYPE);
Walter Jangf10ca152015-09-22 15:23:55 -0700623 if (!kindSectionViews.isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700624 return mKindSectionViews.getChildAt(0).findViewById(R.id.anchor_view);
Walter Jangd35e5ef2015-02-24 09:18:16 -0800625 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700626 return null;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800627 }
628
Walter Jangf10ca152015-09-22 15:23:55 -0700629 public void setGroupMetaData(Cursor groupMetaData) {
630 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
631 GroupMembership.CONTENT_ITEM_TYPE);
632 for (CompactKindSectionView kindSectionView : kindSectionViews) {
633 kindSectionView.setGroupMetaData(groupMetaData);
Walter Jangd6753152015-10-02 09:23:13 -0700634 if (mIsExpanded) {
635 kindSectionView.setHideWhenEmpty(false);
636 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
637 }
Walter Jangf10ca152015-09-22 15:23:55 -0700638 }
639 }
640
Walter Jangf46abd82015-02-20 16:52:04 -0800641 public void setState(RawContactDeltaList rawContactDeltas,
Walter Jang06f73a12015-06-17 11:15:48 -0700642 MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
Walter Jang4f5594a2015-10-06 18:40:31 -0700643 long photoId, String readOnlyDisplayName, boolean hasNewContact,
644 boolean isUserProfile, AccountWithDataSet primaryAccount) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700645 mKindSectionDataMap.clear();
Walter Jangf5dfea42015-09-16 12:30:36 -0700646 mKindSectionViews.removeAllViews();
Walter Jang363d3fd2015-09-16 10:29:07 -0700647 mMoreFields.setVisibility(View.VISIBLE);
Walter Jangcab3dce2015-02-09 17:48:03 -0800648
Walter Jangf46abd82015-02-20 16:52:04 -0800649 mMaterialPalette = materialPalette;
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700650 mViewIdGenerator = viewIdGenerator;
Walter Jang708ea9e2015-09-10 15:42:05 -0700651 mPhotoId = photoId;
Walter Jang4f5594a2015-10-06 18:40:31 -0700652 mReadOnlyDisplayName = readOnlyDisplayName;
Walter Jang708ea9e2015-09-10 15:42:05 -0700653 mHasNewContact = hasNewContact;
654 mIsUserProfile = isUserProfile;
655 mPrimaryAccount = primaryAccount;
656 if (mPrimaryAccount == null) {
657 mPrimaryAccount = ContactEditorUtils.getInstance(getContext()).getDefaultAccount();
658 }
659 vlog("state: primary " + mPrimaryAccount);
Walter Jangcab3dce2015-02-09 17:48:03 -0800660
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700661 // Parse the given raw contact deltas
662 if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
663 elog("No raw contact deltas");
Walter Jang79658e12015-09-24 10:36:26 -0700664 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jangf5dfea42015-09-16 12:30:36 -0700665 return;
666 }
Walter Jangcbd431d2015-10-06 13:07:30 -0700667 parseRawContactDeltas(rawContactDeltas);
Walter Jang79658e12015-09-24 10:36:26 -0700668 if (mKindSectionDataMap.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700669 elog("No kind section data parsed from RawContactDelta(s)");
Walter Jang79658e12015-09-24 10:36:26 -0700670 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700671 return;
672 }
Walter Jangcbd431d2015-10-06 13:07:30 -0700673 mPrimaryRawContactDelta = mKindSectionDataMap.get(StructuredName.CONTENT_ITEM_TYPE)
674 .getEntryToWrite(mPrimaryAccount, mHasNewContact).first.getRawContactDelta();
675 if (mPrimaryRawContactDelta != null) {
676 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
677 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
678 StructuredName.CONTENT_ITEM_TYPE);
679 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
680 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
681 Photo.CONTENT_ITEM_TYPE);
682 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700683
684 // Setup the view
Tingting Wang655ad1a2015-10-05 17:51:14 -0700685 addAccountInfo(rawContactDeltas);
Walter Jang363d3fd2015-09-16 10:29:07 -0700686 addPhotoView();
Walter Jangf5dfea42015-09-16 12:30:36 -0700687 addKindSectionViews();
Walter Jang4f5594a2015-10-06 18:40:31 -0700688 if (mHasNewContact) {
689 maybeCopyPrimaryDisplayName();
Walter Jangd6753152015-10-02 09:23:13 -0700690 }
Walter Jang4f5594a2015-10-06 18:40:31 -0700691 if (mIsExpanded) showAllFields();
Walter Jangd6753152015-10-02 09:23:13 -0700692
693 if (mListener != null) mListener.onEditorsBound();
Walter Jangcab3dce2015-02-09 17:48:03 -0800694 }
695
Walter Jangcbd431d2015-10-06 13:07:30 -0700696 private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700697 // Build the kind section data list map
Walter Jang192a01c2015-09-22 15:23:55 -0700698 vlog("parse: " + rawContactDeltas.size() + " rawContactDelta(s)");
699 for (int j = 0; j < rawContactDeltas.size(); j++) {
700 final RawContactDelta rawContactDelta = rawContactDeltas.get(j);
701 vlog("parse: " + j + " rawContactDelta" + rawContactDelta);
Walter Jang363d3fd2015-09-16 10:29:07 -0700702 if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
Walter Jangcab3dce2015-02-09 17:48:03 -0800703 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jang363d3fd2015-09-16 10:29:07 -0700704 if (accountType == null) continue;
705 final List<DataKind> dataKinds = accountType.getSortedDataKinds();
706 final int dataKindSize = dataKinds == null ? 0 : dataKinds.size();
707 vlog("parse: " + dataKindSize + " dataKinds(s)");
708 for (int i = 0; i < dataKindSize; i++) {
709 final DataKind dataKind = dataKinds.get(i);
Walter Jang192a01c2015-09-22 15:23:55 -0700710 if (dataKind == null || !dataKind.editable) {
711 vlog("parse: " + i + " " + dataKind.mimeType + " dropped read-only");
712 continue;
713 }
714 final String mimeType = dataKind.mimeType;
715
716 // Skip psuedo mime types
717 if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)
718 || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
719 vlog("parse: " + i + " " + dataKind.mimeType + " dropped pseudo type");
720 continue;
721 }
722
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700723 final List<KindSectionData> kindSectionDataList =
Walter Jangcbd431d2015-10-06 13:07:30 -0700724 getOrCreateKindSectionDataList(mimeType);
Walter Jang363d3fd2015-09-16 10:29:07 -0700725 final KindSectionData kindSectionData =
726 new KindSectionData(accountType, dataKind, rawContactDelta);
727 kindSectionDataList.add(kindSectionData);
Walter Jang192a01c2015-09-22 15:23:55 -0700728
Walter Jang79658e12015-09-24 10:36:26 -0700729 // Note we must create nickname entries
Walter Jang192a01c2015-09-22 15:23:55 -0700730 if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jang79658e12015-09-24 10:36:26 -0700731 && kindSectionData.getValuesDeltas().isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700732 RawContactModifier.insertChild(rawContactDelta, dataKind);
733 }
734
Walter Jang363d3fd2015-09-16 10:29:07 -0700735 vlog("parse: " + i + " " + dataKind.mimeType + " " +
Walter Jang192a01c2015-09-22 15:23:55 -0700736 kindSectionData.getValuesDeltas().size() + " value(s)");
Walter Jangac679af2015-06-01 12:17:06 -0700737 }
738 }
Walter Jang3efae4a2015-02-18 11:12:00 -0800739 }
740
Walter Jangcbd431d2015-10-06 13:07:30 -0700741 private List<KindSectionData> getOrCreateKindSectionDataList(String mimeType) {
Walter Jang192a01c2015-09-22 15:23:55 -0700742 // Put structured names and nicknames together
743 mimeType = Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
744 ? StructuredName.CONTENT_ITEM_TYPE : mimeType;
Walter Jangcbd431d2015-10-06 13:07:30 -0700745 KindSectionDataList kindSectionDataList = mKindSectionDataMap.get(mimeType);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700746 if (kindSectionDataList == null) {
Walter Jangcbd431d2015-10-06 13:07:30 -0700747 kindSectionDataList = new KindSectionDataList();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700748 mKindSectionDataMap.put(mimeType, kindSectionDataList);
749 }
750 return kindSectionDataList;
751 }
752
Tingting Wang655ad1a2015-10-05 17:51:14 -0700753 private void addAccountInfo(RawContactDeltaList rawContactDeltas) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700754 if (mPrimaryRawContactDelta == null) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700755 mAccountHeaderContainer.setVisibility(View.GONE);
756 mAccountSelectorContainer.setVisibility(View.GONE);
757 return;
758 }
759
760 // Get the account information for the primary raw contact delta
761 final Pair<String,String> accountInfo = EditorUiUtils.getAccountInfo(getContext(),
762 mIsUserProfile, mPrimaryRawContactDelta.getAccountName(),
763 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager));
764
765 // The account header and selector show the same information so both shouldn't be visible
766 // at the same time
767 final List<AccountWithDataSet> accounts =
768 AccountTypeManager.getInstance(getContext()).getAccounts(true);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700769 mRawContactContainer.setVisibility(View.GONE);
Walter Jang708ea9e2015-09-10 15:42:05 -0700770 if (mHasNewContact && !mIsUserProfile && accounts.size() > 1) {
771 mAccountHeaderContainer.setVisibility(View.GONE);
772 addAccountSelector(accountInfo);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700773 } else if (mHasNewContact && !mIsUserProfile) {
774 addAccountHeader(accountInfo);
775 mAccountSelectorContainer.setVisibility(View.GONE);
776 } else if (rawContactDeltas.size() > 1) {
777 mAccountHeaderContainer.setVisibility(View.GONE);
778 mAccountSelectorContainer.setVisibility(View.GONE);
779 addRawContactAccountSelector(rawContactDeltas);
Walter Jang708ea9e2015-09-10 15:42:05 -0700780 } else {
781 addAccountHeader(accountInfo);
782 mAccountSelectorContainer.setVisibility(View.GONE);
783 }
784 }
785
786 private void addAccountHeader(Pair<String,String> accountInfo) {
Walter Jang03cea2e2015-09-18 17:04:21 -0700787 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700788 // Hide this view so the other text view will be centered vertically
789 mAccountHeaderName.setVisibility(View.GONE);
790 } else {
791 mAccountHeaderName.setVisibility(View.VISIBLE);
792 mAccountHeaderName.setText(accountInfo.first);
793 }
794 mAccountHeaderType.setText(accountInfo.second);
795
796 mAccountHeaderContainer.setContentDescription(
797 EditorUiUtils.getAccountInfoContentDescription(
798 accountInfo.first, accountInfo.second));
799 }
800
801 private void addAccountSelector(Pair<String,String> accountInfo) {
802 mAccountSelectorContainer.setVisibility(View.VISIBLE);
803
Walter Jang03cea2e2015-09-18 17:04:21 -0700804 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700805 // Hide this view so the other text view will be centered vertically
806 mAccountSelectorName.setVisibility(View.GONE);
807 } else {
808 mAccountSelectorName.setVisibility(View.VISIBLE);
809 mAccountSelectorName.setText(accountInfo.first);
810 }
811 mAccountSelectorType.setText(accountInfo.second);
812
813 mAccountSelectorContainer.setContentDescription(
814 EditorUiUtils.getAccountInfoContentDescription(
815 accountInfo.first, accountInfo.second));
816
817 mAccountSelector.setOnClickListener(new View.OnClickListener() {
818 @Override
819 public void onClick(View v) {
820 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
821 final AccountsListAdapter adapter =
822 new AccountsListAdapter(getContext(),
823 AccountsListAdapter.AccountListFilter.ACCOUNTS_CONTACT_WRITABLE,
824 mPrimaryAccount);
825 popup.setWidth(mAccountSelectorContainer.getWidth());
826 popup.setAnchorView(mAccountSelectorContainer);
827 popup.setAdapter(adapter);
828 popup.setModal(true);
829 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
830 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
831 @Override
832 public void onItemClick(AdapterView<?> parent, View view, int position,
833 long id) {
834 UiClosables.closeQuietly(popup);
835 final AccountWithDataSet newAccount = adapter.getItem(position);
836 if (mListener != null && !mPrimaryAccount.equals(newAccount)) {
837 mListener.onRebindEditorsForNewContact(
838 mPrimaryRawContactDelta,
839 mPrimaryAccount,
840 newAccount);
841 }
842 }
843 });
844 popup.show();
845 }
846 });
847 }
848
Tingting Wang655ad1a2015-10-05 17:51:14 -0700849 private void addRawContactAccountSelector(final RawContactDeltaList rawContactDeltas) {
850 mRawContactContainer.setVisibility(View.VISIBLE);
851
852 final String accountsSummary = getRawContactsAccountsSummary(
853 getContext(), rawContactDeltas);
854 mRawContactSummary.setText(accountsSummary);
855 mRawContactContainer.setContentDescription(accountsSummary);
856 if (mPrimaryRawContactDelta != null) {
857 final AccountType primaryAccountType = mPrimaryRawContactDelta.getRawContactAccountType(
858 getContext());
859 mPrimaryAccountIcon.setImageDrawable(primaryAccountType.getDisplayIcon(getContext()));
860 }
861
862 mRawContactContainer.setOnClickListener(new View.OnClickListener() {
863 @Override
864 public void onClick(View v) {
865 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
866 final RawContactAccountListAdapter adapter =
867 new RawContactAccountListAdapter(getContext(), rawContactDeltas);
868 popup.setWidth(mRawContactContainer.getWidth());
869 popup.setAnchorView(mRawContactContainer);
870 popup.setAdapter(adapter);
871 popup.setModal(true);
872 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
873 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
874 @Override
875 public void onItemClick(AdapterView<?> parent, View view, int position,
876 long id) {
877 UiClosables.closeQuietly(popup);
878 final RawContactDelta rawContactDelta = adapter.getItem(position);
879 final long rawContactId = adapter.getItemId(position);
880 final Uri rawContactUri = ContentUris.withAppendedId(
881 ContactsContract.RawContacts.CONTENT_URI, rawContactId);
882 // Start new activity for the raw contact in selected account.
883 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
884 intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
885
886 ArrayList<ContentValues> values = rawContactDelta.getContentValues();
887 intent.putExtra(ContactsContract.Intents.Insert.DATA, values);
888
889 if (mListener != null) {
890 mListener.onRawContactSelected(rawContactUri, rawContactId);
891 }
892 }
893 });
894 popup.show();
895 }
896 });
897 }
898
899 private static String getRawContactsAccountsSummary(
900 Context context, RawContactDeltaList rawContactDeltas) {
901 final Map<String, Integer> accountTypeNumber = new HashMap<>();
902 for (RawContactDelta rawContactDelta : rawContactDeltas) {
903 if (rawContactDelta.isVisible()) {
904 final AccountType accountType = rawContactDelta.getRawContactAccountType(context);
905 final String accountTypeLabel = accountType.getDisplayLabel(context).toString();
906 if (accountTypeNumber.containsKey(accountTypeLabel)) {
907 int number = accountTypeNumber.get(accountTypeLabel);
908 number++;
909 accountTypeNumber.put(accountTypeLabel, number);
910 } else {
911 accountTypeNumber.put(accountTypeLabel, 1);
912 }
913 }
914 }
915
916 final Set<String> linkedAccounts = new HashSet<>();
917 for (String accountTypeLabel : accountTypeNumber.keySet()) {
918 final String number = context.getResources().getQuantityString(
919 R.plurals.quickcontact_suggestion_account_type_number,
920 accountTypeNumber.get(accountTypeLabel),
921 accountTypeNumber.get(accountTypeLabel));
922 final String accountWithNumber = context.getResources().getString(
923 R.string.quickcontact_suggestion_account_type,
924 accountTypeLabel,
925 number);
926 linkedAccounts.add(accountWithNumber);
927 }
928 return TextUtils.join(",", linkedAccounts);
929 }
930
Walter Jang363d3fd2015-09-16 10:29:07 -0700931 private void addPhotoView() {
Walter Jang31a74ad2015-10-02 19:17:39 -0700932 // Get the kind section data and values delta that we will display in the photo view
Walter Jangcbd431d2015-10-06 13:07:30 -0700933 final KindSectionDataList kindSectionDataList =
934 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
935 final Pair<KindSectionData,ValuesDelta> photoToDisplay =
936 kindSectionDataList.getEntryToDisplay(mPhotoId);
937 if (photoToDisplay == null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700938 wlog("photo: no kind section data parsed");
Walter Jangcbd431d2015-10-06 13:07:30 -0700939 mPhotoView.setVisibility(View.GONE);
Walter Jang363d3fd2015-09-16 10:29:07 -0700940 return;
941 }
Walter Jang06f73a12015-06-17 11:15:48 -0700942
Walter Jang31a74ad2015-10-02 19:17:39 -0700943 // Set the photo view
Walter Jangcbd431d2015-10-06 13:07:30 -0700944 mPhotoView.setPhoto(photoToDisplay.second, mMaterialPalette);
Walter Jang31a74ad2015-10-02 19:17:39 -0700945
946 // Find the raw contact ID and values delta that will be written when the photo is edited
Walter Jangcbd431d2015-10-06 13:07:30 -0700947 final Pair<KindSectionData,ValuesDelta> photoToWrite = kindSectionDataList.getEntryToWrite(
948 mPrimaryAccount, mHasNewContact);
949 if (photoToWrite == null) {
Walter Jang31a74ad2015-10-02 19:17:39 -0700950 mPhotoView.setReadOnly(true);
951 return;
952 }
Walter Jang31a74ad2015-10-02 19:17:39 -0700953 mPhotoView.setReadOnly(false);
Walter Jangcbd431d2015-10-06 13:07:30 -0700954 mPhotoRawContactId = photoToWrite.first.getRawContactDelta().getRawContactId();
955 mPhotoValuesDelta = photoToWrite.second;
Walter Jang31a74ad2015-10-02 19:17:39 -0700956 }
957
Walter Jangf5dfea42015-09-16 12:30:36 -0700958 private void addKindSectionViews() {
959 // Sort the kinds
Walter Jangcbd431d2015-10-06 13:07:30 -0700960 final TreeSet<Map.Entry<String,KindSectionDataList>> entries =
Walter Jang192a01c2015-09-22 15:23:55 -0700961 new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
Walter Jangf5dfea42015-09-16 12:30:36 -0700962 entries.addAll(mKindSectionDataMap.entrySet());
Walter Jang3efae4a2015-02-18 11:12:00 -0800963
Walter Jangf5dfea42015-09-16 12:30:36 -0700964 vlog("kind: " + entries.size() + " kindSection(s)");
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700965 int i = -1;
Walter Jangcbd431d2015-10-06 13:07:30 -0700966 for (Map.Entry<String, KindSectionDataList> entry : entries) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700967 i++;
968
Walter Jangf5dfea42015-09-16 12:30:36 -0700969 final String mimeType = entry.getKey();
Walter Jangcbd431d2015-10-06 13:07:30 -0700970 final KindSectionDataList kindSectionDataList = entry.getValue();
Walter Jangab50e6f2015-06-15 08:57:22 -0700971
Walter Jangf5dfea42015-09-16 12:30:36 -0700972 // Ignore mime types that we've already handled
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700973 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
974 vlog("kind: " + i + " " + mimeType + " dropped");
975 continue;
976 }
Walter Jangf5dfea42015-09-16 12:30:36 -0700977
Walter Jang192a01c2015-09-22 15:23:55 -0700978 if (kindSectionDataList != null && !kindSectionDataList.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700979 vlog("kind: " + i + " " + mimeType + ": " + kindSectionDataList.size() +
980 " kindSectionData(s)");
981
Walter Jangf5dfea42015-09-16 12:30:36 -0700982 final CompactKindSectionView kindSectionView = inflateKindSectionView(
983 mKindSectionViews, kindSectionDataList, mimeType);
Walter Jangf5dfea42015-09-16 12:30:36 -0700984 mKindSectionViews.addView(kindSectionView);
Walter Jangf10ca152015-09-22 15:23:55 -0700985
986 // Keep a pointer to all the KindSectionsViews for each mimeType
987 getKindSectionViews(mimeType).add(kindSectionView);
Walter Jangcab3dce2015-02-09 17:48:03 -0800988 }
989 }
990 }
991
Walter Jangf10ca152015-09-22 15:23:55 -0700992 private List<CompactKindSectionView> getKindSectionViews(String mimeType) {
993 List<CompactKindSectionView> kindSectionViews = mKindSectionViewsMap.get(mimeType);
994 if (kindSectionViews == null) {
995 kindSectionViews = new ArrayList<>();
996 mKindSectionViewsMap.put(mimeType, kindSectionViews);
997 }
998 return kindSectionViews;
999 }
1000
Walter Jangf5dfea42015-09-16 12:30:36 -07001001 private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
Walter Jangcbd431d2015-10-06 13:07:30 -07001002 KindSectionDataList kindSectionDataList, String mimeType) {
Walter Jangf5dfea42015-09-16 12:30:36 -07001003 final CompactKindSectionView kindSectionView = (CompactKindSectionView)
1004 mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
1005 /* attachToRoot =*/ false);
1006
Walter Jang192a01c2015-09-22 15:23:55 -07001007 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jangf5dfea42015-09-16 12:30:36 -07001008 || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang192a01c2015-09-22 15:23:55 -07001009 // Phone numbers and email addresses are always displayed,
1010 // even if they are empty
Walter Jangf5dfea42015-09-16 12:30:36 -07001011 kindSectionView.setHideWhenEmpty(false);
1012 }
Walter Jangf5dfea42015-09-16 12:30:36 -07001013
Walter Jang192a01c2015-09-22 15:23:55 -07001014 // Since phone numbers and email addresses displayed even if they are empty,
1015 // they will be the only types you add new values to initially for new contacts
1016 kindSectionView.setShowOneEmptyEditor(true);
1017
1018 // Sort so the editors wind up in the order we want
1019 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
1020 Collections.sort(kindSectionDataList, new NameEditorComparator(getContext(),
1021 mPrimaryRawContactDelta));
1022 } else {
1023 Collections.sort(kindSectionDataList, new EditorComparator(getContext()));
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001024 }
1025
Walter Jang79658e12015-09-24 10:36:26 -07001026 kindSectionView.setState(kindSectionDataList, mViewIdGenerator, mListener);
Walter Jangf5dfea42015-09-16 12:30:36 -07001027
1028 return kindSectionView;
Walter Jangcab3dce2015-02-09 17:48:03 -08001029 }
1030
Walter Jang4f5594a2015-10-06 18:40:31 -07001031 private void maybeCopyPrimaryDisplayName() {
1032 if (TextUtils.isEmpty(mReadOnlyDisplayName)) return;
1033 final List<CompactKindSectionView> kindSectionViews
1034 = mKindSectionViewsMap.get(StructuredName.CONTENT_ITEM_TYPE);
1035 if (kindSectionViews.isEmpty()) return;
1036 final CompactKindSectionView primaryNameKindSectionView = kindSectionViews.get(0);
1037 if (primaryNameKindSectionView.isEmptyName()) {
1038 vlog("name: using read only display name as primary name");
1039 primaryNameKindSectionView.setName(mReadOnlyDisplayName);
1040 mPrimaryNameEditorView = primaryNameKindSectionView.getPrimaryNameEditorView();
1041 }
1042 }
1043
Walter Jangd6753152015-10-02 09:23:13 -07001044 private void showAllFields() {
Walter Jang79658e12015-09-24 10:36:26 -07001045 // Stop hiding empty editors and allow the user to enter values for all kinds now
1046 for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
1047 final CompactKindSectionView kindSectionView =
1048 (CompactKindSectionView) mKindSectionViews.getChildAt(i);
1049 kindSectionView.setHideWhenEmpty(false);
1050 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
1051 }
1052 mIsExpanded = true;
Walter Jang79658e12015-09-24 10:36:26 -07001053
Walter Jangf5dfea42015-09-16 12:30:36 -07001054 // Hide the more fields button
1055 mMoreFields.setVisibility(View.GONE);
Walter Jangcab3dce2015-02-09 17:48:03 -08001056 }
1057
Walter Jangbf63a6d2015-05-05 09:14:35 -07001058 private static void vlog(String message) {
1059 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1060 Log.v(TAG, message);
Walter Jangcab3dce2015-02-09 17:48:03 -08001061 }
1062 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001063
1064 private static void wlog(String message) {
1065 if (Log.isLoggable(TAG, Log.WARN)) {
1066 Log.w(TAG, message);
1067 }
1068 }
1069
1070 private static void elog(String message) {
1071 Log.e(TAG, message);
1072 }
Walter Jangcab3dce2015-02-09 17:48:03 -08001073}