merge from open-source master
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9ff4b4b..4cde2b7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -15,9 +15,10 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.contacts"
- android:sharedUserId="android.uid.shared"
+ package="com.android.contacts"
+ android:sharedUserId="android.uid.shared"
>
+
<uses-permission android:name="android.permission.CALL_PRIVILEGED" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
@@ -108,7 +109,7 @@
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
</activity>
-
+
<!-- An empty activity that presents the DialtactActivity's Contacts tab -->
<activity-alias android:name="DialtactsContactsEntryActivity"
android:targetActivity="DialtactsActivity"
@@ -126,7 +127,7 @@
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.dir/person" />
</intent-filter>
-
+
<intent-filter>
<action android:name="com.android.contacts.action.FILTER_CONTACTS" />
<category android:name="android.intent.category.DEFAULT" />
@@ -144,7 +145,7 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity-alias>
-
+
<!-- The actual list of contacts, usually embedded in ContactsActivity -->
<activity android:name="ContactsListActivity"
android:label="@string/contactsList"
@@ -179,7 +180,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.TAB" />
</intent-filter>
-
+
<intent-filter android:label="@string/frequentList">
<action android:name="com.android.contacts.action.LIST_FREQUENT" />
<category android:name="android.intent.category.DEFAULT" />
@@ -197,14 +198,7 @@
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/person" />
</intent-filter>
-<!--
- <intent-filter android:label="Add To Contacts">
- <action android:name="com.android.contacts.action.ADD_CONTACT" />
- <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
- <data android:scheme="mailto" />
- <data android:scheme="tel" />
- </intent-filter>
--->
+
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/res/drawable/ic_menu_2sec_pause.png b/res/drawable/ic_menu_2sec_pause.png
new file mode 100644
index 0000000..dcaa5ff
--- /dev/null
+++ b/res/drawable/ic_menu_2sec_pause.png
Binary files differ
diff --git a/res/drawable/ic_menu_wait.png b/res/drawable/ic_menu_wait.png
new file mode 100644
index 0000000..c20457a
--- /dev/null
+++ b/res/drawable/ic_menu_wait.png
Binary files differ
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/config.xml b/res/values/config.xml
index d7a3be4..7af1d54 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -19,7 +19,7 @@
<resources>
<!-- Flag indicating whether Contacts app is allowed to import contacts from SDCard -->
- <bool name="config_allow_import_from_sdcard">false</bool>
+ <bool name="config_allow_import_from_sdcard">true</bool>
<!-- If true, all vcard files are imported from SDCard without asking a user.
If not, dialog shows to let the user to select whether all vcard files are imported or not.
If the user selects "not", then the application ask the user to select a file.-->
@@ -34,6 +34,15 @@
<!-- Flag indicating whether Contacts app is allowed to export contacts to SDCard -->
<bool name="config_allow_export_to_sdcard">false</bool>
+ <!-- If true, enable vibration (haptic feedback) for dialer key presses.
+ TODO: If enough users are annoyed by this, we might eventually
+ need to make it a user preference rather than a per-platform
+ resource. -->
+ <bool name="config_enable_dialer_key_vibration">true</bool>
+
+ <!-- How long to vibrate (in msec), if dialer key vibration is enabled. -->
+ <integer name="config_dialer_key_vibrate_duration">40</integer>
+
<!-- The type of VCard for export. Without specifying this, generic VCard will be emitted.
If you want to let the app emit VCard specific to some vendor (like DoCoMo),
please specify the type.-->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c6add99..5ad60b3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -145,6 +145,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>
@@ -172,6 +181,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>
@@ -257,7 +269,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>
@@ -312,6 +324,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>
@@ -360,6 +375,9 @@
<!-- The title of a dialog that displays the IMEI of the phone -->
<string name="imei">IMEI</string>
+ <!-- The title of a dialog that displays the MEID of the CDMA phone -->
+ <string name="meid">MEID</string>
+
<!-- String used for displaying calls to the voicemail number in the call log -->
<string name="voicemail">Voicemail</string>
@@ -636,7 +654,10 @@
<string name="fail_reason_io_error">I/O Error</string>
<!-- The failed reason: "Failed to parse VCard data" -->
- <string name="fail_reason_vcard_parse_error">Failed to parse VCard</string>
+ <string name="fail_reason_vcard_parse_error">Failed to parse VCard with some unexpected reason</string>
+
+ <!-- The failed reason: "The VCard is not supported right now, but may be supported in the future" -->
+ <string name="fail_reason_vcard_not_supported_error">Failed to parse VCard though it seems in valid format, since the current implementation does not support it</string>
<!-- The failed reason: "There is no VCard file" -->
<string name="fail_reason_no_vcard_file">No VCard file found on SD Card</string>
@@ -715,4 +736,8 @@
<!-- The string used to describe Contacts as a searchable item within system search settings. -->
<string name="search_settings_description">Names of your contacts</string>
-</resources>
\ No newline at end of file
+
+ <!-- Menu items for dialpad options as part of Pause and Wait ftr -->
+ <string name="add_2sec_pause">Add 2-sec Pause</string>
+ <string name="add_wait">Add Wait</string>
+</resources>
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/ContactsGroupSyncSelector.java b/src/com/android/contacts/ContactsGroupSyncSelector.java
index e1fbdf1..a94f8db 100644
--- a/src/com/android/contacts/ContactsGroupSyncSelector.java
+++ b/src/com/android/contacts/ContactsGroupSyncSelector.java
@@ -16,14 +16,15 @@
package com.android.contacts;
-import com.google.android.googlelogin.GoogleLoginServiceConstants;
-import com.google.android.googlelogin.GoogleLoginServiceHelper;
-
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.Future2;
+import android.accounts.Future2Callback;
+import android.accounts.OperationCanceledException;
import android.app.ListActivity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
-import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Contacts;
@@ -37,6 +38,9 @@
import android.widget.ListView;
import java.util.ArrayList;
+import java.io.IOException;
+
+import com.google.android.googlelogin.GoogleLoginServiceConstants;
public final class ContactsGroupSyncSelector extends ListActivity implements View.OnClickListener {
@@ -51,8 +55,6 @@
private static final int COLUMN_INDEX_SHOULD_SYNC = 2;
private static final int COLUMN_INDEX_SYSTEM_ID = 3;
- private static final int SUBACTIVITY_GET_ACCOUNT = 1;
-
ArrayList<Boolean> mChecked;
ArrayList<Long> mGroupIds;
boolean mSyncAllGroups;
@@ -160,9 +162,26 @@
// through onActivityResult().
Bundle bundle = new Bundle();
bundle.putCharSequence("optional_message", getText(R.string.contactsSyncPlug));
- GoogleLoginServiceHelper.getCredentials(this, SUBACTIVITY_GET_ACCOUNT,
- bundle, GoogleLoginServiceConstants.PREFER_HOSTED, Gmail.GMAIL_AUTH_SERVICE,
- true);
+ AccountManager.get(this).getAuthTokenByFeatures(
+ GoogleLoginServiceConstants.ACCOUNT_TYPE, Gmail.GMAIL_AUTH_SERVICE,
+ new String[]{GoogleLoginServiceConstants.FEATURE_GOOGLE_OR_DASHER}, this,
+ bundle, null /* loginOptions */, new Future2Callback() {
+ public void run(Future2 future) {
+ try {
+ // do this to check if this request succeeded or not
+ future.getResult();
+ // There is an account setup, build the group list
+ buildItems();
+ adjustChecks();
+ } catch (OperationCanceledException e) {
+ finish();
+ } catch (IOException e) {
+ finish();
+ } catch (AuthenticatorException e) {
+ finish();
+ }
+ }
+ }, null /* handler */);
}
setContentView(R.layout.sync_settings);
@@ -173,20 +192,6 @@
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
}
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
- super.onActivityResult(requestCode, resultCode, intent);
- if (requestCode == SUBACTIVITY_GET_ACCOUNT) {
- if (resultCode == RESULT_OK) {
- // There is an account setup, build the group list
- buildItems();
- adjustChecks();
- } else {
- finish();
- }
- }
- }
-
private void buildItems() {
final ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(Groups.CONTENT_URI, PROJECTION, null, null, Groups.NAME);
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 02c70d2..cb06b0d 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -258,12 +258,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";
@@ -522,23 +522,25 @@
mQueryHandler = new QueryHandler(this);
mJustCreated = true;
- // Check to see if sync is enabled
- final ContentResolver resolver = getContentResolver();
- IContentProvider provider = resolver.acquireProvider(Contacts.CONTENT_URI);
- if (provider == null) {
- // No contacts provider, bail.
- finish();
- return;
- }
-
- try {
- ISyncAdapter sa = provider.getSyncAdapter();
- mSyncEnabled = sa != null;
- } catch (RemoteException e) {
- mSyncEnabled = false;
- } finally {
- resolver.releaseProvider(provider);
- }
+ // TODO(jham) redesign this
+ mSyncEnabled = true;
+// // Check to see if sync is enabled
+// final ContentResolver resolver = getContentResolver();
+// IContentProvider provider = resolver.acquireProvider(Contacts.CONTENT_URI);
+// if (provider == null) {
+// // No contacts provider, bail.
+// finish();
+// return;
+// }
+//
+// try {
+// ISyncAdapter sa = provider.getSyncAdapter();
+// mSyncEnabled = sa != null;
+// } catch (RemoteException e) {
+// mSyncEnabled = false;
+// } finally {
+// resolver.releaseProvider(provider);
+// }
}
private void setEmptyText() {
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..04ca79a 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,127 @@
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 groups for this member and update the list
+ final Uri groupsUri = Uri.withAppendedPath(mUri, GroupMembership.CONTENT_DIRECTORY);
+ Cursor groupCursor = null;
+ try {
+ groupCursor = mResolver.query(groupsUri, ContactsListActivity.GROUPS_PROJECTION,
+ null, null, Groups.DEFAULT_SORT_ORDER);
+ } catch (IllegalArgumentException e) {
+ // Contact is new, so we don't need to do any work.
+ }
+
+ 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 +1025,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 +1131,18 @@
values.put(entry.column, (String) null);
mResolver.update(entry.uri, values, null, null);
}
+ } else if (kind == EditEntry.KIND_GROUP) {
+ if (entry.id != 0 && mGroups != null) {
+ 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();
@@ -1045,7 +1259,16 @@
int entryCount = ContactEntryAdapter.countEntries(mSections, false);
for (int i = 0; i < entryCount; i++) {
EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
- if (entry.kind != EditEntry.KIND_CONTACT) {
+ if (entry.kind == EditEntry.KIND_GROUP && mGroups != null) {
+ long contactId = ContentUris.parseId(contactUri);
+ for (int g = 0; g < mGroups.length; g++) {
+ if (mInTheGroup[g]) {
+ long groupId = getGroupId(mResolver, mGroups[g].toString());
+ People.addToGroup(mResolver, contactId, groupId);
+ numValues++;
+ }
+ }
+ } else if (entry.kind != EditEntry.KIND_CONTACT) {
values.clear();
if (entry.toValues(values)) {
// Only create the entry if there is data
@@ -1152,7 +1375,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 +1547,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 +1815,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 +1912,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 +2091,9 @@
}
case Contacts.KIND_IM: {
- v.setText(getLabelsForKind(activity, kind)[type]);
+ if (type >= 0) {
+ v.setText(getLabelsForKind(activity, kind)[type]);
+ }
break;
}
@@ -2036,6 +2283,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/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index 07eb821..b1dd11a 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -27,17 +27,19 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
-import android.syncml.pim.VBuilder;
-import android.syncml.pim.VBuilderCollection;
-import android.syncml.pim.VParser;
-import android.syncml.pim.vcard.VCardDataBuilder;
-import android.syncml.pim.vcard.VCardEntryCounter;
-import android.syncml.pim.vcard.VCardException;
-import android.syncml.pim.vcard.VCardNestedException;
-import android.syncml.pim.vcard.VCardParser_V21;
-import android.syncml.pim.vcard.VCardParser_V30;
-import android.syncml.pim.vcard.VCardSourceDetector;
-import android.syncml.pim.vcard.VCardVersionException;
+import android.pim.vcard.EntryCommitter;
+import android.pim.vcard.VCardBuilder;
+import android.pim.vcard.VCardBuilderCollection;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardDataBuilder;
+import android.pim.vcard.VCardEntryCounter;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.VCardSourceDetector;
+import android.pim.vcard.exception.VCardException;
+import android.pim.vcard.exception.VCardNestedException;
+import android.pim.vcard.exception.VCardNotSupportedException;
+import android.pim.vcard.exception.VCardVersionException;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.RelativeSizeSpan;
@@ -182,18 +184,19 @@
}
VCardEntryCounter counter = new VCardEntryCounter();
VCardSourceDetector detector = new VCardSourceDetector();
- VBuilderCollection builderCollection = new VBuilderCollection(
+ VCardBuilderCollection builderCollection = new VCardBuilderCollection(
Arrays.asList(counter, detector));
+
boolean result;
try {
- result = readOneVCard(mCanonicalPath,
- VParser.DEFAULT_CHARSET, builderCollection, null, true);
+ result = readOneVCardFile(mCanonicalPath,
+ VCardConfig.DEFAULT_CHARSET, builderCollection, null, true);
} catch (VCardNestedException e) {
try {
// Assume that VCardSourceDetector was able to detect the source.
// Try again with the detector.
- result = readOneVCard(mCanonicalPath,
- VParser.DEFAULT_CHARSET, counter, detector, false);
+ result = readOneVCardFile(mCanonicalPath,
+ VCardConfig.DEFAULT_CHARSET, counter, detector, false);
} catch (VCardNestedException e2) {
result = false;
Log.e(LOG_TAG, "Must not reach here. " + e2);
@@ -227,8 +230,8 @@
VCardSourceDetector detector = new VCardSourceDetector();
try {
- if (!readOneVCard(canonicalPath, VParser.DEFAULT_CHARSET, detector,
- null, true)) {
+ if (!readOneVCardFile(canonicalPath, VCardConfig.DEFAULT_CHARSET,
+ detector, null, true)) {
continue;
}
} catch (VCardNestedException e) {
@@ -248,45 +251,40 @@
private void doActuallyReadOneVCard(String charset, boolean doIncrementProgress,
VCardSourceDetector detector) {
- VCardDataBuilder builder;
final Context context = ImportVCardActivity.this;
+ VCardDataBuilder builder;
+ int nameOrderType =
+ (mLastNameComesBeforeFirstName ?
+ VCardConfig.NAME_ORDER_TYPE_JAPANESE :
+ VCardConfig.NAME_ORDER_TYPE_ENGLISH);
if (charset != null) {
- builder = new VCardDataBuilder(mResolver,
- mProgressDialog,
- context.getString(R.string.reading_vcard_message),
- mHandler,
- charset,
+ builder = new VCardDataBuilder(charset,
charset,
false,
- mLastNameComesBeforeFirstName);
+ nameOrderType);
} else {
- builder = new VCardDataBuilder(mResolver,
- mProgressDialog,
- context.getString(R.string.reading_vcard_message),
- mHandler,
- null,
+ charset = VCardConfig.DEFAULT_CHARSET;
+ builder = new VCardDataBuilder(null,
null,
false,
- mLastNameComesBeforeFirstName);
- charset = VParser.DEFAULT_CHARSET;
+ nameOrderType);
}
- if (doIncrementProgress) {
- builder.setOnProgressRunnable(new Runnable() {
- public void run() {
- mProgressDialog.incrementProgressBy(1);
- }
- });
- }
+ builder.addEntryHandler(new EntryCommitter(mResolver));
+ builder.addEntryHandler(new ProgressShower(mProgressDialog,
+ context.getString(R.string.reading_vcard_message),
+ mHandler,
+ doIncrementProgress));
+
try {
- readOneVCard(mCanonicalPath, charset, builder, detector, false);
+ readOneVCardFile(mCanonicalPath, charset, builder, detector, false);
} catch (VCardNestedException e) {
- Log.e(LOG_TAG, "Must not reach here.");
+ Log.e(LOG_TAG, "Never reach here.");
}
- builder.showDebugInfo();
}
- private boolean readOneVCard(String canonicalPath, String charset, VBuilder builder,
- VCardSourceDetector detector, boolean throwNestedException)
+ private boolean readOneVCardFile(String canonicalPath, String charset,
+ VCardBuilder builder, VCardSourceDetector detector,
+ boolean throwNestedException)
throws VCardNestedException {
FileInputStream is;
try {
@@ -316,9 +314,8 @@
}
}
}
- mVCardParser.showDebugInfo();
} catch (IOException e) {
- Log.e(LOG_TAG, "IOException was emitted: " + e);
+ Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
mProgressDialog.dismiss();
@@ -326,19 +323,15 @@
getString(R.string.fail_reason_io_error) +
" (" + e.getMessage() + ")"));
return false;
- } catch (VCardNestedException e) {
- if (throwNestedException) {
- throw e;
- } else {
- Log.e(LOG_TAG, "VCardNestedException was emitted: " + e);
- mHandler.post(new ErrorDisplayer(
- getString(R.string.fail_reason_vcard_parse_error) +
- " (" + e.getMessage() + ")"));
- return false;
+ } catch (VCardNotSupportedException e) {
+ if ((e instanceof VCardNestedException) && throwNestedException) {
+ throw (VCardNestedException)e;
}
+ mHandler.post(new ErrorDisplayer(
+ getString(R.string.fail_reason_vcard_not_supported_error) +
+ " (" + e.getMessage() + ")"));
+ return false;
} catch (VCardException e) {
- Log.e(LOG_TAG, "VCardException was emitted: " + e);
-
mHandler.post(new ErrorDisplayer(
getString(R.string.fail_reason_vcard_parse_error) +
" (" + e.getMessage() + ")"));
diff --git a/src/com/android/contacts/ProgressShower.java b/src/com/android/contacts/ProgressShower.java
new file mode 100644
index 0000000..9498569
--- /dev/null
+++ b/src/com/android/contacts/ProgressShower.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2009 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;
+
+import android.app.ProgressDialog;
+import android.os.Handler;
+import android.pim.vcard.ContactStruct;
+import android.pim.vcard.EntryHandler;
+import android.pim.vcard.VCardConfig;
+import android.util.Log;
+
+public class ProgressShower implements EntryHandler {
+ public static final String LOG_TAG = "vcard.ProgressShower";
+
+ private final Handler mHandler;
+ private final ProgressDialog mProgressDialog;
+ private final String mProgressMessage;
+ private final boolean mIncrementProgress;
+
+ private long mTime;
+
+ private class ShowProgressRunnable implements Runnable {
+ private ContactStruct mContact;
+
+ public ShowProgressRunnable(ContactStruct contact) {
+ mContact = contact;
+ }
+
+ public void run() {
+ mProgressDialog.setMessage(mProgressMessage + "\n" +
+ mContact.displayString());
+ if (mIncrementProgress) {
+ mProgressDialog.incrementProgressBy(1);
+ }
+ }
+ }
+
+ public ProgressShower(ProgressDialog progressDialog,
+ String progressMessage,
+ Handler handler,
+ boolean incrementProgress) {
+ mHandler = handler;
+ mProgressDialog = progressDialog;
+ mProgressMessage = progressMessage;
+ mIncrementProgress = incrementProgress;
+ }
+
+ public void onEntryCreated(ContactStruct contactStruct) {
+ long start = System.currentTimeMillis();
+
+ if (!contactStruct.isIgnorable()) {
+ if (mProgressDialog != null && mProgressMessage != null) {
+ if (mHandler != null) {
+ mHandler.post(new ShowProgressRunnable(contactStruct));
+ } else {
+ mProgressDialog.setMessage(mProgressMessage + "\n" +
+ contactStruct.displayString());
+ }
+ }
+ }
+
+ mTime += System.currentTimeMillis() - start;
+ }
+
+ public void onFinal() {
+ if (VCardConfig.showPerformanceLog()) {
+ Log.d(LOG_TAG,
+ String.format("Time to progress a dialog: %ld ms", mTime));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/RecentCallsListActivity.java b/src/com/android/contacts/RecentCallsListActivity.java
index 6abaf23..54e9a05 100644
--- a/src/com/android/contacts/RecentCallsListActivity.java
+++ b/src/com/android/contacts/RecentCallsListActivity.java
@@ -24,10 +24,14 @@
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabaseCorruptException;
+import android.database.sqlite.SQLiteDiskIOException;
+import android.database.sqlite.SQLiteFullException;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -147,18 +151,18 @@
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)}.
@@ -277,9 +281,17 @@
values.put(Calls.CACHED_NAME, ci.name);
values.put(Calls.CACHED_NUMBER_TYPE, ci.type);
values.put(Calls.CACHED_NUMBER_LABEL, ci.label);
- RecentCallsListActivity.this.getContentResolver().update(
- Calls.CONTENT_URI,
- values, Calls.NUMBER + "='" + ciq.number + "'", null);
+
+ try {
+ RecentCallsListActivity.this.getContentResolver().update(Calls.CONTENT_URI, values,
+ Calls.NUMBER + "='" + ciq.number + "'", null);
+ } catch (SQLiteDiskIOException e) {
+ Log.w(TAG, "Exception while updating call info", e);
+ } catch (SQLiteFullException e) {
+ Log.w(TAG, "Exception while updating call info", e);
+ } catch (SQLiteDatabaseCorruptException e) {
+ Log.w(TAG, "Exception while updating call info", e);
+ }
}
private void enqueueRequest(String number, int position,
@@ -320,7 +332,7 @@
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;
@@ -378,7 +390,7 @@
views.iconView = (ImageView) view.findViewById(R.id.call_type_icon);
views.callView = view.findViewById(R.id.call_icon);
views.callView.setOnClickListener(this);
-
+
view.setTag(views);
return view;
@@ -394,7 +406,7 @@
String callerName = c.getString(CALLER_NAME_COLUMN_INDEX);
int callerNumberType = c.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
String callerNumberLabel = c.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
-
+
// Store away the number so we can call it directly if you click on the call icon
views.callView.setTag(number);
@@ -418,7 +430,7 @@
enqueueRequest(number, c.getPosition(),
callerName, callerNumberType, callerNumberLabel);
}
-
+
// Format and cache phone number for found contact
if (info.formattedNumber == null) {
info.formattedNumber = formatPhoneNumber(info.number);
@@ -436,7 +448,7 @@
name = callerName;
ntype = callerNumberType;
label = callerNumberLabel;
-
+
// Format the cached call_log phone number
formattedNumber = formatPhoneNumber(number);
}
@@ -478,7 +490,7 @@
// Set the date/time field by mixing relative and absolute times.
int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
-
+
views.dateView.setText(DateUtils.getRelativeTimeSpanString(date,
System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, flags));
@@ -509,6 +521,36 @@
private static final class QueryHandler extends AsyncQueryHandler {
private final WeakReference<RecentCallsListActivity> mActivity;
+ /**
+ * Simple handler that wraps background calls to catch
+ * {@link SQLiteException}, such as when the disk is full.
+ */
+ protected class CatchingWorkerHandler extends AsyncQueryHandler.WorkerHandler {
+ public CatchingWorkerHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ try {
+ // Perform same query while catching any exceptions
+ super.handleMessage(msg);
+ } catch (SQLiteDiskIOException e) {
+ Log.w(TAG, "Exception on background worker thread", e);
+ } catch (SQLiteFullException e) {
+ Log.w(TAG, "Exception on background worker thread", e);
+ } catch (SQLiteDatabaseCorruptException e) {
+ Log.w(TAG, "Exception on background worker thread", e);
+ }
+ }
+ }
+
+ @Override
+ protected Handler createHandler(Looper looper) {
+ // Provide our special handler that catches exceptions
+ return new CatchingWorkerHandler(looper);
+ }
+
public QueryHandler(Context context) {
super(context.getContentResolver());
mActivity = new WeakReference<RecentCallsListActivity>(
@@ -544,11 +586,11 @@
mVoiceMailNumber = ((TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE))
.getVoiceMailNumber();
mQueryHandler = new QueryHandler(this);
-
+
// Reset locale-based formatting cache
sFormattingType = FORMATTING_TYPE_INVALID;
}
-
+
@Override
protected void onResume() {
// The adapter caches looked up numbers, clear it so they will get
@@ -586,7 +628,7 @@
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
-
+
// Clear notifications only when window gains focus. This activity won't
// immediately receive focus if the keyguard screen is above it.
if (hasFocus) {
@@ -619,10 +661,10 @@
if (sFormattingType == FORMATTING_TYPE_INVALID) {
sFormattingType = PhoneNumberUtils.getFormatTypeForLocale(Locale.getDefault());
}
-
+
sEditable.clear();
sEditable.append(number);
-
+
PhoneNumberUtils.formatNumber(sEditable, sFormattingType);
return sEditable.toString();
}
diff --git a/src/com/android/contacts/ShowOrCreateActivity.java b/src/com/android/contacts/ShowOrCreateActivity.java
index 0732ffe..75af4ae 100755
--- a/src/com/android/contacts/ShowOrCreateActivity.java
+++ b/src/com/android/contacts/ShowOrCreateActivity.java
@@ -41,6 +41,7 @@
* connected with a specific E-mail address or phone number. Will search based
* on incoming {@link Intent#getData()} as described by
* {@link Intents#SHOW_OR_CREATE_CONTACT}.
+ *
* <ul>
* <li>If no matching contacts found, will prompt user with dialog to add to a
* contact, then will use {@link Intent#ACTION_INSERT_OR_EDIT} to let create new
diff --git a/src/com/android/contacts/SpecialCharSequenceMgr.java b/src/com/android/contacts/SpecialCharSequenceMgr.java
index 999e141..3f2c304 100644
--- a/src/com/android/contacts/SpecialCharSequenceMgr.java
+++ b/src/com/android/contacts/SpecialCharSequenceMgr.java
@@ -181,12 +181,19 @@
if (input.equals(MMI_IMEI_DISPLAY)) {
int networkType = ((TelephonyManager)context.getSystemService(
Context.TELEPHONY_SERVICE)).getNetworkType();
- // check for GSM
- if(networkType == TelephonyManager.NETWORK_TYPE_GPRS ||
- networkType == TelephonyManager.NETWORK_TYPE_EDGE ||
- networkType == TelephonyManager.NETWORK_TYPE_UMTS ) {
- showIMEIPanel(context, useSystemWindow);
+ if (networkType == TelephonyManager.NETWORK_TYPE_GPRS ||
+ networkType == TelephonyManager.NETWORK_TYPE_EDGE ||
+ networkType == TelephonyManager.NETWORK_TYPE_UMTS) {
+
+ showIMEIPanel(context, useSystemWindow);
+ return true;
+ } else if (networkType == TelephonyManager.NETWORK_TYPE_CDMA ||
+ networkType == TelephonyManager.NETWORK_TYPE_EVDO_0 ||
+ networkType == TelephonyManager.NETWORK_TYPE_EVDO_A ||
+ networkType == TelephonyManager.NETWORK_TYPE_1xRTT) {
+
+ showMEIDPanel(context, useSystemWindow);
return true;
}
}
@@ -207,6 +214,19 @@
alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE);
}
+ static void showMEIDPanel(Context context, boolean useSystemWindow) {
+ String meidStr = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE))
+ .getDeviceId();
+
+ AlertDialog alert = new AlertDialog.Builder(context)
+ .setTitle(R.string.meid)
+ .setMessage(meidStr)
+ .setPositiveButton(android.R.string.ok, null)
+ .setCancelable(false)
+ .show();
+ alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE);
+ }
+
/*******
* This code is used to handle SIM Contact queries
*******/
diff --git a/src/com/android/contacts/TwelveKeyDialer.java b/src/com/android/contacts/TwelveKeyDialer.java
index 33f261e..a7c1bf5 100644
--- a/src/com/android/contacts/TwelveKeyDialer.java
+++ b/src/com/android/contacts/TwelveKeyDialer.java
@@ -34,6 +34,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.Vibrator;
import android.provider.Contacts.Intents.Insert;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
@@ -44,6 +45,7 @@
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.Editable;
+import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.DialerKeyListener;
@@ -73,12 +75,12 @@
AdapterView.OnItemClickListener, TextWatcher {
private static final String TAG = "TwelveKeyDialer";
-
+
private static final int STOP_TONE = 1;
/** The length of DTMF tones in milliseconds */
private static final int TONE_LENGTH_MS = 150;
-
+
/** The DTMF tone volume relative to other sounds in the stream */
private static final int TONE_RELATIVE_VOLUME = 50;
@@ -95,10 +97,22 @@
private View mDialpad;
private ListView mDialpadChooser;
private DialpadChooserAdapter mDialpadChooserAdapter;
+ //Member variables for dialpad options
+ private MenuItem m2SecPauseMenuItem;
+ private MenuItem mWaitMenuItem;
+ private static final int MENU_ADD_CONTACTS = 1;
+ private static final int MENU_2S_PAUSE = 2;
+ private static final int MENU_WAIT = 3;
// determines if we want to playback local DTMF tones.
private boolean mDTMFToneEnabled;
-
+
+ // Vibration (haptic feedback) for dialer key presses.
+ private Vibrator mVibrator;
+ private boolean mVibrateOn;
+ private long mVibrateDuration;
+
+
/** Identifier for the "Add Call" intent extra. */
static final String ADD_CALL_MODE_KEY = "add_call_mode";
/** Indicates if we are opening this dialer to add a call from the InCallScreen. */
@@ -133,7 +147,7 @@
public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
// Do nothing
- // DTMF Tones do not need to be played here any longer -
+ // DTMF Tones do not need to be played here any longer -
// the DTMF dialer handles that functionality now.
}
@@ -207,7 +221,7 @@
synchronized (mToneGeneratorLock) {
if (mToneGenerator == null) {
try {
- mToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
+ mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF,
TONE_RELATIVE_VOLUME);
} catch (RuntimeException e) {
Log.w(TAG, "Exception caught while creating local tone generator: " + e);
@@ -215,6 +229,13 @@
}
}
}
+
+ // Initialize vibration parameters.
+ // TODO: We might eventually need to make mVibrateOn come from a
+ // user preference rather than a per-platform resource, in which
+ // case we would need to update it in onResume() rather than here.
+ mVibrateOn = r.getBoolean(R.bool.config_enable_dialer_key_vibration);
+ mVibrateDuration = (long) r.getInteger(R.integer.config_dialer_key_vibrate_duration);
}
@Override
@@ -233,13 +254,13 @@
protected void onRestoreInstanceState(Bundle icicle) {
// Do nothing, state is restored in onCreate() if needed
}
-
+
protected void maybeAddNumberFormatting() {
mDigits.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
}
-
+
/**
- * Overridden by subclasses to control the resource used by the content view.
+ * Overridden by subclasses to control the resource used by the content view.
*/
protected int getContentViewResource() {
return R.layout.twelve_key_dialer;
@@ -333,7 +354,7 @@
setIntent(newIntent);
resolveIntent();
}
-
+
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
@@ -344,7 +365,7 @@
// will always happen after onRestoreSavedInstanceState().
mDigits.addTextChangedListener(this);
}
-
+
private void setupKeypad() {
// Setup the listeners for the buttons
View view = findViewById(R.id.one);
@@ -371,17 +392,17 @@
@Override
protected void onResume() {
super.onResume();
-
+
// retrieve the DTMF tone play back setting.
mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
- // if the mToneGenerator creation fails, just continue without it. It is
+ // if the mToneGenerator creation fails, just continue without it. It is
// a local audio signal, and is not as important as the dtmf tone itself.
synchronized(mToneGeneratorLock) {
if (mToneGenerator == null) {
try {
- mToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
+ mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF,
TONE_RELATIVE_VOLUME);
} catch (RuntimeException e) {
Log.w(TAG, "Exception caught while creating local tone generator: " + e);
@@ -389,7 +410,7 @@
}
}
}
-
+
Activity parent = getParent();
// See if we were invoked with a DIAL intent. If we were, fill in the appropriate
// digits in the dialer field.
@@ -436,7 +457,7 @@
// have a window token yet in onCreate / onNewIntent
InputMethodManager inputMethodManager = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
- inputMethodManager.hideSoftInputFromWindow(mDigits.getWindowToken(), 0);
+ inputMethodManager.hideSoftInputFromWindow(mDigits.getWindowToken(), 0);
}
}
@@ -459,9 +480,12 @@
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- mAddToContactMenuItem = menu.add(0, 0, 0, R.string.recentCalls_addToContact)
+ mAddToContactMenuItem = menu.add(0, MENU_ADD_CONTACTS, 0, R.string.recentCalls_addToContact)
.setIcon(android.R.drawable.ic_menu_add);
-
+ m2SecPauseMenuItem = menu.add(0, MENU_2S_PAUSE, 0, R.string.add_2sec_pause)
+ .setIcon(R.drawable.ic_menu_2sec_pause);
+ mWaitMenuItem = menu.add(0, MENU_WAIT, 0, R.string.add_wait)
+ .setIcon(R.drawable.ic_menu_wait);
return true;
}
@@ -475,6 +499,8 @@
CharSequence digits = mDigits.getText();
if (digits == null || !TextUtils.isGraphic(digits)) {
mAddToContactMenuItem.setVisible(false);
+ m2SecPauseMenuItem.setVisible(false);
+ mWaitMenuItem.setVisible(false);
} else {
// Put the current digits string into an intent
Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
@@ -482,6 +508,41 @@
intent.setType(People.CONTENT_ITEM_TYPE);
mAddToContactMenuItem.setIntent(intent);
mAddToContactMenuItem.setVisible(true);
+
+ // Check out whether to show Pause & Wait option menu items
+ int selectionStart;
+ int selectionEnd;
+ String strDigits = digits.toString();
+
+ selectionStart = mDigits.getSelectionStart();
+ selectionEnd = mDigits.getSelectionEnd();
+
+ if (selectionStart != -1) {
+ if (selectionStart > selectionEnd) {
+ // swap it as we want start to be less then end
+ int tmp = selectionStart;
+ selectionStart = selectionEnd;
+ selectionEnd = tmp;
+ }
+
+ if (selectionStart != 0) {
+ // Pause can be visible if cursor is not in the begining
+ m2SecPauseMenuItem.setVisible(true);
+
+ // For Wait to be visible set of condition to meet
+ mWaitMenuItem.setVisible(showWait(selectionStart,
+ selectionEnd, strDigits));
+ } else {
+ // cursor in the beginning both pause and wait to be invisible
+ m2SecPauseMenuItem.setVisible(false);
+ mWaitMenuItem.setVisible(false);
+ }
+ } else {
+ // cursor is not selected so assume new digit is added to the end
+ int strLength = strDigits.length();
+ mWaitMenuItem.setVisible(showWait(strLength,
+ strLength, strDigits));
+ }
}
return true;
}
@@ -503,7 +564,7 @@
return true;
}
case KeyEvent.KEYCODE_1: {
- long timeDiff = SystemClock.uptimeMillis() - event.getDownTime();
+ long timeDiff = SystemClock.uptimeMillis() - event.getDownTime();
if (timeDiff >= ViewConfiguration.getLongPressTimeout()) {
// Long press detected, call voice mail
callVoicemail();
@@ -532,8 +593,9 @@
}
return super.onKeyUp(keyCode, event);
}
-
+
private void keyPressed(int keyCode) {
+ vibrate();
KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
mDigits.onKeyDown(keyCode, event);
}
@@ -617,6 +679,7 @@
return;
}
case R.id.digits: {
+ vibrate(); // Vibrate here too, just like we do for the regular keys
placeCall();
return;
}
@@ -719,10 +782,10 @@
Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone);
return;
}
-
+
// Remove pending STOP_TONE messages
mToneStopper.removeMessages(STOP_TONE);
-
+
// Start the new tone (will stop any playing tone)
mToneGenerator.startTone(tone);
mToneStopper.sendEmptyMessageDelayed(STOP_TONE, TONE_LENGTH_MS);
@@ -941,4 +1004,88 @@
}
return phoneInUse;
}
+
+ /**
+ * Triggers haptic feedback (if enabled) for dialer key presses.
+ */
+ private synchronized void vibrate() {
+ if (!mVibrateOn) {
+ return;
+ }
+ if (mVibrator == null) {
+ mVibrator = new Vibrator();
+ }
+ mVibrator.vibrate(mVibrateDuration);
+ }
+
+ /**
+ * Returns true whenever any one of the options from the menu is selected.
+ * Code changes to support dialpad options
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_2S_PAUSE:
+ updateDialString(",");
+ return true;
+ case MENU_WAIT:
+ updateDialString(";");
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Updates the dial string (mDigits) after inserting a Pause character (,)
+ * or Wait character (;).
+ */
+ private void updateDialString(String newDigits) {
+ int selectionStart;
+ int selectionEnd;
+
+ // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
+ selectionStart = mDigits.getSelectionStart();
+ selectionEnd = mDigits.getSelectionEnd();
+
+ Editable digits = mDigits.getText();
+ if (selectionStart != -1 ) {
+ if (selectionStart == selectionEnd) {
+ // then there is no selection. So insert the pause at this
+ // position and update the mDigits.
+ digits.replace(selectionStart, selectionStart, newDigits);
+ } else {
+ digits.delete(selectionStart, selectionEnd);
+ digits.replace(selectionStart, selectionStart, newDigits);
+ }
+ } else {
+ int len = mDigits.length();
+ digits.replace(len, len, newDigits);
+ }
+ }
+
+ /**
+ * This function return true if Wait menu item can be shown
+ * otherwise returns false. Assumes the passed string is non-empty
+ * and the 0th index check is not required.
+ */
+ private boolean showWait(int start, int end, String digits) {
+ if (start == end) {
+ // visible false in this case
+ if (start > digits.length()) return false;
+
+ // preceding char is ';', so visible should be false
+ if (digits.charAt(start-1) == ';') return false;
+
+ // next char is ';', so visible should be false
+ if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
+ } else {
+ // visible false in this case
+ if (start > digits.length() || end > digits.length()) return false;
+
+ // In this case we need to just check for ';' preceding to start
+ // or next to end
+ if (digits.charAt(start-1) == ';') return false;
+ }
+ return true;
+ }
}
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)) {