blob: 86eb6277050a21852447bf62dae1549fa00cd2e4 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.example.partnercustomizations;
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.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* 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 PartnerBookmarksProviderExample extends ContentProvider {
private static final String TAG = "PartnerBookmarksProviderExample";
// 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<String, String> BOOKMARKS_PROJECTION_MAP =
new HashMap<String, String>();
// 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<String, String> 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 mSharedPreferences;
public DatabaseHelper(Context context) {
super(context, DATABASE_FILENAME, null, DATABASE_VERSION);
mSharedPreferences = 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 =
mSharedPreferences.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 = mSharedPreferences.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();
}
/**
* <p>
* The contract between the partner bookmarks provider and applications.
* Contains the definition for the supported URIs and columns.
* </p>
* <p>
* Authority URI: content://com.android.partnerbookmarks
* </p>
* <p>
* Partner bookmarks URI: content://com.android.partnerbookmarks/bookmarks
* </p>
* <p>
* 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.
* </p>
*/
public static 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.
* <p>Type: INTEGER</p>
* <p>Allowed values are:</p>
* <p>
* <ul>
* <li>{@link #BOOKMARK_TYPE_BOOKMARK}</li>
* <li>{@link #BOOKMARK_TYPE_FOLDER}</li>
* </ul>
* </p>
*/
public static final String TYPE = "type";
/**
* The unique ID for a row. Cannot be BOOKMARK_PARENT_ROOT_ID.
* <p>Type: INTEGER (long)</p>
*/
public static final String ID = "_id";
/**
* This column is valid when the row is not a folder.
* <p>Type: TEXT (URL)</p>
*/
public static final String URL = "url";
/**
* The user visible title.
* <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";
/**
* The touch icon for the web page, may be NULL.
* Must decode via {@link BitmapFactory#decodeByteArray}.
* <p>Type: BLOB (image)</p>
*/
public static final String TOUCHICON = "touchicon";
/**
* The ID of the parent folder. BOOKMARK_PARENT_ROOT_ID is the root folder.
* <p>Type: INTEGER (long) (reference to item in the same table)</p>
*/
public static final String PARENT = "parent";
}
}
}