Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | package org.mozilla.gecko.favicons; |
michael@0 | 7 | |
michael@0 | 8 | import org.mozilla.gecko.AboutPages; |
michael@0 | 9 | import org.mozilla.gecko.AppConstants; |
michael@0 | 10 | import org.mozilla.gecko.GeckoAppShell; |
michael@0 | 11 | import org.mozilla.gecko.R; |
michael@0 | 12 | import org.mozilla.gecko.Tab; |
michael@0 | 13 | import org.mozilla.gecko.Tabs; |
michael@0 | 14 | import org.mozilla.gecko.db.BrowserDB; |
michael@0 | 15 | import org.mozilla.gecko.favicons.cache.FaviconCache; |
michael@0 | 16 | import org.mozilla.gecko.util.GeckoJarReader; |
michael@0 | 17 | import org.mozilla.gecko.util.NonEvictingLruCache; |
michael@0 | 18 | import org.mozilla.gecko.util.ThreadUtils; |
michael@0 | 19 | |
michael@0 | 20 | import android.content.Context; |
michael@0 | 21 | import android.content.res.Resources; |
michael@0 | 22 | import android.graphics.Bitmap; |
michael@0 | 23 | import android.graphics.BitmapFactory; |
michael@0 | 24 | import android.text.TextUtils; |
michael@0 | 25 | import android.util.Log; |
michael@0 | 26 | import android.util.SparseArray; |
michael@0 | 27 | |
michael@0 | 28 | import java.io.File; |
michael@0 | 29 | import java.net.URI; |
michael@0 | 30 | import java.net.URISyntaxException; |
michael@0 | 31 | import java.util.Arrays; |
michael@0 | 32 | import java.util.Iterator; |
michael@0 | 33 | import java.util.List; |
michael@0 | 34 | |
michael@0 | 35 | public class Favicons { |
michael@0 | 36 | private static final String LOGTAG = "GeckoFavicons"; |
michael@0 | 37 | |
michael@0 | 38 | // A magic URL representing the app's own favicon, used for about: pages. |
michael@0 | 39 | private static final String BUILT_IN_FAVICON_URL = "about:favicon"; |
michael@0 | 40 | |
michael@0 | 41 | // Size of the favicon bitmap cache, in bytes (Counting payload only). |
michael@0 | 42 | public static final int FAVICON_CACHE_SIZE_BYTES = 512 * 1024; |
michael@0 | 43 | |
michael@0 | 44 | // Number of URL mappings from page URL to Favicon URL to cache in memory. |
michael@0 | 45 | public static final int NUM_PAGE_URL_MAPPINGS_TO_STORE = 128; |
michael@0 | 46 | |
michael@0 | 47 | public static final int NOT_LOADING = 0; |
michael@0 | 48 | public static final int LOADED = 1; |
michael@0 | 49 | public static final int FLAG_PERSIST = 2; |
michael@0 | 50 | public static final int FLAG_SCALE = 4; |
michael@0 | 51 | |
michael@0 | 52 | protected static Context context; |
michael@0 | 53 | |
michael@0 | 54 | // The default Favicon to show if no other can be found. |
michael@0 | 55 | public static Bitmap defaultFavicon; |
michael@0 | 56 | |
michael@0 | 57 | // The density-adjusted default Favicon dimensions. |
michael@0 | 58 | public static int defaultFaviconSize; |
michael@0 | 59 | |
michael@0 | 60 | // The density-adjusted maximum Favicon dimensions. |
michael@0 | 61 | public static int largestFaviconSize; |
michael@0 | 62 | |
michael@0 | 63 | private static final SparseArray<LoadFaviconTask> loadTasks = new SparseArray<LoadFaviconTask>(); |
michael@0 | 64 | |
michael@0 | 65 | // Cache to hold mappings between page URLs and Favicon URLs. Used to avoid going to the DB when |
michael@0 | 66 | // doing so is not necessary. |
michael@0 | 67 | private static final NonEvictingLruCache<String, String> pageURLMappings = new NonEvictingLruCache<String, String>(NUM_PAGE_URL_MAPPINGS_TO_STORE); |
michael@0 | 68 | |
michael@0 | 69 | public static String getFaviconURLForPageURLFromCache(String pageURL) { |
michael@0 | 70 | return pageURLMappings.get(pageURL); |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | /** |
michael@0 | 74 | * Insert the given pageUrl->faviconUrl mapping into the memory cache of such mappings. |
michael@0 | 75 | * Useful for short-circuiting local database access. |
michael@0 | 76 | */ |
michael@0 | 77 | public static void putFaviconURLForPageURLInCache(String pageURL, String faviconURL) { |
michael@0 | 78 | pageURLMappings.put(pageURL, faviconURL); |
michael@0 | 79 | } |
michael@0 | 80 | |
michael@0 | 81 | private static FaviconCache faviconsCache; |
michael@0 | 82 | |
michael@0 | 83 | /** |
michael@0 | 84 | * Returns either NOT_LOADING, or LOADED if the onFaviconLoaded call could |
michael@0 | 85 | * be made on the main thread. |
michael@0 | 86 | * If no listener is provided, NOT_LOADING is returned. |
michael@0 | 87 | */ |
michael@0 | 88 | static int dispatchResult(final String pageUrl, final String faviconURL, final Bitmap image, |
michael@0 | 89 | final OnFaviconLoadedListener listener) { |
michael@0 | 90 | if (listener == null) { |
michael@0 | 91 | return NOT_LOADING; |
michael@0 | 92 | } |
michael@0 | 93 | |
michael@0 | 94 | if (ThreadUtils.isOnUiThread()) { |
michael@0 | 95 | listener.onFaviconLoaded(pageUrl, faviconURL, image); |
michael@0 | 96 | return LOADED; |
michael@0 | 97 | } |
michael@0 | 98 | |
michael@0 | 99 | // We want to always run the listener on UI thread. |
michael@0 | 100 | ThreadUtils.postToUiThread(new Runnable() { |
michael@0 | 101 | @Override |
michael@0 | 102 | public void run() { |
michael@0 | 103 | listener.onFaviconLoaded(pageUrl, faviconURL, image); |
michael@0 | 104 | } |
michael@0 | 105 | }); |
michael@0 | 106 | return NOT_LOADING; |
michael@0 | 107 | } |
michael@0 | 108 | |
michael@0 | 109 | /** |
michael@0 | 110 | * Only returns a non-null Bitmap if the entire path is cached -- the |
michael@0 | 111 | * page URL to favicon URL, and the favicon URL to in-memory bitmaps. |
michael@0 | 112 | * |
michael@0 | 113 | * Returns null otherwise. |
michael@0 | 114 | */ |
michael@0 | 115 | public static Bitmap getSizedFaviconForPageFromCache(final String pageURL, int targetSize) { |
michael@0 | 116 | final String faviconURL = pageURLMappings.get(pageURL); |
michael@0 | 117 | if (faviconURL == null) { |
michael@0 | 118 | return null; |
michael@0 | 119 | } |
michael@0 | 120 | return getSizedFaviconFromCache(faviconURL, targetSize); |
michael@0 | 121 | } |
michael@0 | 122 | |
michael@0 | 123 | /** |
michael@0 | 124 | * Get a Favicon as close as possible to the target dimensions for the URL provided. |
michael@0 | 125 | * If a result is instantly available from the cache, it is returned and the listener is invoked. |
michael@0 | 126 | * Otherwise, the result is drawn from the database or network and the listener invoked when the |
michael@0 | 127 | * result becomes available. |
michael@0 | 128 | * |
michael@0 | 129 | * @param pageURL Page URL for which a Favicon is desired. |
michael@0 | 130 | * @param faviconURL URL of the Favicon to be downloaded, if known. If none provided, an educated |
michael@0 | 131 | * guess is made by the system. |
michael@0 | 132 | * @param targetSize Target size of the returned Favicon |
michael@0 | 133 | * @param listener Listener to call with the result of the load operation, if the result is not |
michael@0 | 134 | * immediately available. |
michael@0 | 135 | * @return The id of the asynchronous task created, NOT_LOADING if none is created, or |
michael@0 | 136 | * LOADED if the value could be dispatched on the current thread. |
michael@0 | 137 | */ |
michael@0 | 138 | public static int getSizedFavicon(String pageURL, String faviconURL, int targetSize, int flags, OnFaviconLoadedListener listener) { |
michael@0 | 139 | // Do we know the favicon URL for this page already? |
michael@0 | 140 | String cacheURL = faviconURL; |
michael@0 | 141 | if (cacheURL == null) { |
michael@0 | 142 | cacheURL = pageURLMappings.get(pageURL); |
michael@0 | 143 | } |
michael@0 | 144 | |
michael@0 | 145 | // If there's no favicon URL given, try and hit the cache with the default one. |
michael@0 | 146 | if (cacheURL == null) { |
michael@0 | 147 | cacheURL = guessDefaultFaviconURL(pageURL); |
michael@0 | 148 | } |
michael@0 | 149 | |
michael@0 | 150 | // If it's something we can't even figure out a default URL for, just give up. |
michael@0 | 151 | if (cacheURL == null) { |
michael@0 | 152 | return dispatchResult(pageURL, null, defaultFavicon, listener); |
michael@0 | 153 | } |
michael@0 | 154 | |
michael@0 | 155 | Bitmap cachedIcon = getSizedFaviconFromCache(cacheURL, targetSize); |
michael@0 | 156 | if (cachedIcon != null) { |
michael@0 | 157 | return dispatchResult(pageURL, cacheURL, cachedIcon, listener); |
michael@0 | 158 | } |
michael@0 | 159 | |
michael@0 | 160 | // Check if favicon has failed. |
michael@0 | 161 | if (faviconsCache.isFailedFavicon(cacheURL)) { |
michael@0 | 162 | return dispatchResult(pageURL, cacheURL, defaultFavicon, listener); |
michael@0 | 163 | } |
michael@0 | 164 | |
michael@0 | 165 | // Failing that, try and get one from the database or internet. |
michael@0 | 166 | return loadUncachedFavicon(pageURL, faviconURL, flags, targetSize, listener); |
michael@0 | 167 | } |
michael@0 | 168 | |
michael@0 | 169 | /** |
michael@0 | 170 | * Returns the cached Favicon closest to the target size if any exists or is coercible. Returns |
michael@0 | 171 | * null otherwise. Does not query the database or network for the Favicon is the result is not |
michael@0 | 172 | * immediately available. |
michael@0 | 173 | * |
michael@0 | 174 | * @param faviconURL URL of the Favicon to query for. |
michael@0 | 175 | * @param targetSize The desired size of the returned Favicon. |
michael@0 | 176 | * @return The cached Favicon, rescaled to be as close as possible to the target size, if any exists. |
michael@0 | 177 | * null if no applicable Favicon exists in the cache. |
michael@0 | 178 | */ |
michael@0 | 179 | public static Bitmap getSizedFaviconFromCache(String faviconURL, int targetSize) { |
michael@0 | 180 | return faviconsCache.getFaviconForDimensions(faviconURL, targetSize); |
michael@0 | 181 | } |
michael@0 | 182 | |
michael@0 | 183 | /** |
michael@0 | 184 | * Attempts to find a Favicon for the provided page URL from either the mem cache or the database. |
michael@0 | 185 | * Does not need an explicit favicon URL, since, as we are accessing the database anyway, we |
michael@0 | 186 | * can query the history DB for the Favicon URL. |
michael@0 | 187 | * Handy for easing the transition from caching with page URLs to caching with Favicon URLs. |
michael@0 | 188 | * |
michael@0 | 189 | * A null result is passed to the listener if no value is locally available. The Favicon is not |
michael@0 | 190 | * added to the failure cache. |
michael@0 | 191 | * |
michael@0 | 192 | * @param pageURL Page URL for which a Favicon is wanted. |
michael@0 | 193 | * @param targetSize Target size of the desired Favicon to pass to the cache query |
michael@0 | 194 | * @param callback Callback to fire with the result. |
michael@0 | 195 | * @return The job ID of the spawned async task, if any. |
michael@0 | 196 | */ |
michael@0 | 197 | public static int getSizedFaviconForPageFromLocal(final String pageURL, final int targetSize, final OnFaviconLoadedListener callback) { |
michael@0 | 198 | // Firstly, try extremely hard to cheat. |
michael@0 | 199 | // Have we cached this favicon URL? If we did, we can consult the memcache right away. |
michael@0 | 200 | String targetURL = pageURLMappings.get(pageURL); |
michael@0 | 201 | if (targetURL != null) { |
michael@0 | 202 | // Check if favicon has failed. |
michael@0 | 203 | if (faviconsCache.isFailedFavicon(targetURL)) { |
michael@0 | 204 | return dispatchResult(pageURL, targetURL, null, callback); |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | // Do we have a Favicon in the cache for this favicon URL? |
michael@0 | 208 | Bitmap result = getSizedFaviconFromCache(targetURL, targetSize); |
michael@0 | 209 | if (result != null) { |
michael@0 | 210 | // Victory - immediate response! |
michael@0 | 211 | return dispatchResult(pageURL, targetURL, result, callback); |
michael@0 | 212 | } |
michael@0 | 213 | } |
michael@0 | 214 | |
michael@0 | 215 | // No joy using in-memory resources. Go to background thread and ask the database. |
michael@0 | 216 | LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageURL, targetURL, 0, callback, targetSize, true); |
michael@0 | 217 | int taskId = task.getId(); |
michael@0 | 218 | synchronized(loadTasks) { |
michael@0 | 219 | loadTasks.put(taskId, task); |
michael@0 | 220 | } |
michael@0 | 221 | task.execute(); |
michael@0 | 222 | return taskId; |
michael@0 | 223 | } |
michael@0 | 224 | |
michael@0 | 225 | public static int getSizedFaviconForPageFromLocal(final String pageURL, final OnFaviconLoadedListener callback) { |
michael@0 | 226 | return getSizedFaviconForPageFromLocal(pageURL, defaultFaviconSize, callback); |
michael@0 | 227 | } |
michael@0 | 228 | |
michael@0 | 229 | /** |
michael@0 | 230 | * Helper method to determine the URL of the Favicon image for a given page URL by querying the |
michael@0 | 231 | * history database. Should only be called from the background thread - does database access. |
michael@0 | 232 | * |
michael@0 | 233 | * @param pageURL The URL of a webpage with a Favicon. |
michael@0 | 234 | * @return The URL of the Favicon used by that webpage, according to either the History database |
michael@0 | 235 | * or a somewhat educated guess. |
michael@0 | 236 | */ |
michael@0 | 237 | public static String getFaviconURLForPageURL(String pageURL) { |
michael@0 | 238 | // Attempt to determine the Favicon URL from the Tabs datastructure. Can dodge having to use |
michael@0 | 239 | // the database sometimes by doing this. |
michael@0 | 240 | String targetURL; |
michael@0 | 241 | Tab theTab = Tabs.getInstance().getFirstTabForUrl(pageURL); |
michael@0 | 242 | if (theTab != null) { |
michael@0 | 243 | targetURL = theTab.getFaviconURL(); |
michael@0 | 244 | if (targetURL != null) { |
michael@0 | 245 | return targetURL; |
michael@0 | 246 | } |
michael@0 | 247 | } |
michael@0 | 248 | |
michael@0 | 249 | targetURL = BrowserDB.getFaviconUrlForHistoryUrl(context.getContentResolver(), pageURL); |
michael@0 | 250 | if (targetURL == null) { |
michael@0 | 251 | // Nothing in the history database. Fall back to the default URL and hope for the best. |
michael@0 | 252 | targetURL = guessDefaultFaviconURL(pageURL); |
michael@0 | 253 | } |
michael@0 | 254 | return targetURL; |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | /** |
michael@0 | 258 | * Helper function to create an async job to load a Favicon which does not exist in the memcache. |
michael@0 | 259 | * Contains logic to prevent the repeated loading of Favicons which have previously failed. |
michael@0 | 260 | * There is no support for recovery from transient failures. |
michael@0 | 261 | * |
michael@0 | 262 | * @param pageUrl URL of the page for which to load a Favicon. If null, no job is created. |
michael@0 | 263 | * @param faviconUrl The URL of the Favicon to load. If null, an attempt to infer the value from |
michael@0 | 264 | * the history database will be made, and ultimately an attempt to guess will |
michael@0 | 265 | * be made. |
michael@0 | 266 | * @param flags Flags to be used by the LoadFaviconTask while loading. Currently only one flag |
michael@0 | 267 | * is supported, LoadFaviconTask.FLAG_PERSIST. |
michael@0 | 268 | * If FLAG_PERSIST is set and the Favicon is ultimately loaded from the internet, |
michael@0 | 269 | * the downloaded Favicon is subsequently stored in the local database. |
michael@0 | 270 | * If FLAG_PERSIST is unset, the downloaded Favicon is stored only in the memcache. |
michael@0 | 271 | * FLAG_PERSIST has no effect on loads which come from the database. |
michael@0 | 272 | * @param listener The OnFaviconLoadedListener to invoke with the result of this Favicon load. |
michael@0 | 273 | * @return The id of the LoadFaviconTask handling this job. |
michael@0 | 274 | */ |
michael@0 | 275 | private static int loadUncachedFavicon(String pageUrl, String faviconUrl, int flags, int targetSize, OnFaviconLoadedListener listener) { |
michael@0 | 276 | // Handle the case where we have no page url. |
michael@0 | 277 | if (TextUtils.isEmpty(pageUrl)) { |
michael@0 | 278 | dispatchResult(null, null, null, listener); |
michael@0 | 279 | return NOT_LOADING; |
michael@0 | 280 | } |
michael@0 | 281 | |
michael@0 | 282 | LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageUrl, faviconUrl, flags, listener, targetSize, false); |
michael@0 | 283 | |
michael@0 | 284 | int taskId = task.getId(); |
michael@0 | 285 | synchronized(loadTasks) { |
michael@0 | 286 | loadTasks.put(taskId, task); |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | task.execute(); |
michael@0 | 290 | |
michael@0 | 291 | return taskId; |
michael@0 | 292 | } |
michael@0 | 293 | |
michael@0 | 294 | public static void putFaviconInMemCache(String pageUrl, Bitmap image) { |
michael@0 | 295 | faviconsCache.putSingleFavicon(pageUrl, image); |
michael@0 | 296 | } |
michael@0 | 297 | |
michael@0 | 298 | /** |
michael@0 | 299 | * Adds the bitmaps given by the specified iterator to the cache associated with the url given. |
michael@0 | 300 | * Future requests for images will be able to select the least larger image than the target |
michael@0 | 301 | * size from this new set of images. |
michael@0 | 302 | * |
michael@0 | 303 | * @param pageUrl The URL to associate the new favicons with. |
michael@0 | 304 | * @param images An iterator over the new favicons to put in the cache. |
michael@0 | 305 | */ |
michael@0 | 306 | public static void putFaviconsInMemCache(String pageUrl, Iterator<Bitmap> images, boolean permanently) { |
michael@0 | 307 | faviconsCache.putFavicons(pageUrl, images, permanently); |
michael@0 | 308 | } |
michael@0 | 309 | |
michael@0 | 310 | public static void putFaviconsInMemCache(String pageUrl, Iterator<Bitmap> images) { |
michael@0 | 311 | putFaviconsInMemCache(pageUrl, images, false); |
michael@0 | 312 | } |
michael@0 | 313 | |
michael@0 | 314 | public static void clearMemCache() { |
michael@0 | 315 | faviconsCache.evictAll(); |
michael@0 | 316 | pageURLMappings.evictAll(); |
michael@0 | 317 | } |
michael@0 | 318 | |
michael@0 | 319 | public static void putFaviconInFailedCache(String faviconURL) { |
michael@0 | 320 | faviconsCache.putFailed(faviconURL); |
michael@0 | 321 | } |
michael@0 | 322 | |
michael@0 | 323 | public static boolean cancelFaviconLoad(int taskId) { |
michael@0 | 324 | if (taskId == NOT_LOADING) { |
michael@0 | 325 | return false; |
michael@0 | 326 | } |
michael@0 | 327 | |
michael@0 | 328 | boolean cancelled; |
michael@0 | 329 | synchronized (loadTasks) { |
michael@0 | 330 | if (loadTasks.indexOfKey(taskId) < 0) { |
michael@0 | 331 | return false; |
michael@0 | 332 | } |
michael@0 | 333 | |
michael@0 | 334 | Log.v(LOGTAG, "Cancelling favicon load " + taskId + "."); |
michael@0 | 335 | |
michael@0 | 336 | LoadFaviconTask task = loadTasks.get(taskId); |
michael@0 | 337 | cancelled = task.cancel(false); |
michael@0 | 338 | } |
michael@0 | 339 | return cancelled; |
michael@0 | 340 | } |
michael@0 | 341 | |
michael@0 | 342 | public static void close() { |
michael@0 | 343 | Log.d(LOGTAG, "Closing Favicons database"); |
michael@0 | 344 | |
michael@0 | 345 | // Cancel any pending tasks |
michael@0 | 346 | synchronized (loadTasks) { |
michael@0 | 347 | final int count = loadTasks.size(); |
michael@0 | 348 | for (int i = 0; i < count; i++) { |
michael@0 | 349 | cancelFaviconLoad(loadTasks.keyAt(i)); |
michael@0 | 350 | } |
michael@0 | 351 | loadTasks.clear(); |
michael@0 | 352 | } |
michael@0 | 353 | |
michael@0 | 354 | LoadFaviconTask.closeHTTPClient(); |
michael@0 | 355 | } |
michael@0 | 356 | |
michael@0 | 357 | /** |
michael@0 | 358 | * Get the dominant colour of the Favicon at the URL given, if any exists in the cache. |
michael@0 | 359 | * |
michael@0 | 360 | * @param url The URL of the Favicon, to be used as the cache key for the colour value. |
michael@0 | 361 | * @return The dominant colour of the provided Favicon. |
michael@0 | 362 | */ |
michael@0 | 363 | public static int getFaviconColor(String url) { |
michael@0 | 364 | return faviconsCache.getDominantColor(url); |
michael@0 | 365 | } |
michael@0 | 366 | |
michael@0 | 367 | /** |
michael@0 | 368 | * Called by GeckoApp on startup to pass this class a reference to the GeckoApp object used as |
michael@0 | 369 | * the application's Context. |
michael@0 | 370 | * Consider replacing with references to a staticly held reference to the GeckoApp object. |
michael@0 | 371 | * |
michael@0 | 372 | * @param context A reference to the GeckoApp instance. |
michael@0 | 373 | */ |
michael@0 | 374 | public static void attachToContext(Context context) throws Exception { |
michael@0 | 375 | final Resources res = context.getResources(); |
michael@0 | 376 | Favicons.context = context; |
michael@0 | 377 | |
michael@0 | 378 | // Decode the default Favicon ready for use. |
michael@0 | 379 | defaultFavicon = BitmapFactory.decodeResource(res, R.drawable.favicon); |
michael@0 | 380 | if (defaultFavicon == null) { |
michael@0 | 381 | throw new Exception("Null default favicon was returned from the resources system!"); |
michael@0 | 382 | } |
michael@0 | 383 | |
michael@0 | 384 | defaultFaviconSize = res.getDimensionPixelSize(R.dimen.favicon_bg); |
michael@0 | 385 | |
michael@0 | 386 | // Screen-density-adjusted upper limit on favicon size. Favicons larger than this are |
michael@0 | 387 | // downscaled to this size or discarded. |
michael@0 | 388 | largestFaviconSize = context.getResources().getDimensionPixelSize(R.dimen.favicon_largest_interesting_size); |
michael@0 | 389 | faviconsCache = new FaviconCache(FAVICON_CACHE_SIZE_BYTES, largestFaviconSize); |
michael@0 | 390 | |
michael@0 | 391 | // Initialize page mappings for each of our special pages. |
michael@0 | 392 | for (String url : AboutPages.getDefaultIconPages()) { |
michael@0 | 393 | pageURLMappings.putWithoutEviction(url, BUILT_IN_FAVICON_URL); |
michael@0 | 394 | } |
michael@0 | 395 | |
michael@0 | 396 | // Load and cache the built-in favicon in each of its sizes. |
michael@0 | 397 | // TODO: don't open the zip twice! |
michael@0 | 398 | List<Bitmap> toInsert = Arrays.asList(loadBrandingBitmap(context, "favicon64.png"), |
michael@0 | 399 | loadBrandingBitmap(context, "favicon32.png")); |
michael@0 | 400 | |
michael@0 | 401 | putFaviconsInMemCache(BUILT_IN_FAVICON_URL, toInsert.iterator(), true); |
michael@0 | 402 | } |
michael@0 | 403 | |
michael@0 | 404 | /** |
michael@0 | 405 | * Compute a string like: |
michael@0 | 406 | * "jar:jar:file:///data/app/org.mozilla.firefox-1.apk!/assets/omni.ja!/chrome/chrome/content/branding/favicon64.png" |
michael@0 | 407 | */ |
michael@0 | 408 | private static String getBrandingBitmapPath(Context context, String name) { |
michael@0 | 409 | final String apkPath = context.getPackageResourcePath(); |
michael@0 | 410 | return "jar:jar:" + new File(apkPath).toURI() + "!/" + |
michael@0 | 411 | AppConstants.OMNIJAR_NAME + "!/" + |
michael@0 | 412 | "chrome/chrome/content/branding/" + name; |
michael@0 | 413 | } |
michael@0 | 414 | |
michael@0 | 415 | private static Bitmap loadBrandingBitmap(Context context, String name) { |
michael@0 | 416 | Bitmap b = GeckoJarReader.getBitmap(context.getResources(), |
michael@0 | 417 | getBrandingBitmapPath(context, name)); |
michael@0 | 418 | if (b == null) { |
michael@0 | 419 | throw new IllegalStateException("Bitmap " + name + " missing from JAR!"); |
michael@0 | 420 | } |
michael@0 | 421 | return b; |
michael@0 | 422 | } |
michael@0 | 423 | |
michael@0 | 424 | /** |
michael@0 | 425 | * Helper method to get the default Favicon URL for a given pageURL. Generally: somewhere.com/favicon.ico |
michael@0 | 426 | * |
michael@0 | 427 | * @param pageURL Page URL for which a default Favicon URL is requested |
michael@0 | 428 | * @return The default Favicon URL. |
michael@0 | 429 | */ |
michael@0 | 430 | public static String guessDefaultFaviconURL(String pageURL) { |
michael@0 | 431 | // Special-casing for about: pages. The favicon for about:pages which don't provide a link tag |
michael@0 | 432 | // is bundled in the database, keyed only by page URL, hence the need to return the page URL |
michael@0 | 433 | // here. If the database ever migrates to stop being silly in this way, this can plausibly |
michael@0 | 434 | // be removed. |
michael@0 | 435 | if (AboutPages.isAboutPage(pageURL) || pageURL.startsWith("jar:")) { |
michael@0 | 436 | return pageURL; |
michael@0 | 437 | } |
michael@0 | 438 | |
michael@0 | 439 | try { |
michael@0 | 440 | // Fall back to trying "someScheme:someDomain.someExtension/favicon.ico". |
michael@0 | 441 | URI u = new URI(pageURL); |
michael@0 | 442 | return new URI(u.getScheme(), |
michael@0 | 443 | u.getAuthority(), |
michael@0 | 444 | "/favicon.ico", null, |
michael@0 | 445 | null).toString(); |
michael@0 | 446 | } catch (URISyntaxException e) { |
michael@0 | 447 | Log.e(LOGTAG, "URISyntaxException getting default favicon URL", e); |
michael@0 | 448 | return null; |
michael@0 | 449 | } |
michael@0 | 450 | } |
michael@0 | 451 | |
michael@0 | 452 | public static void removeLoadTask(int taskId) { |
michael@0 | 453 | synchronized(loadTasks) { |
michael@0 | 454 | loadTasks.delete(taskId); |
michael@0 | 455 | } |
michael@0 | 456 | } |
michael@0 | 457 | |
michael@0 | 458 | /** |
michael@0 | 459 | * Method to wrap FaviconCache.isFailedFavicon for use by LoadFaviconTask. |
michael@0 | 460 | * |
michael@0 | 461 | * @param faviconURL Favicon URL to check for failure. |
michael@0 | 462 | */ |
michael@0 | 463 | static boolean isFailedFavicon(String faviconURL) { |
michael@0 | 464 | return faviconsCache.isFailedFavicon(faviconURL); |
michael@0 | 465 | } |
michael@0 | 466 | |
michael@0 | 467 | /** |
michael@0 | 468 | * Sidestep the cache and get, from either the database or the internet, a favicon |
michael@0 | 469 | * suitable for use as an app icon for the provided URL. |
michael@0 | 470 | * |
michael@0 | 471 | * Useful for creating homescreen shortcuts without being limited |
michael@0 | 472 | * by possibly low-resolution values in the cache. |
michael@0 | 473 | * |
michael@0 | 474 | * Deduces the favicon URL from the browser database, guessing if necessary. |
michael@0 | 475 | * |
michael@0 | 476 | * @param url page URL to get a large favicon image for. |
michael@0 | 477 | * @param onFaviconLoadedListener listener to call back with the result. |
michael@0 | 478 | */ |
michael@0 | 479 | public static void getPreferredSizeFaviconForPage(String url, OnFaviconLoadedListener onFaviconLoadedListener) { |
michael@0 | 480 | int preferredSize = GeckoAppShell.getPreferredIconSize(); |
michael@0 | 481 | loadUncachedFavicon(url, null, 0, preferredSize, onFaviconLoadedListener); |
michael@0 | 482 | } |
michael@0 | 483 | } |