auto import from //branches/cupcake_rel/...@138607
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 39e127a..b9dc4f2 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -73,6 +73,7 @@
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Locale;
/**
* Displays a list of contacts. Usually is embedded into the ContactsActivity.
@@ -83,7 +84,7 @@
private static final String LIST_STATE_KEY = "liststate";
private static final String FOCUS_KEY = "focused";
-
+
static final int MENU_ITEM_VIEW_CONTACT = 1;
static final int MENU_ITEM_CALL = 2;
static final int MENU_ITEM_EDIT_BEFORE_CALL = 3;
@@ -169,6 +170,7 @@
static final String NAME_COLUMN = People.DISPLAY_NAME;
+ static final String SORT_STRING = People.SORT_STRING;
static final String[] CONTACTS_PROJECTION = new String[] {
People._ID, // 0
@@ -180,6 +182,7 @@
People.PRIMARY_PHONE_ID, // 6
People.PRIMARY_EMAIL_ID, // 7
People.PRESENCE_STATUS, // 8
+ SORT_STRING, // 9
};
static final String[] STREQUENT_PROJECTION = new String[] {
@@ -227,6 +230,7 @@
static final int PRIMARY_EMAIL_ID_COLUMN_INDEX = 7;
static final int SERVER_STATUS_COLUMN_INDEX = 8;
static final int PHOTO_COLUMN_INDEX = 9;
+ static final int SORT_STRING_INDEX = 9;
static final int PHONES_PERSON_ID_INDEX = 6;
static final int CONTACT_METHODS_PERSON_ID_INDEX = 6;
@@ -234,9 +238,7 @@
static final int DISPLAY_GROUP_INDEX_ALL_CONTACTS = 0;
static final int DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES = 1;
static final int DISPLAY_GROUP_INDEX_MY_CONTACTS = 2;
-
- static final String SORT_ORDER = NAME_COLUMN + " COLLATE LOCALIZED ASC";
-
+
private static final int QUERY_TOKEN = 42;
private static final String[] GROUPS_PROJECTION = new String[] {
@@ -1137,6 +1139,15 @@
}
}
+ private static String getSortOrder(String[] projectionType) {
+ if (Locale.getDefault().equals(Locale.JAPAN) &&
+ projectionType == CONTACTS_PROJECTION) {
+ return SORT_STRING + " ASC";
+ } else {
+ return NAME_COLUMN + " COLLATE LOCALIZED ASC";
+ }
+ }
+
void startQuery() {
mAdapter.setLoading(true);
@@ -1147,7 +1158,8 @@
switch (mMode) {
case MODE_GROUP:
mQueryHandler.startQuery(QUERY_TOKEN, null,
- mGroupUri, CONTACTS_PROJECTION, null, null, SORT_ORDER);
+ mGroupUri, CONTACTS_PROJECTION, null, null,
+ getSortOrder(CONTACTS_PROJECTION));
break;
case MODE_ALL_CONTACTS:
@@ -1155,18 +1167,20 @@
case MODE_PICK_OR_CREATE_CONTACT:
case MODE_INSERT_OR_EDIT_CONTACT:
mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
- null, null, SORT_ORDER);
+ null, null, getSortOrder(CONTACTS_PROJECTION));
break;
case MODE_WITH_PHONES:
mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
- People.PRIMARY_PHONE_ID + " IS NOT NULL", null, SORT_ORDER);
+ People.PRIMARY_PHONE_ID + " IS NOT NULL", null,
+ getSortOrder(CONTACTS_PROJECTION));
break;
case MODE_QUERY: {
mQuery = getIntent().getStringExtra(SearchManager.QUERY);
mQueryHandler.startQuery(QUERY_TOKEN, null, getPeopleFilterUri(mQuery),
- CONTACTS_PROJECTION, null, null, SORT_ORDER);
+ CONTACTS_PROJECTION, null, null,
+ getSortOrder(CONTACTS_PROJECTION));
break;
}
@@ -1182,27 +1196,31 @@
ssp = data.getSchemeSpecificPart();
mCreateData = ssp;
}
+
+ mCreateExtras = new Bundle();
+ Bundle originalExtras = getIntent().getExtras();
+ if (originalExtras != null) {
+ mCreateExtras.putAll(originalExtras);
+ }
if ("mailto".equals(scheme)) {
- mCreateExtras = new Bundle();
- mCreateExtras.putAll(getIntent().getExtras());
mCreateExtras.putString(Intents.Insert.EMAIL, ssp);
mCreatePersonIndex = CONTACT_METHODS_PERSON_ID_INDEX;
mQueryHandler.startQuery(QUERY_TOKEN, null,
ContactMethods.CONTENT_URI, CONTACT_METHODS_PROJECTION,
ContactMethodsColumns.KIND + "=" + Contacts.KIND_EMAIL + " AND " +
- ContactMethods.DATA + "=?", new String[] { ssp }, SORT_ORDER);
+ ContactMethods.DATA + "=?", new String[] { ssp },
+ getSortOrder(CONTACT_METHODS_PROJECTION));
} else if ("tel".equals(scheme)) {
- mCreateExtras = new Bundle();
- mCreateExtras.putAll(getIntent().getExtras());
mCreateExtras.putString(Intents.Insert.PHONE, ssp);
mCreatePersonIndex = PHONES_PERSON_ID_INDEX;
mQueryHandler.startQuery(QUERY_TOKEN, null,
Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, ssp),
- PHONES_PROJECTION, null, null, SORT_ORDER);
+ PHONES_PROJECTION, null, null,
+ getSortOrder(PHONES_PROJECTION));
} else {
Log.w(TAG, "Invalid intent:" + getIntent());
@@ -1213,14 +1231,16 @@
}
case MODE_STARRED:
- mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
- People.STARRED + "=1", null, SORT_ORDER);
+ mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI,
+ CONTACTS_PROJECTION,
+ People.STARRED + "=1", null, getSortOrder(CONTACTS_PROJECTION));
break;
case MODE_FREQUENT:
- mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
+ mQueryHandler.startQuery(QUERY_TOKEN, null,
+ People.CONTENT_URI, CONTACTS_PROJECTION,
People.TIMES_CONTACTED + " > 0", null,
- People.TIMES_CONTACTED + " DESC, " + NAME_COLUMN + " ASC");
+ People.TIMES_CONTACTED + " DESC, " + getSortOrder(CONTACTS_PROJECTION));
break;
case MODE_STREQUENT:
@@ -1231,13 +1251,14 @@
case MODE_PICK_PHONE:
mQueryHandler.startQuery(QUERY_TOKEN, null, Phones.CONTENT_URI, PHONES_PROJECTION,
- null, null, SORT_ORDER);
+ null, null, getSortOrder(PHONES_PROJECTION));
break;
case MODE_PICK_POSTAL:
mQueryHandler.startQuery(QUERY_TOKEN, null, ContactMethods.CONTENT_URI,
CONTACT_METHODS_PROJECTION,
- ContactMethods.KIND + "=" + Contacts.KIND_POSTAL, null, SORT_ORDER);
+ ContactMethods.KIND + "=" + Contacts.KIND_POSTAL, null,
+ getSortOrder(CONTACT_METHODS_PROJECTION));
break;
}
}
@@ -1259,7 +1280,8 @@
} else {
uri = Uri.withAppendedPath(mGroupFilterUri, Uri.encode(filter));
}
- return resolver.query(uri, CONTACTS_PROJECTION, null, null, SORT_ORDER);
+ return resolver.query(uri, CONTACTS_PROJECTION, null, null,
+ getSortOrder(CONTACTS_PROJECTION));
}
case MODE_ALL_CONTACTS:
@@ -1267,23 +1289,25 @@
case MODE_PICK_OR_CREATE_CONTACT:
case MODE_INSERT_OR_EDIT_CONTACT: {
return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION, null, null,
- SORT_ORDER);
+ getSortOrder(CONTACTS_PROJECTION));
}
case MODE_WITH_PHONES: {
return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
- People.PRIMARY_PHONE_ID + " IS NOT NULL", null, SORT_ORDER);
+ People.PRIMARY_PHONE_ID + " IS NOT NULL", null,
+ getSortOrder(CONTACTS_PROJECTION));
}
case MODE_STARRED: {
return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
- People.STARRED + "=1", null, SORT_ORDER);
+ People.STARRED + "=1", null, getSortOrder(CONTACTS_PROJECTION));
}
case MODE_FREQUENT: {
return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
People.TIMES_CONTACTED + " > 0", null,
- People.TIMES_CONTACTED + " DESC, " + NAME_COLUMN + " ASC");
+ People.TIMES_CONTACTED + " DESC, " + getSortOrder(CONTACTS_PROJECTION));
+
}
case MODE_STREQUENT: {
@@ -1305,7 +1329,8 @@
} else {
uri = Phones.CONTENT_URI;
}
- return resolver.query(uri, PHONES_PROJECTION, null, null, SORT_ORDER);
+ return resolver.query(uri, PHONES_PROJECTION, null, null,
+ getSortOrder(PHONES_PROJECTION));
}
}
throw new UnsupportedOperationException("filtering not allowed in mode " + mMode);
@@ -1475,7 +1500,7 @@
private final class ContactItemListAdapter extends ResourceCursorAdapter
implements SectionIndexer {
- private AlphabetIndexer mIndexer;
+ private SectionIndexer mIndexer;
private String mAlphabet;
private boolean mLoading = true;
private CharSequence mUnknownNameText;
@@ -1507,6 +1532,14 @@
}
}
+ private SectionIndexer getNewIndexer(Cursor cursor) {
+ if (Locale.getDefault().equals(Locale.JAPAN)) {
+ return new JapaneseContactListIndexer(cursor, SORT_STRING_INDEX);
+ } else {
+ return new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
+ }
+ }
+
/**
* Callback on the UI thread when the content observer on the backing cursor fires.
* Instead of calling requery we need to do an async query so that the requery doesn't
@@ -1682,9 +1715,21 @@
private void updateIndexer(Cursor cursor) {
if (mIndexer == null) {
- mIndexer = new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
+ mIndexer = getNewIndexer(cursor);
} else {
- mIndexer.setCursor(cursor);
+ if (Locale.getDefault().equals(Locale.JAPAN)) {
+ if (mIndexer instanceof JapaneseContactListIndexer) {
+ ((JapaneseContactListIndexer)mIndexer).setCursor(cursor);
+ } else {
+ mIndexer = getNewIndexer(cursor);
+ }
+ } else {
+ if (mIndexer instanceof AlphabetIndexer) {
+ ((AlphabetIndexer)mIndexer).setCursor(cursor);
+ } else {
+ mIndexer = getNewIndexer(cursor);
+ }
+ }
}
}
@@ -1716,13 +1761,16 @@
// No cursor, the section doesn't exist so just return 0
return 0;
}
- mIndexer = new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
+ mIndexer = getNewIndexer(cursor);
}
return mIndexer.getPositionForSection(sectionIndex);
}
public int getSectionForPosition(int position) {
+ // Note: JapaneseContactListIndexer depends on the fact
+ // this method always returns 0. If you change this,
+ // please care it too.
return 0;
}
}
diff --git a/src/com/android/contacts/JapaneseContactListIndexer.java b/src/com/android/contacts/JapaneseContactListIndexer.java
new file mode 100644
index 0000000..d5d6dcd
--- /dev/null
+++ b/src/com/android/contacts/JapaneseContactListIndexer.java
@@ -0,0 +1,187 @@
+/*
+ * 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.database.Cursor;
+import android.database.DataSetObserver;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.widget.SectionIndexer;
+
+/**
+ * SectionIndexer which is for "phonetically sortable" String. This class heavily depends on the
+ * algorithm of the SQL function "GET_PHONETICALLY_SORTABLE_STRING", whose implementation
+ * is written in C++.
+ */
+public final class JapaneseContactListIndexer extends DataSetObserver implements SectionIndexer {
+ private static String TAG = "JapaneseContactListIndexer";
+
+ static private final String[] sSections = {
+ " ", // Sections of SectionIndexer should start with " " (some components assume it).
+ "\u3042", "\u304B", "\u3055", "\u305F", "\u306A", // a, ka, sa, ta, na
+ "\u306F", "\u307E", "\u3084", "\u3089", "\u308F", // ha, ma, ya, ra, wa
+ "\uFF21", "\uFF22", "\uFF23", "\uFF24", "\uFF25", // full-width ABCDE
+ "\uFF26", "\uFF27", "\uFF28", "\uFF29", "\uFF2A", // full-width FGHIJ
+ "\uFF2B", "\uFF2C", "\uFF2D", "\uFF2E", "\uFF2F", // full-width KLMNO
+ "\uFF30", "\uFF31", "\uFF32", "\uFF33", "\uFF34", // full-width PQRST
+ "\uFF35", "\uFF36", "\uFF37", "\uFF38", "\uFF39", // full-width UVWXY
+ "\uFF40", // full-width Z
+ "\u6570", "\u8A18" // alphabets, numbers, symbols
+ };
+ static private final int sSectionsLength = sSections.length;
+
+ private int mColumnIndex;
+ private Cursor mDataCursor;
+ private SparseIntArray mStringMap;
+
+ public JapaneseContactListIndexer(Cursor cursor, int columnIndex) {
+ int len = sSections.length;
+ mColumnIndex = columnIndex;
+ mDataCursor = cursor;
+ mStringMap = new SparseIntArray(sSectionsLength);
+ if (cursor != null) {
+ cursor.registerDataSetObserver(this);
+ }
+ }
+
+ public void setCursor(Cursor cursor) {
+ if (mDataCursor != null) {
+ mDataCursor.unregisterDataSetObserver(this);
+ }
+ mDataCursor = cursor;
+ if (cursor != null) {
+ mDataCursor.registerDataSetObserver(this);
+ }
+ }
+
+ private int getSectionCodePoint(int index) {
+ if (index < sSections.length - 2) {
+ return sSections[index].codePointAt(0);
+ } else if (index == sSections.length - 2) {
+ return 0xFF66; // Numbers are mapped from 0xFF66.
+ } else { // index == mSections.length - 1
+ return 0xFF70; // Symbols are mapped from 0xFF70.
+ }
+ }
+
+ public int getPositionForSection(int sectionIndex) {
+ final SparseIntArray stringMap = mStringMap;
+ final Cursor cursor = mDataCursor;
+
+ if (cursor == null || sectionIndex <= 0) {
+ return 0;
+ }
+
+ if (sectionIndex >= sSectionsLength) {
+ sectionIndex = sSectionsLength - 1;
+ }
+
+ int savedCursorPos = cursor.getPosition();
+
+ String targetLetter = sSections[sectionIndex];
+ int key = targetLetter.codePointAt(0);
+
+ // Check cache map
+ {
+ int tmp = stringMap.get(key, Integer.MIN_VALUE);
+ if (Integer.MIN_VALUE != tmp) {
+ return tmp;
+ }
+ }
+
+ int end = cursor.getCount();
+ int pos = 0;
+
+ {
+ // Note that sectionIndex > 0.
+ int prevLetter = sSections[sectionIndex - 1].codePointAt(0);
+ int prevLetterPos = stringMap.get(prevLetter, Integer.MIN_VALUE);
+ if (prevLetterPos != Integer.MIN_VALUE) {
+ pos = prevLetterPos;
+ }
+ }
+
+ // Do rough binary search if there are a lot of entries.
+ while (end - pos > 100) {
+ int tmp = (end + pos) / 2;
+ cursor.moveToPosition(tmp);
+ String sort_name;
+ do {
+ sort_name = cursor.getString(mColumnIndex);
+ if (sort_name == null || sort_name.length() == 0) {
+ // This should not happen, since sort_name field is created
+ // automatically when syncing to a server, or creating/editing
+ // the entry...
+ Log.e(TAG, "sort_name is null or its length is 0. index: " + tmp);
+ cursor.moveToNext();
+ tmp++;
+ continue;
+ }
+ break;
+ } while (tmp < end);
+ if (tmp == end) {
+ break;
+ }
+ int codePoint = sort_name.codePointAt(0);
+ if (codePoint < getSectionCodePoint(sectionIndex)) {
+ pos = tmp;
+ } else {
+ end = tmp;
+ }
+ }
+
+ for (cursor.moveToPosition(pos); !cursor.isAfterLast(); ++pos, cursor.moveToNext()) {
+ String sort_name = cursor.getString(mColumnIndex);
+ if (sort_name == null || sort_name.length() == 0) {
+ // This should not happen, since sort_name field is created
+ // automatically when syncing to a server, or creating/editing
+ // the entry...
+ Log.e(TAG, "sort_name is null or its length is 0. index: " + pos);
+ continue;
+ }
+ int codePoint = sort_name.codePointAt(0);
+ if (codePoint >= getSectionCodePoint(sectionIndex)) {
+ break;
+ }
+ }
+
+ stringMap.put(key, pos);
+ cursor.moveToPosition(savedCursorPos);
+ return pos;
+ }
+
+ public int getSectionForPosition(int position) {
+ // Not used in Contacts. Ignore for now.
+ return 0;
+ }
+
+ public Object[] getSections() {
+ return sSections;
+ }
+
+ @Override
+ public void onChanged() {
+ super.onChanged();
+ mStringMap.clear();
+ }
+
+ @Override
+ public void onInvalidated() {
+ super.onInvalidated();
+ mStringMap.clear();
+ }
+}