michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.favicons.cache; michael@0: michael@0: import android.graphics.Bitmap; michael@0: import android.util.Log; michael@0: import org.mozilla.gecko.gfx.BitmapUtils; michael@0: michael@0: import java.util.ArrayList; michael@0: import java.util.Collections; michael@0: michael@0: public class FaviconsForURL { michael@0: private static final String LOGTAG = "FaviconForURL"; michael@0: michael@0: private volatile int dominantColor = -1; michael@0: michael@0: final long downloadTimestamp; michael@0: final ArrayList favicons; michael@0: michael@0: public final boolean hasFailed; michael@0: michael@0: public FaviconsForURL(int size) { michael@0: this(size, false); michael@0: } michael@0: michael@0: public FaviconsForURL(int size, boolean failed) { michael@0: hasFailed = failed; michael@0: downloadTimestamp = System.currentTimeMillis(); michael@0: favicons = new ArrayList(size); michael@0: } michael@0: michael@0: public FaviconCacheElement addSecondary(Bitmap favicon, int imageSize) { michael@0: return addInternal(favicon, false, imageSize); michael@0: } michael@0: michael@0: public FaviconCacheElement addPrimary(Bitmap favicon) { michael@0: return addInternal(favicon, true, favicon.getWidth()); michael@0: } michael@0: michael@0: private FaviconCacheElement addInternal(Bitmap favicon, boolean isPrimary, int imageSize) { michael@0: FaviconCacheElement c = new FaviconCacheElement(favicon, isPrimary, imageSize, this); michael@0: michael@0: int index = Collections.binarySearch(favicons, c); michael@0: michael@0: // We've already got an equivalent one. We don't care about this new one. This only occurs in certain obscure michael@0: // case conditions. michael@0: if (index >= 0) { michael@0: return favicons.get(index); michael@0: } michael@0: michael@0: // binarySearch returns -x - 1 where x is the insertion point of the element. Convert michael@0: // this to the actual insertion point.. michael@0: index++; michael@0: index = -index; michael@0: favicons.add(index, c); michael@0: michael@0: return c; michael@0: } michael@0: michael@0: /** michael@0: * Get the index of the smallest image in this collection larger than or equal to michael@0: * the given target size. michael@0: * michael@0: * @param targetSize Minimum size for the desired result. michael@0: * @return The index of the smallest image larger than the target size, or -1 if none exists. michael@0: */ michael@0: public int getNextHighestIndex(int targetSize) { michael@0: // Create a dummy object to hold the target value for comparable. michael@0: FaviconCacheElement dummy = new FaviconCacheElement(null, false, targetSize, null); michael@0: michael@0: int index = Collections.binarySearch(favicons, dummy); michael@0: michael@0: // The search routine returns the index of an element equal to dummy, if present. michael@0: // Otherwise, it returns -x - 1, where x is the index in the ArrayList where dummy would be michael@0: // inserted if the list were to remain sorted. michael@0: if (index < 0) { michael@0: index++; michael@0: index = -index; michael@0: } michael@0: michael@0: // index is now 'x', as described above. michael@0: michael@0: // The routine will return favicons.size() as the index iff dummy is larger than all elements michael@0: // present (So the "index at which it should be inserted" is the index after the end. michael@0: // In this case, we set the sentinel value -1 to indicate that we just requested something michael@0: // larger than all primaries. michael@0: if (index == favicons.size()) { michael@0: index = -1; michael@0: } michael@0: michael@0: return index; michael@0: } michael@0: michael@0: /** michael@0: * Get the next valid primary icon from this collection, starting at the given index. michael@0: * If the appropriate icon is found, but is invalid, we return null - the proper response is to michael@0: * reacquire the primary from the database. michael@0: * If no icon is found, the search is repeated going backwards from the start index to find any michael@0: * primary at all (The input index may be a secondary which is larger than the actual available michael@0: * primary.) michael@0: * michael@0: * @param fromIndex The index into favicons from which to start the search. michael@0: * @return The FaviconCacheElement of the next valid primary from the given index. If none exists, michael@0: * then returns the previous valid primary. If none exists, returns null (Insanity.). michael@0: */ michael@0: public FaviconCacheElement getNextPrimary(final int fromIndex) { michael@0: final int numIcons = favicons.size(); michael@0: michael@0: int searchIndex = fromIndex; michael@0: while (searchIndex < numIcons) { michael@0: FaviconCacheElement element = favicons.get(searchIndex); michael@0: michael@0: if (element.isPrimary) { michael@0: if (element.invalidated) { michael@0: // We return null here, despite the possible existence of other primaries, michael@0: // because we know the most suitable primary for this request exists, but is michael@0: // no longer in the cache. By returning null, we cause the caller to load the michael@0: // missing primary from the database and call again. michael@0: return null; michael@0: } michael@0: return element; michael@0: } michael@0: searchIndex++; michael@0: } michael@0: michael@0: // No larger primary available. Let's look for smaller ones... michael@0: searchIndex = fromIndex - 1; michael@0: while (searchIndex >= 0) { michael@0: FaviconCacheElement element = favicons.get(searchIndex); michael@0: michael@0: if (element.isPrimary) { michael@0: if (element.invalidated) { michael@0: return null; michael@0: } michael@0: return element; michael@0: } michael@0: searchIndex--; michael@0: } michael@0: michael@0: Log.e(LOGTAG, "No primaries found in Favicon cache structure. This is madness!"); michael@0: michael@0: return null; michael@0: } michael@0: michael@0: /** michael@0: * Ensure the dominant colour field is populated for this favicon. michael@0: */ michael@0: public int ensureDominantColor() { michael@0: if (dominantColor == -1) { michael@0: // Find a payload, any payload, that is not invalidated. michael@0: for (FaviconCacheElement element : favicons) { michael@0: if (!element.invalidated) { michael@0: dominantColor = BitmapUtils.getDominantColor(element.faviconPayload); michael@0: return dominantColor; michael@0: } michael@0: } michael@0: dominantColor = 0xFFFFFF; michael@0: } michael@0: michael@0: return dominantColor; michael@0: } michael@0: }