SnapshotProvider.java revision 5eac0b68bf5f16db1bcc2980f1ccb148afde7d71
1/*
2 * Copyright (C) 2011 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 */
16package com.android.browser.provider;
17
18import android.content.ContentProvider;
19import android.content.ContentUris;
20import android.content.ContentValues;
21import android.content.Context;
22import android.content.UriMatcher;
23import android.database.Cursor;
24import android.database.DatabaseUtils;
25import android.database.sqlite.SQLiteDatabase;
26import android.database.sqlite.SQLiteOpenHelper;
27import android.database.sqlite.SQLiteQueryBuilder;
28import android.net.Uri;
29import android.os.FileUtils;
30import android.provider.BrowserContract;
31
32import java.io.File;
33
34public class SnapshotProvider extends ContentProvider {
35
36    public static interface Snapshots {
37
38        public static final Uri CONTENT_URI = Uri.withAppendedPath(
39                SnapshotProvider.AUTHORITY_URI, "snapshots");
40        public static final String _ID = "_id";
41        @Deprecated
42        public static final String VIEWSTATE = "view_state";
43        public static final String BACKGROUND = "background";
44        public static final String TITLE = "title";
45        public static final String URL = "url";
46        public static final String FAVICON = "favicon";
47        public static final String THUMBNAIL = "thumbnail";
48        public static final String DATE_CREATED = "date_created";
49        public static final String VIEWSTATE_PATH = "viewstate_path";
50        public static final String VIEWSTATE_SIZE = "viewstate_size";
51    }
52
53    public static final String AUTHORITY = "com.android.browser.snapshots";
54    public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
55
56    static final String TABLE_SNAPSHOTS = "snapshots";
57    static final int SNAPSHOTS = 10;
58    static final int SNAPSHOTS_ID = 11;
59    static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
60    // Workaround that we can't remove the "NOT NULL" constraint on VIEWSTATE
61    static final byte[] NULL_BLOB_HACK = new byte[0];
62
63    SnapshotDatabaseHelper mOpenHelper;
64
65    static {
66        URI_MATCHER.addURI(AUTHORITY, "snapshots", SNAPSHOTS);
67        URI_MATCHER.addURI(AUTHORITY, "snapshots/#", SNAPSHOTS_ID);
68    }
69
70    final static class SnapshotDatabaseHelper extends SQLiteOpenHelper {
71
72        static final String DATABASE_NAME = "snapshots.db";
73        static final int DATABASE_VERSION = 3;
74
75        public SnapshotDatabaseHelper(Context context) {
76            super(context, DATABASE_NAME, null, DATABASE_VERSION);
77        }
78
79        @Override
80        public void onCreate(SQLiteDatabase db) {
81            db.execSQL("CREATE TABLE " + TABLE_SNAPSHOTS + "(" +
82                    Snapshots._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
83                    Snapshots.TITLE + " TEXT," +
84                    Snapshots.URL + " TEXT NOT NULL," +
85                    Snapshots.DATE_CREATED + " INTEGER," +
86                    Snapshots.FAVICON + " BLOB," +
87                    Snapshots.THUMBNAIL + " BLOB," +
88                    Snapshots.BACKGROUND + " INTEGER," +
89                    Snapshots.VIEWSTATE + " BLOB NOT NULL," +
90                    Snapshots.VIEWSTATE_PATH + " TEXT," +
91                    Snapshots.VIEWSTATE_SIZE + " INTEGER" +
92                    ");");
93        }
94
95        @Override
96        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
97            if (oldVersion < 2) {
98                db.execSQL("DROP TABLE " + TABLE_SNAPSHOTS);
99                onCreate(db);
100            }
101            if (oldVersion < 3) {
102                db.execSQL("ALTER TABLE " + TABLE_SNAPSHOTS + " ADD COLUMN "
103                        + Snapshots.VIEWSTATE_PATH + " TEXT");
104                db.execSQL("ALTER TABLE " + TABLE_SNAPSHOTS + " ADD COLUMN "
105                        + Snapshots.VIEWSTATE_SIZE + " INTEGER");
106                db.execSQL("UPDATE " + TABLE_SNAPSHOTS + " SET "
107                        + Snapshots.VIEWSTATE_SIZE + " = length("
108                        + Snapshots.VIEWSTATE + ")");
109            }
110        }
111
112    }
113
114    static File getOldDatabasePath(Context context) {
115        File dir = context.getExternalFilesDir(null);
116        return new File(dir, SnapshotDatabaseHelper.DATABASE_NAME);
117    }
118
119    private void migrateToDataFolder() {
120        File dbPath = getContext().getDatabasePath(SnapshotDatabaseHelper.DATABASE_NAME);
121        if (dbPath.exists()) return;
122        File oldPath = getOldDatabasePath(getContext());
123        if (oldPath.exists()) {
124            // Try to move
125            if (!oldPath.renameTo(dbPath)) {
126                // Failed, do a copy
127                FileUtils.copyFile(oldPath, dbPath);
128            }
129            // Cleanup
130            oldPath.delete();
131        }
132    }
133
134    @Override
135    public boolean onCreate() {
136        migrateToDataFolder();
137        mOpenHelper = new SnapshotDatabaseHelper(getContext());
138        return true;
139    }
140
141    SQLiteDatabase getWritableDatabase() {
142        return mOpenHelper.getWritableDatabase();
143    }
144
145    SQLiteDatabase getReadableDatabase() {
146        return mOpenHelper.getReadableDatabase();
147    }
148
149    @Override
150    public Cursor query(Uri uri, String[] projection, String selection,
151            String[] selectionArgs, String sortOrder) {
152        SQLiteDatabase db = getReadableDatabase();
153        if (db == null) {
154            return null;
155        }
156        final int match = URI_MATCHER.match(uri);
157        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
158        String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
159        switch (match) {
160        case SNAPSHOTS_ID:
161            selection = DatabaseUtils.concatenateWhere(selection, "_id=?");
162            selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
163                    new String[] { Long.toString(ContentUris.parseId(uri)) });
164            // fall through
165        case SNAPSHOTS:
166            qb.setTables(TABLE_SNAPSHOTS);
167            break;
168
169        default:
170            throw new UnsupportedOperationException("Unknown URL " + uri.toString());
171        }
172        Cursor cursor = qb.query(db, projection, selection, selectionArgs,
173                null, null, sortOrder, limit);
174        cursor.setNotificationUri(getContext().getContentResolver(),
175                AUTHORITY_URI);
176        return cursor;
177    }
178
179    @Override
180    public String getType(Uri uri) {
181        return null;
182    }
183
184    @Override
185    public Uri insert(Uri uri, ContentValues values) {
186        SQLiteDatabase db = getWritableDatabase();
187        if (db == null) {
188            return null;
189        }
190        int match = URI_MATCHER.match(uri);
191        long id = -1;
192        switch (match) {
193        case SNAPSHOTS:
194            if (!values.containsKey(Snapshots.VIEWSTATE)) {
195                values.put(Snapshots.VIEWSTATE, NULL_BLOB_HACK);
196            }
197            id = db.insert(TABLE_SNAPSHOTS, Snapshots.TITLE, values);
198            break;
199        default:
200            throw new UnsupportedOperationException("Unknown insert URI " + uri);
201        }
202        if (id < 0) {
203            return null;
204        }
205        Uri inserted = ContentUris.withAppendedId(uri, id);
206        getContext().getContentResolver().notifyChange(inserted, null, false);
207        return inserted;
208    }
209
210    static final String[] DELETE_PROJECTION = new String[] {
211        Snapshots.VIEWSTATE_PATH,
212    };
213    private void deleteDataFiles(SQLiteDatabase db, String selection,
214            String[] selectionArgs) {
215        Cursor c = db.query(TABLE_SNAPSHOTS, DELETE_PROJECTION, selection,
216                selectionArgs, null, null, null);
217        final Context context = getContext();
218        while (c.moveToNext()) {
219            File f = context.getFileStreamPath(c.getString(0));
220            if (f.exists()) {
221                if (!f.delete()) {
222                    f.deleteOnExit();
223                }
224            }
225        }
226        c.close();
227    }
228
229    @Override
230    public int delete(Uri uri, String selection, String[] selectionArgs) {
231        SQLiteDatabase db = getWritableDatabase();
232        if (db == null) {
233            return 0;
234        }
235        int match = URI_MATCHER.match(uri);
236        int deleted = 0;
237        switch (match) {
238        case SNAPSHOTS_ID: {
239            selection = DatabaseUtils.concatenateWhere(selection, TABLE_SNAPSHOTS + "._id=?");
240            selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
241                    new String[] { Long.toString(ContentUris.parseId(uri)) });
242            // fall through
243        }
244        case SNAPSHOTS:
245            deleteDataFiles(db, selection, selectionArgs);
246            deleted = db.delete(TABLE_SNAPSHOTS, selection, selectionArgs);
247            break;
248        default:
249            throw new UnsupportedOperationException("Unknown delete URI " + uri);
250        }
251        if (deleted > 0) {
252            getContext().getContentResolver().notifyChange(uri, null, false);
253        }
254        return deleted;
255    }
256
257    @Override
258    public int update(Uri uri, ContentValues values, String selection,
259            String[] selectionArgs) {
260        throw new UnsupportedOperationException("not implemented");
261    }
262
263}
264