1a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert/* 2a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * Copyright (C) 2007 The Android Open Source Project 3a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * 4a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * Licensed under the Apache License, Version 2.0 (the "License"); 5a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * you may not use this file except in compliance with the License. 6a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * You may obtain a copy of the License at 7a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * 8a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * http://www.apache.org/licenses/LICENSE-2.0 9a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * 10a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * Unless required by applicable law or agreed to in writing, software 11a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * distributed under the License is distributed on an "AS IS" BASIS, 12a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * See the License for the specific language governing permissions and 14a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * limitations under the License. 15a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert */ 16a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 17a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringertpackage com.android.common.content; 18a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 19a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringertimport android.accounts.Account; 20a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringertimport android.content.ContentValues; 21a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringertimport android.database.Cursor; 22a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringertimport android.database.DatabaseUtils; 23a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringertimport android.database.sqlite.SQLiteDatabase; 24a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringertimport android.provider.SyncStateContract; 25a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 26a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert/** 27a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * Extends the schema of a ContentProvider to include the _sync_state table 28a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * and implements query/insert/update/delete to access that table using the 29a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * authority "syncstate". This can be used to store the sync state for a 30a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * set of accounts. 31a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert */ 32a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringertpublic class SyncStateContentProviderHelper { 33a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert private static final String SELECT_BY_ACCOUNT = 34a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert SyncStateContract.Columns.ACCOUNT_NAME + "=? AND " 35a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert + SyncStateContract.Columns.ACCOUNT_TYPE + "=?"; 36a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 37a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert private static final String SYNC_STATE_TABLE = "_sync_state"; 38a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert private static final String SYNC_STATE_META_TABLE = "_sync_state_metadata"; 39a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert private static final String SYNC_STATE_META_VERSION_COLUMN = "version"; 40a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 41a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert private static long DB_VERSION = 1; 42a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 43a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert private static final String[] ACCOUNT_PROJECTION = 44a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert new String[]{SyncStateContract.Columns.ACCOUNT_NAME, 45a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert SyncStateContract.Columns.ACCOUNT_TYPE}; 46a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 47a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert public static final String PATH = "syncstate"; 48a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 49a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert private static final String QUERY_COUNT_SYNC_STATE_ROWS = 50a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert "SELECT count(*)" 51a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert + " FROM " + SYNC_STATE_TABLE 52a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert + " WHERE " + SyncStateContract.Columns._ID + "=?"; 53a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 54a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert public void createDatabase(SQLiteDatabase db) { 55a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert db.execSQL("DROP TABLE IF EXISTS " + SYNC_STATE_TABLE); 56a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert db.execSQL("CREATE TABLE " + SYNC_STATE_TABLE + " (" 57a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert + SyncStateContract.Columns._ID + " INTEGER PRIMARY KEY," 58a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert + SyncStateContract.Columns.ACCOUNT_NAME + " TEXT NOT NULL," 59a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert + SyncStateContract.Columns.ACCOUNT_TYPE + " TEXT NOT NULL," 60a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert + SyncStateContract.Columns.DATA + " TEXT," 61a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert + "UNIQUE(" + SyncStateContract.Columns.ACCOUNT_NAME + ", " 62a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert + SyncStateContract.Columns.ACCOUNT_TYPE + "));"); 63a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 64a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert db.execSQL("DROP TABLE IF EXISTS " + SYNC_STATE_META_TABLE); 65a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert db.execSQL("CREATE TABLE " + SYNC_STATE_META_TABLE + " (" 66a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert + SYNC_STATE_META_VERSION_COLUMN + " INTEGER);"); 67a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert ContentValues values = new ContentValues(); 68a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert values.put(SYNC_STATE_META_VERSION_COLUMN, DB_VERSION); 69a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert db.insert(SYNC_STATE_META_TABLE, SYNC_STATE_META_VERSION_COLUMN, values); 70a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 71a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 72a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert public void onDatabaseOpened(SQLiteDatabase db) { 73a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert long version = DatabaseUtils.longForQuery(db, 74a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert "SELECT " + SYNC_STATE_META_VERSION_COLUMN + " FROM " + SYNC_STATE_META_TABLE, 75a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert null); 76a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert if (version != DB_VERSION) { 77a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert createDatabase(db); 78a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 79a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 80a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 81a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert public Cursor query(SQLiteDatabase db, String[] projection, 82a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert String selection, String[] selectionArgs, String sortOrder) { 83a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert return db.query(SYNC_STATE_TABLE, projection, selection, selectionArgs, 84a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert null, null, sortOrder); 85a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 86a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 87a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert public long insert(SQLiteDatabase db, ContentValues values) { 88a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert return db.replace(SYNC_STATE_TABLE, SyncStateContract.Columns.ACCOUNT_NAME, values); 89a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 90a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 91a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert public int delete(SQLiteDatabase db, String userWhere, String[] whereArgs) { 92a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert return db.delete(SYNC_STATE_TABLE, userWhere, whereArgs); 93a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 94a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 95a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert public int update(SQLiteDatabase db, ContentValues values, 96a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert String selection, String[] selectionArgs) { 97a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert return db.update(SYNC_STATE_TABLE, values, selection, selectionArgs); 98a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 99a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 100a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert public int update(SQLiteDatabase db, long rowId, Object data) { 101a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert if (DatabaseUtils.longForQuery(db, QUERY_COUNT_SYNC_STATE_ROWS, 102a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert new String[]{Long.toString(rowId)}) < 1) { 103a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert return 0; 104a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 105a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert db.execSQL("UPDATE " + SYNC_STATE_TABLE 106a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert + " SET " + SyncStateContract.Columns.DATA + "=?" 107a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert + " WHERE " + SyncStateContract.Columns._ID + "=" + rowId, 108a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert new Object[]{data}); 109a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert // assume a row was modified since we know it exists 110a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert return 1; 111a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 112a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 113a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert public void onAccountsChanged(SQLiteDatabase db, Account[] accounts) { 114a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert Cursor c = db.query(SYNC_STATE_TABLE, ACCOUNT_PROJECTION, null, null, null, null, null); 115a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert try { 116a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert while (c.moveToNext()) { 117a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert final String accountName = c.getString(0); 118a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert final String accountType = c.getString(1); 119a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert Account account = new Account(accountName, accountType); 120a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert if (!contains(accounts, account)) { 121a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert db.delete(SYNC_STATE_TABLE, SELECT_BY_ACCOUNT, 122a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert new String[]{accountName, accountType}); 123a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 124a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 125a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } finally { 126a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert c.close(); 127a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 128a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 129a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert 130a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert /** 131a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * Checks that value is present as at least one of the elements of the array. 132a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * @param array the array to check in 133a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * @param value the value to check for 134a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert * @return true if the value is present in the array 135a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert */ 136a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert private static <T> boolean contains(T[] array, T value) { 137a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert for (T element : array) { 138a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert if (element == null) { 139a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert if (value == null) return true; 140a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } else { 141a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert if (value != null && element.equals(value)) return true; 142a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 143a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 144a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert return false; 145a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert } 146a6e619351f9fc23f0e6ec486b8de6138e4a08f4cBjorn Bringert}