summaryrefslogtreecommitdiffstats
path: root/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsProvider.java
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsProvider.java')
-rw-r--r--packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsProvider.java372
1 files changed, 372 insertions, 0 deletions
diff --git a/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsProvider.java b/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsProvider.java
new file mode 100644
index 0000000..3ae04c7
--- /dev/null
+++ b/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsProvider.java
@@ -0,0 +1,372 @@
+/*
+** Copyright 2006, 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.subscribedfeeds;
+
+import android.content.UriMatcher;
+import android.content.*;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.provider.SubscribedFeeds;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * Manages a list of feeds for which this client is interested in receiving
+ * change notifications.
+ */
+public class SubscribedFeedsProvider extends SyncableContentProvider {
+ private static final String TAG = "SubscribedFeedsProvider";
+ private static final String DATABASE_NAME = "subscribedfeeds.db";
+ private static final int DATABASE_VERSION = 10;
+
+ private static final int FEEDS = 1;
+ private static final int FEED_ID = 2;
+ private static final int DELETED_FEEDS = 3;
+ private static final int ACCOUNTS = 4;
+
+ private static final Map<String, String> ACCOUNTS_PROJECTION_MAP;
+
+ private static final UriMatcher sURLMatcher =
+ new UriMatcher(UriMatcher.NO_MATCH);
+
+ private static String sFeedsTable = "feeds";
+ private static Uri sFeedsUrl =
+ Uri.parse("content://subscribedfeeds/feeds/");
+ private static String sDeletedFeedsTable = "_deleted_feeds";
+ private static Uri sDeletedFeedsUrl =
+ Uri.parse("content://subscribedfeeds/deleted_feeds/");
+
+ public SubscribedFeedsProvider() {
+ super(DATABASE_NAME, DATABASE_VERSION, sFeedsUrl);
+ }
+
+ static {
+ sURLMatcher.addURI("subscribedfeeds", "feeds", FEEDS);
+ sURLMatcher.addURI("subscribedfeeds", "feeds/#", FEED_ID);
+ sURLMatcher.addURI("subscribedfeeds", "deleted_feeds", DELETED_FEEDS);
+ sURLMatcher.addURI("subscribedfeeds", "accounts", ACCOUNTS);
+ }
+
+ @Override
+ protected boolean upgradeDatabase(SQLiteDatabase db,
+ int oldVersion, int newVersion) {
+ Log.w(TAG, "Upgrading database from version " + oldVersion +
+ " to " + newVersion +
+ ", which will destroy all old data");
+ db.execSQL("DROP TRIGGER IF EXISTS feed_cleanup");
+ db.execSQL("DROP TABLE IF EXISTS _deleted_feeds");
+ db.execSQL("DROP TABLE IF EXISTS feeds");
+ bootstrapDatabase(db);
+ return false; // this was lossy
+ }
+
+ @Override
+ protected void bootstrapDatabase(SQLiteDatabase db) {
+ super.bootstrapDatabase(db);
+ db.execSQL("CREATE TABLE feeds (" +
+ "_id INTEGER PRIMARY KEY," +
+ "_sync_account TEXT," + // From the sync source
+ "_sync_id TEXT," + // From the sync source
+ "_sync_time TEXT," + // From the sync source
+ "_sync_version TEXT," + // From the sync source
+ "_sync_local_id INTEGER," + // Used while syncing,
+ // never stored persistently
+ "_sync_dirty INTEGER," + // if syncable, set if the record
+ // has local, unsynced, changes
+ "_sync_mark INTEGER," + // Used to filter out new rows
+ "feed TEXT," +
+ "authority TEXT," +
+ "service TEXT" +
+ ");");
+
+ // Trigger to completely remove feeds data when they're deleted
+ db.execSQL("CREATE TRIGGER feed_cleanup DELETE ON feeds " +
+ "WHEN old._sync_id is not null " +
+ "BEGIN " +
+ "INSERT INTO _deleted_feeds " +
+ "(_sync_id, _sync_account, _sync_version) " +
+ "VALUES (old._sync_id, old._sync_account, " +
+ "old._sync_version);" +
+ "END");
+
+ db.execSQL("CREATE TABLE _deleted_feeds (" +
+ "_sync_version TEXT," + // From the sync source
+ "_sync_id TEXT," +
+ "_sync_account TEXT," +
+ "_sync_mark INTEGER, " + // Used to filter out new rows
+ "UNIQUE(_sync_id))");
+ }
+
+ @Override
+ protected void onDatabaseOpened(SQLiteDatabase db) {
+ db.markTableSyncable("feeds", "_deleted_feeds");
+ }
+
+ @Override
+ protected Iterable<FeedMerger> getMergers() {
+ return Collections.singletonList(new FeedMerger());
+ }
+
+ @Override
+ public String getType(Uri url) {
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case FEEDS:
+ return SubscribedFeeds.Feeds.CONTENT_TYPE;
+ case FEED_ID:
+ return SubscribedFeeds.Feeds.CONTENT_ITEM_TYPE;
+ default:
+ throw new IllegalArgumentException("Unknown URL");
+ }
+ }
+
+ @Override
+ public Cursor queryInternal(Uri url, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+
+
+ // Generate the body of the query
+ int match = sURLMatcher.match(url);
+
+ if (Config.LOGV) Log.v(TAG, "SubscribedFeedsProvider.query: url=" +
+ url + ", match is " + match);
+
+ switch (match) {
+ case FEEDS:
+ qb.setTables(sFeedsTable);
+ break;
+ case DELETED_FEEDS:
+ if (!isTemporary()) {
+ throw new UnsupportedOperationException();
+ }
+ qb.setTables(sDeletedFeedsTable);
+ break;
+ case ACCOUNTS:
+ qb.setTables(sFeedsTable);
+ qb.setDistinct(true);
+ qb.setProjectionMap(ACCOUNTS_PROJECTION_MAP);
+ return qb.query(getDatabase(), projection, selection, selectionArgs,
+ SubscribedFeeds.Feeds._SYNC_ACCOUNT, null, sortOrder);
+ case FEED_ID:
+ qb.setTables(sFeedsTable);
+ qb.appendWhere(sFeedsTable + "._id=");
+ qb.appendWhere(url.getPathSegments().get(1));
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+
+ // run the query
+ return qb.query(getDatabase(), projection, selection, selectionArgs,
+ null, null, sortOrder);
+ }
+
+ @Override
+ public Uri insertInternal(Uri url, ContentValues initialValues) {
+ final SQLiteDatabase db = getDatabase();
+ Uri resultUri = null;
+ long rowID;
+
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case FEEDS:
+ ContentValues values = new ContentValues(initialValues);
+ values.put(SubscribedFeeds.Feeds._SYNC_DIRTY, 1);
+ rowID = db.insert(sFeedsTable, "feed", values);
+ if (rowID > 0) {
+ resultUri = Uri.parse(
+ "content://subscribedfeeds/feeds/" + rowID);
+ }
+ break;
+
+ case DELETED_FEEDS:
+ if (!isTemporary()) {
+ throw new UnsupportedOperationException();
+ }
+ rowID = db.insert(sDeletedFeedsTable, "_sync_id",
+ initialValues);
+ if (rowID > 0) {
+ resultUri = Uri.parse(
+ "content://subscribedfeeds/deleted_feeds/" + rowID);
+ }
+ break;
+
+ default:
+ throw new UnsupportedOperationException(
+ "Cannot insert into URL: " + url);
+ }
+
+ return resultUri;
+ }
+
+ @Override
+ public int deleteInternal(Uri url, String userWhere, String[] whereArgs) {
+ final SQLiteDatabase db = getDatabase();
+ String changedItemId;
+
+ switch (sURLMatcher.match(url)) {
+ case FEEDS:
+ changedItemId = null;
+ break;
+ case FEED_ID:
+ changedItemId = url.getPathSegments().get(1);
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ "Cannot delete that URL: " + url);
+ }
+
+ String where = addIdToWhereClause(changedItemId, userWhere);
+ return db.delete(sFeedsTable, where, whereArgs);
+ }
+
+ @Override
+ public int updateInternal(Uri url, ContentValues initialValues,
+ String userWhere, String[] whereArgs) {
+ final SQLiteDatabase db = getDatabase();
+ ContentValues values = new ContentValues(initialValues);
+ values.put(SubscribedFeeds.Feeds._SYNC_DIRTY, 1);
+
+ String changedItemId;
+ switch (sURLMatcher.match(url)) {
+ case FEEDS:
+ changedItemId = null;
+ break;
+
+ case FEED_ID:
+ changedItemId = url.getPathSegments().get(1);
+ break;
+
+ default:
+ throw new UnsupportedOperationException(
+ "Cannot update URL: " + url);
+ }
+
+ String where = addIdToWhereClause(changedItemId, userWhere);
+ return db.update(sFeedsTable, values, where, whereArgs);
+ }
+
+ private static String addIdToWhereClause(String id, String where) {
+ if (id != null) {
+ StringBuilder whereSb = new StringBuilder("_id=");
+ whereSb.append(id);
+ if (!TextUtils.isEmpty(where)) {
+ whereSb.append(" AND (");
+ whereSb.append(where);
+ whereSb.append(')');
+ }
+ return whereSb.toString();
+ } else {
+ return where;
+ }
+ }
+
+ private class FeedMerger extends AbstractTableMerger {
+ private ContentValues mValues = new ContentValues();
+ FeedMerger() {
+ super(getDatabase(), sFeedsTable, sFeedsUrl, sDeletedFeedsTable, sDeletedFeedsUrl);
+ }
+
+ @Override
+ protected void notifyChanges() {
+ getContext().getContentResolver().notifyChange(
+ sFeedsUrl, null /* data change observer */,
+ false /* do not sync to network */);
+ }
+
+ @Override
+ public void insertRow(ContentProvider diffs, Cursor diffsCursor) {
+ final SQLiteDatabase db = getDatabase();
+ // We don't ever want to add entries from the server, instead
+ // we want to tell the server to delete any entries we receive
+ // from the server that aren't already known by the client.
+ mValues.clear();
+ DatabaseUtils.cursorStringToContentValues(diffsCursor,
+ SubscribedFeeds.Feeds._SYNC_ID, mValues);
+ DatabaseUtils.cursorStringToContentValues(diffsCursor,
+ SubscribedFeeds.Feeds._SYNC_ACCOUNT, mValues);
+ DatabaseUtils.cursorStringToContentValues(diffsCursor,
+ SubscribedFeeds.Feeds._SYNC_VERSION, mValues);
+ db.replace(mDeletedTable, SubscribedFeeds.Feeds._SYNC_ID, mValues);
+ }
+
+ @Override
+ public void updateRow(long localPersonID, ContentProvider diffs,
+ Cursor diffsCursor) {
+ updateOrResolveRow(localPersonID, null, diffs, diffsCursor, false);
+ }
+
+ @Override
+ public void resolveRow(long localPersonID, String syncID,
+ ContentProvider diffs, Cursor diffsCursor) {
+ updateOrResolveRow(localPersonID, syncID, diffs, diffsCursor, true);
+ }
+
+ protected void updateOrResolveRow(long localPersonID, String syncID,
+ ContentProvider diffs, Cursor diffsCursor, boolean conflicts) {
+ mValues.clear();
+ // only copy over the fields that the server owns
+ DatabaseUtils.cursorStringToContentValues(diffsCursor,
+ SubscribedFeeds.Feeds._SYNC_ID, mValues);
+ DatabaseUtils.cursorStringToContentValues(diffsCursor,
+ SubscribedFeeds.Feeds._SYNC_TIME, mValues);
+ DatabaseUtils.cursorStringToContentValues(diffsCursor,
+ SubscribedFeeds.Feeds._SYNC_VERSION, mValues);
+ mValues.put(SubscribedFeeds.Feeds._SYNC_DIRTY, conflicts ? 1 : 0);
+ final SQLiteDatabase db = getDatabase();
+ db.update(mTable, mValues,
+ SubscribedFeeds.Feeds._ID + '=' + localPersonID, null);
+ }
+
+ @Override
+ public void deleteRow(Cursor localCursor) {
+ // Since the client is the authority we don't actually delete
+ // the row when the server says it has been deleted. Instead
+ // we break the association with the server by clearing out
+ // the id, time, and version, then we mark it dirty so that
+ // it will be synced back to the server.
+ long localPersonId = localCursor.getLong(localCursor.getColumnIndex(
+ SubscribedFeeds.Feeds._ID));
+ mValues.clear();
+ mValues.put(SubscribedFeeds.Feeds._SYNC_DIRTY, 1);
+ mValues.put(SubscribedFeeds.Feeds._SYNC_ID, (String) null);
+ mValues.put(SubscribedFeeds.Feeds._SYNC_TIME, (Long) null);
+ mValues.put(SubscribedFeeds.Feeds._SYNC_VERSION, (String) null);
+ final SQLiteDatabase db = getDatabase();
+ db.update(mTable, mValues, SubscribedFeeds.Feeds._ID + '=' + localPersonId, null);
+ localCursor.moveToNext();
+ }
+ }
+
+ static {
+ Map<String, String> map;
+
+ map = new HashMap<String, String>();
+ ACCOUNTS_PROJECTION_MAP = map;
+ map.put(SubscribedFeeds.Accounts._COUNT, "COUNT(*) AS _count");
+ map.put(SubscribedFeeds.Accounts._SYNC_ACCOUNT, SubscribedFeeds.Accounts._SYNC_ACCOUNT);
+ }
+}