summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/browser/ActiveTabsPage.java40
-rw-r--r--src/com/android/browser/AddBookmarkPage.java59
-rw-r--r--src/com/android/browser/BookmarkUtils.java168
-rw-r--r--src/com/android/browser/Bookmarks.java205
-rw-r--r--src/com/android/browser/BookmarksLoader.java70
-rw-r--r--src/com/android/browser/BrowserActivity.java1146
-rw-r--r--src/com/android/browser/BrowserBackupAgent.java2
-rw-r--r--src/com/android/browser/BrowserBookmarksAdapter.java563
-rw-r--r--src/com/android/browser/BrowserBookmarksPage.java1043
-rw-r--r--src/com/android/browser/BrowserDownloadAdapter.java3
-rw-r--r--src/com/android/browser/BrowserDownloadPage.java20
-rw-r--r--src/com/android/browser/BrowserHistoryPage.java302
-rw-r--r--src/com/android/browser/BrowserProvider.java193
-rw-r--r--src/com/android/browser/BrowserSettings.java12
-rw-r--r--src/com/android/browser/CircularProgressView.java140
-rw-r--r--src/com/android/browser/CombinedBookmarkHistoryActivity.java186
-rw-r--r--src/com/android/browser/DateSortedExpandableListAdapter.java117
-rw-r--r--src/com/android/browser/DownloadTouchIcon.java106
-rw-r--r--src/com/android/browser/FindDialog.java247
-rw-r--r--src/com/android/browser/HistoryItem.java7
-rw-r--r--src/com/android/browser/PageProgressView.java94
-rw-r--r--src/com/android/browser/SaveToHomescreenDialog.java127
-rw-r--r--src/com/android/browser/ScrollWebView.java125
-rw-r--r--src/com/android/browser/SelectDialog.java85
-rw-r--r--src/com/android/browser/ShortcutActivity.java69
-rw-r--r--src/com/android/browser/Tab.java202
-rw-r--r--src/com/android/browser/TabBar.java431
-rw-r--r--src/com/android/browser/TabControl.java135
-rw-r--r--src/com/android/browser/TabScrollView.java142
-rw-r--r--src/com/android/browser/TitleBar.java62
-rw-r--r--src/com/android/browser/TitleBarBase.java78
-rw-r--r--src/com/android/browser/TitleBarXLarge.java222
-rw-r--r--src/com/android/browser/UrlInputView.java239
-rw-r--r--src/com/android/browser/WebDialog.java79
-rw-r--r--src/com/android/browser/provider/BrowserProvider2.java1095
-rw-r--r--src/com/android/browser/provider/SQLiteContentProvider.java276
-rw-r--r--src/com/android/browser/search/.DefaultSearchEngine.java.swpbin0 -> 16384 bytes
-rw-r--r--src/com/android/browser/widget/BookmarkStackWidgetProvider.java45
-rw-r--r--src/com/android/browser/widget/BookmarkStackWidgetService.java221
39 files changed, 5756 insertions, 2600 deletions
diff --git a/src/com/android/browser/ActiveTabsPage.java b/src/com/android/browser/ActiveTabsPage.java
index 2de778712..e450a9988 100644
--- a/src/com/android/browser/ActiveTabsPage.java
+++ b/src/com/android/browser/ActiveTabsPage.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -32,6 +33,7 @@ import android.widget.ListView;
import android.widget.TextView;
public class ActiveTabsPage extends LinearLayout {
+ private static final String LOGTAG = "TabPicker";
private final BrowserActivity mBrowserActivity;
private final LayoutInflater mFactory;
private final TabControl mControl;
@@ -51,12 +53,15 @@ public class ActiveTabsPage extends LinearLayout {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
if (mControl.canCreateNewTab()) {
- position--;
+ position -= 2;
}
boolean needToAttach = false;
- if (position == -1) {
+ if (position == -2) {
// Create a new tab
mBrowserActivity.openTabToHomePage();
+ } else if (position == -1) {
+ // Create a new incognito tab
+ mBrowserActivity.openIncognitoTab();
} else {
// Open the corresponding tab
// If the tab is the current one, switchToTab will
@@ -98,7 +103,7 @@ public class ActiveTabsPage extends LinearLayout {
public int getCount() {
int count = mControl.getTabCount();
if (mControl.canCreateNewTab()) {
- count++;
+ count += 2;
}
// XXX: This is a workaround to be more like a real adapter. Most
// adapters call notifyDataSetChanged() whenever the internal data
@@ -128,23 +133,28 @@ public class ActiveTabsPage extends LinearLayout {
}
public int getItemViewType(int position) {
if (mControl.canCreateNewTab()) {
- position--;
+ position -= 2;
}
// Do not recycle the "add new tab" item.
- return position == -1 ? IGNORE_ITEM_VIEW_TYPE : 1;
+ return position < 0 ? IGNORE_ITEM_VIEW_TYPE : 1;
}
public View getView(int position, View convertView, ViewGroup parent) {
final int tabCount = mControl.getTabCount();
if (mControl.canCreateNewTab()) {
- position--;
+ position -= 2;
}
if (convertView == null) {
- convertView = mFactory.inflate(position == -1 ?
- R.layout.tab_view_add_tab : R.layout.tab_view, null);
+ if (position == -2) {
+ convertView = mFactory.inflate(R.layout.tab_view_add_tab, null);
+ } else if (position == -1) {
+ convertView = mFactory.inflate(R.layout.tab_view_add_incognito_tab, null);
+ } else {
+ convertView = mFactory.inflate(R.layout.tab_view, null);
+ }
}
- if (position != -1) {
+ if (position >= 0) {
TextView title =
(TextView) convertView.findViewById(R.id.title);
TextView url = (TextView) convertView.findViewById(R.id.url);
@@ -152,7 +162,19 @@ public class ActiveTabsPage extends LinearLayout {
(ImageView) convertView.findViewById(R.id.favicon);
View close = convertView.findViewById(R.id.close);
Tab tab = mControl.getTab(position);
+ if (tab.getWebView() == null) {
+ // This means that populatePickerData will have to use the
+ // saved state.
+ Log.w(LOGTAG, "Tab " + position + " has a null WebView and "
+ + (tab.getSavedState() == null ? "null" : "non-null")
+ + " saved state ");
+ }
tab.populatePickerData();
+ if (tab.getTitle() == null || tab.getTitle().length() == 0) {
+ Log.w(LOGTAG, "Tab " + position + " has no title. "
+ + "Check above in the Logs to see whether it has a "
+ + "null WebView or null WebHistoryItem");
+ }
title.setText(tab.getTitle());
url.setText(tab.getUrl());
Bitmap icon = tab.getFavicon();
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index 1104d5e25..1d6edc5cc 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -51,6 +51,7 @@ public class AddBookmarkPage extends Activity {
private String mTouchIconUrl;
private Bitmap mThumbnail;
private String mOriginalUrl;
+ private boolean mIsUrlEditable = true;
// Message IDs
private static final int SAVE_BOOKMARK = 100;
@@ -74,13 +75,24 @@ public class AddBookmarkPage extends Activity {
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_LEFT_ICON);
- setContentView(R.layout.browser_add_bookmark);
+
+ mMap = getIntent().getExtras();
+ if (mMap != null) {
+ mIsUrlEditable = mMap.getBoolean("url_editable", true);
+ }
+
+ if (mIsUrlEditable) {
+ setContentView(R.layout.browser_add_bookmark);
+ } else {
+ setContentView(R.layout.browser_add_bookmark_const_url);
+ }
+
setTitle(R.string.save_to_bookmarks);
getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.ic_list_bookmark);
String title = null;
String url = null;
- mMap = getIntent().getExtras();
+
if (mMap != null) {
Bundle b = mMap.getBundle("bookmark");
if (b != null) {
@@ -96,8 +108,11 @@ public class AddBookmarkPage extends Activity {
mTitle = (EditText) findViewById(R.id.title);
mTitle.setText(title);
- mAddress = (EditText) findViewById(R.id.address);
- mAddress.setText(url);
+
+ if (mIsUrlEditable) {
+ mAddress = (EditText) findViewById(R.id.address);
+ mAddress.setText(url);
+ }
View.OnClickListener accept = mSaveBookmark;
mButton = (TextView) findViewById(R.id.OK);
@@ -133,9 +148,9 @@ public class AddBookmarkPage extends Activity {
// Save to the bookmarks DB.
try {
final ContentResolver cr = getContentResolver();
- Bookmarks.addBookmark(null, cr, url, title, thumbnail, true);
+ Bookmarks.addBookmark(AddBookmarkPage.this, false, url, title, thumbnail, true);
if (touchIconUrl != null) {
- new DownloadTouchIcon(cr, url).execute(mTouchIconUrl);
+ new DownloadTouchIcon(AddBookmarkPage.this, cr, url).execute(mTouchIconUrl);
}
mMessage.arg1 = 1;
} catch (IllegalStateException e) {
@@ -173,8 +188,14 @@ public class AddBookmarkPage extends Activity {
createHandler();
String title = mTitle.getText().toString().trim();
- String unfilteredUrl =
- BrowserActivity.fixUrl(mAddress.getText().toString());
+ String unfilteredUrl;
+ if (mIsUrlEditable) {
+ unfilteredUrl =
+ BrowserActivity.fixUrl(mAddress.getText().toString());
+ } else {
+ unfilteredUrl = mOriginalUrl;
+ }
+
boolean emptyTitle = title.length() == 0;
boolean emptyUrl = unfilteredUrl.trim().length() == 0;
Resources r = getResources();
@@ -183,9 +204,15 @@ public class AddBookmarkPage extends Activity {
mTitle.setError(r.getText(R.string.bookmark_needs_title));
}
if (emptyUrl) {
- mAddress.setError(r.getText(R.string.bookmark_needs_url));
+ if (mIsUrlEditable) {
+ mAddress.setError(r.getText(R.string.bookmark_needs_url));
+ } else {
+ Toast.makeText(AddBookmarkPage.this, R.string.bookmark_needs_url,
+ Toast.LENGTH_LONG).show();
+ }
}
return false;
+
}
String url = unfilteredUrl.trim();
try {
@@ -200,7 +227,12 @@ public class AddBookmarkPage extends Activity {
// can't save their bookmark. If it was null, we'll assume
// they meant http when we parse it in the WebAddress class.
if (scheme != null) {
- mAddress.setError(r.getText(R.string.bookmark_cannot_save_url));
+ if (mIsUrlEditable) {
+ mAddress.setError(r.getText(R.string.bookmark_cannot_save_url));
+ } else {
+ Toast.makeText(AddBookmarkPage.this, R.string.bookmark_cannot_save_url,
+ Toast.LENGTH_LONG).show();
+ }
return false;
}
WebAddress address;
@@ -216,7 +248,12 @@ public class AddBookmarkPage extends Activity {
}
}
} catch (URISyntaxException e) {
- mAddress.setError(r.getText(R.string.bookmark_url_not_valid));
+ if (mIsUrlEditable) {
+ mAddress.setError(r.getText(R.string.bookmark_url_not_valid));
+ } else {
+ Toast.makeText(AddBookmarkPage.this, R.string.bookmark_url_not_valid,
+ Toast.LENGTH_LONG).show();
+ }
return false;
}
diff --git a/src/com/android/browser/BookmarkUtils.java b/src/com/android/browser/BookmarkUtils.java
new file mode 100644
index 000000000..0fdad153f
--- /dev/null
+++ b/src/com/android/browser/BookmarkUtils.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2010 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.Intent;
+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.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.provider.Browser;
+import android.util.Log;
+
+class BookmarkUtils {
+ private final static String LOGTAG = "BookmarkUtils";
+
+ // 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";
+
+ enum BookmarkIconType {
+ ICON_INSTALLABLE_WEB_APP, // Icon for an installable web app (launches WebAppRuntime).
+ ICON_HOME_SHORTCUT // Icon for a shortcut on the home screen (launches Browser).
+ };
+
+ /**
+ * Creates an icon to be associated with this bookmark. If available, the apple touch icon
+ * will be used, else we draw our own depending on the type of "bookmark" being created.
+ */
+ static Bitmap createIcon(Context context, Bitmap touchIcon, Bitmap favicon,
+ BookmarkIconType type) {
+ int iconDimension = context.getResources().getDimensionPixelSize(
+ android.R.dimen.app_icon_size);
+
+ Bitmap bm = Bitmap.createBitmap(iconDimension, iconDimension, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bm);
+ Rect iconBounds = new Rect(0, 0, bm.getWidth(), bm.getHeight());
+
+ // Use the apple-touch-icon if available
+ if (touchIcon != null) {
+ drawTouchIconToCanvas(touchIcon, canvas, iconBounds);
+ } else {
+ // No touch icon so create our own.
+ // Set the background based on the type of shortcut (either webapp or home shortcut).
+ Bitmap icon = getIconBackground(context, type);
+
+ if (icon != null) {
+ // Now draw the correct icon background into our new bitmap.
+ canvas.drawBitmap(icon, null, iconBounds, null);
+ }
+
+ // If we have a favicon, overlay it in a nice rounded white box on top of the
+ // background.
+ if (favicon != null) {
+ drawFaviconToCanvas(favicon, canvas, iconBounds,
+ context.getResources().getDisplayMetrics().density);
+ }
+ }
+ return bm;
+ }
+
+ /**
+ * Convenience method for creating an intent that will add a shortcut to the home screen.
+ */
+ static Intent createAddToHomeIntent(Context context, String url, String title,
+ Bitmap touchIcon, Bitmap favicon) {
+ Intent i = new Intent(INSTALL_SHORTCUT);
+ Intent shortcutIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ long urlHash = url.hashCode();
+ long uniqueId = (urlHash << 32) | shortcutIntent.hashCode();
+ shortcutIntent.putExtra(Browser.EXTRA_APPLICATION_ID, Long.toString(uniqueId));
+ i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
+ i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
+ i.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(context, touchIcon, favicon,
+ BookmarkIconType.ICON_HOME_SHORTCUT));
+
+ // Do not allow duplicate items
+ i.putExtra("duplicate", false);
+ return i;
+ }
+
+ private static Bitmap getIconBackground(Context context, BookmarkIconType type) {
+ if (type == BookmarkIconType.ICON_HOME_SHORTCUT) {
+ // Want to create a shortcut icon on the homescreen, so the icon
+ // background is the red bookmark.
+ return BitmapFactory.decodeResource(context.getResources(),
+ R.drawable.ic_launcher_shortcut_browser_bookmark);
+ } else if (type == BookmarkIconType.ICON_INSTALLABLE_WEB_APP) {
+ // Use the web browser icon as the background for the icon for an installable
+ // web app.
+ return BitmapFactory.decodeResource(context.getResources(),
+ R.drawable.ic_launcher_browser);
+ }
+ return null;
+ }
+
+ private static void drawTouchIconToCanvas(Bitmap touchIcon, Canvas canvas, Rect iconBounds) {
+ Rect src = new Rect(0, 0, touchIcon.getWidth(), touchIcon.getHeight());
+
+ // Paint used for scaling the bitmap and drawing the rounded rect.
+ Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setFilterBitmap(true);
+ canvas.drawBitmap(touchIcon, src, iconBounds, paint);
+
+ // 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(iconBounds);
+ rect.inset(1, 1);
+ path.addRoundRect(rect, 8f, 8f, Path.Direction.CW);
+
+ // Reuse the paint and clear the outside of the rectangle.
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ canvas.drawPath(path, paint);
+ }
+
+ private static void drawFaviconToCanvas(Bitmap favicon, Canvas canvas, Rect iconBounds,
+ float density) {
+ // 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 * density; // 16x16 favicon
+ final float padding = 2 * density; // white padding around icon
+ final float rectSize = iconSize + 2 * padding;
+ final float x = iconBounds.exactCenterX() - (rectSize / 2);
+ // Note: Subtract 2 dip from the y position since the box is
+ // slightly higher than center. Use padding since it is already
+ // 2 * density.
+ final float y = iconBounds.exactCenterY() - (rectSize / 2) - padding;
+ RectF r = new RectF(x, y, x + 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);
+ }
+
+};
diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java
index 5ada9dcb8..0bccbed97 100644
--- a/src/com/android/browser/Bookmarks.java
+++ b/src/com/android/browser/Bookmarks.java
@@ -20,16 +20,20 @@ import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
-import android.provider.Browser;
+import android.os.AsyncTask;
+import android.preference.PreferenceManager;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Combined;
+import android.provider.BrowserContract.Images;
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.
@@ -61,72 +65,23 @@ import java.util.Date;
* 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) {
+ /* package */ static void addBookmark(Context context, boolean showToast, String url,
+ String name, Bitmap thumbnail, boolean retainIcon) {
// Want to append to the beginning of the list
- long creationTime = new Date().getTime();
- ContentValues map = new ContentValues();
+ ContentValues values = new ContentValues();
Cursor cursor = null;
try {
- cursor = Browser.getVisitedLike(cr, url);
- 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);
- }
- }
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String accountType = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_TYPE, null);
+ String accountName = prefs.getString(BrowserBookmarksPage.PREF_ACCOUNT_NAME, null);
+ values.put(BrowserContract.Bookmarks.ACCOUNT_TYPE, accountType);
+ values.put(BrowserContract.Bookmarks.ACCOUNT_NAME, accountName);
+ values.put(BrowserContract.Bookmarks.TITLE, name);
+ values.put(BrowserContract.Bookmarks.URL, url);
+ values.put(BrowserContract.Bookmarks.IS_FOLDER, 0);
+ values.put(BrowserContract.Bookmarks.THUMBNAIL,
+ bitmapToBytes(thumbnail));
+ context.getContentResolver().insert(BrowserContract.Bookmarks.CONTENT_URI, values);
} catch (IllegalStateException e) {
Log.e(LOGTAG, "addBookmark", e);
} finally {
@@ -135,7 +90,7 @@ import java.util.Date;
if (retainIcon) {
WebIconDatabase.getInstance().retainIconForPageUrl(url);
}
- if (context != null) {
+ if (showToast) {
Toast.makeText(context, R.string.added_to_bookmarks,
Toast.LENGTH_LONG).show();
}
@@ -155,40 +110,26 @@ import java.util.Date;
ContentResolver cr, String url, String title) {
Cursor cursor = null;
try {
- cursor = cr.query(
- Browser.BOOKMARKS_URI,
- Browser.HISTORY_PROJECTION,
- "url = ? AND title = ?",
+ cursor = cr.query(BrowserContract.Bookmarks.CONTENT_URI,
+ new String[] { BrowserContract.Bookmarks._ID },
+ BrowserContract.Bookmarks.URL + " = ? AND " +
+ BrowserContract.Bookmarks.TITLE + " = ?",
new String[] { url, title },
null);
- boolean first = cursor.moveToFirst();
+
// Should be in the database no matter what
- if (!first) {
+ if (!cursor.moveToFirst()) {
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!");
- }
- }
+ Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI,
+ cursor.getLong(0));
+ cr.delete(uri, null, null);
if (context != null) {
- Toast.makeText(context, R.string.removed_from_bookmarks,
- Toast.LENGTH_LONG).show();
+ Toast.makeText(context, R.string.removed_from_bookmarks, Toast.LENGTH_LONG).show();
}
} catch (IllegalStateException e) {
Log.e(LOGTAG, "removeFromBookmarks", e);
@@ -219,4 +160,86 @@ import java.util.Date;
}
return false;
}
+
+ static final String QUERY_BOOKMARKS_WHERE =
+ Combined.URL + " == ? OR " +
+ Combined.URL + " == ? OR " +
+ Combined.URL + " LIKE ? || '%' OR " +
+ Combined.URL + " LIKE ? || '%'";
+
+ /* package */ static Cursor queryCombinedForUrl(ContentResolver cr,
+ String originalUrl, String url) {
+ if (cr == null || url == null) {
+ return null;
+ }
+
+ // If originalUrl is null, just set it to url.
+ if (originalUrl == null) {
+ originalUrl = url;
+ }
+
+ // 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 + '?';
+
+ // 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 };
+ final String[] projection = new String[] { Combined.URL };
+ return cr.query(Combined.CONTENT_URI, projection, QUERY_BOOKMARKS_WHERE, selArgs, null);
+ }
+
+ // Strip the query from the given url.
+ 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;
+ }
+
+ /**
+ * 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 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 updateFavicon(final ContentResolver cr,
+ final String originalUrl, final String url, final Bitmap favicon) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ Cursor cursor = queryCombinedForUrl(cr, originalUrl, url);
+ try {
+ if (cursor.moveToFirst()) {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
+
+ ContentValues values = new ContentValues();
+ values.put(Images.FAVICON, os.toByteArray());
+ values.put(Images.URL, cursor.getString(0));
+
+ do {
+ cr.update(Images.CONTENT_URI, values, null, null);
+ } while (cursor.moveToNext());
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return null;
+ }
+ }.execute();
+ }
}
diff --git a/src/com/android/browser/BookmarksLoader.java b/src/com/android/browser/BookmarksLoader.java
new file mode 100644
index 000000000..094718484
--- /dev/null
+++ b/src/com/android/browser/BookmarksLoader.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 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.CursorLoader;
+import android.net.Uri;
+import android.provider.BrowserContract.Bookmarks;
+import android.text.TextUtils;
+
+public class BookmarksLoader extends CursorLoader {
+ public static final String ARG_ACCOUNT_TYPE = "acct_type";
+ public static final String ARG_ACCOUNT_NAME = "acct_name";
+
+ public static final int COLUMN_INDEX_ID = 0;
+ public static final int COLUMN_INDEX_URL = 1;
+ public static final int COLUMN_INDEX_TITLE = 2;
+ public static final int COLUMN_INDEX_FAVICON = 3;
+ public static final int COLUMN_INDEX_THUMBNAIL = 4;
+ public static final int COLUMN_INDEX_TOUCH_ICON = 5;
+ public static final int COLUMN_INDEX_IS_FOLDER = 6;
+
+ public static final String[] PROJECTION = new String[] {
+ Bookmarks._ID, // 0
+ Bookmarks.URL, // 1
+ Bookmarks.TITLE, // 2
+ Bookmarks.FAVICON, // 3
+ Bookmarks.THUMBNAIL, // 4
+ Bookmarks.TOUCH_ICON, // 5
+ Bookmarks.IS_FOLDER, // 6
+ Bookmarks.POSITION, // 7
+ };
+
+ private String mAccountType;
+ private String mAccountName;
+
+ public BookmarksLoader(Context context, String accountType, String accountName) {
+ super(context, addAccount(Bookmarks.CONTENT_URI_DEFAULT_FOLDER, accountType, accountName),
+ PROJECTION, null, null, null);
+ mAccountType = accountType;
+ mAccountName = accountName;
+ }
+
+ @Override
+ public void setUri(Uri uri) {
+ super.setUri(addAccount(uri, mAccountType, mAccountName));
+ }
+
+ private static Uri addAccount(Uri uri, String accountType, String accountName) {
+ if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
+ return uri.buildUpon().appendQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE, accountType).
+ appendQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME, accountName).build();
+ }
+ return uri;
+ }
+}
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 098e3b2f4..c3d3d9c95 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -16,12 +16,20 @@
package com.android.browser;
+import com.android.browser.ScrollWebView.ScrollListener;
+import com.android.browser.search.SearchEngine;
+import com.android.common.Search;
+import com.android.common.speech.LoggingEvents;
+
+import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.Dialog;
import android.app.ProgressDialog;
import android.app.SearchManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
+import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentProviderClient;
@@ -32,19 +40,16 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
-import android.database.DatabaseUtils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Picture;
import android.graphics.PixelFormat;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
@@ -60,38 +65,39 @@ import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.provider.Browser;
+import android.provider.BrowserContract;
import android.provider.ContactsContract;
+import android.provider.BrowserContract.Images;
import android.provider.ContactsContract.Intents.Insert;
import android.provider.Downloads;
import android.provider.MediaStore;
import android.speech.RecognizerResultsIntent;
-import android.text.IClipboard;
import android.text.TextUtils;
import android.text.format.DateFormat;
-import android.util.AttributeSet;
import android.util.Log;
import android.util.Patterns;
+import android.view.ActionMode;
import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.accessibility.AccessibilityManager;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.DownloadListener;
import android.webkit.HttpAuthHandler;
-import android.webkit.PluginManager;
import android.webkit.SslErrorHandler;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;
@@ -102,41 +108,30 @@ import android.webkit.WebView;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
-import android.accounts.AccountManagerCallback;
-
-import com.android.browser.search.SearchEngine;
-import com.android.common.Search;
-import com.android.common.speech.LoggingEvents;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
-import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
-import java.text.ParseException;
+import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
import java.util.Map;
-import java.util.Set;
+import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class BrowserActivity extends Activity
- implements View.OnCreateContextMenuListener, DownloadListener {
+ implements View.OnCreateContextMenuListener, DownloadListener,
+ PopupMenu.OnMenuItemClickListener {
/* Define some aliases to make these debugging flags easier to refer to.
* This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG".
@@ -172,6 +167,11 @@ public class BrowserActivity extends Activity
*/
private FrameLayout mBrowserFrameLayout;
+ private boolean mXLargeScreenSize;
+
+ private Boolean mIsProviderPresent = null;
+ private Uri mRlzUri = null;
+
@Override
public void onCreate(Bundle icicle) {
if (LOGV_ENABLED) {
@@ -187,7 +187,11 @@ public class BrowserActivity extends Activity
BitmapFactory.setDefaultConfig(Bitmap.Config.ARGB_8888);
}
- setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+ if (AccessibilityManager.getInstance(this).isEnabled()) {
+ setDefaultKeyMode(DEFAULT_KEYS_DISABLE);
+ } else {
+ setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+ }
mResolver = getContentResolver();
@@ -203,6 +207,13 @@ public class BrowserActivity extends Activity
mMixLockIcon = Resources.getSystem().getDrawable(
android.R.drawable.ic_partial_secure);
+ // Create the tab control and our initial tab
+ mTabControl = new TabControl(this);
+
+ mXLargeScreenSize = (getResources().getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK)
+ == Configuration.SCREENLAYOUT_SIZE_XLARGE;
+
FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
.findViewById(com.android.internal.R.id.content);
mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(this)
@@ -214,13 +225,25 @@ public class BrowserActivity extends Activity
mCustomViewContainer = (FrameLayout) mBrowserFrameLayout
.findViewById(R.id.fullscreen_custom_content);
frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
- mTitleBar = new TitleBar(this);
- // mTitleBar will be always shown in the fully loaded mode
- mTitleBar.setProgress(100);
- mFakeTitleBar = new TitleBar(this);
- // Create the tab control and our initial tab
- mTabControl = new TabControl(this);
+ if (mXLargeScreenSize) {
+ mTitleBar = new TitleBarXLarge(this);
+ mTitleBar.setProgress(100);
+ mFakeTitleBar = new TitleBarXLarge(this);
+ ActionBar actionBar = getActionBar();
+ actionBar.setBackgroundDrawable(getResources().
+ getDrawable(R.drawable.tabbar_bg));
+ mTabBar = new TabBar(this, mTabControl, (TitleBarXLarge) mFakeTitleBar);
+ actionBar.setCustomNavigationMode(mTabBar);
+ // disable built in zoom controls
+ mTabControl.setDisplayZoomControls(false);
+ } else {
+ mTitleBar = new TitleBar(this);
+ // mTitleBar will be always be shown in the fully loaded mode on
+ // phone
+ mTitleBar.setProgress(100);
+ mFakeTitleBar = new TitleBar(this);
+ }
// Open the icon database and retain all the bookmark urls for favicons
retainIconsOnStartup();
@@ -263,59 +286,19 @@ public class BrowserActivity extends Activity
}
};
- IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addDataScheme("package");
- mPackageInstallationReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final String packageName = intent.getData()
- .getSchemeSpecificPart();
- final boolean replacing = intent.getBooleanExtra(
- Intent.EXTRA_REPLACING, false);
- if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
- // if it is replacing, refreshPlugins() when adding
- return;
- }
+ // Unless the last browser usage was within 24 hours, destroy any
+ // remaining incognito tabs.
- if (sGoogleApps.contains(packageName)) {
- BrowserActivity.this.packageChanged(packageName,
- Intent.ACTION_PACKAGE_ADDED.equals(action));
- }
+ Calendar lastActiveDate = icicle != null ? (Calendar) icicle.getSerializable("lastActiveDate") : null;
+ Calendar today = Calendar.getInstance();
+ Calendar yesterday = Calendar.getInstance();
+ yesterday.add(Calendar.DATE, -1);
- 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);
+ boolean dontRestoreIncognitoTabs = lastActiveDate == null
+ || lastActiveDate.before(yesterday)
+ || lastActiveDate.after(today);
- if (!mTabControl.restoreState(icicle)) {
+ if (!mTabControl.restoreState(icicle, dontRestoreIncognitoTabs)) {
// 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(
@@ -323,6 +306,8 @@ public class BrowserActivity extends Activity
// 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();
+ // remove any incognito files
+ WebView.cleanupPrivateBrowsingFiles(this);
final Intent intent = getIntent();
final Bundle extra = intent.getExtras();
// Create an initial tab.
@@ -337,7 +322,8 @@ public class BrowserActivity extends Activity
intent.getData() != null)
|| RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
.equals(action),
- intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
+ intent.getStringExtra(Browser.EXTRA_APPLICATION_ID),
+ urlData.mUrl, false);
mTabControl.setCurrentTab(t);
attachTabToContentView(t);
WebView webView = t.getWebView();
@@ -354,6 +340,10 @@ public class BrowserActivity extends Activity
loadUrlDataIn(t, urlData);
}
} else {
+ if (dontRestoreIncognitoTabs) {
+ WebView.cleanupPrivateBrowsingFiles(this);
+ }
+
// TabControl.restoreState() will create a new tab even if
// restoring the state fails.
attachTabToContentView(mTabControl.getCurrentTab());
@@ -373,8 +363,6 @@ public class BrowserActivity extends Activity
if (jsFlags.trim().length() != 0) {
mTabControl.getCurrentWebView().setJsFlags(jsFlags);
}
- // Work out which packages are installed on the system.
- getInstalledPackages();
// Start watching the default geolocation permissions
mSystemAllowGeolocationOrigins
@@ -382,6 +370,10 @@ public class BrowserActivity extends Activity
mSystemAllowGeolocationOrigins.start();
}
+ ScrollListener getScrollListener() {
+ return mTabBar;
+ }
+
/**
* Feed the previously stored results strings to the BrowserProvider so that
* the SearchDialog will show them instead of the standard searches.
@@ -621,13 +613,16 @@ public class BrowserActivity extends Activity
final ContentResolver cr = mResolver;
final String newUrl = url;
- new AsyncTask<Void, Void, Void>() {
- protected Void doInBackground(Void... unused) {
- Browser.updateVisitedHistory(cr, newUrl, false);
- Browser.addSearchUrl(cr, newUrl);
- return null;
- }
- }.execute();
+ if (mTabControl == null || !mTabControl.getCurrentWebView().isPrivateBrowsingEnabled()) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ Browser.updateVisitedHistory(cr, newUrl, false);
+ Browser.addSearchUrl(cr, newUrl);
+ return null;
+ }
+ }.execute();
+ }
SearchEngine searchEngine = mSettings.getSearchEngine();
if (searchEngine == null) return false;
@@ -674,12 +669,17 @@ public class BrowserActivity extends Activity
url = smartUrlFilter(url);
final ContentResolver cr = mResolver;
final String newUrl = url;
- new AsyncTask<Void, Void, Void>() {
- protected Void doInBackground(Void... unused) {
- Browser.updateVisitedHistory(cr, newUrl, false);
- return null;
- }
- }.execute();
+ if (mTabControl == null
+ || mTabControl.getCurrentWebView() == null
+ || !mTabControl.getCurrentWebView().isPrivateBrowsingEnabled()) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ Browser.updateVisitedHistory(cr, newUrl, false);
+ return null;
+ }
+ }.execute();
+ }
String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
if (url.contains(searchSource)) {
String source = null;
@@ -699,16 +699,14 @@ public class BrowserActivity extends Activity
}
/* package */ void showVoiceTitleBar(String title) {
mTitleBar.setInVoiceMode(true);
- mFakeTitleBar.setInVoiceMode(true);
-
mTitleBar.setDisplayTitle(title);
+ mFakeTitleBar.setInVoiceMode(true);
mFakeTitleBar.setDisplayTitle(title);
}
/* package */ void revertVoiceTitleBar() {
mTitleBar.setInVoiceMode(false);
- mFakeTitleBar.setInVoiceMode(false);
-
mTitleBar.setDisplayTitle(mUrl);
+ mFakeTitleBar.setInVoiceMode(false);
mFakeTitleBar.setDisplayTitle(mUrl);
}
/* package */ static String fixUrl(String inUrl) {
@@ -772,7 +770,7 @@ public class BrowserActivity extends Activity
* would change its appearance, use a different TitleBar to show overlayed
* at the top of the screen, when the menu is open or the page is loading.
*/
- private TitleBar mFakeTitleBar;
+ private TitleBarBase mFakeTitleBar;
/**
* Keeps track of whether the options menu is open. This is important in
@@ -828,9 +826,8 @@ public class BrowserActivity extends Activity
return true;
}
- private void showFakeTitleBar() {
- if (mFakeTitleBar.getParent() == null && mActiveTabsPage == null
- && !mActivityInPause) {
+ void showFakeTitleBar() {
+ if (!isFakeTitleBarShowing() && mActiveTabsPage == null && !mActivityInPause) {
WebView mainView = mTabControl.getCurrentWebView();
// if there is no current WebView, don't show the faked title bar;
if (mainView == null) {
@@ -838,28 +835,31 @@ public class BrowserActivity extends Activity
}
// Do not need to check for null, since the current tab will have
// at least a main WebView, or we would have returned above.
- if (dialogIsUp()) {
- // Do not show the fake title bar, which would cover up the
- // find or select dialog.
+ if (isInCustomActionMode()) {
+ // Do not show the fake title bar, while a custom ActionMode
+ // (i.e. find or select) is showing.
return;
}
-
- 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.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
- params.gravity = Gravity.TOP;
- boolean atTop = mainView.getScrollY() == 0;
- params.windowAnimations = atTop ? 0 : R.style.TitleBar;
- manager.addView(mFakeTitleBar, params);
+ if (mXLargeScreenSize) {
+ mContentView.addView(mFakeTitleBar);
+ mTabBar.onShowTitleBar();
+ } else {
+ 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.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ params.gravity = Gravity.TOP;
+ boolean atTop = mainView.getScrollY() == 0;
+ params.windowAnimations = atTop ? 0 : R.style.TitleBar;
+ manager.addView(mFakeTitleBar, params);
+ }
}
}
@@ -876,21 +876,34 @@ public class BrowserActivity extends Activity
}
}
- private void hideFakeTitleBar() {
- if (mFakeTitleBar.getParent() == null) return;
- WindowManager.LayoutParams params = (WindowManager.LayoutParams)
- mFakeTitleBar.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(mFakeTitleBar, params);
- manager.removeView(mFakeTitleBar);
+ void stopScrolling() {
+ ((ScrollWebView) mTabControl.getCurrentWebView()).stopScroll();
+ }
+
+ void hideFakeTitleBar() {
+ if (!isFakeTitleBarShowing()) return;
+ if (mXLargeScreenSize) {
+ mContentView.removeView(mFakeTitleBar);
+ mTabBar.onHideTitleBar();
+ } else {
+ WindowManager.LayoutParams params =
+ (WindowManager.LayoutParams) mFakeTitleBar.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(mFakeTitleBar, params);
+ manager.removeView(mFakeTitleBar);
+ }
+ }
+
+ boolean isFakeTitleBarShowing() {
+ return (mFakeTitleBar.getParent() != null);
}
/**
@@ -931,6 +944,9 @@ public class BrowserActivity extends Activity
// Save all the tabs
mTabControl.saveState(outState);
+
+ // Save time so that we know how old incognito tabs (if any) are.
+ outState.putSerializable("lastActiveDate", Calendar.getInstance());
}
@Override
@@ -992,8 +1008,6 @@ public class BrowserActivity extends Activity
mTabControl.destroy();
WebIconDatabase.getInstance().close();
- unregisterReceiver(mPackageInstallationReceiver);
-
// Stop watching the default geolocation permissions
mSystemAllowGeolocationOrigins.stop();
mSystemAllowGeolocationOrigins = null;
@@ -1122,12 +1136,14 @@ public class BrowserActivity extends Activity
if (mMenu == null) {
return;
}
+ MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
MenuItem src = mInLoad ?
mMenu.findItem(R.id.stop_menu_id):
- mMenu.findItem(R.id.reload_menu_id);
- MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
- dest.setIcon(src.getIcon());
- dest.setTitle(src.getTitle());
+ mMenu.findItem(R.id.reload_menu_id);
+ if (src != null) {
+ dest.setIcon(src.getIcon());
+ dest.setTitle(src.getTitle());
+ }
}
@Override
@@ -1154,7 +1170,6 @@ public class BrowserActivity extends Activity
break;
// -- Browser context menu
case R.id.open_context_menu_id:
- case R.id.open_newtab_context_menu_id:
case R.id.bookmark_context_menu_id:
case R.id.save_link_context_menu_id:
case R.id.share_link_context_menu_id:
@@ -1278,6 +1293,7 @@ public class BrowserActivity extends Activity
*/
/* package */ void removeActiveTabPage(boolean needToAttach) {
mContentView.removeView(mActiveTabsPage);
+ mTitleBar.setVisibility(View.VISIBLE);
mActiveTabsPage = null;
mMenuState = R.id.MAIN_MENU;
if (needToAttach) {
@@ -1286,23 +1302,22 @@ public class BrowserActivity extends Activity
getTopWindow().requestFocus();
}
- private WebView showDialog(WebDialog dialog) {
- Tab tab = mTabControl.getCurrentTab();
- if (tab.getSubWebView() == null) {
- // If the find or select is being performed on the main webview,
- // remove the embedded title bar.
- WebView mainView = tab.getWebView();
- if (mainView != null) {
- mainView.setEmbeddedTitleBar(null);
- }
- }
+ @Override
+ public ActionMode onStartActionMode(ActionMode.Callback callback) {
+ mActionMode = super.onStartActionMode(callback);
hideFakeTitleBar();
- mMenuState = EMPTY_MENU;
- return tab.showDialog(dialog);
+ // Would like to change the MENU, but onEndActionMode may not be called
+ return mActionMode;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
+ // check the action bar button before mCanChord check, as the prepare call
+ // doesn't come for action bar buttons
+ if (item.getItemId() == R.id.newtab) {
+ openTabToHomePage();
+ return true;
+ }
if (!mCanChord) {
// The user has already fired a shortcut with this hold down of the
// menu key.
@@ -1324,17 +1339,22 @@ public class BrowserActivity extends Activity
openTabToHomePage();
break;
+ case R.id.incognito_menu_id:
+ openIncognitoTab();
+ break;
+
case R.id.goto_menu_id:
editUrl();
break;
case R.id.bookmarks_menu_id:
- bookmarksOrHistoryPicker(false);
+ bookmarksOrHistoryPicker(false, false);
break;
case R.id.active_tabs_menu_id:
mActiveTabsPage = new ActiveTabsPage(this, mTabControl);
removeTabFromContentView(mTabControl.getCurrentTab());
+ mTitleBar.setVisibility(View.GONE);
hideFakeTitleBar();
mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS);
mActiveTabsPage.requestFocus();
@@ -1342,14 +1362,7 @@ public class BrowserActivity extends Activity
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);
+ bookmarkCurrentPage();
break;
case R.id.stop_reload_menu_id:
@@ -1394,17 +1407,39 @@ public class BrowserActivity extends Activity
break;
case R.id.find_menu_id:
- showFindDialog();
+ getTopWindow().showFindDialog(null);
break;
- case R.id.select_text_id:
- if (true) {
- Tab currentTab = mTabControl.getCurrentTab();
- if (currentTab != null) {
- currentTab.getWebView().setUpSelect();
+ case R.id.save_webarchive_menu_id:
+ if (LOGD_ENABLED) {
+ Log.d(LOGTAG, "Save as Web Archive");
+ }
+ String state = Environment.getExternalStorageState();
+ if (Environment.MEDIA_MOUNTED.equals(state)) {
+ String directory = Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + File.separator;
+ File dir = new File(directory);
+ if (!dir.exists() && !dir.mkdirs()) {
+ Log.e(LOGTAG, "Save as Web Archive: mkdirs for " + directory + " failed!");
+ Toast.makeText(BrowserActivity.this, R.string.webarchive_failed,
+ Toast.LENGTH_SHORT).show();
+ break;
}
+ getTopWindow().saveWebArchive(directory, true, new ValueCallback<String>() {
+ @Override
+ public void onReceiveValue(String value) {
+ if (value != null) {
+ Toast.makeText(BrowserActivity.this, R.string.webarchive_saved,
+ Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(BrowserActivity.this, R.string.webarchive_failed,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
} else {
- showSelectDialog();
+ Toast.makeText(BrowserActivity.this, R.string.webarchive_failed,
+ Toast.LENGTH_SHORT).show();
}
break;
@@ -1413,7 +1448,7 @@ public class BrowserActivity extends Activity
break;
case R.id.classic_history_menu_id:
- bookmarksOrHistoryPicker(true);
+ bookmarksOrHistoryPicker(true, false);
break;
case R.id.title_bar_share_page_url:
@@ -1426,7 +1461,8 @@ public class BrowserActivity extends Activity
currentTab.populatePickerData();
sharePage(this, currentTab.getTitle(),
currentTab.getUrl(), currentTab.getFavicon(),
- createScreenshot(currentTab.getWebView()));
+ createScreenshot(currentTab.getWebView(), getDesiredThumbnailWidth(this),
+ getDesiredThumbnailHeight(this)));
break;
case R.id.dump_nav_menu_id:
@@ -1482,60 +1518,57 @@ public class BrowserActivity extends Activity
return true;
}
- private boolean dialogIsUp() {
- return null != mFindDialog && mFindDialog.isVisible() ||
- null != mSelectDialog && mSelectDialog.isVisible();
+ /* package */ void bookmarkCurrentPage() {
+ 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, getDesiredThumbnailWidth(this),
+ getDesiredThumbnailHeight(this)));
+ i.putExtra("url_editable", false);
+ startActivity(i);
}
- private boolean closeDialog(WebDialog dialog) {
- if (null == dialog || !dialog.isVisible()) return false;
- Tab currentTab = mTabControl.getCurrentTab();
- currentTab.closeDialog(dialog);
- dialog.dismiss();
- return true;
+ /*
+ * True if a custom ActionMode (i.e. find or select) is in use.
+ */
+ private boolean isInCustomActionMode() {
+ return mActionMode != null;
}
/*
- * Remove the find dialog or select dialog.
+ * End the current ActionMode.
*/
- public void closeDialogs() {
- if (!(closeDialog(mFindDialog) || closeDialog(mSelectDialog))) return;
- // If the Find was being performed in the main WebView, replace the
- // embedded title bar.
- Tab currentTab = mTabControl.getCurrentTab();
- if (currentTab.getSubWebView() == null) {
- WebView mainView = currentTab.getWebView();
- if (mainView != null) {
- mainView.setEmbeddedTitleBar(mTitleBar);
- }
+ void endActionMode() {
+ if (mActionMode != null) {
+ ActionMode mode = mActionMode;
+ onEndActionMode();
+ mode.finish();
}
- mMenuState = R.id.MAIN_MENU;
+ }
+
+ /*
+ * Called by find and select when they are finished. Replace title bars
+ * as necessary.
+ */
+ public void onEndActionMode() {
+ if (!isInCustomActionMode()) return;
if (mInLoad) {
// The title bar was hidden, because otherwise it would cover up the
- // find or select dialog. Now that the dialog has been removed,
+ // find or select dialog. Now that the dialog has been removed,
// show the fake title bar once again.
showFakeTitleBar();
}
+ // Would like to return the menu state to normal, but this does not
+ // necessarily get called.
+ mActionMode = null;
}
- public void showFindDialog() {
- if (null == mFindDialog) {
- mFindDialog = new FindDialog(this);
- }
- showDialog(mFindDialog).setFindIsUp(true);
- }
-
- public void setFindDialogText(String text) {
- mFindDialog.setText(text);
- }
-
- public void showSelectDialog() {
- if (null == mSelectDialog) {
- mSelectDialog = new SelectDialog(this);
- }
- showDialog(mSelectDialog).setUpSelect();
- mSelectDialog.hideSoftInput();
- }
+ // For select and find, we keep track of the ActionMode so that
+ // finish() can be called as desired.
+ private ActionMode mActionMode;
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
@@ -1575,11 +1608,13 @@ public class BrowserActivity extends Activity
final MenuItem home = menu.findItem(R.id.homepage_menu_id);
home.setEnabled(!isHome);
- menu.findItem(R.id.forward_menu_id)
- .setEnabled(canGoForward);
+ final MenuItem forward = menu.findItem(R.id.forward_menu_id);
+ forward.setEnabled(canGoForward);
- menu.findItem(R.id.new_tab_menu_id).setEnabled(
- mTabControl.canCreateNewTab());
+ if (!mXLargeScreenSize) {
+ final MenuItem newtab = menu.findItem(R.id.new_tab_menu_id);
+ newtab.setEnabled(mTabControl.canCreateNewTab());
+ }
// decide whether to show the share link option
PackageManager pm = getPackageManager();
@@ -1607,7 +1642,7 @@ public class BrowserActivity extends Activity
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
- if (v instanceof TitleBar) {
+ if (v instanceof TitleBarBase) {
return;
}
WebView webview = (WebView) v;
@@ -1634,7 +1669,7 @@ public class BrowserActivity extends Activity
inflater.inflate(R.menu.browsercontext, menu);
// Show the correct menu group
- String extra = result.getExtra();
+ final String extra = result.getExtra();
menu.setGroupVisible(R.id.PHONE_MENU,
type == WebView.HitTestResult.PHONE_TYPE);
menu.setGroupVisible(R.id.EMAIL_MENU,
@@ -1691,8 +1726,23 @@ public class BrowserActivity extends Activity
titleView.setText(extra);
menu.setHeaderView(titleView);
// decide whether to show the open link in new tab option
- menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
- mTabControl.canCreateNewTab());
+ boolean showNewTab = mTabControl.canCreateNewTab();
+ MenuItem newTabItem
+ = menu.findItem(R.id.open_newtab_context_menu_id);
+ newTabItem.setVisible(showNewTab);
+ if (showNewTab) {
+ newTabItem.setOnMenuItemClickListener(
+ new MenuItem.OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ final Tab parent = mTabControl.getCurrentTab();
+ final Tab newTab = openTab(extra, false);
+ if (newTab != parent) {
+ parent.addChildTab(newTab);
+ }
+ return true;
+ }
+ });
+ }
menu.findItem(R.id.bookmark_context_menu_id).setVisible(
Bookmarks.urlHasAcceptableScheme(extra));
PackageManager pm = getPackageManager();
@@ -1752,6 +1802,9 @@ public class BrowserActivity extends Activity
}
// Request focus on the top window.
t.getTopWindow().requestFocus();
+ if (mTabControl.getTabChangeListener() != null) {
+ mTabControl.getTabChangeListener().onCurrentTab(t);
+ }
}
// Attach a sub window to the main WebView of the given tab.
@@ -1799,7 +1852,7 @@ public class BrowserActivity extends Activity
final Tab currentTab = mTabControl.getCurrentTab();
if (mTabControl.canCreateNewTab()) {
final Tab tab = mTabControl.createNewTab(closeOnExit, appId,
- urlData.mUrl);
+ urlData.mUrl, false);
WebView webview = tab.getWebView();
// If the last tab was removed from the active tabs page, currentTab
// will be null.
@@ -1825,8 +1878,8 @@ public class BrowserActivity extends Activity
}
}
- private Tab openTab(String url) {
- if (mSettings.openInBackground()) {
+ private Tab openTab(String url, boolean forceForeground) {
+ if (mSettings.openInBackground() && !forceForeground) {
Tab t = mTabControl.createNewTab();
if (t != null) {
WebView view = t.getWebView();
@@ -1838,6 +1891,20 @@ public class BrowserActivity extends Activity
}
}
+ /* package */ Tab openIncognitoTab() {
+ if (mTabControl.canCreateNewTab()) {
+ Tab currentTab = mTabControl.getCurrentTab();
+ Tab tab = mTabControl.createNewTab(false, null, null, true);
+ if (currentTab != null) {
+ removeTabFromContentView(currentTab);
+ }
+ mTabControl.setCurrentTab(tab);
+ attachTabToContentView(tab);
+ return tab;
+ }
+ return null;
+ }
+
private class Copy implements OnMenuItemClickListener {
private CharSequence mText;
@@ -1898,6 +1965,7 @@ public class BrowserActivity extends Activity
return true;
}
+ @Override
public void run() {
Drawable oldWallpaper = BrowserActivity.this.getWallpaper();
try {
@@ -1941,14 +2009,8 @@ public class BrowserActivity extends Activity
}
private void copy(CharSequence text) {
- try {
- IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
- if (clip != null) {
- clip.setClipboardText(text);
- }
- } catch (android.os.RemoteException e) {
- Log.e(LOGTAG, "Copy failed", e);
- }
+ ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setText(text);
}
/**
@@ -2061,6 +2123,11 @@ public class BrowserActivity extends Activity
}
mTabControl.setCurrentTab(mTabControl.getTab(currentIndex));
resetTitleIconAndProgress();
+ updateLockIconToLatest();
+
+ if (!mTabControl.hasAnyOpenIncognitoTabs()) {
+ WebView.cleanupPrivateBrowsingFiles(this);
+ }
}
/* package */ void goBackOnePageOrQuit() {
@@ -2159,7 +2226,7 @@ public class BrowserActivity extends Activity
return true;
} else if (mCustomView == null && mActiveTabsPage == null
&& event.isLongPress()) {
- bookmarksOrHistoryPicker(true);
+ bookmarksOrHistoryPicker(true, false);
return true;
}
break;
@@ -2252,11 +2319,19 @@ public class BrowserActivity extends Activity
static final int UPDATE_BOOKMARK_THUMBNAIL = 108;
+ private static final int TOUCH_ICON_DOWNLOADED = 109;
+
+ private static final int OPEN_BOOKMARKS = 201;
+
// Private handler for handling javascript and saving passwords
private Handler mHandler = new Handler() {
+ @Override
public void handleMessage(Message msg) {
switch (msg.what) {
+ case OPEN_BOOKMARKS:
+ bookmarksOrHistoryPicker(false, false);
+ break;
case FOCUS_NODE_HREF:
{
String url = (String) msg.getData().get("url");
@@ -2275,13 +2350,6 @@ public class BrowserActivity extends Activity
case R.id.view_image_context_menu_id:
loadUrlFromContext(getTopWindow(), url);
break;
- case R.id.open_newtab_context_menu_id:
- final Tab parent = mTabControl.getCurrentTab();
- final Tab newTab = openTab(url);
- if (newTab != parent) {
- parent.addChildTab(newTab);
- }
- break;
case R.id.bookmark_context_menu_id:
Intent intent = new Intent(BrowserActivity.this,
AddBookmarkPage.class);
@@ -2290,41 +2358,8 @@ public class BrowserActivity extends Activity
startActivity(intent);
break;
case R.id.share_link_context_menu_id:
- // See if this site has been visited before
- StringBuilder sb = new StringBuilder(
- Browser.BookmarkColumns.URL + " = ");
- DatabaseUtils.appendEscapedSQLString(sb, url);
- Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
- Browser.HISTORY_PROJECTION,
- sb.toString(),
- null,
+ sharePage(BrowserActivity.this, title, url, null,
null);
- if (c.moveToFirst()) {
- // The site has been visited before, so grab the
- // info from the database.
- Bitmap favicon = null;
- Bitmap thumbnail = null;
- String linkTitle = c.getString(Browser.
- HISTORY_PROJECTION_TITLE_INDEX);
- byte[] data = c.getBlob(Browser.
- HISTORY_PROJECTION_FAVICON_INDEX);
- if (data != null) {
- favicon = BitmapFactory.decodeByteArray(
- data, 0, data.length);
- }
- data = c.getBlob(Browser.
- HISTORY_PROJECTION_THUMBNAIL_INDEX);
- if (data != null) {
- thumbnail = BitmapFactory.decodeByteArray(
- data, 0, data.length);
- }
- sharePage(BrowserActivity.this,
- linkTitle, url, favicon, thumbnail);
- } else {
- Browser.sendString(BrowserActivity.this, url,
- getString(
- R.string.choosertitle_sharevia));
- }
break;
case R.id.copy_link_context_menu_id:
copy(url);
@@ -2361,6 +2396,14 @@ public class BrowserActivity extends Activity
updateScreenshot(view);
}
break;
+
+ case TOUCH_ICON_DOWNLOADED:
+ Bundle b = msg.getData();
+ showSaveToHomescreenDialog(b.getString("url"),
+ b.getString("title"),
+ (Bitmap) b.getParcelable("touchIcon"),
+ (Bitmap) b.getParcelable("favicon"));
+ break;
}
}
};
@@ -2401,7 +2444,8 @@ public class BrowserActivity extends Activity
// draw, but the API for that (WebViewCore.pictureReady()) is not
// currently accessible here.
- final Bitmap bm = createScreenshot(view);
+ final Bitmap bm = createScreenshot(view, getDesiredThumbnailWidth(this),
+ getDesiredThumbnailHeight(this));
if (bm == null) {
return;
}
@@ -2413,29 +2457,25 @@ public class BrowserActivity extends Activity
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... unused) {
- Cursor c = null;
+ Cursor cursor = null;
try {
- c = BrowserBookmarksAdapter.queryBookmarksForUrl(
- cr, originalUrl, url, true);
- if (c != null) {
- if (c.moveToFirst()) {
- ContentValues values = new ContentValues();
- final ByteArrayOutputStream os
- = new ByteArrayOutputStream();
- bm.compress(Bitmap.CompressFormat.PNG, 100, os);
- values.put(Browser.BookmarkColumns.THUMBNAIL,
- os.toByteArray());
- do {
- cr.update(ContentUris.withAppendedId(
- Browser.BOOKMARKS_URI, c.getInt(0)),
- values, null, null);
- } while (c.moveToNext());
- }
+ cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url);
+ if (cursor != null && cursor.moveToFirst()) {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+
+ ContentValues values = new ContentValues();
+ values.put(Images.THUMBNAIL, os.toByteArray());
+ values.put(Images.URL, cursor.getString(0));
+
+ do {
+ cr.update(Images.CONTENT_URI, values, null, null);
+ } while (cursor.moveToNext());
}
} catch (IllegalStateException e) {
// Ignore
} finally {
- if (c != null) c.close();
+ if (cursor != null) cursor.close();
}
return null;
}
@@ -2443,47 +2483,31 @@ public class BrowserActivity extends Activity
}
/**
- * 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.
+ * @return 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 context.getResources().getDimensionPixelOffset(R.dimen.bookmarkThumbnailWidth);
}
/**
* 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.
+ * @return desired height for thumbnail screenshot.
*/
/* package */ static int getDesiredThumbnailHeight(Context context) {
- // To ensure that they are both initialized.
- getDesiredThumbnailWidth(context);
- return THUMBNAIL_HEIGHT;
+ return context.getResources().getDimensionPixelOffset(R.dimen.bookmarkThumbnailHeight);
}
- private Bitmap createScreenshot(WebView view) {
+ private Bitmap createScreenshot(WebView view, int width, int height) {
Picture thumbnail = view.capturePicture();
if (thumbnail == null) {
return null;
}
- Bitmap bm = Bitmap.createBitmap(getDesiredThumbnailWidth(this),
- getDesiredThumbnailHeight(this), Bitmap.Config.RGB_565);
+ Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bm);
// May need to tweak these values to determine what is the
// best scale factor
@@ -2492,8 +2516,7 @@ public class BrowserActivity extends Activity
float scaleFactorX = 1.0f;
float scaleFactorY = 1.0f;
if (thumbnailWidth > 0) {
- scaleFactorX = (float) getDesiredThumbnailWidth(this) /
- (float)thumbnailWidth;
+ scaleFactorX = (float) width / (float)thumbnailWidth;
} else {
return null;
}
@@ -2503,8 +2526,7 @@ public class BrowserActivity extends Activity
// If the device is in landscape and the page is shorter
// than the height of the view, stretch the thumbnail to fill the
// space.
- scaleFactorY = (float) getDesiredThumbnailHeight(this) /
- (float)thumbnailHeight;
+ scaleFactorY = (float) height / (float)thumbnailHeight;
} else {
// In the portrait case, this looks nice.
scaleFactorY = scaleFactorX;
@@ -2545,7 +2567,7 @@ public class BrowserActivity extends Activity
onProgressChanged(view, INITIAL_PROGRESS);
mDidStopLoad = false;
if (!mIsNetworkUp) createAndShowNetworkDialog();
- closeDialogs();
+ endActionMode();
if (mSettings.isTracing()) {
String host;
try {
@@ -2643,7 +2665,25 @@ public class BrowserActivity extends Activity
}
}
+ private void closeEmptyChildTab() {
+ Tab current = mTabControl.getCurrentTab();
+ if (current != null
+ && current.getWebView().copyBackForwardList().getSize() == 0) {
+ Tab parent = current.getParentTab();
+ if (parent != null) {
+ switchToTab(mTabControl.getTabIndex(parent));
+ closeTab(current);
+ }
+ }
+ }
+
boolean shouldOverrideUrlLoading(WebView view, String url) {
+ if (view.isPrivateBrowsingEnabled()) {
+ // Don't allow urls to leave the browser app when in private browsing mode
+ loadUrl(view, url);
+ return true;
+ }
+
if (url.startsWith(SCHEME_WTAI)) {
// wtai://wp/mc;number
// number=string(phone-number)
@@ -2652,6 +2692,11 @@ public class BrowserActivity extends Activity
Uri.parse(WebView.SCHEME_TEL +
url.substring(SCHEME_WTAI_MC.length())));
startActivity(intent);
+ // before leaving BrowserActivity, close the empty child tab.
+ // If a new tab is created through JavaScript open to load this
+ // url, we would like to close it as we will load this url in a
+ // different Activity.
+ closeEmptyChildTab();
return true;
}
// wtai://wp/sd;dtmf
@@ -2675,6 +2720,29 @@ public class BrowserActivity extends Activity
return false;
}
+ // If this is a Google search, attempt to add an RLZ string (if one isn't already present).
+ if (rlzProviderPresent()) {
+ Uri siteUri = Uri.parse(url);
+ if (needsRlzString(siteUri)) {
+ String rlz = null;
+ Cursor cur = null;
+ try {
+ cur = getContentResolver().query(getRlzUri(), null, null, null, null);
+ if (cur != null && cur.moveToFirst() && !cur.isNull(0)) {
+ url = siteUri.buildUpon()
+ .appendQueryParameter("rlz", cur.getString(0))
+ .build().toString();
+ }
+ } finally {
+ if (cur != null) {
+ cur.close();
+ }
+ }
+ loadUrl(view, url);
+ return true;
+ }
+ }
+
Intent intent;
// perform generic parsing of the URI to turn it into an Intent.
try {
@@ -2693,6 +2761,11 @@ public class BrowserActivity extends Activity
.parse("market://search?q=pname:" + packagename));
intent.addCategory(Intent.CATEGORY_BROWSABLE);
startActivity(intent);
+ // before leaving BrowserActivity, close the empty child tab.
+ // If a new tab is created through JavaScript open to load this
+ // url, we would like to close it as we will load this url in a
+ // different Activity.
+ closeEmptyChildTab();
return true;
} else {
return false;
@@ -2705,6 +2778,11 @@ public class BrowserActivity extends Activity
intent.setComponent(null);
try {
if (startActivityIfNeeded(intent, -1)) {
+ // before leaving BrowserActivity, close the empty child tab.
+ // If a new tab is created through JavaScript open to load this
+ // url, we would like to close it as we will load this url in a
+ // different Activity.
+ closeEmptyChildTab();
return true;
}
} catch (ActivityNotFoundException ex) {
@@ -2713,18 +2791,78 @@ public class BrowserActivity extends Activity
}
if (mMenuIsDown) {
- openTab(url);
+ openTab(url, false);
closeOptionsMenu();
return true;
}
return false;
}
+ // Determine whether the RLZ provider is present on the system.
+ private boolean rlzProviderPresent() {
+ if (mIsProviderPresent == null) {
+ PackageManager pm = getPackageManager();
+ mIsProviderPresent = pm.resolveContentProvider(BrowserSettings.RLZ_PROVIDER, 0) != null;
+ }
+ return mIsProviderPresent;
+ }
+
+ // Retrieve the RLZ access point string and cache the URI used to retrieve RLZ values.
+ private Uri getRlzUri() {
+ if (mRlzUri == null) {
+ String ap = getResources().getString(R.string.rlz_access_point);
+ mRlzUri = Uri.withAppendedPath(BrowserSettings.RLZ_PROVIDER_URI, ap);
+ }
+ return mRlzUri;
+ }
+
+ // Determine if this URI appears to be for a Google search and does not have an RLZ parameter.
+ // Taken largely from Chrome source, src/chrome/browser/google_url_tracker.cc
+ private static boolean needsRlzString(Uri uri) {
+ String scheme = uri.getScheme();
+ if (("http".equals(scheme) || "https".equals(scheme)) &&
+ (uri.getQueryParameter("q") != null) && (uri.getQueryParameter("rlz") == null)) {
+ String host = uri.getHost();
+ if (host == null) {
+ return false;
+ }
+ String[] hostComponents = host.split("\\.");
+
+ if (hostComponents.length < 2) {
+ return false;
+ }
+ int googleComponent = hostComponents.length - 2;
+ String component = hostComponents[googleComponent];
+ if (!"google".equals(component)) {
+ if (hostComponents.length < 3 ||
+ (!"co".equals(component) && !"com".equals(component))) {
+ return false;
+ }
+ googleComponent = hostComponents.length - 3;
+ if (!"google".equals(hostComponents[googleComponent])) {
+ return false;
+ }
+ }
+
+ // Google corp network handling.
+ if (googleComponent > 0 && "corp".equals(hostComponents[googleComponent - 1])) {
+ return false;
+ }
+
+ return true;
+ }
+ return false;
+ }
+
// -------------------------------------------------------------------------
// Helper function for WebChromeClient
// -------------------------------------------------------------------------
void onProgressChanged(WebView view, int newProgress) {
+
+ // On the phone, the fake title bar will always cover up the
+ // regular title bar (or the regular one is offscreen), so only the
+ // fake title bar needs to change its progress
mFakeTitleBar.setProgress(newProgress);
if (newProgress == 100) {
@@ -2826,15 +2964,138 @@ public class BrowserActivity extends Activity
* The Object used to inform the WebView of the file to upload.
*/
private ValueCallback<Uri> mUploadMessage;
+ private String mCameraFilePath;
+
+ void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
+
+ final String imageMimeType = "image/*";
+ final String videoMimeType = "video/*";
+ final String audioMimeType = "audio/*";
+ final String mediaSourceKey = "capture";
+ final String mediaSourceValueCamera = "camera";
+ final String mediaSourceValueFileSystem = "filesystem";
+ final String mediaSourceValueCamcorder = "camcorder";
+ final String mediaSourceValueMicrophone = "microphone";
+
+ // media source can be 'filesystem' or 'camera' or 'camcorder' or 'microphone'.
+ String mediaSource = "";
+
+ // We add the camera intent if there was no accept type (or '*/*' or 'image/*').
+ boolean addCameraIntent = true;
+ // We add the camcorder intent if there was no accept type (or '*/*' or 'video/*').
+ boolean addCamcorderIntent = true;
+
+ if (mUploadMessage != null) {
+ // Already a file picker operation in progress.
+ return;
+ }
- void openFileChooser(ValueCallback<Uri> uploadMsg) {
- if (mUploadMessage != null) return;
mUploadMessage = uploadMsg;
+
+ // Parse the accept type.
+ String params[] = acceptType.split(";");
+ String mimeType = params[0];
+
+ for (String p : params) {
+ String[] keyValue = p.split("=");
+ if (keyValue.length == 2) {
+ // Process key=value parameters.
+ if (mediaSourceKey.equals(keyValue[0])) {
+ mediaSource = keyValue[1];
+ }
+ }
+ }
+
+ // This intent will display the standard OPENABLE file picker.
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
- i.setType("*/*");
- BrowserActivity.this.startActivityForResult(Intent.createChooser(i,
- getString(R.string.choose_upload)), FILE_SELECTED);
+
+ // Create an intent to add to the standard file picker that will
+ // capture an image from the camera. We'll combine this intent with
+ // the standard OPENABLE picker unless the web developer specifically
+ // requested the camera or gallery be opened by passing a parameter
+ // in the accept type.
+ Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ File externalDataDir = Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DCIM);
+ File cameraDataDir = new File(externalDataDir.getAbsolutePath() +
+ File.separator + "browser-photos");
+ cameraDataDir.mkdirs();
+ mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator +
+ System.currentTimeMillis() + ".jpg";
+ cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath)));
+
+ Intent camcorderIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+
+ Intent soundRecIntent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
+
+ if (mimeType.equals(imageMimeType)) {
+ i.setType(imageMimeType);
+ addCamcorderIntent = false;
+ if (mediaSource.equals(mediaSourceValueCamera)) {
+ // Specified 'image/*' and requested the camera, so go ahead and launch the camera
+ // directly.
+ BrowserActivity.this.startActivityForResult(cameraIntent, FILE_SELECTED);
+ return;
+ } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
+ // Specified filesytem as the source, so don't want to consider the camera.
+ addCameraIntent = false;
+ }
+ } else if (mimeType.equals(videoMimeType)) {
+ i.setType(videoMimeType);
+ addCameraIntent = false;
+ // The camcorder saves it's own file and returns it to us in the intent, so
+ // we don't need to generate one here.
+ mCameraFilePath = null;
+
+ if (mediaSource.equals(mediaSourceValueCamcorder)) {
+ // Specified 'video/*' and requested the camcorder, so go ahead and launch the
+ // camcorder directly.
+ BrowserActivity.this.startActivityForResult(camcorderIntent, FILE_SELECTED);
+ return;
+ } else if (mediaSource.equals(mediaSourceValueFileSystem)) {
+ // Specified filesystem as the source, so don't want to consider the camcorder.
+ addCamcorderIntent = false;
+ }
+ } else if (mimeType.equals(audioMimeType)) {
+ i.setType(audioMimeType);
+ addCameraIntent = false;
+ addCamcorderIntent = false;
+ if (mediaSource.equals(mediaSourceValueMicrophone)) {
+ // Specified 'audio/*' and requested microphone, so go ahead and launch the sound
+ // recorder.
+ BrowserActivity.this.startActivityForResult(soundRecIntent, FILE_SELECTED);
+ return;
+ }
+ // On a default system, there is no single option to open an audio "gallery". Both the
+ // sound recorder and music browser respond to the OPENABLE/audio/* intent unlike the
+ // image/* and video/* OPENABLE intents where the image / video gallery are the only
+ // respondants (and so the user is not prompted by default).
+ } else {
+ i.setType("*/*");
+ }
+
+ // Combine the chooser and the extra choices (like camera or camcorder)
+ Intent chooser = new Intent(Intent.ACTION_CHOOSER);
+ chooser.putExtra(Intent.EXTRA_INTENT, i);
+
+ Vector<Intent> extraInitialIntents = new Vector<Intent>(0);
+
+ if (addCameraIntent) {
+ extraInitialIntents.add(cameraIntent);
+ }
+
+ if (addCamcorderIntent) {
+ extraInitialIntents.add(camcorderIntent);
+ }
+
+ if (extraInitialIntents.size() > 0) {
+ Intent[] extraIntents = new Intent[extraInitialIntents.size()];
+ chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraInitialIntents.toArray(extraIntents));
+ }
+
+ chooser.putExtra(Intent.EXTRA_TITLE, getString(R.string.choose_upload));
+ BrowserActivity.this.startActivityForResult(chooser, FILE_SELECTED);
}
// -------------------------------------------------------------------------
@@ -3021,7 +3282,10 @@ public class BrowserActivity extends Activity
* Update the lock icon to correspond to our latest state.
*/
private void updateLockIconToLatest() {
- updateLockIconImage(mTabControl.getCurrentTab().getLockIconType());
+ Tab t = mTabControl.getCurrentTab();
+ if (t != null) {
+ updateLockIconImage(t.getLockIconType());
+ }
}
/**
@@ -3525,16 +3789,33 @@ public class BrowserActivity extends Activity
if (resultCode == RESULT_OK && intent != null) {
String data = intent.getAction();
Bundle extras = intent.getExtras();
- if (extras != null && extras.getBoolean("new_window", false)) {
- openTab(data);
+ if (extras != null &&
+ extras.getBoolean(
+ CombinedBookmarkHistoryActivity.EXTRA_OPEN_NEW_WINDOW,
+ false)) {
+ openTab(data, false);
+ } else if ((extras != null) &&
+ extras.getBoolean(CombinedBookmarkHistoryActivity.NEWTAB_MODE)) {
+ openTab(data, true);
} else {
- final Tab currentTab =
- mTabControl.getCurrentTab();
+ final Tab currentTab = mTabControl.getCurrentTab();
dismissSubWindow(currentTab);
if (data != null && data.length() != 0) {
loadUrl(getTopWindow(), data);
}
}
+ } else if (resultCode == RESULT_CANCELED) {
+ if (intent != null) {
+ float evtx = intent.getFloatExtra(CombinedBookmarkHistoryActivity.EVT_X, -1);
+ float evty = intent.getFloatExtra(CombinedBookmarkHistoryActivity.EVT_Y, -1);
+ long now = System.currentTimeMillis();
+ MotionEvent evt = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_DOWN, evtx, evty, 0);
+ dispatchTouchEvent(evt);
+ MotionEvent up = MotionEvent.obtain(evt);
+ up.setAction(MotionEvent.ACTION_UP);
+ dispatchTouchEvent(up);
+ }
}
// Deliberately fall through to PREFERENCES_PAGE, since the
// same extra may be attached to the COMBO_PAGE
@@ -3551,8 +3832,25 @@ public class BrowserActivity extends Activity
if (null == mUploadMessage) break;
Uri result = intent == null || resultCode != RESULT_OK ? null
: intent.getData();
+
+ // As we ask the camera to save the result of the user taking
+ // a picture, the camera application does not return anything other
+ // than RESULT_OK. So we need to check whether the file we expected
+ // was written to disk in the in the case that we
+ // did not get an intent returned but did get a RESULT_OK. If it was,
+ // we assume that this result has came back from the camera.
+ if (result == null && intent == null && resultCode == RESULT_OK) {
+ File cameraFile = new File(mCameraFilePath);
+ if (cameraFile.exists()) {
+ result = Uri.fromFile(cameraFile);
+ // Broadcast to the media scanner that we have a new photo
+ // so it will be added into the gallery for the user.
+ sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));
+ }
+ }
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
+ mCameraFilePath = null;
break;
default:
break;
@@ -3573,12 +3871,78 @@ public class BrowserActivity extends Activity
}
+ /* package */ void promptAddOrInstallBookmark(View anchor) {
+ PopupMenu popup = new PopupMenu(this, anchor);
+ popup.getMenuInflater().inflate(R.menu.bookmark_shortcut, popup.getMenu());
+ popup.setOnMenuItemClickListener(this);
+ popup.show();
+ }
+
+ /**
+ * popup menu item click listener
+ * @param item
+ */
+ public boolean onMenuItemClick(MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.add_bookmark_menu_id:
+ bookmarkCurrentPage();
+ return true;
+ case R.id.shortcut_to_home_menu_id:
+ Tab current = mTabControl.getCurrentTab();
+ current.populatePickerData();
+ String touchIconUrl = mTabControl.getCurrentWebView().getTouchIconUrl();
+ if (touchIconUrl != null) {
+ // Download the touch icon for this site then save
+ // it to the
+ // homescreen.
+ Bundle b = new Bundle();
+ b.putString("url", current.getUrl());
+ b.putString("title", current.getTitle());
+ b.putParcelable("favicon", current.getFavicon());
+ Message msg = mHandler.obtainMessage(TOUCH_ICON_DOWNLOADED);
+ msg.setData(b);
+ new DownloadTouchIcon(BrowserActivity.this, msg,
+ mTabControl.getCurrentWebView().getSettings().getUserAgentString())
+ .execute(touchIconUrl);
+ } else {
+ // add to homescreen, can do it immediately as there
+ // is no touch
+ // icon.
+ showSaveToHomescreenDialog(
+ current.getUrl(), current.getTitle(), null, current.getFavicon());
+ }
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /* package */Dialog makeAddOrInstallDialog() {
+ final Tab current = mTabControl.getCurrentTab();
+ Resources resources = getResources();
+ CharSequence[] choices =
+ {resources.getString(R.string.save_to_bookmarks),
+ resources.getString(R.string.create_shortcut_bookmark)};
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.add_new_bookmark);
+ builder.setItems(choices, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ if (item == 0) {
+ bookmarkCurrentPage();
+ } else if (item == 1) {
+ }
+ }
+ });
+ return builder.create();
+ }
+
/**
* Open the Go page.
* @param startWithHistory If true, open starting on the history tab.
* Otherwise, start with the bookmarks tab.
*/
- /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) {
+ /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory, boolean newTabMode) {
WebView current = mTabControl.getCurrentWebView();
if (current == null) {
return;
@@ -3587,7 +3951,8 @@ public class BrowserActivity extends Activity
CombinedBookmarkHistoryActivity.class);
String title = current.getTitle();
String url = current.getUrl();
- Bitmap thumbnail = createScreenshot(current);
+ Bitmap thumbnail = createScreenshot(current, getDesiredThumbnailWidth(this),
+ getDesiredThumbnailHeight(this));
// Just in case the user opens bookmarks before a page finishes loading
// so the current history item, and therefore the page, is null.
@@ -3609,12 +3974,50 @@ public class BrowserActivity extends Activity
intent.putExtra("disable_new_window", !mTabControl.canCreateNewTab());
intent.putExtra("touch_icon_url", current.getTouchIconUrl());
if (startWithHistory) {
- intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
- CombinedBookmarkHistoryActivity.HISTORY_TAB);
+ intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_FRAGMENT,
+ CombinedBookmarkHistoryActivity.FRAGMENT_ID_HISTORY);
+ }
+ intent.putExtra(CombinedBookmarkHistoryActivity.NEWTAB_MODE, newTabMode);
+ int top = -1;
+ int height = -1;
+ if (mXLargeScreenSize) {
+ showFakeTitleBar();
+ int titleBarHeight = ((TitleBarXLarge)mFakeTitleBar).getHeightWithoutProgress();
+ top = mTabBar.getBottom() + titleBarHeight;
+ height = getTopWindow().getHeight() - titleBarHeight;
}
+ intent.putExtra(CombinedBookmarkHistoryActivity.EXTRA_TOP, top);
+ intent.putExtra(CombinedBookmarkHistoryActivity.EXTRA_HEIGHT, height);
startActivityForResult(intent, COMBO_PAGE);
}
+ private void showSaveToHomescreenDialog(String url, String title, Bitmap touchIcon,
+ Bitmap favicon) {
+ Intent intent = new Intent(this, SaveToHomescreenDialog.class);
+
+ // Just in case the user tries to save before a page finishes loading
+ // so the current history item, and therefore the page, is null.
+ if (null == url) {
+ url = mLastEnteredUrl;
+ // This can happen.
+ if (null == url) {
+ url = mSettings.getHomePage();
+ }
+ }
+
+ // In case the web page has not yet received its associated title.
+ if (title == null) {
+ title = url;
+ }
+
+ intent.putExtra("title", title);
+ intent.putExtra("url", url);
+ intent.putExtra("favicon", favicon);
+ intent.putExtra("touchIcon", touchIcon);
+ startActivity(intent);
+ }
+
+
// Called when loading from context menu or LOAD_URL message
private void loadUrlFromContext(WebView view, String url) {
// In case the user enters nothing.
@@ -3788,54 +4191,6 @@ public class BrowserActivity extends Activity
}
}
- private void packageChanged(String packageName, boolean wasAdded) {
- WebView w = mTabControl.getCurrentWebView();
- if (w == null) {
- return;
- }
-
- if (wasAdded) {
- w.addPackageName(packageName);
- } else {
- w.removePackageName(packageName);
- }
- }
-
- private void addPackageNames(Set<String> packageNames) {
- WebView w = mTabControl.getCurrentWebView();
- if (w == null) {
- return;
- }
-
- w.addPackageNames(packageNames);
- }
-
- private void getInstalledPackages() {
- AsyncTask<Void, Void, Set<String> > task =
- new AsyncTask<Void, Void, Set<String> >() {
- protected Set<String> doInBackground(Void... unused) {
- Set<String> installedPackages = new HashSet<String>();
- PackageManager pm = BrowserActivity.this.getPackageManager();
- if (pm != null) {
- List<PackageInfo> packages = pm.getInstalledPackages(0);
- for (PackageInfo p : packages) {
- if (BrowserActivity.this.sGoogleApps.contains(p.packageName)) {
- installedPackages.add(p.packageName);
- }
- }
- }
-
- return installedPackages;
- }
-
- // Executes on the UI thread
- protected void onPostExecute(Set<String> installedPackages) {
- addPackageNames(installedPackages);
- }
- };
- task.execute();
- }
-
final static int LOCK_ICON_UNSECURE = 0;
final static int LOCK_ICON_SECURE = 1;
final static int LOCK_ICON_MIXED = 2;
@@ -3856,8 +4211,6 @@ public class BrowserActivity extends Activity
private static final int EMPTY_MENU = -1;
private Menu mMenu;
- private FindDialog mFindDialog;
- private SelectDialog mSelectDialog;
// Used to prevent chording to result in firing two shortcuts immediately
// one after another. Fixes bug 1211714.
boolean mCanChord;
@@ -3971,7 +4324,8 @@ public class BrowserActivity extends Activity
private Toast mStopToast;
- private TitleBar mTitleBar;
+ private TitleBarBase mTitleBar;
+ private TabBar mTabBar;
private LinearLayout mErrorConsoleContainer = null;
private boolean mShouldShowErrorConsole = false;
@@ -3987,8 +4341,6 @@ public class BrowserActivity extends Activity
private IntentFilter mNetworkStateChangedFilter;
private BroadcastReceiver mNetworkStateIntentReceiver;
- private BroadcastReceiver mPackageInstallationReceiver;
-
private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins;
// activity requestCode
@@ -4002,14 +4354,6 @@ public class BrowserActivity extends Activity
// the video progress view
private View mVideoProgressView;
- // The Google packages we monitor for the navigator.isApplicationInstalled()
- // API. Add as needed.
- private static Set<String> sGoogleApps;
- static {
- sGoogleApps = new HashSet<String>();
- sGoogleApps.add("com.google.android.youtube");
- }
-
/**
* A UrlData class to abstract how the content will be set to WebView.
* This base class uses loadUrl to show the content.
diff --git a/src/com/android/browser/BrowserBackupAgent.java b/src/com/android/browser/BrowserBackupAgent.java
index c968ce5d7..fb1933f3d 100644
--- a/src/com/android/browser/BrowserBackupAgent.java
+++ b/src/com/android/browser/BrowserBackupAgent.java
@@ -166,7 +166,7 @@ public class BrowserBackupAgent extends BackupAgent {
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(),
+ Bookmarks.addBookmark(this, false,
mark.url, mark.title, null, false);
nUnique++;
} else {
diff --git a/src/com/android/browser/BrowserBookmarksAdapter.java b/src/com/android/browser/BrowserBookmarksAdapter.java
index 241b33b75..6efb55414 100644
--- a/src/com/android/browser/BrowserBookmarksAdapter.java
+++ b/src/com/android/browser/BrowserBookmarksAdapter.java
@@ -16,567 +16,42 @@
package com.android.browser;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.ContentObserver;
+import android.content.Context;
import android.database.Cursor;
-import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-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.WebView;
-import android.widget.BaseAdapter;
import android.widget.ImageView;
+import android.widget.ResourceCursorAdapter;
import android.widget.TextView;
-import java.io.ByteArrayOutputStream;
-
-class BrowserBookmarksAdapter extends BaseAdapter {
-
- private String mCurrentPage;
- private String mCurrentTitle;
- private Bitmap mCurrentThumbnail;
- private Cursor mCursor;
- private int mCount;
- private BrowserBookmarksPage mBookmarksPage;
- private ContentResolver mContentResolver;
- private boolean mDataValid;
- private BookmarkViewMode mViewMode;
- private boolean mMostVisited;
- private boolean mNeedsOffset;
- private int mExtraOffset;
-
+class BrowserBookmarksAdapter extends ResourceCursorAdapter {
/**
* 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,
- 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;
- mCurrentTitle = curTitle;
- mCurrentThumbnail = curThumbnail;
- mContentResolver = b.getContentResolver();
- mViewMode = BookmarkViewMode.LIST;
-
- String whereClause;
- // FIXME: Should have a default sort order that the user selects.
- String orderBy = Browser.BookmarkColumns.VISITS + " DESC";
- if (mostVisited) {
- whereClause = Browser.BookmarkColumns.VISITS + " != 0";
- } else {
- whereClause = Browser.BookmarkColumns.BOOKMARK + " = 1";
- }
- 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;
- }
-
- /**
- * Return a hashmap with one row's Title, Url, and favicon.
- * @param position Position in the list.
- * @return Bundle Stores title, url of row position, favicon, and id
- * for the url. Return a blank map if position is out of
- * range.
- */
- public Bundle getRow(int position) {
- Bundle map = new Bundle();
- if (position < mExtraOffset || position >= mCount) {
- return map;
- }
- mCursor.moveToPosition(position- mExtraOffset);
- String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
- map.putString(Browser.BookmarkColumns.TITLE,
- mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
- map.putString(Browser.BookmarkColumns.URL, url);
- byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
- if (data != null) {
- map.putParcelable(Browser.BookmarkColumns.FAVICON,
- BitmapFactory.decodeByteArray(data, 0, data.length));
- }
- map.putInt("id", mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX));
- return map;
- }
-
- /**
- * Update a row in the database with new information.
- * Requeries the database if the information has changed.
- * @param map Bundle storing id, title and url of new information
- */
- public void updateRow(Bundle map) {
-
- // Find the record
- int id = map.getInt("id");
- int position = -1;
- for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
- if (mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX) == id) {
- position = mCursor.getPosition();
- break;
- }
- }
- if (position < 0) {
- return;
- }
-
- mCursor.moveToPosition(position);
- ContentValues values = new ContentValues();
- String title = map.getString(Browser.BookmarkColumns.TITLE);
- if (!title.equals(mCursor
- .getString(Browser.HISTORY_PROJECTION_TITLE_INDEX))) {
- values.put(Browser.BookmarkColumns.TITLE, title);
- }
- String url = map.getString(Browser.BookmarkColumns.URL);
- if (!url.equals(mCursor.
- 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) {
- refreshList();
- }
- }
-
- /**
- * Delete a row from the database. Requeries the database.
- * Does nothing if the provided position is out of range.
- * @param position Position in the list.
- */
- public void deleteRow(int position) {
- if (position < mExtraOffset || position >= getCount()) {
- return;
- }
- mCursor.moveToPosition(position- mExtraOffset);
- String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
- String title = mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
- Bookmarks.removeFromBookmarks(null, mContentResolver, url, title);
- refreshList();
- }
-
- /**
- * Delete all bookmarks from the db. Requeries the database.
- * All bookmarks with become visited URLs or if never visited
- * are removed
*/
- public void deleteAllRows() {
- StringBuilder deleteIds = null;
- StringBuilder convertIds = null;
-
- for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
- String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
- WebIconDatabase.getInstance().releaseIconForPageUrl(url);
- int id = mCursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX);
- int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX);
- if (0 == numVisits) {
- if (deleteIds == null) {
- deleteIds = new StringBuilder();
- deleteIds.append("( ");
- } else {
- deleteIds.append(" OR ( ");
- }
- deleteIds.append(BookmarkColumns._ID);
- deleteIds.append(" = ");
- deleteIds.append(id);
- deleteIds.append(" )");
- } else {
- // It is no longer a bookmark, but it is still a visited site.
- if (convertIds == null) {
- convertIds = new StringBuilder();
- convertIds.append("( ");
- } else {
- convertIds.append(" OR ( ");
- }
- convertIds.append(BookmarkColumns._ID);
- convertIds.append(" = ");
- convertIds.append(id);
- convertIds.append(" )");
- }
- }
-
- if (deleteIds != null) {
- mContentResolver.delete(Browser.BOOKMARKS_URI, deleteIds.toString(),
- null);
- }
- if (convertIds != null) {
- ContentValues values = new ContentValues();
- values.put(Browser.BookmarkColumns.BOOKMARK, 0);
- mContentResolver.update(Browser.BOOKMARKS_URI, values,
- convertIds.toString(), null);
- }
- refreshList();
- }
-
- /**
- * Refresh list to recognize a change in the database.
- */
- public void refreshList() {
- mCursor.requery();
- mCount = mCursor.getCount() + mExtraOffset;
- notifyDataSetChanged();
- }
-
- /**
- * 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 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(final ContentResolver cr,
- final String originalUrl, final String url, final Bitmap favicon) {
- new AsyncTask<Void, Void, Void>() {
- protected Void doInBackground(Void... unused) {
- final Cursor c =
- queryBookmarksForUrl(cr, originalUrl, url, true);
- if (c == null) {
- return null;
- }
- if (c.moveToFirst()) {
- ContentValues values = new ContentValues();
- final ByteArrayOutputStream os =
- new ByteArrayOutputStream();
- favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
- values.put(Browser.BookmarkColumns.FAVICON,
- os.toByteArray());
- do {
- cr.update(ContentUris.withAppendedId(
- Browser.BOOKMARKS_URI, c.getInt(0)),
- values, null, null);
- } while (c.moveToNext());
- }
- c.close();
- return null;
- }
- }.execute();
+ public BrowserBookmarksAdapter(Context context) {
+ // Make sure to tell the CursorAdapter to avoid the observer and auto-requery
+ // since the Loader will do that for us.
+ super(context, R.layout.bookmark_thumbnail, null);
}
- /* package */ static Cursor queryBookmarksForUrl(ContentResolver cr,
- String originalUrl, String url, boolean onlyBookmarks) {
- if (cr == null || url == null) {
- return null;
- }
-
- // If originalUrl is null, just set it to url.
- if (originalUrl == null) {
- originalUrl = url;
- }
-
- // 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 + '?';
-
- // 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 + " LIKE ? || '%' OR "
- + BookmarkColumns.URL + " LIKE ? || '%'";
- 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);
- }
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ ImageView thumb = (ImageView) view.findViewById(R.id.thumb);
+ TextView tv = (TextView) view.findViewById(R.id.label);
- // 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;
- }
-
- /**
- * How many items should be displayed in the list.
- * @return Count of items.
- */
- public int getCount() {
- if (mDataValid) {
- return mCount;
- } else {
- return 0;
- }
- }
-
- public boolean areAllItemsEnabled() {
- return true;
- }
-
- public boolean isEnabled(int position) {
- return true;
- }
-
- /**
- * Get the data associated with the specified position in the list.
- * @param position Index of the item whose data we want.
- * @return The data at the specified position.
- */
- public Object getItem(int position) {
- return null;
- }
+ tv.setText(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
- /**
- * Get the row id associated with the specified position in the list.
- * @param position Index of the item whose row id we want.
- * @return The id of the item at the specified position.
- */
- public long getItemId(int position) {
- 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.
- * @param position Index of the item whose view we want.
- * @return A View corresponding to the data at the specified position.
- */
- public View getView(int position, View convertView, ViewGroup parent) {
- if (!mDataValid) {
- throw new IllegalStateException(
- "this should only be called when the cursor is valid");
- }
- if (position < 0 || position > mCount) {
- throw new AssertionError(
- "BrowserBookmarksAdapter tried to get a view out of range");
- }
- 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 = getScreenshot(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;
- } else {
- b = new AddNewBookmark(mBookmarksPage);
- }
- b.setUrl(mCurrentPage);
- return b;
- }
- 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);
- if (mMostVisited) {
- ((HistoryItem) convertView).setIsBookmark(
- getIsBookmark(position));
- }
- return convertView;
- }
-
- /**
- * Return the title for this item in the list.
- */
- public String getTitle(int position) {
- return getString(Browser.HISTORY_PROJECTION_TITLE_INDEX, position);
- }
-
- /**
- * Return the Url for this item in the list.
- */
- public String getUrl(int position) {
- return getString(Browser.HISTORY_PROJECTION_URL_INDEX, position);
- }
-
- /**
- * Return the screenshot for this item in the list.
- */
- public Bitmap getScreenshot(int position) {
- return getBitmap(Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX, position);
- }
-
- /**
- * 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(cursorIndex);
- if (data == null) {
- return null;
- }
- return BitmapFactory.decodeByteArray(data, 0, data.length);
- }
-
- /**
- * 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) {
- if (position < mExtraOffset || position > mCount) {
- return "";
- }
- mCursor.moveToPosition(position- mExtraOffset);
- return mCursor.getString(cursorIndex);
- }
-
- private void bind(BookmarkItem b, int position) {
- mCursor.moveToPosition(position- mExtraOffset);
-
- b.setName(mCursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
- String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
- b.setUrl(url);
- byte[] data = mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+ Bitmap thumbnail = null;
+ byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_THUMBNAIL);
if (data != null) {
- b.setFavicon(BitmapFactory.decodeByteArray(data, 0, data.length));
- } else {
- b.setFavicon(CombinedBookmarkHistoryActivity.getIconListenerSet()
- .getFavicon(url));
+ thumbnail = BitmapFactory.decodeByteArray(data, 0, data.length);
}
- }
- private class ChangeObserver extends ContentObserver {
- public ChangeObserver() {
- super(new Handler(Looper.getMainLooper()));
- }
-
- @Override
- public boolean deliverSelfNotifications() {
- return true;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- refreshList();
- }
- }
-
- private class MyDataSetObserver extends DataSetObserver {
- @Override
- public void onChanged() {
- mDataValid = true;
- notifyDataSetChanged();
- }
-
- @Override
- public void onInvalidated() {
- mDataValid = false;
- notifyDataSetInvalidated();
+ if (thumbnail == null) {
+ thumb.setImageResource(R.drawable.browser_thumbnail);
+ } else {
+ thumb.setImageBitmap(thumbnail);
}
}
}
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index dd01009f8..039aca08f 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -18,73 +18,232 @@ package com.android.browser;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.LoaderManager;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.CursorLoader;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.Loader;
import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
+import android.database.Cursor;
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.Rect;
-import android.graphics.RectF;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.ServiceManager;
-import android.provider.Browser;
-import android.text.IClipboard;
-import android.util.Log;
+import android.preference.PreferenceManager;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Accounts;
+import android.text.TextUtils;
+import android.util.Pair;
import android.view.ContextMenu;
-import android.view.Menu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.view.ContextMenu.ContextMenuInfo;
import android.webkit.WebIconDatabase.IconListener;
+import android.widget.Adapter;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
import android.widget.GridView;
-import android.widget.ListView;
+import android.widget.Spinner;
import android.widget.Toast;
-/*package*/ enum BookmarkViewMode { NONE, GRID, LIST }
+import java.util.ArrayList;
+import java.util.Stack;
+
/**
* 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 ListView mVerticalList;
- private BrowserBookmarksAdapter mBookmarksAdapter;
- private static final int BOOKMARKS_SAVE = 1;
- private boolean mDisableNewWindow;
- private BookmarkItem mContextHeader;
- private AddNewBookmark mAddHeader;
- private boolean mCanceled = false;
- private boolean mCreateShortcut;
- private boolean mMostVisited;
- private View mEmptyView;
- private int mIconSize;
- // 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";
+public class BrowserBookmarksPage extends Fragment implements View.OnCreateContextMenuListener,
+ LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, IconListener, OnClickListener,
+ OnItemSelectedListener {
+
+ static final int BOOKMARKS_SAVE = 1;
+ static final String LOGTAG = "browser";
+
+ static final int LOADER_BOOKMARKS = 1;
+ static final int LOADER_ACCOUNTS = 2;
+ static final int LOADER_ACCOUNTS_THEN_BOOKMARKS = 3;
+
+ static final String EXTRA_SHORTCUT = "create_shortcut";
+ static final String EXTRA_DISABLE_WINDOW = "disable_new_window";
+
+ public static final String PREF_ACCOUNT_TYPE = "acct_type";
+ public static final String PREF_ACCOUNT_NAME = "acct_name";
+
+ static final String DEFAULT_ACCOUNT = "local";
+
+ BookmarksHistoryCallbacks mCallbacks;
+ GridView mGrid;
+ Spinner mAccountSelector;
+ BrowserBookmarksAdapter mAdapter;
+ boolean mDisableNewWindow;
+ BookmarkItem mContextHeader;
+ boolean mCanceled = false;
+ boolean mCreateShortcut;
+ View mEmptyView;
+ View mContentView;
+ Stack<Pair<String, Uri>> mFolderStack = new Stack<Pair<String, Uri>>();
+ Button mUpButton;
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case LOADER_BOOKMARKS: {
+ String accountType = null;
+ String accountName = null;
+ if (args != null) {
+ accountType = args.getString(BookmarksLoader.ARG_ACCOUNT_TYPE);
+ accountName = args.getString(BookmarksLoader.ARG_ACCOUNT_NAME);
+ }
+ return new BookmarksLoader(getActivity(), accountType, accountName);
+ }
+
+ case LOADER_ACCOUNTS:
+ case LOADER_ACCOUNTS_THEN_BOOKMARKS: {
+ return new CursorLoader(getActivity(), Accounts.CONTENT_URI,
+ new String[] { Accounts.ACCOUNT_TYPE, Accounts.ACCOUNT_NAME }, null, null,
+ null);
+ }
+ }
+ throw new UnsupportedOperationException("Unknown loader id " + id);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+ switch (loader.getId()) {
+ case LOADER_BOOKMARKS: {
+ // Set the visibility of the empty vs. content views
+ if (cursor == null || cursor.getCount() == 0) {
+ mEmptyView.setVisibility(View.VISIBLE);
+ mGrid.setVisibility(View.GONE);
+ } else {
+ mEmptyView.setVisibility(View.GONE);
+ mGrid.setVisibility(View.VISIBLE);
+ }
+
+ // Fill in the "up" button if needed
+ BookmarksLoader bl = (BookmarksLoader) loader;
+ String path = bl.getUri().getPath();
+ boolean rootFolder =
+ BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER.getPath().equals(path);
+ if (rootFolder) {
+ mUpButton.setText(R.string.defaultBookmarksUpButton);
+ mUpButton.setEnabled(false);
+ } else {
+ mUpButton.setText(mFolderStack.peek().first);
+ mUpButton.setEnabled(true);
+ }
+ mUpButton.setVisibility(View.VISIBLE);
+
+ // Give the new data to the adapter
+ mAdapter.changeCursor(cursor);
+
+ break;
+ }
+
+ case LOADER_ACCOUNTS:
+ case LOADER_ACCOUNTS_THEN_BOOKMARKS: {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
+ getActivity());
+ String storedAccountType = prefs.getString(PREF_ACCOUNT_TYPE, null);
+ String storedAccountName = prefs.getString(PREF_ACCOUNT_NAME, null);
+ String accountType =
+ TextUtils.isEmpty(storedAccountType) ? DEFAULT_ACCOUNT : storedAccountType;
+ String accountName =
+ TextUtils.isEmpty(storedAccountName) ? DEFAULT_ACCOUNT : storedAccountName;
+
+ Bundle args = null;
+ if (cursor == null || !cursor.moveToFirst()) {
+ // No accounts, set the prefs to the default
+ accountType = DEFAULT_ACCOUNT;
+ accountName = DEFAULT_ACCOUNT;
+ mAccountSelector.setVisibility(View.GONE);
+ } else {
+ int accountPosition = -1;
+
+ if (!DEFAULT_ACCOUNT.equals(accountType) &&
+ !DEFAULT_ACCOUNT.equals(accountName)) {
+ // Check to see if the account in prefs still exists
+ cursor.moveToFirst();
+ do {
+ if (accountType.equals(cursor.getString(0))
+ && accountName.equals(cursor.getString(1))) {
+ accountPosition = cursor.getPosition();
+ break;
+ }
+ } while (cursor.moveToNext());
+ }
+
+ if (accountPosition == -1) {
+ // No account is set in prefs and there is at least one,
+ // so pick the first one as the default
+ cursor.moveToFirst();
+ accountType = cursor.getString(0);
+ accountName = cursor.getString(1);
+ accountPosition = 0;
+ }
+
+ args = new Bundle();
+ args.putString(BookmarksLoader.ARG_ACCOUNT_TYPE, accountType);
+ args.putString(BookmarksLoader.ARG_ACCOUNT_NAME, accountName);
+
+ // Setup the account selector if there is more than 1 account
+ if (cursor.getCount() > 1) {
+ ArrayList<String> accounts = new ArrayList<String>();
+ cursor.moveToFirst();
+ do {
+ accounts.add(cursor.getString(1));
+ } while (cursor.moveToNext());
+
+ mAccountSelector.setAdapter(new ArrayAdapter<String>(getActivity(),
+ android.R.layout.simple_list_item_1, android.R.id.text1, accounts));
+ mAccountSelector.setVisibility(View.VISIBLE);
+ mAccountSelector.setSelection(accountPosition);
+ }
+ }
+ if (!accountType.equals(storedAccountType)
+ || !accountName.equals(storedAccountName)) {
+ prefs.edit()
+ .putString(PREF_ACCOUNT_TYPE, accountType)
+ .putString(PREF_ACCOUNT_NAME, accountName)
+ .apply();
+ }
+ if (loader.getId() == LOADER_ACCOUNTS_THEN_BOOKMARKS) {
+ getLoaderManager().initLoader(LOADER_BOOKMARKS, args, this);
+ }
+
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view == mUpButton) {
+ Pair<String, Uri> pair = mFolderStack.pop();
+ BookmarksLoader loader =
+ (BookmarksLoader) ((Loader) getLoaderManager().getLoader(LOADER_BOOKMARKS));
+ loader.setUri(pair.second);
+ loader.forceLoad();
+ }
+ }
@Override
public boolean onContextItemSelected(MenuItem item) {
+ final Activity activity = getActivity();
// It is possible that the view has been canceled when we get to
// this point as back has a higher priority
if (mCanceled) {
@@ -98,9 +257,6 @@ public class BrowserBookmarksPage extends Activity implements
}
switch (item.getItemId()) {
- case R.id.new_context_menu_id:
- saveCurrentPage();
- break;
case R.id.open_context_menu_id:
loadUrl(i.position);
break;
@@ -108,533 +264,272 @@ public class BrowserBookmarksPage extends Activity implements
editBookmark(i.position);
break;
case R.id.shortcut_context_menu_id:
- final Intent send = createShortcutIntent(i.position);
- send.setAction(INSTALL_SHORTCUT);
- sendBroadcast(send);
+ activity.sendBroadcast(createShortcutIntent(i.position));
break;
case R.id.delete_context_menu_id:
- if (mMostVisited) {
- Browser.deleteFromHistory(getContentResolver(),
- getUrl(i.position));
- refreshList();
- } else {
- displayRemoveBookmarkDialog(i.position);
- }
+ displayRemoveBookmarkDialog(i.position);
break;
case R.id.new_window_context_menu_id:
openInNewWindow(i.position);
break;
- case R.id.share_link_context_menu_id:
- BrowserActivity.sharePage(BrowserBookmarksPage.this,
- mBookmarksAdapter.getTitle(i.position), getUrl(i.position),
- getFavicon(i.position),
- mBookmarksAdapter.getScreenshot(i.position));
+ case R.id.share_link_context_menu_id: {
+ Cursor cursor = (Cursor) mAdapter.getItem(i.position);
+ BrowserActivity.sharePage(activity,
+ cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE),
+ cursor.getString(BookmarksLoader.COLUMN_INDEX_URL),
+ getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON),
+ getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_THUMBNAIL));
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();
+ case R.id.homepage_context_menu_id: {
+ BrowserSettings.getInstance().setHomePage(activity, getUrl(i.position));
+ Toast.makeText(activity, 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();
- }
+ case R.id.save_to_bookmarks_menu_id: {
+ Cursor cursor = (Cursor) mAdapter.getItem(i.position);
+ String name = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
+ String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
// 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);
- }
+ Bookmarks.removeFromBookmarks(activity, activity.getContentResolver(), url, name);
break;
+ }
default:
return super.onContextItemSelected(item);
}
return true;
}
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- AdapterView.AdapterContextMenuInfo i =
- (AdapterView.AdapterContextMenuInfo) menuInfo;
-
- MenuInflater inflater = getMenuInflater();
- if (mMostVisited) {
- inflater.inflate(R.menu.historycontext, menu);
- } else {
- inflater.inflate(R.menu.bookmarkscontext, menu);
- }
-
- if (0 == i.position && !mMostVisited) {
- menu.setGroupVisible(R.id.CONTEXT_MENU, false);
- if (mAddHeader == null) {
- mAddHeader = new AddNewBookmark(BrowserBookmarksPage.this);
- } else if (mAddHeader.getParent() != null) {
- ((ViewGroup) mAddHeader.getParent()).
- removeView(mAddHeader);
- }
- mAddHeader.setUrl(getIntent().getStringExtra("url"));
- menu.setHeaderView(mAddHeader);
- return;
- }
- 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);
- }
- if (mViewMode == BookmarkViewMode.GRID) {
- mBookmarksAdapter.populateBookmarkItem(mContextHeader,
- i.position);
- } else {
- BookmarkItem b = (BookmarkItem) i.targetView;
- b.copyTo(mContextHeader);
- }
- menu.setHeaderView(mContextHeader);
+ Bitmap getBitmap(Cursor cursor, int columnIndex) {
+ byte[] data = cursor.getBlob(columnIndex);
+ if (data == null) {
+ return null;
}
+ return BitmapFactory.decodeByteArray(data, 0, data.length);
+ }
- /**
- * Create a new BrowserBookmarksPage.
- */
@Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
- // Grab the app icon size as a resource.
- mIconSize = getResources().getDimensionPixelSize(
- android.R.dimen.app_icon_size);
+ final Activity activity = getActivity();
+ MenuInflater inflater = activity.getMenuInflater();
+ inflater.inflate(R.menu.bookmarkscontext, menu);
- Intent intent = getIntent();
- if (Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) {
- mCreateShortcut = true;
+ if (mDisableNewWindow) {
+ menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
}
- mDisableNewWindow = intent.getBooleanExtra("disable_new_window",
- false);
- mMostVisited = intent.getBooleanExtra("mostVisited", false);
- if (mCreateShortcut) {
- setTitle(R.string.browser_bookmarks_page_bookmarks_text);
+ if (mContextHeader == null) {
+ mContextHeader = new BookmarkItem(activity);
+ } else if (mContextHeader.getParent() != null) {
+ ((ViewGroup) mContextHeader.getParent()).removeView(mContextHeader);
}
- setContentView(R.layout.empty_history);
- mEmptyView = findViewById(R.id.empty_view);
- mEmptyView.setVisibility(View.GONE);
-
- SharedPreferences p = getPreferences(MODE_PRIVATE);
+ populateBookmarkItem(mAdapter, mContextHeader, info.position);
- // 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);
-
- final boolean createShortcut = mCreateShortcut;
- final boolean mostVisited = mMostVisited;
- final String url = intent.getStringExtra("url");
- final String title = intent.getStringExtra("title");
- final Bitmap thumbnail =
- (Bitmap) intent.getParcelableExtra("thumbnail");
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... unused) {
- BrowserBookmarksAdapter adapter = new BrowserBookmarksAdapter(
- BrowserBookmarksPage.this,
- url,
- title,
- thumbnail,
- createShortcut,
- mostVisited);
- mHandler.obtainMessage(ADAPTER_CREATED, adapter).sendToTarget();
- return null;
- }
- }.execute();
+ menu.setHeaderView(mContextHeader);
}
- @Override
- protected void onDestroy() {
- mHandler.removeCallbacksAndMessages(null);
- super.onDestroy();
+ private void populateBookmarkItem(BrowserBookmarksAdapter adapter, BookmarkItem item,
+ int position) {
+ Cursor cursor = (Cursor) mAdapter.getItem(position);
+ String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
+ item.setUrl(url);
+ item.setName(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
+ Bitmap bitmap = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
+ if (bitmap == null) {
+ bitmap = CombinedBookmarkHistoryActivity.getIconListenerSet().getFavicon(url);
+ }
+ item.setFavicon(bitmap);
}
/**
- * Set the ContentView to be either the grid of thumbnails or the vertical
- * list.
+ * Create a new BrowserBookmarksPage.
*/
- private void switchViewMode(BookmarkViewMode viewMode) {
- if (mViewMode == viewMode) {
- return;
- }
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
- mViewMode = viewMode;
+ Bundle args = getArguments();
+ mCreateShortcut = args == null ? false : args.getBoolean("create_shortcut", false);
+ mDisableNewWindow = args == null ? false : args.getBoolean("disable_new_window", false);
+ }
- // 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.apply();
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mCallbacks = (BookmarksHistoryCallbacks) activity;
+ }
- if (mBookmarksAdapter != null) {
- mBookmarksAdapter.switchViewMode(viewMode);
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ Context context = getActivity();
+
+ View root = inflater.inflate(R.layout.bookmarks, container, false);
+ mEmptyView = root.findViewById(android.R.id.empty);
+ mContentView = root.findViewById(android.R.id.content);
+
+ mGrid = (GridView) root.findViewById(R.id.grid);
+ mGrid.setOnItemClickListener(this);
+ mGrid.setColumnWidth(BrowserActivity.getDesiredThumbnailWidth(getActivity()));
+ if (!mCreateShortcut) {
+ mGrid.setOnCreateContextMenuListener(this);
}
- if (mViewMode == BookmarkViewMode.GRID) {
- if (mGridPage == null) {
- mGridPage = new GridView(this);
- if (mBookmarksAdapter != null) {
- 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);
- }
+
+ mAccountSelector = (Spinner) root.findViewById(R.id.accounts);
+ mAccountSelector.setOnItemSelectedListener(this);
+ mAccountSelector.setVisibility(View.INVISIBLE);
+
+ mUpButton = (Button) root.findViewById(R.id.up);
+ mUpButton.setEnabled(false);
+ mUpButton.setOnClickListener(this);
+ mUpButton.setVisibility(View.GONE);
+
+ mAdapter = new BrowserBookmarksAdapter(getActivity());
+ mGrid.setAdapter(mAdapter);
+
+ // Start the loaders
+ LoaderManager lm = getLoaderManager();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String accountType = prefs.getString(PREF_ACCOUNT_TYPE, null);
+ String accountName = prefs.getString(PREF_ACCOUNT_NAME, null);
+ if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
+ // There is an account set, load up that one
+ Bundle args = null;
+ if (!DEFAULT_ACCOUNT.equals(accountType) && !DEFAULT_ACCOUNT.equals(accountName)) {
+ args = new Bundle();
+ args.putString(BookmarksLoader.ARG_ACCOUNT_TYPE, accountType);
+ args.putString(BookmarksLoader.ARG_ACCOUNT_NAME, accountName);
}
+ lm.initLoader(LOADER_BOOKMARKS, args, this);
+ lm.initLoader(LOADER_ACCOUNTS, null, this);
} else {
- if (null == mVerticalList) {
- ListView listView = new ListView(this);
- if (mBookmarksAdapter != null) {
- 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);
- }
- }
+ // No account set, load them first
+ lm.initLoader(LOADER_ACCOUNTS_THEN_BOOKMARKS, null, this);
}
+
+ // Add our own listener in case there are favicons that have yet to be loaded.
+ CombinedBookmarkHistoryActivity.getIconListenerSet().addListener(this);
+
+ return root;
}
- private static final ViewGroup.LayoutParams FULL_SCREEN_PARAMS
- = new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT);
-
- private static final int SAVE_CURRENT_PAGE = 1000;
- private static final int ADAPTER_CREATED = 1001;
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case SAVE_CURRENT_PAGE:
- saveCurrentPage();
- break;
- case ADAPTER_CREATED:
- mBookmarksAdapter = (BrowserBookmarksAdapter) msg.obj;
- mBookmarksAdapter.switchViewMode(mViewMode);
- if (mGridPage != null) {
- mGridPage.setAdapter(mBookmarksAdapter);
- }
- if (mVerticalList != null) {
- mVerticalList.setAdapter(mBookmarksAdapter);
- }
- // Add our own listener in case there are favicons that
- // have yet to be loaded.
- if (mMostVisited) {
- IconListener listener = new IconListener() {
- public void onReceivedIcon(String url,
- Bitmap icon) {
- if (mGridPage != null) {
- mGridPage.setAdapter(mBookmarksAdapter);
- }
- if (mVerticalList != null) {
- mVerticalList.setAdapter(mBookmarksAdapter);
- }
- }
- };
- CombinedBookmarkHistoryActivity.getIconListenerSet()
- .addListener(listener);
- }
- break;
- }
+ @Override
+ public void onReceivedIcon(String url, Bitmap icon) {
+ // A new favicon has been loaded, so let anything attached to the adapter know about it
+ // so new icons will be loaded.
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onItemClick(AdapterView parent, View v, int position, long id) {
+ // 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(LOGTAG, "item clicked when dismissing");
+ return;
}
- };
-
- private AdapterView.OnItemClickListener mListener = new AdapterView.OnItemClickListener() {
- public void onItemClick(AdapterView parent, View v, int position, long id) {
- // 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(LOGTAG, "item clicked when dismissing");
- return;
- }
- if (!mCreateShortcut) {
- if (0 == position && !mMostVisited) {
- // XXX: Work-around for a framework issue.
- mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE);
- } else {
- loadUrl(position);
- }
- } else {
- final Intent intent = createShortcutIntent(position);
- setResultToParent(RESULT_OK, intent);
- finish();
- }
+
+ if (mCreateShortcut) {
+ Intent intent = createShortcutIntent(position);
+ // the activity handles the intent in startActivityFromFragment
+ startActivity(intent);
+ return;
}
- };
- 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));
- long urlHash = url.hashCode();
- long uniqueId = (urlHash << 32) | shortcutIntent.hashCode();
- shortcutIntent.putExtra(Browser.EXTRA_APPLICATION_ID,
- Long.toString(uniqueId));
- i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
- i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
- // Use the apple-touch-icon if available
- if (touchIcon != null) {
- // Make a copy so we can modify the pixels. We can't use
- // createScaledBitmap or copy since they will preserve the config
- // and lose the ability to add alpha.
- Bitmap bm = Bitmap.createBitmap(mIconSize, mIconSize,
- Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bm);
- Rect src = new Rect(0, 0, touchIcon.getWidth(),
- touchIcon.getHeight());
- Rect dest = new Rect(0, 0, bm.getWidth(), bm.getHeight());
-
- // Paint used for scaling the bitmap and drawing the rounded rect.
- Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- paint.setFilterBitmap(true);
- canvas.drawBitmap(touchIcon, src, dest, paint);
-
- // 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, bm.getWidth(), bm.getHeight());
- rect.inset(1, 1);
- path.addRoundRect(rect, 8f, 8f, Path.Direction.CW);
-
- // Reuse the paint and clear the outside of the rectangle.
- paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
- canvas.drawPath(path, paint);
-
- i.putExtra(Intent.EXTRA_SHORTCUT_ICON, bm);
+ Cursor cursor = (Cursor) mAdapter.getItem(position);
+ boolean isFolder = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
+ if (!isFolder) {
+ mCallbacks.onUrlSelected(getUrl(position), false);
} 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));
+ String title;
+ if (mFolderStack.size() != 0) {
+ title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
} else {
- Bitmap icon = BitmapFactory.decodeResource(getResources(),
- R.drawable.ic_launcher_shortcut_browser_bookmark_icon);
-
- // 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);
-
- final float density =
- getResources().getDisplayMetrics().density;
- // Create a rectangle that is slightly wider than the favicon
- final float iconSize = 16 * density; // 16x16 favicon
- final float padding = 2 * density; // white padding around icon
- final float rectSize = iconSize + 2 * padding;
-
- final Rect iconBounds =
- new Rect(0, 0, icon.getWidth(), icon.getHeight());
- final float x = iconBounds.exactCenterX() - (rectSize / 2);
- // Note: Subtract 2 dip from the y position since the box is
- // slightly higher than center. Use padding since it is already
- // 2 * density.
- final float y = iconBounds.exactCenterY() - (rectSize / 2)
- - padding;
- RectF r = new RectF(x, y, x + 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);
+ // TODO localize
+ title = "Bookmarks";
}
+ LoaderManager manager = getLoaderManager();
+ BookmarksLoader loader =
+ (BookmarksLoader) ((Loader) manager.getLoader(LOADER_BOOKMARKS));
+ mFolderStack.push(new Pair(title, loader.getUri()));
+ Uri uri = ContentUris.withAppendedId(
+ BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, id);
+ loader.setUri(uri);
+ loader.forceLoad();
}
- // Do not allow duplicate items
- i.putExtra("duplicate", false);
- return i;
- }
-
- private void saveCurrentPage() {
- Intent i = new Intent(BrowserBookmarksPage.this,
- AddBookmarkPage.class);
- i.putExtras(getIntent());
- startActivityForResult(i, BOOKMARKS_SAVE);
- }
-
- private void loadUrl(int position) {
- Intent intent = (new Intent()).setAction(getUrl(position));
- setResultToParent(RESULT_OK, intent);
- finish();
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- boolean result = super.onCreateOptionsMenu(menu);
- if (!mCreateShortcut && !mMostVisited) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.bookmarks, menu);
- return true;
- }
- return result;
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ Adapter adapter = parent.getAdapter();
+ String accountType = "com.google";
+ String accountName = adapter.getItem(position).toString();
+
+ // Remember the selection for later
+ PreferenceManager.getDefaultSharedPreferences(getActivity()).edit()
+ .putString(PREF_ACCOUNT_TYPE, accountType)
+ .putString(PREF_ACCOUNT_NAME, accountName)
+ .apply();
+
+ Bundle args = new Bundle();
+ args.putString(BookmarksLoader.ARG_ACCOUNT_TYPE, accountType);
+ args.putString(BookmarksLoader.ARG_ACCOUNT_NAME, accountName);
+ getLoaderManager().restartLoader(LOADER_BOOKMARKS, args, this);
}
@Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- boolean result = super.onPrepareOptionsMenu(menu);
- if (mCreateShortcut || mMostVisited || mBookmarksAdapter == null
- || 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;
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do nothing
}
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- 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;
+ private Intent createShortcutIntent(int position) {
+ Cursor cursor = (Cursor) mAdapter.getItem(position);
+ String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
+ String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
+ Bitmap touchIcon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_TOUCH_ICON);
+ Bitmap favicon = getBitmap(cursor, BookmarksLoader.COLUMN_INDEX_FAVICON);
+ return BookmarkUtils.createAddToHomeIntent(getActivity(), url, title, touchIcon, favicon);
+ }
- default:
- return super.onOptionsItemSelected(item);
- }
- return true;
+ private void loadUrl(int position) {
+ mCallbacks.onUrlSelected(getUrl(position), false);
}
private void openInNewWindow(int position) {
- Bundle b = new Bundle();
- b.putBoolean("new_window", true);
- setResultToParent(RESULT_OK,
- (new Intent()).setAction(getUrl(position)).putExtras(b));
-
- finish();
+ mCallbacks.onUrlSelected(getUrl(position), true);
}
-
private void editBookmark(int position) {
- Intent intent = new Intent(BrowserBookmarksPage.this,
- AddBookmarkPage.class);
- intent.putExtra("bookmark", getRow(position));
+ Intent intent = new Intent(getActivity(), AddBookmarkPage.class);
+ Cursor cursor = (Cursor) mAdapter.getItem(position);
+ Bundle item = new Bundle();
+ item.putString(BrowserContract.Bookmarks.TITLE,
+ cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
+ item.putString(BrowserContract.Bookmarks.URL,
+ cursor.getString(BookmarksLoader.COLUMN_INDEX_URL));
+ byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON);
+ if (data != null) {
+ item.putParcelable(BrowserContract.Bookmarks.FAVICON,
+ BitmapFactory.decodeByteArray(data, 0, data.length));
+ }
+ item.putInt("id", cursor.getInt(BookmarksLoader.COLUMN_INDEX_ID));
+ intent.putExtra("bookmark", item);
startActivityForResult(intent, BOOKMARKS_SAVE);
}
@Override
- protected void onActivityResult(int requestCode, int resultCode,
- Intent data) {
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch(requestCode) {
case BOOKMARKS_SAVE:
- if (resultCode == RESULT_OK) {
+ if (resultCode == Activity.RESULT_OK) {
Bundle extras;
if (data != null && (extras = data.getExtras()) != null) {
// If there are extras, then we need to save
@@ -642,118 +537,88 @@ public class BrowserBookmarksPage extends Activity implements
String title = extras.getString("title");
String url = extras.getString("url");
if (title != null && url != null) {
- mBookmarksAdapter.updateRow(extras);
+ updateRow(extras);
}
- } else {
- // extras == null then a new bookmark was added to
- // the database.
- refreshList();
}
}
break;
- default:
+ }
+ }
+
+ /**
+ * Update a row in the database with new information.
+ * @param map Bundle storing id, title and url of new information
+ */
+ public void updateRow(Bundle map) {
+
+ // Find the record
+ int id = map.getInt("id");
+ int position = -1;
+ Cursor cursor = mAdapter.getCursor();
+ for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
+ if (cursor.getInt(BookmarksLoader.COLUMN_INDEX_ID) == id) {
+ position = cursor.getPosition();
break;
+ }
+ }
+ if (position < 0) {
+ return;
+ }
+
+ cursor.moveToPosition(position);
+ ContentValues values = new ContentValues();
+ String title = map.getString(BrowserContract.Bookmarks.TITLE);
+ if (!title.equals(cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE))) {
+ values.put(BrowserContract.Bookmarks.TITLE, title);
+ }
+ String url = map.getString(BrowserContract.Bookmarks.URL);
+ if (!url.equals(cursor.getString(BookmarksLoader.COLUMN_INDEX_URL))) {
+ values.put(BrowserContract.Bookmarks.URL, url);
+ }
+
+ if (map.getBoolean("invalidateThumbnail") == true) {
+ values.putNull(BrowserContract.Bookmarks.THUMBNAIL);
+ }
+
+ if (values.size() > 0) {
+ getActivity().getContentResolver().update(
+ ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id),
+ values, null, null);
}
}
- private void displayRemoveBookmarkDialog(int position) {
+ private void displayRemoveBookmarkDialog(final int position) {
// Put up a dialog asking if the user really wants to
// delete the bookmark
- final int deletePos = position;
- new AlertDialog.Builder(this)
+ Cursor cursor = (Cursor) mAdapter.getItem(position);
+ Context context = getActivity();
+ final ContentResolver resolver = context.getContentResolver();
+ final Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI,
+ cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
+
+ new AlertDialog.Builder(context)
.setTitle(R.string.delete_bookmark)
.setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(getText(R.string.delete_bookmark_warning).toString().replace(
- "%s", getBookmarkTitle(deletePos)))
+ .setMessage(context.getString(R.string.delete_bookmark_warning,
+ cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE)))
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
- deleteBookmark(deletePos);
+ resolver.delete(uri, null, null);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}
- /**
- * Refresh the shown list after the database has changed.
- */
- private void refreshList() {
- if (mBookmarksAdapter == null) return;
- mBookmarksAdapter.refreshList();
- }
-
- /**
- * Return a hashmap representing the currently highlighted row.
- */
- public Bundle getRow(int position) {
- return mBookmarksAdapter == null ? null
- : mBookmarksAdapter.getRow(position);
- }
-
- /**
- * Return the url of the currently highlighted row.
- */
- public String getUrl(int position) {
- return mBookmarksAdapter == null ? null
- : mBookmarksAdapter.getUrl(position);
- }
-
- /**
- * Return the favicon of the currently highlighted row.
- */
- public Bitmap getFavicon(int position) {
- return mBookmarksAdapter == null ? null
- : mBookmarksAdapter.getFavicon(position);
- }
-
- private Bitmap getTouchIcon(int position) {
- return mBookmarksAdapter == null ? null
- : mBookmarksAdapter.getTouchIcon(position);
+ private String getUrl(int position) {
+ Cursor cursor = (Cursor) mAdapter.getItem(position);
+ return cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
}
private void copy(CharSequence text) {
- try {
- IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
- if (clip != null) {
- clip.setClipboardText(text);
- }
- } catch (android.os.RemoteException e) {
- Log.e(LOGTAG, "Copy failed", e);
- }
- }
-
- public String getBookmarkTitle(int position) {
- return mBookmarksAdapter == null ? null
- : mBookmarksAdapter.getTitle(position);
- }
-
- /**
- * Delete the currently highlighted row.
- */
- public void deleteBookmark(int position) {
- if (mBookmarksAdapter == null) return;
- mBookmarksAdapter.deleteRow(position);
- }
-
- @Override
- public void onBackPressed() {
- setResultToParent(RESULT_CANCELED, null);
- mCanceled = true;
- super.onBackPressed();
- }
-
- // This Activity is generally a sub-Activity of
- // CombinedBookmarkHistoryActivity. In that situation, we need to pass our
- // result code up to our parent. However, if someone calls this Activity
- // directly, then this has no parent, and it needs to set it on itself.
- private void setResultToParent(int resultCode, Intent data) {
- Activity parent = getParent();
- if (parent == null) {
- setResult(resultCode, data);
- } else {
- ((CombinedBookmarkHistoryActivity) parent).setResultFromChild(
- resultCode, data);
- }
+ ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(
+ Context.CLIPBOARD_SERVICE);
+ cm.setPrimaryClip(ClipData.newRawUri(null, null, Uri.parse(text.toString())));
}
}
diff --git a/src/com/android/browser/BrowserDownloadAdapter.java b/src/com/android/browser/BrowserDownloadAdapter.java
index 0f8f721e2..6f498b694 100644
--- a/src/com/android/browser/BrowserDownloadAdapter.java
+++ b/src/com/android/browser/BrowserDownloadAdapter.java
@@ -56,7 +56,8 @@ public class BrowserDownloadAdapter extends DateSortedExpandableListAdapter {
private int mDateColumnId;
public BrowserDownloadAdapter(Context context, Cursor c, int index) {
- super(context, c, index);
+ super(context, index);
+ changeCursor(c);
mTitleColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TITLE);
mDescColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_DESCRIPTION);
mStatusColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS);
diff --git a/src/com/android/browser/BrowserDownloadPage.java b/src/com/android/browser/BrowserDownloadPage.java
index 18faf8b36..a897f9939 100644
--- a/src/com/android/browser/BrowserDownloadPage.java
+++ b/src/com/android/browser/BrowserDownloadPage.java
@@ -18,13 +18,10 @@ package com.android.browser;
import android.app.AlertDialog;
import android.app.ExpandableListActivity;
-import android.content.ActivityNotFoundException;
+import android.content.ContentUris;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.ContentUris;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
@@ -34,17 +31,13 @@ import android.provider.Downloads;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
import android.view.Menu;
-import android.view.MenuItem;
import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.AdapterView;
import android.widget.ExpandableListView;
import java.io.File;
-import java.util.List;
/**
* View showing the user's current browser downloads
@@ -63,6 +56,7 @@ public class BrowserDownloadPage extends ExpandableListActivity {
// Only meaningful while a ContentObserver is registered. The ContextMenu
// will be reopened on this View.
private View mSelectedView;
+ private Handler mHandler;
private final static String LOGTAG = "BrowserDownloadPage";
@Override
@@ -85,7 +79,7 @@ public class BrowserDownloadPage extends ExpandableListActivity {
Downloads.Impl._DATA,
Downloads.Impl.COLUMN_MIME_TYPE},
null, Downloads.Impl.COLUMN_LAST_MODIFICATION + " DESC");
-
+ mHandler = new Handler();
// only attach everything to the listbox if we can access
// the download database. Otherwise, just show it empty
if (mDownloadCursor != null) {
@@ -241,8 +235,8 @@ public class BrowserDownloadPage extends ExpandableListActivity {
*/
private class ChangeObserver extends ContentObserver {
private final Uri mTrack;
- public ChangeObserver(Uri track) {
- super(new Handler());
+ public ChangeObserver(Uri track, Handler handler) {
+ super(handler);
mTrack = track;
}
@@ -313,7 +307,7 @@ public class BrowserDownloadPage extends ExpandableListActivity {
getContentResolver().unregisterContentObserver(
mContentObserver);
}
- mContentObserver = new ChangeObserver(track);
+ mContentObserver = new ChangeObserver(track, mHandler);
mSelectedView = v;
getContentResolver().registerContentObserver(track, false,
mContentObserver);
diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java
index 23080f86b..2295804e5 100644
--- a/src/com/android/browser/BrowserHistoryPage.java
+++ b/src/com/android/browser/BrowserHistoryPage.java
@@ -17,171 +17,193 @@
package com.android.browser;
import android.app.Activity;
-import android.app.ExpandableListActivity;
+import android.app.Fragment;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ClipboardManager;
import android.content.Context;
+import android.content.CursorLoader;
import android.content.Intent;
+import android.content.Loader;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
-import android.os.ServiceManager;
import android.provider.Browser;
-import android.text.IClipboard;
-import android.util.Log;
+import android.provider.BrowserContract.History;
import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+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.ContextMenu.ContextMenuInfo;
-import android.view.ViewStub;
import android.webkit.WebIconDatabase.IconListener;
-import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
+import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.Toast;
/**
* Activity for displaying the browser's history, divided into
* days of viewing.
*/
-public class BrowserHistoryPage extends ExpandableListActivity {
- private HistoryAdapter mAdapter;
- private boolean mDisableNewWindow;
- private HistoryItem mContextHeader;
+public class BrowserHistoryPage extends Fragment
+ implements LoaderCallbacks<Cursor>, OnChildClickListener {
+
+ static final int LOADER_HISTORY = 1;
- private final static String LOGTAG = "browser";
+ BookmarksHistoryCallbacks mCallbacks;
+ ExpandableListView mList;
+ View mEmptyView;
+ HistoryAdapter mAdapter;
+ boolean mDisableNewWindow;
+ HistoryItem mContextHeader;
// Implementation of WebIconDatabase.IconListener
- private class IconReceiver implements IconListener {
+ class IconReceiver implements IconListener {
+ @Override
public void onReceivedIcon(String url, Bitmap icon) {
- setListAdapter(mAdapter);
+ mAdapter.notifyDataSetChanged();
}
}
+
// Instance of IconReceiver
- private final IconReceiver mIconReceiver = new IconReceiver();
-
- /**
- * Report back to the calling activity to load a site.
- * @param url Site to load.
- * @param newWindow True if the URL should be loaded in a new window
- */
- private void loadUrl(String url, boolean newWindow) {
- Intent intent = new Intent().setAction(url);
- if (newWindow) {
- Bundle b = new Bundle();
- b.putBoolean("new_window", true);
- intent.putExtras(b);
- }
- setResultToParent(RESULT_OK, intent);
- finish();
+ final IconReceiver mIconReceiver = new IconReceiver();
+
+ static interface HistoryQuery {
+ static final String[] PROJECTION = new String[] {
+ History._ID, // 0
+ History.DATE_LAST_VISITED, // 1
+ History.TITLE, // 2
+ History.URL, // 3
+ History.FAVICON, // 4
+ };
+
+ static final int INDEX_ID = 0;
+ static final int INDEX_DATE_LAST_VISITED = 1;
+ static final int INDEX_TITE = 2;
+ static final int INDEX_URL = 3;
+ static final int INDEX_FAVICON = 4;
}
-
+
private void copy(CharSequence text) {
- try {
- IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
- if (clip != null) {
- clip.setClipboardText(text);
+ ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(
+ Context.CLIPBOARD_SERVICE);
+ cm.setText(text);
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ switch (id) {
+ case LOADER_HISTORY: {
+ CursorLoader loader = new CursorLoader(getActivity(), History.CONTENT_URI,
+ HistoryQuery.PROJECTION, null, null, null);
+ return loader;
+ }
+
+ default: {
+ throw new IllegalArgumentException();
}
- } catch (android.os.RemoteException e) {
- Log.e(LOGTAG, "Copy failed", e);
}
}
@Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- setTitle(R.string.browser_history);
-
- 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";
-
- Cursor cursor = managedQuery(
- Browser.BOOKMARKS_URI,
- Browser.HISTORY_PROJECTION,
- whereClause, null, orderBy);
-
- mAdapter = new HistoryAdapter(this, cursor,
- Browser.HISTORY_PROJECTION_DATE_INDEX);
- setListAdapter(mAdapter);
- final ExpandableListView list = getExpandableListView();
- list.setOnCreateContextMenuListener(this);
- View v = new ViewStub(this, R.layout.empty_history);
- addContentView(v, new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- list.setEmptyView(v);
- // Do not post the runnable if there is nothing in the list.
- if (list.getExpandableListAdapter().getGroupCount() > 0) {
- list.post(new Runnable() {
- public void run() {
- // In case the history gets cleared before this event
- // happens.
- if (list.getExpandableListAdapter().getGroupCount() > 0) {
- list.expandGroup(0);
- }
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ switch (loader.getId()) {
+ case LOADER_HISTORY: {
+ mAdapter.changeCursor(data);
+
+ // Add an empty view late, so it does not claim an empty
+ // history before the adapter is present
+ mList.setEmptyView(mEmptyView);
+
+ // Do not post the runnable if there is nothing in the list.
+ if (mList.getExpandableListAdapter().getGroupCount() > 0) {
+ mList.post(new Runnable() {
+ @Override
+ public void run() {
+ // In case the history gets cleared before this
+ // event happens
+ if (mList.getExpandableListAdapter().getGroupCount() > 0) {
+ mList.expandGroup(0);
+ }
+ }
+ });
}
- });
+ break;
+ }
+
+ default: {
+ throw new IllegalArgumentException();
+ }
}
- mDisableNewWindow = getIntent().getBooleanExtra("disable_new_window",
- false);
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setHasOptionsMenu(true);
+
+ Bundle args = getArguments();
+ mDisableNewWindow = args.getBoolean("disable_new_window", false);
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mCallbacks = (BookmarksHistoryCallbacks) activity;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.history, container, false);
+ mList = (ExpandableListView) root.findViewById(android.R.id.list);
+ mList.setOnCreateContextMenuListener(this);
+ mList.setOnChildClickListener(this);
+ mAdapter = new HistoryAdapter(getActivity());
+ mList.setAdapter(mAdapter);
+
+ mEmptyView = root.findViewById(android.R.id.empty);
+
+ // Start the loader
+ getLoaderManager().initLoader(LOADER_HISTORY, null, this);
// Register to receive icons in case they haven't all been loaded.
- CombinedBookmarkHistoryActivity.getIconListenerSet()
- .addListener(mIconReceiver);
-
- Activity parent = getParent();
- if (null == parent
- || !(parent instanceof CombinedBookmarkHistoryActivity)) {
- throw new AssertionError("history page can only be viewed as a tab"
- + "in CombinedBookmarkHistoryActivity");
- }
- // initialize the result to canceled, so that if the user just presses
- // back then it will have the correct result
- setResultToParent(RESULT_CANCELED, null);
+ CombinedBookmarkHistoryActivity.getIconListenerSet().addListener(mIconReceiver);
+
+ return root;
}
@Override
- protected void onDestroy() {
+ public void onDestroy() {
super.onDestroy();
- CombinedBookmarkHistoryActivity.getIconListenerSet()
- .removeListener(mIconReceiver);
+ CombinedBookmarkHistoryActivity.getIconListenerSet().removeListener(mIconReceiver);
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
- MenuInflater inflater = getMenuInflater();
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.history, menu);
- return true;
}
@Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- menu.findItem(R.id.clear_history_menu_id).setVisible(Browser.canClearHistory(this.getContentResolver()));
- return true;
+ public void onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(R.id.clear_history_menu_id).setVisible(
+ Browser.canClearHistory(getActivity().getContentResolver()));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.clear_history_menu_id:
- Browser.clearHistory(getContentResolver());
+ Browser.clearHistory(getActivity().getContentResolver());
// BrowserHistoryPage is always a child of
// CombinedBookmarkHistoryActivity
- ((CombinedBookmarkHistoryActivity) getParent())
- .removeParentChildRelationShips();
- mAdapter.refreshData();
+ mCallbacks.onRemoveParentChildRelationShips();
return true;
default:
@@ -191,24 +213,23 @@ public class BrowserHistoryPage extends ExpandableListActivity {
}
@Override
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- ExpandableListContextMenuInfo i =
- (ExpandableListContextMenuInfo) menuInfo;
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ ExpandableListContextMenuInfo i = (ExpandableListContextMenuInfo) menuInfo;
// Do not allow a context menu to come up from the group views.
if (!(i.targetView instanceof HistoryItem)) {
return;
}
// Inflate the menu
- MenuInflater inflater = getMenuInflater();
+ Activity parent = getActivity();
+ MenuInflater inflater = parent.getMenuInflater();
inflater.inflate(R.menu.historycontext, menu);
HistoryItem historyItem = (HistoryItem) i.targetView;
// Setup the header
if (mContextHeader == null) {
- mContextHeader = new HistoryItem(this);
+ mContextHeader = new HistoryItem(parent);
} else if (mContextHeader.getParent() != null) {
((ViewGroup) mContextHeader.getParent()).removeView(mContextHeader);
}
@@ -225,7 +246,7 @@ public class BrowserHistoryPage extends ExpandableListActivity {
item.setTitle(R.string.remove_from_bookmarks);
}
// decide whether to show the share link option
- PackageManager pm = getPackageManager();
+ PackageManager pm = parent.getPackageManager();
Intent send = new Intent(Intent.ACTION_SEND);
send.setType("text/plain");
ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
@@ -241,72 +262,63 @@ public class BrowserHistoryPage extends ExpandableListActivity {
HistoryItem historyItem = (HistoryItem) i.targetView;
String url = historyItem.getUrl();
String title = historyItem.getName();
+ Activity activity = getActivity();
switch (item.getItemId()) {
case R.id.open_context_menu_id:
- loadUrl(url, false);
+ mCallbacks.onUrlSelected(url, false);
return true;
case R.id.new_window_context_menu_id:
- loadUrl(url, true);
+ mCallbacks.onUrlSelected(url, true);
return true;
case R.id.save_to_bookmarks_menu_id:
if (historyItem.isBookmark()) {
- Bookmarks.removeFromBookmarks(this, getContentResolver(),
+ Bookmarks.removeFromBookmarks(activity, activity.getContentResolver(),
url, title);
} else {
- Browser.saveBookmark(this, title, url);
+ Browser.saveBookmark(activity, title, url);
}
return true;
case R.id.share_link_context_menu_id:
- Browser.sendString(this, url,
- getText(R.string.choosertitle_sharevia).toString());
+ Browser.sendString(activity, url,
+ activity.getText(R.string.choosertitle_sharevia).toString());
return true;
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();
+ Browser.deleteFromHistory(activity.getContentResolver(), url);
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();
+ BrowserSettings.getInstance().setHomePage(activity, url);
+ Toast.makeText(activity, R.string.homepage_set, Toast.LENGTH_LONG).show();
return true;
default:
break;
}
return super.onContextItemSelected(item);
}
-
+
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
- if (v instanceof HistoryItem) {
- loadUrl(((HistoryItem) v).getUrl(), false);
+ if (v instanceof BookmarkItem) {
+ mCallbacks.onUrlSelected(((BookmarkItem) v).getUrl(), false);
return true;
}
return false;
}
- // This Activity is always a sub-Activity of
- // CombinedBookmarkHistoryActivity. Therefore, we need to pass our
- // result code up to our parent.
- private void setResultToParent(int resultCode, Intent data) {
- ((CombinedBookmarkHistoryActivity) getParent()).setResultFromChild(
- resultCode, data);
- }
-
private class HistoryAdapter extends DateSortedExpandableListAdapter {
- HistoryAdapter(Context context, Cursor cursor, int index) {
- super(context, cursor, index);
-
+ HistoryAdapter(Context context) {
+ super(context, HistoryQuery.INDEX_DATE_LAST_VISITED);
}
+ @Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
View convertView, ViewGroup parent) {
- HistoryItem item;
- if (null == convertView || !(convertView instanceof HistoryItem)) {
- item = new HistoryItem(BrowserHistoryPage.this);
+ BookmarkItem item;
+ if (null == convertView || !(convertView instanceof BookmarkItem)) {
+ item = new BookmarkItem(getContext());
// Add padding on the left so it will be indented from the
// arrows on the group views.
item.setPadding(item.getPaddingLeft() + 10,
@@ -314,16 +326,18 @@ public class BrowserHistoryPage extends ExpandableListActivity {
item.getPaddingRight(),
item.getPaddingBottom());
} else {
- item = (HistoryItem) convertView;
+ item = (BookmarkItem) convertView;
}
+
// Bail early if the Cursor is closed.
if (!moveCursorToChildPosition(groupPosition, childPosition)) {
return item;
}
- item.setName(getString(Browser.HISTORY_PROJECTION_TITLE_INDEX));
- String url = getString(Browser.HISTORY_PROJECTION_URL_INDEX);
+
+ item.setName(getString(HistoryQuery.INDEX_TITE));
+ String url = getString(HistoryQuery.INDEX_URL);
item.setUrl(url);
- byte[] data = getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX);
+ byte[] data = getBlob(HistoryQuery.INDEX_FAVICON);
if (data != null) {
item.setFavicon(BitmapFactory.decodeByteArray(data, 0,
data.length));
@@ -331,8 +345,6 @@ public class BrowserHistoryPage extends ExpandableListActivity {
item.setFavicon(CombinedBookmarkHistoryActivity
.getIconListenerSet().getFavicon(url));
}
- item.setIsBookmark(1 ==
- getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX));
return item;
}
}
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index f8574eda8..f371e24f7 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -29,8 +29,10 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.UriMatcher;
+import android.content.res.Configuration;
import android.database.AbstractCursor;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
@@ -38,6 +40,7 @@ import android.os.Process;
import android.preference.PreferenceManager;
import android.provider.Browser;
import android.provider.Browser.BookmarkColumns;
+import android.provider.Settings;
import android.speech.RecognizerResultsIntent;
import android.text.TextUtils;
import android.util.Log;
@@ -84,6 +87,17 @@ public class BrowserProvider extends ContentProvider {
private static final int SUGGEST_COLUMN_QUERY_ID = 8;
private static final int SUGGEST_COLUMN_INTENT_EXTRA_DATA = 9;
+ // how many suggestions will be shown in dropdown
+ // 0..SHORT: filled by browser db
+ private static final int MAX_SUGGEST_SHORT_SMALL = 3;
+ // SHORT..LONG: filled by search suggestions
+ private static final int MAX_SUGGEST_LONG_SMALL = 6;
+
+ // large screen size shows more
+ private static final int MAX_SUGGEST_SHORT_LARGE = 6;
+ private static final int MAX_SUGGEST_LONG_LARGE = 9;
+
+
// shared suggestion columns
private static final String[] COLUMNS = new String[] {
"_id",
@@ -97,10 +111,6 @@ public class BrowserProvider extends ContentProvider {
SearchManager.SUGGEST_COLUMN_QUERY,
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;
@@ -160,6 +170,9 @@ public class BrowserProvider extends ContentProvider {
private BrowserSettings mSettings;
+ private int mMaxSuggestionShortSize;
+ private int mMaxSuggestionLongSize;
+
public BrowserProvider() {
}
@@ -343,6 +356,20 @@ public class BrowserProvider extends ContentProvider {
@Override
public boolean onCreate() {
final Context context = getContext();
+ boolean xlargeScreenSize = (context.getResources().getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK)
+ == Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ boolean isPortrait = (context.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_PORTRAIT);
+
+
+ if (xlargeScreenSize && isPortrait) {
+ mMaxSuggestionLongSize = MAX_SUGGEST_LONG_LARGE;
+ mMaxSuggestionShortSize = MAX_SUGGEST_SHORT_LARGE;
+ } else {
+ mMaxSuggestionLongSize = MAX_SUGGEST_LONG_SMALL;
+ mMaxSuggestionShortSize = MAX_SUGGEST_SHORT_SMALL;
+ }
mOpenHelper = new DatabaseHelper(context);
mBackupManager = new BackupManager(context);
// we added "picasa web album" into default bookmarks for version 19.
@@ -409,10 +436,10 @@ public class BrowserProvider extends ContentProvider {
public MySuggestionCursor(Cursor hc, Cursor sc, String string) {
mHistoryCursor = hc;
mSuggestCursor = sc;
- mHistoryCount = hc.getCount();
+ mHistoryCount = hc != null ? hc.getCount() : 0;
mSuggestionCount = sc != null ? sc.getCount() : 0;
- if (mSuggestionCount > (MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount)) {
- mSuggestionCount = MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount;
+ if (mSuggestionCount > (mMaxSuggestionLongSize - mHistoryCount)) {
+ mSuggestionCount = mMaxSuggestionLongSize - mHistoryCount;
}
mString = string;
mIncludeWebSearch = string.length() > 0;
@@ -626,6 +653,7 @@ public class BrowserProvider extends ContentProvider {
}
// TODO Temporary change, finalize after jq's changes go in
+ @Override
public void deactivate() {
if (mHistoryCursor != null) {
mHistoryCursor.deactivate();
@@ -636,12 +664,14 @@ public class BrowserProvider extends ContentProvider {
super.deactivate();
}
+ @Override
public boolean requery() {
return (mHistoryCursor != null ? mHistoryCursor.requery() : false) |
(mSuggestCursor != null ? mSuggestCursor.requery() : false);
}
// TODO Temporary change, finalize after jq's changes go in
+ @Override
public void close() {
super.close();
if (mHistoryCursor != null) {
@@ -706,12 +736,15 @@ public class BrowserProvider extends ContentProvider {
public ResultsCursor(ArrayList<String> results) {
mResults = results;
}
+ @Override
public int getCount() { return mResults.size(); }
+ @Override
public String[] getColumnNames() {
return RESULTS_COLUMNS;
}
+ @Override
public String getString(int column) {
switch (column) {
case RESULT_ACTION_ID:
@@ -733,30 +766,39 @@ public class BrowserProvider extends ContentProvider {
return null;
}
}
+ @Override
public short getShort(int column) {
throw new UnsupportedOperationException();
}
+ @Override
public int getInt(int column) {
throw new UnsupportedOperationException();
}
+ @Override
public long getLong(int column) {
if ((mPos != -1) && column == 0) {
return mPos; // use row# as the _id
}
throw new UnsupportedOperationException();
}
+ @Override
public float getFloat(int column) {
throw new UnsupportedOperationException();
}
+ @Override
public double getDouble(int column) {
throw new UnsupportedOperationException();
}
+ @Override
public boolean isNull(int column) {
throw new UnsupportedOperationException();
}
}
+ /** Contains custom suggestions results set by the UI */
private ResultsCursor mResultsCursor;
+ /** Locks access to {@link #mResultsCursor} */
+ private Object mResultsCursorLock = new Object();
/**
* Provide a set of results to be returned to query, intended to be used
@@ -764,10 +806,12 @@ public class BrowserProvider extends ContentProvider {
* @param results Strings to display in the dropdown from the SearchDialog
*/
/* package */ void setQueryResults(ArrayList<String> results) {
- if (results == null) {
- mResultsCursor = null;
- } else {
- mResultsCursor = new ResultsCursor(results);
+ synchronized (mResultsCursorLock) {
+ if (results == null) {
+ mResultsCursor = null;
+ } else {
+ mResultsCursor = new ResultsCursor(results);
+ }
}
}
@@ -779,57 +823,19 @@ public class BrowserProvider extends ContentProvider {
if (match == -1) {
throw new IllegalArgumentException("Unknown URL");
}
- if (match == URI_MATCH_SUGGEST && mResultsCursor != null) {
- Cursor results = mResultsCursor;
- mResultsCursor = null;
- return results;
- }
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
- if (match == URI_MATCH_SUGGEST || match == URI_MATCH_BOOKMARKS_SUGGEST) {
- String suggestSelection;
- String [] myArgs;
- if (selectionArgs[0] == null || selectionArgs[0].equals("")) {
- suggestSelection = null;
- myArgs = null;
- } else {
- String like = selectionArgs[0] + "%";
- if (selectionArgs[0].startsWith("http")
- || selectionArgs[0].startsWith("file")) {
- myArgs = new String[1];
- myArgs[0] = like;
- suggestSelection = selection;
- } else {
- SUGGEST_ARGS[0] = "http://" + like;
- SUGGEST_ARGS[1] = "http://www." + like;
- SUGGEST_ARGS[2] = "https://" + like;
- SUGGEST_ARGS[3] = "https://www." + like;
- // To match against titles.
- SUGGEST_ARGS[4] = like;
- myArgs = SUGGEST_ARGS;
- suggestSelection = SUGGEST_SELECTION;
- }
+ // If results for the suggestion are already ready just return them directly
+ synchronized (mResultsCursorLock) {
+ if (match == URI_MATCH_SUGGEST && mResultsCursor != null) {
+ Cursor results = mResultsCursor;
+ mResultsCursor = null;
+ return results;
}
+ }
- Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS],
- SUGGEST_PROJECTION, suggestSelection, myArgs, null, null,
- ORDER_BY, MAX_SUGGESTION_LONG_ENTRIES_STRING);
-
- if (match == URI_MATCH_BOOKMARKS_SUGGEST
- || Patterns.WEB_URL.matcher(selectionArgs[0]).matches()) {
- return new MySuggestionCursor(c, null, "");
- } else {
- // get search suggestions if there is still space in the list
- if (myArgs != null && myArgs.length > 1
- && c.getCount() < (MAX_SUGGESTION_SHORT_ENTRIES - 1)) {
- SearchEngine searchEngine = mSettings.getSearchEngine();
- if (searchEngine != null && searchEngine.supportsSuggestions()) {
- Cursor sc = searchEngine.getSuggestions(getContext(), selectionArgs[0]);
- return new MySuggestionCursor(c, sc, selectionArgs[0]);
- }
- }
- return new MySuggestionCursor(c, null, selectionArgs[0]);
- }
+ if (match == URI_MATCH_SUGGEST || match == URI_MATCH_BOOKMARKS_SUGGEST) {
+ // Handle suggestions
+ return doSuggestQuery(selection, selectionArgs, match == URI_MATCH_BOOKMARKS_SUGGEST);
}
String[] projection = null;
@@ -839,27 +845,60 @@ public class BrowserProvider extends ContentProvider {
projection[projectionIn.length] = "_id AS _id";
}
- StringBuilder whereClause = new StringBuilder(256);
+ String whereClause = null;
if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) {
- whereClause.append("(_id = ").append(url.getPathSegments().get(1))
- .append(")");
+ whereClause = "_id = " + url.getPathSegments().get(1);
}
- // Tack on the user's selection, if present
- if (selection != null && selection.length() > 0) {
- if (whereClause.length() > 0) {
- whereClause.append(" AND ");
+ Cursor c = mOpenHelper.getReadableDatabase().query(TABLE_NAMES[match % 10], projection,
+ DatabaseUtils.concatenateWhere(whereClause, selection), selectionArgs,
+ null, null, sortOrder, null);
+ c.setNotificationUri(getContext().getContentResolver(), url);
+ return c;
+ }
+
+ private Cursor doSuggestQuery(String selection, String[] selectionArgs, boolean bookmarksOnly) {
+ String suggestSelection;
+ String [] myArgs;
+ if (selectionArgs[0] == null || selectionArgs[0].equals("")) {
+ return new MySuggestionCursor(null, null, "");
+ } else {
+ String like = selectionArgs[0] + "%";
+ if (selectionArgs[0].startsWith("http")
+ || selectionArgs[0].startsWith("file")) {
+ myArgs = new String[1];
+ myArgs[0] = like;
+ suggestSelection = selection;
+ } else {
+ SUGGEST_ARGS[0] = "http://" + like;
+ SUGGEST_ARGS[1] = "http://www." + like;
+ SUGGEST_ARGS[2] = "https://" + like;
+ SUGGEST_ARGS[3] = "https://www." + like;
+ // To match against titles.
+ SUGGEST_ARGS[4] = like;
+ myArgs = SUGGEST_ARGS;
+ suggestSelection = SUGGEST_SELECTION;
}
+ }
+
+ Cursor c = mOpenHelper.getReadableDatabase().query(TABLE_NAMES[URI_MATCH_BOOKMARKS],
+ SUGGEST_PROJECTION, suggestSelection, myArgs, null, null,
+ ORDER_BY, Integer.toString(mMaxSuggestionLongSize));
- whereClause.append('(');
- whereClause.append(selection);
- whereClause.append(')');
+ if (bookmarksOnly || Patterns.WEB_URL.matcher(selectionArgs[0]).matches()) {
+ return new MySuggestionCursor(c, null, "");
+ } else {
+ // get search suggestions if there is still space in the list
+ if (myArgs != null && myArgs.length > 1
+ && c.getCount() < (MAX_SUGGEST_SHORT_SMALL - 1)) {
+ SearchEngine searchEngine = mSettings.getSearchEngine();
+ if (searchEngine != null && searchEngine.supportsSuggestions()) {
+ Cursor sc = searchEngine.getSuggestions(getContext(), selectionArgs[0]);
+ return new MySuggestionCursor(c, sc, selectionArgs[0]);
+ }
+ }
+ return new MySuggestionCursor(c, null, selectionArgs[0]);
}
- Cursor c = db.query(TABLE_NAMES[match % 10], projection,
- whereClause.toString(), selectionArgs, null, null, sortOrder,
- null);
- c.setNotificationUri(getContext().getContentResolver(), url);
- return c;
}
@Override
@@ -1067,4 +1106,10 @@ public class BrowserProvider extends ContentProvider {
}
}
+ public static Cursor getBookmarksSuggestions(ContentResolver cr, String constraint) {
+ Uri uri = Uri.parse("content://browser/" + SearchManager.SUGGEST_URI_PATH_QUERY);
+ return cr.query(uri, SUGGEST_PROJECTION, SUGGEST_SELECTION,
+ new String[] { constraint }, ORDER_BY);
+ }
+
}
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 3791eb07f..cb4918b50 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -28,6 +28,7 @@ import android.content.pm.ActivityInfo;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Handler;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
@@ -115,8 +116,8 @@ class BrowserSettings extends Observable {
private boolean showConsole = true;
// Private preconfigured values
- private static int minimumFontSize = 8;
- private static int minimumLogicalFontSize = 8;
+ private static int minimumFontSize = 1;
+ private static int minimumLogicalFontSize = 1;
private static int defaultFontSize = 16;
private static int defaultFixedFontSize = 13;
private static WebSettings.TextSize textSize =
@@ -166,6 +167,10 @@ class BrowserSettings extends Observable {
// a ListView
public final static int MAX_TEXTVIEW_LEN = 80;
+ public static final String RLZ_PROVIDER = "com.google.android.partnersetup.rlzappprovider";
+
+ public static final Uri RLZ_PROVIDER_URI = Uri.parse("content://" + RLZ_PROVIDER + "/");
+
private TabControl mTabControl;
// Single instance of the BrowserSettings for use in the Browser app.
@@ -229,6 +234,9 @@ class BrowserSettings extends Observable {
s.setNeedInitialFocus(false);
// Browser supports multiple windows
s.setSupportMultipleWindows(true);
+ // enable smooth transition for better performance during panning or
+ // zooming
+ s.setEnableSmoothTransition(true);
// HTML5 API flags
s.setAppCacheEnabled(b.appCacheEnabled);
diff --git a/src/com/android/browser/CircularProgressView.java b/src/com/android/browser/CircularProgressView.java
new file mode 100644
index 000000000..48f293a42
--- /dev/null
+++ b/src/com/android/browser/CircularProgressView.java
@@ -0,0 +1,140 @@
+
+/*
+ * Copyright (C) 2010 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.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+/**
+ *
+ */
+public class CircularProgressView extends ImageButton {
+
+ private static final int[] ALPHAS = {
+ 64, 96, 128, 160, 192, 192, 160, 128, 96, 64
+ };
+
+ // 100 ms delay between frames, 10fps
+ private static int ALPHA_REFRESH_DELAY = 100;
+
+ private int mEndAngle;
+ private int mProgress;
+ private Paint mPaint;
+ private int mAlpha;
+ private boolean mAnimated;
+ private RectF mRect;
+ private int mMaxProgress;
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public CircularProgressView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public CircularProgressView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ /**
+ * @param context
+ */
+ public CircularProgressView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ private void init(Context ctx) {
+ mEndAngle = 0;
+ mProgress = 0;
+ mMaxProgress = 100;
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setColor(Color.BLACK);
+ mRect = new RectF();
+ }
+
+ void setMaxProgress(int max) {
+ mMaxProgress = max;
+ }
+
+ private synchronized boolean isAnimated() {
+ return mAnimated;
+ }
+
+ private synchronized void setAnimated(boolean animated) {
+ mAnimated = animated;
+ }
+
+ void setProgress(int progress) {
+ mProgress = progress;
+ mEndAngle = 360 * progress / mMaxProgress;
+ invalidate();
+ if (!isAnimated() && (progress > 0) && (progress < mMaxProgress)) {
+ setAnimated(true);
+ mAlpha = 0;
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (isAnimated()) {
+ mAlpha = (mAlpha + 1) % ALPHAS.length;
+ mPaint.setAlpha(ALPHAS[mAlpha]);
+ invalidate();
+ postDelayed(this, ALPHA_REFRESH_DELAY);
+ }
+ }
+ });
+ } else if ((progress <= 0) || (progress >= mMaxProgress)) {
+ setAnimated(false);
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ int w = getWidth();
+ int h = getHeight();
+ float cx = w * 0.5f;
+ float cy = h * 0.5f;
+ mRect.set(0, 0, w, h);
+ if ((mProgress > 0) && (mProgress < mMaxProgress)) {
+ Path p = new Path();
+ p.moveTo(cx, cy);
+ p.lineTo(cx, 0);
+ p.arcTo(mRect, 270, mEndAngle);
+ p.lineTo(cx, cy);
+ int state = canvas.save();
+ canvas.drawPath(p, mPaint);
+ canvas.restoreToCount(state);
+ }
+ super.onDraw(canvas);
+ }
+
+}
diff --git a/src/com/android/browser/CombinedBookmarkHistoryActivity.java b/src/com/android/browser/CombinedBookmarkHistoryActivity.java
index 194956f14..a98408c6c 100644
--- a/src/com/android/browser/CombinedBookmarkHistoryActivity.java
+++ b/src/com/android/browser/CombinedBookmarkHistoryActivity.java
@@ -17,22 +17,48 @@
package com.android.browser;
import android.app.Activity;
-import android.app.TabActivity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
import android.content.Intent;
-import android.content.res.Resources;
+import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Browser;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
import android.webkit.WebIconDatabase;
import android.webkit.WebIconDatabase.IconListener;
-import android.widget.TabHost;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
import java.util.HashMap;
import java.util.Vector;
-public class CombinedBookmarkHistoryActivity extends TabActivity
- implements TabHost.OnTabChangeListener {
+interface BookmarksHistoryCallbacks {
+ public void onUrlSelected(String url, boolean newWindow);
+ public void onRemoveParentChildRelationShips();
+}
+
+public class CombinedBookmarkHistoryActivity extends Activity
+ implements BookmarksHistoryCallbacks, OnItemClickListener {
+ final static String NEWTAB_MODE = "newtab_mode";
+ final static String EXTRA_OPEN_NEW_WINDOW = "new_window";
+ final static String STARTING_FRAGMENT = "fragment";
+ final static String EVT_X = "evt_x";
+ final static String EVT_Y = "evt_y";
+ final static String EXTRA_TOP = "top";
+ final static String EXTRA_HEIGHT = "height";
+
+
+ final static int FRAGMENT_ID_BOOKMARKS = 1;
+ final static int FRAGMENT_ID_HISTORY = 2;
+
/**
* Used to inform BrowserActivity to remove the parent/child relationships
* from all the tabs.
@@ -48,10 +74,15 @@ public class CombinedBookmarkHistoryActivity extends TabActivity
*/
private int mResultCode;
- /* package */ static String BOOKMARKS_TAB = "bookmark";
- /* package */ static String VISITED_TAB = "visited";
- /* package */ static String HISTORY_TAB = "history";
- /* package */ static String STARTING_TAB = "tab";
+ /**
+ * Flag to inform the browser to force the result to open in a new tab.
+ */
+ private boolean mNewTabMode;
+
+ private int mRequestedTop;
+ private int mRequestedHeight;
+
+ long mCurrentFragment;
static class IconListenerSet implements IconListener {
// Used to store favicons as we get them from the database
@@ -64,6 +95,7 @@ public class CombinedBookmarkHistoryActivity extends TabActivity
mUrlsToIcons = new HashMap<String, Bitmap>();
mListeners = new Vector<IconListener>();
}
+ @Override
public void onReceivedIcon(String url, Bitmap icon) {
mUrlsToIcons.put(url, icon);
for (IconListener listener : mListeners) {
@@ -77,9 +109,10 @@ public class CombinedBookmarkHistoryActivity extends TabActivity
mListeners.remove(listener);
}
public Bitmap getFavicon(String url) {
- return (Bitmap) mUrlsToIcons.get(url);
+ return mUrlsToIcons.get(url);
}
}
+
private static IconListenerSet sIconListenerSet;
static IconListenerSet getIconListenerSet() {
if (null == sIconListenerSet) {
@@ -91,80 +124,104 @@ public class CombinedBookmarkHistoryActivity extends TabActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.tabs);
+ setContentView(R.layout.bookmarks_history);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
- getTabHost().setOnTabChangedListener(this);
+ ListView list = (ListView) findViewById(android.R.id.list);
+ list.setOnItemClickListener(this);
+ MatrixCursor cursor = new MatrixCursor(new String[] { "name", "_id" });
+ cursor.newRow().add(getString(R.string.bookmarks)).add(FRAGMENT_ID_BOOKMARKS);
+ cursor.newRow().add(getString(R.string.history)).add(FRAGMENT_ID_HISTORY);
+ list.setAdapter(new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, cursor,
+ new String[] { "name" }, new int[] { android.R.id.text1 }));
+ int startingFragment = FRAGMENT_ID_BOOKMARKS;
Bundle extras = getIntent().getExtras();
-
- Intent bookmarksIntent = new Intent(this, BrowserBookmarksPage.class);
if (extras != null) {
- bookmarksIntent.putExtras(extras);
+ mNewTabMode = extras.getBoolean(NEWTAB_MODE);
+ mRequestedTop = extras.getInt(EXTRA_TOP, -1);
+ mRequestedHeight = extras.getInt(EXTRA_HEIGHT, -1);
+ startingFragment = extras.getInt(STARTING_FRAGMENT, FRAGMENT_ID_BOOKMARKS);
}
- createTab(bookmarksIntent, R.string.tab_bookmarks,
- R.drawable.browser_bookmark_tab, BOOKMARKS_TAB);
-
- Intent visitedIntent = new Intent(this, BrowserBookmarksPage.class);
- // Need to copy extras so the bookmarks activity and this one will be
- // different
- Bundle visitedExtras = extras == null ? new Bundle() : 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);
- String defaultTab = null;
- if (extras != null) {
- historyIntent.putExtras(extras);
- defaultTab = extras.getString(STARTING_TAB);
- }
- createTab(historyIntent, R.string.tab_history,
- R.drawable.browser_history_tab, HISTORY_TAB);
- if (defaultTab != null) {
- getTabHost().setCurrentTab(2);
- }
+ // Start up the default fragment
+ loadFragment(startingFragment);
// XXX: Must do this before launching the AsyncTask to avoid a
// potential crash if the icon database has not been created.
WebIconDatabase.getInstance();
+
// Do this every time we launch the activity in case a new favicon was
// added to the webkit db.
(new AsyncTask<Void, Void, Void>() {
+ @Override
public Void doInBackground(Void... v) {
Browser.requestAllIcons(getContentResolver(),
- Browser.BookmarkColumns.FAVICON + " is NULL",
- getIconListenerSet());
+ Browser.BookmarkColumns.FAVICON + " is NULL", getIconListenerSet());
return null;
}
}).execute();
}
- 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));
+ @Override
+ public void onAttachedToWindow() {
+ if (mRequestedTop > -1) {
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ lp.x = 0;
+ lp.y = mRequestedTop;
+ lp.height = mRequestedHeight;
+ lp.gravity = Gravity.TOP | Gravity.LEFT;
+ getWindow().setAttributes(lp);
+ }
}
- // Copied from DialTacts Activity
- /** {@inheritDoc} */
- public void onTabChanged(String tabId) {
- Activity activity = getLocalActivityManager().getActivity(tabId);
- if (activity != null) {
- activity.onWindowFocusChanged(true);
+
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ if (((evt.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) && (evt.getY() < 0)) {
+ Intent result = new Intent();
+ result.putExtra(EVT_X, evt.getRawX());
+ result.putExtra(EVT_Y, evt.getRawY());
+ setResultFromChild(Activity.RESULT_CANCELED, result);
+ finish();
+ return true;
}
+ return super.onTouchEvent(evt);
+ }
+
+ private void loadFragment(int id) {
+ String fragmentClassName;
+ switch (id) {
+ case FRAGMENT_ID_BOOKMARKS:
+ fragmentClassName = BrowserBookmarksPage.class.getName();
+ break;
+ case FRAGMENT_ID_HISTORY:
+ fragmentClassName = BrowserHistoryPage.class.getName();
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ mCurrentFragment = id;
+
+ FragmentManager fm = getFragmentManager();
+ FragmentTransaction transaction = fm.openTransaction();
+ Fragment frag = Fragment.instantiate(this, fragmentClassName, getIntent().getExtras());
+ transaction.replace(R.id.fragment, frag);
+ transaction.commit();
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (id == mCurrentFragment) return;
+ loadFragment((int) id);
}
/**
* Store extra data in the Intent to return to the calling Activity to tell
* it to clear the parent/child relationships from all tabs.
*/
- /* package */ void removeParentChildRelationShips() {
+ @Override
+ public void onRemoveParentChildRelationShips() {
mExtraData = BrowserSettings.PREF_CLEAR_HISTORY;
}
@@ -174,7 +231,7 @@ public class CombinedBookmarkHistoryActivity extends TabActivity
* @param resultCode Uses same codes as Activity.setResult
* @param data Intent returned to onActivityResult.
*/
- /* package */ void setResultFromChild(int resultCode, Intent data) {
+ private void setResultFromChild(int resultCode, Intent data) {
mResultCode = resultCode;
mResultData = data;
}
@@ -186,7 +243,26 @@ public class CombinedBookmarkHistoryActivity extends TabActivity
if (mResultData == null) mResultData = new Intent();
mResultData.putExtra(Intent.EXTRA_TEXT, mExtraData);
}
+ if (mNewTabMode) {
+ if (mResultData == null) mResultData = new Intent();
+ mResultData.putExtra(NEWTAB_MODE, true);
+ }
setResult(mResultCode, mResultData);
super.finish();
}
+
+ /**
+ * Report back to the calling activity to load a site.
+ * @param url Site to load.
+ * @param newWindow True if the URL should be loaded in a new window
+ */
+ @Override
+ public void onUrlSelected(String url, boolean newWindow) {
+ Intent intent = new Intent().setAction(url);
+ if (newWindow) {
+ intent.putExtra(EXTRA_OPEN_NEW_WINDOW, true);
+ }
+ setResultFromChild(RESULT_OK, intent);
+ finish();
+ }
}
diff --git a/src/com/android/browser/DateSortedExpandableListAdapter.java b/src/com/android/browser/DateSortedExpandableListAdapter.java
index 1d04493aa..a48efe679 100644
--- a/src/com/android/browser/DateSortedExpandableListAdapter.java
+++ b/src/com/android/browser/DateSortedExpandableListAdapter.java
@@ -17,65 +17,55 @@
package com.android.browser;
import android.content.Context;
-import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
-import android.os.Handler;
-import android.provider.BaseColumns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.DateSorter;
-import android.widget.ExpandableListAdapter;
+import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.TextView;
-import java.util.Vector;
-
/**
* ExpandableListAdapter which separates data into categories based on date.
* Used for History and Downloads.
*/
-public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
+public class DateSortedExpandableListAdapter extends BaseExpandableListAdapter {
// Array for each of our bins. Each entry represents how many items are
// in that bin.
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.
private int mNumberOfBins;
- private Vector<DataSetObserver> mObservers;
private Cursor mCursor;
private DateSorter mDateSorter;
private int mDateIndex;
private int mIdIndex;
private Context mContext;
- private class ChangeObserver extends ContentObserver {
- public ChangeObserver() {
- super(new Handler());
- }
+ boolean mDataValid;
+ DataSetObserver mDataSetObserver = new DataSetObserver() {
@Override
- public boolean deliverSelfNotifications() {
- return true;
+ public void onChanged() {
+ mDataValid = true;
+ notifyDataSetChanged();
}
@Override
- public void onChange(boolean selfChange) {
- refreshData();
+ public void onInvalidated() {
+ mDataValid = false;
+ notifyDataSetInvalidated();
}
- }
-
- public DateSortedExpandableListAdapter(Context context, Cursor cursor,
- int dateIndex) {
+ };
+
+ public DateSortedExpandableListAdapter(Context context, int dateIndex) {
mContext = context;
mDateSorter = new DateSorter(context);
- mObservers = new Vector<DataSetObserver>();
- mCursor = cursor;
- mIdIndex = cursor.getColumnIndexOrThrow(BaseColumns._ID);
- cursor.registerContentObserver(new ChangeObserver());
mDateIndex = dateIndex;
- buildMap();
+ mDataValid = false;
+ mIdIndex = -1;
}
/**
@@ -122,6 +112,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
* @return corresponding byte array from the Cursor.
*/
/* package */ byte[] getBlob(int cursorIndex) {
+ if (!mDataValid) return null;
return mCursor.getBlob(cursorIndex);
}
@@ -138,6 +129,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
* @return corresponding integer from the Cursor.
*/
/* package */ int getInt(int cursorIndex) {
+ if (!mDataValid) return 0;
return mCursor.getInt(cursorIndex);
}
@@ -146,6 +138,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
* already been moved to the correct position.
*/
/* package */ long getLong(int cursorIndex) {
+ if (!mDataValid) return 0;
return mCursor.getLong(cursorIndex);
}
@@ -158,6 +151,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
* @return corresponding String from the Cursor.
*/
/* package */ String getString(int cursorIndex) {
+ if (!mDataValid) return null;
return mCursor.getString(cursorIndex);
}
@@ -166,6 +160,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
* @param childId ID of the child view in question.
* @return int Group position of the containing group.
/* package */ int groupFromChildId(long childId) {
+ if (!mDataValid) return -1;
int group = -1;
for (mCursor.moveToFirst(); !mCursor.isAfterLast();
mCursor.moveToNext()) {
@@ -173,11 +168,15 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
int bin = mDateSorter.getIndex(getLong(mDateIndex));
// bin is the same as the group if the number of bins is the
// same as DateSorter
- if (mDateSorter.DAY_COUNT == mNumberOfBins) return bin;
+ if (DateSorter.DAY_COUNT == mNumberOfBins) {
+ return bin;
+ }
// There are some empty bins. Find the corresponding group.
group = 0;
for (int i = 0; i < bin; i++) {
- if (mItemMap[i] != 0) group++;
+ if (mItemMap[i] != 0) {
+ group++;
+ }
}
break;
}
@@ -193,6 +192,7 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
* @return The corresponding bin that holds that group.
*/
private int groupPositionToBin(int groupPosition) {
+ if (!mDataValid) return -1;
if (groupPosition < 0 || groupPosition >= DateSorter.DAY_COUNT) {
throw new AssertionError("group position out of range");
}
@@ -241,7 +241,9 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
*/
/* package */ boolean moveCursorToChildPosition(int groupPosition,
int childPosition) {
- if (mCursor.isClosed()) return false;
+ if (!mDataValid || mCursor.isClosed()) {
+ return false;
+ }
groupPosition = groupPositionToBin(groupPosition);
int index = childPosition;
for (int i = 0; i < groupPosition; i++) {
@@ -250,19 +252,34 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
return mCursor.moveToPosition(index);
}
- /* package */ void refreshData() {
- if (mCursor.isClosed()) {
+ public void changeCursor(Cursor cursor) {
+ if (cursor == mCursor) {
return;
}
- mCursor.requery();
- buildMap();
- for (DataSetObserver o : mObservers) {
- o.onChanged();
+ if (mCursor != null) {
+ mCursor.unregisterDataSetObserver(mDataSetObserver);
+ mCursor.close();
+ }
+ mCursor = cursor;
+ if (cursor != null) {
+ cursor.registerDataSetObserver(mDataSetObserver);
+ mIdIndex = cursor.getColumnIndexOrThrow("_id");
+ mDataValid = true;
+ buildMap();
+ // notify the observers about the new cursor
+ notifyDataSetChanged();
+ } else {
+ mIdIndex = -1;
+ mDataValid = false;
+ // notify the observers about the lack of a data set
+ notifyDataSetInvalidated();
}
}
+ @Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
+ if (!mDataValid) throw new IllegalStateException("Data is not valid");
TextView item;
if (null == convertView || !(convertView instanceof TextView)) {
LayoutInflater factory = LayoutInflater.from(mContext);
@@ -275,73 +292,87 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
return item;
}
+ @Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
+ if (!mDataValid) throw new IllegalStateException("Data is not valid");
return null;
}
+ @Override
public boolean areAllItemsEnabled() {
return true;
}
+ @Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
+ @Override
public int getGroupCount() {
+ if (!mDataValid) return 0;
return mNumberOfBins;
}
+ @Override
public int getChildrenCount(int groupPosition) {
+ if (!mDataValid) return 0;
return mItemMap[groupPositionToBin(groupPosition)];
}
+ @Override
public Object getGroup(int groupPosition) {
return null;
}
+ @Override
public Object getChild(int groupPosition, int childPosition) {
return null;
}
+ @Override
public long getGroupId(int groupPosition) {
+ if (!mDataValid) return 0;
return groupPosition;
}
+ @Override
public long getChildId(int groupPosition, int childPosition) {
+ if (!mDataValid) return 0;
if (moveCursorToChildPosition(groupPosition, childPosition)) {
return getLong(mIdIndex);
}
return 0;
}
+ @Override
public boolean hasStableIds() {
return true;
}
- public void registerDataSetObserver(DataSetObserver observer) {
- mObservers.add(observer);
- }
-
- public void unregisterDataSetObserver(DataSetObserver observer) {
- mObservers.remove(observer);
- }
-
+ @Override
public void onGroupExpanded(int groupPosition) {
}
+ @Override
public void onGroupCollapsed(int groupPosition) {
}
+ @Override
public long getCombinedChildId(long groupId, long childId) {
+ if (!mDataValid) return 0;
return childId;
}
+ @Override
public long getCombinedGroupId(long groupId) {
+ if (!mDataValid) return 0;
return groupId;
}
+ @Override
public boolean isEmpty() {
- return mCursor.isClosed() || mCursor.getCount() == 0;
+ return !mDataValid || mCursor == null || mCursor.isClosed() || mCursor.getCount() == 0;
}
}
diff --git a/src/com/android/browser/DownloadTouchIcon.java b/src/com/android/browser/DownloadTouchIcon.java
index 14404ff06..7bb93dc59 100644
--- a/src/com/android/browser/DownloadTouchIcon.java
+++ b/src/com/android/browser/DownloadTouchIcon.java
@@ -16,26 +16,29 @@
package com.android.browser;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.params.HttpClientParams;
+import org.apache.http.conn.params.ConnRouteParams;
+
+import android.app.Activity;
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.net.Proxy;
+import android.net.http.AndroidHttpClient;
import android.os.AsyncTask;
-import android.provider.Browser;
+import android.os.Bundle;
+import android.os.Message;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Images;
import android.webkit.WebView;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.params.HttpClientParams;
-import org.apache.http.conn.params.ConnRouteParams;
-
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -45,10 +48,17 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
private Cursor mCursor;
private final String mOriginalUrl;
private final String mUrl;
- private final String mUserAgent;
- private final BrowserActivity mActivity;
+ private final String mUserAgent; // Sites may serve a different icon to different UAs
+ private Message mMessage;
+
+ private final Activity mActivity;
/* package */ Tab mTab;
+ /**
+ * Use this ctor to store the touch icon in the bookmarks database for
+ * the originalUrl so we take account of redirects. Used when the user
+ * bookmarks a page from outside the bookmarks activity.
+ */
public DownloadTouchIcon(Tab tab, BrowserActivity activity, ContentResolver cr, WebView view) {
mTab = tab;
mActivity = activity;
@@ -59,24 +69,49 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
mUserAgent = view.getSettings().getUserAgentString();
}
- public DownloadTouchIcon(ContentResolver cr, String url) {
+ /**
+ * Use this ctor to download the touch icon and update the bookmarks database
+ * entry for the given url. Used when the user creates a bookmark from
+ * within the bookmarks activity and there haven't been any redirects.
+ * TODO: Would be nice to set the user agent here so that there is no
+ * potential for the three different ctors here to return different icons.
+ */
+ public DownloadTouchIcon(AddBookmarkPage activity, ContentResolver cr, String url) {
mTab = null;
- mActivity = null;
+ mActivity = activity;
mContentResolver = cr;
mOriginalUrl = null;
mUrl = url;
mUserAgent = null;
}
+ /**
+ * Use this ctor to not store the touch icon in a database, rather add it to
+ * the passed Message's data bundle with the key "touchIcon" and then send
+ * the message.
+ */
+ public DownloadTouchIcon(BrowserActivity activity, Message msg, String userAgent) {
+ mMessage = msg;
+ mActivity = activity;
+ mContentResolver = null;
+ mOriginalUrl = null;
+ mUrl = null;
+ mUserAgent = userAgent;
+ }
+
@Override
public Void doInBackground(String... values) {
- mCursor = BrowserBookmarksAdapter.queryBookmarksForUrl(mContentResolver,
- mOriginalUrl, mUrl, true);
- if (mCursor != null && mCursor.getCount() > 0) {
- String url = values[0];
+ if (mContentResolver != null) {
+ mCursor = Bookmarks.queryCombinedForUrl(mContentResolver,
+ mOriginalUrl, mUrl);
+ }
+
+ boolean inDatabase = mCursor != null && mCursor.getCount() > 0;
+
+ String url = values[0];
- AndroidHttpClient client = AndroidHttpClient.newInstance(
- mUserAgent);
+ if (inDatabase || mMessage != null) {
+ AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent);
HttpHost httpHost = Proxy.getPreferredHttpHost(mActivity, url);
if (httpHost != null) {
ConnRouteParams.setDefaultProxy(client.getParams(), httpHost);
@@ -89,7 +124,6 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
try {
HttpResponse response = client.execute(request);
-
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
if (entity != null) {
@@ -97,7 +131,12 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
if (content != null) {
Bitmap icon = BitmapFactory.decodeStream(
content, null, null);
- storeIcon(icon);
+ if (inDatabase) {
+ storeIcon(icon);
+ } else if (mMessage != null) {
+ Bundle b = mMessage.getData();
+ b.putParcelable("touchIcon", icon);
+ }
}
}
}
@@ -109,9 +148,15 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
client.close();
}
}
+
if (mCursor != null) {
mCursor.close();
}
+
+ if (mMessage != null) {
+ mMessage.sendToTarget();
+ }
+
return null;
}
@@ -133,17 +178,16 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
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()) {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ icon.compress(Bitmap.CompressFormat.PNG, 100, os);
+
+ ContentValues values = new ContentValues();
+ values.put(Images.TOUCH_ICON, os.toByteArray());
+ values.put(Images.URL, mCursor.getString(0));
+
do {
- mContentResolver.update(ContentUris.withAppendedId(
- Browser.BOOKMARKS_URI, mCursor.getInt(0)),
- values, null, null);
+ mContentResolver.update(Images.CONTENT_URI, values, null, null);
} while (mCursor.moveToNext());
}
}
diff --git a/src/com/android/browser/FindDialog.java b/src/com/android/browser/FindDialog.java
deleted file mode 100644
index 726138ef0..000000000
--- a/src/com/android/browser/FindDialog.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2007 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.text.Editable;
-import android.text.Selection;
-import android.text.Spannable;
-import android.text.TextWatcher;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
-import android.view.inputmethod.InputMethodManager;
-import android.webkit.WebView;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/* package */ class FindDialog extends WebDialog implements TextWatcher {
- private TextView mMatches;
-
- // Views with which the user can interact.
- private EditText mEditText;
- private View mNextButton;
- private View mPrevButton;
- private View mMatchesView;
-
- // When the dialog is opened up with old text, enter needs to be pressed
- // (or the text needs to be changed) before WebView.findAll can be called.
- // Once it has been called, enter should move to the next match.
- private boolean mMatchesFound;
- private int mNumberOfMatches;
-
- private View.OnClickListener mFindListener = new View.OnClickListener() {
- public void onClick(View v) {
- findNext();
- }
- };
-
- private View.OnClickListener mFindPreviousListener =
- new View.OnClickListener() {
- public void onClick(View v) {
- if (mWebView == null) {
- throw new AssertionError("No WebView for FindDialog::onClick");
- }
- mWebView.findNext(false);
- updateMatchesString();
- hideSoftInput();
- }
- };
-
- private void disableButtons() {
- mPrevButton.setEnabled(false);
- mNextButton.setEnabled(false);
- mPrevButton.setFocusable(false);
- mNextButton.setFocusable(false);
- }
-
- /* package */ FindDialog(BrowserActivity context) {
- super(context);
-
- LayoutInflater factory = LayoutInflater.from(context);
- factory.inflate(R.layout.browser_find, this);
-
- addCancel();
- mEditText = (EditText) findViewById(R.id.edit);
-
- View button = findViewById(R.id.next);
- button.setOnClickListener(mFindListener);
- mNextButton = button;
-
- button = findViewById(R.id.previous);
- button.setOnClickListener(mFindPreviousListener);
- mPrevButton = button;
-
- mMatches = (TextView) findViewById(R.id.matches);
- mMatchesView = findViewById(R.id.matches_view);
- disableButtons();
-
- }
-
- /**
- * Called by BrowserActivity.closeDialog. Start the animation to hide
- * the dialog, inform the WebView that the dialog is being dismissed,
- * and hide the soft keyboard.
- */
- public void dismiss() {
- super.dismiss();
- mWebView.notifyFindDialogDismissed();
- hideSoftInput();
- }
-
- @Override
- public boolean dispatchKeyEventPreIme(KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
- KeyEvent.DispatcherState state = getKeyDispatcherState();
- if (state != null) {
- int action = event.getAction();
- if (KeyEvent.ACTION_DOWN == action
- && event.getRepeatCount() == 0) {
- state.startTracking(event, this);
- return true;
- } else if (KeyEvent.ACTION_UP == action
- && !event.isCanceled() && state.isTracking(event)) {
- mBrowserActivity.closeDialogs();
- return true;
- }
- }
- }
- return super.dispatchKeyEventPreIme(event);
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- int keyCode = event.getKeyCode();
- if (event.getAction() == KeyEvent.ACTION_UP) {
- if (keyCode == KeyEvent.KEYCODE_ENTER
- && mEditText.hasFocus()) {
- if (mMatchesFound) {
- findNext();
- } else {
- findAll();
- // Set the selection to the end.
- Spannable span = (Spannable) mEditText.getText();
- Selection.setSelection(span, span.length());
- }
- return true;
- }
- }
- return super.dispatchKeyEvent(event);
- }
-
- private void findNext() {
- if (mWebView == null) {
- throw new AssertionError("No WebView for FindDialog::findNext");
- }
- mWebView.findNext(true);
- updateMatchesString();
- hideSoftInput();
- }
-
- public void show() {
- super.show();
- // In case the matches view is showing from a previous search
- mMatchesView.setVisibility(View.INVISIBLE);
- mMatchesFound = false;
- // This text is only here to ensure that mMatches has a height.
- mMatches.setText("0");
- mEditText.requestFocus();
- Spannable span = (Spannable) mEditText.getText();
- int length = span.length();
- Selection.setSelection(span, 0, length);
- span.setSpan(this, 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- disableButtons();
- InputMethodManager imm = (InputMethodManager)
- mBrowserActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(mEditText, 0);
- }
-
- // TextWatcher methods
- public void beforeTextChanged(CharSequence s,
- int start,
- int count,
- int after) {
- }
-
- public void onTextChanged(CharSequence s,
- int start,
- int before,
- int count) {
- findAll();
- }
-
- private void findAll() {
- if (mWebView == null) {
- throw new AssertionError(
- "No WebView for FindDialog::findAll");
- }
- CharSequence find = mEditText.getText();
- if (0 == find.length()) {
- disableButtons();
- mWebView.clearMatches();
- mMatchesView.setVisibility(View.INVISIBLE);
- } else {
- mMatchesView.setVisibility(View.VISIBLE);
- int found = mWebView.findAll(find.toString());
- mMatchesFound = true;
- setMatchesFound(found);
- if (found < 2) {
- disableButtons();
- if (found == 0) {
- // Cannot use getQuantityString, which ignores the "zero"
- // quantity.
- // FIXME: is this fix is beyond the scope
- // of adding touch selection to gingerbread?
- // mMatches.setText(mBrowserActivity.getResources().getString(
- // R.string.no_matches));
- }
- } else {
- mPrevButton.setFocusable(true);
- mNextButton.setFocusable(true);
- mPrevButton.setEnabled(true);
- mNextButton.setEnabled(true);
- }
- }
- }
-
- private void setMatchesFound(int found) {
- mNumberOfMatches = found;
- updateMatchesString();
- }
-
- public void setText(String text) {
- mEditText.setText(text);
- findAll();
- }
-
- private void updateMatchesString() {
- // Note: updateMatchesString is only called by methods that have already
- // checked mWebView for null.
- String template = mBrowserActivity.getResources().
- getQuantityString(R.plurals.matches_found, mNumberOfMatches,
- mWebView.findIndex() + 1, mNumberOfMatches);
-
- mMatches.setText(template);
- }
-
- public void afterTextChanged(Editable s) {
- }
-}
diff --git a/src/com/android/browser/HistoryItem.java b/src/com/android/browser/HistoryItem.java
index 72e1b19e5..b591b03f1 100644
--- a/src/com/android/browser/HistoryItem.java
+++ b/src/com/android/browser/HistoryItem.java
@@ -18,12 +18,8 @@
package com.android.browser;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.provider.Browser;
import android.view.View;
import android.widget.CompoundButton;
-import android.widget.ImageView;
-import android.widget.TextView;
/**
* Layout representing a history item in the classic history viewer.
@@ -45,8 +41,7 @@ import android.widget.TextView;
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {
- Bookmarks.addBookmark(mContext,
- mContext.getContentResolver(), mUrl, getName(), null, true);
+ Bookmarks.addBookmark(mContext, true, mUrl, getName(), null, true);
LogTag.logBookmarkAdded(mUrl, "history");
} else {
Bookmarks.removeFromBookmarks(mContext,
diff --git a/src/com/android/browser/PageProgressView.java b/src/com/android/browser/PageProgressView.java
new file mode 100644
index 000000000..183566add
--- /dev/null
+++ b/src/com/android/browser/PageProgressView.java
@@ -0,0 +1,94 @@
+
+/*
+ * Copyright (C) 2010 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.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ *
+ */
+public class PageProgressView extends ImageView {
+
+ private int mProgress;
+ private int mMaxProgress;
+ private Rect mBounds;
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public PageProgressView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public PageProgressView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ /**
+ * @param context
+ */
+ public PageProgressView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ private void init(Context ctx) {
+ mMaxProgress = 10000;
+ mBounds = new Rect(0,0,0,0);
+ mProgress = 0;
+ }
+
+ @Override
+ public void onLayout(boolean f, int l, int t, int r, int b) {
+ mBounds.left = 0;
+ mBounds.right = (r - l) * mProgress / mMaxProgress;
+ mBounds.top = 0;
+ mBounds.bottom = b-t;
+ }
+
+ void setMaxProgress(int max) {
+ mMaxProgress = max;
+ }
+
+ void setProgress(int progress) {
+ mProgress = progress;
+ mBounds.right = getWidth()*mProgress/mMaxProgress;
+ invalidate();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+// super.onDraw(canvas);
+ Drawable d = getDrawable();
+ d.setBounds(mBounds);
+ d.draw(canvas);
+ }
+
+}
diff --git a/src/com/android/browser/SaveToHomescreenDialog.java b/src/com/android/browser/SaveToHomescreenDialog.java
new file mode 100644
index 000000000..15f0aeafc
--- /dev/null
+++ b/src/com/android/browser/SaveToHomescreenDialog.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2010 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.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.ParseException;
+import android.net.Uri;
+import android.net.WebAddress;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Browser;
+import android.util.Log;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Date;
+
+public class SaveToHomescreenDialog extends Activity {
+
+ private EditText mTitle;
+ private String mUrl;
+ private Bitmap mFavicon;
+ private Bitmap mTouchIcon;
+
+ private View.OnClickListener mOk = new View.OnClickListener() {
+ public void onClick(View v) {
+ if (save()) {
+ finish();
+ }
+ }
+ };
+
+ private View.OnClickListener mCancel = new View.OnClickListener() {
+ public void onClick(View v) {
+ finish();
+ }
+ };
+
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ requestWindowFeature(Window.FEATURE_LEFT_ICON);
+ setContentView(R.layout.browser_add_bookmark_const_url);
+ setTitle(R.string.create_shortcut_bookmark);
+ getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
+ R.drawable.ic_list_bookmark);
+
+ String title = null;
+ String url = null;
+ Bundle map = getIntent().getExtras();
+ if (map != null) {
+ title = map.getString("title");
+ }
+
+ mUrl = map.getString("url");
+ mFavicon = (Bitmap)map.getParcelable("favicon");
+ mTouchIcon = (Bitmap)map.getParcelable("touchIcon");
+
+ Bitmap icon = BookmarkUtils.createIcon(this, mTouchIcon, mFavicon,
+ BookmarkUtils.BookmarkIconType.ICON_HOME_SHORTCUT);
+ getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, new BitmapDrawable(icon));
+
+ mTitle = (EditText) findViewById(R.id.title);
+ mTitle.setText(title);
+
+ Button okButton = (Button) findViewById(R.id.OK);
+ okButton.setOnClickListener(mOk);
+
+ Button cancelButton = (Button) findViewById(R.id.cancel);
+ cancelButton.setOnClickListener(mCancel);
+
+ if (!getWindow().getDecorView().isInTouchMode()) {
+ okButton.requestFocus();
+ }
+ }
+
+ /**
+ * Parse the data entered in the dialog and send an intent to create an
+ * icon on the homescreen.
+ */
+ private boolean save() {
+ String title = mTitle.getText().toString().trim();
+ String unfilteredUrl = BrowserActivity.fixUrl(mUrl);
+ if (title.length() == 0) {
+ mTitle.setError(getResources().getText(R.string.bookmark_needs_title));
+ return false;
+ }
+
+ String url = unfilteredUrl.trim();
+
+ sendBroadcast(BookmarkUtils.createAddToHomeIntent(this, url, title,
+ mTouchIcon, mFavicon));
+ setResult(RESULT_OK);
+ return true;
+ }
+}
diff --git a/src/com/android/browser/ScrollWebView.java b/src/com/android/browser/ScrollWebView.java
new file mode 100644
index 000000000..97bd2c7a1
--- /dev/null
+++ b/src/com/android/browser/ScrollWebView.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 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.util.AttributeSet;
+import android.view.View;
+import android.webkit.WebView;
+
+import java.util.Map;
+
+/**
+ * Manage WebView scroll events
+ */
+public class ScrollWebView extends WebView {
+
+ private ScrollListener mScrollListener;
+ private boolean mIsCancelled;
+ private Runnable mScrollRunnable;
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ * @param javascriptInterfaces
+ */
+ public ScrollWebView(Context context, AttributeSet attrs, int defStyle,
+ Map<String, Object> javascriptInterfaces, boolean privateBrowsing) {
+ super(context, attrs, defStyle, javascriptInterfaces, privateBrowsing);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public ScrollWebView(Context context, AttributeSet attrs, int defStyle,
+ boolean privateBrowsing) {
+ super(context, attrs, defStyle, privateBrowsing);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public ScrollWebView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * @param context
+ */
+ public ScrollWebView(Context context) {
+ super(context);
+ }
+
+ void hideEmbeddedTitleBar() {
+ scrollBy(0, getVisibleTitleHeight());
+ }
+
+ @Override
+ public void setEmbeddedTitleBar(final View title) {
+ super.setEmbeddedTitleBar(title);
+ if (title != null && mScrollListener != null) {
+ // allow the scroll listener to initialize its state
+ post(new Runnable() {
+ @Override
+ public void run() {
+ mScrollListener.onScroll((title.getHeight() == 0 ||
+ getVisibleTitleHeight() > 0));
+ }
+ });
+ }
+ }
+
+ @Override
+ public void stopScroll() {
+ mIsCancelled = true;
+ super.stopScroll();
+ }
+
+ @Override
+ protected void onScrollChanged(int l, final int t, int ol, int ot) {
+ super.onScrollChanged(l, t, ol, ot);
+ if (!mIsCancelled) {
+ post(mScrollRunnable);
+ } else {
+ mIsCancelled = false;
+ }
+ }
+
+ void setScrollListener(ScrollListener l) {
+ mScrollListener = l;
+ if (mScrollListener != null) {
+ mScrollRunnable = new Runnable() {
+ public void run() {
+ if (!mIsCancelled) {
+ mScrollListener.onScroll(getVisibleTitleHeight() > 0);
+ }
+ }
+ };
+ }
+ }
+
+ // callback for scroll events
+
+ interface ScrollListener {
+ public void onScroll(boolean titleVisible);
+ }
+
+}
diff --git a/src/com/android/browser/SelectDialog.java b/src/com/android/browser/SelectDialog.java
deleted file mode 100644
index 461127a5e..000000000
--- a/src/com/android/browser/SelectDialog.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2010 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.provider.Browser;
-import android.view.LayoutInflater;
-import android.view.View;
-
-/* package */ class SelectDialog extends WebDialog {
- private View mCopyButton;
- private View mSelectAllButton;
- private View mShareButton;
- private View mFindButton;
-
- SelectDialog(BrowserActivity context) {
- super(context);
- LayoutInflater factory = LayoutInflater.from(context);
- factory.inflate(R.layout.browser_select, this);
- addCancel();
-
- mCopyButton = findViewById(R.id.copy);
- mCopyButton.setOnClickListener(mCopyListener);
- mSelectAllButton = findViewById(R.id.select_all);
- mSelectAllButton.setOnClickListener(mSelectAllListener);
- mShareButton = findViewById(R.id.share);
- mShareButton.setOnClickListener(mShareListener);
- mFindButton = findViewById(R.id.find);
- mFindButton.setOnClickListener(mFindListener);
- }
-
- private View.OnClickListener mCopyListener = new View.OnClickListener() {
- public void onClick(View v) {
- mWebView.copySelection();
- mBrowserActivity.closeDialogs();
- }
- };
-
- private View.OnClickListener mSelectAllListener = new View.OnClickListener() {
- public void onClick(View v) {
- mWebView.selectAll();
- }
- };
-
- private View.OnClickListener mShareListener = new View.OnClickListener() {
- public void onClick(View v) {
- String selection = mWebView.getSelection();
- Browser.sendString(mBrowserActivity, selection);
- mBrowserActivity.closeDialogs();
- }
- };
-
- private View.OnClickListener mFindListener = new View.OnClickListener() {
- public void onClick(View v) {
- String selection = mWebView.getSelection();
- mBrowserActivity.closeDialogs();
- mBrowserActivity.showFindDialog();
- mBrowserActivity.setFindDialogText(selection);
- }
- };
-
- /**
- * Called by BrowserActivity.closeDialog. Start the animation to hide
- * the dialog, and inform the WebView that the dialog is being dismissed.
- */
- @Override
- public void dismiss() {
- super.dismiss();
- mWebView.notifySelectDialogDismissed();
- }
-
-}
diff --git a/src/com/android/browser/ShortcutActivity.java b/src/com/android/browser/ShortcutActivity.java
new file mode 100644
index 000000000..7994d0a77
--- /dev/null
+++ b/src/com/android/browser/ShortcutActivity.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 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.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class ShortcutActivity extends Activity
+ implements BookmarksHistoryCallbacks {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
+ FragmentManager fm = getFragmentManager();
+ FragmentTransaction transaction = fm.openTransaction();
+ Bundle extras = new Bundle();
+ extras.putBoolean(BrowserBookmarksPage.EXTRA_SHORTCUT, true);
+ extras.putBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW, true);
+ Fragment frag = Fragment.instantiate(this, BrowserBookmarksPage.class.getName(), extras);
+ transaction.add(android.R.id.content, frag);
+ transaction.commit();
+ }
+
+ /**
+ * not used for shortcuts
+ */
+ @Override
+ public void onRemoveParentChildRelationShips() {}
+
+ /**
+ * handle fragment startActivity
+ */
+ @Override
+ public void startActivityFromFragment(Fragment f, Intent intent, int requestCode) {
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ }
+
+ /**
+ * not used for shortcuts
+ */
+ @Override
+ public void onUrlSelected(String url, boolean newWindow) {}
+
+}
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 7019c8a01..dc4242892 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -16,13 +16,8 @@
package com.android.browser;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Vector;
+import com.android.browser.TabControl.TabChangeListener;
+import com.android.common.speech.LoggingEvents;
import android.app.AlertDialog;
import android.app.SearchManager;
@@ -42,14 +37,15 @@ import android.os.Bundle;
import android.os.Message;
import android.os.SystemClock;
import android.provider.Browser;
+import android.provider.BrowserContract.History;
import android.speech.RecognizerResultsIntent;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewStub;
-import android.view.View.OnClickListener;
import android.webkit.ConsoleMessage;
import android.webkit.CookieSyncManager;
import android.webkit.DownloadListener;
@@ -71,7 +67,12 @@ import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
-import com.android.common.speech.LoggingEvents;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Vector;
/**
* Class for maintaining Tabs with a main WebView and a subwindow.
@@ -155,6 +156,7 @@ class Tab {
static final String PARENTTAB = "parentTab";
static final String APPID = "appid";
static final String ORIGINALURL = "originalUrl";
+ static final String INCOGNITO = "privateBrowsingEnabled";
// -------------------------------------------------------------------------
@@ -495,7 +497,7 @@ class Tab {
// update the bookmark database for favicon
if (favicon != null) {
- BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity
+ Bookmarks.updateFavicon(mActivity
.getContentResolver(), null, url, favicon);
}
@@ -510,6 +512,9 @@ class Tab {
if (mInForeground) {
mActivity.onPageStarted(view, url, favicon);
}
+ if (getTabChangeListener() != null) {
+ getTabChangeListener().onPageStarted(Tab.this);
+ }
}
@Override
@@ -531,6 +536,9 @@ class Tab {
if (mInForeground) {
mActivity.onPageFinished(view, url);
}
+ if (getTabChangeListener() != null) {
+ getTabChangeListener().onPageFinished(Tab.this);
+ }
}
// return true if want to hijack the url to let another app to handle it
@@ -590,8 +598,12 @@ class Tab {
errorCode != WebViewClient.ERROR_FILE) {
queueError(errorCode, description);
}
- Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
- + " " + description);
+
+ // Don't log URLs when in private browsing mode
+ if (!getWebView().isPrivateBrowsingEnabled()) {
+ Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
+ + " " + description);
+ }
// We need to reset the title after an error if it is in foreground.
if (mInForeground) {
@@ -661,6 +673,9 @@ class Tab {
@Override
public void doUpdateVisitedHistory(WebView view, String url,
boolean isReload) {
+ // Don't save anything in private browsing mode
+ if (getWebView().isPrivateBrowsingEnabled()) return;
+
if (url.regionMatches(true, 0, "about:", 0, 6)) {
return;
}
@@ -680,6 +695,7 @@ class Tab {
final ContentResolver cr = mActivity.getContentResolver();
final String newUrl = url;
new AsyncTask<Void, Void, Void>() {
+ @Override
protected Void doInBackground(Void... unused) {
Browser.updateVisitedHistory(cr, newUrl, true);
return null;
@@ -956,6 +972,9 @@ class Tab {
if (mInForeground) {
mActivity.onProgressChanged(view, newProgress);
}
+ if (getTabChangeListener() != null) {
+ getTabChangeListener().onProgress(Tab.this, newProgress);
+ }
}
@Override
@@ -965,69 +984,67 @@ class Tab {
// here, if url is null, we want to reset the title
mActivity.setUrlTitle(pageUrl, title);
}
+ TabChangeListener tcl = getTabChangeListener();
+ if (tcl != null) {
+ tcl.onUrlAndTitle(Tab.this, pageUrl,title);
+ }
if (pageUrl == null || pageUrl.length()
>= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
return;
}
- new AsyncTask<Void, Void, Void>() {
- protected Void doInBackground(Void... unused) {
- // See if we can find the current url in our history
- // database and add the new title to it.
- String url = pageUrl;
- if (url.startsWith("http://www.")) {
- url = url.substring(11);
- } else if (url.startsWith("http://")) {
- url = url.substring(4);
- }
- // Escape wildcards for LIKE operator.
- url = url.replace("\\", "\\\\").replace("%", "\\%")
- .replace("_", "\\_");
- Cursor c = null;
- try {
- final ContentResolver cr
- = mActivity.getContentResolver();
- url = "%" + url;
- String [] selArgs = new String[] { url };
- String where = Browser.BookmarkColumns.URL
- + " LIKE ? ESCAPE '\\' AND "
- + Browser.BookmarkColumns.BOOKMARK + " = 0";
- c = cr.query(Browser.BOOKMARKS_URI, new String[]
- { Browser.BookmarkColumns._ID }, where, selArgs,
- null);
- if (c.moveToFirst()) {
- // Current implementation of database only has one
- // entry per url.
- ContentValues map = new ContentValues();
- map.put(Browser.BookmarkColumns.TITLE, title);
- String[] projection = new String[]
- { Integer.valueOf(c.getInt(0)).toString() };
- cr.update(Browser.BOOKMARKS_URI, map, "_id = ?",
- projection);
+
+ // Update the title in the history database if not in private browsing mode
+ if (!getWebView().isPrivateBrowsingEnabled()) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ // See if we can find the current url in our history
+ // database and add the new title to it.
+ String url = pageUrl;
+ if (url.startsWith("http://www.")) {
+ url = url.substring(11);
+ } else if (url.startsWith("http://")) {
+ url = url.substring(4);
}
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "Tab onReceived title", e);
- } catch (SQLiteException ex) {
- Log.e(LOGTAG,
- "onReceivedTitle() caught SQLiteException: ",
- ex);
- } finally {
- if (c != null) c.close();
+ // Escape wildcards for LIKE operator.
+ url = url.replace("\\", "\\\\").replace("%", "\\%")
+ .replace("_", "\\_");
+ Cursor c = null;
+ try {
+ final ContentResolver cr = mActivity.getContentResolver();
+ String selection = History.URL + " LIKE ? ESCAPE '\\'";
+ String [] selectionArgs = new String[] { "%" + url };
+ ContentValues values = new ContentValues();
+ values.put(History.TITLE, title);
+ cr.update(History.CONTENT_URI, values, selection, selectionArgs);
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "Tab onReceived title", e);
+ } catch (SQLiteException ex) {
+ Log.e(LOGTAG,
+ "onReceivedTitle() caught SQLiteException: ",
+ ex);
+ } finally {
+ if (c != null) c.close();
+ }
+ return null;
}
- return null;
- }
- }.execute();
+ }.execute();
+ }
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
if (icon != null) {
- BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity
+ Bookmarks.updateFavicon(mActivity
.getContentResolver(), view.getOriginalUrl(), view
.getUrl(), icon);
}
if (mInForeground) {
mActivity.setFavicon(icon);
}
+ if (getTabChangeListener() != null) {
+ getTabChangeListener().onFavicon(Tab.this, icon);
+ }
}
@Override
@@ -1048,16 +1065,6 @@ class Tab {
}
@Override
- public void onSelectionDone(WebView view) {
- if (mInForeground) mActivity.closeDialogs();
- }
-
- @Override
- public void onSelectionStart(WebView view) {
- if (false && mInForeground) mActivity.showSelectDialog();
- }
-
- @Override
public void onShowCustomView(View view,
WebChromeClient.CustomViewCallback callback) {
if (mInForeground) mActivity.onShowCustomView(view, callback);
@@ -1150,6 +1157,9 @@ class Tab {
}
}
+ // Don't log console messages in private browsing mode
+ if (getWebView().isPrivateBrowsingEnabled()) return true;
+
String message = "Console: " + consoleMessage.message() + " "
+ consoleMessage.sourceId() + ":"
+ consoleMessage.lineNumber();
@@ -1202,9 +1212,9 @@ class Tab {
}
@Override
- public void openFileChooser(ValueCallback<Uri> uploadMsg) {
+ public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
if (mInForeground) {
- mActivity.openFileChooser(uploadMsg);
+ mActivity.openFileChooser(uploadMsg, acceptType);
} else {
uploadMsg.onReceiveValue(null);
}
@@ -1216,10 +1226,12 @@ class Tab {
@Override
public void getVisitedHistory(final ValueCallback<String[]> callback) {
AsyncTask<Void, Void, String[]> task = new AsyncTask<Void, Void, String[]>() {
+ @Override
public String[] doInBackground(Void... unused) {
return Browser.getVisitedHistory(mActivity
.getContentResolver());
}
+ @Override
public void onPostExecute(String[] result) {
callback.onReceiveValue(result);
};
@@ -1248,7 +1260,7 @@ class Tab {
// Unlike the others, do not call mClient's version, which would
// change the progress bar. However, we do want to remove the
// find or select dialog.
- mBrowserActivity.closeDialogs();
+ mBrowserActivity.endActionMode();
}
@Override
public void doUpdateVisitedHistory(WebView view, String url,
@@ -1449,7 +1461,7 @@ class Tab {
*/
boolean createSubWindow() {
if (mSubView == null) {
- mActivity.closeDialogs();
+ mActivity.endActionMode();
mSubViewContainer = mInflateService.inflate(
R.layout.browser_subwindow, null);
mSubView = (WebView) mSubViewContainer.findViewById(R.id.webview);
@@ -1497,7 +1509,7 @@ class Tab {
*/
void dismissSubWindow() {
if (mSubView != null) {
- mActivity.closeDialogs();
+ mActivity.endActionMode();
BrowserSettings.getInstance().deleteObserver(
mSubView.getSettings());
mSubView.destroy();
@@ -1522,7 +1534,7 @@ class Tab {
void removeSubWindow(ViewGroup content) {
if (mSubView != null) {
content.removeView(mSubViewContainer);
- mActivity.closeDialogs();
+ mActivity.endActionMode();
}
}
@@ -1581,7 +1593,7 @@ class Tab {
(FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
wrapper.removeView(mMainView);
content.removeView(mContainer);
- mActivity.closeDialogs();
+ mActivity.endActionMode();
removeSubWindow(content);
}
@@ -1855,6 +1867,9 @@ class Tab {
// FIXME: The only place we cared about subwindow was for
// bookmarking (i.e. not when saving state). Was this deliberate?
final WebBackForwardList list = mMainView.copyBackForwardList();
+ if (list == null) {
+ Log.w(LOGTAG, "populatePickerData called and WebBackForwardList is null");
+ }
final WebHistoryItem item = list != null ? list.getCurrentItem() : null;
populatePickerData(item);
}
@@ -1863,7 +1878,9 @@ class Tab {
// WebView.
private void populatePickerData(WebHistoryItem item) {
mPickerData = new PickerData();
- if (item != null) {
+ if (item == null) {
+ Log.w(LOGTAG, "populatePickerData called with a null WebHistoryItem");
+ } else {
mPickerData.mUrl = item.getUrl();
mPickerData.mTitle = item.getTitle();
mPickerData.mFavicon = item.getFavicon();
@@ -1962,35 +1979,12 @@ class Tab {
return true;
}
- /*
- * Opens the find and select text dialogs. Called by BrowserActivity.
+ /**
+ * always get the TabChangeListener form the tab control
+ * @return the TabControl change listener
*/
- WebView showDialog(WebDialog dialog) {
- LinearLayout container;
- WebView view;
- if (mSubView != null) {
- view = mSubView;
- container = (LinearLayout) mSubViewContainer.findViewById(
- R.id.inner_container);
- } else {
- view = mMainView;
- container = mContainer;
- }
- dialog.show();
- container.addView(dialog, 0, new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- dialog.setWebView(view);
- return view;
+ private TabChangeListener getTabChangeListener() {
+ return mActivity.getTabControl().getTabChangeListener();
}
- /*
- * Close the find or select dialog. Called by BrowserActivity.closeDialog.
- */
- void closeDialog(WebDialog dialog) {
- // The dialog may be attached to the subwindow. Ensure that the
- // correct parent has it removed.
- LinearLayout parent = (LinearLayout) dialog.getParent();
- if (parent != null) parent.removeView(dialog);
- }
}
diff --git a/src/com/android/browser/TabBar.java b/src/com/android/browser/TabBar.java
new file mode 100644
index 000000000..cf0f27ca4
--- /dev/null
+++ b/src/com/android/browser/TabBar.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2010 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.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.PaintDrawable;
+import android.view.ContextMenu;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.webkit.WebView;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.browser.ScrollWebView.ScrollListener;
+import com.android.browser.TabControl.TabChangeListener;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * tabbed title bar for xlarge screen browser
+ */
+public class TabBar extends LinearLayout
+ implements TabChangeListener, ScrollListener, OnClickListener {
+
+ private static final int PROGRESS_MAX = 100;
+
+ private BrowserActivity mBrowserActivity;
+
+ private final int mTabWidthSelected;
+ private final int mTabWidthUnselected;
+
+ private TitleBarXLarge mTitleBar;
+
+ private TabScrollView mTabs;
+ private TabControl mControl;
+
+ private Map<Tab, TabViewData> mTabMap;
+
+ private float mDensityScale;
+ private boolean mUserRequestedUrlbar;
+ private boolean mTitleVisible;
+ private boolean mShowUrlMode;
+
+ public TabBar(BrowserActivity context, TabControl tabcontrol, TitleBarXLarge titlebar) {
+ super(context);
+ Resources res = context.getResources();
+ mTabWidthSelected = (int) res.getDimension(R.dimen.tab_width_selected);
+ mTabWidthUnselected = (int) res.getDimension(R.dimen.tab_width_unselected);
+
+ mTitleBar = titlebar;
+ mTitleBar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT));
+ mDensityScale = context.getResources().getDisplayMetrics().density;
+ mTabMap = new HashMap<Tab, TabViewData>();
+ mBrowserActivity = context;
+ mControl = tabcontrol;
+ Resources resources = context.getResources();
+ LayoutInflater factory = LayoutInflater.from(context);
+ factory.inflate(R.layout.tab_bar, this);
+ mTabs = (TabScrollView) findViewById(R.id.tabs);
+
+ // TODO: Change enabled states based on whether you can go
+ // back/forward. Probably should be done inside onPageStarted.
+
+ // build tabs
+ int tabcount = mControl.getTabCount();
+ for (int i = 0; i < tabcount; i++) {
+ Tab tab = mControl.getTab(i);
+ TabViewData data = buildTab(tab);
+ TabView tv = buildView(data);
+ }
+ mTabs.setSelectedTab(mControl.getCurrentIndex());
+
+ // register the tab change listener
+ mControl.setOnTabChangeListener(this);
+ mUserRequestedUrlbar = false;
+ mTitleVisible = true;
+ }
+
+ public void onClick(View view) {
+ if (mTabs.getSelectedTab() == view) {
+ if (mBrowserActivity.isFakeTitleBarShowing() && !isLoading()) {
+ mBrowserActivity.hideFakeTitleBar();
+ } else {
+ showUrlBar();
+ }
+ // temporarily disabled
+ // mTitleBar.requestUrlInputFocus();
+ } else {
+ TabViewData data = (TabViewData) view.getTag();
+ int ix = mControl.getTabIndex(data.mTab);
+ mTabs.setSelectedTab(ix);
+ mBrowserActivity.switchToTab(ix);
+ }
+ }
+
+ private void showUrlBar() {
+ mBrowserActivity.stopScrolling();
+ mBrowserActivity.showFakeTitleBar();
+ mUserRequestedUrlbar = true;
+ }
+
+ private void setShowUrlMode(boolean showUrl) {
+ mShowUrlMode = showUrl;
+ }
+
+ // callback after fake titlebar is shown
+ void onShowTitleBar() {
+ setShowUrlMode(false);
+ }
+
+ // callback after fake titlebar is hidden
+ void onHideTitleBar() {
+ setShowUrlMode(!mTitleVisible);
+ Tab tab = mControl.getCurrentTab();
+ tab.getWebView().requestFocus();
+ mUserRequestedUrlbar = false;
+ }
+
+ // webview scroll listener
+
+ @Override
+ public void onScroll(boolean titleVisible) {
+ mTitleVisible = titleVisible;
+ if (!mShowUrlMode && !mTitleVisible && !isLoading()) {
+ if (mUserRequestedUrlbar) {
+ mBrowserActivity.hideFakeTitleBar();
+ } else {
+ setShowUrlMode(true);
+ }
+ } else if (mTitleVisible && !isLoading()) {
+ if (mShowUrlMode) {
+ setShowUrlMode(false);
+ }
+ }
+ }
+
+ @Override
+ public void createContextMenu(ContextMenu menu) {
+ MenuInflater inflater = mBrowserActivity.getMenuInflater();
+ inflater.inflate(R.menu.title_context, menu);
+ mBrowserActivity.onCreateContextMenu(menu, this, null);
+ }
+
+ private TabViewData buildTab(Tab tab) {
+ TabViewData data = new TabViewData(tab);
+ mTabMap.put(tab, data);
+ return data;
+ }
+
+ private TabView buildView(final TabViewData data) {
+ TabView tv = new TabView(mBrowserActivity, data);
+ tv.setTag(data);
+ tv.setOnClickListener(this);
+ mTabs.addTab(tv);
+ return tv;
+ }
+
+ /**
+ * View used in the tab bar
+ */
+ class TabView extends LinearLayout implements OnClickListener {
+
+ TabViewData mTabData;
+ View mTabContent;
+ TextView mTitle;
+ View mIncognito;
+ ImageView mIconView;
+ ImageView mLock;
+ ImageView mClose;
+ boolean mSelected;
+ boolean mInLoad;
+
+ /**
+ * @param context
+ */
+ public TabView(Context context, TabViewData tab) {
+ super(context);
+ mTabData = tab;
+ setGravity(Gravity.CENTER_VERTICAL);
+ setOrientation(LinearLayout.HORIZONTAL);
+ setBackgroundResource(R.drawable.tab_background);
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ mTabContent = inflater.inflate(R.layout.tab_title, this, true);
+ mTitle = (TextView) mTabContent.findViewById(R.id.title);
+ mIconView = (ImageView) mTabContent.findViewById(R.id.favicon);
+ mLock = (ImageView) mTabContent.findViewById(R.id.lock);
+ mClose = (ImageView) mTabContent.findViewById(R.id.close);
+ mClose.setOnClickListener(this);
+ mIncognito = mTabContent.findViewById(R.id.incognito);
+ mSelected = false;
+ mInLoad = false;
+ // update the status
+ updateFromData();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mClose) {
+ closeTab();
+ }
+ }
+
+ private void updateFromData() {
+ mTabData.mTabView = this;
+ if (mTabData.mUrl != null) {
+ setDisplayTitle(mTabData.mUrl);
+ }
+ if (mTabData.mTitle != null) {
+ setDisplayTitle(mTabData.mTitle);
+ }
+ setProgress(mTabData.mProgress);
+ if (mTabData.mIcon != null) {
+ setFavicon(mTabData.mIcon);
+ }
+ if (mTabData.mLock != null) {
+ setLock(mTabData.mLock);
+ }
+ if (mTabData.mTab != null) {
+ mIncognito.setVisibility(
+ mTabData.mTab.getWebView().isPrivateBrowsingEnabled() ?
+ View.VISIBLE : View.GONE);
+ }
+ }
+
+ @Override
+ public void setSelected(boolean selected) {
+ mSelected = selected;
+ mClose.setVisibility(mSelected ? View.VISIBLE : View.GONE);
+ mTitle.setTextAppearance(mBrowserActivity, mSelected ?
+ R.style.TabTitleSelected : R.style.TabTitleUnselected);
+ setHorizontalFadingEdgeEnabled(!mSelected);
+ setFadingEdgeLength(50);
+ super.setSelected(selected);
+ setLayoutParams(new LayoutParams(selected ?
+ mTabWidthSelected : mTabWidthUnselected,
+ LayoutParams.MATCH_PARENT));
+ }
+
+ void setDisplayTitle(String title) {
+ mTitle.setText(title);
+ }
+
+ void setFavicon(Drawable d) {
+ mIconView.setImageDrawable(d);
+ }
+
+ void setLock(Drawable d) {
+ if (null == d) {
+ mLock.setVisibility(View.GONE);
+ } else {
+ mLock.setImageDrawable(d);
+ mLock.setVisibility(View.VISIBLE);
+ }
+ }
+
+ void setTitleCompoundDrawables(Drawable left, Drawable top,
+ Drawable right, Drawable bottom) {
+ mTitle.setCompoundDrawables(left, top, right, bottom);
+ }
+
+ void setProgress(int newProgress) {
+ if (newProgress >= PROGRESS_MAX) {
+ mInLoad = false;
+ } else {
+ if (!mInLoad && getWindowToken() != null) {
+ mInLoad = true;
+ }
+ }
+ }
+
+ private void closeTab() {
+ if (mTabData.mTab == mControl.getCurrentTab()) {
+ mBrowserActivity.closeCurrentWindow();
+ } else {
+ mBrowserActivity.closeTab(mTabData.mTab);
+ }
+ }
+
+ }
+
+ /**
+ * Store tab state within the title bar
+ */
+ class TabViewData {
+
+ Tab mTab;
+ TabView mTabView;
+ int mProgress;
+ Drawable mIcon;
+ Drawable mLock;
+ String mTitle;
+ String mUrl;
+
+ TabViewData(Tab tab) {
+ mTab = tab;
+ }
+
+ void setUrlAndTitle(String url, String title) {
+ mUrl = url;
+ mTitle = title;
+ if (mTabView != null) {
+ if (title != null) {
+ mTabView.setDisplayTitle(title);
+ } else if (url != null) {
+ mTabView.setDisplayTitle(url);
+ }
+ }
+ }
+
+ void setProgress(int newProgress) {
+ mProgress = newProgress;
+ if (mTabView != null) {
+ mTabView.setProgress(mProgress);
+ }
+ }
+
+ void setFavicon(Bitmap icon) {
+ Drawable[] array = new Drawable[3];
+ array[0] = new PaintDrawable(Color.BLACK);
+ array[1] = new PaintDrawable(Color.WHITE);
+ 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);
+ mIcon = d;
+ if (mTabView != null) {
+ mTabView.setFavicon(mIcon);
+ }
+ }
+
+ }
+
+ // TabChangeListener implementation
+
+ @Override
+ public void onCurrentTab(Tab tab) {
+ mTabs.setSelectedTab(mControl.getCurrentIndex());
+ TabViewData tvd = mTabMap.get(tab);
+ if (tvd != null) {
+ tvd.setProgress(tvd.mProgress);
+ // update the scroll state
+ WebView webview = tab.getWebView();
+ onScroll(webview.getVisibleTitleHeight() > 0);
+ }
+ }
+
+ @Override
+ public void onFavicon(Tab tab, Bitmap favicon) {
+ TabViewData tvd = mTabMap.get(tab);
+ if (tvd != null) {
+ tvd.setFavicon(favicon);
+ }
+ }
+
+ @Override
+ public void onNewTab(Tab tab) {
+ TabViewData tvd = buildTab(tab);
+ buildView(tvd);
+ }
+
+ @Override
+ public void onProgress(Tab tab, int progress) {
+ TabViewData tvd = mTabMap.get(tab);
+ if (tvd != null) {
+ tvd.setProgress(progress);
+ }
+ }
+
+ @Override
+ public void onRemoveTab(Tab tab) {
+ TabViewData tvd = mTabMap.get(tab);
+ TabView tv = tvd.mTabView;
+ if (tv != null) {
+ mTabs.removeTab(tv);
+ }
+ mTabMap.remove(tab);
+ }
+
+ @Override
+ public void onUrlAndTitle(Tab tab, String url, String title) {
+ TabViewData tvd = mTabMap.get(tab);
+ if (tvd != null) {
+ tvd.setUrlAndTitle(url, title);
+ }
+ }
+
+ @Override
+ public void onPageFinished(Tab tab) {
+ }
+
+ @Override
+ public void onPageStarted(Tab tab) {
+ }
+
+
+ private boolean isLoading() {
+ return mTabMap.get(mControl.getCurrentTab()).mTabView.mInLoad;
+ }
+
+}
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index afd4ea827..d7435d712 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -29,6 +29,7 @@ import android.webkit.WebView;
import java.io.File;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Vector;
class TabControl {
@@ -47,6 +48,8 @@ class TabControl {
private final BrowserActivity mActivity;
// Directory to store thumbnails for each WebView.
private final File mThumbnailDir;
+ // Use on screen zoom buttons
+ private boolean mDisplayZoomControls;
/**
* Construct a new TabControl object that interfaces with the given
@@ -57,6 +60,7 @@ class TabControl {
TabControl(BrowserActivity activity) {
mActivity = activity;
mThumbnailDir = activity.getDir("thumbnails", 0);
+ mDisplayZoomControls = true;
}
File getThumbnailDir() {
@@ -68,6 +72,14 @@ class TabControl {
}
/**
+ * Set if the webview should use the on screen zoom controls
+ * @param enabled
+ */
+ void setDisplayZoomControls(boolean enabled) {
+ mDisplayZoomControls = enabled;
+ }
+
+ /**
* Return the current tab's main WebView. This will always return the main
* WebView for a given tab and not a subwindow.
* @return The current tab's WebView.
@@ -132,7 +144,7 @@ class TabControl {
int getCurrentIndex() {
return mCurrentTab;
}
-
+
/**
* Given a Tab, find it's index
* @param Tab to find
@@ -150,32 +162,49 @@ class TabControl {
}
/**
+ * Returns true if there are any incognito tabs open.
+ * @return True when any incognito tabs are open, false otherwise.
+ */
+ boolean hasAnyOpenIncognitoTabs() {
+ for (Tab tab : mTabs) {
+ if (tab.getWebView() != null && tab.getWebView().isPrivateBrowsingEnabled()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Create a new tab.
* @return The newly createTab or null if we have reached the maximum
* number of open tabs.
*/
- Tab createNewTab(boolean closeOnExit, String appId, String url) {
+ Tab createNewTab(boolean closeOnExit, String appId, String url,
+ boolean privateBrowsing) {
int size = mTabs.size();
// Return false if we have maxed out on tabs
if (MAX_TABS == size) {
return null;
}
- final WebView w = createNewWebView();
+ final WebView w = createNewWebView(privateBrowsing);
// Create a new tab and add it to the tab list
Tab t = new Tab(mActivity, w, closeOnExit, appId, url);
mTabs.add(t);
// Initially put the tab in the background.
t.putInBackground();
+ if (mTabChangeListener != null) {
+ mTabChangeListener.onNewTab(t);
+ }
return t;
}
/**
* Create a new tab with default values for closeOnExit(false),
- * appId(null), and url(null).
+ * appId(null), url(null), and privateBrowsing(false).
*/
Tab createNewTab() {
- return createNewTab(false, null, null);
+ return createNewTab(false, null, null, false);
}
/**
@@ -231,6 +260,9 @@ class TabControl {
// Remove it from the queue of viewed tabs.
mTabQueue.remove(t);
+ if (mTabChangeListener != null) {
+ mTabChangeListener.onRemoveTab(t);
+ }
return true;
}
@@ -277,29 +309,56 @@ class TabControl {
* @return True if there were previous tabs that were restored. False if
* there was no saved state or restoring the state failed.
*/
- boolean restoreState(Bundle inState) {
+ boolean restoreState(Bundle inState, boolean dontRestoreIncognitoTabs) {
final int numTabs = (inState == null)
? -1 : inState.getInt(Tab.NUMTABS, -1);
if (numTabs == -1) {
return false;
} else {
- final int currentTab = inState.getInt(Tab.CURRTAB, -1);
+ final int oldCurrentTab = inState.getInt(Tab.CURRTAB, -1);
+
+ // Determine whether the saved current tab can be restored, and
+ // if not, which tab will take its place.
+ int currentTab = -1;
+ if (!dontRestoreIncognitoTabs
+ || !inState.getBundle(Tab.WEBVIEW + oldCurrentTab).getBoolean(Tab.INCOGNITO)) {
+ currentTab = oldCurrentTab;
+ } else {
+ for (int i = 0; i < numTabs; i++) {
+ if (!inState.getBundle(Tab.WEBVIEW + i).getBoolean(Tab.INCOGNITO)) {
+ currentTab = i;
+ break;
+ }
+ }
+ }
+ if (currentTab < 0) {
+ return false;
+ }
+
+ // Map saved tab indices to new indices, in case any incognito tabs
+ // need to not be restored.
+ HashMap<Integer, Integer> originalTabIndices = new HashMap<Integer, Integer>();
+ originalTabIndices.put(-1, -1);
for (int i = 0; i < numTabs; i++) {
- if (i == currentTab) {
+ Bundle state = inState.getBundle(Tab.WEBVIEW + i);
+
+ if (dontRestoreIncognitoTabs && state != null && state.getBoolean(Tab.INCOGNITO)) {
+ originalTabIndices.put(i, -1);
+ } else if (i == currentTab) {
Tab t = createNewTab();
// Me must set the current tab before restoring the state
// so that all the client classes are set.
setCurrentTab(t);
- if (!t.restoreState(inState.getBundle(Tab.WEBVIEW + i))) {
+ if (!t.restoreState(state)) {
Log.w(LOGTAG, "Fail in restoreState, load home page.");
t.getWebView().loadUrl(BrowserSettings.getInstance()
.getHomePage());
}
+ originalTabIndices.put(i, getTabCount() - 1);
} else {
// Create a new tab and don't restore the state yet, add it
// to the tab list
Tab t = new Tab(mActivity, null, false, null, null);
- Bundle state = inState.getBundle(Tab.WEBVIEW + i);
if (state != null) {
t.setSavedState(state);
t.populatePickerDataFromSavedState();
@@ -311,15 +370,17 @@ class TabControl {
mTabs.add(t);
// added the tab to the front as they are not current
mTabQueue.add(0, t);
+ originalTabIndices.put(i, getTabCount() - 1);
}
}
+
// Rebuild the tree of tabs. Do this after all tabs have been
// created/restored so that the parent tab exists.
for (int i = 0; i < numTabs; i++) {
final Bundle b = inState.getBundle(Tab.WEBVIEW + i);
final Tab t = getTab(i);
if (b != null && t != null) {
- final int parentIndex = b.getInt(Tab.PARENTTAB, -1);
+ final Integer parentIndex = originalTabIndices.get(b.getInt(Tab.PARENTTAB, -1));
if (parentIndex != -1) {
final Tab parent = getTab(parentIndex);
if (parent != null) {
@@ -529,13 +590,25 @@ class TabControl {
* Creates a new WebView and registers it with the global settings.
*/
private WebView createNewWebView() {
+ return createNewWebView(false);
+ }
+
+ /**
+ * Creates a new WebView and registers it with the global settings.
+ * @param privateBrowsing When true, enables private browsing in the new
+ * WebView.
+ */
+ private WebView createNewWebView(boolean privateBrowsing) {
// Create a new WebView
- WebView w = new WebView(mActivity);
+ ScrollWebView w = new ScrollWebView(mActivity, null,
+ com.android.internal.R.attr.webViewStyle, privateBrowsing);
+ w.setScrollListener(mActivity.getScrollListener());
w.setScrollbarFadingEnabled(true);
w.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
w.setMapTrackballToArrowKeys(false); // use trackball directly
// Enable the built-in zoom
w.getSettings().setBuiltInZoomControls(true);
+ w.getSettings().setDisplayZoomControls(mDisplayZoomControls);
// Add this WebView to the settings observer list and update the
// settings
final BrowserSettings s = BrowserSettings.getInstance();
@@ -619,4 +692,42 @@ class TabControl {
}
return true;
}
+
+ interface TabChangeListener {
+
+ public void onNewTab(Tab tab);
+
+ public void onRemoveTab(Tab tab);
+
+ public void onCurrentTab(Tab tab);
+
+ public void onProgress(Tab tab, int progress);
+
+ public void onUrlAndTitle(Tab tab, String url, String title);
+
+ public void onFavicon(Tab tab, Bitmap favicon);
+
+ public void onPageStarted(Tab tab);
+
+ public void onPageFinished(Tab tab);
+
+ }
+
+ private TabChangeListener mTabChangeListener;
+
+ /**
+ * register the TabChangeListener with the tab control
+ * @param listener
+ */
+ void setOnTabChangeListener(TabChangeListener listener) {
+ mTabChangeListener = listener;
+ }
+
+ /**
+ * get the current TabChangeListener (used by the tabs)
+ */
+ TabChangeListener getTabChangeListener() {
+ return mTabChangeListener;
+ }
+
}
diff --git a/src/com/android/browser/TabScrollView.java b/src/com/android/browser/TabScrollView.java
new file mode 100644
index 000000000..b41141679
--- /dev/null
+++ b/src/com/android/browser/TabScrollView.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2010 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.util.AttributeSet;
+import android.view.View;
+import android.widget.HorizontalScrollView;
+import android.widget.LinearLayout;
+
+/**
+ * custom view for displaying tabs in the tabbed title bar
+ */
+public class TabScrollView extends HorizontalScrollView {
+
+ private Context mContext;
+
+ private LinearLayout mContentView;
+
+ private int mSelected;
+
+ /**
+ * @param context
+ * @param attrs
+ * @param defStyle
+ */
+ public TabScrollView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ /**
+ * @param context
+ * @param attrs
+ */
+ public TabScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ /**
+ * @param context
+ */
+ public TabScrollView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ private void init(Context ctx) {
+ mContext = ctx;
+ setHorizontalScrollBarEnabled(false);
+ mContentView = new LinearLayout(mContext);
+ mContentView.setOrientation(LinearLayout.HORIZONTAL);
+ mContentView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.MATCH_PARENT));
+ addView(mContentView);
+ mSelected = -1;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ ensureChildVisible(getSelectedTab());
+ }
+
+ void setSelectedTab(int position) {
+ View v = getSelectedTab();
+ if (v != null) {
+ v.setSelected(false);
+ }
+ mSelected = position;
+ v = getSelectedTab();
+ if (v != null) {
+ v.setSelected(true);
+ }
+ requestLayout();
+ }
+
+ View getSelectedTab() {
+ if ((mSelected >= 0) && (mSelected < mContentView.getChildCount())) {
+ return mContentView.getChildAt(mSelected);
+ } else {
+ return null;
+ }
+ }
+
+ void clearTabs() {
+ mContentView.removeAllViews();
+ }
+
+ void addTab(View tab) {
+ mContentView.addView(tab);
+ tab.setSelected(false);
+ }
+
+ void addTab(View tab, int pos) {
+ mContentView.addView(tab, pos);
+ tab.setSelected(false);
+ }
+
+ void removeTab(View tab) {
+ int ix = mContentView.indexOfChild(tab);
+ if (ix == mSelected) {
+ mSelected = -1;
+ } else if (ix < mSelected) {
+ mSelected--;
+ }
+ mContentView.removeView(tab);
+ }
+
+ void ensureChildVisible(View child) {
+ if (child != null) {
+ int childl = child.getLeft();
+ int childr = childl + child.getWidth();
+ int viewl = getScrollX();
+ int viewr = viewl + getWidth();
+ if (childl < viewl) {
+ // need scrolling to left
+ scrollTo(childl, 0);
+ } else if (childr > viewr) {
+ // need scrolling to right
+ scrollTo(childr - viewr + viewl, 0);
+ }
+ }
+ }
+
+
+}
diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java
index dc4979bd3..bbb55ad0d 100644
--- a/src/com/android/browser/TitleBar.java
+++ b/src/com/android/browser/TitleBar.java
@@ -21,14 +21,8 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
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.speech.RecognizerIntent;
@@ -45,7 +39,6 @@ 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;
@@ -55,21 +48,16 @@ import com.android.common.speech.LoggingEvents;
* This class represents a title bar for a particular "tab" or "window" in the
* browser.
*/
-public class TitleBar extends LinearLayout {
+public class TitleBar extends TitleBarBase {
private TextView mTitle;
- private Drawable mCloseDrawable;
private ImageView mRtButton;
private Drawable mCircularProgress;
private ProgressBar mHorizontalProgress;
- private ImageView mFavicon;
- private ImageView mLockIcon;
private ImageView mStopButton;
private Drawable mBookmarkDrawable;
private Drawable mVoiceDrawable;
private boolean mInLoad;
private BrowserActivity mBrowserActivity;
- private Drawable mGenericFavicon;
- private int mIconDimension;
private View mTitleBg;
private MyHandler mHandler;
private Intent mVoiceSearchIntent;
@@ -84,7 +72,7 @@ public class TitleBar extends LinearLayout {
private static int LONG_PRESS = 1;
public TitleBar(BrowserActivity context) {
- super(context, null);
+ super(context);
mHandler = new MyHandler();
LayoutInflater factory = LayoutInflater.from(context);
factory.inflate(R.layout.title_bar, this);
@@ -107,13 +95,11 @@ public class TitleBar extends LinearLayout {
TypedValue.COMPLEX_UNIT_DIP, 8f, metrics);
mRightMargin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 6f, metrics);
- mIconDimension = (int) TypedValue.applyDimension(
+ int iconDimension = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 20f, metrics);
- mCircularProgress.setBounds(0, 0, mIconDimension, mIconDimension);
+ mCircularProgress.setBounds(0, 0, iconDimension, iconDimension);
mHorizontalProgress = (ProgressBar) findViewById(
R.id.progress_horizontal);
- mGenericFavicon = context.getResources().getDrawable(
- R.drawable.app_web_browser_sm);
mVoiceSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
mVoiceSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
@@ -219,7 +205,7 @@ public class TitleBar extends LinearLayout {
} else if (mInLoad) {
mBrowserActivity.stopLoading();
} else {
- mBrowserActivity.bookmarksOrHistoryPicker(false);
+ mBrowserActivity.promptAddOrInstallBookmark(button);
}
button.setPressed(false);
} else if (mTitleBg.isPressed()) {
@@ -248,25 +234,6 @@ public class TitleBar extends LinearLayout {
}
/**
- * 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);
- }
-
- /**
* Change the TitleBar to or from voice mode. If there is no package to
* handle voice search, the TitleBar cannot be set to voice mode.
*/
@@ -302,18 +269,6 @@ public class TitleBar extends LinearLayout {
}
/**
- * 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) {
@@ -374,11 +329,4 @@ public class TitleBar extends LinearLayout {
}
}
}
-
- /* 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/TitleBarBase.java b/src/com/android/browser/TitleBarBase.java
new file mode 100644
index 000000000..7016dc020
--- /dev/null
+++ b/src/com/android/browser/TitleBarBase.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 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.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.PaintDrawable;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+/**
+ * Base class for a title bar used by the browser.
+ */
+public class TitleBarBase extends LinearLayout {
+ // These need to be set by the subclass.
+ protected ImageView mFavicon;
+ protected ImageView mLockIcon;
+
+ protected Drawable mGenericFavicon;
+
+ public TitleBarBase(Context context) {
+ super(context, null);
+ mGenericFavicon = context.getResources().getDrawable(
+ R.drawable.app_web_browser_sm);
+ }
+
+ /* package */ void setProgress(int newProgress) {}
+ /* package */ void setDisplayTitle(String title) {}
+
+ /* package */ void setLock(Drawable d) {
+ assert mLockIcon != null;
+ if (null == d) {
+ mLockIcon.setVisibility(View.GONE);
+ } else {
+ mLockIcon.setImageDrawable(d);
+ mLockIcon.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /* package */ void setFavicon(Bitmap icon) {
+ assert mFavicon != null;
+ 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);
+ }
+
+ /* package */ void setInVoiceMode(boolean inVoiceMode) {}
+
+}
diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java
new file mode 100644
index 000000000..3408108e6
--- /dev/null
+++ b/src/com/android/browser/TitleBarXLarge.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2010 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 com.android.browser.UrlInputView.UrlInputListener;
+
+import android.app.SearchManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * tabbed title bar for xlarge screen browser
+ */
+public class TitleBarXLarge extends TitleBarBase
+ implements UrlInputListener, OnClickListener, OnFocusChangeListener {
+
+ private static final int PROGRESS_MAX = 100;
+
+ private BrowserActivity mBrowserActivity;
+ private Drawable mStopDrawable;
+ private Drawable mReloadDrawable;
+
+
+ private View mContainer;
+ private View mBackButton;
+ private View mForwardButton;
+ private View mStar;
+ private View mSearchButton;
+ private View mFocusContainer;
+ private View mUnfocusContainer;
+ private View mGoButton;
+ private ImageView mStopButton;
+ private View mAllButton;
+ private PageProgressView mProgressView;
+ private UrlInputView mUrlFocused;
+ private TextView mUrlUnfocused;
+ private boolean mInLoad;
+
+ public TitleBarXLarge(BrowserActivity context) {
+ super(context);
+ mBrowserActivity = context;
+ Resources resources = context.getResources();
+ mStopDrawable = resources.getDrawable(R.drawable.ic_stop_normal);
+ mReloadDrawable = resources.getDrawable(R.drawable.ic_refresh_normal);
+ rebuildLayout(context, true);
+ }
+
+ private void rebuildLayout(Context context, boolean rebuildData) {
+ LayoutInflater factory = LayoutInflater.from(context);
+ factory.inflate(R.layout.url_bar, this);
+
+ mContainer = findViewById(R.id.taburlbar);
+ mUrlFocused = (UrlInputView) findViewById(R.id.url_focused);
+ mUrlUnfocused = (TextView) findViewById(R.id.url_unfocused);
+ mAllButton = findViewById(R.id.all_btn);
+ // TODO: Change enabled states based on whether you can go
+ // back/forward. Probably should be done inside onPageStarted.
+ mBackButton = findViewById(R.id.back);
+ mForwardButton = findViewById(R.id.forward);
+ mStar = findViewById(R.id.star);
+ mStopButton = (ImageView) findViewById(R.id.stop);
+ mSearchButton = findViewById(R.id.search);
+ mLockIcon = (ImageView) findViewById(R.id.lock);
+ mGoButton = findViewById(R.id.go);
+ mProgressView = (PageProgressView) findViewById(R.id.progress);
+ mFocusContainer = findViewById(R.id.urlbar_focused);
+ mUnfocusContainer = findViewById(R.id.urlbar_unfocused);
+
+ mBackButton.setOnClickListener(this);
+ mForwardButton.setOnClickListener(this);
+ mStar.setOnClickListener(this);
+ mAllButton.setOnClickListener(this);
+ mStopButton.setOnClickListener(this);
+ mSearchButton.setOnClickListener(this);
+ mGoButton.setOnClickListener(this);
+ mUrlFocused.setUrlInputListener(this);
+ mUrlUnfocused.setOnFocusChangeListener(this);
+ }
+
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ swapUrlContainer(true);
+ mUrlFocused.selectAll();
+ mUrlFocused.requestFocus();
+ mUrlFocused.setDropDownWidth(mUnfocusContainer.getWidth());
+ mUrlFocused.setDropDownHorizontalOffset(-mUrlFocused.getLeft());
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mBackButton == v) {
+ mBrowserActivity.getTopWindow().goBack();
+ } else if (mForwardButton == v) {
+ mBrowserActivity.getTopWindow().goForward();
+ } else if (mStar == v) {
+ mBrowserActivity.promptAddOrInstallBookmark(mStar);
+ } else if (mAllButton == v) {
+ mBrowserActivity.bookmarksOrHistoryPicker(false, false);
+ } else if (mSearchButton == v) {
+ search();
+ } else if (mStopButton == v) {
+ stopOrRefresh();
+ } else if (mGoButton == v) {
+ onAction(mUrlFocused.getText().toString());
+ }
+ }
+
+ int getHeightWithoutProgress() {
+ return mContainer.getHeight();
+ }
+
+ @Override
+ void setFavicon(Bitmap icon) { }
+
+ // UrlInputListener implementation
+
+ @Override
+ public void onAction(String text) {
+ mBrowserActivity.getTabControl().getCurrentTopWebView().requestFocus();
+ mBrowserActivity.hideFakeTitleBar();
+ Intent i = new Intent();
+ i.setAction(Intent.ACTION_SEARCH);
+ i.putExtra(SearchManager.QUERY, text);
+ mBrowserActivity.onNewIntent(i);
+ swapUrlContainer(false);
+ setDisplayTitle(text);
+ }
+
+ @Override
+ public void onDismiss() {
+ mBrowserActivity.getTabControl().getCurrentTopWebView().requestFocus();
+ mBrowserActivity.hideFakeTitleBar();
+ setDisplayTitle(mBrowserActivity.getTabControl().getCurrentWebView().getUrl());
+ swapUrlContainer(false);
+ }
+
+ @Override
+ public void onEdit(String text) {
+ setDisplayTitle(text);
+ if (text != null) {
+ mUrlFocused.setSelection(text.length());
+ }
+ }
+
+ private void swapUrlContainer(boolean focus) {
+ mUnfocusContainer.setVisibility(focus ? View.GONE : View.VISIBLE);
+ mFocusContainer.setVisibility(focus ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void createContextMenu(ContextMenu menu) {
+ MenuInflater inflater = mBrowserActivity.getMenuInflater();
+ inflater.inflate(R.menu.title_context, menu);
+ mBrowserActivity.onCreateContextMenu(menu, this, null);
+ }
+
+ private void search() {
+ setDisplayTitle("");
+ mUrlUnfocused.requestFocus();
+ }
+
+ private void stopOrRefresh() {
+ if (mInLoad) {
+ mBrowserActivity.stopLoading();
+ } else {
+ mBrowserActivity.getTopWindow().reload();
+ }
+ }
+
+ /**
+ * Update the progress, from 0 to 100.
+ */
+ @Override
+ void setProgress(int newProgress) {
+ if (newProgress >= PROGRESS_MAX) {
+ mProgressView.setVisibility(View.GONE);
+ mInLoad = false;
+ mStopButton.setImageDrawable(mReloadDrawable);
+ } else {
+ if (!mInLoad) {
+ mProgressView.setVisibility(View.VISIBLE);
+ mInLoad = true;
+ mStopButton.setImageDrawable(mStopDrawable);
+ }
+ mProgressView.setProgress(newProgress*10000/PROGRESS_MAX);
+ }
+ }
+
+ @Override
+ /* package */ void setDisplayTitle(String title) {
+ mUrlFocused.setText(title);
+ mUrlUnfocused.setText(title);
+ }
+
+}
diff --git a/src/com/android/browser/UrlInputView.java b/src/com/android/browser/UrlInputView.java
new file mode 100644
index 000000000..96a598019
--- /dev/null
+++ b/src/com/android/browser/UrlInputView.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2010 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.SearchManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.text.Editable;
+import android.text.SpannableStringBuilder;
+import android.text.TextWatcher;
+import android.text.style.BackgroundColorSpan;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AutoCompleteTextView;
+import android.widget.CursorAdapter;
+import android.widget.Filterable;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+/**
+ * url/search input view
+ * handling suggestions
+ */
+public class UrlInputView extends AutoCompleteTextView
+ implements OnFocusChangeListener, OnClickListener, OnEditorActionListener {
+
+ private UrlInputListener mListener;
+ private InputMethodManager mInputManager;
+ private SuggestionsAdapter mAdapter;
+
+ private OnFocusChangeListener mWrappedFocusListener;
+
+ public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ public UrlInputView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public UrlInputView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ private void init(Context ctx) {
+ mInputManager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
+ setOnEditorActionListener(this);
+ super.setOnFocusChangeListener(this);
+ final ContentResolver cr = mContext.getContentResolver();
+ mAdapter = new SuggestionsAdapter(mContext,
+ BrowserProvider.getBookmarksSuggestions(cr, null));
+ setAdapter(mAdapter);
+ setSelectAllOnFocus(false);
+ }
+
+ @Override
+ public ActionMode startActionMode(ActionMode.Callback callback) {
+ // suppress selection action mode
+ return null;
+ }
+
+ @Override
+ public void setOnFocusChangeListener(OnFocusChangeListener focusListener) {
+ mWrappedFocusListener = focusListener;
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ finishInput(getText().toString());
+ return true;
+ }
+
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ forceIme();
+ } else {
+ finishInput(null);
+ }
+ if (mWrappedFocusListener != null) {
+ mWrappedFocusListener.onFocusChange(v, hasFocus);
+ }
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view instanceof ImageButton) {
+ // user pressed edit search button
+ String text = mAdapter.getViewString((View)view.getParent());
+ mListener.onEdit(text);
+ } else {
+ // user selected dropdown item
+ String url = mAdapter.getViewString(view);
+ finishInput(url);
+ }
+ }
+
+ public void setUrlInputListener(UrlInputListener listener) {
+ mListener = listener;
+ }
+
+ public void forceIme() {
+ mInputManager.showSoftInput(this, 0);
+ }
+
+ private void finishInput(String url) {
+ this.dismissDropDown();
+ this.setSelection(0,0);
+ mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
+ if (url == null) {
+ mListener.onDismiss();
+ } else {
+ mListener.onAction(url);
+ }
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent evt) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // catch back key in order to do slightly more cleanup than usual
+ finishInput(null);
+ return true;
+ }
+ return super.onKeyPreIme(keyCode, evt);
+ }
+
+ interface UrlInputListener {
+ public void onDismiss();
+ public void onAction(String text);
+ public void onEdit(String text);
+ }
+
+ /**
+ * adapter used by suggestion dropdown
+ */
+ class SuggestionsAdapter extends CursorAdapter implements Filterable {
+
+ private Cursor mLastCursor;
+ private ContentResolver mContent;
+ private int mIndexText1;
+ private int mIndexText2;
+ private int mIndexIcon;
+
+ public SuggestionsAdapter(Context context, Cursor c) {
+ super(context, c);
+ mContent = context.getContentResolver();
+ mIndexText1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
+ mIndexText2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
+ mIndexIcon = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
+ }
+
+ public String getViewString(View view) {
+ TextView tv2 = (TextView) view.findViewById(android.R.id.text2);
+ if (tv2.getText().length() > 0) {
+ return tv2.getText().toString();
+ } else {
+ TextView tv1 = (TextView) view.findViewById(android.R.id.text1);
+ return tv1.getText().toString();
+ }
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ final View view = inflater.inflate(
+ R.layout.url_dropdown_item, parent, false);
+ bindView(view, context, cursor);
+ return view;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ TextView tv1 = (TextView) view.findViewById(android.R.id.text1);
+ TextView tv2 = (TextView) view.findViewById(android.R.id.text2);
+ ImageView ic1 = (ImageView) view.findViewById(R.id.icon1);
+ View ic2 = view.findViewById(R.id.icon2);
+ tv1.setText(cursor.getString(mIndexText1));
+ String url = cursor.getString(mIndexText2);
+ tv2.setText((url != null) ? url : "");
+ ic2.setOnClickListener(UrlInputView.this);
+ // assume an id
+ try {
+ int id = Integer.parseInt(cursor.getString(mIndexIcon));
+ Drawable d = context.getResources().getDrawable(id);
+ ic1.setImageDrawable(d);
+ ic2.setVisibility((id == R.drawable.ic_search_category_suggest)? View.VISIBLE : View.GONE);
+ } catch (NumberFormatException nfx) {
+ }
+ view.setOnClickListener(UrlInputView.this);
+ }
+
+ @Override
+ public String convertToString(Cursor cursor) {
+ return cursor.getString(mIndexText1);
+ }
+
+ @Override
+ public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+ if (getFilterQueryProvider() != null) {
+ return getFilterQueryProvider().runQuery(constraint);
+ }
+ mLastCursor = BrowserProvider.getBookmarksSuggestions(mContent,
+ (constraint != null) ? constraint.toString() : null);
+ return mLastCursor;
+ }
+
+ }
+
+}
diff --git a/src/com/android/browser/WebDialog.java b/src/com/android/browser/WebDialog.java
deleted file mode 100644
index 9995e8f31..000000000
--- a/src/com/android/browser/WebDialog.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2010 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.View;
-import android.view.animation.AnimationUtils;
-import android.view.inputmethod.InputMethodManager;
-import android.webkit.WebView;
-import android.widget.LinearLayout;
-
-/* package */ class WebDialog extends LinearLayout {
- protected WebView mWebView;
- protected BrowserActivity mBrowserActivity;
- private boolean mIsVisible;
-
- /* package */ WebDialog(BrowserActivity context) {
- super(context);
- mBrowserActivity = context;
- }
-
- /* dialogs that have cancel buttons can optionally share code by including a
- * view with an id of 'done'.
- */
- protected void addCancel() {
- View button = findViewById(R.id.done);
- if (button != null) button.setOnClickListener(mCancelListener);
- }
-
- private View.OnClickListener mCancelListener = new View.OnClickListener() {
- public void onClick(View v) {
- mBrowserActivity.closeDialogs();
- }
- };
-
- protected void dismiss() {
- startAnimation(AnimationUtils.loadAnimation(mBrowserActivity,
- R.anim.dialog_exit));
- mIsVisible = false;
- }
-
- /*
- * Remove the soft keyboard from the screen.
- */
- protected void hideSoftInput() {
- InputMethodManager imm = (InputMethodManager)
- mBrowserActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
- }
-
- protected boolean isVisible() {
- return mIsVisible;
- }
-
- /* package */ void setWebView(WebView webview) {
- mWebView = webview;
- }
-
- protected void show() {
- startAnimation(AnimationUtils.loadAnimation(mBrowserActivity,
- R.anim.dialog_enter));
- mIsVisible = true;
- }
-
-}
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
new file mode 100644
index 000000000..47d92de86
--- /dev/null
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -0,0 +1,1095 @@
+/*
+ * Copyright (C) 2010 he 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.provider;
+
+import com.android.browser.R;
+import com.android.internal.content.SyncStateContentProviderHelper;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.provider.BrowserContract;
+import android.provider.BrowserContract.Accounts;
+import android.provider.BrowserContract.Bookmarks;
+import android.provider.BrowserContract.ChromeSyncColumns;
+import android.provider.BrowserContract.Combined;
+import android.provider.BrowserContract.History;
+import android.provider.BrowserContract.Images;
+import android.provider.BrowserContract.Searches;
+import android.provider.BrowserContract.SyncState;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.SyncStateContract;
+import android.text.TextUtils;
+
+import java.util.HashMap;
+
+public class BrowserProvider2 extends SQLiteContentProvider {
+
+ static final String LEGACY_AUTHORITY = "browser";
+ static final Uri LEGACY_AUTHORITY_URI = new Uri.Builder().authority(LEGACY_AUTHORITY).build();
+
+ static final String TABLE_BOOKMARKS = "bookmarks";
+ static final String TABLE_HISTORY = "history";
+ static final String TABLE_IMAGES = "images";
+ static final String TABLE_SEARCHES = "searches";
+ static final String TABLE_SYNC_STATE = "syncstate";
+ static final String VIEW_COMBINED = "combined";
+
+ static final String TABLE_BOOKMARKS_JOIN_IMAGES = "bookmarks LEFT OUTER JOIN images " +
+ "ON bookmarks.url = images." + Images.URL;
+ static final String TABLE_HISTORY_JOIN_IMAGES = "history LEFT OUTER JOIN images " +
+ "ON history.url = images." + Images.URL;
+
+ static final String DEFAULT_SORT_HISTORY = History.DATE_LAST_VISITED + " DESC";
+
+ static final int BOOKMARKS = 1000;
+ static final int BOOKMARKS_ID = 1001;
+ static final int BOOKMARKS_FOLDER = 1002;
+ static final int BOOKMARKS_FOLDER_ID = 1003;
+
+ static final int HISTORY = 2000;
+ static final int HISTORY_ID = 2001;
+
+ static final int SEARCHES = 3000;
+ static final int SEARCHES_ID = 3001;
+
+ static final int SYNCSTATE = 4000;
+ static final int SYNCSTATE_ID = 4001;
+
+ static final int IMAGES = 5000;
+
+ static final int COMBINED = 6000;
+ static final int COMBINED_ID = 6001;
+
+ static final int ACCOUNTS = 7000;
+
+ static final long FIXED_ID_CHROME_ROOT = 1;
+ static final long FIXED_ID_BOOKMARKS = 2;
+ static final long FIXED_ID_BOOKMARKS_BAR = 3;
+ static final long FIXED_ID_OTHER_BOOKMARKS = 4;
+
+ static final String DEFAULT_BOOKMARKS_SORT_ORDER = "position ASC, _id ASC";
+
+ static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+
+ static final HashMap<String, String> ACCOUNTS_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> BOOKMARKS_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> OTHER_BOOKMARKS_PROJECTION_MAP =
+ new HashMap<String, String>();
+ static final HashMap<String, String> HISTORY_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> SYNC_STATE_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> IMAGES_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> COMBINED_PROJECTION_MAP = new HashMap<String, String>();
+
+ static {
+ final UriMatcher matcher = URI_MATCHER;
+ final String authority = BrowserContract.AUTHORITY;
+ matcher.addURI(authority, "accounts", ACCOUNTS);
+ matcher.addURI(authority, "bookmarks", BOOKMARKS);
+ matcher.addURI(authority, "bookmarks/#", BOOKMARKS_ID);
+ matcher.addURI(authority, "bookmarks/folder", BOOKMARKS_FOLDER);
+ matcher.addURI(authority, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
+ matcher.addURI(authority, "history", HISTORY);
+ matcher.addURI(authority, "history/#", HISTORY_ID);
+ matcher.addURI(authority, "searches", SEARCHES);
+ matcher.addURI(authority, "searches/#", SEARCHES_ID);
+ matcher.addURI(authority, "syncstate", SYNCSTATE);
+ matcher.addURI(authority, "syncstate/#", SYNCSTATE_ID);
+ matcher.addURI(authority, "images", IMAGES);
+ matcher.addURI(authority, "combined", COMBINED);
+ matcher.addURI(authority, "combined/#", COMBINED_ID);
+
+ // Projection maps
+ HashMap<String, String> map;
+
+ // Accounts
+ map = ACCOUNTS_PROJECTION_MAP;
+ map.put(Accounts.ACCOUNT_TYPE, Accounts.ACCOUNT_TYPE);
+ map.put(Accounts.ACCOUNT_NAME, Accounts.ACCOUNT_NAME);
+
+ // Bookmarks
+ map = BOOKMARKS_PROJECTION_MAP;
+ map.put(Bookmarks._ID, qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID));
+ map.put(Bookmarks.TITLE, Bookmarks.TITLE);
+ map.put(Bookmarks.URL, Bookmarks.URL);
+ map.put(Bookmarks.FAVICON, Bookmarks.FAVICON);
+ map.put(Bookmarks.THUMBNAIL, Bookmarks.THUMBNAIL);
+ map.put(Bookmarks.TOUCH_ICON, Bookmarks.TOUCH_ICON);
+ map.put(Bookmarks.IS_FOLDER, Bookmarks.IS_FOLDER);
+ map.put(Bookmarks.PARENT, Bookmarks.PARENT);
+ map.put(Bookmarks.POSITION, Bookmarks.POSITION);
+ map.put(Bookmarks.INSERT_AFTER, Bookmarks.INSERT_AFTER);
+ map.put(Bookmarks.IS_DELETED, Bookmarks.IS_DELETED);
+ map.put(Bookmarks.ACCOUNT_NAME, Bookmarks.ACCOUNT_NAME);
+ map.put(Bookmarks.ACCOUNT_TYPE, Bookmarks.ACCOUNT_TYPE);
+ map.put(Bookmarks.SOURCE_ID, Bookmarks.SOURCE_ID);
+ map.put(Bookmarks.VERSION, Bookmarks.VERSION);
+ map.put(Bookmarks.DATE_CREATED, Bookmarks.DATE_CREATED);
+ map.put(Bookmarks.DATE_MODIFIED, Bookmarks.DATE_MODIFIED);
+ map.put(Bookmarks.DIRTY, Bookmarks.DIRTY);
+ map.put(Bookmarks.SYNC1, Bookmarks.SYNC1);
+ map.put(Bookmarks.SYNC2, Bookmarks.SYNC2);
+ map.put(Bookmarks.SYNC3, Bookmarks.SYNC3);
+ map.put(Bookmarks.SYNC4, Bookmarks.SYNC4);
+ map.put(Bookmarks.SYNC5, Bookmarks.SYNC5);
+ map.put(Bookmarks.PARENT_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID +
+ " FROM " + TABLE_BOOKMARKS + " A WHERE " +
+ "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.PARENT +
+ ") AS " + Bookmarks.PARENT_SOURCE_ID);
+ map.put(Bookmarks.INSERT_AFTER_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID +
+ " FROM " + TABLE_BOOKMARKS + " A WHERE " +
+ "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.INSERT_AFTER +
+ ") AS " + Bookmarks.INSERT_AFTER_SOURCE_ID);
+
+ // Other bookmarks
+ OTHER_BOOKMARKS_PROJECTION_MAP.putAll(BOOKMARKS_PROJECTION_MAP);
+ OTHER_BOOKMARKS_PROJECTION_MAP.put(Bookmarks.POSITION,
+ Long.toString(Long.MAX_VALUE) + " AS " + Bookmarks.POSITION);
+
+ // History
+ map = HISTORY_PROJECTION_MAP;
+ map.put(History._ID, qualifyColumn(TABLE_HISTORY, History._ID));
+ map.put(History.TITLE, History.TITLE);
+ map.put(History.URL, History.URL);
+ map.put(History.FAVICON, History.FAVICON);
+ map.put(History.THUMBNAIL, History.THUMBNAIL);
+ map.put(History.TOUCH_ICON, History.TOUCH_ICON);
+ map.put(History.DATE_CREATED, History.DATE_CREATED);
+ map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED);
+ map.put(History.VISITS, History.VISITS);
+ map.put(History.USER_ENTERED, History.USER_ENTERED);
+
+ // Sync state
+ map = SYNC_STATE_PROJECTION_MAP;
+ map.put(SyncState._ID, SyncState._ID);
+ map.put(SyncState.ACCOUNT_NAME, SyncState.ACCOUNT_NAME);
+ map.put(SyncState.ACCOUNT_TYPE, SyncState.ACCOUNT_TYPE);
+ map.put(SyncState.DATA, SyncState.DATA);
+
+ // Images
+ map = IMAGES_PROJECTION_MAP;
+ map.put(Images.URL, Images.URL);
+ map.put(Images.FAVICON, Images.FAVICON);
+ map.put(Images.THUMBNAIL, Images.THUMBNAIL);
+ map.put(Images.TOUCH_ICON, Images.TOUCH_ICON);
+
+ // Combined history half
+ map = COMBINED_PROJECTION_MAP;
+ map.put(Combined._ID, Combined._ID);
+ map.put(Combined.TITLE, Combined.TITLE);
+ map.put(Combined.URL, Combined.URL);
+ map.put(Combined.DATE_CREATED, Combined.DATE_CREATED);
+ map.put(Combined.DATE_LAST_VISITED, Combined.DATE_LAST_VISITED);
+ map.put(Combined.IS_BOOKMARK, Combined.IS_BOOKMARK);
+ map.put(Combined.VISITS, Combined.VISITS);
+ map.put(Combined.FAVICON, Combined.FAVICON);
+ map.put(Combined.THUMBNAIL, Combined.THUMBNAIL);
+ map.put(Combined.TOUCH_ICON, Combined.TOUCH_ICON);
+ map.put(Combined.USER_ENTERED, Combined.USER_ENTERED);
+ }
+
+ static final String bookmarkOrHistoryColumn(String column) {
+ return "CASE WHEN bookmarks." + column + " IS NOT NULL THEN " +
+ "bookmarks." + column + " ELSE history." + column + " END AS " + column;
+ }
+
+ static final String qualifyColumn(String table, String column) {
+ return table + "." + column + " AS " + column;
+ }
+
+ DatabaseHelper mOpenHelper;
+ SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper();
+
+ final class DatabaseHelper extends SQLiteOpenHelper {
+ static final String DATABASE_NAME = "browser2.db";
+ static final int DATABASE_VERSION = 22;
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
+ Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ Bookmarks.TITLE + " TEXT," +
+ Bookmarks.URL + " TEXT," +
+ Bookmarks.IS_FOLDER + " INTEGER NOT NULL DEFAULT 0," +
+ Bookmarks.PARENT + " INTEGER," +
+ Bookmarks.POSITION + " INTEGER NOT NULL," +
+ Bookmarks.INSERT_AFTER + " INTEGER," +
+ Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0," +
+ Bookmarks.ACCOUNT_NAME + " TEXT," +
+ Bookmarks.ACCOUNT_TYPE + " TEXT," +
+ Bookmarks.SOURCE_ID + " TEXT," +
+ Bookmarks.VERSION + " INTEGER NOT NULL DEFAULT 1," +
+ Bookmarks.DATE_CREATED + " INTEGER," +
+ Bookmarks.DATE_MODIFIED + " INTEGER," +
+ Bookmarks.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
+ Bookmarks.SYNC1 + " TEXT," +
+ Bookmarks.SYNC2 + " TEXT," +
+ Bookmarks.SYNC3 + " TEXT," +
+ Bookmarks.SYNC4 + " TEXT," +
+ Bookmarks.SYNC5 + " TEXT" +
+ ");");
+
+ // TODO indices
+
+ createDefaultBookmarks(db);
+
+ db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
+ History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ History.TITLE + " TEXT," +
+ History.URL + " TEXT NOT NULL," +
+ History.DATE_CREATED + " INTEGER," +
+ History.DATE_LAST_VISITED + " INTEGER," +
+ History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
+ History.USER_ENTERED + " INTEGER" +
+ ");");
+
+ db.execSQL("CREATE TABLE " + TABLE_IMAGES + " (" +
+ Images.URL + " TEXT UNIQUE NOT NULL," +
+ Images.FAVICON + " BLOB," +
+ Images.THUMBNAIL + " BLOB," +
+ Images.TOUCH_ICON + " BLOB" +
+ ");");
+ db.execSQL("CREATE INDEX imagesUrlIndex ON " + TABLE_IMAGES +
+ "(" + Images.URL + ")");
+
+ db.execSQL("CREATE TABLE " + TABLE_SEARCHES + " (" +
+ Searches._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ Searches.SEARCH + " TEXT," +
+ Searches.DATE + " LONG" +
+ ");");
+
+ db.execSQL("CREATE VIEW " + VIEW_COMBINED + " AS " +
+ "SELECT " +
+ bookmarkOrHistoryColumn(Combined._ID) + ", " +
+ bookmarkOrHistoryColumn(Combined.TITLE) + ", " +
+ qualifyColumn(TABLE_HISTORY, Combined.URL) + ", " +
+ qualifyColumn(TABLE_HISTORY, Combined.DATE_CREATED) + ", " +
+ Combined.DATE_LAST_VISITED + ", " +
+ "CASE WHEN bookmarks._id IS NOT NULL THEN 1 ELSE 0 END AS " + Combined.IS_BOOKMARK + ", " +
+ Combined.VISITS + ", " +
+ Combined.FAVICON + ", " +
+ Combined.THUMBNAIL + ", " +
+ Combined.TOUCH_ICON + ", " +
+ "NULL AS " + Combined.USER_ENTERED + " "+
+ "FROM history LEFT OUTER JOIN bookmarks ON history.url = bookmarks.url LEFT OUTER JOIN images ON history.url = images.url_key " +
+
+ "UNION ALL " +
+
+ "SELECT " +
+ Combined._ID + ", " +
+ Combined.TITLE + ", " +
+ Combined.URL + ", " +
+ Combined.DATE_CREATED + ", " +
+ "NULL AS " + Combined.DATE_LAST_VISITED + ", "+
+ "1 AS " + Combined.IS_BOOKMARK + ", " +
+ "0 AS " + Combined.VISITS + ", "+
+ Combined.FAVICON + ", " +
+ Combined.THUMBNAIL + ", " +
+ Combined.TOUCH_ICON + ", " +
+ "NULL AS " + Combined.USER_ENTERED + " "+
+ "FROM bookmarks LEFT OUTER JOIN images ON bookmarks.url = images.url_key WHERE url NOT IN (SELECT url FROM history)");
+
+ mSyncHelper.createDatabase(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // TODO write upgrade logic
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_SEARCHES);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES);
+ db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
+ onCreate(db);
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ mSyncHelper.onDatabaseOpened(db);
+ }
+
+
+ private void createDefaultBookmarks(SQLiteDatabase db) {
+ ContentValues values = new ContentValues();
+ // TODO figure out how to deal with localization for the defaults
+
+ // Chrome sync root folder
+ values.put(Bookmarks._ID, FIXED_ID_CHROME_ROOT);
+ values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_ROOT);
+ values.put(Bookmarks.TITLE, "Google Chrome");
+ values.put(Bookmarks.PARENT, 0);
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ db.insertOrThrow(TABLE_BOOKMARKS, null, values);
+
+ // Bookmarks folder
+ values.put(Bookmarks._ID, FIXED_ID_BOOKMARKS);
+ values.put(ChromeSyncColumns.SERVER_UNIQUE, ChromeSyncColumns.FOLDER_NAME_BOOKMARKS);
+ values.put(Bookmarks.TITLE, "Bookmarks");
+ values.put(Bookmarks.PARENT, FIXED_ID_CHROME_ROOT);
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ db.insertOrThrow(TABLE_BOOKMARKS, null, values);
+
+ // Bookmarks Bar folder
+ values.clear();
+ values.put(Bookmarks._ID, FIXED_ID_BOOKMARKS_BAR);
+ values.put(ChromeSyncColumns.SERVER_UNIQUE,
+ ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR);
+ values.put(Bookmarks.TITLE, "Bookmarks Bar");
+ values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS);
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ db.insertOrThrow(TABLE_BOOKMARKS, null, values);
+
+ // Other Bookmarks folder
+ values.clear();
+ values.put(Bookmarks._ID, FIXED_ID_OTHER_BOOKMARKS);
+ values.put(ChromeSyncColumns.SERVER_UNIQUE,
+ ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS);
+ values.put(Bookmarks.TITLE, "Other Bookmarks");
+ values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS);
+ values.put(Bookmarks.POSITION, 1000);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ db.insertOrThrow(TABLE_BOOKMARKS, null, values);
+
+ addDefaultBookmarks(db, FIXED_ID_BOOKMARKS_BAR);
+
+ // TODO remove this testing code
+ db.execSQL("INSERT INTO bookmarks (" +
+ Bookmarks.TITLE + ", " +
+ Bookmarks.URL + ", " +
+ Bookmarks.IS_FOLDER + "," +
+ Bookmarks.PARENT + "," +
+ Bookmarks.POSITION +
+ ") VALUES (" +
+ "'Google Reader', " +
+ "'http://reader.google.com', " +
+ "0," +
+ Long.toString(FIXED_ID_OTHER_BOOKMARKS) + "," +
+ 0 +
+ ");");
+ }
+
+ private void addDefaultBookmarks(SQLiteDatabase db, long parentId) {
+ final CharSequence[] bookmarks = getContext().getResources().getTextArray(
+ R.array.bookmarks);
+ int size = bookmarks.length;
+ try {
+ String parent = Long.toString(parentId);
+ String now = Long.toString(System.currentTimeMillis());
+ for (int i = 0; i < size; i = i + 2) {
+ CharSequence bookmarkDestination = replaceSystemPropertyInString(getContext(),
+ bookmarks[i + 1]);
+ db.execSQL("INSERT INTO bookmarks (" +
+ Bookmarks.TITLE + ", " +
+ Bookmarks.URL + ", " +
+ Bookmarks.IS_FOLDER + "," +
+ Bookmarks.PARENT + "," +
+ Bookmarks.POSITION + "," +
+ Bookmarks.DATE_CREATED +
+ ") VALUES (" +
+ "'" + bookmarks[i] + "', " +
+ "'" + bookmarkDestination + "', " +
+ "0," +
+ parent + "," +
+ Integer.toString(i) + "," +
+ now +
+ ");");
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ }
+ }
+
+ // XXX: This is a major hack to remove our dependency on gsf constants and
+ // its content provider. http://b/issue?id=2425179
+ private String getClientId(ContentResolver cr) {
+ String ret = "android-google";
+ Cursor c = null;
+ try {
+ c = cr.query(Uri.parse("content://com.google.settings/partner"),
+ new String[] { "value" }, "name='client_id'", null, null);
+ if (c != null && c.moveToNext()) {
+ ret = c.getString(0);
+ }
+ } catch (RuntimeException ex) {
+ // fall through to return the default
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return ret;
+ }
+
+ private CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) {
+ StringBuffer sb = new StringBuffer();
+ int lastCharLoc = 0;
+
+ final String client_id = getClientId(context.getContentResolver());
+
+ for (int i = 0; i < srcString.length(); ++i) {
+ char c = srcString.charAt(i);
+ if (c == '{') {
+ sb.append(srcString.subSequence(lastCharLoc, i));
+ lastCharLoc = i;
+ inner:
+ for (int j = i; j < srcString.length(); ++j) {
+ char k = srcString.charAt(j);
+ if (k == '}') {
+ String propertyKeyValue = srcString.subSequence(i + 1, j).toString();
+ if (propertyKeyValue.equals("CLIENT_ID")) {
+ sb.append(client_id);
+ } else {
+ sb.append("unknown");
+ }
+ lastCharLoc = j + 1;
+ i = j;
+ break inner;
+ }
+ }
+ }
+ }
+ if (srcString.length() - lastCharLoc > 0) {
+ // Put on the tail, if there is one
+ sb.append(srcString.subSequence(lastCharLoc, srcString.length()));
+ }
+ return sb;
+ }
+ }
+
+ @Override
+ public SQLiteOpenHelper getDatabaseHelper(Context context) {
+ synchronized (this) {
+ if (mOpenHelper == null) {
+ mOpenHelper = new DatabaseHelper(context);
+ }
+ return mOpenHelper;
+ }
+ }
+
+ @Override
+ public boolean isCallerSyncAdapter(Uri uri) {
+ return uri.getBooleanQueryParameter(BrowserContract.CALLER_IS_SYNCADAPTER, false);
+ }
+
+ @Override
+ public void notifyChange(boolean callerIsSyncAdapter) {
+ ContentResolver resolver = getContext().getContentResolver();
+ resolver.notifyChange(BrowserContract.AUTHORITY_URI, null, !callerIsSyncAdapter);
+ resolver.notifyChange(LEGACY_AUTHORITY_URI, null, !callerIsSyncAdapter);
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ final int match = URI_MATCHER.match(uri);
+ switch (match) {
+ case BOOKMARKS:
+ return Bookmarks.CONTENT_TYPE;
+ case BOOKMARKS_ID:
+ return Bookmarks.CONTENT_ITEM_TYPE;
+ case HISTORY:
+ return History.CONTENT_TYPE;
+ case HISTORY_ID:
+ return History.CONTENT_ITEM_TYPE;
+ case SEARCHES:
+ return Searches.CONTENT_TYPE;
+ case SEARCHES_ID:
+ return Searches.CONTENT_ITEM_TYPE;
+// case SUGGEST:
+// return SearchManager.SUGGEST_MIME_TYPE;
+ }
+ return null;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ final int match = URI_MATCHER.match(uri);
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
+ switch (match) {
+ case ACCOUNTS: {
+ qb.setTables(TABLE_BOOKMARKS);
+ qb.setProjectionMap(ACCOUNTS_PROJECTION_MAP);
+ qb.setDistinct(true);
+ qb.appendWhere(Bookmarks.ACCOUNT_NAME + " IS NOT NULL");
+ break;
+ }
+
+ case BOOKMARKS_FOLDER_ID:
+ case BOOKMARKS_ID:
+ case BOOKMARKS: {
+ // Only show deleted bookmarks if requested to do so
+ if (!uri.getBooleanQueryParameter(Bookmarks.QUERY_PARAMETER_SHOW_DELETED, false)) {
+ selection = DatabaseUtils.concatenateWhere(
+ Bookmarks.IS_DELETED + "=0", selection);
+ }
+
+ if (match == BOOKMARKS_ID) {
+ // Tack on the ID of the specific bookmark requested
+ selection = DatabaseUtils.concatenateWhere(selection,
+ TABLE_BOOKMARKS + "." + Bookmarks._ID + "=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ } else if (match == BOOKMARKS_FOLDER_ID) {
+ // Tack on the ID of the specific folder requested
+ selection = DatabaseUtils.concatenateWhere(selection,
+ TABLE_BOOKMARKS + "." + Bookmarks.PARENT + "=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ }
+
+ // Look for account info
+ String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
+ String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
+ if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
+ selection = DatabaseUtils.concatenateWhere(selection,
+ Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? ");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { accountType, accountName });
+ }
+
+ // Set a default sort order if one isn't specified
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
+ }
+
+ qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
+ qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
+ break;
+ }
+
+ case BOOKMARKS_FOLDER: {
+ // Don't allow selections to be applied to the default folder
+ if (!TextUtils.isEmpty(selection) || selectionArgs != null) {
+ throw new UnsupportedOperationException(
+ "selections aren't supported on this URI");
+ }
+
+ // Look for an account
+ boolean useAccount = false;
+ String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
+ String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
+ if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
+ useAccount = true;
+ }
+
+ qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
+ String bookmarksBarQuery;
+ String otherBookmarksQuery;
+ String[] args;
+ if (!useAccount) {
+ qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
+ bookmarksBarQuery = qb.buildQuery(projection,
+ Bookmarks.PARENT + "=? AND " + Bookmarks.IS_DELETED + "=0",
+ null, null, null, null, null);
+
+ qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
+ otherBookmarksQuery = qb.buildQuery(projection,
+ Bookmarks._ID + "=?",
+ null, null, null, null, null);
+
+ args = new String[] { Long.toString(FIXED_ID_BOOKMARKS_BAR),
+ Long.toString(FIXED_ID_OTHER_BOOKMARKS) };
+ } else {
+ qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
+ bookmarksBarQuery = qb.buildQuery(projection,
+ Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? " +
+ "AND parent = " +
+ "(SELECT _id FROM " + TABLE_BOOKMARKS + " WHERE " +
+ ChromeSyncColumns.SERVER_UNIQUE + "=" +
+ "'" + ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR + "' " +
+ "AND account_type = ? AND account_name = ?) " +
+ "AND " + Bookmarks.IS_DELETED + "=0",
+ null, null, null, null, null);
+
+ qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
+ otherBookmarksQuery = qb.buildQuery(projection,
+ Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=?" +
+ " AND " + ChromeSyncColumns.SERVER_UNIQUE + "=?",
+ null, null, null, null, null);
+
+ args = new String[] {
+ accountType, accountName, accountType, accountName,
+ accountType, accountName, ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS,
+ };
+ }
+
+ String query = qb.buildUnionQuery(
+ new String[] { bookmarksBarQuery, otherBookmarksQuery },
+ DEFAULT_BOOKMARKS_SORT_ORDER, limit);
+ return db.rawQuery(query, args);
+ }
+
+ case HISTORY_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case HISTORY: {
+ if (sortOrder == null) {
+ sortOrder = DEFAULT_SORT_HISTORY;
+ }
+ qb.setProjectionMap(HISTORY_PROJECTION_MAP);
+ qb.setTables(TABLE_HISTORY_JOIN_IMAGES);
+ break;
+ }
+
+ case SYNCSTATE: {
+ return mSyncHelper.query(db, projection, selection, selectionArgs, sortOrder);
+ }
+
+ case SYNCSTATE_ID: {
+ selection = appendAccountToSelection(uri, selection);
+ String selectionWithId =
+ (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
+ + (selection == null ? "" : " AND (" + selection + ")");
+ return mSyncHelper.query(db, projection, selectionWithId, selectionArgs, sortOrder);
+ }
+
+ case IMAGES: {
+ qb.setTables(TABLE_IMAGES);
+ qb.setProjectionMap(IMAGES_PROJECTION_MAP);
+ break;
+ }
+
+ case COMBINED_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection, VIEW_COMBINED + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case COMBINED: {
+ qb.setTables(VIEW_COMBINED);
+ qb.setProjectionMap(COMBINED_PROJECTION_MAP);
+ break;
+ }
+
+ default: {
+ throw new UnsupportedOperationException("Unknown URL " + uri.toString());
+ }
+ }
+
+ Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder,
+ limit);
+ cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.AUTHORITY_URI);
+ return cursor;
+ }
+
+ @Override
+ public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
+ boolean callerIsSyncAdapter) {
+ final int match = URI_MATCHER.match(uri);
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ switch (match) {
+ case BOOKMARKS_ID:
+ case BOOKMARKS: {
+ //TODO cascade deletes down from folders
+ if (!callerIsSyncAdapter) {
+ // If the caller isn't a sync adapter just go through and update all the
+ // bookmarks to have the deleted flag set.
+ ContentValues values = new ContentValues();
+ values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
+ values.put(Bookmarks.IS_DELETED, 1);
+ return updateInTransaction(uri, values, selection, selectionArgs,
+ callerIsSyncAdapter);
+ } else {
+ // Sync adapters are allowed to actually delete things
+ if (match == BOOKMARKS_ID) {
+ selection = DatabaseUtils.concatenateWhere(selection,
+ TABLE_BOOKMARKS + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ }
+ return db.delete(TABLE_BOOKMARKS, selection, selectionArgs);
+ }
+ }
+
+ case HISTORY_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case HISTORY: {
+ return db.delete(TABLE_HISTORY, selection, selectionArgs);
+ }
+
+ case SEARCHES_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection, TABLE_SEARCHES + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case SEARCHES: {
+ return db.delete(TABLE_SEARCHES, selection, selectionArgs);
+ }
+
+ case SYNCSTATE: {
+ return mSyncHelper.delete(db, selection, selectionArgs);
+ }
+ case SYNCSTATE_ID: {
+ String selectionWithId =
+ (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
+ + (selection == null ? "" : " AND (" + selection + ")");
+ return mSyncHelper.delete(db, selectionWithId, selectionArgs);
+ }
+ }
+ throw new UnsupportedOperationException("Unknown update URI " + uri);
+ }
+
+ @Override
+ public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
+ final int match = URI_MATCHER.match(uri);
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long id = -1;
+ switch (match) {
+ case BOOKMARKS: {
+ // Mark rows dirty if they're not coming from a sync adapater
+ if (!callerIsSyncAdapter) {
+ long now = System.currentTimeMillis();
+ values.put(Bookmarks.DATE_CREATED, now);
+ values.put(Bookmarks.DATE_MODIFIED, now);
+ values.put(Bookmarks.DIRTY, 1);
+
+ // If no parent is set default to the "Bookmarks Bar" folder
+ if (!values.containsKey(Bookmarks.PARENT)) {
+ values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS_BAR);
+ }
+ }
+
+ // If no position is requested put the bookmark at the beginning of the list
+ if (!values.containsKey(Bookmarks.POSITION)) {
+ values.put(Bookmarks.POSITION, Long.toString(Long.MIN_VALUE));
+ }
+
+ // Extract out the image values so they can be inserted into the images table
+ String url = values.getAsString(Bookmarks.URL);
+ ContentValues imageValues = extractImageValues(values, url);
+ Boolean isFolder = values.getAsBoolean(Bookmarks.IS_FOLDER);
+ if ((isFolder == null || !isFolder)
+ && imageValues != null && !TextUtils.isEmpty(url)) {
+ int count = db.update(TABLE_IMAGES, imageValues, Images.URL + "=?",
+ new String[] { url });
+ if (count == 0) {
+ db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
+ }
+ }
+
+ id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
+ break;
+ }
+
+ case HISTORY: {
+ // If no created time is specified set it to now
+ if (!values.containsKey(History.DATE_CREATED)) {
+ values.put(History.DATE_CREATED, System.currentTimeMillis());
+ }
+
+ // Extract out the image values so they can be inserted into the images table
+ ContentValues imageValues = extractImageValues(values,
+ values.getAsString(History.URL));
+ if (imageValues != null) {
+ db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
+ }
+
+ id = db.insertOrThrow(TABLE_HISTORY, History.VISITS, values);
+ break;
+ }
+
+ case SEARCHES: {
+ id = insertSearchesInTransaction(db, values);
+ break;
+ }
+
+ case SYNCSTATE: {
+ id = mSyncHelper.insert(db, values);
+ break;
+ }
+
+ default: {
+ throw new UnsupportedOperationException("Unknown insert URI " + uri);
+ }
+ }
+
+ if (id >= 0) {
+ return ContentUris.withAppendedId(uri, id);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Searches are unique, so perform an UPSERT manually since SQLite doesn't support them.
+ */
+ private long insertSearchesInTransaction(SQLiteDatabase db, ContentValues values) {
+ String search = values.getAsString(Searches.SEARCH);
+ if (TextUtils.isEmpty(search)) {
+ throw new IllegalArgumentException("Must include the SEARCH field");
+ }
+ Cursor cursor = null;
+ try {
+ cursor = db.query(TABLE_SEARCHES, new String[] { Searches._ID },
+ Searches.SEARCH + "=?", new String[] { search }, null, null, null);
+ if (cursor.moveToNext()) {
+ long id = cursor.getLong(0);
+ db.update(TABLE_SEARCHES, values, Searches._ID + "=?",
+ new String[] { Long.toString(id) });
+ return id;
+ } else {
+ return db.insertOrThrow(TABLE_SEARCHES, Searches.SEARCH, values);
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ }
+
+ @Override
+ public int updateInTransaction(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs, boolean callerIsSyncAdapter) {
+ final int match = URI_MATCHER.match(uri);
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ switch (match) {
+ case BOOKMARKS_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection,
+ TABLE_BOOKMARKS + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case BOOKMARKS: {
+ return updateBookmarksInTransaction(values, selection, selectionArgs,
+ callerIsSyncAdapter);
+ }
+
+ case HISTORY_ID: {
+ selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case HISTORY: {
+ return updateHistoryInTransaction(values, selection, selectionArgs);
+ }
+
+ case SYNCSTATE: {
+ return mSyncHelper.update(mDb, values,
+ appendAccountToSelection(uri, selection), selectionArgs);
+ }
+
+ case SYNCSTATE_ID: {
+ selection = appendAccountToSelection(uri, selection);
+ String selectionWithId =
+ (SyncStateContract.Columns._ID + "=" + ContentUris.parseId(uri) + " ")
+ + (selection == null ? "" : " AND (" + selection + ")");
+ return mSyncHelper.update(mDb, values,
+ selectionWithId, selectionArgs);
+ }
+
+ case IMAGES: {
+ String url = values.getAsString(Images.URL);
+ if (TextUtils.isEmpty(url)) {
+ throw new IllegalArgumentException("Images.URL is required");
+ }
+ int count = db.update(TABLE_IMAGES, values, Images.URL + "=?",
+ new String[] { url });
+ if (count == 0) {
+ db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, values);
+ count = 1;
+ }
+ return count;
+ }
+ }
+ throw new UnsupportedOperationException("Unknown update URI " + uri);
+ }
+
+ /**
+ * Does a query to find the matching bookmarks and updates each one with the provided values.
+ */
+ int updateBookmarksInTransaction(ContentValues values, String selection,
+ String[] selectionArgs, boolean callerIsSyncAdapter) {
+ int count = 0;
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Cursor cursor = query(Bookmarks.CONTENT_URI,
+ new String[] { Bookmarks._ID, Bookmarks.VERSION, Bookmarks.URL },
+ selection, selectionArgs, null);
+ try {
+ String[] args = new String[1];
+ // Mark the bookmark dirty if the caller isn't a sync adapter
+ if (!callerIsSyncAdapter) {
+ values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
+ values.put(Bookmarks.DIRTY, 1);
+ }
+
+ boolean updatingUrl = values.containsKey(Bookmarks.URL);
+ String url = null;
+ if (updatingUrl) {
+ url = values.getAsString(Bookmarks.URL);
+ }
+ ContentValues imageValues = extractImageValues(values, url);
+
+ while (cursor.moveToNext()) {
+ args[0] = cursor.getString(0);
+ if (!callerIsSyncAdapter) {
+ // increase the local version for non-sync changes
+ values.put(Bookmarks.VERSION, cursor.getLong(1) + 1);
+ }
+ count += db.update(TABLE_BOOKMARKS, values, "_id=?", args);
+
+ // Update the images over in their table
+ if (imageValues != null) {
+ if (!updatingUrl) {
+ url = cursor.getString(2);
+ imageValues.put(Images.URL, url);
+ }
+
+ if (!TextUtils.isEmpty(url)) {
+ args[0] = url;
+ if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) {
+ db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
+ }
+ }
+ }
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return count;
+ }
+
+ /**
+ * Does a query to find the matching bookmarks and updates each one with the provided values.
+ */
+ int updateHistoryInTransaction(ContentValues values, String selection, String[] selectionArgs) {
+ int count = 0;
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Cursor cursor = query(History.CONTENT_URI,
+ new String[] { History._ID, History.URL },
+ selection, selectionArgs, null);
+ try {
+ String[] args = new String[1];
+
+ boolean updatingUrl = values.containsKey(History.URL);
+ String url = null;
+ if (updatingUrl) {
+ url = values.getAsString(History.URL);
+ }
+ ContentValues imageValues = extractImageValues(values, url);
+
+ while (cursor.moveToNext()) {
+ args[0] = cursor.getString(0);
+ count += db.update(TABLE_HISTORY, values, "_id=?", args);
+
+ // Update the images over in their table
+ if (imageValues != null) {
+ if (!updatingUrl) {
+ url = cursor.getString(1);
+ imageValues.put(Images.URL, url);
+ }
+ args[0] = url;
+ if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) {
+ db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
+ }
+ }
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return count;
+ }
+
+ String appendAccountToSelection(Uri uri, String selection) {
+ final String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME);
+ final String accountType = uri.getQueryParameter(RawContacts.ACCOUNT_TYPE);
+
+ final boolean partialUri = TextUtils.isEmpty(accountName) ^ TextUtils.isEmpty(accountType);
+ if (partialUri) {
+ // Throw when either account is incomplete
+ throw new IllegalArgumentException(
+ "Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE for " + uri);
+ }
+
+ // Accounts are valid by only checking one parameter, since we've
+ // already ruled out partial accounts.
+ final boolean validAccount = !TextUtils.isEmpty(accountName);
+ if (validAccount) {
+ StringBuilder selectionSb = new StringBuilder(RawContacts.ACCOUNT_NAME + "="
+ + DatabaseUtils.sqlEscapeString(accountName) + " AND "
+ + RawContacts.ACCOUNT_TYPE + "="
+ + DatabaseUtils.sqlEscapeString(accountType));
+ if (!TextUtils.isEmpty(selection)) {
+ selectionSb.append(" AND (");
+ selectionSb.append(selection);
+ selectionSb.append(')');
+ }
+ return selectionSb.toString();
+ } else {
+ return selection;
+ }
+ }
+
+ ContentValues extractImageValues(ContentValues values, String url) {
+ ContentValues imageValues = null;
+ // favicon
+ if (values.containsKey(Bookmarks.FAVICON)) {
+ imageValues = new ContentValues();
+ imageValues.put(Images.FAVICON, values.getAsByteArray(Bookmarks.FAVICON));
+ values.remove(Bookmarks.FAVICON);
+ }
+
+ // thumbnail
+ if (values.containsKey(Bookmarks.THUMBNAIL)) {
+ if (imageValues == null) {
+ imageValues = new ContentValues();
+ }
+ imageValues.put(Images.THUMBNAIL, values.getAsByteArray(Bookmarks.THUMBNAIL));
+ values.remove(Bookmarks.THUMBNAIL);
+ }
+
+ // touch icon
+ if (values.containsKey(Bookmarks.TOUCH_ICON)) {
+ if (imageValues == null) {
+ imageValues = new ContentValues();
+ }
+ imageValues.put(Images.TOUCH_ICON, values.getAsByteArray(Bookmarks.TOUCH_ICON));
+ values.remove(Bookmarks.TOUCH_ICON);
+ }
+
+ if (imageValues != null) {
+ imageValues.put(Images.URL, url);
+ }
+ return imageValues;
+ }
+}
diff --git a/src/com/android/browser/provider/SQLiteContentProvider.java b/src/com/android/browser/provider/SQLiteContentProvider.java
new file mode 100644
index 000000000..a50894a41
--- /dev/null
+++ b/src/com/android/browser/provider/SQLiteContentProvider.java
@@ -0,0 +1,276 @@
+/*
+ * 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.provider;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteTransactionListener;
+import android.net.Uri;
+
+import java.util.ArrayList;
+
+/**
+ * General purpose {@link ContentProvider} base class that uses SQLiteDatabase for storage.
+ */
+public abstract class SQLiteContentProvider extends ContentProvider
+ implements SQLiteTransactionListener {
+
+ private static final String TAG = "SQLiteContentProvider";
+
+ private SQLiteOpenHelper mOpenHelper;
+ private volatile boolean mNotifyChange;
+ protected SQLiteDatabase mDb;
+
+ private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
+ private static final int SLEEP_AFTER_YIELD_DELAY = 4000;
+
+ /**
+ * Maximum number of operations allowed in a batch between yield points.
+ */
+ private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500;
+
+ @Override
+ public boolean onCreate() {
+ Context context = getContext();
+ mOpenHelper = getDatabaseHelper(context);
+ return true;
+ }
+
+ /**
+ * Returns a {@link SQLiteOpenHelper} that can open the database.
+ */
+ public abstract SQLiteOpenHelper getDatabaseHelper(Context context);
+
+ /**
+ * The equivalent of the {@link #insert} method, but invoked within a transaction.
+ */
+ public abstract Uri insertInTransaction(Uri uri, ContentValues values,
+ boolean callerIsSyncAdapter);
+
+ /**
+ * The equivalent of the {@link #update} method, but invoked within a transaction.
+ */
+ public abstract int updateInTransaction(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs, boolean callerIsSyncAdapter);
+
+ /**
+ * The equivalent of the {@link #delete} method, but invoked within a transaction.
+ */
+ public abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
+ boolean callerIsSyncAdapter);
+
+ /**
+ * Called when the provider needs to notify the system of a change.
+ * @param callerIsSyncAdapter true if the caller that caused the change was a sync adapter.
+ */
+ public abstract void notifyChange(boolean callerIsSyncAdapter);
+
+ public boolean isCallerSyncAdapter(Uri uri) {
+ return false;
+ }
+
+ public SQLiteOpenHelper getDatabaseHelper() {
+ return mOpenHelper;
+ }
+
+ private boolean applyingBatch() {
+ return mApplyingBatch.get() != null && mApplyingBatch.get();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ Uri result = null;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ boolean applyingBatch = applyingBatch();
+ if (!applyingBatch) {
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransactionWithListener(this);
+ try {
+ result = insertInTransaction(uri, values, callerIsSyncAdapter);
+ if (result != null) {
+ mNotifyChange = true;
+ }
+ mDb.setTransactionSuccessful();
+ } finally {
+ mDb.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ } else {
+ result = insertInTransaction(uri, values, callerIsSyncAdapter);
+ if (result != null) {
+ mNotifyChange = true;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ int numValues = values.length;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransactionWithListener(this);
+ try {
+ for (int i = 0; i < numValues; i++) {
+ Uri result = insertInTransaction(uri, values[i], callerIsSyncAdapter);
+ if (result != null) {
+ mNotifyChange = true;
+ }
+ mDb.yieldIfContendedSafely();
+ }
+ mDb.setTransactionSuccessful();
+ } finally {
+ mDb.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ return numValues;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ int count = 0;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ boolean applyingBatch = applyingBatch();
+ if (!applyingBatch) {
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransactionWithListener(this);
+ try {
+ count = updateInTransaction(uri, values, selection, selectionArgs,
+ callerIsSyncAdapter);
+ if (count > 0) {
+ mNotifyChange = true;
+ }
+ mDb.setTransactionSuccessful();
+ } finally {
+ mDb.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ } else {
+ count = updateInTransaction(uri, values, selection, selectionArgs, callerIsSyncAdapter);
+ if (count > 0) {
+ mNotifyChange = true;
+ }
+ }
+
+ return count;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ int count = 0;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ boolean applyingBatch = applyingBatch();
+ if (!applyingBatch) {
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransactionWithListener(this);
+ try {
+ count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
+ if (count > 0) {
+ mNotifyChange = true;
+ }
+ mDb.setTransactionSuccessful();
+ } finally {
+ mDb.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ } else {
+ count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
+ if (count > 0) {
+ mNotifyChange = true;
+ }
+ }
+ return count;
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ int ypCount = 0;
+ int opCount = 0;
+ boolean callerIsSyncAdapter = false;
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransactionWithListener(this);
+ try {
+ mApplyingBatch.set(true);
+ final int numOperations = operations.size();
+ final ContentProviderResult[] results = new ContentProviderResult[numOperations];
+ for (int i = 0; i < numOperations; i++) {
+ if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) {
+ throw new OperationApplicationException(
+ "Too many content provider operations between yield points. "
+ + "The maximum number of operations per yield point is "
+ + MAX_OPERATIONS_PER_YIELD_POINT, ypCount);
+ }
+ final ContentProviderOperation operation = operations.get(i);
+ if (!callerIsSyncAdapter && isCallerSyncAdapter(operation.getUri())) {
+ callerIsSyncAdapter = true;
+ }
+ if (i > 0 && operation.isYieldAllowed()) {
+ opCount = 0;
+ if (mDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)) {
+ ypCount++;
+ }
+ }
+ results[i] = operation.apply(this, results, i);
+ }
+ mDb.setTransactionSuccessful();
+ return results;
+ } finally {
+ mApplyingBatch.set(false);
+ mDb.endTransaction();
+ onEndTransaction(callerIsSyncAdapter);
+ }
+ }
+
+ @Override
+ public void onBegin() {
+ onBeginTransaction();
+ }
+
+ @Override
+ public void onCommit() {
+ beforeTransactionCommit();
+ }
+
+ @Override
+ public void onRollback() {
+ // not used
+ }
+
+ protected void onBeginTransaction() {
+ }
+
+ protected void beforeTransactionCommit() {
+ }
+
+ protected void onEndTransaction(boolean callerIsSyncAdapter) {
+ if (mNotifyChange) {
+ mNotifyChange = false;
+ notifyChange(callerIsSyncAdapter);
+ }
+ }
+}
diff --git a/src/com/android/browser/search/.DefaultSearchEngine.java.swp b/src/com/android/browser/search/.DefaultSearchEngine.java.swp
new file mode 100644
index 000000000..441153c4a
--- /dev/null
+++ b/src/com/android/browser/search/.DefaultSearchEngine.java.swp
Binary files differ
diff --git a/src/com/android/browser/widget/BookmarkStackWidgetProvider.java b/src/com/android/browser/widget/BookmarkStackWidgetProvider.java
new file mode 100644
index 000000000..0684c616c
--- /dev/null
+++ b/src/com/android/browser/widget/BookmarkStackWidgetProvider.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 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.widget;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Widget that shows a preview of the user's bookmarks.
+ */
+public class BookmarkStackWidgetProvider extends AppWidgetProvider {
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager mngr, int[] ids) {
+ context.startService(new Intent(BookmarkStackWidgetService.UPDATE, null,
+ context, BookmarkStackWidgetService.class));
+ }
+
+ @Override
+ public void onEnabled(Context context) {
+ context.startService(new Intent(context, BookmarkStackWidgetService.class));
+ }
+
+ @Override
+ public void onDisabled(Context context) {
+ context.stopService(new Intent(context, BookmarkStackWidgetService.class));
+ }
+
+}
diff --git a/src/com/android/browser/widget/BookmarkStackWidgetService.java b/src/com/android/browser/widget/BookmarkStackWidgetService.java
new file mode 100644
index 000000000..f58cec1db
--- /dev/null
+++ b/src/com/android/browser/widget/BookmarkStackWidgetService.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2010 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.widget;
+
+import com.android.browser.R;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.BrowserContract;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BookmarkStackWidgetService extends RemoteViewsService {
+
+ private static final String LOGTAG = "browserwidget";
+
+ /** Force the bookmarks to be re-renderer. */
+ public static final String UPDATE = "com.android.browser.widget.UPDATE";
+
+ /** the adapter intent action */
+ public static final String ADAPTER = "com.android.browser.widget.ADAPTER";
+
+ private static final String EXTRA_ID = "_id";
+ private static final String EXTRA_URL = "_url";
+
+ private static final String[] PROJECTION = new String[] {
+ BrowserContract.Bookmarks._ID,
+ BrowserContract.Bookmarks.TITLE,
+ BrowserContract.Bookmarks.URL,
+ BrowserContract.Bookmarks.THUMBNAIL };
+
+ private static final String WHERE_CLAUSE = BrowserContract.Bookmarks.IS_FOLDER +
+ " == 0";
+
+ // No id specified.
+ private static final int NO_ID = -1;
+
+ private static final int MSG_UPDATE = 0;
+
+ List<RenderResult> mBookmarks;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE:
+ updateWidget();
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if ((intent == null) || (intent.getAction() != null) && UPDATE.equals(intent.getAction())) {
+ mHandler.sendEmptyMessage(MSG_UPDATE);
+ }
+ return START_STICKY;
+ }
+
+ private void updateWidget() {
+ RemoteViews views = new RemoteViews(getPackageName(),
+ R.layout.bookmarkstackwidget);
+ Intent adapter = new Intent(BookmarkStackWidgetService.ADAPTER, null,
+ this, BookmarkStackWidgetService.class);
+ views.setRemoteAdapter(R.id.stackwidget_stack, adapter);
+ AppWidgetManager.getInstance(this).updateAppWidget(
+ new ComponentName(this, BookmarkStackWidgetProvider.class),
+ views);
+ }
+
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ return mViewFactory;
+ }
+
+ RemoteViewsService.RemoteViewsFactory mViewFactory = new RemoteViewsFactory () {
+
+ @Override
+ public int getCount() {
+ return mBookmarks.size();
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public RemoteViews getLoadingView() {
+ return null;
+ }
+
+ @Override
+ public RemoteViews getViewAt(int position) {
+ RenderResult res = mBookmarks.get(position);
+ RemoteViews views = new RemoteViews(getPackageName(),
+ R.layout.bookmarkstackwidget_item);
+ views.setOnClickPendingIntent(R.id.stack_item,
+ getOpenUrlPendingIntent(res.mUrl));
+
+ // Set the title of the bookmark. Use the url as a backup.
+ String displayTitle = res.mTitle;
+ if (TextUtils.isEmpty(displayTitle)) {
+ displayTitle = res.mUrl;
+ }
+ views.setTextViewText(R.id.label, displayTitle);
+ if (res.mBitmap != null) {
+ views.setImageViewBitmap(R.id.thumb, res.mBitmap);
+ views.setViewVisibility(R.id.label, View.GONE);
+ }
+ return views;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+
+ @Override
+ public void onCreate() {
+ update();
+ }
+
+ @Override
+ public void onDestroy() {
+ }
+
+ public void update() {
+ mBookmarks = new ArrayList<RenderResult>();
+ // Look up all the bookmarks
+ Cursor c = null;
+ try {
+ c = getContentResolver().query(BrowserContract.Bookmarks.CONTENT_URI,
+ PROJECTION, WHERE_CLAUSE, null, null);
+ if (c != null) {
+ while (c.moveToNext()) {
+ int id = c.getInt(0);
+ String title = c.getString(1);
+ String url = c.getString(2);
+ RenderResult res = new RenderResult(id, title, url);
+ byte[] blob = c.getBlob(3);
+ if (blob != null) {
+ res.mBitmap = BitmapFactory.decodeByteArray(blob, 0, blob.length);
+ }
+ mBookmarks.add(res);
+ }
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "update bookmark widget", e);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ }
+
+ @Override
+ public void onDataSetChanged() {
+ }
+ };
+
+ private PendingIntent getOpenUrlPendingIntent(String url) {
+ Intent vi = new Intent(Intent.ACTION_VIEW);
+ vi.setData(Uri.parse(url));
+ return PendingIntent.getActivity(this, 0, vi, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+
+ // Class containing the rendering information for a specific bookmark.
+ private static class RenderResult {
+ final int mId;
+ final String mTitle;
+ final String mUrl;
+ Bitmap mBitmap;
+
+ RenderResult(int id, String title, String url) {
+ mId = id;
+ mTitle = title;
+ mUrl = url;
+ }
+
+ }
+
+
+}