// 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; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.text.TextUtils; import android.util.SparseArray; import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.JNINamespace; import org.chromium.chrome.R; import org.chromium.chrome.browser.compositor.layouts.content.TitleBitmapFactory; import org.chromium.chrome.browser.favicon.FaviconHelper; import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.util.ColorUtils; import org.chromium.ui.base.DeviceFormFactor; import org.chromium.ui.resources.ResourceManager; import org.chromium.ui.resources.dynamics.BitmapDynamicResource; import org.chromium.ui.resources.dynamics.DynamicResourceLoader; /** * A version of the {@link LayerTitleCache} that builds native cc::Layer objects * that represent the cached title textures. */ @JNINamespace("android") public class LayerTitleCache implements TitleCache { private static int sNextResourceId = 1; private final Context mContext; private TabModelSelector mTabModelSelector; private final SparseArray<Title> mTitles = new SparseArray<Title>(); private final int mFaviconSize; private long mNativeLayerTitleCache; private ResourceManager mResourceManager; private FaviconHelper mFaviconHelper; /** Responsible for building titles on light themes or standard tabs. */ protected TitleBitmapFactory mStandardTitleBitmapFactory; /** Responsible for building incognito or dark theme titles. */ protected TitleBitmapFactory mDarkTitleBitmapFactory; /** * Builds an instance of the LayerTitleCache. */ public LayerTitleCache(Context context) { mContext = context; Resources res = context.getResources(); final int fadeWidthPx = res.getDimensionPixelOffset(R.dimen.border_texture_title_fade); final int faviconStartPaddingPx = res.getDimensionPixelSize(R.dimen.tab_title_favicon_start_padding); final int faviconEndPaddingPx = res.getDimensionPixelSize(R.dimen.tab_title_favicon_end_padding); mNativeLayerTitleCache = nativeInit(fadeWidthPx, faviconStartPaddingPx, faviconEndPaddingPx, R.drawable.spinner, R.drawable.spinner_white); mFaviconSize = res.getDimensionPixelSize(R.dimen.compositor_tab_title_favicon_size); mStandardTitleBitmapFactory = new TitleBitmapFactory(context, false, R.drawable.default_favicon); mDarkTitleBitmapFactory = new TitleBitmapFactory(context, true, R.drawable.default_favicon_white); } /** * @param resourceManager The {@link ResourceManager} for registering title * resources. */ public void setResourceManager(ResourceManager resourceManager) { mResourceManager = resourceManager; } /** * Destroys the native reference. */ public void shutDown() { if (mNativeLayerTitleCache == 0) return; nativeDestroy(mNativeLayerTitleCache); mNativeLayerTitleCache = 0; } public void setTabModelSelector(TabModelSelector tabModelSelector) { mTabModelSelector = tabModelSelector; } @CalledByNative private long getNativePtr() { return mNativeLayerTitleCache; } @CalledByNative private void buildUpdatedTitle(int tabId) { if (mTabModelSelector == null) return; Tab tab = mTabModelSelector.getTabById(tabId); if (tab == null) return; getUpdatedTitle(tab, ""); } @Override public String getUpdatedTitle(Tab tab, String defaultTitle) { // If content view core is null, tab does not have direct access to the favicon, and we // will initially show default favicon. But favicons are stored in the history database, so // we will fetch favicons asynchronously from database. boolean fetchFaviconFromHistory = tab.getContentViewCore() == null; String titleString = getTitleForTab(tab, defaultTitle); getUpdatedTitleInternal(tab, titleString, fetchFaviconFromHistory); if (fetchFaviconFromHistory) fetchFaviconForTab(tab); return titleString; } private String getUpdatedTitleInternal(Tab tab, String titleString, boolean fetchFaviconFromHistory) { final int tabId = tab.getId(); Bitmap originalFavicon = tab.getFavicon(); boolean isDarkTheme = tab.isIncognito(); // The theme might require lighter text. if (!DeviceFormFactor.isTablet(mContext)) { isDarkTheme |= ColorUtils.shouldUseLightForegroundOnBackground(tab.getThemeColor()); } ColorUtils.shouldUseLightForegroundOnBackground(tab.getThemeColor()); boolean isRtl = tab.isTitleDirectionRtl(); TitleBitmapFactory titleBitmapFactory = isDarkTheme ? mDarkTitleBitmapFactory : mStandardTitleBitmapFactory; Title title = mTitles.get(tabId); if (title == null) { title = new Title(); mTitles.put(tabId, title); title.register(); } title.set(titleBitmapFactory.getTitleBitmap(mContext, titleString), titleBitmapFactory.getFaviconBitmap(mContext, originalFavicon), fetchFaviconFromHistory); if (mNativeLayerTitleCache != 0) { nativeUpdateLayer(mNativeLayerTitleCache, tabId, title.getTitleResId(), title.getFaviconResId(), isDarkTheme, isRtl); } return titleString; } private void fetchFaviconForTab(final Tab tab) { if (mFaviconHelper == null) mFaviconHelper = new FaviconHelper(); // Since tab#getProfile() is not available by this time, we will use whatever last used // profile. This should be normal profile since fetching favicons should normally happen on // a cold start. Return otherwise. if (Profile.getLastUsedProfile().hasOffTheRecordProfile()) return; mFaviconHelper.getLocalFaviconImageForURL( Profile.getLastUsedProfile(), tab.getUrl(), mFaviconSize, new FaviconImageCallback() { @Override public void onFaviconAvailable(Bitmap favicon, String iconUrl) { updateFaviconFromHistory(tab, favicon); } }); } /** * Comes up with a valid title to return for a tab. * @param tab The {@link Tab} to build a title for. * @return The title to use. */ private String getTitleForTab(Tab tab, String defaultTitle) { String title = tab.getTitle(); if (TextUtils.isEmpty(title)) { title = tab.getUrl(); if (TextUtils.isEmpty(title)) { title = defaultTitle; if (TextUtils.isEmpty(title)) { title = ""; } } } return title; } private void updateFaviconFromHistory(Tab tab, Bitmap faviconBitmap) { if (!tab.isInitialized()) return; int tabId = tab.getId(); Title title = mTitles.get(tabId); if (title == null) return; if (!title.updateFaviconFromHistory(faviconBitmap)) return; if (mNativeLayerTitleCache != 0) { nativeUpdateFavicon(mNativeLayerTitleCache, tabId, title.getFaviconResId()); } } @Override public void remove(int tabId) { Title title = mTitles.get(tabId); if (title == null) return; title.unregister(); mTitles.remove(tabId); if (mNativeLayerTitleCache == 0) return; nativeUpdateLayer(mNativeLayerTitleCache, tabId, -1, -1, false, false); } @Override public void clearExcept(int exceptId) { Title title = mTitles.get(exceptId); for (int i = 0; i < mTitles.size(); i++) { Title toDelete = mTitles.get(mTitles.keyAt(i)); if (toDelete == title) continue; toDelete.unregister(); } mTitles.clear(); if (title != null) mTitles.put(exceptId, title); if (mNativeLayerTitleCache == 0) return; nativeClearExcept(mNativeLayerTitleCache, exceptId); } private class Title { private final BitmapDynamicResource mFavicon = new BitmapDynamicResource(sNextResourceId++); private final BitmapDynamicResource mTitle = new BitmapDynamicResource(sNextResourceId++); // We don't want to override updated favicon (e.g. from Tab#onFaviconAvailable) with one // fetched from history. You can set this to true / false to control that. private boolean mExpectUpdateFromHistory; public Title() {} public void set(Bitmap titleBitmap, Bitmap faviconBitmap, boolean expectUpdateFromHistory) { mTitle.setBitmap(titleBitmap); mFavicon.setBitmap(faviconBitmap); mExpectUpdateFromHistory = expectUpdateFromHistory; } public boolean updateFaviconFromHistory(Bitmap faviconBitmap) { if (!mExpectUpdateFromHistory) return false; mFavicon.setBitmap(faviconBitmap); mExpectUpdateFromHistory = false; return true; } public void register() { if (mResourceManager == null) return; DynamicResourceLoader loader = mResourceManager.getBitmapDynamicResourceLoader(); loader.registerResource(mFavicon.getResId(), mFavicon); loader.registerResource(mTitle.getResId(), mTitle); } public void unregister() { if (mResourceManager == null) return; DynamicResourceLoader loader = mResourceManager.getBitmapDynamicResourceLoader(); loader.unregisterResource(mFavicon.getResId()); loader.unregisterResource(mTitle.getResId()); } public int getFaviconResId() { return mFavicon.getResId(); } public int getTitleResId() { return mTitle.getResId(); } } private native long nativeInit(int fadeWidth, int faviconStartlPadding, int faviconEndPadding, int spinnerResId, int spinnerIncognitoResId); private static native void nativeDestroy(long nativeLayerTitleCache); private native void nativeClearExcept(long nativeLayerTitleCache, int exceptId); private native void nativeUpdateLayer(long nativeLayerTitleCache, int tabId, int titleResId, int faviconResId, boolean isIncognito, boolean isRtl); private native void nativeUpdateFavicon(long nativeLayerTitleCache, int tabId, int faviconResId); }