diff options
| author | John Reck <jreck@google.com> | 2012-04-18 17:42:06 -0700 |
|---|---|---|
| committer | John Reck <jreck@google.com> | 2012-04-19 10:32:36 -0700 |
| commit | 2b71d6dad1cbdc84da3eed140429a102971a1106 (patch) | |
| tree | b00a91c23f7af77164b3cecd9939f2277aca4b02 /src/com/android | |
| parent | b2ce8741bf389cca77aad12ff5971e51e6e6c3a5 (diff) | |
| download | packages_apps_Browser-2b71d6dad1cbdc84da3eed140429a102971a1106.tar.gz packages_apps_Browser-2b71d6dad1cbdc84da3eed140429a102971a1106.tar.bz2 packages_apps_Browser-2b71d6dad1cbdc84da3eed140429a102971a1106.zip | |
Change where saved pages are stored
Bug: 5416822
Move saved pages out of external storage (b/5605575)
Save them as files instead of in the database, as the database
has a row size limit
Change-Id: I03b5af2459724d8cab67a9acfcc2827c7129e80f
Diffstat (limited to 'src/com/android')
| -rw-r--r-- | src/com/android/browser/BrowserSnapshotPage.java | 6 | ||||
| -rw-r--r-- | src/com/android/browser/SnapshotByteArrayOutputStream.java | 59 | ||||
| -rw-r--r-- | src/com/android/browser/SnapshotTab.java | 50 | ||||
| -rw-r--r-- | src/com/android/browser/Tab.java | 16 | ||||
| -rw-r--r-- | src/com/android/browser/provider/SnapshotProvider.java | 151 |
5 files changed, 124 insertions, 158 deletions
diff --git a/src/com/android/browser/BrowserSnapshotPage.java b/src/com/android/browser/BrowserSnapshotPage.java index be4f9af0f..b0d82056e 100644 --- a/src/com/android/browser/BrowserSnapshotPage.java +++ b/src/com/android/browser/BrowserSnapshotPage.java @@ -61,7 +61,7 @@ public class BrowserSnapshotPage extends Fragment implements private static final String[] PROJECTION = new String[] { Snapshots._ID, Snapshots.TITLE, - "length(" + Snapshots.VIEWSTATE + ")", + Snapshots.VIEWSTATE_SIZE, Snapshots.THUMBNAIL, Snapshots.FAVICON, Snapshots.URL, @@ -69,7 +69,7 @@ public class BrowserSnapshotPage extends Fragment implements }; private static final int SNAPSHOT_ID = 0; private static final int SNAPSHOT_TITLE = 1; - private static final int SNAPSHOT_VIEWSTATE_LENGTH = 2; + private static final int SNAPSHOT_VIEWSTATE_SIZE = 2; private static final int SNAPSHOT_THUMBNAIL = 3; private static final int SNAPSHOT_FAVICON = 4; private static final int SNAPSHOT_URL = 5; @@ -281,7 +281,7 @@ public class BrowserSnapshotPage extends Fragment implements title.setText(cursor.getString(SNAPSHOT_TITLE)); TextView size = (TextView) view.findViewById(R.id.size); if (size != null) { - int stateLen = cursor.getInt(SNAPSHOT_VIEWSTATE_LENGTH); + int stateLen = cursor.getInt(SNAPSHOT_VIEWSTATE_SIZE); size.setText(String.format("%.2fMB", stateLen / 1024f / 1024f)); } long timestamp = cursor.getLong(SNAPSHOT_DATE_CREATED); diff --git a/src/com/android/browser/SnapshotByteArrayOutputStream.java b/src/com/android/browser/SnapshotByteArrayOutputStream.java deleted file mode 100644 index 127eee802..000000000 --- a/src/com/android/browser/SnapshotByteArrayOutputStream.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -public class SnapshotByteArrayOutputStream extends OutputStream { - - // Maximum size, this needs to be small enough such that an entire row - // can fit in CursorWindow's 2MB limit - private static final int MAX_SIZE = 1700000; - private ByteArrayOutputStream mStream; - - public SnapshotByteArrayOutputStream() { - mStream = new ByteArrayOutputStream(MAX_SIZE); - } - - @Override - public synchronized void write(int oneByte) throws IOException { - checkError(1); - mStream.write(oneByte); - } - - @Override - public void write(byte[] buffer, int offset, int count) throws IOException { - checkError(count); - mStream.write(buffer, offset, count); - } - - private void checkError(int expandBy) throws IOException { - if ((size() + expandBy) > MAX_SIZE) { - throw new IOException("Exceeded max size!"); - } - } - - public int size() { - return mStream.size(); - } - - public byte[] toByteArray() { - return mStream.toByteArray(); - } - -} diff --git a/src/com/android/browser/SnapshotTab.java b/src/com/android/browser/SnapshotTab.java index f58f88b4c..e14f09590 100644 --- a/src/com/android/browser/SnapshotTab.java +++ b/src/com/android/browser/SnapshotTab.java @@ -18,11 +18,13 @@ package com.android.browser; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; +import android.content.Context; import android.database.Cursor; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.text.TextUtils; import android.util.Log; import android.webkit.WebView; import android.webkit.WebViewClassic; @@ -30,6 +32,8 @@ import android.webkit.WebViewClassic; import com.android.browser.provider.SnapshotProvider.Snapshots; import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; import java.util.Map; import java.util.zip.GZIPInputStream; @@ -75,7 +79,7 @@ public class SnapshotTab extends Tab { void loadData() { if (mLoadTask == null) { - mLoadTask = new LoadData(this, mContext.getContentResolver()); + mLoadTask = new LoadData(this, mContext); mLoadTask.execute(); } } @@ -152,20 +156,31 @@ public class SnapshotTab extends Tab { static final String[] PROJECTION = new String[] { Snapshots._ID, // 0 - Snapshots.TITLE, // 1 - Snapshots.URL, // 2 + Snapshots.URL, // 1 + Snapshots.TITLE, // 2 Snapshots.FAVICON, // 3 Snapshots.VIEWSTATE, // 4 Snapshots.BACKGROUND, // 5 Snapshots.DATE_CREATED, // 6 + Snapshots.VIEWSTATE_PATH, // 7 }; + static final int SNAPSHOT_ID = 0; + static final int SNAPSHOT_URL = 1; + static final int SNAPSHOT_TITLE = 2; + static final int SNAPSHOT_FAVICON = 3; + static final int SNAPSHOT_VIEWSTATE = 4; + static final int SNAPSHOT_BACKGROUND = 5; + static final int SNAPSHOT_DATE_CREATED = 6; + static final int SNAPSHOT_VIEWSTATE_PATH = 7; private SnapshotTab mTab; private ContentResolver mContentResolver; + private Context mContext; - public LoadData(SnapshotTab t, ContentResolver cr) { + public LoadData(SnapshotTab t, Context context) { mTab = t; - mContentResolver = cr; + mContentResolver = context.getContentResolver(); + mContext = context; } @Override @@ -175,26 +190,35 @@ public class SnapshotTab extends Tab { return mContentResolver.query(uri, PROJECTION, null, null, null); } + private InputStream getInputStream(Cursor c) throws FileNotFoundException { + String path = c.getString(SNAPSHOT_VIEWSTATE_PATH); + if (!TextUtils.isEmpty(path)) { + return mContext.openFileInput(path); + } + byte[] data = c.getBlob(SNAPSHOT_VIEWSTATE); + ByteArrayInputStream bis = new ByteArrayInputStream(data); + return bis; + } + @Override protected void onPostExecute(Cursor result) { try { if (result.moveToFirst()) { - mTab.mCurrentState.mTitle = result.getString(1); - mTab.mCurrentState.mUrl = result.getString(2); - byte[] favicon = result.getBlob(3); + mTab.mCurrentState.mTitle = result.getString(SNAPSHOT_TITLE); + mTab.mCurrentState.mUrl = result.getString(SNAPSHOT_URL); + byte[] favicon = result.getBlob(SNAPSHOT_FAVICON); if (favicon != null) { mTab.mCurrentState.mFavicon = BitmapFactory .decodeByteArray(favicon, 0, favicon.length); } WebViewClassic web = mTab.getWebViewClassic(); if (web != null) { - byte[] data = result.getBlob(4); - ByteArrayInputStream bis = new ByteArrayInputStream(data); - GZIPInputStream stream = new GZIPInputStream(bis); + InputStream ins = getInputStream(result); + GZIPInputStream stream = new GZIPInputStream(ins); web.loadViewState(stream); } - mTab.mBackgroundColor = result.getInt(5); - mTab.mDateCreated = result.getLong(6); + mTab.mBackgroundColor = result.getInt(SNAPSHOT_BACKGROUND); + mTab.mDateCreated = result.getLong(SNAPSHOT_DATE_CREATED); mTab.mWebViewController.onPageFinished(mTab); } } catch (Exception e) { diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java index 9b5a67579..04bee08dd 100644 --- a/src/com/android/browser/Tab.java +++ b/src/com/android/browser/Tab.java @@ -64,8 +64,8 @@ import android.webkit.WebHistoryItem; import android.webkit.WebResourceResponse; import android.webkit.WebStorage; import android.webkit.WebView; -import android.webkit.WebViewClassic; import android.webkit.WebView.PictureListener; +import android.webkit.WebViewClassic; import android.webkit.WebViewClient; import android.widget.CheckBox; import android.widget.Toast; @@ -76,12 +76,15 @@ import com.android.browser.provider.SnapshotProvider.Snapshots; import com.android.common.speech.LoggingEvents; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; +import java.util.UUID; import java.util.Vector; import java.util.regex.Pattern; import java.util.zip.GZIPOutputStream; @@ -2059,9 +2062,10 @@ class Tab implements PictureListener { public ContentValues createSnapshotValues() { if (mMainView == null) return null; - SnapshotByteArrayOutputStream bos = new SnapshotByteArrayOutputStream(); + String path = UUID.randomUUID().toString(); try { - GZIPOutputStream stream = new GZIPOutputStream(bos); + OutputStream outs = mContext.openFileOutput(path, Context.MODE_PRIVATE); + GZIPOutputStream stream = new GZIPOutputStream(outs); if (!getWebViewClassic().saveViewState(stream)) { return null; } @@ -2071,11 +2075,13 @@ class Tab implements PictureListener { Log.w(LOGTAG, "Failed to save view state", e); return null; } - byte[] data = bos.toByteArray(); + File savedFile = mContext.getFileStreamPath(path); + long size = savedFile.length(); ContentValues values = new ContentValues(); values.put(Snapshots.TITLE, mCurrentState.mTitle); values.put(Snapshots.URL, mCurrentState.mUrl); - values.put(Snapshots.VIEWSTATE, data); + values.put(Snapshots.VIEWSTATE_PATH, path); + values.put(Snapshots.VIEWSTATE_SIZE, size); values.put(Snapshots.BACKGROUND, getWebViewClassic().getPageBackgroundColor()); values.put(Snapshots.DATE_CREATED, System.currentTimeMillis()); values.put(Snapshots.FAVICON, compressBitmap(getFavicon())); diff --git a/src/com/android/browser/provider/SnapshotProvider.java b/src/com/android/browser/provider/SnapshotProvider.java index 437a86776..291e93b78 100644 --- a/src/com/android/browser/provider/SnapshotProvider.java +++ b/src/com/android/browser/provider/SnapshotProvider.java @@ -15,13 +15,10 @@ */ 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; @@ -29,15 +26,11 @@ 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.os.FileUtils; 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 { @@ -45,6 +38,7 @@ public class SnapshotProvider extends ContentProvider { public static final Uri CONTENT_URI = Uri.withAppendedPath( SnapshotProvider.AUTHORITY_URI, "snapshots"); public static final String _ID = "_id"; + @Deprecated public static final String VIEWSTATE = "view_state"; public static final String BACKGROUND = "background"; public static final String TITLE = "title"; @@ -52,6 +46,8 @@ public class SnapshotProvider extends ContentProvider { public static final String FAVICON = "favicon"; public static final String THUMBNAIL = "thumbnail"; public static final String DATE_CREATED = "date_created"; + public static final String VIEWSTATE_PATH = "viewstate_path"; + public static final String VIEWSTATE_SIZE = "viewstate_size"; } public static final String AUTHORITY = "com.android.browser.snapshots"; @@ -61,6 +57,8 @@ public class SnapshotProvider extends ContentProvider { static final int SNAPSHOTS = 10; static final int SNAPSHOTS_ID = 11; static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); + // Workaround that we can't remove the "NOT NULL" constraint on VIEWSTATE + static final byte[] NULL_BLOB_HACK = new byte[0]; SnapshotDatabaseHelper mOpenHelper; @@ -72,15 +70,10 @@ public class SnapshotProvider extends ContentProvider { final static class SnapshotDatabaseHelper extends SQLiteOpenHelper { static final String DATABASE_NAME = "snapshots.db"; - static final int DATABASE_VERSION = 2; + static final int DATABASE_VERSION = 3; 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(); + super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override @@ -93,7 +86,9 @@ public class SnapshotProvider extends ContentProvider { Snapshots.FAVICON + " BLOB," + Snapshots.THUMBNAIL + " BLOB," + Snapshots.BACKGROUND + " INTEGER," + - Snapshots.VIEWSTATE + " BLOB NOT NULL" + + Snapshots.VIEWSTATE + " BLOB NOT NULL," + + Snapshots.VIEWSTATE_PATH + " TEXT," + + Snapshots.VIEWSTATE_SIZE + "INTEGER" + ");"); } @@ -103,64 +98,52 @@ public class SnapshotProvider extends ContentProvider { db.execSQL("DROP TABLE " + TABLE_SNAPSHOTS); onCreate(db); } + if (oldVersion < 3) { + db.execSQL("ALTER TABLE " + TABLE_SNAPSHOTS + " ADD COLUMN " + + Snapshots.VIEWSTATE_PATH + " TEXT"); + db.execSQL("ALTER TABLE " + TABLE_SNAPSHOTS + " ADD COLUMN " + + Snapshots.VIEWSTATE_SIZE + " INTEGER"); + db.execSQL("UPDATE " + TABLE_SNAPSHOTS + " SET " + + Snapshots.VIEWSTATE_SIZE + " = length(" + + Snapshots.VIEWSTATE + ")"); + } } } - @Override - public boolean onCreate() { - IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_EJECT); - filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); - getContext().registerReceiver(mExternalStorageReceiver, filter); - return true; + static File getOldDatabasePath(Context context) { + File dir = context.getExternalFilesDir(null); + return new File(dir, SnapshotDatabaseHelper.DATABASE_NAME); } - final BroadcastReceiver mExternalStorageReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - if (mOpenHelper != null) { - 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 - } + private void migrateToDataFolder() { + File dbPath = getContext().getDatabasePath(SnapshotDatabaseHelper.DATABASE_NAME); + if (dbPath.exists()) return; + File oldPath = getOldDatabasePath(getContext()); + if (oldPath.exists()) { + // Try to move + if (!oldPath.renameTo(dbPath)) { + // Failed, do a copy + FileUtils.copyFile(oldPath, dbPath); } + // Cleanup + oldPath.delete(); } - }; + } + + @Override + public boolean onCreate() { + migrateToDataFolder(); + mOpenHelper = new SnapshotDatabaseHelper(getContext()); + return true; + } SQLiteDatabase getWritableDatabase() { - String state = Environment.getExternalStorageState(); - if (Environment.MEDIA_MOUNTED.equals(state)) { - try { - if (mOpenHelper == null) { - mOpenHelper = new SnapshotDatabaseHelper(getContext()); - } - return mOpenHelper.getWritableDatabase(); - } catch (Throwable t) { - return null; - } - } - return null; + return mOpenHelper.getWritableDatabase(); } SQLiteDatabase getReadableDatabase() { - String state = Environment.getExternalStorageState(); - if (Environment.MEDIA_MOUNTED.equals(state) - || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { - try { - if (mOpenHelper == null) { - mOpenHelper = new SnapshotDatabaseHelper(getContext()); - } - return mOpenHelper.getReadableDatabase(); - } catch (Throwable t) { - return null; - } - } - return null; + return mOpenHelper.getReadableDatabase(); } @Override @@ -186,15 +169,11 @@ public class SnapshotProvider extends ContentProvider { 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; - } + Cursor cursor = qb.query(db, projection, selection, selectionArgs, + null, null, sortOrder, limit); + cursor.setNotificationUri(getContext().getContentResolver(), + AUTHORITY_URI); + return cursor; } @Override @@ -212,11 +191,10 @@ public class SnapshotProvider extends ContentProvider { long id = -1; switch (match) { case SNAPSHOTS: - try { - id = db.insert(TABLE_SNAPSHOTS, Snapshots.TITLE, values); - } catch (Throwable t) { - id = -1; + if (!values.containsKey(Snapshots.VIEWSTATE)) { + values.put(Snapshots.VIEWSTATE, NULL_BLOB_HACK); } + id = db.insert(TABLE_SNAPSHOTS, Snapshots.TITLE, values); break; default: throw new UnsupportedOperationException("Unknown insert URI " + uri); @@ -229,6 +207,25 @@ public class SnapshotProvider extends ContentProvider { return inserted; } + static final String[] DELETE_PROJECTION = new String[] { + Snapshots.VIEWSTATE_PATH, + }; + private void deleteDataFiles(SQLiteDatabase db, String selection, + String[] selectionArgs) { + Cursor c = db.query(TABLE_SNAPSHOTS, DELETE_PROJECTION, selection, + selectionArgs, null, null, null); + final Context context = getContext(); + while (c.moveToNext()) { + File f = context.getFileStreamPath(c.getString(0)); + if (f.exists()) { + if (!f.delete()) { + f.deleteOnExit(); + } + } + } + c.close(); + } + @Override public int delete(Uri uri, String selection, String[] selectionArgs) { SQLiteDatabase db = getWritableDatabase(); @@ -245,10 +242,8 @@ public class SnapshotProvider extends ContentProvider { // fall through } case SNAPSHOTS: - try { - deleted = db.delete(TABLE_SNAPSHOTS, selection, selectionArgs); - } catch (Throwable t) { - } + deleteDataFiles(db, selection, selectionArgs); + deleted = db.delete(TABLE_SNAPSHOTS, selection, selectionArgs); break; default: throw new UnsupportedOperationException("Unknown delete URI " + uri); |
