/* * Copyright (C) 2015 The CyanogenMod 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.launcher3; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; public class BaseRecyclerViewScrubberSection { private static final String TAG = "BRVScrubberSections"; private static final String ALPHA_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final int MAX_NUMBER_CUSTOM_SECTIONS = 8; private static final int MAX_SECTIONS = ALPHA_LETTERS.length() + MAX_NUMBER_CUSTOM_SECTIONS; public static final int INVALID_INDEX = -1; private AutoExpandTextView.HighlightedText mHighlightedText; private int mPreviousValidIndex; private int mNextValidIndex; private int mAdapterIndex; public BaseRecyclerViewScrubberSection(String text, boolean highlight, int idx) { mHighlightedText = new AutoExpandTextView.HighlightedText(text, highlight); mAdapterIndex = idx; mPreviousValidIndex = mNextValidIndex = idx; } public boolean getHighlight() { return mHighlightedText.mHighlight; } public String getText() { return mHighlightedText.mText; } public int getPreviousIndex() { return mPreviousValidIndex; } public int getNextIndex() { return mNextValidIndex; } public int getAdapterIndex() { return mAdapterIndex; } private static int getFirstValidIndex(RtlIndexArrayList sections, boolean isRtl) { for (int i = 0; i < sections.size(); i++) { if (sections.get(i, isRtl).getHighlight()) { return i; } } return INVALID_INDEX; } private static void createIndices(RtlIndexArrayList sections, boolean isRtl) { if (sections == null || sections.size() == 0) { return; } // walk forwards and fill out the previous valid index based on the previous highlight int currentIdx = getFirstValidIndex(sections, isRtl); for (int i = 0; i < sections.size(); i++) { if (sections.get(i, isRtl).getHighlight()) { currentIdx = i; } sections.get(i, isRtl).mPreviousValidIndex = currentIdx; } // currentIdx should be now on the last valid index so walk back and fill the other way for (int i = sections.size() - 1; i >= 0; i--) { if (sections.get(i, isRtl).getHighlight()) { currentIdx = i; } sections.get(i, isRtl).mNextValidIndex = currentIdx; } } public static ArrayList getHighlightText( RtlIndexArrayList sections) { if (sections == null) { return null; } ArrayList highlights = new ArrayList<>(sections.size()); for (BaseRecyclerViewScrubberSection section : sections) { highlights.add(section.mHighlightedText); } return highlights; } private static void addAlphaLetters(RtlIndexArrayList sections, HashMap foundAlphaLetters) { for (int i = 0; i < ALPHA_LETTERS.length(); i++) { boolean highlighted = foundAlphaLetters.containsKey(i); int index = highlighted ? foundAlphaLetters.get(i) : BaseRecyclerViewScrubberSection.INVALID_INDEX; sections.add(new BaseRecyclerViewScrubberSection(ALPHA_LETTERS.substring(i, i + 1), highlighted, index)); } } /** * Takes the sections and runs some checks to see if we can create a valid * appDrawerScrubberSection out of it. This list will contain the original header list plus * fill out the remaining sections based on the ALPHA_LETTERS. It will then determine which * ones to highlight as well as what letters to highlight when scrolling over the * grayed out sections * @param sectionNames list of sectionName Strings * @return the list of scrubber sections */ public static RtlIndexArrayList createSections(String[] sectionNames, boolean isRtl) { // check if we have a valid header section if (!validSectionNameList(sectionNames)) { return null; } // this will track the mapping of ALPHA_LETTERS index to the headers index HashMap foundAlphaLetters = new HashMap<>(); RtlIndexArrayList sections = new RtlIndexArrayList<>(sectionNames.length); boolean inAlphaLetterSection = false; for (int i = 0; i < sectionNames.length; i++) { int alphaLetterIndex = TextUtils.isEmpty(sectionNames[i]) ? -1 : ALPHA_LETTERS.indexOf(sectionNames[i]); // if we found an ALPHA_LETTERS store that in foundAlphaLetters and continue if (alphaLetterIndex >= 0) { foundAlphaLetters.put(alphaLetterIndex, i); inAlphaLetterSection = true; } else { // if we are exiting the ALPHA_LETTERS section, add it here if (inAlphaLetterSection) { addAlphaLetters(sections, foundAlphaLetters); inAlphaLetterSection = false; } // add the custom header sections.add(new BaseRecyclerViewScrubberSection(sectionNames[i], true, i)); } } // if the last section are the alpha letters, then add it if (inAlphaLetterSection) { addAlphaLetters(sections, foundAlphaLetters); } // create the forward and backwards indices for scrolling over the grayed out sections BaseRecyclerViewScrubberSection.createIndices(sections, isRtl); return sections; } /** * Walk through the sectionNames and check for a few things: * 1) No more than MAX_NUMBER_CUSTOM_SECTIONS sectionNames exist in the sectionNames list or no more * than MAX_SECTIONS sectionNames exist in the list * 2) the headers that fall in the ALPHA_LETTERS category are in the same order as ALPHA_LETTERS * 3) There are no sectionNames that exceed length of 1 * 4) The alpha letter sectionName is together and not separated by other things */ private static boolean validSectionNameList(String[] sectionNames) { int numCustomSections = 0; int previousAlphaIndex = -1; boolean foundAlphaSections = false; for (String s : sectionNames) { if (TextUtils.isEmpty(s)) { numCustomSections++; continue; } if (s.length() > 1) { Log.w(TAG, "Found section " + s + " with length: " + s.length()); return false; } int alphaIndex = ALPHA_LETTERS.indexOf(s); if (alphaIndex >= 0) { if (previousAlphaIndex != -1) { // if the previous alpha index is >= alphaIndex then it is in the wrong order if (previousAlphaIndex >= alphaIndex) { Log.w(TAG, "Found letter index " + previousAlphaIndex + " which is greater than " + alphaIndex); return false; } } // if we've found headers previously and the index is -1 that means the alpha // letters are separated out into two sections so return false if (foundAlphaSections && previousAlphaIndex == -1) { Log.w(TAG, "Found alpha letters twice"); return false; } previousAlphaIndex = alphaIndex; foundAlphaSections = true; } else { numCustomSections++; previousAlphaIndex = -1; } } final int listSize = foundAlphaSections ? numCustomSections + ALPHA_LETTERS.length() : numCustomSections; // if one of these conditions are satisfied, then return true if (numCustomSections <= MAX_NUMBER_CUSTOM_SECTIONS || listSize <= MAX_SECTIONS) { return true; } if (numCustomSections > MAX_NUMBER_CUSTOM_SECTIONS) { Log.w(TAG, "Found " + numCustomSections + "# custom sections when " + MAX_NUMBER_CUSTOM_SECTIONS + " is allowed!"); } else if (listSize > MAX_SECTIONS) { Log.w(TAG, "Found " + listSize + " sections when " + MAX_SECTIONS + " is allowed!"); } return false; } public static class RtlIndexArrayList extends ArrayList { public RtlIndexArrayList(int capacity) { super(capacity); } public T get(int index, boolean isRtl) { if (isRtl) { index = size() - 1 - index; } return super.get(index); } } }