michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko; michael@0: michael@0: import java.util.ArrayList; michael@0: import java.util.Collection; michael@0: import java.util.HashMap; michael@0: import java.util.regex.Matcher; michael@0: import java.util.regex.Pattern; michael@0: michael@0: import org.json.JSONException; michael@0: import org.json.JSONObject; michael@0: import org.mozilla.gecko.db.BrowserContract.Bookmarks; michael@0: import org.mozilla.gecko.db.BrowserDB; michael@0: import org.mozilla.gecko.gfx.Layer; michael@0: import org.mozilla.gecko.util.ThreadUtils; michael@0: michael@0: import android.content.ContentResolver; michael@0: import android.content.Context; michael@0: import android.graphics.Bitmap; michael@0: import android.graphics.Color; michael@0: import android.graphics.drawable.BitmapDrawable; michael@0: import android.os.Build; michael@0: import android.text.TextUtils; michael@0: import android.util.Log; michael@0: import android.view.View; michael@0: michael@0: public class Tab { michael@0: private static final String LOGTAG = "GeckoTab"; michael@0: michael@0: private static Pattern sColorPattern; michael@0: private final int mId; michael@0: private long mLastUsed; michael@0: private String mUrl; michael@0: private String mBaseDomain; michael@0: private String mUserSearch; michael@0: private String mTitle; michael@0: private Bitmap mFavicon; michael@0: private String mFaviconUrl; michael@0: private int mFaviconSize; michael@0: private boolean mHasFeeds; michael@0: private boolean mHasOpenSearch; michael@0: private SiteIdentity mSiteIdentity; michael@0: private boolean mReaderEnabled; michael@0: private BitmapDrawable mThumbnail; michael@0: private int mHistoryIndex; michael@0: private int mHistorySize; michael@0: private int mParentId; michael@0: private boolean mExternal; michael@0: private boolean mBookmark; michael@0: private boolean mReadingListItem; michael@0: private int mFaviconLoadId; michael@0: private String mContentType; michael@0: private boolean mHasTouchListeners; michael@0: private ZoomConstraints mZoomConstraints; michael@0: private boolean mIsRTL; michael@0: private ArrayList mPluginViews; michael@0: private HashMap mPluginLayers; michael@0: private int mBackgroundColor; michael@0: private int mState; michael@0: private Bitmap mThumbnailBitmap; michael@0: private boolean mDesktopMode; michael@0: private boolean mEnteringReaderMode; michael@0: private Context mAppContext; michael@0: private ErrorType mErrorType = ErrorType.NONE; michael@0: private static final int MAX_HISTORY_LIST_SIZE = 50; michael@0: private volatile int mLoadProgress; michael@0: private volatile int mRecordingCount = 0; michael@0: private String mMostRecentHomePanel; michael@0: michael@0: public static final int STATE_DELAYED = 0; michael@0: public static final int STATE_LOADING = 1; michael@0: public static final int STATE_SUCCESS = 2; michael@0: public static final int STATE_ERROR = 3; michael@0: michael@0: public static final int LOAD_PROGRESS_INIT = 10; michael@0: public static final int LOAD_PROGRESS_START = 20; michael@0: public static final int LOAD_PROGRESS_LOCATION_CHANGE = 60; michael@0: public static final int LOAD_PROGRESS_LOADED = 80; michael@0: public static final int LOAD_PROGRESS_STOP = 100; michael@0: michael@0: private static final int DEFAULT_BACKGROUND_COLOR = Color.WHITE; michael@0: michael@0: public enum ErrorType { michael@0: CERT_ERROR, // Pages with certificate problems michael@0: BLOCKED, // Pages blocked for phishing or malware warnings michael@0: NET_ERROR, // All other types of error michael@0: NONE // Non error pages michael@0: } michael@0: michael@0: public Tab(Context context, int id, String url, boolean external, int parentId, String title) { michael@0: mAppContext = context.getApplicationContext(); michael@0: mId = id; michael@0: mLastUsed = 0; michael@0: mUrl = url; michael@0: mBaseDomain = ""; michael@0: mUserSearch = ""; michael@0: mExternal = external; michael@0: mParentId = parentId; michael@0: mTitle = title == null ? "" : title; michael@0: mFavicon = null; michael@0: mFaviconUrl = null; michael@0: mFaviconSize = 0; michael@0: mHasFeeds = false; michael@0: mHasOpenSearch = false; michael@0: mSiteIdentity = new SiteIdentity(); michael@0: mReaderEnabled = false; michael@0: mEnteringReaderMode = false; michael@0: mThumbnail = null; michael@0: mHistoryIndex = -1; michael@0: mHistorySize = 0; michael@0: mBookmark = false; michael@0: mReadingListItem = false; michael@0: mFaviconLoadId = 0; michael@0: mContentType = ""; michael@0: mZoomConstraints = new ZoomConstraints(false); michael@0: mPluginViews = new ArrayList(); michael@0: mPluginLayers = new HashMap(); michael@0: mState = shouldShowProgress(url) ? STATE_LOADING : STATE_SUCCESS; michael@0: mLoadProgress = LOAD_PROGRESS_INIT; michael@0: michael@0: // At startup, the background is set to a color specified by LayerView michael@0: // when the LayerView is created. Shortly after, this background color michael@0: // will be used before the tab's content is shown. michael@0: mBackgroundColor = DEFAULT_BACKGROUND_COLOR; michael@0: michael@0: updateBookmark(); michael@0: } michael@0: michael@0: private ContentResolver getContentResolver() { michael@0: return mAppContext.getContentResolver(); michael@0: } michael@0: michael@0: public void onDestroy() { michael@0: Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.CLOSED); michael@0: } michael@0: michael@0: public int getId() { michael@0: return mId; michael@0: } michael@0: michael@0: public synchronized void onChange() { michael@0: mLastUsed = System.currentTimeMillis(); michael@0: } michael@0: michael@0: public synchronized long getLastUsed() { michael@0: return mLastUsed; michael@0: } michael@0: michael@0: public int getParentId() { michael@0: return mParentId; michael@0: } michael@0: michael@0: // may be null if user-entered query hasn't yet been resolved to a URI michael@0: public synchronized String getURL() { michael@0: return mUrl; michael@0: } michael@0: michael@0: // mUserSearch should never be null, but it may be an empty string michael@0: public synchronized String getUserSearch() { michael@0: return mUserSearch; michael@0: } michael@0: michael@0: // mTitle should never be null, but it may be an empty string michael@0: public synchronized String getTitle() { michael@0: return mTitle; michael@0: } michael@0: michael@0: public String getDisplayTitle() { michael@0: if (mTitle != null && mTitle.length() > 0) { michael@0: return mTitle; michael@0: } michael@0: michael@0: return mUrl; michael@0: } michael@0: michael@0: public String getBaseDomain() { michael@0: return mBaseDomain; michael@0: } michael@0: michael@0: public Bitmap getFavicon() { michael@0: return mFavicon; michael@0: } michael@0: michael@0: public BitmapDrawable getThumbnail() { michael@0: return mThumbnail; michael@0: } michael@0: michael@0: public String getMostRecentHomePanel() { michael@0: return mMostRecentHomePanel; michael@0: } michael@0: michael@0: public void setMostRecentHomePanel(String panelId) { michael@0: mMostRecentHomePanel = panelId; michael@0: } michael@0: michael@0: public Bitmap getThumbnailBitmap(int width, int height) { michael@0: if (mThumbnailBitmap != null) { michael@0: // Bug 787318 - Honeycomb has a bug with bitmap caching, we can't michael@0: // reuse the bitmap there. michael@0: boolean honeycomb = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB michael@0: && Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR2); michael@0: boolean sizeChange = mThumbnailBitmap.getWidth() != width michael@0: || mThumbnailBitmap.getHeight() != height; michael@0: if (honeycomb || sizeChange) { michael@0: mThumbnailBitmap = null; michael@0: } michael@0: } michael@0: michael@0: if (mThumbnailBitmap == null) { michael@0: Bitmap.Config config = (GeckoAppShell.getScreenDepth() == 24) ? michael@0: Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; michael@0: mThumbnailBitmap = Bitmap.createBitmap(width, height, config); michael@0: } michael@0: michael@0: return mThumbnailBitmap; michael@0: } michael@0: michael@0: public void updateThumbnail(final Bitmap b) { michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: if (b != null) { michael@0: try { michael@0: mThumbnail = new BitmapDrawable(mAppContext.getResources(), b); michael@0: if (mState == Tab.STATE_SUCCESS) michael@0: saveThumbnailToDB(); michael@0: } catch (OutOfMemoryError oom) { michael@0: Log.w(LOGTAG, "Unable to create/scale bitmap.", oom); michael@0: mThumbnail = null; michael@0: } michael@0: } else { michael@0: mThumbnail = null; michael@0: } michael@0: michael@0: Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.THUMBNAIL); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: public synchronized String getFaviconURL() { michael@0: return mFaviconUrl; michael@0: } michael@0: michael@0: public boolean hasFeeds() { michael@0: return mHasFeeds; michael@0: } michael@0: michael@0: public boolean hasOpenSearch() { michael@0: return mHasOpenSearch; michael@0: } michael@0: michael@0: public SiteIdentity getSiteIdentity() { michael@0: return mSiteIdentity; michael@0: } michael@0: michael@0: public boolean getReaderEnabled() { michael@0: return mReaderEnabled; michael@0: } michael@0: michael@0: public boolean isBookmark() { michael@0: return mBookmark; michael@0: } michael@0: michael@0: public boolean isReadingListItem() { michael@0: return mReadingListItem; michael@0: } michael@0: michael@0: public boolean isExternal() { michael@0: return mExternal; michael@0: } michael@0: michael@0: public synchronized void updateURL(String url) { michael@0: if (url != null && url.length() > 0) { michael@0: mUrl = url; michael@0: } michael@0: } michael@0: michael@0: private synchronized void updateUserSearch(String userSearch) { michael@0: mUserSearch = userSearch; michael@0: } michael@0: michael@0: public void setErrorType(String type) { michael@0: if ("blocked".equals(type)) michael@0: setErrorType(ErrorType.BLOCKED); michael@0: else if ("certerror".equals(type)) michael@0: setErrorType(ErrorType.CERT_ERROR); michael@0: else if ("neterror".equals(type)) michael@0: setErrorType(ErrorType.NET_ERROR); michael@0: else michael@0: setErrorType(ErrorType.NONE); michael@0: } michael@0: michael@0: public void setErrorType(ErrorType type) { michael@0: mErrorType = type; michael@0: } michael@0: michael@0: public ErrorType getErrorType() { michael@0: return mErrorType; michael@0: } michael@0: michael@0: public void setContentType(String contentType) { michael@0: mContentType = (contentType == null) ? "" : contentType; michael@0: } michael@0: michael@0: public String getContentType() { michael@0: return mContentType; michael@0: } michael@0: michael@0: public synchronized void updateTitle(String title) { michael@0: // Keep the title unchanged while entering reader mode. michael@0: if (mEnteringReaderMode) { michael@0: return; michael@0: } michael@0: michael@0: // If there was a title, but it hasn't changed, do nothing. michael@0: if (mTitle != null && michael@0: TextUtils.equals(mTitle, title)) { michael@0: return; michael@0: } michael@0: michael@0: mTitle = (title == null ? "" : title); michael@0: Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.TITLE); michael@0: } michael@0: michael@0: public void setState(int state) { michael@0: mState = state; michael@0: michael@0: if (mState != Tab.STATE_LOADING) michael@0: mEnteringReaderMode = false; michael@0: } michael@0: michael@0: public int getState() { michael@0: return mState; michael@0: } michael@0: michael@0: public void setZoomConstraints(ZoomConstraints constraints) { michael@0: mZoomConstraints = constraints; michael@0: } michael@0: michael@0: public ZoomConstraints getZoomConstraints() { michael@0: return mZoomConstraints; michael@0: } michael@0: michael@0: public void setIsRTL(boolean aIsRTL) { michael@0: mIsRTL = aIsRTL; michael@0: } michael@0: michael@0: public boolean getIsRTL() { michael@0: return mIsRTL; michael@0: } michael@0: michael@0: public void setHasTouchListeners(boolean aValue) { michael@0: mHasTouchListeners = aValue; michael@0: } michael@0: michael@0: public boolean getHasTouchListeners() { michael@0: return mHasTouchListeners; michael@0: } michael@0: michael@0: public void setFaviconLoadId(int faviconLoadId) { michael@0: mFaviconLoadId = faviconLoadId; michael@0: } michael@0: michael@0: public int getFaviconLoadId() { michael@0: return mFaviconLoadId; michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the favicon changed. michael@0: */ michael@0: public boolean updateFavicon(Bitmap favicon) { michael@0: if (mFavicon == favicon) { michael@0: return false; michael@0: } michael@0: mFavicon = favicon; michael@0: return true; michael@0: } michael@0: michael@0: public synchronized void updateFaviconURL(String faviconUrl, int size) { michael@0: // If we already have an "any" sized icon, don't update the icon. michael@0: if (mFaviconSize == -1) michael@0: return; michael@0: michael@0: // Only update the favicon if it's bigger than the current favicon. michael@0: // We use -1 to represent icons with sizes="any". michael@0: if (size == -1 || size >= mFaviconSize) { michael@0: mFaviconUrl = faviconUrl; michael@0: mFaviconSize = size; michael@0: } michael@0: } michael@0: michael@0: public synchronized void clearFavicon() { michael@0: // Keep the favicon unchanged while entering reader mode michael@0: if (mEnteringReaderMode) michael@0: return; michael@0: michael@0: mFavicon = null; michael@0: mFaviconUrl = null; michael@0: mFaviconSize = 0; michael@0: } michael@0: michael@0: public void setHasFeeds(boolean hasFeeds) { michael@0: mHasFeeds = hasFeeds; michael@0: } michael@0: michael@0: public void setHasOpenSearch(boolean hasOpenSearch) { michael@0: mHasOpenSearch = hasOpenSearch; michael@0: } michael@0: michael@0: public void updateIdentityData(JSONObject identityData) { michael@0: mSiteIdentity.update(identityData); michael@0: } michael@0: michael@0: public void setReaderEnabled(boolean readerEnabled) { michael@0: mReaderEnabled = readerEnabled; michael@0: Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.MENU_UPDATED); michael@0: } michael@0: michael@0: void updateBookmark() { michael@0: if (getURL() == null) { michael@0: return; michael@0: } michael@0: michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: final String url = getURL(); michael@0: if (url == null) { michael@0: return; michael@0: } michael@0: michael@0: final int flags = BrowserDB.getItemFlags(getContentResolver(), url); michael@0: mBookmark = (flags & Bookmarks.FLAG_BOOKMARK) > 0; michael@0: mReadingListItem = (flags & Bookmarks.FLAG_READING) > 0; michael@0: Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.MENU_UPDATED); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: public void addBookmark() { michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: String url = getURL(); michael@0: if (url == null) michael@0: return; michael@0: michael@0: BrowserDB.addBookmark(getContentResolver(), mTitle, url); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: public void removeBookmark() { michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: String url = getURL(); michael@0: if (url == null) michael@0: return; michael@0: michael@0: BrowserDB.removeBookmarksWithURL(getContentResolver(), url); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: public void addToReadingList() { michael@0: if (!mReaderEnabled) michael@0: return; michael@0: michael@0: JSONObject json = new JSONObject(); michael@0: try { michael@0: json.put("tabID", String.valueOf(getId())); michael@0: } catch (JSONException e) { michael@0: Log.e(LOGTAG, "JSON error - failing to add to reading list", e); michael@0: return; michael@0: } michael@0: michael@0: GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Add", json.toString()); michael@0: GeckoAppShell.sendEventToGecko(e); michael@0: } michael@0: michael@0: public void toggleReaderMode() { michael@0: if (AboutPages.isAboutReader(mUrl)) { michael@0: Tabs.getInstance().loadUrl(ReaderModeUtils.getUrlFromAboutReader(mUrl)); michael@0: } else if (mReaderEnabled) { michael@0: mEnteringReaderMode = true; michael@0: Tabs.getInstance().loadUrl(ReaderModeUtils.getAboutReaderForUrl(mUrl, mId)); michael@0: } michael@0: } michael@0: michael@0: public boolean isEnteringReaderMode() { michael@0: return mEnteringReaderMode; michael@0: } michael@0: michael@0: public void doReload() { michael@0: GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", ""); michael@0: GeckoAppShell.sendEventToGecko(e); michael@0: } michael@0: michael@0: // Our version of nsSHistory::GetCanGoBack michael@0: public boolean canDoBack() { michael@0: return mHistoryIndex > 0; michael@0: } michael@0: michael@0: public boolean doBack() { michael@0: if (!canDoBack()) michael@0: return false; michael@0: michael@0: GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Back", ""); michael@0: GeckoAppShell.sendEventToGecko(e); michael@0: return true; michael@0: } michael@0: michael@0: public boolean showBackHistory() { michael@0: if (!canDoBack()) michael@0: return false; michael@0: return this.showHistory(Math.max(mHistoryIndex - MAX_HISTORY_LIST_SIZE, 0), mHistoryIndex, mHistoryIndex); michael@0: } michael@0: michael@0: public boolean showForwardHistory() { michael@0: if (!canDoForward()) michael@0: return false; michael@0: return this.showHistory(mHistoryIndex, Math.min(mHistorySize - 1, mHistoryIndex + MAX_HISTORY_LIST_SIZE), mHistoryIndex); michael@0: } michael@0: michael@0: public boolean showAllHistory() { michael@0: if (!canDoForward() && !canDoBack()) michael@0: return false; michael@0: michael@0: int min = mHistoryIndex - MAX_HISTORY_LIST_SIZE / 2; michael@0: int max = mHistoryIndex + MAX_HISTORY_LIST_SIZE / 2; michael@0: if (min < 0) { michael@0: max -= min; michael@0: } michael@0: if (max > mHistorySize - 1) { michael@0: min -= max - (mHistorySize - 1); michael@0: max = mHistorySize - 1; michael@0: } michael@0: min = Math.max(min, 0); michael@0: michael@0: return this.showHistory(min, max, mHistoryIndex); michael@0: } michael@0: michael@0: /** michael@0: * This method will show the history starting on fromIndex until toIndex of the history. michael@0: */ michael@0: public boolean showHistory(int fromIndex, int toIndex, int selIndex) { michael@0: JSONObject json = new JSONObject(); michael@0: try { michael@0: json.put("fromIndex", fromIndex); michael@0: json.put("toIndex", toIndex); michael@0: json.put("selIndex", selIndex); michael@0: } catch (JSONException e) { michael@0: Log.e(LOGTAG, "JSON error", e); michael@0: } michael@0: GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:ShowHistory", json.toString()); michael@0: GeckoAppShell.sendEventToGecko(e); michael@0: return true; michael@0: } michael@0: michael@0: public void doStop() { michael@0: GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Stop", ""); michael@0: GeckoAppShell.sendEventToGecko(e); michael@0: } michael@0: michael@0: // Our version of nsSHistory::GetCanGoForward michael@0: public boolean canDoForward() { michael@0: return mHistoryIndex < mHistorySize - 1; michael@0: } michael@0: michael@0: public boolean doForward() { michael@0: if (!canDoForward()) michael@0: return false; michael@0: michael@0: GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Forward", ""); michael@0: GeckoAppShell.sendEventToGecko(e); michael@0: return true; michael@0: } michael@0: michael@0: void handleSessionHistoryMessage(String event, JSONObject message) throws JSONException { michael@0: if (event.equals("New")) { michael@0: final String url = message.getString("url"); michael@0: mHistoryIndex++; michael@0: mHistorySize = mHistoryIndex + 1; michael@0: } else if (event.equals("Back")) { michael@0: if (!canDoBack()) { michael@0: Log.w(LOGTAG, "Received unexpected back notification"); michael@0: return; michael@0: } michael@0: mHistoryIndex--; michael@0: } else if (event.equals("Forward")) { michael@0: if (!canDoForward()) { michael@0: Log.w(LOGTAG, "Received unexpected forward notification"); michael@0: return; michael@0: } michael@0: mHistoryIndex++; michael@0: } else if (event.equals("Goto")) { michael@0: int index = message.getInt("index"); michael@0: if (index < 0 || index >= mHistorySize) { michael@0: Log.w(LOGTAG, "Received unexpected history-goto notification"); michael@0: return; michael@0: } michael@0: mHistoryIndex = index; michael@0: } else if (event.equals("Purge")) { michael@0: int numEntries = message.getInt("numEntries"); michael@0: if (numEntries > mHistorySize) { michael@0: Log.w(LOGTAG, "Received unexpectedly large number of history entries to purge"); michael@0: mHistoryIndex = -1; michael@0: mHistorySize = 0; michael@0: return; michael@0: } michael@0: michael@0: mHistorySize -= numEntries; michael@0: mHistoryIndex -= numEntries; michael@0: michael@0: // If we weren't at the last history entry, mHistoryIndex may have become too small michael@0: if (mHistoryIndex < -1) michael@0: mHistoryIndex = -1; michael@0: } michael@0: } michael@0: michael@0: void handleLocationChange(JSONObject message) throws JSONException { michael@0: final String uri = message.getString("uri"); michael@0: final String oldUrl = getURL(); michael@0: final boolean sameDocument = message.getBoolean("sameDocument"); michael@0: mEnteringReaderMode = ReaderModeUtils.isEnteringReaderMode(oldUrl, uri); michael@0: michael@0: if (!TextUtils.equals(oldUrl, uri)) { michael@0: updateURL(uri); michael@0: updateBookmark(); michael@0: if (!sameDocument) { michael@0: // We can unconditionally clear the favicon and title here: we michael@0: // already filtered both cases in which this was a (pseudo-) michael@0: // spurious location change, so we're definitely loading a new michael@0: // page. michael@0: clearFavicon(); michael@0: updateTitle(null); michael@0: } michael@0: } michael@0: michael@0: if (sameDocument) { michael@0: // We can get a location change event for the same document with an anchor tag michael@0: // Notify listeners so that buttons like back or forward will update themselves michael@0: Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl); michael@0: return; michael@0: } michael@0: michael@0: setContentType(message.getString("contentType")); michael@0: updateUserSearch(message.getString("userSearch")); michael@0: mBaseDomain = message.optString("baseDomain"); michael@0: michael@0: setHasFeeds(false); michael@0: setHasOpenSearch(false); michael@0: updateIdentityData(null); michael@0: setReaderEnabled(false); michael@0: setZoomConstraints(new ZoomConstraints(true)); michael@0: setHasTouchListeners(false); michael@0: setBackgroundColor(DEFAULT_BACKGROUND_COLOR); michael@0: setErrorType(ErrorType.NONE); michael@0: setLoadProgressIfLoading(LOAD_PROGRESS_LOCATION_CHANGE); michael@0: michael@0: Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl); michael@0: } michael@0: michael@0: private static boolean shouldShowProgress(final String url) { michael@0: return !AboutPages.isAboutPage(url); michael@0: } michael@0: michael@0: void handleDocumentStart(boolean restoring, String url) { michael@0: setLoadProgress(LOAD_PROGRESS_START); michael@0: setState((!restoring && shouldShowProgress(url)) ? STATE_LOADING : STATE_SUCCESS); michael@0: updateIdentityData(null); michael@0: setReaderEnabled(false); michael@0: } michael@0: michael@0: void handleDocumentStop(boolean success) { michael@0: setState(success ? STATE_SUCCESS : STATE_ERROR); michael@0: michael@0: final String oldURL = getURL(); michael@0: final Tab tab = this; michael@0: tab.setLoadProgress(LOAD_PROGRESS_STOP); michael@0: ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: // tab.getURL() may return null michael@0: if (!TextUtils.equals(oldURL, getURL())) michael@0: return; michael@0: michael@0: ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab); michael@0: } michael@0: }, 500); michael@0: } michael@0: michael@0: void handleContentLoaded() { michael@0: setLoadProgressIfLoading(LOAD_PROGRESS_LOADED); michael@0: } michael@0: michael@0: protected void saveThumbnailToDB() { michael@0: final BitmapDrawable thumbnail = mThumbnail; michael@0: if (thumbnail == null) { michael@0: return; michael@0: } michael@0: michael@0: try { michael@0: String url = getURL(); michael@0: if (url == null) { michael@0: return; michael@0: } michael@0: michael@0: BrowserDB.updateThumbnailForUrl(getContentResolver(), url, thumbnail); michael@0: } catch (Exception e) { michael@0: // ignore michael@0: } michael@0: } michael@0: michael@0: public void addPluginView(View view) { michael@0: mPluginViews.add(view); michael@0: } michael@0: michael@0: public void removePluginView(View view) { michael@0: mPluginViews.remove(view); michael@0: } michael@0: michael@0: public View[] getPluginViews() { michael@0: return mPluginViews.toArray(new View[mPluginViews.size()]); michael@0: } michael@0: michael@0: public void addPluginLayer(Object surfaceOrView, Layer layer) { michael@0: synchronized(mPluginLayers) { michael@0: mPluginLayers.put(surfaceOrView, layer); michael@0: } michael@0: } michael@0: michael@0: public Layer getPluginLayer(Object surfaceOrView) { michael@0: synchronized(mPluginLayers) { michael@0: return mPluginLayers.get(surfaceOrView); michael@0: } michael@0: } michael@0: michael@0: public Collection getPluginLayers() { michael@0: synchronized(mPluginLayers) { michael@0: return new ArrayList(mPluginLayers.values()); michael@0: } michael@0: } michael@0: michael@0: public Layer removePluginLayer(Object surfaceOrView) { michael@0: synchronized(mPluginLayers) { michael@0: return mPluginLayers.remove(surfaceOrView); michael@0: } michael@0: } michael@0: michael@0: public int getBackgroundColor() { michael@0: return mBackgroundColor; michael@0: } michael@0: michael@0: /** Sets a new color for the background. */ michael@0: public void setBackgroundColor(int color) { michael@0: mBackgroundColor = color; michael@0: } michael@0: michael@0: /** Parses and sets a new color for the background. */ michael@0: public void setBackgroundColor(String newColor) { michael@0: setBackgroundColor(parseColorFromGecko(newColor)); michael@0: } michael@0: michael@0: // Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color michael@0: // cannot be parsed, returns white. michael@0: private static int parseColorFromGecko(String string) { michael@0: if (sColorPattern == null) { michael@0: sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)"); michael@0: } michael@0: michael@0: Matcher matcher = sColorPattern.matcher(string); michael@0: if (!matcher.matches()) { michael@0: return Color.WHITE; michael@0: } michael@0: michael@0: int r = Integer.parseInt(matcher.group(1)); michael@0: int g = Integer.parseInt(matcher.group(2)); michael@0: int b = Integer.parseInt(matcher.group(3)); michael@0: return Color.rgb(r, g, b); michael@0: } michael@0: michael@0: public void setDesktopMode(boolean enabled) { michael@0: mDesktopMode = enabled; michael@0: } michael@0: michael@0: public boolean getDesktopMode() { michael@0: return mDesktopMode; michael@0: } michael@0: michael@0: public boolean isPrivate() { michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * Sets the tab load progress to the given percentage. michael@0: * michael@0: * @param progressPercentage Percentage to set progress to (0-100) michael@0: */ michael@0: void setLoadProgress(int progressPercentage) { michael@0: mLoadProgress = progressPercentage; michael@0: } michael@0: michael@0: /** michael@0: * Sets the tab load progress to the given percentage only if the tab is michael@0: * currently loading. michael@0: * michael@0: * about:neterror can trigger a STOP before other page load events (bug michael@0: * 976426), so any post-START events should make sure the page is loading michael@0: * before updating progress. michael@0: * michael@0: * @param progressPercentage Percentage to set progress to (0-100) michael@0: */ michael@0: void setLoadProgressIfLoading(int progressPercentage) { michael@0: if (getState() == STATE_LOADING) { michael@0: setLoadProgress(progressPercentage); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Gets the tab load progress percentage. michael@0: * michael@0: * @return Current progress percentage michael@0: */ michael@0: public int getLoadProgress() { michael@0: return mLoadProgress; michael@0: } michael@0: michael@0: public void setRecording(boolean isRecording) { michael@0: if (isRecording) { michael@0: mRecordingCount++; michael@0: } else { michael@0: mRecordingCount--; michael@0: } michael@0: } michael@0: michael@0: public boolean isRecording() { michael@0: return mRecordingCount > 0; michael@0: } michael@0: }