OSDN Git Service

auto import from //branches/cupcake_rel/...@138607
[android-x86/packages-apps-Contacts.git] / src / com / android / contacts / JapaneseContactListIndexer.java
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.contacts;
18
19 import android.database.Cursor;
20 import android.database.DataSetObserver;
21 import android.util.Log;
22 import android.util.SparseIntArray;
23 import android.widget.SectionIndexer;
24
25 /**
26  * SectionIndexer which is for "phonetically sortable" String. This class heavily depends on the
27  * algorithm of the SQL function "GET_PHONETICALLY_SORTABLE_STRING", whose implementation
28  * is written in C++.
29  */
30 public final class JapaneseContactListIndexer extends DataSetObserver implements SectionIndexer {
31     private static String TAG = "JapaneseContactListIndexer";
32
33     static private final String[] sSections = {
34             " ", // Sections of SectionIndexer should start with " " (some components assume it).
35             "\u3042", "\u304B", "\u3055", "\u305F", "\u306A", // a, ka, sa, ta, na 
36             "\u306F", "\u307E", "\u3084", "\u3089", "\u308F", // ha, ma, ya, ra, wa
37             "\uFF21", "\uFF22", "\uFF23", "\uFF24", "\uFF25", // full-width ABCDE
38             "\uFF26", "\uFF27", "\uFF28", "\uFF29", "\uFF2A", // full-width FGHIJ
39             "\uFF2B", "\uFF2C", "\uFF2D", "\uFF2E", "\uFF2F", // full-width KLMNO
40             "\uFF30", "\uFF31", "\uFF32", "\uFF33", "\uFF34", // full-width PQRST
41             "\uFF35", "\uFF36", "\uFF37", "\uFF38", "\uFF39", // full-width UVWXY
42             "\uFF40", // full-width Z
43             "\u6570", "\u8A18" // alphabets, numbers, symbols
44             };
45     static private final int sSectionsLength = sSections.length;
46     
47     private int mColumnIndex;
48     private Cursor mDataCursor;
49     private SparseIntArray mStringMap;
50     
51     public JapaneseContactListIndexer(Cursor cursor, int columnIndex) {
52         int len = sSections.length;
53         mColumnIndex = columnIndex;
54         mDataCursor = cursor;
55         mStringMap = new SparseIntArray(sSectionsLength);
56         if (cursor != null) {
57             cursor.registerDataSetObserver(this);
58         }
59     }
60     
61     public void setCursor(Cursor cursor) {
62         if (mDataCursor != null) {
63             mDataCursor.unregisterDataSetObserver(this);
64         }
65         mDataCursor = cursor;
66         if (cursor != null) {
67             mDataCursor.registerDataSetObserver(this);
68         }
69     }
70     
71     private int getSectionCodePoint(int index) {
72         if (index < sSections.length - 2) {
73             return sSections[index].codePointAt(0);
74         } else if (index == sSections.length - 2) {
75             return 0xFF66;  // Numbers are mapped from 0xFF66.
76         } else {  // index == mSections.length - 1
77             return 0xFF70;  // Symbols are mapped from 0xFF70.
78         }
79     }
80     
81     public int getPositionForSection(int sectionIndex) {
82         final SparseIntArray stringMap = mStringMap;
83         final Cursor cursor = mDataCursor;
84
85         if (cursor == null || sectionIndex <= 0) {
86             return 0;
87         }
88         
89         if (sectionIndex >= sSectionsLength) {
90             sectionIndex = sSectionsLength - 1;
91         }
92
93         int savedCursorPos = cursor.getPosition();
94
95         String targetLetter = sSections[sectionIndex];
96         int key = targetLetter.codePointAt(0);
97
98         // Check cache map
99         {
100             int tmp = stringMap.get(key, Integer.MIN_VALUE);
101             if (Integer.MIN_VALUE != tmp) {
102                 return tmp;
103             }
104         }
105
106         int end = cursor.getCount();
107         int pos = 0;
108
109         {
110             // Note that sectionIndex > 0.
111             int prevLetter = sSections[sectionIndex - 1].codePointAt(0);
112             int prevLetterPos = stringMap.get(prevLetter, Integer.MIN_VALUE);
113             if (prevLetterPos != Integer.MIN_VALUE) {
114                 pos = prevLetterPos;
115             }
116         }
117         
118         // Do rough binary search if there are a lot of entries.
119         while (end - pos > 100) {
120             int tmp = (end + pos) / 2;
121             cursor.moveToPosition(tmp);
122             String sort_name;
123             do {
124                 sort_name = cursor.getString(mColumnIndex);
125                 if (sort_name == null || sort_name.length() == 0) {
126                     // This should not happen, since sort_name field is created
127                     // automatically when syncing to a server, or creating/editing
128                     // the entry...
129                     Log.e(TAG, "sort_name is null or its length is 0. index: " + tmp);
130                     cursor.moveToNext();
131                     tmp++;
132                     continue;
133                 }
134                 break;
135             } while (tmp < end);
136             if (tmp == end) {
137                 break;
138             }
139             int codePoint = sort_name.codePointAt(0);
140             if (codePoint < getSectionCodePoint(sectionIndex)) {
141                 pos = tmp;
142             } else {
143                 end = tmp;
144             }
145         }
146         
147         for (cursor.moveToPosition(pos); !cursor.isAfterLast(); ++pos, cursor.moveToNext()) {
148             String sort_name = cursor.getString(mColumnIndex);
149             if (sort_name == null || sort_name.length() == 0) {
150                 // This should not happen, since sort_name field is created
151                 // automatically when syncing to a server, or creating/editing
152                 // the entry...
153                 Log.e(TAG, "sort_name is null or its length is 0. index: " + pos);
154                 continue;
155             }
156             int codePoint = sort_name.codePointAt(0);
157             if (codePoint >= getSectionCodePoint(sectionIndex)) {
158                 break;
159             }
160         }
161         
162         stringMap.put(key, pos);
163         cursor.moveToPosition(savedCursorPos);
164         return pos;
165     }
166     
167     public int getSectionForPosition(int position) {
168         // Not used in Contacts. Ignore for now.
169         return 0;
170     }
171
172     public Object[] getSections() {
173         return sSections;
174     }
175
176     @Override
177     public void onChanged() {
178         super.onChanged();
179         mStringMap.clear();
180     }
181
182     @Override
183     public void onInvalidated() {
184         super.onInvalidated();
185         mStringMap.clear();
186     }
187 }