blob: 40574da31330517a496c7f2ee50249011506bf49 [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
87 private static final String TAG = "CompactEditorView";
88
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
290 private RawContactDeltaComparator mRawContactDeltaComparator;
291 private MimeTypeComparator mMimeTypeComparator;
292 private RawContactDelta mPrimaryRawContactDelta;
293
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 Jang708ea9e2015-09-10 15:42:05 -0700371 private boolean mHasNewContact;
372 private boolean mIsUserProfile;
373 private AccountWithDataSet mPrimaryAccount;
374 private RawContactDelta mPrimaryRawContactDelta;
Walter Jangcbd431d2015-10-06 13:07:30 -0700375 private Map<String,KindSectionDataList> mKindSectionDataMap = new HashMap<>();
Walter Jangcab3dce2015-02-09 17:48:03 -0800376
Walter Jang708ea9e2015-09-10 15:42:05 -0700377 // Account header
378 private View mAccountHeaderContainer;
379 private TextView mAccountHeaderType;
380 private TextView mAccountHeaderName;
381
382 // Account selector
383 private View mAccountSelectorContainer;
384 private View mAccountSelector;
385 private TextView mAccountSelectorType;
386 private TextView mAccountSelectorName;
387
Tingting Wang655ad1a2015-10-05 17:51:14 -0700388 // Raw contacts selector
389 private View mRawContactContainer;
390 private TextView mRawContactSummary;
391 private ImageView mPrimaryAccountIcon;
392
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700393 private CompactPhotoEditorView mPhotoView;
Walter Jangf5dfea42015-09-16 12:30:36 -0700394 private ViewGroup mKindSectionViews;
Walter Jangf10ca152015-09-22 15:23:55 -0700395 private Map<String,List<CompactKindSectionView>> mKindSectionViewsMap = new HashMap<>();
Walter Jangb6ca2722015-02-20 11:10:25 -0800396 private View mMoreFields;
Walter Jangcab3dce2015-02-09 17:48:03 -0800397
Walter Jang79658e12015-09-24 10:36:26 -0700398 private boolean mIsExpanded;
Walter Jangcbd431d2015-10-06 13:07:30 -0700399
Walter Jang3efae4a2015-02-18 11:12:00 -0800400 private long mPhotoRawContactId;
Walter Jang31a74ad2015-10-02 19:17:39 -0700401 private ValuesDelta mPhotoValuesDelta;
Walter Jang3efae4a2015-02-18 11:12:00 -0800402
Walter Jangcab3dce2015-02-09 17:48:03 -0800403 public CompactRawContactsEditorView(Context context) {
404 super(context);
405 }
406
407 public CompactRawContactsEditorView(Context context, AttributeSet attrs) {
408 super(context, attrs);
409 }
410
Walter Jangb6ca2722015-02-20 11:10:25 -0800411 /**
412 * Sets the receiver for {@link CompactRawContactsEditorView} callbacks.
413 */
414 public void setListener(Listener listener) {
415 mListener = listener;
416 }
417
Walter Jangcab3dce2015-02-09 17:48:03 -0800418 @Override
419 protected void onFinishInflate() {
420 super.onFinishInflate();
421
422 mAccountTypeManager = AccountTypeManager.getInstance(getContext());
423 mLayoutInflater = (LayoutInflater)
424 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
425
Walter Jang708ea9e2015-09-10 15:42:05 -0700426 // Account header
427 mAccountHeaderContainer = findViewById(R.id.account_container);
428 mAccountHeaderType = (TextView) findViewById(R.id.account_type);
429 mAccountHeaderName = (TextView) findViewById(R.id.account_name);
430
431 // Account selector
432 mAccountSelectorContainer = findViewById(R.id.account_selector_container);
433 mAccountSelector = findViewById(R.id.account);
434 mAccountSelectorType = (TextView) findViewById(R.id.account_type_selector);
435 mAccountSelectorName = (TextView) findViewById(R.id.account_name_selector);
436
Tingting Wang655ad1a2015-10-05 17:51:14 -0700437 // Raw contacts selector
438 mRawContactContainer = findViewById(R.id.all_rawcontacts_accounts_container);
439 mRawContactSummary = (TextView) findViewById(R.id.rawcontacts_accounts_summary);
440 mPrimaryAccountIcon = (ImageView) findViewById(R.id.primary_account_icon);
441
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700442 mPhotoView = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
Walter Jangf5dfea42015-09-16 12:30:36 -0700443 mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
Walter Jangb6ca2722015-02-20 11:10:25 -0800444 mMoreFields = findViewById(R.id.more_fields);
445 mMoreFields.setOnClickListener(this);
446 }
447
Walter Jangb6ca2722015-02-20 11:10:25 -0800448 @Override
449 public void onClick(View view) {
Walter Jangf5dfea42015-09-16 12:30:36 -0700450 if (view.getId() == R.id.more_fields) {
Walter Jangd6753152015-10-02 09:23:13 -0700451 showAllFields();
Walter Jangb6ca2722015-02-20 11:10:25 -0800452 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800453 }
454
455 @Override
456 public void setEnabled(boolean enabled) {
457 super.setEnabled(enabled);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700458 final int childCount = mKindSectionViews.getChildCount();
459 for (int i = 0; i < childCount; i++) {
460 mKindSectionViews.getChildAt(i).setEnabled(enabled);
Walter Jangcab3dce2015-02-09 17:48:03 -0800461 }
462 }
463
Walter Jang79658e12015-09-24 10:36:26 -0700464 @Override
465 public Parcelable onSaveInstanceState() {
466 final Parcelable superState = super.onSaveInstanceState();
467 final SavedState savedState = new SavedState(superState);
468 savedState.mIsExpanded = mIsExpanded;
469 return savedState;
470 }
471
472 @Override
473 public void onRestoreInstanceState(Parcelable state) {
474 if(!(state instanceof SavedState)) {
475 super.onRestoreInstanceState(state);
476 return;
477 }
478 final SavedState savedState = (SavedState) state;
479 super.onRestoreInstanceState(savedState.getSuperState());
480 mIsExpanded = savedState.mIsExpanded;
Walter Jangd6753152015-10-02 09:23:13 -0700481 if (mIsExpanded) {
482 showAllFields();
Walter Jang79658e12015-09-24 10:36:26 -0700483 }
484 }
485
Walter Jang3efae4a2015-02-18 11:12:00 -0800486 /**
Walter Jang31a74ad2015-10-02 19:17:39 -0700487 * Pass through to {@link CompactPhotoEditorView#setListener}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800488 */
Walter Jang31a74ad2015-10-02 19:17:39 -0700489 public void setPhotoListener(CompactPhotoEditorView.Listener listener) {
490 mPhotoView.setListener(listener);
Walter Jang3efae4a2015-02-18 11:12:00 -0800491 }
492
Walter Jang31a74ad2015-10-02 19:17:39 -0700493 public void removePhoto() {
494 mPhotoValuesDelta.setFromTemplate(false);
495 mPhotoValuesDelta.put(Photo.PHOTO, (byte[]) null);
496
497 mPhotoView.removePhoto();
Walter Jang3efae4a2015-02-18 11:12:00 -0800498 }
499
500 /**
Walter Jang41b3ea12015-03-09 17:30:06 -0700501 * Pass through to {@link CompactPhotoEditorView#setFullSizedPhoto(Uri)}.
502 */
503 public void setFullSizePhoto(Uri photoUri) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700504 mPhotoView.setFullSizedPhoto(photoUri);
Walter Jang41b3ea12015-03-09 17:30:06 -0700505 }
506
Walter Jang31a74ad2015-10-02 19:17:39 -0700507 public void updatePhoto(Uri photoUri) {
508 // Even though high-res photos cannot be saved by passing them via
509 // an EntityDeltaList (since they cause the Bundle size limit to be
510 // exceeded), we still pass a low-res thumbnail. This simplifies
511 // code all over the place, because we don't have to test whether
512 // there is a change in EITHER the delta-list OR a changed photo...
513 // this way, there is always a change in the delta-list.
514 mPhotoValuesDelta.setFromTemplate(false);
515 mPhotoValuesDelta.setSuperPrimary(true);
516 try {
517 final byte[] bytes = EditorUiUtils.getCompressedThumbnailBitmapBytes(
518 getContext(), photoUri);
519 if (bytes != null) {
520 mPhotoValuesDelta.setPhoto(bytes);
521 }
522 } catch (FileNotFoundException e) {
523 elog("Failed to get bitmap from photo Uri");
524 }
525
526 mPhotoView.setFullSizedPhoto(photoUri);
527 }
528
Walter Jang41b3ea12015-03-09 17:30:06 -0700529 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800530 * Pass through to {@link CompactPhotoEditorView#isWritablePhotoSet}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800531 */
532 public boolean isWritablePhotoSet() {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700533 return mPhotoView.isWritablePhotoSet();
Walter Jang3efae4a2015-02-18 11:12:00 -0800534 }
535
536 /**
Walter Jang3efae4a2015-02-18 11:12:00 -0800537 * Get the raw contact ID for the CompactHeaderView photo.
538 */
Walter Jang3efae4a2015-02-18 11:12:00 -0800539 public long getPhotoRawContactId() {
540 return mPhotoRawContactId;
541 }
542
Walter Jang31a74ad2015-10-02 19:17:39 -0700543 /**
544 * Returns a data holder for every non-default/non-empty photo from each raw contact, whether
545 * the raw contact is writable or not.
546 */
547 public ArrayList<CompactPhotoSelectionFragment.Photo> getPhotos() {
548 final ArrayList<CompactPhotoSelectionFragment.Photo> photos = new ArrayList<>();
549
550 final List<KindSectionData> kindSectionDataList =
551 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
552 for (int i = 0; i < kindSectionDataList.size(); i++) {
553 final KindSectionData kindSectionData = kindSectionDataList.get(i);
554 final AccountType accountType = kindSectionData.getAccountType();
555 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
556 if (valuesDeltaList == null || valuesDeltaList.isEmpty()) continue;
557 for (int j = 0; j < valuesDeltaList.size(); j++) {
558 final ValuesDelta valuesDelta = valuesDeltaList.get(j);
559 final Bitmap bitmap = EditorUiUtils.getPhotoBitmap(valuesDelta);
560 if (bitmap == null) continue;
561
562 final CompactPhotoSelectionFragment.Photo photo =
563 new CompactPhotoSelectionFragment.Photo();
564 photo.titleRes = accountType.titleRes;
565 photo.iconRes = accountType.iconRes;
566 photo.syncAdapterPackageName = accountType.syncAdapterPackageName;
567 photo.valuesDelta = valuesDelta;
568 photo.primary = valuesDelta.isSuperPrimary();
569 photo.kindSectionDataListIndex = i;
570 photo.valuesDeltaListIndex = j;
571 photos.add(photo);
572 }
573 }
574
575 return photos;
576 }
577
578 /**
579 * Marks the raw contact photo given as primary for the aggregate contact and updates the
580 * UI.
581 */
582 public void setPrimaryPhoto(CompactPhotoSelectionFragment.Photo photo) {
583 // Unset primary for all other photos
584 final List<KindSectionData> kindSectionDataList =
585 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
586 for (KindSectionData kindSectionData : kindSectionDataList) {
587 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
588 for (ValuesDelta valuesDelta : valuesDeltaList) {
589 valuesDelta.setSuperPrimary(false);
590 }
591 }
592
593 // Find the values delta to mark as primary
594 if (photo.kindSectionDataListIndex < 0
595 || photo.kindSectionDataListIndex >= kindSectionDataList.size()) {
596 wlog("Invalid kind section data list index");
597 return;
598 }
599 final KindSectionData kindSectionData =
600 kindSectionDataList.get(photo.kindSectionDataListIndex);
601 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
602 if (photo.valuesDeltaListIndex >= valuesDeltaList.size()) {
603 wlog("Invalid values delta list index");
604 return;
605 }
606 final ValuesDelta valuesDelta = valuesDeltaList.get(photo.valuesDeltaListIndex);
607 valuesDelta.setFromTemplate(false);
608 valuesDelta.setSuperPrimary(true);
609
610 // Update the UI
611 mPhotoView.setPhoto(valuesDelta, mMaterialPalette);
612 }
613
Walter Jangd35e5ef2015-02-24 09:18:16 -0800614 public View getAggregationAnchorView() {
Walter Jangf10ca152015-09-22 15:23:55 -0700615 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
Walter Jang192a01c2015-09-22 15:23:55 -0700616 StructuredName.CONTENT_ITEM_TYPE);
Walter Jangf10ca152015-09-22 15:23:55 -0700617 if (!kindSectionViews.isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700618 return mKindSectionViews.getChildAt(0).findViewById(R.id.anchor_view);
Walter Jangd35e5ef2015-02-24 09:18:16 -0800619 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700620 return null;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800621 }
622
Walter Jangf10ca152015-09-22 15:23:55 -0700623 public void setGroupMetaData(Cursor groupMetaData) {
624 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
625 GroupMembership.CONTENT_ITEM_TYPE);
626 for (CompactKindSectionView kindSectionView : kindSectionViews) {
627 kindSectionView.setGroupMetaData(groupMetaData);
Walter Jangd6753152015-10-02 09:23:13 -0700628 if (mIsExpanded) {
629 kindSectionView.setHideWhenEmpty(false);
630 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
631 }
Walter Jangf10ca152015-09-22 15:23:55 -0700632 }
633 }
634
Walter Jangf46abd82015-02-20 16:52:04 -0800635 public void setState(RawContactDeltaList rawContactDeltas,
Walter Jang06f73a12015-06-17 11:15:48 -0700636 MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700637 long photoId, boolean hasNewContact, boolean isUserProfile,
638 AccountWithDataSet primaryAccount) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700639 mKindSectionDataMap.clear();
Walter Jangf5dfea42015-09-16 12:30:36 -0700640 mKindSectionViews.removeAllViews();
Walter Jang363d3fd2015-09-16 10:29:07 -0700641 mMoreFields.setVisibility(View.VISIBLE);
Walter Jangcab3dce2015-02-09 17:48:03 -0800642
Walter Jangf46abd82015-02-20 16:52:04 -0800643 mMaterialPalette = materialPalette;
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700644 mViewIdGenerator = viewIdGenerator;
Walter Jang708ea9e2015-09-10 15:42:05 -0700645 mPhotoId = photoId;
Walter Jang708ea9e2015-09-10 15:42:05 -0700646 mHasNewContact = hasNewContact;
647 mIsUserProfile = isUserProfile;
648 mPrimaryAccount = primaryAccount;
649 if (mPrimaryAccount == null) {
650 mPrimaryAccount = ContactEditorUtils.getInstance(getContext()).getDefaultAccount();
651 }
652 vlog("state: primary " + mPrimaryAccount);
Walter Jangcab3dce2015-02-09 17:48:03 -0800653
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700654 // Parse the given raw contact deltas
655 if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
656 elog("No raw contact deltas");
Walter Jang79658e12015-09-24 10:36:26 -0700657 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jangf5dfea42015-09-16 12:30:36 -0700658 return;
659 }
Walter Jangcbd431d2015-10-06 13:07:30 -0700660 parseRawContactDeltas(rawContactDeltas);
Walter Jang79658e12015-09-24 10:36:26 -0700661 if (mKindSectionDataMap.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700662 elog("No kind section data parsed from RawContactDelta(s)");
Walter Jang79658e12015-09-24 10:36:26 -0700663 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700664 return;
665 }
Walter Jangcbd431d2015-10-06 13:07:30 -0700666 mPrimaryRawContactDelta = mKindSectionDataMap.get(StructuredName.CONTENT_ITEM_TYPE)
667 .getEntryToWrite(mPrimaryAccount, mHasNewContact).first.getRawContactDelta();
668 if (mPrimaryRawContactDelta != null) {
669 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
670 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
671 StructuredName.CONTENT_ITEM_TYPE);
672 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
673 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
674 Photo.CONTENT_ITEM_TYPE);
675 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700676
677 // Setup the view
Tingting Wang655ad1a2015-10-05 17:51:14 -0700678 addAccountInfo(rawContactDeltas);
Walter Jang363d3fd2015-09-16 10:29:07 -0700679 addPhotoView();
Walter Jangf5dfea42015-09-16 12:30:36 -0700680 addKindSectionViews();
Walter Jang79658e12015-09-24 10:36:26 -0700681
Walter Jangd6753152015-10-02 09:23:13 -0700682 if (mIsExpanded) {
683 showAllFields();
684 }
685
686 if (mListener != null) mListener.onEditorsBound();
Walter Jangcab3dce2015-02-09 17:48:03 -0800687 }
688
Walter Jangcbd431d2015-10-06 13:07:30 -0700689 private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700690 // Build the kind section data list map
Walter Jang192a01c2015-09-22 15:23:55 -0700691 vlog("parse: " + rawContactDeltas.size() + " rawContactDelta(s)");
692 for (int j = 0; j < rawContactDeltas.size(); j++) {
693 final RawContactDelta rawContactDelta = rawContactDeltas.get(j);
694 vlog("parse: " + j + " rawContactDelta" + rawContactDelta);
Walter Jang363d3fd2015-09-16 10:29:07 -0700695 if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
Walter Jangcab3dce2015-02-09 17:48:03 -0800696 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jang363d3fd2015-09-16 10:29:07 -0700697 if (accountType == null) continue;
698 final List<DataKind> dataKinds = accountType.getSortedDataKinds();
699 final int dataKindSize = dataKinds == null ? 0 : dataKinds.size();
700 vlog("parse: " + dataKindSize + " dataKinds(s)");
701 for (int i = 0; i < dataKindSize; i++) {
702 final DataKind dataKind = dataKinds.get(i);
Walter Jang192a01c2015-09-22 15:23:55 -0700703 if (dataKind == null || !dataKind.editable) {
704 vlog("parse: " + i + " " + dataKind.mimeType + " dropped read-only");
705 continue;
706 }
707 final String mimeType = dataKind.mimeType;
708
709 // Skip psuedo mime types
710 if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)
711 || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
712 vlog("parse: " + i + " " + dataKind.mimeType + " dropped pseudo type");
713 continue;
714 }
715
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700716 final List<KindSectionData> kindSectionDataList =
Walter Jangcbd431d2015-10-06 13:07:30 -0700717 getOrCreateKindSectionDataList(mimeType);
Walter Jang363d3fd2015-09-16 10:29:07 -0700718 final KindSectionData kindSectionData =
719 new KindSectionData(accountType, dataKind, rawContactDelta);
720 kindSectionDataList.add(kindSectionData);
Walter Jang192a01c2015-09-22 15:23:55 -0700721
Walter Jang79658e12015-09-24 10:36:26 -0700722 // Note we must create nickname entries
Walter Jang192a01c2015-09-22 15:23:55 -0700723 if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jang79658e12015-09-24 10:36:26 -0700724 && kindSectionData.getValuesDeltas().isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700725 RawContactModifier.insertChild(rawContactDelta, dataKind);
726 }
727
Walter Jang363d3fd2015-09-16 10:29:07 -0700728 vlog("parse: " + i + " " + dataKind.mimeType + " " +
Walter Jang192a01c2015-09-22 15:23:55 -0700729 kindSectionData.getValuesDeltas().size() + " value(s)");
Walter Jangac679af2015-06-01 12:17:06 -0700730 }
731 }
Walter Jang3efae4a2015-02-18 11:12:00 -0800732 }
733
Walter Jangcbd431d2015-10-06 13:07:30 -0700734 private List<KindSectionData> getOrCreateKindSectionDataList(String mimeType) {
Walter Jang192a01c2015-09-22 15:23:55 -0700735 // Put structured names and nicknames together
736 mimeType = Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
737 ? StructuredName.CONTENT_ITEM_TYPE : mimeType;
Walter Jangcbd431d2015-10-06 13:07:30 -0700738 KindSectionDataList kindSectionDataList = mKindSectionDataMap.get(mimeType);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700739 if (kindSectionDataList == null) {
Walter Jangcbd431d2015-10-06 13:07:30 -0700740 kindSectionDataList = new KindSectionDataList();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700741 mKindSectionDataMap.put(mimeType, kindSectionDataList);
742 }
743 return kindSectionDataList;
744 }
745
Tingting Wang655ad1a2015-10-05 17:51:14 -0700746 private void addAccountInfo(RawContactDeltaList rawContactDeltas) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700747 if (mPrimaryRawContactDelta == null) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700748 mAccountHeaderContainer.setVisibility(View.GONE);
749 mAccountSelectorContainer.setVisibility(View.GONE);
750 return;
751 }
752
753 // Get the account information for the primary raw contact delta
754 final Pair<String,String> accountInfo = EditorUiUtils.getAccountInfo(getContext(),
755 mIsUserProfile, mPrimaryRawContactDelta.getAccountName(),
756 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager));
757
758 // The account header and selector show the same information so both shouldn't be visible
759 // at the same time
760 final List<AccountWithDataSet> accounts =
761 AccountTypeManager.getInstance(getContext()).getAccounts(true);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700762 mRawContactContainer.setVisibility(View.GONE);
Walter Jang708ea9e2015-09-10 15:42:05 -0700763 if (mHasNewContact && !mIsUserProfile && accounts.size() > 1) {
764 mAccountHeaderContainer.setVisibility(View.GONE);
765 addAccountSelector(accountInfo);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700766 } else if (mHasNewContact && !mIsUserProfile) {
767 addAccountHeader(accountInfo);
768 mAccountSelectorContainer.setVisibility(View.GONE);
769 } else if (rawContactDeltas.size() > 1) {
770 mAccountHeaderContainer.setVisibility(View.GONE);
771 mAccountSelectorContainer.setVisibility(View.GONE);
772 addRawContactAccountSelector(rawContactDeltas);
Walter Jang708ea9e2015-09-10 15:42:05 -0700773 } else {
774 addAccountHeader(accountInfo);
775 mAccountSelectorContainer.setVisibility(View.GONE);
776 }
777 }
778
779 private void addAccountHeader(Pair<String,String> accountInfo) {
Walter Jang03cea2e2015-09-18 17:04:21 -0700780 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700781 // Hide this view so the other text view will be centered vertically
782 mAccountHeaderName.setVisibility(View.GONE);
783 } else {
784 mAccountHeaderName.setVisibility(View.VISIBLE);
785 mAccountHeaderName.setText(accountInfo.first);
786 }
787 mAccountHeaderType.setText(accountInfo.second);
788
789 mAccountHeaderContainer.setContentDescription(
790 EditorUiUtils.getAccountInfoContentDescription(
791 accountInfo.first, accountInfo.second));
792 }
793
794 private void addAccountSelector(Pair<String,String> accountInfo) {
795 mAccountSelectorContainer.setVisibility(View.VISIBLE);
796
Walter Jang03cea2e2015-09-18 17:04:21 -0700797 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700798 // Hide this view so the other text view will be centered vertically
799 mAccountSelectorName.setVisibility(View.GONE);
800 } else {
801 mAccountSelectorName.setVisibility(View.VISIBLE);
802 mAccountSelectorName.setText(accountInfo.first);
803 }
804 mAccountSelectorType.setText(accountInfo.second);
805
806 mAccountSelectorContainer.setContentDescription(
807 EditorUiUtils.getAccountInfoContentDescription(
808 accountInfo.first, accountInfo.second));
809
810 mAccountSelector.setOnClickListener(new View.OnClickListener() {
811 @Override
812 public void onClick(View v) {
813 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
814 final AccountsListAdapter adapter =
815 new AccountsListAdapter(getContext(),
816 AccountsListAdapter.AccountListFilter.ACCOUNTS_CONTACT_WRITABLE,
817 mPrimaryAccount);
818 popup.setWidth(mAccountSelectorContainer.getWidth());
819 popup.setAnchorView(mAccountSelectorContainer);
820 popup.setAdapter(adapter);
821 popup.setModal(true);
822 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
823 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
824 @Override
825 public void onItemClick(AdapterView<?> parent, View view, int position,
826 long id) {
827 UiClosables.closeQuietly(popup);
828 final AccountWithDataSet newAccount = adapter.getItem(position);
829 if (mListener != null && !mPrimaryAccount.equals(newAccount)) {
830 mListener.onRebindEditorsForNewContact(
831 mPrimaryRawContactDelta,
832 mPrimaryAccount,
833 newAccount);
834 }
835 }
836 });
837 popup.show();
838 }
839 });
840 }
841
Tingting Wang655ad1a2015-10-05 17:51:14 -0700842 private void addRawContactAccountSelector(final RawContactDeltaList rawContactDeltas) {
843 mRawContactContainer.setVisibility(View.VISIBLE);
844
845 final String accountsSummary = getRawContactsAccountsSummary(
846 getContext(), rawContactDeltas);
847 mRawContactSummary.setText(accountsSummary);
848 mRawContactContainer.setContentDescription(accountsSummary);
849 if (mPrimaryRawContactDelta != null) {
850 final AccountType primaryAccountType = mPrimaryRawContactDelta.getRawContactAccountType(
851 getContext());
852 mPrimaryAccountIcon.setImageDrawable(primaryAccountType.getDisplayIcon(getContext()));
853 }
854
855 mRawContactContainer.setOnClickListener(new View.OnClickListener() {
856 @Override
857 public void onClick(View v) {
858 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
859 final RawContactAccountListAdapter adapter =
860 new RawContactAccountListAdapter(getContext(), rawContactDeltas);
861 popup.setWidth(mRawContactContainer.getWidth());
862 popup.setAnchorView(mRawContactContainer);
863 popup.setAdapter(adapter);
864 popup.setModal(true);
865 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
866 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
867 @Override
868 public void onItemClick(AdapterView<?> parent, View view, int position,
869 long id) {
870 UiClosables.closeQuietly(popup);
871 final RawContactDelta rawContactDelta = adapter.getItem(position);
872 final long rawContactId = adapter.getItemId(position);
873 final Uri rawContactUri = ContentUris.withAppendedId(
874 ContactsContract.RawContacts.CONTENT_URI, rawContactId);
875 // Start new activity for the raw contact in selected account.
876 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
877 intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
878
879 ArrayList<ContentValues> values = rawContactDelta.getContentValues();
880 intent.putExtra(ContactsContract.Intents.Insert.DATA, values);
881
882 if (mListener != null) {
883 mListener.onRawContactSelected(rawContactUri, rawContactId);
884 }
885 }
886 });
887 popup.show();
888 }
889 });
890 }
891
892 private static String getRawContactsAccountsSummary(
893 Context context, RawContactDeltaList rawContactDeltas) {
894 final Map<String, Integer> accountTypeNumber = new HashMap<>();
895 for (RawContactDelta rawContactDelta : rawContactDeltas) {
896 if (rawContactDelta.isVisible()) {
897 final AccountType accountType = rawContactDelta.getRawContactAccountType(context);
898 final String accountTypeLabel = accountType.getDisplayLabel(context).toString();
899 if (accountTypeNumber.containsKey(accountTypeLabel)) {
900 int number = accountTypeNumber.get(accountTypeLabel);
901 number++;
902 accountTypeNumber.put(accountTypeLabel, number);
903 } else {
904 accountTypeNumber.put(accountTypeLabel, 1);
905 }
906 }
907 }
908
909 final Set<String> linkedAccounts = new HashSet<>();
910 for (String accountTypeLabel : accountTypeNumber.keySet()) {
911 final String number = context.getResources().getQuantityString(
912 R.plurals.quickcontact_suggestion_account_type_number,
913 accountTypeNumber.get(accountTypeLabel),
914 accountTypeNumber.get(accountTypeLabel));
915 final String accountWithNumber = context.getResources().getString(
916 R.string.quickcontact_suggestion_account_type,
917 accountTypeLabel,
918 number);
919 linkedAccounts.add(accountWithNumber);
920 }
921 return TextUtils.join(",", linkedAccounts);
922 }
923
Walter Jang363d3fd2015-09-16 10:29:07 -0700924 private void addPhotoView() {
Walter Jang31a74ad2015-10-02 19:17:39 -0700925 // Get the kind section data and values delta that we will display in the photo view
Walter Jangcbd431d2015-10-06 13:07:30 -0700926 final KindSectionDataList kindSectionDataList =
927 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
928 final Pair<KindSectionData,ValuesDelta> photoToDisplay =
929 kindSectionDataList.getEntryToDisplay(mPhotoId);
930 if (photoToDisplay == null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700931 wlog("photo: no kind section data parsed");
Walter Jangcbd431d2015-10-06 13:07:30 -0700932 mPhotoView.setVisibility(View.GONE);
Walter Jang363d3fd2015-09-16 10:29:07 -0700933 return;
934 }
Walter Jang06f73a12015-06-17 11:15:48 -0700935
Walter Jang31a74ad2015-10-02 19:17:39 -0700936 // Set the photo view
Walter Jangcbd431d2015-10-06 13:07:30 -0700937 mPhotoView.setPhoto(photoToDisplay.second, mMaterialPalette);
Walter Jang31a74ad2015-10-02 19:17:39 -0700938
939 // Find the raw contact ID and values delta that will be written when the photo is edited
Walter Jangcbd431d2015-10-06 13:07:30 -0700940 final Pair<KindSectionData,ValuesDelta> photoToWrite = kindSectionDataList.getEntryToWrite(
941 mPrimaryAccount, mHasNewContact);
942 if (photoToWrite == null) {
Walter Jang31a74ad2015-10-02 19:17:39 -0700943 mPhotoView.setReadOnly(true);
944 return;
945 }
Walter Jang31a74ad2015-10-02 19:17:39 -0700946 mPhotoView.setReadOnly(false);
Walter Jangcbd431d2015-10-06 13:07:30 -0700947 mPhotoRawContactId = photoToWrite.first.getRawContactDelta().getRawContactId();
948 mPhotoValuesDelta = photoToWrite.second;
Walter Jang31a74ad2015-10-02 19:17:39 -0700949 }
950
Walter Jangf5dfea42015-09-16 12:30:36 -0700951 private void addKindSectionViews() {
952 // Sort the kinds
Walter Jangcbd431d2015-10-06 13:07:30 -0700953 final TreeSet<Map.Entry<String,KindSectionDataList>> entries =
Walter Jang192a01c2015-09-22 15:23:55 -0700954 new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
Walter Jangf5dfea42015-09-16 12:30:36 -0700955 entries.addAll(mKindSectionDataMap.entrySet());
Walter Jang3efae4a2015-02-18 11:12:00 -0800956
Walter Jangf5dfea42015-09-16 12:30:36 -0700957 vlog("kind: " + entries.size() + " kindSection(s)");
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700958 int i = -1;
Walter Jangcbd431d2015-10-06 13:07:30 -0700959 for (Map.Entry<String, KindSectionDataList> entry : entries) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700960 i++;
961
Walter Jangf5dfea42015-09-16 12:30:36 -0700962 final String mimeType = entry.getKey();
Walter Jangcbd431d2015-10-06 13:07:30 -0700963 final KindSectionDataList kindSectionDataList = entry.getValue();
Walter Jangab50e6f2015-06-15 08:57:22 -0700964
Walter Jangf5dfea42015-09-16 12:30:36 -0700965 // Ignore mime types that we've already handled
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700966 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
967 vlog("kind: " + i + " " + mimeType + " dropped");
968 continue;
969 }
Walter Jangf5dfea42015-09-16 12:30:36 -0700970
Walter Jang192a01c2015-09-22 15:23:55 -0700971 if (kindSectionDataList != null && !kindSectionDataList.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700972 vlog("kind: " + i + " " + mimeType + ": " + kindSectionDataList.size() +
973 " kindSectionData(s)");
974
Walter Jangf5dfea42015-09-16 12:30:36 -0700975 final CompactKindSectionView kindSectionView = inflateKindSectionView(
976 mKindSectionViews, kindSectionDataList, mimeType);
Walter Jangf5dfea42015-09-16 12:30:36 -0700977 mKindSectionViews.addView(kindSectionView);
Walter Jangf10ca152015-09-22 15:23:55 -0700978
979 // Keep a pointer to all the KindSectionsViews for each mimeType
980 getKindSectionViews(mimeType).add(kindSectionView);
Walter Jangcab3dce2015-02-09 17:48:03 -0800981 }
982 }
983 }
984
Walter Jangf10ca152015-09-22 15:23:55 -0700985 private List<CompactKindSectionView> getKindSectionViews(String mimeType) {
986 List<CompactKindSectionView> kindSectionViews = mKindSectionViewsMap.get(mimeType);
987 if (kindSectionViews == null) {
988 kindSectionViews = new ArrayList<>();
989 mKindSectionViewsMap.put(mimeType, kindSectionViews);
990 }
991 return kindSectionViews;
992 }
993
Walter Jangf5dfea42015-09-16 12:30:36 -0700994 private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
Walter Jangcbd431d2015-10-06 13:07:30 -0700995 KindSectionDataList kindSectionDataList, String mimeType) {
Walter Jangf5dfea42015-09-16 12:30:36 -0700996 final CompactKindSectionView kindSectionView = (CompactKindSectionView)
997 mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
998 /* attachToRoot =*/ false);
999
Walter Jang192a01c2015-09-22 15:23:55 -07001000 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jangf5dfea42015-09-16 12:30:36 -07001001 || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang192a01c2015-09-22 15:23:55 -07001002 // Phone numbers and email addresses are always displayed,
1003 // even if they are empty
Walter Jangf5dfea42015-09-16 12:30:36 -07001004 kindSectionView.setHideWhenEmpty(false);
1005 }
Walter Jangf5dfea42015-09-16 12:30:36 -07001006
Walter Jang192a01c2015-09-22 15:23:55 -07001007 // Since phone numbers and email addresses displayed even if they are empty,
1008 // they will be the only types you add new values to initially for new contacts
1009 kindSectionView.setShowOneEmptyEditor(true);
1010
1011 // Sort so the editors wind up in the order we want
1012 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
1013 Collections.sort(kindSectionDataList, new NameEditorComparator(getContext(),
1014 mPrimaryRawContactDelta));
1015 } else {
1016 Collections.sort(kindSectionDataList, new EditorComparator(getContext()));
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001017 }
1018
Walter Jang79658e12015-09-24 10:36:26 -07001019 kindSectionView.setState(kindSectionDataList, mViewIdGenerator, mListener);
Walter Jangf5dfea42015-09-16 12:30:36 -07001020
1021 return kindSectionView;
Walter Jangcab3dce2015-02-09 17:48:03 -08001022 }
1023
Walter Jangd6753152015-10-02 09:23:13 -07001024 private void showAllFields() {
Walter Jang79658e12015-09-24 10:36:26 -07001025 // Stop hiding empty editors and allow the user to enter values for all kinds now
1026 for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
1027 final CompactKindSectionView kindSectionView =
1028 (CompactKindSectionView) mKindSectionViews.getChildAt(i);
1029 kindSectionView.setHideWhenEmpty(false);
1030 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
1031 }
1032 mIsExpanded = true;
Walter Jang79658e12015-09-24 10:36:26 -07001033
Walter Jangf5dfea42015-09-16 12:30:36 -07001034 // Hide the more fields button
1035 mMoreFields.setVisibility(View.GONE);
Walter Jangcab3dce2015-02-09 17:48:03 -08001036 }
1037
Walter Jangbf63a6d2015-05-05 09:14:35 -07001038 private static void vlog(String message) {
1039 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1040 Log.v(TAG, message);
Walter Jangcab3dce2015-02-09 17:48:03 -08001041 }
1042 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001043
1044 private static void wlog(String message) {
1045 if (Log.isLoggable(TAG, Log.WARN)) {
1046 Log.w(TAG, message);
1047 }
1048 }
1049
1050 private static void elog(String message) {
1051 Log.e(TAG, message);
1052 }
Walter Jangcab3dce2015-02-09 17:48:03 -08001053}