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}