From 60c131771453fcb65e03ae77f8a378bfa48e6498 Mon Sep 17 00:00:00 2001 From: Ruslan Abdikeev Date: Mon, 14 May 2012 08:14:11 -0700 Subject: Default implementation of PartnerBookmarksProvider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A “Default” Partner Bookmark content provider implements the Partner Bookmarks Content Provider API, and uses xml and raw resources to extract the set of bookmarks and corresponding icons. Bug: 6399404 Change-Id: I416e87320b2ba958aa4f260277e4d6de20856c48 --- Android.mk | 30 ++ AndroidManifest.xml | 26 ++ res/values/bookmarks_icons.xml | 19 + res/values/strings.xml | 21 + .../partnerbookmarks/PartnerBookmarksContract.java | 163 ++++++++ .../partnerbookmarks/PartnerBookmarksProvider.java | 450 +++++++++++++++++++++ tests/Android.mk | 31 ++ tests/AndroidManifest.xml | 37 ++ .../PartnerBookmarksProviderTest.java | 108 +++++ 9 files changed, 885 insertions(+) create mode 100644 Android.mk create mode 100644 AndroidManifest.xml create mode 100644 res/values/bookmarks_icons.xml create mode 100644 res/values/strings.xml create mode 100644 src/com/android/providers/partnerbookmarks/PartnerBookmarksContract.java create mode 100644 src/com/android/providers/partnerbookmarks/PartnerBookmarksProvider.java create mode 100644 tests/Android.mk create mode 100644 tests/AndroidManifest.xml create mode 100644 tests/src/com/android/providers/partnerbookmarks/PartnerBookmarksProviderTest.java diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..6978cfa --- /dev/null +++ b/Android.mk @@ -0,0 +1,30 @@ +# +# Copyright (C) 2012 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. +# +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_STATIC_JAVA_LIBRARIES := android-common + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := PartnerBookmarksProvider + +include $(BUILD_PACKAGE) + +# additionally, build tests in sub-folders in a separate .apk +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..46dc555 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/res/values/bookmarks_icons.xml b/res/values/bookmarks_icons.xml new file mode 100644 index 0000000..516f0ef --- /dev/null +++ b/res/values/bookmarks_icons.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..f614a2f --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,21 @@ + + + + + Partner Bookmarks + + + diff --git a/src/com/android/providers/partnerbookmarks/PartnerBookmarksContract.java b/src/com/android/providers/partnerbookmarks/PartnerBookmarksContract.java new file mode 100644 index 0000000..2b86062 --- /dev/null +++ b/src/com/android/providers/partnerbookmarks/PartnerBookmarksContract.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2012 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.providers.partnerbookmarks; + +import android.content.ContentUris; +import android.net.Uri; + +/** + *

+ * The contract between the partner bookmarks provider and applications. + * Contains the definition for the supported URIs and columns. + *

+ *

+ * Authority URI: content://com.android.partnerbookmarks + *

+ *

+ * Partner bookmarks URI: content://com.android.partnerbookmarks/bookmarks + *

+ *

