eclair snapshot
diff --git a/src/com/android/browser/ActiveTabsPage.java b/src/com/android/browser/ActiveTabsPage.java
new file mode 100644
index 0000000..90c417a
--- /dev/null
+++ b/src/com/android/browser/ActiveTabsPage.java
@@ -0,0 +1,161 @@
+/*
+ * 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.Bitmap;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class ActiveTabsPage extends LinearLayout {
+ private final BrowserActivity mBrowserActivity;
+ private final LayoutInflater mFactory;
+ private final TabControl mControl;
+ private final TabsListAdapter mAdapter;
+ private final ListView mListView;
+
+ public ActiveTabsPage(BrowserActivity context, TabControl control) {
+ super(context);
+ mBrowserActivity = context;
+ mControl = control;
+ mFactory = LayoutInflater.from(context);
+ mFactory.inflate(R.layout.active_tabs, this);
+ mListView = (ListView) findViewById(R.id.list);
+ mAdapter = new TabsListAdapter();
+ mListView.setAdapter(mAdapter);
+ mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View view,
+ int position, long id) {
+ if (mControl.getTabCount() < TabControl.MAX_TABS) {
+ position--;
+ }
+ boolean needToAttach = false;
+ if (position == -1) {
+ // Create a new tab
+ mBrowserActivity.openTabToHomePage();
+ } else {
+ // Open the corresponding tab
+ // If the tab is the current one, switchToTab will
+ // do nothing and return, so we need to make sure
+ // it gets attached back to its mContentView in
+ // removeActiveTabPage
+ needToAttach = !mBrowserActivity.switchToTab(position);
+ }
+ mBrowserActivity.removeActiveTabPage(needToAttach);
+ }
+ });
+ }
+
+ /**
+ * Special class to hold the close drawable. Its sole purpose is to allow
+ * the parent to be pressed without being pressed itself. This way the line
+ * of a tab can be pressed, but the close button itself is not.
+ */
+ private static class CloseHolder extends ImageView {
+ public CloseHolder(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setPressed(boolean pressed) {
+ // If the parent is pressed, do not set to pressed.
+ if (pressed && ((View) getParent()).isPressed()) {
+ return;
+ }
+ super.setPressed(pressed);
+ }
+ }
+
+ private class TabsListAdapter extends BaseAdapter {
+ public int getCount() {
+ int count = mControl.getTabCount();
+ if (count < TabControl.MAX_TABS) {
+ count++;
+ }
+ return count;
+ }
+ public Object getItem(int position) {
+ return null;
+ }
+ public long getItemId(int position) {
+ return position;
+ }
+ public int getViewTypeCount() {
+ return 2;
+ }
+ public int getItemViewType(int position) {
+ if (mControl.getTabCount() < TabControl.MAX_TABS) {
+ position--;
+ }
+ // Do not recycle the "add new tab" item.
+ return position == -1 ? IGNORE_ITEM_VIEW_TYPE : 1;
+ }
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final int tabCount = mControl.getTabCount();
+ if (tabCount < TabControl.MAX_TABS) {
+ position--;
+ }
+
+ if (convertView == null) {
+ convertView = mFactory.inflate(position == -1 ?
+ R.layout.tab_view_add_tab : R.layout.tab_view, null);
+ }
+
+ if (position != -1) {
+ TextView title =
+ (TextView) convertView.findViewById(R.id.title);
+ TextView url = (TextView) convertView.findViewById(R.id.url);
+ ImageView favicon =
+ (ImageView) convertView.findViewById(R.id.favicon);
+ View close = convertView.findViewById(R.id.close);
+ TabControl.Tab tab = mControl.getTab(position);
+ mControl.populatePickerData(tab);
+ title.setText(tab.getTitle());
+ url.setText(tab.getUrl());
+ Bitmap icon = tab.getFavicon();
+ if (icon != null) {
+ favicon.setImageBitmap(icon);
+ } else {
+ favicon.setImageResource(R.drawable.app_web_browser_sm);
+ }
+ final int closePosition = position;
+ close.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ mBrowserActivity.closeTab(
+ mControl.getTab(closePosition));
+ if (tabCount == 1) {
+ mBrowserActivity.openTabToHomePage();
+ mBrowserActivity.removeActiveTabPage(false);
+ } else {
+ mListView.setAdapter(mAdapter);
+ }
+ }
+ });
+ }
+ return convertView;
+ }
+ }
+}
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index cf3fe70..81123ba 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -18,22 +18,24 @@
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.graphics.Bitmap;
import android.net.ParseException;
import android.net.WebAddress;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
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;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.Date;
public class AddBookmarkPage extends Activity {
@@ -46,18 +48,19 @@
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 String mTouchIconUrl;
+ private Bitmap mThumbnail;
+ private String mOriginalUrl;
+
+ // Message IDs
+ private static final int SAVE_BOOKMARK = 100;
+
+ private Handler mHandler;
private View.OnClickListener mSaveBookmark = new View.OnClickListener() {
public void onClick(View v) {
if (save()) {
finish();
- Toast.makeText(AddBookmarkPage.this, R.string.bookmark_saved,
- Toast.LENGTH_LONG).show();
}
}
};
@@ -73,7 +76,7 @@
requestWindowFeature(Window.FEATURE_LEFT_ICON);
setContentView(R.layout.browser_add_bookmark);
setTitle(R.string.save_to_bookmarks);
- getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.ic_dialog_bookmark);
+ getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.ic_list_bookmark);
String title = null;
String url = null;
@@ -86,7 +89,9 @@
setTitle(R.string.edit_bookmark);
}
title = mMap.getString("title");
- url = mMap.getString("url");
+ url = mOriginalUrl = mMap.getString("url");
+ mTouchIconUrl = mMap.getString("touch_icon_url");
+ mThumbnail = (Bitmap) mMap.getParcelable("thumbnail");
}
mTitle = (EditText) findViewById(R.id.title);
@@ -94,7 +99,6 @@
mAddress = (EditText) findViewById(R.id.address);
mAddress.setText(url);
-
View.OnClickListener accept = mSaveBookmark;
mButton = (TextView) findViewById(R.id.OK);
mButton.setOnClickListener(accept);
@@ -106,13 +110,59 @@
mButton.requestFocus();
}
}
-
+
+ private void createHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SAVE_BOOKMARK:
+ // Unbundle bookmark data.
+ Bundle bundle = msg.getData();
+ String title = bundle.getString("title");
+ String url = bundle.getString("url");
+ boolean invalidateThumbnail = bundle.getBoolean("invalidateThumbnail");
+ Bitmap thumbnail = invalidateThumbnail
+ ? null : (Bitmap) bundle.getParcelable("thumbnail");
+ String touchIconUrl = bundle.getString("touchIconUrl");
+
+ // Save to the bookmarks DB.
+ if (updateBookmarksDB(title, url, thumbnail, touchIconUrl)) {
+ Toast.makeText(AddBookmarkPage.this, R.string.bookmark_saved,
+ Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(AddBookmarkPage.this, R.string.bookmark_not_saved,
+ Toast.LENGTH_LONG).show();
+ }
+ break;
+ }
+ }
+ };
+ }
+ }
+
+ private boolean updateBookmarksDB(String title, String url, Bitmap thumbnail, String touchIconUrl) {
+ try {
+ final ContentResolver cr = getContentResolver();
+ Bookmarks.addBookmark(null, cr, url, title, thumbnail, true);
+ if (touchIconUrl != null) {
+ final Cursor c =
+ BrowserBookmarksAdapter.queryBookmarksForUrl(cr, null, url, true);
+ new DownloadTouchIcon(cr, c, url).execute(mTouchIconUrl);
+ }
+ } catch (IllegalStateException e) {
+ return false;
+ }
+ return true;
+ }
+
/**
- * Save the data to the database.
- * Also, change the view to dialog stating
- * that the webpage has been saved.
+ * Parse the data entered in the dialog and post a message to update the bookmarks database.
*/
boolean save() {
+ createHandler();
+
String title = mTitle.getText().toString().trim();
String unfilteredUrl =
BrowserActivity.fixUrl(mAddress.getText().toString());
@@ -129,98 +179,47 @@
return false;
}
String url = unfilteredUrl;
- if (!(url.startsWith("about:") || url.startsWith("data:") || url
- .startsWith("file:"))) {
- WebAddress address;
- try {
- address = new WebAddress(unfilteredUrl);
- } catch (ParseException e) {
- mAddress.setError(r.getText(R.string.bookmark_url_not_valid));
- return false;
- }
- if (address.mHost.length() == 0) {
- mAddress.setError(r.getText(R.string.bookmark_url_not_valid));
- return false;
- }
- url = address.toString();
- }
try {
- if (mEditingExisting) {
- mMap.putString("title", title);
- mMap.putString("url", url);
- 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);
- }
+ URI uriObj = new URI(url);
+ String scheme = uriObj.getScheme();
+ if (!("about".equals(scheme) || "data".equals(scheme)
+ || "javascript".equals(scheme)
+ || "file".equals(scheme) || "content".equals(scheme))) {
+ WebAddress address;
+ try {
+ address = new WebAddress(unfilteredUrl);
+ } catch (ParseException e) {
+ throw new URISyntaxException("", "");
}
- WebIconDatabase.getInstance().retainIconForPageUrl(url);
- c.deactivate();
- setResult(RESULT_OK);
+ if (address.mHost.length() == 0) {
+ throw new URISyntaxException("", "");
+ }
+ url = address.toString();
}
- } catch (IllegalStateException e) {
- setTitle(r.getText(R.string.no_database));
+ } catch (URISyntaxException e) {
+ mAddress.setError(r.getText(R.string.bookmark_url_not_valid));
return false;
}
+
+ if (mEditingExisting) {
+ mMap.putString("title", title);
+ mMap.putString("url", url);
+ mMap.putBoolean("invalidateThumbnail", !url.equals(mOriginalUrl));
+ setResult(RESULT_OK, (new Intent()).setAction(
+ getIntent().toString()).putExtras(mMap));
+ } else {
+ // Post a message to write to the DB.
+ Bundle bundle = new Bundle();
+ bundle.putString("title", title);
+ bundle.putString("url", url);
+ bundle.putParcelable("thumbnail", mThumbnail);
+ bundle.putBoolean("invalidateThumbnail", !url.equals(mOriginalUrl));
+ bundle.putString("touchIconUrl", mTouchIconUrl);
+ Message msg = Message.obtain(mHandler, SAVE_BOOKMARK);
+ msg.setData(bundle);
+ mHandler.sendMessage(msg);
+ setResult(RESULT_OK);
+ }
return true;
}
}
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 @@
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..c8aaee7
--- /dev/null
+++ b/src/com/android/browser/Bookmarks.java
@@ -0,0 +1,217 @@
+/*
+ * 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.graphics.Bitmap;
+import android.net.Uri;
+import android.provider.Browser;
+import android.util.Log;
+import android.webkit.WebIconDatabase;
+import android.widget.Toast;
+
+import java.io.ByteArrayOutputStream;
+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 thumbnail A thumbnail 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,
+ Bitmap thumbnail, 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);
+ map.put(Browser.BookmarkColumns.THUMBNAIL, bitmapToBytes(thumbnail));
+ 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);
+ map.put(Browser.BookmarkColumns.THUMBNAIL, bitmapToBytes(thumbnail));
+ 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, String title) {
+ Cursor cursor = cr.query(
+ Browser.BOOKMARKS_URI,
+ Browser.HISTORY_PROJECTION,
+ "url = ? AND title = ?",
+ new String[] { url, title },
+ null);
+ boolean first = cursor.moveToFirst();
+ // Should be in the database no matter what
+ if (!first) {
+ throw new AssertionError("URL is not in the database! " + url + " " + title);
+ }
+ // 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();
+ }
+
+ private static byte[] bitmapToBytes(Bitmap bm) {
+ if (bm == null) {
+ return null;
+ }
+
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+ return os.toByteArray();
+ }
+}
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index b58af66..5937881 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -20,7 +20,6 @@
import com.google.android.googlelogin.GoogleLoginServiceConstants;
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.app.SearchManager;
@@ -28,6 +27,7 @@
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 +35,7 @@
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;
@@ -44,19 +45,19 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.DrawFilter;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Picture;
-import android.graphics.drawable.BitmapDrawable;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.PaintDrawable;
import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import android.net.Uri;
import android.net.WebAddress;
import android.net.http.EventHandler;
@@ -75,14 +76,15 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.provider.Browser;
-import android.provider.Contacts;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Intents.Insert;
import android.provider.Downloads;
import android.provider.MediaStore;
-import android.provider.Contacts.Intents.Insert;
import android.text.IClipboard;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.text.util.Regex;
+import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Gravity;
@@ -106,12 +108,17 @@
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.DownloadListener;
+import android.webkit.GeolocationPermissions;
import android.webkit.HttpAuthHandler;
+import android.webkit.PluginManager;
import android.webkit.SslErrorHandler;
import android.webkit.URLUtil;
+import android.webkit.ValueCallback;
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 +128,7 @@
import android.widget.Toast;
import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -143,8 +151,7 @@
import java.util.zip.ZipFile;
public class BrowserActivity extends Activity
- implements KeyTracker.OnKeyTracker,
- View.OnCreateContextMenuListener,
+ implements View.OnCreateContextMenuListener,
DownloadListener {
/* Define some aliases to make these debugging flags easier to refer to.
@@ -268,382 +275,31 @@
mGlsConnection, Context.BIND_AUTO_CREATE);
}
- /**
- * This class is in charge of installing pre-packaged plugins
- * from the Browser assets directory to the user's data partition.
- * Plugins are loaded from the "plugins" directory in the assets;
- * Anything that is in this directory will be copied over to the
- * user data partition in app_plugins.
- */
- private class CopyPlugins implements Runnable {
- final static String TAG = "PluginsInstaller";
- final static String ZIP_FILTER = "assets/plugins/";
- final static String APK_PATH = "/system/app/Browser.apk";
- final static String PLUGIN_EXTENSION = ".so";
- final static String TEMPORARY_EXTENSION = "_temp";
- final static String BUILD_INFOS_FILE = "build.prop";
- final static String SYSTEM_BUILD_INFOS_FILE = "/system/"
- + BUILD_INFOS_FILE;
- final int BUFSIZE = 4096;
- boolean mDoOverwrite = false;
- String pluginsPath;
- Context mContext;
- File pluginsDir;
- AssetManager manager;
-
- public CopyPlugins (boolean overwrite, Context context) {
- mDoOverwrite = overwrite;
- mContext = context;
- }
-
- /**
- * Returned a filtered list of ZipEntry.
- * We list all the files contained in the zip and
- * only returns the ones starting with the ZIP_FILTER
- * path.
- *
- * @param zip the zip file used.
- */
- public Vector<ZipEntry> pluginsFilesFromZip(ZipFile zip) {
- Vector<ZipEntry> list = new Vector<ZipEntry>();
- Enumeration entries = zip.entries();
- while (entries.hasMoreElements()) {
- ZipEntry entry = (ZipEntry) entries.nextElement();
- if (entry.getName().startsWith(ZIP_FILTER)) {
- list.add(entry);
- }
- }
- return list;
- }
-
- /**
- * Utility method to copy the content from an inputstream
- * to a file output stream.
- */
- public void copyStreams(InputStream is, FileOutputStream fos) {
- BufferedOutputStream os = null;
- try {
- byte data[] = new byte[BUFSIZE];
- int count;
- os = new BufferedOutputStream(fos, BUFSIZE);
- while ((count = is.read(data, 0, BUFSIZE)) != -1) {
- os.write(data, 0, count);
- }
- os.flush();
- } catch (IOException e) {
- Log.e(TAG, "Exception while copying: " + e);
- } finally {
- try {
- if (os != null) {
- os.close();
- }
- } catch (IOException e2) {
- Log.e(TAG, "Exception while closing the stream: " + e2);
- }
- }
- }
-
- /**
- * Returns a string containing the contents of a file
- *
- * @param file the target file
- */
- private String contentsOfFile(File file) {
- String ret = null;
- FileInputStream is = null;
- try {
- byte[] buffer = new byte[BUFSIZE];
- int count;
- is = new FileInputStream(file);
- StringBuffer out = new StringBuffer();
-
- while ((count = is.read(buffer, 0, BUFSIZE)) != -1) {
- out.append(new String(buffer, 0, count));
- }
- ret = out.toString();
- } catch (IOException e) {
- Log.e(TAG, "Exception getting contents of file " + e);
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e2) {
- Log.e(TAG, "Exception while closing the file: " + e2);
- }
- }
- }
- return ret;
- }
-
- /**
- * Utility method to initialize the user data plugins path.
- */
- public void initPluginsPath() {
- BrowserSettings s = BrowserSettings.getInstance();
- pluginsPath = s.getPluginsPath();
- if (pluginsPath == null) {
- s.loadFromDb(mContext);
- pluginsPath = s.getPluginsPath();
- }
- if (LOGV_ENABLED) {
- Log.v(TAG, "Plugin path: " + pluginsPath);
- }
- }
-
- /**
- * Utility method to delete a file or a directory
- *
- * @param file the File to delete
- */
- public void deleteFile(File file) {
- File[] files = file.listFiles();
- if ((files != null) && files.length > 0) {
- for (int i=0; i< files.length; i++) {
- deleteFile(files[i]);
- }
- }
- if (!file.delete()) {
- Log.e(TAG, file.getPath() + " could not get deleted");
- }
- }
-
- /**
- * Clean the content of the plugins directory.
- * We delete the directory, then recreate it.
- */
- public void cleanPluginsDirectory() {
- if (LOGV_ENABLED) {
- Log.v(TAG, "delete plugins directory: " + pluginsPath);
- }
- File pluginsDirectory = new File(pluginsPath);
- deleteFile(pluginsDirectory);
- pluginsDirectory.mkdir();
- }
-
-
- /**
- * Copy the SYSTEM_BUILD_INFOS_FILE file containing the
- * informations about the system build to the
- * BUILD_INFOS_FILE in the plugins directory.
- */
- public void copyBuildInfos() {
- try {
- if (LOGV_ENABLED) {
- Log.v(TAG, "Copy build infos to the plugins directory");
- }
- File buildInfoFile = new File(SYSTEM_BUILD_INFOS_FILE);
- File buildInfoPlugins = new File(pluginsPath, BUILD_INFOS_FILE);
- copyStreams(new FileInputStream(buildInfoFile),
- new FileOutputStream(buildInfoPlugins));
- } catch (IOException e) {
- Log.e(TAG, "Exception while copying the build infos: " + e);
- }
- }
-
- /**
- * Returns true if the current system is newer than the
- * system that installed the plugins.
- * We determinate this by checking the build number of the system.
- *
- * At the end of the plugins copy operation, we copy the
- * SYSTEM_BUILD_INFOS_FILE to the BUILD_INFOS_FILE.
- * We then just have to load both and compare them -- if they
- * are different the current system is newer.
- *
- * Loading and comparing the strings should be faster than
- * creating a hash, the files being rather small. Extracting the
- * version number would require some parsing which may be more
- * brittle.
- */
- public boolean newSystemImage() {
- try {
- File buildInfoFile = new File(SYSTEM_BUILD_INFOS_FILE);
- File buildInfoPlugins = new File(pluginsPath, BUILD_INFOS_FILE);
- if (!buildInfoPlugins.exists()) {
- if (LOGV_ENABLED) {
- Log.v(TAG, "build.prop in plugins directory " + pluginsPath
- + " does not exist, therefore it's a new system image");
- }
- return true;
- } else {
- String buildInfo = contentsOfFile(buildInfoFile);
- String buildInfoPlugin = contentsOfFile(buildInfoPlugins);
- if (buildInfo == null || buildInfoPlugin == null
- || buildInfo.compareTo(buildInfoPlugin) != 0) {
- if (LOGV_ENABLED) {
- Log.v(TAG, "build.prop are different, "
- + " therefore it's a new system image");
- }
- return true;
- }
- }
- } catch (Exception e) {
- Log.e(TAG, "Exc in newSystemImage(): " + e);
- }
- return false;
- }
-
- /**
- * Check if the version of the plugins contained in the
- * Browser assets is the same as the version of the plugins
- * in the plugins directory.
- * We simply iterate on every file in the assets/plugins
- * and return false if a file listed in the assets does
- * not exist in the plugins directory.
- */
- private boolean checkIsDifferentVersions() {
- try {
- ZipFile zip = new ZipFile(APK_PATH);
- Vector<ZipEntry> files = pluginsFilesFromZip(zip);
- int zipFilterLength = ZIP_FILTER.length();
-
- Enumeration entries = files.elements();
- while (entries.hasMoreElements()) {
- ZipEntry entry = (ZipEntry) entries.nextElement();
- String path = entry.getName().substring(zipFilterLength);
- File outputFile = new File(pluginsPath, path);
- if (!outputFile.exists()) {
- if (LOGV_ENABLED) {
- Log.v(TAG, "checkIsDifferentVersions(): extracted file "
- + path + " does not exist, we have a different version");
- }
- return true;
- }
- }
- } catch (IOException e) {
- Log.e(TAG, "Exception in checkDifferentVersions(): " + e);
- }
- return false;
- }
-
- /**
- * Copy every files from the assets/plugins directory
- * to the app_plugins directory in the data partition.
- * Once copied, we copy over the SYSTEM_BUILD_INFOS file
- * in the plugins directory.
- *
- * NOTE: we directly access the content from the Browser
- * package (it's a zip file) and do not use AssetManager
- * as there is a limit of 1Mb (see Asset.h)
- */
- public void run() {
- // Lower the priority
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- try {
- if (pluginsPath == null) {
- Log.e(TAG, "No plugins path found!");
- return;
- }
-
- ZipFile zip = new ZipFile(APK_PATH);
- Vector<ZipEntry> files = pluginsFilesFromZip(zip);
- Vector<File> plugins = new Vector<File>();
- int zipFilterLength = ZIP_FILTER.length();
-
- Enumeration entries = files.elements();
- while (entries.hasMoreElements()) {
- ZipEntry entry = (ZipEntry) entries.nextElement();
- String path = entry.getName().substring(zipFilterLength);
- File outputFile = new File(pluginsPath, path);
- outputFile.getParentFile().mkdirs();
-
- if (outputFile.exists() && !mDoOverwrite) {
- if (LOGV_ENABLED) {
- Log.v(TAG, path + " already extracted.");
- }
- } else {
- if (path.endsWith(PLUGIN_EXTENSION)) {
- // We rename plugins to be sure a half-copied
- // plugin is not loaded by the browser.
- plugins.add(outputFile);
- outputFile = new File(pluginsPath,
- path + TEMPORARY_EXTENSION);
- }
- FileOutputStream fos = new FileOutputStream(outputFile);
- if (LOGV_ENABLED) {
- Log.v(TAG, "copy " + entry + " to "
- + pluginsPath + "/" + path);
- }
- copyStreams(zip.getInputStream(entry), fos);
- }
- }
-
- // We now rename the .so we copied, once all their resources
- // are safely copied over to the user data partition.
- Enumeration elems = plugins.elements();
- while (elems.hasMoreElements()) {
- File renamedFile = (File) elems.nextElement();
- File sourceFile = new File(renamedFile.getPath()
- + TEMPORARY_EXTENSION);
- if (LOGV_ENABLED) {
- Log.v(TAG, "rename " + sourceFile.getPath()
- + " to " + renamedFile.getPath());
- }
- sourceFile.renameTo(renamedFile);
- }
-
- copyBuildInfos();
-
- // Refresh the plugin list.
- if (mTabControl.getCurrentWebView() != null) {
- mTabControl.getCurrentWebView().refreshPlugins(false);
- }
- } catch (IOException e) {
- Log.e(TAG, "IO Exception: " + e);
- }
- }
- };
-
- /**
- * Copy the content of assets/plugins/ to the app_plugins directory
- * in the data partition.
- *
- * This function is called every time the browser is started.
- * We first check if the system image is newer than the one that
- * copied the plugins (if there's plugins in the data partition).
- * If this is the case, we then check if the versions are different.
- * If they are different, we clean the plugins directory in the
- * data partition, then start a thread to copy the plugins while
- * the browser continue to load.
- *
- * @param overwrite if true overwrite the files even if they are
- * already present (to let the user "reset" the plugins if needed).
- */
- private void copyPlugins(boolean overwrite) {
- CopyPlugins copyPluginsFromAssets = new CopyPlugins(overwrite, this);
- copyPluginsFromAssets.initPluginsPath();
- if (copyPluginsFromAssets.newSystemImage()) {
- if (copyPluginsFromAssets.checkIsDifferentVersions()) {
- copyPluginsFromAssets.cleanPluginsDirectory();
- Thread copyplugins = new Thread(copyPluginsFromAssets);
- copyplugins.setName("CopyPlugins");
- copyplugins.start();
- }
- }
- }
-
- private class ClearThumbnails extends AsyncTask<File, Void, Void> {
+ private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
@Override
public Void doInBackground(File... files) {
if (files != null) {
for (File f : files) {
- f.delete();
+ if (!f.delete()) {
+ Log.e(LOGTAG, f.getPath() + " was not deleted");
+ }
}
}
return null;
}
}
+ /**
+ * This layout holds everything you see below the status bar, including the
+ * error console, the custom view container, and the webviews.
+ */
+ private FrameLayout mBrowserFrameLayout;
+
@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);
-
// test the browser in OpenGL
// requestWindowFeature(Window.FEATURE_OPENGL);
@@ -651,6 +307,13 @@
mResolver = getContentResolver();
+ // If this was a web search request, pass it on to the default web
+ // search provider and finish this activity.
+ if (handleWebSearchIntent(getIntent())) {
+ finish();
+ return;
+ }
+
//
// start MASF proxy service
//
@@ -665,11 +328,19 @@
android.R.drawable.ic_secure);
mMixLockIcon = Resources.getSystem().getDrawable(
android.R.drawable.ic_partial_secure);
- 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);
+ mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(this)
+ .inflate(R.layout.custom_screen, null);
+ mContentView = (FrameLayout) mBrowserFrameLayout.findViewById(
+ R.id.main_content);
+ mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout
+ .findViewById(R.id.error_console);
+ mCustomViewContainer = (FrameLayout) mBrowserFrameLayout
+ .findViewById(R.id.fullscreen_custom_content);
+ frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
+ mTitleBar = new TitleBar(this);
// Create the tab control and our initial tab
mTabControl = new TabControl(this);
@@ -695,24 +366,67 @@
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(
ConnectivityManager.CONNECTIVITY_ACTION)) {
- boolean down = intent.getBooleanExtra(
+ boolean noConnectivity = intent.getBooleanExtra(
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
- onNetworkToggle(!down);
+ onNetworkToggle(!noConnectivity);
}
}
};
- // If this was a web search request, pass it on to the default web search provider.
- if (handleWebSearchIntent(getIntent())) {
- moveTaskToBack(true);
- return;
- }
+ 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 (!mTabControl.restoreState(icicle)) {
// clear up the thumbnail directory if we can't restore the state as
// 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.
@@ -726,8 +440,6 @@
intent.getData() != null,
intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
mTabControl.setCurrentTab(t);
- // This is one of the only places we call attachTabToContentView
- // without animating from the tab picker.
attachTabToContentView(t);
WebView webView = t.getWebView();
if (extra != null) {
@@ -744,7 +456,6 @@
&& !mSettings.isLoginInitialized()) {
setupHomePage();
}
- copyPlugins(true);
if (urlData.isEmpty()) {
if (mSettings.isLoginInitialized()) {
@@ -761,10 +472,15 @@
}
} else {
// TabControl.restoreState() will create a new tab even if
- // restoring the state fails. Attach it to the view here since we
- // are not animating from the tab picker.
+ // restoring the state fails.
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 +528,7 @@
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,45 +541,47 @@
// page, it can be reused.
boolean needsLoad =
mTabControl.recreateWebView(appTab, urlData.mUrl);
-
+
if (current != appTab) {
- showTab(appTab, needsLoad ? urlData : EMPTY_URL_DATA);
+ switchToTab(mTabControl.getTabIndex(appTab));
+ if (needsLoad) {
+ urlData.loadIn(appTab.getWebView());
+ }
} else {
- if (mTabOverview != null && mAnimationCount == 0) {
- sendAnimateFromOverview(appTab, false,
- 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.
- attachTabToContentView(appTab);
- if (needsLoad) {
- urlData.loadIn(appTab.getWebView());
- }
+ // If the tab was the current tab, we have to attach
+ // it to the view system again.
+ attachTabToContentView(appTab);
+ if (needsLoad) {
+ urlData.loadIn(appTab.getWebView());
}
}
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) {
+ switchToTab(mTabControl.getTabIndex(appTab));
+ }
+ // Otherwise, we are already viewing the correct tab.
+ } 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, 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();
return;
}
- // If the Window overview is up and we are not in the midst of
- // an animation, animate away from the Window overview.
- if (mTabOverview != null && mAnimationCount == 0) {
- sendAnimateFromOverview(current, false, urlData,
- TAB_OVERVIEW_DELAY, null);
- } else {
- // Get rid of the subwindow if it exists
- dismissSubWindow(current);
- urlData.loadIn(current.getWebView());
- }
+ // Get rid of the subwindow if it exists
+ dismissSubWindow(current);
+ urlData.loadIn(current.getWebView());
}
}
}
@@ -894,13 +612,15 @@
String url = null;
final String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action)) {
- url = intent.getData().toString();
+ Uri data = intent.getData();
+ if (data != null) url = data.toString();
} else if (Intent.ACTION_SEARCH.equals(action)
|| MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
|| Intent.ACTION_WEB_SEARCH.equals(action)) {
url = intent.getStringExtra(SearchManager.QUERY);
}
- return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA));
+ return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA),
+ intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
}
/**
@@ -908,7 +628,7 @@
* was identified as plain search terms and not URL/shortcut.
* @return true if the request was handled and web search activity was launched, false if not.
*/
- private boolean handleWebSearchRequest(String inUrl, Bundle appData) {
+ private boolean handleWebSearchRequest(String inUrl, Bundle appData, String extraData) {
if (inUrl == null) return false;
// In general, we shouldn't modify URL from Intent.
@@ -932,6 +652,9 @@
if (appData != null) {
intent.putExtra(SearchManager.APP_DATA, appData);
}
+ if (extraData != null) {
+ intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
+ }
intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName());
startActivity(intent);
@@ -992,6 +715,24 @@
}
/* package */ static String fixUrl(String inUrl) {
+ // FIXME: Converting the url to lower case
+ // duplicates functionality in smartUrlFilter().
+ // However, changing all current callers of fixUrl to
+ // call smartUrlFilter in addition may have unwanted
+ // consequences, and is deferred for now.
+ int colon = inUrl.indexOf(':');
+ boolean allLower = true;
+ for (int index = 0; index < colon; index++) {
+ char ch = inUrl.charAt(index);
+ if (!Character.isLetter(ch)) {
+ break;
+ }
+ allLower &= Character.isLowerCase(ch);
+ if (index == colon - 1 && !allLower) {
+ inUrl = inUrl.substring(0, colon).toLowerCase()
+ + inUrl.substring(colon);
+ }
+ }
if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
return inUrl;
if (inUrl.startsWith("http:") ||
@@ -1107,8 +848,9 @@
return;
}
+ mTabControl.resumeCurrentTab();
mActivityInPause = false;
- resumeWebView();
+ resumeWebViewTimers();
if (mWakeLock.isHeld()) {
mHandler.removeMessages(RELEASE_WAKELOCK);
@@ -1140,6 +882,206 @@
}
/**
+ * Since the actual title bar is embedded in the WebView, and removing it
+ * would change its appearance, create a temporary title bar to go at
+ * the top of the screen while the menu is open.
+ */
+ private TitleBar mFakeTitleBar;
+
+ /**
+ * Holder for the fake title bar. It will have a foreground shadow, as well
+ * as a white background, so the fake title bar looks like the real one.
+ */
+ private ViewGroup mFakeTitleBarHolder;
+
+ /**
+ * Layout parameters for the fake title bar within mFakeTitleBarHolder
+ */
+ private FrameLayout.LayoutParams mFakeTitleBarParams
+ = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ /**
+ * Keeps track of whether the options menu is open. This is important in
+ * determining whether to show or hide the title bar overlay.
+ */
+ private boolean mOptionsMenuOpen;
+
+ /**
+ * Only meaningful when mOptionsMenuOpen is true. This variable keeps track
+ * of whether the configuration has changed. The first onMenuOpened call
+ * after a configuration change is simply a reopening of the same menu
+ * (i.e. mIconView did not change).
+ */
+ private boolean mConfigChanged;
+
+ /**
+ * Whether or not the options menu is in its smaller, icon menu form. When
+ * true, we want the title bar overlay to be up. When false, we do not.
+ * Only meaningful if mOptionsMenuOpen is true.
+ */
+ private boolean mIconView;
+
+ @Override
+ public boolean onMenuOpened(int featureId, Menu menu) {
+ if (Window.FEATURE_OPTIONS_PANEL == featureId) {
+ if (mOptionsMenuOpen) {
+ if (mConfigChanged) {
+ // We do not need to make any changes to the state of the
+ // title bar, since the only thing that happened was a
+ // change in orientation
+ mConfigChanged = false;
+ } else {
+ if (mIconView) {
+ // Switching the menu to expanded view, so hide the
+ // title bar.
+ hideFakeTitleBar();
+ mIconView = false;
+ } else {
+ // Switching the menu back to icon view, so show the
+ // title bar once again.
+ showFakeTitleBar();
+ mIconView = true;
+ }
+ }
+ } else {
+ // The options menu is closed, so open it, and show the title
+ showFakeTitleBar();
+ mOptionsMenuOpen = true;
+ mConfigChanged = false;
+ mIconView = true;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Special class used exclusively for the shadow drawn underneath the fake
+ * title bar. The shadow does not need to be drawn if the WebView
+ * underneath is scrolled to the top, because it will draw directly on top
+ * of the embedded shadow.
+ */
+ private static class Shadow extends View {
+ private WebView mWebView;
+
+ public Shadow(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setWebView(WebView view) {
+ mWebView = view;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ // In general onDraw is the method to override, but we care about
+ // whether or not the background gets drawn, which happens in draw()
+ if (mWebView == null || mWebView.getScrollY() > getHeight()) {
+ super.draw(canvas);
+ }
+ // Need to invalidate so that if the scroll position changes, we
+ // still draw as appropriate.
+ invalidate();
+ }
+ }
+
+ private void showFakeTitleBar() {
+ final View decor = getWindow().peekDecorView();
+ if (mFakeTitleBar == null && mActiveTabsPage == null
+ && !mActivityInPause && decor != null
+ && decor.getWindowToken() != null) {
+ Rect visRect = new Rect();
+ if (!mBrowserFrameLayout.getGlobalVisibleRect(visRect)) {
+ if (LOGD_ENABLED) {
+ Log.d(LOGTAG, "showFakeTitleBar visRect failed");
+ }
+ return;
+ }
+ final WebView webView = getTopWindow();
+ mFakeTitleBar = new TitleBar(this);
+ mFakeTitleBar.setTitleAndUrl(null, webView.getUrl());
+ mFakeTitleBar.setProgress(webView.getProgress());
+ mFakeTitleBar.setFavicon(webView.getFavicon());
+ updateLockIconToLatest();
+
+ WindowManager manager
+ = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+
+ // Add the title bar to the window manager so it can receive touches
+ // while the menu is up
+ WindowManager.LayoutParams params
+ = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ params.gravity = Gravity.TOP;
+ WebView mainView = mTabControl.getCurrentWebView();
+ boolean atTop = mainView != null && mainView.getScrollY() == 0;
+ params.windowAnimations = atTop ? 0 : R.style.TitleBar;
+ // XXX : Without providing an offset, the fake title bar will be
+ // placed underneath the status bar. Use the global visible rect
+ // of mBrowserFrameLayout to determine the bottom of the status bar
+ params.y = visRect.top;
+ // Add a holder for the title bar. It also holds a shadow to show
+ // below the title bar.
+ if (mFakeTitleBarHolder == null) {
+ mFakeTitleBarHolder = (ViewGroup) LayoutInflater.from(this)
+ .inflate(R.layout.title_bar_bg, null);
+ }
+ Shadow shadow = (Shadow) mFakeTitleBarHolder.findViewById(
+ R.id.shadow);
+ shadow.setWebView(mainView);
+ mFakeTitleBarHolder.addView(mFakeTitleBar, 0, mFakeTitleBarParams);
+ manager.addView(mFakeTitleBarHolder, params);
+ }
+ }
+
+ @Override
+ public void onOptionsMenuClosed(Menu menu) {
+ mOptionsMenuOpen = false;
+ if (!mInLoad) {
+ hideFakeTitleBar();
+ } else if (!mIconView) {
+ // The page is currently loading, and we are in expanded mode, so
+ // we were not showing the menu. Show it once again. It will be
+ // removed when the page finishes.
+ showFakeTitleBar();
+ }
+ }
+ private void hideFakeTitleBar() {
+ if (mFakeTitleBar == null) return;
+ WindowManager.LayoutParams params = (WindowManager.LayoutParams)
+ mFakeTitleBarHolder.getLayoutParams();
+ WebView mainView = mTabControl.getCurrentWebView();
+ // Although we decided whether or not to animate based on the current
+ // scroll position, the scroll position may have changed since the
+ // fake title bar was displayed. Make sure it has the appropriate
+ // animation/lack thereof before removing.
+ params.windowAnimations = mainView != null && mainView.getScrollY() == 0
+ ? 0 : R.style.TitleBar;
+ WindowManager manager
+ = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+ manager.updateViewLayout(mFakeTitleBarHolder, params);
+ mFakeTitleBarHolder.removeView(mFakeTitleBar);
+ manager.removeView(mFakeTitleBarHolder);
+ mFakeTitleBar = null;
+ }
+
+ /**
+ * Special method for the fake title bar to call when displaying its context
+ * menu, since it is in its own Window, and its parent does not show a
+ * context menu.
+ */
+ /* package */ void showTitleBarContextMenu() {
+ if (null == mTitleBar.getParent()) {
+ return;
+ }
+ openContextMenu(mTitleBar);
+ }
+
+ /**
* onSaveInstanceState(Bundle map)
* onSaveInstanceState is called right before onStop(). The map contains
* the saved state.
@@ -1166,8 +1108,9 @@
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);
@@ -1179,6 +1122,14 @@
}
mCredsDlg = null;
+ // FIXME: This removes the active tabs page and resets the menu to
+ // MAIN_MENU. A better solution might be to do this work in onNewIntent
+ // but then we would need to save it in onSaveInstanceState and restore
+ // it in onCreate/onRestoreInstanceState
+ if (mActiveTabsPage != null) {
+ removeActiveTabPage(true);
+ }
+
cancelStopToast();
// unregister network state listener
@@ -1195,6 +1146,9 @@
Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
}
super.onDestroy();
+
+ if (mTabControl == null) return;
+
// Remove the current tab and sub window
TabControl.Tab t = mTabControl.getCurrentTab();
if (t != null) {
@@ -1218,10 +1172,13 @@
// "com.android.masfproxyservice",
// "com.android.masfproxyservice.MasfProxyService"));
//stopService(proxyServiceIntent);
+
+ unregisterReceiver(mPackageInstallationReceiver);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
+ mConfigChanged = true;
super.onConfigurationChanged(newConfig);
if (mPageInfoDialog != null) {
@@ -1266,7 +1223,7 @@
mTabControl.freeMemory();
}
- private boolean resumeWebView() {
+ private boolean resumeWebViewTimers() {
if ((!mActivityInPause && !mPageStarted) ||
(mActivityInPause && mPageStarted)) {
CookieSyncManager.getInstance().startSync();
@@ -1280,7 +1237,7 @@
}
}
- private boolean pauseWebView() {
+ private boolean pauseWebViewTimers() {
if (mActivityInPause && !mPageStarted) {
CookieSyncManager.getInstance().stopSync();
WebView w = mTabControl.getCurrentWebView();
@@ -1293,6 +1250,7 @@
}
}
+ // FIXME: Do we want to call this when loading google for the first time?
/*
* This function is called when we are launching for the first time. We
* are waiting for the login credentials before loading Google home
@@ -1403,15 +1361,20 @@
// options selector, so set mCanChord to true so we can access them.
mCanChord = true;
int id = item.getItemId();
- final WebView webView = getTopWindow();
- if (null == webView) {
- return false;
- }
- final HashMap hrefMap = new HashMap();
- hrefMap.put("webview", webView);
- final Message msg = mHandler.obtainMessage(
- FOCUS_NODE_HREF, id, 0, hrefMap);
switch (id) {
+ // For the context menu from the title bar
+ case R.id.title_bar_share_page_url:
+ case R.id.title_bar_copy_page_url:
+ WebView mainView = mTabControl.getCurrentWebView();
+ if (null == mainView) {
+ return false;
+ }
+ if (id == R.id.title_bar_share_page_url) {
+ Browser.sendString(this, mainView.getUrl());
+ } else {
+ copy(mainView.getUrl());
+ }
+ break;
// -- Browser context menu
case R.id.open_context_menu_id:
case R.id.open_newtab_context_menu_id:
@@ -1419,6 +1382,14 @@
case R.id.save_link_context_menu_id:
case R.id.share_link_context_menu_id:
case R.id.copy_link_context_menu_id:
+ final WebView webView = getTopWindow();
+ if (null == webView) {
+ return false;
+ }
+ final HashMap hrefMap = new HashMap();
+ hrefMap.put("webview", webView);
+ final Message msg = mHandler.obtainMessage(
+ FOCUS_NODE_HREF, id, 0, hrefMap);
webView.requestFocusNodeHref(msg);
break;
@@ -1441,7 +1412,8 @@
*/
@Override
public boolean onSearchRequested() {
- String url = getTopWindow().getUrl();
+ if (mOptionsMenuOpen) closeOptionsMenu();
+ String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false);
return true;
@@ -1456,6 +1428,82 @@
super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
}
+ /**
+ * Switch tabs. Called by the TitleBarSet when sliding the title bar
+ * results in changing tabs.
+ * @param index Index of the tab to change to, as defined by
+ * mTabControl.getTabIndex(Tab t).
+ * @return boolean True if we successfully switched to a different tab. If
+ * the indexth tab is null, or if that tab is the same as
+ * the current one, return false.
+ */
+ /* package */ boolean switchToTab(int index) {
+ TabControl.Tab tab = mTabControl.getTab(index);
+ TabControl.Tab currentTab = mTabControl.getCurrentTab();
+ if (tab == null || tab == currentTab) {
+ return false;
+ }
+ if (currentTab != null) {
+ // currentTab may be null if it was just removed. In that case,
+ // we do not need to remove it
+ removeTabFromContentView(currentTab);
+ }
+ mTabControl.setCurrentTab(tab);
+ attachTabToContentView(tab);
+ resetTitleIconAndProgress();
+ updateLockIconToLatest();
+ return true;
+ }
+
+ /* package */ TabControl.Tab openTabToHomePage() {
+ return openTabAndShow(mSettings.getHomePage(), false, null);
+ }
+
+ /* package */ void closeCurrentWindow() {
+ final TabControl.Tab current = mTabControl.getCurrentTab();
+ if (mTabControl.getTabCount() == 1) {
+ // This is the last tab. Open a new one, with the home
+ // page and close the current one.
+ TabControl.Tab newTab = openTabToHomePage();
+ closeTab(current);
+ return;
+ }
+ final TabControl.Tab parent = current.getParentTab();
+ int indexToShow = -1;
+ if (parent != null) {
+ indexToShow = mTabControl.getTabIndex(parent);
+ } else {
+ final int currentIndex = mTabControl.getCurrentIndex();
+ // Try to move to the tab to the right
+ indexToShow = currentIndex + 1;
+ if (indexToShow > mTabControl.getTabCount() - 1) {
+ // Try to move to the tab to the left
+ indexToShow = currentIndex - 1;
+ }
+ }
+ if (switchToTab(indexToShow)) {
+ // Close window
+ closeTab(current);
+ }
+ }
+
+ private ActiveTabsPage mActiveTabsPage;
+
+ /**
+ * Remove the active tabs page.
+ * @param needToAttach If true, the active tabs page did not attach a tab
+ * to the content view, so we need to do that here.
+ */
+ /* package */ void removeActiveTabPage(boolean needToAttach) {
+ mContentView.removeView(mActiveTabsPage);
+ mActiveTabsPage = null;
+ mMenuState = R.id.MAIN_MENU;
+ if (needToAttach) {
+ attachTabToContentView(mTabControl.getCurrentTab());
+ }
+ getTopWindow().requestFocus();
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (!mCanChord) {
@@ -1463,7 +1511,7 @@
// menu key.
return false;
}
- if (null == mTabOverview && null == getTopWindow()) {
+ if (null == getTopWindow()) {
return false;
}
if (mMenuIsDown) {
@@ -1475,23 +1523,36 @@
}
switch (item.getItemId()) {
// -- Main menu
- case R.id.goto_menu_id: {
- String url = getTopWindow().getUrl();
- startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
- createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_GOTO), false);
- }
+ case R.id.new_tab_menu_id:
+ openTabToHomePage();
+ break;
+
+ case R.id.goto_menu_id:
+ onSearchRequested();
break;
case R.id.bookmarks_menu_id:
bookmarksOrHistoryPicker(false);
break;
- case R.id.windows_menu_id:
- if (mTabControl.getTabCount() == 1) {
- openTabAndShow(mSettings.getHomePage(), null, false, null);
- } else {
- tabPicker(true, mTabControl.getCurrentIndex(), false);
- }
+ case R.id.active_tabs_menu_id:
+ mActiveTabsPage = new ActiveTabsPage(this, mTabControl);
+ removeTabFromContentView(mTabControl.getCurrentTab());
+ hideFakeTitleBar();
+ mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS);
+ mActiveTabsPage.requestFocus();
+ mMenuState = EMPTY_MENU;
+ break;
+
+ case R.id.add_bookmark_menu_id:
+ Intent i = new Intent(BrowserActivity.this,
+ AddBookmarkPage.class);
+ WebView w = getTopWindow();
+ i.putExtra("url", w.getUrl());
+ i.putExtra("title", w.getTitle());
+ i.putExtra("touch_icon_url", w.getTouchIconUrl());
+ i.putExtra("thumbnail", createScreenshot(w));
+ startActivity(i);
break;
case R.id.stop_reload_menu_id:
@@ -1516,21 +1577,7 @@
dismissSubWindow(mTabControl.getCurrentTab());
break;
}
- final int currentIndex = mTabControl.getCurrentIndex();
- final TabControl.Tab parent =
- mTabControl.getCurrentTab().getParentTab();
- int indexToShow = -1;
- if (parent != null) {
- indexToShow = mTabControl.getTabIndex(parent);
- } else {
- // Get the last tab in the list. If it is the current tab,
- // subtract 1 more.
- indexToShow = mTabControl.getTabCount() - 1;
- if (currentIndex == indexToShow) {
- indexToShow--;
- }
- }
- switchTabs(currentIndex, indexToShow, true);
+ closeCurrentWindow();
break;
case R.id.homepage_menu_id:
@@ -1568,7 +1615,8 @@
break;
case R.id.share_page_menu_id:
- Browser.sendString(this, getTopWindow().getUrl());
+ Browser.sendString(this, getTopWindow().getUrl(),
+ getText(R.string.choosertitle_sharevia).toString());
break;
case R.id.dump_nav_menu_id:
@@ -1587,62 +1635,6 @@
viewDownloads(null);
break;
- // -- Tab menu
- case R.id.view_tab_menu_id:
- if (mTabListener != null && mTabOverview != null) {
- int pos = mTabOverview.getContextMenuPosition(item);
- mTabOverview.setCurrentIndex(pos);
- mTabListener.onClick(pos);
- }
- break;
-
- case R.id.remove_tab_menu_id:
- if (mTabListener != null && mTabOverview != null) {
- int pos = mTabOverview.getContextMenuPosition(item);
- mTabListener.remove(pos);
- }
- break;
-
- case R.id.new_tab_menu_id:
- // No need to check for mTabOverview here since we are not
- // dependent on it for a position.
- if (mTabListener != null) {
- // If the overview happens to be non-null, make the "New
- // Tab" cell visible.
- if (mTabOverview != null) {
- mTabOverview.setCurrentIndex(ImageGrid.NEW_TAB);
- }
- mTabListener.onClick(ImageGrid.NEW_TAB);
- }
- break;
-
- case R.id.bookmark_tab_menu_id:
- if (mTabListener != null && mTabOverview != null) {
- int pos = mTabOverview.getContextMenuPosition(item);
- TabControl.Tab t = mTabControl.getTab(pos);
- // Since we called populatePickerData for all of the
- // tabs, getTitle and getUrl will return appropriate
- // values.
- Browser.saveBookmark(BrowserActivity.this, t.getTitle(),
- t.getUrl());
- }
- break;
-
- case R.id.history_tab_menu_id:
- bookmarksOrHistoryPicker(true);
- break;
-
- case R.id.bookmarks_tab_menu_id:
- bookmarksOrHistoryPicker(false);
- break;
-
- case R.id.properties_tab_menu_id:
- if (mTabListener != null && mTabOverview != null) {
- int pos = mTabOverview.getContextMenuPosition(item);
- showPageInfo(mTabControl.getTab(pos), false);
- }
- break;
-
case R.id.window_one_menu_id:
case R.id.window_two_menu_id:
case R.id.window_three_menu_id:
@@ -1658,7 +1650,7 @@
TabControl.Tab desiredTab = mTabControl.getTab(id);
if (desiredTab != null &&
desiredTab != mTabControl.getCurrentTab()) {
- switchTabs(mTabControl.getCurrentIndex(), id, false);
+ switchToTab(id);
}
break;
}
@@ -1690,26 +1682,11 @@
// whether the matching shortcut key will function.
super.onPrepareOptionsMenu(menu);
switch (mMenuState) {
- case R.id.TAB_MENU:
- if (mCurrentMenuState != mMenuState) {
- menu.setGroupVisible(R.id.MAIN_MENU, false);
- menu.setGroupEnabled(R.id.MAIN_MENU, false);
- menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
- menu.setGroupVisible(R.id.TAB_MENU, true);
- menu.setGroupEnabled(R.id.TAB_MENU, true);
- }
- boolean newT = mTabControl.getTabCount() < TabControl.MAX_TABS;
- final MenuItem tab = menu.findItem(R.id.new_tab_menu_id);
- tab.setVisible(newT);
- tab.setEnabled(newT);
- break;
case EMPTY_MENU:
if (mCurrentMenuState != mMenuState) {
menu.setGroupVisible(R.id.MAIN_MENU, false);
menu.setGroupEnabled(R.id.MAIN_MENU, false);
menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
- menu.setGroupVisible(R.id.TAB_MENU, false);
- menu.setGroupEnabled(R.id.TAB_MENU, false);
}
break;
default:
@@ -1717,8 +1694,6 @@
menu.setGroupVisible(R.id.MAIN_MENU, true);
menu.setGroupEnabled(R.id.MAIN_MENU, true);
menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
- menu.setGroupVisible(R.id.TAB_MENU, false);
- menu.setGroupEnabled(R.id.TAB_MENU, false);
}
final WebView w = getTopWindow();
boolean canGoBack = false;
@@ -1738,6 +1713,9 @@
menu.findItem(R.id.forward_menu_id)
.setEnabled(canGoForward);
+ menu.findItem(R.id.new_tab_menu_id).setEnabled(
+ mTabControl.getTabCount() < TabControl.MAX_TABS);
+
// decide whether to show the share link option
PackageManager pm = getPackageManager();
Intent send = new Intent(Intent.ACTION_SEND);
@@ -1745,12 +1723,6 @@
ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
- // If there is only 1 window, the text will be "New window"
- final MenuItem windows = menu.findItem(R.id.windows_menu_id);
- windows.setTitleCondensed(mTabControl.getTabCount() > 1 ?
- getString(R.string.view_tabs_condensed) :
- getString(R.string.tab_picker_new_tab));
-
boolean isNavDump = mSettings.isNavDump();
final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
nav.setVisible(isNavDump);
@@ -1811,7 +1783,7 @@
.parse(WebView.SCHEME_TEL + extra)));
Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
- addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
+ addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
menu.findItem(R.id.add_contact_context_menu_id).setIntent(
addIntent);
menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
@@ -1873,176 +1845,122 @@
}
// Attach the given tab to the content view.
+ // this should only be called for the current tab.
private void attachTabToContentView(TabControl.Tab t) {
- final WebView main = t.getWebView();
- // Attach the main WebView.
- mContentView.addView(main, COVER_SCREEN_PARAMS);
- // Attach the sub window if necessary
- attachSubWindow(t);
+ // Attach the container that contains the main WebView and any other UI
+ // associated with the tab.
+ t.attachTabToContentView(mContentView);
+
+ 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));
+ }
+
+ setLockIconType(t.getLockIconType());
+ setPrevLockType(t.getPrevLockIconType());
+
+ // this is to match the code in removeTabFromContentView()
+ if (!mPageStarted && t.getTopWindow().getProgress() < 100) {
+ mPageStarted = true;
+ }
+
+ WebView view = t.getWebView();
+ view.setEmbeddedTitleBar(mTitleBar);
// Request focus on the top window.
t.getTopWindow().requestFocus();
}
// Attach a sub window to the main WebView of the given tab.
private void attachSubWindow(TabControl.Tab t) {
- // If a sub window exists, attach it to the content view.
- final WebView subView = t.getSubWebView();
- if (subView != null) {
- final View container = t.getSubWebViewContainer();
- mContentView.addView(container, COVER_SCREEN_PARAMS);
- subView.requestFocus();
- }
+ t.attachSubWindow(mContentView);
+ getTopWindow().requestFocus();
}
// Remove the given tab from the content view.
private void removeTabFromContentView(TabControl.Tab t) {
- // Remove the main WebView.
- mContentView.removeView(t.getWebView());
- // Remove the sub window if it exists.
- if (t.getSubWebView() != null) {
- mContentView.removeView(t.getSubWebViewContainer());
+ // Remove the container that contains the main WebView.
+ t.removeTabFromContentView(mContentView);
+
+ if (mTabControl.getCurrentErrorConsole(false) != null) {
+ mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
+ }
+
+ WebView view = t.getWebView();
+ if (view != null) {
+ view.setEmbeddedTitleBar(null);
+ }
+
+ // unlike attachTabToContentView(), removeTabFromContentView() can be
+ // called for the non-current tab. Need to add the check.
+ if (t == mTabControl.getCurrentTab()) {
+ t.setLockIconType(getLockIconType());
+ t.setPrevLockIconType(getPrevLockType());
+
+ // this is not a perfect solution. But currently there is one
+ // WebViewClient for all the WebView. if user switches from an
+ // in-load window to an already loaded window, mPageStarted will not
+ // be set to false. If user leaves the Browser, pauseWebViewTimers()
+ // won't do anything and leaves the timer running even Browser is in
+ // the background.
+ if (mPageStarted) {
+ mPageStarted = false;
+ }
}
}
// Remove the sub window if it exists. Also called by TabControl when the
// user clicks the 'X' to dismiss a sub window.
/* package */ void dismissSubWindow(TabControl.Tab t) {
- final WebView mainView = t.getWebView();
- if (t.getSubWebView() != null) {
- // Remove the container view and request focus on the main WebView.
- mContentView.removeView(t.getSubWebViewContainer());
- mainView.requestFocus();
- // Tell the TabControl to dismiss the subwindow. This will destroy
- // the WebView.
- mTabControl.dismissSubWindow(t);
- }
+ t.removeSubWindow(mContentView);
+ // Tell the TabControl to dismiss the subwindow. This will destroy
+ // the WebView.
+ mTabControl.dismissSubWindow(t);
+ getTopWindow().requestFocus();
}
- // Send the ANIMTE_FROM_OVERVIEW message after changing the current tab.
- private void sendAnimateFromOverview(final TabControl.Tab tab,
- final boolean newTab, final UrlData urlData, final int delay,
- final Message msg) {
- // Set the current tab.
- mTabControl.setCurrentTab(tab);
- // Attach the WebView so it will layout.
- attachTabToContentView(tab);
- // Set the view to invisibile for now.
- tab.getWebView().setVisibility(View.INVISIBLE);
- // If there is a sub window, make it invisible too.
- if (tab.getSubWebView() != null) {
- tab.getSubWebViewContainer().setVisibility(View.INVISIBLE);
- }
- // Create our fake animating view.
- final AnimatingView view = new AnimatingView(this, tab);
- // Attach it to the view system and make in invisible so it will
- // layout but not flash white on the screen.
- mContentView.addView(view, COVER_SCREEN_PARAMS);
- view.setVisibility(View.INVISIBLE);
- // Send the animate message.
- final HashMap map = new HashMap();
- map.put("view", view);
- // Load the url after the AnimatingView has captured the picture. This
- // prevents any bad layout or bad scale from being used during
- // animation.
- if (!urlData.isEmpty()) {
- dismissSubWindow(tab);
- urlData.loadIn(tab.getWebView());
- }
- map.put("msg", msg);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- ANIMATE_FROM_OVERVIEW, newTab ? 1 : 0, 0, map), delay);
- // Increment the count to indicate that we are in an animation.
- mAnimationCount++;
- // Remove the listener so we don't get any more tab changes.
- mTabOverview.setListener(null);
- mTabListener = null;
- // Make the menu empty until the animation completes.
- mMenuState = EMPTY_MENU;
-
- }
-
- // 500ms animation with 800ms delay
- private static final int TAB_ANIMATION_DURATION = 200;
- private static final int TAB_OVERVIEW_DELAY = 500;
-
- // Called by TabControl when a tab is requesting focus
- /* package */ void showTab(TabControl.Tab t) {
- showTab(t, EMPTY_URL_DATA);
- }
-
- private void showTab(TabControl.Tab t, UrlData urlData) {
- // Disallow focus change during a tab animation.
- if (mAnimationCount > 0) {
- return;
- }
- int delay = 0;
- if (mTabOverview == null) {
- // Add a delay so the tab overview can be shown before the second
- // animation begins.
- delay = TAB_ANIMATION_DURATION + TAB_OVERVIEW_DELAY;
- tabPicker(false, mTabControl.getTabIndex(t), false);
- }
- sendAnimateFromOverview(t, false, urlData, delay, null);
- }
-
- // A wrapper function of {@link #openTabAndShow(UrlData, Message, boolean, String)}
+ // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
// that accepts url as string.
- private TabControl.Tab openTabAndShow(String url, final Message msg,
- boolean closeOnExit, String appId) {
- return openTabAndShow(new UrlData(url), msg, closeOnExit, appId);
+ private TabControl.Tab openTabAndShow(String url, boolean closeOnExit,
+ String appId) {
+ return openTabAndShow(new UrlData(url), closeOnExit, appId);
}
// This method does a ton of stuff. It will attempt to create a new tab
// if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
- // url isn't null, it will load the given url. If the tab overview is not
- // showing, it will animate to the tab overview, create a new tab and
- // animate away from it. After the animation completes, it will dispatch
- // the given Message. If the tab overview is already showing (i.e. this
- // method is called from TabListener.onClick(), the method will animate
- // away from the tab overview.
- private TabControl.Tab openTabAndShow(UrlData urlData, final Message msg,
+ // url isn't null, it will load the given url.
+ /* package */ TabControl.Tab openTabAndShow(UrlData urlData,
boolean closeOnExit, String appId) {
final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS;
final TabControl.Tab currentTab = mTabControl.getCurrentTab();
if (newTab) {
- int delay = 0;
- // If the tab overview is up and there are animations, just load
- // the url.
- if (mTabOverview != null && mAnimationCount > 0) {
- if (!urlData.isEmpty()) {
- // We should not have a msg here since onCreateWindow
- // checks the animation count and every other caller passes
- // null.
- assert msg == null;
- // just dismiss the subwindow and load the given url.
- dismissSubWindow(currentTab);
- urlData.loadIn(currentTab.getWebView());
- }
- } else {
- // show mTabOverview if it is not there.
- if (mTabOverview == null) {
- // We have to delay the animation from the tab picker by the
- // length of the tab animation. Add a delay so the tab
- // overview can be shown before the second animation begins.
- delay = TAB_ANIMATION_DURATION + TAB_OVERVIEW_DELAY;
- tabPicker(false, ImageGrid.NEW_TAB, false);
- }
- // Animate from the Tab overview after any animations have
- // finished.
- final TabControl.Tab tab = mTabControl.createNewTab(
- closeOnExit, appId, urlData.mUrl);
- sendAnimateFromOverview(tab, true, urlData, delay, msg);
- return tab;
+ final TabControl.Tab tab = mTabControl.createNewTab(
+ closeOnExit, appId, urlData.mUrl);
+ WebView webview = tab.getWebView();
+ // If the last tab was removed from the active tabs page, currentTab
+ // will be null.
+ if (currentTab != null) {
+ removeTabFromContentView(currentTab);
}
- } else if (!urlData.isEmpty()) {
- // We should not have a msg here.
- assert msg == null;
- if (mTabOverview != null && mAnimationCount == 0) {
- sendAnimateFromOverview(currentTab, false, urlData,
- TAB_OVERVIEW_DELAY, null);
- } else {
- // Get rid of the subwindow if it exists
- dismissSubWindow(currentTab);
+ // We must set the new tab as the current tab to reflect the old
+ // animation behavior.
+ mTabControl.setCurrentTab(tab);
+ attachTabToContentView(tab);
+ if (!urlData.isEmpty()) {
+ urlData.loadIn(webview);
+ }
+ return tab;
+ } else {
+ // Get rid of the subwindow if it exists
+ dismissSubWindow(currentTab);
+ if (!urlData.isEmpty()) {
// Load the given url.
urlData.loadIn(currentTab.getWebView());
}
@@ -2050,234 +1968,16 @@
return currentTab;
}
- private Animation createTabAnimation(final AnimatingView view,
- final View cell, boolean scaleDown) {
- final AnimationSet set = new AnimationSet(true);
- final float scaleX = (float) cell.getWidth() / view.getWidth();
- final float scaleY = (float) cell.getHeight() / view.getHeight();
- if (scaleDown) {
- set.addAnimation(new ScaleAnimation(1.0f, scaleX, 1.0f, scaleY));
- set.addAnimation(new TranslateAnimation(0, cell.getLeft(), 0,
- cell.getTop()));
- } else {
- set.addAnimation(new ScaleAnimation(scaleX, 1.0f, scaleY, 1.0f));
- set.addAnimation(new TranslateAnimation(cell.getLeft(), 0,
- cell.getTop(), 0));
- }
- set.setDuration(TAB_ANIMATION_DURATION);
- set.setInterpolator(new DecelerateInterpolator());
- return set;
- }
-
- // Animate to the tab overview. currentIndex tells us which position to
- // animate to and newIndex is the position that should be selected after
- // the animation completes.
- // If remove is true, after the animation stops, a confirmation dialog will
- // be displayed to the user.
- private void animateToTabOverview(final int newIndex, final boolean remove,
- final AnimatingView view) {
- // Find the view in the ImageGrid allowing for the "New Tab" cell.
- int position = mTabControl.getTabIndex(view.mTab);
- if (!((ImageAdapter) mTabOverview.getAdapter()).maxedOut()) {
- position++;
- }
-
- // Offset the tab position with the first visible position to get a
- // number between 0 and 3.
- position -= mTabOverview.getFirstVisiblePosition();
-
- // Grab the view that we are going to animate to.
- final View v = mTabOverview.getChildAt(position);
-
- final Animation.AnimationListener l =
- new Animation.AnimationListener() {
- public void onAnimationStart(Animation a) {
- if (mTabOverview != null) {
- mTabOverview.requestFocus();
- // Clear the listener so we don't trigger a tab
- // selection.
- mTabOverview.setListener(null);
- }
- }
- public void onAnimationRepeat(Animation a) {}
- public void onAnimationEnd(Animation a) {
- // We are no longer animating so decrement the count.
- mAnimationCount--;
- // Make the view GONE so that it will not draw between
- // now and when the Runnable is handled.
- view.setVisibility(View.GONE);
- // Post a runnable since we can't modify the view
- // hierarchy during this callback.
- mHandler.post(new Runnable() {
- public void run() {
- // Remove the AnimatingView.
- mContentView.removeView(view);
- if (mTabOverview != null) {
- // Make newIndex visible.
- mTabOverview.setCurrentIndex(newIndex);
- // Restore the listener.
- mTabOverview.setListener(mTabListener);
- // Change the menu to TAB_MENU if the
- // ImageGrid is interactive.
- if (mTabOverview.isLive()) {
- mMenuState = R.id.TAB_MENU;
- mTabOverview.requestFocus();
- }
- }
- // If a remove was requested, remove the tab.
- if (remove) {
- // During a remove, the current tab has
- // already changed. Remember the current one
- // here.
- final TabControl.Tab currentTab =
- mTabControl.getCurrentTab();
- // Remove the tab at newIndex from
- // TabControl and the tab overview.
- final TabControl.Tab tab =
- mTabControl.getTab(newIndex);
- mTabControl.removeTab(tab);
- // Restore the current tab.
- if (currentTab != tab) {
- mTabControl.setCurrentTab(currentTab);
- }
- if (mTabOverview != null) {
- mTabOverview.remove(newIndex);
- // Make the current tab visible.
- mTabOverview.setCurrentIndex(
- mTabControl.getCurrentIndex());
- }
- }
- }
- });
- }
- };
-
- // Do an animation if there is a view to animate to.
- if (v != null) {
- // Create our animation
- final Animation anim = createTabAnimation(view, v, true);
- anim.setAnimationListener(l);
- // Start animating
- view.startAnimation(anim);
- } else {
- // If something goes wrong and we didn't find a view to animate to,
- // just do everything here.
- l.onAnimationStart(null);
- l.onAnimationEnd(null);
- }
- }
-
- // Animate from the tab picker. The index supplied is the index to animate
- // from.
- private void animateFromTabOverview(final AnimatingView view,
- final boolean newTab, final Message msg) {
- // firstVisible is the first visible tab on the screen. This helps
- // to know which corner of the screen the selected tab is.
- int firstVisible = mTabOverview.getFirstVisiblePosition();
- // tabPosition is the 0-based index of of the tab being opened
- int tabPosition = mTabControl.getTabIndex(view.mTab);
- if (!((ImageAdapter) mTabOverview.getAdapter()).maxedOut()) {
- // Add one to make room for the "New Tab" cell.
- tabPosition++;
- }
- // If this is a new tab, animate from the "New Tab" cell.
- if (newTab) {
- tabPosition = 0;
- }
- // Location corresponds to the four corners of the screen.
- // A new tab or 0 is upper left, 0 for an old tab is upper
- // right, 1 is lower left, and 2 is lower right
- int location = tabPosition - firstVisible;
-
- // Find the view at this location.
- final View v = mTabOverview.getChildAt(location);
-
- // Wait until the animation completes to replace the AnimatingView.
- final Animation.AnimationListener l =
- new Animation.AnimationListener() {
- public void onAnimationStart(Animation a) {}
- public void onAnimationRepeat(Animation a) {}
- public void onAnimationEnd(Animation a) {
- mHandler.post(new Runnable() {
- public void run() {
- mContentView.removeView(view);
- // Dismiss the tab overview. If the cell at the
- // given location is null, set the fade
- // parameter to true.
- dismissTabOverview(v == null);
- TabControl.Tab t =
- mTabControl.getCurrentTab();
- mMenuState = R.id.MAIN_MENU;
- // Resume regular updates.
- t.getWebView().resumeTimers();
- // Dispatch the message after the animation
- // completes.
- if (msg != null) {
- msg.sendToTarget();
- }
- // The animation is done and the tab overview is
- // gone so allow key events and other animations
- // to begin.
- mAnimationCount--;
- // Reset all the title bar info.
- resetTitle();
- }
- });
- }
- };
-
- if (v != null) {
- final Animation anim = createTabAnimation(view, v, false);
- // Set the listener and start animating
- anim.setAnimationListener(l);
- view.startAnimation(anim);
- // Make the view VISIBLE during the animation.
- view.setVisibility(View.VISIBLE);
- } else {
- // Go ahead and do all the cleanup.
- l.onAnimationEnd(null);
- }
- }
-
- // Dismiss the tab overview applying a fade if needed.
- private void dismissTabOverview(final boolean fade) {
- if (fade) {
- AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f);
- anim.setDuration(500);
- anim.startNow();
- mTabOverview.startAnimation(anim);
- }
- // Just in case there was a problem with animating away from the tab
- // overview
- WebView current = mTabControl.getCurrentWebView();
- if (current != null) {
- current.setVisibility(View.VISIBLE);
- } else {
- Log.e(LOGTAG, "No current WebView in dismissTabOverview");
- }
- // Make the sub window container visible.
- if (mTabControl.getCurrentSubWindow() != null) {
- mTabControl.getCurrentTab().getSubWebViewContainer()
- .setVisibility(View.VISIBLE);
- }
- mContentView.removeView(mTabOverview);
- // Clear all the data for tab picker so next time it will be
- // recreated.
- mTabControl.wipeAllPickerData();
- mTabOverview.clear();
- mTabOverview = null;
- mTabListener = null;
- }
-
private TabControl.Tab openTab(String url) {
if (mSettings.openInBackground()) {
TabControl.Tab t = mTabControl.createNewTab();
if (t != null) {
- t.getWebView().loadUrl(url);
+ WebView view = t.getWebView();
+ view.loadUrl(url);
}
return t;
} else {
- return openTabAndShow(url, null, false, null);
+ return openTabAndShow(url, false, null);
}
}
@@ -2319,15 +2019,6 @@
}
/**
- * Resets the browser title-view to whatever it must be (for example, if we
- * load a page from history).
- */
- private void resetTitle() {
- resetLockIcon();
- resetTitleIconAndProgress();
- }
-
- /**
* Resets the browser title-view to whatever it must be
* (for example, if we had a loading error)
* When we have a new page, we call resetTitle, when we
@@ -2374,43 +2065,13 @@
mUrl = url;
mTitle = title;
- // While the tab overview is animating or being shown, block changes
- // to the title.
- if (mAnimationCount == 0 && mTabOverview == null) {
- setTitle(buildUrlTitle(url, title));
+ mTitleBar.setTitleAndUrl(title, url);
+ if (mFakeTitleBar != null) {
+ mFakeTitleBar.setTitleAndUrl(title, url);
}
}
/**
- * Builds and returns the page title, which is some
- * combination of the page URL and title.
- * @param url The URL of the site being loaded.
- * @param title The title of the site being loaded.
- * @return The page title.
- */
- private String buildUrlTitle(String url, String title) {
- String urlTitle = "";
-
- if (url != null) {
- String titleUrl = buildTitleUrl(url);
-
- if (title != null && 0 < title.length()) {
- if (titleUrl != null && 0 < titleUrl.length()) {
- urlTitle = titleUrl + ": " + title;
- } else {
- urlTitle = title;
- }
- } else {
- if (titleUrl != null) {
- urlTitle = titleUrl;
- }
- }
- }
-
- return urlTitle;
- }
-
- /**
* @param url The URL to build a title version of the URL from.
* @return The title version of the URL or null if fails.
* The title version of the URL can be either the URL hostname,
@@ -2418,7 +2079,7 @@
* 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) {
@@ -2449,23 +2110,10 @@
// Set the favicon in the title bar.
private void setFavicon(Bitmap icon) {
- // While the tab overview is animating or being shown, block changes to
- // the favicon.
- if (mAnimationCount > 0 || mTabOverview != null) {
- return;
+ mTitleBar.setFavicon(icon);
+ if (mFakeTitleBar != null) {
+ mFakeTitleBar.setFavicon(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);
}
/**
@@ -2490,29 +2138,22 @@
" revert lock icon to " + mLockIconType);
}
- updateLockIconImage(mLockIconType);
+ updateLockIconToLatest();
}
- private void switchTabs(int indexFrom, int indexToShow, boolean remove) {
- int delay = TAB_ANIMATION_DURATION + TAB_OVERVIEW_DELAY;
- // Animate to the tab picker, remove the current tab, then
- // animate away from the tab picker to the parent WebView.
- tabPicker(false, indexFrom, remove);
- // Change to the parent tab
- final TabControl.Tab tab = mTabControl.getTab(indexToShow);
- if (tab != null) {
- sendAnimateFromOverview(tab, false, EMPTY_URL_DATA, delay, null);
- } else {
- // Increment this here so that no other animations can happen in
- // between the end of the tab picker transition and the beginning
- // of openTabAndShow. This has a matching decrement in the handler
- // of OPEN_TAB_AND_SHOW.
- mAnimationCount++;
- // Send a message to open a new tab.
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(OPEN_TAB_AND_SHOW,
- mSettings.getHomePage()), delay);
+ /**
+ * Close the tab, remove its associated title bar, and adjust mTabControl's
+ * current tab to a valid value.
+ */
+ /* package */ void closeTab(TabControl.Tab t) {
+ int currentIndex = mTabControl.getCurrentIndex();
+ int removeIndex = mTabControl.getTabIndex(t);
+ mTabControl.removeTab(t);
+ if (currentIndex >= removeIndex && currentIndex != 0) {
+ currentIndex--;
}
+ mTabControl.setCurrentTab(mTabControl.getTab(currentIndex));
+ resetTitleIconAndProgress();
}
private void goBackOnePageOrQuit() {
@@ -2526,6 +2167,7 @@
* moveTaskToBack().
*/
moveTaskToBack(true);
+ return;
}
WebView w = current.getWebView();
if (w.canGoBack()) {
@@ -2535,25 +2177,31 @@
// another window. If so, we switch back to that window.
TabControl.Tab parent = current.getParentTab();
if (parent != null) {
- switchTabs(mTabControl.getCurrentIndex(),
- mTabControl.getTabIndex(parent), true);
+ switchToTab(mTabControl.getTabIndex(parent));
+ // Now we close the other tab
+ closeTab(current);
} else {
if (current.closeOnExit()) {
+ // force mPageStarted to be false as we are going to either
+ // finish the activity or remove the tab. This will ensure
+ // pauseWebView() taking action.
+ mPageStarted = false;
if (mTabControl.getTabCount() == 1) {
finish();
return;
}
- // call pauseWebView() now, we won't be able to call it in
- // onPause() as the WebView won't be valid. Temporarily
- // change mActivityInPause to be true as pauseWebView() will
- // do nothing if mActivityInPause is false.
+ // call pauseWebViewTimers() now, we won't be able to call
+ // it in onPause() as the WebView won't be valid.
+ // Temporarily change mActivityInPause to be true as
+ // pauseWebViewTimers() will do nothing if mActivityInPause
+ // is false.
boolean savedState = mActivityInPause;
if (savedState) {
- Log.e(LOGTAG, "BrowserActivity is already paused " +
- "while handing goBackOnePageOrQuit.");
+ Log.e(LOGTAG, "BrowserActivity is already paused "
+ + "while handing goBackOnePageOrQuit.");
}
mActivityInPause = true;
- pauseWebView();
+ pauseWebViewTimers();
mActivityInPause = savedState;
removeTabFromContentView(current);
mTabControl.removeTab(current);
@@ -2570,84 +2218,76 @@
}
}
- public KeyTracker.State onKeyTracker(int keyCode,
- KeyEvent event,
- KeyTracker.Stage stage,
- int duration) {
- // if onKeyTracker() is called after activity onStop()
- // because of accumulated key events,
- // we should ignore it as browser is not active any more.
- WebView topWindow = getTopWindow();
- if (topWindow == null)
- return KeyTracker.State.NOT_TRACKING;
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
+ // still down, we don't want to trigger the search. Pretend to consume
+ // the key and do nothing.
+ if (mMenuIsDown) return true;
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- // 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.
- if (mAnimationCount > 0) {
- return KeyTracker.State.DONE_TRACKING;
- }
- if (stage == KeyTracker.Stage.LONG_REPEAT) {
- bookmarksOrHistoryPicker(true);
- return KeyTracker.State.DONE_TRACKING;
- } else if (stage == KeyTracker.Stage.UP) {
- // FIXME: Currently, we do not have a notion of the
- // history picker for the subwindow, but maybe we
- // should?
- WebView subwindow = mTabControl.getCurrentSubWindow();
- if (subwindow != null) {
- if (subwindow.canGoBack()) {
- subwindow.goBack();
- } else {
- dismissSubWindow(mTabControl.getCurrentTab());
- }
+ switch(keyCode) {
+ case KeyEvent.KEYCODE_MENU:
+ mMenuIsDown = true;
+ break;
+ case KeyEvent.KEYCODE_SPACE:
+ // WebView/WebTextView handle the keys in the KeyDown. As
+ // the Activity's shortcut keys are only handled when WebView
+ // doesn't, have to do it in onKeyDown instead of onKeyUp.
+ if (event.isShiftPressed()) {
+ getTopWindow().pageUp(false);
} else {
- goBackOnePageOrQuit();
+ getTopWindow().pageDown(false);
}
- return KeyTracker.State.DONE_TRACKING;
- }
- return KeyTracker.State.KEEP_TRACKING;
+ return true;
+ case KeyEvent.KEYCODE_BACK:
+ if (event.getRepeatCount() == 0) {
+ event.startTracking();
+ return true;
+ } else if (mCustomView == null && mActiveTabsPage == null
+ && event.isLongPress()) {
+ bookmarksOrHistoryPicker(true);
+ return true;
+ }
+ break;
}
- return KeyTracker.State.NOT_TRACKING;
+ return super.onKeyDown(keyCode, event);
}
- @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_MENU) {
- mMenuIsDown = true;
- } else if (mMenuIsDown) {
- // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
- // still down, we don't want to trigger the search. Pretend to
- // consume the key and do nothing.
- return true;
- }
- boolean handled = mKeyTracker.doKeyDown(keyCode, event);
- if (!handled) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_SPACE:
- if (event.isShiftPressed()) {
- getTopWindow().pageUp(false);
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ switch(keyCode) {
+ case KeyEvent.KEYCODE_MENU:
+ mMenuIsDown = false;
+ break;
+ case KeyEvent.KEYCODE_BACK:
+ if (event.isTracking() && !event.isCanceled()) {
+ if (mCustomView != null) {
+ // if a custom view is showing, hide it
+ mWebChromeClient.onHideCustomView();
+ } else if (mActiveTabsPage != null) {
+ // if tab page is showing, hide it
+ removeActiveTabPage(true);
} else {
- getTopWindow().pageDown(false);
+ WebView subwindow = mTabControl.getCurrentSubWindow();
+ if (subwindow != null) {
+ if (subwindow.canGoBack()) {
+ subwindow.goBack();
+ } else {
+ dismissSubWindow(mTabControl.getCurrentTab());
+ }
+ } else {
+ goBackOnePageOrQuit();
+ }
}
- handled = true;
- break;
-
- default:
- break;
- }
+ return true;
+ }
+ break;
}
- return handled || super.onKeyDown(keyCode, event);
+ return super.onKeyUp(keyCode, event);
}
- @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_MENU) {
- mMenuIsDown = false;
- }
- return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
- }
-
- private void stopLoading() {
+ /* package */ void stopLoading() {
+ mDidStopLoad = true;
resetTitleAndRevertLockIcon();
WebView w = getTopWindow();
w.stopLoading();
@@ -2678,37 +2318,17 @@
// Message Ids
private static final int FOCUS_NODE_HREF = 102;
private static final int CANCEL_CREDS_REQUEST = 103;
- private static final int ANIMATE_FROM_OVERVIEW = 104;
- private static final int ANIMATE_TO_OVERVIEW = 105;
- private static final int OPEN_TAB_AND_SHOW = 106;
- private static final int CHECK_MEMORY = 107;
- private static final int RELEASE_WAKELOCK = 108;
+ private static final int RELEASE_WAKELOCK = 107;
+
+ private static final int UPDATE_BOOKMARK_THUMBNAIL = 108;
// Private handler for handling javascript and saving passwords
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
- case ANIMATE_FROM_OVERVIEW:
- final HashMap map = (HashMap) msg.obj;
- animateFromTabOverview((AnimatingView) map.get("view"),
- msg.arg1 == 1, (Message) map.get("msg"));
- break;
-
- case ANIMATE_TO_OVERVIEW:
- animateToTabOverview(msg.arg1, msg.arg2 == 1,
- (AnimatingView) msg.obj);
- break;
-
- case OPEN_TAB_AND_SHOW:
- // Decrement mAnimationCount before openTabAndShow because
- // the method relies on the value being 0 to start the next
- // animation.
- mAnimationCount--;
- openTabAndShow((String) msg.obj, null, false, null);
- break;
-
case FOCUS_NODE_HREF:
+ {
String url = (String) msg.getData().get("url");
if (url == null || url.length() == 0) {
break;
@@ -2739,7 +2359,8 @@
startActivity(intent);
break;
case R.id.share_link_context_menu_id:
- Browser.sendString(BrowserActivity.this, url);
+ Browser.sendString(BrowserActivity.this, url,
+ getText(R.string.choosertitle_sharevia).toString());
break;
case R.id.copy_link_context_menu_id:
copy(url);
@@ -2750,6 +2371,7 @@
break;
}
break;
+ }
case LOAD_URL:
loadURL(getTopWindow(), (String) msg.obj);
@@ -2763,23 +2385,112 @@
resumeAfterCredentials();
break;
- case CHECK_MEMORY:
- // reschedule to check memory condition
- mHandler.removeMessages(CHECK_MEMORY);
- mHandler.sendMessageDelayed(mHandler.obtainMessage
- (CHECK_MEMORY), CHECK_MEMORY_INTERVAL);
- checkMemory();
- break;
-
case RELEASE_WAKELOCK:
if (mWakeLock.isHeld()) {
mWakeLock.release();
}
break;
+
+ case UPDATE_BOOKMARK_THUMBNAIL:
+ WebView view = (WebView) msg.obj;
+ if (view != null) {
+ updateScreenshot(view);
+ }
+ break;
}
}
};
+ 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.
+
+ ContentResolver cr = getContentResolver();
+ final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
+ cr, view.getOriginalUrl(), view.getUrl(), true);
+ if (c != null) {
+ boolean succeed = c.moveToFirst();
+ ContentValues values = null;
+ while (succeed) {
+ if (values == null) {
+ final ByteArrayOutputStream os
+ = new ByteArrayOutputStream();
+ Bitmap bm = createScreenshot(view);
+ if (bm == null) {
+ c.close();
+ return;
+ }
+ 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();
+ }
+ }
+
+ /**
+ * Values for the size of the thumbnail created when taking a screenshot.
+ * Lazily initialized. Instead of using these directly, use
+ * getDesiredThumbnailWidth() or getDesiredThumbnailHeight().
+ */
+ private static int THUMBNAIL_WIDTH = 0;
+ private static int THUMBNAIL_HEIGHT = 0;
+
+ /**
+ * Return the desired width for thumbnail screenshots, which are stored in
+ * the database, and used on the bookmarks screen.
+ * @param context Context for finding out the density of the screen.
+ * @return int desired width for thumbnail screenshot.
+ */
+ /* package */ static int getDesiredThumbnailWidth(Context context) {
+ if (THUMBNAIL_WIDTH == 0) {
+ float density = context.getResources().getDisplayMetrics().density;
+ THUMBNAIL_WIDTH = (int) (90 * density);
+ THUMBNAIL_HEIGHT = (int) (80 * density);
+ }
+ return THUMBNAIL_WIDTH;
+ }
+
+ /**
+ * Return the desired height for thumbnail screenshots, which are stored in
+ * the database, and used on the bookmarks screen.
+ * @param context Context for finding out the density of the screen.
+ * @return int desired height for thumbnail screenshot.
+ */
+ /* package */ static int getDesiredThumbnailHeight(Context context) {
+ // To ensure that they are both initialized.
+ getDesiredThumbnailWidth(context);
+ return THUMBNAIL_HEIGHT;
+ }
+
+ private Bitmap createScreenshot(WebView view) {
+ Picture thumbnail = view.capturePicture();
+ if (thumbnail == null) {
+ return null;
+ }
+ Bitmap bm = Bitmap.createBitmap(getDesiredThumbnailWidth(this),
+ getDesiredThumbnailHeight(this), Bitmap.Config.ARGB_4444);
+ Canvas canvas = new Canvas(bm);
+ // May need to tweak these values to determine what is the
+ // best scale factor
+ int thumbnailWidth = thumbnail.getWidth();
+ if (thumbnailWidth > 0) {
+ float scaleFactor = (float) getDesiredThumbnailWidth(this) /
+ (float)thumbnailWidth;
+ canvas.scale(scaleFactor, scaleFactor);
+ }
+ thumbnail.draw(canvas);
+ return bm;
+ }
+
// -------------------------------------------------------------------------
// WebViewClient implementation.
//-------------------------------------------------------------------------
@@ -2794,10 +2505,18 @@
return mWebViewClient;
}
+ private void updateIcon(WebView view, Bitmap icon) {
+ if (icon != null) {
+ BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
+ view.getOriginalUrl(), view.getUrl(), icon);
+ }
+ setFavicon(icon);
+ }
+
private void updateIcon(String url, Bitmap icon) {
if (icon != null) {
BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
- url, icon);
+ null, url, icon);
}
setFavicon(icon);
}
@@ -2807,35 +2526,45 @@
public void onPageStarted(WebView view, String url, Bitmap favicon) {
resetLockIcon(url);
setUrlTitle(url, null);
+
+ // We've started to load a new page. If there was a pending message
+ // to save a screenshot then we will now take the new page and
+ // save an incorrect screenshot. Therefore, remove any pending
+ // thumbnail messages from the queue.
+ mHandler.removeMessages(UPDATE_BOOKMARK_THUMBNAIL);
+
+ // If we start a touch icon load and then load a new page, we don't
+ // want to cancel the current touch icon loader. But, we do want to
+ // create a new one when the touch icon url is known.
+ if (mTouchIconLoader != null) {
+ mTouchIconLoader.mActivity = null;
+ mTouchIconLoader = 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);
- if (mSettings.isTracing() == true) {
- // FIXME: we should save the trace file somewhere other than data.
- // I can't use "/tmp" as it competes for system memory.
- File file = getDir("browserTrace", 0);
- String baseDir = file.getPath();
- if (!baseDir.endsWith(File.separator)) baseDir += File.separator;
+ if (mSettings.isTracing()) {
String host;
try {
WebAddress uri = new WebAddress(url);
host = uri.mHost;
} catch (android.net.ParseException ex) {
- host = "unknown_host";
+ host = "browser";
}
host = host.replace('.', '_');
- baseDir = baseDir + host;
- file = new File(baseDir+".data");
- if (file.exists() == true) {
- file.delete();
- }
- file = new File(baseDir+".key");
- if (file.exists() == true) {
- file.delete();
- }
+ host += ".trace";
mInTrace = true;
- Debug.startMethodTracing(baseDir, 8 * 1024 * 1024);
+ Debug.startMethodTracing(host, 20 * 1024 * 1024);
}
// Performance probe
@@ -2855,31 +2584,24 @@
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
CookieSyncManager.getInstance().resetSync();
mInLoad = true;
+ mDidStopLoad = false;
+ showFakeTitleBar();
updateInLoadMenuItems();
if (!mIsNetworkUp) {
- if ( mAlertDialog == null) {
- mAlertDialog = new AlertDialog.Builder(BrowserActivity.this)
- .setTitle(R.string.loadSuspendedTitle)
- .setMessage(R.string.loadSuspended)
- .setPositiveButton(R.string.ok, null)
- .show();
- }
+ createAndShowNetworkDialog();
if (view != null) {
view.setNetworkAvailable(false);
}
}
-
- // schedule to check memory condition
- mHandler.sendMessageDelayed(mHandler.obtainMessage(CHECK_MEMORY),
- CHECK_MEMORY_INTERVAL);
}
@Override
@@ -2888,8 +2610,15 @@
// load.
resetTitleAndIcon(view);
+ if (!mDidStopLoad) {
+ // Only update the bookmark screenshot if the user did not
+ // cancel the load early.
+ Message updateScreenshot = Message.obtain(mHandler, UPDATE_BOOKMARK_THUMBNAIL, view);
+ mHandler.sendMessageDelayed(updateScreenshot, 500);
+ }
+
// Update the lock icon image only once we are done loading
- updateLockIconImage(mLockIconType);
+ updateLockIconToLatest();
// Performance probe
if (false) {
@@ -2977,18 +2706,15 @@
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();
}
}
}
-
- mHandler.removeMessages(CHECK_MEMORY);
- checkMemory();
}
// return true if want to hijack the url to let another app to handle it
@@ -3025,9 +2751,9 @@
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);
@@ -3167,7 +2893,7 @@
}
}
ErrorDialog errDialog = new ErrorDialog(
- err == EventHandler.FILE_NOT_FOUND_ERROR ?
+ err == WebViewClient.ERROR_FILE_NOT_FOUND ?
R.string.browserFrameFileErrorLabel :
R.string.browserFrameNetworkErrorLabel,
desc, err);
@@ -3196,11 +2922,11 @@
@Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
- if (errorCode != EventHandler.ERROR_LOOKUP &&
- errorCode != EventHandler.ERROR_CONNECT &&
- errorCode != EventHandler.ERROR_BAD_URL &&
- errorCode != EventHandler.ERROR_UNSUPPORTED_SCHEME &&
- errorCode != EventHandler.FILE_ERROR) {
+ if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
+ errorCode != WebViewClient.ERROR_CONNECT &&
+ errorCode != WebViewClient.ERROR_BAD_URL &&
+ errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
+ errorCode != WebViewClient.ERROR_FILE) {
queueError(errorCode, description);
}
Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
@@ -3247,6 +2973,19 @@
if (url.regionMatches(true, 0, "about:", 0, 6)) {
return;
}
+ // remove "client" before updating it to the history so that it wont
+ // show up in the auto-complete list.
+ int index = url.indexOf("client=ms-");
+ if (index > 0 && url.contains(".google.")) {
+ int end = url.indexOf('&', index);
+ if (end > 0) {
+ url = url.substring(0, index)
+ .concat(url.substring(end + 1));
+ } else {
+ // the url.charAt(index-1) should be either '?' or '&'
+ url = url.substring(0, index-1);
+ }
+ }
Browser.updateVisitedHistory(mResolver, url, true);
WebIconDatabase.getInstance().retainIconForPageUrl(url);
}
@@ -3412,28 +3151,21 @@
msg.sendToTarget();
} else {
final TabControl.Tab parent = mTabControl.getCurrentTab();
- // openTabAndShow will dispatch the message after creating the
- // new WebView. This will prevent another request from coming
- // in during the animation.
- final TabControl.Tab newTab =
- openTabAndShow(EMPTY_URL_DATA, msg, false, null);
+ final TabControl.Tab newTab
+ = openTabAndShow(EMPTY_URL_DATA, false, null);
if (newTab != parent) {
parent.addChildTab(newTab);
}
WebView.WebViewTransport transport =
(WebView.WebViewTransport) msg.obj;
transport.setWebView(mTabControl.getCurrentWebView());
+ msg.sendToTarget();
}
}
@Override
public boolean onCreateWindow(WebView view, final boolean dialog,
final boolean userGesture, final Message resultMsg) {
- // Ignore these requests during tab animations or if the tab
- // overview is showing.
- if (mAnimationCount > 0 || mTabOverview != null) {
- return false;
- }
// Short-circuit if we can't create any more tabs or sub windows.
if (dialog && mTabControl.getCurrentSubWindow() != null) {
new AlertDialog.Builder(BrowserActivity.this)
@@ -3455,9 +3187,6 @@
// Short-circuit if this was a user gesture.
if (userGesture) {
- // createWindow will call openTabAndShow for new Windows and
- // that will call tabPicker which will increment
- // mAnimationCount.
createWindow(dialog, resultMsg);
return true;
}
@@ -3467,12 +3196,7 @@
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface d,
int which) {
- // Same comment as above for setting
- // mAnimationCount.
createWindow(dialog, resultMsg);
- // Since we incremented mAnimationCount while the
- // dialog was up, we have to decrement it here.
- mAnimationCount--;
}
};
@@ -3481,9 +3205,6 @@
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface d, int which) {
resultMsg.sendToTarget();
- // We are not going to trigger an animation so
- // unblock keys and animation requests.
- mAnimationCount--;
}
};
@@ -3500,51 +3221,57 @@
// Show the confirmation dialog.
d.show();
- // We want to increment mAnimationCount here to prevent a
- // potential race condition. If the user allows a pop-up from a
- // site and that pop-up then triggers another pop-up, it is
- // possible to get the BACK key between here and when the dialog
- // appears.
- mAnimationCount++;
return true;
}
@Override
public void onCloseWindow(WebView window) {
- final int currentIndex = mTabControl.getCurrentIndex();
- final TabControl.Tab parent =
- mTabControl.getCurrentTab().getParentTab();
+ final TabControl.Tab current = mTabControl.getCurrentTab();
+ final TabControl.Tab parent = current.getParentTab();
if (parent != null) {
// JavaScript can only close popup window.
- switchTabs(currentIndex, mTabControl.getTabIndex(parent), true);
+ switchToTab(mTabControl.getTabIndex(parent));
+ // Now we need to close the window
+ closeTab(current);
}
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
- // 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);
+ mTitleBar.setProgress(newProgress);
+ if (mFakeTitleBar != null) {
+ mFakeTitleBar.setProgress(newProgress);
}
if (newProgress == 100) {
- // onProgressChanged() is called for sub-frame too while
- // onPageFinished() is only called for the main frame. sync
- // cookie and cache promptly here.
+ // onProgressChanged() may continue to be called after the main
+ // frame has finished loading, as any remaining sub frames
+ // continue to load. We'll only get called once though with
+ // newProgress as 100 when everything is loaded.
+ // (onPageFinished is called once when the main frame completes
+ // loading regardless of the state of any sub frames so calls
+ // to onProgressChanges may continue after onPageFinished has
+ // executed)
+
+ // sync cookies and cache promptly here.
CookieSyncManager.getInstance().sync();
if (mInLoad) {
mInLoad = false;
updateInLoadMenuItems();
+ // If the options menu is open, leave the title bar
+ if (!mOptionsMenuOpen || !mIconView) {
+ hideFakeTitleBar();
+ }
}
- } else {
+ } else if (!mInLoad) {
// onPageFinished may have already been called but a subframe
// is still loading and updating the progress. Reset mInLoad
// and update the menu items.
- if (!mInLoad) {
- mInLoad = true;
- updateInLoadMenuItems();
+ mInLoad = true;
+ updateInLoadMenuItems();
+ if (!mOptionsMenuOpen || mIconView) {
+ // This page has begun to load, so show the title bar
+ showFakeTitleBar();
}
}
}
@@ -3560,6 +3287,8 @@
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://")) {
@@ -3574,9 +3303,6 @@
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();
@@ -3594,8 +3320,200 @@
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
- updateIcon(view.getUrl(), icon);
+ updateIcon(view, icon);
}
+
+ @Override
+ public void onReceivedTouchIconUrl(WebView view, String url,
+ boolean precomposed) {
+ final ContentResolver cr = getContentResolver();
+ final Cursor c =
+ BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
+ view.getOriginalUrl(), view.getUrl(), true);
+ if (c != null) {
+ if (c.getCount() > 0) {
+ // Let precomposed icons take precedence over non-composed
+ // icons.
+ if (precomposed && mTouchIconLoader != null) {
+ mTouchIconLoader.cancel(false);
+ mTouchIconLoader = null;
+ }
+ // Have only one async task at a time.
+ if (mTouchIconLoader == null) {
+ mTouchIconLoader = new DownloadTouchIcon(
+ BrowserActivity.this, cr, c, view);
+ mTouchIconLoader.execute(url);
+ }
+ } else {
+ c.close();
+ }
+ }
+ }
+
+ @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 estimatedSize the estimated size of the database.
+ * @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 estimatedSize,
+ long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
+ mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
+ url, databaseIdentifier, currentQuota, estimatedSize,
+ 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);
+ }
+
+ /**
+ * Instructs the browser to show a prompt to ask the user to set the
+ * Geolocation permission state for the specified origin.
+ * @param origin The origin for which Geolocation permissions are
+ * requested.
+ * @param callback The callback to call once the user has set the
+ * Geolocation permission state.
+ */
+ @Override
+ public void onGeolocationPermissionsShowPrompt(String origin,
+ GeolocationPermissions.Callback callback) {
+ mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
+ origin, callback);
+ }
+
+ /**
+ * Instructs the browser to hide the Geolocation permissions prompt.
+ */
+ @Override
+ public void onGeolocationPermissionsHidePrompt() {
+ mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
+ }
+
+ /* 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);
+ }
+
+ /**
+ * Ask the browser for an icon to represent a <video> element.
+ * This icon will be used if the Web page did not specify a poster attribute.
+ *
+ * @return Bitmap The icon or null if no such icon is available.
+ * @hide pending API Council approval
+ */
+ @Override
+ public Bitmap getDefaultVideoPoster() {
+ if (mDefaultVideoPoster == null) {
+ mDefaultVideoPoster = BitmapFactory.decodeResource(
+ getResources(), R.drawable.default_video_poster);
+ }
+ return mDefaultVideoPoster;
+ }
+
+ /**
+ * Ask the host application for a custom progress view to show while
+ * a <video> is loading.
+ *
+ * @return View The progress view.
+ * @hide pending API Council approval
+ */
+ @Override
+ public View getVideoLoadingProgressView() {
+ if (mVideoProgressView == null) {
+ LayoutInflater inflater = LayoutInflater.from(BrowserActivity.this);
+ mVideoProgressView = inflater.inflate(R.layout.video_loading_progress, null);
+ }
+ return mVideoProgressView;
+ }
+
+ /**
+ * Deliver a list of already-visited URLs
+ * @hide pending API Council approval
+ */
+ @Override
+ public void getVisitedHistory(final ValueCallback<String[]> callback) {
+ AsyncTask<Void, Void, String[]> task = new AsyncTask<Void, Void, String[]>() {
+ public String[] doInBackground(Void... unused) {
+ return Browser.getVisitedHistory(getContentResolver());
+ }
+
+ public void onPostExecute(String[] result) {
+ callback.onReceiveValue(result);
+
+ };
+ };
+ task.execute();
+ };
};
/**
@@ -3612,23 +3530,36 @@
// if we're dealing wih A/V content that's not explicitly marked
// for download, check if it's streamable.
if (contentDisposition == null
- || !contentDisposition.regionMatches(true, 0, "attachment", 0, 10)) {
+ || !contentDisposition.regionMatches(
+ true, 0, "attachment", 0, 10)) {
// query the package manager to see if there's a registered handler
// that matches.
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(url), mimetype);
- if (getPackageManager().resolveActivity(intent,
- PackageManager.MATCH_DEFAULT_ONLY) != null) {
- // someone knows how to handle this mime type with this scheme, don't download.
- try {
- startActivity(intent);
- return;
- } catch (ActivityNotFoundException ex) {
- if (LOGD_ENABLED) {
- Log.d(LOGTAG, "activity not found for " + mimetype
- + " over " + Uri.parse(url).getScheme(), ex);
+ ResolveInfo info = getPackageManager().resolveActivity(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ if (info != null) {
+ ComponentName myName = getComponentName();
+ // If we resolved to ourselves, we don't want to attempt to
+ // load the url only to try and download it again.
+ if (!myName.getPackageName().equals(
+ info.activityInfo.packageName)
+ || !myName.getClassName().equals(
+ info.activityInfo.name)) {
+ // someone (other than us) knows how to handle this mime
+ // type with this scheme, don't download.
+ try {
+ startActivity(intent);
+ return;
+ } catch (ActivityNotFoundException ex) {
+ if (LOGD_ENABLED) {
+ Log.d(LOGTAG, "activity not found for " + mimetype
+ + " over " + Uri.parse(url).getScheme(),
+ ex);
+ }
+ // Best behavior is to fall back to a download in this
+ // case
}
- // Best behavior is to fall back to a download in this case
}
}
}
@@ -3711,19 +3642,19 @@
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
@@ -3757,23 +3688,27 @@
updateLockIconImage(LOCK_ICON_UNSECURE);
}
+ /* package */ void setLockIconType(int type) {
+ mLockIconType = type;
+ }
+
+ /* package */ int getLockIconType() {
+ return mLockIconType;
+ }
+
+ /* package */ void setPrevLockType(int type) {
+ mPrevLockType = type;
+ }
+
+ /* package */ int getPrevLockType() {
+ return mPrevLockType;
+ }
+
/**
- * Resets the lock icon. This method is called when the icon needs to be
- * reset but we do not know whether we are loading a secure or not secure
- * page.
+ * Update the lock icon to correspond to our latest state.
*/
- private void resetLockIcon() {
- // Save the lock-icon state (we revert to it if the load gets cancelled)
- saveLockIcon();
-
- mLockIconType = LOCK_ICON_UNSECURE;
-
- if (LOGV_ENABLED) {
- Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
- " reset lock icon to " + mLockIconType);
- }
-
- updateLockIconImage(LOCK_ICON_UNSECURE);
+ /* package */ void updateLockIconToLatest() {
+ updateLockIconImage(mLockIconType);
}
/**
@@ -3786,10 +3721,9 @@
} else if (lockIconType == LOCK_ICON_MIXED) {
d = mMixLockIcon;
}
- // 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);
+ mTitleBar.setLock(d);
+ if (mFakeTitleBar != null) {
+ mFakeTitleBar.setLock(d);
}
}
@@ -4259,13 +4193,9 @@
}
} else {
mIsNetworkUp = false;
- if (mInLoad && mAlertDialog == null) {
- mAlertDialog = new AlertDialog.Builder(this)
- .setTitle(R.string.loadSuspendedTitle)
- .setMessage(R.string.loadSuspended)
- .setPositiveButton(R.string.ok, null)
- .show();
- }
+ if (mInLoad) {
+ createAndShowNetworkDialog();
+ }
}
WebView w = mTabControl.getCurrentWebView();
if (w != null) {
@@ -4273,6 +4203,18 @@
}
}
+ // This method shows the network dialog alerting the user that the net is
+ // down. It will only show the dialog if mAlertDialog is null.
+ private void createAndShowNetworkDialog() {
+ if (mAlertDialog == null) {
+ mAlertDialog = new AlertDialog.Builder(this)
+ .setTitle(R.string.loadSuspendedTitle)
+ .setMessage(R.string.loadSuspended)
+ .setPositiveButton(R.string.ok, null)
+ .show();
+ }
+ }
+
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
@@ -4286,17 +4228,9 @@
} else {
final TabControl.Tab currentTab =
mTabControl.getCurrentTab();
- // If the Window overview is up and we are not in the
- // 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);
- } else {
- dismissSubWindow(currentTab);
- if (data != null && data.length() != 0) {
- getTopWindow().loadUrl(data);
- }
+ dismissSubWindow(currentTab);
+ if (data != null && data.length() != 0) {
+ getTopWindow().loadUrl(data);
}
}
}
@@ -4321,176 +4255,11 @@
}
/**
- * Handle results from Tab Switcher mTabOverview tool
+ * Open the Go page.
+ * @param startWithHistory If true, open starting on the history tab.
+ * Otherwise, start with the bookmarks tab.
*/
- private class TabListener implements ImageGrid.Listener {
- public void remove(int position) {
- // Note: Remove is not enabled if we have only one tab.
- if (DEBUG && mTabControl.getTabCount() == 1) {
- throw new AssertionError();
- }
-
- // Remember the current tab.
- TabControl.Tab current = mTabControl.getCurrentTab();
- final TabControl.Tab remove = mTabControl.getTab(position);
- mTabControl.removeTab(remove);
- // If we removed the current tab, use the tab at position - 1 if
- // possible.
- if (current == remove) {
- // If the user removes the last tab, act like the New Tab item
- // was clicked on.
- if (mTabControl.getTabCount() == 0) {
- current = mTabControl.createNewTab();
- sendAnimateFromOverview(current, true,
- new UrlData(mSettings.getHomePage()), TAB_OVERVIEW_DELAY, null);
- } else {
- final int index = position > 0 ? (position - 1) : 0;
- current = mTabControl.getTab(index);
- }
- }
-
- // The tab overview could have been dismissed before this method is
- // called.
- if (mTabOverview != null) {
- // Remove the tab and change the index.
- mTabOverview.remove(position);
- mTabOverview.setCurrentIndex(mTabControl.getTabIndex(current));
- }
-
- // Only the current tab ensures its WebView is non-null. This
- // implies that we are reloading the freed tab.
- mTabControl.setCurrentTab(current);
- }
- public void onClick(int index) {
- // Change the tab if necessary.
- // Index equals ImageGrid.CANCEL when pressing back from the tab
- // overview.
- if (index == ImageGrid.CANCEL) {
- index = mTabControl.getCurrentIndex();
- // The current index is -1 if the current tab was removed.
- if (index == -1) {
- // Take the last tab as a fallback.
- index = mTabControl.getTabCount() - 1;
- }
- }
-
- // NEW_TAB means that the "New Tab" cell was clicked on.
- if (index == ImageGrid.NEW_TAB) {
- openTabAndShow(mSettings.getHomePage(), null, false, null);
- } else {
- sendAnimateFromOverview(mTabControl.getTab(index),
- false, EMPTY_URL_DATA, 0, null);
- }
- }
- }
-
- // A fake View that draws the WebView's picture with a fast zoom filter.
- // The View is used in case the tab is freed during the animation because
- // of low memory.
- private static class AnimatingView extends View {
- private static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG |
- Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG;
- private static final DrawFilter sZoomFilter =
- new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
- private final Picture mPicture;
- private final float mScale;
- private final int mScrollX;
- private final int mScrollY;
- final TabControl.Tab mTab;
-
- 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();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- canvas.save();
- canvas.drawColor(Color.WHITE);
- if (mPicture != null) {
- canvas.setDrawFilter(sZoomFilter);
- float scale = getWidth() * mScale;
- canvas.scale(scale, scale);
- canvas.translate(-mScrollX, -mScrollY);
- canvas.drawPicture(mPicture);
- }
- canvas.restore();
- }
- }
-
- /**
- * Open the tab picker. This function will always use the current tab in
- * its animation.
- * @param stay boolean stating whether the tab picker is to remain open
- * (in which case it needs a listener and its menu) or not.
- * @param index The index of the tab to show as the selection in the tab
- * overview.
- * @param remove If true, the tab at index will be removed after the
- * animation completes.
- */
- private void tabPicker(final boolean stay, final int index,
- final boolean remove) {
- if (mTabOverview != null) {
- return;
- }
-
- int size = mTabControl.getTabCount();
-
- TabListener l = null;
- if (stay) {
- l = mTabListener = new TabListener();
- }
- mTabOverview = new ImageGrid(this, stay, l);
-
- for (int i = 0; i < size; i++) {
- final TabControl.Tab t = mTabControl.getTab(i);
- mTabControl.populatePickerData(t);
- mTabOverview.add(t);
- }
-
- // Tell the tab overview to show the current tab, the tab overview will
- // handle the "New Tab" case.
- int currentIndex = mTabControl.getCurrentIndex();
- mTabOverview.setCurrentIndex(currentIndex);
-
- // Attach the tab overview.
- mContentView.addView(mTabOverview, COVER_SCREEN_PARAMS);
-
- // Create a fake AnimatingView to animate the WebView's picture.
- final TabControl.Tab current = mTabControl.getCurrentTab();
- final AnimatingView v = new AnimatingView(this, current);
- mContentView.addView(v, COVER_SCREEN_PARAMS);
- removeTabFromContentView(current);
- // Pause timers to get the animation smoother.
- current.getWebView().pauseTimers();
-
- // Send a message so the tab picker has a chance to layout and get
- // positions for all the cells.
- mHandler.sendMessage(mHandler.obtainMessage(ANIMATE_TO_OVERVIEW,
- index, remove ? 1 : 0, v));
- // Setting this will indicate that we are animating to the overview. We
- // set it here to prevent another request to animate from coming in
- // between now and when ANIMATE_TO_OVERVIEW is handled.
- 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);
- // 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;
@@ -4499,6 +4268,8 @@
CombinedBookmarkHistoryActivity.class);
String title = current.getTitle();
String url = current.getUrl();
+ Bitmap thumbnail = createScreenshot(current);
+
// Just in case the user opens bookmarks before a page finishes loading
// so the current history item, and therefore the page, is null.
if (null == url) {
@@ -4514,8 +4285,11 @@
}
intent.putExtra("title", title);
intent.putExtra("url", url);
- intent.putExtra("maxTabsOpen",
- mTabControl.getTabCount() >= TabControl.MAX_TABS);
+ intent.putExtra("thumbnail", thumbnail);
+ // Disable opening in a new window if we have maxed out the windows
+ intent.putExtra("disable_new_window", mTabControl.getTabCount()
+ >= TabControl.MAX_TABS);
+ intent.putExtra("touch_icon_url", current.getTouchIconUrl());
if (startWithHistory) {
intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
CombinedBookmarkHistoryActivity.HISTORY_TAB);
@@ -4534,21 +4308,6 @@
}
}
- private void checkMemory() {
- ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
- ((ActivityManager) getSystemService(ACTIVITY_SERVICE))
- .getMemoryInfo(mi);
- // FIXME: mi.lowMemory is too aggressive, use (mi.availMem <
- // mi.threshold) for now
- // if (mi.lowMemory) {
- if (mi.availMem < mi.threshold) {
- Log.w(LOGTAG, "Browser is freeing memory now because: available="
- + (mi.availMem / 1024) + "K threshold="
- + (mi.threshold / 1024) + "K");
- mTabControl.freeMemory();
- }
- }
-
private String smartUrlFilter(Uri inUri) {
if (inUri != null) {
return smartUrlFilter(inUri.toString());
@@ -4566,7 +4325,7 @@
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):\\/\\/" +
@@ -4631,9 +4390,37 @@
return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
}
- private final static int LOCK_ICON_UNSECURE = 0;
- private final static int LOCK_ICON_SECURE = 1;
- private final static int LOCK_ICON_MIXED = 2;
+ /* 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);
+ }
+
+ }
+
+ final static int LOCK_ICON_UNSECURE = 0;
+ final static int LOCK_ICON_SECURE = 1;
+ final static int LOCK_ICON_MIXED = 2;
private int mLockIconType = LOCK_ICON_UNSECURE;
private int mPrevLockType = LOCK_ICON_UNSECURE;
@@ -4642,12 +4429,15 @@
private TabControl mTabControl;
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;
@@ -4658,17 +4448,13 @@
private boolean mInLoad;
private boolean mIsNetworkUp;
+ private boolean mDidStopLoad;
private boolean mPageStarted;
private boolean mActivityInPause = true;
private boolean mMenuIsDown;
- private final KeyTracker mKeyTracker = new KeyTracker(this);
-
- // As trackball doesn't send repeat down, we have to track it ourselves
- private boolean mTrackTrackball;
-
private static boolean mInTrace;
// Performance probe
@@ -4694,7 +4480,6 @@
private Drawable mMixLockIcon;
private Drawable mSecLockIcon;
- private Drawable mGenericFavicon;
/* hold a ref so we can auto-cancel if necessary */
private AlertDialog mAlertDialog;
@@ -4738,6 +4523,11 @@
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,8 +4552,6 @@
private final static String LOGTAG = "browser";
- private TabListener mTabListener;
-
private String mLastEnteredUrl;
private PowerManager.WakeLock mWakeLock;
@@ -4771,11 +4559,10 @@
private Toast mStopToast;
- // 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
- // there are animations in progress.
- private int mAnimationCount;
+ private TitleBar mTitleBar;
+
+ private LinearLayout mErrorConsoleContainer = null;
+ private boolean mShouldShowErrorConsole = false;
// As the ids are dynamically created, we can't guarantee that they will
// be in sequence, so this static array maps ids to a window number.
@@ -4788,13 +4575,20 @@
private IntentFilter mNetworkStateChangedFilter;
private BroadcastReceiver mNetworkStateIntentReceiver;
- // activity requestCode
- final static int COMBO_PAGE = 1;
- final static int DOWNLOAD_PAGE = 2;
- final static int PREFERENCES_PAGE = 3;
+ private BroadcastReceiver mPackageInstallationReceiver;
- // the frenquency of checking whether system memory is low
- final static int CHECK_MEMORY_INTERVAL = 30000; // 30 seconds
+ // AsyncTask for downloading touch icons
+ /* package */ DownloadTouchIcon mTouchIconLoader;
+
+ // activity requestCode
+ final static int COMBO_PAGE = 1;
+ final static int DOWNLOAD_PAGE = 2;
+ final static int PREFERENCES_PAGE = 3;
+
+ // the default <video> poster
+ private Bitmap mDefaultVideoPoster;
+ // the video progress view
+ private View mVideoProgressView;
/**
* A UrlData class to abstract how the content will be set to WebView.
@@ -4841,7 +4635,7 @@
String mEncoding;
@Override
boolean isEmpty() {
- return mInlined == null || mInlined.length() == 0 || super.isEmpty();
+ return mInlined == null || mInlined.length() == 0 || super.isEmpty();
}
@Override
@@ -4850,5 +4644,5 @@
}
}
- private static final UrlData EMPTY_URL_DATA = new UrlData(null);
+ /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
}
diff --git a/src/com/android/browser/BrowserBackupAgent.java b/src/com/android/browser/BrowserBackupAgent.java
new file mode 100644
index 0000000..387555d
--- /dev/null
+++ b/src/com/android/browser/BrowserBackupAgent.java
@@ -0,0 +1,298 @@
+/*
+ * 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 = "BrowserBackupAgent";
+ static final boolean DEBUG = false;
+
+ 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();
+ int nUnique = 0;
+ 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) {
+ if (DEBUG) 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, null, false);
+ nUnique++;
+ } else {
+ if (DEBUG) Log.v(TAG, "Skipping extant url: " + mark.url);
+ }
+ cursor.close();
+ }
+ Log.i(TAG, "Restored " + nUnique + " of " + N + " bookmarks");
+ } 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..dd56d2f 100644
--- a/src/com/android/browser/BrowserBookmarksAdapter.java
+++ b/src/com/android/browser/BrowserBookmarksAdapter.java
@@ -30,38 +30,37 @@
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.webkit.WebView;
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 Bitmap mCurrentThumbnail;
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;
-
- // When true, this adapter is used to pick a bookmark to create a shortcut
- private boolean mCreateShortcut;
- private int mExtraOffset;
+ private BookmarkViewMode mViewMode;
+ private boolean mMostVisited;
+ private boolean mNeedsOffset;
+ private int mExtraOffset;
// Implementation of WebIconDatabase.IconListener
private class IconReceiver implements IconListener {
public void onReceivedIcon(String url, Bitmap icon) {
- updateBookmarkFavicon(mContentResolver, url, icon);
+ updateBookmarkFavicon(mContentResolver, null, url, icon);
}
}
@@ -70,36 +69,44 @@
/**
* 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) {
- mDataValid = false;
- mCreateShortcut = createShortcut;
- mExtraOffset = createShortcut ? 0 : 1;
+ String curTitle, Bitmap curThumbnail, boolean createShortcut,
+ boolean mostVisited) {
+ mNeedsOffset = !(createShortcut || mostVisited);
+ mMostVisited = mostVisited;
+ mExtraOffset = mNeedsOffset ? 1 : 0;
mBookmarksPage = b;
- mCurrentPage = b.getResources().getString(R.string.current_page) +
- curPage;
+ mCurrentPage = b.getResources().getString(R.string.current_page)
+ + curPage;
+ mCurrentTitle = curTitle;
+ mCurrentThumbnail = curThumbnail;
mContentResolver = b.getContentResolver();
- mLastOrderBy = Browser.BookmarkColumns.CREATED + " DESC";
- mChangeObserver = new ChangeObserver();
- mDataSetObserver = new MyDataSetObserver();
+ mViewMode = BookmarkViewMode.LIST;
+
+ String whereClause;
// FIXME: Should have a default sort order that the user selects.
- search(null);
+ String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
+ if (mostVisited) {
+ whereClause = Browser.BookmarkColumns.VISITS + " != 0";
+ } else {
+ whereClause = Browser.BookmarkColumns.BOOKMARK + " != 0";
+ }
+ 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);
@@ -163,6 +170,10 @@
getString(Browser.HISTORY_PROJECTION_URL_INDEX))) {
values.put(Browser.BookmarkColumns.URL, url);
}
+
+ if (map.getBoolean("invalidateThumbnail") == true) {
+ values.put(Browser.BookmarkColumns.THUMBNAIL, new byte[0]);
+ }
if (values.size() > 0
&& mContentResolver.update(Browser.BOOKMARKS_URI, values,
"_id = " + id, null) != -1) {
@@ -181,18 +192,8 @@
}
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);
- }
+ String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
+ Bookmarks.removeFromBookmarks(null, mContentResolver, url, title);
refreshList();
}
@@ -253,84 +254,25 @@
* 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);
+ mCursor.requery();
+ mCount = mCursor.getCount() + mExtraOffset;
+ notifyDataSetChanged();
}
/**
- * 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);
- }
-
- /**
- * Update the bookmark's favicon.
+ * Update the bookmark's favicon. This is a convenience method for updating
+ * a bookmark favicon for the originalUrl and url of the passed in WebView.
* @param cr The ContentResolver to use.
- * @param url The url of the bookmark to update.
+ * @param originalUrl The original url before any redirects.
+ * @param url The current url.
* @param favicon The favicon bitmap to write to the db.
*/
/* package */ static void updateBookmarkFavicon(ContentResolver cr,
- String url, Bitmap favicon) {
- if (url == null || favicon == null) {
+ String originalUrl, String url, Bitmap favicon) {
+ final Cursor c = queryBookmarksForUrl(cr, originalUrl, url, true);
+ if (c == null) {
return;
}
- // Strip the query.
- int query = url.indexOf('?');
- String noQuery = url;
- if (query != -1) {
- noQuery = url.substring(0, query);
- }
- url = noQuery + '?';
- // Use noQuery to search for the base url (i.e. if the url is
- // http://www.yahoo.com/?rs=1, search for http://www.yahoo.com)
- // Use url to match the base url with other queries (i.e. if the url is
- // http://www.google.com/m, search for
- // http://www.google.com/m?some_query)
- final String[] selArgs = new String[] { noQuery, url };
- final String where = "(" + Browser.BookmarkColumns.URL + " == ? OR "
- + Browser.BookmarkColumns.URL + " GLOB ? || '*') AND "
- + Browser.BookmarkColumns.BOOKMARK + " == 1";
- final String[] projection = new String[] { Browser.BookmarkColumns._ID };
- final Cursor c = cr.query(Browser.BOOKMARKS_URI, projection, where,
- selArgs, null);
boolean succeed = c.moveToFirst();
ContentValues values = null;
while (succeed) {
@@ -347,44 +289,55 @@
c.close();
}
- /**
- * 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();
+ /* package */ static Cursor queryBookmarksForUrl(ContentResolver cr,
+ String originalUrl, String url, boolean onlyBookmarks) {
+ if (cr == null || url == null) {
+ return null;
}
- 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);
+ // If originalUrl is null, just set it to url.
+ if (originalUrl == null) {
+ originalUrl = url;
+ }
- mDataValid = true;
- notifyDataSetChanged();
+ // Look for both the original url and the actual url. This takes in to
+ // account redirects.
+ String originalUrlNoQuery = removeQuery(originalUrl);
+ String urlNoQuery = removeQuery(url);
+ originalUrl = originalUrlNoQuery + '?';
+ url = urlNoQuery + '?';
- mCount = mCursor.getCount() + mExtraOffset;
+ // Use NoQuery to search for the base url (i.e. if the url is
+ // http://www.yahoo.com/?rs=1, search for http://www.yahoo.com)
+ // Use url to match the base url with other queries (i.e. if the url is
+ // http://www.google.com/m, search for
+ // http://www.google.com/m?some_query)
+ final String[] selArgs = new String[] {
+ originalUrlNoQuery, urlNoQuery, originalUrl, url };
+ String where = BookmarkColumns.URL + " == ? OR "
+ + BookmarkColumns.URL + " == ? OR "
+ + BookmarkColumns.URL + " GLOB ? || '*' OR "
+ + BookmarkColumns.URL + " GLOB ? || '*'";
+ if (onlyBookmarks) {
+ where = "(" + where + ") AND " + BookmarkColumns.BOOKMARK + " == 1";
+ }
+ final String[] projection =
+ new String[] { Browser.BookmarkColumns._ID };
+ return cr.query(Browser.BOOKMARKS_URI, projection, where, selArgs,
+ null);
+ }
+
+ // Strip the query from the given url.
+ private static String removeQuery(String url) {
+ if (url == null) {
+ return null;
+ }
+ int query = url.indexOf('?');
+ String noQuery = url;
+ if (query != -1) {
+ noQuery = url.substring(0, query);
+ }
+ return noQuery;
}
/**
@@ -425,6 +378,26 @@
return position;
}
+ /* package */ void switchViewMode(BookmarkViewMode viewMode) {
+ mViewMode = viewMode;
+ }
+
+ /* package */ void populateBookmarkItem(BookmarkItem b, int position) {
+ mCursor.moveToPosition(position - mExtraOffset);
+ String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
+ b.setUrl(url);
+ b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
+ byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+ Bitmap bitmap = null;
+ if (data == null) {
+ bitmap = CombinedBookmarkHistoryActivity.getIconListenerSet()
+ .getFavicon(url);
+ } else {
+ bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+ }
+ b.setFavicon(bitmap);
+ }
+
/**
* Get a View that displays the data at the specified position
* in the list.
@@ -440,7 +413,45 @@
throw new AssertionError(
"BrowserBookmarksAdapter tried to get a view out of range");
}
- if (position == 0 && !mCreateShortcut) {
+ if (mViewMode == BookmarkViewMode.GRID) {
+ 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);
+ TextView tv = (TextView) convertView.findViewById(R.id.label);
+
+ if (0 == position && mNeedsOffset) {
+ // This is to create a bookmark for the current page.
+ holder.setVisibility(View.VISIBLE);
+ tv.setText(mCurrentTitle);
+
+ if (mCurrentThumbnail != null) {
+ thumb.setImageBitmap(mCurrentThumbnail);
+ } else {
+ thumb.setImageResource(
+ R.drawable.browser_thumbnail);
+ }
+ return convertView;
+ }
+ holder.setVisibility(View.GONE);
+ mCursor.moveToPosition(position - mExtraOffset);
+ tv.setText(mCursor.getString(
+ Browser.HISTORY_PROJECTION_TITLE_INDEX));
+ Bitmap thumbnail = getBitmap(Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX, position);
+ if (thumbnail == null) {
+ thumb.setImageResource(R.drawable.browser_thumbnail);
+ } else {
+ thumb.setImageBitmap(thumbnail);
+ }
+
+ return convertView;
+
+ }
+ if (position == 0 && mNeedsOffset) {
AddNewBookmark b;
if (convertView instanceof AddNewBookmark) {
b = (AddNewBookmark) convertView;
@@ -450,10 +461,20 @@
b.setUrl(mCurrentPage);
return b;
}
- if (convertView == null || convertView instanceof AddNewBookmark) {
- convertView = new BookmarkItem(mBookmarksPage);
+ if (mMostVisited) {
+ if (convertView == null || !(convertView instanceof HistoryItem)) {
+ convertView = new HistoryItem(mBookmarksPage);
+ }
+ } else {
+ if (convertView == null || !(convertView instanceof BookmarkItem)) {
+ convertView = new BookmarkItem(mBookmarksPage);
+ }
}
- bind((BookmarkItem)convertView, position);
+ bind((BookmarkItem) convertView, position);
+ if (mMostVisited) {
+ ((HistoryItem) convertView).setIsBookmark(
+ getIsBookmark(position));
+ }
return convertView;
}
@@ -475,11 +496,19 @@
* Return the favicon for this item in the list.
*/
public Bitmap getFavicon(int position) {
+ return getBitmap(Browser.HISTORY_PROJECTION_FAVICON_INDEX, position);
+ }
+
+ public Bitmap getTouchIcon(int position) {
+ return getBitmap(Browser.HISTORY_PROJECTION_TOUCH_ICON_INDEX, position);
+ }
+
+ private Bitmap getBitmap(int cursorIndex, int position) {
if (position < mExtraOffset || position > mCount) {
return null;
}
mCursor.moveToPosition(position - mExtraOffset);
- byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+ byte[] data = mCursor.getBlob(cursorIndex);
if (data == null) {
return null;
}
@@ -487,6 +516,17 @@
}
/**
+ * Return whether or not this item represents a bookmarked site.
+ */
+ public boolean getIsBookmark(int position) {
+ if (position < mExtraOffset || position > mCount) {
+ return false;
+ }
+ mCursor.moveToPosition(position - mExtraOffset);
+ return (1 == mCursor.getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX));
+ }
+
+ /**
* Private helper function to return the title or url.
*/
private String getString(int cursorIndex, int position) {
@@ -514,7 +554,8 @@
if (data != null) {
b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length));
} else {
- b.setFavicon(null);
+ b.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet()
+ .getFavicon(url));
}
}
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index dd34c14..6ab011b 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -20,11 +20,16 @@
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Bundle;
@@ -36,35 +41,47 @@
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;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewStub;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView;
+import android.widget.GridView;
import android.widget.ListView;
+import android.widget.Toast;
+/*package*/ enum BookmarkViewMode { NONE, GRID, LIST }
/**
* View showing the user's bookmarks in the browser.
*/
public class BrowserBookmarksPage extends Activity implements
View.OnCreateContextMenuListener {
+ private BookmarkViewMode mViewMode = BookmarkViewMode.NONE;
+ private GridView mGridPage;
+ private View mVerticalList;
private BrowserBookmarksAdapter mBookmarksAdapter;
private static final int BOOKMARKS_SAVE = 1;
- private boolean mMaxTabsOpen;
+ private boolean mDisableNewWindow;
private BookmarkItem mContextHeader;
private AddNewBookmark mAddHeader;
private boolean mCanceled = false;
private boolean mCreateShortcut;
+ private boolean mMostVisited;
+ private View mEmptyView;
// XXX: There is no public string defining this intent so if Home changes
// the value, we have to update this string.
private static final String INSTALL_SHORTCUT =
"com.android.launcher.action.INSTALL_SHORTCUT";
private final static String LOGTAG = "browser";
-
+ private final static String PREF_BOOKMARK_VIEW_MODE = "pref_bookmark_view_mode";
+ private final static String PREF_MOST_VISITED_VIEW_MODE = "pref_most_visited_view_mode";
@Override
public boolean onContextItemSelected(MenuItem item) {
@@ -91,23 +108,58 @@
editBookmark(i.position);
break;
case R.id.shortcut_context_menu_id:
- final Intent send = createShortcutIntent(getUrl(i.position),
- getBookmarkTitle(i.position), getFavicon(i.position));
+ final Intent send = createShortcutIntent(i.position);
send.setAction(INSTALL_SHORTCUT);
sendBroadcast(send);
break;
case R.id.delete_context_menu_id:
- displayRemoveBookmarkDialog(i.position);
+ if (mMostVisited) {
+ Browser.deleteFromHistory(getContentResolver(),
+ getUrl(i.position));
+ refreshList();
+ } else {
+ displayRemoveBookmarkDialog(i.position);
+ }
break;
case R.id.new_window_context_menu_id:
openInNewWindow(i.position);
break;
- case R.id.send_context_menu_id:
- Browser.sendString(BrowserBookmarksPage.this, getUrl(i.position));
+ case R.id.share_link_context_menu_id:
+ Browser.sendString(BrowserBookmarksPage.this, getUrl(i.position),
+ getText(R.string.choosertitle_sharevia).toString());
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;
+ // Only for the Most visited page
+ case R.id.save_to_bookmarks_menu_id:
+ boolean isBookmark;
+ String name;
+ String url;
+ if (mViewMode == BookmarkViewMode.GRID) {
+ isBookmark = mBookmarksAdapter.getIsBookmark(i.position);
+ name = mBookmarksAdapter.getTitle(i.position);
+ url = mBookmarksAdapter.getUrl(i.position);
+ } else {
+ HistoryItem historyItem = ((HistoryItem) i.targetView);
+ isBookmark = historyItem.isBookmark();
+ name = historyItem.getName();
+ url = historyItem.getUrl();
+ }
+ // If the site is bookmarked, the item becomes remove from
+ // bookmarks.
+ if (isBookmark) {
+ Bookmarks.removeFromBookmarks(this, getContentResolver(), url, name);
+ } else {
+ Browser.saveBookmark(this, name, url);
+ }
+ break;
default:
return super.onContextItemSelected(item);
}
@@ -121,9 +173,13 @@
(AdapterView.AdapterContextMenuInfo) menuInfo;
MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.bookmarkscontext, menu);
+ if (mMostVisited) {
+ inflater.inflate(R.menu.historycontext, menu);
+ } else {
+ inflater.inflate(R.menu.bookmarkscontext, menu);
+ }
- if (0 == i.position) {
+ if (0 == i.position && !mMostVisited) {
menu.setGroupVisible(R.id.CONTEXT_MENU, false);
if (mAddHeader == null) {
mAddHeader = new AddNewBookmark(BrowserBookmarksPage.this);
@@ -131,25 +187,40 @@
((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 (mMostVisited) {
+ if ((mViewMode == BookmarkViewMode.LIST
+ && ((HistoryItem) i.targetView).isBookmark())
+ || mBookmarksAdapter.getIsBookmark(i.position)) {
+ MenuItem item = menu.findItem(
+ R.id.save_to_bookmarks_menu_id);
+ item.setTitle(R.string.remove_from_bookmarks);
+ }
+ } else {
+ // The historycontext menu has no ADD_MENU group.
+ menu.setGroupVisible(R.id.ADD_MENU, false);
+ }
+ if (mDisableNewWindow) {
+ 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 (mViewMode == BookmarkViewMode.GRID) {
+ mBookmarksAdapter.populateBookmarkItem(mContextHeader,
+ i.position);
+ } else {
+ BookmarkItem b = (BookmarkItem) i.targetView;
+ b.copyTo(mContextHeader);
}
+ menu.setHeaderView(mContextHeader);
}
/**
@@ -159,28 +230,124 @@
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;
}
+ mDisableNewWindow = getIntent().getBooleanExtra("disable_new_window",
+ false);
+ mMostVisited = getIntent().getBooleanExtra("mostVisited", false);
- mBookmarksAdapter = new BrowserBookmarksAdapter(this,
- getIntent().getStringExtra("url"), mCreateShortcut);
- mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false);
+ if (mCreateShortcut) {
+ setTitle(R.string.browser_bookmarks_page_bookmarks_text);
+ }
+ mBookmarksAdapter = new BrowserBookmarksAdapter(this,
+ getIntent().getStringExtra("url"),
+ getIntent().getStringExtra("title"),
+ (Bitmap) getIntent().getParcelableExtra("thumbnail"),
+ mCreateShortcut,
+ mMostVisited);
- ListView listView = (ListView) findViewById(R.id.list);
- listView.setAdapter(mBookmarksAdapter);
- listView.setDrawSelectorOnTop(false);
- listView.setVerticalScrollBarEnabled(true);
- listView.setOnItemClickListener(mListener);
+ setContentView(R.layout.empty_history);
+ mEmptyView = findViewById(R.id.empty_view);
+ mEmptyView.setVisibility(View.GONE);
- if (!mCreateShortcut) {
- listView.setOnCreateContextMenuListener(this);
+ SharedPreferences p = getPreferences(MODE_PRIVATE);
+
+ // See if the user has set a preference for the view mode of their
+ // bookmarks. Otherwise default to grid mode.
+ BookmarkViewMode preference = BookmarkViewMode.NONE;
+ if (mMostVisited) {
+ // For the most visited page, only use list mode.
+ preference = BookmarkViewMode.LIST;
+ } else {
+ preference = BookmarkViewMode.values()[p.getInt(
+ PREF_BOOKMARK_VIEW_MODE, BookmarkViewMode.GRID.ordinal())];
+ }
+ switchViewMode(preference);
+ }
+
+ /**
+ * Set the ContentView to be either the grid of thumbnails or the vertical
+ * list.
+ */
+ private void switchViewMode(BookmarkViewMode gridMode) {
+ if (mViewMode == gridMode) {
+ return;
+ }
+
+ mViewMode = gridMode;
+
+ // Update the preferences to make the new view mode sticky.
+ Editor ed = getPreferences(MODE_PRIVATE).edit();
+ if (mMostVisited) {
+ ed.putInt(PREF_MOST_VISITED_VIEW_MODE, mViewMode.ordinal());
+ } else {
+ ed.putInt(PREF_BOOKMARK_VIEW_MODE, mViewMode.ordinal());
+ }
+ ed.commit();
+
+ mBookmarksAdapter.switchViewMode(gridMode);
+ if (mViewMode == BookmarkViewMode.GRID) {
+ if (mGridPage == null) {
+ mGridPage = new GridView(this);
+ mGridPage.setAdapter(mBookmarksAdapter);
+ mGridPage.setOnItemClickListener(mListener);
+ mGridPage.setNumColumns(GridView.AUTO_FIT);
+ mGridPage.setColumnWidth(
+ BrowserActivity.getDesiredThumbnailWidth(this));
+ mGridPage.setFocusable(true);
+ mGridPage.setFocusableInTouchMode(true);
+ mGridPage.setSelector(android.R.drawable.gallery_thumb);
+ float density = getResources().getDisplayMetrics().density;
+ mGridPage.setVerticalSpacing((int) (14 * density));
+ mGridPage.setHorizontalSpacing((int) (8 * density));
+ mGridPage.setStretchMode(GridView.STRETCH_SPACING);
+ mGridPage.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
+ mGridPage.setDrawSelectorOnTop(true);
+ if (mMostVisited) {
+ mGridPage.setEmptyView(mEmptyView);
+ }
+ if (!mCreateShortcut) {
+ mGridPage.setOnCreateContextMenuListener(this);
+ }
+ }
+ addContentView(mGridPage, FULL_SCREEN_PARAMS);
+ if (mVerticalList != null) {
+ ViewGroup parent = (ViewGroup) mVerticalList.getParent();
+ if (parent != null) {
+ parent.removeView(mVerticalList);
+ }
+ }
+ } else {
+ if (null == mVerticalList) {
+ ListView listView = new ListView(this);
+ listView.setAdapter(mBookmarksAdapter);
+ listView.setDrawSelectorOnTop(false);
+ listView.setVerticalScrollBarEnabled(true);
+ listView.setOnItemClickListener(mListener);
+ if (mMostVisited) {
+ listView.setEmptyView(mEmptyView);
+ }
+ if (!mCreateShortcut) {
+ listView.setOnCreateContextMenuListener(this);
+ }
+ mVerticalList = listView;
+ }
+ addContentView(mVerticalList, FULL_SCREEN_PARAMS);
+ if (mGridPage != null) {
+ ViewGroup parent = (ViewGroup) mGridPage.getParent();
+ if (parent != null) {
+ parent.removeView(mGridPage);
+ }
+ }
}
}
+ private static final ViewGroup.LayoutParams FULL_SCREEN_PARAMS
+ = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT);
+
private static final int SAVE_CURRENT_PAGE = 1000;
private final Handler mHandler = new Handler() {
@Override
@@ -196,27 +363,29 @@
// 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) {
- if (0 == position) {
+ if (0 == position && !mMostVisited) {
// XXX: Work-around for a framework issue.
mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE);
} else {
loadUrl(position);
}
} else {
- final Intent intent = createShortcutIntent(getUrl(position),
- getBookmarkTitle(position), getFavicon(position));
+ final Intent intent = createShortcutIntent(position);
setResultToParent(RESULT_OK, intent);
finish();
}
}
};
- private Intent createShortcutIntent(String url, String title,
- Bitmap favicon) {
+ private Intent createShortcutIntent(int position) {
+ String url = getUrl(position);
+ String title = getBookmarkTitle(position);
+ Bitmap touchIcon = getTouchIcon(position);
+
final Intent i = new Intent();
final Intent shortcutIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(url));
@@ -226,41 +395,67 @@
Long.toString(uniqueId));
i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
- if (favicon == null) {
- i.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
- Intent.ShortcutIconResource.fromContext(
- BrowserBookmarksPage.this,
- R.drawable.ic_launcher_shortcut_browser_bookmark));
- } else {
- Bitmap icon = BitmapFactory.decodeResource(getResources(),
- R.drawable.ic_launcher_shortcut_browser_bookmark);
-
- // Make a copy of the regular icon so we can modify the pixels.
- Bitmap copy = icon.copy(Bitmap.Config.ARGB_8888, true);
+ // Use the apple-touch-icon if available
+ if (touchIcon != null) {
+ // Make a copy so we can modify the pixels.
+ Bitmap copy = touchIcon.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(copy);
- // Make a Paint for the white background rectangle and for
- // filtering the favicon.
- Paint p = new Paint(Paint.ANTI_ALIAS_FLAG
- | Paint.FILTER_BITMAP_FLAG);
- p.setStyle(Paint.Style.FILL_AND_STROKE);
- p.setColor(Color.WHITE);
+ // Construct a path from a round rect. This will allow drawing with
+ // an inverse fill so we can punch a hole using the round rect.
+ Path path = new Path();
+ path.setFillType(Path.FillType.INVERSE_WINDING);
+ RectF rect = new RectF(0, 0, touchIcon.getWidth(),
+ touchIcon.getHeight());
+ rect.inset(1, 1);
+ path.addRoundRect(rect, 8f, 8f, Path.Direction.CW);
- // Create a rectangle that is slightly wider than the favicon
- final float iconSize = 16; // 16x16 favicon
- final float padding = 2; // white padding around icon
- final float rectSize = iconSize + 2 * padding;
- final float y = icon.getHeight() - rectSize;
- RectF r = new RectF(0, y, rectSize, y + rectSize);
+ // Construct a paint that clears the outside of the rectangle and
+ // draw.
+ Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ canvas.drawPath(path, paint);
- // Draw a white rounded rectangle behind the favicon
- canvas.drawRoundRect(r, 2, 2, p);
-
- // Draw the favicon in the same rectangle as the rounded rectangle
- // but inset by the padding (results in a 16x16 favicon).
- r.inset(padding, padding);
- canvas.drawBitmap(favicon, null, r, p);
i.putExtra(Intent.EXTRA_SHORTCUT_ICON, copy);
+ } else {
+ Bitmap favicon = getFavicon(position);
+ if (favicon == null) {
+ i.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
+ Intent.ShortcutIconResource.fromContext(
+ BrowserBookmarksPage.this,
+ R.drawable.ic_launcher_shortcut_browser_bookmark));
+ } else {
+ Bitmap icon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_launcher_shortcut_browser_bookmark);
+
+ // Make a copy of the regular icon so we can modify the pixels.
+ Bitmap copy = icon.copy(Bitmap.Config.ARGB_8888, true);
+ Canvas canvas = new Canvas(copy);
+
+ // Make a Paint for the white background rectangle and for
+ // filtering the favicon.
+ Paint p = new Paint(Paint.ANTI_ALIAS_FLAG
+ | Paint.FILTER_BITMAP_FLAG);
+ p.setStyle(Paint.Style.FILL_AND_STROKE);
+ p.setColor(Color.WHITE);
+
+ // Create a rectangle that is slightly wider than the favicon
+ final float iconSize = 16; // 16x16 favicon
+ final float padding = 2; // white padding around icon
+ final float rectSize = iconSize + 2 * padding;
+ final float y = icon.getHeight() - rectSize;
+ RectF r = new RectF(0, y, rectSize, y + rectSize);
+
+ // Draw a white rounded rectangle behind the favicon
+ canvas.drawRoundRect(r, 2, 2, p);
+
+ // Draw the favicon in the same rectangle as the rounded
+ // rectangle but inset by the padding
+ // (results in a 16x16 favicon).
+ r.inset(padding, padding);
+ canvas.drawBitmap(favicon, null, r, p);
+ i.putExtra(Intent.EXTRA_SHORTCUT_ICON, copy);
+ }
}
// Do not allow duplicate items
i.putExtra("duplicate", false);
@@ -283,7 +478,7 @@
@Override
public boolean onCreateOptionsMenu(Menu menu) {
boolean result = super.onCreateOptionsMenu(menu);
- if (!mCreateShortcut) {
+ if (!mCreateShortcut && !mMostVisited) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.bookmarks, menu);
return true;
@@ -292,14 +487,45 @@
}
@Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ boolean result = super.onPrepareOptionsMenu(menu);
+ if (mCreateShortcut || mMostVisited
+ || mBookmarksAdapter.getCount() == 0) {
+ // No need to show the menu if there are no items.
+ return result;
+ }
+ MenuItem switchItem = menu.findItem(R.id.switch_mode_menu_id);
+ int titleResId;
+ int iconResId;
+ if (mViewMode == BookmarkViewMode.GRID) {
+ titleResId = R.string.switch_to_list;
+ iconResId = R.drawable.ic_menu_list;
+ } else {
+ titleResId = R.string.switch_to_thumbnails;
+ iconResId = R.drawable.ic_menu_thumbnail;
+ }
+ switchItem.setTitle(titleResId);
+ switchItem.setIcon(iconResId);
+ return true;
+ }
+
+ @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:
+ if (mViewMode == BookmarkViewMode.GRID) {
+ switchViewMode(BookmarkViewMode.LIST);
+ } else {
+ switchViewMode(BookmarkViewMode.GRID);
+ }
+ break;
+
+ default:
+ return super.onOptionsItemSelected(item);
}
return true;
}
@@ -370,7 +596,7 @@
/**
* Refresh the shown list after the database has changed.
*/
- public void refreshList() {
+ private void refreshList() {
mBookmarksAdapter.refreshList();
}
@@ -395,6 +621,10 @@
return mBookmarksAdapter.getFavicon(position);
}
+ private Bitmap getTouchIcon(int position) {
+ return mBookmarksAdapter.getTouchIcon(position);
+ }
+
private void copy(CharSequence text) {
try {
IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
@@ -416,13 +646,12 @@
public void deleteBookmark(int position) {
mBookmarksAdapter.deleteRow(position);
}
-
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.isDown()) {
- setResultToParent(RESULT_CANCELED, null);
- mCanceled = true;
- }
- return super.dispatchKeyEvent(event);
+
+ @Override
+ public void onBackPressed() {
+ setResultToParent(RESULT_CANCELED, null);
+ mCanceled = true;
+ super.onBackPressed();
}
// This Activity is generally a sub-Activity of CombinedHistoryActivity. In
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 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 @@
// 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 @@
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 @@
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 @@
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 4a0f8c5..bdd109b 100644
--- a/src/com/android/browser/BrowserHistoryPage.java
+++ b/src/com/android/browser/BrowserHistoryPage.java
@@ -25,6 +25,7 @@
import android.database.Cursor;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.ServiceManager;
@@ -41,6 +42,7 @@
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 +50,7 @@
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;
@@ -59,7 +62,8 @@
public class BrowserHistoryPage extends ExpandableListActivity {
private HistoryAdapter mAdapter;
private DateSorter mDateSorter;
- private boolean mMaxTabsOpen;
+ private boolean mDisableNewWindow;
+ private HistoryItem mContextHeader;
private final static String LOGTAG = "browser";
@@ -110,8 +114,7 @@
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);
@@ -127,8 +130,9 @@
}
});
}
- mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false);
- CombinedBookmarkHistoryActivity.getIconListenerSet(getContentResolver())
+ mDisableNewWindow = getIntent().getBooleanExtra("disable_new_window",
+ false);
+ CombinedBookmarkHistoryActivity.getIconListenerSet()
.addListener(mIconReceiver);
// initialize the result to canceled, so that if the user just presses
@@ -139,7 +143,7 @@
@Override
protected void onDestroy() {
super.onDestroy();
- CombinedBookmarkHistoryActivity.getIconListenerSet(getContentResolver())
+ CombinedBookmarkHistoryActivity.getIconListenerSet()
.removeListener(mIconReceiver);
}
@@ -172,7 +176,7 @@
}
return super.onOptionsItemSelected(item);
}
-
+
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
@@ -187,12 +191,26 @@
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.historycontext, menu);
- // Setup the header
- menu.setHeaderTitle(((HistoryItem)i.targetView).getUrl());
+ HistoryItem historyItem = (HistoryItem) i.targetView;
- // 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);
-
+ // Setup the header
+ 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 it was not explicitly disabled
+ if (mDisableNewWindow) {
+ menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
+ }
+ // 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);
@@ -207,8 +225,9 @@
public boolean onContextItemSelected(MenuItem item) {
ExpandableListContextMenuInfo i =
(ExpandableListContextMenuInfo) item.getMenuInfo();
- String url = ((HistoryItem)i.targetView).getUrl();
- String title = ((HistoryItem)i.targetView).getName();
+ HistoryItem historyItem = (HistoryItem) i.targetView;
+ String url = historyItem.getUrl();
+ String title = historyItem.getName();
switch (item.getItemId()) {
case R.id.open_context_menu_id:
loadUrl(url, false);
@@ -217,18 +236,29 @@
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, title);
+ } else {
+ Browser.saveBookmark(this, title, url);
+ }
return true;
case R.id.share_link_context_menu_id:
- Browser.sendString(this, url);
+ Browser.sendString(this, url,
+ getText(R.string.choosertitle_sharevia).toString());
return true;
- case R.id.copy_context_menu_id:
+ case R.id.copy_url_context_menu_id:
copy(url);
return true;
case R.id.delete_context_menu_id:
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;
}
@@ -274,18 +304,25 @@
// 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,
@@ -297,6 +334,9 @@
}
void refreshData() {
+ if (mCursor.isClosed()) {
+ return;
+ }
mCursor.requery();
buildMap();
for (DataSetObserver o : mObservers) {
@@ -386,8 +426,14 @@
item.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
item.setUrl(url);
- item.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet(
- getContentResolver()).getFavicon(url));
+ byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+ if (data != null) {
+ item.setFavicon(BitmapFactory.decodeByteArray(data, 0,
+ data.length));
+ } else {
+ item.setFavicon(CombinedBookmarkHistoryActivity
+ .getIconListenerSet().getFavicon(url));
+ }
item.setIsBookmark(1 ==
mCursor.getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX));
return item;
diff --git a/src/com/android/browser/BrowserHomepagePreference.java b/src/com/android/browser/BrowserHomepagePreference.java
index d4708c3..be96db3 100644
--- a/src/com/android/browser/BrowserHomepagePreference.java
+++ b/src/com/android/browser/BrowserHomepagePreference.java
@@ -18,47 +18,46 @@
import android.app.AlertDialog;
import android.content.Context;
-import android.content.DialogInterface;
import android.preference.EditTextPreference;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.text.util.Regex;
import android.util.AttributeSet;
-public class BrowserHomepagePreference extends EditTextPreference implements
- TextWatcher {
+public class BrowserHomepagePreference extends EditTextPreference {
public BrowserHomepagePreference(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
- getEditText().addTextChangedListener(this);
}
public BrowserHomepagePreference(Context context, AttributeSet attrs) {
super(context, attrs);
- getEditText().addTextChangedListener(this);
}
public BrowserHomepagePreference(Context context) {
super(context);
- getEditText().addTextChangedListener(this);
}
- public void afterTextChanged(Editable s) {
- AlertDialog dialog = (AlertDialog) getDialog();
- // This callback is called before the dialog has been fully constructed
- 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());
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ if (positiveResult) {
+ String url = getEditText().getText().toString();
+ if (url.length() > 0
+ && !BrowserActivity.ACCEPTED_URI_SCHEMA.matcher(url)
+ .matches()) {
+ int colon = url.indexOf(':');
+ int space = url.indexOf(' ');
+ if (colon == -1 && space == -1) {
+ // if no colon, no space, add "http://" to make it a url
+ getEditText().setText("http://" + url);
+ } else {
+ // show an error dialog and change the positiveResult to
+ // false so that the bad url will not override the old url
+ new AlertDialog.Builder(this.getContext()).setMessage(
+ R.string.bookmark_url_not_valid).setPositiveButton(
+ R.string.ok, null).show();
+ positiveResult = false;
+ }
+ }
}
- }
-
- public void beforeTextChanged(CharSequence s, int start, int count,
- int after) {
- }
-
- public void onTextChanged(CharSequence s, int start, int before, int count) {
+ super.onDialogClosed(positiveResult);
}
}
diff --git a/src/com/android/browser/BrowserPluginList.java b/src/com/android/browser/BrowserPluginList.java
deleted file mode 100644
index 6689b0e..0000000
--- a/src/com/android/browser/BrowserPluginList.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.browser;
-
-import android.app.ListActivity;
-import android.os.Bundle;
-import android.view.View;
-import android.view.ViewGroup;
-import android.webkit.Plugin;
-import android.webkit.PluginList;
-import android.webkit.WebView;
-import android.widget.ArrayAdapter;
-import android.widget.LinearLayout;
-import android.widget.LinearLayout.LayoutParams;
-import android.widget.ListView;
-import android.widget.TextView;
-import java.util.ArrayList;
-import java.util.List;
-
-// Manages the list of installed (and loaded) plugins.
-public class BrowserPluginList extends ListActivity {
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- // The list of plugins can change under us, as the plugins are
- // loaded and unloaded in a different thread. We make a copy
- // of the list here.
- List loadedPlugins = WebView.getPluginList().getList();
- ArrayList localLoadedPluginList = new ArrayList();
- synchronized (loadedPlugins) {
- localLoadedPluginList.addAll(loadedPlugins);
- }
- setListAdapter(new ArrayAdapter(this,
- android.R.layout.simple_list_item_1,
- localLoadedPluginList));
- setTitle(R.string.pref_plugin_installed);
- // Add a text view to this ListActivity. This text view
- // will be displayed when the list of plugins is empty.
- TextView textView = new TextView(this);
- textView.setId(android.R.id.empty);
- textView.setText(R.string.pref_plugin_installed_empty_list);
- addContentView(textView, new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
-
- }
-
- @Override
- public void onListItemClick(ListView l, View v, int position, long id) {
- WebView.getPluginList().pluginClicked(this, position);
- }
-}
diff --git a/src/com/android/browser/BrowserPreferencesPage.java b/src/com/android/browser/BrowserPreferencesPage.java
index 3b747d1..fb0b2ba 100644
--- a/src/com/android/browser/BrowserPreferencesPage.java
+++ b/src/com/android/browser/BrowserPreferencesPage.java
@@ -17,18 +17,28 @@
package com.android.browser;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.preference.EditTextPreference;
+import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import android.webkit.GeolocationPermissions;
+import android.webkit.ValueCallback;
+import android.webkit.WebStorage;
import android.webkit.WebView;
-import android.webkit.Plugin;
public class BrowserPreferencesPage extends PreferenceActivity
- implements Preference.OnPreferenceChangeListener,
- Preference.OnPreferenceClickListener {
+ implements Preference.OnPreferenceChangeListener {
+
+ private String LOGTAG = "BrowserPreferencesPage";
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -59,13 +69,42 @@
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();
+ final PreferenceScreen websiteSettings = (PreferenceScreen)
+ findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS);
+ websiteSettings.setEnabled(false);
+ WebStorage.getInstance().getOrigins(new ValueCallback<Map>() {
+ public void onReceiveValue(Map webStorageOrigins) {
+ if ((webStorageOrigins != null) && !webStorageOrigins.isEmpty()) {
+ websiteSettings.setEnabled(true);
+ }
+ }
+ });
+ GeolocationPermissions.getInstance().getOrigins(new ValueCallback<Set>() {
+ public void onReceiveValue(Set geolocationOrigins) {
+ if ((geolocationOrigins != null) && !geolocationOrigins.isEmpty()) {
+ websiteSettings.setEnabled(true);
+ }
+ }
+ });
}
@Override
@@ -121,20 +160,6 @@
return false;
}
-
- public boolean onPreferenceClick(Preference pref) {
- if (pref.getKey().equals(BrowserSettings.PREF_GEARS_SETTINGS)) {
- List<Plugin> loadedPlugins = WebView.getPluginList().getList();
- for(Plugin p : loadedPlugins) {
- if (p.getName().equals("gears")) {
- p.dispatchClickEvent(this);
- return true;
- }
- }
-
- }
- return true;
- }
private CharSequence getVisualTextSizeName(String enumName) {
CharSequence[] visualNames = getResources().getTextArray(
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index a3ccf04..8e0929b 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -19,8 +19,10 @@
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;
@@ -36,16 +38,20 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
+import android.os.AsyncTask;
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;
import android.util.Log;
import android.util.TypedValue;
+import java.io.File;
+import java.io.FilenameFilter;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -54,6 +60,7 @@
public class BrowserProvider extends ContentProvider {
private SQLiteOpenHelper mOpenHelper;
+ private BackupManager mBackupManager;
private static final String sDatabaseName = "browser.db";
private static final String TAG = "BrowserProvider";
private static final String ORDER_BY = "visits DESC, date DESC";
@@ -81,6 +88,7 @@
private static final int SUGGEST_COLUMN_ICON_2_ID = 6;
private static final int SUGGEST_COLUMN_QUERY_ID = 7;
private static final int SUGGEST_COLUMN_FORMAT = 8;
+ private static final int SUGGEST_COLUMN_INTENT_EXTRA_DATA = 9;
// shared suggestion columns
private static final String[] COLUMNS = new String[] {
@@ -92,10 +100,13 @@
SearchManager.SUGGEST_COLUMN_ICON_1,
SearchManager.SUGGEST_COLUMN_ICON_2,
SearchManager.SUGGEST_COLUMN_QUERY,
- SearchManager.SUGGEST_COLUMN_FORMAT};
+ SearchManager.SUGGEST_COLUMN_FORMAT,
+ SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA};
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 +154,10 @@
// 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
+ // 20 -> 21 Added touch_icon
+ // 21 -> 22 Remove "clientid"
+ private static final int DATABASE_VERSION = 22;
// Regular expression which matches http://, followed by some stuff, followed by
// optionally a trailing slash, all matched as separate groups.
@@ -215,7 +229,9 @@
"created LONG," +
"description TEXT," +
"bookmark INTEGER," +
- "favicon BLOB DEFAULT NULL" +
+ "favicon BLOB DEFAULT NULL," +
+ "thumbnail BLOB DEFAULT NULL," +
+ "touch_icon BLOB DEFAULT NULL" +
");");
final CharSequence[] bookmarks = mContext.getResources()
@@ -242,21 +258,81 @@
@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;");
+ }
+ if (oldVersion < 21) {
+ db.execSQL("ALTER TABLE bookmarks ADD COLUMN touch_icon BLOB DEFAULT NULL;");
+ }
+ if (oldVersion < 22) {
+ db.execSQL("DELETE FROM bookmarks WHERE (bookmark = 0 AND url LIKE \"%.google.%client=ms-%\")");
+ removeGears();
} else {
db.execSQL("DROP TABLE IF EXISTS bookmarks");
db.execSQL("DROP TABLE IF EXISTS searches");
onCreate(db);
}
}
+
+ private void removeGears() {
+ AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+ public Void doInBackground(Void... unused) {
+ String browserDataDirString = mContext.getApplicationInfo().dataDir;
+ final String appPluginsDirString = "app_plugins";
+ final String gearsPrefix = "gears";
+ File appPluginsDir = new File(browserDataDirString + File.separator
+ + appPluginsDirString);
+ if (!appPluginsDir.exists()) {
+ return null;
+ }
+ // Delete the Gears plugin files
+ File[] gearsFiles = appPluginsDir.listFiles(new FilenameFilter() {
+ public boolean accept(File dir, String filename) {
+ return filename.startsWith(gearsPrefix);
+ }
+ });
+ for (int i = 0; i < gearsFiles.length; ++i) {
+ if (gearsFiles[i].isDirectory()) {
+ deleteDirectory(gearsFiles[i]);
+ } else {
+ gearsFiles[i].delete();
+ }
+ }
+ // Delete the Gears data files
+ File gearsDataDir = new File(browserDataDirString + File.separator
+ + gearsPrefix);
+ if (!gearsDataDir.exists()) {
+ return null;
+ }
+ deleteDirectory(gearsDataDir);
+ return null;
+ }
+
+ private void deleteDirectory(File currentDir) {
+ File[] files = currentDir.listFiles();
+ for (int i = 0; i < files.length; ++i) {
+ if (files[i].isDirectory()) {
+ deleteDirectory(files[i]);
+ }
+ files[i].delete();
+ }
+ currentDir.delete();
+ }
+ };
+
+ task.execute();
+ }
}
@Override
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.
@@ -370,6 +446,7 @@
private int mSuggestText1Id;
private int mSuggestText2Id;
private int mSuggestQueryId;
+ private int mSuggestIntentExtraDataId;
public MySuggestionCursor(Cursor hc, Cursor sc, String string) {
mHistoryCursor = hc;
@@ -389,6 +466,7 @@
mSuggestText1Id = -1;
mSuggestText2Id = -1;
mSuggestQueryId = -1;
+ mSuggestIntentExtraDataId = -1;
} else {
mSuggestText1Id = mSuggestCursor.getColumnIndex(
SearchManager.SUGGEST_COLUMN_TEXT_1);
@@ -396,6 +474,8 @@
SearchManager.SUGGEST_COLUMN_TEXT_2);
mSuggestQueryId = mSuggestCursor.getColumnIndex(
SearchManager.SUGGEST_COLUMN_QUERY);
+ mSuggestIntentExtraDataId = mSuggestCursor.getColumnIndex(
+ SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
}
}
@@ -471,22 +551,22 @@
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) {
@@ -504,6 +584,16 @@
case SUGGEST_COLUMN_FORMAT:
return "html";
+
+ case SUGGEST_COLUMN_INTENT_EXTRA_DATA:
+ if (mHistoryCount > mPos) {
+ return null;
+ } else if (!mBeyondCursor) {
+ if (mSuggestIntentExtraDataId == -1) return null;
+ return mSuggestCursor.getString(mSuggestIntentExtraDataId);
+ } else {
+ return null;
+ }
}
}
return null;
@@ -637,7 +727,8 @@
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 +746,7 @@
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 +819,7 @@
@Override
public Uri insert(Uri url, ContentValues initialValues) {
+ boolean isBookmarkTable = false;
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int match = URI_MATCHER.match(url);
@@ -742,6 +833,7 @@
uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
rowID);
}
+ isBookmarkTable = true;
break;
}
@@ -764,6 +856,15 @@
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 +877,41 @@
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 +925,59 @@
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..e36d54b 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -1,3 +1,4 @@
+
/*
* Copyright (C) 2007 The Android Open Source Project
*
@@ -20,17 +21,25 @@
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.ValueCallback;
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.HashMap;
+import java.util.Map;
+import java.util.Set;
import java.util.Observable;
/*
@@ -48,14 +57,13 @@
*/
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.
private boolean loadsImagesAutomatically = true;
private boolean javaScriptEnabled = true;
private boolean pluginsEnabled = true;
- private String pluginsPath; // default value set in loadFromDb().
private boolean javaScriptCanOpenWindowsAutomatically = false;
private boolean showSecurityWarnings = true;
private boolean rememberPasswords = true;
@@ -65,7 +73,25 @@
private String homeUrl = "";
private boolean loginInitialized = false;
private boolean autoFitPage = true;
+ private boolean landscapeOnly = false;
+ private boolean loadsPageInOverviewMode = true;
private boolean showDebugSettings = false;
+ // HTML5 API flags
+ private boolean appCacheEnabled = true;
+ private boolean databaseEnabled = true;
+ private boolean domStorageEnabled = true;
+ private boolean geolocationEnabled = true;
+ private boolean workersEnabled = true; // only affects V8. JSC does not have a similar setting
+ // HTML5 API configuration params
+ private long appCacheMaxSize = Long.MAX_VALUE;
+ private String appCachePath; // default value set in loadFromDb().
+ private String databasePath; // default value set in loadFromDb()
+ private String geolocationDatabasePath; // default value set in loadFromDb()
+ private WebStorageSizeManager webStorageSizeManager;
+
+ private String jsFlags = "";
+
+ private final static String TAG = "BrowserSettings";
// Development settings
public WebSettings.LayoutAlgorithm layoutAlgorithm =
@@ -75,6 +101,11 @@
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;
@@ -100,19 +131,21 @@
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_GEOLOCATION_ACCESS =
+ "privacy_clear_geolocation_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 +190,6 @@
s.setLoadsImagesAutomatically(b.loadsImagesAutomatically);
s.setJavaScriptEnabled(b.javaScriptEnabled);
s.setPluginsEnabled(b.pluginsEnabled);
- s.setPluginsPath(b.pluginsPath);
s.setJavaScriptCanOpenWindowsAutomatically(
b.javaScriptCanOpenWindowsAutomatically);
s.setDefaultTextEncodingName(b.defaultTextEncodingName);
@@ -171,13 +203,29 @@
s.setLightTouchEnabled(b.lightTouch);
s.setSaveFormData(b.saveFormData);
s.setSavePassword(b.rememberPasswords);
+ s.setLoadWithOverviewMode(b.loadsPageInOverviewMode);
// WebView inside Browser doesn't want initial focus to be set.
s.setNeedInitialFocus(false);
// Browser supports multiple windows
s.setSupportMultipleWindows(true);
- // Turn off file access
- s.setAllowFileAccess(false);
+
+ // HTML5 API flags
+ s.setAppCacheEnabled(b.appCacheEnabled);
+ s.setDatabaseEnabled(b.databaseEnabled);
+ s.setDomStorageEnabled(b.domStorageEnabled);
+ s.setWorkersEnabled(b.workersEnabled); // This only affects V8.
+ s.setGeolocationEnabled(b.geolocationEnabled);
+
+ // HTML5 configuration parameters.
+ s.setAppCacheMaxSize(b.appCacheMaxSize);
+ s.setAppCachePath(b.appCachePath);
+ s.setDatabasePath(b.databasePath);
+ s.setGeolocationDatabasePath(b.geolocationDatabasePath);
+
+ // Enable/Disable the error console.
+ b.mTabControl.getBrowserActivity().setShouldShowErrorConsole(
+ b.showDebugSettings && b.showConsole);
}
}
@@ -193,10 +241,18 @@
public void loadFromDb(Context ctx) {
SharedPreferences p =
PreferenceManager.getDefaultSharedPreferences(ctx);
-
- // 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();
+ // Set the default value for the Geolocation database path.
+ geolocationDatabasePath = ctx.getDir("geolocation", 0).getPath();
homeUrl = getFactoryResetHomeUrl(ctx);
@@ -218,7 +274,6 @@
javaScriptEnabled);
pluginsEnabled = p.getBoolean("enable_plugins",
pluginsEnabled);
- pluginsPath = p.getString("plugins_path", pluginsPath);
javaScriptCanOpenWindowsAutomatically = !p.getBoolean(
"block_popup_windows",
!javaScriptCanOpenWindowsAutomatically);
@@ -238,6 +293,16 @@
zoomDensity = WebSettings.ZoomDensity.valueOf(
p.getString(PREF_DEFAULT_ZOOM, zoomDensity.name()));
autoFitPage = p.getBoolean("autofit_pages", autoFitPage);
+ loadsPageInOverviewMode = p.getBoolean("load_page",
+ loadsPageInOverviewMode);
+ 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,17 +339,41 @@
doFlick = p.getBoolean("enable_flick", doFlick);
userAgent = Integer.parseInt(p.getString("user_agent", "0"));
}
- update();
- }
+ // 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", "");
- public String getPluginsPath() {
- return pluginsPath;
+ // 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);
+
+ // HTML5 API flags
+ appCacheEnabled = p.getBoolean("enable_appcache", appCacheEnabled);
+ databaseEnabled = p.getBoolean("enable_database", databaseEnabled);
+ domStorageEnabled = p.getBoolean("enable_domstorage", domStorageEnabled);
+ geolocationEnabled = p.getBoolean("enable_geolocation", geolocationEnabled);
+ workersEnabled = p.getBoolean("enable_workers", workersEnabled);
+
+ update();
}
public String getHomePage() {
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 +525,48 @@
db.clearHttpAuthUsernamePassword();
}
- /*package*/ void resetDefaultPreferences(Context context) {
+ private void maybeDisableWebsiteSettings(Context context) {
+ PreferenceActivity activity = (PreferenceActivity) context;
+ final PreferenceScreen screen = (PreferenceScreen)
+ activity.findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS);
+ screen.setEnabled(false);
+ WebStorage.getInstance().getOrigins(new ValueCallback<Map>() {
+ public void onReceiveValue(Map webStorageOrigins) {
+ if ((webStorageOrigins != null) && !webStorageOrigins.isEmpty()) {
+ screen.setEnabled(true);
+ }
+ }
+ });
+
+ GeolocationPermissions.getInstance().getOrigins(new ValueCallback<Set>() {
+ public void onReceiveValue(Set geolocationOrigins) {
+ if ((geolocationOrigins != null) && !geolocationOrigins.isEmpty()) {
+ screen.setEnabled(true);
+ }
+ }
+ });
+ }
+
+ /*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..caea092 100644
--- a/src/com/android/browser/BrowserYesNoPreference.java
+++ b/src/com/android/browser/BrowserYesNoPreference.java
@@ -38,6 +38,7 @@
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 @@
getKey())) {
BrowserSettings.getInstance().resetDefaultPreferences(context);
setEnabled(true);
+ } else if (BrowserSettings.PREF_CLEAR_GEOLOCATION_ACCESS.equals(
+ getKey())) {
+ BrowserSettings.getInstance().clearLocationAccess(context);
}
}
}
diff --git a/src/com/android/browser/CombinedBookmarkHistoryActivity.java b/src/com/android/browser/CombinedBookmarkHistoryActivity.java
index 5678b44..a611d79 100644
--- a/src/com/android/browser/CombinedBookmarkHistoryActivity.java
+++ b/src/com/android/browser/CombinedBookmarkHistoryActivity.java
@@ -24,10 +24,10 @@
import android.graphics.Bitmap;
import android.os.Bundle;
import android.provider.Browser;
+import android.view.Window;
import android.webkit.WebIconDatabase.IconListener;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
-import android.view.Window;
import java.util.HashMap;
import java.util.Vector;
@@ -67,10 +67,9 @@
}
}
private static IconListenerSet sIconListenerSet;
- static IconListenerSet getIconListenerSet(ContentResolver cr) {
+ static IconListenerSet getIconListenerSet() {
if (null == sIconListenerSet) {
sIconListenerSet = new IconListenerSet();
- Browser.requestAllIcons(cr, null, sIconListenerSet);
}
return sIconListenerSet;
}
@@ -78,42 +77,52 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.tabs);
- TabHost tabHost = getTabHost();
- tabHost.setOnTabChangedListener(this);
+
+ setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+
+ getTabHost().setOnTabChangedListener(this);
Bundle extras = getIntent().getExtras();
- Resources resources = getResources();
- getIconListenerSet(getContentResolver());
+ getIconListenerSet();
+ // Do this every time we create a new activity so that we get the
+ // newest icons.
+ Browser.requestAllIcons(getContentResolver(), null, sIconListenerSet);
+
Intent bookmarksIntent = new Intent(this, BrowserBookmarksPage.class);
bookmarksIntent.putExtras(extras);
- tabHost.addTab(tabHost.newTabSpec(BOOKMARKS_TAB)
- .setIndicator(resources.getString(R.string.tab_bookmarks),
- resources.getDrawable(R.drawable.browser_bookmark_tab))
- .setContent(bookmarksIntent));
+ createTab(bookmarksIntent, R.string.tab_bookmarks,
+ R.drawable.browser_bookmark_tab, BOOKMARKS_TAB);
- Intent visitedIntent = new Intent(this, MostVisitedActivity.class);
- visitedIntent.putExtras(extras);
- tabHost.addTab(tabHost.newTabSpec(VISITED_TAB)
- .setIndicator(resources.getString(R.string.tab_most_visited),
- resources.getDrawable(R.drawable.browser_visited_tab))
- .setContent(visitedIntent));
+ Intent visitedIntent = new Intent(this, BrowserBookmarksPage.class);
+ // Need to copy extras so the bookmarks activity and this one will be
+ // different
+ Bundle visitedExtras = new Bundle(extras);
+ visitedExtras.putBoolean("mostVisited", true);
+ visitedIntent.putExtras(visitedExtras);
+ createTab(visitedIntent, R.string.tab_most_visited,
+ R.drawable.browser_visited_tab, VISITED_TAB);
Intent historyIntent = new Intent(this, BrowserHistoryPage.class);
historyIntent.putExtras(extras);
- tabHost.addTab(tabHost.newTabSpec(HISTORY_TAB)
- .setIndicator(resources.getString(R.string.tab_history),
- resources.getDrawable(R.drawable.
- browser_history_tab)).setContent(historyIntent));
+ createTab(historyIntent, R.string.tab_history,
+ R.drawable.browser_history_tab, HISTORY_TAB);
String defaultTab = extras.getString(STARTING_TAB);
if (defaultTab != null) {
- tabHost.setCurrentTab(2);
+ getTabHost().setCurrentTab(2);
}
}
+ private void createTab(Intent intent, int labelResId, int iconResId,
+ String tab) {
+ Resources resources = getResources();
+ TabHost tabHost = getTabHost();
+ tabHost.addTab(tabHost.newTabSpec(tab).setIndicator(
+ resources.getText(labelResId), resources.getDrawable(iconResId))
+ .setContent(intent));
+ }
// Copied from DialTacts Activity
/** {@inheritDoc} */
public void onTabChanged(String tabId) {
diff --git a/src/com/android/browser/DownloadTouchIcon.java b/src/com/android/browser/DownloadTouchIcon.java
new file mode 100644
index 0000000..07d2d3a
--- /dev/null
+++ b/src/com/android/browser/DownloadTouchIcon.java
@@ -0,0 +1,136 @@
+/*
+ * 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.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.http.AndroidHttpClient;
+import android.os.AsyncTask;
+import android.provider.Browser;
+import android.webkit.WebView;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.params.HttpClientParams;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+class DownloadTouchIcon extends AsyncTask<String, Void, Bitmap> {
+ private final ContentResolver mContentResolver;
+ private final Cursor mCursor;
+ private final String mOriginalUrl;
+ private final String mUrl;
+ private final String mUserAgent;
+ /* package */ BrowserActivity mActivity;
+
+ public DownloadTouchIcon(BrowserActivity activity, ContentResolver cr,
+ Cursor c, WebView view) {
+ mActivity = activity;
+ mContentResolver = cr;
+ mCursor = c;
+ // Store these in case they change.
+ mOriginalUrl = view.getOriginalUrl();
+ mUrl = view.getUrl();
+ mUserAgent = view.getSettings().getUserAgentString();
+ }
+
+ public DownloadTouchIcon(ContentResolver cr, Cursor c, String url) {
+ mActivity = null;
+ mContentResolver = cr;
+ mCursor = c;
+ mOriginalUrl = null;
+ mUrl = url;
+ mUserAgent = null;
+ }
+
+ @Override
+ public Bitmap doInBackground(String... values) {
+ String url = values[0];
+
+ AndroidHttpClient client = AndroidHttpClient.newInstance(
+ mUserAgent);
+ HttpGet request = new HttpGet(url);
+
+ // Follow redirects
+ HttpClientParams.setRedirecting(client.getParams(), true);
+
+ try {
+ HttpResponse response = client.execute(request);
+
+ if (response.getStatusLine().getStatusCode() == 200) {
+ HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ InputStream content = entity.getContent();
+ if (content != null) {
+ Bitmap icon = BitmapFactory.decodeStream(
+ content, null, null);
+ return icon;
+ }
+ }
+ }
+ } catch (IllegalArgumentException ex) {
+ request.abort();
+ } catch (IOException ex) {
+ request.abort();
+ } finally {
+ client.close();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onCancelled() {
+ if (mCursor != null) {
+ mCursor.close();
+ }
+ }
+
+ @Override
+ public void onPostExecute(Bitmap icon) {
+ // Do this first in case the download failed.
+ if (mActivity != null) {
+ // Remove the touch icon loader from the BrowserActivity.
+ mActivity.mTouchIconLoader = null;
+ }
+
+ if (icon == null || mCursor == null || isCancelled()) {
+ return;
+ }
+
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ icon.compress(Bitmap.CompressFormat.PNG, 100, os);
+ ContentValues values = new ContentValues();
+ values.put(Browser.BookmarkColumns.TOUCH_ICON,
+ os.toByteArray());
+
+ if (mCursor.moveToFirst()) {
+ do {
+ mContentResolver.update(ContentUris.withAppendedId(
+ Browser.BOOKMARKS_URI, mCursor.getInt(0)),
+ values, null, null);
+ } while (mCursor.moveToNext());
+ }
+ mCursor.close();
+ }
+}
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/FakeWebView.java b/src/com/android/browser/FakeWebView.java
deleted file mode 100644
index da5ef5f..0000000
--- a/src/com/android/browser/FakeWebView.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.android.browser;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Picture;
-import android.util.AttributeSet;
-import android.view.View;
-import android.webkit.WebView;
-import android.widget.ImageView;
-
-import android.util.Log;
-
-/**
- * This class is used by ImageAdapter to draw a representation of each tab. It
- * overrides ImageView so it can be used for the new tab image as well.
- */
-public class FakeWebView extends ImageView {
- private TabControl.PickerData mPickerData;
- private boolean mUsesResource;
-
- public FakeWebView(Context context) {
- this(context, null);
- }
-
- public FakeWebView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public FakeWebView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- if (mUsesResource) {
- super.onDraw(canvas);
- } else {
- // Always draw white behind the picture just in case the picture
- // draws nothing.
- // FIXME: We used to draw white only when the WebView was null but
- // sometimes the picture was empty. So now we always draw white. It
- // would be nice to know if the picture is empty so we can avoid
- // drawing white.
- canvas.drawColor(Color.WHITE);
- if (mPickerData != null) {
- final Picture p = mPickerData.mPicture;
- if (p != null) {
- canvas.save();
- float scale = getWidth() * mPickerData.mScale
- / mPickerData.mWidth;
- // Check for NaN and infinity.
- if (Float.isNaN(scale) || Float.isInfinite(scale)) {
- scale = 1.0f;
- }
- canvas.scale(scale, scale);
- canvas.translate(-mPickerData.mScrollX,
- -mPickerData.mScrollY);
- canvas.drawPicture(p);
- canvas.restore();
- }
- }
- }
- }
-
- @Override
- public void setImageResource(int resId) {
- mUsesResource = true;
- mPickerData = null;
- super.setImageResource(resId);
- }
-
- /**
- * Set a WebView for this FakeWebView to represent.
- * @param t The tab whose picture and other data will be used in onDraw.
- */
- public void setTab(TabControl.Tab t) {
- mUsesResource = false;
- if (mPickerData != null) {
- // Clear the old tab's view first
- mPickerData.mFakeWebView = null;
- }
- mPickerData = null;
- if (t != null && t.getPickerData() != null) {
- mPickerData = t.getPickerData();
- mPickerData.mFakeWebView = this;
- }
- }
-}
diff --git a/src/com/android/browser/FetchUrlMimeType.java b/src/com/android/browser/FetchUrlMimeType.java
index 8578643..0081d32 100644
--- a/src/com/android/browser/FetchUrlMimeType.java
+++ b/src/com/android/browser/FetchUrlMimeType.java
@@ -58,7 +58,7 @@
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,21 +66,20 @@
// 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);
}
HttpResponse response;
- Boolean succeeded = true;
String mimeType = null;
try {
response = client.execute(request);
@@ -111,19 +110,19 @@
@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..df212d0 100644
--- a/src/com/android/browser/FindDialog.java
+++ b/src/com/android/browser/FindDialog.java
@@ -42,7 +42,6 @@
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 @@
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 @@
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);
}
@@ -205,6 +194,8 @@
mMatchesView.setVisibility(View.INVISIBLE);
} else {
mMatchesView.setVisibility(View.VISIBLE);
+ mWebView.setFindDialogHeight(
+ getWindow().getDecorView().getHeight());
int found = mWebView.findAll(find.toString());
setMatchesFound(found);
if (found < 2) {
diff --git a/src/com/android/browser/GearsBaseDialog.java b/src/com/android/browser/GearsBaseDialog.java
deleted file mode 100644
index 638ba27..0000000
--- a/src/com/android/browser/GearsBaseDialog.java
+++ /dev/null
@@ -1,474 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.browser;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.Handler;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.style.UnderlineSpan;
-import android.util.Log;
-import android.view.InflateException;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import java.io.InputStream;
-import java.io.IOException;
-import java.lang.ClassCastException;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Base dialog class for gears
- */
-class GearsBaseDialog {
-
- private static final String TAG = "GearsNativeDialog";
- protected Handler mHandler;
- protected Activity mActivity;
- protected String mDialogArguments;
-
- private Bitmap mIcon;
- private final int MAX_ICON_SIZE = 64;
- protected int mChoosenIconSize;
-
- // Dialog closing types
- public static final int CANCEL = 0;
- public static final int ALWAYS_DENY = 1;
- public static final int ALLOW = 2;
- public static final int DENY = 3;
- public static final int NEW_ICON = 4;
- public static final int UPDATE_ICON = 5;
- public static final int REQUEST_ICON = 6;
- public static final int PAUSE_REQUEST_ICON = 7;
- public static final int CLEAR_REQUEST_ICON = 8;
-
- protected final String LOCAL_DATA_STRING = "localData";
- protected final String LOCAL_STORAGE_STRING = "localStorage";
- protected final String LOCATION_DATA_STRING = "locationData";
-
- protected String mGearsVersion = "UNDEFINED";
- protected boolean mDebug = false;
-
- public GearsBaseDialog(Activity activity, Handler handler, String arguments) {
- mActivity = activity;
- mHandler = handler;
- mDialogArguments = arguments;
- }
-
- Resources getResources() {
- return mActivity.getResources();
- }
-
- Object getSystemService(String name) {
- return mActivity.getSystemService(name);
- }
-
- View findViewById(int id) {
- return mActivity.findViewById(id);
- }
-
- private String getString(int id) {
- return mActivity.getString(id);
- }
-
- public void setDebug(boolean debug) {
- mDebug = debug;
- }
-
- public void setGearsVersion(String version) {
- mGearsVersion = version;
- }
-
- public String closeDialog(int closingType) {
- return null;
- }
-
- /*
- * Utility methods for setting up the dialogs elements
- */
-
- /**
- * Inflate a given layout in a view (which has to be
- * a ViewGroup, e.g. LinearLayout).
- * This is used to share the basic dialog outline among
- * the different dialog types.
- */
- void inflate(int layout, int viewID) {
- LayoutInflater inflater = (LayoutInflater) getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- View view = findViewById(viewID);
- if (view != null) {
- try {
- ViewGroup viewGroup = (ViewGroup) view;
- inflater.inflate(layout, viewGroup);
- } catch (ClassCastException e) {
- String msg = "exception, the view (" + view + ")";
- msg += " is not a ViewGroup";
- Log.e(TAG, msg, e);
- } catch (InflateException e) {
- Log.e(TAG, "exception while inflating the layout", e);
- }
- } else {
- String msg = "problem, trying to inflate a non-existent view";
- msg += " (" + viewID + ")";
- Log.e(TAG, msg);
- }
- }
-
- /**
- * Button setup.
- * Set the button's text and its listener. If the text resource's id
- * is 0, makes the button invisible.
- */
- void setupButton(int buttonRscID,
- int rscString,
- View.OnClickListener listener,
- boolean isLink,
- boolean requestFocus) {
- View view = findViewById(buttonRscID);
- if (view == null) {
- return;
- }
-
- Button button = (Button) view;
-
- if (rscString == 0) {
- button.setVisibility(View.GONE);
- } else {
- CharSequence text = getString(rscString);
- button.setText(text);
- button.setOnClickListener(listener);
- if (isLink) {
- displayAsLink(button);
- }
- if (requestFocus) {
- button.requestFocus();
- }
- }
- }
-
- /**
- * Button setup: as the above method, except that 'isLink' and
- * 'requestFocus' default to false.
- */
- void setupButton(int buttonRsc, int rsc,
- View.OnClickListener listener) {
- setupButton(buttonRsc, rsc, listener, false, false);
- }
-
- /**
- * Utility method to setup the three dialog buttons.
- */
- void setupButtons(int alwaysDenyRsc, int allowRsc, int denyRsc) {
- setupButton(R.id.button_alwaysdeny, alwaysDenyRsc,
- new Button.OnClickListener() {
- public void onClick(View v) {
- mHandler.sendEmptyMessage(ALWAYS_DENY);
- }
- });
-
- setupButton(R.id.button_allow, allowRsc,
- new Button.OnClickListener() {
- public void onClick(View v) {
- mHandler.sendEmptyMessage(ALLOW);
- }
- });
-
- setupButton(R.id.button_deny, denyRsc,
- new Button.OnClickListener() {
- public void onClick(View v) {
- mHandler.sendEmptyMessage(DENY);
- }
- });
- }
-
- /**
- * Display a button as an HTML link. Remove the background, set the
- * text color to R.color.dialog_link and draw an underline
- */
- void displayAsLink(Button button) {
- if (button == null) {
- return;
- }
-
- CharSequence text = button.getText();
- button.setBackgroundDrawable(null);
- int color = getResources().getColor(R.color.dialog_link);
- button.setTextColor(color);
- SpannableString str = new SpannableString(text);
- str.setSpan(new UnderlineSpan(), 0, str.length(),
- Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- button.setText(str);
- button.setFocusable(false);
- }
-
- /**
- * Utility method to set elements' text indicated in
- * the dialogs' arguments.
- */
- void setLabel(JSONObject json, String name, int rsc) {
- try {
- if (json.has(name)) {
- String text = json.getString(name);
- View view = findViewById(rsc);
- if (view != null && text != null) {
- TextView textView = (TextView) view;
- textView.setText(text);
- textView.setVisibility(View.VISIBLE);
- }
- }
- } catch (JSONException e) {
- Log.e(TAG, "json exception", e);
- }
- }
-
- /**
- * Utility method to hide a view.
- */
- void hideView(View v, int rsc) {
- if (rsc == 0) {
- return;
- }
- View view;
- if (v == null) {
- view = findViewById(rsc);
- } else {
- view = v.findViewById(rsc);
- }
- if (view != null) {
- view.setVisibility(View.GONE);
- }
- }
-
- /**
- * Utility method to show a view.
- */
- void showView(View v, int rsc) {
- if (rsc == 0) {
- return;
- }
- View view;
- if (v == null) {
- view = findViewById(rsc);
- } else {
- view = v.findViewById(rsc);
- }
- if (view != null) {
- view.setVisibility(View.VISIBLE);
- }
- }
-
- /**
- * Utility method to set a text.
- */
- void setText(View v, int rsc, CharSequence text) {
- if (rsc == 0) {
- return;
- }
- View view = v.findViewById(rsc);
- if (view != null) {
- TextView textView = (TextView) view;
- textView.setText(text);
- textView.setVisibility(View.VISIBLE);
- }
- }
-
- /**
- * Utility method to set a text.
- */
- void setText(View v, int rsc, int txtRsc) {
- if (rsc == 0) {
- return;
- }
- View view = v.findViewById(rsc);
- if (view != null) {
- TextView textView = (TextView) view;
- if (txtRsc == 0) {
- textView.setVisibility(View.GONE);
- } else {
- CharSequence text = getString(txtRsc);
- textView.setText(text);
- textView.setVisibility(View.VISIBLE);
- }
- }
- }
-
- /**
- * Utility class to download an icon in the background.
- * Once done ask the UI thread to update the icon.
- */
- class IconDownload implements Runnable {
- private String mUrlString;
-
- IconDownload(String url) {
- mUrlString = url;
- }
-
- public void run() {
- if (mUrlString == null) {
- return;
- }
- try {
- URL url = new URL(mUrlString);
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
- connection.setDoInput(true);
- connection.connect();
- int length = connection.getContentLength();
- InputStream is = connection.getInputStream();
- Bitmap customIcon = BitmapFactory.decodeStream(is);
- if (customIcon != null) {
- mIcon = customIcon;
- mHandler.sendEmptyMessage(UPDATE_ICON);
- }
- } catch (ClassCastException e) {
- Log.e(TAG, "Class cast exception (" + mUrlString + ")", e);
- } catch (MalformedURLException e) {
- Log.e(TAG, "Malformed url (" + mUrlString + ") ", e);
- } catch (IOException e) {
- Log.e(TAG, "Exception downloading icon (" + mUrlString + ") ", e);
- }
- }
- }
-
- /**
- * Utility method to update the icon.
- * Called on the UI thread.
- */
- public void updateIcon() {
- if (mIcon == null) {
- return;
- }
- View view = findViewById(R.id.origin_icon);
- if (view != null) {
- ImageView imageView = (ImageView) view;
- imageView.setMaxHeight(MAX_ICON_SIZE);
- imageView.setMaxWidth(MAX_ICON_SIZE);
- imageView.setScaleType(ImageView.ScaleType.FIT_XY);
- imageView.setImageBitmap(mIcon);
- imageView.setVisibility(View.VISIBLE);
- }
- }
-
- /**
- * Utility method to download an icon from a url and set
- * it to the GUI element R.id.origin_icon.
- * It is used both in the shortcut dialog and the
- * permission dialog.
- * The actual download is done in the background via
- * IconDownload; once the icon is downlowded the UI is updated
- * via updateIcon().
- * The icon size is included in the layout with the choosen
- * size, although not displayed, to limit text reflow once
- * the icon is received.
- */
- void downloadIcon(String url) {
- if (url == null) {
- return;
- }
- View view = findViewById(R.id.origin_icon);
- if (view != null) {
- view.setMinimumWidth(mChoosenIconSize);
- view.setMinimumHeight(mChoosenIconSize);
- view.setVisibility(View.INVISIBLE);
- }
- Thread thread = new Thread(new IconDownload(url));
- thread.start();
- }
-
- /**
- * Utility method that get the dialogMessage
- * and icon and ask the setupDialog(message,icon)
- * method to set the values.
- */
- public void setupDialog() {
- TextView dialogMessage = null;
- ImageView icon = null;
-
- View view = findViewById(R.id.dialog_message);
- if (view != null) {
- dialogMessage = (TextView) view;
- }
-
- View iconView = findViewById(R.id.icon);
- if (iconView != null) {
- icon = (ImageView) iconView;
- }
-
- if ((dialogMessage != null) && (icon != null)) {
- setupDialog(dialogMessage, icon);
- dialogMessage.setVisibility(View.VISIBLE);
- }
- }
-
- /*
- * Set the message and icon of the dialog
- */
- public void setupDialog(TextView message, ImageView icon) {
- message.setText(R.string.unrecognized_dialog_message);
- icon.setImageResource(R.drawable.ic_dialog_menu_generic);
- message.setVisibility(View.VISIBLE);
- }
-
- /**
- * Setup the dialog
- * By default, just display a simple message.
- */
- public void setup() {
- setupButtons(0, 0, R.string.default_button);
- setupDialog();
- }
-
- /**
- * Method called when the back button is pressed,
- * allowing the dialog to intercept the default behaviour.
- */
- public boolean handleBackButton() {
- return false;
- }
-
- /**
- * Returns the resource string of the notification displayed
- * after the dialog. By default, does not return one.
- */
- public int notification() {
- return 0;
- }
-
- /**
- * If a secondary dialog (e.g. a confirmation dialog) is created,
- * GearsNativeDialog will call this method.
- */
- public Dialog onCreateDialog(int id) {
- // This should be redefined by subclasses as needed.
- return null;
- }
-
-}
diff --git a/src/com/android/browser/GearsNativeDialog.java b/src/com/android/browser/GearsNativeDialog.java
deleted file mode 100644
index b44ec2a..0000000
--- a/src/com/android/browser/GearsNativeDialog.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.browser;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.Window;
-import android.widget.BaseAdapter;
-import android.widget.Toast;
-
-import android.webkit.gears.NativeDialog;
-
-import com.android.browser.GearsBaseDialog;
-import com.android.browser.GearsPermissionsDialog;
-import com.android.browser.GearsSettingsDialog;
-
-/**
- * Native dialog Activity used by gears
- * TODO: rename in GearsNativeDialogActivity
- * @hide
- */
-public class GearsNativeDialog extends Activity {
-
- private static final String TAG = "GearsNativeDialog";
-
- private String mDialogArguments;
-
- private String mGearsVersion = null;
-
- private boolean mDebug = false;
-
- private int mDialogType;
- private final int SETTINGS_DIALOG = 1;
- private final int PERMISSION_DIALOG = 2;
- private final int LOCATION_DIALOG = 3;
-
- private final String VERSION_STRING = "version";
- private final String SETTINGS_DIALOG_STRING = "settings_dialog";
- private final String PERMISSION_DIALOG_STRING = "permissions_dialog";
- private final String LOCATION_DIALOG_STRING = "locations_dialog";
-
- private boolean mDialogDismissed = false;
-
- GearsBaseDialog dialog;
-
- // Handler for callbacks to the UI thread
- final Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- if (msg.what == GearsBaseDialog.NEW_ICON) {
- BaseAdapter adapter = (BaseAdapter) msg.obj;
- adapter.notifyDataSetChanged();
- } else if (msg.what == GearsBaseDialog.UPDATE_ICON) {
- dialog.updateIcon();
- } else if (msg.what == GearsBaseDialog.ALWAYS_DENY) {
- closeDialog(GearsBaseDialog.ALWAYS_DENY);
- } else if (msg.what == GearsBaseDialog.ALLOW) {
- closeDialog(GearsBaseDialog.ALLOW);
- } else if (msg.what == GearsBaseDialog.DENY) {
- closeDialog(GearsBaseDialog.DENY);
- }
- super.handleMessage(msg);
- }
- };
-
- @Override
- public void onCreate(Bundle icicle) {
- getArguments();
- if (mDialogType == SETTINGS_DIALOG) {
- setTheme(android.R.style.Theme);
- }
- super.onCreate(icicle);
- if (mDialogType != SETTINGS_DIALOG) {
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.gears_dialog);
- }
-
- switch (mDialogType) {
- case SETTINGS_DIALOG:
- dialog = new GearsSettingsDialog(this, mHandler, mDialogArguments);
- dialog.setGearsVersion(mGearsVersion);
- break;
- case PERMISSION_DIALOG:
- dialog = new GearsPermissionsDialog(this, mHandler, mDialogArguments);
- break;
- case LOCATION_DIALOG:
- dialog = new GearsPermissionsDialog(this, mHandler, mDialogArguments);
- break;
- default:
- dialog = new GearsBaseDialog(this, mHandler, mDialogArguments);
- }
- dialog.setDebug(mDebug);
- dialog.setup();
- }
-
- /**
- * Get the arguments for the dialog
- *
- * The dialog needs a json string as an argument, as
- * well as a dialogType. In debug mode the arguments
- * are mocked.
- */
- private void getArguments() {
- if (mDebug) {
- mDialogType = LOCATION_DIALOG +1;
- mockArguments();
-
- return;
- }
-
- Intent intent = getIntent();
- mDialogArguments = intent.getStringExtra("dialogArguments");
- String dialogTypeString = intent.getStringExtra("dialogType");
- if (dialogTypeString == null) {
- return;
- }
-
- if (Browser.LOGV_ENABLED) {
- Log.v(TAG, "dialogtype: " + dialogTypeString);
- }
-
- if (dialogTypeString.equalsIgnoreCase(SETTINGS_DIALOG_STRING)) {
- mDialogType = SETTINGS_DIALOG;
- mGearsVersion = intent.getStringExtra(VERSION_STRING);
- } else if (dialogTypeString.equalsIgnoreCase(PERMISSION_DIALOG_STRING)) {
- mDialogType = PERMISSION_DIALOG;
- } else if (dialogTypeString.equalsIgnoreCase(LOCATION_DIALOG_STRING)) {
- mDialogType = LOCATION_DIALOG;
- }
- }
-
- /**
- * Utility method for debugging the dialog.
- *
- * Set mock arguments.
- */
- private void mockArguments() {
-
- String argumentsPermissions = "{ locale: \"en-US\", "
- + "origin: \"http://www.google.com\", dialogType: \"localData\","
- + "customIcon: \"http://google-gears.googlecode.com/"
- + "svn/trunk/gears/test/manual/shortcuts/32.png\","
- + "customName: \"My Application\","
- + "customMessage: \"Press the button to enable my "
- + "application to run offline!\" };";
-
- String argumentsPermissions2 = "{ locale: \"en-US\", "
- + "origin: \"http://www.google.com\", dialogType: \"localData\" };";
-
- String argumentsLocation = "{ locale: \"en-US\", "
- + "origin: \"http://www.google.com\", dialogType: \"locationData\","
- + "customIcon: \"http://google-gears.googlecode.com/"
- + "svn/trunk/gears/test/manual/shortcuts/32.png\","
- + "customName: \"My Application\","
- + "customMessage: \"Press the button to enable my "
- + "application to run offline!\" };";
-
- String argumentsSettings = "{ locale: \"en-US\", permissions: [ { "
- + "name: \"http://www.google.com\", "
- + "localStorage: { permissionState: 0 }, "
- + "locationData: { permissionState: 1 } }, "
- + "{ name: \"http://www.aaronboodman.com\", "
- + "localStorage: { permissionState: 1 }, "
- + "locationData: { permissionState: 2 } }, "
- + "{ name: \"http://www.evil.org\", "
- + "localStorage: { permissionState: 2 }, "
- + "locationData: { permissionState: 2 } } ] }";
-
- switch (mDialogType) {
- case PERMISSION_DIALOG:
- mDialogArguments = argumentsPermissions;
- break;
- case LOCATION_DIALOG:
- mDialogArguments = argumentsLocation;
- break;
- case SETTINGS_DIALOG:
- mDialogArguments = argumentsSettings;
- break;
- }
- }
-
- /**
- * Close the dialog and set the return string value.
- */
- private void closeDialog(int closingType) {
- String ret = dialog.closeDialog(closingType);
-
- if (mDebug) {
- Log.v(TAG, "closeDialog ret value: " + ret);
- }
-
- NativeDialog.closeDialog(ret);
- notifyEndOfDialog();
- finish();
-
- // If the dialog sets a notification, we display it.
- int notification = dialog.notification();
- if (notification != 0) {
- Toast toast = Toast.makeText(this, notification, Toast.LENGTH_LONG);
- toast.setGravity(Gravity.BOTTOM, 0, 0);
- toast.show();
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- // In case we reach this point without
- // notifying NativeDialog, we do it now.
- if (!mDialogDismissed) {
- notifyEndOfDialog();
- }
- }
-
- @Override
- public void onPause(){
- super.onPause();
- if (!mDialogDismissed) {
- closeDialog(GearsBaseDialog.CANCEL);
- }
- }
-
- /**
- * Signal to NativeDialog that we are done.
- */
- private void notifyEndOfDialog() {
- NativeDialog.signalFinishedDialog();
- mDialogDismissed = true;
- }
-
- /**
- * 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 (!dialog.handleBackButton()) {
- // if the dialog doesn't do anything with the back button
- closeDialog(GearsBaseDialog.CANCEL);
- }
- return true; // event consumed
- }
- return super.dispatchKeyEvent(event);
- }
-
- /**
- * If the dialog call showDialog() on ourself, we let
- * it handle the creation of this secondary dialog.
- * It is used in GearsSettingsDialog, to create the confirmation
- * dialog when the user click on "Remove this site from Gears"
- */
- @Override
- protected Dialog onCreateDialog(int id) {
- return dialog.onCreateDialog(id);
- }
-
-}
diff --git a/src/com/android/browser/GearsPermissions.java b/src/com/android/browser/GearsPermissions.java
deleted file mode 100644
index e48e045..0000000
--- a/src/com/android/browser/GearsPermissions.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.browser;
-
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Iterator;
-
-/**
- * The permission mechanism works the following way:
- *
- * PermissionType allows to define a type of permission
- * (e.g. localStorage/locationData), storing a name and a set of
- * resource ids corresponding to the GUI resources.
- *
- * Permission defines an actual permission instance, with a type and a value.
- *
- * OriginPermissions holds an origin with a set of Permission objects
- */
-class GearsPermissions {
-
- private static final String TAG = "GearsPermissions";
-
- /**
- * Defines a type of permission
- *
- * Store the permission's name (used in the json result)
- * Graphically, each permission is a label followed by two radio buttons.
- * We store the resources ids here.
- */
- public static class PermissionType {
- public static final int PERMISSION_NOT_SET = 0;
- public static final int PERMISSION_ALLOWED = 1;
- public static final int PERMISSION_DENIED = 2;
-
- String mName;
- int mTitleRsc;
- int mSubtitleOnRsc;
- int mSubtitleOffRsc;
-
- PermissionType(String name) {
- mName = name;
- }
-
- public void setResources(int titleRsc,
- int subtitleOnRsc, int subtitleOffRsc) {
- mTitleRsc = titleRsc;
- mSubtitleOnRsc = subtitleOnRsc;
- mSubtitleOffRsc = subtitleOffRsc;
- }
-
- public String getName() {
- return mName;
- }
-
- public int getTitleRsc() {
- return mTitleRsc;
- }
-
- public int getSubtitleOnRsc() {
- return mSubtitleOnRsc;
- }
-
- public int getSubtitleOffRsc() {
- return mSubtitleOffRsc;
- }
-
- }
-
- /**
- * Simple class to store an instance of a permission
- *
- * i.e. a permission type and a value
- * Value can be either PERMISSION_NOT_SET,
- * PERMISSION_ALLOWED or PERMISSION_DENIED
- * (defined in PermissionType).
- */
- public static class Permission {
- PermissionType mType;
- int mValue;
-
- Permission(PermissionType type, int value) {
- mType = type;
- mValue = value;
- }
-
- Permission(PermissionType type) {
- mType = type;
- mValue = 0;
- }
-
- public PermissionType getType() {
- return mType;
- }
-
- public void setValue(int value) {
- mValue = value;
- }
-
- public int getValue() {
- return mValue;
- }
- }
-
- /**
- * Interface used by the GearsNativeDialog implementation
- * to listen to changes in the permissions.
- */
- public interface PermissionsChangesListener {
- public boolean setPermission(PermissionType type, int perm);
- }
-
- /**
- * Holds the model for an origin -- each origin has a set of
- * permissions.
- */
- public static class OriginPermissions {
- HashMap<PermissionType, Permission> mPermissions;
- String mOrigin;
- public static PermissionsChangesListener mListener;
-
- public static void setListener(PermissionsChangesListener listener) {
- mListener = listener;
- }
-
- OriginPermissions(String anOrigin) {
- mOrigin = anOrigin;
- mPermissions = new HashMap<PermissionType, Permission>();
- }
-
- OriginPermissions(OriginPermissions perms) {
- mOrigin = perms.getOrigin();
- mPermissions = new HashMap<PermissionType, Permission>();
- HashMap<PermissionType, Permission> permissions = perms.getPermissions();
- Iterator<PermissionType> iterator = permissions.keySet().iterator();
- while (iterator.hasNext()) {
- Permission permission = permissions.get(iterator.next());
- int value = permission.getValue();
- setPermission(permission.getType(), value);
- }
- }
-
- public String getOrigin() {
- return mOrigin;
- }
-
- public HashMap<PermissionType, Permission> getPermissions() {
- return mPermissions;
- }
-
- public int getPermission(PermissionType type) {
- return mPermissions.get(type).getValue();
- }
-
- public void setPermission(PermissionType type, int perm) {
- if (mPermissions.get(type) == null) {
- Permission permission = new Permission(type, perm);
- mPermissions.put(type, permission);
- return;
- }
-
- if (mListener != null) {
- mListener.setPermission(type, perm);
- }
-
- mPermissions.get(type).setValue(perm);
- }
-
- public void print() {
- Log.v(TAG, "Permissions for " + mOrigin);
- Iterator<PermissionType> iterator = mPermissions.keySet().iterator();
- while (iterator.hasNext()) {
- Permission permission = mPermissions.get(iterator.next());
- String name = permission.getType().getName();
- int value = permission.getValue();
- Log.v(TAG, " " + name + ": " + value);
- }
- }
- }
-
-}
diff --git a/src/com/android/browser/GearsPermissionsDialog.java b/src/com/android/browser/GearsPermissionsDialog.java
deleted file mode 100644
index dbec363..0000000
--- a/src/com/android/browser/GearsPermissionsDialog.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.browser;
-
-import android.app.Activity;
-import android.os.Handler;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Gears permission dialog
- */
-class GearsPermissionsDialog extends GearsBaseDialog {
-
- private static final String TAG = "GearsPermissionsDialog";
-
- private String mDialogType;
- private int mNotification = 0;
-
- public GearsPermissionsDialog(Activity activity,
- Handler handler,
- String arguments) {
- super (activity, handler, arguments);
- }
-
- public void setup() {
- inflate(R.layout.gears_dialog_permission, R.id.panel_content);
- setupButtons(R.string.permission_button_alwaysdeny,
- R.string.permission_button_allow,
- R.string.permission_button_deny);
-
- try {
- JSONObject json = new JSONObject(mDialogArguments);
-
- if (json.has("dialogType")) {
- mDialogType = json.getString("dialogType");
- setupDialog();
- }
-
- if (!json.has("customName")) {
- setLabel(json, "origin", R.id.origin_title);
- View titleView = findViewById(R.id.origin_title);
- if (titleView != null) {
- TextView title = (TextView) titleView;
- title.setGravity(Gravity.CENTER);
- }
- } else {
- setLabel(json, "customName", R.id.origin_title);
- setLabel(json, "origin", R.id.origin_subtitle);
- setLabel(json, "customMessage", R.id.origin_message);
- }
-
- if (json.has("customIcon")) {
- String iconUrl = json.getString("customIcon");
- mChoosenIconSize = 32;
- downloadIcon(iconUrl);
- }
-
- View msg = findViewById(R.id.permission_dialog_message);
- if (msg != null) {
- TextView dialogMessage = (TextView) msg;
- if (mDialogType.equalsIgnoreCase(LOCAL_DATA_STRING)) {
- dialogMessage.setText(R.string.query_data_message);
- } else if (mDialogType.equalsIgnoreCase(LOCATION_DATA_STRING)) {
- dialogMessage.setText(R.string.location_message);
- }
- }
-
- } catch (JSONException e) {
- Log.e(TAG, "JSON exception ", e);
- }
- }
-
- public void setupDialog(TextView message, ImageView icon) {
- if (mDialogType.equalsIgnoreCase(LOCAL_DATA_STRING)) {
- message.setText(R.string.query_data_prompt);
- icon.setImageResource(android.R.drawable.ic_popup_disk_full);
- } else if (mDialogType.equalsIgnoreCase(LOCATION_DATA_STRING)) {
- message.setText(R.string.location_prompt);
- icon.setImageResource(R.drawable.ic_dialog_menu_generic);
- }
- }
-
- public String closeDialog(int closingType) {
- String ret = null;
- switch (closingType) {
- case ALWAYS_DENY:
- ret = "{\"allow\": false, \"permanently\": true }";
- if (mDialogType.equalsIgnoreCase(LOCAL_DATA_STRING)) {
- mNotification = R.string.storage_notification_alwaysdeny;
- } else if (mDialogType.equalsIgnoreCase(LOCATION_DATA_STRING)) {
- mNotification = R.string.location_notification_alwaysdeny;
- }
- break;
- case ALLOW:
- ret = "{\"allow\": true, \"permanently\": true }";
- if (mDialogType.equalsIgnoreCase(LOCAL_DATA_STRING)) {
- mNotification = R.string.storage_notification;
- } else if (mDialogType.equalsIgnoreCase(LOCATION_DATA_STRING)) {
- mNotification = R.string.location_notification;
- }
- break;
- case DENY:
- ret = "{\"allow\": false, \"permanently\": false }";
- break;
- }
- return ret;
- }
-
- public int notification() {
- return mNotification;
- }
-}
diff --git a/src/com/android/browser/GearsSettingsDialog.java b/src/com/android/browser/GearsSettingsDialog.java
deleted file mode 100644
index 5ea2342..0000000
--- a/src/com/android/browser/GearsSettingsDialog.java
+++ /dev/null
@@ -1,460 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.browser;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Handler;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.BaseAdapter;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-import android.widget.RadioButton;
-import android.widget.TextView;
-
-import com.android.browser.GearsPermissions.OriginPermissions;
-import com.android.browser.GearsPermissions.Permission;
-import com.android.browser.GearsPermissions.PermissionsChangesListener;
-import com.android.browser.GearsPermissions.PermissionType;
-
-import java.util.Vector;
-import java.util.List;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Gears Settings dialog
- */
-class GearsSettingsDialog extends GearsBaseDialog
- implements PermissionsChangesListener {
-
- private static final String TAG = "GearsPermissionsDialog";
- private Vector<OriginPermissions> mSitesPermissions = null;
- private Vector<OriginPermissions> mOriginalPermissions = null;
- private Vector<OriginPermissions> mCurrentPermissions = null;
-
- private Vector<PermissionType> mPermissions;
- private static final int CONFIRMATION_REMOVE_DIALOG = 1;
-
- // We declare the permissions globally to simplify the code
- private final PermissionType LOCAL_STORAGE =
- new PermissionType(LOCAL_STORAGE_STRING);
- private final PermissionType LOCATION_DATA =
- new PermissionType(LOCATION_DATA_STRING);
-
- private boolean mChanges = false;
-
- SettingsAdapter mListAdapter;
-
- public GearsSettingsDialog(Activity activity,
- Handler handler,
- String arguments) {
- super (activity, handler, arguments);
- activity.setContentView(R.layout.gears_settings);
- }
-
- public void setup() {
- // First let's add the permissions' resources
- LOCAL_STORAGE.setResources(R.string.settings_storage_title,
- R.string.settings_storage_subtitle_on,
- R.string.settings_storage_subtitle_off);
- LOCATION_DATA.setResources(R.string.settings_location_title,
- R.string.settings_location_subtitle_on,
- R.string.settings_location_subtitle_off);
- // add the permissions to the list of permissions.
- mPermissions = new Vector<PermissionType>();
- mPermissions.add(LOCAL_STORAGE);
- mPermissions.add(LOCATION_DATA);
- OriginPermissions.setListener(this);
-
-
- setupDialog();
-
- // We manage the permissions using three vectors, mSitesPermissions,
- // mOriginalPermissions and mCurrentPermissions.
- // The dialog's arguments are parsed and a list of permissions is
- // generated and stored in those three vectors.
- // mOriginalPermissions is a separate copy and will not be modified;
- // mSitesPermissions contains the current permissions _only_ --
- // if an origin is removed, it is also removed from mSitesPermissions.
- // Finally, mCurrentPermissions contains the current permissions and
- // is a clone of mSitesPermissions, but removed sites aren't removed,
- // their permissions are simply set to PERMISSION_NOT_SET. This
- // allows us to easily generate the final difference between the
- // original permissions and the final permissions, while directly
- // using mSitesPermissions for the listView adapter (SettingsAdapter).
-
- mSitesPermissions = new Vector<OriginPermissions>();
- mOriginalPermissions = new Vector<OriginPermissions>();
-
- try {
- JSONObject json = new JSONObject(mDialogArguments);
- if (json.has("permissions")) {
- JSONArray jsonArray = json.getJSONArray("permissions");
- for (int i = 0; i < jsonArray.length(); i++) {
- JSONObject infos = jsonArray.getJSONObject(i);
- String name = null;
- int localStorage = PermissionType.PERMISSION_NOT_SET;
- int locationData = PermissionType.PERMISSION_NOT_SET;
- if (infos.has("name")) {
- name = infos.getString("name");
- }
- if (infos.has(LOCAL_STORAGE_STRING)) {
- JSONObject perm = infos.getJSONObject(LOCAL_STORAGE_STRING);
- if (perm.has("permissionState")) {
- localStorage = perm.getInt("permissionState");
- }
- }
- if (infos.has(LOCATION_DATA_STRING)) {
- JSONObject perm = infos.getJSONObject(LOCATION_DATA_STRING);
- if (perm.has("permissionState")) {
- locationData = perm.getInt("permissionState");
- }
- }
- OriginPermissions perms = new OriginPermissions(name);
- perms.setPermission(LOCAL_STORAGE, localStorage);
- perms.setPermission(LOCATION_DATA, locationData);
-
- mSitesPermissions.add(perms);
- mOriginalPermissions.add(new OriginPermissions(perms));
- }
- }
- } catch (JSONException e) {
- Log.e(TAG, "JSON exception ", e);
- }
- mCurrentPermissions = (Vector<OriginPermissions>)mSitesPermissions.clone();
-
- View listView = findViewById(R.id.sites_list);
- if (listView != null) {
- ListView list = (ListView) listView;
- mListAdapter = new SettingsAdapter(mActivity, mSitesPermissions);
- list.setAdapter(mListAdapter);
- list.setScrollBarStyle(android.view.View.SCROLLBARS_OUTSIDE_INSET);
- list.setOnItemClickListener(mListAdapter);
- }
- if (mDebug) {
- printPermissions();
- }
- }
-
- private void setMainTitle() {
- String windowTitle = mActivity.getString(R.string.pref_extras_gears_settings);
- mActivity.setTitle(windowTitle);
- }
-
- public void setupDialog() {
- setMainTitle();
- }
-
- /**
- * GearsPermissions.PermissionsChangesListener delegate
- */
- public boolean setPermission(PermissionType type, int perm) {
- if (mChanges == false) {
- mChanges = true;
- }
- return mChanges;
- }
-
- public boolean handleBackButton() {
- return mListAdapter.backButtonPressed();
- }
-
- /**
- * We use this to create a confirmation dialog when the user
- * clicks on "remove this site from gears"
- */
- public Dialog onCreateDialog(int id) {
- return new AlertDialog.Builder(mActivity)
- .setTitle(R.string.settings_confirmation_remove_title)
- .setMessage(R.string.settings_confirmation_remove)
- .setPositiveButton(android.R.string.ok,
- new AlertDialog.OnClickListener() {
- public void onClick(DialogInterface dlg, int which) {
- mListAdapter.removeCurrentSite();
- }
- })
- .setNegativeButton(android.R.string.cancel, null)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .create();
- }
-
- /**
- * Adapter class for the list view in the settings dialog
- *
- * We first display a list of all the origins (sites), or
- * a message saying that no permission is set if the list is empty.
- * When the user click on one of the origin, we then display
- * the list of the permissions existing for that origin.
- * Each permission can be either allowed or denied by clicking
- * on the checkbox.
- * The last row is a special case, allowing to remove the entire origin.
- */
- class SettingsAdapter extends BaseAdapter
- implements AdapterView.OnItemClickListener {
- private Activity mContext;
- private List mItems;
- private OriginPermissions mCurrentSite;
- private Vector mCurrentPermissions;
- private int MAX_ROW_HEIGHT = 64;
-
- SettingsAdapter(Activity context, List items) {
- mContext = context;
- mItems = items;
- mCurrentSite = null;
- }
-
- public int getCount() {
- if (mCurrentSite == null) {
- int size = mItems.size();
- if (size == 0) {
- return 1;
- } else {
- return size;
- }
- }
- return mCurrentPermissions.size() + 1;
- }
-
- public long getItemId(int position) {
- return position;
- }
-
- private String shortName(String url) {
- // We remove the http and https prefix
- if (url.startsWith("http://")) {
- return url.substring(7);
- }
- if (url.startsWith("https://")) {
- return url.substring(8);
- }
- return url;
- }
-
- public Object getItem(int position) {
- if (mCurrentSite == null) {
- if (mItems.size() == 0) {
- return null;
- } else {
- return mItems.get(position);
- }
- }
- return mCurrentPermissions.get(position);
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- View row = convertView;
- if (row == null) { // no cached view, we create one
- LayoutInflater inflater = (LayoutInflater) getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- row = inflater.inflate(R.layout.gears_settings_row, null);
- }
- row.setMinimumHeight(MAX_ROW_HEIGHT);
-
- if (mCurrentSite == null) {
- if (mItems.size() == 0) {
- hideView(row, R.id.title);
- hideView(row, R.id.subtitle);
- hideView(row, R.id.checkbox);
- hideView(row, R.id.icon);
- setText(row, R.id.info, R.string.settings_empty);
- } else {
- hideView(row, R.id.subtitle);
- hideView(row, R.id.info);
- hideView(row, R.id.checkbox);
- OriginPermissions perms = (OriginPermissions) mItems.get(position);
- setText(row, R.id.title, shortName(perms.getOrigin()));
- showView(row, R.id.icon);
- }
- } else {
- if (position == getCount() - 1) {
- // last position: "remove this site from gears"
- hideView(row, R.id.subtitle);
- hideView(row, R.id.info);
- hideView(row, R.id.checkbox);
- hideView(row, R.id.icon);
- setText(row, R.id.title, R.string.settings_remove_site);
- } else {
- hideView(row, R.id.info);
- hideView(row, R.id.icon);
- showView(row, R.id.checkbox);
-
- PermissionType type =
- (PermissionType) mCurrentPermissions.get(position);
- setText(row, R.id.title, type.getTitleRsc());
-
- View checkboxView = row.findViewById(R.id.checkbox);
- if (checkboxView != null) {
- CheckBox checkbox = (CheckBox) checkboxView;
- int perm = mCurrentSite.getPermission(type);
- if (perm == PermissionType.PERMISSION_DENIED) {
- setText(row, R.id.subtitle, type.getSubtitleOffRsc());
- checkbox.setChecked(false);
- } else {
- setText(row, R.id.subtitle, type.getSubtitleOnRsc());
- checkbox.setChecked(true);
- }
- }
- }
- }
- return row;
- }
-
- public void removeCurrentSite() {
- mCurrentSite.setPermission(LOCAL_STORAGE,
- PermissionType.PERMISSION_NOT_SET);
- mCurrentSite.setPermission(LOCATION_DATA,
- PermissionType.PERMISSION_NOT_SET);
- mSitesPermissions.remove(mCurrentSite);
- mCurrentSite = null;
- setMainTitle();
- notifyDataSetChanged();
- }
-
- public void onItemClick(AdapterView<?> parent,
- View view,
- int position,
- long id) {
- if (mItems.size() == 0) {
- return;
- }
- if (mCurrentSite == null) {
- mCurrentSite = (OriginPermissions) mItems.get(position);
- mCurrentPermissions = new Vector();
- for (int i = 0; i < mPermissions.size(); i++) {
- PermissionType type = mPermissions.get(i);
- int perm = mCurrentSite.getPermission(type);
- if (perm != PermissionType.PERMISSION_NOT_SET) {
- mCurrentPermissions.add(type);
- }
- }
- mContext.setTitle(shortName(mCurrentSite.getOrigin()));
- } else {
- if (position == getCount() - 1) { // last item (remove site)
- // Ask the user to confirm
- // If yes, removeCurrentSite() will be called via the dialog callback.
- mActivity.showDialog(CONFIRMATION_REMOVE_DIALOG);
- } else {
- PermissionType type =
- (PermissionType) mCurrentPermissions.get(position);
- if (mCurrentSite.getPermission(type) ==
- PermissionType.PERMISSION_ALLOWED) {
- mCurrentSite.setPermission(type, PermissionType.PERMISSION_DENIED);
- } else {
- mCurrentSite.setPermission(type, PermissionType.PERMISSION_ALLOWED);
- }
- }
- }
- notifyDataSetChanged();
- }
-
- public boolean backButtonPressed() {
- if (mCurrentSite != null) { // we intercept the back button
- mCurrentSite = null;
- setMainTitle();
- notifyDataSetChanged();
- return true;
- }
- return false;
- }
-
- }
-
- /**
- * Utility method used in debug mode to print the list of
- * permissions (original values and current values).
- */
- public void printPermissions() {
- Log.v(TAG, "Original Permissions: ");
- for (int i = 0; i < mOriginalPermissions.size(); i++) {
- OriginPermissions p = mOriginalPermissions.get(i);
- p.print();
- }
- Log.v(TAG, "Current Permissions: ");
- for (int i = 0; i < mSitesPermissions.size(); i++) {
- OriginPermissions p = mSitesPermissions.get(i);
- p.print();
- }
- }
-
- /**
- * Computes the difference between the original permissions and the
- * current ones. Returns a json-formatted string.
- * It is used by the Settings dialog.
- */
- public String computeDiff(boolean modif) {
- String ret = null;
- try {
- JSONObject results = new JSONObject();
- JSONArray permissions = new JSONArray();
-
- for (int i = 0; modif && i < mOriginalPermissions.size(); i++) {
- OriginPermissions original = mOriginalPermissions.get(i);
- OriginPermissions current = mCurrentPermissions.get(i);
- JSONObject permission = new JSONObject();
- boolean modifications = false;
-
- for (int j = 0; j < mPermissions.size(); j++) {
- PermissionType type = mPermissions.get(j);
-
- if (current.getPermission(type) != original.getPermission(type)) {
- JSONObject state = new JSONObject();
- state.put("permissionState", current.getPermission(type));
- permission.put(type.getName(), state);
- modifications = true;
- }
- }
-
- if (modifications) {
- permission.put("name", current.getOrigin());
- permissions.put(permission);
- }
- }
- results.put("modifiedOrigins", permissions);
- ret = results.toString();
- } catch (JSONException e) {
- Log.e(TAG, "JSON exception ", e);
- }
- return ret;
- }
-
- public String closeDialog(int closingType) {
- String ret = computeDiff(mChanges);
-
- if (mDebug) {
- printPermissions();
- }
-
- return ret;
- }
-
-}
diff --git a/src/com/android/browser/GeolocationPermissionsPrompt.java b/src/com/android/browser/GeolocationPermissionsPrompt.java
new file mode 100755
index 0000000..a21bc3e
--- /dev/null
+++ b/src/com/android/browser/GeolocationPermissionsPrompt.java
@@ -0,0 +1,120 @@
+/*
+ * 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.net.Uri;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.webkit.WebView;
+import android.webkit.GeolocationPermissions;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class GeolocationPermissionsPrompt extends LinearLayout {
+ private LinearLayout mInner;
+ private TextView mMessage;
+ private Button mShareButton;
+ private Button mDontShareButton;
+ private CheckBox mRemember;
+ private GeolocationPermissions.Callback mCallback;
+ private String mOrigin;
+
+ public GeolocationPermissionsPrompt(Context context) {
+ this(context, null);
+ }
+
+ public GeolocationPermissionsPrompt(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ LayoutInflater factory = LayoutInflater.from(context);
+ factory.inflate(R.layout.geolocation_permissions_prompt, this);
+
+ mInner = (LinearLayout) findViewById(R.id.inner);
+ mMessage = (TextView) findViewById(R.id.message);
+ mShareButton = (Button) findViewById(R.id.share_button);
+ mDontShareButton = (Button) findViewById(R.id.dont_share_button);
+ mRemember = (CheckBox) findViewById(R.id.remember);
+ setButtonClickListeners();
+ }
+
+ /**
+ * Shows the prompt for the given origin. When the user clicks on one of
+ * the buttons, the supplied callback is be called.
+ */
+ public void show(String origin, GeolocationPermissions.Callback callback) {
+ mOrigin = origin;
+ mCallback = callback;
+ Uri uri = Uri.parse(mOrigin);
+ setMessage("http".equals(uri.getScheme()) ? mOrigin.substring(7) : mOrigin);
+ // The checkbox should always be intially checked.
+ mRemember.setChecked(true);
+ showDialog(true);
+ }
+
+ /**
+ * Hides the prompt.
+ */
+ public void hide() {
+ showDialog(false);
+ }
+
+ /**
+ * Sets the on click listeners for the buttons.
+ */
+ private void setButtonClickListeners() {
+ final GeolocationPermissionsPrompt me = this;
+ mShareButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ me.handleButtonClick(true);
+ }
+ });
+ mDontShareButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ me.handleButtonClick(false);
+ }
+ });
+ }
+
+ /**
+ * Handles a click on one the buttons by invoking the callback.
+ */
+ private void handleButtonClick(boolean allow) {
+ boolean remember = mRemember.isChecked();
+ showDialog(false);
+ mCallback.invoke(mOrigin, allow, remember);
+ }
+
+ /**
+ * Sets the prompt's message.
+ */
+ private void setMessage(CharSequence origin) {
+ mMessage.setText(String.format(
+ getResources().getString(R.string.geolocation_permissions_prompt_message),
+ origin));
+ }
+
+ /**
+ * Shows or hides the prompt.
+ */
+ private void showDialog(boolean shown) {
+ mInner.setVisibility(shown ? View.VISIBLE : View.GONE);
+ }
+}
diff --git a/src/com/android/browser/HistoryItem.java b/src/com/android/browser/HistoryItem.java
index 55e43f0..51cb026 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 @@
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(), null, 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, getName());
}
- 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 @@
}
/**
+ * 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/ImageAdapter.java b/src/com/android/browser/ImageAdapter.java
deleted file mode 100644
index f95753a..0000000
--- a/src/com/android/browser/ImageAdapter.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.browser;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.DataSetObserver;
-import android.graphics.Color;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.LayoutInflater;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-
-/**
- * Adapter used by ImageGrid.
- */
-public class ImageAdapter implements ListAdapter {
-
- ArrayList<TabControl.Tab> mItems; // Items shown in the grid
- private ArrayList<DataSetObserver> mDataObservers; // Data change listeners
- private Context mContext; // Context to use to inflate views
- private boolean mMaxedOut;
- private ImageGrid mImageGrid;
- private boolean mIsLive;
- private int mTabHeight;
-
- ImageAdapter(Context context, ImageGrid grid, boolean live) {
- mContext = context;
- mIsLive = live;
- mItems = new ArrayList<TabControl.Tab>();
- mImageGrid = grid;
- mDataObservers = new ArrayList<DataSetObserver>();
- }
-
- void heightChanged(int newHeight) {
- mTabHeight = newHeight;
- }
-
- /**
- * Whether the adapter is at its limit, determined by TabControl.MAX_TABS
- *
- * @return True if the number of Tabs represented in this Adapter is at its
- * maximum.
- */
- public boolean maxedOut() {
- return mMaxedOut;
- }
-
- /**
- * Clear the internal WebViews and remove their picture listeners.
- */
- public void clear() {
- mItems.clear();
- notifyObservers();
- }
-
- /**
- * Add a new window web page to the grid
- *
- * @param t The tab to display
- */
- public void add(TabControl.Tab t) {
- if (mMaxedOut) {
- return;
- }
- mItems.add(t);
- notifyObservers();
- if (mItems.size() == TabControl.MAX_TABS) {
- mMaxedOut = true;
- }
- }
-
- /**
- * Remove a window from the list. At this point, the window
- * has already gone. It just needs to be removed from the screen
- *
- * @param index window to remove
- */
- public void remove(int index) {
- if (index >= 0 && index < mItems.size()) {
- mItems.remove(index);
- notifyObservers();
- mMaxedOut = false;
- }
- }
-
- /* (non-Javadoc)
- * @see android.widget.ListAdapter#areAllItemsSelectable()
- */
- public boolean areAllItemsEnabled() {
- return true;
- }
-
- /* (non-Javadoc)
- * @see android.widget.ListAdapter#isSelectable(int)
- */
- public boolean isEnabled(int position) {
- if (position >= 0 && position <= mItems.size()) {
- return true;
- }
- return false;
- }
-
- /* (non-Javadoc)
- * @see android.widget.Adapter#getCount()
- */
- public int getCount() {
- // Include the New Window button if we have not reached the tab limit
- if (!mMaxedOut) {
- return mItems.size()+1;
- }
- return mItems.size();
- }
-
- /* (non-Javadoc)
- * @see android.widget.Adapter#getItem(int)
- */
- public Object getItem(int position) {
- if (!mMaxedOut) {
- if (0 == position) {
- return null;
- }
- return mItems.get(position);
- }
- return mItems.get(position);
- }
-
- /* (non-Javadoc)
- * @see android.widget.Adapter#getItemId(int)
- */
- public long getItemId(int position) {
- return position;
- }
-
- /* (non-Javadoc)
- * @see android.widget.Adapter#getView(int, android.view.View,
- * android.view.ViewGroup)
- */
- public View getView(int position, View convertView, ViewGroup parent) {
- View v = null;
- if (convertView != null) {
- v = convertView;
- } else {
- LayoutInflater factory = LayoutInflater.from(mContext);
- v = factory.inflate(R.layout.tabitem, null);
- }
- FakeWebView img = (FakeWebView) v.findViewById(R.id.icon);
- ImageView close = (ImageView) v.findViewById(R.id.close);
- TextView tv = (TextView) v.findViewById(R.id.label);
-
- // position needs to be in the range of Tab indices.
- if (!mMaxedOut) {
- position--;
- }
-
- // Create the View for actual tabs
- if (position != ImageGrid.NEW_TAB) {
- TabControl.Tab t = mItems.get(position);
- img.setTab(t);
- tv.setText(t.getTitle());
- // Do not put the 'X' if the tab picker isn't "live" (meaning the
- // user cannot click on a tab)
- if (!mIsLive) {
- close.setVisibility(View.GONE);
- } else {
- close.setVisibility(View.VISIBLE);
- final int pos = position;
- close.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- ImageAdapter.this.confirmClose(pos);
- }
- });
- }
- } else {
- img.setBackgroundColor(Color.BLACK);
- img.setImageResource(R.drawable.ic_new_window);
- img.setScaleType(ImageView.ScaleType.CENTER);
- img.setPadding(0, 0, 0, 34);
- tv.setText(R.string.new_window);
- close.setVisibility(View.GONE);
- }
- ViewGroup.LayoutParams lp = img.getLayoutParams();
- if (lp.height != mTabHeight) {
- lp.height = mTabHeight;
- img.requestLayout();
- }
- return v;
- }
-
- /*
- * Pop a confirmation dialog to the user asking if they want to close this
- * tab.
- */
- private void confirmClose(final int position) {
- final ImageGrid.Listener l = mImageGrid.getListener();
- if (l == null) {
- return;
- }
- l.remove(position);
- }
-
- /* (non-Javadoc)
- * @see android.widget.Adapter#registerDataSetObserver(android.database.DataSetObserver)
- */
- public void registerDataSetObserver(DataSetObserver observer) {
- mDataObservers.add(observer);
- }
-
- /* (non-Javadoc)
- * @see android.widget.Adapter#hasStableIds()
- */
- public boolean hasStableIds() {
- return true;
- }
-
- /* (non-Javadoc)
- * @see android.widget.Adapter#unregisterDataSetObserver(android.database.DataSetObserver)
- */
- public void unregisterDataSetObserver(DataSetObserver observer) {
- mDataObservers.remove(observer);
- }
-
- /**
- * Notify all the observers that a change has happened.
- */
- void notifyObservers() {
- for (DataSetObserver observer : mDataObservers) {
- observer.onChanged();
- }
- }
-
- public int getItemViewType(int position) {
- return 0;
- }
-
- public int getViewTypeCount() {
- return 1;
- }
-
- public boolean isEmpty() {
- return getCount() == 0;
- }
-}
diff --git a/src/com/android/browser/ImageGrid.java b/src/com/android/browser/ImageGrid.java
deleted file mode 100644
index 9967f36..0000000
--- a/src/com/android/browser/ImageGrid.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.browser;
-
-import android.content.Context;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.KeyEvent;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnCreateContextMenuListener;
-import android.webkit.WebView;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.GridView;
-
-/**
- * This class implements a Grid layout of Views for the Tab picker.
- */
-class ImageGrid extends GridView implements OnItemClickListener,
- OnCreateContextMenuListener {
-
- private Listener mListener;
- private ImageAdapter mAdapter;
- private boolean mIsLive;
- private static final int SPACING = 10;
- public static final int CANCEL = -99;
- public static final int NEW_TAB = -1;
-
- /**
- * Constructor
- * @param context Context to use when inflating resources.
- * @param live TRUE if the view can accept touch or click
- * @param l Listener to respond to clicks etc.
- */
- public ImageGrid(Context context, boolean live, Listener l) {
- super(context);
-
- mIsLive = live;
- if (live) {
- setFocusable(true);
- setFocusableInTouchMode(true);
- setOnItemClickListener(this);
- setOnCreateContextMenuListener(this);
- }
- mListener = l;
-
- mAdapter = new ImageAdapter(context, this, live);
- setAdapter(mAdapter);
-
- setBackgroundColor(0xFF000000);
-
- setVerticalSpacing(SPACING);
- setHorizontalSpacing(SPACING);
- setNumColumns(2);
- setStretchMode(GridView.STRETCH_COLUMN_WIDTH);
- setSelector(android.R.drawable.gallery_thumb);
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- // We always consume the BACK key even if mListener is null or the
- // ImageGrid is not "live." This prevents crashes during tab animations
- // if the user presses BACK.
- if ((event.getAction() == KeyEvent.ACTION_DOWN) &&
- (event.getKeyCode() == KeyEvent.KEYCODE_BACK)) {
- if (mListener != null && mIsLive) {
- mListener.onClick(CANCEL);
- invalidate();
- }
- return true;
- }
- return super.dispatchKeyEvent(event);
- }
-
- /**
- * Called by BrowserActivity to add a new window to the tab picker.
- * This does not happen dynamically, this only happens during view
- * setup.
- *
- * @param v Webview of the tab to add
- * @param name Web page title
- * @param url URL of the webpage
- */
- public void add(TabControl.Tab t) {
- mAdapter.add(t);
- }
-
- /**
- * Called by BrowserActivity when a window has been removed from the
- * tab list.
- *
- * @param index Window to remove, from 0 to MAX_TABS-1
- */
- public void remove(int index) {
- if (Browser.DEBUG && (index < 0 || index >= TabControl.MAX_TABS)) {
- throw new AssertionError();
- }
- mAdapter.remove(index);
- }
-
- /**
- * Request focus to initially set to a particular tab.
- *
- * @param startingIndex This is a Tab index from 0 - MAX_TABS-1 and does not
- * include the "New Tab" cell.
- */
- public void setCurrentIndex(int startingIndex) {
- if (!mAdapter.maxedOut()) {
- startingIndex++;
- }
- setSelection(startingIndex);
- }
-
- public Listener getListener() {
- return mListener;
- }
-
- public void setListener(Listener l) {
- mListener = l;
- }
-
- /**
- * Return true if the ImageGrid is live. This means that tabs can be chosen
- * and the menu can be invoked.
- */
- public boolean isLive() {
- return mIsLive;
- }
-
- /**
- * Do some internal cleanup of the ImageGrid's adapter.
- */
- public void clear() {
- mAdapter.clear();
- }
-
- /* (non-Javadoc)
- * @see android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget.AdapterView, android.view.View, int, long)
- */
- public void onItemClick(AdapterView parent, View v, int position, long id) {
- if (!mAdapter.maxedOut()) {
- position--;
- }
- // Position will be -1 for the "New Tab" cell.
- if (mListener != null) {
- mListener.onClick(position);
- }
- }
-
- /* (non-Javadoc)
- * @see android.view.View.OnCreateContextMenuListener#onCreateContextMenu(android.view.ContextMenu, android.view.View, java.lang.Object)
- */
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- // Do not create the context menu if there is no listener or the Tab
- // overview is not "live."
- if (mListener == null || !mIsLive) {
- return;
- }
- AdapterView.AdapterContextMenuInfo info =
- (AdapterView.AdapterContextMenuInfo) menuInfo;
- boolean maxed = mAdapter.maxedOut();
- if (info.position > 0 || maxed) {
- MenuInflater inflater = new MenuInflater(mContext);
- inflater.inflate(R.menu.tabscontext, menu);
- int position = info.position;
- if (!maxed) {
- position--;
- }
- menu.setHeaderTitle(mAdapter.mItems.get(position).getTitle());
- }
- }
-
- // convert a context menu position to an actual tab position. Since context
- // menus are not created for the "New Tab" cell, this will always return a
- // valid tab position.
- public int getContextMenuPosition(MenuItem menu) {
- AdapterView.AdapterContextMenuInfo info =
- (AdapterView.AdapterContextMenuInfo) menu.getMenuInfo();
- int pos = info.position;
- if (!mAdapter.maxedOut()) {
- pos--;
- }
- return pos;
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- // Called when our orientation changes. Tell the adapter about the new
- // size. Compute the individual tab height by taking the grid height
- // and subtracting the SPACING. Then subtract the list padding twice
- // (once for each tab on screen) and divide the remaining height by 2.
- int tabHeight = (h - SPACING
- - 2 * (getListPaddingTop() + getListPaddingBottom())) / 2;
- mAdapter.heightChanged(tabHeight);
- super.onSizeChanged(w, h, oldw, oldh);
- }
-
- /**
- * Listener to be notified by behavior of ImageGrid.
- */
- public interface Listener {
- /**
- * Called when enter is pressed on the list.
- * @param position The index of the selected image when
- * enter is pressed.
- */
- void onClick(int position);
-
- /**
- * Called when remove is called on the grid.
- */
- void remove(int position);
- }
-
-}
diff --git a/src/com/android/browser/KeyTracker.java b/src/com/android/browser/KeyTracker.java
deleted file mode 100644
index 344e4f8..0000000
--- a/src/com/android/browser/KeyTracker.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2006 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.view.KeyEvent;
-import android.view.ViewConfiguration;
-
-class KeyTracker {
-
- public enum Stage {
- DOWN, //!< the key has just been pressed
- SHORT_REPEAT, //!< repeated key, but duration is under the long-press threshold
- LONG_REPEAT, //!< repeated key, but duration is over the long-press threshold
- UP //!< the key is being released
- }
-
- public enum State {
- KEEP_TRACKING, //!< return this to continue to track the key
- DONE_TRACKING, //!< return this if you handled the key, but need not track it anymore
- NOT_TRACKING //!< return this if you will not handle this key
- }
-
- public interface OnKeyTracker {
-
- /** Called whenever there is a key event [down, short/long repeat, up]
- @param keyCode The current keyCode (see KeyEvent class)
- @param msg The message associated with the keyCode
- @maram stage The state the key press is in [down, short/long repeat, up]
- @param duration The number of milliseconds since this key was initially pressed
- @return your state after seeing the key. If you return DONE_TRACKING or NOT_TRACKING,
- you will not be called again for the lifetime of this key event.
- */
- public State onKeyTracker(int keyCode, KeyEvent event, Stage stage, int duration);
- }
-
- public KeyTracker(OnKeyTracker tracker) {
- mTracker = tracker;
- }
-
- public boolean doKeyDown(int keyCode, KeyEvent event) {
- long now = System.currentTimeMillis();
- Stage stage = null;
-
- // check if its a new/different key
- if (mKeyCode != keyCode || event.getRepeatCount() == 0) {
- mKeyCode = keyCode;
- mStartMS = now;
- stage = Stage.DOWN;
- }
- else if (mState == State.KEEP_TRACKING) {
- stage = (now - mStartMS) >= LONG_PRESS_DURATION_MS ? Stage.LONG_REPEAT : Stage.SHORT_REPEAT;
- }
-
- if (stage != null) {
- mEvent = event;
- callTracker(stage, now);
- }
-
- return mState != State.NOT_TRACKING;
- }
-
- public boolean doKeyUp(int keyCode, KeyEvent event) {
- boolean handled = false;
-
- if (mState == State.KEEP_TRACKING && mKeyCode == keyCode) {
- mEvent = event;
- callTracker(Stage.UP, System.currentTimeMillis());
- handled = mState != State.NOT_TRACKING;
- }
- mKeyCode = NOT_A_KEYCODE;
- return handled;
- }
-
- private void callTracker(Stage stage, long now) {
- mState = mTracker.onKeyTracker(mKeyCode, mEvent, stage, (int)(now - mStartMS));
- }
-
- private void dump() {
- System.out.println(" key=" + mKeyCode + " dur=" + (System.currentTimeMillis() - mStartMS) +
- " state=" + mState);
- }
-
- private int mKeyCode = NOT_A_KEYCODE;
- private KeyEvent mEvent;
- private long mStartMS;
- private State mState;
- private OnKeyTracker mTracker;
-
- private static final int LONG_PRESS_DURATION_MS =
- ViewConfiguration.getLongPressTimeout();
- private static final int NOT_A_KEYCODE = -123456;
-}
-
diff --git a/src/com/android/browser/MostVisitedActivity.java b/src/com/android/browser/MostVisitedActivity.java
index 83342a1..d03c7a3 100644
--- a/src/com/android/browser/MostVisitedActivity.java
+++ b/src/com/android/browser/MostVisitedActivity.java
@@ -23,6 +23,7 @@
import android.database.Cursor;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Browser;
@@ -35,6 +36,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.ViewStub;
import java.util.Vector;
@@ -49,12 +51,11 @@
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdapter = new MyAdapter();
- CombinedBookmarkHistoryActivity.getIconListenerSet(getContentResolver())
+ CombinedBookmarkHistoryActivity.getIconListenerSet()
.addListener(mIconReceiver);
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);
@@ -63,7 +64,7 @@
@Override
protected void onDestroy() {
super.onDestroy();
- CombinedBookmarkHistoryActivity.getIconListenerSet(getContentResolver())
+ CombinedBookmarkHistoryActivity.getIconListenerSet()
.removeListener(mIconReceiver);
}
@@ -94,16 +95,18 @@
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;
+ private static final int mFaviconIndex = 3;
MyAdapter() {
mObservers = new Vector<DataSetObserver>();
String[] projection = new String[] {
Browser.BookmarkColumns.URL,
Browser.BookmarkColumns.TITLE,
- Browser.BookmarkColumns.BOOKMARK };
+ Browser.BookmarkColumns.BOOKMARK,
+ Browser.BookmarkColumns.FAVICON };
String whereClause = Browser.BookmarkColumns.VISITS + " != 0";
String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
mCursor = managedQuery(Browser.BOOKMARKS_URI, projection,
@@ -145,8 +148,14 @@
item.setName(mCursor.getString(mTitleIndex));
String url = mCursor.getString(mUrlIndex);
item.setUrl(url);
- item.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet(
- getContentResolver()).getFavicon(url));
+ byte[] data = mCursor.getBlob(mFaviconIndex);
+ if (data != null) {
+ item.setFavicon(BitmapFactory.decodeByteArray(data, 0,
+ data.length));
+ } else {
+ item.setFavicon(CombinedBookmarkHistoryActivity
+ .getIconListenerSet().getFavicon(url));
+ }
item.setIsBookmark(1 == mCursor.getInt(mBookmarkIndex));
return item;
}
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index 575be8d..2f15a9c 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -17,6 +17,7 @@
package com.android.browser;
import android.content.Context;
+import android.graphics.Bitmap;
import android.graphics.Picture;
import android.net.http.SslError;
import android.os.Bundle;
@@ -38,6 +39,7 @@
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.ImageButton;
+import android.widget.LinearLayout;
import java.io.File;
import java.io.FileInputStream;
@@ -68,7 +70,7 @@
private final LayoutInflater mInflateService;
// Subclass of WebViewClient used in subwindows to notify the main
// WebViewClient of certain WebView activities.
- private class SubWindowClient extends WebViewClient {
+ private static class SubWindowClient extends WebViewClient {
// The main WebViewClient.
private final WebViewClient mClient;
@@ -104,6 +106,16 @@
String description, String failingUrl) {
mClient.onReceivedError(view, errorCode, description, failingUrl);
}
+ @Override
+ public boolean shouldOverrideKeyEvent(WebView view,
+ android.view.KeyEvent event) {
+ return mClient.shouldOverrideKeyEvent(view, event);
+ }
+ @Override
+ public void onUnhandledKeyEvent(WebView view,
+ android.view.KeyEvent event) {
+ mClient.onUnhandledKeyEvent(view, event);
+ }
}
// Subclass of WebChromeClient to display javascript dialogs.
private class SubWindowChromeClient extends WebChromeClient {
@@ -139,7 +151,7 @@
public void onRequestFocus(WebView view) {
Tab t = getTabFromView(view);
if (t != getCurrentTab()) {
- mActivity.showTab(t);
+ mActivity.switchToTab(getTabIndex(t));
}
}
}
@@ -148,20 +160,19 @@
public static class PickerData {
String mUrl;
String mTitle;
+ Bitmap mFavicon;
float mScale;
int mScrollX;
int mScrollY;
- int mWidth;
- Picture mPicture;
- // This can be null. When a new picture comes in, this view should be
- // invalidated to show the new picture.
- FakeWebView mFakeWebView;
}
/**
* Private class for maintaining Tabs with a main WebView and a subwindow.
*/
- public class Tab implements WebView.PictureListener {
+ public class Tab {
+ // The Geolocation permissions prompt
+ private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
+ private View mContainer;
// Main WebView
private WebView mMainView;
// Subwindow WebView
@@ -195,12 +206,103 @@
// url has not changed.
private String mOriginalUrl;
+ private ErrorConsoleView mErrorConsole;
+ // the lock icon type and previous lock icon type for the tab
+ private int mSavedLockIconType;
+ private int mSavedPrevLockIconType;
+
// Construct a new tab
- private Tab(WebView w, boolean closeOnExit, String appId, String url) {
- mMainView = w;
+ private Tab(WebView w, boolean closeOnExit, String appId, String url, Context context) {
mCloseOnExit = closeOnExit;
mAppId = appId;
mOriginalUrl = url;
+ mSavedLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
+ mSavedPrevLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
+
+ // The tab consists of a container view, which contains the main
+ // WebView, as well as any other UI elements associated with the tab.
+ LayoutInflater factory = LayoutInflater.from(context);
+ mContainer = factory.inflate(R.layout.tab, null);
+
+ mGeolocationPermissionsPrompt =
+ (GeolocationPermissionsPrompt) mContainer.findViewById(
+ R.id.geolocation_permissions_prompt);
+
+ setWebView(w);
+ }
+
+ /**
+ * Sets the WebView for this tab, correctly removing the old WebView
+ * from the container view.
+ */
+ public void setWebView(WebView w) {
+ if (mMainView == w) {
+ return;
+ }
+ // If the WebView is changing, the page will be reloaded, so any ongoing Geolocation
+ // permission requests are void.
+ mGeolocationPermissionsPrompt.hide();
+
+ // Just remove the old one.
+ FrameLayout wrapper =
+ (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
+ wrapper.removeView(mMainView);
+ mMainView = w;
+ }
+
+ /**
+ * This method attaches both the WebView and any sub window to the
+ * given content view.
+ */
+ public void attachTabToContentView(ViewGroup content) {
+ if (mMainView == null) {
+ return;
+ }
+
+ // Attach the WebView to the container and then attach the
+ // container to the content view.
+ FrameLayout wrapper =
+ (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
+ wrapper.addView(mMainView);
+ content.addView(mContainer, BrowserActivity.COVER_SCREEN_PARAMS);
+ attachSubWindow(content);
+ }
+
+ /**
+ * Remove the WebView and any sub window from the given content view.
+ */
+ public void removeTabFromContentView(ViewGroup content) {
+ if (mMainView == null) {
+ return;
+ }
+
+ // Remove the container from the content and then remove the
+ // WebView from the container. This will trigger a focus change
+ // needed by WebView.
+ FrameLayout wrapper =
+ (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
+ wrapper.removeView(mMainView);
+ content.removeView(mContainer);
+ removeSubWindow(content);
+ }
+
+ /**
+ * Attach the sub window to the content view.
+ */
+ public void attachSubWindow(ViewGroup content) {
+ if (mSubView != null) {
+ content.addView(mSubViewContainer,
+ BrowserActivity.COVER_SCREEN_PARAMS);
+ }
+ }
+
+ /**
+ * Remove the sub window from the content view.
+ */
+ public void removeSubWindow(ViewGroup content) {
+ if (mSubView != null) {
+ content.removeView(mSubViewContainer);
+ }
}
/**
@@ -226,6 +328,13 @@
}
/**
+ * @return The geolocation permissions prompt for this tab.
+ */
+ public GeolocationPermissionsPrompt getGeolocationPermissionsPrompt() {
+ return mGeolocationPermissionsPrompt;
+ }
+
+ /**
* Return the subwindow of this tab or null if there is no subwindow.
* @return The subwindow of this tab or null.
*/
@@ -234,15 +343,6 @@
}
/**
- * Return the subwindow container of this tab or null if there is no
- * subwindow.
- * @return The subwindow's container View.
- */
- public View getSubWebViewContainer() {
- return mSubViewContainer;
- }
-
- /**
* Get the url of this tab. Valid after calling populatePickerData, but
* before calling wipePickerData, or if the webview has been destroyed.
*
@@ -269,11 +369,11 @@
return null;
}
- /**
- * Returns the picker data.
- */
- public PickerData getPickerData() {
- return mPickerData;
+ public Bitmap getFavicon() {
+ if (mPickerData != null) {
+ return mPickerData.mFavicon;
+ }
+ return null;
}
private void setParentTab(Tab parent) {
@@ -336,16 +436,20 @@
return mCloseOnExit;
}
- public void onNewPicture(WebView view, Picture p) {
- if (mPickerData == null) {
- return;
- }
+ void setLockIconType(int type) {
+ mSavedLockIconType = type;
+ }
- mPickerData.mPicture = p;
- // Tell the FakeWebView to redraw.
- if (mPickerData.mFakeWebView != null) {
- mPickerData.mFakeWebView.invalidate();
- }
+ int getLockIconType() {
+ return mSavedLockIconType;
+ }
+
+ void setPrevLockIconType(int type) {
+ mSavedPrevLockIconType = type;
+ }
+
+ int getPrevLockIconType() {
+ return mSavedPrevLockIconType;
}
};
@@ -388,6 +492,28 @@
}
/**
+ * 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 +572,9 @@
* @return index of Tab or -1 if not found
*/
int getTabIndex(Tab tab) {
+ if (tab == null) {
+ return -1;
+ }
return mTabs.indexOf(tab);
}
@@ -461,8 +590,9 @@
return null;
}
final WebView w = createNewWebView();
+
// Create a new tab and add it to the tab list
- Tab t = new Tab(w, closeOnExit, appId, url);
+ Tab t = new Tab(w, closeOnExit, appId, url, mActivity);
mTabs.add(t);
// Initially put the tab in the background.
putTabInBackground(t);
@@ -499,9 +629,10 @@
// observers.
BrowserSettings.getInstance().deleteObserver(
t.mMainView.getSettings());
- // Destroy the main view and subview
- t.mMainView.destroy();
- t.mMainView = null;
+ WebView w = t.mMainView;
+ t.setWebView(null);
+ // Destroy the main view
+ w.destroy();
}
// clear it's references to parent and children
t.removeFromTree();
@@ -561,8 +692,9 @@
if (t.mMainView != null) {
dismissSubWindow(t);
s.deleteObserver(t.mMainView.getSettings());
- t.mMainView.destroy();
- t.mMainView = null;
+ WebView w = t.mMainView;
+ t.setWebView(null);
+ w.destroy();
}
}
mTabs.clear();
@@ -583,7 +715,6 @@
private static final String CURRTAB = "currentTab";
private static final String CURRURL = "currentUrl";
private static final String CURRTITLE = "currentTitle";
- private static final String CURRWIDTH = "currentWidth";
private static final String CURRPICTURE = "currentPicture";
private static final String CLOSEONEXIT = "closeonexit";
private static final String PARENTTAB = "parentTab";
@@ -634,7 +765,7 @@
} else {
// Create a new tab and don't restore the state yet, add it
// to the tab list
- Tab t = new Tab(null, false, null, null);
+ Tab t = new Tab(null, false, null, null, mActivity);
t.mSavedState = inState.getBundle(WEBVIEW + i);
if (t.mSavedState != null) {
populatePickerDataFromSavedState(t);
@@ -671,8 +802,10 @@
* WebView cache;
*/
void freeMemory() {
+ if (getTabCount() == 0) return;
+
// free the least frequently used background tab
- Tab t = getLeastUsedTab();
+ Tab t = getLeastUsedTab(getCurrentTab());
if (t != null) {
Log.w(LOGTAG, "Free a tab in the browser");
freeTab(t);
@@ -681,19 +814,20 @@
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();
}
- private Tab getLeastUsedTab() {
- // Don't do anything if we only have 1 tab.
- if (getTabCount() == 1) {
+ private Tab getLeastUsedTab(Tab current) {
+ // Don't do anything if we only have 1 tab or if the current tab is
+ // null.
+ if (getTabCount() == 1 || current == null) {
return null;
}
@@ -707,11 +841,13 @@
}
do {
t = mTabQueue.get(i++);
- } while (i < queueSize && t != null && t.mMainView == null);
+ } while (i < queueSize
+ && ((t != null && t.mMainView == null)
+ || t == current.mParentTab));
// Don't do anything if the last remaining tab is the current one or if
// the last tab has been freed already.
- if (t == getCurrentTab() || t.mMainView == null) {
+ if (t == current || t.mMainView == null) {
return null;
}
@@ -727,8 +863,9 @@
// Remove the WebView's settings from the BrowserSettings list of
// observers.
BrowserSettings.getInstance().deleteObserver(t.mMainView.getSettings());
- t.mMainView.destroy();
- t.mMainView = null;
+ WebView w = t.mMainView;
+ t.setWebView(null);
+ w.destroy();
}
/**
@@ -801,6 +938,45 @@
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.
@@ -827,7 +1003,7 @@
}
// Create a new WebView. If this tab is the current tab, we need to put
// back all the clients so force it to be the current tab.
- t.mMainView = createNewWebView();
+ t.setWebView(createNewWebView());
if (getCurrentTab() == t) {
setCurrentTab(t, true);
}
@@ -846,6 +1022,8 @@
private WebView createNewWebView() {
// Create a new WebView
WebView w = new WebView(mActivity);
+ w.setScrollbarFadingEnabled(true);
+ w.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
w.setMapTrackballToArrowKeys(false); // use trackball directly
// Enable the built-in zoom
w.getSettings().setBuiltInZoomControls(true);
@@ -865,6 +1043,48 @@
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 +1110,6 @@
mTabQueue.add(newTab);
WebView mainView;
- WebView subView;
// Display the new current tab
mCurrentTab = mTabs.indexOf(newTab);
@@ -898,19 +1117,15 @@
boolean needRestore = (mainView == null);
if (needRestore) {
// Same work as in createNewTab() except don't do new Tab()
- newTab.mMainView = mainView = createNewWebView();
+ mainView = createNewWebView();
+ newTab.setWebView(mainView);
}
- 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 +1140,9 @@
* 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);
}
}
@@ -979,15 +1180,6 @@
final WebHistoryItem item =
list != null ? list.getCurrentItem() : null;
populatePickerData(t, item);
-
- // This method is only called during the tab picker creation. At this
- // point we need to listen for new pictures since the WebView is still
- // active.
- final WebView w = t.getTopWindow();
- w.setPictureListener(t);
- // Capture the picture here instead of populatePickerData since it can
- // be called when saving the state of a tab.
- t.mPickerData.mPicture = w.capturePicture();
}
// Create the PickerData and populate it using the saved state of the tab.
@@ -1000,25 +1192,12 @@
final Bundle state = t.mSavedState;
data.mUrl = state.getString(CURRURL);
data.mTitle = state.getString(CURRTITLE);
- data.mWidth = state.getInt(CURRWIDTH, 0);
// XXX: These keys are from WebView.savePicture so if they change, this
// will break.
data.mScale = state.getFloat("scale", 1.0f);
data.mScrollX = state.getInt("scrollX", 0);
data.mScrollY = state.getInt("scrollY", 0);
- if (state.containsKey(CURRPICTURE)) {
- final File f = new File(t.mSavedState.getString(CURRPICTURE));
- try {
- final FileInputStream in = new FileInputStream(f);
- data.mPicture = Picture.createFromStream(in);
- in.close();
- } catch (Exception ex) {
- // Ignore any problems with inflating the picture. We just
- // won't draw anything.
- }
- }
-
// Set the tab's picker data.
t.mPickerData = data;
}
@@ -1030,6 +1209,7 @@
if (item != null) {
data.mUrl = item.getUrl();
data.mTitle = item.getTitle();
+ data.mFavicon = item.getFavicon();
if (data.mTitle == null) {
data.mTitle = data.mUrl;
}
@@ -1037,10 +1217,10 @@
// We want to display the top window in the tab picker but use the url
// and title of the main window.
final WebView w = t.getTopWindow();
- data.mWidth = w.getWidth();
data.mScale = w.getScale();
data.mScrollX = w.getScrollX();
data.mScrollY = w.getScrollY();
+
t.mPickerData = data;
}
@@ -1054,13 +1234,6 @@
if (t != null && t.mSavedState == null) {
t.mPickerData = null;
}
- if (t.mMainView != null) {
- // Clear the picture listeners.
- t.mMainView.setPictureListener(null);
- if (t.mSubView != null) {
- t.mSubView.setPictureListener(null);
- }
- }
}
}
@@ -1099,7 +1272,6 @@
if (data.mTitle != null) {
b.putString(CURRTITLE, data.mTitle);
}
- b.putInt(CURRWIDTH, data.mWidth);
b.putBoolean(CLOSEONEXIT, t.mCloseOnExit);
if (t.mAppId != null) {
b.putString(APPID, t.mAppId);
diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java
new file mode 100644
index 0000000..23b1ed5
--- /dev/null
+++ b/src/com/android/browser/TitleBar.java
@@ -0,0 +1,269 @@
+/*
+ * 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.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.PaintDrawable;
+import android.os.Handler;
+import android.os.Message;
+import android.util.TypedValue;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * This class represents a title bar for a particular "tab" or "window" in the
+ * browser.
+ */
+public class TitleBar extends LinearLayout {
+ private TextView mTitle;
+ private Drawable mCloseDrawable;
+ private ImageView mRtButton;
+ private Drawable mCircularProgress;
+ private ProgressBar mHorizontalProgress;
+ private ImageView mFavicon;
+ private ImageView mLockIcon;
+ private Drawable mStopDrawable;
+ private Drawable mBookmarkDrawable;
+ private boolean mInLoad;
+ private BrowserActivity mBrowserActivity;
+ private Drawable mGenericFavicon;
+ private int mIconDimension;
+ private View mTitleBg;
+ private MyHandler mHandler;
+
+ private static int LONG_PRESS = 1;
+
+ public TitleBar(BrowserActivity context) {
+ super(context, null);
+ mHandler = new MyHandler();
+ LayoutInflater factory = LayoutInflater.from(context);
+ factory.inflate(R.layout.title_bar, this);
+ mBrowserActivity = context;
+
+ mTitle = (TextView) findViewById(R.id.title);
+ mTitle.setCompoundDrawablePadding(5);
+
+ mTitleBg = findViewById(R.id.title_bg);
+ mLockIcon = (ImageView) findViewById(R.id.lock);
+ mFavicon = (ImageView) findViewById(R.id.favicon);
+
+ mRtButton = (ImageView) findViewById(R.id.rt_btn);
+ Resources resources = context.getResources();
+ mCircularProgress = (Drawable) resources.getDrawable(
+ com.android.internal.R.drawable.search_spinner);
+ mIconDimension = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, 20f,
+ resources.getDisplayMetrics());
+ mCircularProgress.setBounds(0, 0, mIconDimension, mIconDimension);
+ mHorizontalProgress = (ProgressBar) findViewById(
+ R.id.progress_horizontal);
+ mGenericFavicon = context.getResources().getDrawable(
+ R.drawable.app_web_browser_sm);
+ }
+
+ private class MyHandler extends Handler {
+ public void handleMessage(Message msg) {
+ if (msg.what == LONG_PRESS) {
+ // Prevent the normal action from happening by setting the title
+ // bar's state to false.
+ mTitleBg.setPressed(false);
+ // Need to call a special method on BrowserActivity for when the
+ // fake title bar is up, because its ViewGroup does not show a
+ // context menu.
+ mBrowserActivity.showTitleBarContextMenu();
+ }
+ }
+ };
+
+ @Override
+ protected void onCreateContextMenu(ContextMenu menu) {
+ MenuInflater inflater = mBrowserActivity.getMenuInflater();
+ inflater.inflate(R.menu.title_context, menu);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ // Make all touches hit either the textfield or the button,
+ // depending on which side of the right edge of the textfield
+ // they hit.
+ if ((int) event.getX() > mTitleBg.getRight()) {
+ mRtButton.setPressed(true);
+ } else {
+ mTitleBg.setPressed(true);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ LONG_PRESS),
+ ViewConfiguration.getLongPressTimeout());
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ int slop = ViewConfiguration.get(mBrowserActivity)
+ .getScaledTouchSlop();
+ if ((int) event.getY() > getHeight() + slop) {
+ // We only trigger the actions in ACTION_UP if one or the
+ // other is pressed. Since the user moved off the title
+ // bar, mark both as not pressed.
+ mTitleBg.setPressed(false);
+ mRtButton.setPressed(false);
+ mHandler.removeMessages(LONG_PRESS);
+ break;
+ }
+ int x = (int) event.getX();
+ int titleRight = mTitleBg.getRight();
+ if (mTitleBg.isPressed() && x > titleRight + slop) {
+ mTitleBg.setPressed(false);
+ mHandler.removeMessages(LONG_PRESS);
+ } else if (mRtButton.isPressed() && x < titleRight - slop) {
+ mRtButton.setPressed(false);
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ mRtButton.setPressed(false);
+ mTitleBg.setPressed(false);
+ mHandler.removeMessages(LONG_PRESS);
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mRtButton.isPressed()) {
+ if (mInLoad) {
+ mBrowserActivity.stopLoading();
+ } else {
+ mBrowserActivity.bookmarksOrHistoryPicker(false);
+ }
+ mRtButton.setPressed(false);
+ } else if (mTitleBg.isPressed()) {
+ mHandler.removeMessages(LONG_PRESS);
+ mBrowserActivity.onSearchRequested();
+ mTitleBg.setPressed(false);
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Return whether the associated WebView is currently loading. Needed to
+ * determine whether a click should stop the load or close the tab.
+ */
+ /* package */ boolean isInLoad() {
+ return mInLoad;
+ }
+
+ /**
+ * Set a new Bitmap for the Favicon.
+ */
+ /* package */ void setFavicon(Bitmap icon) {
+ 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);
+ mFavicon.setImageDrawable(d);
+ }
+
+ /**
+ * Set the Drawable for the lock icon, or null to hide it.
+ */
+ /* package */ void setLock(Drawable d) {
+ if (null == d) {
+ mLockIcon.setVisibility(View.GONE);
+ } else {
+ mLockIcon.setImageDrawable(d);
+ mLockIcon.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Update the progress, from 0 to 100.
+ */
+ /* package */ void setProgress(int newProgress) {
+ if (newProgress >= mHorizontalProgress.getMax()) {
+ mTitle.setCompoundDrawables(null, null, null, null);
+ ((Animatable) mCircularProgress).stop();
+ mHorizontalProgress.setVisibility(View.INVISIBLE);
+ if (mBookmarkDrawable != null) {
+ mRtButton.setImageDrawable(mBookmarkDrawable);
+ }
+ mInLoad = false;
+ } else {
+ mHorizontalProgress.setProgress(newProgress);
+ if (!mInLoad && getWindowToken() != null) {
+ // checking the window token lets us be sure that we
+ // are attached to a window before starting the animation,
+ // preventing a potential race condition
+ // (fix for bug http://b/2115736)
+ mTitle.setCompoundDrawables(null, null, mCircularProgress,
+ null);
+ ((Animatable) mCircularProgress).start();
+ mHorizontalProgress.setVisibility(View.VISIBLE);
+ if (mBookmarkDrawable == null) {
+ mBookmarkDrawable = mRtButton.getDrawable();
+ }
+ if (mStopDrawable == null) {
+ mRtButton.setImageResource(R.drawable.ic_btn_stop_v2);
+ mStopDrawable = mRtButton.getDrawable();
+ } else {
+ mRtButton.setImageDrawable(mStopDrawable);
+ }
+ mInLoad = true;
+ }
+ }
+ }
+
+ /**
+ * Update the title and url.
+ */
+ /* package */ void setTitleAndUrl(CharSequence title, CharSequence url) {
+ if (url == null) {
+ mTitle.setText(R.string.title_bar_loading);
+ } else {
+ mTitle.setText(url.toString());
+ }
+ }
+
+ /* package */ void setToTabPicker() {
+ mTitle.setText(R.string.tab_picker_title);
+ setFavicon(null);
+ setLock(null);
+ 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..3afcadc
--- /dev/null
+++ b/src/com/android/browser/WebStorageSizeManager.java
@@ -0,0 +1,406 @@
+/*
+ * 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 its estimated size. If we cannot satisfy
+ * the estimated size, then WebCore will not create the database.
+ * Quota increases are done in steps, where the increase step is
+ * min(QUOTA_INCREASE_STEP, unused_quota).
+ *
+ * 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 time of the last out of space notification
+ private static long mLastOutOfSpaceNotificationTime = -1;
+ // Delay between two notification in ms
+ private final static long NOTIFICATION_INTERVAL = 5 * 60 * 1000;
+ // Delay in ms used when resetting the notification time
+ private final static long RESET_NOTIFICATION_INTERVAL = 3 * 1000;
+ // 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 estimatedSize,
+ 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, give it the size it asked for if possible.
+ // If we cannot satisfy the estimatedSize, we should return 0 as
+ // returning a value less that what the site requested will lead
+ // to webcore not creating the database.
+ if (totalUnusedQuota >= estimatedSize) {
+ newOriginQuota = estimatedSize;
+ } else {
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG,
+ "onExceededDatabaseQuota: Unable to satisfy" +
+ " estimatedSize for the new database " +
+ " (estimatedSize: " + estimatedSize +
+ ", unused quota: " + totalUnusedQuota);
+ }
+ newOriginQuota = 0;
+ }
+ } 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);
+ }
+ }
+
+ // Reset the notification time; we use this iff the user
+ // use clear all; we reset it to some time in the future instead
+ // of just setting it to -1, as the clear all method is asynchronous
+ static void resetLastOutOfSpaceNotificationTime() {
+ mLastOutOfSpaceNotificationTime = System.currentTimeMillis() -
+ NOTIFICATION_INTERVAL + RESET_NOTIFICATION_INTERVAL;
+ }
+
+ // 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;
+ }
+ if ((mLastOutOfSpaceNotificationTime == -1) ||
+ (System.currentTimeMillis() - mLastOutOfSpaceNotificationTime > NOTIFICATION_INTERVAL)) {
+ // setup the notification boilerplate.
+ int icon = android.R.drawable.stat_sys_warning;
+ 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) {
+ mLastOutOfSpaceNotificationTime = System.currentTimeMillis();
+ mgr.notify(OUT_OF_SPACE_ID, notification);
+ }
+ }
+ }
+}
diff --git a/src/com/android/browser/WebsiteSettingsActivity.java b/src/com/android/browser/WebsiteSettingsActivity.java
new file mode 100644
index 0000000..645e084
--- /dev/null
+++ b/src/com/android/browser/WebsiteSettingsActivity.java
@@ -0,0 +1,579 @@
+/*
+ * 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.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.GeolocationPermissions;
+import android.webkit.ValueCallback;
+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 Bitmap mUsageEmptyIcon;
+ private Bitmap mUsageLowIcon;
+ private Bitmap mUsageHighIcon;
+ private Bitmap mLocationAllowedIcon;
+ private Bitmap mLocationDisallowedIcon;
+ 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);
+ mUsageEmptyIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_list_data_off);
+ mUsageLowIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_list_data_small);
+ mUsageHighIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_list_data_large);
+ mLocationAllowedIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_list_gps_on);
+ mLocationDisallowedIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_list_gps_denied);
+ askForOrigins();
+ }
+
+ /**
+ * 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 askForOrigins() {
+ // 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.
+
+ WebStorage.getInstance().getOrigins(new ValueCallback<Map>() {
+ public void onReceiveValue(Map origins) {
+ Map sites = new HashMap<String, Site>();
+ if (origins != null) {
+ Iterator<String> iter = origins.keySet().iterator();
+ while (iter.hasNext()) {
+ addFeatureToSite(sites, iter.next(), Site.FEATURE_WEB_STORAGE);
+ }
+ }
+ askForGeolocation(sites);
+ }
+ });
+ }
+
+ public void askForGeolocation(final Map sites) {
+ GeolocationPermissions.getInstance().getOrigins(new ValueCallback<Set>() {
+ public void onReceiveValue(Set origins) {
+ if (origins != null) {
+ Iterator<String> iter = origins.iterator();
+ while (iter.hasNext()) {
+ addFeatureToSite(sites, iter.next(), Site.FEATURE_GEOLOCATION);
+ }
+ }
+ populateIcons(sites);
+ populateOrigins(sites);
+ }
+ });
+ }
+
+ public void populateIcons(Map sites) {
+ // 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());
+ }
+
+ c.close();
+ }
+
+
+ public void populateOrigins(Map sites) {
+ clear();
+
+ // We can now simply populate our array with Site instances
+ Set keys = sites.keySet();
+ Iterator<String> originIter = keys.iterator();
+ while (originIter.hasNext()) {
+ String origin = originIter.next();
+ Site site = (Site) sites.get(origin);
+ add(site);
+ }
+
+ notifyDataSetChanged();
+
+ if (getCount() == 0) {
+ finish(); // we close the screen
+ }
+ }
+
+ public int getCount() {
+ if (mCurrentSite == null) {
+ return super.getCount();
+ }
+ return mCurrentSite.getFeatureCount();
+ }
+
+ public String sizeValueToString(long bytes) {
+ // We display the size in MB, to 1dp, rounding up to the next 0.1MB.
+ // bytes should always be greater than zero.
+ if (bytes <= 0) {
+ Log.e(LOGTAG, "sizeValueToString called with non-positive value");
+ return "0";
+ }
+ float megabytes = (float) bytes / (1024.0F * 1024.0F);
+ int truncated = (int) Math.ceil(megabytes * 10.0F);
+ float result = (float) (truncated / 10.0F);
+ return String.valueOf(result);
+ }
+
+ /*
+ * 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;
+ askForOrigins();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @hide
+ * Utility function
+ * Set the icon according to the usage
+ */
+ public void setIconForUsage(ImageView usageIcon, long usageInBytes) {
+ float usageInMegabytes = (float) usageInBytes / (1024.0F * 1024.0F);
+ usageIcon.setVisibility(View.VISIBLE);
+
+ // We set the correct icon:
+ // 0 < empty < 0.1MB
+ // 0.1MB < low < 5MB
+ // 5MB < high
+ if (usageInMegabytes <= 0.1) {
+ usageIcon.setImageBitmap(mUsageEmptyIcon);
+ } else if (usageInMegabytes > 0.1 && usageInMegabytes <= 5) {
+ usageIcon.setImageBitmap(mUsageLowIcon);
+ } else if (usageInMegabytes > 5) {
+ usageIcon.setImageBitmap(mUsageHighIcon);
+ }
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+ final TextView title;
+ final TextView subtitle;
+ ImageView icon;
+ final ImageView usageIcon;
+ final ImageView locationIcon;
+
+ 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);
+ usageIcon = (ImageView) view.findViewById(R.id.usage_icon);
+ locationIcon = (ImageView) view.findViewById(R.id.location_icon);
+ usageIcon.setVisibility(View.GONE);
+ locationIcon.setVisibility(View.GONE);
+
+ if (mCurrentSite == null) {
+ setTitle(getString(R.string.pref_extras_website_settings));
+
+ Site site = getItem(position);
+ title.setText(site.getPrettyTitle());
+ subtitle.setText(site.getPrettyOrigin());
+ icon.setVisibility(View.VISIBLE);
+ usageIcon.setVisibility(View.INVISIBLE);
+ locationIcon.setVisibility(View.INVISIBLE);
+ 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);
+
+ String origin = site.getOrigin();
+ if (site.hasFeature(Site.FEATURE_WEB_STORAGE)) {
+ WebStorage.getInstance().getUsageForOrigin(origin, new ValueCallback<Long>() {
+ public void onReceiveValue(Long value) {
+ if (value != null) {
+ setIconForUsage(usageIcon, value.longValue());
+ }
+ }
+ });
+ }
+
+ if (site.hasFeature(Site.FEATURE_GEOLOCATION)) {
+ locationIcon.setVisibility(View.VISIBLE);
+ GeolocationPermissions.getInstance().getAllowed(origin, new ValueCallback<Boolean>() {
+ public void onReceiveValue(Boolean allowed) {
+ if (allowed != null) {
+ if (allowed.booleanValue()) {
+ locationIcon.setImageBitmap(mLocationAllowedIcon);
+ } else {
+ locationIcon.setImageBitmap(mLocationDisallowedIcon);
+ }
+ }
+ }
+ });
+ }
+ } else {
+ setTitle(mCurrentSite.getPrettyTitle());
+ icon.setVisibility(View.GONE);
+ String origin = mCurrentSite.getOrigin();
+ switch (mCurrentSite.getFeatureByIndex(position)) {
+ case Site.FEATURE_WEB_STORAGE:
+ WebStorage.getInstance().getUsageForOrigin(origin, new ValueCallback<Long>() {
+ public void onReceiveValue(Long value) {
+ if (value != null) {
+ String usage = sizeValueToString(value.longValue()) + " " + 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);
+ GeolocationPermissions.getInstance().getAllowed(origin, new ValueCallback<Boolean>() {
+ public void onReceiveValue(Boolean allowed) {
+ if (allowed != null) {
+ if (allowed.booleanValue()) {
+ subtitle.setText(R.string.geolocation_settings_page_summary_allowed);
+ } else {
+ subtitle.setText(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;
+ askForOrigins();
+ }})
+ .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;
+ askForOrigins();
+ }})
+ .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.website_settings_row);
+ setListAdapter(mAdapter);
+ getListView().setOnItemClickListener(mAdapter);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.websitesettings, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ // If we aren't listing any sites hide the clear all button (and hence the menu).
+ return mAdapter.getCount() > 0;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.website_settings_menu_clear_all:
+ // Show the prompt to clear all origins of their data and geolocation permissions.
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.website_settings_clear_all_dialog_title)
+ .setMessage(R.string.website_settings_clear_all_dialog_message)
+ .setPositiveButton(R.string.website_settings_clear_all_dialog_ok_button,
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dlg, int which) {
+ WebStorage.getInstance().deleteAllData();
+ GeolocationPermissions.getInstance().clearAll();
+ WebStorageSizeManager.resetLastOutOfSpaceNotificationTime();
+ mAdapter.askForOrigins();
+ finish();
+ }})
+ .setNegativeButton(R.string.website_settings_clear_all_dialog_cancel_button, null)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ return true;
+ }
+ return false;
+ }
+}