blob: d023d35a7a419edcc502b71142047228115248e3 [file] [log] [blame]
Walter Jangcab3dce2015-02-09 17:48:03 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts.editor;
18
19import com.android.contacts.R;
20import com.android.contacts.common.model.AccountTypeManager;
21import com.android.contacts.common.model.RawContactDelta;
22import com.android.contacts.common.model.RawContactDeltaList;
Walter Jange720fde2015-02-17 10:54:14 -080023import com.android.contacts.common.model.RawContactModifier;
Walter Jangcab3dce2015-02-09 17:48:03 -080024import com.android.contacts.common.model.ValuesDelta;
25import com.android.contacts.common.model.account.AccountType;
Walter Jang2d3f31c2015-06-18 23:15:31 -070026import com.android.contacts.common.model.account.AccountWithDataSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080027import com.android.contacts.common.model.dataitem.DataKind;
Walter Jang708ea9e2015-09-10 15:42:05 -070028import com.android.contacts.common.util.AccountsListAdapter;
Walter Jangf46abd82015-02-20 16:52:04 -080029import com.android.contacts.common.util.MaterialColorMapUtils;
Walter Jang708ea9e2015-09-10 15:42:05 -070030import com.android.contacts.util.UiClosables;
Walter Jangcab3dce2015-02-09 17:48:03 -080031
Tingting Wang655ad1a2015-10-05 17:51:14 -070032import android.content.ContentUris;
Walter Jangcab3dce2015-02-09 17:48:03 -080033import android.content.Context;
Walter Jangf10ca152015-09-22 15:23:55 -070034import android.database.Cursor;
Walter Jang3efae4a2015-02-18 11:12:00 -080035import android.graphics.Bitmap;
Walter Jang41b3ea12015-03-09 17:30:06 -070036import android.net.Uri;
Walter Jang3f18d612015-10-07 16:01:05 -070037import android.os.Bundle;
Walter Jang79658e12015-09-24 10:36:26 -070038import android.os.Parcel;
39import android.os.Parcelable;
Tingting Wang655ad1a2015-10-05 17:51:14 -070040import android.provider.ContactsContract;
Walter Jangcab3dce2015-02-09 17:48:03 -080041import android.provider.ContactsContract.CommonDataKinds.Email;
Walter Jangf5dfea42015-09-16 12:30:36 -070042import android.provider.ContactsContract.CommonDataKinds.Event;
Walter Jangcab3dce2015-02-09 17:48:03 -080043import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Walter Jangf5dfea42015-09-16 12:30:36 -070044import android.provider.ContactsContract.CommonDataKinds.Im;
Walter Jang3efae4a2015-02-18 11:12:00 -080045import android.provider.ContactsContract.CommonDataKinds.Nickname;
Walter Jangf5dfea42015-09-16 12:30:36 -070046import android.provider.ContactsContract.CommonDataKinds.Note;
47import android.provider.ContactsContract.CommonDataKinds.Organization;
Walter Jangcab3dce2015-02-09 17:48:03 -080048import android.provider.ContactsContract.CommonDataKinds.Phone;
49import android.provider.ContactsContract.CommonDataKinds.Photo;
Walter Jangf5dfea42015-09-16 12:30:36 -070050import android.provider.ContactsContract.CommonDataKinds.Relation;
51import android.provider.ContactsContract.CommonDataKinds.SipAddress;
Walter Jangcab3dce2015-02-09 17:48:03 -080052import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Walter Jangf5dfea42015-09-16 12:30:36 -070053import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
54import android.provider.ContactsContract.CommonDataKinds.Website;
Walter Jangcab3dce2015-02-09 17:48:03 -080055import android.text.TextUtils;
56import android.util.AttributeSet;
57import android.util.Log;
Walter Jang2d3f31c2015-06-18 23:15:31 -070058import android.util.Pair;
Walter Jangcab3dce2015-02-09 17:48:03 -080059import android.view.LayoutInflater;
Walter Jangb1c87622015-02-13 17:51:38 -080060import android.view.View;
Walter Jangcab3dce2015-02-09 17:48:03 -080061import android.view.ViewGroup;
Walter Jang708ea9e2015-09-10 15:42:05 -070062import android.widget.AdapterView;
Tingting Wang655ad1a2015-10-05 17:51:14 -070063import android.widget.BaseAdapter;
64import android.widget.ImageView;
Walter Jangcab3dce2015-02-09 17:48:03 -080065import android.widget.LinearLayout;
Walter Jang708ea9e2015-09-10 15:42:05 -070066import android.widget.ListPopupWindow;
Walter Jang2d3f31c2015-06-18 23:15:31 -070067import android.widget.TextView;
Walter Jangcab3dce2015-02-09 17:48:03 -080068
Walter Jang708774a2015-10-16 09:32:06 -070069import java.io.FileNotFoundException;
Walter Jangcab3dce2015-02-09 17:48:03 -080070import java.util.ArrayList;
Walter Jangf5dfea42015-09-16 12:30:36 -070071import java.util.Arrays;
Walter Jang192a01c2015-09-22 15:23:55 -070072import java.util.Collections;
Walter Jangf5dfea42015-09-16 12:30:36 -070073import java.util.Comparator;
Walter Jang19d985f2015-07-01 13:36:27 -070074import java.util.HashMap;
Walter Jangcab3dce2015-02-09 17:48:03 -080075import java.util.List;
Walter Jang19d985f2015-07-01 13:36:27 -070076import java.util.Map;
Walter Jangf5dfea42015-09-16 12:30:36 -070077import java.util.TreeSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080078
79/**
Walter Jangf5dfea42015-09-16 12:30:36 -070080 * View to display information from multiple {@link RawContactDelta}s grouped together.
Walter Jangcab3dce2015-02-09 17:48:03 -080081 */
Walter Jangb6ca2722015-02-20 11:10:25 -080082public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
Walter Jangcab3dce2015-02-09 17:48:03 -080083
Walter Jang4f5594a2015-10-06 18:40:31 -070084 static final String TAG = "CompactEditorView";
Walter Jangcab3dce2015-02-09 17:48:03 -080085
Walter Jang192a01c2015-09-22 15:23:55 -070086 private static final KindSectionDataMapEntryComparator
87 KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR = new KindSectionDataMapEntryComparator();
Walter Jangf5dfea42015-09-16 12:30:36 -070088
Walter Jangb6ca2722015-02-20 11:10:25 -080089 /**
90 * Callbacks for hosts of {@link CompactRawContactsEditorView}s.
91 */
92 public interface Listener {
93
94 /**
Walter Jang151f3e62015-02-26 15:29:40 -080095 * Invoked when the structured name editor field has changed.
96 *
97 * @param rawContactId The raw contact ID from the underlying {@link RawContactDelta}.
98 * @param valuesDelta The values from the underlying {@link RawContactDelta}.
99 */
100 public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta);
Walter Jang708ea9e2015-09-10 15:42:05 -0700101
102 /**
103 * Invoked when the compact editor should rebind editors for a new account.
104 *
105 * @param oldState Old data being edited.
106 * @param oldAccount Old account associated with oldState.
107 * @param newAccount New account to be used.
108 */
109 public void onRebindEditorsForNewContact(RawContactDelta oldState,
110 AccountWithDataSet oldAccount, AccountWithDataSet newAccount);
Walter Jang79658e12015-09-24 10:36:26 -0700111
112 /**
113 * Invoked when no editors could be bound for the contact.
114 */
115 public void onBindEditorsFailed();
Walter Jangd6753152015-10-02 09:23:13 -0700116
117 /**
118 * Invoked after editors have been bound for the contact.
119 */
120 public void onEditorsBound();
Tingting Wang655ad1a2015-10-05 17:51:14 -0700121
122 /**
Tingting Wang5585c6e2015-10-14 11:19:58 -0700123 * Invoked when a rawcontact from linked contacts is selected in editor.
Tingting Wang655ad1a2015-10-05 17:51:14 -0700124 */
Tingting Wang91cee282015-10-07 13:48:17 -0700125 public void onRawContactSelected(Uri uri, long rawContactId, boolean isReadOnly);
Walter Jang3f18d612015-10-07 16:01:05 -0700126
127 /**
128 * Returns the map of raw contact IDs to newly taken or selected photos that have not
129 * yet been saved to CP2.
130 */
131 public Bundle getUpdatedPhotos();
Tingting Wang655ad1a2015-10-05 17:51:14 -0700132 }
133
134 /**
135 * Used to list the account info for the given raw contacts list.
136 */
137 private static final class RawContactAccountListAdapter extends BaseAdapter {
138 private final LayoutInflater mInflater;
139 private final Context mContext;
140 private final RawContactDeltaList mRawContactDeltas;
141
142 public RawContactAccountListAdapter(Context context, RawContactDeltaList rawContactDeltas) {
143 mContext = context;
144 mRawContactDeltas = new RawContactDeltaList();
145 for (RawContactDelta rawContactDelta : rawContactDeltas) {
Walter Jang23709542015-10-22 12:50:58 -0700146 if (rawContactDelta.isVisible() && rawContactDelta.getRawContactId() > 0) {
Tingting Wang655ad1a2015-10-05 17:51:14 -0700147 mRawContactDeltas.add(rawContactDelta);
148 }
149 }
150 mInflater = LayoutInflater.from(context);
151 }
152
153 @Override
154 public View getView(int position, View convertView, ViewGroup parent) {
155 final View resultView = convertView != null ? convertView
156 : mInflater.inflate(R.layout.account_selector_list_item, parent, false);
157
158 final RawContactDelta rawContactDelta = mRawContactDeltas.get(position);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700159
160 final TextView text1 = (TextView) resultView.findViewById(android.R.id.text1);
Walter Jang9488a762015-10-16 13:42:48 -0700161 final AccountType accountType = rawContactDelta.getRawContactAccountType(mContext);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700162 text1.setText(accountType.getDisplayLabel(mContext));
163
Tingting Wang655ad1a2015-10-05 17:51:14 -0700164 final TextView text2 = (TextView) resultView.findViewById(android.R.id.text2);
Walter Jang9488a762015-10-16 13:42:48 -0700165 final String accountName = rawContactDelta.getAccountName();
Tingting Wanga0b69402015-10-13 14:27:58 -0700166 if (TextUtils.isEmpty(accountName)) {
167 text2.setVisibility(View.GONE);
168 } else {
Walter Jang9488a762015-10-16 13:42:48 -0700169 // Truncate email addresses in the middle so we don't lose the domain
Tingting Wanga0b69402015-10-13 14:27:58 -0700170 text2.setText(accountName);
171 text2.setEllipsize(TextUtils.TruncateAt.MIDDLE);
172 }
Tingting Wang655ad1a2015-10-05 17:51:14 -0700173
174 final ImageView icon = (ImageView) resultView.findViewById(android.R.id.icon);
175 icon.setImageDrawable(accountType.getDisplayIcon(mContext));
176
177 return resultView;
178 }
179
180 @Override
181 public int getCount() {
182 return mRawContactDeltas.size();
183 }
184
185 @Override
186 public RawContactDelta getItem(int position) {
187 return mRawContactDeltas.get(position);
188 }
189
190 @Override
191 public long getItemId(int position) {
192 return getItem(position).getRawContactId();
193 }
Walter Jang151f3e62015-02-26 15:29:40 -0800194 }
195
Walter Jang192a01c2015-09-22 15:23:55 -0700196 /** Used to sort entire kind sections. */
197 private static final class KindSectionDataMapEntryComparator implements
Walter Jangcbd431d2015-10-06 13:07:30 -0700198 Comparator<Map.Entry<String,KindSectionDataList>> {
Walter Jangf5dfea42015-09-16 12:30:36 -0700199
Walter Jang192a01c2015-09-22 15:23:55 -0700200 final MimeTypeComparator mMimeTypeComparator = new MimeTypeComparator();
201
202 @Override
Walter Jangcbd431d2015-10-06 13:07:30 -0700203 public int compare(Map.Entry<String, KindSectionDataList> entry1,
204 Map.Entry<String, KindSectionDataList> entry2) {
Walter Jang192a01c2015-09-22 15:23:55 -0700205 if (entry1 == entry2) return 0;
206 if (entry1 == null) return -1;
207 if (entry2 == null) return 1;
208
209 final String mimeType1 = entry1.getKey();
210 final String mimeType2 = entry2.getKey();
211
212 return mMimeTypeComparator.compare(mimeType1, mimeType2);
213 }
214 }
215
216 /**
217 * Sorts kinds roughly the same as quick contacts; we diverge in the following ways:
218 * <ol>
219 * <li>All names are together at the top.</li>
220 * <li>IM is moved up after addresses</li>
221 * <li>SIP addresses are moved to below phone numbers</li>
Walter Jang79658e12015-09-24 10:36:26 -0700222 * <li>Group membership is placed at the end</li>
Walter Jang192a01c2015-09-22 15:23:55 -0700223 * </ol>
224 */
225 private static final class MimeTypeComparator implements Comparator<String> {
226
Walter Jangf5dfea42015-09-16 12:30:36 -0700227 private static final List<String> MIME_TYPE_ORDER = Arrays.asList(new String[] {
228 StructuredName.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700229 Nickname.CONTENT_ITEM_TYPE,
Walter Jang2ae21752015-09-25 11:25:38 -0700230 Organization.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700231 Phone.CONTENT_ITEM_TYPE,
232 SipAddress.CONTENT_ITEM_TYPE,
233 Email.CONTENT_ITEM_TYPE,
234 StructuredPostal.CONTENT_ITEM_TYPE,
235 Im.CONTENT_ITEM_TYPE,
236 Website.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700237 Event.CONTENT_ITEM_TYPE,
238 Relation.CONTENT_ITEM_TYPE,
Walter Jangf10ca152015-09-22 15:23:55 -0700239 Note.CONTENT_ITEM_TYPE,
240 GroupMembership.CONTENT_ITEM_TYPE
Walter Jangf5dfea42015-09-16 12:30:36 -0700241 });
242
243 @Override
Walter Jang192a01c2015-09-22 15:23:55 -0700244 public int compare(String mimeType1, String mimeType2) {
245 if (mimeType1 == mimeType2) return 0;
246 if (mimeType1 == null) return -1;
247 if (mimeType2 == null) return 1;
Walter Jangf5dfea42015-09-16 12:30:36 -0700248
249 int index1 = MIME_TYPE_ORDER.indexOf(mimeType1);
250 int index2 = MIME_TYPE_ORDER.indexOf(mimeType2);
251
252 // Fallback to alphabetical ordering of the mime type if both are not found
253 if (index1 < 0 && index2 < 0) return mimeType1.compareTo(mimeType2);
254 if (index1 < 0) return 1;
255 if (index2 < 0) return -1;
256
257 return index1 < index2 ? -1 : 1;
258 }
259 }
260
Walter Jang192a01c2015-09-22 15:23:55 -0700261 /**
262 * Sorts primary accounts and google account types before others.
263 */
264 private static final class EditorComparator implements Comparator<KindSectionData> {
265
266 private RawContactDeltaComparator mRawContactDeltaComparator;
267
268 private EditorComparator(Context context) {
269 mRawContactDeltaComparator = new RawContactDeltaComparator(context);
270 }
271
272 @Override
273 public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
274 if (kindSectionData1 == kindSectionData2) return 0;
275 if (kindSectionData1 == null) return -1;
276 if (kindSectionData2 == null) return 1;
277
278 final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
279 final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
280
281 if (rawContactDelta1 == rawContactDelta2) return 0;
282 if (rawContactDelta1 == null) return -1;
283 if (rawContactDelta2 == null) return 1;
284
285 return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
286 }
287 }
288
Walter Jang79658e12015-09-24 10:36:26 -0700289 public static class SavedState extends BaseSavedState {
290
291 public static final Parcelable.Creator<SavedState> CREATOR =
292 new Parcelable.Creator<SavedState>() {
293 public SavedState createFromParcel(Parcel in) {
294 return new SavedState(in);
295 }
296 public SavedState[] newArray(int size) {
297 return new SavedState[size];
298 }
299 };
300
301 private boolean mIsExpanded;
302
303 public SavedState(Parcelable superState) {
304 super(superState);
305 }
306
307 private SavedState(Parcel in) {
308 super(in);
309 mIsExpanded = in.readInt() != 0;
310 }
311
312 @Override
313 public void writeToParcel(Parcel out, int flags) {
314 super.writeToParcel(out, flags);
315 out.writeInt(mIsExpanded ? 1 : 0);
316 }
317 }
318
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700319 private CompactRawContactsEditorView.Listener mListener;
Walter Jangb6ca2722015-02-20 11:10:25 -0800320
Walter Jangcab3dce2015-02-09 17:48:03 -0800321 private AccountTypeManager mAccountTypeManager;
322 private LayoutInflater mLayoutInflater;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800323
Walter Jangcab3dce2015-02-09 17:48:03 -0800324 private ViewIdGenerator mViewIdGenerator;
Walter Jangf46abd82015-02-20 16:52:04 -0800325 private MaterialColorMapUtils.MaterialPalette mMaterialPalette;
Walter Jang82acd422015-10-17 14:01:27 -0700326 private long mPhotoId = -1;
Walter Jang708ea9e2015-09-10 15:42:05 -0700327 private boolean mHasNewContact;
328 private boolean mIsUserProfile;
329 private AccountWithDataSet mPrimaryAccount;
Walter Jang9a552372016-08-24 11:51:05 -0700330 private RawContactDeltaList mRawContactDeltas;
331 private long mRawContactIdToDisplayAlone = -1;
332 private boolean mRawContactDisplayAloneIsReadOnly;
333 private boolean mIsEditingReadOnlyRawContactWithNewContact;
Walter Jangcbd431d2015-10-06 13:07:30 -0700334 private Map<String,KindSectionDataList> mKindSectionDataMap = new HashMap<>();
Walter Jangcab3dce2015-02-09 17:48:03 -0800335
Walter Jang708ea9e2015-09-10 15:42:05 -0700336 // Account header
337 private View mAccountHeaderContainer;
338 private TextView mAccountHeaderType;
339 private TextView mAccountHeaderName;
Tingting Wang91cee282015-10-07 13:48:17 -0700340 private ImageView mAccountHeaderIcon;
Walter Jang708ea9e2015-09-10 15:42:05 -0700341
342 // Account selector
343 private View mAccountSelectorContainer;
344 private View mAccountSelector;
345 private TextView mAccountSelectorType;
346 private TextView mAccountSelectorName;
347
Tingting Wang655ad1a2015-10-05 17:51:14 -0700348 // Raw contacts selector
349 private View mRawContactContainer;
350 private TextView mRawContactSummary;
Tingting Wang655ad1a2015-10-05 17:51:14 -0700351
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700352 private CompactPhotoEditorView mPhotoView;
Walter Jangf5dfea42015-09-16 12:30:36 -0700353 private ViewGroup mKindSectionViews;
Walter Jangf10ca152015-09-22 15:23:55 -0700354 private Map<String,List<CompactKindSectionView>> mKindSectionViewsMap = new HashMap<>();
Walter Jangb6ca2722015-02-20 11:10:25 -0800355 private View mMoreFields;
Walter Jangcab3dce2015-02-09 17:48:03 -0800356
Walter Jang79658e12015-09-24 10:36:26 -0700357 private boolean mIsExpanded;
Walter Jangcbd431d2015-10-06 13:07:30 -0700358
Walter Jang3efae4a2015-02-18 11:12:00 -0800359 private long mPhotoRawContactId;
Walter Jang31a74ad2015-10-02 19:17:39 -0700360 private ValuesDelta mPhotoValuesDelta;
Walter Jang82acd422015-10-17 14:01:27 -0700361
362 private Pair<KindSectionData, ValuesDelta> mPrimaryNameKindSectionData;
Walter Jang3efae4a2015-02-18 11:12:00 -0800363
Walter Jangcab3dce2015-02-09 17:48:03 -0800364 public CompactRawContactsEditorView(Context context) {
365 super(context);
366 }
367
368 public CompactRawContactsEditorView(Context context, AttributeSet attrs) {
369 super(context, attrs);
370 }
371
Walter Jangb6ca2722015-02-20 11:10:25 -0800372 /**
373 * Sets the receiver for {@link CompactRawContactsEditorView} callbacks.
374 */
375 public void setListener(Listener listener) {
376 mListener = listener;
377 }
378
Walter Jangcab3dce2015-02-09 17:48:03 -0800379 @Override
380 protected void onFinishInflate() {
381 super.onFinishInflate();
382
383 mAccountTypeManager = AccountTypeManager.getInstance(getContext());
384 mLayoutInflater = (LayoutInflater)
385 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
386
Walter Jang708ea9e2015-09-10 15:42:05 -0700387 // Account header
388 mAccountHeaderContainer = findViewById(R.id.account_container);
389 mAccountHeaderType = (TextView) findViewById(R.id.account_type);
390 mAccountHeaderName = (TextView) findViewById(R.id.account_name);
Tingting Wang91cee282015-10-07 13:48:17 -0700391 mAccountHeaderIcon = (ImageView) findViewById(R.id.account_type_icon);
Walter Jang708ea9e2015-09-10 15:42:05 -0700392
393 // Account selector
394 mAccountSelectorContainer = findViewById(R.id.account_selector_container);
395 mAccountSelector = findViewById(R.id.account);
396 mAccountSelectorType = (TextView) findViewById(R.id.account_type_selector);
397 mAccountSelectorName = (TextView) findViewById(R.id.account_name_selector);
398
Tingting Wang655ad1a2015-10-05 17:51:14 -0700399 // Raw contacts selector
400 mRawContactContainer = findViewById(R.id.all_rawcontacts_accounts_container);
401 mRawContactSummary = (TextView) findViewById(R.id.rawcontacts_accounts_summary);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700402
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700403 mPhotoView = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
Walter Jangf5dfea42015-09-16 12:30:36 -0700404 mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
Walter Jangb6ca2722015-02-20 11:10:25 -0800405 mMoreFields = findViewById(R.id.more_fields);
406 mMoreFields.setOnClickListener(this);
407 }
408
Walter Jangb6ca2722015-02-20 11:10:25 -0800409 @Override
410 public void onClick(View view) {
Walter Jangf5dfea42015-09-16 12:30:36 -0700411 if (view.getId() == R.id.more_fields) {
Walter Jangd6753152015-10-02 09:23:13 -0700412 showAllFields();
Walter Jangb6ca2722015-02-20 11:10:25 -0800413 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800414 }
415
416 @Override
417 public void setEnabled(boolean enabled) {
418 super.setEnabled(enabled);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700419 final int childCount = mKindSectionViews.getChildCount();
420 for (int i = 0; i < childCount; i++) {
421 mKindSectionViews.getChildAt(i).setEnabled(enabled);
Walter Jangcab3dce2015-02-09 17:48:03 -0800422 }
423 }
424
Walter Jang79658e12015-09-24 10:36:26 -0700425 @Override
426 public Parcelable onSaveInstanceState() {
427 final Parcelable superState = super.onSaveInstanceState();
428 final SavedState savedState = new SavedState(superState);
429 savedState.mIsExpanded = mIsExpanded;
430 return savedState;
431 }
432
433 @Override
434 public void onRestoreInstanceState(Parcelable state) {
435 if(!(state instanceof SavedState)) {
436 super.onRestoreInstanceState(state);
437 return;
438 }
439 final SavedState savedState = (SavedState) state;
440 super.onRestoreInstanceState(savedState.getSuperState());
441 mIsExpanded = savedState.mIsExpanded;
Walter Jangd6753152015-10-02 09:23:13 -0700442 if (mIsExpanded) {
443 showAllFields();
Walter Jang79658e12015-09-24 10:36:26 -0700444 }
445 }
446
Walter Jang3efae4a2015-02-18 11:12:00 -0800447 /**
Walter Jang31a74ad2015-10-02 19:17:39 -0700448 * Pass through to {@link CompactPhotoEditorView#setListener}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800449 */
Walter Jang31a74ad2015-10-02 19:17:39 -0700450 public void setPhotoListener(CompactPhotoEditorView.Listener listener) {
451 mPhotoView.setListener(listener);
Walter Jang3efae4a2015-02-18 11:12:00 -0800452 }
453
Walter Jang31a74ad2015-10-02 19:17:39 -0700454 public void removePhoto() {
Walter Jang45b86d52015-10-15 15:23:16 -0700455 mPhotoValuesDelta.setFromTemplate(true);
Walter Jang31a74ad2015-10-02 19:17:39 -0700456 mPhotoValuesDelta.put(Photo.PHOTO, (byte[]) null);
457
458 mPhotoView.removePhoto();
Walter Jang3efae4a2015-02-18 11:12:00 -0800459 }
460
461 /**
Walter Jang41b3ea12015-03-09 17:30:06 -0700462 * Pass through to {@link CompactPhotoEditorView#setFullSizedPhoto(Uri)}.
463 */
464 public void setFullSizePhoto(Uri photoUri) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700465 mPhotoView.setFullSizedPhoto(photoUri);
Walter Jang41b3ea12015-03-09 17:30:06 -0700466 }
467
Walter Jang31a74ad2015-10-02 19:17:39 -0700468 public void updatePhoto(Uri photoUri) {
Walter Jang45b86d52015-10-15 15:23:16 -0700469 mPhotoValuesDelta.setFromTemplate(false);
Wenyi Wangb8ab8612015-10-27 11:18:49 -0700470 // Unset primary for all photos
Wenyi Wang9086fb92015-11-16 09:58:20 -0800471 unsetSuperPrimaryFromAllPhotos();
Wenyi Wangb8ab8612015-10-27 11:18:49 -0700472 // Mark the currently displayed photo as primary
473 mPhotoValuesDelta.setSuperPrimary(true);
Walter Jang31a74ad2015-10-02 19:17:39 -0700474
Wenyi Wang4c9cf3a2015-10-15 17:58:23 -0700475 // Even though high-res photos cannot be saved by passing them via
476 // an EntityDeltaList (since they cause the Bundle size limit to be
477 // exceeded), we still pass a low-res thumbnail. This simplifies
478 // code all over the place, because we don't have to test whether
479 // there is a change in EITHER the delta-list OR a changed photo...
480 // this way, there is always a change in the delta-list.
481 try {
482 final byte[] bytes = EditorUiUtils.getCompressedThumbnailBitmapBytes(
483 getContext(), photoUri);
484 if (bytes != null) {
485 mPhotoValuesDelta.setPhoto(bytes);
486 }
487 } catch (FileNotFoundException e) {
488 elog("Failed to get bitmap from photo Uri");
489 }
490
Walter Jang31a74ad2015-10-02 19:17:39 -0700491 mPhotoView.setFullSizedPhoto(photoUri);
492 }
493
Wenyi Wang9086fb92015-11-16 09:58:20 -0800494 private void unsetSuperPrimaryFromAllPhotos() {
Walter Jang3f18d612015-10-07 16:01:05 -0700495 final List<KindSectionData> kindSectionDataList =
496 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
497 for (KindSectionData kindSectionData : kindSectionDataList) {
Wenyi Wang9086fb92015-11-16 09:58:20 -0800498 for (ValuesDelta valuesDelta : kindSectionData.getNonEmptyValuesDeltas()) {
499 valuesDelta.setSuperPrimary(false);
Walter Jang3f18d612015-10-07 16:01:05 -0700500 }
501 }
502 }
503
Walter Jang41b3ea12015-03-09 17:30:06 -0700504 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800505 * Pass through to {@link CompactPhotoEditorView#isWritablePhotoSet}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800506 */
507 public boolean isWritablePhotoSet() {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700508 return mPhotoView.isWritablePhotoSet();
Walter Jang3efae4a2015-02-18 11:12:00 -0800509 }
510
511 /**
Walter Jang3efae4a2015-02-18 11:12:00 -0800512 * Get the raw contact ID for the CompactHeaderView photo.
513 */
Walter Jang3efae4a2015-02-18 11:12:00 -0800514 public long getPhotoRawContactId() {
515 return mPhotoRawContactId;
516 }
517
Walter Jang4f5594a2015-10-06 18:40:31 -0700518 public StructuredNameEditorView getPrimaryNameEditorView() {
Walter Jange3945952015-10-27 12:44:54 -0700519 final CompactKindSectionView primaryNameKindSectionView = getPrimaryNameKindSectionView();
520 return primaryNameKindSectionView == null
521 ? null : primaryNameKindSectionView.getPrimaryNameEditorView();
Walter Jang4f5594a2015-10-06 18:40:31 -0700522 }
523
Walter Jang31a74ad2015-10-02 19:17:39 -0700524 /**
525 * Returns a data holder for every non-default/non-empty photo from each raw contact, whether
526 * the raw contact is writable or not.
527 */
528 public ArrayList<CompactPhotoSelectionFragment.Photo> getPhotos() {
529 final ArrayList<CompactPhotoSelectionFragment.Photo> photos = new ArrayList<>();
530
Walter Jang3f18d612015-10-07 16:01:05 -0700531 final Bundle updatedPhotos = mListener == null ? null : mListener.getUpdatedPhotos();
532
Walter Jang31a74ad2015-10-02 19:17:39 -0700533 final List<KindSectionData> kindSectionDataList =
534 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
535 for (int i = 0; i < kindSectionDataList.size(); i++) {
536 final KindSectionData kindSectionData = kindSectionDataList.get(i);
537 final AccountType accountType = kindSectionData.getAccountType();
Walter Jang228e02f2015-11-13 09:11:01 -0800538 final List<ValuesDelta> valuesDeltas = kindSectionData.getNonEmptyValuesDeltas();
539 if (valuesDeltas.isEmpty()) continue;
540 for (int j = 0; j < valuesDeltas.size(); j++) {
541 final ValuesDelta valuesDelta = valuesDeltas.get(j);
Walter Jang31a74ad2015-10-02 19:17:39 -0700542 final Bitmap bitmap = EditorUiUtils.getPhotoBitmap(valuesDelta);
543 if (bitmap == null) continue;
544
545 final CompactPhotoSelectionFragment.Photo photo =
546 new CompactPhotoSelectionFragment.Photo();
547 photo.titleRes = accountType.titleRes;
548 photo.iconRes = accountType.iconRes;
549 photo.syncAdapterPackageName = accountType.syncAdapterPackageName;
550 photo.valuesDelta = valuesDelta;
551 photo.primary = valuesDelta.isSuperPrimary();
552 photo.kindSectionDataListIndex = i;
553 photo.valuesDeltaListIndex = j;
Walter Jangda258ff2015-11-17 11:06:02 -0800554 photo.photoId = valuesDelta.getId();
Walter Jang3f18d612015-10-07 16:01:05 -0700555
556 if (updatedPhotos != null) {
557 photo.updatedPhotoUri = (Uri) updatedPhotos.get(String.valueOf(
558 kindSectionData.getRawContactDelta().getRawContactId()));
559 }
560
Wenyi Wangd375c352016-03-28 15:12:45 -0700561 final CharSequence accountTypeLabel = accountType.getDisplayLabel(getContext());
562 photo.accountType = accountTypeLabel == null ? "" : accountTypeLabel.toString();
563 final String accountName = kindSectionData.getRawContactDelta().getAccountName();
564 photo.accountName = accountName == null ? "" : accountName;
Wenyi Wangab360172015-12-15 10:35:58 -0800565
Walter Jang31a74ad2015-10-02 19:17:39 -0700566 photos.add(photo);
567 }
568 }
569
570 return photos;
571 }
572
573 /**
574 * Marks the raw contact photo given as primary for the aggregate contact and updates the
575 * UI.
576 */
577 public void setPrimaryPhoto(CompactPhotoSelectionFragment.Photo photo) {
Walter Jang31a74ad2015-10-02 19:17:39 -0700578 // Find the values delta to mark as primary
Walter Jang3f18d612015-10-07 16:01:05 -0700579 final KindSectionDataList kindSectionDataList =
580 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
Walter Jang31a74ad2015-10-02 19:17:39 -0700581 if (photo.kindSectionDataListIndex < 0
582 || photo.kindSectionDataListIndex >= kindSectionDataList.size()) {
583 wlog("Invalid kind section data list index");
584 return;
585 }
586 final KindSectionData kindSectionData =
587 kindSectionDataList.get(photo.kindSectionDataListIndex);
Walter Jang228e02f2015-11-13 09:11:01 -0800588 final List<ValuesDelta> valuesDeltaList = kindSectionData.getNonEmptyValuesDeltas();
Walter Jang31a74ad2015-10-02 19:17:39 -0700589 if (photo.valuesDeltaListIndex >= valuesDeltaList.size()) {
590 wlog("Invalid values delta list index");
591 return;
592 }
Walter Jangda258ff2015-11-17 11:06:02 -0800593
594 // Update values delta
Walter Jang31a74ad2015-10-02 19:17:39 -0700595 final ValuesDelta valuesDelta = valuesDeltaList.get(photo.valuesDeltaListIndex);
596 valuesDelta.setFromTemplate(false);
Wenyi Wang9086fb92015-11-16 09:58:20 -0800597 unsetSuperPrimaryFromAllPhotos();
Wenyi Wangb8ab8612015-10-27 11:18:49 -0700598 valuesDelta.setSuperPrimary(true);
Walter Jangda258ff2015-11-17 11:06:02 -0800599
Walter Jang31a74ad2015-10-02 19:17:39 -0700600 // Update the UI
601 mPhotoView.setPhoto(valuesDelta, mMaterialPalette);
602 }
603
Walter Jangd35e5ef2015-02-24 09:18:16 -0800604 public View getAggregationAnchorView() {
Walter Jangf10ca152015-09-22 15:23:55 -0700605 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
Walter Jang192a01c2015-09-22 15:23:55 -0700606 StructuredName.CONTENT_ITEM_TYPE);
Walter Jangf10ca152015-09-22 15:23:55 -0700607 if (!kindSectionViews.isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700608 return mKindSectionViews.getChildAt(0).findViewById(R.id.anchor_view);
Walter Jangd35e5ef2015-02-24 09:18:16 -0800609 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700610 return null;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800611 }
612
Walter Jangf10ca152015-09-22 15:23:55 -0700613 public void setGroupMetaData(Cursor groupMetaData) {
614 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
615 GroupMembership.CONTENT_ITEM_TYPE);
616 for (CompactKindSectionView kindSectionView : kindSectionViews) {
617 kindSectionView.setGroupMetaData(groupMetaData);
Walter Jangd6753152015-10-02 09:23:13 -0700618 if (mIsExpanded) {
619 kindSectionView.setHideWhenEmpty(false);
620 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
621 }
Walter Jangf10ca152015-09-22 15:23:55 -0700622 }
623 }
624
Walter Jangf46abd82015-02-20 16:52:04 -0800625 public void setState(RawContactDeltaList rawContactDeltas,
Walter Jang06f73a12015-06-17 11:15:48 -0700626 MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
Walter Jange3945952015-10-27 12:44:54 -0700627 long photoId, boolean hasNewContact, boolean isUserProfile,
Walter Jang9a552372016-08-24 11:51:05 -0700628 AccountWithDataSet primaryAccount, long rawContactIdToDisplayAlone,
629 boolean rawContactDisplayAloneIsReadOnly,
630 boolean isEditingReadOnlyRawContactWithNewContact) {
631 mRawContactDeltas = rawContactDeltas;
632 mRawContactIdToDisplayAlone = rawContactIdToDisplayAlone;
633 mRawContactDisplayAloneIsReadOnly = rawContactDisplayAloneIsReadOnly;
634 mIsEditingReadOnlyRawContactWithNewContact = isEditingReadOnlyRawContactWithNewContact;
635
Walter Jang363d3fd2015-09-16 10:29:07 -0700636 mKindSectionDataMap.clear();
Walter Jangf5dfea42015-09-16 12:30:36 -0700637 mKindSectionViews.removeAllViews();
Walter Jang363d3fd2015-09-16 10:29:07 -0700638 mMoreFields.setVisibility(View.VISIBLE);
Walter Jangcab3dce2015-02-09 17:48:03 -0800639
Walter Jangf46abd82015-02-20 16:52:04 -0800640 mMaterialPalette = materialPalette;
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700641 mViewIdGenerator = viewIdGenerator;
Walter Jang708ea9e2015-09-10 15:42:05 -0700642 mPhotoId = photoId;
Tingting Wangf695eb32015-10-15 18:45:15 -0700643
Walter Jang708ea9e2015-09-10 15:42:05 -0700644 mHasNewContact = hasNewContact;
645 mIsUserProfile = isUserProfile;
646 mPrimaryAccount = primaryAccount;
647 if (mPrimaryAccount == null) {
648 mPrimaryAccount = ContactEditorUtils.getInstance(getContext()).getDefaultAccount();
649 }
650 vlog("state: primary " + mPrimaryAccount);
Walter Jangcab3dce2015-02-09 17:48:03 -0800651
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700652 // Parse the given raw contact deltas
653 if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
654 elog("No raw contact deltas");
Walter Jang79658e12015-09-24 10:36:26 -0700655 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jangf5dfea42015-09-16 12:30:36 -0700656 return;
657 }
Walter Jangcbd431d2015-10-06 13:07:30 -0700658 parseRawContactDeltas(rawContactDeltas);
Walter Jang79658e12015-09-24 10:36:26 -0700659 if (mKindSectionDataMap.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700660 elog("No kind section data parsed from RawContactDelta(s)");
Walter Jang79658e12015-09-24 10:36:26 -0700661 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700662 return;
663 }
Walter Jang82acd422015-10-17 14:01:27 -0700664
665 // Get the primary name kind section data
666 mPrimaryNameKindSectionData = mKindSectionDataMap.get(StructuredName.CONTENT_ITEM_TYPE)
667 .getEntryToWrite(/* id =*/ -1, mPrimaryAccount, mIsUserProfile);
668 if (mPrimaryNameKindSectionData != null) {
669 // Ensure that a structured name and photo exists
670 final RawContactDelta rawContactDelta =
671 mPrimaryNameKindSectionData.first.getRawContactDelta();
672 RawContactModifier.ensureKindExists(
673 rawContactDelta,
674 rawContactDelta.getAccountType(mAccountTypeManager),
Walter Jangcbd431d2015-10-06 13:07:30 -0700675 StructuredName.CONTENT_ITEM_TYPE);
Walter Jang82acd422015-10-17 14:01:27 -0700676 RawContactModifier.ensureKindExists(
677 rawContactDelta,
678 rawContactDelta.getAccountType(mAccountTypeManager),
Walter Jangcbd431d2015-10-06 13:07:30 -0700679 Photo.CONTENT_ITEM_TYPE);
680 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700681
682 // Setup the view
Walter Jang363d3fd2015-09-16 10:29:07 -0700683 addPhotoView();
Walter Jang9a552372016-08-24 11:51:05 -0700684 if (isSingleReadOnlyRawContact()) {
685 // We're want to display the inputs fields for a single read only raw contact
686 addReadOnlyRawContactEditorViews();
687 // Hide the "More fields" link
688 mMoreFields.setVisibility(View.GONE);
689 } else if (mIsEditingReadOnlyRawContactWithNewContact) {
690 // A new writable raw contact was created and joined with the read only contact
691 // that the user is trying to edit.
692 setupCompactEditorNormally();
Walter Jange3945952015-10-27 12:44:54 -0700693
Walter Jang9a552372016-08-24 11:51:05 -0700694 // TODO: Hide the raw contact selector since it will just contain the read-only raw
695 // contact and clicking that will just open the exact same editor. When we clean up
696 // the whole account header, selector, and raw contact selector mess, we can prevent
697 // the selector from being displayed in a less hacky way.
698 mRawContactContainer.setVisibility(View.GONE);
699 } else if (mRawContactDeltas.size() > 1) {
700 // We're editing an aggregate composed of more than one writable raw contacts
Walter Jangd6753152015-10-02 09:23:13 -0700701
Walter Jang9a552372016-08-24 11:51:05 -0700702 // TODO: Don't render any input fields. Eventually we will show a list of account
703 // types and names but for now just show the account selector and hide the "More fields"
704 // link.
705 addAccountInfo(rawContactDeltas);
706 mMoreFields.setVisibility(View.GONE);
707 } else {
708 setupCompactEditorNormally();
709 }
Walter Jangd6753152015-10-02 09:23:13 -0700710 if (mListener != null) mListener.onEditorsBound();
Walter Jangcab3dce2015-02-09 17:48:03 -0800711 }
712
Walter Jang9a552372016-08-24 11:51:05 -0700713 private void setupCompactEditorNormally() {
714 addAccountInfo(mRawContactDeltas);
715 addKindSectionViews();
716 if (mIsExpanded) showAllFields();
717 }
718
719 private boolean isSingleReadOnlyRawContact() {
720 return mRawContactDeltas.size() == 1
721 && mRawContactDeltas.get(0).getRawContactId() == mRawContactIdToDisplayAlone
722 && mRawContactDisplayAloneIsReadOnly;
723 }
724
Walter Jangcbd431d2015-10-06 13:07:30 -0700725 private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700726 // Build the kind section data list map
Walter Jang192a01c2015-09-22 15:23:55 -0700727 vlog("parse: " + rawContactDeltas.size() + " rawContactDelta(s)");
728 for (int j = 0; j < rawContactDeltas.size(); j++) {
729 final RawContactDelta rawContactDelta = rawContactDeltas.get(j);
730 vlog("parse: " + j + " rawContactDelta" + rawContactDelta);
Walter Jang363d3fd2015-09-16 10:29:07 -0700731 if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
Walter Jangcab3dce2015-02-09 17:48:03 -0800732 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jang363d3fd2015-09-16 10:29:07 -0700733 if (accountType == null) continue;
734 final List<DataKind> dataKinds = accountType.getSortedDataKinds();
735 final int dataKindSize = dataKinds == null ? 0 : dataKinds.size();
736 vlog("parse: " + dataKindSize + " dataKinds(s)");
737 for (int i = 0; i < dataKindSize; i++) {
738 final DataKind dataKind = dataKinds.get(i);
Walter Jang9a552372016-08-24 11:51:05 -0700739 if (dataKind == null) {
740 vlog("parse: " + i + " " + dataKind.mimeType + " dropped null data kind");
Walter Jang192a01c2015-09-22 15:23:55 -0700741 continue;
742 }
743 final String mimeType = dataKind.mimeType;
744
745 // Skip psuedo mime types
746 if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)
747 || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
748 vlog("parse: " + i + " " + dataKind.mimeType + " dropped pseudo type");
749 continue;
750 }
751
Walter Jang3f18d612015-10-07 16:01:05 -0700752 final KindSectionDataList kindSectionDataList =
Walter Jangcbd431d2015-10-06 13:07:30 -0700753 getOrCreateKindSectionDataList(mimeType);
Walter Jang363d3fd2015-09-16 10:29:07 -0700754 final KindSectionData kindSectionData =
755 new KindSectionData(accountType, dataKind, rawContactDelta);
756 kindSectionDataList.add(kindSectionData);
Walter Jang192a01c2015-09-22 15:23:55 -0700757
Walter Jang363d3fd2015-09-16 10:29:07 -0700758 vlog("parse: " + i + " " + dataKind.mimeType + " " +
Walter Jang228e02f2015-11-13 09:11:01 -0800759 kindSectionData.getValuesDeltas().size() + " value(s) " +
Tingting Wangd482e0c2015-11-16 10:13:29 -0800760 kindSectionData.getNonEmptyValuesDeltas().size() + " non-empty value(s) " +
Wenyi Wang42d740c2015-11-13 14:23:54 -0800761 kindSectionData.getVisibleValuesDeltas().size() +
Walter Jang228e02f2015-11-13 09:11:01 -0800762 " visible value(s)");
Walter Jangac679af2015-06-01 12:17:06 -0700763 }
764 }
Walter Jang3efae4a2015-02-18 11:12:00 -0800765 }
766
Walter Jang3f18d612015-10-07 16:01:05 -0700767 private KindSectionDataList getOrCreateKindSectionDataList(String mimeType) {
Walter Jangcbd431d2015-10-06 13:07:30 -0700768 KindSectionDataList kindSectionDataList = mKindSectionDataMap.get(mimeType);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700769 if (kindSectionDataList == null) {
Walter Jangcbd431d2015-10-06 13:07:30 -0700770 kindSectionDataList = new KindSectionDataList();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700771 mKindSectionDataMap.put(mimeType, kindSectionDataList);
772 }
773 return kindSectionDataList;
774 }
775
Walter Jang9a552372016-08-24 11:51:05 -0700776 private void addReadOnlyRawContactEditorViews() {
777 final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
778 Context.LAYOUT_INFLATER_SERVICE);
779 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(
780 getContext());
781
782 for (int i = 0; i < mRawContactDeltas.size(); i++) {
783 final RawContactDelta rawContactDelta = mRawContactDeltas.get(i);
784 if (!rawContactDelta.isVisible()) continue;
785 final AccountType type = rawContactDelta.getAccountType(accountTypes);
786 if (type.areContactsWritable()) continue;
787
788 final BaseRawContactEditorView editor = (BaseRawContactEditorView) inflater.inflate(
789 R.layout.raw_contact_readonly_editor_view, mKindSectionViews, false);
790 editor.setCollapsed(false);
791 mKindSectionViews.addView(editor);
792 editor.setState(rawContactDelta, type, mViewIdGenerator, mIsUserProfile);
793 }
794 }
795
796 // TODO: we have mRawContactDeltas, we don't need to pass the RawContactDeltaList to this method
Tingting Wang655ad1a2015-10-05 17:51:14 -0700797 private void addAccountInfo(RawContactDeltaList rawContactDeltas) {
Walter Jang82acd422015-10-17 14:01:27 -0700798 mAccountHeaderContainer.setVisibility(View.GONE);
799 mAccountSelectorContainer.setVisibility(View.GONE);
800 mRawContactContainer.setVisibility(View.GONE);
801
802 if (mPrimaryNameKindSectionData == null) return;
803 final RawContactDelta rawContactDelta =
804 mPrimaryNameKindSectionData.first.getRawContactDelta();
Walter Jang708ea9e2015-09-10 15:42:05 -0700805
806 // Get the account information for the primary raw contact delta
Walter Jangbe5e1b12015-10-17 11:38:29 -0700807 final Pair<String,String> accountInfo = mIsUserProfile
808 ? EditorUiUtils.getLocalAccountInfo(getContext(),
Walter Jang82acd422015-10-17 14:01:27 -0700809 rawContactDelta.getAccountName(),
810 rawContactDelta.getAccountType(mAccountTypeManager))
Walter Jangbe5e1b12015-10-17 11:38:29 -0700811 : EditorUiUtils.getAccountInfo(getContext(),
Walter Jang82acd422015-10-17 14:01:27 -0700812 rawContactDelta.getAccountName(),
813 rawContactDelta.getAccountType(mAccountTypeManager));
Walter Jang708ea9e2015-09-10 15:42:05 -0700814
Walter Jang23709542015-10-22 12:50:58 -0700815 // Either the account header or selector should be shown, not both.
Walter Jang708ea9e2015-09-10 15:42:05 -0700816 final List<AccountWithDataSet> accounts =
817 AccountTypeManager.getInstance(getContext()).getAccounts(true);
Walter Jang82acd422015-10-17 14:01:27 -0700818 if (mHasNewContact && !mIsUserProfile) {
819 if (accounts.size() > 1) {
820 addAccountSelector(accountInfo, rawContactDelta);
Walter Jangbe5e1b12015-10-17 11:38:29 -0700821 } else {
Tingting Wang1594c6f2015-10-26 17:39:16 -0700822 addAccountHeader(accountInfo);
Walter Jangbe5e1b12015-10-17 11:38:29 -0700823 }
Tingting Wang1594c6f2015-10-26 17:39:16 -0700824 } else if (mIsUserProfile || !shouldHideAccountContainer(rawContactDeltas)) {
825 addAccountHeader(accountInfo);
Walter Jang708ea9e2015-09-10 15:42:05 -0700826 }
Walter Jang23709542015-10-22 12:50:58 -0700827
828 // The raw contact selector should only display linked raw contacts that can be edited in
829 // the full editor (i.e. they are not newly created raw contacts)
Tingting Wang2bb85d22015-10-23 13:01:09 -0700830 final RawContactAccountListAdapter adapter = new RawContactAccountListAdapter(getContext(),
831 getRawContactDeltaListForSelector(rawContactDeltas));
832 if (adapter.getCount() > 0) {
833 final String accountsSummary = getResources().getQuantityString(
834 R.plurals.compact_editor_linked_contacts_selector_title,
835 adapter.getCount(), adapter.getCount());
Walter Jang23709542015-10-22 12:50:58 -0700836 addRawContactAccountSelector(accountsSummary, adapter);
837 }
Walter Jang708ea9e2015-09-10 15:42:05 -0700838 }
839
Tingting Wang2bb85d22015-10-23 13:01:09 -0700840 private RawContactDeltaList getRawContactDeltaListForSelector(
841 RawContactDeltaList rawContactDeltas) {
842 // Sort raw contacts so google accounts come first
843 Collections.sort(rawContactDeltas, new RawContactDeltaComparator(getContext()));
844
845 final RawContactDeltaList result = new RawContactDeltaList();
846 for (RawContactDelta rawContactDelta : rawContactDeltas) {
847 if (rawContactDelta.isVisible() && rawContactDelta.getRawContactId() > 0) {
848 // Only add raw contacts that can be opened in the editor
849 result.add(rawContactDelta);
850 }
851 }
852 // Don't return a list of size 1 that would just open the raw contact being edited
853 // in the compact editor in the full editor
854 if (result.size() == 1 && result.get(0).getRawContactAccountType(
855 getContext()).areContactsWritable()) {
856 result.clear();
857 return result;
858 }
859 return result;
860 }
861
Tingting Wang1594c6f2015-10-26 17:39:16 -0700862 // Returns true if there are multiple writable rawcontacts and no read-only ones,
863 // or there are both writable and read-only rawcontacts.
864 private boolean shouldHideAccountContainer(RawContactDeltaList rawContactDeltas) {
Tingting Wang2bb85d22015-10-23 13:01:09 -0700865 int writable = 0;
866 int readonly = 0;
867 for (RawContactDelta rawContactDelta : rawContactDeltas) {
868 if (rawContactDelta.isVisible() && rawContactDelta.getRawContactId() > 0) {
869 if (rawContactDelta.getRawContactAccountType(getContext()).areContactsWritable()) {
870 writable++;
871 } else {
872 readonly++;
873 }
874 }
875 }
876 return (writable > 1 || (writable > 0 && readonly > 0));
877 }
878
Tingting Wang1594c6f2015-10-26 17:39:16 -0700879 private void addAccountHeader(Pair<String,String> accountInfo) {
Walter Jang82acd422015-10-17 14:01:27 -0700880 mAccountHeaderContainer.setVisibility(View.VISIBLE);
881
Walter Jangbe5e1b12015-10-17 11:38:29 -0700882 // Set the account name
883 final String accountName = TextUtils.isEmpty(accountInfo.first)
884 ? accountInfo.second : accountInfo.first;
885 mAccountHeaderName.setVisibility(View.VISIBLE);
886 mAccountHeaderName.setText(accountName);
887
888 // Set the account type
889 final String selectorTitle = getResources().getString(
890 R.string.compact_editor_account_selector_title);
891 mAccountHeaderType.setText(selectorTitle);
892
893 // Set the icon
Walter Jang82acd422015-10-17 14:01:27 -0700894 if (mPrimaryNameKindSectionData != null) {
895 final RawContactDelta rawContactDelta =
896 mPrimaryNameKindSectionData.first.getRawContactDelta();
897 if (rawContactDelta != null) {
898 final AccountType accountType =
899 rawContactDelta.getRawContactAccountType(getContext());
900 mAccountHeaderIcon.setImageDrawable(accountType.getDisplayIcon(getContext()));
901 }
902 }
Walter Jangbe5e1b12015-10-17 11:38:29 -0700903
904 // Set the content description
905 mAccountHeaderContainer.setContentDescription(
906 EditorUiUtils.getAccountInfoContentDescription(accountName, selectorTitle));
907 }
908
Walter Jang82acd422015-10-17 14:01:27 -0700909 private void addAccountSelector(Pair<String,String> accountInfo,
910 final RawContactDelta rawContactDelta) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700911 mAccountSelectorContainer.setVisibility(View.VISIBLE);
912
Walter Jang03cea2e2015-09-18 17:04:21 -0700913 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700914 // Hide this view so the other text view will be centered vertically
915 mAccountSelectorName.setVisibility(View.GONE);
916 } else {
917 mAccountSelectorName.setVisibility(View.VISIBLE);
918 mAccountSelectorName.setText(accountInfo.first);
919 }
Walter Jang9488a762015-10-16 13:42:48 -0700920
921 final String selectorTitle = getResources().getString(
922 R.string.compact_editor_account_selector_title);
923 mAccountSelectorType.setText(selectorTitle);
Walter Jang708ea9e2015-09-10 15:42:05 -0700924
Tingting Wangb7933742015-11-02 10:52:57 -0800925 mAccountSelectorContainer.setContentDescription(getResources().getString(
926 R.string.compact_editor_account_selector_description, accountInfo.first));
Walter Jang708ea9e2015-09-10 15:42:05 -0700927
Wenyi Wang3e8cc352015-10-28 18:14:10 -0700928 mAccountSelectorContainer.setOnClickListener(new View.OnClickListener() {
Walter Jang708ea9e2015-09-10 15:42:05 -0700929 @Override
930 public void onClick(View v) {
931 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
932 final AccountsListAdapter adapter =
933 new AccountsListAdapter(getContext(),
934 AccountsListAdapter.AccountListFilter.ACCOUNTS_CONTACT_WRITABLE,
935 mPrimaryAccount);
936 popup.setWidth(mAccountSelectorContainer.getWidth());
937 popup.setAnchorView(mAccountSelectorContainer);
938 popup.setAdapter(adapter);
939 popup.setModal(true);
940 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
941 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
942 @Override
943 public void onItemClick(AdapterView<?> parent, View view, int position,
944 long id) {
945 UiClosables.closeQuietly(popup);
946 final AccountWithDataSet newAccount = adapter.getItem(position);
947 if (mListener != null && !mPrimaryAccount.equals(newAccount)) {
948 mListener.onRebindEditorsForNewContact(
Walter Jang82acd422015-10-17 14:01:27 -0700949 rawContactDelta,
Walter Jang708ea9e2015-09-10 15:42:05 -0700950 mPrimaryAccount,
951 newAccount);
952 }
953 }
954 });
955 popup.show();
956 }
957 });
958 }
959
Walter Jang23709542015-10-22 12:50:58 -0700960 private void addRawContactAccountSelector(String accountsSummary,
961 final RawContactAccountListAdapter adapter) {
Tingting Wang655ad1a2015-10-05 17:51:14 -0700962 mRawContactContainer.setVisibility(View.VISIBLE);
963
Tingting Wang655ad1a2015-10-05 17:51:14 -0700964 mRawContactSummary.setText(accountsSummary);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700965
966 mRawContactContainer.setOnClickListener(new View.OnClickListener() {
967 @Override
968 public void onClick(View v) {
969 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700970 popup.setWidth(mRawContactContainer.getWidth());
971 popup.setAnchorView(mRawContactContainer);
972 popup.setAdapter(adapter);
973 popup.setModal(true);
974 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
975 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
976 @Override
977 public void onItemClick(AdapterView<?> parent, View view, int position,
978 long id) {
979 UiClosables.closeQuietly(popup);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700980
981 if (mListener != null) {
Tingting Wang91cee282015-10-07 13:48:17 -0700982 final long rawContactId = adapter.getItemId(position);
983 final Uri rawContactUri = ContentUris.withAppendedId(
984 ContactsContract.RawContacts.CONTENT_URI, rawContactId);
985 final RawContactDelta rawContactDelta = adapter.getItem(position);
986 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(
987 getContext());
988 final AccountType accountType = rawContactDelta.getAccountType(
989 accountTypes);
990 final boolean isReadOnly = !accountType.areContactsWritable();
991
992 mListener.onRawContactSelected(rawContactUri, rawContactId, isReadOnly);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700993 }
994 }
995 });
996 popup.show();
997 }
998 });
999 }
1000
Walter Jang363d3fd2015-09-16 10:29:07 -07001001 private void addPhotoView() {
Walter Jang31a74ad2015-10-02 19:17:39 -07001002 // Get the kind section data and values delta that we will display in the photo view
Walter Jangcbd431d2015-10-06 13:07:30 -07001003 final KindSectionDataList kindSectionDataList =
1004 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
1005 final Pair<KindSectionData,ValuesDelta> photoToDisplay =
1006 kindSectionDataList.getEntryToDisplay(mPhotoId);
1007 if (photoToDisplay == null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001008 wlog("photo: no kind section data parsed");
Walter Jangcbd431d2015-10-06 13:07:30 -07001009 mPhotoView.setVisibility(View.GONE);
Walter Jang363d3fd2015-09-16 10:29:07 -07001010 return;
1011 }
Walter Jang06f73a12015-06-17 11:15:48 -07001012
Walter Jang31a74ad2015-10-02 19:17:39 -07001013 // Set the photo view
Walter Jangcbd431d2015-10-06 13:07:30 -07001014 mPhotoView.setPhoto(photoToDisplay.second, mMaterialPalette);
Walter Jang31a74ad2015-10-02 19:17:39 -07001015
1016 // Find the raw contact ID and values delta that will be written when the photo is edited
Walter Jangda258ff2015-11-17 11:06:02 -08001017 final Pair<KindSectionData, ValuesDelta> photoToWrite = kindSectionDataList.getEntryToWrite(
Walter Jang82acd422015-10-17 14:01:27 -07001018 mPhotoId, mPrimaryAccount, mIsUserProfile);
Walter Jangda258ff2015-11-17 11:06:02 -08001019 if (photoToWrite == null) {
Walter Jang31a74ad2015-10-02 19:17:39 -07001020 mPhotoView.setReadOnly(true);
1021 return;
1022 }
Walter Jang31a74ad2015-10-02 19:17:39 -07001023 mPhotoView.setReadOnly(false);
Walter Jangda258ff2015-11-17 11:06:02 -08001024 mPhotoRawContactId = photoToWrite.first.getRawContactDelta().getRawContactId();
1025 mPhotoValuesDelta = photoToWrite.second;
Walter Jang31a74ad2015-10-02 19:17:39 -07001026 }
1027
Walter Jangf5dfea42015-09-16 12:30:36 -07001028 private void addKindSectionViews() {
1029 // Sort the kinds
Walter Jangcbd431d2015-10-06 13:07:30 -07001030 final TreeSet<Map.Entry<String,KindSectionDataList>> entries =
Walter Jang192a01c2015-09-22 15:23:55 -07001031 new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
Walter Jangf5dfea42015-09-16 12:30:36 -07001032 entries.addAll(mKindSectionDataMap.entrySet());
Walter Jang3efae4a2015-02-18 11:12:00 -08001033
Walter Jangf5dfea42015-09-16 12:30:36 -07001034 vlog("kind: " + entries.size() + " kindSection(s)");
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001035 int i = -1;
Walter Jangcbd431d2015-10-06 13:07:30 -07001036 for (Map.Entry<String, KindSectionDataList> entry : entries) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001037 i++;
1038
Walter Jangf5dfea42015-09-16 12:30:36 -07001039 final String mimeType = entry.getKey();
Walter Jang82acd422015-10-17 14:01:27 -07001040
Tingting Wangf695eb32015-10-15 18:45:15 -07001041 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang82acd422015-10-17 14:01:27 -07001042 if (mPrimaryNameKindSectionData == null) {
1043 vlog("kind: " + i + " " + mimeType + " dropped");
1044 continue;
Tingting Wangf695eb32015-10-15 18:45:15 -07001045 }
Walter Jang82acd422015-10-17 14:01:27 -07001046 vlog("kind: " + i + " " + mimeType + " using first entry only");
1047 final KindSectionDataList kindSectionDataList = new KindSectionDataList();
1048 kindSectionDataList.add(mPrimaryNameKindSectionData.first);
Walter Jangf5dfea42015-09-16 12:30:36 -07001049 final CompactKindSectionView kindSectionView = inflateKindSectionView(
Walter Jang82acd422015-10-17 14:01:27 -07001050 mKindSectionViews, kindSectionDataList, mimeType,
1051 mPrimaryNameKindSectionData.second);
Walter Jangf5dfea42015-09-16 12:30:36 -07001052 mKindSectionViews.addView(kindSectionView);
Walter Jangf10ca152015-09-22 15:23:55 -07001053
1054 // Keep a pointer to all the KindSectionsViews for each mimeType
1055 getKindSectionViews(mimeType).add(kindSectionView);
Walter Jang82acd422015-10-17 14:01:27 -07001056 } else {
1057 final KindSectionDataList kindSectionDataList = entry.getValue();
1058
1059 // Ignore mime types that we've already handled
1060 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
1061 vlog("kind: " + i + " " + mimeType + " dropped");
1062 continue;
1063 }
1064
1065 // Don't show more than one group editor on the compact editor.
1066 // Groups will still be editable for each raw contact individually on the full editor.
1067 if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)
1068 && kindSectionDataList.size() > 1) {
1069 vlog("kind: " + i + " " + mimeType + " dropped");
1070 continue;
1071 }
1072
1073 if (kindSectionDataList != null && !kindSectionDataList.isEmpty()) {
1074 vlog("kind: " + i + " " + mimeType + " " + kindSectionDataList.size() +
1075 " kindSectionData(s)");
1076
1077 final CompactKindSectionView kindSectionView = inflateKindSectionView(
1078 mKindSectionViews, kindSectionDataList, mimeType,
1079 /* primaryValueDelta =*/ null);
1080 mKindSectionViews.addView(kindSectionView);
1081
1082 // Keep a pointer to all the KindSectionsViews for each mimeType
1083 getKindSectionViews(mimeType).add(kindSectionView);
1084 }
Walter Jangcab3dce2015-02-09 17:48:03 -08001085 }
1086 }
1087 }
1088
Walter Jangf10ca152015-09-22 15:23:55 -07001089 private List<CompactKindSectionView> getKindSectionViews(String mimeType) {
1090 List<CompactKindSectionView> kindSectionViews = mKindSectionViewsMap.get(mimeType);
1091 if (kindSectionViews == null) {
1092 kindSectionViews = new ArrayList<>();
1093 mKindSectionViewsMap.put(mimeType, kindSectionViews);
1094 }
1095 return kindSectionViews;
1096 }
1097
Walter Jangf5dfea42015-09-16 12:30:36 -07001098 private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
Walter Jang82acd422015-10-17 14:01:27 -07001099 KindSectionDataList kindSectionDataList, String mimeType,
1100 ValuesDelta primaryValuesDelta) {
Walter Jangf5dfea42015-09-16 12:30:36 -07001101 final CompactKindSectionView kindSectionView = (CompactKindSectionView)
1102 mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
1103 /* attachToRoot =*/ false);
Walter Jang45b86d52015-10-15 15:23:16 -07001104 kindSectionView.setIsUserProfile(mIsUserProfile);
Walter Jangf5dfea42015-09-16 12:30:36 -07001105
Walter Jang192a01c2015-09-22 15:23:55 -07001106 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jangf5dfea42015-09-16 12:30:36 -07001107 || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang192a01c2015-09-22 15:23:55 -07001108 // Phone numbers and email addresses are always displayed,
1109 // even if they are empty
Walter Jangf5dfea42015-09-16 12:30:36 -07001110 kindSectionView.setHideWhenEmpty(false);
1111 }
Walter Jangf5dfea42015-09-16 12:30:36 -07001112
Walter Jang192a01c2015-09-22 15:23:55 -07001113 // Since phone numbers and email addresses displayed even if they are empty,
1114 // they will be the only types you add new values to initially for new contacts
1115 kindSectionView.setShowOneEmptyEditor(true);
1116
Walter Jang82acd422015-10-17 14:01:27 -07001117 // Sort non-name editors so they wind up in the order we want
1118 if (!StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang192a01c2015-09-22 15:23:55 -07001119 Collections.sort(kindSectionDataList, new EditorComparator(getContext()));
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001120 }
1121
Walter Jang82acd422015-10-17 14:01:27 -07001122 kindSectionView.setState(kindSectionDataList, mViewIdGenerator, mListener,
1123 primaryValuesDelta);
Walter Jangf5dfea42015-09-16 12:30:36 -07001124
1125 return kindSectionView;
Walter Jangcab3dce2015-02-09 17:48:03 -08001126 }
1127
Walter Jange3945952015-10-27 12:44:54 -07001128 void maybeSetReadOnlyDisplayNameAsPrimary(String readOnlyDisplayName) {
1129 if (TextUtils.isEmpty(readOnlyDisplayName)) return;
1130 final CompactKindSectionView primaryNameKindSectionView = getPrimaryNameKindSectionView();
1131 if (primaryNameKindSectionView != null && primaryNameKindSectionView.isEmptyName()) {
1132 vlog("name: using read only display name as primary name");
1133 primaryNameKindSectionView.setName(readOnlyDisplayName);
1134 }
1135 }
1136
1137 private CompactKindSectionView getPrimaryNameKindSectionView() {
Walter Jang4f5594a2015-10-06 18:40:31 -07001138 final List<CompactKindSectionView> kindSectionViews
1139 = mKindSectionViewsMap.get(StructuredName.CONTENT_ITEM_TYPE);
Walter Jange3945952015-10-27 12:44:54 -07001140 return kindSectionViews == null || kindSectionViews.isEmpty()
1141 ? null : kindSectionViews.get(0);
Walter Jang4f5594a2015-10-06 18:40:31 -07001142 }
1143
Walter Jangd6753152015-10-02 09:23:13 -07001144 private void showAllFields() {
Walter Jang79658e12015-09-24 10:36:26 -07001145 // Stop hiding empty editors and allow the user to enter values for all kinds now
1146 for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
1147 final CompactKindSectionView kindSectionView =
1148 (CompactKindSectionView) mKindSectionViews.getChildAt(i);
1149 kindSectionView.setHideWhenEmpty(false);
1150 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
1151 }
1152 mIsExpanded = true;
Walter Jang79658e12015-09-24 10:36:26 -07001153
Walter Jangf5dfea42015-09-16 12:30:36 -07001154 // Hide the more fields button
1155 mMoreFields.setVisibility(View.GONE);
Walter Jangcab3dce2015-02-09 17:48:03 -08001156 }
1157
Walter Jangbf63a6d2015-05-05 09:14:35 -07001158 private static void vlog(String message) {
1159 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1160 Log.v(TAG, message);
Walter Jangcab3dce2015-02-09 17:48:03 -08001161 }
1162 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001163
1164 private static void wlog(String message) {
1165 if (Log.isLoggable(TAG, Log.WARN)) {
1166 Log.w(TAG, message);
1167 }
1168 }
1169
1170 private static void elog(String message) {
1171 Log.e(TAG, message);
1172 }
Walter Jangcab3dce2015-02-09 17:48:03 -08001173}