Merge "Add suggestion card for quick contact (P2)" into ub-contactsdialer-a-dev
diff --git a/res/layout-land/compact_contact_editor_fragment.xml b/res/layout-land/compact_contact_editor_fragment.xml
index 187deae..0d86f89 100644
--- a/res/layout-land/compact_contact_editor_fragment.xml
+++ b/res/layout-land/compact_contact_editor_fragment.xml
@@ -17,7 +17,7 @@
<com.android.contacts.editor.CompactRawContactsEditorView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/editors"
+ android:id="@+id/raw_contacts_editor_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background_primary"
diff --git a/res/layout/account_type_info.xml b/res/layout/account_type_info.xml
new file mode 100644
index 0000000..64e553c
--- /dev/null
+++ b/res/layout/account_type_info.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/account_type"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:visibility="gone">
+
+ <ImageView
+ android:id="@+id/account_type_icon"
+ style="@style/AccountTypeIconStyle" />
+
+ <TextView
+ android:id="@+id/account_type_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/AccountTypeNameStyle" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/compact_contact_editor_fields.xml b/res/layout/compact_contact_editor_fields.xml
index 1ddd0b1..710ba94 100644
--- a/res/layout/compact_contact_editor_fields.xml
+++ b/res/layout/compact_contact_editor_fields.xml
@@ -18,41 +18,11 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
- android:id="@+id/names"
+ android:id="@+id/kind_section_views"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/editor_compact_first_field_padding"
- android:orientation="vertical"/>
-
- <LinearLayout
- android:id="@+id/phonetic_names"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"/>
-
- <LinearLayout
- android:id="@+id/nicknames"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"/>
-
- <LinearLayout
- android:id="@+id/phone_numbers"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"/>
-
- <LinearLayout
- android:id="@+id/emails"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"/>
-
- <LinearLayout
- android:id="@+id/other"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"/>
+ android:orientation="vertical"
+ android:animateLayoutChanges="true"/>
<LinearLayout
style="@style/SelectableItem"
diff --git a/res/layout/compact_contact_editor_fragment.xml b/res/layout/compact_contact_editor_fragment.xml
index a07381c..b3818c9 100644
--- a/res/layout/compact_contact_editor_fragment.xml
+++ b/res/layout/compact_contact_editor_fragment.xml
@@ -24,7 +24,7 @@
android:fillViewport="true">
<com.android.contacts.editor.CompactRawContactsEditorView
- android:id="@+id/editors"
+ android:id="@+id/raw_contacts_editor_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
diff --git a/res/layout/compact_item_kind_section.xml b/res/layout/compact_item_kind_section.xml
new file mode 100644
index 0000000..771ccfe
--- /dev/null
+++ b/res/layout/compact_item_kind_section.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- The body surrounding all editors for a specific kind -->
+
+<com.android.contacts.editor.CompactKindSectionView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/kind_icon"
+ style="@style/EditKindIconStyle" />
+
+ <LinearLayout
+ android:id="@+id/kind_editors"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+
+</com.android.contacts.editor.CompactKindSectionView>
\ No newline at end of file
diff --git a/res/layout/contact_editor_accounts_changed_activity_with_picker.xml b/res/layout/contact_editor_accounts_changed_activity_with_picker.xml
index a5aab20..a4f1b0a 100644
--- a/res/layout/contact_editor_accounts_changed_activity_with_picker.xml
+++ b/res/layout/contact_editor_accounts_changed_activity_with_picker.xml
@@ -27,7 +27,12 @@
<TextView android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:padding="15dip"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip"
+ android:paddingTop="15dip"
+ android:paddingBottom="15dip"
+ android:paddingLeft="10dip"
+ android:paddingRight="10dip"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<View
@@ -39,6 +44,7 @@
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
+ android:layout_marginLeft="16dip"
android:fadingEdge="none"/>
<View
diff --git a/res/layout/structured_name_editor_view.xml b/res/layout/structured_name_editor_view.xml
index 2b0ee7b..830f4d8 100644
--- a/res/layout/structured_name_editor_view.xml
+++ b/res/layout/structured_name_editor_view.xml
@@ -27,27 +27,27 @@
<include
android:id="@+id/spinner"
layout="@layout/edit_spinner"
- android:visibility="gone" />
+ android:visibility="gone"/>
<ImageView
android:id="@+id/kind_icon"
android:src="@drawable/ic_person_black_24dp"
android:contentDescription="@string/header_name_entry"
- style="@style/EditKindIconStyle" />
+ style="@style/EditKindIconStyle"/>
<include
- layout="@layout/edit_field_list_with_anchor_view" />
+ layout="@layout/edit_field_list_with_anchor_view"/>
<include
android:id="@+id/expansion_view_container"
layout="@layout/name_edit_expansion_view"
- android:visibility="gone" />
+ android:visibility="gone"/>
<!-- This isn't used in StructuredNameEditorView. It is only included so that
StructuredNameEditorView's base classes don't need extra null checks. -->
<include
android:id="@+id/delete_button_container"
layout="@layout/edit_delete_button"
- android:visibility="gone" />
+ android:visibility="gone"/>
</com.android.contacts.editor.StructuredNameEditorView>
diff --git a/res/layout/structured_name_readonly_editor_view.xml b/res/layout/structured_name_readonly_editor_view.xml
new file mode 100644
index 0000000..4778570
--- /dev/null
+++ b/res/layout/structured_name_readonly_editor_view.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/editor_min_line_item_height"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/display_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="4dp"
+ android:layout_marginBottom="6dp"
+ android:singleLine="true"
+ android:textSize="@dimen/editor_form_text_size"
+ android:textColor="?android:attr/textColorSecondary"
+ android:enabled="false"/>
+
+ <include layout="@layout/account_type_info"
+ android:layout_marginStart="12dp" />
+
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d4979f4..1104a24 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -768,6 +768,9 @@
<!-- Content description for the compact contact editor photo overlay which, when clicked, shows a dialog with the options for changing the contact photo. [CHAR LIMIT=30] -->
<string name="compact_editor_change_photo_content_description">Change photo</string>
+ <!-- Toast message displayed when the editor fails to load for a contacts. [CHAR LIMIT=NONE] -->
+ <string name="compact_editor_failed_to_load">Failed to open editor.</string>
+
<!-- Quick contact display name with phonetic name -->
<string name="quick_contact_display_name_with_phonetic"><xliff:g id="display_name">%s</xliff:g> (<xliff:g id="phonetic_name">%s</xliff:g>)</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8fce0f1..14dfee2 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -325,4 +325,17 @@
<item name="android:layout_marginEnd">28dp</item>
<item name="android:layout_marginTop">@dimen/editor_kind_icon_top_margin</item>
</style>
+
+ <style name="AccountTypeIconStyle">
+ <item name="android:layout_width">12dp</item>
+ <item name="android:layout_height">12dp</item>
+ <item name="android:layout_marginStart">4dp</item>
+ <item name="android:layout_marginEnd">4dp</item>
+ </style>
+
+ <style name="AccountTypeNameStyle">
+ <item name="android:textSize">10sp</item>
+ <item name="android:textColor">#363636</item>
+ <item name="android:fontFamily">sans-serif</item>
+ </style>
</resources>
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 634e29a..1989606 100755
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -313,7 +313,7 @@
Bundle bundle = new Bundle();
bundle.putParcelable(String.valueOf(rawContactId), updatedPhotoPath);
return createSaveContactIntent(context, state, saveModeExtraKey, saveMode, isProfile,
- callbackActivity, callbackAction, bundle, /* backPressed =*/ false);
+ callbackActivity, callbackAction, bundle);
}
/**
@@ -322,13 +322,11 @@
* This variant is used when multiple contacts' photos may be updated, as in the
* Contact Editor.
* @param updatedPhotos maps each raw-contact's ID to the file-path of the new photo.
- * @param backPressed whether the save was initiated as a result of a back button press
- * or because the framework stopped the editor Activity
*/
public static Intent createSaveContactIntent(Context context, RawContactDeltaList state,
String saveModeExtraKey, int saveMode, boolean isProfile,
Class<? extends Activity> callbackActivity, String callbackAction,
- Bundle updatedPhotos, boolean backPressed) {
+ Bundle updatedPhotos) {
Intent serviceIntent = new Intent(
context, ContactSaveService.class);
serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT);
@@ -347,11 +345,6 @@
Intent callbackIntent = new Intent(context, callbackActivity);
callbackIntent.putExtra(saveModeExtraKey, saveMode);
callbackIntent.setAction(callbackAction);
- if (updatedPhotos != null) {
- callbackIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos);
- }
- callbackIntent.putExtra(ContactEditorFragment.INTENT_EXTRA_SAVE_BACK_PRESSED,
- backPressed);
serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
}
return serviceIntent;
diff --git a/src/com/android/contacts/activities/CompactContactEditorActivity.java b/src/com/android/contacts/activities/CompactContactEditorActivity.java
index 082bb74..28dd8a5 100644
--- a/src/com/android/contacts/activities/CompactContactEditorActivity.java
+++ b/src/com/android/contacts/activities/CompactContactEditorActivity.java
@@ -56,11 +56,4 @@
final Uri uri = Intent.ACTION_EDIT.equals(action) ? getIntent().getData() : null;
mFragment.load(action, uri, getIntent().getExtras());
}
-
- @Override
- public void onBackPressed() {
- if (mFragment != null) {
- mFragment.revert();
- }
- }
}
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index 800a267..293e8c0 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -50,11 +50,4 @@
|| Intent.ACTION_EDIT.equals(action) ? getIntent().getData() : null;
mFragment.load(action, uri, getIntent().getExtras());
}
-
- @Override
- public void onBackPressed() {
- if (mFragment != null) {
- mFragment.save(ContactEditor.SaveMode.COMPACT, /* backPressed =*/ true);
- }
- }
}
diff --git a/src/com/android/contacts/activities/ContactEditorBaseActivity.java b/src/com/android/contacts/activities/ContactEditorBaseActivity.java
index 12f1e96..a86196b 100644
--- a/src/com/android/contacts/activities/ContactEditorBaseActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorBaseActivity.java
@@ -155,11 +155,8 @@
/**
* Saves or creates the contact based on the mode, and if successful
* finishes the activity.
- *
- * @param backPressed whether the save was initiated as a result of a back button press
- * or because the framework stopped the editor Activity
*/
- boolean save(int saveMode, boolean backPressed);
+ boolean save(int saveMode);
/**
* If there are no unsaved changes, just close the editor, otherwise the user is prompted
@@ -171,8 +168,7 @@
* Invoked after the contact is saved.
*/
void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded,
- Uri contactLookupUri, Bundle updatedPhotos, boolean backPressed, long photoId,
- long nameId);
+ Uri contactLookupUri);
/**
* Invoked after the contact is joined.
@@ -259,12 +255,7 @@
intent.getIntExtra(ContactEditorFragment.SAVE_MODE_EXTRA_KEY,
ContactEditor.SaveMode.CLOSE),
intent.getBooleanExtra(ContactSaveService.EXTRA_SAVE_SUCCEEDED, false),
- intent.getData(),
- (Bundle) intent.getParcelableExtra(ContactSaveService.EXTRA_UPDATED_PHOTOS),
- intent.getBooleanExtra(ContactEditorFragment.INTENT_EXTRA_SAVE_BACK_PRESSED,
- false),
- intent.getLongExtra(ContactEditorFragment.INTENT_EXTRA_PHOTO_ID, -1),
- intent.getLongExtra(ContactEditorFragment.INTENT_EXTRA_NAME_ID, -1));
+ intent.getData());
} else if (ACTION_JOIN_COMPLETED.equals(action)) {
mFragment.onJoinCompleted(intent.getData());
}
@@ -279,6 +270,13 @@
return null;
}
+ @Override
+ public void onBackPressed() {
+ if (mFragment != null) {
+ mFragment.revert();
+ }
+ }
+
protected final ContactEditorBaseFragment.Listener mFragmentListener =
new ContactEditorBaseFragment.Listener() {
@@ -294,15 +292,11 @@
@Override
public void onSaveFinished(Intent resultIntent) {
- final boolean backPressed = resultIntent == null ? false : resultIntent.getBooleanExtra(
- ContactEditorBaseFragment.INTENT_EXTRA_SAVE_BACK_PRESSED, false);
if (mFinishActivityOnSaveCompleted) {
setResult(resultIntent == null ? RESULT_CANCELED : RESULT_OK, resultIntent);
} else if (resultIntent != null) {
- if (backPressed) {
- ImplicitIntentsUtil.startActivityInApp(ContactEditorBaseActivity.this,
- resultIntent);
- }
+ ImplicitIntentsUtil.startActivityInApp(ContactEditorBaseActivity.this,
+ resultIntent);
}
finish();
}
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index 53bfce0..ee6173e 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -84,8 +84,6 @@
private ContactsIntentResolver mIntentResolver;
protected ContactEntryListFragment<?> mListFragment;
- private boolean mIsVisible;
-
private int mActionCode = -1;
private boolean mIsSearchMode;
private boolean mIsSearchSupported;
@@ -139,25 +137,6 @@
prepareSearchViewAndActionBar();
}
- @Override
- protected void onStart() {
- super.onStart();
- mIsVisible = true;
- }
-
- @Override
- protected void onStop() {
- mIsVisible = false;
- super.onStop();
- }
-
- /**
- * Returns true when the Activity is currently visible (between onStart and onStop).
- */
- /* package */ boolean isVisible() {
- return mIsVisible;
- }
-
private void prepareSearchViewAndActionBar() {
final ActionBar actionBar = getActionBar();
mSearchViewContainer = LayoutInflater.from(actionBar.getThemedContext())
@@ -233,7 +212,6 @@
@Override
protected void onSaveInstanceState(Bundle outState) {
- mIsVisible = false;
super.onSaveInstanceState(outState);
outState.putInt(KEY_ACTION_CODE, mActionCode);
outState.putBoolean(KEY_SEARCH_MODE, mIsSearchMode);
@@ -449,8 +427,7 @@
} else {
// Otherwise launch the full contact editor.
startActivityAndForwardResult(EditorIntents.createEditContactIntent(
- contactLookupUri, /* materialPalette =*/ null, /* photoId =*/ -1,
- /* nameId =*/ -1));
+ contactLookupUri, /* materialPalette =*/ null, /* photoId =*/ -1));
}
}
@@ -506,13 +483,13 @@
private final class PhoneNumberPickerActionListener implements
OnPhoneNumberPickerActionListener {
@Override
- public void onPickPhoneNumberAction(Uri dataUri, int callInitiationType) {
+ public void onPickDataUri(Uri dataUri, int callInitiationType) {
returnPickerResult(dataUri);
}
@Override
- public void onCallNumberDirectly(String phoneNumber, boolean isVideoCall,
- int callInitiationType) {
+ public void onPickPhoneNumber(String phoneNumber, boolean isVideoCall,
+ int callInitiationType) {
Log.w(TAG, "Unsupported call.");
}
@@ -691,7 +668,7 @@
@Override
public void onBackPressed() {
- if (!isVisible()) {
+ if (!isSafeToCommitTransactions()) {
return;
}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index ec78b4d..26add9d 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -142,8 +142,6 @@
private MultiSelectContactsListFragment mAllFragment;
private ContactTileListFragment mFavoritesFragment;
- private boolean mIsVisible;
-
/** ViewPager for swipe */
private ViewPager mTabPager;
private ViewPagerTabs mViewPagerTabs;
@@ -412,14 +410,6 @@
configureFragments(!mIsRecreatedInstance);
}
super.onStart();
-
- mIsVisible = true;
- }
-
- @Override
- protected void onStop() {
- mIsVisible = false;
- super.onStop();
}
@Override
@@ -464,13 +454,6 @@
super.onDestroy();
}
- /**
- * Returns true when the Activity is currently visible (between onStart and onStop).
- */
- /* package */ boolean isVisible() {
- return mIsVisible;
- }
-
private void configureFragments(boolean fromRequest) {
if (fromRequest) {
ContactListFilter filter = null;
@@ -1034,8 +1017,7 @@
@Override
public void onImportContactsFromFileAction() {
- ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
- PeopleActivity.class);
+ showImportExportDialogFragment();
}
}
@@ -1212,8 +1194,7 @@
deleteSelectedContacts();
return true;
case R.id.menu_import_export: {
- ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
- PeopleActivity.class);
+ showImportExportDialogFragment();
return true;
}
case R.id.menu_clear_frequents: {
@@ -1242,6 +1223,17 @@
return false;
}
+ private void showImportExportDialogFragment(){
+ final boolean isOnFavoriteTab = mTabPagerAdapter.mCurrentPrimaryItem == mFavoritesFragment;
+ if (isOnFavoriteTab) {
+ ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
+ PeopleActivity.class, ImportExportDialogFragment.EXPORT_MODE_FAVORITES);
+ } else {
+ ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
+ PeopleActivity.class, ImportExportDialogFragment.EXPORT_MODE_ALL_CONTACTS);
+ }
+ }
+
@Override
public boolean onSearchRequested() { // Search key pressed.
if (!mActionBarAdapter.isSelectionMode()) {
@@ -1355,7 +1347,7 @@
@Override
public void onBackPressed() {
- if (!isVisible()) {
+ if (!isSafeToCommitTransactions()) {
return;
}
@@ -1371,8 +1363,6 @@
@Override
protected void onSaveInstanceState(Bundle outState) {
- mIsVisible = false;
-
super.onSaveInstanceState(outState);
mActionBarAdapter.onSaveInstanceState(outState);
diff --git a/src/com/android/contacts/editor/CompactContactEditorFragment.java b/src/com/android/contacts/editor/CompactContactEditorFragment.java
index 287d814..e98a8de 100644
--- a/src/com/android/contacts/editor/CompactContactEditorFragment.java
+++ b/src/com/android/contacts/editor/CompactContactEditorFragment.java
@@ -25,7 +25,6 @@
import com.android.contacts.common.model.ValuesDelta;
import com.android.contacts.common.model.account.AccountType;
import com.android.contacts.common.model.account.AccountWithDataSet;
-import com.android.contacts.common.util.ImplicitIntentsUtil;
import com.android.contacts.detail.PhotoSelectionHandler;
import com.android.contacts.util.ContactPhotoUtils;
@@ -41,6 +40,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
+import android.widget.Toast;
import java.io.FileNotFoundException;
@@ -52,6 +52,7 @@
private static final String KEY_PHOTO_URI = "photo_uri";
private static final String KEY_PHOTO_RAW_CONTACT_ID = "photo_raw_contact_id";
+ private static final String KEY_UPDATED_PHOTOS = "updated_photos";
/**
* Displays a PopupWindow with photo edit options.
@@ -80,10 +81,6 @@
}
getContent().setPhoto(bitmap);
- // Clear any previously saved full resolution photos under negative raw contact IDs
- // so that we will use the newly selected photo, instead of an old one on rotations.
- removeNewRawContactPhotos();
-
// If a new photo was chosen but not yet saved,
// we need to update the UI immediately
mUpdatedPhotos.putParcelable(String.valueOf(mPhotoRawContactId), uri);
@@ -142,6 +139,7 @@
private PhotoHandler mPhotoHandler;
private Uri mPhotoUri;
private long mPhotoRawContactId;
+ private Bundle mUpdatedPhotos = new Bundle();
@Override
public void onCreate(Bundle savedState) {
@@ -150,6 +148,7 @@
if (savedState != null) {
mPhotoUri = savedState.getParcelable(KEY_PHOTO_URI);
mPhotoRawContactId = savedState.getLong(KEY_PHOTO_RAW_CONTACT_ID);
+ mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS);
}
}
@@ -159,7 +158,7 @@
final View view = inflater.inflate(
R.layout.compact_contact_editor_fragment, container, false);
- mContent = (LinearLayout) view.findViewById(R.id.editors);
+ mContent = (LinearLayout) view.findViewById(R.id.raw_contacts_editor_view);
return view;
}
@@ -167,6 +166,7 @@
public void onSaveInstanceState(Bundle outState) {
outState.putParcelable(KEY_PHOTO_URI, mPhotoUri);
outState.putLong(KEY_PHOTO_RAW_CONTACT_ID, mPhotoRawContactId);
+ outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos);
super.onSaveInstanceState(outState);
}
@@ -188,7 +188,7 @@
// If anything was left unsaved, save it now
if (!getActivity().isChangingConfigurations() && mStatus == Status.EDITING) {
- save(SaveMode.RELOAD, /* backPressed =*/ false);
+ save(SaveMode.RELOAD);
}
}
@@ -209,28 +209,13 @@
// Add input fields for the loaded Contact
final CompactRawContactsEditorView editorView = getContent();
editorView.setListener(this);
- editorView.setState(mState, getMaterialPalette(), mViewIdGenerator, mPhotoId, mNameId,
- mReadOnlyDisplayName, mHasNewContact, mIsUserProfile, mAccountWithDataSet);
- if (mReadOnlyDisplayName != null) {
- mReadOnlyNameEditorView = editorView.getDefaultNameEditorView();
- }
+ editorView.setState(mState, getMaterialPalette(), mViewIdGenerator, mPhotoId,
+ mHasNewContact, mIsUserProfile, mAccountWithDataSet);
// Set up the photo widget
mPhotoHandler = createPhotoHandler();
mPhotoRawContactId = editorView.getPhotoRawContactId();
- if (mPhotoRawContactId < 0) {
- // Since the raw contact IDs for new contacts are random negative numbers
- // we consider any negative key a match
- for (String key : mUpdatedPhotos.keySet()) {
- try {
- if (Integer.parseInt(key) < 0) {
- editorView.setFullSizePhoto((Uri) mUpdatedPhotos.getParcelable(key));
- break;
- }
- } catch (NumberFormatException ignored) {
- }
- }
- } else if (mUpdatedPhotos.containsKey(String.valueOf(mPhotoRawContactId))) {
+ if (mUpdatedPhotos.containsKey(String.valueOf(mPhotoRawContactId))) {
editorView.setFullSizePhoto((Uri) mUpdatedPhotos.getParcelable(
String.valueOf(mPhotoRawContactId)));
}
@@ -305,16 +290,17 @@
@Override
protected void setGroupMetaData() {
- // The compact editor does not support groups.
+ if (mGroupMetaData != null) {
+ getContent().setGroupMetaData(mGroupMetaData);
+ }
}
@Override
- protected boolean doSaveAction(int saveMode, boolean backPressed) {
- // Save contact. No need to pass the palette since we are finished editing after the save.
+ protected boolean doSaveAction(int saveMode) {
final Intent intent = ContactSaveService.createSaveContactIntent(mContext, mState,
SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
((Activity) mContext).getClass(),
- CompactContactEditorActivity.ACTION_SAVE_COMPLETED, mUpdatedPhotos, backPressed);
+ CompactContactEditorActivity.ACTION_SAVE_COMPLETED, mUpdatedPhotos);
mContext.startService(intent);
return true;
@@ -350,30 +336,6 @@
}
@Override
- public void onExpandEditor() {
- // Determine if this is an insert (new contact) or edit
- final boolean isInsert = isInsert(getActivity().getIntent());
-
- if (isInsert) {
- // For inserts, prevent any changes from being saved when the base fragment is destroyed
- mStatus = Status.CLOSING;
- } else if (hasPendingRawContactChanges()) {
- // Save whatever is in the form
- save(SaveMode.CLOSE, /* backPressed =*/ false);
- }
-
- // Prepare an Intent to start the expanded editor
- final Intent intent = isInsert
- ? EditorIntents.createInsertContactIntent(mState, getDisplayName(),
- getPhoneticName(), mUpdatedPhotos, mNewLocalProfile)
- : EditorIntents.createEditContactIntent(mLookupUri, getMaterialPalette(),
- mPhotoId, mNameId);
- ImplicitIntentsUtil.startActivityInApp(getActivity(), intent);
-
- getActivity().finish();
- }
-
- @Override
public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta) {
final Activity activity = getActivity();
if (activity == null || activity.isFinishing()) {
@@ -393,19 +355,14 @@
}
@Override
- public String getDisplayName() {
- final StructuredNameEditorView structuredNameEditorView =
- getContent().getStructuredNameEditorView();
- return structuredNameEditorView == null
- ? null : structuredNameEditorView.getDisplayName();
- }
-
- @Override
- public String getPhoneticName() {
- final PhoneticNameEditorView phoneticNameEditorView =
- getContent().getFirstPhoneticNameEditorView();
- return phoneticNameEditorView == null
- ? null : phoneticNameEditorView.getPhoneticName();
+ public void onBindEditorsFailed() {
+ final Activity activity = getActivity();
+ if (activity != null && !activity.isFinishing()) {
+ Toast.makeText(activity, R.string.compact_editor_failed_to_load,
+ Toast.LENGTH_SHORT).show();
+ activity.setResult(Activity.RESULT_CANCELED);
+ activity.finish();
+ }
}
private CompactRawContactsEditorView getContent() {
diff --git a/src/com/android/contacts/editor/CompactKindSectionView.java b/src/com/android/contacts/editor/CompactKindSectionView.java
new file mode 100644
index 0000000..a5f159c
--- /dev/null
+++ b/src/com/android/contacts/editor/CompactKindSectionView.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.editor;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.contacts.R;
+import com.android.contacts.common.model.RawContactDelta;
+import com.android.contacts.common.model.RawContactModifier;
+import com.android.contacts.common.model.ValuesDelta;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.dataitem.DataKind;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Version of {@link KindSectionView} that supports multiple RawContactDeltas.
+ */
+public class CompactKindSectionView extends LinearLayout {
+
+ /**
+ * Marks a name as super primary when it is changed.
+ *
+ * This is for the case when two or more raw contacts with names are joined where neither is
+ * marked as super primary.
+ */
+ private static final class StructuredNameEditorListener implements Editor.EditorListener {
+
+ private final ValuesDelta mValuesDelta;
+ private final long mRawContactId;
+ private final CompactRawContactsEditorView.Listener mListener;
+
+ public StructuredNameEditorListener(ValuesDelta valuesDelta, long rawContactId,
+ CompactRawContactsEditorView.Listener listener) {
+ mValuesDelta = valuesDelta;
+ mRawContactId = rawContactId;
+ mListener = listener;
+ }
+
+ @Override
+ public void onRequest(int request) {
+ if (request == Editor.EditorListener.FIELD_CHANGED) {
+ mValuesDelta.setSuperPrimary(true);
+ if (mListener != null) {
+ mListener.onNameFieldChanged(mRawContactId, mValuesDelta);
+ }
+ } else if (request == Editor.EditorListener.FIELD_TURNED_EMPTY) {
+ mValuesDelta.setSuperPrimary(false);
+ }
+ }
+
+ @Override
+ public void onDeleteRequested(Editor editor) {
+ editor.clearAllFields();
+ }
+ }
+
+ /**
+ * Clears fields when deletes are requested (on phonetic and nickename fields);
+ * does not change the number of editors.
+ */
+ private static final class OtherNameKindEditorListener implements Editor.EditorListener {
+
+ @Override
+ public void onRequest(int request) {
+ }
+
+ @Override
+ public void onDeleteRequested(Editor editor) {
+ editor.clearAllFields();
+ }
+ }
+
+ /**
+ * Updates empty fields when fields are deleted or turns empty.
+ * Whether a new empty editor is added is controlled by {@link #setShowOneEmptyEditor} and
+ * {@link #setHideWhenEmpty}.
+ */
+ private final class NonNameEditorListener implements Editor.EditorListener {
+
+ @Override
+ public void onRequest(int request) {
+ // If a field has become empty or non-empty, then check if another row
+ // can be added dynamically.
+ if (request == FIELD_TURNED_EMPTY || request == FIELD_TURNED_NON_EMPTY) {
+ updateEmptyEditors(/* shouldAnimate = */ true);
+ }
+ }
+
+ @Override
+ public void onDeleteRequested(Editor editor) {
+ if (mShowOneEmptyEditor && mEditors.getChildCount() == 1) {
+ // If there is only 1 editor in the section, then don't allow the user to
+ // delete it. Just clear the fields in the editor.
+ editor.clearAllFields();
+ } else {
+ editor.deleteEditor();
+ }
+ }
+ }
+
+ private List<KindSectionData> mKindSectionDataList;
+ private ViewIdGenerator mViewIdGenerator;
+ private CompactRawContactsEditorView.Listener mListener;
+
+ private boolean mShowOneEmptyEditor = false;
+ private boolean mHideIfEmpty = true;
+
+ private LayoutInflater mLayoutInflater;
+ private ViewGroup mEditors;
+ private ImageView mIcon;
+
+ public CompactKindSectionView(Context context) {
+ this(context, /* attrs =*/ null);
+ }
+
+ public CompactKindSectionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (mEditors != null) {
+ int childCount = mEditors.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ mEditors.getChildAt(i).setEnabled(enabled);
+ }
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ setDrawingCacheEnabled(true);
+ setAlwaysDrawnWithCacheEnabled(true);
+
+ mLayoutInflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ mEditors = (ViewGroup) findViewById(R.id.kind_editors);
+ mIcon = (ImageView) findViewById(R.id.kind_icon);
+ }
+
+ /**
+ * @param showOneEmptyEditor If true, we will always show one empty editor, otherwise an empty
+ * editor will not be shown until the user enters a value. Note, this does not apply
+ * to name editors since those are always displayed.
+ */
+ public void setShowOneEmptyEditor(boolean showOneEmptyEditor) {
+ mShowOneEmptyEditor = showOneEmptyEditor;
+ }
+
+ /**
+ * @param hideWhenEmpty If true, the entire section will be hidden if all inputs are empty,
+ * otherwise one empty input will always be displayed. Note, this does not apply
+ * to name editors since those are always displayed.
+ */
+ public void setHideWhenEmpty(boolean hideWhenEmpty) {
+ mHideIfEmpty = hideWhenEmpty;
+ }
+
+ /** Binds the given group data to every {@link GroupMembershipView}. */
+ public void setGroupMetaData(Cursor cursor) {
+ for (int i = 0; i < mEditors.getChildCount(); i++) {
+ final View view = mEditors.getChildAt(i);
+ if (view instanceof GroupMembershipView) {
+ ((GroupMembershipView) view).setGroupMetaData(cursor);
+ }
+ }
+ }
+
+ /**
+ * Binds views for the given {@link KindSectionData} list.
+ *
+ * We create a structured name and phonetic name editor for each {@link DataKind} with a
+ * {@link }StructuredName#CONTENT_ITEM_TYPE} mime type. The number and order of editors are
+ * rendered as they are given to {@link #setState}.
+ *
+ * Empty name editors are never added and at least one structured name editor is always
+ * displayed, even if it is empty.
+ */
+ public void setState(List<KindSectionData> kindSectionDataList,
+ ViewIdGenerator viewIdGenerator, CompactRawContactsEditorView.Listener listener) {
+ mKindSectionDataList = kindSectionDataList;
+ mViewIdGenerator = viewIdGenerator;
+ mListener = listener;
+
+ // Set the icon using the first DataKind
+ final DataKind dataKind = mKindSectionDataList.isEmpty()
+ ? null : mKindSectionDataList.get(0).getDataKind();
+ if (dataKind != null) {
+ mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(),
+ dataKind.mimeType));
+ if (mIcon.getDrawable() != null) {
+ mIcon.setContentDescription(dataKind.titleRes == -1 || dataKind.titleRes == 0
+ ? "" : getResources().getString(dataKind.titleRes));
+ }
+ }
+
+ rebuildFromState();
+
+ updateEmptyEditors(/* shouldAnimate = */ false);
+ }
+
+ private void rebuildFromState() {
+ mEditors.removeAllViews();
+
+ for (KindSectionData kindSectionData : mKindSectionDataList) {
+ final String mimeType = kindSectionData.getDataKind().mimeType;
+ if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ for (ValuesDelta valuesDelta : kindSectionData.getValuesDeltas()) {
+ addNameEditorViews(kindSectionData.getAccountType(),
+ valuesDelta, kindSectionData.getRawContactDelta());
+ }
+ } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ addGroupEditorView(kindSectionData.getRawContactDelta(),
+ kindSectionData.getDataKind());
+ } else {
+ final Editor.EditorListener editorListener = kindSectionData.isNicknameDataKind()
+ ? new OtherNameKindEditorListener() : new NonNameEditorListener();
+ for (ValuesDelta valuesDelta : kindSectionData.getValuesDeltas()) {
+ addNonNameEditorView(kindSectionData.getRawContactDelta(),
+ kindSectionData.getDataKind(), valuesDelta, editorListener);
+ }
+ }
+ }
+ }
+
+ private void addNameEditorViews(AccountType accountType,
+ ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
+ final boolean readOnly = !accountType.areContactsWritable();
+
+ if (readOnly) {
+ final View nameView = mLayoutInflater.inflate(
+ R.layout.structured_name_readonly_editor_view, mEditors,
+ /* attachToRoot =*/ false);
+
+ // Display name
+ ((TextView) nameView.findViewById(R.id.display_name))
+ .setText(valuesDelta.getDisplayName());
+
+ // Account type info
+ final LinearLayout accountTypeLayout = (LinearLayout)
+ nameView.findViewById(R.id.account_type);
+ accountTypeLayout.setVisibility(View.VISIBLE);
+ ((ImageView) accountTypeLayout.findViewById(R.id.account_type_icon))
+ .setImageDrawable(accountType.getDisplayIcon(getContext()));
+ ((TextView) accountTypeLayout.findViewById(R.id.account_type_name))
+ .setText(accountType.getDisplayLabel(getContext()));
+
+ mEditors.addView(nameView);
+ return;
+ }
+
+ // Structured name
+ final StructuredNameEditorView nameView = (StructuredNameEditorView) mLayoutInflater
+ .inflate(R.layout.structured_name_editor_view, mEditors, /* attachToRoot =*/ false);
+ nameView.setEditorListener(new StructuredNameEditorListener(valuesDelta,
+ rawContactDelta.getRawContactId(), mListener));
+ nameView.setDeletable(false);
+ nameView.setValues(
+ accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
+ valuesDelta, rawContactDelta, readOnly, mViewIdGenerator);
+
+ // Correct start margin since there is a second icon in the structured name layout
+ nameView.findViewById(R.id.kind_icon).setVisibility(View.GONE);
+ mEditors.addView(nameView);
+
+ // Phonetic name
+ final PhoneticNameEditorView phoneticNameView = (PhoneticNameEditorView) mLayoutInflater
+ .inflate(R.layout.phonetic_name_editor_view, mEditors, /* attachToRoot =*/ false);
+ phoneticNameView.setEditorListener(new OtherNameKindEditorListener());
+ phoneticNameView.setDeletable(false);
+ phoneticNameView.setValues(
+ accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME),
+ valuesDelta, rawContactDelta, readOnly, mViewIdGenerator);
+
+ // Fix the start margin for phonetic name views
+ final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ layoutParams.setMargins(0, 0, 0, 0);
+ phoneticNameView.setLayoutParams(layoutParams);
+ mEditors.addView(phoneticNameView);
+ }
+
+ private void addGroupEditorView(RawContactDelta rawContactDelta, DataKind dataKind) {
+ final GroupMembershipView view = (GroupMembershipView) mLayoutInflater.inflate(
+ R.layout.item_group_membership, mEditors, /* attachToRoot =*/ false);
+ view.setKind(dataKind);
+ view.setEnabled(isEnabled());
+ view.setState(rawContactDelta);
+
+ // Correct start margin since there is a second icon in the group layout
+ view.findViewById(R.id.kind_icon).setVisibility(View.GONE);
+
+ mEditors.addView(view);
+ }
+
+ private View addNonNameEditorView(RawContactDelta rawContactDelta, DataKind dataKind,
+ ValuesDelta valuesDelta, Editor.EditorListener editorListener) {
+ // Inflate the layout
+ final View view = mLayoutInflater.inflate(
+ EditorUiUtils.getLayoutResourceId(dataKind.mimeType), mEditors, false);
+ view.setEnabled(isEnabled());
+
+ // Hide the types drop downs until the associated edit field is focused
+ if (view instanceof LabeledEditorView) {
+ ((LabeledEditorView) view).setHideTypeInitially(true);
+ }
+
+ if (view instanceof Editor) {
+ final Editor editor = (Editor) view;
+ editor.setDeletable(true);
+ editor.setEditorListener(editorListener);
+ editor.setValues(dataKind, valuesDelta, rawContactDelta, !dataKind.editable,
+ mViewIdGenerator);
+ }
+ mEditors.addView(view);
+
+ return view;
+ }
+
+ /**
+ * Updates the editors being displayed to the user removing extra empty
+ * {@link Editor}s, so there is only max 1 empty {@link Editor} view at a time.
+ * If there is only 1 empty editor and {@link #setHideWhenEmpty} was set to true,
+ * then the entire section is hidden.
+ */
+ public void updateEmptyEditors(boolean shouldAnimate) {
+ final boolean isNameKindSection = mKindSectionDataList.get(0).isNameDataKind();
+ final boolean isGroupKindSection = GroupMembership.CONTENT_ITEM_TYPE.equals(
+ mKindSectionDataList.get(0).getDataKind().mimeType);
+
+ if (isNameKindSection) {
+ // The name kind section is always visible
+ setVisibility(VISIBLE);
+
+ updateEmptyNameEditors(shouldAnimate);
+ } else if (isGroupKindSection) {
+ // Check whether metadata has been bound for all group views
+ for (int i = 0; i < mEditors.getChildCount(); i++) {
+ final View view = mEditors.getChildAt(i);
+ if (view instanceof GroupMembershipView
+ && !((GroupMembershipView) view).wasGroupMetaDataBound()) {
+ setVisibility(GONE);
+ return;
+ }
+ }
+ // Check that the user has selected to display all fields
+ if (mHideIfEmpty) {
+ setVisibility(GONE);
+ return;
+ }
+ setVisibility(VISIBLE);
+
+ // We don't check the emptiness of the group views
+ } else {
+ // Determine if the entire kind section should be visible
+ final int editorCount = mEditors.getChildCount();
+ final List<View> emptyEditors = getEmptyEditors();
+ if (editorCount == emptyEditors.size() && mHideIfEmpty) {
+ setVisibility(GONE);
+ return;
+ }
+ setVisibility(VISIBLE);
+
+ updateEmptyNonNameEditors(shouldAnimate);
+ }
+ }
+
+ private void updateEmptyNameEditors(boolean shouldAnimate) {
+ boolean isEmptyNameEditorVisible = false;
+
+ for (int i = 0; i < mEditors.getChildCount(); i++) {
+ final View view = mEditors.getChildAt(i);
+ if (!(view instanceof Editor)) continue; // Skip read-only names
+ final Editor editor = (Editor) view;
+ if (view instanceof StructuredNameEditorView) {
+ // We always show one empty structured name view
+ if (editor.isEmpty()) {
+ if (isEmptyNameEditorVisible) {
+ // If we're already showing an empty editor then hide any other empties
+ if (mHideIfEmpty) {
+ view.setVisibility(View.GONE);
+ }
+ } else {
+ isEmptyNameEditorVisible = true;
+ }
+ } else {
+ showView(view, shouldAnimate);
+ isEmptyNameEditorVisible = true;
+ }
+ } else {
+ // For phonetic names and nicknames, which can't be added, just show or hide them
+ if (mHideIfEmpty && editor.isEmpty()) {
+ hideView(view);
+ } else {
+ showView(view, /* shouldAnimate =*/ false); // Animation here causes jank
+ }
+ }
+ }
+ }
+
+ private void updateEmptyNonNameEditors(boolean shouldAnimate) {
+ // Prune excess empty editors
+ final List<View> emptyEditors = getEmptyEditors();
+ if (emptyEditors.size() > 1) {
+ // If there is more than 1 empty editor, then remove it from the list of editors.
+ int deleted = 0;
+ for (final View view : emptyEditors) {
+ // If no child {@link View}s are being focused on within this {@link View}, then
+ // remove this empty editor. We can assume that at least one empty editor has
+ // focus. One way to get two empty editors is by deleting characters from a
+ // non-empty editor, in which case this editor has focus. Another way is if
+ // there is more values delta so we must also count number of editors deleted.
+ if (view.findFocus() == null) {
+ deleteView(view, shouldAnimate);
+ deleted++;
+ if (deleted == emptyEditors.size() - 1) break;
+ }
+ }
+ return;
+ }
+ // Determine if we should add a new empty editor
+ final DataKind dataKind = mKindSectionDataList.get(0).getDataKind();
+ if (dataKind == null // There is nothing we can do.
+ // We have already reached the maximum number of editors, don't add any more.
+ || (dataKind.typeOverallMax == mEditors.getChildCount()
+ && dataKind.typeOverallMax != 0)
+ // We have already reached the maximum number of empty editors, don't add any more.
+ || emptyEditors.size() == 1) {
+ return;
+ }
+ // Add a new empty editor
+ if (mShowOneEmptyEditor) {
+ final RawContactDelta rawContactDelta =
+ mKindSectionDataList.get(0).getRawContactDelta();
+ final ValuesDelta values = RawContactModifier.insertChild(rawContactDelta, dataKind);
+ final View view = addNonNameEditorView(rawContactDelta, dataKind, values,
+ new NonNameEditorListener());
+ showView(view, shouldAnimate);
+ }
+ }
+
+ private void hideView(View view) {
+ view.setVisibility(View.GONE);
+ }
+
+ private void deleteView(View view, boolean shouldAnimate) {
+ if (shouldAnimate) {
+ final Editor editor = (Editor) view;
+ editor.deleteEditor();
+ } else {
+ mEditors.removeView(view);
+ }
+ }
+
+ private void showView(View view, boolean shouldAnimate) {
+ if (shouldAnimate) {
+ view.setVisibility(View.GONE);
+ EditorAnimator.getInstance().showFieldFooter(view);
+ } else {
+ view.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private List<View> getEmptyEditors() {
+ final List<View> emptyEditors = new ArrayList<>();
+ for (int i = 0; i < mEditors.getChildCount(); i++) {
+ final View view = mEditors.getChildAt(i);
+ if (view instanceof Editor && ((Editor) view).isEmpty()) {
+ emptyEditors.add(view);
+ }
+ }
+ return emptyEditors;
+ }
+}
diff --git a/src/com/android/contacts/editor/CompactPhotoEditorView.java b/src/com/android/contacts/editor/CompactPhotoEditorView.java
index 5f3e9af..783e044 100644
--- a/src/com/android/contacts/editor/CompactPhotoEditorView.java
+++ b/src/com/android/contacts/editor/CompactPhotoEditorView.java
@@ -138,7 +138,7 @@
* RawContactDelta underlying this view is not modified in any way. Using this method allows
* you to show one photo (from a read-only contact, for example) and yet have a different
* raw contact updated when a new photo is set (from the new raw contact created and attached
- * to the read-only contact). See go/editing-read-only-contacts
+ * to the read-only contact).
*/
public void setPhoto(ValuesDelta valuesDelta) {
if (valuesDelta == null) {
diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
index 693b8fd..9d81276 100644
--- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java
+++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
@@ -23,7 +23,6 @@
import com.android.contacts.common.model.RawContactModifier;
import com.android.contacts.common.model.ValuesDelta;
import com.android.contacts.common.model.account.AccountType;
-import com.android.contacts.common.model.account.AccountType.EditField;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.model.dataitem.DataKind;
import com.android.contacts.common.util.AccountsListAdapter;
@@ -32,14 +31,25 @@
import com.android.contacts.util.UiClosables;
import android.content.Context;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -53,30 +63,31 @@
import android.widget.TextView;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.TreeSet;
/**
- * View to display information from multiple {@link RawContactDelta}s grouped together
- * (e.g. all the phone numbers from a {@link com.android.contacts.common.model.Contact} together.
+ * View to display information from multiple {@link RawContactDelta}s grouped together.
*/
public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
private static final String TAG = "CompactEditorView";
+ private static final KindSectionDataMapEntryComparator
+ KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR = new KindSectionDataMapEntryComparator();
+
/**
* Callbacks for hosts of {@link CompactRawContactsEditorView}s.
*/
public interface Listener {
/**
- * Invoked when the compact editor should be expanded to show all fields.
- */
- public void onExpandEditor();
-
- /**
* Invoked when the structured name editor field has changed.
*
* @param rawContactId The raw contact ID from the underlying {@link RawContactDelta}.
@@ -93,49 +104,187 @@
*/
public void onRebindEditorsForNewContact(RawContactDelta oldState,
AccountWithDataSet oldAccount, AccountWithDataSet newAccount);
+
+ /**
+ * Invoked when no editors could be bound for the contact.
+ */
+ public void onBindEditorsFailed();
+ }
+
+ /** Used to sort entire kind sections. */
+ private static final class KindSectionDataMapEntryComparator implements
+ Comparator<Map.Entry<String,List<KindSectionData>>> {
+
+ final MimeTypeComparator mMimeTypeComparator = new MimeTypeComparator();
+
+ @Override
+ public int compare(Map.Entry<String, List<KindSectionData>> entry1,
+ Map.Entry<String, List<KindSectionData>> entry2) {
+ if (entry1 == entry2) return 0;
+ if (entry1 == null) return -1;
+ if (entry2 == null) return 1;
+
+ final String mimeType1 = entry1.getKey();
+ final String mimeType2 = entry2.getKey();
+
+ return mMimeTypeComparator.compare(mimeType1, mimeType2);
+ }
}
/**
- * Marks a name as super primary when it is changed.
- *
- * This is for the case when two or more raw contacts with names are joined where neither is
- * marked as super primary. If the user hits back (which causes a save) after changing the
- * name that was arbitrarily displayed, we want that to be the name that is used.
- *
- * Should only be set when a super primary name does not already exist since we only show
- * one name field.
+ * Sorts kinds roughly the same as quick contacts; we diverge in the following ways:
+ * <ol>
+ * <li>All names are together at the top.</li>
+ * <li>IM is moved up after addresses</li>
+ * <li>SIP addresses are moved to below phone numbers</li>
+ * <li>Group membership is placed at the end</li>
+ * </ol>
*/
- static final class NameEditorListener implements Editor.EditorListener {
+ private static final class MimeTypeComparator implements Comparator<String> {
- private final ValuesDelta mValuesDelta;
- private final long mRawContactId;
- private final Listener mListener;
-
- public NameEditorListener(ValuesDelta valuesDelta, long rawContactId,
- Listener listener) {
- mValuesDelta = valuesDelta;
- mRawContactId = rawContactId;
- mListener = listener;
- }
+ private static final List<String> MIME_TYPE_ORDER = Arrays.asList(new String[] {
+ StructuredName.CONTENT_ITEM_TYPE,
+ Nickname.CONTENT_ITEM_TYPE,
+ Organization.CONTENT_ITEM_TYPE,
+ Phone.CONTENT_ITEM_TYPE,
+ SipAddress.CONTENT_ITEM_TYPE,
+ Email.CONTENT_ITEM_TYPE,
+ StructuredPostal.CONTENT_ITEM_TYPE,
+ Im.CONTENT_ITEM_TYPE,
+ Website.CONTENT_ITEM_TYPE,
+ Event.CONTENT_ITEM_TYPE,
+ Relation.CONTENT_ITEM_TYPE,
+ Note.CONTENT_ITEM_TYPE,
+ GroupMembership.CONTENT_ITEM_TYPE
+ });
@Override
- public void onRequest(int request) {
- if (request == Editor.EditorListener.FIELD_CHANGED) {
- mValuesDelta.setSuperPrimary(true);
- if (mListener != null) {
- mListener.onNameFieldChanged(mRawContactId, mValuesDelta);
- }
- } else if (request == Editor.EditorListener.FIELD_TURNED_EMPTY) {
- mValuesDelta.setSuperPrimary(false);
- }
- }
+ public int compare(String mimeType1, String mimeType2) {
+ if (mimeType1 == mimeType2) return 0;
+ if (mimeType1 == null) return -1;
+ if (mimeType2 == null) return 1;
- @Override
- public void onDeleteRequested(Editor editor) {
+ int index1 = MIME_TYPE_ORDER.indexOf(mimeType1);
+ int index2 = MIME_TYPE_ORDER.indexOf(mimeType2);
+
+ // Fallback to alphabetical ordering of the mime type if both are not found
+ if (index1 < 0 && index2 < 0) return mimeType1.compareTo(mimeType2);
+ if (index1 < 0) return 1;
+ if (index2 < 0) return -1;
+
+ return index1 < index2 ? -1 : 1;
}
}
- private Listener mListener;
+ /**
+ * Sorts primary accounts and google account types before others.
+ */
+ private static final class EditorComparator implements Comparator<KindSectionData> {
+
+ private RawContactDeltaComparator mRawContactDeltaComparator;
+
+ private EditorComparator(Context context) {
+ mRawContactDeltaComparator = new RawContactDeltaComparator(context);
+ }
+
+ @Override
+ public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
+ if (kindSectionData1 == kindSectionData2) return 0;
+ if (kindSectionData1 == null) return -1;
+ if (kindSectionData2 == null) return 1;
+
+ final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
+ final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
+
+ if (rawContactDelta1 == rawContactDelta2) return 0;
+ if (rawContactDelta1 == null) return -1;
+ if (rawContactDelta2 == null) return 1;
+
+ return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
+ }
+ }
+
+ /**
+ * Sorts primary account names first, followed by google account types, and other account
+ * types last. For names from the same account we order structured names before nicknames,
+ * but still keep names from the same account together.
+ */
+ private static final class NameEditorComparator implements Comparator<KindSectionData> {
+
+ private RawContactDeltaComparator mRawContactDeltaComparator;
+ private MimeTypeComparator mMimeTypeComparator;
+ private RawContactDelta mPrimaryRawContactDelta;
+
+ private NameEditorComparator(Context context, RawContactDelta primaryRawContactDelta) {
+ mRawContactDeltaComparator = new RawContactDeltaComparator(context);
+ mMimeTypeComparator = new MimeTypeComparator();
+ mPrimaryRawContactDelta = primaryRawContactDelta;
+ }
+
+ @Override
+ public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
+ if (kindSectionData1 == kindSectionData2) return 0;
+ if (kindSectionData1 == null) return -1;
+ if (kindSectionData2 == null) return 1;
+
+ final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
+ final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
+
+ if (rawContactDelta1 == rawContactDelta2) return 0;
+ if (rawContactDelta1 == null) return -1;
+ if (rawContactDelta2 == null) return 1;
+
+ final boolean isRawContactDelta1Primary =
+ mPrimaryRawContactDelta.equals(rawContactDelta1);
+ final boolean isRawContactDelta2Primary =
+ mPrimaryRawContactDelta.equals(rawContactDelta2);
+
+ // If both names are from the primary account, sort my by mime type
+ if (isRawContactDelta1Primary && isRawContactDelta2Primary) {
+ final String mimeType1 = kindSectionData1.getDataKind().mimeType;
+ final String mimeType2 = kindSectionData2.getDataKind().mimeType;
+ return mMimeTypeComparator.compare(mimeType1, mimeType2);
+ }
+
+ // The primary account name should be before all others
+ if (isRawContactDelta1Primary) return -1;
+ if (isRawContactDelta2Primary) return 1;
+
+ return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
+ }
+ }
+
+ public static class SavedState extends BaseSavedState {
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+
+ private boolean mIsExpanded;
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ mIsExpanded = in.readInt() != 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(mIsExpanded ? 1 : 0);
+ }
+ }
+
+ private CompactRawContactsEditorView.Listener mListener;
private AccountTypeManager mAccountTypeManager;
private LayoutInflater mLayoutInflater;
@@ -143,12 +292,11 @@
private ViewIdGenerator mViewIdGenerator;
private MaterialColorMapUtils.MaterialPalette mMaterialPalette;
private long mPhotoId;
- private long mNameId;
- private String mReadOnlyDisplayName;
private boolean mHasNewContact;
private boolean mIsUserProfile;
private AccountWithDataSet mPrimaryAccount;
private RawContactDelta mPrimaryRawContactDelta;
+ private Map<String,List<KindSectionData>> mKindSectionDataMap = new HashMap<>();
// Account header
private View mAccountHeaderContainer;
@@ -161,23 +309,14 @@
private TextView mAccountSelectorType;
private TextView mAccountSelectorName;
- private CompactPhotoEditorView mPhoto;
- private ViewGroup mNames;
- private ViewGroup mPhoneticNames;
- private ViewGroup mNicknames;
- private ViewGroup mPhoneNumbers;
- private ViewGroup mEmails;
- private ViewGroup mOtherTypes;
- private Map<String,LinearLayout> mOtherTypesMap = new HashMap<>();
+ private CompactPhotoEditorView mPhotoView;
+ private ViewGroup mKindSectionViews;
+ private Map<String,List<CompactKindSectionView>> mKindSectionViewsMap = new HashMap<>();
private View mMoreFields;
- // The ValuesDelta for the non super primary name that was displayed to the user.
- private ValuesDelta mNameValuesDelta;
-
+ private boolean mIsExpanded;
private long mPhotoRawContactId;
- private StructuredNameEditorView mDefaultNameEditorView;
-
public CompactRawContactsEditorView(Context context) {
super(context);
}
@@ -212,43 +351,48 @@
mAccountSelectorType = (TextView) findViewById(R.id.account_type_selector);
mAccountSelectorName = (TextView) findViewById(R.id.account_name_selector);
- mPhoto = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
- mNames = (LinearLayout) findViewById(R.id.names);
- mPhoneticNames = (LinearLayout) findViewById(R.id.phonetic_names);
- mNicknames = (LinearLayout) findViewById(R.id.nicknames);
- mPhoneNumbers = (LinearLayout) findViewById(R.id.phone_numbers);
- mEmails = (LinearLayout) findViewById(R.id.emails);
- mOtherTypes = (LinearLayout) findViewById(R.id.other);
+ mPhotoView = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
+ mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
mMoreFields = findViewById(R.id.more_fields);
mMoreFields.setOnClickListener(this);
}
@Override
public void onClick(View view) {
- if (view.getId() == R.id.more_fields && mListener != null ) {
- mListener.onExpandEditor();
+ if (view.getId() == R.id.more_fields) {
+ showMoreFields();
+ updateMoreFieldsButton();
}
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
- setEnabled(enabled, mNames);
- setEnabled(enabled, mPhoneticNames);
- setEnabled(enabled, mNicknames);
- setEnabled(enabled, mPhoneNumbers);
- setEnabled(enabled, mEmails);
- for (Map.Entry<String,LinearLayout> otherType : mOtherTypesMap.entrySet()) {
- setEnabled(enabled, otherType.getValue());
+ final int childCount = mKindSectionViews.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ mKindSectionViews.getChildAt(i).setEnabled(enabled);
}
}
- private void setEnabled(boolean enabled, ViewGroup viewGroup) {
- if (viewGroup != null) {
- final int childCount = viewGroup.getChildCount();
- for (int i = 0; i < childCount; i++) {
- viewGroup.getChildAt(i).setEnabled(enabled);
- }
+ @Override
+ public Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ final SavedState savedState = new SavedState(superState);
+ savedState.mIsExpanded = mIsExpanded;
+ return savedState;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ if(!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+ final SavedState savedState = (SavedState) state;
+ super.onRestoreInstanceState(savedState.getSuperState());
+ mIsExpanded = savedState.mIsExpanded;
+ if (mIsExpanded && !mKindSectionDataMap.isEmpty()) {
+ showMoreFields();
}
}
@@ -256,28 +400,28 @@
* Pass through to {@link CompactPhotoEditorView#setPhotoHandler}.
*/
public void setPhotoHandler(PhotoHandler photoHandler) {
- mPhoto.setPhotoHandler(photoHandler);
+ mPhotoView.setPhotoHandler(photoHandler);
}
/**
* Pass through to {@link CompactPhotoEditorView#setPhoto}.
*/
public void setPhoto(Bitmap bitmap) {
- mPhoto.setPhoto(bitmap);
+ mPhotoView.setPhoto(bitmap);
}
/**
* Pass through to {@link CompactPhotoEditorView#setFullSizedPhoto(Uri)}.
*/
public void setFullSizePhoto(Uri photoUri) {
- mPhoto.setFullSizedPhoto(photoUri);
+ mPhotoView.setFullSizedPhoto(photoUri);
}
/**
* Pass through to {@link CompactPhotoEditorView#isWritablePhotoSet}.
*/
public boolean isWritablePhotoSet() {
- return mPhoto.isWritablePhotoSet();
+ return mPhotoView.isWritablePhotoSet();
}
/**
@@ -287,58 +431,41 @@
return mPhotoRawContactId;
}
- public StructuredNameEditorView getDefaultNameEditorView() {
- return mDefaultNameEditorView;
- }
-
- public StructuredNameEditorView getStructuredNameEditorView() {
- // We only ever show one StructuredName
- return mNames.getChildCount() == 0
- ? null : (StructuredNameEditorView) mNames.getChildAt(0);
- }
-
- public PhoneticNameEditorView getFirstPhoneticNameEditorView() {
- // There should only ever be one phonetic name
- return mPhoneticNames.getChildCount() == 0
- ? null : (PhoneticNameEditorView) mPhoneticNames.getChildAt(0);
- }
-
public View getAggregationAnchorView() {
- // Since there is only one structured name we can just return it as the anchor for
- // the aggregation suggestions popup
- if (mNames.getChildCount() == 0) {
- return null;
+ final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
+ StructuredName.CONTENT_ITEM_TYPE);
+ if (!kindSectionViews.isEmpty()) {
+ return mKindSectionViews.getChildAt(0).findViewById(R.id.anchor_view);
}
- return mNames.getChildAt(0).findViewById(R.id.anchor_view);
+ return null;
}
- /**
- * @param readOnlyDisplayName The display name to set on the new raw contact created in order
- * to edit a read-only contact.
- */
+ public void setGroupMetaData(Cursor groupMetaData) {
+ final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
+ GroupMembership.CONTENT_ITEM_TYPE);
+ for (CompactKindSectionView kindSectionView : kindSectionViews) {
+ kindSectionView.setGroupMetaData(groupMetaData);
+ }
+
+ // Groups metadata may be set after we restore expansion state so just do it again
+ if (mIsExpanded) {
+ showMoreFields();
+ }
+ updateMoreFieldsButton();
+ }
+
public void setState(RawContactDeltaList rawContactDeltas,
MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
- long photoId, long nameId, String readOnlyDisplayName, boolean hasNewContact,
- boolean isUserProfile, AccountWithDataSet primaryAccount) {
- mNames.removeAllViews();
- mPhoneticNames.removeAllViews();
- mNicknames.removeAllViews();
- mPhoneNumbers.removeAllViews();
- mEmails.removeAllViews();
- mOtherTypes.removeAllViews();
- mOtherTypesMap.clear();
+ long photoId, boolean hasNewContact, boolean isUserProfile,
+ AccountWithDataSet primaryAccount) {
+ // Clear previous state and reset views
+ mKindSectionDataMap.clear();
+ mKindSectionViews.removeAllViews();
+ mMoreFields.setVisibility(View.VISIBLE);
- if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
- return;
- }
-
- mViewIdGenerator = viewIdGenerator;
- setId(mViewIdGenerator.getId(rawContactDeltas.get(0), /* dataKind =*/ null,
- /* valuesDelta =*/ null, ViewIdGenerator.NO_VIEW_INDEX));
mMaterialPalette = materialPalette;
+ mViewIdGenerator = viewIdGenerator;
mPhotoId = photoId;
- mNameId = nameId;
- mReadOnlyDisplayName = readOnlyDisplayName;
mHasNewContact = hasNewContact;
mIsUserProfile = isUserProfile;
mPrimaryAccount = primaryAccount;
@@ -347,48 +474,39 @@
}
vlog("state: primary " + mPrimaryAccount);
- vlog("state: setting compact editor state from " + rawContactDeltas);
- parseRawContactDeltas(rawContactDeltas);
- addAccountInfo();
- addPhotoView(rawContactDeltas, viewIdGenerator, photoId, readOnlyDisplayName);
- addStructuredNameView(rawContactDeltas, nameId, readOnlyDisplayName);
- addEditorViews(rawContactDeltas);
- updateKindEditorEmptyFields(mPhoneNumbers);
- updateKindEditorIcons(mPhoneNumbers);
- updateKindEditorEmptyFields(mEmails);
- updateKindEditorIcons(mEmails);
- for (Map.Entry<String,LinearLayout> otherTypes : mOtherTypesMap.entrySet()) {
- updateKindEditorIcons(otherTypes.getValue());
+ // Parse the given raw contact deltas
+ if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
+ elog("No raw contact deltas");
+ if (mListener != null) mListener.onBindEditorsFailed();
+ return;
}
+ parseRawContactDeltas(rawContactDeltas, mPrimaryAccount);
+ if (mKindSectionDataMap.isEmpty()) {
+ elog("No kind section data parsed from RawContactDelta(s)");
+ if (mListener != null) mListener.onBindEditorsFailed();
+ return;
+ }
+
+ // Setup the view
+ setId(mViewIdGenerator.getId(rawContactDeltas.get(0), /* dataKind =*/ null,
+ /* valuesDelta =*/ null, ViewIdGenerator.NO_VIEW_INDEX));
+ addAccountInfo();
+ addPhotoView();
+ addKindSectionViews();
+
+ updateMoreFieldsButton();
}
- private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
- // Get the raw contact delta for the primary account (the one displayed at the top)
- if (mPrimaryAccount == null || TextUtils.isEmpty(mPrimaryAccount.name)
- || !TextUtils.isEmpty(mReadOnlyDisplayName)) {
- // Use the first writable contact if this is an insert for a read-only contact.
- // In this case we can assume the first writable raw contact is the newly created one
- // because inserts have a raw contact delta list of size 1 and read-only contacts have
- // a list of size 2.
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
- if (accountType != null && accountType.areContactsWritable()) {
- vlog("parse: using first writable raw contact as primary");
- mPrimaryRawContactDelta = rawContactDelta;
- break;
- }
- }
- } else {
+ private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas,
+ AccountWithDataSet primaryAccount) {
+ if (primaryAccount != null) {
// Use the first writable contact that matches the primary account
for (RawContactDelta rawContactDelta : rawContactDeltas) {
if (!rawContactDelta.isVisible()) continue;
final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
- if (accountType != null && accountType.areContactsWritable()
- && Objects.equals(mPrimaryAccount.name, rawContactDelta.getAccountName())
- && Objects.equals(mPrimaryAccount.type, rawContactDelta.getAccountType())
- && Objects.equals(mPrimaryAccount.dataSet, rawContactDelta.getDataSet())) {
- vlog("parse: matched the primary account raw contact");
+ if (accountType == null || !accountType.areContactsWritable()) continue;
+ if (matchesAccount(primaryAccount, rawContactDelta)) {
+ vlog("parse: matched primary account raw contact");
mPrimaryRawContactDelta = rawContactDelta;
break;
}
@@ -406,140 +524,83 @@
}
}
}
+
+ if (mPrimaryRawContactDelta != null) {
+ RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
+ mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
+ StructuredName.CONTENT_ITEM_TYPE);
+ RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
+ mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
+ Photo.CONTENT_ITEM_TYPE);
+ }
+
+ // Build the kind section data list map
+ vlog("parse: " + rawContactDeltas.size() + " rawContactDelta(s)");
+ for (int j = 0; j < rawContactDeltas.size(); j++) {
+ final RawContactDelta rawContactDelta = rawContactDeltas.get(j);
+ vlog("parse: " + j + " rawContactDelta" + rawContactDelta);
+ if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
+ final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
+ if (accountType == null) continue;
+ final List<DataKind> dataKinds = accountType.getSortedDataKinds();
+ final int dataKindSize = dataKinds == null ? 0 : dataKinds.size();
+ vlog("parse: " + dataKindSize + " dataKinds(s)");
+ for (int i = 0; i < dataKindSize; i++) {
+ final DataKind dataKind = dataKinds.get(i);
+ if (dataKind == null || !dataKind.editable) {
+ vlog("parse: " + i + " " + dataKind.mimeType + " dropped read-only");
+ continue;
+ }
+ final String mimeType = dataKind.mimeType;
+
+ // Skip psuedo mime types
+ if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)
+ || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
+ vlog("parse: " + i + " " + dataKind.mimeType + " dropped pseudo type");
+ continue;
+ }
+
+ final List<KindSectionData> kindSectionDataList =
+ getKindSectionDataList(mimeType);
+ final KindSectionData kindSectionData =
+ new KindSectionData(accountType, dataKind, rawContactDelta);
+ kindSectionDataList.add(kindSectionData);
+
+ // Note we must create nickname entries
+ if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
+ && kindSectionData.getValuesDeltas().isEmpty()) {
+ RawContactModifier.insertChild(rawContactDelta, dataKind);
+ }
+
+ vlog("parse: " + i + " " + dataKind.mimeType + " " +
+ kindSectionData.getValuesDeltas().size() + " value(s)");
+ }
+ }
}
- private void addPhotoView(RawContactDeltaList rawContactDeltas,
- ViewIdGenerator viewIdGenerator, long photoId, String readOnlyDisplayName) {
- // If we're editing a read-only contact, the display name from the read-only
- // contact is non empty and we can use it determine whether to back the photo editor with
- // the empty new raw contact delta. See go/editing-read-only-contacts
- final boolean readOnlyContact = !TextUtils.isEmpty(readOnlyDisplayName);
- if (readOnlyContact) {
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
- // Make sure we have a photo
- RawContactModifier.ensureKindExists(
- rawContactDelta, accountType, Photo.CONTENT_ITEM_TYPE);
-
- final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
- if (accountType.areContactsWritable()) {
- for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(
- Photo.CONTENT_ITEM_TYPE)) {
- if (valuesDelta != null) {
- // Break the loop but don't return because we need to keep going to
- // in order to show the photo from the read-only contact.
- mPhotoRawContactId = rawContactDelta.getRawContactId();
- mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
- /* readOnly =*/ false, mMaterialPalette, viewIdGenerator);
- break;
- }
- }
- }
- }
+ private List<KindSectionData> getKindSectionDataList(String mimeType) {
+ // Put structured names and nicknames together
+ mimeType = Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
+ ? StructuredName.CONTENT_ITEM_TYPE : mimeType;
+ List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
+ if (kindSectionDataList == null) {
+ kindSectionDataList = new ArrayList<>();
+ mKindSectionDataMap.put(mimeType, kindSectionDataList);
}
+ return kindSectionDataList;
+ }
- // Look for a match for the photo ID that was passed in
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
- // Make sure we have a photo
- RawContactModifier.ensureKindExists(
- rawContactDelta, accountType, Photo.CONTENT_ITEM_TYPE);
-
- final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
- if (dataKind != null && dataKind.editable) {
- for (ValuesDelta valuesDelta
- : rawContactDelta.getMimeEntries(Photo.CONTENT_ITEM_TYPE)) {
- if (valuesDelta != null && valuesDelta.getId() != null
- && valuesDelta.getId().equals(photoId)) {
- if (readOnlyContact) {
- mPhoto.setPhoto(valuesDelta);
- } else {
- mPhotoRawContactId = rawContactDelta.getRawContactId();
- mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
- !accountType.areContactsWritable(),
- mMaterialPalette, viewIdGenerator);
- }
- return;
- }
- }
- }
- }
-
- // Look for a non-empty super primary photo
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
- final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
- if (dataKind != null && dataKind.editable) {
- final ValuesDelta valuesDelta = getNonEmptySuperPrimaryValuesDeltas(
- rawContactDelta, Photo.CONTENT_ITEM_TYPE, dataKind);
- if (valuesDelta != null) {
- if (readOnlyContact) {
- mPhoto.setPhoto(valuesDelta);
- } else {
- mPhotoRawContactId = rawContactDelta.getRawContactId();
- mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
- !accountType.areContactsWritable(), mMaterialPalette,
- viewIdGenerator);
- }
- return;
- }
- }
- }
- // We didn't find a non-empty super primary photo, use the first non-empty one
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
- final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
- if (dataKind != null && dataKind.editable) {
- final List<ValuesDelta> valuesDeltas = getNonEmptyValuesDeltas(
- rawContactDelta, Photo.CONTENT_ITEM_TYPE, dataKind);
- if (valuesDeltas != null && !valuesDeltas.isEmpty()) {
- if (readOnlyContact) {
- mPhoto.setPhoto(valuesDeltas.get(0));
- } else {
- mPhotoRawContactId = rawContactDelta.getRawContactId();
- mPhoto.setValues(dataKind, valuesDeltas.get(0), rawContactDelta,
- !accountType.areContactsWritable(), mMaterialPalette,
- viewIdGenerator);
- }
- return;
- }
- }
- }
- // No suitable non-empty photo
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
- final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
- if (dataKind != null && dataKind.editable) {
- final ValuesDelta valuesDelta = rawContactDelta.getSuperPrimaryEntry(
- dataKind.mimeType, /* forceSelection =*/ true);
- if (valuesDelta != null) {
- if (readOnlyContact) {
- mPhoto.setPhoto(valuesDelta);
- } else {
- mPhotoRawContactId = rawContactDelta.getRawContactId();
- mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
- !accountType.areContactsWritable(), mMaterialPalette,
- viewIdGenerator);
- }
- return;
- }
- }
- }
- // Should not happen since we ensure the kind exists but if we unexpectedly get here
- // we must remove the photo section so that it does not take up the entire view
- mPhoto.setVisibility(View.GONE);
+ /** Whether the given RawContactDelta belong to the given account. */
+ private boolean matchesAccount(AccountWithDataSet accountWithDataSet,
+ RawContactDelta rawContactDelta) {
+ if (accountWithDataSet == null) return false;
+ return Objects.equals(accountWithDataSet.name, rawContactDelta.getAccountName())
+ && Objects.equals(accountWithDataSet.type, rawContactDelta.getAccountType())
+ && Objects.equals(accountWithDataSet.dataSet, rawContactDelta.getDataSet());
}
private void addAccountInfo() {
if (mPrimaryRawContactDelta == null) {
- vlog("account info: hidden because no raw contact delta");
mAccountHeaderContainer.setVisibility(View.GONE);
mAccountSelectorContainer.setVisibility(View.GONE);
return;
@@ -626,384 +687,179 @@
});
}
- private void addStructuredNameView(RawContactDeltaList rawContactDeltas, long nameId,
- String readOnlyDisplayName) {
- // If we're editing a read-only contact we want to display the name from the read-only
- // contact in a structured name editor backed by the new raw contact that was created.
- // The new raw contact is writable and merging it with the read-only contact allows us
- // to edit the read-only contact. See go/editing-read-only-contacts
- if (!TextUtils.isEmpty(readOnlyDisplayName)) {
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
+ private void addPhotoView() {
+ // Get the kind section data and values delta that will back the photo view
+ Pair<KindSectionData,ValuesDelta> pair = getPrimaryKindSectionData(mPhotoId);
+ if (pair == null) {
+ wlog("photo: no kind section data parsed");
+ return;
+ }
+ final KindSectionData kindSectionData = pair.first;
+ final ValuesDelta valuesDelta = pair.second;
- // Make sure we have a structured name
- RawContactModifier.ensureKindExists(
- rawContactDelta, accountType, StructuredName.CONTENT_ITEM_TYPE);
-
- if (accountType.areContactsWritable()) {
- for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(
- StructuredName.CONTENT_ITEM_TYPE)) {
- if (valuesDelta != null) {
- mNameValuesDelta = valuesDelta;
- final NameEditorListener nameEditorListener = new NameEditorListener(
- mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
- final StructuredNameEditorView nameEditorView =
- inflateStructuredNameEditorView(mNames, accountType,
- mNameValuesDelta, rawContactDelta, nameEditorListener,
- !accountType.areContactsWritable());
- nameEditorView.setDisplayName(readOnlyDisplayName);
- mNames.addView(nameEditorView);
- mDefaultNameEditorView = nameEditorView;
- return;
- }
- }
- }
- }
+ // If we're editing a read-only contact we want to display the photo from the
+ // read-only contact in a photo editor backed by the new raw contact
+ // that was created.
+ if (mHasNewContact) {
+ mPhotoRawContactId = mPrimaryRawContactDelta == null
+ ? null : mPrimaryRawContactDelta.getRawContactId();
}
- // Look for a match for the name ID that was passed in
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
- // Make sure we have a structured name
- RawContactModifier.ensureKindExists(
- rawContactDelta, accountType, StructuredName.CONTENT_ITEM_TYPE);
-
- // Note use of pseudo mime type to get the DataKind and StructuredName to get value
- final DataKind dataKind = accountType.getKindForMimetype(
- DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
- if (dataKind == null || !dataKind.editable) continue;
-
- for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(
- StructuredName.CONTENT_ITEM_TYPE)) {
- if (valuesDelta != null && valuesDelta.getId() != null
- && valuesDelta.getId().equals(nameId)) {
- mNameValuesDelta = valuesDelta;
- final NameEditorListener nameEditorListener = new NameEditorListener(
- mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
- mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
- mNameValuesDelta, rawContactDelta, nameEditorListener,
- !accountType.areContactsWritable()));
- return;
- }
- }
- }
- // Look for a super primary name
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
- final DataKind dataKind = accountType.getKindForMimetype(
- DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
- if (dataKind == null || !dataKind.editable) continue;
-
- final ValuesDelta superPrimaryValuesDelta = getNonEmptySuperPrimaryValuesDeltas(
- rawContactDelta, StructuredName.CONTENT_ITEM_TYPE, dataKind);
- if (superPrimaryValuesDelta != null) {
- // Our first preference is for a non-empty super primary name
- final NameEditorListener nameEditorListener = new NameEditorListener(
- superPrimaryValuesDelta, rawContactDelta.getRawContactId(), mListener);
- mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
- superPrimaryValuesDelta, rawContactDelta, nameEditorListener,
- !accountType.areContactsWritable()));
- return;
- }
- }
- // We didn't find a super primary name
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
- final DataKind dataKind = accountType.getKindForMimetype(
- DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
- if (dataKind == null || !dataKind.editable) continue;
-
- final List<ValuesDelta> nonEmptyValuesDeltas = getNonEmptyValuesDeltas(
- rawContactDelta, StructuredName.CONTENT_ITEM_TYPE, dataKind);
- if (nonEmptyValuesDeltas != null && !nonEmptyValuesDeltas.isEmpty()) {
- // Take the first non-empty name
- mNameValuesDelta = nonEmptyValuesDeltas.get(0);
- final NameEditorListener nameEditorListener = new NameEditorListener(
- mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
- mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
- mNameValuesDelta, rawContactDelta, nameEditorListener,
- !accountType.areContactsWritable()));
- return;
- }
- }
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
- final DataKind dataKind = accountType.getKindForMimetype(
- DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
- if (dataKind == null || !dataKind.editable) continue;
-
- // Fall back to the first entry
- final ArrayList<ValuesDelta> valuesDeltas = rawContactDelta.getMimeEntries(
- StructuredName.CONTENT_ITEM_TYPE);
- if (valuesDeltas != null && !valuesDeltas.isEmpty()) {
- mNameValuesDelta = valuesDeltas.get(0);
- final NameEditorListener nameEditorListener = new NameEditorListener(
- mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
- mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
- mNameValuesDelta, rawContactDelta, nameEditorListener,
- !accountType.areContactsWritable()));
- return;
- }
- }
+ mPhotoRawContactId = kindSectionData.getRawContactDelta().getRawContactId();
+ mPhotoView.setValues(kindSectionData.getDataKind(), valuesDelta,
+ kindSectionData.getRawContactDelta(),
+ !kindSectionData.getAccountType().areContactsWritable(), mMaterialPalette,
+ mViewIdGenerator);
}
- private void addEditorViews(RawContactDeltaList rawContactDeltas) {
- for (RawContactDelta rawContactDelta : rawContactDeltas) {
- if (!rawContactDelta.isVisible()) continue;
- final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
+ private Pair<KindSectionData,ValuesDelta> getPrimaryKindSectionData(long id) {
+ final String mimeType = Photo.CONTENT_ITEM_TYPE;
+ final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
- for (DataKind dataKind : accountType.getSortedDataKinds()) {
- if (!dataKind.editable) continue;
-
- final String mimeType = dataKind.mimeType;
- vlog(mimeType + " " + dataKind.fieldList.size() + " field(s)");
- if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)
- || StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)
- || GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
- // Photos and structured names are handled separately and
- // group membership is not supported
- continue;
- } else if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
- // Only add phonetic names if there is a non-empty one. Note the use of
- // StructuredName mimeType below, even though we matched a pseudo mime type.
- final ValuesDelta valuesDelta = rawContactDelta.getSuperPrimaryEntry(
- StructuredName.CONTENT_ITEM_TYPE, /* forceSelection =*/ true);
- if (hasNonEmptyValue(dataKind, valuesDelta)) {
- mPhoneticNames.addView(inflatePhoneticNameEditorView(
- mPhoneticNames, accountType, valuesDelta, rawContactDelta));
- }
- } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
- // Add all non-empty nicknames
- final List<ValuesDelta> valuesDeltas = getNonEmptyValuesDeltas(
- rawContactDelta, Nickname.CONTENT_ITEM_TYPE, dataKind);
- if (valuesDeltas != null && !valuesDeltas.isEmpty()) {
- for (ValuesDelta valuesDelta : valuesDeltas) {
- mNicknames.addView(inflateNicknameEditorView(
- mNicknames, dataKind, valuesDelta, rawContactDelta));
- }
- }
- } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
- final KindSectionView kindSectionView =
- inflateKindSectionView(mPhoneNumbers, dataKind, rawContactDelta);
- kindSectionView.setListener(new KindSectionView.Listener() {
- @Override
- public void onDeleteRequested(Editor editor) {
- if (kindSectionView.getEditorCount() == 1) {
- kindSectionView.markForRemoval();
- EditorAnimator.getInstance().removeEditorView(kindSectionView);
- } else {
- editor.deleteEditor();
- }
- updateKindEditorEmptyFields(mPhoneNumbers);
- updateKindEditorIcons(mPhoneNumbers);
- }
- });
- mPhoneNumbers.addView(kindSectionView);
- } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
- final KindSectionView kindSectionView =
- inflateKindSectionView(mEmails, dataKind, rawContactDelta);
- kindSectionView.setListener(new KindSectionView.Listener() {
- @Override
- public void onDeleteRequested(Editor editor) {
- if (kindSectionView.getEditorCount() == 1) {
- kindSectionView.markForRemoval();
- EditorAnimator.getInstance().removeEditorView(kindSectionView);
- } else {
- editor.deleteEditor();
- }
- updateKindEditorEmptyFields(mEmails);
- updateKindEditorIcons(mEmails);
- }
- });
- mEmails.addView(kindSectionView);
- } else if (hasNonEmptyValuesDelta(rawContactDelta, mimeType, dataKind)) {
- final LinearLayout otherTypeViewGroup;
- if (mOtherTypesMap.containsKey(mimeType)) {
- otherTypeViewGroup = mOtherTypesMap.get(mimeType);
- } else {
- otherTypeViewGroup = new LinearLayout(getContext());
- otherTypeViewGroup.setOrientation(LinearLayout.VERTICAL);
- mOtherTypes.addView(otherTypeViewGroup);
- mOtherTypesMap.put(mimeType, otherTypeViewGroup);
- }
- final KindSectionView kindSectionView =
- inflateKindSectionView(mOtherTypes, dataKind, rawContactDelta);
- kindSectionView.setListener(new KindSectionView.Listener() {
- @Override
- public void onDeleteRequested(Editor editor) {
- if (kindSectionView.getEditorCount() == 1) {
- kindSectionView.markForRemoval();
- EditorAnimator.getInstance().removeEditorView(kindSectionView);
- } else {
- editor.deleteEditor();
- }
- updateKindEditorIcons(otherTypeViewGroup);
- }
- });
- otherTypeViewGroup.addView(kindSectionView);
+ KindSectionData resultKindSectionData = null;
+ ValuesDelta resultValuesDelta = null;
+ if (id > 0) {
+ // Look for a match for the ID that was passed in
+ for (KindSectionData kindSectionData : kindSectionDataList) {
+ resultValuesDelta = kindSectionData.getValuesDeltaById(id);
+ if (resultValuesDelta != null) {
+ vlog("photo: matched kind section data by ID");
+ resultKindSectionData = kindSectionData;
+ break;
}
}
}
- }
-
- private static void updateKindEditorEmptyFields(ViewGroup viewGroup) {
- KindSectionView lastVisibleKindSectionView = null;
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- if (viewGroup.getChildAt(i).getVisibility() == View.VISIBLE) {
- lastVisibleKindSectionView = (KindSectionView) viewGroup.getChildAt(i);
- }
- }
- // Only the last editor should show an empty editor
- if (lastVisibleKindSectionView != null) {
- // Hide all empty kind sections except the last one
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- final KindSectionView kindSectionView = (KindSectionView) viewGroup.getChildAt(i);
- if (kindSectionView != lastVisibleKindSectionView
- && kindSectionView.areAllEditorsEmpty()) {
- kindSectionView.setVisibility(View.GONE);
+ if (resultKindSectionData == null) {
+ // Look for a super primary photo
+ for (KindSectionData kindSectionData : kindSectionDataList) {
+ resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
+ if (resultValuesDelta != null) {
+ wlog("photo: matched super primary kind section data");
+ resultKindSectionData = kindSectionData;
+ break;
}
}
- // Set the last editor to show empty editor fields
- lastVisibleKindSectionView.setShowOneEmptyEditor(true);
- lastVisibleKindSectionView.updateEmptyEditors(/* shouldAnimate =*/ false);
}
+ if (resultKindSectionData == null) {
+ // Fall back to the first non-empty value
+ for (KindSectionData kindSectionData : kindSectionDataList) {
+ resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
+ if (resultValuesDelta != null) {
+ vlog("photo: using first non empty value");
+ resultKindSectionData = kindSectionData;
+ break;
+ }
+ }
+ }
+ if (resultKindSectionData == null || resultValuesDelta == null) {
+ final List<ValuesDelta> valuesDeltaList = kindSectionDataList.get(0).getValuesDeltas();
+ if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
+ vlog("photo: falling back to first empty entry");
+ resultValuesDelta = valuesDeltaList.get(0);
+ resultKindSectionData = kindSectionDataList.get(0);
+ }
+ }
+ return resultKindSectionData != null && resultValuesDelta != null
+ ? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
}
- private static void updateKindEditorIcons(ViewGroup viewGroup) {
- // Show the icon on the first visible kind editor
- boolean iconVisible = false;
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- final KindSectionView kindSectionView = (KindSectionView) viewGroup.getChildAt(i);
- if (kindSectionView.getVisibility() != View.VISIBLE
- || kindSectionView.isMarkedForRemoval()) {
+ private void addKindSectionViews() {
+ // Sort the kinds
+ final TreeSet<Map.Entry<String,List<KindSectionData>>> entries =
+ new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
+ entries.addAll(mKindSectionDataMap.entrySet());
+
+ vlog("kind: " + entries.size() + " kindSection(s)");
+ int i = -1;
+ for (Map.Entry<String, List<KindSectionData>> entry : entries) {
+ i++;
+
+ final String mimeType = entry.getKey();
+ final List<KindSectionData> kindSectionDataList = entry.getValue();
+
+ // Ignore mime types that we've already handled
+ if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ vlog("kind: " + i + " " + mimeType + " dropped");
continue;
}
- if (!iconVisible) {
- kindSectionView.setIconVisibility(true);
- iconVisible = true;
- } else {
- kindSectionView.setIconVisibility(false);
+
+ if (kindSectionDataList != null && !kindSectionDataList.isEmpty()) {
+ vlog("kind: " + i + " " + mimeType + ": " + kindSectionDataList.size() +
+ " kindSectionData(s)");
+
+ final CompactKindSectionView kindSectionView = inflateKindSectionView(
+ mKindSectionViews, kindSectionDataList, mimeType);
+ mKindSectionViews.addView(kindSectionView);
+
+ // Keep a pointer to all the KindSectionsViews for each mimeType
+ getKindSectionViews(mimeType).add(kindSectionView);
}
}
}
- private static boolean hasNonEmptyValuesDelta(RawContactDelta rawContactDelta,
- String mimeType, DataKind dataKind) {
- return !getNonEmptyValuesDeltas(rawContactDelta, mimeType, dataKind).isEmpty();
+ private List<CompactKindSectionView> getKindSectionViews(String mimeType) {
+ List<CompactKindSectionView> kindSectionViews = mKindSectionViewsMap.get(mimeType);
+ if (kindSectionViews == null) {
+ kindSectionViews = new ArrayList<>();
+ mKindSectionViewsMap.put(mimeType, kindSectionViews);
+ }
+ return kindSectionViews;
}
- private static ValuesDelta getNonEmptySuperPrimaryValuesDeltas(RawContactDelta rawContactDelta,
- String mimeType, DataKind dataKind) {
- for (ValuesDelta valuesDelta : getNonEmptyValuesDeltas(
- rawContactDelta, mimeType, dataKind)) {
- if (valuesDelta.isSuperPrimary()) {
- return valuesDelta;
+ private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
+ List<KindSectionData> kindSectionDataList, String mimeType) {
+ final CompactKindSectionView kindSectionView = (CompactKindSectionView)
+ mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
+ /* attachToRoot =*/ false);
+
+ if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
+ || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ // Phone numbers and email addresses are always displayed,
+ // even if they are empty
+ kindSectionView.setHideWhenEmpty(false);
+ }
+
+ // Since phone numbers and email addresses displayed even if they are empty,
+ // they will be the only types you add new values to initially for new contacts
+ kindSectionView.setShowOneEmptyEditor(true);
+
+ // Sort so the editors wind up in the order we want
+ if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
+ Collections.sort(kindSectionDataList, new NameEditorComparator(getContext(),
+ mPrimaryRawContactDelta));
+ } else {
+ Collections.sort(kindSectionDataList, new EditorComparator(getContext()));
+ }
+
+ kindSectionView.setState(kindSectionDataList, mViewIdGenerator, mListener);
+
+ return kindSectionView;
+ }
+
+ private void showMoreFields() {
+ // Stop hiding empty editors and allow the user to enter values for all kinds now
+ for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
+ final CompactKindSectionView kindSectionView =
+ (CompactKindSectionView) mKindSectionViews.getChildAt(i);
+ kindSectionView.setHideWhenEmpty(false);
+ kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
+ }
+ mIsExpanded = true;
+ }
+
+ private void updateMoreFieldsButton() {
+ // If any kind section views are hidden then show the link
+ for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
+ final CompactKindSectionView kindSectionView =
+ (CompactKindSectionView) mKindSectionViews.getChildAt(i);
+ if (kindSectionView.getVisibility() == View.GONE) {
+ // Show the more fields button
+ mMoreFields.setVisibility(View.VISIBLE);
+ return;
}
}
- return null;
- }
-
- static List<ValuesDelta> getNonEmptyValuesDeltas(RawContactDelta rawContactDelta,
- String mimeType, DataKind dataKind) {
- final List<ValuesDelta> result = new ArrayList<>();
- if (rawContactDelta == null) {
- vlog("Null RawContactDelta");
- return result;
- }
- if (!rawContactDelta.hasMimeEntries(mimeType)) {
- vlog("No ValueDeltas");
- return result;
- }
- for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(mimeType)) {
- if (hasNonEmptyValue(dataKind, valuesDelta)) {
- result.add(valuesDelta);
- }
- }
- return result;
- }
-
- private static boolean hasNonEmptyValue(DataKind dataKind, ValuesDelta valuesDelta) {
- if (valuesDelta == null) {
- vlog("Null valuesDelta");
- return false;
- }
- for (EditField editField : dataKind.fieldList) {
- final String column = editField.column;
- final String value = valuesDelta == null ? null : valuesDelta.getAsString(column);
- vlog("Field " + column + " empty=" + TextUtils.isEmpty(value) + " value=" + value);
- if (!TextUtils.isEmpty(value)) {
- return true;
- }
- }
- return false;
- }
-
- private StructuredNameEditorView inflateStructuredNameEditorView(ViewGroup viewGroup,
- AccountType accountType, ValuesDelta valuesDelta, RawContactDelta rawContactDelta,
- NameEditorListener nameEditorListener, boolean readOnly) {
- final StructuredNameEditorView result = (StructuredNameEditorView) mLayoutInflater.inflate(
- R.layout.structured_name_editor_view, viewGroup, /* attachToRoot =*/ false);
- if (nameEditorListener != null) {
- result.setEditorListener(nameEditorListener);
- }
- result.setDeletable(false);
- result.setValues(
- accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
- valuesDelta,
- rawContactDelta,
- readOnly,
- mViewIdGenerator);
- return result;
- }
-
- private PhoneticNameEditorView inflatePhoneticNameEditorView(ViewGroup viewGroup,
- AccountType accountType, ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
- final PhoneticNameEditorView result = (PhoneticNameEditorView) mLayoutInflater.inflate(
- R.layout.phonetic_name_editor_view, viewGroup, /* attachToRoot =*/ false);
- result.setDeletable(false);
- result.setValues(
- accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME),
- valuesDelta,
- rawContactDelta,
- /* readOnly =*/ false,
- mViewIdGenerator);
- return result;
- }
-
- private TextFieldsEditorView inflateNicknameEditorView(ViewGroup viewGroup, DataKind dataKind,
- ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
- final TextFieldsEditorView result = (TextFieldsEditorView) mLayoutInflater.inflate(
- R.layout.nick_name_editor_view, viewGroup, /* attachToRoot =*/ false);
- result.setDeletable(false);
- result.setValues(
- dataKind,
- valuesDelta,
- rawContactDelta,
- /* readOnly =*/ false,
- mViewIdGenerator);
- return result;
- }
-
-
- private KindSectionView inflateKindSectionView(ViewGroup viewGroup, DataKind dataKind,
- RawContactDelta rawContactDelta) {
- final KindSectionView result = (KindSectionView) mLayoutInflater.inflate(
- R.layout.item_kind_section, viewGroup, /* attachToRoot =*/ false);
- result.setState(
- dataKind,
- rawContactDelta,
- /* readOnly =*/ false,
- mViewIdGenerator);
- return result;
+ // Hide the more fields button
+ mMoreFields.setVisibility(View.GONE);
}
private static void vlog(String message) {
@@ -1011,4 +867,14 @@
Log.v(TAG, message);
}
}
+
+ private static void wlog(String message) {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, message);
+ }
+ }
+
+ private static void elog(String message) {
+ Log.e(TAG, message);
+ }
}
diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
index 4e5fff4..5b341a0 100644
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
@@ -113,7 +113,6 @@
private static final String KEY_NEW_LOCAL_PROFILE = "newLocalProfile";
private static final String KEY_MATERIAL_PALETTE = "materialPalette";
private static final String KEY_PHOTO_ID = "photoId";
- private static final String KEY_NAME_ID = "nameId";
private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
@@ -145,12 +144,12 @@
// Join Activity
private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin";
- private static final String KEY_UPDATED_PHOTOS = "updatedPhotos";
-
protected static final int REQUEST_CODE_JOIN = 0;
protected static final int REQUEST_CODE_ACCOUNTS_CHANGED = 1;
protected static final int REQUEST_CODE_PICK_RINGTONE = 2;
+ private static final int CURRENT_API_VERSION = android.os.Build.VERSION.SDK_INT;
+
/**
* An intent extra that forces the editor to add the edited contact
* to the default group (e.g. "My Contacts").
@@ -179,33 +178,16 @@
"material_palette_secondary_color";
/**
- * Intent key to pass a Bundle of raw contact IDs to photos URIs between the compact editor
- * and the fully expanded one.
- */
- public static final String INTENT_EXTRA_UPDATED_PHOTOS = "updated_photos";
-
- /**
* Intent key to pass the ID of the photo to display on the editor.
*/
public static final String INTENT_EXTRA_PHOTO_ID = "photo_id";
/**
- * Intent key to pass the ID of the name to display on the editor.
- */
- public static final String INTENT_EXTRA_NAME_ID = "name_id";
-
- /**
* Intent extra to specify a {@link ContactEditor.SaveMode}.
*/
public static final String SAVE_MODE_EXTRA_KEY = "saveMode";
/**
- * Intent extra to specify whether the save was initiated as a result of a back button press
- * or because the framework stopped the editor Activity.
- */
- public static final String INTENT_EXTRA_SAVE_BACK_PRESSED = "saveBackPressed";
-
- /**
* Callbacks for Activities that host contact editors Fragments.
*/
public interface Listener {
@@ -331,7 +313,6 @@
protected boolean mNewLocalProfile;
protected MaterialColorMapUtils.MaterialPalette mMaterialPalette;
protected long mPhotoId = -1;
- protected long mNameId = -1;
//
// Helpers
@@ -382,20 +363,6 @@
// Join Activity
protected long mContactIdForJoin;
- // Full resolution photo URIs
- protected Bundle mUpdatedPhotos = new Bundle();
-
- //
- // Not saved/restored on rotates
- //
-
- // Used to pre-populate the editor with a display name when a user edits a read-only contact.
- protected String mReadOnlyDisplayName;
-
- // The name editor view for the new raw contact that was created so that the user can
- // edit a read-only contact (to which the new raw contact was joined)
- protected StructuredNameEditorView mReadOnlyNameEditorView;
-
/**
* The contact data loader listener.
*/
@@ -488,7 +455,6 @@
mNewLocalProfile = savedState.getBoolean(KEY_NEW_LOCAL_PROFILE);
mMaterialPalette = savedState.getParcelable(KEY_MATERIAL_PALETTE);
mPhotoId = savedState.getLong(KEY_PHOTO_ID);
- mNameId = savedState.getLong(KEY_NAME_ID);
mRawContacts = ImmutableList.copyOf(savedState.<RawContact>getParcelableArrayList(
KEY_RAW_CONTACTS));
@@ -520,9 +486,6 @@
// Join Activity
mContactIdForJoin = savedState.getLong(KEY_CONTACT_ID_FOR_JOIN);
-
- // Full resolution photo URIs
- mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS);
}
// mState can still be null because it may not have have finished loading before
@@ -613,7 +576,6 @@
outState.putParcelable(KEY_MATERIAL_PALETTE, mMaterialPalette);
}
outState.putLong(KEY_PHOTO_ID, mPhotoId);
- outState.putLong(KEY_NAME_ID, mNameId);
outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
@@ -648,9 +610,6 @@
// Join Activity
outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
- // Full resolution photo URIs
- outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos);
-
super.onSaveInstanceState(outState);
}
@@ -659,6 +618,11 @@
super.onStop();
UiClosables.closeQuietly(mAggregationSuggestionPopup);
+
+ // If anything was left unsaved, save it now but keep the editor open.
+ if (!getActivity().isChangingConfigurations() && mStatus == Status.EDITING) {
+ save(SaveMode.RELOAD);
+ }
}
@Override
@@ -716,13 +680,7 @@
}
private void onRingtonePicked(Uri pickedUri) {
- if (pickedUri == null) {
- mCustomRingtone = ""; // silent ringtone
- } else if (RingtoneManager.isDefault(pickedUri)){
- mCustomRingtone = null; // default ringtone
- } else {
- mCustomRingtone = pickedUri.toString();
- }
+ mCustomRingtone = EditorUiUtils.getRingtoneStringFromUri(pickedUri, CURRENT_API_VERSION);
Intent intent = ContactSaveService.createSetRingtone(
mContext, mLookupUri, mCustomRingtone);
mContext.startService(intent);
@@ -824,7 +782,7 @@
switch (item.getItemId()) {
case R.id.menu_save:
- return save(SaveMode.CLOSE, /* backPressed =*/ true);
+ return save(SaveMode.CLOSE);
case R.id.menu_discard:
return revert();
case R.id.menu_delete:
@@ -881,7 +839,7 @@
}
mState.markRawContactsForSplitting();
- save(SaveMode.SPLIT, /* backPressed =*/ false);
+ save(SaveMode.SPLIT);
}
private boolean doSplitContactAction() {
@@ -899,13 +857,13 @@
// If we just started creating a new contact and haven't added any data, it's too
// early to do a join
if (mState.size() == 1 && mState.get(0).isContactInsert()
- && !hasPendingRawContactChanges()) {
+ && !hasPendingChanges()) {
Toast.makeText(mContext, R.string.toast_join_with_empty_contact,
Toast.LENGTH_LONG).show();
return true;
}
- return save(SaveMode.JOIN, /* backPressed =*/ false);
+ return save(SaveMode.JOIN);
}
private void doPickRingtone() {
@@ -917,17 +875,8 @@
// Allow the user to pick a silent ringtone
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
- final Uri ringtoneUri;
- if (mCustomRingtone != null) {
- if ("".equals(mCustomRingtone)) { // select silent ringtone in RingtonePickerActivity
- ringtoneUri = null;
- } else {
- ringtoneUri = Uri.parse(mCustomRingtone);
- }
- } else {
- // Otherwise pick default ringtone Uri so that something is selected.
- ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
- }
+ final Uri ringtoneUri = EditorUiUtils.getRingtoneUriFromString(mCustomRingtone,
+ CURRENT_API_VERSION);
// Put checkmark next to the current ringtone for this contact
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
@@ -941,7 +890,7 @@
}
@Override
- public boolean save(int saveMode, boolean backPressed) {
+ public boolean save(int saveMode) {
if (!hasValidState() || mStatus != Status.EDITING) {
return false;
}
@@ -965,31 +914,19 @@
return true;
}
onSaveCompleted(/* hadChanges =*/ false, saveMode,
- /* saveSucceeded =*/ mLookupUri != null, mLookupUri,
- /* updatedPhotos =*/ null, backPressed, mPhotoId, mNameId);
+ /* saveSucceeded =*/ mLookupUri != null, mLookupUri);
return true;
}
setEnabled(false);
- if (isInsert(getActivity().getIntent()) && saveMode == SaveMode.COMPACT
- && mListener != null && backPressed) {
- // If we're coming back from the fully expanded editor and this is an insert, just
- // pass any values entered by the user back to the compact editor without doing a save
- final Intent resultIntent = EditorIntents.createCompactInsertContactIntent(
- mState, getDisplayName(), getPhoneticName(), mUpdatedPhotos, mNewLocalProfile);
- resultIntent.putExtra(INTENT_EXTRA_SAVE_BACK_PRESSED, backPressed);
- mListener.onSaveFinished(resultIntent);
- return true;
- }
- // Otherwise this is an edit or a back press so do an actual save
- return doSaveAction(saveMode, backPressed);
+ return doSaveAction(saveMode);
}
/**
* Persist the accumulated editor deltas.
*/
- abstract protected boolean doSaveAction(int saveMode, boolean backPressed);
+ abstract protected boolean doSaveAction(int saveMode);
//
// State accessor methods
@@ -1011,42 +948,12 @@
* Return true if there are any edits to the current contact which need to
* be saved.
*/
- protected boolean hasPendingRawContactChanges() {
+ protected boolean hasPendingChanges() {
final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
return RawContactModifier.hasChanges(mState, accountTypes);
}
/**
- * Determines if changes were made in the editor that need to be saved, while taking into
- * account that name changes are not realfor read-only contacts.
- * See go/editing-read-only-contacts
- */
- protected boolean hasPendingChanges() {
- if (mReadOnlyNameEditorView == null || mReadOnlyDisplayName == null) {
- return hasPendingRawContactChanges();
- }
- // We created a new raw contact delta with a default display name.
- // We must test for pending changes while ignoring the default display name.
- final String displayName = mReadOnlyNameEditorView.getDisplayName();
- if (mReadOnlyDisplayName.equals(displayName)) {
- // The user did not modify the default display name, erase it and
- // check if the user made any other changes
- mReadOnlyNameEditorView.setDisplayName(null);
- if (hasPendingRawContactChanges()) {
- // Other changes were made to the aggregate contact, restore
- // the display name and proceed.
- mReadOnlyNameEditorView.setDisplayName(displayName);
- return true;
- } else {
- // No other changes were made to the aggregate contact. Don't add back
- // the displayName so that a "bogus" contact is not created.
- return false;
- }
- }
- return true;
- }
-
- /**
* Whether editor inputs and the options menu should be enabled.
*/
protected boolean isEnabled() {
@@ -1060,16 +967,6 @@
return mMaterialPalette;
}
- /**
- * Returns the currently displayed display name.
- */
- abstract protected String getDisplayName();
-
- /**
- * Returns the currently displayed phonetic name.
- */
- abstract protected String getPhoneticName();
-
//
// Account creation
//
@@ -1166,7 +1063,6 @@
}
}
- String readOnlyDisplayName = null;
// Check for writable raw contacts. If there are none, then we need to create one so user
// can edit. For the user profile case, there is already an editable contact.
if (!contact.isUserProfile() && !contact.isWritableContact(mContext)) {
@@ -1174,13 +1070,11 @@
// This is potentially an asynchronous call and will add deltas to list.
selectAccountAndCreateContact();
-
- readOnlyDisplayName = contact.getDisplayName();
}
// This also adds deltas to list. If readOnlyDisplayName is null at this point it is
// simply ignored later on by the editor.
- setStateForExistingContact(readOnlyDisplayName, contact.isUserProfile(), mRawContacts);
+ setStateForExistingContact(contact.isUserProfile(), mRawContacts);
}
/**
@@ -1251,10 +1145,9 @@
/**
* Prepare {@link #mState} for an existing contact.
*/
- protected void setStateForExistingContact(String readOnlyDisplayName, boolean isUserProfile,
+ protected void setStateForExistingContact(boolean isUserProfile,
ImmutableList<RawContact> rawContacts) {
setEnabled(true);
- mReadOnlyDisplayName = readOnlyDisplayName;
mState.addAll(rawContacts.iterator());
setIntentExtras(mIntentExtras);
@@ -1366,8 +1259,7 @@
setStateForNewContact(newAccount, newAccountType, oldState, oldAccountType,
isEditingUserProfile());
if (mIsEdit) {
- setStateForExistingContact(mReadOnlyDisplayName, isEditingUserProfile(),
- mRawContacts);
+ setStateForExistingContact(isEditingUserProfile(), mRawContacts);
}
}
}
@@ -1400,11 +1292,7 @@
mIntentExtras.getInt(INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR),
mIntentExtras.getInt(INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR));
}
- if (mIntentExtras.containsKey(INTENT_EXTRA_UPDATED_PHOTOS)) {
- mUpdatedPhotos = mIntentExtras.getParcelable(INTENT_EXTRA_UPDATED_PHOTOS);
- }
mPhotoId = mIntentExtras.getLong(INTENT_EXTRA_PHOTO_ID);
- mNameId = mIntentExtras.getLong(INTENT_EXTRA_NAME_ID);
}
}
@@ -1427,14 +1315,12 @@
@Override
public void onJoinCompleted(Uri uri) {
- onSaveCompleted(false, SaveMode.RELOAD, uri != null, uri, /* updatedPhotos =*/ null,
- /* backPressed =*/ false, mPhotoId, mNameId);
+ onSaveCompleted(false, SaveMode.RELOAD, uri != null, uri);
}
@Override
public void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded,
- Uri contactLookupUri, Bundle updatedPhotos, boolean backPressed, long photoId,
- long nameId) {
+ Uri contactLookupUri) {
if (hadChanges) {
if (saveSucceeded) {
if (saveMode != SaveMode.JOIN) {
@@ -1445,43 +1331,22 @@
}
}
switch (saveMode) {
- case SaveMode.CLOSE: {
+ case SaveMode.CLOSE:
+ case SaveMode.COMPACT: {
final Intent resultIntent;
if (saveSucceeded && contactLookupUri != null) {
final Uri lookupUri = maybeConvertToLegacyLookupUri(
mContext, contactLookupUri, mLookupUri);
resultIntent = ImplicitIntentsUtil.composeQuickContactIntent(lookupUri,
QuickContactActivity.MODE_FULLY_EXPANDED);
- resultIntent.putExtra(INTENT_EXTRA_SAVE_BACK_PRESSED, backPressed);
} else {
resultIntent = null;
}
+ // It is already saved, so prevent it from being saved again
mStatus = Status.CLOSING;
if (mListener != null) mListener.onSaveFinished(resultIntent);
break;
}
- case SaveMode.COMPACT: {
- if (!hadChanges && !backPressed && isInsert(getActivity().getIntent())) {
- // Reload the empty editor when the Contacts app is resumed
- mStatus = Status.EDITING;
- } else if (backPressed) {
- final Uri lookupUri = maybeConvertToLegacyLookupUri(
- mContext, contactLookupUri, mLookupUri);
- final Intent resultIntent = isInsert(getActivity().getIntent())
- ? EditorIntents.createCompactInsertContactIntent(
- mState, getDisplayName(), getPhoneticName(), updatedPhotos,
- mNewLocalProfile)
- : EditorIntents.createCompactEditContactIntent(
- lookupUri, getMaterialPalette(), updatedPhotos, photoId,
- nameId);
- resultIntent.putExtra(INTENT_EXTRA_SAVE_BACK_PRESSED, true);
- mStatus = Status.CLOSING;
- if (mListener != null) mListener.onSaveFinished(resultIntent);
- } else {
- reloadFullEditor(contactLookupUri);
- }
- break;
- }
case SaveMode.RELOAD:
case SaveMode.JOIN:
if (saveSucceeded && contactLookupUri != null) {
@@ -1489,7 +1354,13 @@
if (saveMode == SaveMode.JOIN && hasValidState()) {
showJoinAggregateActivity(contactLookupUri);
}
- reloadFullEditor(contactLookupUri);
+
+ // If this was in INSERT, we are changing into an EDIT now.
+ // If it already was an EDIT, we are changing to the new Uri now
+ mState = new RawContactDeltaList();
+ load(Intent.ACTION_EDIT, contactLookupUri, null);
+ mStatus = Status.LOADING;
+ getLoaderManager().restartLoader(LOADER_DATA, null, mDataLoaderListener);
}
break;
@@ -1504,13 +1375,6 @@
}
}
- private void reloadFullEditor(Uri contactLookupUri) {
- mState = new RawContactDeltaList();
- load(ContactEditorBaseActivity.ACTION_EDIT, contactLookupUri, null);
- mStatus = Status.LOADING;
- getLoaderManager().restartLoader(LOADER_DATA, null, mDataLoaderListener);
- }
-
/**
* Shows a list of aggregates that can be joined into the currently viewed aggregate.
*
@@ -1646,7 +1510,7 @@
}
mState.setJoinWithRawContacts(rawContactIds);
- save(SaveMode.RELOAD, /* backPressed =*/ false);
+ save(SaveMode.RELOAD);
}
@Override
@@ -1677,25 +1541,6 @@
abstract protected void joinAggregate(long contactId);
//
- // Photos
- //
-
- /**
- * Removes the full resolution photo URIs for new raw contacts (identified by negative raw
- * contact IDs) from the member Bundle of updated photos.
- */
- protected void removeNewRawContactPhotos() {
- for (String key : mUpdatedPhotos.keySet()) {
- try {
- if (Integer.parseInt(key) < 0) {
- mUpdatedPhotos.remove(key);
- }
- } catch (NumberFormatException ignored) {
- }
- }
- }
-
- //
// Utility methods
//
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 186640a..ccbf615 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -26,7 +26,6 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
-import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
@@ -64,6 +63,7 @@
private static final String KEY_RAW_CONTACT_ID_REQUESTING_PHOTO = "photorequester";
private static final String KEY_CURRENT_PHOTO_URI = "currentphotouri";
+ private static final String KEY_UPDATED_PHOTOS = "updatedPhotos";
// Used to store which raw contact editors have been expanded. Keyed on raw contact ids.
private HashMap<Long, Boolean> mExpandedEditors = new HashMap<Long, Boolean>();
@@ -84,6 +84,7 @@
*/
private PhotoHandler mCurrentPhotoHandler;
private Uri mCurrentPhotoUri;
+ private Bundle mUpdatedPhotos = new Bundle();
public ContactEditorFragment() {
}
@@ -109,16 +110,7 @@
mRawContactIdRequestingPhoto = savedState.getLong(
KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
mCurrentPhotoUri = savedState.getParcelable(KEY_CURRENT_PHOTO_URI);
- }
- }
-
- @Override
- public void onStop() {
- super.onStop();
-
- // If anything was left unsaved, save it now and return to the compact editor.
- if (!getActivity().isChangingConfigurations() && mStatus == Status.EDITING) {
- save(SaveMode.COMPACT, /* backPressed =*/ false);
+ mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS);
}
}
@@ -130,14 +122,6 @@
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == android.R.id.home) {
- return save(SaveMode.COMPACT, /* backPressed =*/ true);
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
public void onEditorExpansionChanged() {
updatedExpandedEditorsMap();
}
@@ -261,11 +245,6 @@
final StructuredNameEditorView nameEditor = rawContactEditor.getNameEditor();
nameEditor.setEditorListener(structuredNameListener);
- if (TextUtils.isEmpty(nameEditor.getDisplayName()) &&
- !TextUtils.isEmpty(mReadOnlyDisplayName)) {
- nameEditor.setDisplayName(mReadOnlyDisplayName);
- mReadOnlyNameEditorView = nameEditor;
- }
rawContactEditor.setAutoAddToDefaultGroup(mAutoAddToDefaultGroup);
@@ -306,60 +285,6 @@
}
}
- @Override
- public String getDisplayName() {
- // Return the super primary name if it is non-empty
- for (int i = 0; i < mContent.getChildCount(); i++) {
- final View view = mContent.getChildAt(i);
- if (view instanceof RawContactEditorView) {
- final RawContactEditorView rawContactEditorView = (RawContactEditorView) view;
- final StructuredNameEditorView nameEditorView =
- rawContactEditorView.getNameEditor();
- if (nameEditorView != null) {
- final String displayName = nameEditorView.getDisplayName();
- if (!TextUtils.isEmpty(displayName)) {
- return displayName;
- }
- }
- }
- }
- // Return the first non-empty name
- for (int i = 0; i < mContent.getChildCount(); i++) {
- final View view = mContent.getChildAt(i);
- if (view instanceof RawContactEditorView) {
- final RawContactEditorView rawContactEditorView = (RawContactEditorView) view;
- final StructuredNameEditorView nameEditorView =
- rawContactEditorView.getNameEditor();
- if (nameEditorView != null) {
- final String displayName = nameEditorView.getDisplayName();
- if (!TextUtils.isEmpty(displayName)) {
- return displayName;
- }
- }
- }
- }
- return null;
- }
-
- @Override
- public String getPhoneticName() {
- for (int i = 0; i < mContent.getChildCount(); i++) {
- final View view = mContent.getChildAt(i);
- if (view instanceof RawContactEditorView) {
- final RawContactEditorView rawContactEditorView = (RawContactEditorView) view;
- final PhoneticNameEditorView phoneticNameEditorView =
- (PhoneticNameEditorView) rawContactEditorView.getPhoneticNameEditor();
- if (phoneticNameEditorView != null) {
- final String phoneticName = phoneticNameEditorView.getPhoneticName();
- if (!TextUtils.isEmpty(phoneticName)) {
- return phoneticName;
- }
- }
- }
- }
- return null;
- }
-
/**
* Update the values in {@link #mExpandedEditors}.
*/
@@ -457,21 +382,12 @@
}
@Override
- protected boolean doSaveAction(int saveMode, boolean backPressed) {
- // Save contact and reload the compact editor after saving.
- // Note, the full resolution photos Bundle must be passed to the ContactSaveService
- // and then passed along in the result Intent in order for the compact editor to
- // receive it, instead of mUpdatedPhotos being accessed directly in onSaveCompleted,
- // because we clear mUpdatedPhotos after starting the save service below.
- Intent intent = ContactSaveService.createSaveContactIntent(mContext, mState,
+ protected boolean doSaveAction(int saveMode) {
+ final Intent intent = ContactSaveService.createSaveContactIntent(mContext, mState,
SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
((Activity) mContext).getClass(), ContactEditorActivity.ACTION_SAVE_COMPLETED,
- mUpdatedPhotos, backPressed);
+ mUpdatedPhotos);
mContext.startService(intent);
-
- // Don't try to save the same photos twice.
- mUpdatedPhotos = new Bundle();
-
return true;
}
@@ -480,6 +396,7 @@
outState.putSerializable(KEY_EXPANDED_EDITORS, mExpandedEditors);
outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
outState.putParcelable(KEY_CURRENT_PHOTO_URI, mCurrentPhotoUri);
+ outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos);
super.onSaveInstanceState(outState);
}
@@ -534,12 +451,6 @@
Log.w(TAG, "The contact that requested the photo is no longer present.");
}
- // For inserts where the raw contact ID is a negative number, we must clear any previously
- // saved full resolution photos under negative raw contact IDs so that the compact editor
- // will use the newly selected photo, instead of an old one.
- if (isInsert(getActivity().getIntent()) && rawContact < 0) {
- removeNewRawContactPhotos();
- }
mUpdatedPhotos.putParcelable(String.valueOf(rawContact), photoUri);
}
diff --git a/src/com/android/contacts/editor/EditorIntents.java b/src/com/android/contacts/editor/EditorIntents.java
index 68ce76f..26279df 100644
--- a/src/com/android/contacts/editor/EditorIntents.java
+++ b/src/com/android/contacts/editor/EditorIntents.java
@@ -44,12 +44,10 @@
* existing contact.
*/
public static Intent createCompactEditContactIntent(Uri contactLookupUri,
- MaterialPalette materialPalette, Bundle updatedPhotos, long photoId, long nameId) {
+ MaterialPalette materialPalette, long photoId) {
final Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
putMaterialPalette(intent, materialPalette);
- putUpdatedPhotos(intent, updatedPhotos);
putPhotoId(intent, photoId);
- putNameId(intent, nameId);
return intent;
}
@@ -58,7 +56,7 @@
*/
public static Intent createCompactInsertContactIntent() {
return createCompactInsertContactIntent(/* rawContactDeltaList =*/ null,
- /* displayName =*/ null, /* phoneticName =*/ null, /* updatedPhotos =*/ null,
+ /* displayName =*/ null, /* phoneticName =*/ null,
/* isNewLocalProfile =*/ false);
}
@@ -67,14 +65,13 @@
* the field values specified by rawContactDeltaList pre-populate in the form.
*/
public static Intent createCompactInsertContactIntent(RawContactDeltaList rawContactDeltaList,
- String displayName, String phoneticName, Bundle updatedPhotos,
+ String displayName, String phoneticName, /* Bundle updatedPhotos, */
boolean isNewLocalProfile) {
final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
intent.putExtra(ContactEditorFragment.INTENT_EXTRA_NEW_LOCAL_PROFILE, isNewLocalProfile);
if (rawContactDeltaList != null || displayName != null || phoneticName != null) {
putRawContactDeltaValues(intent, rawContactDeltaList, displayName, phoneticName);
}
- putUpdatedPhotos(intent, updatedPhotos);
return intent;
}
@@ -97,36 +94,32 @@
}
/**
- * Returns an Intent to start the fully expanded {@link ContactEditorActivity} for a
- * new contact.
+ * Returns an Intent to start the fully expanded {@link ContactEditorActivity} for an
+ * existing contact.
*/
public static Intent createEditContactIntent(Uri contactLookupUri,
- MaterialPalette materialPalette, long photoId, long nameId) {
+ MaterialPalette materialPalette, long photoId) {
final Intent intent = new Intent(ContactEditorBaseActivity.ACTION_EDIT, contactLookupUri);
addContactIntentFlags(intent);
putMaterialPalette(intent, materialPalette);
putPhotoId(intent, photoId);
- putNameId(intent, nameId);
return intent;
}
/**
- * Returns an Intent to start the fully expanded {@link ContactEditorActivity} for an
- * existing contact.
+ * Returns an Intent to start the fully expanded {@link ContactEditorActivity} for a
+ * new contact.
*/
public static Intent createInsertContactIntent(RawContactDeltaList rawContactDeltaList,
- String displayName, String phoneticName, Bundle updatedPhotos,
- boolean isNewLocalProfile) {
+ String displayName, String phoneticName, boolean isNewLocalProfile) {
final Intent intent = new Intent(ContactEditorBaseActivity.ACTION_INSERT,
Contacts.CONTENT_URI);
intent.putExtra(ContactEditorFragment.INTENT_EXTRA_NEW_LOCAL_PROFILE, isNewLocalProfile);
addContactIntentFlags(intent);
putRawContactDeltaValues(intent, rawContactDeltaList, displayName, phoneticName);
- putUpdatedPhotos(intent, updatedPhotos);
return intent;
}
-
private static void addContactIntentFlags(Intent intent) {
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_FORWARD_RESULT);
@@ -141,24 +134,12 @@
}
}
- private static void putUpdatedPhotos(Intent intent, Bundle updatedPhotos) {
- if (updatedPhotos != null && !updatedPhotos.isEmpty()) {
- intent.putExtra(ContactEditorBaseFragment.INTENT_EXTRA_UPDATED_PHOTOS, updatedPhotos);
- }
- }
-
private static void putPhotoId(Intent intent, long photoId) {
if (photoId >= 0) {
intent.putExtra(ContactEditorBaseFragment.INTENT_EXTRA_PHOTO_ID, photoId);
}
}
- private static void putNameId(Intent intent, long nameId) {
- if (nameId >= 0) {
- intent.putExtra(ContactEditorBaseFragment.INTENT_EXTRA_NAME_ID, nameId);
- }
- }
-
private static void putRawContactDeltaValues(Intent intent,
RawContactDeltaList rawContactDeltaList, String displayName, String phoneticName) {
// Pass on all the data that has been entered so far
diff --git a/src/com/android/contacts/editor/EditorUiUtils.java b/src/com/android/contacts/editor/EditorUiUtils.java
index 56be3c0..106c5e3 100644
--- a/src/com/android/contacts/editor/EditorUiUtils.java
+++ b/src/com/android/contacts/editor/EditorUiUtils.java
@@ -16,12 +16,25 @@
package com.android.contacts.editor;
-import static android.provider.ContactsContract.CommonDataKinds.Event;
import static android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import static android.provider.ContactsContract.CommonDataKinds.Photo;
import static android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
import android.text.TextUtils;
import android.util.Pair;
import com.android.contacts.R;
@@ -142,4 +155,68 @@
}
return builder.toString();
}
+
+ /**
+ * Return an icon that represents {@param mimeType}.
+ */
+ public static Drawable getMimeTypeDrawable(Context context, String mimeType) {
+ switch (mimeType) {
+ case StructuredName.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_person_black_24dp);
+ case StructuredPostal.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_place_24dp);
+ case SipAddress.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_dialer_sip_black_24dp);
+ case Phone.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_phone_24dp);
+ case Im.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_message_24dp);
+ case Event.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_event_24dp);
+ case Email.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_email_24dp);
+ case Website.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_public_black_24dp);
+ case Photo.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_camera_alt_black_24dp);
+ case GroupMembership.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_people_black_24dp);
+ case Organization.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_business_black_24dp);
+ case Note.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(R.drawable.ic_insert_comment_black_24dp);
+ case Relation.CONTENT_ITEM_TYPE:
+ return context.getResources().getDrawable(
+ R.drawable.ic_circles_extended_black_24dp);
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns a ringtone string based on the ringtone URI and version #.
+ */
+ public static String getRingtoneStringFromUri(Uri pickedUri, int currentVersion) {
+ if (isNewerThanM(currentVersion)) {
+ if (pickedUri == null) return ""; // silent ringtone
+ if (RingtoneManager.isDefault(pickedUri)) return null; // default ringtone
+ }
+ if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) return null;
+ return pickedUri.toString();
+ }
+
+ /**
+ * Returns a ringtone URI, based on the string and version #.
+ */
+ public static Uri getRingtoneUriFromString(String str, int currentVersion) {
+ if (str != null) {
+ if (isNewerThanM(currentVersion) && TextUtils.isEmpty(str)) return null;
+ return Uri.parse(str);
+ }
+ return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+ }
+
+ private static boolean isNewerThanM(int currentVersion) {
+ return currentVersion > Build.VERSION_CODES.M;
+ }
}
diff --git a/src/com/android/contacts/editor/GroupMembershipView.java b/src/com/android/contacts/editor/GroupMembershipView.java
index f1d9db9..b13da62 100644
--- a/src/com/android/contacts/editor/GroupMembershipView.java
+++ b/src/com/android/contacts/editor/GroupMembershipView.java
@@ -202,6 +202,11 @@
}
}
+ /** Whether {@link #setGroupMetaData} has been invoked yet. */
+ public boolean wasGroupMetaDataBound() {
+ return mGroupMetaData != null;
+ }
+
public void setState(RawContactDelta state) {
mState = state;
mAccountType = mState.getAccountType();
diff --git a/src/com/android/contacts/editor/KindSectionData.java b/src/com/android/contacts/editor/KindSectionData.java
new file mode 100644
index 0000000..d46001a
--- /dev/null
+++ b/src/com/android/contacts/editor/KindSectionData.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.editor;
+
+import com.android.contacts.common.model.RawContactDelta;
+import com.android.contacts.common.model.ValuesDelta;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountType.EditField;
+import com.android.contacts.common.model.dataitem.DataKind;
+
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.text.TextUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Holder for the multi account raw contact data needed to back an editor input field.
+ */
+public final class KindSectionData {
+
+ private final AccountType mAccountType;
+ private final List<ValuesDelta> mValuesDeltas;
+ private final DataKind mDataKind;
+ private final RawContactDelta mRawContactDelta;
+
+ public KindSectionData(AccountType accountType, DataKind dataKind,
+ RawContactDelta rawContactDelta) {
+ mAccountType = accountType;
+ mDataKind = dataKind;
+ mRawContactDelta = rawContactDelta;
+ mValuesDeltas = mRawContactDelta.getMimeEntries(dataKind.mimeType, /* lazyCreate= */ true);
+ }
+
+ public AccountType getAccountType() {
+ return mAccountType;
+ }
+
+ public List<ValuesDelta> getValuesDeltas() {
+ return mValuesDeltas;
+ }
+
+ /** Returns the super primary ValuesDelta for the data kind this section represents. */
+ public ValuesDelta getSuperPrimaryValuesDelta() {
+ for (ValuesDelta valuesDelta : mValuesDeltas) {
+ if (valuesDelta.isSuperPrimary()) return valuesDelta;
+ }
+ return null;
+ }
+
+ /** Returns the ValuesDelta with the given ID. */
+ public ValuesDelta getValuesDeltaById(Long id) {
+ for (ValuesDelta valuesDelta : mValuesDeltas) {
+ if (valuesDelta.getId().equals(id)) return valuesDelta;
+ }
+ return null;
+ }
+
+ /** Returns the first non empty ValuesDelta for the data kind this section represents. */
+ public ValuesDelta getFirstNonEmptyValuesDelta() {
+ for (ValuesDelta valuesDelta : mValuesDeltas) {
+ if (!isEmpty(valuesDelta)) return valuesDelta;
+ }
+ return null;
+ }
+
+ /** Whether the given ValuesDelta is empty for the data kind this section represents. */
+ public boolean isEmpty(ValuesDelta valuesDelta) {
+ if (valuesDelta.isNoop()) return true;
+
+ if (mDataKind.fieldList != null) {
+ for (EditField editField : mDataKind.fieldList) {
+ final String column = editField.column;
+ final String value = valuesDelta.getAsString(column);
+ if (TextUtils.isEmpty(value)) return true;
+ }
+ }
+ return false;
+ }
+
+ public DataKind getDataKind() {
+ return mDataKind;
+ }
+
+ public boolean isNameDataKind() {
+ return StructuredName.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType);
+ }
+
+ public boolean isNicknameDataKind() {
+ return Nickname.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType);
+ }
+
+ public RawContactDelta getRawContactDelta() {
+ return mRawContactDelta;
+ }
+
+ public String toString() {
+ return String.format("%s<accountType=%s dataSet=%s values=%s>",
+ KindSectionData.class.getSimpleName(),
+ mAccountType.accountType,
+ mAccountType.dataSet,
+ getValuesDeltas().size());
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/editor/KindSectionView.java b/src/com/android/contacts/editor/KindSectionView.java
index 26ef058..23b0c21 100644
--- a/src/com/android/contacts/editor/KindSectionView.java
+++ b/src/com/android/contacts/editor/KindSectionView.java
@@ -17,19 +17,6 @@
package com.android.contacts.editor;
import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.provider.Contacts.GroupMembership;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Event;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.Relation;
-import android.provider.ContactsContract.CommonDataKinds.SipAddress;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
import android.provider.ContactsContract.Data;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -70,15 +57,6 @@
private DataKind mKind;
private RawContactDelta mState;
private boolean mReadOnly;
- private boolean mShowOneEmptyEditor;
-
- /**
- * Whether this KindSectionView will be removed from the layout.
- * We need this because we want to animate KindSectionViews away (which takes time),
- * but calculate which KindSectionViews will be visible immediately after starting removal
- * animations.
- */
- private boolean mMarkedForRemoval;
private ViewIdGenerator mViewIdGenerator;
@@ -125,7 +103,7 @@
@Override
public void onDeleteRequested(Editor editor) {
- if (mShowOneEmptyEditor && getEditorCount() == 1) {
+ if (getEditorCount() == 1) {
// If there is only 1 editor in the section, then don't allow the user to delete it.
// Just clear the fields in the editor.
editor.clearAllFields();
@@ -141,25 +119,6 @@
}
}
- /**
- * Calling this signifies that this entire section view is intended to be removed from the
- * layout. Note, calling this does not change the deleted state of any underlying
- * {@link Editor}, i.e. {@link com.android.contacts.common.model.ValuesDelta#markDeleted()}
- * is not invoked on any editor in this section. It is purely marked for higher level UI
- * layers to manipulate the layout w/o introducing jank.
- * See b/22228718 for context.
- */
- public void markForRemoval() {
- mMarkedForRemoval = true;
- }
-
- /**
- * Whether the entire section view is intended to be removed from the layout.
- */
- public boolean isMarkedForRemoval() {
- return mMarkedForRemoval;
- }
-
@Override
public void onRequest(int request) {
// If a field has become empty or non-empty, then check if another row
@@ -169,22 +128,10 @@
}
}
- /**
- * @param showOneEmptyEditor If true, one empty input will always be displayed,
- * otherwise an empty input will only be displayed if there is no non-empty value.
- */
- public void setShowOneEmptyEditor(boolean showOneEmptyEditor) {
- mShowOneEmptyEditor = showOneEmptyEditor;
- }
-
public void setListener(Listener listener) {
mListener = listener;
}
- public void setIconVisibility(boolean visible) {
- mIcon.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- }
-
public void setState(DataKind kind, RawContactDelta state, boolean readOnly,
ViewIdGenerator vig) {
mKind = kind;
@@ -200,7 +147,7 @@
: getResources().getString(kind.titleRes);
mIcon.setContentDescription(titleString);
- mIcon.setImageDrawable(getMimeTypeDrawable(kind.mimeType));
+ mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(), kind.mimeType));
if (mIcon.getDrawable() == null) {
mIcon.setContentDescription(null);
}
@@ -314,7 +261,7 @@
} else if (emptyEditors.size() == 1) {
// We have already reached the maximum number of empty editors. Lets not add any more.
return;
- } else if (mShowOneEmptyEditor) {
+ } else {
final ValuesDelta values = RawContactModifier.insertChild(mState, mKind);
final View newField = createEditorView(values);
if (shouldAnimate) {
@@ -338,16 +285,6 @@
return emptyEditorViews;
}
- public boolean areAllEditorsEmpty() {
- for (int i = 0; i < mEditors.getChildCount(); i++) {
- final View view = mEditors.getChildAt(i);
- if (!((Editor) view).isEmpty()) {
- return false;
- }
- }
- return true;
- }
-
public int getEditorCount() {
return mEditors.getChildCount();
}
@@ -355,38 +292,4 @@
public DataKind getKind() {
return mKind;
}
-
- /**
- * Return an icon that represents {@param mimeType}.
- */
- private Drawable getMimeTypeDrawable(String mimeType) {
- switch (mimeType) {
- case StructuredPostal.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_place_24dp);
- case SipAddress.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_dialer_sip_black_24dp);
- case Phone.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_phone_24dp);
- case Im.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_message_24dp);
- case Event.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_event_24dp);
- case Email.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_email_24dp);
- case Website.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_public_black_24dp);
- case Photo.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_camera_alt_black_24dp);
- case GroupMembership.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_people_black_24dp);
- case Organization.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_business_black_24dp);
- case Note.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_insert_comment_black_24dp);
- case Relation.CONTENT_ITEM_TYPE:
- return getResources().getDrawable(R.drawable.ic_circles_extended_black_24dp);
- default:
- return null;
- }
- }
}
diff --git a/src/com/android/contacts/editor/LabeledEditorView.java b/src/com/android/contacts/editor/LabeledEditorView.java
index 931d7cf..244b682 100644
--- a/src/com/android/contacts/editor/LabeledEditorView.java
+++ b/src/com/android/contacts/editor/LabeledEditorView.java
@@ -378,6 +378,9 @@
if (mIsDeletable) mDeleteContainer.setVisibility(View.VISIBLE);
}
mWasEmpty = isEmpty;
+
+ // Update the label text color
+ mEditTypeAdapter.notifyDataSetChanged();
}
}
diff --git a/src/com/android/contacts/editor/PhoneticNameEditorView.java b/src/com/android/contacts/editor/PhoneticNameEditorView.java
index f094d55..420575c 100644
--- a/src/com/android/contacts/editor/PhoneticNameEditorView.java
+++ b/src/com/android/contacts/editor/PhoneticNameEditorView.java
@@ -151,8 +151,4 @@
return !TextUtils.isEmpty(family) || !TextUtils.isEmpty(middle)
|| !TextUtils.isEmpty(given);
}
-
- public String getPhoneticName() {
- return getEntry().getAsString(DataKind.PSEUDO_COLUMN_PHONETIC_NAME);
- }
}
diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java
index f021ec7..1832ce2 100644
--- a/src/com/android/contacts/editor/RawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -282,7 +282,6 @@
if (kind.fieldList == null) continue;
final KindSectionView section = (KindSectionView)mInflater.inflate(
R.layout.item_kind_section, mFields, false);
- section.setShowOneEmptyEditor(true);
section.setEnabled(isEnabled());
section.setState(kind, state, /* readOnly =*/ false, vig);
mFields.addView(section);
diff --git a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
index fd99ddc..76fa097 100644
--- a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
+++ b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
@@ -170,7 +170,7 @@
phoneNumber, phone.getPhoneNormalizedNumber(),
GeoUtil.getCurrentCountryIso(getContext()));
CharSequence phoneType = null;
- if (phone.phoneHasType()) {
+ if (phone.hasPhoneType()) {
phoneType = Phone.getTypeLabel(
res, phone.getPhoneType(), phone.getPhoneLabel());
}
@@ -192,7 +192,7 @@
continue;
}
CharSequence emailType = null;
- if (email.emailHasType()) {
+ if (email.hasEmailType()) {
emailType = Email.getTypeLabel(
res, email.getEmailType(), email.getEmailLabel());
}
diff --git a/src/com/android/contacts/editor/StructuredNameEditorView.java b/src/com/android/contacts/editor/StructuredNameEditorView.java
index c0463b0..4cc8003 100644
--- a/src/com/android/contacts/editor/StructuredNameEditorView.java
+++ b/src/com/android/contacts/editor/StructuredNameEditorView.java
@@ -16,6 +16,7 @@
package com.android.contacts.editor;
+import com.android.contacts.R;
import android.content.ContentValues;
import android.content.Context;
import android.os.Parcel;
@@ -23,9 +24,14 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
import com.android.contacts.common.model.RawContactDelta;
import com.android.contacts.common.model.ValuesDelta;
+import com.android.contacts.common.model.account.AccountType;
import com.android.contacts.common.model.dataitem.DataItem;
import com.android.contacts.common.model.dataitem.DataKind;
import com.android.contacts.common.util.NameConverter;
@@ -75,6 +81,18 @@
updateEmptiness();
}
+ /**
+ * Displays the icon and name for the given account under the name name input fields.
+ */
+ public void setAccountType(AccountType accountType) {
+ final LinearLayout layout = (LinearLayout) findViewById(R.id.account_type);
+ layout.setVisibility(View.VISIBLE);
+ final ImageView imageView = (ImageView) layout.findViewById(R.id.account_type_icon);
+ imageView.setImageDrawable(accountType.getDisplayIcon(getContext()));
+ final TextView textView = (TextView) layout.findViewById(R.id.account_type_name);
+ textView.setText(accountType.getDisplayLabel(getContext()));
+ }
+
@Override
public void onFieldChanged(String column, String value) {
if (!isFieldChanged(column, value)) {
@@ -207,34 +225,6 @@
super.setValue(0, name);
}
- /**
- * Returns the display name currently displayed in the editor.
- */
- public String getDisplayName() {
- final ValuesDelta valuesDelta = getValues();
- if (hasShortAndLongForms()) {
- if (areOptionalFieldsVisible()) {
- final Map<String, String> structuredNameMap = valuesToStructuredNameMap(valuesDelta);
- final String displayName = NameConverter.structuredNameToDisplayName(
- getContext(), structuredNameMap);
- if (!TextUtils.isEmpty(displayName)) {
- return displayName;
- }
- } else {
- final String displayName = valuesDelta.getDisplayName();
- if (!TextUtils.isEmpty(displayName)) {
- return displayName;
- }
- }
- }
- return valuesDelta.getDisplayName();
- }
-
- @Override
- public boolean isEmpty() {
- return TextUtils.isEmpty(getDisplayName());
- }
-
@Override
protected Parcelable onSaveInstanceState() {
SavedState state = new SavedState(super.onSaveInstanceState());
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index fe476ed..51c9d94 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -213,7 +213,7 @@
}
boolean hidePossible = false;
- int fieldCount = kind.fieldList.size();
+ int fieldCount = kind.fieldList == null ? 0 : kind.fieldList.size();
mFieldEditTexts = new EditText[fieldCount];
for (int index = 0; index < fieldCount; index++) {
final EditField field = kind.fieldList.get(index);
diff --git a/src/com/android/contacts/quickcontact/InvisibleContactUtil.java b/src/com/android/contacts/quickcontact/InvisibleContactUtil.java
index de70424..3609fbc 100644
--- a/src/com/android/contacts/quickcontact/InvisibleContactUtil.java
+++ b/src/com/android/contacts/quickcontact/InvisibleContactUtil.java
@@ -94,7 +94,7 @@
final Intent intent = ContactSaveService.createSaveContactIntent(
context,
contactDeltaList, "", 0, false, QuickContactActivity.class,
- Intent.ACTION_VIEW, null, /* backPressed =*/ false);
+ Intent.ACTION_VIEW, null);
context.startService(intent);
}
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index c89c5f4..3ac9472 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -2525,9 +2525,7 @@
mContactData.getLookupUri(),
mHasComputedThemeColor
? new MaterialPalette(mColorFilterColor, mStatusBarColor) : null,
- /* updatedPhotos =*/ null,
- mContactData.getPhotoId(),
- mContactData.getNameRawContactId());
+ mContactData.getPhotoId());
}
private void editContact() {
diff --git a/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java b/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java
index 58e6f26..4a180ef 100644
--- a/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java
+++ b/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java
@@ -21,6 +21,10 @@
import com.android.contacts.common.model.account.GoogleAccountType;
import android.content.Context;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.Settings;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Pair;
@@ -37,6 +41,8 @@
private static final String GOOGLE_ACCOUNT_NAME = "somebody@gmail.com";
private static final String GOOGLE_DISPLAY_LABEL = "Google";
+ private static final String RINGTONE = "content://media/external/audio/media/31";
+
private static final class MockAccountType extends AccountType {
private final String mDisplayLabel;
@@ -140,4 +146,41 @@
getContext().getString(R.string.account_phone)),
pair.second); // "Phone-only, unsynced contact"
}
+
+ public void testGetRingtongStrFromUri_lessThanOrEqualsToM() {
+ final int currentVersion = Build.VERSION_CODES.M;
+ assertNull(EditorUiUtils.getRingtoneStringFromUri(null, currentVersion));
+ assertNull(EditorUiUtils.getRingtoneStringFromUri(Settings.System.DEFAULT_RINGTONE_URI,
+ currentVersion));
+ assertEquals(RINGTONE, EditorUiUtils.getRingtoneStringFromUri(Uri.parse(RINGTONE),
+ currentVersion));
+ }
+
+ public void testGetRingtongStrFromUri_nOrGreater() {
+ final int currentVersion = Build.VERSION_CODES.M + 1;
+ assertEquals("", EditorUiUtils.getRingtoneStringFromUri(null, currentVersion));
+ assertNull(EditorUiUtils.getRingtoneStringFromUri(Settings.System.DEFAULT_RINGTONE_URI,
+ currentVersion));
+ assertEquals(RINGTONE, EditorUiUtils.getRingtoneStringFromUri(Uri.parse(RINGTONE),
+ currentVersion));
+ }
+
+ public void testGetRingtongUriFromStr_lessThanOrEqualsToM() {
+ final int currentVersion = Build.VERSION_CODES.M;
+ assertEquals(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE), EditorUiUtils
+ .getRingtoneUriFromString(null, currentVersion));
+ assertEquals(Uri.parse(""), EditorUiUtils.getRingtoneUriFromString("", currentVersion));
+ assertEquals(Uri.parse(RINGTONE), EditorUiUtils.getRingtoneUriFromString(RINGTONE,
+ currentVersion));
+ }
+
+ public void testGetRingtongUriFromStr_nOrGreater() {
+ final int currentVersion = Build.VERSION_CODES.M + 1;
+ assertEquals(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE), EditorUiUtils
+ .getRingtoneUriFromString(null, currentVersion));
+ assertNull(EditorUiUtils.getRingtoneUriFromString("", currentVersion));
+ assertEquals(Uri.parse(RINGTONE), EditorUiUtils.getRingtoneUriFromString(RINGTONE,
+ currentVersion));
+ }
+
}