// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.compositor.layouts.content; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.util.SparseArray; import android.view.View; import org.chromium.base.CommandLine; import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.JNINamespace; import org.chromium.chrome.R; import org.chromium.chrome.browser.ChromeSwitches; import org.chromium.chrome.browser.NativePage; import org.chromium.chrome.browser.tab.Tab; import org.chromium.ui.base.DeviceFormFactor; import java.util.ArrayList; import java.util.List; /** * The TabContentManager is responsible for serving tab contents to the UI components. Contents * could be live or static thumbnails. */ @JNINamespace("android") public class TabContentManager { private final Context mContext; private final float mThumbnailScale; private final int mFullResThumbnailsMaxSize; private final ContentOffsetProvider mContentOffsetProvider; private int[] mPriorityTabIds; private long mNativeTabContentManager; /** * A callback interface for decompressing the thumbnail for a tab into a bitmap. */ public static interface DecompressThumbnailCallback { /** * Called when the decompression finishes. * @param bitmap The {@link Bitmap} of the content. Null will be passed for failure. */ public void onFinishGetBitmap(Bitmap bitmap); } private final ArrayList<ThumbnailChangeListener> mListeners = new ArrayList<ThumbnailChangeListener>(); private final SparseArray<DecompressThumbnailCallback> mDecompressRequests = new SparseArray<TabContentManager.DecompressThumbnailCallback>(); private boolean mSnapshotsEnabled; /** * The Java interface for listening to thumbnail changes. */ public interface ThumbnailChangeListener { /** * @param id The tab id. */ public void onThumbnailChange(int id); } /** * @param context The context that this cache is created in. * @param resourceId The resource that this value might be defined in. * @param commandLineSwitch The switch for which we would like to extract value from. * @return the value of an integer resource. If the value is overridden on the command line * with the given switch, return the override instead. */ private static int getIntegerResourceWithOverride(Context context, int resourceId, String commandLineSwitch) { int val = context.getResources().getInteger(resourceId); String switchCount = CommandLine.getInstance().getSwitchValue(commandLineSwitch); if (switchCount != null) { int count = Integer.parseInt(switchCount); val = count; } return val; } /** * @param context The context that this cache is created in. * @param contentOffsetProvider The provider of content parameter. */ public TabContentManager(Context context, ContentOffsetProvider contentOffsetProvider, boolean snapshotsEnabled) { mContext = context; mContentOffsetProvider = contentOffsetProvider; mSnapshotsEnabled = snapshotsEnabled; // Override the cache size on the command line with --thumbnails=100 int defaultCacheSize = getIntegerResourceWithOverride(mContext, R.integer.default_thumbnail_cache_size, ChromeSwitches.THUMBNAILS); mFullResThumbnailsMaxSize = defaultCacheSize; int compressionQueueMaxSize = mContext.getResources().getInteger( R.integer.default_compression_queue_size); int writeQueueMaxSize = mContext.getResources().getInteger( R.integer.default_write_queue_size); // Override the cache size on the command line with // --approximation-thumbnails=100 int approximationCacheSize = getIntegerResourceWithOverride(mContext, R.integer.default_approximation_thumbnail_cache_size, ChromeSwitches.APPROXIMATION_THUMBNAILS); float thumbnailScale = 1.f; boolean useApproximationThumbnails; float deviceDensity = mContext.getResources().getDisplayMetrics().density; if (DeviceFormFactor.isTablet(mContext)) { // Scale all tablets to MDPI. thumbnailScale = 1.f / deviceDensity; useApproximationThumbnails = false; } else { // For phones, reduce the amount of memory usage by capturing a lower-res thumbnail for // devices with resolution higher than HDPI (crbug.com/357740). if (deviceDensity > 1.5f) { thumbnailScale = 1.5f / deviceDensity; } useApproximationThumbnails = true; } mThumbnailScale = thumbnailScale; mPriorityTabIds = new int[mFullResThumbnailsMaxSize]; mNativeTabContentManager = nativeInit(defaultCacheSize, approximationCacheSize, compressionQueueMaxSize, writeQueueMaxSize, useApproximationThumbnails); } /** * Destroy the native component. */ public void destroy() { if (mNativeTabContentManager != 0) { nativeDestroy(mNativeTabContentManager); mNativeTabContentManager = 0; } } @CalledByNative private long getNativePtr() { return mNativeTabContentManager; } /** * Add a listener to thumbnail changes. * @param listener The listener of thumbnail change events. */ public void addThumbnailChangeListener(ThumbnailChangeListener listener) { if (!mListeners.contains(listener)) { mListeners.add(listener); } } /** * Remove a listener to thumbnail changes. * @param listener The listener of thumbnail change events. */ public void removeThumbnailChangeListener(ThumbnailChangeListener listener) { mListeners.remove(listener); } private Bitmap readbackNativePage(final Tab tab, float scale) { Bitmap bitmap = null; NativePage page = tab.getNativePage(); if (page == null) { return bitmap; } View viewToDraw = page.getView(); if (viewToDraw == null || viewToDraw.getWidth() == 0 || viewToDraw.getHeight() == 0) { return bitmap; } if (page instanceof InvalidationAwareThumbnailProvider) { if (!((InvalidationAwareThumbnailProvider) page).shouldCaptureThumbnail()) { return null; } } int overlayTranslateY = mContentOffsetProvider.getOverlayTranslateY(); try { bitmap = Bitmap.createBitmap( (int) (viewToDraw.getWidth() * mThumbnailScale), (int) ((viewToDraw.getHeight() - overlayTranslateY) * mThumbnailScale), Bitmap.Config.ARGB_8888); } catch (OutOfMemoryError ex) { return null; } Canvas c = new Canvas(bitmap); c.scale(scale, scale); c.translate(0.f, -overlayTranslateY); if (page instanceof InvalidationAwareThumbnailProvider) { ((InvalidationAwareThumbnailProvider) page).captureThumbnail(c); } else { viewToDraw.draw(c); } return bitmap; } /** * @param tabId The id of the {@link Tab} to check for a full sized thumbnail of. * @return Whether or not there is a full sized cached thumbnail for the {@link Tab} * identified by {@code tabId}. */ public boolean hasFullCachedThumbnail(int tabId) { if (mNativeTabContentManager == 0) return false; return nativeHasFullCachedThumbnail(mNativeTabContentManager, tabId); } /** * Cache the content of a tab as a thumbnail. * @param tab The tab whose content we will cache. */ public void cacheTabThumbnail(final Tab tab) { if (mNativeTabContentManager != 0 && mSnapshotsEnabled) { if (tab.getNativePage() != null) { Bitmap nativePageBitmap = readbackNativePage(tab, mThumbnailScale); if (nativePageBitmap == null) return; nativeCacheTabWithBitmap(mNativeTabContentManager, tab, nativePageBitmap, mThumbnailScale); nativePageBitmap.recycle(); } else { if (tab.getWebContents() == null) return; nativeCacheTab(mNativeTabContentManager, tab, mThumbnailScale); } } } /** * Send a request to thumbnail store to read and decompress the thumbnail for the given tab id. * @param tabId The tab id for which the thumbnail should be requested. * @param callback The callback to use when the thumbnail bitmap is decompressed and sent back. */ public void getThumbnailForId(int tabId, DecompressThumbnailCallback callback) { if (mNativeTabContentManager == 0) return; if (mDecompressRequests.get(tabId) != null) return; mDecompressRequests.put(tabId, callback); nativeGetDecompressedThumbnail(mNativeTabContentManager, tabId); } @CalledByNative private void notifyDecompressBitmapFinished(int tabId, Bitmap bitmap) { DecompressThumbnailCallback callback = mDecompressRequests.get(tabId); mDecompressRequests.remove(tabId); if (callback == null) return; callback.onFinishGetBitmap(bitmap); } /** * Invalidate a thumbnail if the content of the tab has been changed. * @param tabId The id of the {@link Tab} thumbnail to check. * @param url The current URL of the {@link Tab}. */ public void invalidateIfChanged(int tabId, String url) { if (mNativeTabContentManager != 0) { nativeInvalidateIfChanged(mNativeTabContentManager, tabId, url); } } /** * Invalidate a thumbnail of the tab whose id is |id|. * @param tabId The id of the {@link Tab} thumbnail to check. * @param url The current URL of the {@link Tab}. */ public void invalidateTabThumbnail(int id, String url) { invalidateIfChanged(id, url); } /** * Update the priority-ordered list of visible tabs. * @param priority The list of tab ids ordered in terms of priority. */ public void updateVisibleIds(List<Integer> priority) { if (mNativeTabContentManager != 0) { int idsSize = Math.min(mFullResThumbnailsMaxSize, priority.size()); if (idsSize != mPriorityTabIds.length) { mPriorityTabIds = new int[idsSize]; } for (int i = 0; i < idsSize; i++) { mPriorityTabIds[i] = priority.get(i); } nativeUpdateVisibleIds(mNativeTabContentManager, mPriorityTabIds); } } /** * Removes a thumbnail of the tab whose id is |tabId|. * @param tabId The Id of the tab whose thumbnail is being removed. */ public void removeTabThumbnail(int tabId) { if (mNativeTabContentManager != 0) { nativeRemoveTabThumbnail(mNativeTabContentManager, tabId); } } @CalledByNative protected void notifyListenersOfThumbnailChange(int tabId) { for (ThumbnailChangeListener listener : mListeners) { listener.onThumbnailChange(tabId); } } // Class Object Methods private native long nativeInit(int defaultCacheSize, int approximationCacheSize, int compressionQueueMaxSize, int writeQueueMaxSize, boolean useApproximationThumbnail); private native boolean nativeHasFullCachedThumbnail(long nativeTabContentManager, int tabId); private native void nativeCacheTab( long nativeTabContentManager, Object tab, float thumbnailScale); private native void nativeCacheTabWithBitmap(long nativeTabContentManager, Object tab, Object bitmap, float thumbnailScale); private native void nativeInvalidateIfChanged(long nativeTabContentManager, int tabId, String url); private native void nativeUpdateVisibleIds(long nativeTabContentManager, int[] priority); private native void nativeRemoveTabThumbnail(long nativeTabContentManager, int tabId); private native void nativeGetDecompressedThumbnail(long nativeTabContentManager, int tabId); private static native void nativeDestroy(long nativeTabContentManager); }