Merge "Adding fast scroller in app list"
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 716dde6..7d33156 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -22,12 +22,15 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.icu.text.AlphabeticIndex;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.preference.PreferenceFrameLayout;
+import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.LocaleList;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -45,6 +48,7 @@
 import android.widget.Filterable;
 import android.widget.FrameLayout;
 import android.widget.ListView;
+import android.widget.SectionIndexer;
 import android.widget.Spinner;
 import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.settings.AppHeader;
@@ -79,6 +83,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Locale;
 
 /**
  * Activity to pick an application that will be used to display installation information and
@@ -300,6 +305,7 @@
             }
             mListView.setAdapter(mApplications);
             mListView.setRecyclerListener(mApplications);
+            mListView.setFastScrollEnabled(isFastScrollEnabled());
 
             Utils.prepareCustomPreferencesList(container, mRootView, mListView, false);
         }
@@ -372,6 +378,17 @@
         }
     }
 
+    private boolean isFastScrollEnabled() {
+        switch (mListType) {
+            case LIST_TYPE_MAIN:
+            case LIST_TYPE_NOTIFICATION:
+            case LIST_TYPE_STORAGE:
+                return mSortOrder == R.id.sort_order_alpha;
+            default:
+                return false;
+        }
+    }
+
     @Override
     protected int getMetricsCategory() {
         switch (mListType) {
@@ -545,6 +562,7 @@
             case R.id.sort_order_alpha:
             case R.id.sort_order_size:
                 mSortOrder = menuId;
+                mListView.setFastScrollEnabled(isFastScrollEnabled());
                 if (mApplications != null) {
                     mApplications.rebuild(mSortOrder);
                 }
@@ -699,7 +717,9 @@
      */
     static class ApplicationsAdapter extends BaseAdapter implements Filterable,
             ApplicationsState.Callbacks, AppStateBaseBridge.Callback,
-            AbsListView.RecyclerListener {
+            AbsListView.RecyclerListener, SectionIndexer {
+        private static final SectionInfo[] EMPTY_SECTIONS = new SectionInfo[0];
+
         private final ApplicationsState mState;
         private final ApplicationsState.Session mSession;
         private final ManageApplications mManageApplications;
@@ -718,6 +738,10 @@
         private boolean mHasReceivedLoadEntries;
         private boolean mHasReceivedBridgeCallback;
 
+        private AlphabeticIndex.ImmutableIndex mIndex;
+        private SectionInfo[] mSections = EMPTY_SECTIONS;
+        private int[] mPositionToSectionIndex;
+
         private Filter mFilter = new Filter() {
             @Override
             protected FilterResults performFiltering(CharSequence constraint) {
@@ -867,9 +891,49 @@
             mBaseEntries = entries;
             if (mBaseEntries != null) {
                 mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries);
+
+                if (mManageApplications.mListView.isFastScrollEnabled()) {
+                    // Rebuild sections
+                    if (mIndex == null) {
+                        LocaleList locales = mContext.getResources().getConfiguration().getLocales();
+                        if (locales.size() == 0) {
+                            locales = new LocaleList(Locale.ENGLISH);
+                        }
+                        AlphabeticIndex index = new AlphabeticIndex<>(locales.getPrimary());
+                        int localeCount = locales.size();
+                        for (int i = 1; i < localeCount; i++) {
+                            index.addLabels(locales.get(i));
+                        }
+                        // Ensure we always have some base English locale buckets
+                        index.addLabels(Locale.ENGLISH);
+                        mIndex = index.buildImmutableIndex();
+                    }
+
+                    ArrayList<SectionInfo> sections = new ArrayList<>();
+                    int lastSecId = -1;
+                    int totalEntries = mEntries.size();
+                    mPositionToSectionIndex = new int[totalEntries];
+
+                    for (int pos = 0; pos < totalEntries; pos++) {
+                        String label = mEntries.get(pos).label;
+                        int secId = mIndex.getBucketIndex(TextUtils.isEmpty(label) ? "" : label);
+                        if (secId != lastSecId) {
+                            lastSecId = secId;
+                            sections.add(new SectionInfo(mIndex.getBucket(secId).getLabel(), pos));
+                        }
+                        mPositionToSectionIndex[pos] = sections.size() - 1;
+                    }
+                    mSections = sections.toArray(EMPTY_SECTIONS);
+                } else {
+                    mSections = EMPTY_SECTIONS;
+                    mPositionToSectionIndex = null;
+                }
             } else {
                 mEntries = null;
+                mSections = EMPTY_SECTIONS;
+                mPositionToSectionIndex = null;
             }
+
             notifyDataSetChanged();
 
             if (mSession.getAllApps().size() != 0
@@ -1112,6 +1176,21 @@
                 return mContext.getString(R.string.domain_urls_summary_some, result.valueAt(0));
             }
         }
+
+        @Override
+        public Object[] getSections() {
+            return mSections;
+        }
+
+        @Override
+        public int getPositionForSection(int sectionIndex) {
+            return mSections[sectionIndex].position;
+        }
+
+        @Override
+        public int getSectionForPosition(int position) {
+            return mPositionToSectionIndex[position];
+        }
     }
 
     private static class SummaryProvider implements SummaryLoader.SummaryProvider,
@@ -1185,6 +1264,21 @@
         }
     }
 
+    private static class SectionInfo {
+        final String label;
+        final int position;
+
+        public SectionInfo(String label, int position) {
+            this.label = label;
+            this.position = position;
+        }
+
+        @Override
+        public String toString() {
+            return label;
+        }
+    }
+
     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
             = new SummaryLoader.SummaryProviderFactory() {
         @Override