summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/browser/AddBookmarkPage.java74
-rw-r--r--src/com/android/browser/AddNewBookmark.java12
-rw-r--r--src/com/android/browser/Bookmarks.java202
-rw-r--r--src/com/android/browser/BrowserActivity.java564
-rw-r--r--src/com/android/browser/BrowserBackupAgent.java295
-rw-r--r--src/com/android/browser/BrowserBookmarksAdapter.java214
-rw-r--r--src/com/android/browser/BrowserBookmarksPage.java116
-rw-r--r--src/com/android/browser/BrowserDownloadAdapter.java16
-rw-r--r--src/com/android/browser/BrowserDownloadPage.java28
-rw-r--r--src/com/android/browser/BrowserHistoryPage.java62
-rw-r--r--src/com/android/browser/BrowserHomepagePreference.java4
-rw-r--r--src/com/android/browser/BrowserPreferencesPage.java39
-rw-r--r--src/com/android/browser/BrowserProvider.java115
-rw-r--r--src/com/android/browser/BrowserSettings.java142
-rw-r--r--src/com/android/browser/BrowserYesNoPreference.java4
-rw-r--r--src/com/android/browser/ErrorConsoleView.java339
-rw-r--r--src/com/android/browser/FetchUrlMimeType.java14
-rw-r--r--src/com/android/browser/FindDialog.java23
-rw-r--r--src/com/android/browser/HistoryItem.java72
-rw-r--r--src/com/android/browser/MostVisitedActivity.java10
-rw-r--r--src/com/android/browser/TabControl.java153
-rw-r--r--src/com/android/browser/TitleBar.java170
-rw-r--r--src/com/android/browser/WebStorageSizeManager.java382
-rw-r--r--src/com/android/browser/WebsiteSettingsActivity.java430
24 files changed, 2966 insertions, 514 deletions
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index cf3fe70..191659a 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -18,18 +18,14 @@ package com.android.browser;
import android.app.Activity;
import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
import android.content.Intent;
import android.content.res.Resources;
-import android.database.Cursor;
import android.net.ParseException;
import android.net.WebAddress;
import android.os.Bundle;
import android.provider.Browser;
import android.view.View;
import android.view.Window;
-import android.webkit.WebIconDatabase;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
@@ -46,11 +42,6 @@ public class AddBookmarkPage extends Activity {
private View mCancelButton;
private boolean mEditingExisting;
private Bundle mMap;
-
- private static final String[] mProjection =
- { "_id", "url", "bookmark", "created", "title", "visits" };
- private static final String WHERE_CLAUSE = "url = ?";
- private final String[] SELECTION_ARGS = new String[1];
private View.OnClickListener mSaveBookmark = new View.OnClickListener() {
public void onClick(View v) {
@@ -151,70 +142,7 @@ public class AddBookmarkPage extends Activity {
setResult(RESULT_OK, (new Intent()).setAction(
getIntent().toString()).putExtras(mMap));
} else {
- // Want to append to the beginning of the list
- long creationTime = new Date().getTime();
- SELECTION_ARGS[0] = url;
- ContentResolver cr = getContentResolver();
- Cursor c = cr.query(Browser.BOOKMARKS_URI,
- mProjection,
- WHERE_CLAUSE,
- SELECTION_ARGS,
- null);
- ContentValues map = new ContentValues();
- if (c.moveToFirst() && c.getInt(c.getColumnIndexOrThrow(
- Browser.BookmarkColumns.BOOKMARK)) == 0) {
- // This means we have been to this site but not bookmarked
- // it, so convert the history item to a bookmark
- map.put(Browser.BookmarkColumns.CREATED, creationTime);
- map.put(Browser.BookmarkColumns.TITLE, title);
- map.put(Browser.BookmarkColumns.BOOKMARK, 1);
- cr.update(Browser.BOOKMARKS_URI, map,
- "_id = " + c.getInt(0), null);
- } else {
- int count = c.getCount();
- boolean matchedTitle = false;
- for (int i = 0; i < count; i++) {
- // One or more bookmarks already exist for this site.
- // Check the names of each
- c.moveToPosition(i);
- if (c.getString(c.getColumnIndexOrThrow(
- Browser.BookmarkColumns.TITLE)).equals(title)) {
- // The old bookmark has the same name.
- // Update its creation time.
- map.put(Browser.BookmarkColumns.CREATED,
- creationTime);
- cr.update(Browser.BOOKMARKS_URI, map,
- "_id = " + c.getInt(0), null);
- matchedTitle = true;
- }
- }
- if (!matchedTitle) {
- // Adding a bookmark for a site the user has visited,
- // or a new bookmark (with a different name) for a site
- // the user has visited
- map.put(Browser.BookmarkColumns.TITLE, title);
- map.put(Browser.BookmarkColumns.URL, url);
- map.put(Browser.BookmarkColumns.CREATED, creationTime);
- map.put(Browser.BookmarkColumns.BOOKMARK, 1);
- map.put(Browser.BookmarkColumns.DATE, 0);
- int visits = 0;
- if (count > 0) {
- // The user has already bookmarked, and possibly
- // visited this site. However, they are creating
- // a new bookmark with the same url but a different
- // name. The new bookmark should have the same
- // number of visits as the already created bookmark.
- visits = c.getInt(c.getColumnIndexOrThrow(
- Browser.BookmarkColumns.VISITS));
- }
- // Bookmark starts with 3 extra visits so that it will
- // bubble up in the most visited and goto search box
- map.put(Browser.BookmarkColumns.VISITS, visits + 3);
- cr.insert(Browser.BOOKMARKS_URI, map);
- }
- }
- WebIconDatabase.getInstance().retainIconForPageUrl(url);
- c.deactivate();
+ Bookmarks.addBookmark(null, getContentResolver(), url, title, true);
setResult(RESULT_OK);
}
} catch (IllegalStateException e) {
diff --git a/src/com/android/browser/AddNewBookmark.java b/src/com/android/browser/AddNewBookmark.java
index a75d002..5308f6b 100644
--- a/src/com/android/browser/AddNewBookmark.java
+++ b/src/com/android/browser/AddNewBookmark.java
@@ -47,17 +47,7 @@ class AddNewBookmark extends LinearLayout {
mUrlText = (TextView) findViewById(R.id.url);
mImageView = (ImageView) findViewById(R.id.favicon);
}
-
- /**
- * Copy this BookmarkItem to item.
- * @param item BookmarkItem to receive the info from this BookmarkItem.
- */
- /* package */ void copyTo(AddNewBookmark item) {
- item.mTextView.setText(mTextView.getText());
- item.mUrlText.setText(mUrlText.getText());
- item.mImageView.setImageDrawable(mImageView.getDrawable());
- }
-
+
/**
* Set the new url for the bookmark item.
* @param url The new url for the bookmark item.
diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java
new file mode 100644
index 0000000..a3dc919
--- /dev/null
+++ b/src/com/android/browser/Bookmarks.java
@@ -0,0 +1,202 @@
+/*
+ * 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.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Browser;
+import android.util.Log;
+import android.webkit.WebIconDatabase;
+import android.widget.Toast;
+
+import java.util.Date;
+
+/**
+ * This class is purely to have a common place for adding/deleting bookmarks.
+ */
+/* package */ class Bookmarks {
+ private static final String WHERE_CLAUSE
+ = "url = ? OR url = ? OR url = ? OR url = ?";
+ private static final String WHERE_CLAUSE_SECURE = "url = ? OR url = ?";
+
+ private static String[] SELECTION_ARGS;
+
+ /**
+ * Add a bookmark to the database.
+ * @param context Context of the calling Activity. This is used to make
+ * Toast confirming that the bookmark has been added. If the
+ * caller provides null, the Toast will not be shown.
+ * @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,
+ 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
+ // site. They may have bookmarked it in a different way from
+ // how it's stored in the database, so allow different combos
+ // to map to the same url.
+ boolean secure = false;
+ String compareString = url;
+ if (compareString.startsWith("http://")) {
+ compareString = compareString.substring(7);
+ } else if (compareString.startsWith("https://")) {
+ compareString = compareString.substring(8);
+ secure = true;
+ }
+ if (compareString.startsWith("www.")) {
+ compareString = compareString.substring(4);
+ }
+ if (secure) {
+ SELECTION_ARGS = new String[2];
+ SELECTION_ARGS[0] = "https://" + compareString;
+ SELECTION_ARGS[1] = "https://www." + compareString;
+ } else {
+ SELECTION_ARGS = new String[4];
+ SELECTION_ARGS[0] = compareString;
+ SELECTION_ARGS[1] = "www." + compareString;
+ SELECTION_ARGS[2] = "http://" + compareString;
+ SELECTION_ARGS[3] = "http://" + SELECTION_ARGS[1];
+ }
+ Cursor cursor = cr.query(Browser.BOOKMARKS_URI,
+ Browser.HISTORY_PROJECTION,
+ secure ? WHERE_CLAUSE_SECURE : WHERE_CLAUSE,
+ SELECTION_ARGS,
+ null);
+ ContentValues map = new ContentValues();
+ if (cursor.moveToFirst() && cursor.getInt(
+ Browser.HISTORY_PROJECTION_BOOKMARK_INDEX) == 0) {
+ // This means we have been to this site but not bookmarked
+ // it, so convert the history item to a bookmark
+ map.put(Browser.BookmarkColumns.CREATED, creationTime);
+ map.put(Browser.BookmarkColumns.TITLE, name);
+ map.put(Browser.BookmarkColumns.BOOKMARK, 1);
+ cr.update(Browser.BOOKMARKS_URI, map,
+ "_id = " + cursor.getInt(0), null);
+ } else {
+ int count = cursor.getCount();
+ boolean matchedTitle = false;
+ for (int i = 0; i < count; i++) {
+ // One or more bookmarks already exist for this site.
+ // Check the names of each
+ cursor.moveToPosition(i);
+ if (cursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX)
+ .equals(name)) {
+ // The old bookmark has the same name.
+ // Update its creation time.
+ map.put(Browser.BookmarkColumns.CREATED,
+ creationTime);
+ cr.update(Browser.BOOKMARKS_URI, map,
+ "_id = " + cursor.getInt(0), null);
+ matchedTitle = true;
+ break;
+ }
+ }
+ if (!matchedTitle) {
+ // Adding a bookmark for a site the user has visited,
+ // or a new bookmark (with a different name) for a site
+ // the user has visited
+ map.put(Browser.BookmarkColumns.TITLE, name);
+ map.put(Browser.BookmarkColumns.URL, url);
+ map.put(Browser.BookmarkColumns.CREATED, creationTime);
+ map.put(Browser.BookmarkColumns.BOOKMARK, 1);
+ map.put(Browser.BookmarkColumns.DATE, 0);
+ int visits = 0;
+ if (count > 0) {
+ // The user has already bookmarked, and possibly
+ // visited this site. However, they are creating
+ // a new bookmark with the same url but a different
+ // name. The new bookmark should have the same
+ // number of visits as the already created bookmark.
+ visits = cursor.getInt(
+ Browser.HISTORY_PROJECTION_VISITS_INDEX);
+ }
+ // Bookmark starts with 3 extra visits so that it will
+ // bubble up in the most visited and goto search box
+ map.put(Browser.BookmarkColumns.VISITS, visits + 3);
+ cr.insert(Browser.BOOKMARKS_URI, map);
+ }
+ }
+ if (retainIcon) {
+ WebIconDatabase.getInstance().retainIconForPageUrl(url);
+ }
+ cursor.deactivate();
+ if (context != null) {
+ Toast.makeText(context, R.string.added_to_bookmarks,
+ Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Remove a bookmark from the database. If the url is a visited site, it
+ * will remain in the database, but only as a history item, and not as a
+ * bookmarked site.
+ * @param context Context of the calling Activity. This is used to make
+ * Toast confirming that the bookmark has been removed. If the
+ * caller provides null, the Toast will not be shown.
+ * @param cr The ContentResolver being used to remove the bookmark.
+ * @param url URL of the website to be removed.
+ */
+ /* package */ static void removeFromBookmarks(Context context,
+ ContentResolver cr, String url) {
+ Cursor cursor = cr.query(
+ Browser.BOOKMARKS_URI,
+ Browser.HISTORY_PROJECTION,
+ "url = ?",
+ new String[] { url },
+ null);
+ boolean first = cursor.moveToFirst();
+ // Should be in the database no matter what
+ if (!first) {
+ throw new AssertionError("URL is not in the database!");
+ }
+ // Remove from bookmarks
+ WebIconDatabase.getInstance().releaseIconForPageUrl(url);
+ Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
+ cursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
+ int numVisits = cursor.getInt(
+ Browser.HISTORY_PROJECTION_VISITS_INDEX);
+ if (0 == numVisits) {
+ cr.delete(uri, null, null);
+ } else {
+ // It is no longer a bookmark, but it is still a visited
+ // site.
+ ContentValues values = new ContentValues();
+ values.put(Browser.BookmarkColumns.BOOKMARK, 0);
+ try {
+ cr.update(uri, values, null, null);
+ } catch (IllegalStateException e) {
+ Log.e("removeFromBookmarks", "no database!");
+ }
+ }
+ if (context != null) {
+ Toast.makeText(context, R.string.removed_from_bookmarks,
+ Toast.LENGTH_LONG).show();
+ }
+ cursor.deactivate();
+ }
+} \ No newline at end of file
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 93ba1b1..5134cf8 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -28,6 +28,7 @@ import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
@@ -35,6 +36,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.DialogInterface.OnCancelListener;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.AssetManager;
@@ -107,11 +109,14 @@ import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.DownloadListener;
import android.webkit.HttpAuthHandler;
+import android.webkit.PluginManager;
import android.webkit.SslErrorHandler;
import android.webkit.URLUtil;
import android.webkit.WebChromeClient;
+import android.webkit.WebChromeClient.CustomViewCallback;
import android.webkit.WebHistoryItem;
import android.webkit.WebIconDatabase;
+import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
@@ -121,6 +126,7 @@ import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -583,11 +589,6 @@ public class BrowserActivity extends Activity
}
copyBuildInfos();
-
- // Refresh the plugin list.
- if (mTabControl.getCurrentWebView() != null) {
- mTabControl.getCurrentWebView().refreshPlugins(false);
- }
} catch (IOException e) {
Log.e(TAG, "IO Exception: " + e);
}
@@ -634,16 +635,22 @@ public class BrowserActivity extends Activity
}
}
+ // Flag to enable the touchable browser bar with buttons
+ private final boolean CUSTOM_BROWSER_BAR = true;
+
@Override public void onCreate(Bundle icicle) {
if (LOGV_ENABLED) {
Log.v(LOGTAG, this + " onStart");
}
super.onCreate(icicle);
- this.requestWindowFeature(Window.FEATURE_LEFT_ICON);
- this.requestWindowFeature(Window.FEATURE_RIGHT_ICON);
- this.requestWindowFeature(Window.FEATURE_PROGRESS);
- this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-
+ if (CUSTOM_BROWSER_BAR) {
+ this.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ } else {
+ this.requestWindowFeature(Window.FEATURE_LEFT_ICON);
+ this.requestWindowFeature(Window.FEATURE_RIGHT_ICON);
+ this.requestWindowFeature(Window.FEATURE_PROGRESS);
+ this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ }
// test the browser in OpenGL
// requestWindowFeature(Window.FEATURE_OPENGL);
@@ -668,8 +675,37 @@ public class BrowserActivity extends Activity
mGenericFavicon = getResources().getDrawable(
R.drawable.app_web_browser_sm);
- mContentView = (FrameLayout) getWindow().getDecorView().findViewById(
- com.android.internal.R.id.content);
+ FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
+ .findViewById(com.android.internal.R.id.content);
+ if (CUSTOM_BROWSER_BAR) {
+ // This FrameLayout will hold the custom FrameLayout and a LinearLayout
+ // that contains the title bar and a FrameLayout, which
+ // holds everything else.
+ FrameLayout browserFrameLayout = (FrameLayout) LayoutInflater.from(this)
+ .inflate(R.layout.custom_screen, null);
+ mTitleBar = (TitleBar) browserFrameLayout.findViewById(R.id.title_bar);
+ mTitleBar.setBrowserActivity(this);
+ mContentView = (FrameLayout) browserFrameLayout.findViewById(
+ R.id.main_content);
+ mErrorConsoleContainer = (LinearLayout) browserFrameLayout.findViewById(
+ R.id.error_console);
+ mCustomViewContainer = (FrameLayout) browserFrameLayout
+ .findViewById(R.id.fullscreen_custom_content);
+ frameLayout.addView(browserFrameLayout, COVER_SCREEN_PARAMS);
+ } else {
+ mCustomViewContainer = new FrameLayout(this);
+ mCustomViewContainer.setBackgroundColor(Color.BLACK);
+ mContentView = new FrameLayout(this);
+
+ LinearLayout linearLayout = new LinearLayout(this);
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+ mErrorConsoleContainer = new LinearLayout(this);
+ linearLayout.addView(mErrorConsoleContainer, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ linearLayout.addView(mContentView, COVER_SCREEN_PARAMS);
+ frameLayout.addView(mCustomViewContainer, COVER_SCREEN_PARAMS);
+ frameLayout.addView(linearLayout, COVER_SCREEN_PARAMS);
+ }
// Create the tab control and our initial tab
mTabControl = new TabControl(this);
@@ -702,6 +738,52 @@ public class BrowserActivity extends Activity
}
};
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ mPackageInstallationReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final String packageName = intent.getData()
+ .getSchemeSpecificPart();
+ final boolean replacing = intent.getBooleanExtra(
+ Intent.EXTRA_REPLACING, false);
+ if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
+ // if it is replacing, refreshPlugins() when adding
+ return;
+ }
+ PackageManager pm = BrowserActivity.this.getPackageManager();
+ PackageInfo pkgInfo = null;
+ try {
+ pkgInfo = pm.getPackageInfo(packageName,
+ PackageManager.GET_PERMISSIONS);
+ } catch (PackageManager.NameNotFoundException e) {
+ return;
+ }
+ if (pkgInfo != null) {
+ String permissions[] = pkgInfo.requestedPermissions;
+ if (permissions == null) {
+ return;
+ }
+ boolean permissionOk = false;
+ for (String permit : permissions) {
+ if (PluginManager.PLUGIN_PERMISSION.equals(permit)) {
+ permissionOk = true;
+ break;
+ }
+ }
+ if (permissionOk) {
+ PluginManager.getInstance(BrowserActivity.this)
+ .refreshPlugins(
+ Intent.ACTION_PACKAGE_ADDED
+ .equals(action));
+ }
+ }
+ }
+ };
+ registerReceiver(mPackageInstallationReceiver, filter);
+
// If this was a web search request, pass it on to the default web search provider.
if (handleWebSearchIntent(getIntent())) {
moveTaskToBack(true);
@@ -713,6 +795,9 @@ public class BrowserActivity extends Activity
// none of the files in the directory are referenced any more.
new ClearThumbnails().execute(
mTabControl.getThumbnailDir().listFiles());
+ // there is no quit on Android. But if we can't restore the state,
+ // we can treat it as a new Browser, remove the old session cookies.
+ CookieManager.getInstance().removeSessionCookie();
final Intent intent = getIntent();
final Bundle extra = intent.getExtras();
// Create an initial tab.
@@ -765,6 +850,12 @@ public class BrowserActivity extends Activity
// are not animating from the tab picker.
attachTabToContentView(mTabControl.getCurrentTab());
}
+
+ // Read JavaScript flags if it exists.
+ String jsFlags = mSettings.getJsFlags();
+ if (jsFlags.trim().length() != 0) {
+ mTabControl.getCurrentWebView().setJsFlags(jsFlags);
+ }
}
@Override
@@ -812,7 +903,7 @@ public class BrowserActivity extends Activity
if (Intent.ACTION_VIEW.equals(action)
&& !getPackageName().equals(appId)
&& (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
- final TabControl.Tab appTab = mTabControl.getTabFromId(appId);
+ TabControl.Tab appTab = mTabControl.getTabFromId(appId);
if (appTab != null) {
Log.i(LOGTAG, "Reusing tab for " + appId);
// Dismiss the subwindow if applicable.
@@ -825,14 +916,14 @@ public class BrowserActivity extends Activity
// page, it can be reused.
boolean needsLoad =
mTabControl.recreateWebView(appTab, urlData.mUrl);
-
+
if (current != appTab) {
showTab(appTab, needsLoad ? urlData : EMPTY_URL_DATA);
} else {
if (mTabOverview != null && mAnimationCount == 0) {
sendAnimateFromOverview(appTab, false,
- needsLoad ? urlData : EMPTY_URL_DATA, TAB_OVERVIEW_DELAY,
- null);
+ needsLoad ? urlData : EMPTY_URL_DATA,
+ TAB_OVERVIEW_DELAY, null);
} else {
// If the tab was the current tab, we have to attach
// it to the view system again.
@@ -843,12 +934,32 @@ public class BrowserActivity extends Activity
}
}
return;
+ } else {
+ // No matching application tab, try to find a regular tab
+ // with a matching url.
+ appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
+ if (appTab != null) {
+ if (current != appTab) {
+ // Use EMPTY_URL_DATA so we do not reload the page
+ showTab(appTab, EMPTY_URL_DATA);
+ } else {
+ if (mTabOverview != null && mAnimationCount == 0) {
+ sendAnimateFromOverview(appTab, false,
+ EMPTY_URL_DATA, TAB_OVERVIEW_DELAY,
+ null);
+ }
+ // Don't do anything here since we are on the
+ // correct page.
+ }
+ } else {
+ // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
+ // will be opened in a new tab unless we have reached
+ // MAX_TABS. Then the url will be opened in the current
+ // tab. If a new tab is created, it will have "true" for
+ // exit on close.
+ openTabAndShow(urlData, null, true, appId);
+ }
}
- // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url will be
- // opened in a new tab unless we have reached MAX_TABS. Then the
- // url will be opened in the current tab. If a new tab is
- // created, it will have "true" for exit on close.
- openTabAndShow(urlData, null, true, appId);
} else {
if ("about:debug".equals(urlData.mUrl)) {
mSettings.toggleDebugSettings();
@@ -1107,8 +1218,9 @@ public class BrowserActivity extends Activity
return;
}
+ mTabControl.resumeCurrentTab();
mActivityInPause = false;
- resumeWebView();
+ resumeWebViewTimers();
if (mWakeLock.isHeld()) {
mHandler.removeMessages(RELEASE_WAKELOCK);
@@ -1166,8 +1278,9 @@ public class BrowserActivity extends Activity
return;
}
+ mTabControl.pauseCurrentTab();
mActivityInPause = true;
- if (mTabControl.getCurrentIndex() >= 0 && !pauseWebView()) {
+ if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
mWakeLock.acquire();
mHandler.sendMessageDelayed(mHandler
.obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
@@ -1218,6 +1331,8 @@ public class BrowserActivity extends Activity
// "com.android.masfproxyservice",
// "com.android.masfproxyservice.MasfProxyService"));
//stopService(proxyServiceIntent);
+
+ unregisterReceiver(mPackageInstallationReceiver);
}
@Override
@@ -1266,7 +1381,7 @@ public class BrowserActivity extends Activity
mTabControl.freeMemory();
}
- private boolean resumeWebView() {
+ private boolean resumeWebViewTimers() {
if ((!mActivityInPause && !mPageStarted) ||
(mActivityInPause && mPageStarted)) {
CookieSyncManager.getInstance().startSync();
@@ -1280,7 +1395,7 @@ public class BrowserActivity extends Activity
}
}
- private boolean pauseWebView() {
+ private boolean pauseWebViewTimers() {
if (mActivityInPause && !mPageStarted) {
CookieSyncManager.getInstance().stopSync();
WebView w = mTabControl.getCurrentWebView();
@@ -1441,7 +1556,7 @@ public class BrowserActivity extends Activity
*/
@Override
public boolean onSearchRequested() {
- String url = getTopWindow().getUrl();
+ String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false);
return true;
@@ -1877,6 +1992,20 @@ public class BrowserActivity extends Activity
final WebView main = t.getWebView();
// Attach the main WebView.
mContentView.addView(main, COVER_SCREEN_PARAMS);
+
+ if (mShouldShowErrorConsole) {
+ ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
+ if (errorConsole.numberOfErrors() == 0) {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
+ } else {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
+ }
+
+ mErrorConsoleContainer.addView(errorConsole,
+ new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ }
+
// Attach the sub window if necessary
attachSubWindow(t);
// Request focus on the top window.
@@ -1898,6 +2027,11 @@ public class BrowserActivity extends Activity
private void removeTabFromContentView(TabControl.Tab t) {
// Remove the main WebView.
mContentView.removeView(t.getWebView());
+
+ if (mTabControl.getCurrentErrorConsole(false) != null) {
+ mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
+ }
+
// Remove the sub window if it exists.
if (t.getSubWebView() != null) {
mContentView.removeView(t.getSubWebViewContainer());
@@ -2377,7 +2511,11 @@ public class BrowserActivity extends Activity
// While the tab overview is animating or being shown, block changes
// to the title.
if (mAnimationCount == 0 && mTabOverview == null) {
- setTitle(buildUrlTitle(url, title));
+ if (CUSTOM_BROWSER_BAR) {
+ mTitleBar.setTitleAndUrl(title, url);
+ } else {
+ setTitle(buildUrlTitle(url, title));
+ }
}
}
@@ -2418,7 +2556,7 @@ public class BrowserActivity extends Activity
* or an empty string if, for example, the URL in question is a
* file:// URL with no hostname.
*/
- private static String buildTitleUrl(String url) {
+ /* package */ static String buildTitleUrl(String url) {
String titleUrl = null;
if (url != null) {
@@ -2454,18 +2592,34 @@ public class BrowserActivity extends Activity
if (mAnimationCount > 0 || mTabOverview != null) {
return;
}
- Drawable[] array = new Drawable[2];
- PaintDrawable p = new PaintDrawable(Color.WHITE);
- p.setCornerRadius(3f);
- array[0] = p;
- if (icon == null) {
- array[1] = mGenericFavicon;
+ if (CUSTOM_BROWSER_BAR) {
+ Drawable[] array = new Drawable[3];
+ array[0] = new PaintDrawable(Color.BLACK);
+ PaintDrawable p = new PaintDrawable(Color.WHITE);
+ array[1] = p;
+ if (icon == null) {
+ array[2] = mGenericFavicon;
+ } else {
+ array[2] = new BitmapDrawable(icon);
+ }
+ LayerDrawable d = new LayerDrawable(array);
+ d.setLayerInset(1, 1, 1, 1, 1);
+ d.setLayerInset(2, 2, 2, 2, 2);
+ mTitleBar.setFavicon(d);
} else {
- array[1] = new BitmapDrawable(icon);
+ Drawable[] array = new Drawable[2];
+ PaintDrawable p = new PaintDrawable(Color.WHITE);
+ p.setCornerRadius(3f);
+ array[0] = p;
+ if (icon == null) {
+ array[1] = mGenericFavicon;
+ } else {
+ array[1] = new BitmapDrawable(icon);
+ }
+ LayerDrawable d = new LayerDrawable(array);
+ d.setLayerInset(1, 2, 2, 2, 2);
+ getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
}
- LayerDrawable d = new LayerDrawable(array);
- d.setLayerInset(1, 2, 2, 2, 2);
- getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
}
/**
@@ -2543,9 +2697,9 @@ public class BrowserActivity extends Activity
finish();
return;
}
- // call pauseWebView() now, we won't be able to call it in
- // onPause() as the WebView won't be valid.
- pauseWebView();
+ // call pauseWebViewTimers() now, we won't be able to call
+ // it in onPause() as the WebView won't be valid.
+ pauseWebViewTimers();
removeTabFromContentView(current);
mTabControl.removeTab(current);
}
@@ -2569,10 +2723,15 @@ public class BrowserActivity extends Activity
// because of accumulated key events,
// we should ignore it as browser is not active any more.
WebView topWindow = getTopWindow();
- if (topWindow == null)
+ if (topWindow == null && mCustomView == null)
return KeyTracker.State.NOT_TRACKING;
if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // Check if a custom view is currently showing and, if it is, hide it.
+ if (mCustomView != null) {
+ mWebChromeClient.onHideCustomView();
+ return KeyTracker.State.DONE_TRACKING;
+ }
// During animations, block the back key so that other animations
// are not triggered and so that we don't end up destroying all the
// WebViews before finishing the animation.
@@ -2771,6 +2930,58 @@ public class BrowserActivity extends Activity
}
};
+ private void updateScreenshot(WebView view) {
+ // If this is a bookmarked site, add a screenshot to the database.
+ // FIXME: When should we update? Every time?
+ // FIXME: Would like to make sure there is actually something to
+ // draw, but the API for that (WebViewCore.pictureReady()) is not
+ // currently accessible here.
+ String original = view.getOriginalUrl();
+ if (original != null) {
+ // copied from BrowserBookmarksAdapter
+ int query = original.indexOf('?');
+ String noQuery = original;
+ if (query != -1) {
+ noQuery = original.substring(0, query);
+ }
+ String URL = noQuery + '?';
+ String[] selArgs = new String[] { noQuery, URL };
+ final String where
+ = "(url == ? OR url GLOB ? || '*') AND bookmark == 1";
+ final String[] projection
+ = new String[] { Browser.BookmarkColumns._ID };
+ ContentResolver cr = getContentResolver();
+ final Cursor c = cr.query(Browser.BOOKMARKS_URI, projection,
+ where, selArgs, null);
+ boolean succeed = c.moveToFirst();
+ ContentValues values = null;
+ while (succeed) {
+ if (values == null) {
+ final ByteArrayOutputStream os
+ = new ByteArrayOutputStream();
+ Picture thumbnail = view.capturePicture();
+ // Keep width and height in sync with BrowserBookmarksPage
+ // and bookmark_thumb
+ Bitmap bm = Bitmap.createBitmap(100, 80,
+ Bitmap.Config.ARGB_4444);
+ Canvas canvas = new Canvas(bm);
+ // May need to tweak these values to determine what is the
+ // best scale factor
+ canvas.scale(.5f, .5f);
+ thumbnail.draw(canvas);
+ bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+ values = new ContentValues();
+ values.put(Browser.BookmarkColumns.THUMBNAIL,
+ os.toByteArray());
+ }
+ cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
+ c.getInt(0)), values, null, null);
+ succeed = c.moveToNext();
+ }
+ c.close();
+ }
+ }
+
// -------------------------------------------------------------------------
// WebViewClient implementation.
//-------------------------------------------------------------------------
@@ -2798,6 +3009,15 @@ public class BrowserActivity extends Activity
public void onPageStarted(WebView view, String url, Bitmap favicon) {
resetLockIcon(url);
setUrlTitle(url, null);
+
+ ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
+ if (errorConsole != null) {
+ errorConsole.clearErrorMessages();
+ if (mShouldShowErrorConsole) {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
+ }
+ }
+
// Call updateIcon instead of setFavicon so the bookmark
// database can be updated.
updateIcon(url, favicon);
@@ -2846,8 +3066,9 @@ public class BrowserActivity extends Activity
if (!mPageStarted) {
mPageStarted = true;
- // if onResume() has been called, resumeWebView() does nothing.
- resumeWebView();
+ // if onResume() has been called, resumeWebViewTimers() does
+ // nothing.
+ resumeWebViewTimers();
}
// reset sync timer to avoid sync starts during loading a page
@@ -2881,6 +3102,7 @@ public class BrowserActivity extends Activity
// Update the lock icon image only once we are done loading
updateLockIconImage(mLockIconType);
+ updateScreenshot(view);
// Performance probe
if (false) {
@@ -2968,9 +3190,9 @@ public class BrowserActivity extends Activity
if (mPageStarted) {
mPageStarted = false;
- // pauseWebView() will do nothing and return false if onPause()
- // is not called yet.
- if (pauseWebView()) {
+ // pauseWebViewTimers() will do nothing and return false if
+ // onPause() is not called yet.
+ if (pauseWebViewTimers()) {
if (mWakeLock.isHeld()) {
mHandler.removeMessages(RELEASE_WAKELOCK);
mWakeLock.release();
@@ -3016,9 +3238,9 @@ public class BrowserActivity extends Activity
if (url.startsWith("about:")) {
return false;
}
-
+
Intent intent;
-
+
// perform generic parsing of the URI to turn it into an Intent.
try {
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
@@ -3516,8 +3738,13 @@ public class BrowserActivity extends Activity
// Block progress updates to the title bar while the tab overview
// is animating or being displayed.
if (mAnimationCount == 0 && mTabOverview == null) {
- getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
- newProgress * 100);
+ if (CUSTOM_BROWSER_BAR) {
+ mTitleBar.setProgress(newProgress);
+ } else {
+ getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
+ newProgress * 100);
+
+ }
}
if (newProgress == 100) {
@@ -3551,6 +3778,8 @@ public class BrowserActivity extends Activity
url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
return;
}
+ // See if we can find the current url in our history database and
+ // add the new title to it.
if (url.startsWith("http://www.")) {
url = url.substring(11);
} else if (url.startsWith("http://")) {
@@ -3565,9 +3794,6 @@ public class BrowserActivity extends Activity
Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
Browser.HISTORY_PROJECTION, where, selArgs, null);
if (c.moveToFirst()) {
- if (LOGV_ENABLED) {
- Log.v(LOGTAG, "updating cursor");
- }
// Current implementation of database only has one entry per
// url.
ContentValues map = new ContentValues();
@@ -3587,6 +3813,96 @@ public class BrowserActivity extends Activity
public void onReceivedIcon(WebView view, Bitmap icon) {
updateIcon(view.getUrl(), icon);
}
+
+ @Override
+ public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
+ if (mCustomView != null)
+ return;
+
+ // Add the custom view to its container.
+ mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
+ mCustomView = view;
+ mCustomViewCallback = callback;
+ // Save the menu state and set it to empty while the custom
+ // view is showing.
+ mOldMenuState = mMenuState;
+ mMenuState = EMPTY_MENU;
+ // Hide the content view.
+ mContentView.setVisibility(View.GONE);
+ // Finally show the custom view container.
+ mCustomViewContainer.setVisibility(View.VISIBLE);
+ mCustomViewContainer.bringToFront();
+ }
+
+ @Override
+ public void onHideCustomView() {
+ if (mCustomView == null)
+ return;
+
+ // Hide the custom view.
+ mCustomView.setVisibility(View.GONE);
+ // Remove the custom view from its container.
+ mCustomViewContainer.removeView(mCustomView);
+ mCustomView = null;
+ // Reset the old menu state.
+ mMenuState = mOldMenuState;
+ mOldMenuState = EMPTY_MENU;
+ mCustomViewContainer.setVisibility(View.GONE);
+ mCustomViewCallback.onCustomViewHidden();
+ // Show the content view.
+ mContentView.setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * 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, long totalUsedQuota,
+ WebStorage.QuotaUpdater quotaUpdater) {
+ 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.
+ * @param message The error message to report.
+ * @param lineNumber The line number of the error.
+ * @param sourceID The name of the source file that caused the error.
+ */
+ @Override
+ public void addMessageToConsole(String message, int lineNumber, String sourceID) {
+ ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
+ errorConsole.addErrorMessage(message, sourceID, lineNumber);
+ if (mShouldShowErrorConsole &&
+ errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
+ }
+ Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
+ }
};
/**
@@ -3702,19 +4018,19 @@ public class BrowserActivity extends Activity
String cookies = CookieManager.getInstance().getCookie(url);
ContentValues values = new ContentValues();
- values.put(Downloads.URI, uri.toString());
- values.put(Downloads.COOKIE_DATA, cookies);
- values.put(Downloads.USER_AGENT, userAgent);
- values.put(Downloads.NOTIFICATION_PACKAGE,
+ values.put(Downloads.COLUMN_URI, uri.toString());
+ values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
+ values.put(Downloads.COLUMN_USER_AGENT, userAgent);
+ values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
getPackageName());
- values.put(Downloads.NOTIFICATION_CLASS,
+ values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
BrowserDownloadPage.class.getCanonicalName());
- values.put(Downloads.VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
- values.put(Downloads.MIMETYPE, mimetype);
- values.put(Downloads.FILENAME_HINT, filename);
- values.put(Downloads.DESCRIPTION, uri.getHost());
+ values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+ values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
+ values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
+ values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
if (contentLength > 0) {
- values.put(Downloads.TOTAL_BYTES, contentLength);
+ values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
}
if (mimetype == null) {
// We must have long pressed on a link or image to download it. We
@@ -3780,7 +4096,11 @@ public class BrowserActivity extends Activity
// If the tab overview is animating or being shown, do not update the
// lock icon.
if (mAnimationCount == 0 && mTabOverview == null) {
- getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
+ if (CUSTOM_BROWSER_BAR) {
+ mTitleBar.setLock(d);
+ } else {
+ getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
+ }
}
}
@@ -4273,7 +4593,17 @@ public class BrowserActivity extends Activity
String data = intent.getAction();
Bundle extras = intent.getExtras();
if (extras != null && extras.getBoolean("new_window", false)) {
- openTab(data);
+ final TabControl.Tab newTab = openTab(data);
+ if (mSettings.openInBackground() &&
+ newTab != null && mTabOverview != null) {
+ mTabControl.populatePickerData(newTab);
+ mTabControl.setCurrentTab(newTab);
+ mTabOverview.add(newTab);
+ mTabOverview.setCurrentIndex(
+ mTabControl.getCurrentIndex());
+ sendAnimateFromOverview(newTab, false,
+ EMPTY_URL_DATA, TAB_OVERVIEW_DELAY, null);
+ }
} else {
final TabControl.Tab currentTab =
mTabControl.getCurrentTab();
@@ -4281,8 +4611,8 @@ public class BrowserActivity extends Activity
// middle of an animation, animate away from it to the
// current tab.
if (mTabOverview != null && mAnimationCount == 0) {
- sendAnimateFromOverview(currentTab, false, new UrlData(data),
- TAB_OVERVIEW_DELAY, null);
+ sendAnimateFromOverview(currentTab, false,
+ new UrlData(data), TAB_OVERVIEW_DELAY, null);
} else {
dismissSubWindow(currentTab);
if (data != null && data.length() != 0) {
@@ -4332,8 +4662,8 @@ public class BrowserActivity extends Activity
// was clicked on.
if (mTabControl.getTabCount() == 0) {
current = mTabControl.createNewTab();
- sendAnimateFromOverview(current, true,
- new UrlData(mSettings.getHomePage()), TAB_OVERVIEW_DELAY, null);
+ sendAnimateFromOverview(current, true, new UrlData(
+ mSettings.getHomePage()), TAB_OVERVIEW_DELAY, null);
} else {
final int index = position > 0 ? (position - 1) : 0;
current = mTabControl.getTab(index);
@@ -4369,8 +4699,8 @@ public class BrowserActivity extends Activity
if (index == ImageGrid.NEW_TAB) {
openTabAndShow(mSettings.getHomePage(), null, false, null);
} else {
- sendAnimateFromOverview(mTabControl.getTab(index),
- false, EMPTY_URL_DATA, 0, null);
+ sendAnimateFromOverview(mTabControl.getTab(index), false,
+ EMPTY_URL_DATA, 0, null);
}
}
}
@@ -4392,13 +4722,19 @@ public class BrowserActivity extends Activity
AnimatingView(Context ctxt, TabControl.Tab t) {
super(ctxt);
mTab = t;
- // Use the top window in the animation since the tab overview will
- // display the top window in each cell.
- final WebView w = t.getTopWindow();
- mPicture = w.capturePicture();
- mScale = w.getScale() / w.getWidth();
- mScrollX = w.getScrollX();
- mScrollY = w.getScrollY();
+ if (t != null && t.getTopWindow() != null) {
+ // Use the top window in the animation since the tab overview
+ // will display the top window in each cell.
+ final WebView w = t.getTopWindow();
+ mPicture = w.capturePicture();
+ mScale = w.getScale() / w.getWidth();
+ mScrollX = w.getScrollX();
+ mScrollY = w.getScrollY();
+ } else {
+ mPicture = null;
+ mScale = 1.0f;
+ mScrollX = mScrollY = 0;
+ }
}
@Override
@@ -4472,16 +4808,20 @@ public class BrowserActivity extends Activity
mAnimationCount++;
// Always change the title bar to the window overview title while
// animating.
- getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, null);
- getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, null);
- getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
- Window.PROGRESS_VISIBILITY_OFF);
- setTitle(R.string.tab_picker_title);
+ if (CUSTOM_BROWSER_BAR) {
+ mTitleBar.setToTabPicker();
+ } else {
+ getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, null);
+ getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, null);
+ getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
+ Window.PROGRESS_VISIBILITY_OFF);
+ setTitle(R.string.tab_picker_title);
+ }
// Make the menu empty until the animation completes.
mMenuState = EMPTY_MENU;
}
- private void bookmarksOrHistoryPicker(boolean startWithHistory) {
+ /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) {
WebView current = mTabControl.getCurrentWebView();
if (current == null) {
return;
@@ -4557,7 +4897,7 @@ public class BrowserActivity extends Activity
return 0;
}
- static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
+ protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
"(?i)" + // switch on case insensitive matching
"(" + // begin group for schema
"(?:http|https|file):\\/\\/" +
@@ -4622,6 +4962,34 @@ public class BrowserActivity extends Activity
return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
}
+ /* package */ void setShouldShowErrorConsole(boolean flag) {
+ if (flag == mShouldShowErrorConsole) {
+ // Nothing to do.
+ return;
+ }
+
+ mShouldShowErrorConsole = flag;
+
+ ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
+
+ if (flag) {
+ // Setting the show state of the console will cause it's the layout to be inflated.
+ if (errorConsole.numberOfErrors() > 0) {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
+ } else {
+ errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
+ }
+
+ // Now we can add it to the main view.
+ mErrorConsoleContainer.addView(errorConsole,
+ new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ } else {
+ mErrorConsoleContainer.removeView(errorConsole);
+ }
+
+ }
+
private final static int LOCK_ICON_UNSECURE = 0;
private final static int LOCK_ICON_SECURE = 1;
private final static int LOCK_ICON_MIXED = 2;
@@ -4634,11 +5002,15 @@ public class BrowserActivity extends Activity
private ContentResolver mResolver;
private FrameLayout mContentView;
private ImageGrid mTabOverview;
+ private View mCustomView;
+ private FrameLayout mCustomViewContainer;
+ private WebChromeClient.CustomViewCallback mCustomViewCallback;
// FIXME, temp address onPrepareMenu performance problem. When we move everything out of
// view, we should rewrite this.
private int mCurrentMenuState = 0;
private int mMenuState = R.id.MAIN_MENU;
+ private int mOldMenuState = EMPTY_MENU;
private static final int EMPTY_MENU = -1;
private Menu mMenu;
@@ -4729,6 +5101,11 @@ public class BrowserActivity extends Activity
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT);
+ /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
+ new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT,
+ Gravity.CENTER);
// Google search
final static String QuickSearch_G = "http://www.google.com/m?q=%s";
// Wikipedia search
@@ -4762,6 +5139,11 @@ public class BrowserActivity extends Activity
private Toast mStopToast;
+ private TitleBar mTitleBar;
+
+ private LinearLayout mErrorConsoleContainer = null;
+ private boolean mShouldShowErrorConsole = false;
+
// Used during animations to prevent other animations from being triggered.
// A count is used since the animation to and from the Window overview can
// overlap. A count of 0 means no animation where a count of > 0 means
@@ -4779,10 +5161,12 @@ public class BrowserActivity extends Activity
private IntentFilter mNetworkStateChangedFilter;
private BroadcastReceiver mNetworkStateIntentReceiver;
+ private BroadcastReceiver mPackageInstallationReceiver;
+
// 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;
// the frenquency of checking whether system memory is low
final static int CHECK_MEMORY_INTERVAL = 30000; // 30 seconds
@@ -4832,7 +5216,7 @@ public class BrowserActivity extends Activity
String mEncoding;
@Override
boolean isEmpty() {
- return mInlined == null || mInlined.length() == 0 || super.isEmpty();
+ return mInlined == null || mInlined.length() == 0 || super.isEmpty();
}
@Override
diff --git a/src/com/android/browser/BrowserBackupAgent.java b/src/com/android/browser/BrowserBackupAgent.java
new file mode 100644
index 0000000..c239b12
--- /dev/null
+++ b/src/com/android/browser/BrowserBackupAgent.java
@@ -0,0 +1,295 @@
+/*
+ * 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 java.io.IOException;
+
+import android.app.BackupAgent;
+import android.backup.BackupDataInput;
+import android.backup.BackupDataOutput;
+import android.database.Cursor;
+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;
+
+/**
+ * Settings backup agent for the Android browser. Currently the only thing
+ * 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
+ * last time we did a backup, we store the following bits of info in the
+ * state file after a backup:
+ *
+ * 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
+ * the bookmarks didn't really change and we don't need to send the data.
+ * (If they don't match, of course, then they've changed and we do indeed
+ * send the new flattened file to be backed up.)
+ */
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ 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(
+ new FileInputStream(oldState.getFileDescriptor()));
+ try {
+ savedFileSize = in.readLong();
+ savedCrc = in.readLong();
+ savedVersion = in.readInt();
+ } catch (EOFException e) {
+ // It means we had no previous state; that's fine
+ }
+
+ // Build a flattened representation of the bookmarks table
+ File tmpfile = File.createTempFile("bkp", null, getCacheDir());
+ try {
+ FileOutputStream outfstream = new FileOutputStream(tmpfile);
+ long newCrc = buildBookmarkFile(outfstream);
+ outfstream.close();
+
+ // 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);
+ }
+
+ // 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();
+ }
+ }
+
+ /**
+ * Restore from backup -- reads in the flattened bookmark file as supplied from
+ * the backup service, parses that out, and rebuilds the bookmarks table in the
+ * browser database from it.
+ */
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode,
+ ParcelFileDescriptor newState) throws IOException {
+ long crc = -1;
+ 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());
+
+ 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
+ // changes whenever we get invoked for backup in the future
+ writeBackupState(tmpfile.length(), crc, newState);
+ }
+ } finally {
+ // Whatever happens, delete the temp file
+ tmpfile.delete();
+ }
+ }
+
+ 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 {
+ final int CHUNK = 8192;
+ byte[] buf = new byte[CHUNK];
+
+ int toCopy = (int) file.length();
+ data.writeEntityHeader(key, toCopy);
+
+ FileInputStream in = new FileInputStream(file);
+ int nRead;
+ while (toCopy > 0) {
+ nRead = in.read(buf, 0, CHUNK);
+ data.writeEntityData(buf, nRead);
+ toCopy -= nRead;
+ }
+ in.close();
+ }
+
+ // Read the given file from backup to a file, calculating a CRC32 along the way
+ private long copyBackupToFile(BackupDataInput data, File file, int toRead)
+ throws IOException {
+ 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();
+ }
+
+ // Write the given metrics to the new state file
+ private void writeBackupState(long fileSize, long crc, ParcelFileDescriptor stateFile)
+ throws IOException {
+ DataOutputStream out = new DataOutputStream(
+ new FileOutputStream(stateFile.getFileDescriptor()));
+ out.writeLong(fileSize);
+ out.writeLong(crc);
+ out.writeInt(BACKUP_AGENT_VERSION);
+ }
+}
diff --git a/src/com/android/browser/BrowserBookmarksAdapter.java b/src/com/android/browser/BrowserBookmarksAdapter.java
index 27782e0..75be45b 100644
--- a/src/com/android/browser/BrowserBookmarksAdapter.java
+++ b/src/com/android/browser/BrowserBookmarksAdapter.java
@@ -30,29 +30,27 @@ import android.os.Handler;
import android.provider.Browser;
import android.provider.Browser.BookmarkColumns;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebIconDatabase;
import android.webkit.WebIconDatabase.IconListener;
import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
import java.io.ByteArrayOutputStream;
class BrowserBookmarksAdapter extends BaseAdapter {
- private final String LOGTAG = "Bookmarks";
-
private String mCurrentPage;
+ private String mCurrentTitle;
private Cursor mCursor;
private int mCount;
- private String mLastWhereClause;
- private String[] mLastSelectionArgs;
- private String mLastOrderBy;
private BrowserBookmarksPage mBookmarksPage;
private ContentResolver mContentResolver;
- private ChangeObserver mChangeObserver;
- private DataSetObserver mDataSetObserver;
private boolean mDataValid;
+ private boolean mGridMode;
// When true, this adapter is used to pick a bookmark to create a shortcut
private boolean mCreateShortcut;
@@ -70,36 +68,37 @@ class BrowserBookmarksAdapter extends BaseAdapter {
/**
* Create a new BrowserBookmarksAdapter.
- * @param b BrowserBookmarksPage that instantiated this.
- * Necessary so it will adjust its focus
- * appropriately after a search.
- */
- public BrowserBookmarksAdapter(BrowserBookmarksPage b, String curPage) {
- this(b, curPage, false);
- }
-
- /**
- * Create a new BrowserBookmarksAdapter.
* @param b BrowserBookmarksPage that instantiated this.
* Necessary so it will adjust its focus
* appropriately after a search.
*/
public BrowserBookmarksAdapter(BrowserBookmarksPage b, String curPage,
- boolean createShortcut) {
+ String curTitle, boolean createShortcut) {
mDataValid = false;
mCreateShortcut = createShortcut;
mExtraOffset = createShortcut ? 0 : 1;
mBookmarksPage = b;
- mCurrentPage = b.getResources().getString(R.string.current_page) +
- curPage;
+ mCurrentPage = b.getResources().getString(R.string.current_page)
+ + curPage;
+ mCurrentTitle = curTitle;
mContentResolver = b.getContentResolver();
- mLastOrderBy = Browser.BookmarkColumns.CREATED + " DESC";
- mChangeObserver = new ChangeObserver();
- mDataSetObserver = new MyDataSetObserver();
+ mGridMode = false;
+
// FIXME: Should have a default sort order that the user selects.
- search(null);
+ String whereClause = Browser.BookmarkColumns.BOOKMARK + " != 0";
+ String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
+ mCursor = b.managedQuery(Browser.BOOKMARKS_URI,
+ Browser.HISTORY_PROJECTION, whereClause, null, orderBy);
+ mCursor.registerContentObserver(new ChangeObserver());
+ mCursor.registerDataSetObserver(new MyDataSetObserver());
+
+ mDataValid = true;
+ notifyDataSetChanged();
+
+ mCount = mCursor.getCount() + mExtraOffset;
+
// FIXME: This requires another query of the database after the
- // initial search(null). Can we optimize this?
+ // managedQuery. Can we optimize this?
Browser.requestAllIcons(mContentResolver,
Browser.BookmarkColumns.FAVICON + " is NULL AND " +
Browser.BookmarkColumns.BOOKMARK + " == 1", mIconReceiver);
@@ -181,18 +180,7 @@ class BrowserBookmarksAdapter extends BaseAdapter {
}
mCursor.moveToPosition(position- mExtraOffset);
String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
- WebIconDatabase.getInstance().releaseIconForPageUrl(url);
- Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI, mCursor
- .getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
- int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
- if (0 == numVisits) {
- mContentResolver.delete(uri, null, null);
- } else {
- // It is no longer a bookmark, but it is still a visited site.
- ContentValues values = new ContentValues();
- values.put(Browser.BookmarkColumns.BOOKMARK, 0);
- mContentResolver.update(uri, values, null, null);
- }
+ Bookmarks.removeFromBookmarks(null, mContentResolver, url);
refreshList();
}
@@ -253,52 +241,9 @@ class BrowserBookmarksAdapter extends BaseAdapter {
* Refresh list to recognize a change in the database.
*/
public void refreshList() {
- // FIXME: consider using requery().
- // Need to do more work to get it to function though.
- searchInternal(mLastWhereClause, mLastSelectionArgs, mLastOrderBy);
- }
-
- /**
- * Search the database for bookmarks that match the input string.
- * @param like String to use to search the database. Strings with spaces
- * are treated as having multiple search terms using the
- * OR operator. Search both the title and url.
- */
- public void search(String like) {
- String whereClause = Browser.BookmarkColumns.BOOKMARK + " == 1";
- String[] selectionArgs = null;
- if (like != null) {
- String[] likes = like.split(" ");
- int count = 0;
- boolean firstTerm = true;
- StringBuilder andClause = new StringBuilder(256);
- for (int j = 0; j < likes.length; j++) {
- if (likes[j].length() > 0) {
- if (firstTerm) {
- firstTerm = false;
- } else {
- andClause.append(" OR ");
- }
- andClause.append(Browser.BookmarkColumns.TITLE
- + " LIKE ? OR " + Browser.BookmarkColumns.URL
- + " LIKE ? ");
- count += 2;
- }
- }
- if (count > 0) {
- selectionArgs = new String[count];
- count = 0;
- for (int j = 0; j < likes.length; j++) {
- if (likes[j].length() > 0) {
- like = "%" + likes[j] + "%";
- selectionArgs[count++] = like;
- selectionArgs[count++] = like;
- }
- }
- whereClause += " AND (" + andClause + ")";
- }
- }
- searchInternal(whereClause, selectionArgs, mLastOrderBy);
+ mCursor.requery();
+ mCount = mCursor.getCount() + mExtraOffset;
+ notifyDataSetChanged();
}
/**
@@ -348,46 +293,6 @@ class BrowserBookmarksAdapter extends BaseAdapter {
}
/**
- * This sorts alphabetically, with non-capitalized titles before
- * capitalized.
- */
- public void sortAlphabetical() {
- searchInternal(mLastWhereClause, mLastSelectionArgs,
- Browser.BookmarkColumns.TITLE + " COLLATE UNICODE ASC");
- }
-
- /**
- * Internal function used in search, sort, and refreshList.
- */
- private void searchInternal(String whereClause, String[] selectionArgs,
- String orderBy) {
- if (mCursor != null) {
- mCursor.unregisterContentObserver(mChangeObserver);
- mCursor.unregisterDataSetObserver(mDataSetObserver);
- mBookmarksPage.stopManagingCursor(mCursor);
- mCursor.deactivate();
- }
-
- mLastWhereClause = whereClause;
- mLastSelectionArgs = selectionArgs;
- mLastOrderBy = orderBy;
- mCursor = mContentResolver.query(
- Browser.BOOKMARKS_URI,
- Browser.HISTORY_PROJECTION,
- whereClause,
- selectionArgs,
- orderBy);
- mCursor.registerContentObserver(mChangeObserver);
- mCursor.registerDataSetObserver(mDataSetObserver);
- mBookmarksPage.startManagingCursor(mCursor);
-
- mDataValid = true;
- notifyDataSetChanged();
-
- mCount = mCursor.getCount() + mExtraOffset;
- }
-
- /**
* How many items should be displayed in the list.
* @return Count of items.
*/
@@ -425,6 +330,20 @@ class BrowserBookmarksAdapter extends BaseAdapter {
return position;
}
+ /* package */ void switchViewMode(boolean toGrid) {
+ mGridMode = toGrid;
+ }
+
+ /* package */ void populateBookmarkItem(BookmarkItem b, int position) {
+ mCursor.moveToPosition(position - mExtraOffset);
+ b.setUrl(mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX));
+ b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
+ byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+ Bitmap bitmap = (null == data) ? null :
+ BitmapFactory.decodeByteArray(data, 0, data.length);
+ b.setFavicon(bitmap);
+ }
+
/**
* Get a View that displays the data at the specified position
* in the list.
@@ -440,6 +359,53 @@ class BrowserBookmarksAdapter extends BaseAdapter {
throw new AssertionError(
"BrowserBookmarksAdapter tried to get a view out of range");
}
+ if (mGridMode) {
+ if (convertView == null || convertView instanceof AddNewBookmark
+ || convertView instanceof BookmarkItem) {
+ LayoutInflater factory = LayoutInflater.from(mBookmarksPage);
+ convertView
+ = factory.inflate(R.layout.bookmark_thumbnail, null);
+ }
+ View holder = convertView.findViewById(R.id.holder);
+ ImageView thumb = (ImageView) convertView.findViewById(R.id.thumb);
+ ImageView fav = (ImageView) convertView.findViewById(R.id.fav);
+ TextView tv = (TextView) convertView.findViewById(R.id.label);
+
+ if (0 == position && !mCreateShortcut) {
+ // This is to create a bookmark for the current page.
+ holder.setVisibility(View.VISIBLE);
+ fav.setVisibility(View.GONE);
+ tv.setText(mCurrentTitle);
+ // FIXME: Want to show the screenshot of the current page
+ thumb.setImageResource(R.drawable.blank);
+ return convertView;
+ }
+ holder.setVisibility(View.GONE);
+ mCursor.moveToPosition(position - mExtraOffset);
+ tv.setText(mCursor.getString(
+ Browser.HISTORY_PROJECTION_TITLE_INDEX));
+ byte[] data = mCursor.getBlob(
+ Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX);
+ if (data == null) {
+ // Backup is to just show white
+ thumb.setImageResource(R.drawable.blank);
+ } else {
+ thumb.setImageBitmap(
+ BitmapFactory.decodeByteArray(data, 0, data.length));
+ }
+ // Now show the favicon
+ data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+ if (data == null) {
+ fav.setVisibility(View.GONE);
+ } else {
+ fav.setVisibility(View.VISIBLE);
+ fav.setImageBitmap(
+ BitmapFactory.decodeByteArray(data, 0, data.length));
+ }
+
+ return convertView;
+
+ }
if (position == 0 && !mCreateShortcut) {
AddNewBookmark b;
if (convertView instanceof AddNewBookmark) {
@@ -450,7 +416,7 @@ class BrowserBookmarksAdapter extends BaseAdapter {
b.setUrl(mCurrentPage);
return b;
}
- if (convertView == null || convertView instanceof AddNewBookmark) {
+ if (convertView == null || !(convertView instanceof BookmarkItem)) {
convertView = new BookmarkItem(mBookmarksPage);
}
bind((BookmarkItem)convertView, position);
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index dd34c14..428aa92 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -36,6 +36,7 @@ import android.text.IClipboard;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -43,7 +44,9 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView;
+import android.widget.GridView;
import android.widget.ListView;
+import android.widget.Toast;
/**
* View showing the user's bookmarks in the browser.
@@ -51,6 +54,9 @@ import android.widget.ListView;
public class BrowserBookmarksPage extends Activity implements
View.OnCreateContextMenuListener {
+ private boolean mGridMode;
+ private GridView mGridPage;
+ private View mVerticalList;
private BrowserBookmarksAdapter mBookmarksAdapter;
private static final int BOOKMARKS_SAVE = 1;
private boolean mMaxTabsOpen;
@@ -107,7 +113,13 @@ public class BrowserBookmarksPage extends Activity implements
break;
case R.id.copy_url_context_menu_id:
copy(getUrl(i.position));
-
+ break;
+ case R.id.homepage_context_menu_id:
+ BrowserSettings.getInstance().setHomePage(this,
+ getUrl(i.position));
+ Toast.makeText(this, R.string.homepage_set,
+ Toast.LENGTH_LONG).show();
+ break;
default:
return super.onContextItemSelected(item);
}
@@ -131,25 +143,29 @@ public class BrowserBookmarksPage extends Activity implements
((ViewGroup) mAddHeader.getParent()).
removeView(mAddHeader);
}
- ((AddNewBookmark) i.targetView).copyTo(mAddHeader);
+ mAddHeader.setUrl(getIntent().getStringExtra("url"));
menu.setHeaderView(mAddHeader);
return;
}
menu.setGroupVisible(R.id.ADD_MENU, false);
- BookmarkItem b = (BookmarkItem) i.targetView;
+ if (mMaxTabsOpen) {
+ menu.findItem(R.id.new_window_context_menu_id).setVisible(
+ false);
+ }
if (mContextHeader == null) {
mContextHeader = new BookmarkItem(BrowserBookmarksPage.this);
} else if (mContextHeader.getParent() != null) {
((ViewGroup) mContextHeader.getParent()).
removeView(mContextHeader);
}
- b.copyTo(mContextHeader);
- menu.setHeaderView(mContextHeader);
-
- if (mMaxTabsOpen) {
- menu.findItem(R.id.new_window_context_menu_id).setVisible(
- false);
+ if (mGridMode) {
+ mBookmarksAdapter.populateBookmarkItem(mContextHeader,
+ i.position);
+ } else {
+ BookmarkItem b = (BookmarkItem) i.targetView;
+ b.copyTo(mContextHeader);
}
+ menu.setHeaderView(mContextHeader);
}
/**
@@ -159,25 +175,61 @@ public class BrowserBookmarksPage extends Activity implements
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- setContentView(R.layout.browser_bookmarks_page);
- setTitle(R.string.browser_bookmarks_page_bookmarks_text);
-
if (Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) {
mCreateShortcut = true;
}
-
- mBookmarksAdapter = new BrowserBookmarksAdapter(this,
- getIntent().getStringExtra("url"), mCreateShortcut);
mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false);
- ListView listView = (ListView) findViewById(R.id.list);
- listView.setAdapter(mBookmarksAdapter);
- listView.setDrawSelectorOnTop(false);
- listView.setVerticalScrollBarEnabled(true);
- listView.setOnItemClickListener(mListener);
+ setTitle(R.string.browser_bookmarks_page_bookmarks_text);
+ mBookmarksAdapter = new BrowserBookmarksAdapter(this,
+ getIntent().getStringExtra("url"),
+ getIntent().getStringExtra("title"), mCreateShortcut);
+ switchViewMode(true);
+ }
- if (!mCreateShortcut) {
- listView.setOnCreateContextMenuListener(this);
+ /**
+ * Set the ContentView to be either the grid of thumbnails or the vertical
+ * list. Pass true to set it to the grid.
+ */
+ private void switchViewMode(boolean gridMode) {
+ mGridMode = gridMode;
+ mBookmarksAdapter.switchViewMode(gridMode);
+ if (mGridMode) {
+ if (mGridPage == null) {
+ mGridPage = new GridView(this);
+ mGridPage.setAdapter(mBookmarksAdapter);
+ mGridPage.setOnItemClickListener(mListener);
+ mGridPage.setNumColumns(GridView.AUTO_FIT);
+ // Keep this in sync with bookmark_thumb and
+ // BrowserActivity.updateScreenshot
+ mGridPage.setColumnWidth(100);
+ mGridPage.setFocusable(true);
+ mGridPage.setFocusableInTouchMode(true);
+ mGridPage.setSelector(android.R.drawable.gallery_thumb);
+ mGridPage.setVerticalSpacing(10);
+ if (!mCreateShortcut) {
+ mGridPage.setOnCreateContextMenuListener(this);
+ }
+ }
+ setContentView(mGridPage);
+ } else {
+ if (null == mVerticalList) {
+ LayoutInflater factory = LayoutInflater.from(this);
+ mVerticalList = factory.inflate(R.layout.browser_bookmarks_page,
+ null);
+
+ ListView listView
+ = (ListView) mVerticalList.findViewById(R.id.list);
+ listView.setAdapter(mBookmarksAdapter);
+ listView.setDrawSelectorOnTop(false);
+ listView.setVerticalScrollBarEnabled(true);
+ listView.setOnItemClickListener(mListener);
+
+ if (!mCreateShortcut) {
+ listView.setOnCreateContextMenuListener(this);
+ }
+ }
+ setContentView(mVerticalList);
}
}
@@ -196,7 +248,7 @@ public class BrowserBookmarksPage extends Activity implements
// It is possible that the view has been canceled when we get to
// this point as back has a higher priority
if (mCanceled) {
- android.util.Log.e("browser", "item clicked when dismising");
+ android.util.Log.e(LOGTAG, "item clicked when dismissing");
return;
}
if (!mCreateShortcut) {
@@ -294,12 +346,16 @@ public class BrowserBookmarksPage extends Activity implements
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case R.id.new_context_menu_id:
- saveCurrentPage();
- break;
-
- default:
- return super.onOptionsItemSelected(item);
+ case R.id.new_context_menu_id:
+ saveCurrentPage();
+ break;
+
+ case R.id.switch_mode_menu_id:
+ switchViewMode(!mGridMode);
+ break;
+
+ default:
+ return super.onOptionsItemSelected(item);
}
return true;
}
@@ -370,7 +426,7 @@ public class BrowserBookmarksPage extends Activity implements
/**
* Refresh the shown list after the database has changed.
*/
- public void refreshList() {
+ private void refreshList() {
mBookmarksAdapter.refreshList();
}
diff --git a/src/com/android/browser/BrowserDownloadAdapter.java b/src/com/android/browser/BrowserDownloadAdapter.java
index 38b83fe..16cb982 100644
--- a/src/com/android/browser/BrowserDownloadAdapter.java
+++ b/src/com/android/browser/BrowserDownloadAdapter.java
@@ -60,14 +60,14 @@ public class BrowserDownloadAdapter extends ResourceCursorAdapter {
public BrowserDownloadAdapter(Context context, int layout, Cursor c) {
super(context, layout, c);
mFilenameColumnId = c.getColumnIndexOrThrow(Downloads._DATA);
- mTitleColumnId = c.getColumnIndexOrThrow(Downloads.TITLE);
- mDescColumnId = c.getColumnIndexOrThrow(Downloads.DESCRIPTION);
- mStatusColumnId = c.getColumnIndexOrThrow(Downloads.STATUS);
- mTotalBytesColumnId = c.getColumnIndexOrThrow(Downloads.TOTAL_BYTES);
+ mTitleColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_TITLE);
+ mDescColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_DESCRIPTION);
+ mStatusColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
+ mTotalBytesColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_TOTAL_BYTES);
mCurrentBytesColumnId =
- c.getColumnIndexOrThrow(Downloads.CURRENT_BYTES);
- mMimetypeColumnId = c.getColumnIndexOrThrow(Downloads.MIMETYPE);
- mDateColumnId = c.getColumnIndexOrThrow(Downloads.LAST_MODIFICATION);
+ c.getColumnIndexOrThrow(Downloads.COLUMN_CURRENT_BYTES);
+ mMimetypeColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE);
+ mDateColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_LAST_MODIFICATION);
}
@Override
@@ -106,7 +106,7 @@ public class BrowserDownloadAdapter extends ResourceCursorAdapter {
// We have a filename, so we can build a title from that
title = new File(fullFilename).getName();
ContentValues values = new ContentValues();
- values.put(Downloads.TITLE, title);
+ values.put(Downloads.COLUMN_TITLE, title);
// assume "_id" is the first column for the cursor
context.getContentResolver().update(
ContentUris.withAppendedId(Downloads.CONTENT_URI,
diff --git a/src/com/android/browser/BrowserDownloadPage.java b/src/com/android/browser/BrowserDownloadPage.java
index 4397337..22e0e65 100644
--- a/src/com/android/browser/BrowserDownloadPage.java
+++ b/src/com/android/browser/BrowserDownloadPage.java
@@ -66,34 +66,30 @@ public class BrowserDownloadPage extends Activity
setTitle(getText(R.string.download_title));
mListView = (ListView) findViewById(R.id.list);
- LayoutInflater factory = LayoutInflater.from(this);
- View v = factory.inflate(R.layout.no_downloads, null);
- addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
- LayoutParams.FILL_PARENT));
- mListView.setEmptyView(v);
+ mListView.setEmptyView(findViewById(R.id.empty));
mDownloadCursor = managedQuery(Downloads.CONTENT_URI,
- new String [] {"_id", Downloads.TITLE, Downloads.STATUS,
- Downloads.TOTAL_BYTES, Downloads.CURRENT_BYTES,
- Downloads._DATA, Downloads.DESCRIPTION,
- Downloads.MIMETYPE, Downloads.LAST_MODIFICATION,
- Downloads.VISIBILITY},
+ new String [] {"_id", Downloads.COLUMN_TITLE, Downloads.COLUMN_STATUS,
+ Downloads.COLUMN_TOTAL_BYTES, Downloads.COLUMN_CURRENT_BYTES,
+ Downloads._DATA, Downloads.COLUMN_DESCRIPTION,
+ Downloads.COLUMN_MIME_TYPE, Downloads.COLUMN_LAST_MODIFICATION,
+ Downloads.COLUMN_VISIBILITY},
null, null);
// only attach everything to the listbox if we can access
// the download database. Otherwise, just show it empty
if (mDownloadCursor != null) {
mStatusColumnId =
- mDownloadCursor.getColumnIndexOrThrow(Downloads.STATUS);
+ mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
mIdColumnId =
mDownloadCursor.getColumnIndexOrThrow(Downloads._ID);
mTitleColumnId =
- mDownloadCursor.getColumnIndexOrThrow(Downloads.TITLE);
+ mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_TITLE);
// Create a list "controller" for the data
mDownloadAdapter = new BrowserDownloadAdapter(this,
R.layout.browser_download_item, mDownloadCursor);
-
+
mListView.setAdapter(mDownloadAdapter);
mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
mListView.setOnCreateContextMenuListener(this);
@@ -403,7 +399,7 @@ public class BrowserDownloadPage extends Activity
mDownloadCursor.getColumnIndexOrThrow(Downloads._DATA);
String filename = mDownloadCursor.getString(filenameColumnId);
int mimetypeColumnId =
- mDownloadCursor.getColumnIndexOrThrow(Downloads.MIMETYPE);
+ mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE);
String mimetype = mDownloadCursor.getString(mimetypeColumnId);
Uri path = Uri.parse(filename);
// If there is no scheme, then it must be a file
@@ -453,13 +449,13 @@ public class BrowserDownloadPage extends Activity
private void hideCompletedDownload() {
int status = mDownloadCursor.getInt(mStatusColumnId);
- int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(Downloads.VISIBILITY);
+ int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_VISIBILITY);
int visibility = mDownloadCursor.getInt(visibilityColumn);
if (Downloads.isStatusCompleted(status) &&
visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
ContentValues values = new ContentValues();
- values.put(Downloads.VISIBILITY, Downloads.VISIBILITY_VISIBLE);
+ values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE);
getContentResolver().update(
ContentUris.withAppendedId(Downloads.CONTENT_URI,
mDownloadCursor.getLong(mIdColumnId)), values, null, null);
diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java
index 42ca848..335d8fe 100644
--- a/src/com/android/browser/BrowserHistoryPage.java
+++ b/src/com/android/browser/BrowserHistoryPage.java
@@ -41,6 +41,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ContextMenu.ContextMenuInfo;
+import android.view.ViewStub;
import android.webkit.DateSorter;
import android.webkit.WebIconDatabase.IconListener;
import android.widget.AdapterView;
@@ -48,6 +49,7 @@ import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.TextView;
+import android.widget.Toast;
import java.util.List;
import java.util.Vector;
@@ -60,6 +62,7 @@ public class BrowserHistoryPage extends ExpandableListActivity {
private HistoryAdapter mAdapter;
private DateSorter mDateSorter;
private boolean mMaxTabsOpen;
+ private HistoryItem mContextHeader;
private final static String LOGTAG = "browser";
@@ -110,8 +113,7 @@ public class BrowserHistoryPage extends ExpandableListActivity {
setListAdapter(mAdapter);
final ExpandableListView list = getExpandableListView();
list.setOnCreateContextMenuListener(this);
- LayoutInflater factory = LayoutInflater.from(this);
- View v = factory.inflate(R.layout.empty_history, null);
+ View v = new ViewStub(this, R.layout.empty_history);
addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
list.setEmptyView(v);
@@ -165,7 +167,7 @@ public class BrowserHistoryPage extends ExpandableListActivity {
}
return super.onOptionsItemSelected(item);
}
-
+
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
@@ -180,12 +182,25 @@ public class BrowserHistoryPage extends ExpandableListActivity {
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);
@@ -200,8 +215,9 @@ public class BrowserHistoryPage extends ExpandableListActivity {
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);
@@ -210,7 +226,12 @@ public class BrowserHistoryPage extends ExpandableListActivity {
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);
@@ -222,6 +243,11 @@ public class BrowserHistoryPage extends ExpandableListActivity {
Browser.deleteFromHistory(getContentResolver(), url);
mAdapter.refreshData();
return true;
+ case R.id.homepage_context_menu_id:
+ BrowserSettings.getInstance().setHomePage(this, url);
+ Toast.makeText(this, R.string.homepage_set,
+ Toast.LENGTH_LONG).show();
+ return true;
default:
break;
}
@@ -267,18 +293,25 @@ public class BrowserHistoryPage extends ExpandableListActivity {
// Array for each of our bins. Each entry represents how many items are
// in that bin.
- int mItemMap[];
+ private int mItemMap[];
// This is our GroupCount. We will have at most DateSorter.DAY_COUNT
// bins, less if the user has no items in one or more bins.
- int mNumberOfBins;
- Vector<DataSetObserver> mObservers;
- Cursor mCursor;
+ private int mNumberOfBins;
+ private Vector<DataSetObserver> mObservers;
+ private Cursor mCursor;
HistoryAdapter() {
mObservers = new Vector<DataSetObserver>();
- String whereClause = Browser.BookmarkColumns.VISITS + " > 0 ";
- String orderBy = Browser.BookmarkColumns.DATE + " DESC";
+ final String whereClause = Browser.BookmarkColumns.VISITS + " > 0"
+ // In AddBookmarkPage, where we save new bookmarks, we add
+ // three visits to newly created bookmarks, so that
+ // bookmarks that have not been visited will show up in the
+ // most visited, and higher in the goto search box.
+ // However, this puts the site in the history, unless we
+ // ignore sites with a DATE of 0, which the next line does.
+ + " AND " + Browser.BookmarkColumns.DATE + " > 0";
+ final String orderBy = Browser.BookmarkColumns.DATE + " DESC";
mCursor = managedQuery(
Browser.BOOKMARKS_URI,
@@ -290,6 +323,9 @@ public class BrowserHistoryPage extends ExpandableListActivity {
}
void refreshData() {
+ if (mCursor.isClosed()) {
+ return;
+ }
mCursor.requery();
buildMap();
for (DataSetObserver o : mObservers) {
diff --git a/src/com/android/browser/BrowserHomepagePreference.java b/src/com/android/browser/BrowserHomepagePreference.java
index d4708c3..7324f24 100644
--- a/src/com/android/browser/BrowserHomepagePreference.java
+++ b/src/com/android/browser/BrowserHomepagePreference.java
@@ -50,8 +50,8 @@ public class BrowserHomepagePreference extends EditTextPreference implements
if (dialog != null) {
String url = s.toString();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(
- url.length() == 0 || url.equals("about:blank") ||
- Regex.WEB_URL_PATTERN.matcher(url).matches());
+ url.length() == 0 ||
+ BrowserActivity.ACCEPTED_URI_SCHEMA.matcher(url).matches());
}
}
diff --git a/src/com/android/browser/BrowserPreferencesPage.java b/src/com/android/browser/BrowserPreferencesPage.java
index 3b747d1..2348af0 100644
--- a/src/com/android/browser/BrowserPreferencesPage.java
+++ b/src/com/android/browser/BrowserPreferencesPage.java
@@ -17,19 +17,29 @@
package com.android.browser;
import java.util.List;
+import java.util.Vector;
+import java.util.Set;
+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.webkit.WebView;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import android.webkit.GeolocationPermissions;
import android.webkit.Plugin;
+import android.webkit.WebStorage;
+import android.webkit.WebView;
public class BrowserPreferencesPage extends PreferenceActivity
implements Preference.OnPreferenceChangeListener,
Preference.OnPreferenceClickListener {
+ String TAG = "BrowserPreferencesPage";
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -59,13 +69,36 @@ public class BrowserPreferencesPage extends PreferenceActivity
e = findPreference(BrowserSettings.PREF_DEFAULT_TEXT_ENCODING);
e.setOnPreferenceChangeListener(this);
-
+
if (BrowserSettings.getInstance().showDebugSettings()) {
addPreferencesFromResource(R.xml.debug_preferences);
}
-
+
e = findPreference(BrowserSettings.PREF_GEARS_SETTINGS);
e.setOnPreferenceClickListener(this);
+
+ PreferenceScreen websiteSettings = (PreferenceScreen)
+ findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS);
+ Intent intent = new Intent(this, WebsiteSettingsActivity.class);
+ websiteSettings.setIntent(intent);
+ }
+
+ /*
+ * We need to set the PreferenceScreen state in onResume(), as the number of
+ * origins with active features (WebStorage, Geolocation etc) could have
+ * changed after calling the WebsiteSettingsActivity.
+ */
+ @Override
+ protected void onResume() {
+ super.onResume();
+ PreferenceScreen websiteSettings = (PreferenceScreen)
+ findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS);
+ Set webStorageOrigins = WebStorage.getInstance().getOrigins();
+ Set geolocationOrigins =
+ GeolocationPermissions.getInstance().getOrigins();
+ websiteSettings.setEnabled(
+ ((webStorageOrigins != null) && !webStorageOrigins.isEmpty()) ||
+ ((geolocationOrigins != null) && !geolocationOrigins.isEmpty()));
}
@Override
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index a3ccf04..cdab3a3 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -19,8 +19,10 @@ package com.android.browser;
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.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
@@ -40,6 +42,7 @@ import android.os.Handler;
import android.preference.PreferenceManager;
import android.provider.Browser;
import android.provider.Settings;
+import android.provider.Browser.BookmarkColumns;
import android.server.search.SearchableInfo;
import android.text.TextUtils;
import android.text.util.Regex;
@@ -54,6 +57,7 @@ import java.util.regex.Pattern;
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";
@@ -96,6 +100,8 @@ public class BrowserProvider extends ContentProvider {
private static final int MAX_SUGGESTION_SHORT_ENTRIES = 3;
private static final int MAX_SUGGESTION_LONG_ENTRIES = 6;
+ private static final String MAX_SUGGESTION_LONG_ENTRIES_STRING =
+ Integer.valueOf(MAX_SUGGESTION_LONG_ENTRIES).toString();
// make sure that these match the index of TABLE_NAMES
private static final int URI_MATCH_BOOKMARKS = 0;
@@ -143,7 +149,8 @@ public class BrowserProvider extends ContentProvider {
// 15 -> 17 Set it up for the SearchManager
// 17 -> 18 Added favicon in bookmarks table for Home shortcuts
// 18 -> 19 Remove labels table
- private static final int DATABASE_VERSION = 19;
+ // 19 -> 20 Added thumbnail
+ private static final int DATABASE_VERSION = 20;
// Regular expression which matches http://, followed by some stuff, followed by
// optionally a trailing slash, all matched as separate groups.
@@ -215,7 +222,8 @@ public class BrowserProvider extends ContentProvider {
"created LONG," +
"description TEXT," +
"bookmark INTEGER," +
- "favicon BLOB DEFAULT NULL" +
+ "favicon BLOB DEFAULT NULL," +
+ "thumbnail BLOB DEFAULT NULL" +
");");
final CharSequence[] bookmarks = mContext.getResources()
@@ -242,9 +250,12 @@ public class BrowserProvider extends ContentProvider {
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
- + newVersion + ", which will destroy all old data");
+ + newVersion);
if (oldVersion == 18) {
db.execSQL("DROP TABLE IF EXISTS labels");
+ }
+ if (oldVersion <= 19) {
+ db.execSQL("ALTER TABLE bookmarks ADD COLUMN thumbnail BLOB DEFAULT NULL;");
} else {
db.execSQL("DROP TABLE IF EXISTS bookmarks");
db.execSQL("DROP TABLE IF EXISTS searches");
@@ -257,6 +268,7 @@ public class BrowserProvider extends ContentProvider {
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.
@@ -471,22 +483,22 @@ public class BrowserProvider extends ContentProvider {
case SUGGEST_COLUMN_ICON_1_ID:
if (mHistoryCount > mPos) {
if (mHistoryCursor.getInt(3) == 1) {
- return new Integer(
+ return Integer.valueOf(
R.drawable.ic_search_category_bookmark)
.toString();
} else {
- return new Integer(
+ return Integer.valueOf(
R.drawable.ic_search_category_history)
.toString();
}
} else {
- return new Integer(
+ return Integer.valueOf(
R.drawable.ic_search_category_suggest)
.toString();
}
case SUGGEST_COLUMN_ICON_2_ID:
- return new String("0");
+ return "0";
case SUGGEST_COLUMN_QUERY_ID:
if (mHistoryCount > mPos) {
@@ -637,7 +649,8 @@ public class BrowserProvider extends ContentProvider {
myArgs = null;
} else {
String like = selectionArgs[0] + "%";
- if (selectionArgs[0].startsWith("http")) {
+ if (selectionArgs[0].startsWith("http")
+ || selectionArgs[0].startsWith("file")) {
myArgs = new String[1];
myArgs[0] = like;
suggestSelection = selection;
@@ -655,8 +668,7 @@ public class BrowserProvider extends ContentProvider {
Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS],
SUGGEST_PROJECTION, suggestSelection, myArgs, null, null,
- ORDER_BY,
- (new Integer(MAX_SUGGESTION_LONG_ENTRIES)).toString());
+ ORDER_BY, MAX_SUGGESTION_LONG_ENTRIES_STRING);
if (match == URI_MATCH_BOOKMARKS_SUGGEST
|| Regex.WEB_URL_PATTERN.matcher(selectionArgs[0]).matches()) {
@@ -729,6 +741,7 @@ public class BrowserProvider extends ContentProvider {
@Override
public Uri insert(Uri url, ContentValues initialValues) {
+ boolean isBookmarkTable = false;
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int match = URI_MATCHER.match(url);
@@ -742,6 +755,7 @@ public class BrowserProvider extends ContentProvider {
uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
rowID);
}
+ isBookmarkTable = true;
break;
}
@@ -764,6 +778,15 @@ public class BrowserProvider extends ContentProvider {
throw new IllegalArgumentException("Unknown URL");
}
getContext().getContentResolver().notifyChange(uri, null);
+
+ // Back up the new bookmark set if we just inserted one.
+ // A row created when bookmarks are added from scratch will have
+ // bookmark=1 in the initial value set.
+ if (isBookmarkTable
+ && initialValues.containsKey(BookmarkColumns.BOOKMARK)
+ && initialValues.getAsInteger(BookmarkColumns.BOOKMARK) != 0) {
+ mBackupManager.dataChanged();
+ }
return uri;
}
@@ -776,20 +799,41 @@ public class BrowserProvider extends ContentProvider {
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 of reasons
+ boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID);
+ String id = null;
+
+ if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) {
StringBuilder sb = new StringBuilder();
if (where != null && where.length() > 0) {
sb.append("( ");
sb.append(where);
sb.append(" ) AND ");
}
+ id = url.getPathSegments().get(1);
sb.append("_id = ");
- sb.append(url.getPathSegments().get(1));
+ sb.append(id);
where = sb.toString();
}
+ ContentResolver cr = getContext().getContentResolver();
+
+ // we'lll need to back up the bookmark set if we are about to delete one
+ if (isBookmarkTable) {
+ Cursor cursor = cr.query(Browser.BOOKMARKS_URI,
+ new String[] { BookmarkColumns.BOOKMARK },
+ "_id = " + id, null, null);
+ if (cursor.moveToNext()) {
+ if (cursor.getInt(0) != 0) {
+ // yep, this record is a bookmark
+ mBackupManager.dataChanged();
+ }
+ }
+ cursor.close();
+ }
+
int count = db.delete(TABLE_NAMES[match % 10], where, whereArgs);
- getContext().getContentResolver().notifyChange(url, null);
+ cr.notifyChange(url, null);
return count;
}
@@ -803,20 +847,59 @@ public class BrowserProvider extends ContentProvider {
throw new IllegalArgumentException("Unknown URL");
}
- if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) {
+ String id = null;
+ boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID);
+ boolean changingBookmarks = false;
+
+ if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) {
StringBuilder sb = new StringBuilder();
if (where != null && where.length() > 0) {
sb.append("( ");
sb.append(where);
sb.append(" ) AND ");
}
+ id = url.getPathSegments().get(1);
sb.append("_id = ");
- sb.append(url.getPathSegments().get(1));
+ sb.append(id);
where = sb.toString();
}
+ ContentResolver cr = getContext().getContentResolver();
+
+ // Not all bookmark-table updates should be backed up. Look to see
+ // whether we changed the title, url, or "is a bookmark" state, and
+ // request a backup if so.
+ if (isBookmarkTable) {
+ // Alterations to the bookmark field inherently change the bookmark
+ // set, so we don't need to query the record; we know a priori that
+ // we will need to back up this change.
+ if (values.containsKey(BookmarkColumns.BOOKMARK)) {
+ changingBookmarks = true;
+ }
+ // changing the title or URL of a bookmark record requires a backup,
+ // but we don't know wether such an update is on a bookmark without
+ // querying the record
+ if (!changingBookmarks &&
+ (values.containsKey(BookmarkColumns.TITLE)
+ || values.containsKey(BookmarkColumns.URL))) {
+ // when isBookmarkTable is true, the 'id' var was assigned above
+ Cursor cursor = cr.query(Browser.BOOKMARKS_URI,
+ new String[] { BookmarkColumns.BOOKMARK },
+ "_id = " + id, null, null);
+ if (cursor.moveToNext()) {
+ changingBookmarks = (cursor.getInt(0) != 0);
+ }
+ cursor.close();
+ }
+
+ // if this *is* a bookmark row we're altering, we need to back it up.
+ if (changingBookmarks) {
+ mBackupManager.dataChanged();
+ }
+ }
+
int ret = db.update(TABLE_NAMES[match % 10], values, where, whereArgs);
- getContext().getContentResolver().notifyChange(url, null);
+ cr.notifyChange(url, null);
return ret;
}
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index a5e23c9..3ed6cf0 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -20,16 +20,22 @@ import com.google.android.providers.GoogleSettings.Partner;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
import android.webkit.CookieManager;
+import android.webkit.GeolocationPermissions;
import android.webkit.WebView;
import android.webkit.WebViewDatabase;
import android.webkit.WebIconDatabase;
import android.webkit.WebSettings;
+import android.webkit.WebStorage;
import android.preference.PreferenceManager;
import android.provider.Browser;
+import java.util.Set;
import java.util.HashMap;
import java.util.Observable;
@@ -48,7 +54,7 @@ import java.util.Observable;
*/
class BrowserSettings extends Observable {
- // Public variables for settings
+ // Private variables for settings
// NOTE: these defaults need to be kept in sync with the XML
// until the performance of PreferenceManager.setDefaultValues()
// is improved.
@@ -65,7 +71,21 @@ class BrowserSettings extends Observable {
private String homeUrl = "";
private boolean loginInitialized = false;
private boolean autoFitPage = true;
+ private boolean landscapeOnly = false;
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 long appCacheMaxSize = Long.MAX_VALUE;
+ private WebStorageSizeManager webStorageSizeManager;
+ private boolean domStorageEnabled = true;
+ private String jsFlags = "";
+ private boolean geolocationEnabled = true;
+
+ private final static String TAG = "BrowserSettings";
// Development settings
public WebSettings.LayoutAlgorithm layoutAlgorithm =
@@ -75,6 +95,11 @@ class BrowserSettings extends Observable {
private boolean tracing = false;
private boolean lightTouch = false;
private boolean navDump = false;
+
+ // By default the error console is shown once the user navigates to about:debug.
+ // The setting can be then toggled from the settings menu.
+ private boolean showConsole = true;
+
// Browser only settings
private boolean doFlick = false;
@@ -97,22 +122,27 @@ class BrowserSettings extends Observable {
"privacy_clear_form_data";
public final static String PREF_CLEAR_PASSWORDS =
"privacy_clear_passwords";
+ 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_WEBSITE_SETTINGS = "website_settings";
public final static String PREF_TEXT_SIZE = "text_size";
public final static String PREF_DEFAULT_ZOOM = "default_zoom";
public final static String PREF_DEFAULT_TEXT_ENCODING =
"default_text_encoding";
+ public final static String PREF_CLEAR_LOCATION_ACCESS =
+ "privacy_clear_location_access";
private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (Macintosh; " +
- "U; Intel Mac OS X 10_5_5; en-us) AppleWebKit/525.18 (KHTML, " +
- "like Gecko) Version/3.1.2 Safari/525.20.1";
+ "U; Intel Mac OS X 10_5_7; en-us) AppleWebKit/530.17 (KHTML, " +
+ "like Gecko) Version/4.0 Safari/530.17";
private static final String IPHONE_USERAGENT = "Mozilla/5.0 (iPhone; U; " +
- "CPU iPhone OS 2_2 like Mac OS X; en-us) AppleWebKit/525.18.1 " +
- "(KHTML, like Gecko) Version/3.1.1 Mobile/5G77 Safari/525.20";
+ "CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 " +
+ "(KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16";
// Value to truncate strings when adding them to a TextView within
// a ListView
@@ -157,7 +187,6 @@ class BrowserSettings extends Observable {
s.setLoadsImagesAutomatically(b.loadsImagesAutomatically);
s.setJavaScriptEnabled(b.javaScriptEnabled);
s.setPluginsEnabled(b.pluginsEnabled);
- s.setPluginsPath(b.pluginsPath);
s.setJavaScriptCanOpenWindowsAutomatically(
b.javaScriptCanOpenWindowsAutomatically);
s.setDefaultTextEncodingName(b.defaultTextEncodingName);
@@ -178,6 +207,24 @@ class BrowserSettings extends Observable {
s.setSupportMultipleWindows(true);
// Turn off file access
s.setAllowFileAccess(false);
+
+ s.setDatabasePath(b.databasePath);
+ s.setDatabaseEnabled(b.databaseEnabled);
+ s.setDomStorageEnabled(b.domStorageEnabled);
+ s.setWebStorageDefaultQuota(b.webStorageDefaultQuota);
+
+ // Turn on Application Caches.
+ s.setAppCachePath(b.appCachePath);
+ s.setAppCacheEnabled(b.appCacheEnabled);
+ s.setAppCacheMaxSize(b.appCacheMaxSize);
+
+ // Enable/Disable the error console.
+ b.mTabControl.getBrowserActivity().setShouldShowErrorConsole(
+ b.showDebugSettings && b.showConsole);
+
+ // Configure the Geolocation permissions manager to deny all
+ // permission requests if Geolocation is disabled in the browser.
+ // TODO(steveblock): Implement
}
}
@@ -197,6 +244,16 @@ class BrowserSettings extends Observable {
// Set the default value for the plugins path to the application's
// local directory.
pluginsPath = ctx.getDir("plugins", 0).getPath();
+ // Set the default value for the Application Caches path.
+ appCachePath = ctx.getDir("appcache", 0).getPath();
+ // Determine the maximum size of the application cache.
+ webStorageSizeManager = new WebStorageSizeManager(
+ ctx,
+ new WebStorageSizeManager.StatFsDiskInfo(appCachePath),
+ new WebStorageSizeManager.WebKitAppCacheInfo(appCachePath));
+ appCacheMaxSize = webStorageSizeManager.getAppCacheMaxSize();
+ // Set the default value for the Database path.
+ databasePath = ctx.getDir("databases", 0).getPath();
homeUrl = getFactoryResetHomeUrl(ctx);
@@ -219,6 +276,15 @@ class BrowserSettings extends Observable {
pluginsEnabled = p.getBoolean("enable_plugins",
pluginsEnabled);
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);
+ domStorageEnabled = p.getBoolean("enable_domstorage",
+ domStorageEnabled);
+ appCachePath = p.getString("appcache_path", appCachePath);
javaScriptCanOpenWindowsAutomatically = !p.getBoolean(
"block_popup_windows",
!javaScriptCanOpenWindowsAutomatically);
@@ -238,6 +304,14 @@ class BrowserSettings extends Observable {
zoomDensity = WebSettings.ZoomDensity.valueOf(
p.getString(PREF_DEFAULT_ZOOM, zoomDensity.name()));
autoFitPage = p.getBoolean("autofit_pages", autoFitPage);
+ boolean landscapeOnlyTemp =
+ p.getBoolean("landscape_only", landscapeOnly);
+ if (landscapeOnlyTemp != landscapeOnly) {
+ landscapeOnly = landscapeOnlyTemp;
+ mTabControl.getBrowserActivity().setRequestedOrientation(
+ landscapeOnly ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+ : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ }
useWideViewPort = true; // use wide view port for either setting
if (autoFitPage) {
layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS;
@@ -274,6 +348,21 @@ class BrowserSettings extends Observable {
doFlick = p.getBoolean("enable_flick", doFlick);
userAgent = Integer.parseInt(p.getString("user_agent", "0"));
}
+ // JS flags is loaded from DB even if showDebugSettings is false,
+ // so that it can be set once and be effective all the time.
+ jsFlags = p.getString("js_engine_flags", "");
+
+ // Read the setting for showing/hiding the JS Console always so that should the
+ // user enable debug settings, we already know if we should show the console.
+ // The user will never see the console unless they navigate to about:debug,
+ // regardless of the setting we read here. This setting is only used after debug
+ // is enabled.
+ showConsole = p.getBoolean("javascript_console", showConsole);
+ mTabControl.getBrowserActivity().setShouldShowErrorConsole(
+ showDebugSettings && showConsole);
+
+ geolocationEnabled = p.getBoolean("enable_geolocation", geolocationEnabled);
+
update();
}
@@ -285,6 +374,14 @@ class BrowserSettings extends Observable {
return homeUrl;
}
+ public String getJsFlags() {
+ return jsFlags;
+ }
+
+ public WebStorageSizeManager getWebStorageSizeManager() {
+ return webStorageSizeManager;
+ }
+
public void setHomePage(Context context, String url) {
Editor ed = PreferenceManager.
getDefaultSharedPreferences(context).edit();
@@ -436,14 +533,39 @@ class BrowserSettings extends Observable {
db.clearHttpAuthUsernamePassword();
}
- /*package*/ void resetDefaultPreferences(Context context) {
+ private void maybeDisableWebsiteSettings(Context context) {
+ Set webStorageOrigins = WebStorage.getInstance().getOrigins();
+ Set geolocationOrigins =
+ GeolocationPermissions.getInstance().getOrigins();
+ if (((webStorageOrigins == null) || webStorageOrigins.isEmpty()) &&
+ ((geolocationOrigins == null) || geolocationOrigins.isEmpty())) {
+ PreferenceActivity activity = (PreferenceActivity) context;
+ PreferenceScreen screen = (PreferenceScreen)
+ activity.findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS);
+ screen.setEnabled(false);
+ }
+ }
+
+ /*package*/ void clearDatabases(Context context) {
+ WebStorage.getInstance().deleteAllData();
+ maybeDisableWebsiteSettings(context);
+ }
+
+ /*package*/ void clearLocationAccess(Context context) {
+ GeolocationPermissions.getInstance().clearAll();
+ maybeDisableWebsiteSettings(context);
+ }
+
+ /*package*/ void resetDefaultPreferences(Context ctx) {
SharedPreferences p =
- PreferenceManager.getDefaultSharedPreferences(context);
+ PreferenceManager.getDefaultSharedPreferences(ctx);
p.edit().clear().commit();
- PreferenceManager.setDefaultValues(context, R.xml.browser_preferences,
+ PreferenceManager.setDefaultValues(ctx, R.xml.browser_preferences,
true);
// reset homeUrl
- setHomePage(context, getFactoryResetHomeUrl(context));
+ setHomePage(ctx, getFactoryResetHomeUrl(ctx));
+ // reset appcache max size
+ appCacheMaxSize = webStorageSizeManager.getAppCacheMaxSize();
}
private String getFactoryResetHomeUrl(Context context) {
diff --git a/src/com/android/browser/BrowserYesNoPreference.java b/src/com/android/browser/BrowserYesNoPreference.java
index 65cde71..e380e57 100644
--- a/src/com/android/browser/BrowserYesNoPreference.java
+++ b/src/com/android/browser/BrowserYesNoPreference.java
@@ -38,6 +38,7 @@ class BrowserYesNoPreference extends YesNoPreference {
Context context = getContext();
if (BrowserSettings.PREF_CLEAR_CACHE.equals(getKey())) {
BrowserSettings.getInstance().clearCache(context);
+ BrowserSettings.getInstance().clearDatabases(context);
} else if (BrowserSettings.PREF_CLEAR_COOKIES.equals(getKey())) {
BrowserSettings.getInstance().clearCookies(context);
} else if (BrowserSettings.PREF_CLEAR_HISTORY.equals(getKey())) {
@@ -50,6 +51,9 @@ class BrowserYesNoPreference extends YesNoPreference {
getKey())) {
BrowserSettings.getInstance().resetDefaultPreferences(context);
setEnabled(true);
+ } else if (BrowserSettings.PREF_CLEAR_LOCATION_ACCESS.equals(
+ getKey())) {
+ BrowserSettings.getInstance().clearLocationAccess(context);
}
}
}
diff --git a/src/com/android/browser/ErrorConsoleView.java b/src/com/android/browser/ErrorConsoleView.java
new file mode 100644
index 0000000..56f663b
--- /dev/null
+++ b/src/com/android/browser/ErrorConsoleView.java
@@ -0,0 +1,339 @@
+/*
+ * 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.database.DataSetObserver;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.webkit.WebView;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.TwoLineListItem;
+
+import java.util.Vector;
+
+/* package */ class ErrorConsoleView extends LinearLayout {
+
+ /**
+ * Define some constants to describe the visibility of the error console.
+ */
+ public static final int SHOW_MINIMIZED = 0;
+ public static final int SHOW_MAXIMIZED = 1;
+ public static final int SHOW_NONE = 2;
+
+ private TextView mConsoleHeader;
+ private ErrorConsoleListView mErrorList;
+ private LinearLayout mEvalJsViewGroup;
+ private EditText mEvalEditText;
+ private Button mEvalButton;
+ private WebView mWebView;
+ private int mCurrentShowState = SHOW_NONE;
+
+ private boolean mSetupComplete = false;
+
+ // Before we've been asked to display the console, cache any messages that should
+ // be added to the console. Then when we do display the console, add them to the view
+ // then.
+ private Vector<ErrorConsoleMessage> mErrorMessageCache;
+
+ public ErrorConsoleView(Context context) {
+ super(context);
+ }
+
+ public ErrorConsoleView(Context context, AttributeSet attributes) {
+ super(context, attributes);
+ }
+
+ private void commonSetupIfNeeded() {
+ if (mSetupComplete) {
+ return;
+ }
+
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.error_console, this);
+
+ // Get references to each ui element.
+ mConsoleHeader = (TextView) findViewById(R.id.error_console_header_id);
+ mErrorList = (ErrorConsoleListView) findViewById(R.id.error_console_list_id);
+ mEvalJsViewGroup = (LinearLayout) findViewById(R.id.error_console_eval_view_group_id);
+ mEvalEditText = (EditText) findViewById(R.id.error_console_eval_text_id);
+ mEvalButton = (Button) findViewById(R.id.error_console_eval_button_id);
+
+ mEvalButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ // Send the javascript to be evaluated to webkit as a javascript: url
+ // TODO: Can we expose access to webkit's JS interpreter here and evaluate it that
+ // way? Note that this is called on the UI thread so we will need to post a message
+ // to the WebCore thread to implement this.
+ if (mWebView != null) {
+ mWebView.loadUrl("javascript:" + mEvalEditText.getText());
+ }
+
+ mEvalEditText.setText("");
+ }
+ });
+
+ // Make clicking on the console title bar min/maximse it.
+ mConsoleHeader.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mCurrentShowState == SHOW_MINIMIZED) {
+ showConsole(SHOW_MAXIMIZED);
+ } else {
+ showConsole(SHOW_MINIMIZED);
+ }
+ }
+ });
+
+ // Add any cached messages to the list now that we've assembled the view.
+ if (mErrorMessageCache != null) {
+ for (ErrorConsoleMessage msg : mErrorMessageCache) {
+ mErrorList.addErrorMessage(msg.getMessage(), msg.getSourceID(), msg.getLineNumber());
+ }
+ mErrorMessageCache.clear();
+ }
+
+ mSetupComplete = true;
+ }
+
+ /**
+ * Adds a message to the set of messages the console uses.
+ */
+ public void addErrorMessage(String msg, String sourceId, int lineNumber) {
+ if (mSetupComplete) {
+ mErrorList.addErrorMessage(msg, sourceId, lineNumber);
+ } else {
+ if (mErrorMessageCache == null) {
+ mErrorMessageCache = new Vector<ErrorConsoleMessage>();
+ }
+ mErrorMessageCache.add(new ErrorConsoleMessage(msg, sourceId, lineNumber));
+ }
+ }
+
+ /**
+ * Removes all error messages from the console.
+ */
+ public void clearErrorMessages() {
+ if (mSetupComplete) {
+ mErrorList.clearErrorMessages();
+ } else if (mErrorMessageCache != null) {
+ mErrorMessageCache.clear();
+ }
+ }
+
+ /**
+ * Returns the current number of errors displayed in the console.
+ */
+ public int numberOfErrors() {
+ if (mSetupComplete) {
+ return mErrorList.getCount();
+ } else {
+ return (mErrorMessageCache == null) ? 0 : mErrorMessageCache.size();
+ }
+ }
+
+ /**
+ * Sets the webview that this console is associated with. Currently this is used so
+ * we can call into webkit to evaluate JS expressions in the console.
+ */
+ public void setWebView(WebView webview) {
+ mWebView = webview;
+ }
+
+ /**
+ * Sets the visibility state of the console.
+ */
+ public void showConsole(int show_state) {
+ commonSetupIfNeeded();
+ switch (show_state) {
+ case SHOW_MINIMIZED:
+ mConsoleHeader.setVisibility(View.VISIBLE);
+ mConsoleHeader.setText(R.string.error_console_header_text_minimized);
+ mErrorList.setVisibility(View.GONE);
+ mEvalJsViewGroup.setVisibility(View.GONE);
+ break;
+
+ case SHOW_MAXIMIZED:
+ mConsoleHeader.setVisibility(View.VISIBLE);
+ mConsoleHeader.setText(R.string.error_console_header_text_maximized);
+ mErrorList.setVisibility(View.VISIBLE);
+ mEvalJsViewGroup.setVisibility(View.VISIBLE);
+ break;
+
+ case SHOW_NONE:
+ mConsoleHeader.setVisibility(View.GONE);
+ mErrorList.setVisibility(View.GONE);
+ mEvalJsViewGroup.setVisibility(View.GONE);
+ break;
+ }
+ mCurrentShowState = show_state;
+ }
+
+ /**
+ * Returns the current visibility state of the console.
+ */
+ public int getShowState() {
+ if (mSetupComplete) {
+ return mCurrentShowState;
+ } else {
+ return SHOW_NONE;
+ }
+ }
+
+ /**
+ * This class extends ListView to implement the View that will actually display the set of
+ * errors encountered on the current page.
+ */
+ private static class ErrorConsoleListView extends ListView {
+ // An adapter for this View that contains a list of error messages.
+ private ErrorConsoleMessageList mConsoleMessages;
+
+ public ErrorConsoleListView(Context context, AttributeSet attributes) {
+ super(context, attributes);
+ mConsoleMessages = new ErrorConsoleMessageList(context);
+ setAdapter(mConsoleMessages);
+ }
+
+ public void addErrorMessage(String msg, String sourceId, int lineNumber) {
+ mConsoleMessages.add(msg, sourceId, lineNumber);
+ setSelection(mConsoleMessages.getCount());
+ }
+
+ public void clearErrorMessages() {
+ mConsoleMessages.clear();
+ }
+
+ /**
+ * This class is an adapter for ErrorConsoleListView that contains the error console
+ * message data.
+ */
+ private class ErrorConsoleMessageList extends android.widget.BaseAdapter
+ implements android.widget.ListAdapter {
+
+ private Vector<ErrorConsoleMessage> mMessages;
+ private LayoutInflater mInflater;
+
+ public ErrorConsoleMessageList(Context context) {
+ mMessages = new Vector<ErrorConsoleMessage>();
+ mInflater = (LayoutInflater)context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ /**
+ * Add a new message to the list and update the View.
+ */
+ public void add(String msg, String sourceID, int lineNumber) {
+ mMessages.add(new ErrorConsoleMessage(msg, sourceID, lineNumber));
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Remove all messages from the list and update the view.
+ */
+ public void clear() {
+ mMessages.clear();
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return false;
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public Object getItem(int position) {
+ return mMessages.get(position);
+ }
+
+ public int getCount() {
+ return mMessages.size();
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ /**
+ * Constructs a TwoLineListItem for the error at position.
+ */
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+ ErrorConsoleMessage error = mMessages.get(position);
+
+ if (error == null) {
+ return null;
+ }
+
+ if (convertView == null) {
+ view = mInflater.inflate(android.R.layout.two_line_list_item, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ TextView headline = (TextView) view.findViewById(android.R.id.text1);
+ TextView subText = (TextView) view.findViewById(android.R.id.text2);
+ headline.setText(error.getSourceID() + ":" + error.getLineNumber());
+ subText.setText(error.getMessage());
+ return view;
+ }
+
+ }
+ }
+
+ /**
+ * This class holds the data for a single error message in the console.
+ */
+ private static class ErrorConsoleMessage {
+ private String mMessage;
+ private String mSourceID;
+ private int mLineNumber;
+
+ public ErrorConsoleMessage(String msg, String sourceID, int lineNumber) {
+ mMessage = msg;
+ mSourceID = sourceID;
+ mLineNumber = lineNumber;
+ }
+
+ public String getMessage() {
+ return mMessage;
+ }
+
+ public String getSourceID() {
+ return mSourceID;
+ }
+
+ public int getLineNumber() {
+ return mLineNumber;
+ }
+ }
+}
diff --git a/src/com/android/browser/FetchUrlMimeType.java b/src/com/android/browser/FetchUrlMimeType.java
index 8578643..c585dbb 100644
--- a/src/com/android/browser/FetchUrlMimeType.java
+++ b/src/com/android/browser/FetchUrlMimeType.java
@@ -58,7 +58,7 @@ class FetchUrlMimeType extends AsyncTask<ContentValues, String, String> {
mValues = values[0];
// Check to make sure we have a URI to download
- String uri = mValues.getAsString(Downloads.URI);
+ String uri = mValues.getAsString(Downloads.COLUMN_URI);
if (uri == null || uri.length() == 0) {
return null;
}
@@ -66,15 +66,15 @@ class FetchUrlMimeType extends AsyncTask<ContentValues, String, String> {
// User agent is likely to be null, though the AndroidHttpClient
// seems ok with that.
AndroidHttpClient client = AndroidHttpClient.newInstance(
- mValues.getAsString(Downloads.USER_AGENT));
+ mValues.getAsString(Downloads.COLUMN_USER_AGENT));
HttpHead request = new HttpHead(uri);
- String cookie = mValues.getAsString(Downloads.COOKIE_DATA);
+ String cookie = mValues.getAsString(Downloads.COLUMN_COOKIE_DATA);
if (cookie != null && cookie.length() > 0) {
request.addHeader("Cookie", cookie);
}
- String referer = mValues.getAsString(Downloads.REFERER);
+ String referer = mValues.getAsString(Downloads.COLUMN_REFERER);
if (referer != null && referer.length() > 0) {
request.addHeader("Referer", referer);
}
@@ -111,19 +111,19 @@ class FetchUrlMimeType extends AsyncTask<ContentValues, String, String> {
@Override
public void onPostExecute(String mimeType) {
if (mimeType != null) {
- String url = mValues.getAsString(Downloads.URI);
+ String url = mValues.getAsString(Downloads.COLUMN_URI);
if (mimeType.equalsIgnoreCase("text/plain") ||
mimeType.equalsIgnoreCase("application/octet-stream")) {
String newMimeType =
MimeTypeMap.getSingleton().getMimeTypeFromExtension(
MimeTypeMap.getFileExtensionFromUrl(url));
if (newMimeType != null) {
- mValues.put(Downloads.MIMETYPE, newMimeType);
+ mValues.put(Downloads.COLUMN_MIME_TYPE, newMimeType);
}
}
String filename = URLUtil.guessFileName(url,
null, mimeType);
- mValues.put(Downloads.FILENAME_HINT, filename);
+ mValues.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
}
// Start the download
diff --git a/src/com/android/browser/FindDialog.java b/src/com/android/browser/FindDialog.java
index 6e9574c..2049bd0 100644
--- a/src/com/android/browser/FindDialog.java
+++ b/src/com/android/browser/FindDialog.java
@@ -42,7 +42,6 @@ import android.widget.TextView;
private BrowserActivity mBrowserActivity;
// Views with which the user can interact.
- private View mOk;
private EditText mEditText;
private View mNextButton;
private View mPrevButton;
@@ -129,7 +128,6 @@ import android.widget.TextView;
button = findViewById(R.id.done);
button.setOnClickListener(mFindCancelListener);
- mOk = button;
mMatches = (TextView) findViewById(R.id.matches);
mMatchesView = findViewById(R.id.matches_view);
@@ -143,23 +141,14 @@ import android.widget.TextView;
mBrowserActivity.closeFind();
mWebView.clearMatches();
}
-
+
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- int code = event.getKeyCode();
- boolean up = event.getAction() == KeyEvent.ACTION_UP;
- switch (code) {
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_ENTER:
- if (!mEditText.hasFocus()) {
- break;
- }
- if (up) {
- findNext();
- }
- return true;
- default:
- break;
+ if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
+ && event.getAction() == KeyEvent.ACTION_UP
+ && mEditText.hasFocus()) {
+ findNext();
+ return true;
}
return super.dispatchKeyEvent(event);
}
diff --git a/src/com/android/browser/HistoryItem.java b/src/com/android/browser/HistoryItem.java
index 55e43f0..e8f15b1 100644
--- a/src/com/android/browser/HistoryItem.java
+++ b/src/com/android/browser/HistoryItem.java
@@ -17,23 +17,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.Bitmap;
-import android.net.Uri;
import android.provider.Browser;
-import android.util.Log;
import android.view.View;
-import android.webkit.WebIconDatabase;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
-import android.widget.Toast;
-
-import java.util.Date;
/**
* Layout representing a history item in the classic history viewer.
@@ -54,61 +44,18 @@ import java.util.Date;
mListener = new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
- ContentResolver cr = mContext.getContentResolver();
- Cursor cursor = cr.query(
- Browser.BOOKMARKS_URI,
- Browser.HISTORY_PROJECTION,
- "url = ?",
- new String[] { mUrl },
- null);
- boolean first = cursor.moveToFirst();
- // Should be in the database no matter what
- if (!first) {
- throw new AssertionError("URL is not in the database!");
- }
if (isChecked) {
- // Add to bookmarks
- // FIXME: Share code with AddBookmarkPage.java
- ContentValues map = new ContentValues();
- map.put(Browser.BookmarkColumns.CREATED,
- new Date().getTime());
- map.put(Browser.BookmarkColumns.TITLE, getName());
- map.put(Browser.BookmarkColumns.BOOKMARK, 1);
- try {
- cr.update(Browser.BOOKMARKS_URI, map,
- "_id = " + cursor.getInt(0), null);
- } catch (IllegalStateException e) {
- Log.e("HistoryItem", "no database!");
- }
- WebIconDatabase.getInstance().retainIconForPageUrl(mUrl);
- // catch IllegalStateException?
- Toast.makeText(mContext, R.string.added_to_bookmarks,
- Toast.LENGTH_LONG).show();
+ Bookmarks.addBookmark(mContext,
+ mContext.getContentResolver(), mUrl, getName(), true);
} else {
- // Remove from bookmarks
- // FIXME: This code should be shared with
- // BrowserBookmarksAdapter.java
- WebIconDatabase.getInstance().releaseIconForPageUrl(mUrl);
- Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
- cursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
- // It is no longer a bookmark, but it is still a visited
- // site.
- ContentValues values = new ContentValues();
- values.put(Browser.BookmarkColumns.BOOKMARK, 0);
- try {
- cr.update(uri, values, null, null);
- } catch (IllegalStateException e) {
- Log.e("HistoryItem", "no database!");
- }
- Toast.makeText(mContext, R.string.removed_from_bookmarks,
- Toast.LENGTH_LONG).show();
+ Bookmarks.removeFromBookmarks(mContext,
+ mContext.getContentResolver(), mUrl);
}
- cursor.deactivate();
}
};
}
- void copyTo(HistoryItem item) {
+ /* package */ void copyTo(HistoryItem item) {
item.mTextView.setText(mTextView.getText());
item.mUrlText.setText(mUrlText.getText());
item.setIsBookmark(mStar.isChecked());
@@ -116,10 +63,17 @@ import java.util.Date;
}
/**
+ * 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/MostVisitedActivity.java b/src/com/android/browser/MostVisitedActivity.java
index 704ee27..90052d3 100644
--- a/src/com/android/browser/MostVisitedActivity.java
+++ b/src/com/android/browser/MostVisitedActivity.java
@@ -35,6 +35,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.ViewStub;
import java.util.Vector;
@@ -50,8 +51,7 @@ public class MostVisitedActivity extends ListActivity {
.addListener(new IconReceiver());
setListAdapter(mAdapter);
ListView list = getListView();
- LayoutInflater factory = LayoutInflater.from(this);
- View v = factory.inflate(R.layout.empty_history, null);
+ View v = new ViewStub(this, R.layout.empty_history);
addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
list.setEmptyView(v);
@@ -84,9 +84,9 @@ public class MostVisitedActivity extends ListActivity {
private Vector<DataSetObserver> mObservers;
private Cursor mCursor;
// These correspond with projection below.
- private final int mUrlIndex = 0;
- private final int mTitleIndex = 1;
- private final int mBookmarkIndex = 2;
+ private static final int mUrlIndex = 0;
+ private static final int mTitleIndex = 1;
+ private static final int mBookmarkIndex = 2;
MyAdapter() {
mObservers = new Vector<DataSetObserver>();
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index 575be8d..c7c3e3f 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -195,6 +195,8 @@ class TabControl {
// url has not changed.
private String mOriginalUrl;
+ private ErrorConsoleView mErrorConsole;
+
// Construct a new tab
private Tab(WebView w, boolean closeOnExit, String appId, String url) {
mMainView = w;
@@ -388,6 +390,28 @@ class TabControl {
}
/**
+ * Return the current tab's error console. Creates the console if createIfNEcessary
+ * is true and we haven't already created the console.
+ * @param createIfNecessary Flag to indicate if the console should be created if it has
+ * not been already.
+ * @return The current tab's error console, or null if one has not been created and
+ * createIfNecessary is false.
+ */
+ ErrorConsoleView getCurrentErrorConsole(boolean createIfNecessary) {
+ Tab t = getTab(mCurrentTab);
+ if (t == null) {
+ return null;
+ }
+
+ if (createIfNecessary && t.mErrorConsole == null) {
+ t.mErrorConsole = new ErrorConsoleView(mActivity);
+ t.mErrorConsole.setWebView(t.mMainView);
+ }
+
+ return t.mErrorConsole;
+ }
+
+ /**
* Return the current tab's top-level WebView. This can return a subwindow
* if one exists.
* @return The top-level WebView of the current tab.
@@ -446,6 +470,9 @@ class TabControl {
* @return index of Tab or -1 if not found
*/
int getTabIndex(Tab tab) {
+ if (tab == null) {
+ return -1;
+ }
return mTabs.indexOf(tab);
}
@@ -681,11 +708,11 @@ class TabControl {
return;
}
- // free the WebView cache
- Log.w(LOGTAG, "Free WebView cache");
+ // free the WebView's unused memory (this includes the cache)
+ Log.w(LOGTAG, "Free WebView's unused memory and cache");
WebView view = getCurrentWebView();
if (view != null) {
- view.clearCache(false);
+ view.freeMemory();
}
// force a gc
System.gc();
@@ -801,6 +828,45 @@ class TabControl {
return null;
}
+ // This method checks if a non-app tab (one created within the browser)
+ // matches the given url.
+ private boolean tabMatchesUrl(Tab t, String url) {
+ if (t.mAppId != null) {
+ return false;
+ } else if (t.mMainView == null) {
+ return false;
+ } else if (url.equals(t.mMainView.getUrl()) ||
+ url.equals(t.mMainView.getOriginalUrl())) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return the tab that has no app id associated with it and the url of the
+ * tab matches the given url.
+ * @param url The url to search for.
+ */
+ Tab findUnusedTabWithUrl(String url) {
+ if (url == null) {
+ return null;
+ }
+ // Check the current tab first.
+ Tab t = getCurrentTab();
+ if (t != null && tabMatchesUrl(t, url)) {
+ return t;
+ }
+ // Now check all the rest.
+ final int size = getTabCount();
+ for (int i = 0; i < size; i++) {
+ t = getTab(i);
+ if (tabMatchesUrl(t, url)) {
+ return t;
+ }
+ }
+ return null;
+ }
+
/**
* Recreate the main WebView of the given tab. Returns true if the WebView
* was deleted.
@@ -865,6 +931,48 @@ class TabControl {
return setCurrentTab(newTab, false);
}
+ /*package*/ void pauseCurrentTab() {
+ Tab t = getCurrentTab();
+ if (t != null) {
+ t.mMainView.onPause();
+ if (t.mSubView != null) {
+ t.mSubView.onPause();
+ }
+ }
+ }
+
+ /*package*/ void resumeCurrentTab() {
+ Tab t = getCurrentTab();
+ if (t != null) {
+ t.mMainView.onResume();
+ if (t.mSubView != null) {
+ t.mSubView.onResume();
+ }
+ }
+ }
+
+ private void putViewInForeground(WebView v, WebViewClient vc,
+ WebChromeClient cc) {
+ v.setWebViewClient(vc);
+ v.setWebChromeClient(cc);
+ v.setOnCreateContextMenuListener(mActivity);
+ v.setDownloadListener(mActivity);
+ v.onResume();
+ }
+
+ private void putViewInBackground(WebView v) {
+ // Set an empty callback so that default actions are not triggered.
+ v.setWebViewClient(mEmptyClient);
+ v.setWebChromeClient(mBackgroundChromeClient);
+ v.setOnCreateContextMenuListener(null);
+ // Leave the DownloadManager attached so that downloads can start in
+ // a non-active window. This can happen when going to a site that does
+ // a redirect after a period of time. The user could have switched to
+ // another tab while waiting for the download to start.
+ v.setDownloadListener(mActivity);
+ v.onPause();
+ }
+
/**
* If force is true, this method skips the check for newTab == current.
*/
@@ -890,7 +998,6 @@ class TabControl {
mTabQueue.add(newTab);
WebView mainView;
- WebView subView;
// Display the new current tab
mCurrentTab = mTabs.indexOf(newTab);
@@ -900,17 +1007,12 @@ class TabControl {
// Same work as in createNewTab() except don't do new Tab()
newTab.mMainView = mainView = createNewWebView();
}
- mainView.setWebViewClient(mActivity.getWebViewClient());
- mainView.setWebChromeClient(mActivity.getWebChromeClient());
- mainView.setOnCreateContextMenuListener(mActivity);
- mainView.setDownloadListener(mActivity);
+ putViewInForeground(mainView, mActivity.getWebViewClient(),
+ mActivity.getWebChromeClient());
// Add the subwindow if it exists
if (newTab.mSubViewContainer != null) {
- subView = newTab.mSubView;
- subView.setWebViewClient(newTab.mSubViewClient);
- subView.setWebChromeClient(newTab.mSubViewChromeClient);
- subView.setOnCreateContextMenuListener(mActivity);
- subView.setDownloadListener(mActivity);
+ putViewInForeground(newTab.mSubView, newTab.mSubViewClient,
+ newTab.mSubViewChromeClient);
}
if (needRestore) {
// Have to finish setCurrentTab work before calling restoreState
@@ -925,23 +1027,9 @@ class TabControl {
* Put the tab in the background using all the empty/background clients.
*/
private void putTabInBackground(Tab t) {
- WebView mainView = t.mMainView;
- // Set an empty callback so that default actions are not triggered.
- mainView.setWebViewClient(mEmptyClient);
- mainView.setWebChromeClient(mBackgroundChromeClient);
- mainView.setOnCreateContextMenuListener(null);
- // Leave the DownloadManager attached so that downloads can start in
- // a non-active window. This can happen when going to a site that does
- // a redirect after a period of time. The user could have switched to
- // another tab while waiting for the download to start.
- mainView.setDownloadListener(mActivity);
- WebView subView = t.mSubView;
- if (subView != null) {
- // Set an empty callback so that default actions are not triggered.
- subView.setWebViewClient(mEmptyClient);
- subView.setWebChromeClient(mBackgroundChromeClient);
- subView.setOnCreateContextMenuListener(null);
- subView.setDownloadListener(mActivity);
+ putViewInBackground(t.mMainView);
+ if (t.mSubView != null) {
+ putViewInBackground(t.mSubView);
}
}
@@ -1041,6 +1129,11 @@ class TabControl {
data.mScale = w.getScale();
data.mScrollX = w.getScrollX();
data.mScrollY = w.getScrollY();
+
+ // Remember the old picture if possible.
+ if (t.mPickerData != null) {
+ data.mPicture = t.mPickerData.mPicture;
+ }
t.mPickerData = data;
}
diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java
new file mode 100644
index 0000000..f534a03
--- /dev/null
+++ b/src/com/android/browser/TitleBar.java
@@ -0,0 +1,170 @@
+/*
+ * 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.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.webkit.WebView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+public class TitleBar extends LinearLayout {
+ private TextView mTitle;
+ private TextView mUrl;
+ private ImageView mLftButton;
+ private Drawable mBookmarkDrawable;
+ private View mRtButton;
+ private View mDivider;
+ private ProgressBar mCircularProgress;
+ private ProgressBar mHorizontalProgress;
+ private ImageView mFavicon;
+ private ImageView mLockIcon;
+ private boolean mInLoad;
+ private boolean mTitleSet;
+
+ public TitleBar(Context context) {
+ this(context, null);
+ }
+
+ public TitleBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ LayoutInflater factory = LayoutInflater.from(context);
+ factory.inflate(R.layout.title_bar, this);
+
+ mTitle = (TextView) findViewById(R.id.title);
+ mUrl = (TextView) findViewById(R.id.url);
+
+ mLftButton = (ImageView) findViewById(R.id.lft_button);
+ mRtButton = findViewById(R.id.rt_button);
+
+ mCircularProgress = (ProgressBar) findViewById(R.id.progress_circular);
+ mHorizontalProgress = (ProgressBar) findViewById(
+ R.id.progress_horizontal);
+ mFavicon = (ImageView) findViewById(R.id.favicon);
+ mLockIcon = (ImageView) findViewById(R.id.lock_icon);
+ mDivider = findViewById(R.id.divider);
+ }
+
+ /* package */ void setBrowserActivity(final BrowserActivity activity) {
+ mLftButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ if (mInLoad) {
+ WebView webView = activity.getTopWindow();
+ if (webView != null) {
+ webView.stopLoading();
+ }
+ } else {
+ activity.bookmarksOrHistoryPicker(false);
+ }
+ }
+ });
+ mRtButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ WebView webView = activity.getTopWindow();
+ if (webView != null) {
+ webView.zoomScrollOut();
+ }
+ }
+ });
+ setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ activity.onSearchRequested();
+ }
+ });
+ }
+
+ /* package */ void setFavicon(Drawable d) {
+ mFavicon.setImageDrawable(d);
+ }
+
+ /* package */ void setLock(Drawable d) {
+ if (d == null) {
+ mLockIcon.setVisibility(View.GONE);
+ } else {
+ mLockIcon.setImageDrawable(d);
+ mLockIcon.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /* package */ void setProgress(int newProgress) {
+ if (newProgress == mCircularProgress.getMax()) {
+ mCircularProgress.setVisibility(View.GONE);
+ mHorizontalProgress.setVisibility(View.GONE);
+ mDivider.setVisibility(View.VISIBLE);
+ mRtButton.setVisibility(View.VISIBLE);
+ mLftButton.setImageDrawable(mBookmarkDrawable);
+ mInLoad = false;
+ if (!mTitleSet) {
+ mTitle.setText(mUrl.getText());
+ mUrl.setText(null);
+ mTitleSet = true;
+ }
+ } else {
+ mCircularProgress.setProgress(newProgress);
+ mHorizontalProgress.setProgress(newProgress);
+ mCircularProgress.setVisibility(View.VISIBLE);
+ mHorizontalProgress.setVisibility(View.VISIBLE);
+ mDivider.setVisibility(View.GONE);
+ mRtButton.setVisibility(View.GONE);
+ if (mBookmarkDrawable == null) {
+ // The drawable was assigned in the xml file, so it already
+ // exists. Keep a pointer to it when we switch to the resource
+ // so we can easily switch back.
+ mBookmarkDrawable = mLftButton.getDrawable();
+ }
+ mLftButton.setImageResource(
+ com.android.internal.R.drawable.ic_menu_stop);
+ mInLoad = true;
+ }
+ }
+
+ /* package */ void setTitleAndUrl(CharSequence title, CharSequence url) {
+ if (url != null) {
+ url = BrowserActivity.buildTitleUrl(url.toString());
+ }
+ if (null == title) {
+ if (mInLoad) {
+ mTitleSet = false;
+ mTitle.setText(R.string.title_bar_loading);
+ } else {
+ // If the page has no title, put the url in the title space
+ // and leave the url blank.
+ mTitle.setText(url);
+ mUrl.setText(null);
+ mTitleSet = true;
+ return;
+ }
+ } else {
+ mTitle.setText(title);
+ mTitleSet = true;
+ }
+ mUrl.setText(url);
+ }
+
+ /* package */ void setToTabPicker() {
+ mTitle.setText(R.string.tab_picker_title);
+ setFavicon(null);
+ setLock(null);
+ mCircularProgress.setVisibility(View.GONE);
+ mHorizontalProgress.setVisibility(View.GONE);
+ }
+}
diff --git a/src/com/android/browser/WebStorageSizeManager.java b/src/com/android/browser/WebStorageSizeManager.java
new file mode 100644
index 0000000..40d30a2
--- /dev/null
+++ b/src/com/android/browser/WebStorageSizeManager.java
@@ -0,0 +1,382 @@
+/*
+ * 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.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+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.
+ */
+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.
+ public final static long ORIGIN_DEFAULT_QUOTA = 3 * 1024 * 1024; // 3MB
+ // The default value for quota increases.
+ public final static long QUOTA_INCREASE_STEP = 1 * 1024 * 1024; // 1MB
+ // Extra padding space for appcache maximum size increases. This is needed
+ // because WebKit sends us an estimate of the amount of space needed
+ // but this estimate may, currently, be slightly less than what is actually
+ // needed. We therefore add some 'padding'.
+ // TODO(andreip): fix this in WebKit.
+ public final static long APPCACHE_MAXSIZE_PADDING = 512 * 1024; // 512KB
+ // The system status bar notification id.
+ private final static int OUT_OF_SPACE_ID = 1;
+ // The application context.
+ private final Context mContext;
+ // The global Web storage limit.
+ private final long mGlobalLimit;
+ // The maximum size of the application cache file.
+ private long mAppCacheMaxSize;
+
+ /**
+ * Interface used by the WebStorageSizeManager to obtain information
+ * about the underlying file system. This functionality is separated
+ * into its own interface mainly for testing purposes.
+ */
+ public interface DiskInfo {
+ /**
+ * @return the size of the free space in the file system.
+ */
+ public long getFreeSpaceSizeBytes();
+
+ /**
+ * @return the total size of the file system.
+ */
+ public long getTotalSizeBytes();
+ };
+
+ private DiskInfo mDiskInfo;
+ // For convenience, we provide a DiskInfo implementation that uses StatFs.
+ public static class StatFsDiskInfo implements DiskInfo {
+ private StatFs mFs;
+
+ public StatFsDiskInfo(String path) {
+ mFs = new StatFs(path);
+ }
+
+ public long getFreeSpaceSizeBytes() {
+ return mFs.getAvailableBlocks() * mFs.getBlockSize();
+ }
+
+ public long getTotalSizeBytes() {
+ return mFs.getBlockCount() * mFs.getBlockSize();
+ }
+ };
+
+ /**
+ * Interface used by the WebStorageSizeManager to obtain information
+ * about the appcache file. This functionality is separated into its own
+ * interface mainly for testing purposes.
+ */
+ public interface AppCacheInfo {
+ /**
+ * @return the current size of the appcache file.
+ */
+ public long getAppCacheSizeBytes();
+ };
+
+ // For convenience, we provide an AppCacheInfo implementation.
+ public static class WebKitAppCacheInfo implements AppCacheInfo {
+ // The name of the application cache file. Keep in sync with
+ // WebCore/loader/appcache/ApplicationCacheStorage.cpp
+ private final static String APPCACHE_FILE = "ApplicationCache.db";
+ private String mAppCachePath;
+
+ public WebKitAppCacheInfo(String path) {
+ mAppCachePath = path;
+ }
+
+ public long getAppCacheSizeBytes() {
+ File file = new File(mAppCachePath
+ + File.separator
+ + APPCACHE_FILE);
+ return file.length();
+ }
+ };
+
+ /**
+ * Public ctor
+ * @param ctx is the application context
+ * @param diskInfo is the DiskInfo instance used to query the file system.
+ * @param appCacheInfo is the AppCacheInfo used to query info about the
+ * appcache file.
+ */
+ public WebStorageSizeManager(Context ctx, DiskInfo diskInfo,
+ AppCacheInfo appCacheInfo) {
+ mContext = ctx;
+ mDiskInfo = diskInfo;
+ mGlobalLimit = getGlobalLimit();
+ // 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,
+ appCacheInfo.getAppCacheSizeBytes());
+ }
+
+ /**
+ * 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
+ + ", total used quota: "
+ + totalUsedQuota
+ + ")");
+ }
+ long totalUnusedQuota = mGlobalLimit - totalUsedQuota - mAppCacheMaxSize;
+
+ if (totalUnusedQuota <= 0) {
+ // There definitely isn't any more space. Fire notifications
+ // if needed and exit.
+ if (totalUsedQuota > 0) {
+ // We only fire the notification if there are some other websites
+ // using some of the quota. This avoids the degenerate case where
+ // the first ever website to use Web storage tries to use more
+ // data than it is actually available. In such a case, showing
+ // the notification would not help at all since there is nothing
+ // the user can do.
+ 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.
+ 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 + APPCACHE_MAXSIZE_PADDING) {
+ // There definitely isn't any more space. Fire notifications
+ // if needed and exit.
+ if (totalUsedQuota > 0) {
+ // We only fire the notification if there are some other websites
+ // using some of the quota. This avoids the degenerate case where
+ // the first ever website to use Web storage tries to use more
+ // data than it is actually available. In such a case, showing
+ // the notification would not help at all since there is nothing
+ // the user can do.
+ 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 + APPCACHE_MAXSIZE_PADDING;
+ 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() {
+ long freeSpace = mDiskInfo.getFreeSpaceSizeBytes();
+ long fileSystemSize = mDiskInfo.getTotalSizeBytes();
+ return calculateGlobalLimit(fileSystemSize, freeSpace);
+ }
+
+ /*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() {
+ if(LOGV_ENABLED) {
+ Log.v(LOGTAG, "scheduleOutOfSpaceNotification called.");
+ }
+ if (mContext == null) {
+ // mContext can be null if we're running unit tests.
+ return;
+ }
+ // setup the notification boilerplate.
+ int icon = R.drawable.ic_launcher_browser;
+ CharSequence title = mContext.getString(
+ R.string.webstorage_outofspace_notification_title);
+ CharSequence text = mContext.getString(
+ R.string.webstorage_outofspace_notification_text);
+ long when = System.currentTimeMillis();
+ Intent intent = new Intent(mContext, WebsiteSettingsActivity.class);
+ PendingIntent contentIntent =
+ PendingIntent.getActivity(mContext, 0, intent, 0);
+ Notification notification = new Notification(icon, title, when);
+ notification.setLatestEventInfo(mContext, title, text, contentIntent);
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+ // Fire away.
+ String ns = Context.NOTIFICATION_SERVICE;
+ NotificationManager mgr =
+ (NotificationManager) mContext.getSystemService(ns);
+ if (mgr != null) {
+ mgr.notify(OUT_OF_SPACE_ID, notification);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/browser/WebsiteSettingsActivity.java b/src/com/android/browser/WebsiteSettingsActivity.java
new file mode 100644
index 0000000..89e5963
--- /dev/null
+++ b/src/com/android/browser/WebsiteSettingsActivity.java
@@ -0,0 +1,430 @@
+/*
+ * 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.AlertDialog;
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Browser;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.GeolocationPermissions;
+import android.webkit.WebIconDatabase;
+import android.webkit.WebStorage;
+import android.widget.ArrayAdapter;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+/**
+ * Manage the settings for an origin.
+ * We use it to keep track of the 'HTML5' settings, i.e. database (webstorage)
+ * and Geolocation.
+ */
+public class WebsiteSettingsActivity extends ListActivity {
+
+ private String LOGTAG = "WebsiteSettingsActivity";
+ private static String sMBStored = null;
+ private SiteAdapter mAdapter = null;
+
+ class Site {
+ private String mOrigin;
+ private String mTitle;
+ private Bitmap mIcon;
+ private int mFeatures;
+
+ // These constants provide the set of features that a site may support
+ // They must be consecutive. To add a new feature, add a new FEATURE_XXX
+ // variable with value equal to the current value of FEATURE_COUNT, then
+ // increment FEATURE_COUNT.
+ private final static int FEATURE_WEB_STORAGE = 0;
+ private final static int FEATURE_GEOLOCATION = 1;
+ // The number of features available.
+ private final static int FEATURE_COUNT = 2;
+
+ public Site(String origin) {
+ mOrigin = origin;
+ mTitle = null;
+ mIcon = null;
+ mFeatures = 0;
+ }
+
+ public void addFeature(int feature) {
+ mFeatures |= (1 << feature);
+ }
+
+ public boolean hasFeature(int feature) {
+ return (mFeatures & (1 << feature)) != 0;
+ }
+
+ /**
+ * Gets the number of features supported by this site.
+ */
+ public int getFeatureCount() {
+ int count = 0;
+ for (int i = 0; i < FEATURE_COUNT; ++i) {
+ count += hasFeature(i) ? 1 : 0;
+ }
+ return count;
+ }
+
+ /**
+ * Gets the ID of the nth (zero-based) feature supported by this site.
+ * The return value is a feature ID - one of the FEATURE_XXX values.
+ * This is required to determine which feature is displayed at a given
+ * position in the list of features for this site. This is used both
+ * when populating the view and when responding to clicks on the list.
+ */
+ public int getFeatureByIndex(int n) {
+ int j = -1;
+ for (int i = 0; i < FEATURE_COUNT; ++i) {
+ j += hasFeature(i) ? 1 : 0;
+ if (j == n) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public String getOrigin() {
+ return mOrigin;
+ }
+
+ public void setTitle(String title) {
+ mTitle = title;
+ }
+
+ public void setIcon(Bitmap icon) {
+ mIcon = icon;
+ }
+
+ 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>
+ implements AdapterView.OnItemClickListener {
+ private int mResource;
+ private LayoutInflater mInflater;
+ private Bitmap mDefaultIcon;
+ private Site mCurrentSite;
+
+ public SiteAdapter(Context context, int rsc) {
+ super(context, rsc);
+ mResource = rsc;
+ mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDefaultIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_launcher_shortcut_browser_bookmark);
+ populateOrigins();
+ }
+
+ /**
+ * Adds the specified feature to the site corresponding to supplied
+ * origin in the map. Creates the site if it does not already exist.
+ */
+ private void addFeatureToSite(Map sites, String origin, int feature) {
+ Site site = null;
+ if (sites.containsKey(origin)) {
+ site = (Site) sites.get(origin);
+ } else {
+ site = new Site(origin);
+ sites.put(origin, site);
+ }
+ site.addFeature(feature);
+ }
+
+ public void populateOrigins() {
+ clear();
+
+ // Get the list of origins we want to display.
+ // All 'HTML 5 modules' (Database, Geolocation etc) form these
+ // origin strings using WebCore::SecurityOrigin::toString(), so it's
+ // safe to group origins here. Note that WebCore::SecurityOrigin
+ // uses 0 (which is not printed) for the port if the port is the
+ // default for the protocol. Eg http://www.google.com and
+ // http://www.google.com:80 both record a port of 0 and hence
+ // toString() == 'http://www.google.com' for both.
+ Set origins = WebStorage.getInstance().getOrigins();
+ Map sites = new HashMap<String, Site>();
+ if (origins != null) {
+ Iterator<String> iter = origins.iterator();
+ while (iter.hasNext()) {
+ addFeatureToSite(sites, iter.next(), Site.FEATURE_WEB_STORAGE);
+ }
+ }
+ origins = GeolocationPermissions.getInstance().getOrigins();
+ if (origins != null) {
+ Iterator<String> iter = origins.iterator();
+ while (iter.hasNext()) {
+ addFeatureToSite(sites, iter.next(), Site.FEATURE_GEOLOCATION);
+ }
+ }
+
+ // 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> >();
+ Set keys = sites.keySet();
+ Iterator<String> originIter = keys.iterator();
+ while (originIter.hasNext()) {
+ String origin = originIter.next();
+ Site site = (Site) sites.get(origin);
+ String host = Uri.parse(origin).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);
+
+ if ((c != null) && c.moveToFirst()) {
+ int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
+ int titleIndex = c.getColumnIndex(Browser.BookmarkColumns.TITLE);
+ int faviconIndex = c.getColumnIndex(Browser.BookmarkColumns.FAVICON);
+ do {
+ String url = c.getString(urlIndex);
+ String host = Uri.parse(url).getHost();
+ if (hosts.containsKey(host)) {
+ String title = c.getString(titleIndex);
+ Bitmap bmp = null;
+ byte[] data = c.getBlob(faviconIndex);
+ if (data != null) {
+ bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
+ }
+ Set matchingSites = (Set) hosts.get(host);
+ Iterator<Site> sitesIter = matchingSites.iterator();
+ while (sitesIter.hasNext()) {
+ Site site = sitesIter.next();
+ site.setTitle(title);
+ if (bmp != null) {
+ site.setIcon(bmp);
+ }
+ }
+ }
+ } while (c.moveToNext());
+ }
+
+ // We can now simply populate our array with Site instances
+ keys = sites.keySet();
+ originIter = keys.iterator();
+ while (originIter.hasNext()) {
+ String origin = originIter.next();
+ Site site = (Site) sites.get(origin);
+ add(site);
+ }
+
+ if (getCount() == 0) {
+ finish(); // we close the screen
+ }
+ }
+
+ public int getCount() {
+ if (mCurrentSite == null) {
+ return super.getCount();
+ }
+ return mCurrentSite.getFeatureCount();
+ }
+
+ public 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);
+ }
+
+ /*
+ * If we receive the back event and are displaying
+ * site's settings, we want to go back to the main
+ * list view. If not, we just do nothing (see
+ * dispatchKeyEvent() below).
+ */
+ public boolean backKeyPressed() {
+ if (mCurrentSite != null) {
+ mCurrentSite = null;
+ populateOrigins();
+ notifyDataSetChanged();
+ return true;
+ }
+ return false;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+ TextView title;
+ TextView subtitle;
+ ImageView icon;
+
+ if (convertView == null) {
+ view = mInflater.inflate(mResource, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ title = (TextView) view.findViewById(R.id.title);
+ subtitle = (TextView) view.findViewById(R.id.subtitle);
+ icon = (ImageView) view.findViewById(R.id.icon);
+
+ if (mCurrentSite == null) {
+ Site site = getItem(position);
+ title.setText(site.getPrettyTitle());
+ subtitle.setText(site.getPrettyOrigin());
+ icon.setVisibility(View.VISIBLE);
+ Bitmap bmp = site.getIcon();
+ if (bmp == null) {
+ bmp = mDefaultIcon;
+ }
+ icon.setImageBitmap(bmp);
+ // We set the site as the view's tag,
+ // so that we can get it in onItemClick()
+ view.setTag(site);
+ } else {
+ icon.setVisibility(View.GONE);
+ String origin = mCurrentSite.getOrigin();
+ switch (mCurrentSite.getFeatureByIndex(position)) {
+ case Site.FEATURE_WEB_STORAGE:
+ long usageValue = WebStorage.getInstance().getUsageForOrigin(origin);
+ String usage = sizeValueToString(usageValue) + " " + sMBStored;
+
+ title.setText(R.string.webstorage_clear_data_title);
+ subtitle.setText(usage);
+ break;
+ case Site.FEATURE_GEOLOCATION:
+ title.setText(R.string.geolocation_settings_page_title);
+ boolean allowed = GeolocationPermissions.getInstance().getAllowed(origin);
+ subtitle.setText(allowed ?
+ R.string.geolocation_settings_page_summary_allowed :
+ R.string.geolocation_settings_page_summary_not_allowed);
+ break;
+ }
+ }
+
+ return view;
+ }
+
+ public void onItemClick(AdapterView<?> parent,
+ View view,
+ int position,
+ long id) {
+ if (mCurrentSite != null) {
+ switch (mCurrentSite.getFeatureByIndex(position)) {
+ case Site.FEATURE_WEB_STORAGE:
+ new AlertDialog.Builder(getContext())
+ .setTitle(R.string.webstorage_clear_data_dialog_title)
+ .setMessage(R.string.webstorage_clear_data_dialog_message)
+ .setPositiveButton(R.string.webstorage_clear_data_dialog_ok_button,
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dlg, int which) {
+ WebStorage.getInstance().deleteOrigin(mCurrentSite.getOrigin());
+ mCurrentSite = null;
+ populateOrigins();
+ notifyDataSetChanged();
+ }})
+ .setNegativeButton(R.string.webstorage_clear_data_dialog_cancel_button, null)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ break;
+ case Site.FEATURE_GEOLOCATION:
+ new AlertDialog.Builder(getContext())
+ .setTitle(R.string.geolocation_settings_page_dialog_title)
+ .setMessage(R.string.geolocation_settings_page_dialog_message)
+ .setPositiveButton(R.string.geolocation_settings_page_dialog_ok_button,
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dlg, int which) {
+ GeolocationPermissions.getInstance().clear(mCurrentSite.getOrigin());
+ mCurrentSite = null;
+ populateOrigins();
+ notifyDataSetChanged();
+ }})
+ .setNegativeButton(R.string.geolocation_settings_page_dialog_cancel_button, null)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ break;
+ }
+ } else {
+ mCurrentSite = (Site) view.getTag();
+ notifyDataSetChanged();
+ }
+ }
+ }
+
+ /**
+ * Intercepts the back key to immediately notify
+ * NativeDialog that we are done.
+ */
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK)
+ && (event.getAction() == KeyEvent.ACTION_DOWN)) {
+ if ((mAdapter != null) && (mAdapter.backKeyPressed())){
+ return true; // event consumed
+ }
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ if (sMBStored == null) {
+ sMBStored = getString(R.string.webstorage_origin_summary_mb_stored);
+ }
+ mAdapter = new SiteAdapter(this, R.layout.application);
+ setListAdapter(mAdapter);
+ getListView().setOnItemClickListener(mAdapter);
+ }
+}