Restore overhaul
Bug: 5069192
Store thumbnails in a database restored async for each tab
Fix restoring a tab not restoring its current state
Change-Id: I2c14e352638aac0ef766fb3bf4036ff220c53ecd
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index beac2ff..a4bcc99 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -20,11 +20,13 @@
import android.app.AlertDialog;
import android.app.SearchManager;
import android.content.ContentResolver;
+import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
@@ -53,7 +55,6 @@
import android.webkit.SslErrorHandler;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;
-import android.webkit.WebBackForwardList;
import android.webkit.WebBackForwardListClient;
import android.webkit.WebChromeClient;
import android.webkit.WebHistoryItem;
@@ -68,10 +69,12 @@
import android.widget.Toast;
import com.android.browser.homepages.HomeProvider;
+import com.android.browser.provider.BrowserProvider2.Thumbnails;
import com.android.browser.provider.SnapshotProvider.Snapshots;
import com.android.common.speech.LoggingEvents;
import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -95,6 +98,8 @@
private static final int MSG_CAPTURE = 42;
private static final int CAPTURE_DELAY = 500;
+ private static Bitmap sDefaultFavicon;
+
public enum LockIcon {
LOCK_ICON_UNSECURE,
LOCK_ICON_SECURE,
@@ -161,6 +166,13 @@
private Bitmap mCapture;
private Handler mHandler;
+ private static synchronized Bitmap getDefaultFavicon(Context context) {
+ if (sDefaultFavicon == null) {
+ sDefaultFavicon = BitmapFactory.decodeResource(
+ context.getResources(), R.drawable.app_web_browser_sm);
+ }
+ return sDefaultFavicon;
+ }
// All the state needed for a page
protected static class PageState {
@@ -179,8 +191,7 @@
mOriginalUrl = mUrl = "";
mTitle = c.getString(R.string.new_tab);
}
- mFavicon = BitmapFactory.decodeResource(
- c.getResources(), R.drawable.app_web_browser_sm);
+ mFavicon = null;
mLockIcon = LockIcon.LOCK_ICON_UNSECURE;
}
@@ -192,13 +203,9 @@
} else {
mLockIcon = LockIcon.LOCK_ICON_UNSECURE;
}
- if (favicon != null) {
- mFavicon = favicon;
- } else {
- mFavicon = BitmapFactory.decodeResource(
- c.getResources(), R.drawable.app_web_browser_sm);
- }
+ mFavicon = favicon;
}
+
}
// The current/loading page's state
@@ -211,7 +218,6 @@
static final String PARENTTAB = "parentTab";
static final String APPID = "appid";
static final String INCOGNITO = "privateBrowsingEnabled";
- static final String SCREENSHOT = "screenshot";
static final String USERAGENT = "useragent";
// -------------------------------------------------------------------------
@@ -576,19 +582,7 @@
url, SystemClock.uptimeMillis() - mLoadStartTime);
}
mInPageLoad = false;
- // Sync state (in case of stop/timeout)
- mCurrentState.mUrl = view.getUrl();
- if (mCurrentState.mUrl == null) {
- mCurrentState.mUrl = url != null ? url : "";
- }
- mCurrentState.mOriginalUrl = view.getOriginalUrl();
- mCurrentState.mTitle = view.getTitle();
- mCurrentState.mFavicon = view.getFavicon();
- if (!URLUtil.isHttpsUrl(mCurrentState.mUrl)) {
- // In case we stop when loading an HTTPS page from an HTTP page
- // but before a provisional load occurred
- mCurrentState.mLockIcon = LockIcon.LOCK_ICON_UNSECURE;
- }
+ syncCurrentState(view, url);
mWebViewController.onPageFinished(Tab.this);
}
@@ -894,6 +888,22 @@
};
+ private void syncCurrentState(WebView view, String url) {
+ // Sync state (in case of stop/timeout)
+ mCurrentState.mUrl = view.getUrl();
+ if (mCurrentState.mUrl == null) {
+ mCurrentState.mUrl = url != null ? url : "";
+ }
+ mCurrentState.mOriginalUrl = view.getOriginalUrl();
+ mCurrentState.mTitle = view.getTitle();
+ mCurrentState.mFavicon = view.getFavicon();
+ if (!URLUtil.isHttpsUrl(mCurrentState.mUrl)) {
+ // In case we stop when loading an HTTPS page from an HTTP page
+ // but before a provisional load occurred
+ mCurrentState.mLockIcon = LockIcon.LOCK_ICON_UNSECURE;
+ }
+ }
+
// Called by DeviceAccountLogin when the Tab needs to have the auto-login UI
// displayed.
void setDeviceAccountLogin(DeviceAccountLogin login) {
@@ -1355,11 +1365,16 @@
// -------------------------------------------------------------------------
- // TODO temporarily use activity here
- // remove later
-
// Construct a new tab
Tab(WebViewController wvcontroller, WebView w) {
+ this(wvcontroller, w, null);
+ }
+
+ Tab(WebViewController wvcontroller, Bundle state) {
+ this(wvcontroller, null, state);
+ }
+
+ Tab(WebViewController wvcontroller, WebView w, Bundle state) {
mWebViewController = wvcontroller;
mContext = mWebViewController.getContext();
mSettings = BrowserSettings.getInstance();
@@ -1393,21 +1408,46 @@
}
};
+ mCaptureWidth = mContext.getResources().getDimensionPixelSize(
+ R.dimen.tab_thumbnail_width);
+ mCaptureHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.tab_thumbnail_height);
+ updateShouldCaptureThumbnails();
+ restoreState(state);
setWebView(w);
- mCaptureWidth = mContext.getResources().getDimensionPixelSize(R.dimen.nav_tab_width);
- mCaptureHeight = mContext.getResources().getDimensionPixelSize(R.dimen.nav_tab_height);
- mCapture = Bitmap.createBitmap(mCaptureWidth, mCaptureHeight,
- Bitmap.Config.RGB_565);
mHandler = new Handler() {
public void handleMessage(Message m) {
- Tab.this.capture();
+ switch (m.what) {
+ case MSG_CAPTURE:
+ capture();
+ break;
+ }
}
};
+ }
+ public void updateShouldCaptureThumbnails() {
+ if (mWebViewController.shouldCaptureThumbnails()) {
+ synchronized (Tab.this) {
+ if (mCapture == null) {
+ mCapture = Bitmap.createBitmap(mCaptureWidth, mCaptureHeight,
+ Bitmap.Config.RGB_565);
+ if (mInForeground) {
+ postCapture();
+ }
+ }
+ }
+ } else {
+ synchronized (Tab.this) {
+ mCapture = null;
+ deleteThumbnail();
+ }
+ }
}
public void setController(WebViewController ctl) {
mWebViewController = ctl;
+ updateShouldCaptureThumbnails();
}
public void setId(long id) {
@@ -1435,6 +1475,13 @@
mWebViewController.onSetWebView(this, w);
+ if (mMainView != null) {
+ if (w != null) {
+ syncCurrentState(w, null);
+ } else {
+ mCurrentState = new PageState(mContext, false);
+ }
+ }
// set the new one
mMainView = w;
// attach the WebViewClient, WebChromeClient and DownloadListener
@@ -1448,6 +1495,10 @@
mMainView.setDownloadListener(mDownloadListener);
mMainView.setWebBackForwardListClient(mWebBackForwardListClient);
mMainView.setPictureListener(this);
+ if (mSavedState != null) {
+ mMainView.restoreState(mSavedState);
+ mSavedState = null;
+ }
}
}
@@ -1480,6 +1531,7 @@
if (mParent != null) {
mParent.mChildren.remove(this);
}
+ deleteThumbnail();
}
/**
@@ -1739,7 +1791,10 @@
* Get the favicon of this tab.
*/
Bitmap getFavicon() {
- return mCurrentState.mFavicon;
+ if (mCurrentState.mFavicon != null) {
+ return mCurrentState.mFavicon;
+ }
+ return getDefaultFavicon(mContext);
}
public boolean isBookmarkedSite() {
@@ -1796,43 +1851,19 @@
}
/**
- * Get the cached saved state bundle.
- * @return cached state bundle
+ * @return The Bundle with the tab's state if it can be saved, otherwise null
*/
- Bundle getSavedState() {
- return mSavedState;
- }
-
- Bundle getSavedState(boolean saveImages) {
- if (saveImages && mCapture != null) {
- Bundle b = new Bundle(mSavedState);
- b.putParcelable(SCREENSHOT, mCapture);
- return b;
- }
- return mSavedState;
- }
-
- /**
- * Set the saved state.
- */
- void setSavedState(Bundle state) {
- mSavedState = state;
- }
-
- /**
- * @return TRUE if succeed in saving the state.
- */
- boolean saveState() {
+ public Bundle saveState() {
// If the WebView is null it means we ran low on memory and we already
// stored the saved state in mSavedState.
if (mMainView == null) {
- return mSavedState != null;
+ return mSavedState;
}
// If the tab is the homepage or has no URL, don't save it
String homepage = BrowserSettings.getInstance().getHomePage();
if (TextUtils.equals(homepage, mCurrentState.mUrl)
|| TextUtils.isEmpty(mCurrentState.mUrl)) {
- return false;
+ return null;
}
mSavedState = new Bundle();
@@ -1841,6 +1872,7 @@
mSavedState.putLong(ID, mId);
mSavedState.putString(CURRURL, mCurrentState.mUrl);
mSavedState.putString(CURRTITLE, mCurrentState.mTitle);
+ mSavedState.putBoolean(INCOGNITO, mMainView.isPrivateBrowsingEnabled());
if (mAppId != null) {
mSavedState.putString(APPID, mAppId);
}
@@ -1850,35 +1882,35 @@
}
mSavedState.putBoolean(USERAGENT,
mSettings.hasDesktopUseragent(getWebView()));
- return true;
+ return mSavedState;
}
/*
* Restore the state of the tab.
*/
- boolean restoreState(Bundle b) {
- if (b == null) {
- return false;
+ private void restoreState(Bundle b) {
+ mSavedState = b;
+ if (mSavedState == null) {
+ return;
}
// Restore the internal state even if the WebView fails to restore.
// This will maintain the app id, original url and close-on-exit values.
- mSavedState = null;
mId = b.getLong(ID);
mAppId = b.getString(APPID);
- final Bitmap sshot = b.getParcelable(SCREENSHOT);
- if (sshot != null) {
- mCapture = sshot;
- }
if (b.getBoolean(USERAGENT)
!= mSettings.hasDesktopUseragent(getWebView())) {
mSettings.toggleDesktopUseragent(getWebView());
}
-
- final WebBackForwardList list = mMainView.restoreState(b);
- if (list == null) {
- return false;
+ String url = b.getString(CURRURL);
+ String title = b.getString(CURRTITLE);
+ boolean incognito = b.getBoolean(INCOGNITO);
+ mCurrentState = new PageState(mContext, incognito, url, null);
+ mCurrentState.mTitle = title;
+ synchronized (Tab.this) {
+ if (mCapture != null) {
+ BackgroundHandler.execute(mLoadThumbnail);
+ }
}
- return true;
}
public void updateBookmarkedStatus() {
@@ -1896,12 +1928,10 @@
}
};
- public void setScreenshot(Bitmap screenshot) {
- mCapture = screenshot;
- }
-
public Bitmap getScreenshot() {
- return mCapture;
+ synchronized (Tab.this) {
+ return mCapture;
+ }
}
public boolean isSnapshot() {
@@ -1963,11 +1993,16 @@
float scale = mCaptureWidth / (float) mMainView.getWidth();
c.scale(scale, scale, left, top);
mMainView.draw(c);
+ persistThumbnail();
}
@Override
public void onNewPicture(WebView view, Picture picture) {
//update screenshot
+ postCapture();
+ }
+
+ private void postCapture() {
if (!mHandler.hasMessages(MSG_CAPTURE)) {
mHandler.sendEmptyMessageDelayed(MSG_CAPTURE, CAPTURE_DELAY);
}
@@ -1993,4 +2028,84 @@
}
}
+ protected void persistThumbnail() {
+ BackgroundHandler.execute(mSaveThumbnail);
+ }
+
+ protected void deleteThumbnail() {
+ BackgroundHandler.execute(mDeleteThumbnail);
+ }
+
+ private void updateCaptureFromBlob(byte[] blob) {
+ synchronized (Tab.this) {
+ if (mCapture == null) {
+ return;
+ }
+ mCapture.copyPixelsFromBuffer(ByteBuffer.wrap(blob));
+ }
+ }
+
+ private byte[] getCaptureBlob() {
+ synchronized (Tab.this) {
+ if (mCapture == null) {
+ return null;
+ }
+ ByteBuffer buffer = ByteBuffer.allocate(mCapture.getByteCount());
+ mCapture.copyPixelsToBuffer(buffer);
+ return buffer.array();
+ }
+ }
+
+ private Runnable mSaveThumbnail = new Runnable() {
+
+ @Override
+ public void run() {
+ byte[] blob = getCaptureBlob();
+ if (blob == null) {
+ return;
+ }
+ ContentResolver cr = mContext.getContentResolver();
+ ContentValues values = new ContentValues();
+ values.put(Thumbnails._ID, mId);
+ values.put(Thumbnails.THUMBNAIL, blob);
+ cr.insert(Thumbnails.CONTENT_URI, values);
+ }
+ };
+
+ private Runnable mDeleteThumbnail = new Runnable() {
+
+ @Override
+ public void run() {
+ ContentResolver cr = mContext.getContentResolver();
+ try {
+ cr.delete(ContentUris.withAppendedId(Thumbnails.CONTENT_URI, mId),
+ null, null);
+ } catch (Throwable t) {}
+ }
+ };
+
+ private Runnable mLoadThumbnail = new Runnable() {
+
+ @Override
+ public void run() {
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor c = null;
+ try {
+ Uri uri = ContentUris.withAppendedId(Thumbnails.CONTENT_URI, mId);
+ c = cr.query(uri, new String[] {Thumbnails._ID,
+ Thumbnails.THUMBNAIL}, null, null, null);
+ if (c.moveToFirst()) {
+ byte[] data = c.getBlob(1);
+ if (data != null && data.length > 0) {
+ updateCaptureFromBlob(data);
+ }
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ }
+ };
+
}