FileDocCategorySizeDatePackage
JapaneseContactListIndexer.javaAPI DocAndroid 1.5 API6656Wed May 06 22:42:44 BST 2009com.android.contacts

JapaneseContactListIndexer.java

/*
 * 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();
    }
}