Merge change 8853
* changes:
Make the favicons look better on the bookmark page.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 145889e..f3707d4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -19,6 +19,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.browser">
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
+ <uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
@@ -27,8 +28,8 @@
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
- <uses-permission android:name="android.permission.READ_HISTORY_BOOKMARKS"/>
- <uses-permission android:name="android.permission.WRITE_HISTORY_BOOKMARKS"/>
+ <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/>
+ <uses-permission android:name="com.android.browser.permission.WRITE_HISTORY_BOOKMARKS"/>
<application android:name="Browser"
android:label="@string/application_name"
@@ -39,8 +40,8 @@
<provider android:name="BrowserProvider"
android:authorities="browser"
android:multiprocess="true"
- android:readPermission="android.permission.READ_HISTORY_BOOKMARKS"
- android:writePermission="android.permission.WRITE_HISTORY_BOOKMARKS">
+ android:readPermission="com.android.browser.permission.READ_HISTORY_BOOKMARKS"
+ android:writePermission="com.android.browser.permission.WRITE_HISTORY_BOOKMARKS">
<path-permission android:path="/bookmarks/search_suggest_query"
android:readPermission="android.permission.GLOBAL_SEARCH" />
</provider>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0d1c229..b108bbc 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -144,6 +144,8 @@
<string name="open_bookmark">Open</string>
<!-- Menu item to remove the currently highlighted bookmark-->
<string name="remove_bookmark">Delete bookmark</string>
+ <!-- Context menu item to remove a history item from bookmarks -->
+ <string name="remove_from_bookmarks">Remove from bookmarks</string>
<!-- Menu item to remove the currently highlighted history entry from the list of previously visited sites -->
<string name="remove_history_item">Remove from history</string>
<!-- Context menu item for setting the bookmark/history item as the homepage -->
@@ -401,7 +403,7 @@
<!-- Settings summary -->
<string name="pref_extras_gears_enable_summary">Applications that extend browser functionality</string>
<!-- Settings label -->
- <string name="pref_extras_website_settings">Website Settings</string>
+ <string name="pref_extras_website_settings">Website settings</string>
<!-- Settings summary -->
<string name="pref_extras_website_settings_summary">View advanced settings for individual websites</string>
<!-- Settings label -->
@@ -768,11 +770,11 @@
<!-- Used as a toast notification after the user close the html5 webstorage permission dialog -->
<string name="webstorage_notification">The quota for this site can be changed in the Local Storage section of the Browser settings</string>
<!-- Used in the Browser Settings -->
- <string name="webstorage_clear_data_title">Clear Stored Data</string>
+ <string name="webstorage_clear_data_title">Clear stored data</string>
<string name="webstorage_clear_data_summary">Remove all databases associated with this website</string>
<!-- Confirmation dialog when the user ask to clear all data for an origin -->
- <string name="webstorage_clear_data_dialog_title">Clear All Data</string>
- <string name="webstorage_clear_data_dialog_message">All stored data by this origin will be deleted</string>
+ <string name="webstorage_clear_data_dialog_title">Clear stored data</string>
+ <string name="webstorage_clear_data_dialog_message">All data stored by this website will be deleted</string>
<string name="webstorage_clear_data_dialog_ok_button">Clear All</string>
<string name="webstorage_clear_data_dialog_cancel_button">Cancel</string>
<!-- Strings used in the summary of origins -->
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index e827a7e..191659a 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -142,7 +142,7 @@
setResult(RESULT_OK, (new Intent()).setAction(
getIntent().toString()).putExtras(mMap));
} else {
- Bookmarks.addBookmark(null, getContentResolver(), url, title);
+ Bookmarks.addBookmark(null, getContentResolver(), url, title, true);
setResult(RESULT_OK);
}
} catch (IllegalStateException e) {
diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java
index 97e6b20..a3dc919 100644
--- a/src/com/android/browser/Bookmarks.java
+++ b/src/com/android/browser/Bookmarks.java
@@ -47,9 +47,13 @@
* @param cr The ContentResolver being used to add the bookmark to the db.
* @param url URL of the website to be bookmarked.
* @param name Provided name for the bookmark.
+ * @param retainIcon Whether to retain the page's icon in the icon database.
+ * This will usually be <code>true</code> except when bookmarks are
+ * added by a settings restore agent.
*/
/* package */ static void addBookmark(Context context,
- ContentResolver cr, String url, String name) {
+ ContentResolver cr, String url, String name,
+ boolean retainIcon) {
// Want to append to the beginning of the list
long creationTime = new Date().getTime();
// First we check to see if the user has already visited this
@@ -137,7 +141,9 @@
cr.insert(Browser.BOOKMARKS_URI, map);
}
}
- WebIconDatabase.getInstance().retainIconForPageUrl(url);
+ if (retainIcon) {
+ WebIconDatabase.getInstance().retainIconForPageUrl(url);
+ }
cursor.deactivate();
if (context != null) {
Toast.makeText(context, R.string.added_to_bookmarks,
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index d148c0a..7f40494 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -165,8 +165,6 @@
private SensorManager mSensorManager = null;
- private WebStorage.QuotaUpdater mWebStorageQuotaUpdater = null;
-
// These are single-character shortcuts for searching popular sources.
private static final int SHORTCUT_INVALID = 0;
private static final int SHORTCUT_GOOGLE_SEARCH = 1;
@@ -3850,36 +3848,38 @@
}
/**
- * The origin has exceeded it's database quota.
+ * The origin has exceeded its database quota.
* @param url the URL that exceeded the quota
* @param databaseIdentifier the identifier of the database on
* which the transaction that caused the quota overflow was run
* @param currentQuota the current quota for the origin.
+ * @param totalUsedQuota is the sum of all origins' quota.
* @param quotaUpdater The callback to run when a decision to allow or
* deny quota has been made. Don't forget to call this!
*/
@Override
public void onExceededDatabaseQuota(String url,
- String databaseIdentifier, long currentQuota,
+ String databaseIdentifier, long currentQuota, long totalUsedQuota,
WebStorage.QuotaUpdater quotaUpdater) {
- if(LOGV_ENABLED) {
- Log.v(LOGTAG,
- "BrowserActivity received onExceededDatabaseQuota for "
- + url +
- ":"
- + databaseIdentifier +
- "(current quota: "
- + currentQuota +
- ")");
- }
- mWebStorageQuotaUpdater = quotaUpdater;
- String DIALOG_PACKAGE = "com.android.browser";
- String DIALOG_CLASS = DIALOG_PACKAGE + ".PermissionDialog";
- Intent intent = new Intent();
- intent.setClassName(DIALOG_PACKAGE, DIALOG_CLASS);
- intent.putExtra(PermissionDialog.PARAM_ORIGIN, url);
- intent.putExtra(PermissionDialog.PARAM_QUOTA, currentQuota);
- startActivityForResult(intent, WEBSTORAGE_QUOTA_DIALOG);
+ mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
+ url, databaseIdentifier, currentQuota, totalUsedQuota,
+ quotaUpdater);
+ }
+
+ /**
+ * The Application Cache has exceeded its max size.
+ * @param spaceNeeded is the amount of disk space that would be needed
+ * in order for the last appcache operation to succeed.
+ * @param totalUsedQuota is the sum of all origins' quota.
+ * @param quotaUpdater A callback to inform the WebCore thread that a new
+ * app cache size is available. This callback must always be executed at
+ * some point to ensure that the sleeping WebCore thread is woken up.
+ */
+ @Override
+ public void onReachedMaxAppCacheSize(long spaceNeeded,
+ long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
+ mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
+ spaceNeeded, totalUsedQuota, quotaUpdater);
}
/* Adds a JavaScript error message to the system log.
@@ -4616,14 +4616,6 @@
}
}
break;
- case WEBSTORAGE_QUOTA_DIALOG:
- long currentQuota = 0;
- if (resultCode == RESULT_OK && intent != null) {
- currentQuota = intent.getLongExtra(
- PermissionDialog.PARAM_QUOTA, currentQuota);
- }
- mWebStorageQuotaUpdater.updateQuota(currentQuota);
- break;
default:
break;
}
@@ -5169,7 +5161,6 @@
final static int COMBO_PAGE = 1;
final static int DOWNLOAD_PAGE = 2;
final static int PREFERENCES_PAGE = 3;
- final static int WEBSTORAGE_QUOTA_DIALOG = 4;
// the frenquency of checking whether system memory is low
final static int CHECK_MEMORY_INTERVAL = 30000; // 30 seconds
diff --git a/src/com/android/browser/BrowserBackupAgent.java b/src/com/android/browser/BrowserBackupAgent.java
index 9e10370..c239b12 100644
--- a/src/com/android/browser/BrowserBackupAgent.java
+++ b/src/com/android/browser/BrowserBackupAgent.java
@@ -25,14 +25,16 @@
import android.os.ParcelFileDescriptor;
import android.provider.Browser;
import android.provider.Browser.BookmarkColumns;
+import android.util.Log;
+import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-
+import java.util.ArrayList;
import java.util.zip.CRC32;
/**
@@ -40,9 +42,16 @@
* stored is the set of bookmarks. It's okay if I/O exceptions are thrown
* out of the agent; the calling code handles it and the backup operation
* simply fails.
+ *
+ * @hide
*/
public class BrowserBackupAgent extends BackupAgent {
+ static final String TAG = "BrowserBookmarkAgent";
+ static final boolean DEBUG = true;
+
static final String BOOKMARK_KEY = "_bookmarks_";
+ /** this version num MUST be incremented if the flattened-file schema ever changes */
+ static final int BACKUP_AGENT_VERSION = 0;
/**
* In order to determine whether the bookmark set has changed since the
@@ -51,6 +60,7 @@
*
* 1. the size of the flattened bookmark file
* 2. the CRC32 of that file
+ * 3. the agent version number [relevant following an OTA]
*
* After we flatten the bookmarks file here in onBackup, we compare its
* metrics with the values from the saved state. If they match, it means
@@ -63,6 +73,7 @@
ParcelFileDescriptor newState) throws IOException {
long savedFileSize = -1;
long savedCrc = -1;
+ int savedVersion = -1;
// Extract the previous bookmark file size & CRC from the saved state
DataInputStream in = new DataInputStream(
@@ -70,59 +81,28 @@
try {
savedFileSize = in.readLong();
savedCrc = in.readLong();
+ savedVersion = in.readInt();
} catch (EOFException e) {
// It means we had no previous state; that's fine
}
- // TODO: BUILD THE FLATTENED BOOKMARK FILE FROM THE DB (into tmpfile)
- File tmpfile = getFilesDir().createTempFile("bkp", null);
- CRC32 crc = new CRC32();
+ // Build a flattened representation of the bookmarks table
+ File tmpfile = File.createTempFile("bkp", null, getCacheDir());
try {
- Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI,
- new String[] { BookmarkColumns.URL, BookmarkColumns.VISITS,
- BookmarkColumns.DATE, BookmarkColumns.CREATED,
- BookmarkColumns.TITLE },
- BookmarkColumns.BOOKMARK + " == 1 ", null, null);
- int count = cursor.getCount();
- FileOutputStream out = new FileOutputStream(tmpfile);
- for (int i = 0; i < count; i++) {
- StringBuilder sb = new StringBuilder();
- // URL
- sb.append("'");
- sb.append(cursor.getString(0));
- sb.append("','");
- // VISITS
- sb.append(cursor.getInt(1));
- sb.append("','");
- // DATE
- sb.append(cursor.getLong(2));
- sb.append("','");
- // CREATED
- sb.append(cursor.getLong(3));
- sb.append("','");
- // TITLE
- sb.append(cursor.getString(4));
- sb.append("'");
- out.write(sb.toString().getBytes());
+ FileOutputStream outfstream = new FileOutputStream(tmpfile);
+ long newCrc = buildBookmarkFile(outfstream);
+ outfstream.close();
- cursor.moveToNext();
- }
- out.close();
- /*
- android.util.Log.d("s", "backing up data" +
- getContentResolver().openFileDescriptor(Browser.BOOKMARKS_URI, "r").toString());
- */
- // NOTE: feed the flattened data through the crc engine on the fly
- // to save re-reading it later just to checksum it
-
- // Once the file is built, compare its metrics with the saved ones
- if ((crc.getValue() != savedCrc) || (tmpfile.length() != savedFileSize)) {
+ // Any changes since the last backup?
+ if ((savedVersion != BACKUP_AGENT_VERSION)
+ || (newCrc != savedCrc)
+ || (tmpfile.length() != savedFileSize)) {
// Different checksum or different size, so we need to back it up
copyFileToBackup(BOOKMARK_KEY, tmpfile, data);
}
- // Last, record the metrics of the bookmark file that we just stored
- writeBackupState(tmpfile.length(), crc.getValue(), newState);
+ // Record our backup state and we're done
+ writeBackupState(tmpfile.length(), newCrc, newState);
} finally {
// Make sure to tidy up when we're done
tmpfile.delete();
@@ -138,14 +118,60 @@
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
long crc = -1;
- File tmpfile = getFilesDir().createTempFile("rst", null);
+ File tmpfile = File.createTempFile("rst", null, getFilesDir());
try {
while (data.readNextHeader()) {
if (BOOKMARK_KEY.equals(data.getKey())) {
// Read the flattened bookmark data into a temp file
crc = copyBackupToFile(data, tmpfile, data.getDataSize());
- // TODO: READ THE FLAT BOOKMARKS FILE 'tmpfile' AND REBUILD THE DB TABLE
+ FileInputStream infstream = new FileInputStream(tmpfile);
+ DataInputStream in = new DataInputStream(infstream);
+
+ try {
+ int count = in.readInt();
+ ArrayList<Bookmark> bookmarks = new ArrayList<Bookmark>(count);
+
+ // Read all the bookmarks, then process later -- if we can't read
+ // all the data successfully, we don't touch the bookmarks table
+ for (int i = 0; i < count; i++) {
+ Bookmark mark = new Bookmark();
+ mark.url = in.readUTF();
+ mark.visits = in.readInt();
+ mark.date = in.readLong();
+ mark.created = in.readLong();
+ mark.title = in.readUTF();
+ bookmarks.add(mark);
+ }
+
+ // Okay, we have all the bookmarks -- now see if we need to add
+ // them to the browser's database
+ int N = bookmarks.size();
+ if (DEBUG) Log.v(TAG, "Restoring " + N + " bookmarks");
+ String[] urlCol = new String[] { BookmarkColumns.URL };
+ for (int i = 0; i < N; i++) {
+ Bookmark mark = bookmarks.get(i);
+
+ // Does this URL exist in the bookmark table?
+ Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI,
+ urlCol, BookmarkColumns.URL + " == '" + mark.url + "' AND " +
+ BookmarkColumns.BOOKMARK + " == 1 ", null, null);
+ // if not, insert it
+ if (cursor.getCount() <= 0) {
+ Log.v(TAG, "Did not see url: " + mark.url);
+ // Right now we do not reconstruct the db entry in its
+ // entirety; we just add a new bookmark with the same data
+ Bookmarks.addBookmark(null, getContentResolver(),
+ mark.url, mark.title, false);
+ } else {
+ Log.v(TAG, "Skipping extant url: " + mark.url);
+ }
+ cursor.close();
+ }
+ } catch (IOException ioe) {
+ Log.w(TAG, "Bad backup data; not restoring");
+ crc = -1;
+ }
}
// Last, write the state we just restored from so we can discern
@@ -158,10 +184,67 @@
}
}
+ class Bookmark {
+ public String url;
+ public int visits;
+ public long date;
+ public long created;
+ public String title;
+ }
/*
* Utility functions
*/
+ // Flatten the bookmarks table into the given file, calculating its CRC in the process
+ private long buildBookmarkFile(FileOutputStream outfstream) throws IOException {
+ CRC32 crc = new CRC32();
+ ByteArrayOutputStream bufstream = new ByteArrayOutputStream(512);
+ DataOutputStream bout = new DataOutputStream(bufstream);
+
+ Cursor cursor = getContentResolver().query(Browser.BOOKMARKS_URI,
+ new String[] { BookmarkColumns.URL, BookmarkColumns.VISITS,
+ BookmarkColumns.DATE, BookmarkColumns.CREATED,
+ BookmarkColumns.TITLE },
+ BookmarkColumns.BOOKMARK + " == 1 ", null, null);
+
+ // The first thing in the file is the row count...
+ int count = cursor.getCount();
+ if (DEBUG) Log.v(TAG, "Backing up " + count + " bookmarks");
+ bout.writeInt(count);
+ byte[] record = bufstream.toByteArray();
+ crc.update(record);
+ outfstream.write(record);
+
+ // ... followed by the data for each row
+ for (int i = 0; i < count; i++) {
+ cursor.moveToNext();
+
+ String url = cursor.getString(0);
+ int visits = cursor.getInt(1);
+ long date = cursor.getLong(2);
+ long created = cursor.getLong(3);
+ String title = cursor.getString(4);
+
+ // construct the flattened record in a byte array
+ bufstream.reset();
+ bout.writeUTF(url);
+ bout.writeInt(visits);
+ bout.writeLong(date);
+ bout.writeLong(created);
+ bout.writeUTF(title);
+
+ // Update the CRC and write the record to the temp file
+ record = bufstream.toByteArray();
+ crc.update(record);
+ outfstream.write(record);
+
+ if (DEBUG) Log.v(TAG, " wrote url " + url);
+ }
+
+ cursor.close();
+ return crc.getValue();
+ }
+
// Write the file to backup as a single record under the given key
private void copyFileToBackup(String key, File file, BackupDataOutput data)
throws IOException {
@@ -187,13 +270,16 @@
final int CHUNK = 8192;
byte[] buf = new byte[CHUNK];
CRC32 crc = new CRC32();
+ FileOutputStream out = new FileOutputStream(file);
while (toRead > 0) {
int numRead = data.readEntityData(buf, 0, CHUNK);
crc.update(buf, 0, numRead);
+ out.write(buf, 0, numRead);
toRead -= numRead;
}
+ out.close();
return crc.getValue();
}
@@ -204,5 +290,6 @@
new FileOutputStream(stateFile.getFileDescriptor()));
out.writeLong(fileSize);
out.writeLong(crc);
+ out.writeInt(BACKUP_AGENT_VERSION);
}
}
diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java
index d5e7049..335d8fe 100644
--- a/src/com/android/browser/BrowserHistoryPage.java
+++ b/src/com/android/browser/BrowserHistoryPage.java
@@ -62,6 +62,7 @@
private HistoryAdapter mAdapter;
private DateSorter mDateSorter;
private boolean mMaxTabsOpen;
+ private HistoryItem mContextHeader;
private final static String LOGTAG = "browser";
@@ -166,7 +167,7 @@
}
return super.onOptionsItemSelected(item);
}
-
+
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
@@ -181,12 +182,25 @@
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.historycontext, menu);
+ HistoryItem historyItem = (HistoryItem) i.targetView;
+
// Setup the header
- menu.setHeaderTitle(((HistoryItem)i.targetView).getUrl());
+ if (mContextHeader == null) {
+ mContextHeader = new HistoryItem(this);
+ } else if (mContextHeader.getParent() != null) {
+ ((ViewGroup) mContextHeader.getParent()).removeView(mContextHeader);
+ }
+ historyItem.copyTo(mContextHeader);
+ menu.setHeaderView(mContextHeader);
// Only show open in new tab if we have not maxed out available tabs
menu.findItem(R.id.new_window_context_menu_id).setVisible(!mMaxTabsOpen);
+ // For a bookmark, provide the option to remove it from bookmarks
+ if (historyItem.isBookmark()) {
+ MenuItem item = menu.findItem(R.id.save_to_bookmarks_menu_id);
+ item.setTitle(R.string.remove_from_bookmarks);
+ }
// decide whether to show the share link option
PackageManager pm = getPackageManager();
Intent send = new Intent(Intent.ACTION_SEND);
@@ -201,8 +215,9 @@
public boolean onContextItemSelected(MenuItem item) {
ExpandableListContextMenuInfo i =
(ExpandableListContextMenuInfo) item.getMenuInfo();
- String url = ((HistoryItem)i.targetView).getUrl();
- String title = ((HistoryItem)i.targetView).getName();
+ HistoryItem historyItem = (HistoryItem) i.targetView;
+ String url = historyItem.getUrl();
+ String title = historyItem.getName();
switch (item.getItemId()) {
case R.id.open_context_menu_id:
loadUrl(url, false);
@@ -211,7 +226,12 @@
loadUrl(url, true);
return true;
case R.id.save_to_bookmarks_menu_id:
- Browser.saveBookmark(this, title, url);
+ if (historyItem.isBookmark()) {
+ Bookmarks.removeFromBookmarks(this, getContentResolver(),
+ url);
+ } else {
+ Browser.saveBookmark(this, title, url);
+ }
return true;
case R.id.share_link_context_menu_id:
Browser.sendString(this, url);
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index 29c65e8..b74c9eb 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -19,6 +19,7 @@
import com.google.android.providers.GoogleSettings.Partner;
import android.app.SearchManager;
+import android.backup.BackupManager;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentUris;
@@ -54,6 +55,7 @@
public class BrowserProvider extends ContentProvider {
private SQLiteOpenHelper mOpenHelper;
+ private BackupManager mBackupManager;
private static final String sDatabaseName = "browser.db";
private static final String TAG = "BrowserProvider";
private static final String ORDER_BY = "visits DESC, date DESC";
@@ -264,6 +266,7 @@
public boolean onCreate() {
final Context context = getContext();
mOpenHelper = new DatabaseHelper(context);
+ mBackupManager = new BackupManager(context);
// we added "picasa web album" into default bookmarks for version 19.
// To avoid erasing the bookmark table, we added it explicitly for
// version 18 and 19 as in the other cases, we will erase the table.
@@ -736,6 +739,7 @@
@Override
public Uri insert(Uri url, ContentValues initialValues) {
+ boolean isBookmarkTable = false;
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int match = URI_MATCHER.match(url);
@@ -749,6 +753,7 @@
uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
rowID);
}
+ isBookmarkTable = true;
break;
}
@@ -771,6 +776,11 @@
throw new IllegalArgumentException("Unknown URL");
}
getContext().getContentResolver().notifyChange(uri, null);
+
+ // back up the new bookmark set if we just inserted one
+ if (isBookmarkTable) {
+ mBackupManager.dataChanged();
+ }
return uri;
}
@@ -783,7 +793,10 @@
throw new IllegalArgumentException("Unknown URL");
}
- if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) {
+ // need to know whether it's the bookmarks table for a couple fo reasons
+ boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID);
+
+ if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) {
StringBuilder sb = new StringBuilder();
if (where != null && where.length() > 0) {
sb.append("( ");
@@ -797,6 +810,11 @@
int count = db.delete(TABLE_NAMES[match % 10], where, whereArgs);
getContext().getContentResolver().notifyChange(url, null);
+
+ // back up the new bookmark set if we just deleted one
+ if (isBookmarkTable) {
+ mBackupManager.dataChanged();
+ }
return count;
}
@@ -810,7 +828,9 @@
throw new IllegalArgumentException("Unknown URL");
}
- if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) {
+ boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID);
+
+ if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) {
StringBuilder sb = new StringBuilder();
if (where != null && where.length() > 0) {
sb.append("( ");
@@ -824,6 +844,11 @@
int ret = db.update(TABLE_NAMES[match % 10], values, where, whereArgs);
getContext().getContentResolver().notifyChange(url, null);
+
+ // back up the new bookmark set if we just changed one
+ if (isBookmarkTable) {
+ mBackupManager.dataChanged();
+ }
return ret;
}
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index d2371cd..3074d2b 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -25,7 +25,6 @@
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
-import android.os.StatFs;
import android.webkit.CookieManager;
import android.webkit.WebView;
import android.webkit.WebViewDatabase;
@@ -79,6 +78,7 @@
private boolean appCacheEnabled = true;
private String appCachePath; // default value set in loadFromDb().
private long appCacheMaxSize = Long.MAX_VALUE;
+ private WebStorageSizeManager webStorageSizeManager;
private boolean domStorageEnabled = true;
private String jsFlags = "";
@@ -238,7 +238,9 @@
// Set the default value for the Application Caches path.
appCachePath = ctx.getDir("appcache", 0).getPath();
// Determine the maximum size of the application cache.
- appCacheMaxSize = getAppCacheMaxSize();
+ webStorageSizeManager = WebStorageSizeManager.getInstance(appCachePath,
+ ctx);
+ appCacheMaxSize = webStorageSizeManager.getAppCacheMaxSize();
// Set the default value for the Database path.
databasePath = ctx.getDir("databases", 0).getPath();
@@ -363,6 +365,10 @@
return jsFlags;
}
+ public WebStorageSizeManager getWebStorageSizeManager() {
+ return webStorageSizeManager;
+ }
+
public void setHomePage(Context context, String url) {
Editor ed = PreferenceManager.
getDefaultSharedPreferences(context).edit();
@@ -532,6 +538,8 @@
true);
// reset homeUrl
setHomePage(ctx, getFactoryResetHomeUrl(ctx));
+ // reset appcache max size
+ appCacheMaxSize = webStorageSizeManager.getAppCacheMaxSize();
}
private String getFactoryResetHomeUrl(Context context) {
@@ -543,35 +551,6 @@
return url;
}
- private long getAppCacheMaxSize() {
- StatFs dataPartition = new StatFs(appCachePath);
- long freeSpace = dataPartition.getAvailableBlocks()
- * dataPartition.getBlockSize();
- long fileSystemSize = dataPartition.getBlockCount()
- * dataPartition.getBlockSize();
- return calculateAppCacheMaxSize(fileSystemSize, freeSpace);
- }
-
- /*package*/ static long calculateAppCacheMaxSize(long fileSystemSizeBytes,
- long freeSpaceBytes) {
- if (fileSystemSizeBytes <= 0
- || freeSpaceBytes <= 0
- || freeSpaceBytes > fileSystemSizeBytes) {
- return 0;
- }
-
- long fileSystemSizeRatio =
- 4 << ((int) Math.floor(Math.log10(fileSystemSizeBytes / (1024 * 1024))));
- long maxSizeBytes = (long) Math.min(Math.floor(fileSystemSizeBytes / fileSystemSizeRatio),
- Math.floor(freeSpaceBytes / 4));
- // Round maxSizeBytes up to a multiple of 512KB (except when freeSpaceBytes < 1MB).
- long maxSizeStepBytes = 512 * 1024;
- if (freeSpaceBytes < maxSizeStepBytes * 2) {
- return 0;
- }
- return (maxSizeStepBytes * ((maxSizeBytes / maxSizeStepBytes) + 1));
- }
-
// Private constructor that does nothing.
private BrowserSettings() {
}
diff --git a/src/com/android/browser/HistoryItem.java b/src/com/android/browser/HistoryItem.java
index b37a3bd..e8f15b1 100644
--- a/src/com/android/browser/HistoryItem.java
+++ b/src/com/android/browser/HistoryItem.java
@@ -46,7 +46,7 @@
boolean isChecked) {
if (isChecked) {
Bookmarks.addBookmark(mContext,
- mContext.getContentResolver(), mUrl, getName());
+ mContext.getContentResolver(), mUrl, getName(), true);
} else {
Bookmarks.removeFromBookmarks(mContext,
mContext.getContentResolver(), mUrl);
@@ -55,7 +55,7 @@
};
}
- void copyTo(HistoryItem item) {
+ /* package */ void copyTo(HistoryItem item) {
item.mTextView.setText(mTextView.getText());
item.mUrlText.setText(mUrlText.getText());
item.setIsBookmark(mStar.isChecked());
@@ -63,10 +63,17 @@
}
/**
+ * Whether or not this item represents a bookmarked site
+ */
+ /* package */ boolean isBookmark() {
+ return mStar.isChecked();
+ }
+
+ /**
* Set whether or not this represents a bookmark, and make sure the star
* behaves appropriately.
*/
- void setIsBookmark(boolean isBookmark) {
+ /* package */ void setIsBookmark(boolean isBookmark) {
mStar.setOnCheckedChangeListener(null);
mStar.setChecked(isBookmark);
mStar.setOnCheckedChangeListener(mListener);
diff --git a/src/com/android/browser/PermissionDialog.java b/src/com/android/browser/PermissionDialog.java
deleted file mode 100644
index b71261a..0000000
--- a/src/com/android/browser/PermissionDialog.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2009 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 android.app.Activity;
-import android.app.Dialog;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.Window;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-/**
- * Permission dialog for HTML5
- * @hide
- */
-public class PermissionDialog extends Activity {
-
- private static final String TAG = "PermissionDialog";
- public static final String PARAM_ORIGIN = "origin";
- public static final String PARAM_QUOTA = "quota";
-
- private String mWebStorageOrigin;
- private long mWebStorageQuota = 0;
- private int mNotification = 0;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- getParameters();
- setupDialog();
- }
-
- private void getParameters() {
- Intent intent = getIntent();
- mWebStorageOrigin = intent.getStringExtra(PARAM_ORIGIN);
- mWebStorageQuota = intent.getLongExtra(PARAM_QUOTA, 0);
- }
-
- private void setupDialog() {
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.permission_dialog);
-
- setIcon(R.id.icon, android.R.drawable.ic_popup_disk_full);
- setText(R.id.dialog_title, R.string.query_storage_quota_prompt);
- setText(R.id.dialog_message, R.string.query_storage_quota_message);
- setCharSequence(R.id.origin, mWebStorageOrigin);
-
- setupButton(R.id.button_allow, R.string.permission_button_allow,
- new View.OnClickListener() {
- public void onClick(View v) { allow(); }
- });
- setupButton(R.id.button_alwaysdeny, R.string.permission_button_alwaysdeny,
- new View.OnClickListener() {
- public void onClick(View v) { alwaysdeny(); }
- });
- setupButton(R.id.button_deny, R.string.permission_button_deny,
- new View.OnClickListener() {
- public void onClick(View v) { deny(); }
- });
- }
-
- private void setText(int viewID, int stringID) {
- setCharSequence(viewID, getString(stringID));
- }
-
- private void setCharSequence(int viewID, CharSequence string) {
- View view = findViewById(viewID);
- if (view == null) {
- return;
- }
- view.setVisibility(View.VISIBLE);
- TextView textView = (TextView) view;
- textView.setText(string);
- }
-
- private void setIcon(int viewID, int imageID) {
- View view = findViewById(viewID);
- if (view == null) {
- return;
- }
- view.setVisibility(View.VISIBLE);
- ImageView icon = (ImageView) view;
- icon.setImageResource(imageID);
- }
-
- private void setupButton(int viewID, int stringID,
- View.OnClickListener listener) {
- View view = findViewById(viewID);
- if (view == null) {
- return;
- }
- setText(viewID, stringID);
- view.setOnClickListener(listener);
- }
-
- private void useNextQuota() {
- CharSequence[] values = getResources().getTextArray(
- R.array.webstorage_quota_entries_values);
- for (int i=0; i<values.length; i++) {
- long value = Long.parseLong(values[i].toString());
- value *= (1024 * 1024); // the string array is expressed in MB
- if (value > mWebStorageQuota) {
- mWebStorageQuota = value;
- break;
- }
- }
- }
-
- private void allow() {
- // If somehow there is no "next quota" in the ladder,
- // we'll add 1MB anyway.
- mWebStorageQuota += 1024*1024;
- useNextQuota();
- mNotification = R.string.webstorage_notification;
- closeDialog();
- }
-
- private void alwaysdeny() {
- // Setting the quota to 0 will prevent any new data to be
- // added, but the existing data will not be deleted.
- mWebStorageQuota = 0;
- mNotification = R.string.webstorage_notification;
- closeDialog();
- }
-
- private void deny() {
- closeDialog();
- }
-
- private void closeDialog() {
- Intent intent = new Intent();
- intent.putExtra(PARAM_QUOTA, mWebStorageQuota);
- setResult(RESULT_OK, intent);
- showToast();
- finish();
- }
-
- private void showToast() {
- if (mNotification != 0) {
- Toast toast = Toast.makeText(this, mNotification, Toast.LENGTH_LONG);
- toast.setGravity(Gravity.BOTTOM, 0, 0);
- toast.show();
- }
- }
-
- public boolean dispatchKeyEvent(KeyEvent event) {
- if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK)
- && (event.getAction() == KeyEvent.ACTION_DOWN)) {
- closeDialog();
- return true; // event consumed
- }
- return super.dispatchKeyEvent(event);
- }
-
-}
diff --git a/src/com/android/browser/WebStorageSizeManager.java b/src/com/android/browser/WebStorageSizeManager.java
new file mode 100644
index 0000000..e524f4c
--- /dev/null
+++ b/src/com/android/browser/WebStorageSizeManager.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2009 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 android.content.Context;
+import android.os.StatFs;
+import android.util.Log;
+import android.webkit.WebStorage;
+
+import java.io.File;
+import java.util.Set;
+
+
+/**
+ * Package level class for managing the disk size consumed by the WebDatabase
+ * and ApplicationCaches APIs (henceforth called Web storage).
+ *
+ * Currently, the situation on the WebKit side is as follows:
+ * - WebDatabase enforces a quota for each origin.
+ * - Session/LocalStorage do not enforce any disk limits.
+ * - ApplicationCaches enforces a maximum size for all origins.
+ *
+ * The WebStorageSizeManager maintains a global limit for the disk space
+ * consumed by the WebDatabase and ApplicationCaches. As soon as WebKit will
+ * have a limit for Session/LocalStorage, this class will manage the space used
+ * by those APIs as well.
+ *
+ * The global limit is computed as a function of the size of the partition where
+ * these APIs store their data (they must store it on the same partition for
+ * this to work) and the size of the available space on that partition.
+ * The global limit is not subject to user configuration but we do provide
+ * a debug-only setting.
+ * TODO(andreip): implement the debug setting.
+ *
+ * The size of the disk space used for Web storage is initially divided between
+ * WebDatabase and ApplicationCaches as follows:
+ *
+ * 75% for WebDatabase
+ * 25% for ApplicationCaches
+ *
+ * When an origin's database usage reaches its current quota, WebKit invokes
+ * the following callback function:
+ * - exceededDatabaseQuota(Frame* frame, const String& database_name);
+ * Note that the default quota for a new origin is 0, so we will receive the
+ * 'exceededDatabaseQuota' callback before a new origin gets the chance to
+ * create its first database.
+ *
+ * When the total ApplicationCaches usage reaches its current quota, WebKit
+ * invokes the following callback function:
+ * - void reachedMaxAppCacheSize(int64_t spaceNeeded);
+ *
+ * The WebStorageSizeManager's main job is to respond to the above two callbacks
+ * by inspecting the amount of unused Web storage quota (i.e. global limit -
+ * sum of all other origins' quota) and deciding if a quota increase for the
+ * out-of-space origin is allowed or not.
+ *
+ * The default quota for an origin is min(ORIGIN_DEFAULT_QUOTA, unused_quota).
+ * Quota increases are done in steps, where the increase step is
+ * min(QUOTA_INCREASE_STEP, unused_quota).
+ *
+ * This approach has the drawback that space may remain unused if there
+ * are many websites that store a lot less content than ORIGIN_DEFAULT_QUOTA.
+ * We deal with this by picking a value for ORIGIN_DEFAULT_QUOTA that is smaller
+ * than what the HTML 5 spec recommends. At the same time, picking a very small
+ * value for ORIGIN_DEFAULT_QUOTA may create performance problems since it's
+ * more likely for origins to have to rollback and restart transactions as a
+ * result of reaching the quota more often.
+ *
+ * When all the Web storage space is used, the WebStorageSizeManager creates
+ * a system notification that will guide the user to the WebSettings UI. There,
+ * the user can free some of the Web storage space by deleting all the data used
+ * by an origin.
+ * TODO(andreip): implement the notification.
+ */
+class WebStorageSizeManager {
+ // Logging flags.
+ private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED;
+ private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
+ private final static String LOGTAG = "browser";
+ // The default quota value for an origin.
+ private final static long ORIGIN_DEFAULT_QUOTA = 4 * 1024 * 1024; // 4MB
+ // The default value for quota increases.
+ private final static long QUOTA_INCREASE_STEP = 2 * 1024 * 1024; // 2MB
+ // The name of the application cache file. Keep in sync with
+ // WebCore/loader/appcache/ApplicationCacheStorage.cpp
+ private final static String APPCACHE_FILE = "ApplicationCache.db";
+ // The WebStorageSizeManager singleton.
+ private static WebStorageSizeManager mManager;
+ // The application context.
+ private Context mContext;
+ // The global Web storage limit.
+ private long mGlobalLimit;
+ // The maximum size of the application cache file.
+ private long mAppCacheMaxSize;
+
+ /**
+ * Factory method.
+ * @param path is a path on the partition where the app cache data is kept.
+ * @param ctx is the browser application context.
+ * @param storage is the WebStorage singleton.
+ *
+ */
+ public static WebStorageSizeManager getInstance(String appCachePath,
+ Context ctx) {
+ if (mManager == null) {
+ mManager = new WebStorageSizeManager(appCachePath, ctx);
+ }
+ return mManager;
+ }
+
+ /**
+ * Returns the maximum size of the application cache.
+ */
+ public long getAppCacheMaxSize() {
+ return mAppCacheMaxSize;
+ }
+
+ /**
+ * The origin has exceeded its database quota.
+ * @param url the URL that exceeded the quota
+ * @param databaseIdentifier the identifier of the database on
+ * which the transaction that caused the quota overflow was run
+ * @param currentQuota the current quota for the origin.
+ * @param totalUsedQuota is the sum of all origins' quota.
+ * @param quotaUpdater The callback to run when a decision to allow or
+ * deny quota has been made. Don't forget to call this!
+ */
+ public void onExceededDatabaseQuota(String url,
+ String databaseIdentifier, long currentQuota, long totalUsedQuota,
+ WebStorage.QuotaUpdater quotaUpdater) {
+ if(LOGV_ENABLED) {
+ Log.v(LOGTAG,
+ "Received onExceededDatabaseQuota for "
+ + url
+ + ":"
+ + databaseIdentifier
+ + "(current quota: "
+ + currentQuota
+ + ")");
+ }
+ long totalUnusedQuota = mGlobalLimit - totalUsedQuota - mAppCacheMaxSize;
+
+ if (totalUnusedQuota < QUOTA_INCREASE_STEP) {
+ // There definitely isn't any more space. Fire notifications
+ // and exit.
+ scheduleOutOfSpaceNotification();
+ quotaUpdater.updateQuota(currentQuota);
+ if(LOGV_ENABLED) {
+ Log.v(LOGTAG, "onExceededDatabaseQuota: out of space.");
+ }
+ return;
+ }
+ // We have enough space inside mGlobalLimit.
+ long newOriginQuota = currentQuota;
+ if (newOriginQuota == 0) {
+ // This is a new origin. It wants an initial quota. It is guaranteed
+ // to get at least ORIGIN_INCREASE_STEP bytes.
+ newOriginQuota =
+ Math.min(ORIGIN_DEFAULT_QUOTA, totalUnusedQuota);
+ } else {
+ // This is an origin we have seen before. It wants a quota
+ // increase.
+ newOriginQuota +=
+ Math.min(QUOTA_INCREASE_STEP, totalUnusedQuota);
+ }
+ quotaUpdater.updateQuota(newOriginQuota);
+
+ if(LOGV_ENABLED) {
+ Log.v(LOGTAG, "onExceededDatabaseQuota set new quota to "
+ + newOriginQuota);
+ }
+ }
+
+ /**
+ * The Application Cache has exceeded its max size.
+ * @param spaceNeeded is the amount of disk space that would be needed
+ * in order for the last appcache operation to succeed.
+ * @param totalUsedQuota is the sum of all origins' quota.
+ * @param quotaUpdater A callback to inform the WebCore thread that a new
+ * app cache size is available. This callback must always be executed at
+ * some point to ensure that the sleeping WebCore thread is woken up.
+ */
+ public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
+ WebStorage.QuotaUpdater quotaUpdater) {
+ if(LOGV_ENABLED) {
+ Log.v(LOGTAG, "Received onReachedMaxAppCacheSize with spaceNeeded "
+ + spaceNeeded + " bytes.");
+ }
+
+ long totalUnusedQuota = mGlobalLimit - totalUsedQuota - mAppCacheMaxSize;
+
+ if (totalUnusedQuota < spaceNeeded) {
+ // There definitely isn't any more space. Fire notifications
+ // and exit.
+ scheduleOutOfSpaceNotification();
+ quotaUpdater.updateQuota(0);
+ if(LOGV_ENABLED) {
+ Log.v(LOGTAG, "onReachedMaxAppCacheSize: out of space.");
+ }
+ return;
+ }
+ // There is enough space to accommodate spaceNeeded bytes.
+ mAppCacheMaxSize += spaceNeeded;
+ quotaUpdater.updateQuota(mAppCacheMaxSize);
+
+ if(LOGV_ENABLED) {
+ Log.v(LOGTAG, "onReachedMaxAppCacheSize set new max size to "
+ + mAppCacheMaxSize);
+ }
+ }
+
+ // Computes the global limit as a function of the size of the data
+ // partition and the amount of free space on that partition.
+ private long getGlobalLimit(String path) {
+ StatFs dataPartition = new StatFs(path);
+ long freeSpace = dataPartition.getAvailableBlocks()
+ * dataPartition.getBlockSize();
+ long fileSystemSize = dataPartition.getBlockCount()
+ * dataPartition.getBlockSize();
+ return calculateGlobalLimit(fileSystemSize, freeSpace);
+ }
+
+ // Returns the current size (in bytes) of the application cache file.
+ private long getCurrentAppCacheSize(String path) {
+ File file = new File(path + File.separator + APPCACHE_FILE);
+ return file.length();
+ }
+
+ /*package*/ static long calculateGlobalLimit(long fileSystemSizeBytes,
+ long freeSpaceBytes) {
+ if (fileSystemSizeBytes <= 0
+ || freeSpaceBytes <= 0
+ || freeSpaceBytes > fileSystemSizeBytes) {
+ return 0;
+ }
+
+ long fileSystemSizeRatio =
+ 2 << ((int) Math.floor(Math.log10(
+ fileSystemSizeBytes / (1024 * 1024))));
+ long maxSizeBytes = (long) Math.min(Math.floor(
+ fileSystemSizeBytes / fileSystemSizeRatio),
+ Math.floor(freeSpaceBytes / 2));
+ // Round maxSizeBytes up to a multiple of 1024KB (but only if
+ // maxSizeBytes > 1MB).
+ long maxSizeStepBytes = 1024 * 1024;
+ if (maxSizeBytes < maxSizeStepBytes) {
+ return 0;
+ }
+ long roundingExtra = maxSizeBytes % maxSizeStepBytes == 0 ? 0 : 1;
+ return (maxSizeStepBytes
+ * ((maxSizeBytes / maxSizeStepBytes) + roundingExtra));
+ }
+
+ // Schedules a system notification that takes the user to the WebSettings
+ // activity when clicked.
+ private void scheduleOutOfSpaceNotification() {
+ // TODO(andreip): implement.
+ }
+ // Private ctor.
+ private WebStorageSizeManager(String appCachePath, Context ctx) {
+ mContext = ctx;
+ mGlobalLimit = getGlobalLimit(appCachePath);
+ // The initial max size of the app cache is either 25% of the global
+ // limit or the current size of the app cache file, whichever is bigger.
+ mAppCacheMaxSize = Math.max(mGlobalLimit / 4,
+ getCurrentAppCacheSize(appCachePath));
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/browser/WebsiteSettingsActivity.java b/src/com/android/browser/WebsiteSettingsActivity.java
index c9042cd..f91879f 100644
--- a/src/com/android/browser/WebsiteSettingsActivity.java
+++ b/src/com/android/browser/WebsiteSettingsActivity.java
@@ -40,6 +40,7 @@
import android.widget.TextView;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
@@ -59,10 +60,10 @@
private String mTitle;
private Bitmap mIcon;
- public Site(String origin, String title, Bitmap icon) {
+ public Site(String origin) {
mOrigin = origin;
- mTitle = title;
- mIcon = icon;
+ mTitle = null;
+ mIcon = null;
}
public String getOrigin() {
@@ -73,10 +74,6 @@
mTitle = title;
}
- public String getTitle() {
- return mTitle;
- }
-
public void setIcon(Bitmap icon) {
mIcon = icon;
}
@@ -84,6 +81,19 @@
public Bitmap getIcon() {
return mIcon;
}
+
+ public String getPrettyOrigin() {
+ return mTitle == null ? null : hideHttp(mOrigin);
+ }
+
+ public String getPrettyTitle() {
+ return mTitle == null ? hideHttp(mOrigin) : mTitle;
+ }
+
+ private String hideHttp(String str) {
+ Uri uri = Uri.parse(str);
+ return "http".equals(uri.getScheme()) ? str.substring(7) : str;
+ }
}
class SiteAdapter extends ArrayAdapter<Site>
@@ -107,19 +117,36 @@
clear();
// Get the list of origins we want to display
- HashMap<String, Site> uris = new HashMap<String, Site>();
Set origins = WebStorage.getInstance().getOrigins();
+ Set sites = new HashSet<Site>();
if (origins != null) {
Iterator<String> iter = origins.iterator();
while (iter.hasNext()) {
String origin = iter.next();
- Site site = new Site(origin, origin, null);
- uris.put(Uri.parse(origin).getHost(), site);
+ Site site = new Site(origin);
+ sites.add(site);
}
}
- // Check the bookmark db -- if one of our origin matches,
- // we set its title and favicon
+ // Create a map from host to origin. This is used to add metadata
+ // (title, icon) for this origin from the bookmarks DB.
+ HashMap hosts = new HashMap<String, Set<Site> >();
+ Iterator<Site> sitesIter = sites.iterator();
+ while (sitesIter.hasNext()) {
+ Site site = sitesIter.next();
+ String host = Uri.parse(site.getOrigin()).getHost();
+ Set hostSites = null;
+ if (hosts.containsKey(host)) {
+ hostSites = (Set) hosts.get(host);
+ } else {
+ hostSites = new HashSet<Site>();
+ hosts.put(host, hostSites);
+ }
+ hostSites.add(site);
+ }
+
+ // Check the bookmark DB. If we have data for a host used by any of
+ // our origins, use it to set their title and favicon
Cursor c = getContext().getContentResolver().query(Browser.BOOKMARKS_URI,
new String[] { Browser.BookmarkColumns.URL, Browser.BookmarkColumns.TITLE,
Browser.BookmarkColumns.FAVICON }, "bookmark = 1", null, null);
@@ -131,13 +158,18 @@
do {
String url = c.getString(urlIndex);
String host = Uri.parse(url).getHost();
- if (uris.containsKey(host)) {
+ if (hosts.containsKey(host)) {
String title = c.getString(titleIndex);
- Site site = uris.get(host);
- site.setTitle(title);
+ Bitmap bmp = null;
byte[] data = c.getBlob(faviconIndex);
if (data != null) {
- Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
+ bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
+ }
+ Set matchingSites = (Set) hosts.get(host);
+ sitesIter = matchingSites.iterator();
+ while (sitesIter.hasNext()) {
+ Site site = sitesIter.next();
+ site.setTitle(title);
if (bmp != null) {
site.setIcon(bmp);
}
@@ -147,11 +179,9 @@
}
// We can now simply populate our array with Site instances
- Set keys = uris.keySet();
- Iterator iter = keys.iterator();
- while (iter.hasNext()) {
- String origin = (String) iter.next();
- Site site = uris.get(origin);
+ sitesIter = sites.iterator();
+ while (sitesIter.hasNext()) {
+ Site site = sitesIter.next();
add(site);
}
@@ -211,8 +241,8 @@
if (mCurrentSite == null) {
Site site = getItem(position);
- title.setText(site.getTitle());
- subtitle.setText(site.getOrigin());
+ title.setText(site.getPrettyTitle());
+ subtitle.setText(site.getPrettyOrigin());
icon.setVisibility(View.VISIBLE);
Bitmap bmp = site.getIcon();
if (bmp == null) {
diff --git a/tests/src/com/android/browser/BrowserSettingsUnitTests.java b/tests/src/com/android/browser/BrowserSettingsUnitTests.java
deleted file mode 100644
index fdaa5c8..0000000
--- a/tests/src/com/android/browser/BrowserSettingsUnitTests.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2008 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 android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-
-/**
- * This is a series of unit tests for the BrowserSettings class.
- *
- */
-@MediumTest
-public class BrowserSettingsUnitTests extends AndroidTestCase {
-
- /**
- * Test the application caches max size calculator.
- */
- public void testCalculateAppCacheMaxSize() {
- long fileSystemSize = 78643200; // 75 MB
- long freeSpaceSize = 25165824; // 24 MB
- long maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
- assertEquals(6815744, maxSize); // 6.5MB
-
- fileSystemSize = 78643200; // 75 MB
- freeSpaceSize = 60 * 1024 * 1024; // 60MB
- maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
- assertEquals(9961472, maxSize); // 9.5MB
-
- fileSystemSize = 8589934592L; // 8 GB
- freeSpaceSize = 4294967296L; // 4 GB
- maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
- assertEquals(268959744L, maxSize); // 256.5 MB
-
- fileSystemSize = -14;
- freeSpaceSize = 21;
- maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
- assertEquals(0, maxSize);
-
- fileSystemSize = 100;
- freeSpaceSize = 101;
- maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
- assertEquals(0, maxSize);
-
- fileSystemSize = 3774873; // ~4.2 MB
- freeSpaceSize = 2560000; // ~2.4 MB
- maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
- assertEquals(1048576, maxSize); // 1 MB
-
- fileSystemSize = 4404019; // ~4.2 MB
- freeSpaceSize = 3774873; // ~3.6 MB
- maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
- assertEquals(1048576, maxSize); // 1 MB
-
- fileSystemSize = 4404019; // ~4.2 MB
- freeSpaceSize = 4404019; // ~4.2 MB
- maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
- assertEquals(1572864, maxSize); // 1.5 MB
-
- fileSystemSize = 1048576; // 1 MB
- freeSpaceSize = 1048575; // 1 MB - 1 byte
- maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
- assertEquals(0, maxSize);
-
- fileSystemSize = 1048576; // 1 MB
- freeSpaceSize = 1048576; // 1 MB
- maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
- assertEquals(524288, maxSize); // 512KB
-
- fileSystemSize = 3774873; // ~3.6 MB
- freeSpaceSize = 2097151; // 2 MB - 1 byte
- maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
- assertEquals(524288, maxSize); // 512KB
- }
-}
diff --git a/tests/src/com/android/browser/WebStorageSizeManagerUnitTests.java b/tests/src/com/android/browser/WebStorageSizeManagerUnitTests.java
new file mode 100644
index 0000000..4ba758d
--- /dev/null
+++ b/tests/src/com/android/browser/WebStorageSizeManagerUnitTests.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2008 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 android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/**
+ * This is a series of unit tests for the WebStorageSizeManager class.
+ *
+ */
+@MediumTest
+public class WebStorageSizeManagerUnitTests extends AndroidTestCase {
+
+ /**
+ * Test the application caches max size calculator.
+ */
+ public void testCalculateGlobalLimit() {
+ long fileSystemSize = 78643200; // 75 MB
+ long freeSpaceSize = 25165824; // 24 MB
+ long maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize);
+ assertEquals(12582912, maxSize); // 12MB
+
+ fileSystemSize = 78643200; // 75 MB
+ freeSpaceSize = 60 * 1024 * 1024; // 60MB
+ maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize);
+ assertEquals(19922944, maxSize); // 19MB
+
+ fileSystemSize = 8589934592L; // 8 GB
+ freeSpaceSize = 4294967296L; // 4 GB
+ maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize);
+ assertEquals(536870912L, maxSize); // 512 MB
+
+ fileSystemSize = -14;
+ freeSpaceSize = 21;
+ maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize);
+ assertEquals(0, maxSize);
+
+ fileSystemSize = 100;
+ freeSpaceSize = 101;
+ maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize);
+ assertEquals(0, maxSize);
+
+ fileSystemSize = 3774873; // ~4.2 MB
+ freeSpaceSize = 2560000; // ~2.4 MB
+ maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize);
+ assertEquals(2097152, maxSize); // 2 MB
+
+ fileSystemSize = 4404019; // ~4.2 MB
+ freeSpaceSize = 3774873; // ~3.6 MB
+ maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize);
+ assertEquals(2097152, maxSize); // 2 MB
+
+ fileSystemSize = 4404019; // ~4.2 MB
+ freeSpaceSize = 4404019; // ~4.2 MB
+ maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize);
+ assertEquals(3145728, maxSize); // 3 MB
+
+ fileSystemSize = 1048576; // 1 MB
+ freeSpaceSize = 1048575; // 1 MB - 1 byte
+ maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize);
+ assertEquals(0, maxSize);
+
+ fileSystemSize = 3774873; // ~3.6 MB
+ freeSpaceSize = 2097151; // 2 MB - 1 byte
+ maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize);
+ assertEquals(0, maxSize);
+
+ fileSystemSize = 3774873; // ~3.6 MB
+ freeSpaceSize = 2097151; // 2 MB
+ maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize);
+ assertEquals(0, maxSize);
+ }
+}