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.

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

mercurial