package com.android.launcher3; import android.content.ComponentName; import android.content.Context; import android.support.v7.widget.RecyclerView; import com.android.launcher3.compat.AlphabeticIndexCompat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * The alphabetically sorted list of applications. */ public class AlphabeticalAppsList { /** * Info about a section in the alphabetic list */ public class SectionInfo { public String sectionName; public int numAppsInSection; } /** * A filter interface to limit the set of applications in the apps list. */ public interface Filter { public boolean retainApp(AppInfo info); } // Hack to force RecyclerView to break sections public static final AppInfo SECTION_BREAK_INFO = null; private List mApps = new ArrayList<>(); private List mFilteredApps = new ArrayList<>(); private List mSectionedFilteredApps = new ArrayList<>(); private List mSections = new ArrayList<>(); private RecyclerView.Adapter mAdapter; private Filter mFilter; private AlphabeticIndexCompat mIndexer; public AlphabeticalAppsList(Context context) { mIndexer = new AlphabeticIndexCompat(context); } /** * Sets the adapter to notify when this dataset changes. */ public void setAdapter(RecyclerView.Adapter adapter) { mAdapter = adapter; } /** * Returns sections of all the current filtered applications. */ public List getSections() { return mSections; } /** * Returns the current filtered list of applications broken down into their sections. */ public List getApps() { return mSectionedFilteredApps; } /** * Returns the current filtered list of applications. */ public List getAppsWithoutSectionBreaks() { return mFilteredApps; } /** * Returns the section name for the application. */ public String getSectionNameForApp(AppInfo info) { return mIndexer.computeSectionName(info.title.toString().trim()); } /** * Returns the indexer for this locale. */ public AlphabeticIndexCompat getIndexer() { return mIndexer; } /** * Returns whether there are no filtered results. */ public boolean hasNoFilteredResults() { return (mFilter != null) && mFilteredApps.isEmpty(); } /** * Sets the current filter for this list of apps. */ public void setFilter(Filter f) { mFilter = f; onAppsUpdated(); mAdapter.notifyDataSetChanged(); } /** * Sets the current set of apps. */ public void setApps(List apps) { Collections.sort(apps, LauncherModel.getAppNameComparator()); mApps.clear(); mApps.addAll(apps); onAppsUpdated(); mAdapter.notifyDataSetChanged(); } /** * Adds new apps to the list. */ public void addApps(List apps) { // We add it in place, in alphabetical order for (AppInfo info : apps) { addApp(info); } } /** * Updates existing apps in the list */ public void updateApps(List apps) { for (AppInfo info : apps) { int index = mApps.indexOf(info); if (index != -1) { mApps.set(index, info); onAppsUpdated(); mAdapter.notifyItemChanged(index); } else { addApp(info); } } } /** * Removes some apps from the list. */ public void removeApps(List apps) { for (AppInfo info : apps) { int removeIndex = findAppByComponent(mApps, info); if (removeIndex != -1) { int sectionedIndex = mSectionedFilteredApps.indexOf(info); int numAppsInSection = numAppsInSection(info); mApps.remove(removeIndex); onAppsUpdated(); if (numAppsInSection == 1) { // Remove the section and the icon mAdapter.notifyItemRemoved(sectionedIndex - 1); mAdapter.notifyItemRemoved(sectionedIndex - 1); } else { mAdapter.notifyItemRemoved(sectionedIndex); } } } } /** * Finds the index of an app given a target AppInfo. */ private int findAppByComponent(List apps, AppInfo targetInfo) { ComponentName targetComponent = targetInfo.intent.getComponent(); int length = apps.size(); for (int i = 0; i < length; ++i) { AppInfo info = apps.get(i); if (info.user.equals(info.user) && info.intent.getComponent().equals(targetComponent)) { return i; } } return -1; } /** * Implementation to actually add an app to the alphabetic list */ private void addApp(AppInfo info) { Comparator appNameComparator = LauncherModel.getAppNameComparator(); int index = Collections.binarySearch(mApps, info, appNameComparator); if (index < 0) { mApps.add(-(index + 1), info); onAppsUpdated(); int sectionedIndex = mSectionedFilteredApps.indexOf(info); int numAppsInSection = numAppsInSection(info); if (numAppsInSection == 1) { // New section added along with icon mAdapter.notifyItemInserted(sectionedIndex - 1); mAdapter.notifyItemInserted(sectionedIndex - 1); } else { mAdapter.notifyItemInserted(sectionedIndex); } } } /** * Returns the number of apps in the section that the given info is in. */ private int numAppsInSection(AppInfo info) { int appIndex = mFilteredApps.indexOf(info); int appCount = 0; for (SectionInfo section : mSections) { if (appCount + section.numAppsInSection > appIndex) { return section.numAppsInSection; } appCount += section.numAppsInSection; } return 1; } /** * Updates internals when the set of apps are updated. */ private void onAppsUpdated() { // Recreate the filtered apps mFilteredApps.clear(); for (AppInfo info : mApps) { if (mFilter == null || mFilter.retainApp(info)) { mFilteredApps.add(info); } } // Section the apps (for convenience for the grid layout) mSections.clear(); mSectionedFilteredApps.clear(); SectionInfo lastSectionInfo = null; for (AppInfo info : mFilteredApps) { String sectionName = getSectionNameForApp(info); if (lastSectionInfo == null || !lastSectionInfo.sectionName.equals(sectionName)) { lastSectionInfo = new SectionInfo(); lastSectionInfo.sectionName = sectionName; mSectionedFilteredApps.add(SECTION_BREAK_INFO); mSections.add(lastSectionInfo); } lastSectionInfo.numAppsInSection++; mSectionedFilteredApps.add(info); } } }