package org.music.player; import android.database.Cursor; import android.provider.MediaStore; import java.util.Arrays; /** * Like android.widget.AlphabetIndexer, but handles MediaStore sorting order * (strips "a", "the", etc). */ public class MusicAlphabetIndexer { /** * The indexing sections. */ private static final String[] ALPHABET = { " ", "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" }; /** * The result of {@link android.provider.MediaStore.Audio#keyFor(String)} called on each * letter of the alphabet. */ private static String[] ALPHABET_KEYS = null; /** * Cached section postions. */ private final int mAlphaMap[]; /** * Cursor that is used by the adapter of the list view. */ private Cursor mDataCursor; /** * The index of the cursor column that this list is sorted on. */ private final int mColumnIndex; /** * Constructs the indexer. * * @param sortedColumnIndex the column number in the cursor that is sorted * alphabetically */ public MusicAlphabetIndexer(int sortedColumnIndex) { if (ALPHABET_KEYS == null) { String[] keys = new String[ALPHABET.length]; for (int i = ALPHABET.length; --i != -1; ) { keys[i] = MediaStore.Audio.keyFor(ALPHABET[i]); } ALPHABET_KEYS = keys; } mColumnIndex = sortedColumnIndex; mAlphaMap = new int[ALPHABET.length]; } /** * Returns the latin alphabet. */ public static Object[] getSections() { return ALPHABET; } /** * 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) { mDataCursor = cursor; Arrays.fill(mAlphaMap, -1); } /** * 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 getPositionForSection(int sectionIndex) { Cursor cursor = mDataCursor; if (cursor == null) return 0; if (sectionIndex <= 0) return 0; int count = cursor.getCount(); if (sectionIndex >= ALPHABET.length) return count; int pos = mAlphaMap[sectionIndex]; if (pos != -1) return pos; int savedCursorPos = cursor.getPosition(); int start = mAlphaMap[sectionIndex - 1]; int end = count; if (start == -1) start = 0; String targetLetter = ALPHABET_KEYS[sectionIndex]; pos = (end + start) / 2; while (pos < end) { cursor.moveToPosition(pos); String curName = cursor.getString(mColumnIndex); String curLetter = MediaStore.Audio.keyFor(curName).substring(0, 3); int diff = curLetter.compareTo(targetLetter); if (diff != 0) { if (diff < 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; } mAlphaMap[sectionIndex] = pos; cursor.moveToPosition(savedCursorPos); return pos; } /** * Returns the section index for a given position in the list by querying the item * and comparing it with all items in the section array. */ public int getSectionForPosition(int position) { int savedCursorPos = mDataCursor.getPosition(); mDataCursor.moveToPosition(position); String key = MediaStore.Audio.keyFor(mDataCursor.getString(mColumnIndex)); mDataCursor.moveToPosition(savedCursorPos); String[] alphabet = ALPHABET_KEYS; for (int i = 1, len = alphabet.length; i != len; ++i) { if (key.startsWith(alphabet[i])) return i; } return 0; } }