+ * If the provider is found, and the set of bookmarks is non-empty, exactly one + * top-level folder with “parent” set to {@link #BOOKMARK_PARENT_ROOT_ID} + * shall be provided; more than one bookmark with “parent” set to + * {@link #BOOKMARK_PARENT_ROOT_ID} will cause the import to fail. + *

+ */ +public class PartnerBookmarksContract { + /** The authority for the partner bookmarks provider */ + public static final String AUTHORITY = "com.android.partnerbookmarks"; + + /** A content:// style uri to the authority for the partner bookmarks provider */ + public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); + + /** + * A parameter for use when querying any table that allows specifying + * a limit on the number of rows returned. + */ + public static final String PARAM_LIMIT = "limit"; + + /** + * A parameter for use when querying any table that allows specifying + * grouping of the rows returned. + */ + public static final String PARAM_GROUP_BY = "groupBy"; + + /** + * The bookmarks table, which holds the partner bookmarks. + */ + public static final class Bookmarks { + /** + * 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 root partner bookmarks folder + */ + public static final Uri CONTENT_URI_PARTNER_BOOKMARKS_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_PARTNER_BOOKMARKS_FOLDER, folderId); + } + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of bookmarks. + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/partnerbookmark"; + + /** + * The MIME type of a {@link #CONTENT_URI} of a single bookmark. + */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/partnerbookmark"; + + /** + * Used in {@link #TYPE} column and indicates the row is a bookmark. + */ + public static final int BOOKMARK_TYPE_BOOKMARK = 1; + + /** + * Used in {@link #TYPE} column and indicates the row is a folder. + */ + public static final int BOOKMARK_TYPE_FOLDER = 2; + + /** + * Used in {@link #PARENT} column and indicates the row doesn't have a parent. + */ + public static final int BOOKMARK_PARENT_ROOT_ID = 0; + + /** + * The type of the item. + *

Type: INTEGER

+ *

Allowed values are:

+ *

+ *

+ *

+ */ + public static final String TYPE = "type"; + + /** + * The unique ID for a row. Cannot be BOOKMARK_PARENT_ROOT_ID. + *

Type: INTEGER (long)

+ */ + public static final String ID = "_id"; + + /** + * This column is valid when the row is not a folder. + *

Type: TEXT (URL)

+ */ + public static final String URL = "url"; + + /** + * The user visible title. + *

Type: TEXT

+ */ + public static final String TITLE = "title"; + + /** + * The favicon of the bookmark, may be NULL. + * Must decode via {@link BitmapFactory#decodeByteArray}. + *

Type: BLOB (image)

+ */ + public static final String FAVICON = "favicon"; + + /** + * The touch icon for the web page, may be NULL. + * Must decode via {@link BitmapFactory#decodeByteArray}. + *

Type: BLOB (image)

+ */ + public static final String TOUCHICON = "touchicon"; + + /** + * The ID of the parent folder. BOOKMARK_PARENT_ROOT_ID is the root folder. + *

Type: INTEGER (long) (reference to item in the same table)

+ */ + public static final String PARENT = "parent"; + } +} diff --git a/src/com/android/providers/partnerbookmarks/PartnerBookmarksProvider.java b/src/com/android/providers/partnerbookmarks/PartnerBookmarksProvider.java new file mode 100644 index 0000000..2ad371e --- /dev/null +++ b/src/com/android/providers/partnerbookmarks/PartnerBookmarksProvider.java @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2012 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.providers.partnerbookmarks; + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.UriMatcher; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.MatrixCursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Default partner bookmarks provider implementation of {@link PartnerBookmarksContract} API. + * It reads the flat list of bookmarks and the name of the root partner + * bookmarks folder using getResources() API. + * + * Sample resources structure: + * res/ + * values/ + * strings.xml + * string name="bookmarks_folder_name" + * string-array name="bookmarks" + * item TITLE1 + * item URL1 + * item TITLE2 + * item URL2... + * bookmarks_icons.xml + * array name="bookmark_preloads" + * item @raw/favicon1 + * item @raw/touchicon1 + * item @raw/favicon2 + * item @raw/touchicon2 + * ... + */ +public class PartnerBookmarksProvider extends ContentProvider { + private static final String TAG = "PartnerBookmarksProvider"; + + // URI matcher + private static final int URI_MATCH_BOOKMARKS = 1000; + private static final int URI_MATCH_BOOKMARKS_ID = 1001; + private static final int URI_MATCH_BOOKMARKS_FOLDER = 1002; + private static final int URI_MATCH_BOOKMARKS_FOLDER_ID = 1003; + private static final int URI_MATCH_BOOKMARKS_PARTNER_BOOKMARKS_FOLDER_ID = 1004; + + private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); + private static final Map BOOKMARKS_PROJECTION_MAP + = new HashMap(); + + // Default sort order for unsync'd bookmarks + private static final String DEFAULT_BOOKMARKS_SORT_ORDER = + PartnerBookmarksContract.Bookmarks.ID + " DESC, " + + PartnerBookmarksContract.Bookmarks.ID + " ASC"; + + // Initial bookmark id when for getResources() importing + // Make sure to fix tests if you are changing this + private static final long FIXED_ID_PARTNER_BOOKMARKS_ROOT = + PartnerBookmarksContract.Bookmarks.BOOKMARK_PARENT_ROOT_ID + 1; + + // DB table name + private static final String TABLE_BOOKMARKS = "bookmarks"; + + static { + final UriMatcher matcher = URI_MATCHER; + final String authority = PartnerBookmarksContract.AUTHORITY; + matcher.addURI(authority, "bookmarks", URI_MATCH_BOOKMARKS); + matcher.addURI(authority, "bookmarks/#", URI_MATCH_BOOKMARKS_ID); + matcher.addURI(authority, "bookmarks/folder", URI_MATCH_BOOKMARKS_FOLDER); + matcher.addURI(authority, "bookmarks/folder/#", URI_MATCH_BOOKMARKS_FOLDER_ID); + matcher.addURI(authority, "bookmarks/folder/id", + URI_MATCH_BOOKMARKS_PARTNER_BOOKMARKS_FOLDER_ID); + // Projection maps + Map map = BOOKMARKS_PROJECTION_MAP; + map.put(PartnerBookmarksContract.Bookmarks.ID, + PartnerBookmarksContract.Bookmarks.ID); + map.put(PartnerBookmarksContract.Bookmarks.TITLE, + PartnerBookmarksContract.Bookmarks.TITLE); + map.put(PartnerBookmarksContract.Bookmarks.URL, + PartnerBookmarksContract.Bookmarks.URL); + map.put(PartnerBookmarksContract.Bookmarks.TYPE, + PartnerBookmarksContract.Bookmarks.TYPE); + map.put(PartnerBookmarksContract.Bookmarks.PARENT, + PartnerBookmarksContract.Bookmarks.PARENT); + map.put(PartnerBookmarksContract.Bookmarks.FAVICON, + PartnerBookmarksContract.Bookmarks.FAVICON); + map.put(PartnerBookmarksContract.Bookmarks.TOUCHICON, + PartnerBookmarksContract.Bookmarks.TOUCHICON); + } + + private final class DatabaseHelper extends SQLiteOpenHelper { + private static final String DATABASE_FILENAME = "partnerBookmarks.db"; + private static final int DATABASE_VERSION = 1; + private static final String PREFERENCES_FILENAME = "pbppref"; + private static final String ACTIVE_CONFIGURATION_PREFNAME = "config"; + private final SharedPreferences sharedPreferences; + + public DatabaseHelper(Context context) { + super(context, DATABASE_FILENAME, null, DATABASE_VERSION); + sharedPreferences = context.getSharedPreferences( + PREFERENCES_FILENAME, Context.MODE_PRIVATE); + } + + private String getConfigSignature(Configuration config) { + return "mmc=" + Integer.toString(config.mcc) + + "-mnc=" + Integer.toString(config.mnc) + + "-loc=" + config.locale.toString(); + } + + public synchronized void prepareForConfiguration(Configuration config) { + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + String newSignature = getConfigSignature(config); + String activeSignature = + sharedPreferences.getString(ACTIVE_CONFIGURATION_PREFNAME, null); + if (activeSignature == null || !activeSignature.equals(newSignature)) { + db.delete(TABLE_BOOKMARKS, null, null); + if (!createDefaultBookmarks(db)) { + // Failure to read/insert bookmarks should be treated as "no bookmarks" + db.delete(TABLE_BOOKMARKS, null, null); + } + } + } + + private void setActiveConfiguration(Configuration config) { + Editor editor = sharedPreferences.edit(); + editor.putString(ACTIVE_CONFIGURATION_PREFNAME, getConfigSignature(config)); + editor.apply(); + } + + private void createTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" + + PartnerBookmarksContract.Bookmarks.ID + + " INTEGER NOT NULL DEFAULT 0," + + PartnerBookmarksContract.Bookmarks.TITLE + + " TEXT," + + PartnerBookmarksContract.Bookmarks.URL + + " TEXT," + + PartnerBookmarksContract.Bookmarks.TYPE + + " INTEGER NOT NULL DEFAULT 0," + + PartnerBookmarksContract.Bookmarks.PARENT + + " INTEGER," + + PartnerBookmarksContract.Bookmarks.FAVICON + + " BLOB," + + PartnerBookmarksContract.Bookmarks.TOUCHICON + + " BLOB" + ");"); + } + + private void dropTable(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS); + } + + @Override + public void onCreate(SQLiteDatabase db) { + synchronized (this) { + createTable(db); + if (!createDefaultBookmarks(db)) { + // Failure to read/insert bookmarks should be treated as "no bookmarks" + dropTable(db); + createTable(db); + } + } + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + dropTable(db); + onCreate(db); + } + + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + dropTable(db); + onCreate(db); + } + + private boolean createDefaultBookmarks(SQLiteDatabase db) { + Resources res = getContext().getResources(); + try { + CharSequence bookmarksFolderName = res.getText(R.string.bookmarks_folder_name); + final CharSequence[] bookmarks = res.getTextArray(R.array.bookmarks); + if (bookmarks.length >= 1) { + if (bookmarksFolderName.length() < 1) { + Log.i(TAG, "bookmarks_folder_name was not specified; bailing out"); + return false; + } + if (!addRootFolder(db, + FIXED_ID_PARTNER_BOOKMARKS_ROOT, bookmarksFolderName.toString())) { + Log.i(TAG, "failed to insert root folder; bailing out"); + return false; + } + if (!addDefaultBookmarks(db, + FIXED_ID_PARTNER_BOOKMARKS_ROOT, FIXED_ID_PARTNER_BOOKMARKS_ROOT + 1)) { + Log.i(TAG, "failed to insert bookmarks; bailing out"); + return false; + } + } + setActiveConfiguration(res.getConfiguration()); + } catch (android.content.res.Resources.NotFoundException e) { + Log.i(TAG, "failed to fetch resources; bailing out"); + return false; + } + return true; + } + + private boolean addRootFolder(SQLiteDatabase db, long id, String bookmarksFolderName) { + ContentValues values = new ContentValues(); + values.put(PartnerBookmarksContract.Bookmarks.ID, id); + values.put(PartnerBookmarksContract.Bookmarks.TITLE, + bookmarksFolderName); + values.put(PartnerBookmarksContract.Bookmarks.PARENT, + PartnerBookmarksContract.Bookmarks.BOOKMARK_PARENT_ROOT_ID); + values.put(PartnerBookmarksContract.Bookmarks.TYPE, + PartnerBookmarksContract.Bookmarks.BOOKMARK_TYPE_FOLDER); + return db.insertOrThrow(TABLE_BOOKMARKS, null, values) != -1; + } + + private boolean addDefaultBookmarks(SQLiteDatabase db, long parentId, long firstBookmarkId) { + long bookmarkId = firstBookmarkId; + Resources res = getContext().getResources(); + final CharSequence[] bookmarks = res.getTextArray(R.array.bookmarks); + int size = bookmarks.length; + TypedArray preloads = res.obtainTypedArray(R.array.bookmark_preloads); + DatabaseUtils.InsertHelper insertHelper = null; + try { + insertHelper = new DatabaseUtils.InsertHelper(db, TABLE_BOOKMARKS); + final int idColumn = insertHelper.getColumnIndex( + PartnerBookmarksContract.Bookmarks.ID); + final int titleColumn = insertHelper.getColumnIndex( + PartnerBookmarksContract.Bookmarks.TITLE); + final int urlColumn = insertHelper.getColumnIndex( + PartnerBookmarksContract.Bookmarks.URL); + final int typeColumn = insertHelper.getColumnIndex( + PartnerBookmarksContract.Bookmarks.TYPE); + final int parentColumn = insertHelper.getColumnIndex( + PartnerBookmarksContract.Bookmarks.PARENT); + final int faviconColumn = insertHelper.getColumnIndex( + PartnerBookmarksContract.Bookmarks.FAVICON); + final int touchiconColumn = insertHelper.getColumnIndex( + PartnerBookmarksContract.Bookmarks.TOUCHICON); + + for (int i = 0; i + 1 < size; i = i + 2) { + CharSequence bookmarkDestination = bookmarks[i + 1]; + + String bookmarkTitle = bookmarks[i].toString(); + String bookmarkUrl = bookmarkDestination.toString(); + byte[] favicon = null; + if (i < preloads.length()) { + int faviconId = preloads.getResourceId(i, 0); + try { + favicon = readRaw(res, faviconId); + } catch (IOException e) { + Log.i(TAG, "Failed to read favicon for " + bookmarkTitle, e); + } + } + byte[] touchicon = null; + if (i + 1 < preloads.length()) { + int touchiconId = preloads.getResourceId(i + 1, 0); + try { + touchicon = readRaw(res, touchiconId); + } catch (IOException e) { + Log.i(TAG, "Failed to read touchicon for " + bookmarkTitle, e); + } + } + insertHelper.prepareForInsert(); + insertHelper.bind(idColumn, bookmarkId); + insertHelper.bind(titleColumn, bookmarkTitle); + insertHelper.bind(urlColumn, bookmarkUrl); + insertHelper.bind(typeColumn, + PartnerBookmarksContract.Bookmarks.BOOKMARK_TYPE_BOOKMARK); + insertHelper.bind(parentColumn, parentId); + if (favicon != null) { + insertHelper.bind(faviconColumn, favicon); + } + if (touchicon != null) { + insertHelper.bind(touchiconColumn, touchicon); + } + bookmarkId++; + if (insertHelper.execute() == -1) { + Log.i(TAG, "Failed to insert bookmark " + bookmarkTitle); + return false; + } + } + } finally { + preloads.recycle(); + insertHelper.close(); + } + return true; + } + + private byte[] readRaw(Resources res, int id) throws IOException { + if (id == 0) return null; + InputStream is = res.openRawResource(id); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + byte[] buf = new byte[4096]; + int read; + while ((read = is.read(buf)) > 0) { + bos.write(buf, 0, read); + } + bos.flush(); + return bos.toByteArray(); + } finally { + is.close(); + bos.close(); + } + } + } + + private DatabaseHelper mOpenHelper; + + @Override + public boolean onCreate() { + mOpenHelper = new DatabaseHelper(getContext()); + return true; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + mOpenHelper.prepareForConfiguration(getContext().getResources().getConfiguration()); + } + + @Override + public Cursor query(Uri uri, String[] projection, + String selection, String[] selectionArgs, String sortOrder) { + final int match = URI_MATCHER.match(uri); + mOpenHelper.prepareForConfiguration(getContext().getResources().getConfiguration()); + final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + String limit = uri.getQueryParameter(PartnerBookmarksContract.PARAM_LIMIT); + String groupBy = uri.getQueryParameter(PartnerBookmarksContract.PARAM_GROUP_BY); + switch (match) { + case URI_MATCH_BOOKMARKS_FOLDER_ID: + case URI_MATCH_BOOKMARKS_ID: + case URI_MATCH_BOOKMARKS: { + if (match == URI_MATCH_BOOKMARKS_ID) { + // Tack on the ID of the specific bookmark requested + selection = DatabaseUtils.concatenateWhere(selection, + TABLE_BOOKMARKS + "." + + PartnerBookmarksContract.Bookmarks.ID + "=?"); + selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, + new String[] { Long.toString(ContentUris.parseId(uri)) }); + } else if (match == URI_MATCH_BOOKMARKS_FOLDER_ID) { + // Tack on the ID of the specific folder requested + selection = DatabaseUtils.concatenateWhere(selection, + TABLE_BOOKMARKS + "." + + PartnerBookmarksContract.Bookmarks.PARENT + "=?"); + selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, + new String[] { Long.toString(ContentUris.parseId(uri)) }); + } + // Set a default sort order if one isn't specified + if (TextUtils.isEmpty(sortOrder)) { + sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER; + } + qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP); + qb.setTables(TABLE_BOOKMARKS); + break; + } + + case URI_MATCH_BOOKMARKS_FOLDER: { + qb.setTables(TABLE_BOOKMARKS); + String[] args; + String query; + // Set a default sort order if one isn't specified + if (TextUtils.isEmpty(sortOrder)) { + sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER; + } + qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP); + String where = PartnerBookmarksContract.Bookmarks.PARENT + "=?"; + where = DatabaseUtils.concatenateWhere(where, selection); + args = new String[] { Long.toString(FIXED_ID_PARTNER_BOOKMARKS_ROOT) }; + if (selectionArgs != null) { + args = DatabaseUtils.appendSelectionArgs(args, selectionArgs); + } + query = qb.buildQuery(projection, where, null, null, sortOrder, null); + Cursor cursor = db.rawQuery(query, args); + return cursor; + } + + case URI_MATCH_BOOKMARKS_PARTNER_BOOKMARKS_FOLDER_ID: { + MatrixCursor c = new MatrixCursor( + new String[] {PartnerBookmarksContract.Bookmarks.ID}); + c.newRow().add(FIXED_ID_PARTNER_BOOKMARKS_ROOT); + return c; + } + + default: { + throw new UnsupportedOperationException("Unknown URL " + uri.toString()); + } + } + + return qb.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder, limit); + } + + @Override + public String getType(Uri uri) { + final int match = URI_MATCHER.match(uri); + if (match == UriMatcher.NO_MATCH) return null; + return PartnerBookmarksContract.Bookmarks.CONTENT_ITEM_TYPE; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + throw new UnsupportedOperationException(); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException(); + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException(); + } +} diff --git a/tests/Android.mk b/tests/Android.mk new file mode 100644 index 0000000..7177397 --- /dev/null +++ b/tests/Android.mk @@ -0,0 +1,31 @@ +# +# Copyright (C) 2012 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. +# +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +# Include all test java files. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := PartnerBookmarksProviderTest + +LOCAL_JAVA_LIBRARIES := ext android.test.runner + +LOCAL_INSTRUMENTATION_FOR := PartnerBookmarksProvider + +include $(BUILD_PACKAGE) diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml new file mode 100644 index 0000000..6e6ba14 --- /dev/null +++ b/tests/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + diff --git a/tests/src/com/android/providers/partnerbookmarks/PartnerBookmarksProviderTest.java b/tests/src/com/android/providers/partnerbookmarks/PartnerBookmarksProviderTest.java new file mode 100644 index 0000000..9014d14 --- /dev/null +++ b/tests/src/com/android/providers/partnerbookmarks/PartnerBookmarksProviderTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2012 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.providers.partnerbookmarks; + +import android.content.ContentProviderClient; +import android.database.Cursor; +import android.test.InstrumentationTestCase; +import android.test.mock.MockContext; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.TextUtils; +import android.util.Log; + +import java.util.Arrays; + +import junit.framework.TestCase; + +public class PartnerBookmarksProviderTest extends InstrumentationTestCase { + private static final String TAG = "PartnerBookmarksProviderTest"; + private static final long FIXED_ID_PARTNER_BOOKMARKS_ROOT = + PartnerBookmarksContract.Bookmarks.BOOKMARK_PARENT_ROOT_ID + 1; + private static final long NO_FOLDER_FILTER = -1; + + @Override + public void setUp() { + } + + @Override + public void tearDown() { + } + + private int countBookmarksInFolder(long folderFilter) throws android.os.RemoteException { + ContentProviderClient providerClient = + getInstrumentation().getTargetContext(). + getContentResolver().acquireContentProviderClient( + PartnerBookmarksContract.Bookmarks.CONTENT_URI); + assertNotNull( + "Failed to acquire " + PartnerBookmarksContract.Bookmarks.CONTENT_URI, + providerClient); + Cursor cursor = providerClient.query(PartnerBookmarksContract.Bookmarks.CONTENT_URI, + null, null, null, null); + assertNotNull("Failed to query for bookmarks", cursor); + int bookmarksCount = 0; + while (!cursor.isLast() && !cursor.isAfterLast()) { + cursor.moveToNext(); + long id = cursor.getLong( + cursor.getColumnIndexOrThrow(PartnerBookmarksContract.Bookmarks.ID)); + long parent = cursor.getLong( + cursor.getColumnIndexOrThrow(PartnerBookmarksContract.Bookmarks.PARENT)); + if (folderFilter != NO_FOLDER_FILTER && folderFilter != parent) + continue; + boolean isFolder = cursor.getInt( + cursor.getColumnIndexOrThrow(PartnerBookmarksContract.Bookmarks.TYPE)) + == PartnerBookmarksContract.Bookmarks.BOOKMARK_TYPE_FOLDER; + String url = cursor.getString( + cursor.getColumnIndexOrThrow(PartnerBookmarksContract.Bookmarks.URL)); + String title = cursor.getString( + cursor.getColumnIndexOrThrow(PartnerBookmarksContract.Bookmarks.TITLE)); + bookmarksCount++; + } + return bookmarksCount; + } + + @SmallTest + public void testExactlyOneRoot() throws android.os.RemoteException { + int totalBookmarks = countBookmarksInFolder(NO_FOLDER_FILTER); + if (totalBookmarks > 0) { + assertEquals("There must be at most one root", + countBookmarksInFolder( + PartnerBookmarksContract.Bookmarks.BOOKMARK_PARENT_ROOT_ID), + 1); + } + } + + @SmallTest + public void testRootMustBeNonEmptyIfPresent() throws android.os.RemoteException { + int totalBookmarks = countBookmarksInFolder(NO_FOLDER_FILTER); + if (totalBookmarks > 0) { + assertTrue("If the root exists, it must be non-empty", + countBookmarksInFolder(FIXED_ID_PARTNER_BOOKMARKS_ROOT) > 0); + } + } + + @SmallTest + public void testDefaultPBPSupportsOnlyFlatListOfBookmarks() + throws android.os.RemoteException { + int totalBookmarks = countBookmarksInFolder(NO_FOLDER_FILTER); + if (totalBookmarks > 0) { + assertEquals("Default PBP supports only flat list of bookmarks", + countBookmarksInFolder(FIXED_ID_PARTNER_BOOKMARKS_ROOT), + totalBookmarks - 1); + } + } +} -- cgit v1.2.3