summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJeff Hamilton <jham@android.com>2010-08-05 14:29:28 -0500
committerJeff Hamilton <jham@android.com>2010-08-16 11:07:27 -0500
commit8402962ef58546d3cfd48fbb211b5e36df0f118e (patch)
tree2d8a10b9426760ea11e4f0288850a41eadcb931b /src
parented217745066c160f785626e9a15ebe70af5e25e4 (diff)
downloadandroid_packages_apps_Gello-8402962ef58546d3cfd48fbb211b5e36df0f118e.tar.gz
android_packages_apps_Gello-8402962ef58546d3cfd48fbb211b5e36df0f118e.tar.bz2
android_packages_apps_Gello-8402962ef58546d3cfd48fbb211b5e36df0f118e.zip
First revision of the new browser provider.
This one has support for bookmarks sync, has the bookmarks and history in separate tables, and supports hierarchical bookmarks. Compatibility with the old APIs is not yet complete. The Bookmarks UI has been switched over to the new provider. Creating bookmarks puts them in the UIs root folder. Change-Id: Ib21713ddd19f43d178d49dbac977f749e7103368
Diffstat (limited to 'src')
-rw-r--r--src/com/android/browser/Bookmarks.java164
-rw-r--r--src/com/android/browser/BookmarksLoader.java49
-rw-r--r--src/com/android/browser/BrowserActivity.java2
-rw-r--r--src/com/android/browser/BrowserBookmarksAdapter.java565
-rw-r--r--src/com/android/browser/BrowserBookmarksPage.java741
-rw-r--r--src/com/android/browser/BrowserProvider.java4
-rw-r--r--src/com/android/browser/CombinedBookmarkHistoryActivity.java10
-rw-r--r--src/com/android/browser/DownloadTouchIcon.java2
-rw-r--r--src/com/android/browser/Tab.java4
-rw-r--r--src/com/android/browser/provider/BrowserContract.java367
-rw-r--r--src/com/android/browser/provider/BrowserProvider2.java725
-rw-r--r--src/com/android/browser/provider/SQLiteContentProvider.java276
12 files changed, 1821 insertions, 1088 deletions
diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java
index 5ada9dcb..da39799b 100644
--- a/src/com/android/browser/Bookmarks.java
+++ b/src/com/android/browser/Bookmarks.java
@@ -16,6 +16,8 @@
package com.android.browser;
+import com.android.browser.provider.BrowserContract;
+
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -23,7 +25,9 @@ import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
+import android.os.AsyncTask;
import android.provider.Browser;
+import android.provider.Browser.BookmarkColumns;
import android.util.Log;
import android.webkit.WebIconDatabase;
import android.widget.Toast;
@@ -61,72 +65,18 @@ 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, ContentResolver cr, 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);
- }
- }
+ 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));
+ cr.insert(BrowserContract.Bookmarks.CONTENT_URI, values);
} catch (IllegalStateException e) {
Log.e(LOGTAG, "addBookmark", e);
} finally {
@@ -219,4 +169,92 @@ import java.util.Date;
}
return false;
}
+
+ /* 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 = Bookmarks.removeQuery(originalUrl);
+ String urlNoQuery = Bookmarks.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);
+ }
+
+ // 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 updateBookmarkFavicon(final ContentResolver cr,
+ final String originalUrl, final String url, final Bitmap favicon) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ final Cursor c =
+ Bookmarks.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();
+ }
}
diff --git a/src/com/android/browser/BookmarksLoader.java b/src/com/android/browser/BookmarksLoader.java
new file mode 100644
index 00000000..cdb03179
--- /dev/null
+++ b/src/com/android/browser/BookmarksLoader.java
@@ -0,0 +1,49 @@
+/*
+ * 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.provider.BrowserContract.Bookmarks;
+
+import android.content.Context;
+import android.content.CursorLoader;
+
+public class BookmarksLoader extends CursorLoader {
+ public static final String ARG_ROOT_FOLDER = "root";
+
+ 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
+ };
+
+ public BookmarksLoader(Context context, int rootFolder) {
+ super(context, Bookmarks.CONTENT_URI_DEFAULT_FOLDER, PROJECTION, null, null, null);
+ }
+}
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index ee231a74..a2c2f32c 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -2474,7 +2474,7 @@ public class BrowserActivity extends Activity
protected Void doInBackground(Void... unused) {
Cursor c = null;
try {
- c = BrowserBookmarksAdapter.queryBookmarksForUrl(
+ c = Bookmarks.queryBookmarksForUrl(
cr, originalUrl, url, true);
if (c != null) {
if (c.moveToFirst()) {
diff --git a/src/com/android/browser/BrowserBookmarksAdapter.java b/src/com/android/browser/BrowserBookmarksAdapter.java
index 241b33b7..e29ca186 100644
--- a/src/com/android/browser/BrowserBookmarksAdapter.java
+++ b/src/com/android/browser/BrowserBookmarksAdapter.java
@@ -16,567 +16,44 @@
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) {
+ View holder = view.findViewById(R.id.holder);
+ 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;
- }
+ holder.setVisibility(View.GONE);
+ 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 4d5c5fa9..40fd1e12 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -16,60 +16,115 @@
package com.android.browser;
+import com.android.browser.provider.BrowserContract;
+
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.LoaderManager;
import android.content.ClipboardManager;
+import android.content.ClippedData;
+import android.content.ContentUris;
+import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
+import android.content.Loader;
+import android.database.Cursor;
import android.graphics.Bitmap;
-import android.os.AsyncTask;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.ServiceManager;
import android.provider.Browser;
-import android.util.Log;
+import android.util.Pair;
import android.view.ContextMenu;
-import android.view.Menu;
+import android.view.ContextMenu.ContextMenuInfo;
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.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Button;
import android.widget.GridView;
-import android.widget.ListView;
import android.widget.Toast;
-import android.widget.AdapterView.OnItemClickListener;
-/*package*/ enum BookmarkViewMode { NONE, GRID, LIST }
+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;
-
- 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 Activity implements View.OnCreateContextMenuListener,
+ LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, IconListener, OnClickListener {
+
+ static final int BOOKMARKS_SAVE = 1;
+ static final String LOGTAG = "browser";
+
+ static final int LOADER_BOOKMARKS = 1;
+
+ GridView mGrid;
+ 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: {
+ int rootFolder = 0;
+ if (args != null) {
+ args.getInt(BookmarksLoader.ARG_ROOT_FOLDER, 0);
+ }
+ return new BookmarksLoader(this, rootFolder);
+ }
+ }
+ throw new UnsupportedOperationException("Unknown loader id " + id);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ // Set the visibility of the empty vs. content views
+ if (data == null || data.getCount() == 0) {
+ mEmptyView.setVisibility(View.VISIBLE);
+ mContentView.setVisibility(View.GONE);
+ } else {
+ mEmptyView.setVisibility(View.GONE);
+ mContentView.setVisibility(View.VISIBLE);
+ }
+
+ // Fill in the "up" button if needed
+ BookmarksLoader bl = (BookmarksLoader) loader;
+ boolean rootFolder =
+ (BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER.equals(bl.getUri()));
+ if (rootFolder) {
+ mUpButton.setText(R.string.defaultBookmarksUpButton);
+ mUpButton.setEnabled(false);
+ } else {
+ mUpButton.setText(mFolderStack.peek().first);
+ mUpButton.setEnabled(true);
+ }
+
+ // Give the new data to the adapter
+ mAdapter.changeCursor(data);
+ }
+
+ @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) {
@@ -86,9 +141,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;
@@ -99,117 +151,88 @@ public class BrowserBookmarksPage extends Activity implements
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:
+ case R.id.share_link_context_menu_id: {
+ Cursor cursor = (Cursor) mAdapter.getItem(i.position);
BrowserActivity.sharePage(BrowserBookmarksPage.this,
- mBookmarksAdapter.getTitle(i.position), getUrl(i.position),
- getFavicon(i.position),
- mBookmarksAdapter.getScreenshot(i.position));
+ 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:
+ case R.id.homepage_context_menu_id: {
BrowserSettings.getInstance().setHomePage(this,
getUrl(i.position));
Toast.makeText(this, R.string.homepage_set,
Toast.LENGTH_LONG).show();
break;
+ }
// Only for the Most visited page
- case R.id.save_to_bookmarks_menu_id:
- boolean isBookmark;
- String name;
- String url;
- if (mViewMode == BookmarkViewMode.GRID) {
- isBookmark = mBookmarksAdapter.getIsBookmark(i.position);
- name = mBookmarksAdapter.getTitle(i.position);
- url = mBookmarksAdapter.getUrl(i.position);
- } else {
- HistoryItem historyItem = ((HistoryItem) i.targetView);
- isBookmark = historyItem.isBookmark();
- name = historyItem.getName();
- url = historyItem.getUrl();
- }
+ 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(this, getContentResolver(), url, name);
break;
+ }
default:
return super.onContextItemSelected(item);
}
return true;
}
+ Bitmap getBitmap(Cursor cursor, int columnIndex) {
+ byte[] data = cursor.getBlob(columnIndex);
+ if (data == null) {
+ return null;
+ }
+ return BitmapFactory.decodeByteArray(data, 0, data.length);
+ }
+
@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);
- }
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
+
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.bookmarkscontext, menu);
+
+ if (mDisableNewWindow) {
+ menu.findItem(R.id.new_window_context_menu_id).setVisible(false);
+ }
- 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);
+ if (mContextHeader == null) {
+ mContextHeader = new BookmarkItem(BrowserBookmarksPage.this);
+ } else if (mContextHeader.getParent() != null) {
+ ((ViewGroup) mContextHeader.getParent()).removeView(mContextHeader);
+ }
+
+ populateBookmarkItem(mAdapter, mContextHeader, info.position);
+
+ menu.setHeaderView(mContextHeader);
+ }
+
+ 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);
+ }
/**
* Create a new BrowserBookmarksPage.
@@ -218,339 +241,126 @@ public class BrowserBookmarksPage extends Activity implements
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- // Grab the app icon size as a resource.
- mIconSize = getResources().getDimensionPixelSize(
- android.R.dimen.app_icon_size);
-
Intent intent = getIntent();
if (Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) {
mCreateShortcut = true;
- }
- mDisableNewWindow = intent.getBooleanExtra("disable_new_window",
- false);
- mMostVisited = intent.getBooleanExtra("mostVisited", false);
-
- if (mCreateShortcut) {
setTitle(R.string.browser_bookmarks_page_bookmarks_text);
}
+ mDisableNewWindow = intent.getBooleanExtra("disable_new_window", false);
+
+ setContentView(R.layout.bookmarks);
+ mEmptyView = findViewById(android.R.id.empty);
+ mContentView = findViewById(android.R.id.content);
+
+ mGrid = (GridView) findViewById(R.id.grid);
+ mGrid.setOnItemClickListener(this);
+ mGrid.setColumnWidth(
+ BrowserActivity.getDesiredThumbnailWidth(this));
+ if (!mCreateShortcut) {
+ mGrid.setOnCreateContextMenuListener(this);
+ }
- setContentView(R.layout.empty_history);
- mEmptyView = findViewById(R.id.empty_view);
- mEmptyView.setVisibility(View.GONE);
+ mUpButton = (Button) findViewById(R.id.up);
+ mUpButton.setEnabled(false);
+ mUpButton.setOnClickListener(this);
- SharedPreferences p = getPreferences(MODE_PRIVATE);
+ mAdapter = new BrowserBookmarksAdapter(this);
+ mGrid.setAdapter(mAdapter);
- // 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();
+ // Start the loader for the bookmark data
+ getLoaderManager().initLoader(LOADER_BOOKMARKS, null, this);
+
+ // Add our own listener in case there are favicons that have yet to be loaded.
+ CombinedBookmarkHistoryActivity.getIconListenerSet().addListener(this);
}
@Override
- protected void onDestroy() {
- mHandler.removeCallbacksAndMessages(null);
- super.onDestroy();
+ 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();
}
- /**
- * Set the ContentView to be either the grid of thumbnails or the vertical
- * list.
- */
- private void switchViewMode(BookmarkViewMode viewMode) {
- if (mViewMode == viewMode) {
+ @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;
}
-
- mViewMode = viewMode;
-
- // Update the preferences to make the new view mode sticky.
- SharedPreferences preferences = getPreferences(MODE_PRIVATE);
- Editor ed = preferences.edit();
- int pref = mViewMode.ordinal();
- if (mMostVisited && preferences.getInt(PREF_MOST_VISITED_VIEW_MODE, -1) != pref) {
- ed.putInt(PREF_MOST_VISITED_VIEW_MODE, pref);
- } else if (!mMostVisited && preferences.getInt(PREF_BOOKMARK_VIEW_MODE, -1) != pref) {
- ed.putInt(PREF_BOOKMARK_VIEW_MODE, pref);
+ if (mCreateShortcut) {
+ setResultToParent(RESULT_OK, createShortcutIntent(position));
+ finish();
+ return;
}
- ed.commit();
- if (mBookmarksAdapter != null) {
- mBookmarksAdapter.switchViewMode(viewMode);
- }
- 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);
- }
- }
+ Cursor cursor = (Cursor) mAdapter.getItem(position);
+ boolean isFolder = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
+ if (!isFolder) {
+ loadUrl(position);
} else {
- if (null == mVerticalList) {
- ListView listView = new ListView(this);
- if (mBookmarksAdapter != null) {
- listView.setAdapter(mBookmarksAdapter);
- }
- listView.setDrawSelectorOnTop(false);
- listView.setVerticalScrollBarEnabled(true);
- listView.setOnItemClickListener(mListListener);
- if (mMostVisited) {
- listView.setEmptyView(mEmptyView);
- }
- if (!mCreateShortcut) {
- listView.setOnCreateContextMenuListener(this);
- }
- mVerticalList = listView;
- }
- addContentView(mVerticalList, FULL_SCREEN_PARAMS);
- if (mGridPage != null) {
- ViewGroup parent = (ViewGroup) mGridPage.getParent();
- if (parent != null) {
- parent.removeView(mGridPage);
- }
- }
- }
- }
-
- private static final ViewGroup.LayoutParams FULL_SCREEN_PARAMS
- = new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.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;
- }
- }
- };
-
- private OnItemClickListener mListener = new 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);
- }
+ String title;
+ if (mFolderStack.size() != 0) {
+ title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
} else {
- setResultToParent(RESULT_OK, createShortcutIntent(position));
- finish();
+ // 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();
}
- };
-
- private OnItemClickListener mListListener = new 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 {
- setResultToParent(RESULT_OK, createShortcutIntent(position));
- finish();
- }
- }
- };
+ }
private Intent createShortcutIntent(int position) {
- String url = getUrl(position);
- String title = getBookmarkTitle(position);
- Bitmap touchIcon = getTouchIcon(position);
- Bitmap favicon = getFavicon(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(this, url, title, touchIcon, favicon);
}
- 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));
+ Cursor cursor = (Cursor) mAdapter.getItem(position);
+ Intent intent = new Intent(cursor.getString(BookmarksLoader.COLUMN_INDEX_URL));
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;
- }
-
- @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;
- }
-
- @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;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- return true;
- }
-
private void openInNewWindow(int position) {
Bundle b = new Bundle();
b.putBoolean("new_window", true);
- setResultToParent(RESULT_OK,
- (new Intent()).setAction(getUrl(position)).putExtras(b));
-
+ setResultToParent(RESULT_OK, (new Intent(getUrl(position))).putExtras(b));
finish();
}
-
private void editBookmark(int position) {
- Intent intent = new Intent(BrowserBookmarksPage.this,
- AddBookmarkPage.class);
- intent.putExtra("bookmark", getRow(position));
+ Intent intent = new Intent(this, AddBookmarkPage.class);
+ Cursor cursor = (Cursor) mAdapter.getItem(position);
+ Bundle item = new Bundle();
+ item.putString(Browser.BookmarkColumns.TITLE,
+ cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
+ item.putString(Browser.BookmarkColumns.URL,
+ cursor.getString(BookmarksLoader.COLUMN_INDEX_URL));
+ byte[] data = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON);
+ if (data != null) {
+ item.putParcelable(Browser.BookmarkColumns.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) {
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch(requestCode) {
case BOOKMARKS_SAVE:
if (resultCode == RESULT_OK) {
@@ -561,92 +371,93 @@ 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.
+ * 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;
+ 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(Browser.BookmarkColumns.TITLE);
+ if (!title.equals(cursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX))) {
+ values.put(Browser.BookmarkColumns.TITLE, title);
+ }
+ String url = map.getString(Browser.BookmarkColumns.URL);
+ if (!url.equals(cursor.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) {
+ getContentResolver().update(Browser.BOOKMARKS_URI, values,
+ "_id = ?", new String[] { Integer.toString(id) });
}
}
- 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;
+ Cursor cursor = (Cursor) mAdapter.getItem(position);
new AlertDialog.Builder(this)
.setTitle(R.string.delete_bookmark)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(getText(R.string.delete_bookmark_warning).toString().replace(
- "%s", getBookmarkTitle(deletePos)))
+ "%s", cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE)))
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
- deleteBookmark(deletePos);
+ deleteBookmark(position);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}
- /**
- * Refresh the shown list after the database has changed.
- */
- private void refreshList() {
- if (mBookmarksAdapter == null) return;
- mBookmarksAdapter.refreshList();
+ private String getUrl(int position) {
+ Cursor cursor = (Cursor) mAdapter.getItem(position);
+ return cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
}
-
- /**
- * 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 void copy(CharSequence text) {
- ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
- cm.setText(text);
- }
-
- public String getBookmarkTitle(int position) {
- return mBookmarksAdapter == null ? null
- : mBookmarksAdapter.getTitle(position);
+ ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setPrimaryClip(new ClippedData(null, null, new ClippedData.Item(text)));
}
/**
* Delete the currently highlighted row.
*/
public void deleteBookmark(int position) {
- if (mBookmarksAdapter == null) return;
- mBookmarksAdapter.deleteRow(position);
+ Cursor cursor = (Cursor) mAdapter.getItem(position);
+ String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
+ String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
+ Bookmarks.removeFromBookmarks(null, getContentResolver(), url, title);
}
@Override
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index 36cc490f..87c38e2d 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -27,8 +27,8 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.UriMatcher;
import android.content.SharedPreferences.Editor;
+import android.content.UriMatcher;
import android.content.res.Configuration;
import android.database.AbstractCursor;
import android.database.ContentObserver;
@@ -41,8 +41,8 @@ import android.os.Handler;
import android.os.Process;
import android.preference.PreferenceManager;
import android.provider.Browser;
-import android.provider.Settings;
import android.provider.Browser.BookmarkColumns;
+import android.provider.Settings;
import android.speech.RecognizerResultsIntent;
import android.text.TextUtils;
import android.util.Log;
diff --git a/src/com/android/browser/CombinedBookmarkHistoryActivity.java b/src/com/android/browser/CombinedBookmarkHistoryActivity.java
index 64e8673a..78fcb4b4 100644
--- a/src/com/android/browser/CombinedBookmarkHistoryActivity.java
+++ b/src/com/android/browser/CombinedBookmarkHistoryActivity.java
@@ -54,7 +54,6 @@ public class CombinedBookmarkHistoryActivity extends TabActivity
private boolean mNewTabMode;
/* package */ static String BOOKMARKS_TAB = "bookmark";
- /* package */ static String VISITED_TAB = "visited";
/* package */ static String HISTORY_TAB = "history";
/* package */ static String STARTING_TAB = "tab";
@@ -117,15 +116,6 @@ public class CombinedBookmarkHistoryActivity extends TabActivity
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) {
diff --git a/src/com/android/browser/DownloadTouchIcon.java b/src/com/android/browser/DownloadTouchIcon.java
index 765d288f..2816f58c 100644
--- a/src/com/android/browser/DownloadTouchIcon.java
+++ b/src/com/android/browser/DownloadTouchIcon.java
@@ -101,7 +101,7 @@ class DownloadTouchIcon extends AsyncTask<String, Void, Void> {
@Override
public Void doInBackground(String... values) {
if (mContentResolver != null) {
- mCursor = BrowserBookmarksAdapter.queryBookmarksForUrl(mContentResolver,
+ mCursor = Bookmarks.queryBookmarksForUrl(mContentResolver,
mOriginalUrl, mUrl, true);
}
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 1d9482bd..b45b8cbf 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -488,7 +488,7 @@ class Tab {
// update the bookmark database for favicon
if (favicon != null) {
- BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity
+ Bookmarks.updateBookmarkFavicon(mActivity
.getContentResolver(), null, url, favicon);
}
@@ -1029,7 +1029,7 @@ class Tab {
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
if (icon != null) {
- BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity
+ Bookmarks.updateBookmarkFavicon(mActivity
.getContentResolver(), view.getOriginalUrl(), view
.getUrl(), icon);
}
diff --git a/src/com/android/browser/provider/BrowserContract.java b/src/com/android/browser/provider/BrowserContract.java
new file mode 100644
index 00000000..1c31c855
--- /dev/null
+++ b/src/com/android/browser/provider/BrowserContract.java
@@ -0,0 +1,367 @@
+/*
+ * 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.provider;
+
+import android.accounts.Account;
+import android.content.ContentProviderClient;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.SyncStateContract;
+import android.util.Pair;
+
+public class BrowserContract {
+ /** The authority for the browser provider */
+ public static final String AUTHORITY = "com.android.browser";
+
+ /** A content:// style uri to the authority for the browser provider */
+ public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+ /**
+ * An optional insert, update or delete URI parameter that allows the caller
+ * to specify that it is a sync adapter. The default value is false. If true
+ * the dirty flag is not automatically set and the "syncToNetwork" parameter
+ * is set to false when calling
+ * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
+ */
+ public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
+
+ /**
+ * Generic columns for use by sync adapters. The specific functions of
+ * these columns are private to the sync adapter. Other clients of the API
+ * should not attempt to either read or write these columns.
+ */
+ interface BaseSyncColumns {
+ /** Generic column for use by sync adapters. */
+ public static final String SYNC1 = "sync1";
+ /** Generic column for use by sync adapters. */
+ public static final String SYNC2 = "sync2";
+ /** Generic column for use by sync adapters. */
+ public static final String SYNC3 = "sync3";
+ /** Generic column for use by sync adapters. */
+ public static final String SYNC4 = "sync4";
+ /** Generic column for use by sync adapters. */
+ public static final String SYNC5 = "sync5";
+ }
+
+ /**
+ * Columns that appear when each row of a table belongs to a specific
+ * account, including sync information that an account may need.
+ */
+ interface SyncColumns extends BaseSyncColumns {
+ /**
+ * The name of the account instance to which this row belongs, which when paired with
+ * {@link #ACCOUNT_TYPE} identifies a specific account.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ACCOUNT_NAME = "account_name";
+
+ /**
+ * The type of account to which this row belongs, which when paired with
+ * {@link #ACCOUNT_NAME} identifies a specific account.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ACCOUNT_TYPE = "account_type";
+
+ /**
+ * String that uniquely identifies this row to its source account.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SOURCE_ID = "sourceid";
+
+ /**
+ * Version number that is updated whenever this row or its related data
+ * changes.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String VERSION = "version";
+
+ /**
+ * Flag indicating that {@link #VERSION} has changed, and this row needs
+ * to be synchronized by its owning account.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String DIRTY = "dirty";
+ }
+
+ interface BookmarkColumns {
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _ID = "_id";
+
+ /**
+ * The URL of the bookmark.
+ * <P>Type: TEXT (URL)</P>
+ */
+ public static final String URL = "url";
+
+ /**
+ * The user visible title of the bookmark.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TITLE = "title";
+
+ /**
+ * The favicon of the bookmark, may be NULL.
+ * Must decode via {@link BitmapFactory#decodeByteArray}.
+ * <p>Type: BLOB (image)</p>
+ */
+ public static final String FAVICON = "favicon";
+
+ /**
+ * A thumbnail of the page,may be NULL.
+ * Must decode via {@link BitmapFactory#decodeByteArray}.
+ * <p>Type: BLOB (image)</p>
+ */
+ public static final String THUMBNAIL = "thumbnail";
+
+ /**
+ * The touch icon for the web page, may be NULL.
+ * Must decode via {@link BitmapFactory#decodeByteArray}.
+ * <p>Type: BLOB (image)</p>
+ * @hide
+ */
+ public static final String TOUCH_ICON = "touch_icon";
+
+ /**
+ * @hide
+ */
+ public static final String USER_ENTERED = "user_entered";
+ }
+
+ /**
+ * The bookmarks table, which holds the user's browser bookmarks.
+ */
+ public static final class Bookmarks implements BookmarkColumns, SyncColumns {
+ /**
+ * This utility class cannot be instantiated.
+ */
+ private Bookmarks() {}
+
+ /**
+ * The content:// style URI for this table
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "bookmarks");
+
+ /**
+ * The content:// style URI for the default folder
+ */
+ public static final Uri CONTENT_URI_DEFAULT_FOLDER =
+ Uri.withAppendedPath(CONTENT_URI, "folder");
+
+ /**
+ * Builds a URI that points to a specific folder.
+ * @param folderId the ID of the folder to point to
+ */
+ public static final Uri buildFolderUri(long folderId) {
+ return ContentUris.withAppendedId(CONTENT_URI_DEFAULT_FOLDER, folderId);
+ }
+
+ /**
+ * The MIME type of {@link #CONTENT_URI} providing a directory of bookmarks.
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/bookmark";
+
+ /**
+ * The MIME type of a {@link #CONTENT_URI} of a single bookmark.
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/bookmark";
+
+ /**
+ * Query parameter to use if you want to see deleted bookmarks that are still
+ * around on the device and haven't been synced yet.
+ * @see #IS_DELETED
+ */
+ public static final String QUERY_PARAMETER_SHOW_DELETED = "show_deleted";
+
+ /**
+ * Flag indicating if an item is a folder or bookmark. Non-zero values indicate
+ * a folder and zero indicates a bookmark.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String IS_FOLDER = "folder";
+
+ /**
+ * The ID of the parent folder. ID 0 is the root folder.
+ * <P>Type: INTEGER (reference to item in the same table)</P>
+ */
+ public static final String PARENT = "parent";
+
+ /**
+ * The position of the bookmark in relation to it's siblings that share the same
+ * {@link #PARENT}. May be negative.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String POSITION = "position";
+
+ /**
+ * The item that the bookmark should be inserted after.
+ * May be negative.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String INSERT_AFTER = "insert_after";
+
+ /**
+ * A flag to indicate if an item has been deleted. Queries will not return deleted
+ * entries unless you add the {@link #QUERY_PARAMETER_SHOW_DELETED} query paramter
+ * to the URI when performing your query.
+ * <p>Type: INTEGER (non-zero if the item has been deleted, zero if it hasn't)
+ * @see #QUERY_PARAMETER_SHOW_DELETED
+ */
+ public static final String IS_DELETED = "deleted";
+ }
+
+ /**
+ * The history table, which holds the browsing history.
+ */
+ public static final class History implements BookmarkColumns {
+ /**
+ * This utility class cannot be instantiated.
+ */
+ private History() {}
+
+ /**
+ * The content:// style URI for this table
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "history");
+
+ /**
+ * The MIME type of {@link #CONTENT_URI} providing a directory of browser history items.
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/browser-history";
+
+ /**
+ * The MIME type of a {@link #CONTENT_URI} of a single browser history item.
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/browser-history";
+
+ /**
+ * The date the item was last visited, in milliseconds since the epoch.
+ * <p>Type: INTEGER (date in milliseconds since January 1, 1970)</p>
+ */
+ public static final String DATE_LAST_VISITED = "date";
+
+ /**
+ * The date the item created, in milliseconds since the epoch.
+ * <p>Type: NUMBER (date in milliseconds since January 1, 1970)</p>
+ */
+ public static final String DATE_CREATED = "created";
+
+ /**
+ * The number of times the item has been visited.
+ * <p>Type: INTEGER</p>
+ */
+ public static final String VISITS = "visits";
+ }
+
+ /**
+ * The search history table.
+ * @hide
+ */
+ public static final class Searches {
+ private Searches() {}
+
+ /**
+ * The content:// style URI for this table
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "searches");
+
+ /**
+ * The MIME type of {@link #CONTENT_URI} providing a directory of browser search items.
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/searches";
+
+ /**
+ * The MIME type of a {@link #CONTENT_URI} of a single browser search item.
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/searches";
+
+ /**
+ * The unique ID for a row.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _ID = "_id";
+
+ /**
+ * The user entered search term.
+ */
+ public static final String SEARCH = "search";
+
+ /**
+ * The date the search was performed, in milliseconds since the epoch.
+ * <p>Type: NUMBER (date in milliseconds since January 1, 1970)</p>
+ */
+ public static final String DATE = "date";
+ }
+
+ /**
+ * A table provided for sync adapters to use for storing private sync state data.
+ *
+ * @see SyncStateContract
+ */
+ public static final class SyncState implements SyncStateContract.Columns {
+ /**
+ * This utility class cannot be instantiated
+ */
+ private SyncState() {}
+
+ public static final String CONTENT_DIRECTORY =
+ SyncStateContract.Constants.CONTENT_DIRECTORY;
+
+ /**
+ * The content:// style URI for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.withAppendedPath(AUTHORITY_URI, CONTENT_DIRECTORY);
+
+ /**
+ * @see android.provider.SyncStateContract.Helpers#get
+ */
+ public static byte[] get(ContentProviderClient provider, Account account)
+ throws RemoteException {
+ return SyncStateContract.Helpers.get(provider, CONTENT_URI, account);
+ }
+
+ /**
+ * @see android.provider.SyncStateContract.Helpers#get
+ */
+ public static Pair<Uri, byte[]> getWithUri(ContentProviderClient provider, Account account)
+ throws RemoteException {
+ return SyncStateContract.Helpers.getWithUri(provider, CONTENT_URI, account);
+ }
+
+ /**
+ * @see android.provider.SyncStateContract.Helpers#set
+ */
+ public static void set(ContentProviderClient provider, Account account, byte[] data)
+ throws RemoteException {
+ SyncStateContract.Helpers.set(provider, CONTENT_URI, account, data);
+ }
+
+ /**
+ * @see android.provider.SyncStateContract.Helpers#newSetOperation
+ */
+ public static ContentProviderOperation newSetOperation(Account account, byte[] data) {
+ return SyncStateContract.Helpers.newSetOperation(CONTENT_URI, account, data);
+ }
+ }
+}
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
new file mode 100644
index 00000000..83924045
--- /dev/null
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -0,0 +1,725 @@
+/*
+ * 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.browser.provider.BrowserContract.Bookmarks;
+import com.android.browser.provider.BrowserContract.History;
+import com.android.browser.provider.BrowserContract.Searches;
+import com.android.browser.provider.BrowserContract.SyncState;
+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.ContactsContract.RawContacts;
+import android.provider.SyncStateContract;
+import android.text.TextUtils;
+
+import java.util.HashMap;
+
+public class BrowserProvider2 extends SQLiteContentProvider {
+
+ static final Uri LEGACY_BROWSER_AUTHORITY_URI = Uri.parse("browser");
+
+ static final String TABLE_BOOKMARKS = "bookmarks";
+ static final String TABLE_HISTORY = "history";
+ static final String TABLE_SEARCHES = "searches";
+ static final String TABLE_SYNC_STATE = "syncstate";
+
+ static final String HISTORY_JOIN_BOOKMARKS =
+ "history LEFT OUTER JOIN bookmarks ON (history.url = bookmarks.url)";
+
+ 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 long FIXED_ID_BOOKMARKS = 1;
+ static final long FIXED_ID_BOOKMARKS_BAR = 2;
+ static final long FIXED_ID_OTHER_BOOKMARKS = 3;
+
+ 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> 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> SEARCHES_PROJECTION_MAP = new HashMap<String, String>();
+ static final HashMap<String, String> SYNC_STATE_PROJECTION_MAP = new HashMap<String, String>();
+
+ static {
+ final UriMatcher matcher = URI_MATCHER;
+ matcher.addURI(BrowserContract.AUTHORITY, "bookmarks", BOOKMARKS);
+ matcher.addURI(BrowserContract.AUTHORITY, "bookmarks/#", BOOKMARKS_ID);
+ matcher.addURI(BrowserContract.AUTHORITY, "bookmarks/folder", BOOKMARKS_FOLDER);
+ matcher.addURI(BrowserContract.AUTHORITY, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
+ matcher.addURI(BrowserContract.AUTHORITY, "history", HISTORY);
+ matcher.addURI(BrowserContract.AUTHORITY, "history/#", HISTORY_ID);
+ matcher.addURI(BrowserContract.AUTHORITY, "searches", SEARCHES);
+ matcher.addURI(BrowserContract.AUTHORITY, "searches/#", SEARCHES_ID);
+ matcher.addURI(BrowserContract.AUTHORITY, "syncstate", SYNCSTATE);
+ matcher.addURI(BrowserContract.AUTHORITY, "syncstate/#", SYNCSTATE_ID);
+
+ // Common BookmarkColumns
+ HashMap<String, String> bookmarksColumns = new HashMap();
+ bookmarksColumns.put(Bookmarks.TITLE, Bookmarks.TITLE);
+ bookmarksColumns.put(Bookmarks.URL, Bookmarks.URL);
+ bookmarksColumns.put(Bookmarks.FAVICON, Bookmarks.FAVICON);
+ bookmarksColumns.put(Bookmarks.THUMBNAIL, Bookmarks.THUMBNAIL);
+ bookmarksColumns.put(Bookmarks.TOUCH_ICON, Bookmarks.TOUCH_ICON);
+ bookmarksColumns.put(Bookmarks.USER_ENTERED, Bookmarks.USER_ENTERED);
+
+ // Bookmarks
+ HashMap<String, String> map = BOOKMARKS_PROJECTION_MAP;
+ map.putAll(bookmarksColumns);
+ map.put(Bookmarks._ID, TABLE_BOOKMARKS + "._id AS _id");
+ map.put(Bookmarks.IS_FOLDER, Bookmarks.IS_FOLDER);
+ map.put(Bookmarks.PARENT, Bookmarks.PARENT);
+ map.put(Bookmarks.POSITION, Bookmarks.POSITION);
+ 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.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);
+
+ // 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.putAll(bookmarksColumns);
+ map.put(History._ID, TABLE_HISTORY + "._id AS _id");
+ map.put(History.DATE_CREATED, History.DATE_CREATED);
+ map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED);
+ map.put(History.VISITS, History.VISITS);
+
+ // 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);
+ }
+
+ DatabaseHelper mOpenHelper;
+ SyncStateContentProviderHelper mSyncHelper = new SyncStateContentProviderHelper();
+
+ final class DatabaseHelper extends SQLiteOpenHelper {
+ static final String DATABASE_NAME = "browser2.db";
+ static final int DATABASE_VERSION = 10;
+ 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.FAVICON + " BLOB," +
+ Bookmarks.THUMBNAIL + " BLOB," +
+ Bookmarks.TOUCH_ICON + " BLOB," +
+ Bookmarks.USER_ENTERED + " INTEGER," +
+ Bookmarks.IS_FOLDER + " INTEGER NOT NULL DEFAULT 0," +
+ Bookmarks.PARENT + " INTEGER NOT NULL DEFAULT 0," +
+ 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.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.URL + " TEXT NOT NULL," +
+ History.DATE_CREATED + " INTEGER," +
+ History.DATE_LAST_VISITED + " INTEGER," +
+ History.VISITS + " INTEGER NOT NULL DEFAULT 0" +
+ ");");
+
+ db.execSQL("CREATE TABLE " + TABLE_SEARCHES + " (" +
+ Searches._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ Searches.SEARCH + " TEXT," +
+ Searches.DATE + " LONG" +
+ ");");
+
+ 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);
+ 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
+ // TODO fill in the server unique tags for the sync adapter
+
+ // Bookmarks folder
+ values.put(Bookmarks._ID, FIXED_ID_BOOKMARKS);
+ values.put(Bookmarks.TITLE, "Bookmarks");
+ values.put(Bookmarks.PARENT, 0);
+ values.put(Bookmarks.POSITION, 0);
+ values.put(Bookmarks.IS_FOLDER, true);
+ values.put(Bookmarks.DIRTY, true);
+ long bookmarksId = db.insertOrThrow(TABLE_BOOKMARKS, null, values);
+
+ // Bookmarks Bar folder
+ values.clear();
+ values.put(Bookmarks._ID, FIXED_ID_BOOKMARKS_BAR);
+ values.put(Bookmarks.TITLE, "Bookmarks Bar");
+ values.put(Bookmarks.PARENT, bookmarksId);
+ 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(Bookmarks.TITLE, "Other Bookmarks");
+ values.put(Bookmarks.PARENT, bookmarksId);
+ 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 {
+ 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 +
+ ") VALUES (" +
+ "'" + bookmarks[i] + "', " +
+ "'" + bookmarkDestination + "', " +
+ "0," +
+ Long.toString(parentId) + "," +
+ Integer.toString(i) +
+ ");");
+ }
+ } 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_BROWSER_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();
+ switch (match) {
+ 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(
+ TABLE_BOOKMARKS + "." + Bookmarks._ID + "=?", selection);
+ 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(
+ TABLE_BOOKMARKS + "." + Bookmarks.PARENT + "=?", selection);
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ }
+
+ if (TextUtils.isEmpty(sortOrder)) {
+ sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
+ }
+
+ qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
+ qb.setTables(TABLE_BOOKMARKS);
+ 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");
+ }
+
+ qb.setTables(TABLE_BOOKMARKS);
+ qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
+ String bookmarksBarQuery = qb.buildQuery(projection,
+ Bookmarks.PARENT + "=?",
+ null, null, null, null, null);
+
+ qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
+ String otherBookmarksQuery = qb.buildQuery(projection,
+ Bookmarks._ID + "=?",
+ null, null, null, null, null);
+
+ String query = qb.buildUnionQuery(
+ new String[] { bookmarksBarQuery, otherBookmarksQuery },
+ DEFAULT_BOOKMARKS_SORT_ORDER, null);
+
+ return db.rawQuery(query, new String[] {
+ Long.toString(FIXED_ID_BOOKMARKS_BAR),
+ Long.toString(FIXED_ID_OTHER_BOOKMARKS)});
+ }
+
+ case HISTORY_ID: {
+ selection = DatabaseUtils.concatenateWhere(
+ TABLE_HISTORY + "._id=?", selection);
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ // fall through
+ }
+ case HISTORY: {
+ qb.setProjectionMap(HISTORY_PROJECTION_MAP);
+ qb.setTables(HISTORY_JOIN_BOOKMARKS);
+ 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);
+ }
+
+ default: {
+ throw new UnsupportedOperationException("Unknown URL " + uri.toString());
+ }
+ }
+
+ Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
+ 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.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) {
+ 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));
+ }
+
+ id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
+ break;
+ }
+
+ case HISTORY: {
+ id = db.insertOrThrow(TABLE_HISTORY, History.VISITS, values);
+ break;
+ }
+
+ case SEARCHES: {
+ id = db.insertOrThrow(TABLE_SEARCHES, Searches.SEARCH, values);
+ break;
+ }
+
+ case SYNCSTATE: {
+ id = mSyncHelper.insert(mDb, values);
+ break;
+ }
+
+ default: {
+ throw new UnsupportedOperationException("Unknown insert URI " + uri);
+ }
+ }
+
+ if (id >= 0) {
+ return ContentUris.withAppendedId(uri, id);
+ } else {
+ return null;
+ }
+ }
+
+ @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: {
+ // Mark the bookmark dirty if the caller isn't a sync adapter
+ if (!callerIsSyncAdapter) {
+ values = new ContentValues(values);
+ values.put(Bookmarks.DIRTY, 1);
+ }
+ selection = DatabaseUtils.concatenateWhere(selection,
+ TABLE_BOOKMARKS + "._id=?");
+ selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+ new String[] { Long.toString(ContentUris.parseId(uri)) });
+ return db.update(TABLE_BOOKMARKS, values, selection, selectionArgs);
+ }
+
+ case BOOKMARKS: {
+ if (!callerIsSyncAdapter) {
+ values = new ContentValues(values);
+ values.put(Bookmarks.DIRTY, 1);
+ }
+ return updateBookmarksInTransaction(values, 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.update(TABLE_HISTORY, values, 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.update(TABLE_SEARCHES, 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);
+ }
+ }
+ throw new UnsupportedOperationException("Unknown update URI " + uri);
+ }
+
+ /**
+ * Does a query to find the matching bookmarks and updates each one with the provided values.
+ */
+ private int updateBookmarksInTransaction(ContentValues values, String selection,
+ String[] selectionArgs) {
+ int count = 0;
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Cursor cursor = query(Bookmarks.CONTENT_URI, new String[] { Bookmarks._ID },
+ selection, selectionArgs, null);
+ try {
+ String[] args = new String[1];
+ while (cursor.moveToNext()) {
+ args[0] = cursor.getString(0);
+ count += db.update(TABLE_BOOKMARKS, values, "_id=?", args);
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return count;
+ }
+
+ private 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;
+ }
+ }
+}
diff --git a/src/com/android/browser/provider/SQLiteContentProvider.java b/src/com/android/browser/provider/SQLiteContentProvider.java
new file mode 100644
index 00000000..a50894a4
--- /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);
+ }
+ }
+}