Change Aizy to use a primary alphabet and fixed letter distances
Change-Id: I4da56785e99d51c893ba0e5097b37da751382fb7
diff --git a/res/layout-xlarge/contacts_list_content.xml b/res/layout-xlarge/contacts_list_content.xml
new file mode 100644
index 0000000..403a46a
--- /dev/null
+++ b/res/layout-xlarge/contacts_list_content.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pinned_header_list_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ >
+
+ <com.android.contacts.list.ContactListAizyView
+ android:id="@+id/contacts_list_aizy"
+ android:layout_width="40dip"
+ android:layout_height="match_parent"
+ />
+
+ <LinearLayout
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_weight="1"
+ >
+
+ <view
+ class="com.android.contacts.ContactEntryListView"
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:fastScrollEnabled="true"
+ android:layout_weight="1"
+ />
+
+ <include layout="@layout/contacts_list_empty"/>
+
+ <ViewStub android:id="@+id/footer_stub"
+ android:layout="@layout/footer_panel"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ />
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/aizy_popup_window.xml b/res/layout/aizy_popup_window.xml
index 470f116..801b481 100644
--- a/res/layout/aizy_popup_window.xml
+++ b/res/layout/aizy_popup_window.xml
@@ -22,11 +22,11 @@
>
<TextView
- android:layout_width="wrap_content"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/caption"
android:width="75dip"
- android:textSize="50sp"
+ android:textSize="70sp"
android:paddingLeft="20dip"
android:gravity="center" />
diff --git a/res/layout/contacts_list_content.xml b/res/layout/contacts_list_content.xml
index f5bb785..8d1c50a 100644
--- a/res/layout/contacts_list_content.xml
+++ b/res/layout/contacts_list_content.xml
@@ -22,12 +22,6 @@
android:orientation="horizontal"
>
- <com.android.contacts.list.ContactListAizyView
- android:id="@+id/contacts_list_aizy"
- android:layout_width="50dip"
- android:layout_height="match_parent"
- />
-
<LinearLayout
android:layout_width="0px"
android:layout_height="match_parent"
@@ -52,4 +46,9 @@
android:layout_height="wrap_content"
/>
</LinearLayout>
+ <com.android.contacts.list.ContactListAizyView
+ android:id="@+id/contacts_list_aizy"
+ android:layout_width="30dip"
+ android:layout_height="match_parent"
+ />
</LinearLayout>
diff --git a/res/values-xlarge/dimens.xml b/res/values-xlarge/dimens.xml
new file mode 100644
index 0000000..e99b257
--- /dev/null
+++ b/res/values-xlarge/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<resources>
+ <!-- Size of the text in the aizy visual scroll control -->
+ <dimen name="aizy_text_size">17dip</dimen>
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 5f39532..c475148 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -26,4 +26,10 @@
<color name="pinned_header_background">#ff202020</color>
<color name="translucent_search_background">#cc000000</color>
+
+ <!-- Color used in the Aizy visual scroll control for empty section -->
+ <color name="aizy_empty_section">#ff666666</color>
+
+ <!-- Color used in the Aizy visual scroll control for non-empty sections -->
+ <color name="aizy_non_empty_section">#ffcccccc</color>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index ff9e92e..b8b06ff 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -45,4 +45,8 @@
<dimen name="aggregation_suggestion_icon_size">40dip</dimen>
+ <!-- Size of the text in the aizy visual scroll control -->
+ <dimen name="aizy_text_size">13dip</dimen>
+ <dimen name="aizy_preview_width">130dip</dimen>
+ <dimen name="aizy_preview_height">115dip</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5bdfdf8..085b9ea 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1268,4 +1268,13 @@
<!-- The button next to the message about multiple contact aggregation suggestions in Contact editor. [CHAR LIMIT=12]-->
<string name="aggregation_suggestion_view_button">View</string>
+ <!-- Primary alphabet of this language. Each of these characters always has its own section in
+ the visual scroll control next to the contact list. These letters must be uppercase.
+ While there is no hard limit on the number of characters, there should not be more than
+ 40. If the language requires more, this string should instead be empty so that the
+ visual scroll control adapts to the contents.
+ This text must only contain letters that can appear as sections, otherwise they would
+ only be empty.
+ Translations require extensive QA! -->
+ <string name="visualScrollerAlphabet">A;B;C;D;E;F;G;H;I;J;K;L;M;N;O;P;Q;R;S;T;U;V;W;X;Y;Z</string>
</resources>
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index ee1cfe0..708cdd8 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -361,7 +361,7 @@
mAdapter.changeCursor(partitionIndex, data);
showCount(partitionIndex, data);
if (partitionIndex == mAdapter.getIndexedPartition()) {
- mAizy.setIndexer(mAdapter.getIndexer());
+ mAizy.readFromIndexer(mAdapter.getIndexer());
}
// TODO should probably only restore instance state after all directories are loaded
@@ -603,7 +603,12 @@
}
mAizy = (ContactListAizyView) mView.findViewById(R.id.contacts_list_aizy);
- mAizy.setListView(mListView);
+ mAizy.setListener(new ContactListAizyView.Listener() {
+ @Override
+ public void onScroll(int position) {
+ mListView.setSelectionFromTop(position + mListView.getHeaderViewsCount(), 0);
+ }
+ });
configureAizy();
configurePhotoLoader();
@@ -652,11 +657,9 @@
}
}
+ @Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
- if (isAizyEnabled()) {
- mAizy.listOnScroll(firstVisibleItem);
- }
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
diff --git a/src/com/android/contacts/list/ContactListAizyView.java b/src/com/android/contacts/list/ContactListAizyView.java
index 07ce12c..a2fa706 100644
--- a/src/com/android/contacts/list/ContactListAizyView.java
+++ b/src/com/android/contacts/list/ContactListAizyView.java
@@ -17,21 +17,27 @@
package com.android.contacts.list;
import com.android.contacts.R;
+import com.android.contacts.util.PhonebookCollatorFactory;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.FontMetrics;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.widget.ListView;
import android.widget.PopupWindow;
import android.widget.SectionIndexer;
import android.widget.TextView;
+import java.text.Collator;
+import java.util.ArrayList;
+
/**
* A View that displays the sections given by an Indexer and their relative sizes. For
* English and similar languages, this is an A to Z list (where only the used letters are
@@ -41,23 +47,50 @@
public class ContactListAizyView extends View {
private static final String TAG = "ContactListAizyView";
- // TODO: Put these into resource files or create from image resources
- private static final int TEXT_WIDTH = 20;
- private static final int CIRCLE_DIAMETER = 30;
- private static final int PREVIEW_WIDTH = 130;
- private static final int PREVIEW_HEIGHT = 115;
+ private static final int PREVIEW_TIME_DELAY_MS = 400;
- private SectionIndexer mIndexer;
-
- private boolean mCalculateYCoordinates;
- private ListView mListView;
- private float mPosition;
- private float mFactor;
+ private Listener mListener;
private PopupWindow mPreviewPopupWindow;
private TextView mPreviewPopupTextView;
+
+ private ResourceValues mResourceValues;
+
+ /**
+ * True if the popup window is currently visible.
+ */
private boolean mPreviewPopupVisible;
+
+ /**
+ * Time when the user started tapping. This is used to calculate the time delay before fading
+ * in the PopupWindow
+ */
+ private long mPreviewPopupStartTime;
+
+ /**
+ * Needed only inside {@link #onTouchEvent(MotionEvent)} to get the location of touch events.
+ */
private int[] mWindowOffset;
- private float[] yPositions = null;
+
+ /**
+ * Needed to measure text. Used inside {@link #onDraw(Canvas)}
+ */
+ private final Rect bounds = new Rect();
+
+ /**
+ * Used and cached inside {@link #onDraw(Canvas)}
+ */
+ private FontMetrics mFontMetrics;
+
+ /**
+ * Used and cached inside {@link #onDraw(Canvas)}
+ */
+ private Paint mPaint;
+
+ /**
+ * The list of displayed sections. "Virtual" sections can be empty and therefore don't show
+ * up as regular sections
+ */
+ private final ArrayList<VirtualSection> mVirtualSections = new ArrayList<VirtualSection>();
public ContactListAizyView(Context context) {
super(context);
@@ -75,41 +108,120 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ mResourceValues = new ResourceValues(getResources());
+
final LayoutInflater inflater =
(LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mPreviewPopupWindow = new PopupWindow(
inflater.inflate(R.layout.aizy_popup_window, null, false),
- PREVIEW_WIDTH, PREVIEW_HEIGHT);
+ (int) mResourceValues.previewWidth, (int) mResourceValues.previewHeight);
+ mPreviewPopupWindow.setAnimationStyle(android.R.style.Animation_Toast);
mPreviewPopupTextView =
(TextView) mPreviewPopupWindow.getContentView().findViewById(R.id.caption);
}
- public void setIndexer(SectionIndexer indexer) {
- mIndexer = indexer;
- mCalculateYCoordinates = true;
+ /**
+ * Sets up the Aizy based on the indexer and completely reads its contents.
+ * This function has to be called everytime the data is changed.
+ */
+ public void readFromIndexer(SectionIndexer indexer) {
+ mVirtualSections.clear();
+ final String alphabetString = getResources().getString(R.string.visualScrollerAlphabet);
+ final String[] alphabet = alphabetString.split(";");
+
+ // We expect to get 10 additional items that the base alphabet
+ mVirtualSections.ensureCapacity(alphabet.length + 10);
+
+ if (indexer != null) {
+ // Add the real sections
+ final Object[] sections = indexer.getSections();
+ for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
+ final Object section = sections[sectionIndex];
+ final String caption = section == null ? "" : section.toString();
+ final int position = indexer.getPositionForSection(sectionIndex);
+ mVirtualSections.add(new VirtualSection(caption, sectionIndex, position));
+ }
+ }
+
+ final Collator collator = PhonebookCollatorFactory.getCollator();
+
+ // Add the base alphabet if missing
+ for (String caption : alphabet) {
+ boolean insertAtEnd = true;
+ VirtualSection previousVirtualSection = null;
+ for (int i = 0; i < mVirtualSections.size(); i++) {
+ final VirtualSection virtualSection = mVirtualSections.get(i);
+ final String virtualSectionCaption = virtualSection.getCaption();
+ final int comparison = collator.compare(virtualSectionCaption, caption);
+ if (comparison == 0) {
+ // element is already in the list.
+ insertAtEnd = false;
+ break;
+ }
+ if (comparison > 0) {
+ // we stepped too far. the element belongs before the element at i
+ insertAtEnd = false;
+ final int realSectionPosition = previousVirtualSection == null ? 0
+ : previousVirtualSection.getRealSectionPosition();
+ mVirtualSections.add(i, new VirtualSection(caption, -1, realSectionPosition));
+ break;
+ }
+ previousVirtualSection = virtualSection;
+ }
+ if (insertAtEnd) {
+ final int realSectionPosition = previousVirtualSection == null ? 0
+ : previousVirtualSection.getRealSectionPosition();
+ mVirtualSections.add(new VirtualSection(caption, -1, realSectionPosition));
+ }
+ }
+ invalidate();
}
- public void setListView(ListView listView) {
- mListView = listView;
+ /**
+ * Sets the Listener that is called everytime the user taps on this control.
+ */
+ public void setListener(Listener listener) {
+ mListener = listener;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(TEXT_WIDTH + CIRCLE_DIAMETER, resolveSize(0, heightMeasureSpec));
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- mCalculateYCoordinates = true;
+ setMeasuredDimension(resolveSize(0, widthMeasureSpec), resolveSize(0, heightMeasureSpec));
}
@Override
protected void onDraw(Canvas canvas) {
- if (mIndexer == null) return;
+ if (mPaint == null) {
+ mPaint = new Paint();
+ mPaint.setTextSize(mResourceValues.textSize);
+ mPaint.setAntiAlias(true);
+ mPaint.setTextAlign(Align.CENTER);
+ }
+ if (mFontMetrics == null) {
+ mFontMetrics = mPaint.getFontMetrics();
+ }
+ final float fontHeight = mFontMetrics.descent - mFontMetrics.ascent;
+ final int halfWidth = getWidth() / 2;
+ // Draw
+ float lastVisibleY = Float.NEGATIVE_INFINITY;
+ final float sectionHeight = (float) getHeight() / mVirtualSections.size();
+ for (int i = 0; i < mVirtualSections.size(); i++) {
+ final VirtualSection virtualSection = mVirtualSections.get(i);
+ final String caption = virtualSection.getCaption();
+ if (!virtualSection.isMeasured()) {
+ mPaint.getTextBounds(caption, 0, caption.length(), bounds);
+ virtualSection.setMeasuredSize(-bounds.top);
+ }
+ final float y = i * sectionHeight;
+ if (lastVisibleY + fontHeight < y) {
+ mPaint.setColor(virtualSection.getRealSectionIndex() != -1
+ ? mResourceValues.nonEmptySectionColor : mResourceValues.emptySectionColor);
- calcYCoordinates();
-
- drawLineAndText(canvas);
+ canvas.drawText(caption, halfWidth,
+ y + sectionHeight / 2 + virtualSection.getMeasuredSize() / 2, mPaint);
+ lastVisibleY = y;
+ }
+ }
}
@Override
@@ -119,10 +231,31 @@
getLocationInWindow(mWindowOffset);
}
+ // Scroll the list itself
+ final int boundedY = Math.min(Math.max(0, (int) (event.getY())), getHeight() - 1);
+ final int index = boundedY * mVirtualSections.size() / getHeight();
+ final VirtualSection virtualSection = mVirtualSections.get(index);
+ final int sectionY = index * getHeight() / mVirtualSections.size();
+ mPreviewPopupTextView.setText(virtualSection.getCaption());
+ mPreviewPopupTextView.setTextColor(virtualSection.getRealSectionIndex() != -1
+ ? mResourceValues.nonEmptySectionColor : mResourceValues.emptySectionColor);
+
+ // Draw popup window
final int previewX = mWindowOffset[0] + getWidth();
- final int previewY = (int) event.getY() + mWindowOffset[1]
- - mPreviewPopupWindow.getHeight() / 2;
- final boolean previewPopupVisible = event.getActionMasked() == MotionEvent.ACTION_MOVE;
+ final float sectionHeight = (float) getHeight() / mVirtualSections.size();
+ final int previewY = (int) (sectionY + mWindowOffset[1] + (sectionHeight -
+ mPreviewPopupWindow.getHeight()) / 2);
+ final int actionMasked = event.getActionMasked();
+ final boolean fingerIsDown = actionMasked == MotionEvent.ACTION_DOWN;
+ if (fingerIsDown) {
+ mPreviewPopupStartTime = System.currentTimeMillis();
+ }
+ final boolean fingerIsDownOrScrubbing =
+ actionMasked == MotionEvent.ACTION_MOVE || actionMasked == MotionEvent.ACTION_DOWN;
+
+ final boolean previewPopupVisible = fingerIsDownOrScrubbing &&
+ (System.currentTimeMillis() > mPreviewPopupStartTime + PREVIEW_TIME_DELAY_MS);
+
if (previewPopupVisible != mPreviewPopupVisible) {
if (previewPopupVisible) {
mPreviewPopupWindow.showAtLocation(this, Gravity.LEFT | Gravity.TOP,
@@ -134,90 +267,71 @@
} else {
mPreviewPopupWindow.update(previewX, previewY, -1, -1);
}
- final int position = Math.max(0, (int) (event.getY() / mFactor));
- if (mIndexer != null) {
- final int index = mIndexer.getSectionForPosition(position);
- final Object[] sections = mIndexer.getSections();
- final String caption =
- (index != -1 && index < sections.length) ? sections[index].toString() : "";
- mPreviewPopupTextView.setText(caption);
- }
- if (mListView != null) {
- mListView.setSelectionFromTop(position, 0);
- }
+
+ // Perform the actual scrolling
+ if (mListener != null) mListener.onScroll(virtualSection.getRealSectionPosition());
super.onTouchEvent(event);
return true;
}
- private void calcYCoordinates() {
- if (!mCalculateYCoordinates) return;
- mCalculateYCoordinates = false;
+ /**
+ * Reads an provides all values from the resource files
+ */
+ private static class ResourceValues {
+ private final int emptySectionColor;
+ private final int nonEmptySectionColor;
+ private final float textSize;
+ private final float previewWidth;
+ private final float previewHeight;
- // Get a String[] of the sections.
- final Object[] sectionObjects = mIndexer.getSections();
- final int sectionCount = sectionObjects.length;
- final String[] sections;
- if (sectionObjects instanceof String[]) {
- sections = (String[]) sectionObjects;
- } else {
- sections = new String[sectionCount];
- for (int i = 0; i < sectionCount; i++) {
- sections[i] = sectionObjects[i] == null ? null : sectionObjects[i].toString();
- }
+ private ResourceValues(Resources resources) {
+ emptySectionColor = resources.getColor(R.color.aizy_empty_section);
+ nonEmptySectionColor = resources.getColor(R.color.aizy_non_empty_section);
+ textSize = resources.getDimension(R.dimen.aizy_text_size);
+ previewWidth = resources.getDimension(R.dimen.aizy_preview_width);
+ previewHeight = resources.getDimension(R.dimen.aizy_preview_height);
}
-
- mFactor = (float) getHeight() / mListView.getCount();
}
- private void drawLineAndText(Canvas canvas) {
- // TODO: Figure out how to set the text size and fetch the height in pixels. This
- // behaviour is OK for prototypes, but has to be refined later
- final float textSize = 20.0f;
+ private static class VirtualSection {
+ private final String mCaption;
+ private final int mRealSectionIndex;
+ private final int mRealSectionPosition;
+ private float mMeasuredSize = Float.NaN;
- // Move A down, Z up
- final Paint paint = new Paint();
- paint.setColor(Color.LTGRAY);
- paint.setTextSize(textSize);
- paint.setAntiAlias(true);
- final Object[] sections = mIndexer.getSections();
- canvas.drawLine(
- TEXT_WIDTH + CIRCLE_DIAMETER * 0.5f, 0.0f,
- TEXT_WIDTH + CIRCLE_DIAMETER * 0.5f, getHeight(),
- paint);
- final int sectionCount = sections.length;
- if (yPositions == null || yPositions.length != sectionCount) {
- yPositions = new float[sectionCount];
+ public String getCaption() {
+ return mCaption;
}
- // Calculate Positions
- for (int i = 0; i < sectionCount; i++) {
- yPositions[i] = mIndexer.getPositionForSection(i) * mFactor;
+ public int getRealSectionIndex() {
+ return mRealSectionIndex;
}
- // Draw
- float lastVisibleY = Float.MAX_VALUE;
- for (int i = sectionCount - 1; i >= 0; i--) {
- final float y = yPositions[i];
- if (lastVisibleY - textSize > y) {
- canvas.drawText(sections[i].toString(), 0.0f, y + 0.5f * textSize, paint);
- lastVisibleY = y;
- }
- canvas.drawLine(
- TEXT_WIDTH + CIRCLE_DIAMETER * 0.5f - 2, y,
- TEXT_WIDTH + CIRCLE_DIAMETER * 0.5f + 2, y,
- paint);
+ public int getRealSectionPosition() {
+ return mRealSectionPosition;
}
- paint.setColor(Color.YELLOW);
- canvas.drawLine(
- TEXT_WIDTH + CIRCLE_DIAMETER * 0.0f, mPosition * mFactor,
- TEXT_WIDTH + CIRCLE_DIAMETER * 1.0f, mPosition * mFactor,
- paint);
+ public boolean isMeasured() {
+ return mMeasuredSize == Float.NaN;
+ }
+
+ public void setMeasuredSize(float value) {
+ mMeasuredSize = value;
+ }
+
+ public float getMeasuredSize() {
+ return mMeasuredSize;
+ }
+
+ public VirtualSection(String caption, int realSectionIndex, int realSectionPosition) {
+ mCaption = caption;
+ mRealSectionIndex = realSectionIndex;
+ mRealSectionPosition = realSectionPosition;
+ }
}
- public void listOnScroll(int firstVisibleItem) {
- mPosition = firstVisibleItem;
- invalidate();
+ public interface Listener {
+ void onScroll(int position);
}
}
diff --git a/src/com/android/contacts/util/PhonebookCollatorFactory.java b/src/com/android/contacts/util/PhonebookCollatorFactory.java
new file mode 100644
index 0000000..08ce27a
--- /dev/null
+++ b/src/com/android/contacts/util/PhonebookCollatorFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 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.util;
+
+import java.text.Collator;
+import java.util.Locale;
+
+/**
+ * Returns the collator that can be used to sort contact list entries. This
+ * collator is the same as the one that is used in sqlite.
+ */
+public final class PhonebookCollatorFactory {
+ public static final Collator getCollator() {
+ final Locale defaultLocale = Locale.getDefault();
+ final String defaultLocaleString = defaultLocale.toString();
+ // For Japanese we use a special collator that puts japanese characters before foreign
+ // ones (this is called a dictionary collator)
+ // Warning: This function has to match the behavior in sqlite3_android.cpp (located in
+ // the framework)
+ final Locale locale;
+ if ("ja".equals(defaultLocaleString) || "ja_JP".equals(defaultLocaleString)) {
+ locale = new Locale("ja@collation=phonebook");
+ } else {
+ locale = defaultLocale;
+ }
+
+ return Collator.getInstance(locale);
+ }
+}