Wed, 31 Dec 2014 07:22:50 +0100
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 }