1 /* 2 * Copyright (C) 2007 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.common.content; 18 19 import android.accounts.Account; 20 import android.content.ContentValues; 21 import android.database.Cursor; 22 import android.database.DatabaseUtils; 23 import android.database.sqlite.SQLiteDatabase; 24 import android.provider.SyncStateContract; 25 26 /** 27 * Extends the schema of a ContentProvider to include the _sync_state table 28 * and implements query/insert/update/delete to access that table using the 29 * authority "syncstate". This can be used to store the sync state for a 30 * set of accounts. 31 */ 32 public class SyncStateContentProviderHelper { 33 private static final String SELECT_BY_ACCOUNT = 34 SyncStateContract.Columns.ACCOUNT_NAME + "=? AND " 35 + SyncStateContract.Columns.ACCOUNT_TYPE + "=?"; 36 37 private static final String SYNC_STATE_TABLE = "_sync_state"; 38 private static final String SYNC_STATE_META_TABLE = "_sync_state_metadata"; 39 private static final String SYNC_STATE_META_VERSION_COLUMN = "version"; 40 41 private static long DB_VERSION = 1; 42 43 private static final String[] ACCOUNT_PROJECTION = 44 new String[]{SyncStateContract.Columns.ACCOUNT_NAME, 45 SyncStateContract.Columns.ACCOUNT_TYPE}; 46 47 public static final String PATH = "syncstate"; 48 49 private static final String QUERY_COUNT_SYNC_STATE_ROWS = 50 "SELECT count(*)" 51 + " FROM " + SYNC_STATE_TABLE 52 + " WHERE " + SyncStateContract.Columns._ID + "=?"; 53 54 public void createDatabase(SQLiteDatabase db) { 55 db.execSQL("DROP TABLE IF EXISTS " + SYNC_STATE_TABLE); 56 db.execSQL("CREATE TABLE " + SYNC_STATE_TABLE + " (" 57 + SyncStateContract.Columns._ID + " INTEGER PRIMARY KEY," 58 + SyncStateContract.Columns.ACCOUNT_NAME + " TEXT NOT NULL," 59 + SyncStateContract.Columns.ACCOUNT_TYPE + " TEXT NOT NULL," 60 + SyncStateContract.Columns.DATA + " TEXT," 61 + "UNIQUE(" + SyncStateContract.Columns.ACCOUNT_NAME + ", " 62 + SyncStateContract.Columns.ACCOUNT_TYPE + "));"); 63 64 db.execSQL("DROP TABLE IF EXISTS " + SYNC_STATE_META_TABLE); 65 db.execSQL("CREATE TABLE " + SYNC_STATE_META_TABLE + " (" 66 + SYNC_STATE_META_VERSION_COLUMN + " INTEGER);"); 67 ContentValues values = new ContentValues(); 68 values.put(SYNC_STATE_META_VERSION_COLUMN, DB_VERSION); 69 db.insert(SYNC_STATE_META_TABLE, SYNC_STATE_META_VERSION_COLUMN, values); 70 } 71 72 public void onDatabaseOpened(SQLiteDatabase db) { 73 long version = DatabaseUtils.longForQuery(db, 74 "SELECT " + SYNC_STATE_META_VERSION_COLUMN + " FROM " + SYNC_STATE_META_TABLE, 75 null); 76 if (version != DB_VERSION) { 77 createDatabase(db); 78 } 79 } 80 81 public Cursor query(SQLiteDatabase db, String[] projection, 82 String selection, String[] selectionArgs, String sortOrder) { 83 return db.query(SYNC_STATE_TABLE, projection, selection, selectionArgs, 84 null, null, sortOrder); 85 } 86 87 public long insert(SQLiteDatabase db, ContentValues values) { 88 return db.replace(SYNC_STATE_TABLE, SyncStateContract.Columns.ACCOUNT_NAME, values); 89 } 90 91 public int delete(SQLiteDatabase db, String userWhere, String[] whereArgs) { 92 return db.delete(SYNC_STATE_TABLE, userWhere, whereArgs); 93 } 94 95 public int update(SQLiteDatabase db, ContentValues values, 96 String selection, String[] selectionArgs) { 97 return db.update(SYNC_STATE_TABLE, values, selection, selectionArgs); 98 } 99 100 public int update(SQLiteDatabase db, long rowId, Object data) { 101 if (DatabaseUtils.longForQuery(db, QUERY_COUNT_SYNC_STATE_ROWS, 102 new String[]{Long.toString(rowId)}) < 1) { 103 return 0; 104 } 105 db.execSQL("UPDATE " + SYNC_STATE_TABLE 106 + " SET " + SyncStateContract.Columns.DATA + "=?" 107 + " WHERE " + SyncStateContract.Columns._ID + "=" + rowId, 108 new Object[]{data}); 109 // assume a row was modified since we know it exists 110 return 1; 111 } 112 113 public void onAccountsChanged(SQLiteDatabase db, Account[] accounts) { 114 Cursor c = db.query(SYNC_STATE_TABLE, ACCOUNT_PROJECTION, null, null, null, null, null); 115 try { 116 while (c.moveToNext()) { 117 final String accountName = c.getString(0); 118 final String accountType = c.getString(1); 119 Account account = new Account(accountName, accountType); 120 if (!contains(accounts, account)) { 121 db.delete(SYNC_STATE_TABLE, SELECT_BY_ACCOUNT, 122 new String[]{accountName, accountType}); 123 } 124 } 125 } finally { 126 c.close(); 127 } 128 } 129 130 /** 131 * Checks that value is present as at least one of the elements of the array. 132 * @param array the array to check in 133 * @param value the value to check for 134 * @return true if the value is present in the array 135 */ 136 private static <T> boolean contains(T[] array, T value) { 137 for (T element : array) { 138 if (element == null) { 139 if (value == null) return true; 140 } else { 141 if (value != null && element.equals(value)) return true; 142 } 143 } 144 return false; 145 } 146 }