Commit 8cc92358 authored by John Reck's avatar John Reck
Browse files

Move Snapshots to own DB on sdcard

 Bug: 4982126

Change-Id: Ib66b2880d163de4feb4d880e1d01996301bbea08
parent dc0282ad
......@@ -254,6 +254,10 @@
</intent-filter>
</receiver>
<provider android:name=".provider.SnapshotProvider"
android:authorities="com.android.browser.snapshots"
android:exported="false" />
</application>
</manifest>
......
......@@ -40,8 +40,9 @@
android:textSize="12sp"
android:typeface="sans"
android:textColor="@android:color/white"
android:paddingLeft="2dip"
android:paddingRight="2dip" />
android:paddingLeft="6dip"
android:paddingRight="2dip"
android:gravity="center_vertical" />
<ImageView
android:id="@+id/divider"
android:src="?android:attr/dividerVertical"
......
......@@ -43,7 +43,7 @@ import android.widget.ImageView;
import android.widget.ResourceCursorAdapter;
import android.widget.TextView;
import com.android.browser.provider.BrowserProvider2.Snapshots;
import com.android.browser.provider.SnapshotProvider.Snapshots;
import java.text.DateFormat;
import java.util.Date;
......@@ -61,12 +61,14 @@ public class BrowserSnapshotPage extends Fragment implements
Snapshots.THUMBNAIL,
Snapshots.FAVICON,
Snapshots.URL,
Snapshots.DATE_CREATED,
};
private static final int SNAPSHOT_TITLE = 1;
private static final int SNAPSHOT_VIEWSTATE_LENGTH = 2;
private static final int SNAPSHOT_THUMBNAIL = 3;
private static final int SNAPSHOT_FAVICON = 4;
private static final int SNAPSHOT_URL = 5;
private static final int SNAPSHOT_DATE_CREATED = 6;
GridView mGrid;
View mEmpty;
......@@ -113,10 +115,9 @@ public class BrowserSnapshotPage extends Fragment implements
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == LOADER_SNAPSHOTS) {
// TODO: Sort by date created
return new CursorLoader(getActivity(),
Snapshots.CONTENT_URI, PROJECTION,
null, null, null);
null, null, Snapshots.DATE_CREATED + " DESC");
}
return null;
}
......@@ -216,12 +217,11 @@ public class BrowserSnapshotPage extends Fragment implements
title.setText(cursor.getString(SNAPSHOT_TITLE));
TextView size = (TextView) view.findViewById(R.id.size);
int stateLen = cursor.getInt(SNAPSHOT_VIEWSTATE_LENGTH);
size.setText(String.format("%.1fMB", stateLen / 1024f / 1024f));
// We don't actually have the date in the database yet
// Use the current date as a placeholder
size.setText(String.format("%.2fMB", stateLen / 1024f / 1024f));
long timestamp = cursor.getLong(SNAPSHOT_DATE_CREATED);
TextView date = (TextView) view.findViewById(R.id.date);
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT);
date.setText(dateFormat.format(new Date()));
date.setText(dateFormat.format(new Date(timestamp)));
}
@Override
......
......@@ -79,7 +79,7 @@ import com.android.browser.IntentHandler.UrlData;
import com.android.browser.UI.ComboViews;
import com.android.browser.UI.DropdownChangeListener;
import com.android.browser.provider.BrowserProvider;
import com.android.browser.provider.BrowserProvider2.Snapshots;
import com.android.browser.provider.SnapshotProvider.Snapshots;
import com.android.browser.search.SearchEngine;
import com.android.common.Search;
......@@ -1945,7 +1945,7 @@ public class Controller
return null;
}
private static Bitmap createScreenshot(WebView view, int width, int height) {
static Bitmap createScreenshot(WebView view, int width, int height) {
// We render to a bitmap 2x the desired size so that we can then
// re-scale it with filtering since canvas.scale doesn't filter
// This helps reduce aliasing at the cost of being slightly blurry
......
......@@ -20,19 +20,21 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;
import com.android.browser.provider.BrowserProvider2.Snapshots;
import com.android.browser.provider.SnapshotProvider.Snapshots;
import java.io.ByteArrayInputStream;
import java.util.zip.GZIPInputStream;
public class SnapshotTab extends Tab {
private static final String LOGTAG = "SnapshotTab";
private long mSnapshotId;
private LoadData mLoadTask;
private WebViewFactory mWebViewFactory;
......@@ -145,8 +147,13 @@ public class SnapshotTab extends Tab {
WebView web = mTab.getWebView();
if (web != null) {
byte[] data = result.getBlob(4);
ByteArrayInputStream stream = new ByteArrayInputStream(data);
web.loadViewState(stream);
ByteArrayInputStream bis = new ByteArrayInputStream(data);
try {
GZIPInputStream stream = new GZIPInputStream(bis);
web.loadViewState(stream);
} catch (Exception e) {
Log.w(LOGTAG, "Failed to load view state", e);
}
}
mTab.mBackgroundColor = result.getInt(5);
mTab.mWebViewController.onPageFinished(mTab);
......
......@@ -27,6 +27,7 @@ import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap.CompressFormat;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Bundle;
......@@ -63,7 +64,7 @@ import android.widget.TextView;
import android.widget.Toast;
import com.android.browser.homepages.HomeProvider;
import com.android.browser.provider.BrowserProvider2.Snapshots;
import com.android.browser.provider.SnapshotProvider.Snapshots;
import com.android.common.speech.LoggingEvents;
import java.io.ByteArrayOutputStream;
......@@ -73,6 +74,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Vector;
import java.util.zip.GZIPOutputStream;
/**
* Class for maintaining Tabs with a main WebView and a subwindow.
......@@ -1875,28 +1877,42 @@ class Tab {
public ContentValues createSnapshotValues() {
if (mMainView == null) return null;
/*
* TODO: Compression
* Some quick tests indicate GZIPing the stream will result in
* some decent savings. There is little overhead for sites with mostly
* images (such as the "Most Visited" page), dropping from 235kb
* to 200kb. Sites with a decent amount of text (hardocp.com), the size
* drops from 522kb to 381kb. Do this as part of the switch to saving
* to the SD card.
*/
ByteArrayOutputStream stream = new ByteArrayOutputStream();
if (!mMainView.saveViewState(stream)) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
GZIPOutputStream stream = new GZIPOutputStream(bos);
if (!mMainView.saveViewState(stream)) {
return null;
}
stream.flush();
stream.close();
} catch (Exception e) {
Log.w(LOGTAG, "Failed to save view state", e);
return null;
}
byte[] data = stream.toByteArray();
byte[] data = bos.toByteArray();
ContentValues values = new ContentValues();
values.put(Snapshots.TITLE, mCurrentState.mTitle);
values.put(Snapshots.URL, mCurrentState.mUrl);
values.put(Snapshots.VIEWSTATE, data);
values.put(Snapshots.BACKGROUND, mMainView.getPageBackgroundColor());
values.put(Snapshots.DATE_CREATED, System.currentTimeMillis());
values.put(Snapshots.FAVICON, compressBitmap(getFavicon()));
Bitmap screenshot = Controller.createScreenshot(mMainView,
Controller.getDesiredThumbnailWidth(mContext),
Controller.getDesiredThumbnailHeight(mContext));
values.put(Snapshots.THUMBNAIL, compressBitmap(screenshot));
return values;
}
public byte[] compressBitmap(Bitmap bitmap) {
if (bitmap == null) {
return null;
}
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.PNG, 100, stream);
return stream.toByteArray();
}
public void loadUrl(String url, Map<String, String> headers) {
if (mMainView != null) {
mCurrentState = new PageState(mContext, false, url, null);
......
......@@ -68,19 +68,6 @@ import java.util.HashMap;
public class BrowserProvider2 extends SQLiteContentProvider {
public static interface Snapshots {
public static final Uri CONTENT_URI = Uri.withAppendedPath(
BrowserContract.AUTHORITY_URI, "snapshots");
public static final String _ID = "_id";
public static final String VIEWSTATE = "view_state";
public static final String BACKGROUND = "background";
public static final String TITLE = History.TITLE;
public static final String URL = History.URL;
public static final String FAVICON = History.FAVICON;
public static final String THUMBNAIL = History.THUMBNAIL;
}
public static final String PARAM_GROUP_BY = "groupBy";
public static final String LEGACY_AUTHORITY = "browser";
......@@ -152,9 +139,6 @@ public class BrowserProvider2 extends SQLiteContentProvider {
static final int LEGACY = 9000;
static final int LEGACY_ID = 9001;
static final int SNAPSHOTS = 10000;
static final int SNAPSHOTS_ID = 10001;
public static final long FIXED_ID_ROOT = 1;
// Default sort order for unsync'd bookmarks
......@@ -216,9 +200,6 @@ public class BrowserProvider2 extends SQLiteContentProvider {
"bookmarks/" + SearchManager.SUGGEST_URI_PATH_QUERY,
BOOKMARKS_SUGGESTIONS);
matcher.addURI(authority, "snapshots", SNAPSHOTS);
matcher.addURI(authority, "snapshots/#", SNAPSHOTS_ID);
// Projection maps
HashMap<String, String> map;
......@@ -352,7 +333,7 @@ public class BrowserProvider2 extends SQLiteContentProvider {
final class DatabaseHelper extends SQLiteOpenHelper {
static final String DATABASE_NAME = "browser2.db";
static final int DATABASE_VERSION = 29;
static final int DATABASE_VERSION = 30;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
......@@ -423,8 +404,6 @@ public class BrowserProvider2 extends SQLiteContentProvider {
}
enableSync(db);
createSnapshots(db);
}
void enableSync(SQLiteDatabase db) {
......@@ -521,8 +500,9 @@ public class BrowserProvider2 extends SQLiteContentProvider {
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 29) {
createSnapshots(db);
if (oldVersion < 30) {
db.execSQL("DROP VIEW IF EXISTS " + VIEW_SNAPSHOTS_COMBINED);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_SNAPSHOTS);
}
if (oldVersion < 28) {
enableSync(db);
......@@ -544,23 +524,6 @@ public class BrowserProvider2 extends SQLiteContentProvider {
}
}
void createSnapshots(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_SNAPSHOTS);
db.execSQL("CREATE TABLE " + TABLE_SNAPSHOTS + " (" +
Snapshots._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Snapshots.URL + " TEXT NOT NULL," +
Snapshots.TITLE + " TEXT," +
Snapshots.BACKGROUND + " INTEGER," +
Snapshots.VIEWSTATE + " BLOB NOT NULL" +
");");
db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_SNAPSHOTS_COMBINED +
" AS SELECT * FROM " + TABLE_SNAPSHOTS +
" LEFT OUTER JOIN " + TABLE_IMAGES +
" ON " + TABLE_SNAPSHOTS + "." + Snapshots.URL +
" = images.url_key");
}
@Override
public void onOpen(SQLiteDatabase db) {
db.enableWriteAheadLogging();
mSyncHelper.onDatabaseOpened(db);
......@@ -1011,17 +974,6 @@ public class BrowserProvider2 extends SQLiteContentProvider {
break;
}
case SNAPSHOTS_ID: {
selection = DatabaseUtils.concatenateWhere(selection, "_id=?");
selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
// fall through
}
case SNAPSHOTS: {
qb.setTables(VIEW_SNAPSHOTS_COMBINED);
break;
}
default: {
throw new UnsupportedOperationException("Unknown URL " + uri.toString());
}
......@@ -1221,16 +1173,6 @@ public class BrowserProvider2 extends SQLiteContentProvider {
}
break;
}
case SNAPSHOTS_ID: {
selection = DatabaseUtils.concatenateWhere(selection, TABLE_SNAPSHOTS + "._id=?");
selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
// fall through
}
case SNAPSHOTS: {
deleted = db.delete(TABLE_SNAPSHOTS, selection, selectionArgs);
break;
}
default: {
throw new UnsupportedOperationException("Unknown delete URI " + uri);
}
......@@ -1368,11 +1310,6 @@ public class BrowserProvider2 extends SQLiteContentProvider {
break;
}
case SNAPSHOTS: {
id = db.insertOrThrow(TABLE_SNAPSHOTS, Snapshots.TITLE, values);
break;
}
default: {
throw new UnsupportedOperationException("Unknown insert URI " + uri);
}
......
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.browser.provider;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Environment;
import android.provider.BrowserContract;
import java.io.File;
/**
* This provider is expected to be potentially flaky. It uses a database
* stored on external storage, which could be yanked unexpectedly.
*/
public class SnapshotProvider extends ContentProvider {
public static interface Snapshots {
public static final Uri CONTENT_URI = Uri.withAppendedPath(
SnapshotProvider.AUTHORITY_URI, "snapshots");
public static final String _ID = "_id";
public static final String VIEWSTATE = "view_state";
public static final String BACKGROUND = "background";
public static final String TITLE = "title";
public static final String URL = "url";
public static final String FAVICON = "favicon";
public static final String THUMBNAIL = "thumbnail";
public static final String DATE_CREATED = "date_created";
}
public static final String AUTHORITY = "com.android.browser.snapshots";
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
static final String TABLE_SNAPSHOTS = "snapshots";
static final int SNAPSHOTS = 10;
static final int SNAPSHOTS_ID = 11;
static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
SnapshotDatabaseHelper mOpenHelper;
static {
URI_MATCHER.addURI(AUTHORITY, "snapshots", SNAPSHOTS);
URI_MATCHER.addURI(AUTHORITY, "snapshots/#", SNAPSHOTS_ID);
}
final static class SnapshotDatabaseHelper extends SQLiteOpenHelper {
static final String DATABASE_NAME = "snapshots.db";
static final int DATABASE_VERSION = 1;
public SnapshotDatabaseHelper(Context context) {
super(context, getFullDatabaseName(context), null, DATABASE_VERSION);
}
static String getFullDatabaseName(Context context) {
File dir = context.getExternalFilesDir(null);
return new File(dir, DATABASE_NAME).getAbsolutePath();
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_SNAPSHOTS + "(" +
Snapshots._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Snapshots.TITLE + " TEXT," +
Snapshots.URL + " TEXT NOT NULL," +
Snapshots.DATE_CREATED + " INTEGER," +
Snapshots.FAVICON + " BLOB," +
Snapshots.THUMBNAIL + " BLOB," +
Snapshots.BACKGROUND + " INTEGER," +
Snapshots.VIEWSTATE + " BLOB NOT NULL" +
");");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Not needed yet
}
}
@Override
public boolean onCreate() {
mOpenHelper = new SnapshotDatabaseHelper(getContext());
IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_EJECT);
filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
getContext().registerReceiver(mExternalStorageReceiver, filter);
return true;
}
final BroadcastReceiver mExternalStorageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
try {
mOpenHelper.close();
} catch (Throwable t) {
// We failed to close the open helper, which most likely means
// another thread is busy attempting to open the database
// or use the database. Let that thread try to gracefully
// deal with the error
}
}
};
SQLiteDatabase getWritableDatabase() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
try {
return mOpenHelper.getWritableDatabase();
} catch (Throwable t) {
return null;
}
}
return null;
}
SQLiteDatabase getReadableDatabase() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
try {
return mOpenHelper.getReadableDatabase();
} catch (Throwable t) {
return null;
}
}
return null;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = getReadableDatabase();
if (db == null) {
return null;
}
final int match = URI_MATCHER.match(uri);
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
switch (match) {
case SNAPSHOTS_ID:
selection = DatabaseUtils.concatenateWhere(selection, "_id=?");
selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
// fall through
case SNAPSHOTS:
qb.setTables(TABLE_SNAPSHOTS);
break;
default:
throw new UnsupportedOperationException("Unknown URL " + uri.toString());
}
try {
Cursor cursor = qb.query(db, projection, selection, selectionArgs,
null, null, sortOrder, limit);
cursor.setNotificationUri(getContext().getContentResolver(),
AUTHORITY_URI);
return cursor;
} catch (Throwable t) {
return null;
}
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = getWritableDatabase();
if (db == null) {
return null;
}
int match = URI_MATCHER.match(uri);
long id = -1;
switch (match) {
case SNAPSHOTS:
try {
id = db.insert(TABLE_SNAPSHOTS, Snapshots.TITLE, values);
} catch (Throwable t) {
id = -1;
}
break;
default:
throw new UnsupportedOperationException("Unknown insert URI " + uri);
}
if (id < 0) {
return null;
}
Uri inserted = ContentUris.withAppendedId(uri, id);
getContext().getContentResolver().notifyChange(inserted, null, false);
return inserted;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = getWritableDatabase();
if (db == null) {
return 0;
}
int match = URI_MATCHER.match(uri);
int deleted = 0;
switch (match) {
case SNAPSHOTS_ID: {
selection = DatabaseUtils.concatenateWhere(selection, TABLE_SNAPSHOTS + "._id=?");
selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
// fall through
}
case SNAPSHOTS:
try {
deleted = db.delete(TABLE_SNAPSHOTS, selection, selectionArgs);
} catch (Throwable t) {
}
break;
default:
throw new UnsupportedOperationException("Unknown delete URI " + uri);
}
if (deleted > 0) {
getContext().getContentResolver().notifyChange(uri, null, false);
}
return deleted;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
throw new UnsupportedOperationException("not implemented");
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment