summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/ui/contact/ContactSectionIndexer.java
diff options
context:
space:
mode:
authorMike Dodd <mdodd@google.com>2015-08-11 11:16:59 -0700
committerMike Dodd <mdodd@google.com>2015-08-12 12:47:26 -0700
commitd3b009ae55651f1e60950342468e3c37fdeb0796 (patch)
treebc4b489af52d0e2521e21167d2ad76a47256f348 /src/com/android/messaging/ui/contact/ContactSectionIndexer.java
parentef8c7abbcfc9c770385d6609a4b4bc70240ebdc4 (diff)
downloadandroid_packages_apps_Messaging-d3b009ae55651f1e60950342468e3c37fdeb0796.tar.gz
android_packages_apps_Messaging-d3b009ae55651f1e60950342468e3c37fdeb0796.tar.bz2
android_packages_apps_Messaging-d3b009ae55651f1e60950342468e3c37fdeb0796.zip
Initial checkin of AOSP Messaging app.
b/23110861 Change-Id: I11db999bd10656801e618f78ab2b2ef74136fff1
Diffstat (limited to 'src/com/android/messaging/ui/contact/ContactSectionIndexer.java')
-rw-r--r--src/com/android/messaging/ui/contact/ContactSectionIndexer.java169
1 files changed, 169 insertions, 0 deletions
diff --git a/src/com/android/messaging/ui/contact/ContactSectionIndexer.java b/src/com/android/messaging/ui/contact/ContactSectionIndexer.java
new file mode 100644
index 0000000..1d5abf3
--- /dev/null
+++ b/src/com/android/messaging/ui/contact/ContactSectionIndexer.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2015 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.messaging.ui.contact;
+
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.text.TextUtils;
+import android.widget.SectionIndexer;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContactUtil;
+import com.android.messaging.util.LogUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Indexes contact alphabetical sections so we can report to the fast scrolling list view
+ * where we are in the list when the user scrolls through the contact list, allowing us to show
+ * alphabetical indicators for the fast scroller as well as list section headers.
+ */
+public class ContactSectionIndexer implements SectionIndexer {
+ private String[] mSections;
+ private ArrayList<Integer> mSectionStartingPositions;
+ private static final String BLANK_HEADER_STRING = " ";
+
+ public ContactSectionIndexer(final Cursor contactsCursor) {
+ buildIndexer(contactsCursor);
+ }
+
+ @Override
+ public Object[] getSections() {
+ return mSections;
+ }
+
+ @Override
+ public int getPositionForSection(final int sectionIndex) {
+ if (mSectionStartingPositions.isEmpty()) {
+ return 0;
+ }
+ // Clamp to the bounds of the section position array per Android API doc.
+ return mSectionStartingPositions.get(
+ Math.max(Math.min(sectionIndex, mSectionStartingPositions.size() - 1), 0));
+ }
+
+ @Override
+ public int getSectionForPosition(final int position) {
+ if (mSectionStartingPositions.isEmpty()) {
+ return 0;
+ }
+
+ // Perform a binary search on the starting positions of the sections to the find the
+ // section for the position.
+ int left = 0;
+ int right = mSectionStartingPositions.size() - 1;
+
+ // According to getSectionForPosition()'s doc, we should always clamp the value when the
+ // position is out of bound.
+ if (position <= mSectionStartingPositions.get(left)) {
+ return left;
+ } else if (position >= mSectionStartingPositions.get(right)) {
+ return right;
+ }
+
+ while (left <= right) {
+ final int mid = (left + right) / 2;
+ final int startingPos = mSectionStartingPositions.get(mid);
+ final int nextStartingPos = mSectionStartingPositions.get(mid + 1);
+ if (position >= startingPos && position < nextStartingPos) {
+ return mid;
+ } else if (position < startingPos) {
+ right = mid - 1;
+ } else if (position >= nextStartingPos) {
+ left = mid + 1;
+ }
+ }
+ Assert.fail("Invalid section indexer state: couldn't find section for pos " + position);
+ return -1;
+ }
+
+ private boolean buildIndexerFromCursorExtras(final Cursor cursor) {
+ if (cursor == null) {
+ return false;
+ }
+ final Bundle cursorExtras = cursor.getExtras();
+ if (cursorExtras == null) {
+ return false;
+ }
+ final String[] sections = cursorExtras.getStringArray(
+ Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
+ final int[] counts = cursorExtras.getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
+ if (sections == null || counts == null) {
+ return false;
+ }
+
+ if (sections.length != counts.length) {
+ return false;
+ }
+
+ this.mSections = sections;
+ mSectionStartingPositions = new ArrayList<Integer>(counts.length);
+ int position = 0;
+ for (int i = 0; i < counts.length; i++) {
+ if (TextUtils.isEmpty(mSections[i])) {
+ mSections[i] = BLANK_HEADER_STRING;
+ } else if (!mSections[i].equals(BLANK_HEADER_STRING)) {
+ mSections[i] = mSections[i].trim();
+ }
+
+ mSectionStartingPositions.add(position);
+ position += counts[i];
+ }
+ return true;
+ }
+
+ private void buildIndexerFromDisplayNames(final Cursor cursor) {
+ // Loop through the contact cursor and get the starting position for each first character.
+ // The result is stored into two arrays, one for the section header (i.e. the first
+ // character), and one for the starting position, which is guaranteed to be sorted in
+ // ascending order.
+ final ArrayList<String> sections = new ArrayList<String>();
+ mSectionStartingPositions = new ArrayList<Integer>();
+ if (cursor != null) {
+ cursor.moveToPosition(-1);
+ int currentPosition = 0;
+ while (cursor.moveToNext()) {
+ // The sort key is typically the contact's display name, so for example, a contact
+ // named "Bob" will go into section "B". The Contacts provider generally uses a
+ // a slightly more sophisticated heuristic, but as a fallback this is good enough.
+ final String sortKey = cursor.getString(ContactUtil.INDEX_SORT_KEY);
+ final String section = TextUtils.isEmpty(sortKey) ? BLANK_HEADER_STRING :
+ sortKey.substring(0, 1).toUpperCase();
+
+ final int lastIndex = sections.size() - 1;
+ final String currentSection = lastIndex >= 0 ? sections.get(lastIndex) : null;
+ if (!TextUtils.equals(currentSection, section)) {
+ sections.add(section);
+ mSectionStartingPositions.add(currentPosition);
+ }
+ currentPosition++;
+ }
+ }
+ mSections = new String[sections.size()];
+ sections.toArray(mSections);
+ }
+
+ private void buildIndexer(final Cursor cursor) {
+ // First check if we get indexer label extras from the contact provider; if not, fall back
+ // to building from display names.
+ if (!buildIndexerFromCursorExtras(cursor)) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "contact provider didn't provide contact label " +
+ "information, fall back to using display name!");
+ buildIndexerFromDisplayNames(cursor);
+ }
+ }
+}