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