1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.partnerbookmarks; 18 19 import android.content.ContentProvider; 20 import android.content.ContentUris; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.content.SharedPreferences; 24 import android.content.SharedPreferences.Editor; 25 import android.content.UriMatcher; 26 import android.content.res.Configuration; 27 import android.content.res.Resources; 28 import android.content.res.TypedArray; 29 import android.database.Cursor; 30 import android.database.DatabaseUtils; 31 import android.database.MatrixCursor; 32 import android.database.sqlite.SQLiteDatabase; 33 import android.database.sqlite.SQLiteOpenHelper; 34 import android.database.sqlite.SQLiteQueryBuilder; 35 import android.net.Uri; 36 import android.text.TextUtils; 37 import android.util.Log; 38 39 import java.io.ByteArrayOutputStream; 40 import java.io.File; 41 import java.io.IOException; 42 import java.io.InputStream; 43 import java.util.ArrayList; 44 import java.util.HashMap; 45 import java.util.Iterator; 46 import java.util.Map; 47 import java.util.Set; 48 49 /** 50 * Default partner bookmarks provider implementation of {@link PartnerBookmarksContract} API. 51 * It reads the flat list of bookmarks and the name of the root partner 52 * bookmarks folder using getResources() API. 53 * 54 * Sample resources structure: 55 * res/ 56 * values/ 57 * strings.xml 58 * string name="bookmarks_folder_name" 59 * string-array name="bookmarks" 60 * item TITLE1 61 * item URL1 62 * item TITLE2 63 * item URL2... 64 * bookmarks_icons.xml 65 * array name="bookmark_preloads" 66 * item @raw/favicon1 67 * item @raw/touchicon1 68 * item @raw/favicon2 69 * item @raw/touchicon2 70 * ... 71 */ 72 public class PartnerBookmarksProvider extends ContentProvider { 73 private static final String TAG = "PartnerBookmarksProvider"; 74 75 // URI matcher 76 private static final int URI_MATCH_BOOKMARKS = 1000; 77 private static final int URI_MATCH_BOOKMARKS_ID = 1001; 78 private static final int URI_MATCH_BOOKMARKS_FOLDER = 1002; 79 private static final int URI_MATCH_BOOKMARKS_FOLDER_ID = 1003; 80 private static final int URI_MATCH_BOOKMARKS_PARTNER_BOOKMARKS_FOLDER_ID = 1004; 81 82 private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 83 private static final Map<String, String> BOOKMARKS_PROJECTION_MAP 84 = new HashMap<String, String>(); 85 86 // Default sort order for unsync'd bookmarks 87 private static final String DEFAULT_BOOKMARKS_SORT_ORDER = 88 PartnerBookmarksContract.Bookmarks.ID + " DESC, " 89 + PartnerBookmarksContract.Bookmarks.ID + " ASC"; 90 91 // Initial bookmark id when for getResources() importing 92 // Make sure to fix tests if you are changing this 93 private static final long FIXED_ID_PARTNER_BOOKMARKS_ROOT = 94 PartnerBookmarksContract.Bookmarks.BOOKMARK_PARENT_ROOT_ID + 1; 95 96 // DB table name 97 private static final String TABLE_BOOKMARKS = "bookmarks"; 98 99 static { 100 final UriMatcher matcher = URI_MATCHER; 101 final String authority = PartnerBookmarksContract.AUTHORITY; matcher.addURI(authority, "bookmarks", URI_MATCH_BOOKMARKS)102 matcher.addURI(authority, "bookmarks", URI_MATCH_BOOKMARKS); matcher.addURI(authority, "bookmarks/#", URI_MATCH_BOOKMARKS_ID)103 matcher.addURI(authority, "bookmarks/#", URI_MATCH_BOOKMARKS_ID); matcher.addURI(authority, "bookmarks/folder", URI_MATCH_BOOKMARKS_FOLDER)104 matcher.addURI(authority, "bookmarks/folder", URI_MATCH_BOOKMARKS_FOLDER); matcher.addURI(authority, "bookmarks/folder/#", URI_MATCH_BOOKMARKS_FOLDER_ID)105 matcher.addURI(authority, "bookmarks/folder/#", URI_MATCH_BOOKMARKS_FOLDER_ID); matcher.addURI(authority, "bookmarks/folder/id", URI_MATCH_BOOKMARKS_PARTNER_BOOKMARKS_FOLDER_ID)106 matcher.addURI(authority, "bookmarks/folder/id", 107 URI_MATCH_BOOKMARKS_PARTNER_BOOKMARKS_FOLDER_ID); 108 // Projection maps 109 Map<String, String> map = BOOKMARKS_PROJECTION_MAP; map.put(PartnerBookmarksContract.Bookmarks.ID, PartnerBookmarksContract.Bookmarks.ID)110 map.put(PartnerBookmarksContract.Bookmarks.ID, 111 PartnerBookmarksContract.Bookmarks.ID); map.put(PartnerBookmarksContract.Bookmarks.TITLE, PartnerBookmarksContract.Bookmarks.TITLE)112 map.put(PartnerBookmarksContract.Bookmarks.TITLE, 113 PartnerBookmarksContract.Bookmarks.TITLE); map.put(PartnerBookmarksContract.Bookmarks.URL, PartnerBookmarksContract.Bookmarks.URL)114 map.put(PartnerBookmarksContract.Bookmarks.URL, 115 PartnerBookmarksContract.Bookmarks.URL); map.put(PartnerBookmarksContract.Bookmarks.TYPE, PartnerBookmarksContract.Bookmarks.TYPE)116 map.put(PartnerBookmarksContract.Bookmarks.TYPE, 117 PartnerBookmarksContract.Bookmarks.TYPE); map.put(PartnerBookmarksContract.Bookmarks.PARENT, PartnerBookmarksContract.Bookmarks.PARENT)118 map.put(PartnerBookmarksContract.Bookmarks.PARENT, 119 PartnerBookmarksContract.Bookmarks.PARENT); map.put(PartnerBookmarksContract.Bookmarks.FAVICON, PartnerBookmarksContract.Bookmarks.FAVICON)120 map.put(PartnerBookmarksContract.Bookmarks.FAVICON, 121 PartnerBookmarksContract.Bookmarks.FAVICON); map.put(PartnerBookmarksContract.Bookmarks.TOUCHICON, PartnerBookmarksContract.Bookmarks.TOUCHICON)122 map.put(PartnerBookmarksContract.Bookmarks.TOUCHICON, 123 PartnerBookmarksContract.Bookmarks.TOUCHICON); 124 } 125 126 private final class DatabaseHelper extends SQLiteOpenHelper { 127 private static final String DATABASE_FILENAME = "partnerBookmarks.db"; 128 private static final int DATABASE_VERSION = 1; 129 private static final String PREFERENCES_FILENAME = "pbppref"; 130 private static final String ACTIVE_CONFIGURATION_PREFNAME = "config"; 131 private final SharedPreferences sharedPreferences; 132 DatabaseHelper(Context context)133 public DatabaseHelper(Context context) { 134 super(context, DATABASE_FILENAME, null, DATABASE_VERSION); 135 sharedPreferences = context.getSharedPreferences( 136 PREFERENCES_FILENAME, Context.MODE_PRIVATE); 137 } 138 getConfigSignature(Configuration config)139 private String getConfigSignature(Configuration config) { 140 return "mmc=" + Integer.toString(config.mcc) 141 + "-mnc=" + Integer.toString(config.mnc) 142 + "-loc=" + config.locale.toString(); 143 } 144 prepareForConfiguration(Configuration config)145 public synchronized void prepareForConfiguration(Configuration config) { 146 final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 147 String newSignature = getConfigSignature(config); 148 String activeSignature = 149 sharedPreferences.getString(ACTIVE_CONFIGURATION_PREFNAME, null); 150 if (activeSignature == null || !activeSignature.equals(newSignature)) { 151 db.delete(TABLE_BOOKMARKS, null, null); 152 if (!createDefaultBookmarks(db)) { 153 // Failure to read/insert bookmarks should be treated as "no bookmarks" 154 db.delete(TABLE_BOOKMARKS, null, null); 155 } 156 } 157 } 158 setActiveConfiguration(Configuration config)159 private void setActiveConfiguration(Configuration config) { 160 Editor editor = sharedPreferences.edit(); 161 editor.putString(ACTIVE_CONFIGURATION_PREFNAME, getConfigSignature(config)); 162 editor.apply(); 163 } 164 createTable(SQLiteDatabase db)165 private void createTable(SQLiteDatabase db) { 166 db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" + 167 PartnerBookmarksContract.Bookmarks.ID + 168 " INTEGER NOT NULL DEFAULT 0," + 169 PartnerBookmarksContract.Bookmarks.TITLE + 170 " TEXT," + 171 PartnerBookmarksContract.Bookmarks.URL + 172 " TEXT," + 173 PartnerBookmarksContract.Bookmarks.TYPE + 174 " INTEGER NOT NULL DEFAULT 0," + 175 PartnerBookmarksContract.Bookmarks.PARENT + 176 " INTEGER," + 177 PartnerBookmarksContract.Bookmarks.FAVICON + 178 " BLOB," + 179 PartnerBookmarksContract.Bookmarks.TOUCHICON + 180 " BLOB" + ");"); 181 } 182 dropTable(SQLiteDatabase db)183 private void dropTable(SQLiteDatabase db) { 184 db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS); 185 } 186 187 @Override onCreate(SQLiteDatabase db)188 public void onCreate(SQLiteDatabase db) { 189 synchronized (this) { 190 createTable(db); 191 if (!createDefaultBookmarks(db)) { 192 // Failure to read/insert bookmarks should be treated as "no bookmarks" 193 dropTable(db); 194 createTable(db); 195 } 196 } 197 } 198 199 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)200 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 201 dropTable(db); 202 onCreate(db); 203 } 204 205 @Override onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)206 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 207 dropTable(db); 208 onCreate(db); 209 } 210 createDefaultBookmarks(SQLiteDatabase db)211 private boolean createDefaultBookmarks(SQLiteDatabase db) { 212 Resources res = getContext().getResources(); 213 try { 214 CharSequence bookmarksFolderName = res.getText(R.string.bookmarks_folder_name); 215 final CharSequence[] bookmarks = res.getTextArray(R.array.bookmarks); 216 if (bookmarks.length >= 1) { 217 if (bookmarksFolderName.length() < 1) { 218 Log.i(TAG, "bookmarks_folder_name was not specified; bailing out"); 219 return false; 220 } 221 if (!addRootFolder(db, 222 FIXED_ID_PARTNER_BOOKMARKS_ROOT, bookmarksFolderName.toString())) { 223 Log.i(TAG, "failed to insert root folder; bailing out"); 224 return false; 225 } 226 if (!addDefaultBookmarks(db, 227 FIXED_ID_PARTNER_BOOKMARKS_ROOT, FIXED_ID_PARTNER_BOOKMARKS_ROOT + 1)) { 228 Log.i(TAG, "failed to insert bookmarks; bailing out"); 229 return false; 230 } 231 } 232 setActiveConfiguration(res.getConfiguration()); 233 } catch (android.content.res.Resources.NotFoundException e) { 234 Log.i(TAG, "failed to fetch resources; bailing out"); 235 return false; 236 } 237 return true; 238 } 239 addRootFolder(SQLiteDatabase db, long id, String bookmarksFolderName)240 private boolean addRootFolder(SQLiteDatabase db, long id, String bookmarksFolderName) { 241 ContentValues values = new ContentValues(); 242 values.put(PartnerBookmarksContract.Bookmarks.ID, id); 243 values.put(PartnerBookmarksContract.Bookmarks.TITLE, 244 bookmarksFolderName); 245 values.put(PartnerBookmarksContract.Bookmarks.PARENT, 246 PartnerBookmarksContract.Bookmarks.BOOKMARK_PARENT_ROOT_ID); 247 values.put(PartnerBookmarksContract.Bookmarks.TYPE, 248 PartnerBookmarksContract.Bookmarks.BOOKMARK_TYPE_FOLDER); 249 return db.insertOrThrow(TABLE_BOOKMARKS, null, values) != -1; 250 } 251 addDefaultBookmarks(SQLiteDatabase db, long parentId, long firstBookmarkId)252 private boolean addDefaultBookmarks(SQLiteDatabase db, long parentId, long firstBookmarkId) { 253 long bookmarkId = firstBookmarkId; 254 Resources res = getContext().getResources(); 255 final CharSequence[] bookmarks = res.getTextArray(R.array.bookmarks); 256 int size = bookmarks.length; 257 TypedArray preloads = res.obtainTypedArray(R.array.bookmark_preloads); 258 DatabaseUtils.InsertHelper insertHelper = null; 259 try { 260 insertHelper = new DatabaseUtils.InsertHelper(db, TABLE_BOOKMARKS); 261 final int idColumn = insertHelper.getColumnIndex( 262 PartnerBookmarksContract.Bookmarks.ID); 263 final int titleColumn = insertHelper.getColumnIndex( 264 PartnerBookmarksContract.Bookmarks.TITLE); 265 final int urlColumn = insertHelper.getColumnIndex( 266 PartnerBookmarksContract.Bookmarks.URL); 267 final int typeColumn = insertHelper.getColumnIndex( 268 PartnerBookmarksContract.Bookmarks.TYPE); 269 final int parentColumn = insertHelper.getColumnIndex( 270 PartnerBookmarksContract.Bookmarks.PARENT); 271 final int faviconColumn = insertHelper.getColumnIndex( 272 PartnerBookmarksContract.Bookmarks.FAVICON); 273 final int touchiconColumn = insertHelper.getColumnIndex( 274 PartnerBookmarksContract.Bookmarks.TOUCHICON); 275 276 for (int i = 0; i + 1 < size; i = i + 2) { 277 CharSequence bookmarkDestination = bookmarks[i + 1]; 278 279 String bookmarkTitle = bookmarks[i].toString(); 280 String bookmarkUrl = bookmarkDestination.toString(); 281 byte[] favicon = null; 282 if (i < preloads.length()) { 283 int faviconId = preloads.getResourceId(i, 0); 284 try { 285 favicon = readRaw(res, faviconId); 286 } catch (IOException e) { 287 Log.i(TAG, "Failed to read favicon for " + bookmarkTitle, e); 288 } 289 } 290 byte[] touchicon = null; 291 if (i + 1 < preloads.length()) { 292 int touchiconId = preloads.getResourceId(i + 1, 0); 293 try { 294 touchicon = readRaw(res, touchiconId); 295 } catch (IOException e) { 296 Log.i(TAG, "Failed to read touchicon for " + bookmarkTitle, e); 297 } 298 } 299 insertHelper.prepareForInsert(); 300 insertHelper.bind(idColumn, bookmarkId); 301 insertHelper.bind(titleColumn, bookmarkTitle); 302 insertHelper.bind(urlColumn, bookmarkUrl); 303 insertHelper.bind(typeColumn, 304 PartnerBookmarksContract.Bookmarks.BOOKMARK_TYPE_BOOKMARK); 305 insertHelper.bind(parentColumn, parentId); 306 if (favicon != null) { 307 insertHelper.bind(faviconColumn, favicon); 308 } 309 if (touchicon != null) { 310 insertHelper.bind(touchiconColumn, touchicon); 311 } 312 bookmarkId++; 313 if (insertHelper.execute() == -1) { 314 Log.i(TAG, "Failed to insert bookmark " + bookmarkTitle); 315 return false; 316 } 317 } 318 } finally { 319 preloads.recycle(); 320 insertHelper.close(); 321 } 322 return true; 323 } 324 readRaw(Resources res, int id)325 private byte[] readRaw(Resources res, int id) throws IOException { 326 if (id == 0) return null; 327 InputStream is = res.openRawResource(id); 328 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 329 try { 330 byte[] buf = new byte[4096]; 331 int read; 332 while ((read = is.read(buf)) > 0) { 333 bos.write(buf, 0, read); 334 } 335 bos.flush(); 336 return bos.toByteArray(); 337 } finally { 338 is.close(); 339 bos.close(); 340 } 341 } 342 } 343 344 private DatabaseHelper mOpenHelper; 345 346 @Override onCreate()347 public boolean onCreate() { 348 mOpenHelper = new DatabaseHelper(getContext()); 349 return true; 350 } 351 352 @Override onConfigurationChanged(Configuration newConfig)353 public void onConfigurationChanged(Configuration newConfig) { 354 mOpenHelper.prepareForConfiguration(getContext().getResources().getConfiguration()); 355 } 356 357 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)358 public Cursor query(Uri uri, String[] projection, 359 String selection, String[] selectionArgs, String sortOrder) { 360 final int match = URI_MATCHER.match(uri); 361 mOpenHelper.prepareForConfiguration(getContext().getResources().getConfiguration()); 362 final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 363 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 364 String limit = uri.getQueryParameter(PartnerBookmarksContract.PARAM_LIMIT); 365 String groupBy = uri.getQueryParameter(PartnerBookmarksContract.PARAM_GROUP_BY); 366 switch (match) { 367 case URI_MATCH_BOOKMARKS_FOLDER_ID: 368 case URI_MATCH_BOOKMARKS_ID: 369 case URI_MATCH_BOOKMARKS: { 370 if (match == URI_MATCH_BOOKMARKS_ID) { 371 // Tack on the ID of the specific bookmark requested 372 selection = DatabaseUtils.concatenateWhere(selection, 373 TABLE_BOOKMARKS + "." + 374 PartnerBookmarksContract.Bookmarks.ID + "=?"); 375 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 376 new String[] { Long.toString(ContentUris.parseId(uri)) }); 377 } else if (match == URI_MATCH_BOOKMARKS_FOLDER_ID) { 378 // Tack on the ID of the specific folder requested 379 selection = DatabaseUtils.concatenateWhere(selection, 380 TABLE_BOOKMARKS + "." + 381 PartnerBookmarksContract.Bookmarks.PARENT + "=?"); 382 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 383 new String[] { Long.toString(ContentUris.parseId(uri)) }); 384 } 385 // Set a default sort order if one isn't specified 386 if (TextUtils.isEmpty(sortOrder)) { 387 sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER; 388 } 389 qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP); 390 qb.setTables(TABLE_BOOKMARKS); 391 break; 392 } 393 394 case URI_MATCH_BOOKMARKS_FOLDER: { 395 qb.setTables(TABLE_BOOKMARKS); 396 String[] args; 397 String query; 398 // Set a default sort order if one isn't specified 399 if (TextUtils.isEmpty(sortOrder)) { 400 sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER; 401 } 402 qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP); 403 String where = PartnerBookmarksContract.Bookmarks.PARENT + "=?"; 404 where = DatabaseUtils.concatenateWhere(where, selection); 405 args = new String[] { Long.toString(FIXED_ID_PARTNER_BOOKMARKS_ROOT) }; 406 if (selectionArgs != null) { 407 args = DatabaseUtils.appendSelectionArgs(args, selectionArgs); 408 } 409 query = qb.buildQuery(projection, where, null, null, sortOrder, null); 410 Cursor cursor = db.rawQuery(query, args); 411 return cursor; 412 } 413 414 case URI_MATCH_BOOKMARKS_PARTNER_BOOKMARKS_FOLDER_ID: { 415 MatrixCursor c = new MatrixCursor( 416 new String[] {PartnerBookmarksContract.Bookmarks.ID}); 417 c.newRow().add(FIXED_ID_PARTNER_BOOKMARKS_ROOT); 418 return c; 419 } 420 421 default: { 422 throw new UnsupportedOperationException("Unknown URL " + uri.toString()); 423 } 424 } 425 426 return qb.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder, limit); 427 } 428 429 @Override getType(Uri uri)430 public String getType(Uri uri) { 431 final int match = URI_MATCHER.match(uri); 432 if (match == UriMatcher.NO_MATCH) return null; 433 return PartnerBookmarksContract.Bookmarks.CONTENT_ITEM_TYPE; 434 } 435 436 @Override insert(Uri uri, ContentValues values)437 public Uri insert(Uri uri, ContentValues values) { 438 throw new UnsupportedOperationException(); 439 } 440 441 @Override delete(Uri uri, String selection, String[] selectionArgs)442 public int delete(Uri uri, String selection, String[] selectionArgs) { 443 throw new UnsupportedOperationException(); 444 } 445 446 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)447 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 448 throw new UnsupportedOperationException(); 449 } 450 } 451