/* * Tweetings - Twitter client for Android * * Copyright (C) 2012-2013 RBD Solutions Limited <apps@tweetings.net> * Copyright (C) 2012 Mariotaku Lee <mariotaku.lee@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.dwdesign.tweetings.provider; import static com.dwdesign.tweetings.util.DatabaseUpgradeHelper.safeUpgrade; import static com.dwdesign.tweetings.util.Utils.clearAccountColor; import static com.dwdesign.tweetings.util.Utils.clearAccountName; import static com.dwdesign.tweetings.util.Utils.getTableId; import static com.dwdesign.tweetings.util.Utils.getTableNameForContentUri; import static com.dwdesign.tweetings.util.Utils.showErrorToast; import static com.dwdesign.tweetings.util.Utils.isOnWifi; import java.util.List; import com.dwdesign.tweetings.Constants; import com.dwdesign.tweetings.model.ImageSpec; import com.dwdesign.tweetings.provider.TweetStore.Accounts; import com.dwdesign.tweetings.provider.TweetStore.CachedTrends; import com.dwdesign.tweetings.provider.TweetStore.CachedUsers; import com.dwdesign.tweetings.provider.TweetStore.DirectMessages; import com.dwdesign.tweetings.provider.TweetStore.Drafts; import com.dwdesign.tweetings.provider.TweetStore.Filters; import com.dwdesign.tweetings.provider.TweetStore.Mentions; import com.dwdesign.tweetings.provider.TweetStore.Statuses; import com.dwdesign.tweetings.provider.TweetStore.Tabs; import com.dwdesign.tweetings.util.ArrayUtils; import com.dwdesign.tweetings.util.ImagePreloader; import com.dwdesign.tweetings.util.Utils; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.net.ConnectivityManager; import android.net.Uri; import android.os.Handler; import android.os.Message; public final class TweetStoreProvider extends ContentProvider implements Constants { private Context mContext; private SQLiteDatabase database; private SharedPreferences mPreferences; private ImagePreloader mImagePreloader; private final Handler mErrorToastHandler = new Handler() { @Override public void handleMessage(final Message msg) { if (msg.obj instanceof Exception) { showErrorToast(getContext(), null, msg.obj, false); } super.handleMessage(msg); } }; @Override public int bulkInsert(final Uri uri, final ContentValues[] values) { final String table = getTableNameForContentUri(uri); int result = 0; if (table != null) { database.beginTransaction(); for (final ContentValues contentValues : values) { database.insert(table, null, contentValues); result++; } database.setTransactionSuccessful(); database.endTransaction(); } if (result > 0) { onDatabaseUpdated(uri, false); } onNewItemsInserted(uri, values); return result; }; @Override public int delete(final Uri uri, final String selection, final String[] selectionArgs) { final String table = getTableNameForContentUri(uri); int result = 0; if (table != null) { try { result = database.delete(table, selection, selectionArgs); } catch (final SQLiteException e) { mErrorToastHandler.sendMessage(mErrorToastHandler.obtainMessage(0, e)); } } if (result > 0) { onDatabaseUpdated(uri, false); } return result; } @Override public String getType(final Uri uri) { return null; } @Override public Uri insert(final Uri uri, final ContentValues values) { final String table = getTableNameForContentUri(uri); if (table == null) return null; if (TABLE_DIRECT_MESSAGES_CONVERSATION.equals(table)) // read-only here. return null; else if (TABLE_DIRECT_MESSAGES.equals(table)) // read-only here. return null; else if (TABLE_DIRECT_MESSAGES_CONVERSATIONS_ENTRY.equals(table)) // read-only // here. return null; final long row_id = database.insert(table, null, values); onDatabaseUpdated(uri, true); onNewItemsInserted(uri, values); try { return Uri.withAppendedPath(uri, String.valueOf(row_id)); } catch (final SQLiteException e) { mErrorToastHandler.sendMessage(mErrorToastHandler.obtainMessage(0, e)); } return null; } private void onNewItemsInserted(final Uri uri, final ContentValues... values) { if (uri == null || values == null || values.length == 0) return; preloadImages(values); } private void preloadImages(final ContentValues... values) { if (values == null) return; boolean shouldPreload = true; if (mPreferences.getBoolean(PREFERENCE_KEY_PRELOAD_WIFI_ONLY, true) && (mPreferences.getBoolean(PREFERENCE_KEY_PRELOAD_PROFILE_IMAGES, false) || mPreferences.getBoolean(PREFERENCE_KEY_PRELOAD_PREVIEW_IMAGES, false))) { shouldPreload = false; if (isOnWifi(mContext)) { shouldPreload = true; } } if (shouldPreload) { for (final ContentValues v : values) { if (mPreferences.getBoolean(PREFERENCE_KEY_PRELOAD_PROFILE_IMAGES, false)) { final String profile_image_url = v.getAsString(Statuses.PROFILE_IMAGE_URL); if (profile_image_url != null) { mImagePreloader.preloadImage(DIR_NAME_IMAGE_CACHE, profile_image_url); } final String sender_profile_image_url = v.getAsString(DirectMessages.SENDER_PROFILE_IMAGE_URL); if (sender_profile_image_url != null) { mImagePreloader.preloadImage(DIR_NAME_IMAGE_CACHE, sender_profile_image_url); } final String recipient_profile_image_url = v.getAsString(DirectMessages.RECIPIENT_PROFILE_IMAGE_URL); if (recipient_profile_image_url != null) { mImagePreloader.preloadImage(DIR_NAME_IMAGE_CACHE, recipient_profile_image_url); } } if (mPreferences.getBoolean(PREFERENCE_KEY_PRELOAD_PREVIEW_IMAGES, false)) { final String text_html = v.getAsString(Statuses.TEXT); for (final ImageSpec spec : Utils.getImagesInStatus(text_html)) { if (spec != null && spec.preview_image_link != null) { mImagePreloader.preloadImage(DIR_NAME_IMAGE_CACHE, spec.preview_image_link); } } } } } } @Override public boolean onCreate() { mContext = getContext(); database = new DatabaseHelper(getContext(), DATABASES_NAME, DATABASES_VERSION).getWritableDatabase(); mImagePreloader = new ImagePreloader(mContext); mPreferences = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); return database != null; } @Override public Cursor query(final Uri uri, final String[] projection, final String selection, final String[] selectionArgs, final String sortOrder) { final String table = getTableNameForContentUri(uri); if (table == null) return null; final String projection_string = projection != null ? ArrayUtils.toString(projection, ',', false) : "*"; if (TABLE_DIRECT_MESSAGES_CONVERSATION.equals(table)) { // read-only here. final List<String> segments = uri.getPathSegments(); if (segments.size() != 3) return null; final StringBuilder sql_builder = new StringBuilder(); sql_builder.append("SELECT " + projection_string); sql_builder.append(" FROM " + TABLE_DIRECT_MESSAGES_INBOX); sql_builder.append(" WHERE " + DirectMessages.ACCOUNT_ID + " = " + segments.get(1)); sql_builder.append(" AND " + DirectMessages.SENDER_ID + " = " + segments.get(2)); if (selection != null) { sql_builder.append(" AND " + selection); } sql_builder.append(" UNION "); sql_builder.append("SELECT " + projection_string); sql_builder.append(" FROM " + TABLE_DIRECT_MESSAGES_OUTBOX); sql_builder.append(" WHERE " + DirectMessages.ACCOUNT_ID + " = " + segments.get(1)); sql_builder.append(" AND " + DirectMessages.RECIPIENT_ID + " = " + segments.get(2)); if (selection != null) { sql_builder.append(" AND " + selection); } sql_builder.append(" ORDER BY " + (sortOrder != null ? sortOrder : DirectMessages.Conversation.DEFAULT_SORT_ORDER)); try { return database.rawQuery(sql_builder.toString(), selectionArgs); } catch (final SQLiteException e) { mErrorToastHandler.sendMessage(mErrorToastHandler.obtainMessage(0, e)); } } else if (TABLE_DIRECT_MESSAGES_CONVERSATION_SCREEN_NAME.equals(table)) { // read-only here. final List<String> segments = uri.getPathSegments(); if (segments.size() != 3) return null; final StringBuilder sql_builder = new StringBuilder(); sql_builder.append("SELECT " + projection_string); sql_builder.append(" FROM " + TABLE_DIRECT_MESSAGES_INBOX); sql_builder.append(" WHERE " + DirectMessages.ACCOUNT_ID + " = " + segments.get(1)); sql_builder.append(" AND " + DirectMessages.SENDER_SCREEN_NAME + " = '" + segments.get(2) + "'"); if (selection != null) { sql_builder.append(" AND " + selection); } sql_builder.append(" UNION "); sql_builder.append("SELECT " + projection_string); sql_builder.append(" FROM " + TABLE_DIRECT_MESSAGES_OUTBOX); sql_builder.append(" WHERE " + DirectMessages.ACCOUNT_ID + " = " + segments.get(1)); sql_builder.append(" AND " + DirectMessages.RECIPIENT_SCREEN_NAME + " = '" + segments.get(2) + "'"); if (selection != null) { sql_builder.append(" AND " + selection); } sql_builder.append(" ORDER BY " + (sortOrder != null ? sortOrder : DirectMessages.Conversation.DEFAULT_SORT_ORDER)); try { return database.rawQuery(sql_builder.toString(), selectionArgs); } catch (final SQLiteException e) { mErrorToastHandler.sendMessage(mErrorToastHandler.obtainMessage(0, e)); } } else if (TABLE_DIRECT_MESSAGES.equals(table)) { // read-only here. final StringBuilder sql_builder = new StringBuilder(); sql_builder.append("SELECT " + projection_string); sql_builder.append(" FROM " + TABLE_DIRECT_MESSAGES_INBOX); if (selection != null) { sql_builder.append(" WHERE " + selection); } sql_builder.append(" UNION "); sql_builder.append("SELECT " + projection_string); sql_builder.append(" FROM " + TABLE_DIRECT_MESSAGES_OUTBOX); if (selection != null) { sql_builder.append(" WHERE " + selection); } sql_builder.append(" ORDER BY " + (sortOrder != null ? sortOrder : DirectMessages.DEFAULT_SORT_ORDER)); try { return database.rawQuery(sql_builder.toString(), selectionArgs); } catch (final SQLiteException e) { mErrorToastHandler.sendMessage(mErrorToastHandler.obtainMessage(0, e)); } } else if (TABLE_DIRECT_MESSAGES_CONVERSATIONS_ENTRY.equals(table)) { try { return database.rawQuery(DirectMessages.ConversationsEntry.buildSQL(selection), null); } catch (final SQLiteException e) { mErrorToastHandler.sendMessage(mErrorToastHandler.obtainMessage(0, e)); } } else { try { return database.query(table, projection, selection, selectionArgs, null, null, sortOrder); } catch (final SQLiteException e) { mErrorToastHandler.sendMessage(mErrorToastHandler.obtainMessage(0, e)); } } return null; } @Override public int update(final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) { final String table = getTableNameForContentUri(uri); int result = 0; if (table != null) { if (TABLE_DIRECT_MESSAGES_CONVERSATION.equals(table)) // read-only here. return 0; else if (TABLE_DIRECT_MESSAGES.equals(table)) // read-only here. return 0; else if (TABLE_DIRECT_MESSAGES_CONVERSATIONS_ENTRY.equals(table)) // read-only // here. return 0; try { result = database.update(table, values, selection, selectionArgs); } catch (final SQLiteException e) { mErrorToastHandler.sendMessage(mErrorToastHandler.obtainMessage(0, e)); } } if (result > 0) { boolean notifyUpdate = true; if (TABLE_ACCOUNTS.equals(table)) { if (selection != null && selection.endsWith("AND 1 = 1")) { notifyUpdate = false; } } if (notifyUpdate) { onDatabaseUpdated(uri, false); } } return result; } private void onDatabaseUpdated(final Uri uri, final boolean is_insert) { if (uri == null || "false".equals(uri.getQueryParameter(QUERY_PARAM_NOTIFY))) return; final Context context = getContext(); switch (getTableId(uri)) { case URI_ACCOUNTS: { clearAccountColor(); clearAccountName(); context.sendBroadcast(new Intent(BROADCAST_ACCOUNT_LIST_DATABASE_UPDATED)); break; } case URI_DRAFTS: { context.sendBroadcast(new Intent(BROADCAST_DRAFTS_DATABASE_UPDATED)); break; } case URI_STATUSES: { if (!is_insert || "true".equals(uri.getQueryParameter(QUERY_PARAM_NOTIFY))) { context.sendBroadcast(new Intent(BROADCAST_HOME_TIMELINE_DATABASE_UPDATED).putExtra( INTENT_KEY_SUCCEED, true)); } break; } case URI_MENTIONS: { if (!is_insert || "true".equals(uri.getQueryParameter(QUERY_PARAM_NOTIFY))) { context.sendBroadcast(new Intent(BROADCAST_MENTIONS_DATABASE_UPDATED).putExtra(INTENT_KEY_SUCCEED, true)); } break; } case URI_DIRECT_MESSAGES_INBOX: { if (!is_insert || "true".equals(uri.getQueryParameter(QUERY_PARAM_NOTIFY))) { context.sendBroadcast(new Intent(BROADCAST_RECEIVED_DIRECT_MESSAGES_DATABASE_UPDATED).putExtra( INTENT_KEY_SUCCEED, true)); } break; } case URI_DIRECT_MESSAGES_OUTBOX: { if (!is_insert || "true".equals(uri.getQueryParameter(QUERY_PARAM_NOTIFY))) { context.sendBroadcast(new Intent(BROADCAST_SENT_DIRECT_MESSAGES_DATABASE_UPDATED).putExtra( INTENT_KEY_SUCCEED, true)); } break; } case URI_TRENDS_DAILY: case URI_TRENDS_WEEKLY: case URI_TRENDS_LOCAL: if (!is_insert || "true".equals(uri.getQueryParameter(QUERY_PARAM_NOTIFY))) { context.sendBroadcast(new Intent(BROADCAST_TRENDS_UPDATED).putExtra(INTENT_KEY_SUCCEED, true)); } break; case URI_TABS: { if (!"false".equals(uri.getQueryParameter(QUERY_PARAM_NOTIFY))) { context.sendBroadcast(new Intent(BROADCAST_TABS_UPDATED).putExtra(INTENT_KEY_SUCCEED, true)); } break; } case URI_FILTERED_USERS: case URI_FILTERED_KEYWORDS: case URI_FILTERED_SOURCES: if (!"false".equals(uri.getQueryParameter(QUERY_PARAM_NOTIFY))) { context.sendBroadcast(new Intent(BROADCAST_FILTERS_UPDATED).putExtra(INTENT_KEY_SUCCEED, true)); } break; default: return; } context.sendBroadcast(new Intent(BROADCAST_DATABASE_UPDATED)); } public static class DatabaseHelper extends SQLiteOpenHelper { public DatabaseHelper(final Context context, final String name, final int version) { super(context, name, null, version); } @Override public void onCreate(final SQLiteDatabase db) { db.beginTransaction(); db.execSQL(createTable(TABLE_ACCOUNTS, Accounts.COLUMNS, Accounts.TYPES, true)); db.execSQL(createTable(TABLE_STATUSES, Statuses.COLUMNS, Statuses.TYPES, true)); db.execSQL(createTable(TABLE_MENTIONS, Mentions.COLUMNS, Mentions.TYPES, true)); db.execSQL(createTable(TABLE_DRAFTS, Drafts.COLUMNS, Drafts.TYPES, true)); db.execSQL(createTable(TABLE_CACHED_USERS, CachedUsers.COLUMNS, CachedUsers.TYPES, true)); db.execSQL(createTable(TABLE_FILTERED_USERS, Filters.Users.COLUMNS, Filters.Users.TYPES, true)); db.execSQL(createTable(TABLE_FILTERED_KEYWORDS, Filters.Keywords.COLUMNS, Filters.Keywords.TYPES, true)); db.execSQL(createTable(TABLE_FILTERED_SOURCES, Filters.Sources.COLUMNS, Filters.Sources.TYPES, true)); db.execSQL(createTable(TABLE_FILTERED_SOURCES, Filters.Sources.COLUMNS, Filters.Sources.TYPES, true)); db.execSQL(createTable(TABLE_DIRECT_MESSAGES_INBOX, DirectMessages.Inbox.COLUMNS, DirectMessages.Inbox.TYPES, true)); db.execSQL(createTable(TABLE_DIRECT_MESSAGES_OUTBOX, DirectMessages.Outbox.COLUMNS, DirectMessages.Outbox.TYPES, true)); db.execSQL(createTable(TABLE_TRENDS_DAILY, CachedTrends.Daily.COLUMNS, CachedTrends.Daily.TYPES, true)); db.execSQL(createTable(TABLE_TRENDS_WEEKLY, CachedTrends.Weekly.COLUMNS, CachedTrends.Weekly.TYPES, true)); db.execSQL(createTable(TABLE_TRENDS_LOCAL, CachedTrends.Local.COLUMNS, CachedTrends.Local.TYPES, true)); db.execSQL(createTable(TABLE_TABS, Tabs.COLUMNS, Tabs.TYPES, false)); db.setTransactionSuccessful(); db.endTransaction(); } @Override public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { handleVersionChange(db); } @Override public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { handleVersionChange(db); } private String createTable(final String tableName, final String[] columns, final String[] types, final boolean create_if_not_exists) { if (tableName == null || columns == null || types == null || types.length != columns.length || types.length == 0) throw new IllegalArgumentException("Invalid parameters for creating table " + tableName); final StringBuilder stringBuilder = new StringBuilder(create_if_not_exists ? "CREATE TABLE IF NOT EXISTS " : "CREATE TABLE "); stringBuilder.append(tableName); stringBuilder.append(" ("); final int length = columns.length; for (int n = 0, i = length; n < i; n++) { if (n > 0) { stringBuilder.append(", "); } stringBuilder.append(columns[n]).append(' ').append(types[n]); } return stringBuilder.append(");").toString(); } private void handleVersionChange(SQLiteDatabase db) { safeUpgrade(db, TABLE_ACCOUNTS, Accounts.COLUMNS, Accounts.TYPES, true, false); safeUpgrade(db, TABLE_STATUSES, Statuses.COLUMNS, Statuses.TYPES, true, true); safeUpgrade(db, TABLE_MENTIONS, Mentions.COLUMNS, Mentions.TYPES, true, true); safeUpgrade(db, TABLE_DRAFTS, Drafts.COLUMNS, Drafts.TYPES, true, false); safeUpgrade(db, TABLE_CACHED_USERS, CachedUsers.COLUMNS, CachedUsers.TYPES, true, true); safeUpgrade(db, TABLE_FILTERED_USERS, Filters.Users.COLUMNS, Filters.Users.TYPES, true, false); safeUpgrade(db, TABLE_FILTERED_KEYWORDS, Filters.Keywords.COLUMNS, Filters.Keywords.TYPES, true, false); safeUpgrade(db, TABLE_FILTERED_SOURCES, Filters.Sources.COLUMNS, Filters.Sources.TYPES, true, false); safeUpgrade(db, TABLE_DIRECT_MESSAGES_INBOX, DirectMessages.Inbox.COLUMNS, DirectMessages.Inbox.TYPES, true, true); safeUpgrade(db, TABLE_DIRECT_MESSAGES_OUTBOX, DirectMessages.Outbox.COLUMNS, DirectMessages.Outbox.TYPES, true, true); safeUpgrade(db, TABLE_TRENDS_DAILY, CachedTrends.Daily.COLUMNS, CachedTrends.Daily.TYPES, true, true); safeUpgrade(db, TABLE_TRENDS_WEEKLY, CachedTrends.Weekly.COLUMNS, CachedTrends.Weekly.TYPES, true, true); safeUpgrade(db, TABLE_TRENDS_LOCAL, CachedTrends.Local.COLUMNS, CachedTrends.Local.TYPES, true, true); safeUpgrade(db, TABLE_TABS, Tabs.COLUMNS, Tabs.TYPES, true, false); } } }