mobile/android/base/favicons/LoadFaviconTask.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 package org.mozilla.gecko.favicons;
michael@0 6
michael@0 7
michael@0 8 import android.content.ContentResolver;
michael@0 9 import android.graphics.Bitmap;
michael@0 10 import android.net.http.AndroidHttpClient;
michael@0 11 import android.os.Handler;
michael@0 12 import android.text.TextUtils;
michael@0 13 import android.util.Log;
michael@0 14 import org.apache.http.Header;
michael@0 15 import org.apache.http.HttpEntity;
michael@0 16 import org.apache.http.HttpResponse;
michael@0 17 import org.apache.http.client.methods.HttpGet;
michael@0 18 import org.mozilla.gecko.GeckoAppShell;
michael@0 19 import org.mozilla.gecko.db.BrowserDB;
michael@0 20 import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
michael@0 21 import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
michael@0 22 import org.mozilla.gecko.util.GeckoJarReader;
michael@0 23 import org.mozilla.gecko.util.ThreadUtils;
michael@0 24 import org.mozilla.gecko.util.UiAsyncTask;
michael@0 25 import static org.mozilla.gecko.favicons.Favicons.context;
michael@0 26
michael@0 27 import java.io.IOException;
michael@0 28 import java.io.InputStream;
michael@0 29 import java.net.URI;
michael@0 30 import java.net.URISyntaxException;
michael@0 31 import java.util.HashMap;
michael@0 32 import java.util.HashSet;
michael@0 33 import java.util.LinkedList;
michael@0 34 import java.util.concurrent.atomic.AtomicInteger;
michael@0 35
michael@0 36 /**
michael@0 37 * Class representing the asynchronous task to load a Favicon which is not currently in the in-memory
michael@0 38 * cache.
michael@0 39 * The implementation initially tries to get the Favicon from the database. Upon failure, the icon
michael@0 40 * is loaded from the internet.
michael@0 41 */
michael@0 42 public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
michael@0 43 private static final String LOGTAG = "LoadFaviconTask";
michael@0 44
michael@0 45 // Access to this map needs to be synchronized prevent multiple jobs loading the same favicon
michael@0 46 // from executing concurrently.
michael@0 47 private static final HashMap<String, LoadFaviconTask> loadsInFlight = new HashMap<String, LoadFaviconTask>();
michael@0 48
michael@0 49 public static final int FLAG_PERSIST = 1;
michael@0 50 public static final int FLAG_SCALE = 2;
michael@0 51 private static final int MAX_REDIRECTS_TO_FOLLOW = 5;
michael@0 52 // The default size of the buffer to use for downloading Favicons in the event no size is given
michael@0 53 // by the server.
michael@0 54 private static final int DEFAULT_FAVICON_BUFFER_SIZE = 25000;
michael@0 55
michael@0 56 private static AtomicInteger nextFaviconLoadId = new AtomicInteger(0);
michael@0 57 private int id;
michael@0 58 private String pageUrl;
michael@0 59 private String faviconURL;
michael@0 60 private OnFaviconLoadedListener listener;
michael@0 61 private int flags;
michael@0 62
michael@0 63 private final boolean onlyFromLocal;
michael@0 64
michael@0 65 // Assuming square favicons, judging by width only is acceptable.
michael@0 66 protected int targetWidth;
michael@0 67 private LinkedList<LoadFaviconTask> chainees;
michael@0 68 private boolean isChaining;
michael@0 69
michael@0 70 static AndroidHttpClient httpClient = AndroidHttpClient.newInstance(GeckoAppShell.getGeckoInterface().getDefaultUAString());
michael@0 71
michael@0 72 public LoadFaviconTask(Handler backgroundThreadHandler,
michael@0 73 String pageUrl, String faviconUrl, int flags,
michael@0 74 OnFaviconLoadedListener listener) {
michael@0 75 this(backgroundThreadHandler, pageUrl, faviconUrl, flags, listener, -1, false);
michael@0 76 }
michael@0 77 public LoadFaviconTask(Handler backgroundThreadHandler,
michael@0 78 String pageUrl, String faviconUrl, int flags,
michael@0 79 OnFaviconLoadedListener listener, int targetWidth, boolean onlyFromLocal) {
michael@0 80 super(backgroundThreadHandler);
michael@0 81
michael@0 82 id = nextFaviconLoadId.incrementAndGet();
michael@0 83
michael@0 84 this.pageUrl = pageUrl;
michael@0 85 this.faviconURL = faviconUrl;
michael@0 86 this.listener = listener;
michael@0 87 this.flags = flags;
michael@0 88 this.targetWidth = targetWidth;
michael@0 89 this.onlyFromLocal = onlyFromLocal;
michael@0 90 }
michael@0 91
michael@0 92 // Runs in background thread
michael@0 93 private LoadFaviconResult loadFaviconFromDb() {
michael@0 94 ContentResolver resolver = context.getContentResolver();
michael@0 95 return BrowserDB.getFaviconForFaviconUrl(resolver, faviconURL);
michael@0 96 }
michael@0 97
michael@0 98 // Runs in background thread
michael@0 99 private void saveFaviconToDb(final byte[] encodedFavicon) {
michael@0 100 if (encodedFavicon == null) {
michael@0 101 return;
michael@0 102 }
michael@0 103
michael@0 104 if ((flags & FLAG_PERSIST) == 0) {
michael@0 105 return;
michael@0 106 }
michael@0 107
michael@0 108 ContentResolver resolver = context.getContentResolver();
michael@0 109 BrowserDB.updateFaviconForUrl(resolver, pageUrl, encodedFavicon, faviconURL);
michael@0 110 }
michael@0 111
michael@0 112 /**
michael@0 113 * Helper method for trying the download request to grab a Favicon.
michael@0 114 * @param faviconURI URL of Favicon to try and download
michael@0 115 * @return The HttpResponse containing the downloaded Favicon if successful, null otherwise.
michael@0 116 */
michael@0 117 private HttpResponse tryDownload(URI faviconURI) throws URISyntaxException, IOException {
michael@0 118 HashSet<String> visitedLinkSet = new HashSet<String>();
michael@0 119 visitedLinkSet.add(faviconURI.toString());
michael@0 120 return tryDownloadRecurse(faviconURI, visitedLinkSet);
michael@0 121 }
michael@0 122 private HttpResponse tryDownloadRecurse(URI faviconURI, HashSet<String> visited) throws URISyntaxException, IOException {
michael@0 123 if (visited.size() == MAX_REDIRECTS_TO_FOLLOW) {
michael@0 124 return null;
michael@0 125 }
michael@0 126
michael@0 127 HttpGet request = new HttpGet(faviconURI);
michael@0 128 HttpResponse response = httpClient.execute(request);
michael@0 129 if (response == null) {
michael@0 130 return null;
michael@0 131 }
michael@0 132
michael@0 133 if (response.getStatusLine() != null) {
michael@0 134
michael@0 135 // Was the response a failure?
michael@0 136 int status = response.getStatusLine().getStatusCode();
michael@0 137
michael@0 138 // Handle HTTP status codes requesting a redirect.
michael@0 139 if (status >= 300 && status < 400) {
michael@0 140 Header header = response.getFirstHeader("Location");
michael@0 141
michael@0 142 // Handle mad webservers.
michael@0 143 if (header == null) {
michael@0 144 return null;
michael@0 145 }
michael@0 146
michael@0 147 String newURI = header.getValue();
michael@0 148 if (newURI == null || newURI.equals(faviconURI.toString())) {
michael@0 149 return null;
michael@0 150 }
michael@0 151
michael@0 152 if (visited.contains(newURI)) {
michael@0 153 // Already been redirected here - abort.
michael@0 154 return null;
michael@0 155 }
michael@0 156
michael@0 157 visited.add(newURI);
michael@0 158 return tryDownloadRecurse(new URI(newURI), visited);
michael@0 159 }
michael@0 160
michael@0 161 if (status >= 400) {
michael@0 162 return null;
michael@0 163 }
michael@0 164 }
michael@0 165 return response;
michael@0 166 }
michael@0 167
michael@0 168 /**
michael@0 169 * Retrieve the specified favicon from the JAR, returning null if it's not
michael@0 170 * a JAR URI.
michael@0 171 */
michael@0 172 private static Bitmap fetchJARFavicon(String uri) {
michael@0 173 if (uri == null) {
michael@0 174 return null;
michael@0 175 }
michael@0 176 if (uri.startsWith("jar:jar:")) {
michael@0 177 Log.d(LOGTAG, "Fetching favicon from JAR.");
michael@0 178 try {
michael@0 179 return GeckoJarReader.getBitmap(context.getResources(), uri);
michael@0 180 } catch (Exception e) {
michael@0 181 // Just about anything could happen here.
michael@0 182 Log.w(LOGTAG, "Error fetching favicon from JAR.", e);
michael@0 183 return null;
michael@0 184 }
michael@0 185 }
michael@0 186 return null;
michael@0 187 }
michael@0 188
michael@0 189 // Runs in background thread.
michael@0 190 // Does not attempt to fetch from JARs.
michael@0 191 private LoadFaviconResult downloadFavicon(URI targetFaviconURI) {
michael@0 192 if (targetFaviconURI == null) {
michael@0 193 return null;
michael@0 194 }
michael@0 195
michael@0 196 // Only get favicons for HTTP/HTTPS.
michael@0 197 String scheme = targetFaviconURI.getScheme();
michael@0 198 if (!"http".equals(scheme) && !"https".equals(scheme)) {
michael@0 199 return null;
michael@0 200 }
michael@0 201
michael@0 202 LoadFaviconResult result = null;
michael@0 203
michael@0 204 try {
michael@0 205 result = downloadAndDecodeImage(targetFaviconURI);
michael@0 206 } catch (Exception e) {
michael@0 207 Log.e(LOGTAG, "Error reading favicon", e);
michael@0 208 }
michael@0 209
michael@0 210 return result;
michael@0 211 }
michael@0 212
michael@0 213 /**
michael@0 214 * Download the Favicon from the given URL and pass it to the decoder function.
michael@0 215 *
michael@0 216 * @param targetFaviconURL URL of the favicon to download.
michael@0 217 * @return A LoadFaviconResult containing the bitmap(s) extracted from the downloaded file, or
michael@0 218 * null if no or corrupt data ware received.
michael@0 219 * @throws IOException If attempts to fully read the stream result in such an exception, such as
michael@0 220 * in the event of a transient connection failure.
michael@0 221 * @throws URISyntaxException If the underlying call to tryDownload retries and raises such an
michael@0 222 * exception trying a fallback URL.
michael@0 223 */
michael@0 224 private LoadFaviconResult downloadAndDecodeImage(URI targetFaviconURL) throws IOException, URISyntaxException {
michael@0 225 // Try the URL we were given.
michael@0 226 HttpResponse response = tryDownload(targetFaviconURL);
michael@0 227 if (response == null) {
michael@0 228 return null;
michael@0 229 }
michael@0 230
michael@0 231 HttpEntity entity = response.getEntity();
michael@0 232 if (entity == null) {
michael@0 233 return null;
michael@0 234 }
michael@0 235
michael@0 236 // This may not be provided, but if it is, it's useful.
michael@0 237 final long entityReportedLength = entity.getContentLength();
michael@0 238 int bufferSize;
michael@0 239 if (entityReportedLength > 0) {
michael@0 240 // The size was reported and sane, so let's use that.
michael@0 241 // Integer overflow should not be a problem for Favicon sizes...
michael@0 242 bufferSize = (int) entityReportedLength + 1;
michael@0 243 } else {
michael@0 244 // No declared size, so guess and reallocate later if it turns out to be too small.
michael@0 245 bufferSize = DEFAULT_FAVICON_BUFFER_SIZE;
michael@0 246 }
michael@0 247
michael@0 248 // Allocate a buffer to hold the raw favicon data downloaded.
michael@0 249 byte[] buffer = new byte[bufferSize];
michael@0 250
michael@0 251 // The offset of the start of the buffer's free space.
michael@0 252 int bPointer = 0;
michael@0 253
michael@0 254 // The quantity of bytes the last call to read yielded.
michael@0 255 int lastRead = 0;
michael@0 256 InputStream contentStream = entity.getContent();
michael@0 257 try {
michael@0 258 // Fully read the entity into the buffer - decoding of streams is not supported
michael@0 259 // (and questionably pointful - what would one do with a half-decoded Favicon?)
michael@0 260 while (lastRead != -1) {
michael@0 261 // Read as many bytes as are currently available into the buffer.
michael@0 262 lastRead = contentStream.read(buffer, bPointer, buffer.length - bPointer);
michael@0 263 bPointer += lastRead;
michael@0 264
michael@0 265 // If buffer has overflowed, double its size and carry on.
michael@0 266 if (bPointer == buffer.length) {
michael@0 267 bufferSize *= 2;
michael@0 268 byte[] newBuffer = new byte[bufferSize];
michael@0 269
michael@0 270 // Copy the contents of the old buffer into the new buffer.
michael@0 271 System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
michael@0 272 buffer = newBuffer;
michael@0 273 }
michael@0 274 }
michael@0 275 } finally {
michael@0 276 contentStream.close();
michael@0 277 }
michael@0 278
michael@0 279 // Having downloaded the image, decode it.
michael@0 280 return FaviconDecoder.decodeFavicon(buffer, 0, bPointer + 1);
michael@0 281 }
michael@0 282
michael@0 283 @Override
michael@0 284 protected Bitmap doInBackground(Void... unused) {
michael@0 285 if (isCancelled()) {
michael@0 286 return null;
michael@0 287 }
michael@0 288
michael@0 289 String storedFaviconUrl;
michael@0 290 boolean isUsingDefaultURL = false;
michael@0 291
michael@0 292 // Handle the case of malformed favicon URL.
michael@0 293 // If favicon is empty, fall back to the stored one.
michael@0 294 if (TextUtils.isEmpty(faviconURL)) {
michael@0 295 // Try to get the favicon URL from the memory cache.
michael@0 296 storedFaviconUrl = Favicons.getFaviconURLForPageURLFromCache(pageUrl);
michael@0 297
michael@0 298 // If that failed, try to get the URL from the database.
michael@0 299 if (storedFaviconUrl == null) {
michael@0 300 storedFaviconUrl = Favicons.getFaviconURLForPageURL(pageUrl);
michael@0 301 if (storedFaviconUrl != null) {
michael@0 302 // If that succeeded, cache the URL loaded from the database in memory.
michael@0 303 Favicons.putFaviconURLForPageURLInCache(pageUrl, storedFaviconUrl);
michael@0 304 }
michael@0 305 }
michael@0 306
michael@0 307 // If we found a faviconURL - use it.
michael@0 308 if (storedFaviconUrl != null) {
michael@0 309 faviconURL = storedFaviconUrl;
michael@0 310 } else {
michael@0 311 // If we don't have a stored one, fall back to the default.
michael@0 312 faviconURL = Favicons.guessDefaultFaviconURL(pageUrl);
michael@0 313
michael@0 314 if (TextUtils.isEmpty(faviconURL)) {
michael@0 315 return null;
michael@0 316 }
michael@0 317 isUsingDefaultURL = true;
michael@0 318 }
michael@0 319 }
michael@0 320
michael@0 321 // Check if favicon has failed - if so, give up. We need this check because, sometimes, we
michael@0 322 // didn't know the real Favicon URL until we asked the database.
michael@0 323 if (Favicons.isFailedFavicon(faviconURL)) {
michael@0 324 return null;
michael@0 325 }
michael@0 326
michael@0 327 if (isCancelled()) {
michael@0 328 return null;
michael@0 329 }
michael@0 330
michael@0 331 Bitmap image;
michael@0 332 // Determine if there is already an ongoing task to fetch the Favicon we desire.
michael@0 333 // If there is, just join the queue and wait for it to finish. If not, we carry on.
michael@0 334 synchronized(loadsInFlight) {
michael@0 335 // Another load of the current Favicon is already underway
michael@0 336 LoadFaviconTask existingTask = loadsInFlight.get(faviconURL);
michael@0 337 if (existingTask != null && !existingTask.isCancelled()) {
michael@0 338 existingTask.chainTasks(this);
michael@0 339 isChaining = true;
michael@0 340
michael@0 341 // If we are chaining, we want to keep the first task started to do this job as the one
michael@0 342 // in the hashmap so subsequent tasks will add themselves to its chaining list.
michael@0 343 return null;
michael@0 344 }
michael@0 345
michael@0 346 // We do not want to update the hashmap if the task has chained - other tasks need to
michael@0 347 // chain onto the same parent task.
michael@0 348 loadsInFlight.put(faviconURL, this);
michael@0 349 }
michael@0 350
michael@0 351 if (isCancelled()) {
michael@0 352 return null;
michael@0 353 }
michael@0 354
michael@0 355 // If there are no valid bitmaps decoded, the returned LoadFaviconResult is null.
michael@0 356 LoadFaviconResult loadedBitmaps = loadFaviconFromDb();
michael@0 357 if (loadedBitmaps != null) {
michael@0 358 return pushToCacheAndGetResult(loadedBitmaps);
michael@0 359 }
michael@0 360
michael@0 361 if (onlyFromLocal || isCancelled()) {
michael@0 362 return null;
michael@0 363 }
michael@0 364
michael@0 365 // Let's see if it's in a JAR.
michael@0 366 image = fetchJARFavicon(faviconURL);
michael@0 367 if (imageIsValid(image)) {
michael@0 368 // We don't want to put this into the DB.
michael@0 369 Favicons.putFaviconInMemCache(faviconURL, image);
michael@0 370 return image;
michael@0 371 }
michael@0 372
michael@0 373 try {
michael@0 374 loadedBitmaps = downloadFavicon(new URI(faviconURL));
michael@0 375 } catch (URISyntaxException e) {
michael@0 376 Log.e(LOGTAG, "The provided favicon URL is not valid");
michael@0 377 return null;
michael@0 378 } catch (Exception e) {
michael@0 379 Log.e(LOGTAG, "Couldn't download favicon.", e);
michael@0 380 }
michael@0 381
michael@0 382 if (loadedBitmaps != null) {
michael@0 383 // Fetching bytes to store can fail. saveFaviconToDb will
michael@0 384 // do the right thing, but we still choose to cache the
michael@0 385 // downloaded icon in memory.
michael@0 386 saveFaviconToDb(loadedBitmaps.getBytesForDatabaseStorage());
michael@0 387 return pushToCacheAndGetResult(loadedBitmaps);
michael@0 388 }
michael@0 389
michael@0 390 if (isUsingDefaultURL) {
michael@0 391 Favicons.putFaviconInFailedCache(faviconURL);
michael@0 392 return null;
michael@0 393 }
michael@0 394
michael@0 395 if (isCancelled()) {
michael@0 396 return null;
michael@0 397 }
michael@0 398
michael@0 399 // If we're not already trying the default URL, try it now.
michael@0 400 final String guessed = Favicons.guessDefaultFaviconURL(pageUrl);
michael@0 401 if (guessed == null) {
michael@0 402 Favicons.putFaviconInFailedCache(faviconURL);
michael@0 403 return null;
michael@0 404 }
michael@0 405
michael@0 406 image = fetchJARFavicon(guessed);
michael@0 407 if (imageIsValid(image)) {
michael@0 408 // We don't want to put this into the DB.
michael@0 409 Favicons.putFaviconInMemCache(faviconURL, image);
michael@0 410 return image;
michael@0 411 }
michael@0 412
michael@0 413 try {
michael@0 414 loadedBitmaps = downloadFavicon(new URI(guessed));
michael@0 415 } catch (Exception e) {
michael@0 416 // Not interesting. It was an educated guess, anyway.
michael@0 417 return null;
michael@0 418 }
michael@0 419
michael@0 420 if (loadedBitmaps != null) {
michael@0 421 saveFaviconToDb(loadedBitmaps.getBytesForDatabaseStorage());
michael@0 422 return pushToCacheAndGetResult(loadedBitmaps);
michael@0 423 }
michael@0 424
michael@0 425 return null;
michael@0 426 }
michael@0 427
michael@0 428 /**
michael@0 429 * Helper method to put the result of a favicon load into the memory cache and then query the
michael@0 430 * cache for the particular bitmap we want for this request.
michael@0 431 * This call is certain to succeed, provided there was enough memory to decode this favicon.
michael@0 432 *
michael@0 433 * @param loadedBitmaps LoadFaviconResult to store.
michael@0 434 * @return The optimal favicon available to satisfy this LoadFaviconTask's request, or null if
michael@0 435 * we are under extreme memory pressure and find ourselves dropping the cache immediately.
michael@0 436 */
michael@0 437 private Bitmap pushToCacheAndGetResult(LoadFaviconResult loadedBitmaps) {
michael@0 438 Favicons.putFaviconsInMemCache(faviconURL, loadedBitmaps.getBitmaps());
michael@0 439 Bitmap result = Favicons.getSizedFaviconFromCache(faviconURL, targetWidth);
michael@0 440 return result;
michael@0 441 }
michael@0 442
michael@0 443 private static boolean imageIsValid(final Bitmap image) {
michael@0 444 return image != null &&
michael@0 445 image.getWidth() > 0 &&
michael@0 446 image.getHeight() > 0;
michael@0 447 }
michael@0 448
michael@0 449 @Override
michael@0 450 protected void onPostExecute(Bitmap image) {
michael@0 451 if (isChaining) {
michael@0 452 return;
michael@0 453 }
michael@0 454
michael@0 455 // Process the result, scale for the listener, etc.
michael@0 456 processResult(image);
michael@0 457
michael@0 458 synchronized (loadsInFlight) {
michael@0 459 // Prevent any other tasks from chaining on this one.
michael@0 460 loadsInFlight.remove(faviconURL);
michael@0 461 }
michael@0 462
michael@0 463 // Since any update to chainees is done while holding the loadsInFlight lock, once we reach
michael@0 464 // this point no further updates to that list can possibly take place (As far as other tasks
michael@0 465 // are concerned, there is no longer a task to chain from. The above block will have waited
michael@0 466 // for any tasks that were adding themselves to the list before reaching this point.)
michael@0 467
michael@0 468 // As such, I believe we're safe to do the following without holding the lock.
michael@0 469 // This is nice - we do not want to take the lock unless we have to anyway, and chaining rarely
michael@0 470 // actually happens outside of the strange situations unit tests create.
michael@0 471
michael@0 472 // Share the result with all chained tasks.
michael@0 473 if (chainees != null) {
michael@0 474 for (LoadFaviconTask t : chainees) {
michael@0 475 // In the case that we just decoded multiple favicons, either we're passing the right
michael@0 476 // image now, or the call into the cache in processResult will fetch the right one.
michael@0 477 t.processResult(image);
michael@0 478 }
michael@0 479 }
michael@0 480 }
michael@0 481
michael@0 482 private void processResult(Bitmap image) {
michael@0 483 Favicons.removeLoadTask(id);
michael@0 484 Bitmap scaled = image;
michael@0 485
michael@0 486 // Notify listeners, scaling if required.
michael@0 487 if (targetWidth != -1 && image != null && image.getWidth() != targetWidth) {
michael@0 488 scaled = Favicons.getSizedFaviconFromCache(faviconURL, targetWidth);
michael@0 489 }
michael@0 490
michael@0 491 Favicons.dispatchResult(pageUrl, faviconURL, scaled, listener);
michael@0 492 }
michael@0 493
michael@0 494 @Override
michael@0 495 protected void onCancelled() {
michael@0 496 Favicons.removeLoadTask(id);
michael@0 497
michael@0 498 synchronized(loadsInFlight) {
michael@0 499 // Only remove from the hashmap if the task there is the one that's being canceled.
michael@0 500 // Cancellation of a task that would have chained is not interesting to the hashmap.
michael@0 501 final LoadFaviconTask primary = loadsInFlight.get(faviconURL);
michael@0 502 if (primary == this) {
michael@0 503 loadsInFlight.remove(faviconURL);
michael@0 504 return;
michael@0 505 }
michael@0 506 if (primary == null) {
michael@0 507 // This shouldn't happen.
michael@0 508 return;
michael@0 509 }
michael@0 510 if (primary.chainees != null) {
michael@0 511 primary.chainees.remove(this);
michael@0 512 }
michael@0 513 }
michael@0 514
michael@0 515 // Note that we don't call the listener callback if the
michael@0 516 // favicon load is cancelled.
michael@0 517 }
michael@0 518
michael@0 519 /**
michael@0 520 * When the result of this job is ready, also notify the chainee of the result.
michael@0 521 * Used for aggregating concurrent requests for the same Favicon into a single actual request.
michael@0 522 * (Don't want to download a hundred instances of Google's Favicon at once, for example).
michael@0 523 * The loadsInFlight lock must be held when calling this function.
michael@0 524 *
michael@0 525 * @param aChainee LoadFaviconTask
michael@0 526 */
michael@0 527 private void chainTasks(LoadFaviconTask aChainee) {
michael@0 528 if (chainees == null) {
michael@0 529 chainees = new LinkedList<LoadFaviconTask>();
michael@0 530 }
michael@0 531
michael@0 532 chainees.add(aChainee);
michael@0 533 }
michael@0 534
michael@0 535 int getId() {
michael@0 536 return id;
michael@0 537 }
michael@0 538
michael@0 539 static void closeHTTPClient() {
michael@0 540 // This work must be done on a background thread because it shuts down
michael@0 541 // the connection pool, which typically involves closing a connection --
michael@0 542 // which counts as network activity.
michael@0 543 if (ThreadUtils.isOnBackgroundThread()) {
michael@0 544 if (httpClient != null) {
michael@0 545 httpClient.close();
michael@0 546 }
michael@0 547 return;
michael@0 548 }
michael@0 549
michael@0 550 ThreadUtils.postToBackgroundThread(new Runnable() {
michael@0 551 @Override
michael@0 552 public void run() {
michael@0 553 LoadFaviconTask.closeHTTPClient();
michael@0 554 }
michael@0 555 });
michael@0 556 }
michael@0 557 }

mercurial