blob: f808d4dc87aea2a7a18123149edae9cb3cca62d0 [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;
Walter Jang31a74ad2015-10-02 19:17:39 -070020import com.android.contacts.common.ContactsUtils;
Walter Jangcab3dce2015-02-09 17:48:03 -080021import com.android.contacts.common.model.AccountTypeManager;
22import com.android.contacts.common.model.RawContactDelta;
23import com.android.contacts.common.model.RawContactDeltaList;
Walter Jange720fde2015-02-17 10:54:14 -080024import com.android.contacts.common.model.RawContactModifier;
Walter Jangcab3dce2015-02-09 17:48:03 -080025import com.android.contacts.common.model.ValuesDelta;
26import com.android.contacts.common.model.account.AccountType;
Walter Jang2d3f31c2015-06-18 23:15:31 -070027import com.android.contacts.common.model.account.AccountWithDataSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080028import com.android.contacts.common.model.dataitem.DataKind;
Walter Jang708ea9e2015-09-10 15:42:05 -070029import com.android.contacts.common.util.AccountsListAdapter;
Walter Jangf46abd82015-02-20 16:52:04 -080030import com.android.contacts.common.util.MaterialColorMapUtils;
Walter Jang31a74ad2015-10-02 19:17:39 -070031import com.android.contacts.util.ContactPhotoUtils;
Walter Jang708ea9e2015-09-10 15:42:05 -070032import com.android.contacts.util.UiClosables;
Walter Jangcab3dce2015-02-09 17:48:03 -080033
Tingting Wang655ad1a2015-10-05 17:51:14 -070034import android.content.ContentUris;
35import android.content.ContentValues;
Walter Jangcab3dce2015-02-09 17:48:03 -080036import android.content.Context;
Tingting Wang655ad1a2015-10-05 17:51:14 -070037import android.content.Intent;
Walter Jangf10ca152015-09-22 15:23:55 -070038import android.database.Cursor;
Walter Jang3efae4a2015-02-18 11:12:00 -080039import android.graphics.Bitmap;
Walter Jang41b3ea12015-03-09 17:30:06 -070040import android.net.Uri;
Walter Jang79658e12015-09-24 10:36:26 -070041import android.os.Parcel;
42import android.os.Parcelable;
Tingting Wang655ad1a2015-10-05 17:51:14 -070043import android.provider.ContactsContract;
Walter Jangcab3dce2015-02-09 17:48:03 -080044import android.provider.ContactsContract.CommonDataKinds.Email;
Walter Jangf5dfea42015-09-16 12:30:36 -070045import android.provider.ContactsContract.CommonDataKinds.Event;
Walter Jangcab3dce2015-02-09 17:48:03 -080046import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Walter Jangf5dfea42015-09-16 12:30:36 -070047import android.provider.ContactsContract.CommonDataKinds.Im;
Walter Jang3efae4a2015-02-18 11:12:00 -080048import android.provider.ContactsContract.CommonDataKinds.Nickname;
Walter Jangf5dfea42015-09-16 12:30:36 -070049import android.provider.ContactsContract.CommonDataKinds.Note;
50import android.provider.ContactsContract.CommonDataKinds.Organization;
Walter Jangcab3dce2015-02-09 17:48:03 -080051import android.provider.ContactsContract.CommonDataKinds.Phone;
52import android.provider.ContactsContract.CommonDataKinds.Photo;
Walter Jangf5dfea42015-09-16 12:30:36 -070053import android.provider.ContactsContract.CommonDataKinds.Relation;
54import android.provider.ContactsContract.CommonDataKinds.SipAddress;
Walter Jangcab3dce2015-02-09 17:48:03 -080055import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Walter Jangf5dfea42015-09-16 12:30:36 -070056import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
57import android.provider.ContactsContract.CommonDataKinds.Website;
Walter Jangcab3dce2015-02-09 17:48:03 -080058import android.text.TextUtils;
59import android.util.AttributeSet;
60import android.util.Log;
Walter Jang2d3f31c2015-06-18 23:15:31 -070061import android.util.Pair;
Walter Jangcab3dce2015-02-09 17:48:03 -080062import android.view.LayoutInflater;
Walter Jangb1c87622015-02-13 17:51:38 -080063import android.view.View;
Walter Jangcab3dce2015-02-09 17:48:03 -080064import android.view.ViewGroup;
Walter Jang708ea9e2015-09-10 15:42:05 -070065import android.widget.AdapterView;
Tingting Wang655ad1a2015-10-05 17:51:14 -070066import android.widget.BaseAdapter;
67import android.widget.ImageView;
Walter Jangcab3dce2015-02-09 17:48:03 -080068import android.widget.LinearLayout;
Walter Jang708ea9e2015-09-10 15:42:05 -070069import android.widget.ListPopupWindow;
Walter Jang2d3f31c2015-06-18 23:15:31 -070070import android.widget.TextView;
Walter Jangcab3dce2015-02-09 17:48:03 -080071
Walter Jang31a74ad2015-10-02 19:17:39 -070072import java.io.File;
73import java.io.FileNotFoundException;
Walter Jangcab3dce2015-02-09 17:48:03 -080074import java.util.ArrayList;
Walter Jangf5dfea42015-09-16 12:30:36 -070075import java.util.Arrays;
Walter Jang192a01c2015-09-22 15:23:55 -070076import java.util.Collections;
Walter Jangf5dfea42015-09-16 12:30:36 -070077import java.util.Comparator;
Walter Jang19d985f2015-07-01 13:36:27 -070078import java.util.HashMap;
Tingting Wang655ad1a2015-10-05 17:51:14 -070079import java.util.HashSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080080import java.util.List;
Walter Jang19d985f2015-07-01 13:36:27 -070081import java.util.Map;
Walter Jang708ea9e2015-09-10 15:42:05 -070082import java.util.Objects;
Tingting Wang655ad1a2015-10-05 17:51:14 -070083import java.util.Set;
Walter Jangf5dfea42015-09-16 12:30:36 -070084import java.util.TreeSet;
Walter Jangcab3dce2015-02-09 17:48:03 -080085
86/**
Walter Jangf5dfea42015-09-16 12:30:36 -070087 * View to display information from multiple {@link RawContactDelta}s grouped together.
Walter Jangcab3dce2015-02-09 17:48:03 -080088 */
Walter Jangb6ca2722015-02-20 11:10:25 -080089public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
Walter Jangcab3dce2015-02-09 17:48:03 -080090
91 private static final String TAG = "CompactEditorView";
92
Walter Jang192a01c2015-09-22 15:23:55 -070093 private static final KindSectionDataMapEntryComparator
94 KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR = new KindSectionDataMapEntryComparator();
Walter Jangf5dfea42015-09-16 12:30:36 -070095
Walter Jangb6ca2722015-02-20 11:10:25 -080096 /**
97 * Callbacks for hosts of {@link CompactRawContactsEditorView}s.
98 */
99 public interface Listener {
100
101 /**
Walter Jang151f3e62015-02-26 15:29:40 -0800102 * Invoked when the structured name editor field has changed.
103 *
104 * @param rawContactId The raw contact ID from the underlying {@link RawContactDelta}.
105 * @param valuesDelta The values from the underlying {@link RawContactDelta}.
106 */
107 public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta);
Walter Jang708ea9e2015-09-10 15:42:05 -0700108
109 /**
110 * Invoked when the compact editor should rebind editors for a new account.
111 *
112 * @param oldState Old data being edited.
113 * @param oldAccount Old account associated with oldState.
114 * @param newAccount New account to be used.
115 */
116 public void onRebindEditorsForNewContact(RawContactDelta oldState,
117 AccountWithDataSet oldAccount, AccountWithDataSet newAccount);
Walter Jang79658e12015-09-24 10:36:26 -0700118
119 /**
120 * Invoked when no editors could be bound for the contact.
121 */
122 public void onBindEditorsFailed();
Walter Jangd6753152015-10-02 09:23:13 -0700123
124 /**
125 * Invoked after editors have been bound for the contact.
126 */
127 public void onEditorsBound();
Tingting Wang655ad1a2015-10-05 17:51:14 -0700128
129 /**
130 * Invoked when a rawcontact from merged contacts is selected in editor.
131 */
132 public void onRawContactSelected(Uri uri, long rawContactId);
133 }
134
135 /**
136 * Used to list the account info for the given raw contacts list.
137 */
138 private static final class RawContactAccountListAdapter extends BaseAdapter {
139 private final LayoutInflater mInflater;
140 private final Context mContext;
141 private final RawContactDeltaList mRawContactDeltas;
142
143 public RawContactAccountListAdapter(Context context, RawContactDeltaList rawContactDeltas) {
144 mContext = context;
145 mRawContactDeltas = new RawContactDeltaList();
146 for (RawContactDelta rawContactDelta : rawContactDeltas) {
147 if (rawContactDelta.isVisible()) {
148 mRawContactDeltas.add(rawContactDelta);
149 }
150 }
151 mInflater = LayoutInflater.from(context);
152 }
153
154 @Override
155 public View getView(int position, View convertView, ViewGroup parent) {
156 final View resultView = convertView != null ? convertView
157 : mInflater.inflate(R.layout.account_selector_list_item, parent, false);
158
159 final RawContactDelta rawContactDelta = mRawContactDeltas.get(position);
160 final String accountName = rawContactDelta.getAccountName();
161 final AccountType accountType = rawContactDelta.getRawContactAccountType(mContext);
162
163 final TextView text1 = (TextView) resultView.findViewById(android.R.id.text1);
164 text1.setText(accountType.getDisplayLabel(mContext));
165
166 // For email addresses, we don't want to truncate at end, which might cut off the domain
167 // name.
168 final TextView text2 = (TextView) resultView.findViewById(android.R.id.text2);
169 text2.setText(accountName);
170 text2.setEllipsize(TextUtils.TruncateAt.MIDDLE);
171
172 final ImageView icon = (ImageView) resultView.findViewById(android.R.id.icon);
173 icon.setImageDrawable(accountType.getDisplayIcon(mContext));
174
175 return resultView;
176 }
177
178 @Override
179 public int getCount() {
180 return mRawContactDeltas.size();
181 }
182
183 @Override
184 public RawContactDelta getItem(int position) {
185 return mRawContactDeltas.get(position);
186 }
187
188 @Override
189 public long getItemId(int position) {
190 return getItem(position).getRawContactId();
191 }
Walter Jang151f3e62015-02-26 15:29:40 -0800192 }
193
Walter Jang192a01c2015-09-22 15:23:55 -0700194 /** Used to sort entire kind sections. */
195 private static final class KindSectionDataMapEntryComparator implements
Walter Jangf5dfea42015-09-16 12:30:36 -0700196 Comparator<Map.Entry<String,List<KindSectionData>>> {
197
Walter Jang192a01c2015-09-22 15:23:55 -0700198 final MimeTypeComparator mMimeTypeComparator = new MimeTypeComparator();
199
200 @Override
201 public int compare(Map.Entry<String, List<KindSectionData>> entry1,
202 Map.Entry<String, List<KindSectionData>> entry2) {
203 if (entry1 == entry2) return 0;
204 if (entry1 == null) return -1;
205 if (entry2 == null) return 1;
206
207 final String mimeType1 = entry1.getKey();
208 final String mimeType2 = entry2.getKey();
209
210 return mMimeTypeComparator.compare(mimeType1, mimeType2);
211 }
212 }
213
214 /**
215 * Sorts kinds roughly the same as quick contacts; we diverge in the following ways:
216 * <ol>
217 * <li>All names are together at the top.</li>
218 * <li>IM is moved up after addresses</li>
219 * <li>SIP addresses are moved to below phone numbers</li>
Walter Jang79658e12015-09-24 10:36:26 -0700220 * <li>Group membership is placed at the end</li>
Walter Jang192a01c2015-09-22 15:23:55 -0700221 * </ol>
222 */
223 private static final class MimeTypeComparator implements Comparator<String> {
224
Walter Jangf5dfea42015-09-16 12:30:36 -0700225 private static final List<String> MIME_TYPE_ORDER = Arrays.asList(new String[] {
226 StructuredName.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700227 Nickname.CONTENT_ITEM_TYPE,
Walter Jang2ae21752015-09-25 11:25:38 -0700228 Organization.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700229 Phone.CONTENT_ITEM_TYPE,
230 SipAddress.CONTENT_ITEM_TYPE,
231 Email.CONTENT_ITEM_TYPE,
232 StructuredPostal.CONTENT_ITEM_TYPE,
233 Im.CONTENT_ITEM_TYPE,
234 Website.CONTENT_ITEM_TYPE,
Walter Jangf5dfea42015-09-16 12:30:36 -0700235 Event.CONTENT_ITEM_TYPE,
236 Relation.CONTENT_ITEM_TYPE,
Walter Jangf10ca152015-09-22 15:23:55 -0700237 Note.CONTENT_ITEM_TYPE,
238 GroupMembership.CONTENT_ITEM_TYPE
Walter Jangf5dfea42015-09-16 12:30:36 -0700239 });
240
241 @Override
Walter Jang192a01c2015-09-22 15:23:55 -0700242 public int compare(String mimeType1, String mimeType2) {
243 if (mimeType1 == mimeType2) return 0;
244 if (mimeType1 == null) return -1;
245 if (mimeType2 == null) return 1;
Walter Jangf5dfea42015-09-16 12:30:36 -0700246
247 int index1 = MIME_TYPE_ORDER.indexOf(mimeType1);
248 int index2 = MIME_TYPE_ORDER.indexOf(mimeType2);
249
250 // Fallback to alphabetical ordering of the mime type if both are not found
251 if (index1 < 0 && index2 < 0) return mimeType1.compareTo(mimeType2);
252 if (index1 < 0) return 1;
253 if (index2 < 0) return -1;
254
255 return index1 < index2 ? -1 : 1;
256 }
257 }
258
Walter Jang192a01c2015-09-22 15:23:55 -0700259 /**
260 * Sorts primary accounts and google account types before others.
261 */
262 private static final class EditorComparator implements Comparator<KindSectionData> {
263
264 private RawContactDeltaComparator mRawContactDeltaComparator;
265
266 private EditorComparator(Context context) {
267 mRawContactDeltaComparator = new RawContactDeltaComparator(context);
268 }
269
270 @Override
271 public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
272 if (kindSectionData1 == kindSectionData2) return 0;
273 if (kindSectionData1 == null) return -1;
274 if (kindSectionData2 == null) return 1;
275
276 final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
277 final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
278
279 if (rawContactDelta1 == rawContactDelta2) return 0;
280 if (rawContactDelta1 == null) return -1;
281 if (rawContactDelta2 == null) return 1;
282
283 return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
284 }
285 }
286
287 /**
288 * Sorts primary account names first, followed by google account types, and other account
289 * types last. For names from the same account we order structured names before nicknames,
290 * but still keep names from the same account together.
291 */
292 private static final class NameEditorComparator implements Comparator<KindSectionData> {
293
294 private RawContactDeltaComparator mRawContactDeltaComparator;
295 private MimeTypeComparator mMimeTypeComparator;
296 private RawContactDelta mPrimaryRawContactDelta;
297
298 private NameEditorComparator(Context context, RawContactDelta primaryRawContactDelta) {
299 mRawContactDeltaComparator = new RawContactDeltaComparator(context);
300 mMimeTypeComparator = new MimeTypeComparator();
301 mPrimaryRawContactDelta = primaryRawContactDelta;
302 }
303
304 @Override
305 public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
306 if (kindSectionData1 == kindSectionData2) return 0;
307 if (kindSectionData1 == null) return -1;
308 if (kindSectionData2 == null) return 1;
309
310 final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
311 final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
312
313 if (rawContactDelta1 == rawContactDelta2) return 0;
314 if (rawContactDelta1 == null) return -1;
315 if (rawContactDelta2 == null) return 1;
316
317 final boolean isRawContactDelta1Primary =
318 mPrimaryRawContactDelta.equals(rawContactDelta1);
319 final boolean isRawContactDelta2Primary =
320 mPrimaryRawContactDelta.equals(rawContactDelta2);
321
322 // If both names are from the primary account, sort my by mime type
323 if (isRawContactDelta1Primary && isRawContactDelta2Primary) {
324 final String mimeType1 = kindSectionData1.getDataKind().mimeType;
325 final String mimeType2 = kindSectionData2.getDataKind().mimeType;
326 return mMimeTypeComparator.compare(mimeType1, mimeType2);
327 }
328
329 // The primary account name should be before all others
Walter Jang79658e12015-09-24 10:36:26 -0700330 if (isRawContactDelta1Primary) return -1;
331 if (isRawContactDelta2Primary) return 1;
Walter Jang192a01c2015-09-22 15:23:55 -0700332
333 return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
334 }
335 }
336
Walter Jang79658e12015-09-24 10:36:26 -0700337 public static class SavedState extends BaseSavedState {
338
339 public static final Parcelable.Creator<SavedState> CREATOR =
340 new Parcelable.Creator<SavedState>() {
341 public SavedState createFromParcel(Parcel in) {
342 return new SavedState(in);
343 }
344 public SavedState[] newArray(int size) {
345 return new SavedState[size];
346 }
347 };
348
349 private boolean mIsExpanded;
350
351 public SavedState(Parcelable superState) {
352 super(superState);
353 }
354
355 private SavedState(Parcel in) {
356 super(in);
357 mIsExpanded = in.readInt() != 0;
358 }
359
360 @Override
361 public void writeToParcel(Parcel out, int flags) {
362 super.writeToParcel(out, flags);
363 out.writeInt(mIsExpanded ? 1 : 0);
364 }
365 }
366
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700367 private CompactRawContactsEditorView.Listener mListener;
Walter Jangb6ca2722015-02-20 11:10:25 -0800368
Walter Jangcab3dce2015-02-09 17:48:03 -0800369 private AccountTypeManager mAccountTypeManager;
370 private LayoutInflater mLayoutInflater;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800371
Walter Jangcab3dce2015-02-09 17:48:03 -0800372 private ViewIdGenerator mViewIdGenerator;
Walter Jangf46abd82015-02-20 16:52:04 -0800373 private MaterialColorMapUtils.MaterialPalette mMaterialPalette;
Walter Jang708ea9e2015-09-10 15:42:05 -0700374 private long mPhotoId;
Walter Jang708ea9e2015-09-10 15:42:05 -0700375 private boolean mHasNewContact;
376 private boolean mIsUserProfile;
377 private AccountWithDataSet mPrimaryAccount;
378 private RawContactDelta mPrimaryRawContactDelta;
Walter Jang363d3fd2015-09-16 10:29:07 -0700379 private Map<String,List<KindSectionData>> mKindSectionDataMap = new HashMap<>();
Walter Jangcab3dce2015-02-09 17:48:03 -0800380
Walter Jang708ea9e2015-09-10 15:42:05 -0700381 // Account header
382 private View mAccountHeaderContainer;
383 private TextView mAccountHeaderType;
384 private TextView mAccountHeaderName;
385
386 // Account selector
387 private View mAccountSelectorContainer;
388 private View mAccountSelector;
389 private TextView mAccountSelectorType;
390 private TextView mAccountSelectorName;
391
Tingting Wang655ad1a2015-10-05 17:51:14 -0700392 // Raw contacts selector
393 private View mRawContactContainer;
394 private TextView mRawContactSummary;
395 private ImageView mPrimaryAccountIcon;
396
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700397 private CompactPhotoEditorView mPhotoView;
Walter Jangf5dfea42015-09-16 12:30:36 -0700398 private ViewGroup mKindSectionViews;
Walter Jangf10ca152015-09-22 15:23:55 -0700399 private Map<String,List<CompactKindSectionView>> mKindSectionViewsMap = new HashMap<>();
Walter Jangb6ca2722015-02-20 11:10:25 -0800400 private View mMoreFields;
Walter Jangcab3dce2015-02-09 17:48:03 -0800401
Walter Jang79658e12015-09-24 10:36:26 -0700402 private boolean mIsExpanded;
Walter Jang3efae4a2015-02-18 11:12:00 -0800403 private long mPhotoRawContactId;
Walter Jang31a74ad2015-10-02 19:17:39 -0700404 private ValuesDelta mPhotoValuesDelta;
Walter Jang3efae4a2015-02-18 11:12:00 -0800405
Walter Jangcab3dce2015-02-09 17:48:03 -0800406 public CompactRawContactsEditorView(Context context) {
407 super(context);
408 }
409
410 public CompactRawContactsEditorView(Context context, AttributeSet attrs) {
411 super(context, attrs);
412 }
413
Walter Jangb6ca2722015-02-20 11:10:25 -0800414 /**
415 * Sets the receiver for {@link CompactRawContactsEditorView} callbacks.
416 */
417 public void setListener(Listener listener) {
418 mListener = listener;
419 }
420
Walter Jangcab3dce2015-02-09 17:48:03 -0800421 @Override
422 protected void onFinishInflate() {
423 super.onFinishInflate();
424
425 mAccountTypeManager = AccountTypeManager.getInstance(getContext());
426 mLayoutInflater = (LayoutInflater)
427 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
428
Walter Jang708ea9e2015-09-10 15:42:05 -0700429 // Account header
430 mAccountHeaderContainer = findViewById(R.id.account_container);
431 mAccountHeaderType = (TextView) findViewById(R.id.account_type);
432 mAccountHeaderName = (TextView) findViewById(R.id.account_name);
433
434 // Account selector
435 mAccountSelectorContainer = findViewById(R.id.account_selector_container);
436 mAccountSelector = findViewById(R.id.account);
437 mAccountSelectorType = (TextView) findViewById(R.id.account_type_selector);
438 mAccountSelectorName = (TextView) findViewById(R.id.account_name_selector);
439
Tingting Wang655ad1a2015-10-05 17:51:14 -0700440 // Raw contacts selector
441 mRawContactContainer = findViewById(R.id.all_rawcontacts_accounts_container);
442 mRawContactSummary = (TextView) findViewById(R.id.rawcontacts_accounts_summary);
443 mPrimaryAccountIcon = (ImageView) findViewById(R.id.primary_account_icon);
444
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700445 mPhotoView = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
Walter Jangf5dfea42015-09-16 12:30:36 -0700446 mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
Walter Jangb6ca2722015-02-20 11:10:25 -0800447 mMoreFields = findViewById(R.id.more_fields);
448 mMoreFields.setOnClickListener(this);
449 }
450
Walter Jangb6ca2722015-02-20 11:10:25 -0800451 @Override
452 public void onClick(View view) {
Walter Jangf5dfea42015-09-16 12:30:36 -0700453 if (view.getId() == R.id.more_fields) {
Walter Jangd6753152015-10-02 09:23:13 -0700454 showAllFields();
Walter Jangb6ca2722015-02-20 11:10:25 -0800455 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800456 }
457
458 @Override
459 public void setEnabled(boolean enabled) {
460 super.setEnabled(enabled);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700461 final int childCount = mKindSectionViews.getChildCount();
462 for (int i = 0; i < childCount; i++) {
463 mKindSectionViews.getChildAt(i).setEnabled(enabled);
Walter Jangcab3dce2015-02-09 17:48:03 -0800464 }
465 }
466
Walter Jang79658e12015-09-24 10:36:26 -0700467 @Override
468 public Parcelable onSaveInstanceState() {
469 final Parcelable superState = super.onSaveInstanceState();
470 final SavedState savedState = new SavedState(superState);
471 savedState.mIsExpanded = mIsExpanded;
472 return savedState;
473 }
474
475 @Override
476 public void onRestoreInstanceState(Parcelable state) {
477 if(!(state instanceof SavedState)) {
478 super.onRestoreInstanceState(state);
479 return;
480 }
481 final SavedState savedState = (SavedState) state;
482 super.onRestoreInstanceState(savedState.getSuperState());
483 mIsExpanded = savedState.mIsExpanded;
Walter Jangd6753152015-10-02 09:23:13 -0700484 if (mIsExpanded) {
485 showAllFields();
Walter Jang79658e12015-09-24 10:36:26 -0700486 }
487 }
488
Walter Jang3efae4a2015-02-18 11:12:00 -0800489 /**
Walter Jang31a74ad2015-10-02 19:17:39 -0700490 * Pass through to {@link CompactPhotoEditorView#setListener}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800491 */
Walter Jang31a74ad2015-10-02 19:17:39 -0700492 public void setPhotoListener(CompactPhotoEditorView.Listener listener) {
493 mPhotoView.setListener(listener);
Walter Jang3efae4a2015-02-18 11:12:00 -0800494 }
495
Walter Jang31a74ad2015-10-02 19:17:39 -0700496 public void removePhoto() {
497 mPhotoValuesDelta.setFromTemplate(false);
498 mPhotoValuesDelta.put(Photo.PHOTO, (byte[]) null);
499
500 mPhotoView.removePhoto();
Walter Jang3efae4a2015-02-18 11:12:00 -0800501 }
502
503 /**
Walter Jang41b3ea12015-03-09 17:30:06 -0700504 * Pass through to {@link CompactPhotoEditorView#setFullSizedPhoto(Uri)}.
505 */
506 public void setFullSizePhoto(Uri photoUri) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700507 mPhotoView.setFullSizedPhoto(photoUri);
Walter Jang41b3ea12015-03-09 17:30:06 -0700508 }
509
Walter Jang31a74ad2015-10-02 19:17:39 -0700510 public void updatePhoto(Uri photoUri) {
511 // Even though high-res photos cannot be saved by passing them via
512 // an EntityDeltaList (since they cause the Bundle size limit to be
513 // exceeded), we still pass a low-res thumbnail. This simplifies
514 // code all over the place, because we don't have to test whether
515 // there is a change in EITHER the delta-list OR a changed photo...
516 // this way, there is always a change in the delta-list.
517 mPhotoValuesDelta.setFromTemplate(false);
518 mPhotoValuesDelta.setSuperPrimary(true);
519 try {
520 final byte[] bytes = EditorUiUtils.getCompressedThumbnailBitmapBytes(
521 getContext(), photoUri);
522 if (bytes != null) {
523 mPhotoValuesDelta.setPhoto(bytes);
524 }
525 } catch (FileNotFoundException e) {
526 elog("Failed to get bitmap from photo Uri");
527 }
528
529 mPhotoView.setFullSizedPhoto(photoUri);
530 }
531
Walter Jang41b3ea12015-03-09 17:30:06 -0700532 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800533 * Pass through to {@link CompactPhotoEditorView#isWritablePhotoSet}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800534 */
535 public boolean isWritablePhotoSet() {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700536 return mPhotoView.isWritablePhotoSet();
Walter Jang3efae4a2015-02-18 11:12:00 -0800537 }
538
539 /**
Walter Jang3efae4a2015-02-18 11:12:00 -0800540 * Get the raw contact ID for the CompactHeaderView photo.
541 */
Walter Jang3efae4a2015-02-18 11:12:00 -0800542 public long getPhotoRawContactId() {
543 return mPhotoRawContactId;
544 }
545
Walter Jang31a74ad2015-10-02 19:17:39 -0700546 /**
547 * Returns a data holder for every non-default/non-empty photo from each raw contact, whether
548 * the raw contact is writable or not.
549 */
550 public ArrayList<CompactPhotoSelectionFragment.Photo> getPhotos() {
551 final ArrayList<CompactPhotoSelectionFragment.Photo> photos = new ArrayList<>();
552
553 final List<KindSectionData> kindSectionDataList =
554 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
555 for (int i = 0; i < kindSectionDataList.size(); i++) {
556 final KindSectionData kindSectionData = kindSectionDataList.get(i);
557 final AccountType accountType = kindSectionData.getAccountType();
558 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
559 if (valuesDeltaList == null || valuesDeltaList.isEmpty()) continue;
560 for (int j = 0; j < valuesDeltaList.size(); j++) {
561 final ValuesDelta valuesDelta = valuesDeltaList.get(j);
562 final Bitmap bitmap = EditorUiUtils.getPhotoBitmap(valuesDelta);
563 if (bitmap == null) continue;
564
565 final CompactPhotoSelectionFragment.Photo photo =
566 new CompactPhotoSelectionFragment.Photo();
567 photo.titleRes = accountType.titleRes;
568 photo.iconRes = accountType.iconRes;
569 photo.syncAdapterPackageName = accountType.syncAdapterPackageName;
570 photo.valuesDelta = valuesDelta;
571 photo.primary = valuesDelta.isSuperPrimary();
572 photo.kindSectionDataListIndex = i;
573 photo.valuesDeltaListIndex = j;
574 photos.add(photo);
575 }
576 }
577
578 return photos;
579 }
580
581 /**
582 * Marks the raw contact photo given as primary for the aggregate contact and updates the
583 * UI.
584 */
585 public void setPrimaryPhoto(CompactPhotoSelectionFragment.Photo photo) {
586 // Unset primary for all other photos
587 final List<KindSectionData> kindSectionDataList =
588 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
589 for (KindSectionData kindSectionData : kindSectionDataList) {
590 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
591 for (ValuesDelta valuesDelta : valuesDeltaList) {
592 valuesDelta.setSuperPrimary(false);
593 }
594 }
595
596 // Find the values delta to mark as primary
597 if (photo.kindSectionDataListIndex < 0
598 || photo.kindSectionDataListIndex >= kindSectionDataList.size()) {
599 wlog("Invalid kind section data list index");
600 return;
601 }
602 final KindSectionData kindSectionData =
603 kindSectionDataList.get(photo.kindSectionDataListIndex);
604 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
605 if (photo.valuesDeltaListIndex >= valuesDeltaList.size()) {
606 wlog("Invalid values delta list index");
607 return;
608 }
609 final ValuesDelta valuesDelta = valuesDeltaList.get(photo.valuesDeltaListIndex);
610 valuesDelta.setFromTemplate(false);
611 valuesDelta.setSuperPrimary(true);
612
613 // Update the UI
614 mPhotoView.setPhoto(valuesDelta, mMaterialPalette);
615 }
616
Walter Jangd35e5ef2015-02-24 09:18:16 -0800617 public View getAggregationAnchorView() {
Walter Jangf10ca152015-09-22 15:23:55 -0700618 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
Walter Jang192a01c2015-09-22 15:23:55 -0700619 StructuredName.CONTENT_ITEM_TYPE);
Walter Jangf10ca152015-09-22 15:23:55 -0700620 if (!kindSectionViews.isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700621 return mKindSectionViews.getChildAt(0).findViewById(R.id.anchor_view);
Walter Jangd35e5ef2015-02-24 09:18:16 -0800622 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700623 return null;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800624 }
625
Walter Jangf10ca152015-09-22 15:23:55 -0700626 public void setGroupMetaData(Cursor groupMetaData) {
627 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
628 GroupMembership.CONTENT_ITEM_TYPE);
629 for (CompactKindSectionView kindSectionView : kindSectionViews) {
630 kindSectionView.setGroupMetaData(groupMetaData);
Walter Jangd6753152015-10-02 09:23:13 -0700631 if (mIsExpanded) {
632 kindSectionView.setHideWhenEmpty(false);
633 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
634 }
Walter Jangf10ca152015-09-22 15:23:55 -0700635 }
636 }
637
Walter Jangf46abd82015-02-20 16:52:04 -0800638 public void setState(RawContactDeltaList rawContactDeltas,
Walter Jang06f73a12015-06-17 11:15:48 -0700639 MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700640 long photoId, boolean hasNewContact, boolean isUserProfile,
641 AccountWithDataSet primaryAccount) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700642 mKindSectionDataMap.clear();
Walter Jangf5dfea42015-09-16 12:30:36 -0700643 mKindSectionViews.removeAllViews();
Walter Jang363d3fd2015-09-16 10:29:07 -0700644 mMoreFields.setVisibility(View.VISIBLE);
Walter Jangcab3dce2015-02-09 17:48:03 -0800645
Walter Jangf46abd82015-02-20 16:52:04 -0800646 mMaterialPalette = materialPalette;
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700647 mViewIdGenerator = viewIdGenerator;
Walter Jang708ea9e2015-09-10 15:42:05 -0700648 mPhotoId = photoId;
Walter Jang708ea9e2015-09-10 15:42:05 -0700649 mHasNewContact = hasNewContact;
650 mIsUserProfile = isUserProfile;
651 mPrimaryAccount = primaryAccount;
652 if (mPrimaryAccount == null) {
653 mPrimaryAccount = ContactEditorUtils.getInstance(getContext()).getDefaultAccount();
654 }
655 vlog("state: primary " + mPrimaryAccount);
Walter Jangcab3dce2015-02-09 17:48:03 -0800656
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700657 // Parse the given raw contact deltas
658 if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
659 elog("No raw contact deltas");
Walter Jang79658e12015-09-24 10:36:26 -0700660 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jangf5dfea42015-09-16 12:30:36 -0700661 return;
662 }
Walter Jang192a01c2015-09-22 15:23:55 -0700663 parseRawContactDeltas(rawContactDeltas, mPrimaryAccount);
Walter Jang79658e12015-09-24 10:36:26 -0700664 if (mKindSectionDataMap.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700665 elog("No kind section data parsed from RawContactDelta(s)");
Walter Jang79658e12015-09-24 10:36:26 -0700666 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700667 return;
668 }
669
670 // Setup the view
Tingting Wang655ad1a2015-10-05 17:51:14 -0700671 addAccountInfo(rawContactDeltas);
Walter Jang363d3fd2015-09-16 10:29:07 -0700672 addPhotoView();
Walter Jangf5dfea42015-09-16 12:30:36 -0700673 addKindSectionViews();
Walter Jang79658e12015-09-24 10:36:26 -0700674
Walter Jangd6753152015-10-02 09:23:13 -0700675 if (mIsExpanded) {
676 showAllFields();
677 }
678
679 if (mListener != null) mListener.onEditorsBound();
Walter Jangcab3dce2015-02-09 17:48:03 -0800680 }
681
Walter Jang192a01c2015-09-22 15:23:55 -0700682 private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas,
683 AccountWithDataSet primaryAccount) {
684 if (primaryAccount != null) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700685 // Use the first writable contact that matches the primary account
686 for (RawContactDelta rawContactDelta : rawContactDeltas) {
687 if (!rawContactDelta.isVisible()) continue;
688 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700689 if (accountType == null || !accountType.areContactsWritable()) continue;
Walter Jang192a01c2015-09-22 15:23:55 -0700690 if (matchesAccount(primaryAccount, rawContactDelta)) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700691 vlog("parse: matched primary account raw contact");
Walter Jang708ea9e2015-09-10 15:42:05 -0700692 mPrimaryRawContactDelta = rawContactDelta;
693 break;
694 }
Walter Jang2d3f31c2015-06-18 23:15:31 -0700695 }
696 }
Walter Jang708ea9e2015-09-10 15:42:05 -0700697 if (mPrimaryRawContactDelta == null) {
698 // Fall back to the first writable raw contact
699 for (RawContactDelta rawContactDelta : rawContactDeltas) {
700 if (!rawContactDelta.isVisible()) continue;
701 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
702 if (accountType != null && accountType.areContactsWritable()) {
703 vlog("parse: falling back to the first writable raw contact as primary");
704 mPrimaryRawContactDelta = rawContactDelta;
705 break;
706 }
707 }
Walter Jang2d3f31c2015-06-18 23:15:31 -0700708 }
Walter Jang192a01c2015-09-22 15:23:55 -0700709
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700710 if (mPrimaryRawContactDelta != null) {
711 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
712 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
713 StructuredName.CONTENT_ITEM_TYPE);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700714 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
715 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
716 Photo.CONTENT_ITEM_TYPE);
717 }
Walter Jangfa127a12015-06-18 09:48:18 -0700718
Walter Jang363d3fd2015-09-16 10:29:07 -0700719 // Build the kind section data list map
Walter Jang192a01c2015-09-22 15:23:55 -0700720 vlog("parse: " + rawContactDeltas.size() + " rawContactDelta(s)");
721 for (int j = 0; j < rawContactDeltas.size(); j++) {
722 final RawContactDelta rawContactDelta = rawContactDeltas.get(j);
723 vlog("parse: " + j + " rawContactDelta" + rawContactDelta);
Walter Jang363d3fd2015-09-16 10:29:07 -0700724 if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
Walter Jangcab3dce2015-02-09 17:48:03 -0800725 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jang363d3fd2015-09-16 10:29:07 -0700726 if (accountType == null) continue;
727 final List<DataKind> dataKinds = accountType.getSortedDataKinds();
728 final int dataKindSize = dataKinds == null ? 0 : dataKinds.size();
729 vlog("parse: " + dataKindSize + " dataKinds(s)");
730 for (int i = 0; i < dataKindSize; i++) {
731 final DataKind dataKind = dataKinds.get(i);
Walter Jang192a01c2015-09-22 15:23:55 -0700732 if (dataKind == null || !dataKind.editable) {
733 vlog("parse: " + i + " " + dataKind.mimeType + " dropped read-only");
734 continue;
735 }
736 final String mimeType = dataKind.mimeType;
737
738 // Skip psuedo mime types
739 if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)
740 || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
741 vlog("parse: " + i + " " + dataKind.mimeType + " dropped pseudo type");
742 continue;
743 }
744
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700745 final List<KindSectionData> kindSectionDataList =
Walter Jang192a01c2015-09-22 15:23:55 -0700746 getKindSectionDataList(mimeType);
Walter Jang363d3fd2015-09-16 10:29:07 -0700747 final KindSectionData kindSectionData =
748 new KindSectionData(accountType, dataKind, rawContactDelta);
749 kindSectionDataList.add(kindSectionData);
Walter Jang192a01c2015-09-22 15:23:55 -0700750
Walter Jang79658e12015-09-24 10:36:26 -0700751 // Note we must create nickname entries
Walter Jang192a01c2015-09-22 15:23:55 -0700752 if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jang79658e12015-09-24 10:36:26 -0700753 && kindSectionData.getValuesDeltas().isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700754 RawContactModifier.insertChild(rawContactDelta, dataKind);
755 }
756
Walter Jang363d3fd2015-09-16 10:29:07 -0700757 vlog("parse: " + i + " " + dataKind.mimeType + " " +
Walter Jang192a01c2015-09-22 15:23:55 -0700758 kindSectionData.getValuesDeltas().size() + " value(s)");
Walter Jangac679af2015-06-01 12:17:06 -0700759 }
760 }
Walter Jang3efae4a2015-02-18 11:12:00 -0800761 }
762
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700763 private List<KindSectionData> getKindSectionDataList(String mimeType) {
Walter Jang192a01c2015-09-22 15:23:55 -0700764 // Put structured names and nicknames together
765 mimeType = Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
766 ? StructuredName.CONTENT_ITEM_TYPE : mimeType;
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700767 List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
768 if (kindSectionDataList == null) {
769 kindSectionDataList = new ArrayList<>();
770 mKindSectionDataMap.put(mimeType, kindSectionDataList);
771 }
772 return kindSectionDataList;
773 }
774
Walter Jang192a01c2015-09-22 15:23:55 -0700775 /** Whether the given RawContactDelta belong to the given account. */
776 private boolean matchesAccount(AccountWithDataSet accountWithDataSet,
777 RawContactDelta rawContactDelta) {
778 if (accountWithDataSet == null) return false;
779 return Objects.equals(accountWithDataSet.name, rawContactDelta.getAccountName())
780 && Objects.equals(accountWithDataSet.type, rawContactDelta.getAccountType())
781 && Objects.equals(accountWithDataSet.dataSet, rawContactDelta.getDataSet());
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700782 }
783
Tingting Wang655ad1a2015-10-05 17:51:14 -0700784 private void addAccountInfo(RawContactDeltaList rawContactDeltas) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700785 if (mPrimaryRawContactDelta == null) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700786 mAccountHeaderContainer.setVisibility(View.GONE);
787 mAccountSelectorContainer.setVisibility(View.GONE);
788 return;
789 }
790
791 // Get the account information for the primary raw contact delta
792 final Pair<String,String> accountInfo = EditorUiUtils.getAccountInfo(getContext(),
793 mIsUserProfile, mPrimaryRawContactDelta.getAccountName(),
794 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager));
795
796 // The account header and selector show the same information so both shouldn't be visible
797 // at the same time
798 final List<AccountWithDataSet> accounts =
799 AccountTypeManager.getInstance(getContext()).getAccounts(true);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700800 mRawContactContainer.setVisibility(View.GONE);
Walter Jang708ea9e2015-09-10 15:42:05 -0700801 if (mHasNewContact && !mIsUserProfile && accounts.size() > 1) {
802 mAccountHeaderContainer.setVisibility(View.GONE);
803 addAccountSelector(accountInfo);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700804 } else if (mHasNewContact && !mIsUserProfile) {
805 addAccountHeader(accountInfo);
806 mAccountSelectorContainer.setVisibility(View.GONE);
807 } else if (rawContactDeltas.size() > 1) {
808 mAccountHeaderContainer.setVisibility(View.GONE);
809 mAccountSelectorContainer.setVisibility(View.GONE);
810 addRawContactAccountSelector(rawContactDeltas);
Walter Jang708ea9e2015-09-10 15:42:05 -0700811 } else {
812 addAccountHeader(accountInfo);
813 mAccountSelectorContainer.setVisibility(View.GONE);
814 }
815 }
816
817 private void addAccountHeader(Pair<String,String> accountInfo) {
Walter Jang03cea2e2015-09-18 17:04:21 -0700818 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700819 // Hide this view so the other text view will be centered vertically
820 mAccountHeaderName.setVisibility(View.GONE);
821 } else {
822 mAccountHeaderName.setVisibility(View.VISIBLE);
823 mAccountHeaderName.setText(accountInfo.first);
824 }
825 mAccountHeaderType.setText(accountInfo.second);
826
827 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);
909 final RawContactDelta rawContactDelta = adapter.getItem(position);
910 final long rawContactId = adapter.getItemId(position);
911 final Uri rawContactUri = ContentUris.withAppendedId(
912 ContactsContract.RawContacts.CONTENT_URI, rawContactId);
913 // Start new activity for the raw contact in selected account.
914 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
915 intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
916
917 ArrayList<ContentValues> values = rawContactDelta.getContentValues();
918 intent.putExtra(ContactsContract.Intents.Insert.DATA, values);
919
920 if (mListener != null) {
921 mListener.onRawContactSelected(rawContactUri, rawContactId);
922 }
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
964 Pair<KindSectionData,ValuesDelta> pair = getPrimaryPhotoKindSectionData(mPhotoId);
Walter Jang363d3fd2015-09-16 10:29:07 -0700965 if (pair == null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700966 wlog("photo: no kind section data parsed");
Walter Jang31a74ad2015-10-02 19:17:39 -0700967 mPhotoView.setReadOnly(true);
Walter Jang363d3fd2015-09-16 10:29:07 -0700968 return;
969 }
Walter Jang06f73a12015-06-17 11:15:48 -0700970
Walter Jang31a74ad2015-10-02 19:17:39 -0700971 // Set the photo view
972 final ValuesDelta primaryValuesDelta = pair.second;
973 mPhotoView.setPhoto(primaryValuesDelta, mMaterialPalette);
974
975 // Find the raw contact ID and values delta that will be written when the photo is edited
976 final KindSectionData primaryKindSectionData = pair.first;
977 if (mHasNewContact && mPrimaryRawContactDelta != null
978 && !primaryKindSectionData.getValuesDeltas().isEmpty()) {
979 // If we're editing a read-only contact we want to display the photo from the
980 // read-only contact in a photo editor view, but update the new raw contact
981 // that was created.
982 mPhotoRawContactId = mPrimaryRawContactDelta.getRawContactId();
983 mPhotoValuesDelta = primaryKindSectionData.getValuesDeltas().get(0);
984 mPhotoView.setReadOnly(false);
985 return;
986 }
987 if (primaryKindSectionData.getAccountType().areContactsWritable() &&
988 !primaryKindSectionData.getValuesDeltas().isEmpty()) {
989 mPhotoRawContactId = primaryKindSectionData.getRawContactDelta().getRawContactId();
990 mPhotoValuesDelta = primaryKindSectionData.getValuesDeltas().get(0);
991 mPhotoView.setReadOnly(false);
992 return;
Walter Jang363d3fd2015-09-16 10:29:07 -0700993 }
Walter Jang06f73a12015-06-17 11:15:48 -0700994
Walter Jang31a74ad2015-10-02 19:17:39 -0700995 final KindSectionData writableKindSectionData = getFirstWritablePhotoKindSectionData();
996 if (writableKindSectionData == null
997 || writableKindSectionData.getValuesDeltas().isEmpty()) {
998 mPhotoView.setReadOnly(true);
999 return;
1000 }
1001 mPhotoRawContactId = writableKindSectionData.getRawContactDelta().getRawContactId();
1002 mPhotoValuesDelta = writableKindSectionData.getValuesDeltas().get(0);
1003 mPhotoView.setReadOnly(false);
Walter Jang363d3fd2015-09-16 10:29:07 -07001004 }
1005
Walter Jang31a74ad2015-10-02 19:17:39 -07001006 private Pair<KindSectionData,ValuesDelta> getPrimaryPhotoKindSectionData(long id) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001007 final String mimeType = Photo.CONTENT_ITEM_TYPE;
Walter Jang363d3fd2015-09-16 10:29:07 -07001008 final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
Walter Jang151f3e62015-02-26 15:29:40 -08001009
Walter Jang363d3fd2015-09-16 10:29:07 -07001010 KindSectionData resultKindSectionData = null;
1011 ValuesDelta resultValuesDelta = null;
1012 if (id > 0) {
1013 // Look for a match for the ID that was passed in
1014 for (KindSectionData kindSectionData : kindSectionDataList) {
1015 resultValuesDelta = kindSectionData.getValuesDeltaById(id);
1016 if (resultValuesDelta != null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001017 vlog("photo: matched kind section data by ID");
Walter Jang363d3fd2015-09-16 10:29:07 -07001018 resultKindSectionData = kindSectionData;
1019 break;
Walter Jang398cd4b2015-06-16 11:17:53 -07001020 }
1021 }
1022 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001023 if (resultKindSectionData == null) {
1024 // Look for a super primary photo
1025 for (KindSectionData kindSectionData : kindSectionDataList) {
1026 resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
1027 if (resultValuesDelta != null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001028 wlog("photo: matched super primary kind section data");
Walter Jang363d3fd2015-09-16 10:29:07 -07001029 resultKindSectionData = kindSectionData;
1030 break;
1031 }
Walter Jang151f3e62015-02-26 15:29:40 -08001032 }
1033 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001034 if (resultKindSectionData == null) {
1035 // Fall back to the first non-empty value
1036 for (KindSectionData kindSectionData : kindSectionDataList) {
1037 resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
1038 if (resultValuesDelta != null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001039 vlog("photo: using first non empty value");
Walter Jang363d3fd2015-09-16 10:29:07 -07001040 resultKindSectionData = kindSectionData;
1041 break;
1042 }
Walter Jang151f3e62015-02-26 15:29:40 -08001043 }
1044 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001045 if (resultKindSectionData == null || resultValuesDelta == null) {
1046 final List<ValuesDelta> valuesDeltaList = kindSectionDataList.get(0).getValuesDeltas();
1047 if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001048 vlog("photo: falling back to first empty entry");
Walter Jang363d3fd2015-09-16 10:29:07 -07001049 resultValuesDelta = valuesDeltaList.get(0);
1050 resultKindSectionData = kindSectionDataList.get(0);
Walter Jang10446452015-02-20 13:51:16 -08001051 }
1052 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001053 return resultKindSectionData != null && resultValuesDelta != null
1054 ? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
Walter Jang10446452015-02-20 13:51:16 -08001055 }
1056
Walter Jang31a74ad2015-10-02 19:17:39 -07001057 private KindSectionData getFirstWritablePhotoKindSectionData() {
1058 final String mimeType = Photo.CONTENT_ITEM_TYPE;
1059 final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
1060 for (KindSectionData kindSectionData : kindSectionDataList) {
1061 if (kindSectionData.getAccountType().areContactsWritable()) {
1062 return kindSectionData;
1063 }
1064 }
1065 return null;
1066 }
1067
Walter Jangf5dfea42015-09-16 12:30:36 -07001068 private void addKindSectionViews() {
1069 // Sort the kinds
1070 final TreeSet<Map.Entry<String,List<KindSectionData>>> entries =
Walter Jang192a01c2015-09-22 15:23:55 -07001071 new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
Walter Jangf5dfea42015-09-16 12:30:36 -07001072 entries.addAll(mKindSectionDataMap.entrySet());
Walter Jang3efae4a2015-02-18 11:12:00 -08001073
Walter Jangf5dfea42015-09-16 12:30:36 -07001074 vlog("kind: " + entries.size() + " kindSection(s)");
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001075 int i = -1;
Walter Jangf5dfea42015-09-16 12:30:36 -07001076 for (Map.Entry<String, List<KindSectionData>> entry : entries) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001077 i++;
1078
Walter Jangf5dfea42015-09-16 12:30:36 -07001079 final String mimeType = entry.getKey();
1080 final List<KindSectionData> kindSectionDataList = entry.getValue();
Walter Jangab50e6f2015-06-15 08:57:22 -07001081
Walter Jangf5dfea42015-09-16 12:30:36 -07001082 // Ignore mime types that we've already handled
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001083 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
1084 vlog("kind: " + i + " " + mimeType + " dropped");
1085 continue;
1086 }
Walter Jangf5dfea42015-09-16 12:30:36 -07001087
Walter Jang192a01c2015-09-22 15:23:55 -07001088 if (kindSectionDataList != null && !kindSectionDataList.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001089 vlog("kind: " + i + " " + mimeType + ": " + kindSectionDataList.size() +
1090 " kindSectionData(s)");
1091
Walter Jangf5dfea42015-09-16 12:30:36 -07001092 final CompactKindSectionView kindSectionView = inflateKindSectionView(
1093 mKindSectionViews, kindSectionDataList, mimeType);
Walter Jangf5dfea42015-09-16 12:30:36 -07001094 mKindSectionViews.addView(kindSectionView);
Walter Jangf10ca152015-09-22 15:23:55 -07001095
1096 // Keep a pointer to all the KindSectionsViews for each mimeType
1097 getKindSectionViews(mimeType).add(kindSectionView);
Walter Jangcab3dce2015-02-09 17:48:03 -08001098 }
1099 }
1100 }
1101
Walter Jangf10ca152015-09-22 15:23:55 -07001102 private List<CompactKindSectionView> getKindSectionViews(String mimeType) {
1103 List<CompactKindSectionView> kindSectionViews = mKindSectionViewsMap.get(mimeType);
1104 if (kindSectionViews == null) {
1105 kindSectionViews = new ArrayList<>();
1106 mKindSectionViewsMap.put(mimeType, kindSectionViews);
1107 }
1108 return kindSectionViews;
1109 }
1110
Walter Jangf5dfea42015-09-16 12:30:36 -07001111 private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
1112 List<KindSectionData> kindSectionDataList, String mimeType) {
1113 final CompactKindSectionView kindSectionView = (CompactKindSectionView)
1114 mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
1115 /* attachToRoot =*/ false);
1116
Walter Jang192a01c2015-09-22 15:23:55 -07001117 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jangf5dfea42015-09-16 12:30:36 -07001118 || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang192a01c2015-09-22 15:23:55 -07001119 // Phone numbers and email addresses are always displayed,
1120 // even if they are empty
Walter Jangf5dfea42015-09-16 12:30:36 -07001121 kindSectionView.setHideWhenEmpty(false);
1122 }
Walter Jangf5dfea42015-09-16 12:30:36 -07001123
Walter Jang192a01c2015-09-22 15:23:55 -07001124 // Since phone numbers and email addresses displayed even if they are empty,
1125 // they will be the only types you add new values to initially for new contacts
1126 kindSectionView.setShowOneEmptyEditor(true);
1127
1128 // Sort so the editors wind up in the order we want
1129 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
1130 Collections.sort(kindSectionDataList, new NameEditorComparator(getContext(),
1131 mPrimaryRawContactDelta));
1132 } else {
1133 Collections.sort(kindSectionDataList, new EditorComparator(getContext()));
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001134 }
1135
Walter Jang79658e12015-09-24 10:36:26 -07001136 kindSectionView.setState(kindSectionDataList, mViewIdGenerator, mListener);
Walter Jangf5dfea42015-09-16 12:30:36 -07001137
1138 return kindSectionView;
Walter Jangcab3dce2015-02-09 17:48:03 -08001139 }
1140
Walter Jangd6753152015-10-02 09:23:13 -07001141 private void showAllFields() {
Walter Jang79658e12015-09-24 10:36:26 -07001142 // Stop hiding empty editors and allow the user to enter values for all kinds now
1143 for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
1144 final CompactKindSectionView kindSectionView =
1145 (CompactKindSectionView) mKindSectionViews.getChildAt(i);
1146 kindSectionView.setHideWhenEmpty(false);
1147 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
1148 }
1149 mIsExpanded = true;
Walter Jang79658e12015-09-24 10:36:26 -07001150
Walter Jangf5dfea42015-09-16 12:30:36 -07001151 // Hide the more fields button
1152 mMoreFields.setVisibility(View.GONE);
Walter Jangcab3dce2015-02-09 17:48:03 -08001153 }
1154
Walter Jangbf63a6d2015-05-05 09:14:35 -07001155 private static void vlog(String message) {
1156 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1157 Log.v(TAG, message);
Walter Jangcab3dce2015-02-09 17:48:03 -08001158 }
1159 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001160
1161 private static void wlog(String message) {
1162 if (Log.isLoggable(TAG, Log.WARN)) {
1163 Log.w(TAG, message);
1164 }
1165 }
1166
1167 private static void elog(String message) {
1168 Log.e(TAG, message);
1169 }
Walter Jangcab3dce2015-02-09 17:48:03 -08001170}