summaryrefslogtreecommitdiffstats
path: root/src/com/android/providers/partnerbookmarks/PartnerBookmarksProvider.java
blob: 2ad371e1ae9390c0057e02219905a8253043cb73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
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<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 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();
    }
}