Merge branch 'open_source_no_contributions' into google_internal
diff --git a/res/layout-finger/edit_contact_entry_group.xml b/res/layout-finger/edit_contact_entry_group.xml
new file mode 100644
index 0000000..b233ca8
--- /dev/null
+++ b/res/layout-finger/edit_contact_entry_group.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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/entry_group"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="?android:attr/scrollbarSize"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:background="@android:drawable/list_selector_background"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:focusable="true"
+ android:clickable="true"
+ >
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="14dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_weight="1"
+ android:duplicateParentState="true"
+ >
+
+ <TextView android:id="@+id/label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:duplicateParentState="true"
+ />
+
+ <TextView android:id="@+id/data"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/label"
+ android:layout_alignLeft="@+id/label"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:duplicateParentState="true"
+ />
+
+ </RelativeLayout>
+
+ <ImageView
+ style="@style/MoreButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+</LinearLayout>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 30d410e..694cf42 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -92,6 +92,8 @@
<string name="syncAllGroups">"Synchronizovat všechny kontakty"</string>
<string name="groupNameMyContacts">"Moje kontakty"</string>
<string name="groupNameWithPhones">"Kontakty s telefonním číslem"</string>
+ <!-- no translation found for starredInAndroid (6495527538140213440) -->
+ <skip />
<string name="contactCreatedToast">"Kontakt byl vytvořen."</string>
<string name="contactSavedToast">"Kontakt byl uložen."</string>
<string name="listSeparatorCallNumber">"Vytočit číslo"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index cabc99d..4cbdb37 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -92,6 +92,8 @@
<string name="syncAllGroups">"Alle Kontakte synchronisieren"</string>
<string name="groupNameMyContacts">"Meine Kontakte"</string>
<string name="groupNameWithPhones">"Kontakte mit Telefonnummern"</string>
+ <!-- no translation found for starredInAndroid (6495527538140213440) -->
+ <skip />
<string name="contactCreatedToast">"Kontakt erstellt"</string>
<string name="contactSavedToast">"Kontakt gespeichert"</string>
<string name="listSeparatorCallNumber">"Rufnummer"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index af47792..6ffaae5 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -92,6 +92,8 @@
<string name="syncAllGroups">"Sincronizar todos los contactos"</string>
<string name="groupNameMyContacts">"Mis contactos"</string>
<string name="groupNameWithPhones">"Contactos con números de teléfono"</string>
+ <!-- no translation found for starredInAndroid (6495527538140213440) -->
+ <skip />
<string name="contactCreatedToast">"Se ha creado el contacto."</string>
<string name="contactSavedToast">"El contacto se ha guardado."</string>
<string name="listSeparatorCallNumber">"Marcar número"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index b84b99a..e2413b1 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -92,6 +92,8 @@
<string name="syncAllGroups">"Synchroniser tous les contacts"</string>
<string name="groupNameMyContacts">"Mes contacts"</string>
<string name="groupNameWithPhones">"Contacts avec des n° de téléphone"</string>
+ <!-- no translation found for starredInAndroid (6495527538140213440) -->
+ <skip />
<string name="contactCreatedToast">"Contact créé."</string>
<string name="contactSavedToast">"Contact enregistré."</string>
<string name="listSeparatorCallNumber">"Composer le numéro"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 8c7a4ef..f0bdd1b 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -92,6 +92,8 @@
<string name="syncAllGroups">"Sincronizza tutti i contatti"</string>
<string name="groupNameMyContacts">"I miei contatti"</string>
<string name="groupNameWithPhones">"Contatti con numeri di telefono"</string>
+ <!-- no translation found for starredInAndroid (6495527538140213440) -->
+ <skip />
<string name="contactCreatedToast">"Contatto creato."</string>
<string name="contactSavedToast">"Contatto salvato."</string>
<string name="listSeparatorCallNumber">"Componi numero"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 9275244..187caf8 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -92,6 +92,8 @@
<string name="syncAllGroups">"모든 연락처 동기화"</string>
<string name="groupNameMyContacts">"내 연락처"</string>
<string name="groupNameWithPhones">"전화번호가 포함된 연락처"</string>
+ <!-- no translation found for starredInAndroid (6495527538140213440) -->
+ <skip />
<string name="contactCreatedToast">"연락처가 생성되었습니다."</string>
<string name="contactSavedToast">"연락처가 저장되었습니다."</string>
<string name="listSeparatorCallNumber">"전화번호"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index c00eb4a..3964100 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -93,6 +93,8 @@
<string name="syncAllGroups">"Synkroniser alle kontakter"</string>
<string name="groupNameMyContacts">"Mine kontakter"</string>
<string name="groupNameWithPhones">"Kontakter med telefonnummer"</string>
+ <!-- no translation found for starredInAndroid (6495527538140213440) -->
+ <skip />
<string name="contactCreatedToast">"Kontakt opprettet."</string>
<string name="contactSavedToast">"Kontakt lagret."</string>
<string name="listSeparatorCallNumber">"Ring nummer"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 9aed711..bd63307 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -92,6 +92,8 @@
<string name="syncAllGroups">"Alle contacten synchroniseren"</string>
<string name="groupNameMyContacts">"Mijn contacten"</string>
<string name="groupNameWithPhones">"Contacten met telefoonnummers"</string>
+ <!-- no translation found for starredInAndroid (6495527538140213440) -->
+ <skip />
<string name="contactCreatedToast">"Contactpersoon is gemaakt."</string>
<string name="contactSavedToast">"Contactpersoon opgeslagen."</string>
<string name="listSeparatorCallNumber">"Nummer bellen"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 844c214..c708f96 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -92,6 +92,8 @@
<string name="syncAllGroups">"Synchronizuj wszystkie kontakty"</string>
<string name="groupNameMyContacts">"Moje kontakty"</string>
<string name="groupNameWithPhones">"Kontakty z numerami telefonu"</string>
+ <!-- no translation found for starredInAndroid (6495527538140213440) -->
+ <skip />
<string name="contactCreatedToast">"Utworzono kontakt."</string>
<string name="contactSavedToast">"Kontakt został zapisany."</string>
<string name="listSeparatorCallNumber">"Wybierz numer"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index d63b72c..ed9bbe4 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -92,6 +92,8 @@
<string name="syncAllGroups">"Синхронизировать все контакты"</string>
<string name="groupNameMyContacts">"Мои контакты"</string>
<string name="groupNameWithPhones">"Контакты с телефонными номерами"</string>
+ <!-- no translation found for starredInAndroid (6495527538140213440) -->
+ <skip />
<string name="contactCreatedToast">"Контакт создан."</string>
<string name="contactSavedToast">"Контакт сохранен."</string>
<string name="listSeparatorCallNumber">"Набрать номер"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 6a18e65..d8dcd42 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -92,6 +92,8 @@
<string name="syncAllGroups">"同步所有联系人"</string>
<string name="groupNameMyContacts">"我的联系人"</string>
<string name="groupNameWithPhones">"拥有电话号码的联系人"</string>
+ <!-- no translation found for starredInAndroid (6495527538140213440) -->
+ <skip />
<string name="contactCreatedToast">"已创建“联系人”。"</string>
<string name="contactSavedToast">"已保存联系人。"</string>
<string name="listSeparatorCallNumber">"拔号"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 17689c7..a73f664 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -92,6 +92,8 @@
<string name="syncAllGroups">"同步處理所有連絡人"</string>
<string name="groupNameMyContacts">"我的聯絡人"</string>
<string name="groupNameWithPhones">"有電話號碼的連絡人"</string>
+ <!-- no translation found for starredInAndroid (6495527538140213440) -->
+ <skip />
<string name="contactCreatedToast">"已建立連絡人"</string>
<string name="contactSavedToast">"連絡人已儲存。"</string>
<string name="listSeparatorCallNumber">"撥打號碼"</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 747cd7d..7906f4a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -128,6 +128,15 @@
<!-- The label describing the custom ringtone for a contact -->
<string name="label_ringtone">Ringtone</string>
+ <!-- The label for a list of all the groups that the contact is associated with -->
+ <string name="label_groups">Groups</string>
+
+ <!-- Provides a delimeter in a list of groups. For example, "g1, g2" has ", g2" generated using this string -->
+ <string name="group_list">, <xliff:g id="groupName">%s</xliff:g></string>
+
+ <!-- Menu item used to send user to the edit contact screen -->
+ <string name="menu_viewGroup">Edit groups</string>
+
<!-- Hint text for the contact name when editing -->
<string name="ghostData_name">First and Last</string>
@@ -155,6 +164,9 @@
<!-- Hint text for the postal address field when editing -->
<string name="ghostData_postal">Postal address</string>
+ <!-- Hint text for the group field when editing -->
+ <string name="ghostData_group">Display group</string>
+
<!-- Message displayed in a toast when you try to view the details of a contact that
for some reason doesn't exist anymore. -->
<string name="invalidContactMessage">The contact does not exist.</string>
@@ -240,7 +252,7 @@
<!-- Title for group selection dialog. The dialog contains a list of contact groups that the
user can pick from, indicating they only want to see the contacts in that group. -->
<string name="select_group_title">Groups</string>
-
+
<!-- The text displayed when the contacts list is empty while displaying a single group of contacts -->
<string name="groupEmpty">Your \"<xliff:g id="groupName">%s</xliff:g>\" group is empty.</string>
@@ -295,6 +307,9 @@
<!-- Separator in the contact details list describing that the items below are non-actionable organization information -->
<string name="listSeparatorOrganizations">Organizations</string>
+ <!-- Separator in the contact details list describing that the items below are non-actionable group information -->
+ <string name="listSeparatorGroups">Groups</string>
+
<!-- Separator in the contact details list describing that the items below are random other non-actionable information about a contact -->
<string name="listSeparatorOtherInformation">Other information</string>
diff --git a/src/com/android/contacts/ContactEntryAdapter.java b/src/com/android/contacts/ContactEntryAdapter.java
index c5b7ccf..b8d9fe8 100644
--- a/src/com/android/contacts/ContactEntryAdapter.java
+++ b/src/com/android/contacts/ContactEntryAdapter.java
@@ -122,7 +122,9 @@
/** Synthesized phone entry that will send an SMS instead of call the number */
public static final int KIND_SMS = -2;
/** A section separator */
- public static final int KIND_SEPARATOR = -3;
+ public static final int KIND_SEPARATOR = -3;
+ /** Signifies a group row that is stored in the group membership table */
+ public static final int KIND_GROUP = -4;
public String label;
public String data;
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 7dbc76c..e063ef8 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -249,12 +249,12 @@
private static final int QUERY_TOKEN = 42;
- private static final String[] GROUPS_PROJECTION = new String[] {
+ static final String[] GROUPS_PROJECTION = new String[] {
Groups.SYSTEM_ID, // 0
Groups.NAME, // 1
};
- private static final int GROUPS_COLUMN_INDEX_SYSTEM_ID = 0;
- private static final int GROUPS_COLUMN_INDEX_NAME = 1;
+ static final int GROUPS_COLUMN_INDEX_SYSTEM_ID = 0;
+ static final int GROUPS_COLUMN_INDEX_NAME = 1;
static final String GROUP_WITH_PHONES = "android_smartgroup_phone";
diff --git a/src/com/android/contacts/DialtactsActivity.java b/src/com/android/contacts/DialtactsActivity.java
index 73d702b..8f933b8 100644
--- a/src/com/android/contacts/DialtactsActivity.java
+++ b/src/com/android/contacts/DialtactsActivity.java
@@ -213,6 +213,8 @@
}
} else if (FAVORITES_ENTRY_COMPONENT.equals(componentName)) {
mTabHost.setCurrentTab(TAB_INDEX_FAVORITES);
+ } else if (Contacts.Intents.UI.FILTER_CONTACTS_ACTION.equals(intent.getAction())) {
+ mTabHost.setCurrentTab(TAB_INDEX_CONTACTS);
} else {
SharedPreferences prefs = getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE);
boolean favoritesAsContacts = prefs.getBoolean(PREF_FAVORITES_AS_CONTACTS,
diff --git a/src/com/android/contacts/EditContactActivity.java b/src/com/android/contacts/EditContactActivity.java
index b89573b..4d4a423 100644
--- a/src/com/android/contacts/EditContactActivity.java
+++ b/src/com/android/contacts/EditContactActivity.java
@@ -44,6 +44,8 @@
import static com.android.contacts.ContactEntryAdapter.PHONES_PROJECTION;
import static com.android.contacts.ContactEntryAdapter.PHONES_TYPE_COLUMN;
+import com.android.contacts.ViewContactActivity.ViewEntry;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -69,6 +71,7 @@
import android.provider.Contacts;
import android.provider.Contacts.ContactMethods;
import android.provider.Contacts.Intents.Insert;
+import android.provider.Contacts.GroupMembership;
import android.provider.Contacts.Groups;
import android.provider.Contacts.Organizations;
import android.provider.Contacts.People;
@@ -171,6 +174,20 @@
/** Flag marking this contact as changed, meaning we should write changes back. */
private boolean mContactChanged = false;
+
+ /** List of all the group names */
+ private CharSequence[] mGroups;
+
+ /** Is this contact part of the group */
+ private boolean[] mInTheGroup;
+
+ private static final String[] GROUP_ID_PROJECTION = new String[] {
+ Groups._ID,
+ };
+
+ private static final String[] GROUPMEMBERSHIP_ID_PROJECTION = new String[] {
+ GroupMembership._ID,
+ };
// These are accessed by inner classes. They're package scoped to make access more efficient.
/* package */ ContentResolver mResolver;
@@ -188,7 +205,7 @@
/* package */ static final int MSG_ADD_PHONE = 3;
/* package */ static final int MSG_ADD_EMAIL = 4;
/* package */ static final int MSG_ADD_POSTAL = 5;
-
+
private static final int[] TYPE_PRECEDENCE_PHONES = new int[] {
Phones.TYPE_MOBILE, Phones.TYPE_HOME, Phones.TYPE_WORK, Phones.TYPE_OTHER
};
@@ -222,6 +239,12 @@
break;
}
+ case R.id.entry_group: {
+ EditEntry entry = findEntryForView(v);
+ doPickGroup(entry);
+ break;
+ }
+
case R.id.entry_ringtone: {
EditEntry entry = findEntryForView(v);
doPickRingtone(entry);
@@ -716,6 +739,121 @@
setPhotoPresent(false);
}
+ private void populateGroups() {
+ // Create a list of all the groups
+ Cursor cursor = mResolver.query(Groups.CONTENT_URI, ContactsListActivity.GROUPS_PROJECTION,
+ null, null, Groups.DEFAULT_SORT_ORDER);
+ try {
+ ArrayList<Long> ids = new ArrayList<Long>();
+ ArrayList<String> items = new ArrayList<String>();
+
+ while (cursor.moveToNext()) {
+ String systemId = cursor.getString(ContactsListActivity.GROUPS_COLUMN_INDEX_SYSTEM_ID);
+ String name = cursor.getString(ContactsListActivity.GROUPS_COLUMN_INDEX_NAME);
+
+ if (systemId != null || Groups.GROUP_MY_CONTACTS.equals(systemId)) {
+ continue;
+ }
+
+ if (!TextUtils.isEmpty(name)) {
+ ids.add(new Long(cursor.getLong(ContactsListActivity.GROUPS_COLUMN_INDEX_SYSTEM_ID)));
+ items.add(name);
+ }
+ }
+
+ mGroups = items.toArray(new CharSequence[items.size()]);
+ mInTheGroup = new boolean[items.size()];
+ } finally {
+ cursor.close();
+ }
+
+ if (mGroups != null) {
+
+ // Go through the mGroups for this member and update the list
+ final Uri groupsUri = Uri.withAppendedPath(mUri, GroupMembership.CONTENT_DIRECTORY);
+ Cursor groupCursor = mResolver.query(groupsUri, ContactsListActivity.GROUPS_PROJECTION,
+ null, null, Groups.DEFAULT_SORT_ORDER);
+ if (groupCursor != null) {
+ try {
+ while (groupCursor.moveToNext()) {
+ String systemId = groupCursor.getString(ContactsListActivity.GROUPS_COLUMN_INDEX_SYSTEM_ID);
+ String name = groupCursor.getString(ContactsListActivity.GROUPS_COLUMN_INDEX_NAME);
+
+ if (systemId != null || Groups.GROUP_MY_CONTACTS.equals(systemId)) {
+ continue;
+ }
+
+ if (!TextUtils.isEmpty(name)) {
+ for (int i = 0; i < mGroups.length; i++) {
+ if (name.equals(mGroups[i])) {
+ mInTheGroup[i] = true;
+ break;
+ }
+ }
+ }
+ }
+ } finally {
+ groupCursor.close();
+ }
+ }
+ }
+ }
+
+ private String generateGroupList() {
+ StringBuilder groupList = new StringBuilder();
+ for (int i = 0; mGroups != null && i < mGroups.length; i++) {
+ if (mInTheGroup[i]) {
+ if (groupList.length() == 0) {
+ groupList.append(mGroups[i]);
+ } else {
+ groupList.append(getString(R.string.group_list, mGroups[i]));
+ }
+ }
+ }
+ return groupList.length() > 0 ? groupList.toString() : null;
+ }
+
+ private void doPickGroup(EditEntry entry) {
+ if (mGroups != null) {
+ GroupDialogListener listener = new GroupDialogListener(this, entry);
+
+ new AlertDialog.Builder(EditContactActivity.this)
+ .setTitle(R.string.label_groups)
+ .setMultiChoiceItems(mGroups, mInTheGroup, listener)
+ .setPositiveButton(android.R.string.ok, listener)
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+ }
+
+ /** Handles the clicks in the groups dialog */
+ private static final class GroupDialogListener implements DialogInterface.OnClickListener,
+ DialogInterface.OnMultiChoiceClickListener {
+
+ private EditContactActivity mEditContactActivity;
+ private EditEntry mEntry;
+ private boolean[] mInTheGroup;
+
+ public GroupDialogListener(EditContactActivity editContactActivity, EditEntry entry) {
+ mEditContactActivity = editContactActivity;
+ mEntry = entry;
+ mInTheGroup = editContactActivity.mInTheGroup.clone();
+ }
+
+ /** Called when the dialog's ok button is clicked */
+ public void onClick(DialogInterface dialog, int which) {
+ mEditContactActivity.mInTheGroup = mInTheGroup;
+ mEntry.data = mEditContactActivity.generateGroupList();
+ mEditContactActivity.updateDataView(mEntry, mEntry.data);
+ }
+
+ /** Called when each group is clicked */
+ public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+ mInTheGroup[which] = isChecked;
+ }
+ }
+
+
private void doPickRingtone(EditEntry entry) {
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
// Allow user to pick 'Default'
@@ -881,6 +1019,64 @@
}
finish();
}
+
+ /**
+ * Gets the group id based on group name.
+ *
+ * @param resolver the resolver to use
+ * @param groupName the name of the group to add the contact to
+ * @return the id of the group
+ * @throws IllegalStateException if the group can't be found
+ */
+ private long getGroupId(ContentResolver resolver, String groupName) {
+ long groupId = 0;
+ Cursor groupsCursor = resolver.query(Groups.CONTENT_URI, GROUP_ID_PROJECTION,
+ Groups.NAME + "=?", new String[] { groupName }, null);
+ if (groupsCursor != null) {
+ try {
+ if (groupsCursor.moveToFirst()) {
+ groupId = groupsCursor.getLong(0);
+ }
+ } finally {
+ groupsCursor.close();
+ }
+ }
+
+ if (groupId == 0) {
+ throw new IllegalStateException("Failed to find the " + groupName + "group");
+ }
+
+ return groupId;
+ }
+
+ /**
+ * Deletes group membership based on person and group ids.
+ *
+ * @param personId the person id
+ * @param groupId the group id
+ * @return the id of the group membership
+ */
+ private void deleteGroupMembership(long personId, long groupId) {
+ long groupMembershipId = 0;
+ Cursor groupsCursor = mResolver.query(GroupMembership.CONTENT_URI, GROUPMEMBERSHIP_ID_PROJECTION,
+ GroupMembership.PERSON_ID + "=? AND " + GroupMembership.GROUP_ID + "=?",
+ new String[] {String.valueOf(personId), String.valueOf(groupId)}, null);
+ if (groupsCursor != null) {
+ try {
+ if (groupsCursor.moveToFirst()) {
+ groupMembershipId = groupsCursor.getLong(0);
+ }
+ } finally {
+ groupsCursor.close();
+ }
+ }
+
+ if (groupMembershipId != 0) {
+ final Uri groupsUri = ContentUris.withAppendedId(
+ GroupMembership.CONTENT_URI,groupMembershipId);
+ mResolver.delete(groupsUri, null, null);
+ }
+ }
/**
* Save the various fields to the existing contact.
@@ -929,6 +1125,18 @@
values.put(entry.column, (String) null);
mResolver.update(entry.uri, values, null, null);
}
+ } else if (kind == EditEntry.KIND_GROUP) {
+ if (entry.id != 0) {
+ for (int g = 0; g < mGroups.length; g++) {
+ long groupId = getGroupId(mResolver, mGroups[g].toString());
+ if (mInTheGroup[g]) {
+ Contacts.People.addToGroup(mResolver, entry.id, groupId);
+ numValues++;
+ } else {
+ deleteGroupMembership(entry.id, groupId);
+ }
+ }
+ }
} else {
if (!empty) {
values.clear();
@@ -1152,7 +1360,15 @@
mUri);
mNoteEntries.add(entry);
}
-
+
+ // Groups
+ populateGroups();
+ if (mGroups != null) {
+ entry = EditEntry.newGroupEntry(this, generateGroupList(), mUri,
+ personCursor.getLong(0));
+ mOtherEntries.add(entry);
+ }
+
// Ringtone
entry = EditEntry.newRingtoneEntry(this,
personCursor.getString(CONTACT_CUSTOM_RINGTONE_COLUMN), mUri);
@@ -1316,6 +1532,13 @@
mEmailEntries.add(entry);
}
+ // Group
+ populateGroups();
+ if (mGroups != null) {
+ entry = EditEntry.newGroupEntry(this, null, mUri, 0);
+ mOtherEntries.add(entry);
+ }
+
// Ringtone
entry = EditEntry.newRingtoneEntry(this, null, mUri);
mOtherEntries.add(entry);
@@ -1577,6 +1800,9 @@
// with some additional logic.
if (entry.kind == Contacts.KIND_ORGANIZATION) {
view = mInflater.inflate(R.layout.edit_contact_entry_org, parent, false);
+ } else if (isOtherEntry(entry, GroupMembership.GROUP_ID)) {
+ view = mInflater.inflate(R.layout.edit_contact_entry_group, parent, false);
+ view.setOnFocusChangeListener(this);
} else if (isOtherEntry(entry, People.CUSTOM_RINGTONE)) {
view = mInflater.inflate(R.layout.edit_contact_entry_ringtone, parent, false);
view.setOnFocusChangeListener(this);
@@ -1671,6 +1897,10 @@
private void fillViewData(final EditEntry entry) {
if (isOtherEntry(entry, People.CUSTOM_RINGTONE)) {
updateRingtoneView(entry);
+ } else if (isOtherEntry(entry, GroupMembership.GROUP_ID)) {
+ if (entry.data != null) {
+ updateDataView(entry, entry.data);
+ }
} else if (isOtherEntry(entry, People.SEND_TO_VOICEMAIL)) {
CheckBox checkBox = (CheckBox) entry.view.findViewById(R.id.checkbox);
boolean sendToVoicemail = false;
@@ -1846,7 +2076,9 @@
}
case Contacts.KIND_IM: {
- v.setText(getLabelsForKind(activity, kind)[type]);
+ if (type >= 0) {
+ v.setText(getLabelsForKind(activity, kind)[type]);
+ }
break;
}
@@ -2036,6 +2268,24 @@
}
/**
+ * Create a new group entry with the given data.
+ */
+ public static final EditEntry newGroupEntry(EditContactActivity activity,
+ String data, Uri uri, long personId) {
+ EditEntry entry = new EditEntry(activity);
+ entry.label = activity.getString(R.string.label_groups);
+ entry.data = data;
+ entry.uri = uri;
+ entry.id = personId;
+ entry.column = GroupMembership.GROUP_ID;
+ entry.kind = KIND_GROUP;
+ entry.isStaticLabel = true;
+ entry.syncDataWithView = false;
+ entry.lines = -1;
+ return entry;
+ }
+
+ /**
* Create a new ringtone entry with the given data.
*/
public static final EditEntry newRingtoneEntry(EditContactActivity activity,
diff --git a/src/com/android/contacts/RecentCallsListActivity.java b/src/com/android/contacts/RecentCallsListActivity.java
index 8949f6e..6abaf23 100644
--- a/src/com/android/contacts/RecentCallsListActivity.java
+++ b/src/com/android/contacts/RecentCallsListActivity.java
@@ -39,6 +39,7 @@
import android.provider.Contacts.Intents.Insert;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
+import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
@@ -62,6 +63,7 @@
import java.util.HashMap;
import java.util.LinkedList;
+import java.util.Locale;
import java.lang.ref.WeakReference;
/**
@@ -124,6 +126,7 @@
public int type;
public String label;
public String number;
+ public String formattedNumber;
public static ContactInfo EMPTY = new ContactInfo();
}
@@ -144,6 +147,23 @@
int numberType;
String numberLabel;
}
+
+ /**
+ * Shared builder used by {@link #formatPhoneNumber(String)} to minimize
+ * allocations when formatting phone numbers.
+ */
+ private static final SpannableStringBuilder sEditable = new SpannableStringBuilder();
+
+ /**
+ * Invalid formatting type constant for {@link #sFormattingType}.
+ */
+ private static final int FORMATTING_TYPE_INVALID = -1;
+
+ /**
+ * Cached formatting type for current {@link Locale}, as provided by
+ * {@link PhoneNumberUtils#getFormatTypeForLocale(Locale)}.
+ */
+ private static int sFormattingType = FORMATTING_TYPE_INVALID;
/** Adapter class to fill in data for the Call Log */
final class RecentCallsAdapter extends ResourceCursorAdapter
@@ -300,6 +320,10 @@
info.type = phonesCursor.getInt(PHONE_TYPE_COLUMN_INDEX);
info.label = phonesCursor.getString(LABEL_COLUMN_INDEX);
info.number = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
+
+ // New incoming phone number invalidates our formatted
+ // cache. Any cache fills happen only on the GUI thread.
+ info.formattedNumber = null;
mContactInfo.put(ciq.number, info);
// Inform list to update this item, if in view
@@ -366,6 +390,7 @@
final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
String number = c.getString(NUMBER_COLUMN_INDEX);
+ String formattedNumber = null;
String callerName = c.getString(CALLER_NAME_COLUMN_INDEX);
int callerNumberType = c.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
String callerNumberLabel = c.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
@@ -393,6 +418,12 @@
enqueueRequest(number, c.getPosition(),
callerName, callerNumberType, callerNumberLabel);
}
+
+ // Format and cache phone number for found contact
+ if (info.formattedNumber == null) {
+ info.formattedNumber = formatPhoneNumber(info.number);
+ }
+ formattedNumber = info.formattedNumber;
}
String name = info.name;
@@ -405,6 +436,9 @@
name = callerName;
ntype = callerNumberType;
label = callerNumberLabel;
+
+ // Format the cached call_log phone number
+ formattedNumber = formatPhoneNumber(number);
}
// Set the text lines
if (!TextUtils.isEmpty(name)) {
@@ -413,7 +447,7 @@
CharSequence numberLabel = Phones.getDisplayLabel(context, ntype, label,
mLabelArray);
views.numberView.setVisibility(View.VISIBLE);
- views.numberView.setText(number);
+ views.numberView.setText(formattedNumber);
if (!TextUtils.isEmpty(numberLabel)) {
views.labelView.setText(numberLabel);
views.labelView.setVisibility(View.VISIBLE);
@@ -430,8 +464,8 @@
} else if (number.equals(mVoiceMailNumber)) {
number = getString(R.string.voicemail);
} else {
- // Just a raw number, format it to look pretty
- number = PhoneNumberUtils.formatNumber(number);
+ // Just a raw number, and no cache, so format it nicely
+ number = formatPhoneNumber(number);
}
views.line1View.setText(number);
@@ -510,6 +544,9 @@
mVoiceMailNumber = ((TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE))
.getVoiceMailNumber();
mQueryHandler = new QueryHandler(this);
+
+ // Reset locale-based formatting cache
+ sFormattingType = FORMATTING_TYPE_INVALID;
}
@Override
@@ -568,6 +605,28 @@
}
}
+ /**
+ * Format the given phone number using
+ * {@link PhoneNumberUtils#formatNumber(android.text.Editable, int)}. This
+ * helper method uses {@link #sEditable} and {@link #sFormattingType} to
+ * prevent allocations between multiple calls.
+ * <p>
+ * Because of the shared {@link #sEditable} builder, <b>this method is not
+ * thread safe</b>, and should only be called from the GUI thread.
+ */
+ private String formatPhoneNumber(String number) {
+ // Cache formatting type if not already present
+ if (sFormattingType == FORMATTING_TYPE_INVALID) {
+ sFormattingType = PhoneNumberUtils.getFormatTypeForLocale(Locale.getDefault());
+ }
+
+ sEditable.clear();
+ sEditable.append(number);
+
+ PhoneNumberUtils.formatNumber(sEditable, sFormattingType);
+ return sEditable.toString();
+ }
+
private void resetNewCallsFlag() {
// Mark all "new" missed calls as not new anymore
StringBuilder where = new StringBuilder("type=");
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index 28a82b4..e5afdc2 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -73,6 +73,8 @@
import android.provider.Contacts;
import android.provider.Im;
import android.provider.Contacts.ContactMethods;
+import android.provider.Contacts.Groups;
+import android.provider.Contacts.GroupMembership;
import android.provider.Contacts.Organizations;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
@@ -136,6 +138,7 @@
/* package */ ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>();
/* package */ ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>();
/* package */ ArrayList<ViewEntry> mOrganizationEntries = new ArrayList<ViewEntry>();
+ /* package */ ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
/* package */ ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
/* package */ ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
@@ -229,6 +232,7 @@
mSections.add(mImEntries);
mSections.add(mPostalEntries);
mSections.add(mOrganizationEntries);
+ mSections.add(mGroupEntries);
mSections.add(mOtherEntries);
//TODO Read this value from a preference
@@ -395,6 +399,11 @@
menu.add(0, 0, 0, R.string.menu_viewAddress).setIntent(entry.intent);
break;
}
+
+ case ContactEntryAdapter.Entry.KIND_GROUP: {
+ menu.add(0, 0, 0, R.string.menu_viewGroup).setIntent(entry.intent);
+ break;
+ }
}
}
@@ -579,6 +588,11 @@
separator = new ViewEntry();
separator.kind = ViewEntry.KIND_SEPARATOR;
+ separator.data = getString(R.string.listSeparatorGroups);
+ mGroupEntries.add(separator);
+
+ separator = new ViewEntry();
+ separator.kind = ViewEntry.KIND_SEPARATOR;
separator.data = getString(R.string.listSeparatorOtherInformation);
mOtherEntries.add(separator);
}
@@ -716,7 +730,7 @@
case Contacts.KIND_IM: {
Object protocolObj = ContactMethods.decodeImProtocol(
methodsCursor.getString(METHODS_AUX_DATA_COLUMN));
- String host;
+ String host = null;
if (protocolObj instanceof Number) {
int protocol = ((Number) protocolObj).intValue();
entry.label = buildActionString(R.string.actionChat,
@@ -726,7 +740,7 @@
|| protocol == ContactMethods.PROTOCOL_MSN) {
entry.maxLabelLines = 2;
}
- } else {
+ } else if (protocolObj != null) {
String providerName = (String) protocolObj;
entry.label = buildActionString(R.string.actionChat,
providerName, false);
@@ -848,7 +862,6 @@
organizationsCursor.close();
}
-
// Build the other entries
String note = personCursor.getString(CONTACT_NOTES_COLUMN);
if (!TextUtils.isEmpty(note)) {
@@ -863,7 +876,49 @@
entry.actionIcon = R.drawable.sym_note;
mOtherEntries.add(entry);
}
-
+
+ // Build the group entries
+ final Uri groupsUri = Uri.withAppendedPath(mUri, GroupMembership.CONTENT_DIRECTORY);
+ Cursor groupCursor = mResolver.query(groupsUri, ContactsListActivity.GROUPS_PROJECTION,
+ null, null, Groups.DEFAULT_SORT_ORDER);
+ if (groupCursor != null) {
+ try {
+ StringBuilder sb = new StringBuilder();
+
+ while (groupCursor.moveToNext()) {
+ String systemId = groupCursor.getString(
+ ContactsListActivity.GROUPS_COLUMN_INDEX_SYSTEM_ID);
+
+ if (systemId != null || Groups.GROUP_MY_CONTACTS.equals(systemId)) {
+ continue;
+ }
+
+ String name = groupCursor.getString(ContactsListActivity.GROUPS_COLUMN_INDEX_NAME);
+ if (!TextUtils.isEmpty(name)) {
+ if (sb.length() == 0) {
+ sb.append(name);
+ } else {
+ sb.append(getString(R.string.group_list, name));
+ }
+ }
+ }
+
+ if (sb.length() > 0) {
+ ViewEntry entry = new ViewEntry();
+ entry.kind = ContactEntryAdapter.Entry.KIND_GROUP;
+ entry.label = getString(R.string.label_groups);
+ entry.data = sb.toString();
+ entry.intent = new Intent(Intent.ACTION_EDIT, mUri);
+
+ // TODO: Add an icon for the groups item.
+
+ mGroupEntries.add(entry);
+ }
+ } finally {
+ groupCursor.close();
+ }
+ }
+
// Build the ringtone entry
String ringtoneStr = personCursor.getString(CONTACT_CUSTOM_RINGTONE_COLUMN);
if (!TextUtils.isEmpty(ringtoneStr)) {