Add UI support in the browser for HTML5 databases
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index af65072..c24577c 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -163,6 +163,8 @@
private SensorManager mSensorManager = null;
+ private WebStorage.QuotaUpdater mWebStorageQuotaUpdater = null;
+
/* Whitelisted webpages
private static HashSet<String> sWhiteList;
@@ -3456,9 +3458,14 @@
+ currentQuota +
")");
}
- // Give the origin an extra megabyte to play with.
- // TODO: This should show a prompt to the user, really :)
- quotaUpdater.updateQuota(currentQuota + 1024 * 1024);
+ 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);
}
};
@@ -4165,6 +4172,14 @@
}
}
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;
}
@@ -4721,9 +4736,10 @@
private BroadcastReceiver mNetworkStateIntentReceiver;
// activity requestCode
- final static int COMBO_PAGE = 1;
- final static int DOWNLOAD_PAGE = 2;
- final static int PREFERENCES_PAGE = 3;
+ 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/BrowserPreferencesPage.java b/src/com/android/browser/BrowserPreferencesPage.java
index 5d6795b..e093ced 100644
--- a/src/com/android/browser/BrowserPreferencesPage.java
+++ b/src/com/android/browser/BrowserPreferencesPage.java
@@ -17,12 +17,19 @@
package com.android.browser;
import java.util.List;
+import java.util.Vector;
+import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.preference.EditTextPreference;
+import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import android.webkit.Plugin;
+import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.Plugin;
@@ -30,6 +37,8 @@
implements Preference.OnPreferenceChangeListener,
Preference.OnPreferenceClickListener {
+ String TAG = "BrowserPreferencesPage";
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -60,6 +69,23 @@
e = findPreference(BrowserSettings.PREF_GEARS_SETTINGS);
e.setOnPreferenceClickListener(this);
+
+ PreferenceScreen s = (PreferenceScreen)
+ findPreference(BrowserSettings.PREF_WEBSTORAGE_SETTINGS);
+
+ Vector origins = WebStorage.getInstance().getOrigins();
+ if (origins != null) {
+ for (int i = 0; i < origins.size(); i++) {
+ OriginSettings origin =
+ new OriginSettings(this, (String) origins.get(i));
+ PreferenceScreen screen =
+ getPreferenceManager().createPreferenceScreen(this);
+ origin.setScreen(screen);
+ origin.setRootScreen(s);
+ origin.setup();
+ s.addPreference(screen);
+ }
+ }
}
@Override
diff --git a/src/com/android/browser/BrowserQuotaPreference.java b/src/com/android/browser/BrowserQuotaPreference.java
new file mode 100644
index 0000000..653c085
--- /dev/null
+++ b/src/com/android/browser/BrowserQuotaPreference.java
@@ -0,0 +1,81 @@
+/*
+ * 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.preference.ListPreference;
+import android.content.Context;
+import android.preference.PreferenceScreen;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.webkit.WebStorage;
+
+/**
+ * Utility class to display and manage the choosen quota
+ * for an origin (HTML5 WebStorage feature)
+ */
+class BrowserQuotaPreference extends ListPreference {
+
+ private String TAG = "BrowserQuotaPreference";
+ private OriginSettings mOrigin = null;
+ private static long sOneMB = 1024 * 1024;
+
+ // This is the constructor called by the inflater
+ public BrowserQuotaPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public BrowserQuotaPreference(Context context, OriginSettings origin) {
+ super(context);
+ mOrigin = origin;
+ }
+
+ public void setCurrentIndex () {
+ CharSequence[] values = getEntryValues();
+ long quota = 0;
+ if (mOrigin != null) {
+ quota = mOrigin.getQuota();
+ }
+ for (int i = 0; i < values.length; i++) {
+ long value = Long.parseLong(values[i].toString());
+ value *= sOneMB; // the string array is expressed in MB
+ if (value >= quota) {
+ setValueIndex(i);
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected View onCreateDialogView() {
+ setCurrentIndex();
+ return super.onCreateDialogView();
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+ if (mOrigin == null) {
+ return;
+ }
+ if (positiveResult) {
+ long quota = Long.parseLong(getValue());
+ quota *= sOneMB; // getValue() is in MB
+ mOrigin.setQuota(quota);
+ }
+ }
+}
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 95ed17b..9be22ea 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -25,6 +25,9 @@
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.SystemProperties;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.util.Log;
import android.view.WindowManager;
import android.webkit.CacheManager;
import android.webkit.CookieManager;
@@ -32,6 +35,7 @@
import android.webkit.WebViewDatabase;
import android.webkit.WebIconDatabase;
import android.webkit.WebSettings;
+import android.webkit.WebStorage;
import android.preference.PreferenceManager;
import android.provider.Browser;
@@ -76,10 +80,13 @@
private boolean showDebugSettings = false;
private String databasePath; // default value set in loadFromDb()
private boolean databaseEnabled = true;
+ private long webStorageDefaultQuota = 5 * 1024 * 1024;
// The Browser always enables Application Caches.
private boolean appCacheEnabled = true;
private String appCachePath; // default value set in loadFromDb().
+ private final static String TAG = "BrowserSettings";
+
// Development settings
public WebSettings.LayoutAlgorithm layoutAlgorithm =
WebSettings.LayoutAlgorithm.NARROW_COLUMNS;
@@ -108,10 +115,19 @@
"privacy_clear_form_data";
public final static String PREF_CLEAR_PASSWORDS =
"privacy_clear_passwords";
+ public final static String PREF_CLEAR_DATABASES =
+ "webstorage_clear_databases";
+ public final static String PREF_CLEAR_ALL_DATA =
+ "webstorage_clear_all_data";
+ public final static String PREF_MANAGE_QUOTA =
+ "webstorage_manage_quota";
+ public final static String PREF_DEFAULT_QUOTA =
+ "webstorage_default_quota";
public final static String PREF_EXTRAS_RESET_DEFAULTS =
"reset_default_preferences";
public final static String PREF_DEBUG_SETTINGS = "debug_menu";
public final static String PREF_GEARS_SETTINGS = "gears_settings";
+ public final static String PREF_WEBSTORAGE_SETTINGS = "webstorage_manage_databases";
public final static String PREF_TEXT_SIZE = "text_size";
public final static String PREF_DEFAULT_TEXT_ENCODING =
"default_text_encoding";
@@ -190,6 +206,7 @@
s.setDatabasePath(b.databasePath);
s.setDatabaseEnabled(b.databaseEnabled);
+ s.setWebStorageDefaultQuota(b.webStorageDefaultQuota);
// Turn on Application Caches.
s.setAppCachePath(b.appCachePath);
@@ -242,6 +259,8 @@
pluginsPath = p.getString("plugins_path", pluginsPath);
databasePath = p.getString("database_path", databasePath);
databaseEnabled = p.getBoolean("enable_database", databaseEnabled);
+ webStorageDefaultQuota = Long.parseLong(p.getString(PREF_DEFAULT_QUOTA,
+ String.valueOf(webStorageDefaultQuota)));
appCacheEnabled = p.getBoolean("enable_appcache",
appCacheEnabled);
appCachePath = p.getString("appcache_path", appCachePath);
@@ -466,6 +485,15 @@
db.clearHttpAuthUsernamePassword();
}
+ /*package*/ void clearDatabases(Context context) {
+ WebStorage.getInstance().deleteAllDatabases();
+ // Remove all listed databases from the preferences
+ PreferenceActivity activity = (PreferenceActivity) context;
+ PreferenceScreen screen = (PreferenceScreen)
+ activity.findPreference(BrowserSettings.PREF_WEBSTORAGE_SETTINGS);
+ screen.removeAll();
+ }
+
/*package*/ void resetDefaultPreferences(Context ctx) {
SharedPreferences p =
PreferenceManager.getDefaultSharedPreferences(ctx);
diff --git a/src/com/android/browser/BrowserYesNoPreference.java b/src/com/android/browser/BrowserYesNoPreference.java
index 65cde71..11a577b 100644
--- a/src/com/android/browser/BrowserYesNoPreference.java
+++ b/src/com/android/browser/BrowserYesNoPreference.java
@@ -23,11 +23,21 @@
class BrowserYesNoPreference extends YesNoPreference {
+ // This is used for the HTML5 pref UI, where we construct
+ // BrowserYesNoPreference objects on the fly and where we need
+ // to save the corresponding origin.
+ OriginSettings mOrigin = null;
+
// This is the constructor called by the inflater
public BrowserYesNoPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
+ public BrowserYesNoPreference(Context context, OriginSettings origin) {
+ super(context);
+ mOrigin = origin;
+ }
+
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
@@ -46,6 +56,12 @@
BrowserSettings.getInstance().clearFormData(context);
} else if (BrowserSettings.PREF_CLEAR_PASSWORDS.equals(getKey())) {
BrowserSettings.getInstance().clearPasswords(context);
+ } else if (BrowserSettings.PREF_CLEAR_DATABASES.equals(getKey())) {
+ BrowserSettings.getInstance().clearDatabases(context);
+ } else if (BrowserSettings.PREF_CLEAR_ALL_DATA.equals(getKey())) {
+ if (mOrigin != null) {
+ mOrigin.delete();
+ }
} else if (BrowserSettings.PREF_EXTRAS_RESET_DEFAULTS.equals(
getKey())) {
BrowserSettings.getInstance().resetDefaultPreferences(context);
diff --git a/src/com/android/browser/OriginSettings.java b/src/com/android/browser/OriginSettings.java
new file mode 100644
index 0000000..3c7273d
--- /dev/null
+++ b/src/com/android/browser/OriginSettings.java
@@ -0,0 +1,134 @@
+/*
+ * 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.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import android.webkit.WebStorage;
+
+/**
+ * Manage the settings for an origin.
+ * We use it to keep track of the HTML5 settings, i.e. database (webstorage).
+ */
+class OriginSettings {
+
+ private String TAG = "OriginSettings";
+ private String mOrigin = null;
+ private long mQuota = 0;
+ private long mUsage = 0;
+ private PreferenceScreen mInfoScreen;
+ private PreferenceScreen mRootScreen;
+ private PreferenceActivity mActivity;
+
+ private static String sMBUsed = null;
+ private static String sNoQuotaLeft = null;
+ private static String sMBLeft = null;
+
+ public OriginSettings(PreferenceActivity activity, String origin) {
+ mOrigin = origin;
+ mUsage = WebStorage.getInstance().getUsageForOrigin(mOrigin);
+ mQuota = WebStorage.getInstance().getQuotaForOrigin(mOrigin);
+ mActivity = activity;
+ if (sMBUsed == null) {
+ sMBUsed = mActivity.getString(
+ R.string.webstorage_origin_summary_mb_used);
+ sNoQuotaLeft = mActivity.getString(
+ R.string.webstorage_origin_summary_no_quota_left);
+ sMBLeft = mActivity.getString(
+ R.string.webstorage_origin_summary_mb_left);
+ }
+ }
+
+ public String getOrigin() {
+ return mOrigin;
+ }
+
+ public long getQuota() {
+ return mQuota;
+ }
+
+ public void setScreen(PreferenceScreen screen) {
+ mInfoScreen = screen;
+ }
+
+ public void setRootScreen(PreferenceScreen screen) {
+ mRootScreen = screen;
+ }
+
+ private String sizeValueToString(long value) {
+ float mb = (float) value / (1024.0F * 1024.0F);
+ int val = (int) (mb * 10);
+ float ret = (float) (val / 10.0F);
+ if (ret <= 0) {
+ return "0";
+ }
+ return String.valueOf(ret);
+ }
+
+ public void updateSummary() {
+ String summary = sizeValueToString(mUsage) + " " + sMBUsed;
+ if ((mQuota <= 0) || ((mQuota - mUsage) <= 0)) {
+ summary += ", " + sNoQuotaLeft;
+ } else {
+ summary += " (" + sizeValueToString(mQuota - mUsage);
+ summary += " " + sMBLeft + ")";
+ }
+ mInfoScreen.setSummary(summary);
+ mActivity.onContentChanged();
+ }
+
+ public void setup() {
+ mInfoScreen.setTitle(mOrigin);
+ mInfoScreen.setKey(mOrigin);
+ updateSummary();
+
+ BrowserQuotaPreference manageSite = new BrowserQuotaPreference(mActivity, this);
+ BrowserYesNoPreference clearAllData = new BrowserYesNoPreference(mActivity, this);
+
+ manageSite.setTitle(R.string.webstorage_manage_quota_title);
+ manageSite.setSummary(R.string.webstorage_manage_quota_summary);
+ manageSite.setKey(BrowserSettings.PREF_MANAGE_QUOTA);
+ manageSite.setEntries(R.array.webstorage_quota_entries);
+ manageSite.setEntryValues(R.array.webstorage_quota_entries_values);
+
+ clearAllData.setTitle(R.string.webstorage_clear_data_title);
+ clearAllData.setSummary(R.string.webstorage_clear_data_summary);
+ clearAllData.setKey(BrowserSettings.PREF_CLEAR_ALL_DATA);
+ clearAllData.setDialogTitle(R.string.webstorage_clear_data_dialog_title);
+ clearAllData.setDialogMessage(R.string.webstorage_clear_data_dialog_message);
+ clearAllData.setDialogIcon(android.R.drawable.ic_dialog_alert);
+
+ mInfoScreen.addPreference(manageSite);
+ mInfoScreen.addPreference(clearAllData);
+ }
+
+ public void setQuota(long quota) {
+ mQuota = quota;
+ WebStorage.getInstance().setQuotaForOrigin(mOrigin, mQuota);
+ mInfoScreen.getDialog().dismiss();
+ updateSummary();
+ }
+
+ public void delete() {
+ WebStorage.getInstance().deleteOrigin(mOrigin);
+ mInfoScreen.removeAll();
+ mRootScreen.removePreference(mInfoScreen);
+ mInfoScreen.getDialog().dismiss();
+ }
+}
diff --git a/src/com/android/browser/PermissionDialog.java b/src/com/android/browser/PermissionDialog.java
new file mode 100644
index 0000000..b71261a
--- /dev/null
+++ b/src/com/android/browser/PermissionDialog.java
@@ -0,0 +1,175 @@
+/*
+ * 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);
+ }
+
+}