Code drop from //branches/cupcake/...@124589
diff --git a/src/com/android/contacts/AlphabetIndexer.java b/src/com/android/contacts/AlphabetIndexer.java
deleted file mode 100644
index 4e5fb0f..0000000
--- a/src/com/android/contacts/AlphabetIndexer.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.contacts;
-
-import android.database.Cursor;
-import android.database.DataSetObserver;
-import android.util.SparseIntArray;
-
-/**
- * This class essentially helps in building an index of section boundaries of a
- * sorted column of a cursor. For instance, if a cursor contains a data set
- * sorted by first name of a person or the title of a song, this class will
- * perform a binary search to identify the first row that begins with a
- * particular letter. The search is case-insensitive. The class caches the index
- * such that subsequent queries for the same letter will return right away.
- */
-public class AlphabetIndexer extends DataSetObserver {
-
- protected Cursor mDataCursor;
- protected int mColumnIndex;
- protected Object[] mAlphabetArray;
- private SparseIntArray mAlphaMap;
- private java.text.Collator mCollator;
-
- /**
- * Constructs the indexer.
- * @param cursor the cursor containing the data set
- * @param columnIndex the column number in the cursor that is sorted
- * alphabetically
- * @param sections the array of objects that represent the sections. The
- * toString() method of each item is called and the first letter of the
- * String is used as the letter to search for.
- */
- public AlphabetIndexer(Cursor cursor, int columnIndex, Object[] sections) {
- mDataCursor = cursor;
- mColumnIndex = columnIndex;
- mAlphabetArray = sections;
- mAlphaMap = new SparseIntArray(26 /* Optimize for English */);
- if (cursor != null) {
- cursor.registerDataSetObserver(this);
- }
- // Get a Collator for the current locale for string comparisons.
- mCollator = java.text.Collator.getInstance();
- mCollator.setStrength(java.text.Collator.PRIMARY);
- }
-
- /**
- * Sets a new cursor as the data set and resets the cache of indices.
- * @param cursor the new cursor to use as the data set
- */
- public void setCursor(Cursor cursor) {
- if (mDataCursor != null) {
- mDataCursor.unregisterDataSetObserver(this);
- }
- mDataCursor = cursor;
- if (cursor != null) {
- mDataCursor.registerDataSetObserver(this);
- }
- mAlphaMap.clear();
- }
-
- /**
- * Performs a binary search or cache lookup to find the first row that
- * matches a given section's starting letter.
- * @param sectionIndex the section to search for
- * @return the row index of the first occurrence, or the nearest next letter.
- * For instance, if searching for "T" and no "T" is found, then the first
- * row starting with "U" or any higher letter is returned. If there is no
- * data following "T" at all, then the list size is returned.
- */
- public int indexOf(int sectionIndex) {
- final SparseIntArray alphaMap = mAlphaMap;
- final Cursor cursor = mDataCursor;
-
- if (cursor == null || mAlphabetArray == null) {
- return 0;
- }
-
- // Check bounds
- if (sectionIndex <= 0) {
- return 0;
- }
- if (sectionIndex >= mAlphabetArray.length) {
- sectionIndex = mAlphabetArray.length - 1;
- }
-
- int savedCursorPos = cursor.getPosition();
-
- int count = cursor.getCount();
- int start = 0;
- int end = count;
- int pos;
-
- String letter = mAlphabetArray[sectionIndex].toString();
- letter = letter.toUpperCase();
- int key = letter.charAt(0);
- // Check map
- if (Integer.MIN_VALUE != (pos = alphaMap.get(key, Integer.MIN_VALUE))) {
- // Is it approximate? Using negative value to indicate that it's
- // an approximation and positive value when it is the accurate
- // position.
- if (pos < 0) {
- pos = -pos;
- end = pos;
- } else {
- // Not approximate, this is the confirmed start of section, return it
- return pos;
- }
- }
-
- // Do we have the position of the previous section?
- if (sectionIndex > 0) {
- int prevLetter =
- mAlphabetArray[sectionIndex - 1].toString().charAt(0);
- int prevLetterPos = alphaMap.get(prevLetter, Integer.MIN_VALUE);
- if (prevLetterPos != Integer.MIN_VALUE) {
- start = Math.abs(prevLetterPos);
- }
- }
-
- // Now that we have a possibly optimized start and end, let's binary search
-
- pos = (end + start) / 2;
-
- while (pos < end) {
- // Get letter at pos
- cursor.moveToPosition(pos);
- String curName = cursor.getString(mColumnIndex);
- if (curName == null) {
- if (pos == 0) {
- break;
- } else {
- pos--;
- continue;
- }
- }
- int curLetter = Character.toUpperCase(curName.charAt(0));
-
- if (curLetter != key) {
- // Enter approximation in hash if a better solution doesn't exist
- int curPos = alphaMap.get(curLetter, Integer.MIN_VALUE);
- if (curPos == Integer.MIN_VALUE || Math.abs(curPos) > pos) {
- // Negative pos indicates that it is an approximation
- alphaMap.put(curLetter, -pos);
- }
- if (mCollator.compare(curName, letter) < 0) {
- start = pos + 1;
- if (start >= count) {
- pos = count;
- break;
- }
- } else {
- end = pos;
- }
- } else {
- // They're the same, but that doesn't mean it's the start
- if (start == pos) {
- // This is it
- break;
- } else {
- // Need to go further lower to find the starting row
- end = pos;
- }
- }
- pos = (start + end) / 2;
- }
- alphaMap.put(key, pos);
- cursor.moveToPosition(savedCursorPos);
- return pos;
- }
-
- @Override
- public void onChanged() {
- super.onChanged();
- mAlphaMap.clear();
- }
-
- @Override
- public void onInvalidated() {
- super.onInvalidated();
- mAlphaMap.clear();
- }
-}
diff --git a/src/com/android/contacts/AttachImage.java b/src/com/android/contacts/AttachImage.java
index 6346031..8c91722 100644
--- a/src/com/android/contacts/AttachImage.java
+++ b/src/com/android/contacts/AttachImage.java
@@ -28,22 +28,22 @@
/**
* Provides an external interface for other applications to attach images
- * to contacts. It will first present a contact picker and then run the
+ * to contacts. It will first present a contact picker and then run the
* image that is handed to it through the cropper to make the image the proper
* size and give the user a chance to use the face detector.
*/
-class AttachImage extends Activity {
+public class AttachImage extends Activity {
private static final int REQUEST_PICK_CONTACT = 1;
private static final int REQUEST_CROP_PHOTO = 2;
private static final String CONTACT_URI_KEY = "contact_uri";
public AttachImage() {
-
+
}
Uri mContactUri;
-
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
diff --git a/src/com/android/contacts/ContactsGroupSyncSelector.java b/src/com/android/contacts/ContactsGroupSyncSelector.java
index 1043299..6081c79 100644
--- a/src/com/android/contacts/ContactsGroupSyncSelector.java
+++ b/src/com/android/contacts/ContactsGroupSyncSelector.java
@@ -16,28 +16,27 @@
package com.android.contacts;
+import com.google.android.googlelogin.GoogleLoginServiceConstants;
+import com.google.android.googlelogin.GoogleLoginServiceHelper;
+
+import android.app.ListActivity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
-import android.content.DialogInterface;
+import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Contacts;
+import android.provider.Gmail;
import android.provider.Contacts.Groups;
import android.provider.Contacts.Settings;
import android.text.TextUtils;
-import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
-
-public final class ContactsGroupSyncSelector extends AlertActivity implements
- ListView.OnItemClickListener, DialogInterface.OnClickListener {
+public final class ContactsGroupSyncSelector extends ListActivity implements View.OnClickListener {
private static final String[] PROJECTION = new String[] {
Groups._ID, // 0
@@ -50,9 +49,7 @@
private static final int COLUMN_INDEX_SHOULD_SYNC = 2;
private static final int COLUMN_INDEX_SYSTEM_ID = 3;
- private ContentResolver mResolver;
- private ListView mListView;
- private GroupsAdapter mAdapter;
+ private static final int SUBACTIVITY_GET_ACCOUNT = 1;
boolean[] mChecked;
boolean mSyncAllGroups;
@@ -60,8 +57,7 @@
private final class GroupsAdapter extends ArrayAdapter<CharSequence> {
public GroupsAdapter(CharSequence[] items) {
- super(new ContextThemeWrapper(ContactsGroupSyncSelector.this,
- android.R.style.Theme_Light),
+ super(ContactsGroupSyncSelector.this,
android.R.layout.simple_list_item_checked,
android.R.id.text1, items);
}
@@ -95,8 +91,9 @@
/**
* Handles clicks on the list items
*/
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- boolean isChecked = mListView.isItemChecked(position);
+ @Override
+ protected void onListItemClick(ListView list, View view, int position, long id) {
+ boolean isChecked = list.isItemChecked(position);
mChecked[position] = isChecked;
if (position == 0) {
mSyncAllGroups = isChecked;
@@ -105,58 +102,85 @@
}
/**
- * Handles clicks on the OK button
+ * Handles clicks on the OK and cancel buttons
*/
- public void onClick(DialogInterface dialog, int which) {
- if (mSyncAllGroups) {
- // For now we only support a single account and the UI doesn't know what
- // the account name is, so we're using a global setting for SYNC_EVERYTHING.
- // Some day when we add multiple accounts to the UI this should use the per
- // account setting.
- Settings.setSetting(mResolver, null, Settings.SYNC_EVERYTHING, "1");
- } else {
- final ContentResolver resolver = mResolver;
- ContentValues values = new ContentValues();
- int count = mChecked.length;
- for (int i = 1; i < count; i++) {
- values.clear();
- values.put(Groups.SHOULD_SYNC, mChecked[i]);
- resolver.update(ContentUris.withAppendedId(Groups.CONTENT_URI, mGroupIds[i]),
- values, null, null);
+ public void onClick(View view) {
+ switch (view.getId()) {
+ case R.id.cancel: {
+ finish();
+ break;
}
- // For now we only support a single account and the UI doesn't know what
- // the account name is, so we're using a global setting for SYNC_EVERYTHING.
- // Some day when we add multiple accounts to the UI this should use the per
- // account setting.
- Settings.setSetting(resolver, null, Settings.SYNC_EVERYTHING, "0");
+
+ case R.id.ok: {
+ final ContentResolver resolver = getContentResolver();
+ if (mSyncAllGroups) {
+ // For now we only support a single account and the UI doesn't know what
+ // the account name is, so we're using a global setting for SYNC_EVERYTHING.
+ // Some day when we add multiple accounts to the UI this should use the per
+ // account setting.
+ Settings.setSetting(resolver, null, Settings.SYNC_EVERYTHING, "1");
+ } else {
+ ContentValues values = new ContentValues();
+ int count = mChecked.length;
+ for (int i = 1; i < count; i++) {
+ values.clear();
+ values.put(Groups.SHOULD_SYNC, mChecked[i]);
+ resolver.update(ContentUris.withAppendedId(Groups.CONTENT_URI, mGroupIds[i]),
+ values, null, null);
+ }
+ // For now we only support a single account and the UI doesn't know what
+ // the account name is, so we're using a global setting for SYNC_EVERYTHING.
+ // Some day when we add multiple accounts to the UI this should use the per
+ // account setting.
+ Settings.setSetting(resolver, null, Settings.SYNC_EVERYTHING, "0");
+ }
+ finish();
+ break;
+ }
}
}
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
- mResolver = getContentResolver();
- // Set the alert parameters
- AlertController.AlertParams params = mAlertParams;
- params.mTitle = getText(R.string.syncGroupChooserTitle);
- params.mIcon = getResources().getDrawable(R.drawable.ic_tab_unselected_contacts);
- params.mPositiveButtonText = getText(R.string.okButtonText);
- params.mPositiveButtonListener = this;
- params.mNegativeButtonText = getText(R.string.cancelButtonText);
- buildItems(params);
+ // Only look for an account on first run.
+ if (savedState == null) {
+ // This will request a Gmail account and if none are present, it will
+ // invoke SetupWizard to login or create one. The result is returned
+ // 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);
+ }
- // Takes the info in mAlertParams and creates the layout
- setupAlert();
+ setContentView(R.layout.sync_settings);
- mListView = mAlert.getListView();
- mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
- mListView.setOnItemClickListener(this);
- adjustChecks();
+ findViewById(R.id.ok).setOnClickListener(this);
+ findViewById(R.id.cancel).setOnClickListener(this);
+
+ getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
}
- private void buildItems(AlertController.AlertParams params) {
- Cursor cursor = mResolver.query(Groups.CONTENT_URI, PROJECTION, null, null, Groups.NAME);
+ @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);
if (cursor != null) {
try {
int count = cursor.getCount() + 1;
@@ -183,13 +207,12 @@
}
}
mChecked = checked;
- mSyncAllGroups = getShouldSyncEverything(mResolver);
+ mSyncAllGroups = getShouldSyncEverything(resolver);
checked[0] = mSyncAllGroups;
mGroupIds = groupIds;
// Setup the adapter
- mAdapter = new GroupsAdapter(items);
- params.mAdapter = mAdapter;
+ setListAdapter(new GroupsAdapter(items));
} finally {
cursor.close();
}
@@ -197,7 +220,7 @@
}
private void adjustChecks() {
- ListView list = mListView;
+ final ListView list = getListView();
if (mSyncAllGroups) {
int count = list.getCount();
for (int i = 0; i < count; i++) {
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 0ffa786..5e1fd63 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -28,8 +28,8 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.database.Cursor;
import android.database.CharArrayBuffer;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
@@ -43,10 +43,10 @@
import android.provider.Contacts.Phones;
import android.provider.Contacts.Presence;
import android.provider.Contacts.Intents.UI;
-import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -55,11 +55,14 @@
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView;
+import android.widget.AlphabetIndexer;
import android.widget.ListView;
import android.widget.ResourceCursorAdapter;
+import android.widget.SectionIndexer;
import android.widget.TextView;
import java.util.ArrayList;
+import java.lang.ref.WeakReference;
/**
* Displays a list of contacts. Usually is embedded into the ContactsActivity.
@@ -125,6 +128,31 @@
static final int DEFAULT_MODE = MODE_ALL_CONTACTS;
+ /**
+ * The type of data to display in the main contacts list.
+ */
+ static final String PREF_DISPLAY_TYPE = "display_system_group";
+
+ /** Unknown display type. */
+ static final int DISPLAY_TYPE_UNKNOWN = -1;
+ /** Display all contacts */
+ static final int DISPLAY_TYPE_ALL = 0;
+ /** Display all contacts that have phone numbers */
+ static final int DISPLAY_TYPE_ALL_WITH_PHONES = 1;
+ /** Display a system group */
+ static final int DISPLAY_TYPE_SYSTEM_GROUP = 2;
+ /** Display a user group */
+ static final int DISPLAY_TYPE_USER_GROUP = 3;
+
+ /**
+ * Info about what to display. If {@link #PREF_DISPLAY_TYPE}
+ * is {@link #DISPLAY_TYPE_SYSTEM_GROUP} then this will be the system id.
+ * If {@link #PREF_DISPLAY_TYPE} is {@link #DISPLAY_TYPE_USER_GROUP} then this will
+ * be the group name.
+ */
+ static final String PREF_DISPLAY_INFO = "display_group";
+
+
static final String NAME_COLUMN = People.DISPLAY_NAME;
static final String[] CONTACTS_PROJECTION = new String[] {
@@ -244,9 +272,6 @@
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- // Make sure the preferences are setup
- PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
-
// Resolve the intent
final Intent intent = getIntent();
@@ -255,7 +280,7 @@
if (title != null) {
setTitle(title);
}
-
+
final String action = intent.getAction();
mMode = MODE_UNKNOWN;
@@ -355,12 +380,6 @@
mMode = DEFAULT_MODE;
}
-/*
- if (!mDefaultMode) {
- findViewById(R.id.contact_group).banner.setVisibility(View.GONE);
- }
-*/
-
// Setup the UI
final ListView list = getListView();
list.setFocusable(true);
@@ -392,12 +411,14 @@
}
private void setEmptyText() {
- TextView empty = (TextView) findViewById(android.R.id.empty);
+ TextView empty = (TextView) findViewById(R.id.emptyText);
+ // Center the text by default
+ int gravity = Gravity.CENTER;
switch (mMode) {
case MODE_GROUP:
if (Groups.GROUP_MY_CONTACTS.equals(mDisplayInfo)) {
- empty.setText(getString(R.string.groupEmpty,
- getText(R.string.groupNameMyContacts)));
+ empty.setText(getText(R.string.noContactsHelpText));
+ gravity = Gravity.NO_GRAVITY;
} else {
empty.setText(getString(R.string.groupEmpty, mDisplayInfo));
}
@@ -417,6 +438,7 @@
empty.setText(getText(R.string.noContacts));
break;
}
+ empty.setGravity(gravity);
}
/**
@@ -449,18 +471,17 @@
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
// Lookup the group to display
- mDisplayType = prefs.getInt(ContactsPreferenceActivity.PREF_DISPLAY_TYPE,
- ContactsPreferenceActivity.DISPLAY_TYPE_UNKNOWN);
+ mDisplayType = prefs.getInt(PREF_DISPLAY_TYPE, DISPLAY_TYPE_UNKNOWN);
switch (mDisplayType) {
- case ContactsPreferenceActivity.DISPLAY_TYPE_ALL_WITH_PHONES: {
+ case DISPLAY_TYPE_ALL_WITH_PHONES: {
mMode = MODE_WITH_PHONES;
mDisplayInfo = null;
break;
}
- case ContactsPreferenceActivity.DISPLAY_TYPE_SYSTEM_GROUP: {
+ case DISPLAY_TYPE_SYSTEM_GROUP: {
String systemId = prefs.getString(
- ContactsPreferenceActivity.PREF_DISPLAY_INFO, null);
+ PREF_DISPLAY_INFO, null);
if (!TextUtils.isEmpty(systemId)) {
// Display the selected system group
mMode = MODE_GROUP;
@@ -470,14 +491,14 @@
// No valid group is present, display everything
mMode = MODE_WITH_PHONES;
mDisplayInfo = null;
- mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_ALL;
+ mDisplayType = DISPLAY_TYPE_ALL;
}
break;
}
- case ContactsPreferenceActivity.DISPLAY_TYPE_USER_GROUP: {
+ case DISPLAY_TYPE_USER_GROUP: {
String displayGroup = prefs.getString(
- ContactsPreferenceActivity.PREF_DISPLAY_INFO, null);
+ PREF_DISPLAY_INFO, null);
if (!TextUtils.isEmpty(displayGroup)) {
// Display the selected user group
mMode = MODE_GROUP;
@@ -487,12 +508,12 @@
// No valid group is present, display everything
mMode = MODE_WITH_PHONES;
mDisplayInfo = null;
- mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_ALL;
+ mDisplayType = DISPLAY_TYPE_ALL;
}
break;
}
- case ContactsPreferenceActivity.DISPLAY_TYPE_ALL: {
+ case DISPLAY_TYPE_ALL: {
mMode = MODE_ALL_CONTACTS;
mDisplayInfo = null;
break;
@@ -501,7 +522,7 @@
default: {
// We don't know what to display, default to My Contacts
mMode = MODE_GROUP;
- mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_SYSTEM_GROUP;
+ mDisplayType = DISPLAY_TYPE_SYSTEM_GROUP;
buildSystemGroupUris(Groups.GROUP_MY_CONTACTS);
mDisplayInfo = Groups.GROUP_MY_CONTACTS;
break;
@@ -596,19 +617,33 @@
return false;
}
+ // New contact
menu.add(0, MENU_NEW_CONTACT, 0, R.string.menu_newContact)
.setIcon(android.R.drawable.ic_menu_add)
.setIntent(new Intent(Intents.Insert.ACTION, People.CONTENT_URI))
.setAlphabeticShortcut('n');
- if (isChild()) {
- menu.add(0, 0, 0, R.string.menu_preferences)
- .setIcon(android.R.drawable.ic_menu_preferences)
- .setIntent(new Intent(this, ContactsPreferenceActivity.class));
- }
+
+ // Display group
if (mDefaultMode) {
menu.add(0, MENU_DISPLAY_GROUP, 0, R.string.menu_displayGroup)
.setIcon(R.drawable.ic_menu_allfriends);
}
+
+ // Sync settings
+ Intent syncIntent = new Intent(Intent.ACTION_VIEW);
+ syncIntent.setClass(this, ContactsGroupSyncSelector.class);
+ menu.add(0, 0, 0, R.string.syncGroupPreference)
+ .setIcon(R.drawable.ic_menu_refresh)
+ .setIntent(syncIntent);
+
+ // SIM import
+ Intent importIntent = new Intent(Intent.ACTION_VIEW);
+ importIntent.setType("vnd.android.cursor.item/sim-contact");
+ importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts");
+ menu.add(0, 0, 0, R.string.importFromSim)
+ .setIcon(R.drawable.ic_menu_import_contact)
+ .setIntent(importIntent);
+
return super.onCreateOptionsMenu(menu);
}
@@ -622,27 +657,27 @@
// Set the group to display
if (mDisplayGroupCurrentSelection == DISPLAY_GROUP_INDEX_ALL_CONTACTS) {
// Display all
- mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_ALL;
+ mDisplayType = DISPLAY_TYPE_ALL;
mDisplayInfo = null;
} else if (mDisplayGroupCurrentSelection
== DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES) {
// Display all with phone numbers
- mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_ALL_WITH_PHONES;
+ mDisplayType = DISPLAY_TYPE_ALL_WITH_PHONES;
mDisplayInfo = null;
} else if (mDisplayGroupsIncludesMyContacts &&
mDisplayGroupCurrentSelection == DISPLAY_GROUP_INDEX_MY_CONTACTS) {
- mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_SYSTEM_GROUP;
+ mDisplayType = DISPLAY_TYPE_SYSTEM_GROUP;
mDisplayInfo = Groups.GROUP_MY_CONTACTS;
} else {
- mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_USER_GROUP;
+ mDisplayType = DISPLAY_TYPE_USER_GROUP;
mDisplayInfo = mDisplayGroups[mDisplayGroupCurrentSelection].toString();
}
// Save the changes to the preferences
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit()
- .putInt(ContactsPreferenceActivity.PREF_DISPLAY_TYPE, mDisplayType)
- .putString(ContactsPreferenceActivity.PREF_DISPLAY_INFO, mDisplayInfo)
+ .putInt(PREF_DISPLAY_TYPE, mDisplayType)
+ .putString(PREF_DISPLAY_INFO, mDisplayInfo)
.commit();
// Update the display state
@@ -778,8 +813,8 @@
.setTitle(R.string.deleteConfirmation_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.deleteConfirmation)
- .setNegativeButton(R.string.noButton, null)
- .setPositiveButton(R.string.yesButton, new DeleteClickListener(uri))
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok, new DeleteClickListener(uri))
.setCancelable(false)
.show();
return true;
@@ -810,8 +845,8 @@
.setTitle(R.string.deleteConfirmation_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.deleteConfirmation)
- .setNegativeButton(R.string.noButton, null)
- .setPositiveButton(R.string.yesButton, new DeleteClickListener(uri))
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok, new DeleteClickListener(uri))
.setCancelable(false)
.show();
return true;
@@ -1159,7 +1194,7 @@
// The My Contacts group
groups.add(DISPLAY_GROUP_INDEX_MY_CONTACTS,
getString(R.string.groupNameMyContacts));
- if (mDisplayType == ContactsPreferenceActivity.DISPLAY_TYPE_SYSTEM_GROUP
+ if (mDisplayType == DISPLAY_TYPE_SYSTEM_GROUP
&& Groups.GROUP_MY_CONTACTS.equals(mDisplayInfo)) {
currentIndex = DISPLAY_GROUP_INDEX_MY_CONTACTS;
}
@@ -1180,25 +1215,29 @@
}
}
- private final class QueryHandler extends AsyncQueryHandler {
+ private static final class QueryHandler extends AsyncQueryHandler {
+ private final WeakReference<ContactsListActivity> mActivity;
+
public QueryHandler(Context context) {
super(context.getContentResolver());
+ mActivity = new WeakReference<ContactsListActivity>((ContactsListActivity) context);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- if (!isFinishing()) {
- mAdapter.setLoading(false);
- mAdapter.changeCursor(cursor);
+ final ContactsListActivity activity = mActivity.get();
+ if (activity != null && !activity.isFinishing()) {
+ activity.mAdapter.setLoading(false);
+ activity.mAdapter.changeCursor(cursor);
// Now that the cursor is populated again, it's possible to restore the list state
- if (mListState != null) {
- mList.onRestoreInstanceState(mListState);
- if (mListHasFocus) {
- mList.requestFocus();
+ if (activity.mListState != null) {
+ activity.mList.onRestoreInstanceState(activity.mListState);
+ if (activity.mListHasFocus) {
+ activity.mList.requestFocus();
}
- mListHasFocus = false;
- mListState = null;
+ activity.mListHasFocus = false;
+ activity.mListState = null;
}
} else {
cursor.close();
@@ -1216,20 +1255,22 @@
}
private final class ContactItemListAdapter extends ResourceCursorAdapter
- implements FastScrollView.SectionIndexer {
+ implements SectionIndexer {
- private String [] mAlphabet;
private AlphabetIndexer mIndexer;
+ private String mAlphabet;
private boolean mLoading = true;
private CharSequence mUnknownNameText;
private CharSequence[] mLocalizedLabels;
public ContactItemListAdapter(Context context, int resource, Cursor cursor) {
super(context, resource, cursor);
- getAlphabet(context);
+
+ mAlphabet = context.getString(com.android.internal.R.string.fast_scroll_alphabet);
if (cursor != null) {
mIndexer = new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
}
+
mUnknownNameText = context.getText(android.R.string.unknownName);
switch (mMode) {
case MODE_PICK_POSTAL:
@@ -1249,22 +1290,20 @@
@Override
public boolean isEmpty() {
- if (mLoading) {
- // We don't want the empty state to show when loading.
+ if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW) {
+ // This mode mask adds a header and we always want it to show up, even
+ // if the list is empty, so always claim the list is not empty.
return false;
} else {
- return super.isEmpty();
+ if (mLoading) {
+ // We don't want the empty state to show when loading.
+ return false;
+ } else {
+ return super.isEmpty();
+ }
}
}
- private void getAlphabet(Context context) {
- String alphabetString = context.getResources().getString(R.string.alphabet);
- mAlphabet = new String[alphabetString.length()];
- for (int i = 0; i < mAlphabet.length; i++) {
- mAlphabet[i] = String.valueOf(alphabetString.charAt(i));
- }
- }
-
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
final View view = super.newView(context, cursor, parent);
@@ -1377,8 +1416,8 @@
if (mMode == MODE_STREQUENT) {
return new String[] { " " };
} else {
- return mAlphabet;
- }
+ return mIndexer.getSections();
+ }
}
public int getPositionForSection(int sectionIndex) {
@@ -1395,7 +1434,7 @@
mIndexer = new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
}
- return mIndexer.indexOf(sectionIndex);
+ return mIndexer.getPositionForSection(sectionIndex);
}
public int getSectionForPosition(int position) {
diff --git a/src/com/android/contacts/ContactsLiveFolders.java b/src/com/android/contacts/ContactsLiveFolders.java
new file mode 100644
index 0000000..8ca199a
--- /dev/null
+++ b/src/com/android/contacts/ContactsLiveFolders.java
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+package com.android.contacts;
+
+import android.content.Intent;
+import android.content.Context;
+import android.net.Uri;
+import android.app.Activity;
+import android.os.Bundle;
+import android.provider.LiveFolders;
+import android.provider.Contacts;
+
+public class ContactsLiveFolders {
+ public static class StarredContacts extends Activity {
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://contacts/live_folders/favorites");
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = getIntent();
+ final String action = intent.getAction();
+
+ if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) {
+ setResult(RESULT_OK, createLiveFolder(this, CONTENT_URI,
+ getString(R.string.liveFolder_favorites_label),
+ R.drawable.ic_launcher_contacts_starred));
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+
+ finish();
+ }
+ }
+
+ public static class PhoneContacts extends Activity {
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://contacts/live_folders/people_with_phones");
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = getIntent();
+ final String action = intent.getAction();
+
+ if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) {
+ setResult(RESULT_OK, createLiveFolder(this, CONTENT_URI,
+ getString(R.string.liveFolder_phones_label),
+ R.drawable.ic_launcher_contacts_phones));
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+
+ finish();
+ }
+ }
+
+ public static class AllContacts extends Activity {
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://contacts/live_folders/people");
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = getIntent();
+ final String action = intent.getAction();
+
+ if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) {
+ setResult(RESULT_OK, createLiveFolder(this, CONTENT_URI,
+ getString(R.string.liveFolder_all_label),
+ R.drawable.ic_launcher_contacts));
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+
+ finish();
+ }
+ }
+
+ private static Intent createLiveFolder(Context context, Uri uri, String name,
+ int icon) {
+
+ final Intent intent = new Intent();
+
+ intent.setData(uri);
+ intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT, new Intent(Intent.ACTION_VIEW,
+ Contacts.People.CONTENT_URI));
+ intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, name);
+ intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON,
+ Intent.ShortcutIconResource.fromContext(context, icon));
+ intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE, LiveFolders.DISPLAY_MODE_LIST);
+
+ return intent;
+ }
+}
diff --git a/src/com/android/contacts/ContactsPreferenceActivity.java b/src/com/android/contacts/ContactsPreferenceActivity.java
deleted file mode 100644
index b625fe5..0000000
--- a/src/com/android/contacts/ContactsPreferenceActivity.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.contacts;
-
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-
-public final class ContactsPreferenceActivity extends PreferenceActivity {
- /**
- * The type of data to display in the main contacts list.
- */
- static final String PREF_DISPLAY_TYPE = "display_system_group";
-
- /** Unknown display type. */
- static final int DISPLAY_TYPE_UNKNOWN = -1;
- /** Display all contacts */
- static final int DISPLAY_TYPE_ALL = 0;
- /** Display all contacts that have phone numbers */
- static final int DISPLAY_TYPE_ALL_WITH_PHONES = 1;
- /** Display a system group */
- static final int DISPLAY_TYPE_SYSTEM_GROUP = 2;
- /** Display a user group */
- static final int DISPLAY_TYPE_USER_GROUP = 3;
-
- /**
- * Info about what to display. If {@link #PREF_DISPLAY_TYPE}
- * is {@link #DISPLAY_TYPE_SYSTEM_GROUP} then this will be the system id.
- * If {@link #PREF_DISPLAY_TYPE} is {@link #DISPLAY_TYPE_USER_GROUP} then this will
- * be the group name.
- */
- static final String PREF_DISPLAY_INFO = "display_group";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Load the preferences from an XML resource
- addPreferencesFromResource(R.xml.preferences);
- }
-
-}
diff --git a/src/com/android/contacts/EditContactActivity.java b/src/com/android/contacts/EditContactActivity.java
index c301473..3a7610d 100644
--- a/src/com/android/contacts/EditContactActivity.java
+++ b/src/com/android/contacts/EditContactActivity.java
@@ -86,6 +86,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
@@ -144,12 +145,6 @@
public static final int MENU_ITEM_ADD = 5;
public static final int MENU_ITEM_PHOTO = 6;
- // Key listener types
- final static int INPUT_TEXT = 1;
- final static int INPUT_TEXT_WORDS = 2;
- final static int INPUT_TEXT_SENTENCES = 3;
- final static int INPUT_DIALER = 4;
-
/** Used to represent an invalid type for a contact entry */
private static final int INVALID_TYPE = -1;
@@ -171,7 +166,7 @@
private EditText mNameView;
private ImageView mPhotoImageView;
- private Button mPhotoButton;
+ private View mPhotoButton;
private CheckBox mSendToVoicemailCheckBox;
private LinearLayout mLayout;
private LayoutInflater mInflater;
@@ -258,7 +253,7 @@
mPhotoMenuItem.setIcon(android.R.drawable.ic_menu_delete);
} else {
mPhotoMenuItem.setTitle(R.string.addPicture);
- mPhotoMenuItem.setIcon(android.R.drawable.ic_menu_add);
+ mPhotoMenuItem.setIcon(R.drawable.ic_menu_add_picture);
}
}
}
@@ -310,7 +305,7 @@
mPhotoImageView = (ImageView) findViewById(R.id.photoImage);
mPhotoImageView.setOnClickListener(this);
mPhotoImageView.setVisibility(View.GONE);
- mPhotoButton = (Button) findViewById(R.id.photoButton);
+ mPhotoButton = findViewById(R.id.photoButton);
mPhotoButton.setOnClickListener(this);
mSendToVoicemailCheckBox = (CheckBox) findViewById(R.id.send_to_voicemail);
@@ -532,7 +527,7 @@
new AlertDialog.Builder(EditContactActivity.this)
.setTitle(R.string.errorDialogTitle)
.setMessage(R.string.photoPickerNotFoundText)
- .setPositiveButton(R.string.okButtonText, null)
+ .setPositiveButton(android.R.string.ok, null)
.show();
}
}
@@ -607,8 +602,8 @@
.setTitle(R.string.deleteConfirmation_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.deleteConfirmation)
- .setNegativeButton(R.string.noButton, null)
- .setPositiveButton(R.string.yesButton, mDeleteContactDialogListener)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok, mDeleteContactDialogListener)
.setCancelable(false)
.create();
}
@@ -713,7 +708,7 @@
case OTHER_ORGANIZATION:
entry = EditEntry.newOrganizationEntry(EditContactActivity.this,
Uri.withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY),
- ContactMethods.TYPE_WORK);
+ Organizations.TYPE_WORK);
mOtherEntries.add(entry);
break;
@@ -874,7 +869,7 @@
new AlertDialog.Builder(this)
.setView(label)
.setTitle(R.string.customLabelPickerTitle)
- .setPositiveButton(R.string.okButtonText, new DialogInterface.OnClickListener() {
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
entry.setLabel(EditContactActivity.this, ContactMethods.TYPE_CUSTOM,
label.getText().toString());
@@ -885,7 +880,7 @@
}
}
})
- .setNegativeButton(R.string.cancelButtonText, null)
+ .setNegativeButton(android.R.string.cancel, null)
.show();
}
@@ -1004,10 +999,10 @@
// Add the contact to the group that is being displayed in the contact list
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- int displayType = prefs.getInt(ContactsPreferenceActivity.PREF_DISPLAY_TYPE,
- ContactsPreferenceActivity.DISPLAY_TYPE_UNKNOWN);
- if (displayType == ContactsPreferenceActivity.DISPLAY_TYPE_USER_GROUP) {
- String displayGroup = prefs.getString(ContactsPreferenceActivity.PREF_DISPLAY_INFO,
+ int displayType = prefs.getInt(ContactsListActivity.PREF_DISPLAY_TYPE,
+ ContactsListActivity.DISPLAY_TYPE_UNKNOWN);
+ if (displayType == ContactsListActivity.DISPLAY_TYPE_USER_GROUP) {
+ String displayGroup = prefs.getString(ContactsListActivity.PREF_DISPLAY_INFO,
null);
if (!TextUtils.isEmpty(displayGroup)) {
People.addToGroup(mResolver, ContentUris.parseId(contactUri), displayGroup);
@@ -1311,42 +1306,20 @@
}
// Email entries from extras
- CharSequence email = extras.getCharSequence(Insert.EMAIL);
- int emailType = extras.getInt(Insert.EMAIL_TYPE, INVALID_TYPE);
- if (!TextUtils.isEmpty(email) && emailType == INVALID_TYPE) {
- emailType = DEFAULT_EMAIL_TYPE;
- mPrimaryEmailAdded = true;
- }
+ addEmailFromExtras(extras, methodsUri, Insert.EMAIL, Insert.EMAIL_TYPE,
+ Insert.EMAIL_ISPRIMARY);
+ addEmailFromExtras(extras, methodsUri, Insert.SECONDARY_EMAIL, Insert.SECONDARY_EMAIL_TYPE,
+ null);
+ addEmailFromExtras(extras, methodsUri, Insert.TERTIARY_EMAIL, Insert.TERTIARY_EMAIL_TYPE,
+ null);
- if (emailType != INVALID_TYPE) {
- entry = EditEntry.newEmailEntry(this, null, emailType, email.toString(), methodsUri, 0);
- entry.isPrimary = extras.getBoolean(Insert.EMAIL_ISPRIMARY);
- mEmailEntries.add(entry);
-
- // Keep track of which primary types have been added
- if (entry.isPrimary) {
- mPrimaryEmailAdded = true;
- }
- }
-
- // Phone entries from extras
- CharSequence phoneNumber = extras.getCharSequence(Insert.PHONE);
- int phoneType = extras.getInt(Insert.PHONE_TYPE, INVALID_TYPE);
- if (!TextUtils.isEmpty(phoneNumber) && phoneType == INVALID_TYPE) {
- phoneType = DEFAULT_PHONE_TYPE;
- }
-
- if (phoneType != INVALID_TYPE) {
- entry = EditEntry.newPhoneEntry(this, null, phoneType,
- phoneNumber.toString(), phonesUri, 0);
- entry.isPrimary = extras.getBoolean(Insert.PHONE_ISPRIMARY);
- mPhoneEntries.add(entry);
-
- // Keep track of which primary types have been added
- if (phoneType == Phones.TYPE_MOBILE) {
- mMobilePhoneAdded = true;
- }
- }
+ // Phone entries from extras
+ addPhoneFromExtras(extras, phonesUri, Insert.PHONE, Insert.PHONE_TYPE,
+ Insert.PHONE_ISPRIMARY);
+ addPhoneFromExtras(extras, phonesUri, Insert.SECONDARY_PHONE, Insert.SECONDARY_PHONE_TYPE,
+ null);
+ addPhoneFromExtras(extras, phonesUri, Insert.TERTIARY_PHONE, Insert.TERTIARY_PHONE_TYPE,
+ null);
// IM entries from extras
CharSequence imHandle = extras.getCharSequence(Insert.IM_HANDLE);
@@ -1368,6 +1341,49 @@
}
}
+ private void addEmailFromExtras(Bundle extras, Uri methodsUri, String emailField,
+ String typeField, String primaryField) {
+ CharSequence email = extras.getCharSequence(emailField);
+ int emailType = extras.getInt(typeField, INVALID_TYPE);
+ if (!TextUtils.isEmpty(email) && emailType == INVALID_TYPE) {
+ emailType = DEFAULT_EMAIL_TYPE;
+ mPrimaryEmailAdded = true;
+ }
+
+ if (emailType != INVALID_TYPE) {
+ EditEntry entry = EditEntry.newEmailEntry(this, null, emailType, email.toString(),
+ methodsUri, 0);
+ entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
+ mEmailEntries.add(entry);
+
+ // Keep track of which primary types have been added
+ if (entry.isPrimary) {
+ mPrimaryEmailAdded = true;
+ }
+ }
+ }
+
+ private void addPhoneFromExtras(Bundle extras, Uri phonesUri, String phoneField,
+ String typeField, String primaryField) {
+ CharSequence phoneNumber = extras.getCharSequence(phoneField);
+ int phoneType = extras.getInt(typeField, INVALID_TYPE);
+ if (!TextUtils.isEmpty(phoneNumber) && phoneType == INVALID_TYPE) {
+ phoneType = DEFAULT_PHONE_TYPE;
+ }
+
+ if (phoneType != INVALID_TYPE) {
+ EditEntry entry = EditEntry.newPhoneEntry(this, null, phoneType,
+ phoneNumber.toString(), phonesUri, 0);
+ entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
+ mPhoneEntries.add(entry);
+
+ // Keep track of which primary types have been added
+ if (phoneType == Phones.TYPE_MOBILE) {
+ mMobilePhoneAdded = true;
+ }
+ }
+ }
+
/**
* Removes all existing views, builds new ones for all the entries, and adds them.
*/
@@ -1475,42 +1491,20 @@
data2.setLines(entry.lines);
data2.setMaxLines(entry.maxLines);
}
- } else if (entry.lines >= 0) {
- data.setSingleLine();
- if (data2 != null) {
- data2.setSingleLine();
- }
}
- switch (entry.keyListener) {
- case INPUT_TEXT:
- data.setKeyListener(TextKeyListener.getInstance());
- if (data2 != null) {
- data2.setKeyListener(TextKeyListener.getInstance());
- }
- break;
-
- case INPUT_TEXT_WORDS:
- data.setKeyListener(TextKeyListener.getInstance(true, Capitalize.WORDS));
- if (data2 != null) {
- data2.setKeyListener(TextKeyListener.getInstance(true, Capitalize.WORDS));
- }
- break;
-
- case INPUT_TEXT_SENTENCES:
- data.setKeyListener(TextKeyListener.getInstance(true, Capitalize.SENTENCES));
- if (data2 != null) {
- data2.setKeyListener(TextKeyListener.getInstance(true, Capitalize.SENTENCES));
- }
- break;
-
- case INPUT_DIALER:
- data.setKeyListener(DialerKeyListener.getInstance());
+ int contentType = entry.contentType;
+ if (contentType != EditorInfo.TYPE_NULL) {
+ data.setInputType(contentType);
+ if (data2 != null) {
+ data2.setInputType(contentType);
+ }
+ if ((contentType&EditorInfo.TYPE_MASK_CLASS)
+ == EditorInfo.TYPE_CLASS_PHONE) {
data.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
if (data2 != null) {
- data2.setKeyListener(DialerKeyListener.getInstance());
data2.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
}
- break;
+ }
}
// Hook up the delete button
@@ -1569,7 +1563,7 @@
public String column;
public String contentDirectory;
public String data2;
- public int keyListener;
+ public int contentType;
public int type;
/**
* If 0 or 1, setSingleLine will be called. If negative, setSingleLine
@@ -1614,7 +1608,7 @@
parcel.writeString(column);
parcel.writeString(contentDirectory);
parcel.writeString(data2);
- parcel.writeInt(keyListener);
+ parcel.writeInt(contentType);
parcel.writeInt(type);
parcel.writeInt(lines);
parcel.writeInt(isPrimary ? 1 : 0);
@@ -1637,7 +1631,7 @@
entry.column = in.readString();
entry.contentDirectory = in.readString();
entry.data2 = in.readString();
- entry.keyListener = in.readInt();
+ entry.contentType = in.readInt();
entry.type = in.readInt();
entry.lines = in.readInt();
entry.isPrimary = in.readInt() == 1;
@@ -1834,7 +1828,8 @@
entry.column = Organizations.COMPANY;
entry.contentDirectory = Organizations.CONTENT_DIRECTORY;
entry.kind = Contacts.KIND_ORGANIZATION;
- entry.keyListener = INPUT_TEXT_WORDS;
+ entry.contentType = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
return entry;
}
@@ -1853,7 +1848,9 @@
entry.lines = 2;
entry.id = 0;
entry.kind = KIND_CONTACT;
- entry.keyListener = INPUT_TEXT_SENTENCES;
+ entry.contentType = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
+ | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
entry.isStaticLabel = true;
return entry;
}
@@ -1894,7 +1891,7 @@
entry.column = People.Phones.NUMBER;
entry.contentDirectory = People.Phones.CONTENT_DIRECTORY;
entry.kind = Contacts.KIND_PHONE;
- entry.keyListener = INPUT_DIALER;
+ entry.contentType = EditorInfo.TYPE_CLASS_PHONE;
return entry;
}
@@ -1917,7 +1914,8 @@
entry.column = ContactMethods.DATA;
entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
entry.kind = Contacts.KIND_EMAIL;
- entry.keyListener = INPUT_TEXT;
+ entry.contentType = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
return entry;
}
@@ -1946,7 +1944,10 @@
entry.column = ContactMethods.DATA;
entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
entry.kind = Contacts.KIND_POSTAL;
- entry.keyListener = INPUT_TEXT_WORDS;
+ entry.contentType = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS
+ | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
entry.maxLines = 4;
entry.lines = 2;
return entry;
@@ -1969,7 +1970,7 @@
entry.column = ContactMethods.DATA;
entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
entry.kind = Contacts.KIND_IM;
- entry.keyListener = INPUT_TEXT;
+ entry.contentType = EditorInfo.TYPE_CLASS_TEXT;
return entry;
}
}
diff --git a/src/com/android/contacts/FastScrollView.java b/src/com/android/contacts/FastScrollView.java
deleted file mode 100644
index f45e947..0000000
--- a/src/com/android/contacts/FastScrollView.java
+++ /dev/null
@@ -1,450 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.contacts;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup.OnHierarchyChangeListener;
-import android.widget.AbsListView;
-import android.widget.Adapter;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.HeaderViewListAdapter;
-import android.widget.ListView;
-import android.widget.AbsListView.OnScrollListener;
-
-/**
- * FastScrollView is meant for embedding {@link ListView}s that contain a large number of
- * items that can be indexed in some fashion. It displays a special scroll bar that allows jumping
- * quickly to indexed sections of the list in touch-mode. Only one child can be added to this
- * view group and it must be a {@link ListView}, with an adapter that is derived from
- * {@link BaseAdapter}.
- */
-public class FastScrollView extends FrameLayout
- implements OnScrollListener, OnHierarchyChangeListener {
-
- private Drawable mCurrentThumb;
- private Drawable mOverlayDrawable;
-
- private int mThumbH;
- private int mThumbW;
- private int mThumbY;
-
- private RectF mOverlayPos;
-
- // Hard coding these for now
- private int mOverlaySize = 104;
-
- private boolean mDragging;
- private ListView mList;
- private boolean mScrollCompleted;
- private boolean mThumbVisible;
- private int mVisibleItem;
- private Paint mPaint;
- private int mListOffset;
-
- private Object [] mSections;
- private String mSectionText;
- private boolean mDrawOverlay;
- private ScrollFade mScrollFade;
-
- private Handler mHandler = new Handler();
-
- private BaseAdapter mListAdapter;
-
- private boolean mChangedBounds;
-
- interface SectionIndexer {
- Object[] getSections();
-
- int getPositionForSection(int section);
-
- int getSectionForPosition(int position);
- }
-
- public FastScrollView(Context context) {
- super(context);
-
- init(context);
- }
-
-
- public FastScrollView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- init(context);
- }
-
- public FastScrollView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- init(context);
- }
-
- private void useThumbDrawable(Drawable drawable) {
- mCurrentThumb = drawable;
- mThumbW = 64; //mCurrentThumb.getIntrinsicWidth();
- mThumbH = 52; //mCurrentThumb.getIntrinsicHeight();
- mChangedBounds = true;
- }
-
- private void init(Context context) {
- // Get both the scrollbar states drawables
- final Resources res = context.getResources();
- useThumbDrawable(res.getDrawable(
- com.android.internal.R.drawable.scrollbar_handle_accelerated_anim2));
-
- mOverlayDrawable = res.getDrawable(R.drawable.dialog_full_dark);
-
- mScrollCompleted = true;
- setWillNotDraw(false);
-
- // Need to know when the ListView is added
- setOnHierarchyChangeListener(this);
-
- mOverlayPos = new RectF();
- mScrollFade = new ScrollFade();
- mPaint = new Paint();
- mPaint.setAntiAlias(true);
- mPaint.setTextAlign(Paint.Align.CENTER);
- mPaint.setTextSize(mOverlaySize / 2);
- mPaint.setColor(0xFFFFFFFF);
- mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
- }
-
- private void removeThumb() {
-
- mThumbVisible = false;
- // Draw one last time to remove thumb
- invalidate();
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
-
- if (!mThumbVisible) {
- // No need to draw the rest
- return;
- }
-
- final int y = mThumbY;
- final int viewWidth = getWidth();
- final FastScrollView.ScrollFade scrollFade = mScrollFade;
-
- int alpha = -1;
- if (scrollFade.mStarted) {
- alpha = scrollFade.getAlpha();
- if (alpha < ScrollFade.ALPHA_MAX / 2) {
- mCurrentThumb.setAlpha(alpha * 2);
- }
- int left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
- mCurrentThumb.setBounds(left, 0, viewWidth, mThumbH);
- mChangedBounds = true;
- }
-
- canvas.translate(0, y);
- mCurrentThumb.draw(canvas);
- canvas.translate(0, -y);
-
- // If user is dragging the scroll bar, draw the alphabet overlay
- if (mDragging && mDrawOverlay) {
- mOverlayDrawable.draw(canvas);
- final Paint paint = mPaint;
- float descent = paint.descent();
- final RectF rectF = mOverlayPos;
- canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2,
- (int) (rectF.bottom + rectF.top) / 2 + mOverlaySize / 4 - descent, paint);
- } else if (alpha == 0) {
- scrollFade.mStarted = false;
- removeThumb();
- } else {
- invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
- }
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- if (mCurrentThumb != null) {
- mCurrentThumb.setBounds(w - mThumbW, 0, w, mThumbH);
- }
- final RectF pos = mOverlayPos;
- pos.left = (w - mOverlaySize) / 2;
- pos.right = pos.left + mOverlaySize;
- pos.top = h / 10; // 10% from top
- pos.bottom = pos.top + mOverlaySize;
- mOverlayDrawable.setBounds((int) pos.left, (int) pos.top,
- (int) pos.right, (int) pos.bottom);
- }
-
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- }
-
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
-
- if (totalItemCount - visibleItemCount > 0 && !mDragging) {
- mThumbY = ((getHeight() - mThumbH) * firstVisibleItem) / (totalItemCount - visibleItemCount);
- if (mChangedBounds) {
- final int viewWidth = getWidth();
- mCurrentThumb.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH);
- mChangedBounds = false;
- }
- }
- mScrollCompleted = true;
- if (firstVisibleItem == mVisibleItem) {
- return;
- }
- mVisibleItem = firstVisibleItem;
- if (!mThumbVisible || mScrollFade.mStarted) {
- mThumbVisible = true;
- mCurrentThumb.setAlpha(ScrollFade.ALPHA_MAX);
- }
- mHandler.removeCallbacks(mScrollFade);
- mScrollFade.mStarted = false;
- if (!mDragging) {
- mHandler.postDelayed(mScrollFade, 1500);
- }
- }
-
-
- private void getSections() {
- Adapter adapter = mList.getAdapter();
- if (adapter instanceof HeaderViewListAdapter) {
- mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount();
- adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
- }
- if (adapter instanceof SectionIndexer) {
- mListAdapter = (BaseAdapter) adapter;
- mSections = ((SectionIndexer) mListAdapter).getSections();
- }
- }
-
- public void onChildViewAdded(View parent, View child) {
- if (child instanceof ListView) {
- mList = (ListView)child;
-
- mList.setOnScrollListener(this);
- getSections();
- }
- }
-
- public void onChildViewRemoved(View parent, View child) {
- if (child == mList) {
- mList = null;
- mListAdapter = null;
- mSections = null;
- }
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (mThumbVisible && ev.getAction() == MotionEvent.ACTION_DOWN) {
- if (ev.getX() > getWidth() - mThumbW && ev.getY() >= mThumbY &&
- ev.getY() <= mThumbY + mThumbH) {
- mDragging = true;
- return true;
- }
- }
- return false;
- }
-
- private void scrollTo(float position) {
- int count = mList.getCount();
- mScrollCompleted = false;
- final Object[] sections = mSections;
- int sectionIndex;
- if (sections != null && sections.length > 1) {
- final int nSections = sections.length;
- int section = (int) (position * nSections);
- if (section >= nSections) {
- section = nSections - 1;
- }
- sectionIndex = section;
- final SectionIndexer baseAdapter = (SectionIndexer) mListAdapter;
- int index = baseAdapter.getPositionForSection(section);
-
- // Given the expected section and index, the following code will
- // try to account for missing sections (no names starting with..)
- // It will compute the scroll space of surrounding empty sections
- // and interpolate the currently visible letter's range across the
- // available space, so that there is always some list movement while
- // the user moves the thumb.
- int nextIndex = count;
- int prevIndex = index;
- int prevSection = section;
- int nextSection = section + 1;
- // Assume the next section is unique
- if (section < nSections - 1) {
- nextIndex = baseAdapter.getPositionForSection(section + 1);
- }
-
- // Find the previous index if we're slicing the previous section
- if (nextIndex == index) {
- // Non-existent letter
- while (section > 0) {
- section--;
- prevIndex = baseAdapter.getPositionForSection(section);
- if (prevIndex != index) {
- prevSection = section;
- sectionIndex = section;
- break;
- }
- }
- }
- // Find the next index, in case the assumed next index is not
- // unique. For instance, if there is no P, then request for P's
- // position actually returns Q's. So we need to look ahead to make
- // sure that there is really a Q at Q's position. If not, move
- // further down...
- int nextNextSection = nextSection + 1;
- while (nextNextSection < nSections &&
- baseAdapter.getPositionForSection(nextNextSection) == nextIndex) {
- nextNextSection++;
- nextSection++;
- }
- // Compute the beginning and ending scroll range percentage of the
- // currently visible letter. This could be equal to or greater than
- // (1 / nSections).
- float fPrev = (float) prevSection / nSections;
- float fNext = (float) nextSection / nSections;
- index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev)
- / (fNext - fPrev));
- // Don't overflow
- if (index > count - 1) index = count - 1;
-
- mList.setSelectionFromTop(index + mListOffset, 0);
- } else {
- int index = (int) (position * count);
- mList.setSelectionFromTop(index + mListOffset, 0);
- sectionIndex = -1;
- }
-
- if (sectionIndex >= 0) {
- String text = mSectionText = sections[sectionIndex].toString();
- mDrawOverlay = (text.length() != 1 || text.charAt(0) != ' ') &&
- sectionIndex < sections.length;
- } else {
- mDrawOverlay = false;
- }
- }
-
- private void cancelFling() {
- // Cancel the list fling
- MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
- mList.onTouchEvent(cancelFling);
- cancelFling.recycle();
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent me) {
- if (me.getAction() == MotionEvent.ACTION_DOWN) {
- if (me.getX() > getWidth() - mThumbW
- && me.getY() >= mThumbY
- && me.getY() <= mThumbY + mThumbH) {
-
- mDragging = true;
- if (mListAdapter == null && mList != null) {
- getSections();
- }
-
- cancelFling();
- return true;
- }
- } else if (me.getAction() == MotionEvent.ACTION_UP) {
- if (mDragging) {
- mDragging = false;
- final Handler handler = mHandler;
- handler.removeCallbacks(mScrollFade);
- handler.postDelayed(mScrollFade, 1000);
- return true;
- }
- } else if (me.getAction() == MotionEvent.ACTION_MOVE) {
- if (mDragging) {
- final int viewHeight = getHeight();
- mThumbY = (int) me.getY() - mThumbH + 10;
- if (mThumbY < 0) {
- mThumbY = 0;
- } else if (mThumbY + mThumbH > viewHeight) {
- mThumbY = viewHeight - mThumbH;
- }
- // If the previous scrollTo is still pending
- if (mScrollCompleted) {
- scrollTo((float) mThumbY / (viewHeight - mThumbH));
- }
- return true;
- }
- }
-
- return super.onTouchEvent(me);
- }
-
- public class ScrollFade implements Runnable {
-
- long mStartTime;
- long mFadeDuration;
- boolean mStarted;
- static final int ALPHA_MAX = 255;
- static final long FADE_DURATION = 200;
-
- void startFade() {
- mFadeDuration = FADE_DURATION;
- mStartTime = SystemClock.uptimeMillis();
- mStarted = true;
- }
-
- int getAlpha() {
- if (!mStarted) {
- return ALPHA_MAX;
- }
- int alpha;
- long now = SystemClock.uptimeMillis();
- if (now > mStartTime + mFadeDuration) {
- alpha = 0;
- } else {
- alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration);
- }
- return alpha;
- }
-
- public void run() {
- if (!mStarted) {
- startFade();
- invalidate();
- }
-
- if (getAlpha() > 0) {
- final int y = mThumbY;
- final int viewWidth = getWidth();
- invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
- } else {
- mStarted = false;
- removeThumb();
- }
- }
- }
-}
diff --git a/src/com/android/contacts/RecentCallsListActivity.java b/src/com/android/contacts/RecentCallsListActivity.java
index 32ecd97..550a385 100644
--- a/src/com/android/contacts/RecentCallsListActivity.java
+++ b/src/com/android/contacts/RecentCallsListActivity.java
@@ -32,13 +32,13 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.pim.DateUtils;
import android.provider.CallLog.Calls;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
import android.provider.Contacts.Intents.Insert;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
@@ -60,14 +60,15 @@
import java.util.HashMap;
import java.util.LinkedList;
+import java.lang.ref.WeakReference;
/**
* Displays a list of call log entries.
*/
-public class RecentCallsListActivity extends ListActivity
+public class RecentCallsListActivity extends ListActivity
implements View.OnCreateContextMenuListener {
private static final String TAG = "RecentCallsList";
-
+
/** The projection to use when querying the call log table */
static final String[] CALL_LOG_PROJECTION = new String[] {
Calls._ID,
@@ -79,7 +80,7 @@
Calls.CACHED_NUMBER_TYPE,
Calls.CACHED_NUMBER_LABEL
};
-
+
static final int ID_COLUMN_INDEX = 0;
static final int NUMBER_COLUMN_INDEX = 1;
static final int DATE_COLUMN_INDEX = 2;
@@ -97,7 +98,7 @@
Phones.LABEL,
Phones.NUMBER
};
-
+
static final int PERSON_ID_COLUMN_INDEX = 0;
static final int NAME_COLUMN_INDEX = 1;
static final int PHONE_TYPE_COLUMN_INDEX = 2;
@@ -107,20 +108,20 @@
private static final int MENU_ITEM_DELETE = 1;
private static final int MENU_ITEM_DELETE_ALL = 2;
private static final int MENU_ITEM_VIEW_CONTACTS = 3;
-
+
private static final int QUERY_TOKEN = 53;
private static final int UPDATE_TOKEN = 54;
private RecentCallsAdapter mAdapter;
private QueryHandler mQueryHandler;
private String mVoiceMailNumber;
-
+
private CharSequence[] mLabelArray;
-
+
private Drawable mDrawableIncoming;
private Drawable mDrawableOutgoing;
- private Drawable mDrawableMissed;
-
+ private Drawable mDrawableMissed;
+
private static final class ContactInfo {
public long personId;
public String name;
@@ -137,8 +138,8 @@
TextView durationView;
TextView dateView;
ImageView iconView;
- }
-
+ }
+
private static final class CallerInfoQuery {
String number;
int position;
@@ -148,25 +149,26 @@
}
/** Adapter class to fill in data for the Call Log */
- private final class RecentCallsAdapter extends ResourceCursorAdapter
+ private final class RecentCallsAdapter extends ResourceCursorAdapter
implements Runnable, ViewTreeObserver.OnPreDrawListener {
HashMap<String,ContactInfo> mContactInfo;
- private LinkedList<CallerInfoQuery> mRequests;
- private boolean mDone;
+ private final LinkedList<CallerInfoQuery> mRequests;
+ private volatile boolean mDone;
private boolean mLoading = true;
ViewTreeObserver.OnPreDrawListener mPreDrawListener;
private static final int REDRAW = 1;
private static final int START_THREAD = 2;
private boolean mFirst;
-
+ private Thread mCallerIdThread;
+
public boolean onPreDraw() {
if (mFirst) {
mHandler.sendEmptyMessageDelayed(START_THREAD, 1000);
mFirst = false;
}
- return true;
+ return true;
}
-
+
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -180,7 +182,7 @@
}
}
};
-
+
public RecentCallsAdapter() {
super(RecentCallsListActivity.this, R.layout.recent_calls_list_item, null);
@@ -192,7 +194,7 @@
void setLoading(boolean loading) {
mLoading = loading;
}
-
+
@Override
public boolean isEmpty() {
if (mLoading) {
@@ -209,13 +211,14 @@
public void startRequestProcessing() {
mDone = false;
- Thread callerIdThread = new Thread(this);
- callerIdThread.setPriority(Thread.MIN_PRIORITY);
- callerIdThread.start();
+ mCallerIdThread = new Thread(this);
+ mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
+ mCallerIdThread.start();
}
public void stopRequestProcessing() {
mDone = true;
+ if (mCallerIdThread != null) mCallerIdThread.interrupt();
}
public void clearCache() {
@@ -226,7 +229,7 @@
private void updateCallLog(CallerInfoQuery ciq, ContactInfo ci) {
// Check if they are different. If not, don't update.
- if (TextUtils.equals(ciq.name, ci.name)
+ if (TextUtils.equals(ciq.name, ci.name)
&& TextUtils.equals(ciq.numberLabel, ci.label)
&& ciq.numberType == ci.type) {
return;
@@ -236,10 +239,10 @@
values.put(Calls.CACHED_NUMBER_TYPE, ci.type);
values.put(Calls.CACHED_NUMBER_LABEL, ci.label);
RecentCallsListActivity.this.getContentResolver().update(
- Calls.CONTENT_URI,
+ Calls.CONTENT_URI,
values, Calls.NUMBER + "='" + ciq.number + "'", null);
}
-
+
private void enqueueRequest(String number, int position,
String name, int numberType, String numberLabel) {
CallerInfoQuery ciq = new CallerInfoQuery();
@@ -253,7 +256,7 @@
mRequests.notifyAll();
}
}
-
+
private void queryContactInfo(CallerInfoQuery ciq) {
// First check if there was a prior request for the same number
// that was already satisfied
@@ -265,9 +268,9 @@
}
}
} else {
- Cursor phonesCursor =
+ Cursor phonesCursor =
RecentCallsListActivity.this.getContentResolver().query(
- Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
+ Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
ciq.number),
PHONES_PROJECTION, null, null, null);
if (phonesCursor != null) {
@@ -278,7 +281,7 @@
info.type = phonesCursor.getInt(PHONE_TYPE_COLUMN_INDEX);
info.label = phonesCursor.getString(LABEL_COLUMN_INDEX);
info.number = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
-
+
mContactInfo.put(ciq.number, info);
// Inform list to update this item, if in view
synchronized (mRequests) {
@@ -310,7 +313,7 @@
mRequests.wait(1000);
} catch (InterruptedException ie) {
// Ignore and continue processing requests
- }
+ }
}
}
if (ciq != null) {
@@ -318,11 +321,11 @@
}
}
}
-
+
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = super.newView(context, cursor, parent);
-
+
// Get the views to bind to
RecentCallsListItemViews views = new RecentCallsListItemViews();
views.line1View = (TextView) view.findViewById(R.id.line1);
@@ -336,7 +339,7 @@
return view;
}
-
+
@Override
public void bindView(View view, Context context, Cursor c) {
final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
@@ -345,7 +348,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);
-
+
// Lookup contacts with this number
ContactInfo info = mContactInfo.get(number);
if (info == null) {
@@ -359,11 +362,11 @@
// Check if any data is different from the data cached in the
// calls db. If so, queue the request so that we can update
// the calls db.
- if (!TextUtils.equals(info.name, callerName)
+ if (!TextUtils.equals(info.name, callerName)
|| info.type != callerNumberType
|| !TextUtils.equals(info.label, callerNumberLabel)) {
// Something is amiss, so sync up.
- enqueueRequest(number, c.getPosition(),
+ enqueueRequest(number, c.getPosition(),
callerName, callerNumberType, callerNumberLabel);
}
}
@@ -382,7 +385,7 @@
// Set the text lines
if (!TextUtils.isEmpty(name)) {
views.line1View.setText(name);
- CharSequence numberLabel = Phones.getDisplayLabel(context, ntype, label,
+ CharSequence numberLabel = Phones.getDisplayLabel(context, ntype, label,
mLabelArray);
if (!TextUtils.isEmpty(numberLabel)) {
views.line2View.setText(numberLabel);
@@ -447,7 +450,7 @@
views.iconView.setImageDrawable(mDrawableMissed);
break;
}
- // Listen for the first draw
+ // Listen for the first draw
if (mPreDrawListener == null) {
mFirst = true;
mPreDrawListener = this;
@@ -455,17 +458,23 @@
}
}
}
-
- private final class QueryHandler extends AsyncQueryHandler {
+
+ private static final class QueryHandler extends AsyncQueryHandler {
+ private final WeakReference<RecentCallsListActivity> mActivity;
+
public QueryHandler(Context context) {
super(context.getContentResolver());
+ mActivity = new WeakReference<RecentCallsListActivity>(
+ (RecentCallsListActivity) context);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- if (!isFinishing()) {
- mAdapter.setLoading(false);
- mAdapter.changeCursor(cursor);
+ final RecentCallsListActivity activity = mActivity.get();
+ if (activity != null && !activity.isFinishing()) {
+ final RecentCallsListActivity.RecentCallsAdapter callsAdapter = activity.mAdapter;
+ callsAdapter.setLoading(false);
+ callsAdapter.changeCursor(cursor);
} else {
cursor.close();
}
@@ -477,12 +486,12 @@
super.onCreate(state);
setContentView(R.layout.recent_calls);
-
+
mDrawableIncoming = getResources().getDrawable(android.R.drawable.sym_call_incoming);
mDrawableOutgoing = getResources().getDrawable(android.R.drawable.sym_call_outgoing);
mDrawableMissed = getResources().getDrawable(android.R.drawable.sym_call_missed);
mLabelArray = getResources().getTextArray(com.android.internal.R.array.phoneTypes);
-
+
// Typing here goes to the dialer
setDefaultKeyMode(DEFAULT_KEYS_DIALER);
@@ -505,21 +514,25 @@
startQuery();
resetNewCallsFlag();
-
+
super.onResume();
try {
- ITelephony.Stub.asInterface(ServiceManager.getService("phone"))
- .cancelMissedCallsNotification();
+ ITelephony iTelephony = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
+ if (iTelephony != null) {
+ iTelephony.cancelMissedCallsNotification();
+ } else {
+ Log.w(TAG, "Telephony service is null, can't call cancelMissedCallsNotification");
+ }
} catch (RemoteException e) {
Log.e(TAG, "Failed to clear missed calls notification due to remote excetpion");
}
mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
}
-
+
@Override
protected void onPause() {
super.onPause();
-
+
// Kill the requests thread
mAdapter.stopRequestProcessing();
}
@@ -527,6 +540,7 @@
@Override
protected void onDestroy() {
super.onDestroy();
+ mAdapter.stopRequestProcessing();
Cursor cursor = mAdapter.getCursor();
if (cursor != null && !cursor.isClosed()) {
cursor.close();
@@ -541,19 +555,19 @@
ContentValues values = new ContentValues(1);
values.put(Calls.NEW, "0");
- mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI,
+ mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI,
values, where.toString(), null);
}
private void startQuery() {
mAdapter.setLoading(true);
-
+
// Cancel any pending queries
mQueryHandler.cancelOperation(QUERY_TOKEN);
- mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI,
+ mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI,
CALL_LOG_PROJECTION, null, null, Calls.DEFAULT_SORT_ORDER);
}
-
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, MENU_ITEM_DELETE_ALL, 0, R.string.recentCalls_deleteAll)
@@ -589,7 +603,7 @@
}
ContactInfo info = mAdapter.getContactInfo(number);
- boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY);
+ boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY);
if (contactInfoPresent) {
menu.setHeaderTitle(info.name);
} else {
@@ -631,7 +645,7 @@
case MENU_ITEM_DELETE_ALL: {
getContentResolver().delete(Calls.CONTENT_URI, null, null);
//TODO The change notification should do this automatically, but it isn't working
- // right now. Remove this when the change notification is working properly.
+ // right now. Remove this when the change notification is working properly.
startQuery();
return true;
}
@@ -704,17 +718,17 @@
} catch (RemoteException re) {
// Fall through and try to call the contact
}
-
+
callEntry(getListView().getSelectedItemPosition());
return true;
}
return super.onKeyUp(keyCode, event);
}
-
+
/*
* Get the number from the Contacts, if available, since sometimes
* the number provided by caller id may not be formatted properly
- * depending on the carrier (roaming) in use at the time of the
+ * depending on the carrier (roaming) in use at the time of the
* incoming call.
* Logic : If the caller-id number starts with a "+", use it
* Else if the number in the contacts starts with a "+", use that one
@@ -728,9 +742,9 @@
matchingNumber = ci.number;
} else {
try {
- Cursor phonesCursor =
+ Cursor phonesCursor =
RecentCallsListActivity.this.getContentResolver().query(
- Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
+ Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
number),
PHONES_PROJECTION, null, null, null);
if (phonesCursor != null) {
@@ -743,14 +757,14 @@
// Use the number from the call log
}
}
- if (!TextUtils.isEmpty(matchingNumber) &&
- (matchingNumber.startsWith("+")
+ if (!TextUtils.isEmpty(matchingNumber) &&
+ (matchingNumber.startsWith("+")
|| matchingNumber.length() > number.length())) {
number = matchingNumber;
}
return number;
}
-
+
private void callEntry(int position) {
if (position < 0) {
// In touch mode you may often not have something selected, so
@@ -769,8 +783,8 @@
}
int callType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
- if (!number.startsWith("+") &&
- (callType == Calls.INCOMING_TYPE
+ if (!number.startsWith("+") &&
+ (callType == Calls.INCOMING_TYPE
|| callType == Calls.MISSED_TYPE)) {
// If the caller-id matches a contact with a better qualified number, use it
number = getBetterNumberFromContacts(number);
diff --git a/src/com/android/contacts/SpecialCharSequenceMgr.java b/src/com/android/contacts/SpecialCharSequenceMgr.java
index e23d460..38bc93d 100644
--- a/src/com/android/contacts/SpecialCharSequenceMgr.java
+++ b/src/com/android/contacts/SpecialCharSequenceMgr.java
@@ -193,7 +193,7 @@
AlertDialog alert = new AlertDialog.Builder(context)
.setTitle(R.string.imei)
.setMessage(imeiStr)
- .setPositiveButton(R.string.ok, null)
+ .setPositiveButton(android.R.string.ok, null)
.setCancelable(false)
.show();
alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE);
diff --git a/src/com/android/contacts/TwelveKeyDialer.java b/src/com/android/contacts/TwelveKeyDialer.java
index caa77a1..41adc5a 100644
--- a/src/com/android/contacts/TwelveKeyDialer.java
+++ b/src/com/android/contacts/TwelveKeyDialer.java
@@ -18,10 +18,12 @@
import android.app.Activity;
import android.content.ActivityNotFoundException;
-
+import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.ToneGenerator;
@@ -29,32 +31,45 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
-import android.provider.Settings;
+import android.provider.Contacts.Intents.Insert;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
import android.provider.Contacts.PhonesColumns;
-import android.provider.Contacts.Intents.Insert;
+import android.provider.Settings;
import android.telephony.PhoneNumberFormattingTextWatcher;
import android.telephony.PhoneNumberUtils;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
import android.text.Editable;
-import android.text.Selection;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.DialerKeyListener;
import android.util.Log;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.internal.telephony.ITelephony;
/**
* Dialer activity that displays the typical twelve key interface.
*/
public class TwelveKeyDialer extends Activity implements View.OnClickListener,
- View.OnLongClickListener, View.OnKeyListener, TextWatcher {
+ View.OnLongClickListener, View.OnKeyListener,
+ AdapterView.OnItemClickListener, TextWatcher {
private static final String TAG = "TwelveKeyDialer";
@@ -75,7 +90,11 @@
private Drawable mDigitsEmptyBackground;
private Drawable mDeleteBackground;
private Drawable mDeleteEmptyBackground;
-
+ private View mDigitsAndBackspace;
+ private View mDialpad;
+ private ListView mDialpadChooser;
+ private DialpadChooserAdapter mDialpadChooserAdapter;
+
// determines if we want to playback local DTMF tones.
private boolean mDTMFToneEnabled;
@@ -84,6 +103,29 @@
/** Indicates if we are opening this dialer to add a call from the InCallScreen. */
private boolean mIsAddCallMode;
+ PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ /**
+ * Listen for phone state changes so that we can take down the
+ * "dialpad chooser" if the phone becomes idle while the
+ * chooser UI is visible.
+ */
+ @Override
+ public void onCallStateChanged(int state, String incomingNumber) {
+ // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
+ // + state + ", '" + incomingNumber + "'");
+ if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
+ // Log.i(TAG, "Call ended with dialpad chooser visible! Taking it down...");
+ // Note there's a race condition in the UI here: the
+ // dialpad chooser could conceivably disappear (on its
+ // own) at the exact moment the user was trying to select
+ // one of the choices, which would be confusing. (But at
+ // least that's better than leaving the dialpad chooser
+ // onscreen, but useless...)
+ showDialpadChooser(false);
+ }
+ }
+ };
+
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing
}
@@ -148,6 +190,17 @@
view.setOnLongClickListener(this);
mDelete = view;
+ mDigitsAndBackspace = (View) findViewById(R.id.digitsAndBackspace);
+ mDialpad = (View) findViewById(R.id.dialpad); // This is null in landscape mode
+
+ // Set up the "dialpad chooser" UI; see showDialpadChooser().
+ mDialpadChooser = (ListView) findViewById(R.id.dialpadChooser);
+ mDialpadChooser.setOnItemClickListener(this);
+ // Add a dummy "footer" view so that the divider under the bottom
+ // item will be visible.
+ // (We set android:footerDividersEnabled="true" on this ListView in XML.)
+ mDialpadChooser.addFooterView(new View(this), null, false);
+
if (!resolveIntent() && icicle != null) {
super.onRestoreInstanceState(icicle);
}
@@ -206,10 +259,14 @@
} else {
intent = getIntent();
}
+ // Log.i(TAG, "==> resolveIntent(): intent: " + intent);
// by default we are not adding a call.
mIsAddCallMode = false;
-
+
+ // By default we don't show the "dialpad chooser" UI.
+ boolean needToShowDialpadChooser = false;
+
// Resolve the intent
final String action = intent.getAction();
if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
@@ -238,8 +295,26 @@
}
}
}
+ } else if (Intent.ACTION_MAIN.equals(action)) {
+ // The MAIN action means we're bringing up a blank dialer
+ // (e.g. by selecting the Home shortcut, or tabbing over from
+ // Contacts or Call log.)
+ //
+ // At this point, IF there's already an active call, there's a
+ // good chance that the user got here accidentally (but really
+ // wanted the in-call dialpad instead). So we bring up an
+ // intermediate UI to make the user confirm what they really
+ // want to do.
+ if (phoneIsInUse()) {
+ // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
+ needToShowDialpadChooser = true;
+ }
}
+ // Bring up the "dialpad chooser" IFF we need to make the user
+ // confirm which dialpad they really want.
+ showDialpadChooser(needToShowDialpadChooser);
+
return ignoreState;
}
@@ -326,12 +401,42 @@
resolveIntent();
}
}
+
+ // While we're in the foreground, listen for phone state changes,
+ // purely so that we can take down the "dialpad chooser" if the
+ // phone becomes idle while the chooser UI is visible.
+ TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
+ telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+
+ // Potentially show hint text in the mDigits field when the user
+ // hasn't typed any digits yet. (If there's already an active call,
+ // this hint text will remind the user that he's about to add a new
+ // call.)
+ //
+ // TODO: consider adding better UI for the case where *both* lines
+ // are currently in use. (Right now we let the user try to add
+ // another call, but that call is guaranteed to fail. Perhaps the
+ // entire dialer UI should be disabled instead.)
+ if (phoneIsInUse()) {
+ mDigits.setHint(R.string.dialerDialpadHintText);
+ } else {
+ // Common case; no hint necessary.
+ mDigits.setHint(null);
+
+ // Also, a sanity-check: the "dialpad chooser" UI should NEVER
+ // be visible if the phone is idle!
+ showDialpadChooser(false);
+ }
}
@Override
protected void onPause() {
super.onPause();
+ // Stop listening for phone state changes.
+ TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
+ telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+
synchronized(mToneGeneratorLock) {
if (mToneGenerator != null) {
mToneStopper.removeMessages(STOP_TONE);
@@ -351,6 +456,11 @@
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
+ // We never show a menu if the "choose dialpad" UI is up.
+ if (dialpadChooserVisible()) {
+ return false;
+ }
+
CharSequence digits = mDigits.getText();
if (digits == null || !TextUtils.isGraphic(digits)) {
mAddToContactMenuItem.setVisible(false);
@@ -591,5 +701,217 @@
mToneStopper.sendEmptyMessageDelayed(STOP_TONE, TONE_LENGTH_MS);
}
}
-}
+ /**
+ * Brings up the "dialpad chooser" UI in place of the usual Dialer
+ * elements (the textfield/button and the dialpad underneath).
+ *
+ * We show this UI if the user brings up the Dialer while a call is
+ * already in progress, since there's a good chance we got here
+ * accidentally (and the user really wanted the in-call dialpad instead).
+ * So in this situation we display an intermediate UI that lets the user
+ * explicitly choose between the in-call dialpad ("Use touch tone
+ * keypad") and the regular Dialer ("Add call"). (Or, the option "Return
+ * to call in progress" just goes back to the in-call UI with no dialpad
+ * at all.)
+ *
+ * @param enabled If true, show the "dialpad chooser" instead
+ * of the regular Dialer UI
+ */
+ private void showDialpadChooser(boolean enabled) {
+ if (enabled) {
+ // Log.i(TAG, "Showing dialpad chooser!");
+ mDigitsAndBackspace.setVisibility(View.GONE);
+ if (mDialpad != null) mDialpad.setVisibility(View.GONE);
+ mDialpadChooser.setVisibility(View.VISIBLE);
+
+ // Instantiate the DialpadChooserAdapter and hook it up to the
+ // ListView. We do this only once.
+ if (mDialpadChooserAdapter == null) {
+ mDialpadChooserAdapter = new DialpadChooserAdapter(this);
+ mDialpadChooser.setAdapter(mDialpadChooserAdapter);
+ }
+ } else {
+ // Log.i(TAG, "Displaying normal Dialer UI.");
+ mDigitsAndBackspace.setVisibility(View.VISIBLE);
+ if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
+ mDialpadChooser.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * @return true if we're currently showing the "dialpad chooser" UI.
+ */
+ private boolean dialpadChooserVisible() {
+ return mDialpadChooser.getVisibility() == View.VISIBLE;
+ }
+
+ /**
+ * Simple list adapter, binding to an icon + text label
+ * for each item in the "dialpad chooser" list.
+ */
+ private static class DialpadChooserAdapter extends BaseAdapter {
+ private LayoutInflater mInflater;
+
+ // Simple struct for a single "choice" item.
+ static class ChoiceItem {
+ String text;
+ Bitmap icon;
+ int id;
+
+ public ChoiceItem(String s, Bitmap b, int i) {
+ text = s;
+ icon = b;
+ id = i;
+ }
+ }
+
+ // IDs for the possible "choices":
+ static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
+ static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
+ static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
+
+ private static final int NUM_ITEMS = 3;
+ private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
+
+ public DialpadChooserAdapter(Context context) {
+ // Cache the LayoutInflate to avoid asking for a new one each time.
+ mInflater = LayoutInflater.from(context);
+
+ // Initialize the possible choices.
+ // TODO: could this be specified entirely in XML?
+
+ // - "Use touch tone keypad"
+ mChoiceItems[0] = new ChoiceItem(
+ context.getString(R.string.dialer_useDtmfDialpad),
+ BitmapFactory.decodeResource(context.getResources(),
+ R.drawable.ic_dialer_fork_tt_keypad),
+ DIALPAD_CHOICE_USE_DTMF_DIALPAD);
+
+ // - "Return to call in progress"
+ mChoiceItems[1] = new ChoiceItem(
+ context.getString(R.string.dialer_returnToInCallScreen),
+ BitmapFactory.decodeResource(context.getResources(),
+ R.drawable.ic_dialer_fork_current_call),
+ DIALPAD_CHOICE_RETURN_TO_CALL);
+
+ // - "Add call"
+ mChoiceItems[2] = new ChoiceItem(
+ context.getString(R.string.dialer_addAnotherCall),
+ BitmapFactory.decodeResource(context.getResources(),
+ R.drawable.ic_dialer_fork_add_call),
+ DIALPAD_CHOICE_ADD_NEW_CALL);
+ }
+
+ public int getCount() {
+ return NUM_ITEMS;
+ }
+
+ /**
+ * Return the ChoiceItem for a given position.
+ */
+ public Object getItem(int position) {
+ return mChoiceItems[position];
+ }
+
+ /**
+ * Return a unique ID for each possible choice.
+ */
+ public long getItemId(int position) {
+ return position;
+ }
+
+ /**
+ * Make a view for each row.
+ */
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // When convertView is non-null, we can reuse it (there's no need
+ // to reinflate it.)
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
+ }
+
+ TextView text = (TextView) convertView.findViewById(R.id.text);
+ text.setText(mChoiceItems[position].text);
+
+ ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
+ icon.setImageBitmap(mChoiceItems[position].icon);
+
+ return convertView;
+ }
+ }
+
+ /**
+ * Handle clicks from the dialpad chooser.
+ */
+ public void onItemClick(AdapterView parent, View v, int position, long id) {
+ DialpadChooserAdapter.ChoiceItem item =
+ (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
+ int itemId = item.id;
+ switch (itemId) {
+ case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
+ // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
+ // Fire off an intent to go back to the in-call UI
+ // with the dialpad visible.
+ returnToInCallScreen(true);
+ break;
+
+ case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
+ // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
+ // Fire off an intent to go back to the in-call UI
+ // (with the dialpad hidden).
+ returnToInCallScreen(false);
+ break;
+
+ case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
+ // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
+ // Ok, guess the user really did want to be here (in the
+ // regular Dialer) after all. Bring back the normal Dialer UI.
+ showDialpadChooser(false);
+ break;
+
+ default:
+ Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
+ break;
+ }
+ }
+
+ /**
+ * Returns to the in-call UI (where there's presumably a call in
+ * progress) in response to the user selecting "use touch tone keypad"
+ * or "return to call" from the dialpad chooser.
+ */
+ private void returnToInCallScreen(boolean showDialpad) {
+ try {
+ ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+ if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
+ } catch (RemoteException e) {
+ Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
+ }
+
+ // Finally, finish() ourselves so that we don't stay on the
+ // activity stack.
+ // Note that we do this whether or not the showCallScreenWithDialpad()
+ // call above had any effect or not! (That call is a no-op if the
+ // phone is idle, which can happen if the current call ends while
+ // the dialpad chooser is up. In this case we can't show the
+ // InCallScreen, and there's no point staying here in the Dialer,
+ // so we just take the user back where he came from...)
+ finish();
+ }
+
+ /**
+ * @return true if the phone is "in use", meaning that at least one line
+ * is active (ie. off hook or ringing or dialing).
+ */
+ private boolean phoneIsInUse() {
+ boolean phoneInUse = false;
+ try {
+ ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+ if (phone != null) phoneInUse = !phone.isIdle();
+ } catch (RemoteException e) {
+ Log.w(TAG, "phone.isIdle() failed", e);
+ }
+ return phoneInUse;
+ }
+}
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index c1e9bc5..9a11f76 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -55,6 +55,8 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
@@ -91,6 +93,7 @@
import android.widget.Toast;
import java.util.ArrayList;
+import java.util.List;
/**
* Displays the details of a specific contact.
@@ -99,11 +102,25 @@
implements View.OnCreateContextMenuListener, View.OnClickListener,
DialogInterface.OnClickListener {
private static final String TAG = "ViewContact";
+ private static final String SHOW_BARCODE_INTENT = "com.google.zxing.client.android.ENCODE";
+
+ private static final String[] PHONE_KEYS = {
+ Contacts.Intents.Insert.PHONE,
+ Contacts.Intents.Insert.SECONDARY_PHONE,
+ Contacts.Intents.Insert.TERTIARY_PHONE
+ };
+
+ private static final String[] EMAIL_KEYS = {
+ Contacts.Intents.Insert.EMAIL,
+ Contacts.Intents.Insert.SECONDARY_EMAIL,
+ Contacts.Intents.Insert.TERTIARY_EMAIL
+ };
private static final int DIALOG_CONFIRM_DELETE = 1;
public static final int MENU_ITEM_DELETE = 1;
public static final int MENU_ITEM_MAKE_DEFAULT = 2;
+ public static final int MENU_ITEM_SHOW_BARCODE = 3;
private Uri mUri;
private ContentResolver mResolver;
@@ -256,8 +273,8 @@
.setTitle(R.string.deleteConfirmation_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.deleteConfirmation)
- .setNegativeButton(R.string.noButton, null)
- .setPositiveButton(R.string.yesButton, this)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok, this)
.setCancelable(false)
.create();
}
@@ -310,6 +327,29 @@
}
@Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ // Perform this check each time the menu is about to be shown, because the Barcode Scanner
+ // could be installed or uninstalled at any time.
+ if (isBarcodeScannerInstalled()) {
+ if (menu.findItem(MENU_ITEM_SHOW_BARCODE) == null) {
+ menu.add(0, MENU_ITEM_SHOW_BARCODE, 0, R.string.menu_showBarcode)
+ .setIcon(R.drawable.ic_menu_show_barcode);
+ }
+ } else {
+ menu.removeItem(MENU_ITEM_SHOW_BARCODE);
+ }
+ return true;
+ }
+
+ private boolean isBarcodeScannerInstalled() {
+ final Intent intent = new Intent(SHOW_BARCODE_INTENT);
+ List<ResolveInfo> list = getPackageManager().queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ return list.size() > 0;
+ }
+
+ @Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
AdapterView.AdapterContextMenuInfo info;
try {
@@ -356,6 +396,44 @@
showDialog(DIALOG_CONFIRM_DELETE);
return true;
}
+ case MENU_ITEM_SHOW_BARCODE:
+ if (mCursor.moveToFirst()) {
+ Intent intent = new Intent(SHOW_BARCODE_INTENT);
+ intent.putExtra("ENCODE_TYPE", "CONTACT_TYPE");
+ Bundle bundle = new Bundle();
+ String name = mCursor.getString(CONTACT_NAME_COLUMN);
+ if (!TextUtils.isEmpty(name)) {
+ bundle.putString(Contacts.Intents.Insert.NAME, name);
+ // The 0th ViewEntry in each ArrayList below is a separator item
+ int entriesToAdd = Math.min(mPhoneEntries.size() - 1, PHONE_KEYS.length);
+ for (int x = 0; x < entriesToAdd; x++) {
+ ViewEntry entry = mPhoneEntries.get(x + 1);
+ bundle.putString(PHONE_KEYS[x], entry.data);
+ }
+ entriesToAdd = Math.min(mEmailEntries.size() - 1, EMAIL_KEYS.length);
+ for (int x = 0; x < entriesToAdd; x++) {
+ ViewEntry entry = mEmailEntries.get(x + 1);
+ bundle.putString(EMAIL_KEYS[x], entry.data);
+ }
+ if (mPostalEntries.size() >= 2) {
+ ViewEntry entry = mPostalEntries.get(1);
+ bundle.putString(Contacts.Intents.Insert.POSTAL, entry.data);
+ }
+ intent.putExtra("ENCODE_DATA", bundle);
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ // The check in onPrepareOptionsMenu() should make this impossible, but
+ // for safety I'm catching the exception rather than crashing. Ideally
+ // I'd call Menu.removeItem() here too, but I don't see a way to get
+ // the options menu.
+ Log.e(TAG, "Show barcode menu item was clicked but Barcode Scanner " +
+ "was not installed.");
+ }
+ return true;
+ }
+ }
+ break;
}
return super.onOptionsItemSelected(item);
}