blob: 72cf650756bfc44bef59243b8c781cbf8b9cbbf9 [file] [log] [blame]
Walter Jangcab3dce2015-02-09 17:48:03 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts.editor;
18
19import com.android.contacts.R;
20import com.android.contacts.common.model.AccountTypeManager;
21import com.android.contacts.common.model.RawContactDelta;
22import com.android.contacts.common.model.RawContactDeltaList;
Walter Jange720fde2015-02-17 10:54:14 -080023import com.android.contacts.common.model.RawContactModifier;
Walter Jangcab3dce2015-02-09 17:48:03 -080024import com.android.contacts.common.model.ValuesDelta;
25import com.android.contacts.common.model.account.AccountType;
26import com.android.contacts.common.model.account.AccountType.EditField;
27import com.android.contacts.common.model.dataitem.DataKind;
Walter Jang3efae4a2015-02-18 11:12:00 -080028import com.android.contacts.editor.CompactContactEditorFragment.PhotoHandler;
Walter Jangcab3dce2015-02-09 17:48:03 -080029
30import android.content.Context;
Walter Jang3efae4a2015-02-18 11:12:00 -080031import android.graphics.Bitmap;
Walter Jangcab3dce2015-02-09 17:48:03 -080032import android.provider.ContactsContract.CommonDataKinds.Email;
33import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
Walter Jang3efae4a2015-02-18 11:12:00 -080034import android.provider.ContactsContract.CommonDataKinds.Nickname;
Walter Jangcab3dce2015-02-09 17:48:03 -080035import android.provider.ContactsContract.CommonDataKinds.Phone;
36import android.provider.ContactsContract.CommonDataKinds.Photo;
Walter Jangcab3dce2015-02-09 17:48:03 -080037import android.provider.ContactsContract.CommonDataKinds.StructuredName;
38import android.text.TextUtils;
39import android.util.AttributeSet;
40import android.util.Log;
41import android.view.LayoutInflater;
Walter Jangb1c87622015-02-13 17:51:38 -080042import android.view.View;
Walter Jangcab3dce2015-02-09 17:48:03 -080043import android.view.ViewGroup;
44import android.widget.LinearLayout;
45
46import java.util.ArrayList;
47import java.util.List;
48
49/**
50 * View to display information from multiple {@link RawContactDelta}s grouped together
51 * (e.g. all the phone numbers from a {@link com.android.contacts.common.model.Contact} together.
52 */
Walter Jangb6ca2722015-02-20 11:10:25 -080053public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
Walter Jangcab3dce2015-02-09 17:48:03 -080054
55 private static final String TAG = "CompactEditorView";
56
Walter Jangb6ca2722015-02-20 11:10:25 -080057 /**
58 * Callbacks for hosts of {@link CompactRawContactsEditorView}s.
59 */
60 public interface Listener {
61
62 /**
63 * Invoked when the compact editor should be expanded to show all fields.
64 */
65 public void onExpandEditor();
66 }
67
68 private Listener mListener;
69
Walter Jangcab3dce2015-02-09 17:48:03 -080070 private AccountTypeManager mAccountTypeManager;
71 private LayoutInflater mLayoutInflater;
72 private ViewIdGenerator mViewIdGenerator;
73
Walter Jang3efae4a2015-02-18 11:12:00 -080074 private CompactHeaderView mHeader;
Walter Jangcab3dce2015-02-09 17:48:03 -080075 private ViewGroup mNames;
Walter Jangb1c87622015-02-13 17:51:38 -080076 private ViewGroup mPhoneticNames;
77 private ViewGroup mNicknames;
Walter Jangcab3dce2015-02-09 17:48:03 -080078 private ViewGroup mPhoneNumbers;
79 private ViewGroup mEmails;
80 private ViewGroup mOther;
Walter Jangb6ca2722015-02-20 11:10:25 -080081 private View mMoreFields;
Walter Jangcab3dce2015-02-09 17:48:03 -080082
Walter Jang3efae4a2015-02-18 11:12:00 -080083 // TODO: remove this after we handle account selection for photos
84 private long mPhotoRawContactId;
85
Walter Jangcab3dce2015-02-09 17:48:03 -080086 public CompactRawContactsEditorView(Context context) {
87 super(context);
88 }
89
90 public CompactRawContactsEditorView(Context context, AttributeSet attrs) {
91 super(context, attrs);
92 }
93
Walter Jangb6ca2722015-02-20 11:10:25 -080094 /**
95 * Sets the receiver for {@link CompactRawContactsEditorView} callbacks.
96 */
97 public void setListener(Listener listener) {
98 mListener = listener;
99 }
100
Walter Jangcab3dce2015-02-09 17:48:03 -0800101 @Override
102 protected void onFinishInflate() {
103 super.onFinishInflate();
104
105 mAccountTypeManager = AccountTypeManager.getInstance(getContext());
106 mLayoutInflater = (LayoutInflater)
107 getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
108
Walter Jang3efae4a2015-02-18 11:12:00 -0800109 mHeader = (CompactHeaderView) findViewById(R.id.header);
Walter Jangcab3dce2015-02-09 17:48:03 -0800110 mNames = (LinearLayout) findViewById(R.id.names);
Walter Jangb1c87622015-02-13 17:51:38 -0800111 mPhoneticNames = (LinearLayout) findViewById(R.id.phonetic_names);
112 mNicknames = (LinearLayout) findViewById(R.id.nicknames);
Walter Jangcab3dce2015-02-09 17:48:03 -0800113 mPhoneNumbers = (LinearLayout) findViewById(R.id.phone_numbers);
114 mEmails = (LinearLayout) findViewById(R.id.emails);
115 mOther = (LinearLayout) findViewById(R.id.other);
Walter Jangb6ca2722015-02-20 11:10:25 -0800116 mMoreFields = findViewById(R.id.more_fields);
117 mMoreFields.setOnClickListener(this);
118 }
119
120
121 @Override
122 public void onClick(View view) {
123 if (view.getId() == R.id.more_fields && mListener != null ) {
124 mListener.onExpandEditor();
125 }
Walter Jangcab3dce2015-02-09 17:48:03 -0800126 }
127
128 @Override
129 public void setEnabled(boolean enabled) {
130 super.setEnabled(enabled);
131 setEnabled(enabled, mNames);
Walter Jangb1c87622015-02-13 17:51:38 -0800132 setEnabled(enabled, mPhoneticNames);
133 setEnabled(enabled, mNicknames);
Walter Jangcab3dce2015-02-09 17:48:03 -0800134 setEnabled(enabled, mPhoneNumbers);
135 setEnabled(enabled, mEmails);
136 setEnabled(enabled, mOther);
137 }
138
139 private void setEnabled(boolean enabled, ViewGroup viewGroup) {
140 if (viewGroup != null) {
141 final int childCount = viewGroup.getChildCount();
142 for (int i = 0; i < childCount; i++) {
143 viewGroup.getChildAt(i).setEnabled(enabled);
144 }
145 }
146 }
147
Walter Jang3efae4a2015-02-18 11:12:00 -0800148 /**
149 * Pass through to {@link CompactHeaderView#setPhotoHandler}.
150 */
151 public void setPhotoHandler(PhotoHandler photoHandler) {
152 mHeader.setPhotoHandler(photoHandler);
153 }
154
155 /**
156 * Pass through to {@link CompactHeaderView#setPhoto}.
157 */
158 public void setPhoto(Bitmap bitmap) {
159 mHeader.setPhoto(bitmap);
160 }
161
162 /**
163 * Pass through to {@link CompactHeaderView#isWritablePhotoSet}.
164 */
165 public boolean isWritablePhotoSet() {
166 return mHeader.isWritablePhotoSet();
167 }
168
169 /**
170 * Pass through to {@link CompactHeaderView#getPhotoPopupAnchorView}.
171 */
172 public View getPhotoPopupAnchorView() {
173 return mHeader.getPhotoPopupAnchorView();
174 }
175
176
177 /**
178 * Get the raw contact ID for the CompactHeaderView photo.
179 */
180 // TODO: remove me once we support multiple accounts
181 public long getPhotoRawContactId() {
182 return mPhotoRawContactId;
183 }
184
Walter Jangcab3dce2015-02-09 17:48:03 -0800185 public void setState(RawContactDeltaList rawContactDeltas, ViewIdGenerator viewIdGenerator) {
186 mNames.removeAllViews();
Walter Jangb1c87622015-02-13 17:51:38 -0800187 mPhoneticNames.removeAllViews();
188 mNicknames.removeAllViews();
Walter Jangcab3dce2015-02-09 17:48:03 -0800189 mPhoneNumbers.removeAllViews();
190 mEmails.removeAllViews();
191 mOther.removeAllViews();
192
193 if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
194 return;
195 }
196
197 mViewIdGenerator = viewIdGenerator;
Walter Jangcab3dce2015-02-09 17:48:03 -0800198 setId(mViewIdGenerator.getId(rawContactDeltas.get(0), /* dataKind =*/ null,
199 /* valuesDelta =*/ null, ViewIdGenerator.NO_VIEW_INDEX));
200
Walter Jang3efae4a2015-02-18 11:12:00 -0800201 addHeaderView(rawContactDeltas, viewIdGenerator);
202 addEditorViews(rawContactDeltas);
Walter Jange720fde2015-02-17 10:54:14 -0800203 removeExtraEmptyTextFields(mNames);
204 removeExtraEmptyTextFields(mPhoneNumbers);
205 removeExtraEmptyTextFields(mEmails);
Walter Jangcab3dce2015-02-09 17:48:03 -0800206 }
207
Walter Jang3efae4a2015-02-18 11:12:00 -0800208 private void addHeaderView(RawContactDeltaList rawContactDeltas,
Walter Jange720fde2015-02-17 10:54:14 -0800209 ViewIdGenerator viewIdGenerator) {
Walter Jangcab3dce2015-02-09 17:48:03 -0800210 for (RawContactDelta rawContactDelta : rawContactDeltas) {
211 if (!rawContactDelta.isVisible()) {
212 continue;
213 }
214 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
Walter Jange720fde2015-02-17 10:54:14 -0800215
Walter Jang3efae4a2015-02-18 11:12:00 -0800216 // Make sure we have a photo
217 RawContactModifier.ensureKindExists(
218 rawContactDelta, accountType, Photo.CONTENT_ITEM_TYPE);
219
220 final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
221 if (dataKind != null) {
222 final String mimeType = dataKind.mimeType;
223 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
224 mPhotoRawContactId = rawContactDelta.getRawContactId();
225 final ValuesDelta valuesDelta = rawContactDelta.getSuperPrimaryEntry(
226 mimeType, /* forceSelection =*/ true);
227 mHeader.setValues(dataKind, valuesDelta, rawContactDelta,
228 /* readOnly =*/ !dataKind.editable, viewIdGenerator);
229 return;
230 }
231 }
232 }
233 }
234
235 private void addEditorViews(RawContactDeltaList rawContactDeltas) {
236 for (RawContactDelta rawContactDelta : rawContactDeltas) {
237 if (!rawContactDelta.isVisible()) {
238 continue;
239 }
240 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
241
242 // Make sure we have a structured name
Walter Jange720fde2015-02-17 10:54:14 -0800243 RawContactModifier.ensureKindExists(
244 rawContactDelta, accountType, StructuredName.CONTENT_ITEM_TYPE);
245
Walter Jangcab3dce2015-02-09 17:48:03 -0800246 for (DataKind dataKind : accountType.getSortedDataKinds()) {
247 if (!dataKind.editable) {
248 continue;
249 }
250 final String mimeType = dataKind.mimeType;
251 log(Log.VERBOSE, mimeType + " " + dataKind.fieldList.size() + " field(s)");
252 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)
253 || GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
254 // Photos are handled separately and group membership is not supported
255 continue;
256 } else if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
257 final ValuesDelta valuesDelta = rawContactDelta.getPrimaryEntry(mimeType);
Walter Jangcab3dce2015-02-09 17:48:03 -0800258 if (valuesDelta != null) {
259 mNames.addView(inflateStructuredNameEditorView(
260 mNames, accountType, valuesDelta, rawContactDelta));
261 }
262 } else if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
263 // Use the StructuredName mime type to get values
264 if (hasNonEmptyPrimaryValuesDelta(
265 rawContactDelta, StructuredName.CONTENT_ITEM_TYPE, dataKind)) {
266 final ValuesDelta valuesDelta = rawContactDelta.getPrimaryEntry(
267 StructuredName.CONTENT_ITEM_TYPE);
Walter Jangb1c87622015-02-13 17:51:38 -0800268 mPhoneticNames.addView(inflatePhoneticNameEditorView(
269 mPhoneticNames, accountType, valuesDelta, rawContactDelta));
Walter Jangcab3dce2015-02-09 17:48:03 -0800270 }
271 } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
272 if (hasNonEmptyValuesDelta(rawContactDelta, mimeType, dataKind)) {
Walter Jangb1c87622015-02-13 17:51:38 -0800273 mNicknames.addView(inflateNicknameEditorView(
274 mNicknames, dataKind, rawContactDelta));
Walter Jangcab3dce2015-02-09 17:48:03 -0800275 }
276 } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jange720fde2015-02-17 10:54:14 -0800277 mPhoneNumbers.addView(inflateKindSectionView(
278 mPhoneNumbers, dataKind, rawContactDelta));
Walter Jangcab3dce2015-02-09 17:48:03 -0800279 } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
Walter Jange720fde2015-02-17 10:54:14 -0800280 mEmails.addView(inflateKindSectionView(
281 mEmails, dataKind, rawContactDelta));
Walter Jangcab3dce2015-02-09 17:48:03 -0800282 } else if (hasNonEmptyValuesDelta(rawContactDelta, mimeType, dataKind)) {
283 mOther.addView(inflateKindSectionView(
284 mOther, dataKind, rawContactDelta));
285 }
286 }
287 }
288 }
289
Walter Jange720fde2015-02-17 10:54:14 -0800290 // TODO: avoid inflating extra views and deleting them
291 private void removeExtraEmptyTextFields(ViewGroup viewGroup) {
292 // If there is one (or less) editors, leave it whether it is empty or not
293 if (viewGroup.getChildCount() <= 1) {
Walter Jangb1c87622015-02-13 17:51:38 -0800294 return;
295 }
Walter Jange720fde2015-02-17 10:54:14 -0800296 // Determine if there are any non-empty editors
297 boolean hasAtLeastOneNonEmptyEditorView = false;
298 for (int i = 0; i < viewGroup.getChildCount(); i++) {
299 if (!isEmptyEditorView(viewGroup.getChildAt(i))) {
300 hasAtLeastOneNonEmptyEditorView = true;
Walter Jangb1c87622015-02-13 17:51:38 -0800301 break;
302 }
303 }
Walter Jange720fde2015-02-17 10:54:14 -0800304 if (hasAtLeastOneNonEmptyEditorView) {
305 // There is at least one non-empty editor, remove all the empty ones
306 for (int i = 0; i < viewGroup.getChildCount(); i++) {
307 if (isEmptyEditorView(viewGroup.getChildAt(i))) {
308 viewGroup.getChildAt(i).setVisibility(View.GONE);
Walter Jangb1c87622015-02-13 17:51:38 -0800309 }
310 }
311 } else {
Walter Jange720fde2015-02-17 10:54:14 -0800312 // There is no non-empty editor, keep the first empty view and remove the rest
313 for (int i = 1; i < viewGroup.getChildCount(); i++) {
314 viewGroup.getChildAt(i).setVisibility(View.GONE);
Walter Jangb1c87622015-02-13 17:51:38 -0800315 }
316 }
317 }
318
Walter Jange720fde2015-02-17 10:54:14 -0800319 // TODO: remove this after KindSectionView is rewritten
320 private static boolean isEmptyEditorView(View view) {
321 if (view instanceof TextFieldsEditorView) {
322 final TextFieldsEditorView textFieldsEditorView = (TextFieldsEditorView) view;
323 return textFieldsEditorView.isEmpty();
324 }
325 if (view instanceof KindSectionView) {
326 final KindSectionView kindSectionView = (KindSectionView) view;
327 return kindSectionView.hasEmptyEditor();
328 }
329 return false;
330 }
331
Walter Jangcab3dce2015-02-09 17:48:03 -0800332 private static boolean hasNonEmptyValuesDelta(RawContactDelta rawContactDelta,
333 String mimeType, DataKind dataKind) {
334 return !getNonEmptyValuesDeltas(rawContactDelta, mimeType, dataKind).isEmpty();
335 }
336
337 private static List<ValuesDelta> getNonEmptyValuesDeltas(RawContactDelta rawContactDelta,
338 String mimeType, DataKind dataKind) {
339 final List<ValuesDelta> result = new ArrayList<>();
340 if (rawContactDelta == null) {
341 log(Log.VERBOSE, "Null RawContactDelta");
342 return result;
343 }
344 if (!rawContactDelta.hasMimeEntries(mimeType)) {
345 log(Log.VERBOSE, "No ValueDeltas");
346 return result;
347 }
348 for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(mimeType)) {
349 if (valuesDelta == null) {
350 log(Log.VERBOSE, "Null valuesDelta");
351 }
352 for (EditField editField : dataKind.fieldList) {
353 final String column = editField.column;
354 final String value = valuesDelta == null ? null : valuesDelta.getAsString(column);
355 log(Log.VERBOSE, "Field " + column + " empty=" + TextUtils.isEmpty(value) +
356 " value=" + value);
357 if (!TextUtils.isEmpty(value)) {
358 result.add(valuesDelta);
359 }
360 }
361 }
362 return result;
363 }
364
365 private static boolean hasNonEmptyPrimaryValuesDelta(RawContactDelta rawContactDelta,
366 String mimeType, DataKind dataKind) {
367 final ValuesDelta valuesDelta = rawContactDelta.getPrimaryEntry(mimeType);
368 if (valuesDelta == null) {
369 return false;
370 }
371 for (EditField editField : dataKind.fieldList) {
372 final String column = editField.column;
373 final String value = valuesDelta == null ? null : valuesDelta.getAsString(column);
374 log(Log.VERBOSE, "Field (primary) " + column + " empty=" + TextUtils.isEmpty(value) +
375 " value=" + value);
376 if (!TextUtils.isEmpty(value)) {
377 return true;
378 }
379 }
380 return false;
381 }
382
383 private StructuredNameEditorView inflateStructuredNameEditorView(ViewGroup viewGroup,
384 AccountType accountType, ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
385 final StructuredNameEditorView result = (StructuredNameEditorView) mLayoutInflater.inflate(
386 R.layout.structured_name_editor_view, viewGroup, /* attachToRoot =*/ false);
387 result.setValues(
388 accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
389 valuesDelta,
390 rawContactDelta,
391 /* readOnly =*/ false,
392 mViewIdGenerator);
393 return result;
394 }
395
396 private PhoneticNameEditorView inflatePhoneticNameEditorView(ViewGroup viewGroup,
397 AccountType accountType, ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
398 final PhoneticNameEditorView result = (PhoneticNameEditorView) mLayoutInflater.inflate(
399 R.layout.phonetic_name_editor_view, viewGroup, /* attachToRoot =*/ false);
400 result.setValues(
401 accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME),
402 valuesDelta,
403 rawContactDelta,
404 /* readOnly =*/ false,
405 mViewIdGenerator);
406 return result;
407 }
408
Walter Jangb1c87622015-02-13 17:51:38 -0800409 private KindSectionView inflateNicknameEditorView(ViewGroup viewGroup, DataKind dataKind,
410 RawContactDelta rawContactDelta) {
411 final KindSectionView result = (KindSectionView) mLayoutInflater.inflate(
412 R.layout.item_kind_section, viewGroup, /* attachToRoot =*/ false);
413 result.setState(
414 dataKind,
Walter Jangcab3dce2015-02-09 17:48:03 -0800415 rawContactDelta,
416 /* readOnly =*/ false,
417 mViewIdGenerator);
418 return result;
419 }
420
421 private KindSectionView inflateKindSectionView(ViewGroup viewGroup, DataKind dataKind,
422 RawContactDelta rawContactDelta) {
423 final KindSectionView result = (KindSectionView) mLayoutInflater.inflate(
424 R.layout.item_kind_section, viewGroup, /* attachToRoot =*/ false);
425 result.setState(
426 dataKind,
427 rawContactDelta,
428 /* readOnly =*/ false,
429 mViewIdGenerator);
430 return result;
431 }
432
433 private static void log(int level, String message) {
434 log(TAG, level, message);
435 }
436
437 private static void log(String tag, int level, String message) {
438 if (Log.isLoggable(tag, level)) {
439 switch (level) {
440 case Log.VERBOSE:
441 Log.v(tag, message);
442 break;
443 case Log.DEBUG:
444 Log.d(tag, message);
445 break;
446 case Log.INFO:
447 Log.i(tag, message);
448 break;
449 case Log.WARN:
450 Log.w(tag, message);
451 break;
452 case Log.ERROR:
453 Log.e(tag, message);
454 break;
455 default:
456 Log.v(tag, message);
457 break;
458 }
459 }
460 }
461}