mobile/android/base/ThumbnailHelper.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko;
     8 import org.mozilla.gecko.db.BrowserDB;
     9 import org.mozilla.gecko.gfx.BitmapUtils;
    10 import org.mozilla.gecko.gfx.IntSize;
    11 import org.mozilla.gecko.mozglue.DirectBufferAllocator;
    12 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
    14 import android.graphics.Bitmap;
    15 import android.util.Log;
    16 import android.content.res.Resources;
    18 import java.nio.ByteBuffer;
    19 import java.util.LinkedList;
    20 import java.util.concurrent.atomic.AtomicInteger;
    22 /**
    23  * Helper class to generate thumbnails for tabs.
    24  * Internally, a queue of pending thumbnails is maintained in mPendingThumbnails.
    25  * The head of the queue is the thumbnail that is currently being processed; upon
    26  * completion of the current thumbnail the next one is automatically processed.
    27  * Changes to the thumbnail width are stashed in mPendingWidth and the change is
    28  * applied between thumbnail processing. This allows a single thumbnail buffer to
    29  * be used for all thumbnails.
    30  */
    31 public final class ThumbnailHelper {
    32     private static final String LOGTAG = "GeckoThumbnailHelper";
    34     public static final float THUMBNAIL_ASPECT_RATIO = 0.571f;  // this is a 4:7 ratio (as per UX decision)
    36     // static singleton stuff
    38     private static ThumbnailHelper sInstance;
    40     public static synchronized ThumbnailHelper getInstance() {
    41         if (sInstance == null) {
    42             sInstance = new ThumbnailHelper();
    43         }
    44         return sInstance;
    45     }
    47     // instance stuff
    49     private final LinkedList<Tab> mPendingThumbnails;    // synchronized access only
    50     private AtomicInteger mPendingWidth;
    51     private int mWidth;
    52     private int mHeight;
    53     private ByteBuffer mBuffer;
    55     private ThumbnailHelper() {
    56         mPendingThumbnails = new LinkedList<Tab>();
    57         try {
    58             mPendingWidth = new AtomicInteger((int)GeckoAppShell.getContext().getResources().getDimension(R.dimen.tab_thumbnail_width));
    59         } catch (Resources.NotFoundException nfe) { mPendingWidth = new AtomicInteger(0); }
    60         mWidth = -1;
    61         mHeight = -1;
    62     }
    64     public void getAndProcessThumbnailFor(Tab tab) {
    65         if (AboutPages.isAboutHome(tab.getURL())) {
    66             tab.updateThumbnail(null);
    67             return;
    68         }
    70         if (tab.getState() == Tab.STATE_DELAYED) {
    71             String url = tab.getURL();
    72             if (url != null) {
    73                 byte[] thumbnail = BrowserDB.getThumbnailForUrl(GeckoAppShell.getContext().getContentResolver(), url);
    74                 if (thumbnail != null) {
    75                     setTabThumbnail(tab, null, thumbnail);
    76                 }
    77             }
    78             return;
    79         }
    81         synchronized (mPendingThumbnails) {
    82             if (mPendingThumbnails.lastIndexOf(tab) > 0) {
    83                 // This tab is already in the queue, so don't add it again.
    84                 // Note that if this tab is only at the *head* of the queue,
    85                 // (i.e. mPendingThumbnails.lastIndexOf(tab) == 0) then we do
    86                 // add it again because it may have already been thumbnailed
    87                 // and now we need to do it again.
    88                 return;
    89             }
    91             mPendingThumbnails.add(tab);
    92             if (mPendingThumbnails.size() > 1) {
    93                 // Some thumbnail was already being processed, so wait
    94                 // for that to be done.
    95                 return;
    96             }
    97         }
    98         requestThumbnailFor(tab);
    99     }
   101     public void setThumbnailWidth(int width) {
   102         // Check inverted for safety: Bug 803299 Comment 34.
   103         if (GeckoAppShell.getScreenDepth() == 24) {
   104             mPendingWidth.set(width);
   105         } else {
   106             // Bug 776906: on 16-bit screens we need to ensure an even width.
   107             mPendingWidth.set((width & 1) == 0 ? width : width + 1);
   108         }
   109     }
   111     private void updateThumbnailSize() {
   112         // Apply any pending width updates.
   113         mWidth = mPendingWidth.get();
   115         mHeight = Math.round(mWidth * THUMBNAIL_ASPECT_RATIO);
   117         int pixelSize = (GeckoAppShell.getScreenDepth() == 24) ? 4 : 2;
   118         int capacity = mWidth * mHeight * pixelSize;
   119         Log.d(LOGTAG, "Using new thumbnail size: " + capacity + " (width " + mWidth + ")");
   120         if (mBuffer == null || mBuffer.capacity() != capacity) {
   121             if (mBuffer != null) {
   122                 mBuffer = DirectBufferAllocator.free(mBuffer);
   123             }
   124             try {
   125                 mBuffer = DirectBufferAllocator.allocate(capacity);
   126             } catch (IllegalArgumentException iae) {
   127                 Log.w(LOGTAG, iae.toString());
   128             } catch (OutOfMemoryError oom) {
   129                 Log.w(LOGTAG, "Unable to allocate thumbnail buffer of capacity " + capacity);
   130             }
   131             // If we hit an error above, mBuffer will be pointing to null, so we are in a sane state.
   132         }
   133     }
   135     private void requestThumbnailFor(Tab tab) {
   136         updateThumbnailSize();
   138         if (mBuffer == null) {
   139             // Buffer allocation may have failed. In this case we can't send the
   140             // event requesting the screenshot which means we won't get back a response
   141             // and so our queue will grow unboundedly. Handle this scenario by clearing
   142             // the queue (no point trying more thumbnailing right now since we're likely
   143             // low on memory). We will try again normally on the next call to
   144             // getAndProcessThumbnailFor which will hopefully be when we have more free memory.
   145             synchronized (mPendingThumbnails) {
   146                 mPendingThumbnails.clear();
   147             }
   148             return;
   149         }
   151         Log.d(LOGTAG, "Sending thumbnail event: " + mWidth + ", " + mHeight);
   152         GeckoEvent e = GeckoEvent.createThumbnailEvent(tab.getId(), mWidth, mHeight, mBuffer);
   153         GeckoAppShell.sendEventToGecko(e);
   154     }
   156     /* This method is invoked by JNI once the thumbnail data is ready. */
   157     @WrapElementForJNI(stubName = "SendThumbnail")
   158     public static void notifyThumbnail(ByteBuffer data, int tabId, boolean success) {
   159         Tab tab = Tabs.getInstance().getTab(tabId);
   160         ThumbnailHelper helper = ThumbnailHelper.getInstance();
   161         if (success && tab != null) {
   162             helper.handleThumbnailData(tab, data);
   163         }
   164         helper.processNextThumbnail(tab);
   165     }
   167     private void processNextThumbnail(Tab tab) {
   168         Tab nextTab = null;
   169         synchronized (mPendingThumbnails) {
   170             if (tab != null && tab != mPendingThumbnails.peek()) {
   171                 Log.e(LOGTAG, "handleThumbnailData called with unexpected tab's data!");
   172                 // This should never happen, but recover gracefully by processing the
   173                 // unexpected tab that we found in the queue
   174             } else {
   175                 mPendingThumbnails.remove();
   176             }
   177             nextTab = mPendingThumbnails.peek();
   178         }
   179         if (nextTab != null) {
   180             requestThumbnailFor(nextTab);
   181         }
   182     }
   184     private void handleThumbnailData(Tab tab, ByteBuffer data) {
   185         Log.d(LOGTAG, "handleThumbnailData: " + data.capacity());
   186         if (data != mBuffer) {
   187             // This should never happen, but log it and recover gracefully
   188             Log.e(LOGTAG, "handleThumbnailData called with an unexpected ByteBuffer!");
   189         }
   191         if (shouldUpdateThumbnail(tab)) {
   192             processThumbnailData(tab, data);
   193         }
   194     }
   196     private void processThumbnailData(Tab tab, ByteBuffer data) {
   197         Bitmap b = tab.getThumbnailBitmap(mWidth, mHeight);
   198         data.position(0);
   199         b.copyPixelsFromBuffer(data);
   200         setTabThumbnail(tab, b, null);
   201     }
   203     private void setTabThumbnail(Tab tab, Bitmap bitmap, byte[] compressed) {
   204         if (bitmap == null) {
   205             if (compressed == null) {
   206                 Log.w(LOGTAG, "setTabThumbnail: one of bitmap or compressed must be non-null!");
   207                 return;
   208             }
   209             bitmap = BitmapUtils.decodeByteArray(compressed);
   210         }
   211         tab.updateThumbnail(bitmap);
   212     }
   214     private boolean shouldUpdateThumbnail(Tab tab) {
   215         return (Tabs.getInstance().isSelectedTab(tab) || (GeckoAppShell.getGeckoInterface() != null && GeckoAppShell.getGeckoInterface().areTabsShown()));
   216     }
   217 }

mercurial