blob: 88894ec7483f017bfca72f71b196d52334e48a54 [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 Jang3f18d612015-10-07 16:01:05 -070039import android.os.Bundle;
Walter Jang79658e12015-09-24 10:36:26 -070040import android.os.Parcel;
41import android.os.Parcelable;
Tingting Wang655ad1a2015-10-05 17:51:14 -070042import android.provider.ContactsContract;
Walter Jangcab3dce2015-02-09 17:48:03 -080043import android.provider.ContactsContract.CommonDataKinds.Email;
Walter Jangf5dfea42015-09-16 12:30:36 -070044import android.provider.ContactsContract.CommonDataKinds.Event;
Walter Jangcab3dce2015-02-09 17:48:03 -080045import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Walter Jangf5dfea42015-09-16 12:30:36 -070046import android.provider.ContactsContract.CommonDataKinds.Im;
Walter Jang3efae4a2015-02-18 11:12:00 -080047import android.provider.ContactsContract.CommonDataKinds.Nickname;
Walter Jangf5dfea42015-09-16 12:30:36 -070048import android.provider.ContactsContract.CommonDataKinds.Note;
49import android.provider.ContactsContract.CommonDataKinds.Organization;
Walter Jangcab3dce2015-02-09 17:48:03 -080050import android.provider.ContactsContract.CommonDataKinds.Phone;
51import android.provider.ContactsContract.CommonDataKinds.Photo;
Walter Jangf5dfea42015-09-16 12:30:36 -070052import android.provider.ContactsContract.CommonDataKinds.Relation;
53import android.provider.ContactsContract.CommonDataKinds.SipAddress;
Walter Jangcab3dce2015-02-09 17:48:03 -080054import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Walter Jangf5dfea42015-09-16 12:30:36 -070055import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
56import android.provider.ContactsContract.CommonDataKinds.Website;
Walter Jangcab3dce2015-02-09 17:48:03 -080057import android.text.TextUtils;
58import android.util.AttributeSet;
59import android.util.Log;
Walter Jang2d3f31c2015-06-18 23:15:31 -070060import android.util.Pair;
Walter Jangcab3dce2015-02-09 17:48:03 -080061import android.view.LayoutInflater;
Walter Jangb1c87622015-02-13 17:51:38 -080062import android.view.View;
Walter Jangcab3dce2015-02-09 17:48:03 -080063import android.view.ViewGroup;
Walter Jang708ea9e2015-09-10 15:42:05 -070064import android.widget.AdapterView;
Tingting Wang655ad1a2015-10-05 17:51:14 -070065import android.widget.BaseAdapter;
66import android.widget.ImageView;
Walter Jangcab3dce2015-02-09 17:48:03 -080067import android.widget.LinearLayout;
Walter Jang708ea9e2015-09-10 15:42:05 -070068import android.widget.ListPopupWindow;
Walter Jang2d3f31c2015-06-18 23:15:31 -070069import android.widget.TextView;
Walter Jangcab3dce2015-02-09 17:48:03 -080070
Walter Jang31a74ad2015-10-02 19:17:39 -070071import java.io.FileNotFoundException;
Walter Jangcab3dce2015-02-09 17:48:03 -080072import java.util.ArrayList;
Walter Jangf5dfea42015-09-16 12:30:36 -070073import java.util.Arrays;
Walter Jang192a01c2015-09-22 15:23:55 -070074import java.util.Collections;
Walter Jangf5dfea42015-09-16 12:30:36 -070075import java.util.Comparator;
Walter Jang19d985f2015-07-01 13:36:27 -070076import java.util.HashMap;
Tingting Wang655ad1a2015-10-05 17:51:14 -070077import java.util.HashSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080078import java.util.List;
Walter Jang19d985f2015-07-01 13:36:27 -070079import java.util.Map;
Tingting Wang655ad1a2015-10-05 17:51:14 -070080import java.util.Set;
Walter Jangf5dfea42015-09-16 12:30:36 -070081import java.util.TreeSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080082
83/**
Walter Jangf5dfea42015-09-16 12:30:36 -070084 * View to display information from multiple {@link RawContactDelta}s grouped together.
Walter Jangcab3dce2015-02-09 17:48:03 -080085 */
Walter Jangb6ca2722015-02-20 11:10:25 -080086public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
Walter Jangcab3dce2015-02-09 17:48:03 -080087
Walter Jang4f5594a2015-10-06 18:40:31 -070088 static final String TAG = "CompactEditorView";
Walter Jangcab3dce2015-02-09 17:48:03 -080089
Walter Jang192a01c2015-09-22 15:23:55 -070090 private static final KindSectionDataMapEntryComparator
91 KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR = new KindSectionDataMapEntryComparator();
Walter Jangf5dfea42015-09-16 12:30:36 -070092
Walter Jangb6ca2722015-02-20 11:10:25 -080093 /**
94 * Callbacks for hosts of {@link CompactRawContactsEditorView}s.
95 */
96 public interface Listener {
97
98 /**
Walter Jang151f3e62015-02-26 15:29:40 -080099 * Invoked when the structured name editor field has changed.
100 *
101 * @param rawContactId The raw contact ID from the underlying {@link RawContactDelta}.
102 * @param valuesDelta The values from the underlying {@link RawContactDelta}.
103 */
104 public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta);
Walter Jang708ea9e2015-09-10 15:42:05 -0700105
106 /**
107 * Invoked when the compact editor should rebind editors for a new account.
108 *
109 * @param oldState Old data being edited.
110 * @param oldAccount Old account associated with oldState.
111 * @param newAccount New account to be used.
112 */
113 public void onRebindEditorsForNewContact(RawContactDelta oldState,
114 AccountWithDataSet oldAccount, AccountWithDataSet newAccount);
Walter Jang79658e12015-09-24 10:36:26 -0700115
116 /**
117 * Invoked when no editors could be bound for the contact.
118 */
119 public void onBindEditorsFailed();
Walter Jangd6753152015-10-02 09:23:13 -0700120
121 /**
122 * Invoked after editors have been bound for the contact.
123 */
124 public void onEditorsBound();
Tingting Wang655ad1a2015-10-05 17:51:14 -0700125
126 /**
127 * Invoked when a rawcontact from merged contacts is selected in editor.
128 */
Tingting Wang91cee282015-10-07 13:48:17 -0700129 public void onRawContactSelected(Uri uri, long rawContactId, boolean isReadOnly);
Walter Jang3f18d612015-10-07 16:01:05 -0700130
131 /**
132 * Returns the map of raw contact IDs to newly taken or selected photos that have not
133 * yet been saved to CP2.
134 */
135 public Bundle getUpdatedPhotos();
Tingting Wang655ad1a2015-10-05 17:51:14 -0700136 }
137
138 /**
139 * Used to list the account info for the given raw contacts list.
140 */
141 private static final class RawContactAccountListAdapter extends BaseAdapter {
142 private final LayoutInflater mInflater;
143 private final Context mContext;
144 private final RawContactDeltaList mRawContactDeltas;
145
146 public RawContactAccountListAdapter(Context context, RawContactDeltaList rawContactDeltas) {
147 mContext = context;
148 mRawContactDeltas = new RawContactDeltaList();
149 for (RawContactDelta rawContactDelta : rawContactDeltas) {
150 if (rawContactDelta.isVisible()) {
151 mRawContactDeltas.add(rawContactDelta);
152 }
153 }
154 mInflater = LayoutInflater.from(context);
155 }
156
157 @Override
158 public View getView(int position, View convertView, ViewGroup parent) {
159 final View resultView = convertView != null ? convertView
160 : mInflater.inflate(R.layout.account_selector_list_item, parent, false);
161
162 final RawContactDelta rawContactDelta = mRawContactDeltas.get(position);
163 final String accountName = rawContactDelta.getAccountName();
164 final AccountType accountType = rawContactDelta.getRawContactAccountType(mContext);
165
166 final TextView text1 = (TextView) resultView.findViewById(android.R.id.text1);
167 text1.setText(accountType.getDisplayLabel(mContext));
168
169 // For email addresses, we don't want to truncate at end, which might cut off the domain
170 // name.
171 final TextView text2 = (TextView) resultView.findViewById(android.R.id.text2);
172 text2.setText(accountName);
173 text2.setEllipsize(TextUtils.TruncateAt.MIDDLE);
174
175 final ImageView icon = (ImageView) resultView.findViewById(android.R.id.icon);
176 icon.setImageDrawable(accountType.getDisplayIcon(mContext));
177
178 return resultView;
179 }
180
181 @Override
182 public int getCount() {
183 return mRawContactDeltas.size();
184 }
185
186 @Override
187 public RawContactDelta getItem(int position) {
188 return mRawContactDeltas.get(position);
189 }
190
191 @Override
192 public long getItemId(int position) {
193 return getItem(position).getRawContactId();
194 }
Walter Jang151f3e62015-02-26 15:29:40 -0800195 }
196
Walter Jang192a01c2015-09-22 15:23:55 -0700197 /** Used to sort entire kind sections. */
198 private static final class KindSectionDataMapEntryComparator implements
Walter Jangcbd431d2015-10-06 13:07:30 -0700199 Comparator<Map.Entry<String,KindSectionDataList>> {
Walter Jangf5dfea42015-09-16 12:30:36 -0700200
Walter Jang192a01c2015-09-22 15:23:55 -0700201 final MimeTypeComparator mMimeTypeComparator = new MimeTypeComparator();
202
203 @Override
Walter Jangcbd431d2015-10-06 13:07:30 -0700204 public int compare(Map.Entry<String, KindSectionDataList> entry1,
205 Map.Entry<String, KindSectionDataList> entry2) {
Walter Jang192a01c2015-09-22 15:23:55 -0700206 if (entry1 == entry2) return 0;
207 if (entry1 == null) return -1;
208 if (entry2 == null) return 1;
209
210 final String mimeType1 = entry1.getKey();
211 final String mimeType2 = entry2.getKey();
212
213 return mMimeTypeComparator.compare(mimeType1, mimeType2);
214 }
215 }
216
217 /**
218 * Sorts kinds roughly the same as quick contacts; we diverge in the following ways:
219 * <ol>
220 * <li>All names are together at the top.</li>
221 * <li>IM is moved up after addresses</li>
222 * <li>SIP addresses are moved to below phone numbers</li>
Walter Jang79658e12015-09-24 10:36:26 -0700223 * <li>Group membership is placed at the end</li>
Walter Jang192a01c2015-09-22 15:23:55 -0700224 * </ol>
225 */
226 private static final class MimeTypeComparator implements Comparator<String> {
227
Walter Jangf5dfea42015-09-16 12:30:36 -0700228 private static final List<String> MIME_TYPE_ORDER = Arrays.asList(new String[] {
229 StructuredName.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700230 Nickname.CONTENT_ITEM_TYPE,
Walter Jang2ae21752015-09-25 11:25:38 -0700231 Organization.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700232 Phone.CONTENT_ITEM_TYPE,
233 SipAddress.CONTENT_ITEM_TYPE,
234 Email.CONTENT_ITEM_TYPE,
235 StructuredPostal.CONTENT_ITEM_TYPE,
236 Im.CONTENT_ITEM_TYPE,
237 Website.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700238 Event.CONTENT_ITEM_TYPE,
239 Relation.CONTENT_ITEM_TYPE,
Walter Jangf10ca152015-09-22 15:23:55 -0700240 Note.CONTENT_ITEM_TYPE,
241 GroupMembership.CONTENT_ITEM_TYPE
Walter Jangf5dfea42015-09-16 12:30:36 -0700242 });
243
244 @Override
Walter Jang192a01c2015-09-22 15:23:55 -0700245 public int compare(String mimeType1, String mimeType2) {
246 if (mimeType1 == mimeType2) return 0;
247 if (mimeType1 == null) return -1;
248 if (mimeType2 == null) return 1;
Walter Jangf5dfea42015-09-16 12:30:36 -0700249
250 int index1 = MIME_TYPE_ORDER.indexOf(mimeType1);
251 int index2 = MIME_TYPE_ORDER.indexOf(mimeType2);
252
253 // Fallback to alphabetical ordering of the mime type if both are not found
254 if (index1 < 0 && index2 < 0) return mimeType1.compareTo(mimeType2);
255 if (index1 < 0) return 1;
256 if (index2 < 0) return -1;
257
258 return index1 < index2 ? -1 : 1;
259 }
260 }
261
Walter Jang192a01c2015-09-22 15:23:55 -0700262 /**
263 * Sorts primary accounts and google account types before others.
264 */
265 private static final class EditorComparator implements Comparator<KindSectionData> {
266
267 private RawContactDeltaComparator mRawContactDeltaComparator;
268
269 private EditorComparator(Context context) {
270 mRawContactDeltaComparator = new RawContactDeltaComparator(context);
271 }
272
273 @Override
274 public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
275 if (kindSectionData1 == kindSectionData2) return 0;
276 if (kindSectionData1 == null) return -1;
277 if (kindSectionData2 == null) return 1;
278
279 final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
280 final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
281
282 if (rawContactDelta1 == rawContactDelta2) return 0;
283 if (rawContactDelta1 == null) return -1;
284 if (rawContactDelta2 == null) return 1;
285
286 return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
287 }
288 }
289
290 /**
291 * Sorts primary account names first, followed by google account types, and other account
292 * types last. For names from the same account we order structured names before nicknames,
293 * but still keep names from the same account together.
294 */
295 private static final class NameEditorComparator implements Comparator<KindSectionData> {
296
Walter Jang4f5594a2015-10-06 18:40:31 -0700297 private final RawContactDeltaComparator mRawContactDeltaComparator;
298 private final MimeTypeComparator mMimeTypeComparator;
299 private final RawContactDelta mPrimaryRawContactDelta;
Walter Jang192a01c2015-09-22 15:23:55 -0700300
301 private NameEditorComparator(Context context, RawContactDelta primaryRawContactDelta) {
302 mRawContactDeltaComparator = new RawContactDeltaComparator(context);
303 mMimeTypeComparator = new MimeTypeComparator();
304 mPrimaryRawContactDelta = primaryRawContactDelta;
305 }
306
307 @Override
308 public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
309 if (kindSectionData1 == kindSectionData2) return 0;
310 if (kindSectionData1 == null) return -1;
311 if (kindSectionData2 == null) return 1;
312
313 final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
314 final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
315
316 if (rawContactDelta1 == rawContactDelta2) return 0;
317 if (rawContactDelta1 == null) return -1;
318 if (rawContactDelta2 == null) return 1;
319
320 final boolean isRawContactDelta1Primary =
321 mPrimaryRawContactDelta.equals(rawContactDelta1);
322 final boolean isRawContactDelta2Primary =
323 mPrimaryRawContactDelta.equals(rawContactDelta2);
324
325 // If both names are from the primary account, sort my by mime type
326 if (isRawContactDelta1Primary && isRawContactDelta2Primary) {
327 final String mimeType1 = kindSectionData1.getDataKind().mimeType;
328 final String mimeType2 = kindSectionData2.getDataKind().mimeType;
329 return mMimeTypeComparator.compare(mimeType1, mimeType2);
330 }
331
332 // The primary account name should be before all others
Walter Jang79658e12015-09-24 10:36:26 -0700333 if (isRawContactDelta1Primary) return -1;
334 if (isRawContactDelta2Primary) return 1;
Walter Jang192a01c2015-09-22 15:23:55 -0700335
336 return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
337 }
338 }
339
Walter Jang79658e12015-09-24 10:36:26 -0700340 public static class SavedState extends BaseSavedState {
341
342 public static final Parcelable.Creator<SavedState> CREATOR =
343 new Parcelable.Creator<SavedState>() {
344 public SavedState createFromParcel(Parcel in) {
345 return new SavedState(in);
346 }
347 public SavedState[] newArray(int size) {
348 return new SavedState[size];
349 }
350 };
351
352 private boolean mIsExpanded;
353
354 public SavedState(Parcelable superState) {
355 super(superState);
356 }
357
358 private SavedState(Parcel in) {
359 super(in);
360 mIsExpanded = in.readInt() != 0;
361 }
362
363 @Override
364 public void writeToParcel(Parcel out, int flags) {
365 super.writeToParcel(out, flags);
366 out.writeInt(mIsExpanded ? 1 : 0);
367 }
368 }
369
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700370 private CompactRawContactsEditorView.Listener mListener;
Walter Jangb6ca2722015-02-20 11:10:25 -0800371
Walter Jangcab3dce2015-02-09 17:48:03 -0800372 private AccountTypeManager mAccountTypeManager;
373 private LayoutInflater mLayoutInflater;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800374
Walter Jangcab3dce2015-02-09 17:48:03 -0800375 private ViewIdGenerator mViewIdGenerator;
Walter Jangf46abd82015-02-20 16:52:04 -0800376 private MaterialColorMapUtils.MaterialPalette mMaterialPalette;
Walter Jang708ea9e2015-09-10 15:42:05 -0700377 private long mPhotoId;
Walter Jang4f5594a2015-10-06 18:40:31 -0700378 private String mReadOnlyDisplayName;
Walter Jang708ea9e2015-09-10 15:42:05 -0700379 private boolean mHasNewContact;
380 private boolean mIsUserProfile;
381 private AccountWithDataSet mPrimaryAccount;
382 private RawContactDelta mPrimaryRawContactDelta;
Walter Jangcbd431d2015-10-06 13:07:30 -0700383 private Map<String,KindSectionDataList> mKindSectionDataMap = new HashMap<>();
Walter Jangcab3dce2015-02-09 17:48:03 -0800384
Walter Jang708ea9e2015-09-10 15:42:05 -0700385 // Account header
386 private View mAccountHeaderContainer;
387 private TextView mAccountHeaderType;
388 private TextView mAccountHeaderName;
Tingting Wang91cee282015-10-07 13:48:17 -0700389 private ImageView mAccountHeaderIcon;
Walter Jang708ea9e2015-09-10 15:42:05 -0700390
391 // Account selector
392 private View mAccountSelectorContainer;
393 private View mAccountSelector;
394 private TextView mAccountSelectorType;
395 private TextView mAccountSelectorName;
396
Tingting Wang655ad1a2015-10-05 17:51:14 -0700397 // Raw contacts selector
398 private View mRawContactContainer;
399 private TextView mRawContactSummary;
400 private ImageView mPrimaryAccountIcon;
401
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700402 private CompactPhotoEditorView mPhotoView;
Walter Jangf5dfea42015-09-16 12:30:36 -0700403 private ViewGroup mKindSectionViews;
Walter Jangf10ca152015-09-22 15:23:55 -0700404 private Map<String,List<CompactKindSectionView>> mKindSectionViewsMap = new HashMap<>();
Walter Jangb6ca2722015-02-20 11:10:25 -0800405 private View mMoreFields;
Walter Jangcab3dce2015-02-09 17:48:03 -0800406
Walter Jang79658e12015-09-24 10:36:26 -0700407 private boolean mIsExpanded;
Walter Jangcbd431d2015-10-06 13:07:30 -0700408
Walter Jang3efae4a2015-02-18 11:12:00 -0800409 private long mPhotoRawContactId;
Walter Jang31a74ad2015-10-02 19:17:39 -0700410 private ValuesDelta mPhotoValuesDelta;
Walter Jang4f5594a2015-10-06 18:40:31 -0700411 private StructuredNameEditorView mPrimaryNameEditorView;
Walter Jang3efae4a2015-02-18 11:12:00 -0800412
Walter Jangcab3dce2015-02-09 17:48:03 -0800413 public CompactRawContactsEditorView(Context context) {
414 super(context);
415 }
416
417 public CompactRawContactsEditorView(Context context, AttributeSet attrs) {
418 super(context, attrs);
419 }
420
Walter Jangb6ca2722015-02-20 11:10:25 -0800421 /**
422 * Sets the receiver for {@link CompactRawContactsEditorView} callbacks.
423 */
424 public void setListener(Listener listener) {
425 mListener = listener;
426 }
427
Walter Jangcab3dce2015-02-09 17:48:03 -0800428 @Override
429 protected void onFinishInflate() {
430 super.onFinishInflate();
431
432 mAccountTypeManager = AccountTypeManager.getInstance(getContext());
433 mLayoutInflater = (LayoutInflater)
434 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
435
Walter Jang708ea9e2015-09-10 15:42:05 -0700436 // Account header
437 mAccountHeaderContainer = findViewById(R.id.account_container);
438 mAccountHeaderType = (TextView) findViewById(R.id.account_type);
439 mAccountHeaderName = (TextView) findViewById(R.id.account_name);
Tingting Wang91cee282015-10-07 13:48:17 -0700440 mAccountHeaderIcon = (ImageView) findViewById(R.id.account_type_icon);
Walter Jang708ea9e2015-09-10 15:42:05 -0700441
442 // Account selector
443 mAccountSelectorContainer = findViewById(R.id.account_selector_container);
444 mAccountSelector = findViewById(R.id.account);
445 mAccountSelectorType = (TextView) findViewById(R.id.account_type_selector);
446 mAccountSelectorName = (TextView) findViewById(R.id.account_name_selector);
447
Tingting Wang655ad1a2015-10-05 17:51:14 -0700448 // Raw contacts selector
449 mRawContactContainer = findViewById(R.id.all_rawcontacts_accounts_container);
450 mRawContactSummary = (TextView) findViewById(R.id.rawcontacts_accounts_summary);
451 mPrimaryAccountIcon = (ImageView) findViewById(R.id.primary_account_icon);
452
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700453 mPhotoView = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
Walter Jangf5dfea42015-09-16 12:30:36 -0700454 mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
Walter Jangb6ca2722015-02-20 11:10:25 -0800455 mMoreFields = findViewById(R.id.more_fields);
456 mMoreFields.setOnClickListener(this);
457 }
458
Walter Jangb6ca2722015-02-20 11:10:25 -0800459 @Override
460 public void onClick(View view) {
Walter Jangf5dfea42015-09-16 12:30:36 -0700461 if (view.getId() == R.id.more_fields) {
Walter Jangd6753152015-10-02 09:23:13 -0700462 showAllFields();
Walter Jangb6ca2722015-02-20 11:10:25 -0800463 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800464 }
465
466 @Override
467 public void setEnabled(boolean enabled) {
468 super.setEnabled(enabled);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700469 final int childCount = mKindSectionViews.getChildCount();
470 for (int i = 0; i < childCount; i++) {
471 mKindSectionViews.getChildAt(i).setEnabled(enabled);
Walter Jangcab3dce2015-02-09 17:48:03 -0800472 }
473 }
474
Walter Jang79658e12015-09-24 10:36:26 -0700475 @Override
476 public Parcelable onSaveInstanceState() {
477 final Parcelable superState = super.onSaveInstanceState();
478 final SavedState savedState = new SavedState(superState);
479 savedState.mIsExpanded = mIsExpanded;
480 return savedState;
481 }
482
483 @Override
484 public void onRestoreInstanceState(Parcelable state) {
485 if(!(state instanceof SavedState)) {
486 super.onRestoreInstanceState(state);
487 return;
488 }
489 final SavedState savedState = (SavedState) state;
490 super.onRestoreInstanceState(savedState.getSuperState());
491 mIsExpanded = savedState.mIsExpanded;
Walter Jangd6753152015-10-02 09:23:13 -0700492 if (mIsExpanded) {
493 showAllFields();
Walter Jang79658e12015-09-24 10:36:26 -0700494 }
495 }
496
Walter Jang3efae4a2015-02-18 11:12:00 -0800497 /**
Walter Jang31a74ad2015-10-02 19:17:39 -0700498 * Pass through to {@link CompactPhotoEditorView#setListener}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800499 */
Walter Jang31a74ad2015-10-02 19:17:39 -0700500 public void setPhotoListener(CompactPhotoEditorView.Listener listener) {
501 mPhotoView.setListener(listener);
Walter Jang3efae4a2015-02-18 11:12:00 -0800502 }
503
Walter Jang31a74ad2015-10-02 19:17:39 -0700504 public void removePhoto() {
505 mPhotoValuesDelta.setFromTemplate(false);
506 mPhotoValuesDelta.put(Photo.PHOTO, (byte[]) null);
507
508 mPhotoView.removePhoto();
Walter Jang3efae4a2015-02-18 11:12:00 -0800509 }
510
511 /**
Walter Jang41b3ea12015-03-09 17:30:06 -0700512 * Pass through to {@link CompactPhotoEditorView#setFullSizedPhoto(Uri)}.
513 */
514 public void setFullSizePhoto(Uri photoUri) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700515 mPhotoView.setFullSizedPhoto(photoUri);
Walter Jang41b3ea12015-03-09 17:30:06 -0700516 }
517
Walter Jang31a74ad2015-10-02 19:17:39 -0700518 public void updatePhoto(Uri photoUri) {
Walter Jang3f18d612015-10-07 16:01:05 -0700519 // Unset primary for all photos
520 unsetSuperPrimary();
521
522 // Mark the currently displayed photo as primary
Walter Jang31a74ad2015-10-02 19:17:39 -0700523 mPhotoValuesDelta.setSuperPrimary(true);
Walter Jang31a74ad2015-10-02 19:17:39 -0700524
Wenyi Wang4c9cf3a2015-10-15 17:58:23 -0700525 // Even though high-res photos cannot be saved by passing them via
526 // an EntityDeltaList (since they cause the Bundle size limit to be
527 // exceeded), we still pass a low-res thumbnail. This simplifies
528 // code all over the place, because we don't have to test whether
529 // there is a change in EITHER the delta-list OR a changed photo...
530 // this way, there is always a change in the delta-list.
531 try {
532 final byte[] bytes = EditorUiUtils.getCompressedThumbnailBitmapBytes(
533 getContext(), photoUri);
534 if (bytes != null) {
535 mPhotoValuesDelta.setPhoto(bytes);
536 }
537 } catch (FileNotFoundException e) {
538 elog("Failed to get bitmap from photo Uri");
539 }
540
Walter Jang31a74ad2015-10-02 19:17:39 -0700541 mPhotoView.setFullSizedPhoto(photoUri);
542 }
543
Walter Jang3f18d612015-10-07 16:01:05 -0700544 private void unsetSuperPrimary() {
545 final List<KindSectionData> kindSectionDataList =
546 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
547 for (KindSectionData kindSectionData : kindSectionDataList) {
548 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
549 for (ValuesDelta valuesDelta : valuesDeltaList) {
550 valuesDelta.setSuperPrimary(false);
551 }
552 }
553 }
554
Walter Jang41b3ea12015-03-09 17:30:06 -0700555 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800556 * Pass through to {@link CompactPhotoEditorView#isWritablePhotoSet}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800557 */
558 public boolean isWritablePhotoSet() {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700559 return mPhotoView.isWritablePhotoSet();
Walter Jang3efae4a2015-02-18 11:12:00 -0800560 }
561
562 /**
Walter Jang3efae4a2015-02-18 11:12:00 -0800563 * Get the raw contact ID for the CompactHeaderView photo.
564 */
Walter Jang3efae4a2015-02-18 11:12:00 -0800565 public long getPhotoRawContactId() {
566 return mPhotoRawContactId;
567 }
568
Walter Jang4f5594a2015-10-06 18:40:31 -0700569 public StructuredNameEditorView getPrimaryNameEditorView() {
570 return mPrimaryNameEditorView;
571 }
572
Walter Jang31a74ad2015-10-02 19:17:39 -0700573 /**
574 * Returns a data holder for every non-default/non-empty photo from each raw contact, whether
575 * the raw contact is writable or not.
576 */
577 public ArrayList<CompactPhotoSelectionFragment.Photo> getPhotos() {
578 final ArrayList<CompactPhotoSelectionFragment.Photo> photos = new ArrayList<>();
579
Walter Jang3f18d612015-10-07 16:01:05 -0700580 final Bundle updatedPhotos = mListener == null ? null : mListener.getUpdatedPhotos();
581
Walter Jang31a74ad2015-10-02 19:17:39 -0700582 final List<KindSectionData> kindSectionDataList =
583 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
584 for (int i = 0; i < kindSectionDataList.size(); i++) {
585 final KindSectionData kindSectionData = kindSectionDataList.get(i);
586 final AccountType accountType = kindSectionData.getAccountType();
587 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
588 if (valuesDeltaList == null || valuesDeltaList.isEmpty()) continue;
589 for (int j = 0; j < valuesDeltaList.size(); j++) {
590 final ValuesDelta valuesDelta = valuesDeltaList.get(j);
591 final Bitmap bitmap = EditorUiUtils.getPhotoBitmap(valuesDelta);
592 if (bitmap == null) continue;
593
594 final CompactPhotoSelectionFragment.Photo photo =
595 new CompactPhotoSelectionFragment.Photo();
596 photo.titleRes = accountType.titleRes;
597 photo.iconRes = accountType.iconRes;
598 photo.syncAdapterPackageName = accountType.syncAdapterPackageName;
599 photo.valuesDelta = valuesDelta;
600 photo.primary = valuesDelta.isSuperPrimary();
601 photo.kindSectionDataListIndex = i;
602 photo.valuesDeltaListIndex = j;
Walter Jang3f18d612015-10-07 16:01:05 -0700603
604 if (updatedPhotos != null) {
605 photo.updatedPhotoUri = (Uri) updatedPhotos.get(String.valueOf(
606 kindSectionData.getRawContactDelta().getRawContactId()));
607 }
608
Walter Jang31a74ad2015-10-02 19:17:39 -0700609 photos.add(photo);
610 }
611 }
612
613 return photos;
614 }
615
616 /**
617 * Marks the raw contact photo given as primary for the aggregate contact and updates the
618 * UI.
619 */
620 public void setPrimaryPhoto(CompactPhotoSelectionFragment.Photo photo) {
Walter Jang3f18d612015-10-07 16:01:05 -0700621 // Unset primary for all photos
622 unsetSuperPrimary();
Walter Jang31a74ad2015-10-02 19:17:39 -0700623
624 // Find the values delta to mark as primary
Walter Jang3f18d612015-10-07 16:01:05 -0700625 final KindSectionDataList kindSectionDataList =
626 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
Walter Jang31a74ad2015-10-02 19:17:39 -0700627 if (photo.kindSectionDataListIndex < 0
628 || photo.kindSectionDataListIndex >= kindSectionDataList.size()) {
629 wlog("Invalid kind section data list index");
630 return;
631 }
632 final KindSectionData kindSectionData =
633 kindSectionDataList.get(photo.kindSectionDataListIndex);
634 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
635 if (photo.valuesDeltaListIndex >= valuesDeltaList.size()) {
636 wlog("Invalid values delta list index");
637 return;
638 }
639 final ValuesDelta valuesDelta = valuesDeltaList.get(photo.valuesDeltaListIndex);
640 valuesDelta.setFromTemplate(false);
641 valuesDelta.setSuperPrimary(true);
642
643 // Update the UI
644 mPhotoView.setPhoto(valuesDelta, mMaterialPalette);
645 }
646
Walter Jangd35e5ef2015-02-24 09:18:16 -0800647 public View getAggregationAnchorView() {
Walter Jangf10ca152015-09-22 15:23:55 -0700648 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
Walter Jang192a01c2015-09-22 15:23:55 -0700649 StructuredName.CONTENT_ITEM_TYPE);
Walter Jangf10ca152015-09-22 15:23:55 -0700650 if (!kindSectionViews.isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700651 return mKindSectionViews.getChildAt(0).findViewById(R.id.anchor_view);
Walter Jangd35e5ef2015-02-24 09:18:16 -0800652 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700653 return null;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800654 }
655
Walter Jangf10ca152015-09-22 15:23:55 -0700656 public void setGroupMetaData(Cursor groupMetaData) {
657 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
658 GroupMembership.CONTENT_ITEM_TYPE);
659 for (CompactKindSectionView kindSectionView : kindSectionViews) {
660 kindSectionView.setGroupMetaData(groupMetaData);
Walter Jangd6753152015-10-02 09:23:13 -0700661 if (mIsExpanded) {
662 kindSectionView.setHideWhenEmpty(false);
663 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
664 }
Walter Jangf10ca152015-09-22 15:23:55 -0700665 }
666 }
667
Walter Jangf46abd82015-02-20 16:52:04 -0800668 public void setState(RawContactDeltaList rawContactDeltas,
Walter Jang06f73a12015-06-17 11:15:48 -0700669 MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
Walter Jang4f5594a2015-10-06 18:40:31 -0700670 long photoId, String readOnlyDisplayName, boolean hasNewContact,
671 boolean isUserProfile, AccountWithDataSet primaryAccount) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700672 mKindSectionDataMap.clear();
Walter Jangf5dfea42015-09-16 12:30:36 -0700673 mKindSectionViews.removeAllViews();
Walter Jang363d3fd2015-09-16 10:29:07 -0700674 mMoreFields.setVisibility(View.VISIBLE);
Walter Jangcab3dce2015-02-09 17:48:03 -0800675
Walter Jangf46abd82015-02-20 16:52:04 -0800676 mMaterialPalette = materialPalette;
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700677 mViewIdGenerator = viewIdGenerator;
Walter Jang708ea9e2015-09-10 15:42:05 -0700678 mPhotoId = photoId;
Walter Jang4f5594a2015-10-06 18:40:31 -0700679 mReadOnlyDisplayName = readOnlyDisplayName;
Walter Jang708ea9e2015-09-10 15:42:05 -0700680 mHasNewContact = hasNewContact;
681 mIsUserProfile = isUserProfile;
682 mPrimaryAccount = primaryAccount;
683 if (mPrimaryAccount == null) {
684 mPrimaryAccount = ContactEditorUtils.getInstance(getContext()).getDefaultAccount();
685 }
686 vlog("state: primary " + mPrimaryAccount);
Walter Jangcab3dce2015-02-09 17:48:03 -0800687
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700688 // Parse the given raw contact deltas
689 if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
690 elog("No raw contact deltas");
Walter Jang79658e12015-09-24 10:36:26 -0700691 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jangf5dfea42015-09-16 12:30:36 -0700692 return;
693 }
Walter Jangcbd431d2015-10-06 13:07:30 -0700694 parseRawContactDeltas(rawContactDeltas);
Walter Jang79658e12015-09-24 10:36:26 -0700695 if (mKindSectionDataMap.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700696 elog("No kind section data parsed from RawContactDelta(s)");
Walter Jang79658e12015-09-24 10:36:26 -0700697 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700698 return;
699 }
Walter Jangcbd431d2015-10-06 13:07:30 -0700700 mPrimaryRawContactDelta = mKindSectionDataMap.get(StructuredName.CONTENT_ITEM_TYPE)
701 .getEntryToWrite(mPrimaryAccount, mHasNewContact).first.getRawContactDelta();
702 if (mPrimaryRawContactDelta != null) {
703 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
704 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
705 StructuredName.CONTENT_ITEM_TYPE);
706 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
707 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
708 Photo.CONTENT_ITEM_TYPE);
709 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700710
711 // Setup the view
Tingting Wang655ad1a2015-10-05 17:51:14 -0700712 addAccountInfo(rawContactDeltas);
Walter Jang363d3fd2015-09-16 10:29:07 -0700713 addPhotoView();
Walter Jangf5dfea42015-09-16 12:30:36 -0700714 addKindSectionViews();
Walter Jang4f5594a2015-10-06 18:40:31 -0700715 if (mHasNewContact) {
716 maybeCopyPrimaryDisplayName();
Walter Jangd6753152015-10-02 09:23:13 -0700717 }
Walter Jang4f5594a2015-10-06 18:40:31 -0700718 if (mIsExpanded) showAllFields();
Walter Jangd6753152015-10-02 09:23:13 -0700719
720 if (mListener != null) mListener.onEditorsBound();
Walter Jangcab3dce2015-02-09 17:48:03 -0800721 }
722
Walter Jangcbd431d2015-10-06 13:07:30 -0700723 private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700724 // Build the kind section data list map
Walter Jang192a01c2015-09-22 15:23:55 -0700725 vlog("parse: " + rawContactDeltas.size() + " rawContactDelta(s)");
726 for (int j = 0; j < rawContactDeltas.size(); j++) {
727 final RawContactDelta rawContactDelta = rawContactDeltas.get(j);
728 vlog("parse: " + j + " rawContactDelta" + rawContactDelta);
Walter Jang363d3fd2015-09-16 10:29:07 -0700729 if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
Walter Jangcab3dce2015-02-09 17:48:03 -0800730 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jang363d3fd2015-09-16 10:29:07 -0700731 if (accountType == null) continue;
732 final List<DataKind> dataKinds = accountType.getSortedDataKinds();
733 final int dataKindSize = dataKinds == null ? 0 : dataKinds.size();
734 vlog("parse: " + dataKindSize + " dataKinds(s)");
735 for (int i = 0; i < dataKindSize; i++) {
736 final DataKind dataKind = dataKinds.get(i);
Walter Jang192a01c2015-09-22 15:23:55 -0700737 if (dataKind == null || !dataKind.editable) {
738 vlog("parse: " + i + " " + dataKind.mimeType + " dropped read-only");
739 continue;
740 }
741 final String mimeType = dataKind.mimeType;
742
743 // Skip psuedo mime types
744 if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)
745 || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
746 vlog("parse: " + i + " " + dataKind.mimeType + " dropped pseudo type");
747 continue;
748 }
749
Walter Jang3f18d612015-10-07 16:01:05 -0700750 final KindSectionDataList kindSectionDataList =
Walter Jangcbd431d2015-10-06 13:07:30 -0700751 getOrCreateKindSectionDataList(mimeType);
Walter Jang363d3fd2015-09-16 10:29:07 -0700752 final KindSectionData kindSectionData =
753 new KindSectionData(accountType, dataKind, rawContactDelta);
754 kindSectionDataList.add(kindSectionData);
Walter Jang192a01c2015-09-22 15:23:55 -0700755
Walter Jang79658e12015-09-24 10:36:26 -0700756 // Note we must create nickname entries
Walter Jang192a01c2015-09-22 15:23:55 -0700757 if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jang79658e12015-09-24 10:36:26 -0700758 && kindSectionData.getValuesDeltas().isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700759 RawContactModifier.insertChild(rawContactDelta, dataKind);
760 }
761
Walter Jang363d3fd2015-09-16 10:29:07 -0700762 vlog("parse: " + i + " " + dataKind.mimeType + " " +
Walter Jang192a01c2015-09-22 15:23:55 -0700763 kindSectionData.getValuesDeltas().size() + " value(s)");
Walter Jangac679af2015-06-01 12:17:06 -0700764 }
765 }
Walter Jang3efae4a2015-02-18 11:12:00 -0800766 }
767
Walter Jang3f18d612015-10-07 16:01:05 -0700768 private KindSectionDataList getOrCreateKindSectionDataList(String mimeType) {
Walter Jang192a01c2015-09-22 15:23:55 -0700769 // Put structured names and nicknames together
770 mimeType = Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
771 ? StructuredName.CONTENT_ITEM_TYPE : mimeType;
Walter Jangcbd431d2015-10-06 13:07:30 -0700772 KindSectionDataList kindSectionDataList = mKindSectionDataMap.get(mimeType);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700773 if (kindSectionDataList == null) {
Walter Jangcbd431d2015-10-06 13:07:30 -0700774 kindSectionDataList = new KindSectionDataList();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700775 mKindSectionDataMap.put(mimeType, kindSectionDataList);
776 }
777 return kindSectionDataList;
778 }
779
Tingting Wang655ad1a2015-10-05 17:51:14 -0700780 private void addAccountInfo(RawContactDeltaList rawContactDeltas) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700781 if (mPrimaryRawContactDelta == null) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700782 mAccountHeaderContainer.setVisibility(View.GONE);
783 mAccountSelectorContainer.setVisibility(View.GONE);
784 return;
785 }
786
787 // Get the account information for the primary raw contact delta
788 final Pair<String,String> accountInfo = EditorUiUtils.getAccountInfo(getContext(),
789 mIsUserProfile, mPrimaryRawContactDelta.getAccountName(),
790 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager));
791
792 // The account header and selector show the same information so both shouldn't be visible
793 // at the same time
794 final List<AccountWithDataSet> accounts =
795 AccountTypeManager.getInstance(getContext()).getAccounts(true);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700796 mRawContactContainer.setVisibility(View.GONE);
Walter Jang708ea9e2015-09-10 15:42:05 -0700797 if (mHasNewContact && !mIsUserProfile && accounts.size() > 1) {
798 mAccountHeaderContainer.setVisibility(View.GONE);
799 addAccountSelector(accountInfo);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700800 } else if (mHasNewContact && !mIsUserProfile) {
801 addAccountHeader(accountInfo);
802 mAccountSelectorContainer.setVisibility(View.GONE);
803 } else if (rawContactDeltas.size() > 1) {
804 mAccountHeaderContainer.setVisibility(View.GONE);
805 mAccountSelectorContainer.setVisibility(View.GONE);
806 addRawContactAccountSelector(rawContactDeltas);
Walter Jang708ea9e2015-09-10 15:42:05 -0700807 } else {
808 addAccountHeader(accountInfo);
809 mAccountSelectorContainer.setVisibility(View.GONE);
810 }
811 }
812
813 private void addAccountHeader(Pair<String,String> accountInfo) {
Walter Jang03cea2e2015-09-18 17:04:21 -0700814 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700815 // Hide this view so the other text view will be centered vertically
816 mAccountHeaderName.setVisibility(View.GONE);
817 } else {
818 mAccountHeaderName.setVisibility(View.VISIBLE);
819 mAccountHeaderName.setText(accountInfo.first);
820 }
821 mAccountHeaderType.setText(accountInfo.second);
822
Tingting Wang91cee282015-10-07 13:48:17 -0700823 final AccountType primaryAccountType = mPrimaryRawContactDelta.getRawContactAccountType(
824 getContext());
825 mAccountHeaderIcon.setImageDrawable(primaryAccountType.getDisplayIcon(getContext()));
826
Walter Jang708ea9e2015-09-10 15:42:05 -0700827 mAccountHeaderContainer.setContentDescription(
828 EditorUiUtils.getAccountInfoContentDescription(
829 accountInfo.first, accountInfo.second));
830 }
831
832 private void addAccountSelector(Pair<String,String> accountInfo) {
833 mAccountSelectorContainer.setVisibility(View.VISIBLE);
834
Walter Jang03cea2e2015-09-18 17:04:21 -0700835 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700836 // Hide this view so the other text view will be centered vertically
837 mAccountSelectorName.setVisibility(View.GONE);
838 } else {
839 mAccountSelectorName.setVisibility(View.VISIBLE);
840 mAccountSelectorName.setText(accountInfo.first);
841 }
842 mAccountSelectorType.setText(accountInfo.second);
843
844 mAccountSelectorContainer.setContentDescription(
845 EditorUiUtils.getAccountInfoContentDescription(
846 accountInfo.first, accountInfo.second));
847
848 mAccountSelector.setOnClickListener(new View.OnClickListener() {
849 @Override
850 public void onClick(View v) {
851 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
852 final AccountsListAdapter adapter =
853 new AccountsListAdapter(getContext(),
854 AccountsListAdapter.AccountListFilter.ACCOUNTS_CONTACT_WRITABLE,
855 mPrimaryAccount);
856 popup.setWidth(mAccountSelectorContainer.getWidth());
857 popup.setAnchorView(mAccountSelectorContainer);
858 popup.setAdapter(adapter);
859 popup.setModal(true);
860 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
861 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
862 @Override
863 public void onItemClick(AdapterView<?> parent, View view, int position,
864 long id) {
865 UiClosables.closeQuietly(popup);
866 final AccountWithDataSet newAccount = adapter.getItem(position);
867 if (mListener != null && !mPrimaryAccount.equals(newAccount)) {
868 mListener.onRebindEditorsForNewContact(
869 mPrimaryRawContactDelta,
870 mPrimaryAccount,
871 newAccount);
872 }
873 }
874 });
875 popup.show();
876 }
877 });
878 }
879
Tingting Wang655ad1a2015-10-05 17:51:14 -0700880 private void addRawContactAccountSelector(final RawContactDeltaList rawContactDeltas) {
881 mRawContactContainer.setVisibility(View.VISIBLE);
882
883 final String accountsSummary = getRawContactsAccountsSummary(
884 getContext(), rawContactDeltas);
885 mRawContactSummary.setText(accountsSummary);
886 mRawContactContainer.setContentDescription(accountsSummary);
887 if (mPrimaryRawContactDelta != null) {
888 final AccountType primaryAccountType = mPrimaryRawContactDelta.getRawContactAccountType(
889 getContext());
890 mPrimaryAccountIcon.setImageDrawable(primaryAccountType.getDisplayIcon(getContext()));
891 }
892
893 mRawContactContainer.setOnClickListener(new View.OnClickListener() {
894 @Override
895 public void onClick(View v) {
896 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
897 final RawContactAccountListAdapter adapter =
898 new RawContactAccountListAdapter(getContext(), rawContactDeltas);
899 popup.setWidth(mRawContactContainer.getWidth());
900 popup.setAnchorView(mRawContactContainer);
901 popup.setAdapter(adapter);
902 popup.setModal(true);
903 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
904 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
905 @Override
906 public void onItemClick(AdapterView<?> parent, View view, int position,
907 long id) {
908 UiClosables.closeQuietly(popup);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700909
910 if (mListener != null) {
Tingting Wang91cee282015-10-07 13:48:17 -0700911 final long rawContactId = adapter.getItemId(position);
912 final Uri rawContactUri = ContentUris.withAppendedId(
913 ContactsContract.RawContacts.CONTENT_URI, rawContactId);
914 final RawContactDelta rawContactDelta = adapter.getItem(position);
915 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(
916 getContext());
917 final AccountType accountType = rawContactDelta.getAccountType(
918 accountTypes);
919 final boolean isReadOnly = !accountType.areContactsWritable();
920
921 mListener.onRawContactSelected(rawContactUri, rawContactId, isReadOnly);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700922 }
923 }
924 });
925 popup.show();
926 }
927 });
928 }
929
930 private static String getRawContactsAccountsSummary(
931 Context context, RawContactDeltaList rawContactDeltas) {
932 final Map<String, Integer> accountTypeNumber = new HashMap<>();
933 for (RawContactDelta rawContactDelta : rawContactDeltas) {
934 if (rawContactDelta.isVisible()) {
935 final AccountType accountType = rawContactDelta.getRawContactAccountType(context);
936 final String accountTypeLabel = accountType.getDisplayLabel(context).toString();
937 if (accountTypeNumber.containsKey(accountTypeLabel)) {
938 int number = accountTypeNumber.get(accountTypeLabel);
939 number++;
940 accountTypeNumber.put(accountTypeLabel, number);
941 } else {
942 accountTypeNumber.put(accountTypeLabel, 1);
943 }
944 }
945 }
946
947 final Set<String> linkedAccounts = new HashSet<>();
948 for (String accountTypeLabel : accountTypeNumber.keySet()) {
949 final String number = context.getResources().getQuantityString(
950 R.plurals.quickcontact_suggestion_account_type_number,
951 accountTypeNumber.get(accountTypeLabel),
952 accountTypeNumber.get(accountTypeLabel));
953 final String accountWithNumber = context.getResources().getString(
954 R.string.quickcontact_suggestion_account_type,
955 accountTypeLabel,
956 number);
957 linkedAccounts.add(accountWithNumber);
958 }
959 return TextUtils.join(",", linkedAccounts);
960 }
961
Walter Jang363d3fd2015-09-16 10:29:07 -0700962 private void addPhotoView() {
Walter Jang31a74ad2015-10-02 19:17:39 -0700963 // Get the kind section data and values delta that we will display in the photo view
Walter Jangcbd431d2015-10-06 13:07:30 -0700964 final KindSectionDataList kindSectionDataList =
965 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
966 final Pair<KindSectionData,ValuesDelta> photoToDisplay =
967 kindSectionDataList.getEntryToDisplay(mPhotoId);
968 if (photoToDisplay == null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700969 wlog("photo: no kind section data parsed");
Walter Jangcbd431d2015-10-06 13:07:30 -0700970 mPhotoView.setVisibility(View.GONE);
Walter Jang363d3fd2015-09-16 10:29:07 -0700971 return;
972 }
Walter Jang06f73a12015-06-17 11:15:48 -0700973
Walter Jang31a74ad2015-10-02 19:17:39 -0700974 // Set the photo view
Walter Jangcbd431d2015-10-06 13:07:30 -0700975 mPhotoView.setPhoto(photoToDisplay.second, mMaterialPalette);
Walter Jang31a74ad2015-10-02 19:17:39 -0700976
977 // Find the raw contact ID and values delta that will be written when the photo is edited
Walter Jangcbd431d2015-10-06 13:07:30 -0700978 final Pair<KindSectionData,ValuesDelta> photoToWrite = kindSectionDataList.getEntryToWrite(
979 mPrimaryAccount, mHasNewContact);
980 if (photoToWrite == null) {
Walter Jang31a74ad2015-10-02 19:17:39 -0700981 mPhotoView.setReadOnly(true);
982 return;
983 }
Walter Jang31a74ad2015-10-02 19:17:39 -0700984 mPhotoView.setReadOnly(false);
Walter Jangcbd431d2015-10-06 13:07:30 -0700985 mPhotoRawContactId = photoToWrite.first.getRawContactDelta().getRawContactId();
986 mPhotoValuesDelta = photoToWrite.second;
Walter Jang31a74ad2015-10-02 19:17:39 -0700987 }
988
Walter Jangf5dfea42015-09-16 12:30:36 -0700989 private void addKindSectionViews() {
990 // Sort the kinds
Walter Jangcbd431d2015-10-06 13:07:30 -0700991 final TreeSet<Map.Entry<String,KindSectionDataList>> entries =
Walter Jang192a01c2015-09-22 15:23:55 -0700992 new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
Walter Jangf5dfea42015-09-16 12:30:36 -0700993 entries.addAll(mKindSectionDataMap.entrySet());
Walter Jang3efae4a2015-02-18 11:12:00 -0800994
Walter Jangf5dfea42015-09-16 12:30:36 -0700995 vlog("kind: " + entries.size() + " kindSection(s)");
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700996 int i = -1;
Walter Jangcbd431d2015-10-06 13:07:30 -0700997 for (Map.Entry<String, KindSectionDataList> entry : entries) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700998 i++;
999
Walter Jangf5dfea42015-09-16 12:30:36 -07001000 final String mimeType = entry.getKey();
Walter Jangcbd431d2015-10-06 13:07:30 -07001001 final KindSectionDataList kindSectionDataList = entry.getValue();
Walter Jangab50e6f2015-06-15 08:57:22 -07001002
Walter Jangf5dfea42015-09-16 12:30:36 -07001003 // Ignore mime types that we've already handled
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001004 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
1005 vlog("kind: " + i + " " + mimeType + " dropped");
1006 continue;
1007 }
Walter Jangf5dfea42015-09-16 12:30:36 -07001008
Walter Jang192a01c2015-09-22 15:23:55 -07001009 if (kindSectionDataList != null && !kindSectionDataList.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001010 vlog("kind: " + i + " " + mimeType + ": " + kindSectionDataList.size() +
1011 " kindSectionData(s)");
1012
Walter Jangf5dfea42015-09-16 12:30:36 -07001013 final CompactKindSectionView kindSectionView = inflateKindSectionView(
1014 mKindSectionViews, kindSectionDataList, mimeType);
Walter Jangf5dfea42015-09-16 12:30:36 -07001015 mKindSectionViews.addView(kindSectionView);
Walter Jangf10ca152015-09-22 15:23:55 -07001016
1017 // Keep a pointer to all the KindSectionsViews for each mimeType
1018 getKindSectionViews(mimeType).add(kindSectionView);
Walter Jangcab3dce2015-02-09 17:48:03 -08001019 }
1020 }
1021 }
1022
Walter Jangf10ca152015-09-22 15:23:55 -07001023 private List<CompactKindSectionView> getKindSectionViews(String mimeType) {
1024 List<CompactKindSectionView> kindSectionViews = mKindSectionViewsMap.get(mimeType);
1025 if (kindSectionViews == null) {
1026 kindSectionViews = new ArrayList<>();
1027 mKindSectionViewsMap.put(mimeType, kindSectionViews);
1028 }
1029 return kindSectionViews;
1030 }
1031
Walter Jangf5dfea42015-09-16 12:30:36 -07001032 private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
Walter Jangcbd431d2015-10-06 13:07:30 -07001033 KindSectionDataList kindSectionDataList, String mimeType) {
Walter Jangf5dfea42015-09-16 12:30:36 -07001034 final CompactKindSectionView kindSectionView = (CompactKindSectionView)
1035 mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
1036 /* attachToRoot =*/ false);
1037
Walter Jang192a01c2015-09-22 15:23:55 -07001038 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jangf5dfea42015-09-16 12:30:36 -07001039 || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang192a01c2015-09-22 15:23:55 -07001040 // Phone numbers and email addresses are always displayed,
1041 // even if they are empty
Walter Jangf5dfea42015-09-16 12:30:36 -07001042 kindSectionView.setHideWhenEmpty(false);
1043 }
Walter Jangf5dfea42015-09-16 12:30:36 -07001044
Walter Jang192a01c2015-09-22 15:23:55 -07001045 // Since phone numbers and email addresses displayed even if they are empty,
1046 // they will be the only types you add new values to initially for new contacts
1047 kindSectionView.setShowOneEmptyEditor(true);
1048
1049 // Sort so the editors wind up in the order we want
1050 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
1051 Collections.sort(kindSectionDataList, new NameEditorComparator(getContext(),
1052 mPrimaryRawContactDelta));
1053 } else {
1054 Collections.sort(kindSectionDataList, new EditorComparator(getContext()));
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001055 }
1056
Walter Jang79658e12015-09-24 10:36:26 -07001057 kindSectionView.setState(kindSectionDataList, mViewIdGenerator, mListener);
Walter Jangf5dfea42015-09-16 12:30:36 -07001058
1059 return kindSectionView;
Walter Jangcab3dce2015-02-09 17:48:03 -08001060 }
1061
Walter Jang4f5594a2015-10-06 18:40:31 -07001062 private void maybeCopyPrimaryDisplayName() {
1063 if (TextUtils.isEmpty(mReadOnlyDisplayName)) return;
1064 final List<CompactKindSectionView> kindSectionViews
1065 = mKindSectionViewsMap.get(StructuredName.CONTENT_ITEM_TYPE);
1066 if (kindSectionViews.isEmpty()) return;
1067 final CompactKindSectionView primaryNameKindSectionView = kindSectionViews.get(0);
1068 if (primaryNameKindSectionView.isEmptyName()) {
1069 vlog("name: using read only display name as primary name");
1070 primaryNameKindSectionView.setName(mReadOnlyDisplayName);
1071 mPrimaryNameEditorView = primaryNameKindSectionView.getPrimaryNameEditorView();
1072 }
1073 }
1074
Walter Jangd6753152015-10-02 09:23:13 -07001075 private void showAllFields() {
Walter Jang79658e12015-09-24 10:36:26 -07001076 // Stop hiding empty editors and allow the user to enter values for all kinds now
1077 for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
1078 final CompactKindSectionView kindSectionView =
1079 (CompactKindSectionView) mKindSectionViews.getChildAt(i);
1080 kindSectionView.setHideWhenEmpty(false);
1081 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
1082 }
1083 mIsExpanded = true;
Walter Jang79658e12015-09-24 10:36:26 -07001084
Walter Jangf5dfea42015-09-16 12:30:36 -07001085 // Hide the more fields button
1086 mMoreFields.setVisibility(View.GONE);
Walter Jangcab3dce2015-02-09 17:48:03 -08001087 }
1088
Walter Jangbf63a6d2015-05-05 09:14:35 -07001089 private static void vlog(String message) {
1090 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1091 Log.v(TAG, message);
Walter Jangcab3dce2015-02-09 17:48:03 -08001092 }
1093 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001094
1095 private static void wlog(String message) {
1096 if (Log.isLoggable(TAG, Log.WARN)) {
1097 Log.w(TAG, message);
1098 }
1099 }
1100
1101 private static void elog(String message) {
1102 Log.e(TAG, message);
1103 }
Walter Jangcab3dce2015-02-09 17:48:03 -08001104}