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 
createDatabase(SQLiteDatabase db)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 
onDatabaseOpened(SQLiteDatabase db)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 
query(SQLiteDatabase db, String[] projection, String selection, String[] selectionArgs, String sortOrder)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 
insert(SQLiteDatabase db, ContentValues values)87     public long insert(SQLiteDatabase db, ContentValues values) {
88         return db.replace(SYNC_STATE_TABLE, SyncStateContract.Columns.ACCOUNT_NAME, values);
89     }
90 
delete(SQLiteDatabase db, String userWhere, String[] whereArgs)91     public int delete(SQLiteDatabase db, String userWhere, String[] whereArgs) {
92         return db.delete(SYNC_STATE_TABLE, userWhere, whereArgs);
93     }
94 
update(SQLiteDatabase db, ContentValues values, String selection, String[] selectionArgs)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 
update(SQLiteDatabase db, long rowId, Object data)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 
onAccountsChanged(SQLiteDatabase db, Account[] accounts)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      */
contains(T[] array, T value)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 }