summaryrefslogtreecommitdiffstats
path: root/src/com/android/browser
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/browser')
-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 5ada9dc..da39799 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 0000000..cdb0317
--- /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 ee231a7..a2c2f32 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 241b33b..e29ca18 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 4d5c5fa..40fd1e1 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 36cc490..87c38e2 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 64e8673..78fcb4b 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 765d288..2816f58 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 1d9482b..b45b8cb 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 0000000..1c31c85
--- /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 0000000..8392404
--- /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 0000000..a50894a
--- /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);
+ }
+ }
+}