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.BroadcastReceiver; 19import android.content.ContentProvider; 20import android.content.ContentUris; 21import android.content.ContentValues; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.content.UriMatcher; 26import android.database.Cursor; 27import android.database.DatabaseUtils; 28import android.database.sqlite.SQLiteDatabase; 29import android.database.sqlite.SQLiteOpenHelper; 30import android.database.sqlite.SQLiteQueryBuilder; 31import android.net.Uri; 32import android.os.Environment; 33import android.provider.BrowserContract; 34 35import java.io.File; 36 37/** 38 * This provider is expected to be potentially flaky. It uses a database 39 * stored on external storage, which could be yanked unexpectedly. 40 */ 41public class SnapshotProvider extends ContentProvider { 42 43 public static interface Snapshots { 44 45 public static final Uri CONTENT_URI = Uri.withAppendedPath( 46 SnapshotProvider.AUTHORITY_URI, "snapshots"); 47 public static final String _ID = "_id"; 48 public static final String VIEWSTATE = "view_state"; 49 public static final String BACKGROUND = "background"; 50 public static final String TITLE = "title"; 51 public static final String URL = "url"; 52 public static final String FAVICON = "favicon"; 53 public static final String THUMBNAIL = "thumbnail"; 54 public static final String DATE_CREATED = "date_created"; 55 } 56 57 public static final String AUTHORITY = "com.android.browser.snapshots"; 58 public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); 59 60 static final String TABLE_SNAPSHOTS = "snapshots"; 61 static final int SNAPSHOTS = 10; 62 static final int SNAPSHOTS_ID = 11; 63 static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 64 65 SnapshotDatabaseHelper mOpenHelper; 66 67 static { 68 URI_MATCHER.addURI(AUTHORITY, "snapshots", SNAPSHOTS); 69 URI_MATCHER.addURI(AUTHORITY, "snapshots/#", SNAPSHOTS_ID); 70 } 71 72 final static class SnapshotDatabaseHelper extends SQLiteOpenHelper { 73 74 static final String DATABASE_NAME = "snapshots.db"; 75 static final int DATABASE_VERSION = 2; 76 77 public SnapshotDatabaseHelper(Context context) { 78 super(context, getFullDatabaseName(context), null, DATABASE_VERSION); 79 } 80 81 static String getFullDatabaseName(Context context) { 82 File dir = context.getExternalFilesDir(null); 83 return new File(dir, DATABASE_NAME).getAbsolutePath(); 84 } 85 86 @Override 87 public void onCreate(SQLiteDatabase db) { 88 db.execSQL("CREATE TABLE " + TABLE_SNAPSHOTS + "(" + 89 Snapshots._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 90 Snapshots.TITLE + " TEXT," + 91 Snapshots.URL + " TEXT NOT NULL," + 92 Snapshots.DATE_CREATED + " INTEGER," + 93 Snapshots.FAVICON + " BLOB," + 94 Snapshots.THUMBNAIL + " BLOB," + 95 Snapshots.BACKGROUND + " INTEGER," + 96 Snapshots.VIEWSTATE + " BLOB NOT NULL" + 97 ");"); 98 } 99 100 @Override 101 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 102 if (oldVersion < 2) { 103 db.execSQL("DROP TABLE " + TABLE_SNAPSHOTS); 104 onCreate(db); 105 } 106 } 107 108 } 109 110 @Override 111 public boolean onCreate() { 112 IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_EJECT); 113 filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); 114 getContext().registerReceiver(mExternalStorageReceiver, filter); 115 return true; 116 } 117 118 final BroadcastReceiver mExternalStorageReceiver = new BroadcastReceiver() { 119 120 @Override 121 public void onReceive(Context context, Intent intent) { 122 if (mOpenHelper != null) { 123 try { 124 mOpenHelper.close(); 125 } catch (Throwable t) { 126 // We failed to close the open helper, which most likely means 127 // another thread is busy attempting to open the database 128 // or use the database. Let that thread try to gracefully 129 // deal with the error 130 } 131 } 132 } 133 }; 134 135 SQLiteDatabase getWritableDatabase() { 136 String state = Environment.getExternalStorageState(); 137 if (Environment.MEDIA_MOUNTED.equals(state)) { 138 try { 139 if (mOpenHelper == null) { 140 mOpenHelper = new SnapshotDatabaseHelper(getContext()); 141 } 142 return mOpenHelper.getWritableDatabase(); 143 } catch (Throwable t) { 144 return null; 145 } 146 } 147 return null; 148 } 149 150 SQLiteDatabase getReadableDatabase() { 151 String state = Environment.getExternalStorageState(); 152 if (Environment.MEDIA_MOUNTED.equals(state) 153 || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { 154 try { 155 if (mOpenHelper == null) { 156 mOpenHelper = new SnapshotDatabaseHelper(getContext()); 157 } 158 return mOpenHelper.getReadableDatabase(); 159 } catch (Throwable t) { 160 return null; 161 } 162 } 163 return null; 164 } 165 166 @Override 167 public Cursor query(Uri uri, String[] projection, String selection, 168 String[] selectionArgs, String sortOrder) { 169 SQLiteDatabase db = getReadableDatabase(); 170 if (db == null) { 171 return null; 172 } 173 final int match = URI_MATCHER.match(uri); 174 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 175 String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT); 176 switch (match) { 177 case SNAPSHOTS_ID: 178 selection = DatabaseUtils.concatenateWhere(selection, "_id=?"); 179 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 180 new String[] { Long.toString(ContentUris.parseId(uri)) }); 181 // fall through 182 case SNAPSHOTS: 183 qb.setTables(TABLE_SNAPSHOTS); 184 break; 185 186 default: 187 throw new UnsupportedOperationException("Unknown URL " + uri.toString()); 188 } 189 try { 190 Cursor cursor = qb.query(db, projection, selection, selectionArgs, 191 null, null, sortOrder, limit); 192 cursor.setNotificationUri(getContext().getContentResolver(), 193 AUTHORITY_URI); 194 return cursor; 195 } catch (Throwable t) { 196 return null; 197 } 198 } 199 200 @Override 201 public String getType(Uri uri) { 202 return null; 203 } 204 205 @Override 206 public Uri insert(Uri uri, ContentValues values) { 207 SQLiteDatabase db = getWritableDatabase(); 208 if (db == null) { 209 return null; 210 } 211 int match = URI_MATCHER.match(uri); 212 long id = -1; 213 switch (match) { 214 case SNAPSHOTS: 215 try { 216 id = db.insert(TABLE_SNAPSHOTS, Snapshots.TITLE, values); 217 } catch (Throwable t) { 218 id = -1; 219 } 220 break; 221 default: 222 throw new UnsupportedOperationException("Unknown insert URI " + uri); 223 } 224 if (id < 0) { 225 return null; 226 } 227 Uri inserted = ContentUris.withAppendedId(uri, id); 228 getContext().getContentResolver().notifyChange(inserted, null, false); 229 return inserted; 230 } 231 232 @Override 233 public int delete(Uri uri, String selection, String[] selectionArgs) { 234 SQLiteDatabase db = getWritableDatabase(); 235 if (db == null) { 236 return 0; 237 } 238 int match = URI_MATCHER.match(uri); 239 int deleted = 0; 240 switch (match) { 241 case SNAPSHOTS_ID: { 242 selection = DatabaseUtils.concatenateWhere(selection, TABLE_SNAPSHOTS + "._id=?"); 243 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, 244 new String[] { Long.toString(ContentUris.parseId(uri)) }); 245 // fall through 246 } 247 case SNAPSHOTS: 248 try { 249 deleted = db.delete(TABLE_SNAPSHOTS, selection, selectionArgs); 250 } catch (Throwable t) { 251 } 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 263 public int update(Uri uri, ContentValues values, String selection, 264 String[] selectionArgs) { 265 throw new UnsupportedOperationException("not implemented"); 266 } 267 268} 269