blob: a6f2d1c2956e50740c48f4697bffed6787a34217 [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 */
Tingting Wang91cee282015-10-07 13:48:17 -0700132 public void onRawContactSelected(Uri uri, long rawContactId, boolean isReadOnly);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700133 }
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;
Tingting Wang91cee282015-10-07 13:48:17 -0700385 private ImageView mAccountHeaderIcon;
Walter Jang708ea9e2015-09-10 15:42:05 -0700386
387 // Account selector
388 private View mAccountSelectorContainer;
389 private View mAccountSelector;
390 private TextView mAccountSelectorType;
391 private TextView mAccountSelectorName;
392
Tingting Wang655ad1a2015-10-05 17:51:14 -0700393 // Raw contacts selector
394 private View mRawContactContainer;
395 private TextView mRawContactSummary;
396 private ImageView mPrimaryAccountIcon;
397
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700398 private CompactPhotoEditorView mPhotoView;
Walter Jangf5dfea42015-09-16 12:30:36 -0700399 private ViewGroup mKindSectionViews;
Walter Jangf10ca152015-09-22 15:23:55 -0700400 private Map<String,List<CompactKindSectionView>> mKindSectionViewsMap = new HashMap<>();
Walter Jangb6ca2722015-02-20 11:10:25 -0800401 private View mMoreFields;
Walter Jangcab3dce2015-02-09 17:48:03 -0800402
Walter Jang79658e12015-09-24 10:36:26 -0700403 private boolean mIsExpanded;
Walter Jang3efae4a2015-02-18 11:12:00 -0800404 private long mPhotoRawContactId;
Walter Jang31a74ad2015-10-02 19:17:39 -0700405 private ValuesDelta mPhotoValuesDelta;
Walter Jang3efae4a2015-02-18 11:12:00 -0800406
Walter Jangcab3dce2015-02-09 17:48:03 -0800407 public CompactRawContactsEditorView(Context context) {
408 super(context);
409 }
410
411 public CompactRawContactsEditorView(Context context, AttributeSet attrs) {
412 super(context, attrs);
413 }
414
Walter Jangb6ca2722015-02-20 11:10:25 -0800415 /**
416 * Sets the receiver for {@link CompactRawContactsEditorView} callbacks.
417 */
418 public void setListener(Listener listener) {
419 mListener = listener;
420 }
421
Walter Jangcab3dce2015-02-09 17:48:03 -0800422 @Override
423 protected void onFinishInflate() {
424 super.onFinishInflate();
425
426 mAccountTypeManager = AccountTypeManager.getInstance(getContext());
427 mLayoutInflater = (LayoutInflater)
428 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
429
Walter Jang708ea9e2015-09-10 15:42:05 -0700430 // Account header
431 mAccountHeaderContainer = findViewById(R.id.account_container);
432 mAccountHeaderType = (TextView) findViewById(R.id.account_type);
433 mAccountHeaderName = (TextView) findViewById(R.id.account_name);
Tingting Wang91cee282015-10-07 13:48:17 -0700434 mAccountHeaderIcon = (ImageView) findViewById(R.id.account_type_icon);
Walter Jang708ea9e2015-09-10 15:42:05 -0700435
436 // Account selector
437 mAccountSelectorContainer = findViewById(R.id.account_selector_container);
438 mAccountSelector = findViewById(R.id.account);
439 mAccountSelectorType = (TextView) findViewById(R.id.account_type_selector);
440 mAccountSelectorName = (TextView) findViewById(R.id.account_name_selector);
441
Tingting Wang655ad1a2015-10-05 17:51:14 -0700442 // Raw contacts selector
443 mRawContactContainer = findViewById(R.id.all_rawcontacts_accounts_container);
444 mRawContactSummary = (TextView) findViewById(R.id.rawcontacts_accounts_summary);
445 mPrimaryAccountIcon = (ImageView) findViewById(R.id.primary_account_icon);
446
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700447 mPhotoView = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
Walter Jangf5dfea42015-09-16 12:30:36 -0700448 mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
Walter Jangb6ca2722015-02-20 11:10:25 -0800449 mMoreFields = findViewById(R.id.more_fields);
450 mMoreFields.setOnClickListener(this);
451 }
452
Walter Jangb6ca2722015-02-20 11:10:25 -0800453 @Override
454 public void onClick(View view) {
Walter Jangf5dfea42015-09-16 12:30:36 -0700455 if (view.getId() == R.id.more_fields) {
Walter Jangd6753152015-10-02 09:23:13 -0700456 showAllFields();
Walter Jangb6ca2722015-02-20 11:10:25 -0800457 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800458 }
459
460 @Override
461 public void setEnabled(boolean enabled) {
462 super.setEnabled(enabled);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700463 final int childCount = mKindSectionViews.getChildCount();
464 for (int i = 0; i < childCount; i++) {
465 mKindSectionViews.getChildAt(i).setEnabled(enabled);
Walter Jangcab3dce2015-02-09 17:48:03 -0800466 }
467 }
468
Walter Jang79658e12015-09-24 10:36:26 -0700469 @Override
470 public Parcelable onSaveInstanceState() {
471 final Parcelable superState = super.onSaveInstanceState();
472 final SavedState savedState = new SavedState(superState);
473 savedState.mIsExpanded = mIsExpanded;
474 return savedState;
475 }
476
477 @Override
478 public void onRestoreInstanceState(Parcelable state) {
479 if(!(state instanceof SavedState)) {
480 super.onRestoreInstanceState(state);
481 return;
482 }
483 final SavedState savedState = (SavedState) state;
484 super.onRestoreInstanceState(savedState.getSuperState());
485 mIsExpanded = savedState.mIsExpanded;
Walter Jangd6753152015-10-02 09:23:13 -0700486 if (mIsExpanded) {
487 showAllFields();
Walter Jang79658e12015-09-24 10:36:26 -0700488 }
489 }
490
Walter Jang3efae4a2015-02-18 11:12:00 -0800491 /**
Walter Jang31a74ad2015-10-02 19:17:39 -0700492 * Pass through to {@link CompactPhotoEditorView#setListener}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800493 */
Walter Jang31a74ad2015-10-02 19:17:39 -0700494 public void setPhotoListener(CompactPhotoEditorView.Listener listener) {
495 mPhotoView.setListener(listener);
Walter Jang3efae4a2015-02-18 11:12:00 -0800496 }
497
Walter Jang31a74ad2015-10-02 19:17:39 -0700498 public void removePhoto() {
499 mPhotoValuesDelta.setFromTemplate(false);
500 mPhotoValuesDelta.put(Photo.PHOTO, (byte[]) null);
501
502 mPhotoView.removePhoto();
Walter Jang3efae4a2015-02-18 11:12:00 -0800503 }
504
505 /**
Walter Jang41b3ea12015-03-09 17:30:06 -0700506 * Pass through to {@link CompactPhotoEditorView#setFullSizedPhoto(Uri)}.
507 */
508 public void setFullSizePhoto(Uri photoUri) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700509 mPhotoView.setFullSizedPhoto(photoUri);
Walter Jang41b3ea12015-03-09 17:30:06 -0700510 }
511
Walter Jang31a74ad2015-10-02 19:17:39 -0700512 public void updatePhoto(Uri photoUri) {
513 // Even though high-res photos cannot be saved by passing them via
514 // an EntityDeltaList (since they cause the Bundle size limit to be
515 // exceeded), we still pass a low-res thumbnail. This simplifies
516 // code all over the place, because we don't have to test whether
517 // there is a change in EITHER the delta-list OR a changed photo...
518 // this way, there is always a change in the delta-list.
519 mPhotoValuesDelta.setFromTemplate(false);
520 mPhotoValuesDelta.setSuperPrimary(true);
521 try {
522 final byte[] bytes = EditorUiUtils.getCompressedThumbnailBitmapBytes(
523 getContext(), photoUri);
524 if (bytes != null) {
525 mPhotoValuesDelta.setPhoto(bytes);
526 }
527 } catch (FileNotFoundException e) {
528 elog("Failed to get bitmap from photo Uri");
529 }
530
531 mPhotoView.setFullSizedPhoto(photoUri);
532 }
533
Walter Jang41b3ea12015-03-09 17:30:06 -0700534 /**
Walter Janga5e4bb22015-02-24 13:08:16 -0800535 * Pass through to {@link CompactPhotoEditorView#isWritablePhotoSet}.
Walter Jang3efae4a2015-02-18 11:12:00 -0800536 */
537 public boolean isWritablePhotoSet() {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700538 return mPhotoView.isWritablePhotoSet();
Walter Jang3efae4a2015-02-18 11:12:00 -0800539 }
540
541 /**
Walter Jang3efae4a2015-02-18 11:12:00 -0800542 * Get the raw contact ID for the CompactHeaderView photo.
543 */
Walter Jang3efae4a2015-02-18 11:12:00 -0800544 public long getPhotoRawContactId() {
545 return mPhotoRawContactId;
546 }
547
Walter Jang31a74ad2015-10-02 19:17:39 -0700548 /**
549 * Returns a data holder for every non-default/non-empty photo from each raw contact, whether
550 * the raw contact is writable or not.
551 */
552 public ArrayList<CompactPhotoSelectionFragment.Photo> getPhotos() {
553 final ArrayList<CompactPhotoSelectionFragment.Photo> photos = new ArrayList<>();
554
555 final List<KindSectionData> kindSectionDataList =
556 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
557 for (int i = 0; i < kindSectionDataList.size(); i++) {
558 final KindSectionData kindSectionData = kindSectionDataList.get(i);
559 final AccountType accountType = kindSectionData.getAccountType();
560 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
561 if (valuesDeltaList == null || valuesDeltaList.isEmpty()) continue;
562 for (int j = 0; j < valuesDeltaList.size(); j++) {
563 final ValuesDelta valuesDelta = valuesDeltaList.get(j);
564 final Bitmap bitmap = EditorUiUtils.getPhotoBitmap(valuesDelta);
565 if (bitmap == null) continue;
566
567 final CompactPhotoSelectionFragment.Photo photo =
568 new CompactPhotoSelectionFragment.Photo();
569 photo.titleRes = accountType.titleRes;
570 photo.iconRes = accountType.iconRes;
571 photo.syncAdapterPackageName = accountType.syncAdapterPackageName;
572 photo.valuesDelta = valuesDelta;
573 photo.primary = valuesDelta.isSuperPrimary();
574 photo.kindSectionDataListIndex = i;
575 photo.valuesDeltaListIndex = j;
576 photos.add(photo);
577 }
578 }
579
580 return photos;
581 }
582
583 /**
584 * Marks the raw contact photo given as primary for the aggregate contact and updates the
585 * UI.
586 */
587 public void setPrimaryPhoto(CompactPhotoSelectionFragment.Photo photo) {
588 // Unset primary for all other photos
589 final List<KindSectionData> kindSectionDataList =
590 mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
591 for (KindSectionData kindSectionData : kindSectionDataList) {
592 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
593 for (ValuesDelta valuesDelta : valuesDeltaList) {
594 valuesDelta.setSuperPrimary(false);
595 }
596 }
597
598 // Find the values delta to mark as primary
599 if (photo.kindSectionDataListIndex < 0
600 || photo.kindSectionDataListIndex >= kindSectionDataList.size()) {
601 wlog("Invalid kind section data list index");
602 return;
603 }
604 final KindSectionData kindSectionData =
605 kindSectionDataList.get(photo.kindSectionDataListIndex);
606 final List<ValuesDelta> valuesDeltaList = kindSectionData.getValuesDeltas();
607 if (photo.valuesDeltaListIndex >= valuesDeltaList.size()) {
608 wlog("Invalid values delta list index");
609 return;
610 }
611 final ValuesDelta valuesDelta = valuesDeltaList.get(photo.valuesDeltaListIndex);
612 valuesDelta.setFromTemplate(false);
613 valuesDelta.setSuperPrimary(true);
614
615 // Update the UI
616 mPhotoView.setPhoto(valuesDelta, mMaterialPalette);
617 }
618
Walter Jangd35e5ef2015-02-24 09:18:16 -0800619 public View getAggregationAnchorView() {
Walter Jangf10ca152015-09-22 15:23:55 -0700620 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
Walter Jang192a01c2015-09-22 15:23:55 -0700621 StructuredName.CONTENT_ITEM_TYPE);
Walter Jangf10ca152015-09-22 15:23:55 -0700622 if (!kindSectionViews.isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700623 return mKindSectionViews.getChildAt(0).findViewById(R.id.anchor_view);
Walter Jangd35e5ef2015-02-24 09:18:16 -0800624 }
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700625 return null;
Walter Jangd35e5ef2015-02-24 09:18:16 -0800626 }
627
Walter Jangf10ca152015-09-22 15:23:55 -0700628 public void setGroupMetaData(Cursor groupMetaData) {
629 final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
630 GroupMembership.CONTENT_ITEM_TYPE);
631 for (CompactKindSectionView kindSectionView : kindSectionViews) {
632 kindSectionView.setGroupMetaData(groupMetaData);
Walter Jangd6753152015-10-02 09:23:13 -0700633 if (mIsExpanded) {
634 kindSectionView.setHideWhenEmpty(false);
635 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
636 }
Walter Jangf10ca152015-09-22 15:23:55 -0700637 }
638 }
639
Walter Jangf46abd82015-02-20 16:52:04 -0800640 public void setState(RawContactDeltaList rawContactDeltas,
Walter Jang06f73a12015-06-17 11:15:48 -0700641 MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700642 long photoId, boolean hasNewContact, boolean isUserProfile,
643 AccountWithDataSet primaryAccount) {
Walter Jang363d3fd2015-09-16 10:29:07 -0700644 mKindSectionDataMap.clear();
Walter Jangf5dfea42015-09-16 12:30:36 -0700645 mKindSectionViews.removeAllViews();
Walter Jang363d3fd2015-09-16 10:29:07 -0700646 mMoreFields.setVisibility(View.VISIBLE);
Walter Jangcab3dce2015-02-09 17:48:03 -0800647
Walter Jangf46abd82015-02-20 16:52:04 -0800648 mMaterialPalette = materialPalette;
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700649 mViewIdGenerator = viewIdGenerator;
Walter Jang708ea9e2015-09-10 15:42:05 -0700650 mPhotoId = photoId;
Walter Jang708ea9e2015-09-10 15:42:05 -0700651 mHasNewContact = hasNewContact;
652 mIsUserProfile = isUserProfile;
653 mPrimaryAccount = primaryAccount;
654 if (mPrimaryAccount == null) {
655 mPrimaryAccount = ContactEditorUtils.getInstance(getContext()).getDefaultAccount();
656 }
657 vlog("state: primary " + mPrimaryAccount);
Walter Jangcab3dce2015-02-09 17:48:03 -0800658
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700659 // Parse the given raw contact deltas
660 if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
661 elog("No raw contact deltas");
Walter Jang79658e12015-09-24 10:36:26 -0700662 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jangf5dfea42015-09-16 12:30:36 -0700663 return;
664 }
Walter Jang192a01c2015-09-22 15:23:55 -0700665 parseRawContactDeltas(rawContactDeltas, mPrimaryAccount);
Walter Jang79658e12015-09-24 10:36:26 -0700666 if (mKindSectionDataMap.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700667 elog("No kind section data parsed from RawContactDelta(s)");
Walter Jang79658e12015-09-24 10:36:26 -0700668 if (mListener != null) mListener.onBindEditorsFailed();
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700669 return;
670 }
671
672 // Setup the view
Tingting Wang655ad1a2015-10-05 17:51:14 -0700673 addAccountInfo(rawContactDeltas);
Walter Jang363d3fd2015-09-16 10:29:07 -0700674 addPhotoView();
Walter Jangf5dfea42015-09-16 12:30:36 -0700675 addKindSectionViews();
Walter Jang79658e12015-09-24 10:36:26 -0700676
Walter Jangd6753152015-10-02 09:23:13 -0700677 if (mIsExpanded) {
678 showAllFields();
679 }
680
681 if (mListener != null) mListener.onEditorsBound();
Walter Jangcab3dce2015-02-09 17:48:03 -0800682 }
683
Walter Jang192a01c2015-09-22 15:23:55 -0700684 private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas,
685 AccountWithDataSet primaryAccount) {
686 if (primaryAccount != null) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700687 // Use the first writable contact that matches the primary account
688 for (RawContactDelta rawContactDelta : rawContactDeltas) {
689 if (!rawContactDelta.isVisible()) continue;
690 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700691 if (accountType == null || !accountType.areContactsWritable()) continue;
Walter Jang192a01c2015-09-22 15:23:55 -0700692 if (matchesAccount(primaryAccount, rawContactDelta)) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700693 vlog("parse: matched primary account raw contact");
Walter Jang708ea9e2015-09-10 15:42:05 -0700694 mPrimaryRawContactDelta = rawContactDelta;
695 break;
696 }
Walter Jang2d3f31c2015-06-18 23:15:31 -0700697 }
698 }
Walter Jang708ea9e2015-09-10 15:42:05 -0700699 if (mPrimaryRawContactDelta == null) {
700 // Fall back to the first writable raw contact
701 for (RawContactDelta rawContactDelta : rawContactDeltas) {
702 if (!rawContactDelta.isVisible()) continue;
703 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
704 if (accountType != null && accountType.areContactsWritable()) {
705 vlog("parse: falling back to the first writable raw contact as primary");
706 mPrimaryRawContactDelta = rawContactDelta;
707 break;
708 }
709 }
Walter Jang2d3f31c2015-06-18 23:15:31 -0700710 }
Walter Jang192a01c2015-09-22 15:23:55 -0700711
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700712 if (mPrimaryRawContactDelta != null) {
713 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
714 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
715 StructuredName.CONTENT_ITEM_TYPE);
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700716 RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
717 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
718 Photo.CONTENT_ITEM_TYPE);
719 }
Walter Jangfa127a12015-06-18 09:48:18 -0700720
Walter Jang363d3fd2015-09-16 10:29:07 -0700721 // Build the kind section data list map
Walter Jang192a01c2015-09-22 15:23:55 -0700722 vlog("parse: " + rawContactDeltas.size() + " rawContactDelta(s)");
723 for (int j = 0; j < rawContactDeltas.size(); j++) {
724 final RawContactDelta rawContactDelta = rawContactDeltas.get(j);
725 vlog("parse: " + j + " rawContactDelta" + rawContactDelta);
Walter Jang363d3fd2015-09-16 10:29:07 -0700726 if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
Walter Jangcab3dce2015-02-09 17:48:03 -0800727 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jang363d3fd2015-09-16 10:29:07 -0700728 if (accountType == null) continue;
729 final List<DataKind> dataKinds = accountType.getSortedDataKinds();
730 final int dataKindSize = dataKinds == null ? 0 : dataKinds.size();
731 vlog("parse: " + dataKindSize + " dataKinds(s)");
732 for (int i = 0; i < dataKindSize; i++) {
733 final DataKind dataKind = dataKinds.get(i);
Walter Jang192a01c2015-09-22 15:23:55 -0700734 if (dataKind == null || !dataKind.editable) {
735 vlog("parse: " + i + " " + dataKind.mimeType + " dropped read-only");
736 continue;
737 }
738 final String mimeType = dataKind.mimeType;
739
740 // Skip psuedo mime types
741 if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)
742 || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
743 vlog("parse: " + i + " " + dataKind.mimeType + " dropped pseudo type");
744 continue;
745 }
746
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700747 final List<KindSectionData> kindSectionDataList =
Walter Jang192a01c2015-09-22 15:23:55 -0700748 getKindSectionDataList(mimeType);
Walter Jang363d3fd2015-09-16 10:29:07 -0700749 final KindSectionData kindSectionData =
750 new KindSectionData(accountType, dataKind, rawContactDelta);
751 kindSectionDataList.add(kindSectionData);
Walter Jang192a01c2015-09-22 15:23:55 -0700752
Walter Jang79658e12015-09-24 10:36:26 -0700753 // Note we must create nickname entries
Walter Jang192a01c2015-09-22 15:23:55 -0700754 if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jang79658e12015-09-24 10:36:26 -0700755 && kindSectionData.getValuesDeltas().isEmpty()) {
Walter Jang192a01c2015-09-22 15:23:55 -0700756 RawContactModifier.insertChild(rawContactDelta, dataKind);
757 }
758
Walter Jang363d3fd2015-09-16 10:29:07 -0700759 vlog("parse: " + i + " " + dataKind.mimeType + " " +
Walter Jang192a01c2015-09-22 15:23:55 -0700760 kindSectionData.getValuesDeltas().size() + " value(s)");
Walter Jangac679af2015-06-01 12:17:06 -0700761 }
762 }
Walter Jang3efae4a2015-02-18 11:12:00 -0800763 }
764
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700765 private List<KindSectionData> getKindSectionDataList(String mimeType) {
Walter Jang192a01c2015-09-22 15:23:55 -0700766 // Put structured names and nicknames together
767 mimeType = Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
768 ? StructuredName.CONTENT_ITEM_TYPE : mimeType;
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700769 List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
770 if (kindSectionDataList == null) {
771 kindSectionDataList = new ArrayList<>();
772 mKindSectionDataMap.put(mimeType, kindSectionDataList);
773 }
774 return kindSectionDataList;
775 }
776
Walter Jang192a01c2015-09-22 15:23:55 -0700777 /** Whether the given RawContactDelta belong to the given account. */
778 private boolean matchesAccount(AccountWithDataSet accountWithDataSet,
779 RawContactDelta rawContactDelta) {
780 if (accountWithDataSet == null) return false;
781 return Objects.equals(accountWithDataSet.name, rawContactDelta.getAccountName())
782 && Objects.equals(accountWithDataSet.type, rawContactDelta.getAccountType())
783 && Objects.equals(accountWithDataSet.dataSet, rawContactDelta.getDataSet());
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700784 }
785
Tingting Wang655ad1a2015-10-05 17:51:14 -0700786 private void addAccountInfo(RawContactDeltaList rawContactDeltas) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700787 if (mPrimaryRawContactDelta == null) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700788 mAccountHeaderContainer.setVisibility(View.GONE);
789 mAccountSelectorContainer.setVisibility(View.GONE);
790 return;
791 }
792
793 // Get the account information for the primary raw contact delta
794 final Pair<String,String> accountInfo = EditorUiUtils.getAccountInfo(getContext(),
795 mIsUserProfile, mPrimaryRawContactDelta.getAccountName(),
796 mPrimaryRawContactDelta.getAccountType(mAccountTypeManager));
797
798 // The account header and selector show the same information so both shouldn't be visible
799 // at the same time
800 final List<AccountWithDataSet> accounts =
801 AccountTypeManager.getInstance(getContext()).getAccounts(true);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700802 mRawContactContainer.setVisibility(View.GONE);
Walter Jang708ea9e2015-09-10 15:42:05 -0700803 if (mHasNewContact && !mIsUserProfile && accounts.size() > 1) {
804 mAccountHeaderContainer.setVisibility(View.GONE);
805 addAccountSelector(accountInfo);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700806 } else if (mHasNewContact && !mIsUserProfile) {
807 addAccountHeader(accountInfo);
808 mAccountSelectorContainer.setVisibility(View.GONE);
809 } else if (rawContactDeltas.size() > 1) {
810 mAccountHeaderContainer.setVisibility(View.GONE);
811 mAccountSelectorContainer.setVisibility(View.GONE);
812 addRawContactAccountSelector(rawContactDeltas);
Walter Jang708ea9e2015-09-10 15:42:05 -0700813 } else {
814 addAccountHeader(accountInfo);
815 mAccountSelectorContainer.setVisibility(View.GONE);
816 }
817 }
818
819 private void addAccountHeader(Pair<String,String> accountInfo) {
Walter Jang03cea2e2015-09-18 17:04:21 -0700820 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700821 // Hide this view so the other text view will be centered vertically
822 mAccountHeaderName.setVisibility(View.GONE);
823 } else {
824 mAccountHeaderName.setVisibility(View.VISIBLE);
825 mAccountHeaderName.setText(accountInfo.first);
826 }
827 mAccountHeaderType.setText(accountInfo.second);
828
Tingting Wang91cee282015-10-07 13:48:17 -0700829 final AccountType primaryAccountType = mPrimaryRawContactDelta.getRawContactAccountType(
830 getContext());
831 mAccountHeaderIcon.setImageDrawable(primaryAccountType.getDisplayIcon(getContext()));
832
Walter Jang708ea9e2015-09-10 15:42:05 -0700833 mAccountHeaderContainer.setContentDescription(
834 EditorUiUtils.getAccountInfoContentDescription(
835 accountInfo.first, accountInfo.second));
836 }
837
838 private void addAccountSelector(Pair<String,String> accountInfo) {
839 mAccountSelectorContainer.setVisibility(View.VISIBLE);
840
Walter Jang03cea2e2015-09-18 17:04:21 -0700841 if (TextUtils.isEmpty(accountInfo.first)) {
Walter Jang708ea9e2015-09-10 15:42:05 -0700842 // Hide this view so the other text view will be centered vertically
843 mAccountSelectorName.setVisibility(View.GONE);
844 } else {
845 mAccountSelectorName.setVisibility(View.VISIBLE);
846 mAccountSelectorName.setText(accountInfo.first);
847 }
848 mAccountSelectorType.setText(accountInfo.second);
849
850 mAccountSelectorContainer.setContentDescription(
851 EditorUiUtils.getAccountInfoContentDescription(
852 accountInfo.first, accountInfo.second));
853
854 mAccountSelector.setOnClickListener(new View.OnClickListener() {
855 @Override
856 public void onClick(View v) {
857 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
858 final AccountsListAdapter adapter =
859 new AccountsListAdapter(getContext(),
860 AccountsListAdapter.AccountListFilter.ACCOUNTS_CONTACT_WRITABLE,
861 mPrimaryAccount);
862 popup.setWidth(mAccountSelectorContainer.getWidth());
863 popup.setAnchorView(mAccountSelectorContainer);
864 popup.setAdapter(adapter);
865 popup.setModal(true);
866 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
867 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
868 @Override
869 public void onItemClick(AdapterView<?> parent, View view, int position,
870 long id) {
871 UiClosables.closeQuietly(popup);
872 final AccountWithDataSet newAccount = adapter.getItem(position);
873 if (mListener != null && !mPrimaryAccount.equals(newAccount)) {
874 mListener.onRebindEditorsForNewContact(
875 mPrimaryRawContactDelta,
876 mPrimaryAccount,
877 newAccount);
878 }
879 }
880 });
881 popup.show();
882 }
883 });
884 }
885
Tingting Wang655ad1a2015-10-05 17:51:14 -0700886 private void addRawContactAccountSelector(final RawContactDeltaList rawContactDeltas) {
887 mRawContactContainer.setVisibility(View.VISIBLE);
888
889 final String accountsSummary = getRawContactsAccountsSummary(
890 getContext(), rawContactDeltas);
891 mRawContactSummary.setText(accountsSummary);
892 mRawContactContainer.setContentDescription(accountsSummary);
893 if (mPrimaryRawContactDelta != null) {
894 final AccountType primaryAccountType = mPrimaryRawContactDelta.getRawContactAccountType(
895 getContext());
896 mPrimaryAccountIcon.setImageDrawable(primaryAccountType.getDisplayIcon(getContext()));
897 }
898
899 mRawContactContainer.setOnClickListener(new View.OnClickListener() {
900 @Override
901 public void onClick(View v) {
902 final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
903 final RawContactAccountListAdapter adapter =
904 new RawContactAccountListAdapter(getContext(), rawContactDeltas);
905 popup.setWidth(mRawContactContainer.getWidth());
906 popup.setAnchorView(mRawContactContainer);
907 popup.setAdapter(adapter);
908 popup.setModal(true);
909 popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
910 popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
911 @Override
912 public void onItemClick(AdapterView<?> parent, View view, int position,
913 long id) {
914 UiClosables.closeQuietly(popup);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700915
916 if (mListener != null) {
Tingting Wang91cee282015-10-07 13:48:17 -0700917 final long rawContactId = adapter.getItemId(position);
918 final Uri rawContactUri = ContentUris.withAppendedId(
919 ContactsContract.RawContacts.CONTENT_URI, rawContactId);
920 final RawContactDelta rawContactDelta = adapter.getItem(position);
921 final AccountTypeManager accountTypes = AccountTypeManager.getInstance(
922 getContext());
923 final AccountType accountType = rawContactDelta.getAccountType(
924 accountTypes);
925 final boolean isReadOnly = !accountType.areContactsWritable();
926
927 mListener.onRawContactSelected(rawContactUri, rawContactId, isReadOnly);
Tingting Wang655ad1a2015-10-05 17:51:14 -0700928 }
929 }
930 });
931 popup.show();
932 }
933 });
934 }
935
936 private static String getRawContactsAccountsSummary(
937 Context context, RawContactDeltaList rawContactDeltas) {
938 final Map<String, Integer> accountTypeNumber = new HashMap<>();
939 for (RawContactDelta rawContactDelta : rawContactDeltas) {
940 if (rawContactDelta.isVisible()) {
941 final AccountType accountType = rawContactDelta.getRawContactAccountType(context);
942 final String accountTypeLabel = accountType.getDisplayLabel(context).toString();
943 if (accountTypeNumber.containsKey(accountTypeLabel)) {
944 int number = accountTypeNumber.get(accountTypeLabel);
945 number++;
946 accountTypeNumber.put(accountTypeLabel, number);
947 } else {
948 accountTypeNumber.put(accountTypeLabel, 1);
949 }
950 }
951 }
952
953 final Set<String> linkedAccounts = new HashSet<>();
954 for (String accountTypeLabel : accountTypeNumber.keySet()) {
955 final String number = context.getResources().getQuantityString(
956 R.plurals.quickcontact_suggestion_account_type_number,
957 accountTypeNumber.get(accountTypeLabel),
958 accountTypeNumber.get(accountTypeLabel));
959 final String accountWithNumber = context.getResources().getString(
960 R.string.quickcontact_suggestion_account_type,
961 accountTypeLabel,
962 number);
963 linkedAccounts.add(accountWithNumber);
964 }
965 return TextUtils.join(",", linkedAccounts);
966 }
967
Walter Jang363d3fd2015-09-16 10:29:07 -0700968 private void addPhotoView() {
Walter Jang31a74ad2015-10-02 19:17:39 -0700969 // Get the kind section data and values delta that we will display in the photo view
970 Pair<KindSectionData,ValuesDelta> pair = getPrimaryPhotoKindSectionData(mPhotoId);
Walter Jang363d3fd2015-09-16 10:29:07 -0700971 if (pair == null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -0700972 wlog("photo: no kind section data parsed");
Walter Jang31a74ad2015-10-02 19:17:39 -0700973 mPhotoView.setReadOnly(true);
Walter Jang363d3fd2015-09-16 10:29:07 -0700974 return;
975 }
Walter Jang06f73a12015-06-17 11:15:48 -0700976
Walter Jang31a74ad2015-10-02 19:17:39 -0700977 // Set the photo view
978 final ValuesDelta primaryValuesDelta = pair.second;
979 mPhotoView.setPhoto(primaryValuesDelta, mMaterialPalette);
980
981 // Find the raw contact ID and values delta that will be written when the photo is edited
982 final KindSectionData primaryKindSectionData = pair.first;
983 if (mHasNewContact && mPrimaryRawContactDelta != null
984 && !primaryKindSectionData.getValuesDeltas().isEmpty()) {
985 // If we're editing a read-only contact we want to display the photo from the
986 // read-only contact in a photo editor view, but update the new raw contact
987 // that was created.
988 mPhotoRawContactId = mPrimaryRawContactDelta.getRawContactId();
989 mPhotoValuesDelta = primaryKindSectionData.getValuesDeltas().get(0);
990 mPhotoView.setReadOnly(false);
991 return;
992 }
993 if (primaryKindSectionData.getAccountType().areContactsWritable() &&
994 !primaryKindSectionData.getValuesDeltas().isEmpty()) {
995 mPhotoRawContactId = primaryKindSectionData.getRawContactDelta().getRawContactId();
996 mPhotoValuesDelta = primaryKindSectionData.getValuesDeltas().get(0);
997 mPhotoView.setReadOnly(false);
998 return;
Walter Jang363d3fd2015-09-16 10:29:07 -0700999 }
Walter Jang06f73a12015-06-17 11:15:48 -07001000
Walter Jang31a74ad2015-10-02 19:17:39 -07001001 final KindSectionData writableKindSectionData = getFirstWritablePhotoKindSectionData();
1002 if (writableKindSectionData == null
1003 || writableKindSectionData.getValuesDeltas().isEmpty()) {
1004 mPhotoView.setReadOnly(true);
1005 return;
1006 }
1007 mPhotoRawContactId = writableKindSectionData.getRawContactDelta().getRawContactId();
1008 mPhotoValuesDelta = writableKindSectionData.getValuesDeltas().get(0);
1009 mPhotoView.setReadOnly(false);
Walter Jang363d3fd2015-09-16 10:29:07 -07001010 }
1011
Walter Jang31a74ad2015-10-02 19:17:39 -07001012 private Pair<KindSectionData,ValuesDelta> getPrimaryPhotoKindSectionData(long id) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001013 final String mimeType = Photo.CONTENT_ITEM_TYPE;
Walter Jang363d3fd2015-09-16 10:29:07 -07001014 final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
Walter Jang151f3e62015-02-26 15:29:40 -08001015
Walter Jang363d3fd2015-09-16 10:29:07 -07001016 KindSectionData resultKindSectionData = null;
1017 ValuesDelta resultValuesDelta = null;
1018 if (id > 0) {
1019 // Look for a match for the ID that was passed in
1020 for (KindSectionData kindSectionData : kindSectionDataList) {
1021 resultValuesDelta = kindSectionData.getValuesDeltaById(id);
1022 if (resultValuesDelta != null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001023 vlog("photo: matched kind section data by ID");
Walter Jang363d3fd2015-09-16 10:29:07 -07001024 resultKindSectionData = kindSectionData;
1025 break;
Walter Jang398cd4b2015-06-16 11:17:53 -07001026 }
1027 }
1028 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001029 if (resultKindSectionData == null) {
1030 // Look for a super primary photo
1031 for (KindSectionData kindSectionData : kindSectionDataList) {
1032 resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
1033 if (resultValuesDelta != null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001034 wlog("photo: matched super primary kind section data");
Walter Jang363d3fd2015-09-16 10:29:07 -07001035 resultKindSectionData = kindSectionData;
1036 break;
1037 }
Walter Jang151f3e62015-02-26 15:29:40 -08001038 }
1039 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001040 if (resultKindSectionData == null) {
1041 // Fall back to the first non-empty value
1042 for (KindSectionData kindSectionData : kindSectionDataList) {
1043 resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
1044 if (resultValuesDelta != null) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001045 vlog("photo: using first non empty value");
Walter Jang363d3fd2015-09-16 10:29:07 -07001046 resultKindSectionData = kindSectionData;
1047 break;
1048 }
Walter Jang151f3e62015-02-26 15:29:40 -08001049 }
1050 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001051 if (resultKindSectionData == null || resultValuesDelta == null) {
1052 final List<ValuesDelta> valuesDeltaList = kindSectionDataList.get(0).getValuesDeltas();
1053 if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001054 vlog("photo: falling back to first empty entry");
Walter Jang363d3fd2015-09-16 10:29:07 -07001055 resultValuesDelta = valuesDeltaList.get(0);
1056 resultKindSectionData = kindSectionDataList.get(0);
Walter Jang10446452015-02-20 13:51:16 -08001057 }
1058 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001059 return resultKindSectionData != null && resultValuesDelta != null
1060 ? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
Walter Jang10446452015-02-20 13:51:16 -08001061 }
1062
Walter Jang31a74ad2015-10-02 19:17:39 -07001063 private KindSectionData getFirstWritablePhotoKindSectionData() {
1064 final String mimeType = Photo.CONTENT_ITEM_TYPE;
1065 final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
1066 for (KindSectionData kindSectionData : kindSectionDataList) {
1067 if (kindSectionData.getAccountType().areContactsWritable()) {
1068 return kindSectionData;
1069 }
1070 }
1071 return null;
1072 }
1073
Walter Jangf5dfea42015-09-16 12:30:36 -07001074 private void addKindSectionViews() {
1075 // Sort the kinds
1076 final TreeSet<Map.Entry<String,List<KindSectionData>>> entries =
Walter Jang192a01c2015-09-22 15:23:55 -07001077 new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
Walter Jangf5dfea42015-09-16 12:30:36 -07001078 entries.addAll(mKindSectionDataMap.entrySet());
Walter Jang3efae4a2015-02-18 11:12:00 -08001079
Walter Jangf5dfea42015-09-16 12:30:36 -07001080 vlog("kind: " + entries.size() + " kindSection(s)");
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001081 int i = -1;
Walter Jangf5dfea42015-09-16 12:30:36 -07001082 for (Map.Entry<String, List<KindSectionData>> entry : entries) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001083 i++;
1084
Walter Jangf5dfea42015-09-16 12:30:36 -07001085 final String mimeType = entry.getKey();
1086 final List<KindSectionData> kindSectionDataList = entry.getValue();
Walter Jangab50e6f2015-06-15 08:57:22 -07001087
Walter Jangf5dfea42015-09-16 12:30:36 -07001088 // Ignore mime types that we've already handled
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001089 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
1090 vlog("kind: " + i + " " + mimeType + " dropped");
1091 continue;
1092 }
Walter Jangf5dfea42015-09-16 12:30:36 -07001093
Walter Jang192a01c2015-09-22 15:23:55 -07001094 if (kindSectionDataList != null && !kindSectionDataList.isEmpty()) {
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001095 vlog("kind: " + i + " " + mimeType + ": " + kindSectionDataList.size() +
1096 " kindSectionData(s)");
1097
Walter Jangf5dfea42015-09-16 12:30:36 -07001098 final CompactKindSectionView kindSectionView = inflateKindSectionView(
1099 mKindSectionViews, kindSectionDataList, mimeType);
Walter Jangf5dfea42015-09-16 12:30:36 -07001100 mKindSectionViews.addView(kindSectionView);
Walter Jangf10ca152015-09-22 15:23:55 -07001101
1102 // Keep a pointer to all the KindSectionsViews for each mimeType
1103 getKindSectionViews(mimeType).add(kindSectionView);
Walter Jangcab3dce2015-02-09 17:48:03 -08001104 }
1105 }
1106 }
1107
Walter Jangf10ca152015-09-22 15:23:55 -07001108 private List<CompactKindSectionView> getKindSectionViews(String mimeType) {
1109 List<CompactKindSectionView> kindSectionViews = mKindSectionViewsMap.get(mimeType);
1110 if (kindSectionViews == null) {
1111 kindSectionViews = new ArrayList<>();
1112 mKindSectionViewsMap.put(mimeType, kindSectionViews);
1113 }
1114 return kindSectionViews;
1115 }
1116
Walter Jangf5dfea42015-09-16 12:30:36 -07001117 private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
1118 List<KindSectionData> kindSectionDataList, String mimeType) {
1119 final CompactKindSectionView kindSectionView = (CompactKindSectionView)
1120 mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
1121 /* attachToRoot =*/ false);
1122
Walter Jang192a01c2015-09-22 15:23:55 -07001123 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
Walter Jangf5dfea42015-09-16 12:30:36 -07001124 || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jang192a01c2015-09-22 15:23:55 -07001125 // Phone numbers and email addresses are always displayed,
1126 // even if they are empty
Walter Jangf5dfea42015-09-16 12:30:36 -07001127 kindSectionView.setHideWhenEmpty(false);
1128 }
Walter Jangf5dfea42015-09-16 12:30:36 -07001129
Walter Jang192a01c2015-09-22 15:23:55 -07001130 // Since phone numbers and email addresses displayed even if they are empty,
1131 // they will be the only types you add new values to initially for new contacts
1132 kindSectionView.setShowOneEmptyEditor(true);
1133
1134 // Sort so the editors wind up in the order we want
1135 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
1136 Collections.sort(kindSectionDataList, new NameEditorComparator(getContext(),
1137 mPrimaryRawContactDelta));
1138 } else {
1139 Collections.sort(kindSectionDataList, new EditorComparator(getContext()));
Walter Jang3e5ae0d2015-09-20 12:43:37 -07001140 }
1141
Walter Jang79658e12015-09-24 10:36:26 -07001142 kindSectionView.setState(kindSectionDataList, mViewIdGenerator, mListener);
Walter Jangf5dfea42015-09-16 12:30:36 -07001143
1144 return kindSectionView;
Walter Jangcab3dce2015-02-09 17:48:03 -08001145 }
1146
Walter Jangd6753152015-10-02 09:23:13 -07001147 private void showAllFields() {
Walter Jang79658e12015-09-24 10:36:26 -07001148 // Stop hiding empty editors and allow the user to enter values for all kinds now
1149 for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
1150 final CompactKindSectionView kindSectionView =
1151 (CompactKindSectionView) mKindSectionViews.getChildAt(i);
1152 kindSectionView.setHideWhenEmpty(false);
1153 kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
1154 }
1155 mIsExpanded = true;
Walter Jang79658e12015-09-24 10:36:26 -07001156
Walter Jangf5dfea42015-09-16 12:30:36 -07001157 // Hide the more fields button
1158 mMoreFields.setVisibility(View.GONE);
Walter Jangcab3dce2015-02-09 17:48:03 -08001159 }
1160
Walter Jangbf63a6d2015-05-05 09:14:35 -07001161 private static void vlog(String message) {
1162 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1163 Log.v(TAG, message);
Walter Jangcab3dce2015-02-09 17:48:03 -08001164 }
1165 }
Walter Jang363d3fd2015-09-16 10:29:07 -07001166
1167 private static void wlog(String message) {
1168 if (Log.isLoggable(TAG, Log.WARN)) {
1169 Log.w(TAG, message);
1170 }
1171 }
1172
1173 private static void elog(String message) {
1174 Log.e(TAG, message);
1175 }
Walter Jangcab3dce2015-02-09 17:48:03 -08001176}