Java tutorial
/* * Twidere - Twitter client for Android * * Copyright (C) 2012-2015 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 org.mariotaku.twidere.util; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.provider.BaseColumns; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.support.v4.util.LongSparseArray; import android.text.TextUtils; import com.bluelinelabs.logansquare.JsonMapper; import org.apache.commons.lang3.ArrayUtils; import org.mariotaku.commons.logansquare.LoganSquareMapperFinder; import org.mariotaku.microblog.library.twitter.model.Activity; import org.mariotaku.sqliteqb.library.ArgsArray; import org.mariotaku.sqliteqb.library.Columns; import org.mariotaku.sqliteqb.library.Columns.Column; import org.mariotaku.sqliteqb.library.Expression; import org.mariotaku.sqliteqb.library.OrderBy; import org.mariotaku.sqliteqb.library.SQLFunctions; import org.mariotaku.sqliteqb.library.SQLQueryBuilder; import org.mariotaku.sqliteqb.library.Table; import org.mariotaku.sqliteqb.library.Tables; import org.mariotaku.sqliteqb.library.query.SQLSelectQuery; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.TwidereConstants; import org.mariotaku.twidere.model.ParcelableAccount; import org.mariotaku.twidere.model.ParcelableActivity; import org.mariotaku.twidere.model.ParcelableActivityCursorIndices; import org.mariotaku.twidere.model.ParcelableActivityValuesCreator; import org.mariotaku.twidere.model.ParcelableCredentials; import org.mariotaku.twidere.model.ParcelableCredentialsCursorIndices; import org.mariotaku.twidere.model.ParcelableStatus; import org.mariotaku.twidere.model.UserFollowState; import org.mariotaku.twidere.model.UserKey; import org.mariotaku.twidere.model.tab.extra.HomeTabExtras; import org.mariotaku.twidere.model.tab.extra.InteractionsTabExtras; import org.mariotaku.twidere.model.tab.extra.TabExtras; import org.mariotaku.twidere.provider.TwidereDataStore; import org.mariotaku.twidere.provider.TwidereDataStore.AccountSupportColumns; import org.mariotaku.twidere.provider.TwidereDataStore.Accounts; import org.mariotaku.twidere.provider.TwidereDataStore.Activities; import org.mariotaku.twidere.provider.TwidereDataStore.CacheFiles; import org.mariotaku.twidere.provider.TwidereDataStore.CachedHashtags; import org.mariotaku.twidere.provider.TwidereDataStore.CachedImages; import org.mariotaku.twidere.provider.TwidereDataStore.CachedRelationships; import org.mariotaku.twidere.provider.TwidereDataStore.CachedStatuses; import org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends; import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers; import org.mariotaku.twidere.provider.TwidereDataStore.DNS; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages; import org.mariotaku.twidere.provider.TwidereDataStore.Drafts; import org.mariotaku.twidere.provider.TwidereDataStore.Filters; import org.mariotaku.twidere.provider.TwidereDataStore.Notifications; import org.mariotaku.twidere.provider.TwidereDataStore.Permissions; import org.mariotaku.twidere.provider.TwidereDataStore.Preferences; import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches; import org.mariotaku.twidere.provider.TwidereDataStore.SearchHistory; import org.mariotaku.twidere.provider.TwidereDataStore.Statuses; import org.mariotaku.twidere.provider.TwidereDataStore.Suggestions; import org.mariotaku.twidere.provider.TwidereDataStore.Tabs; import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static android.text.TextUtils.isEmpty; import static org.mariotaku.twidere.provider.TwidereDataStore.ACTIVITIES_URIS; import static org.mariotaku.twidere.provider.TwidereDataStore.CACHE_URIS; import static org.mariotaku.twidere.provider.TwidereDataStore.DIRECT_MESSAGES_URIS; import static org.mariotaku.twidere.provider.TwidereDataStore.STATUSES_URIS; /** * Created by mariotaku on 15/11/28. */ public class DataStoreUtils implements Constants { static final UriMatcher CONTENT_PROVIDER_URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); static Map<UserKey, String> sAccountScreenNames = new HashMap<>(); static Map<UserKey, String> sAccountNames = new HashMap<>(); static { CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Accounts.CONTENT_PATH, TABLE_ID_ACCOUNTS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Statuses.CONTENT_PATH, TABLE_ID_STATUSES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Activities.AboutMe.CONTENT_PATH, TABLE_ID_ACTIVITIES_ABOUT_ME); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Activities.ByFriends.CONTENT_PATH, TABLE_ID_ACTIVITIES_BY_FRIENDS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH, TABLE_ID_DRAFTS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH, TABLE_ID_CACHED_USERS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Users.CONTENT_PATH, TABLE_ID_FILTERED_USERS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Keywords.CONTENT_PATH, TABLE_ID_FILTERED_KEYWORDS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Sources.CONTENT_PATH, TABLE_ID_FILTERED_SOURCES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Links.CONTENT_PATH, TABLE_ID_FILTERED_LINKS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.CONTENT_PATH, TABLE_ID_DIRECT_MESSAGES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.Inbox.CONTENT_PATH, TABLE_ID_DIRECT_MESSAGES_INBOX); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.Outbox.CONTENT_PATH, TABLE_ID_DIRECT_MESSAGES_OUTBOX); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.Conversation.CONTENT_PATH + "/*/*", TABLE_ID_DIRECT_MESSAGES_CONVERSATION); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.Conversation.CONTENT_PATH_SCREEN_NAME + "/*/*", TABLE_ID_DIRECT_MESSAGES_CONVERSATION_SCREEN_NAME); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DirectMessages.ConversationEntries.CONTENT_PATH, TABLE_ID_DIRECT_MESSAGES_CONVERSATIONS_ENTRIES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedTrends.Local.CONTENT_PATH, TABLE_ID_TRENDS_LOCAL); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Tabs.CONTENT_PATH, TABLE_ID_TABS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedStatuses.CONTENT_PATH, TABLE_ID_CACHED_STATUSES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedHashtags.CONTENT_PATH, TABLE_ID_CACHED_HASHTAGS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedRelationships.CONTENT_PATH, TABLE_ID_CACHED_RELATIONSHIPS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, SavedSearches.CONTENT_PATH, TABLE_ID_SAVED_SEARCHES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, SearchHistory.CONTENT_PATH, TABLE_ID_SEARCH_HISTORY); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Notifications.CONTENT_PATH, VIRTUAL_TABLE_ID_NOTIFICATIONS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Notifications.CONTENT_PATH + "/#", VIRTUAL_TABLE_ID_NOTIFICATIONS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Notifications.CONTENT_PATH + "/#/*", VIRTUAL_TABLE_ID_NOTIFICATIONS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Permissions.CONTENT_PATH, VIRTUAL_TABLE_ID_PERMISSIONS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DNS.CONTENT_PATH + "/*", VIRTUAL_TABLE_ID_DNS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedImages.CONTENT_PATH, VIRTUAL_TABLE_ID_CACHED_IMAGES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CacheFiles.CONTENT_PATH + "/*", VIRTUAL_TABLE_ID_CACHE_FILES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Preferences.CONTENT_PATH, VIRTUAL_TABLE_ID_ALL_PREFERENCES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Preferences.CONTENT_PATH + "/*", VIRTUAL_TABLE_ID_PREFERENCES); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, UnreadCounts.CONTENT_PATH, VIRTUAL_TABLE_ID_UNREAD_COUNTS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, UnreadCounts.CONTENT_PATH + "/#", VIRTUAL_TABLE_ID_UNREAD_COUNTS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, UnreadCounts.CONTENT_PATH + "/#/#/*", VIRTUAL_TABLE_ID_UNREAD_COUNTS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, UnreadCounts.ByType.CONTENT_PATH + "/*", VIRTUAL_TABLE_ID_UNREAD_COUNTS_BY_TYPE); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, TwidereDataStore.CONTENT_PATH_DATABASE_READY, VIRTUAL_TABLE_ID_DATABASE_READY); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH_WITH_RELATIONSHIP + "/*", VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH_WITH_SCORE + "/*", VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH_UNSENT, VIRTUAL_TABLE_ID_DRAFTS_UNSENT); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH_NOTIFICATIONS, VIRTUAL_TABLE_ID_DRAFTS_NOTIFICATIONS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH_NOTIFICATIONS, VIRTUAL_TABLE_ID_DRAFTS_NOTIFICATIONS); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Suggestions.AutoComplete.CONTENT_PATH, VIRTUAL_TABLE_ID_SUGGESTIONS_AUTO_COMPLETE); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Suggestions.Search.CONTENT_PATH, VIRTUAL_TABLE_ID_SUGGESTIONS_SEARCH); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, TwidereDataStore.CONTENT_PATH_EMPTY, VIRTUAL_TABLE_ID_EMPTY); CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, TwidereDataStore.CONTENT_PATH_RAW_QUERY + "/*", VIRTUAL_TABLE_ID_RAW_QUERY); } @NonNull public static String[] getNewestMessageIds(@NonNull final Context context, @NonNull final Uri uri, @NonNull final UserKey[] accountKeys) { return getStringFieldArray(context, uri, accountKeys, DirectMessages.ACCOUNT_KEY, DirectMessages.MESSAGE_ID, new OrderBy(SQLFunctions.MAX(DirectMessages.MESSAGE_TIMESTAMP))); } @NonNull public static String[] getNewestStatusIds(@NonNull final Context context, @NonNull final Uri uri, @NonNull final UserKey[] accountKeys) { return getStringFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.STATUS_ID, new OrderBy(SQLFunctions.MAX(Statuses.STATUS_TIMESTAMP))); } @NonNull public static long[] getNewestStatusSortIds(@NonNull final Context context, @NonNull final Uri uri, @NonNull final UserKey[] accountKeys) { return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.SORT_ID, new OrderBy(SQLFunctions.MAX(Statuses.STATUS_TIMESTAMP))); } @NonNull public static String[] getOldestMessageIds(@NonNull final Context context, @NonNull final Uri uri, @NonNull final UserKey[] accountKeys) { return getStringFieldArray(context, uri, accountKeys, DirectMessages.ACCOUNT_KEY, DirectMessages.MESSAGE_ID, new OrderBy(SQLFunctions.MIN(DirectMessages.MESSAGE_TIMESTAMP))); } @NonNull public static String[] getOldestStatusIds(@NonNull final Context context, @NonNull final Uri uri, @NonNull final UserKey[] accountKeys) { return getStringFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.STATUS_ID, new OrderBy(SQLFunctions.MIN(Statuses.STATUS_TIMESTAMP))); } @NonNull public static long[] getOldestStatusSortIds(@NonNull final Context context, @NonNull final Uri uri, @NonNull final UserKey[] accountKeys) { return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.SORT_ID, new OrderBy(SQLFunctions.MIN(Statuses.STATUS_TIMESTAMP))); } @NonNull public static String[] getNewestActivityMaxPositions(final Context context, final Uri uri, final UserKey[] accountKeys) { return getStringFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY, Activities.MAX_REQUEST_POSITION, new OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP))); } @NonNull public static String[] getOldestActivityMaxPositions(@NonNull final Context context, @NonNull final Uri uri, @NonNull final UserKey[] accountKeys) { return getStringFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY, Activities.MAX_REQUEST_POSITION, new OrderBy(SQLFunctions.MIN(Activities.TIMESTAMP))); } @NonNull public static long[] getNewestActivityMaxSortPositions(final Context context, final Uri uri, final UserKey[] accountKeys) { return getLongFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY, Activities.MAX_SORT_POSITION, new OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP))); } @NonNull public static long[] getOldestActivityMaxSortPositions(@NonNull final Context context, @NonNull final Uri uri, @NonNull final UserKey[] accountKeys) { return getLongFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY, Activities.MAX_SORT_POSITION, new OrderBy(SQLFunctions.MIN(Activities.TIMESTAMP))); } public static int getStatusCount(final Context context, final Uri uri, final UserKey accountId) { final String where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).getSQL(); final String[] whereArgs = { accountId.toString() }; return queryCount(context, uri, where, whereArgs); } public static int getActivitiesCount(@NonNull final Context context, @NonNull final Uri uri, @NonNull final UserKey accountKey) { final String where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).getSQL(); return queryCount(context, uri, where, new String[] { accountKey.toString() }); } @NonNull public static UserKey[] getFilteredUserIds(Context context) { if (context == null) return new UserKey[0]; final ContentResolver resolver = context.getContentResolver(); final String[] projection = { Filters.Users.USER_KEY }; final Cursor cur = resolver.query(Filters.Users.CONTENT_URI, projection, null, null, null); if (cur == null) return new UserKey[0]; try { final UserKey[] ids = new UserKey[cur.getCount()]; cur.moveToFirst(); int i = 0; while (!cur.isAfterLast()) { ids[i] = UserKey.valueOf(cur.getString(0)); cur.moveToNext(); i++; } cur.close(); return ids; } finally { cur.close(); } } @NonNull public static Expression buildStatusFilterWhereClause(@NonNull final String table, final Expression extraSelection) { final SQLSelectQuery filteredUsersQuery = SQLQueryBuilder .select(new Column(new Table(Filters.Users.TABLE_NAME), Filters.Users.USER_KEY)) .from(new Tables(Filters.Users.TABLE_NAME)).build(); final Expression filteredUsersWhere = Expression.or( Expression.in(new Column(new Table(table), Statuses.USER_KEY), filteredUsersQuery), Expression.in(new Column(new Table(table), Statuses.RETWEETED_BY_USER_KEY), filteredUsersQuery), Expression.in(new Column(new Table(table), Statuses.QUOTED_USER_KEY), filteredUsersQuery)); final SQLSelectQuery.Builder filteredIdsQueryBuilder = SQLQueryBuilder .select(new Column(new Table(table), Statuses._ID)).from(new Tables(table)) .where(filteredUsersWhere).union().select(new Columns(new Column(new Table(table), Statuses._ID))) .from(new Tables(table, Filters.Sources.TABLE_NAME)).where( Expression.or( Expression.likeRaw(new Column(new Table(table), Statuses.SOURCE), "'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'"), Expression.likeRaw(new Column(new Table(table), Statuses.QUOTED_SOURCE), "'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'"))) .union().select(new Columns(new Column(new Table(table), Statuses._ID))) .from(new Tables(table, Filters.Keywords.TABLE_NAME)).where( Expression.or( Expression.likeRaw(new Column(new Table(table), Statuses.TEXT_PLAIN), "'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'"), Expression.likeRaw(new Column(new Table(table), Statuses.QUOTED_TEXT_PLAIN), "'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'"))) .union().select(new Columns(new Column(new Table(table), Statuses._ID))) .from(new Tables(table, Filters.Links.TABLE_NAME)) .where(Expression.or( Expression.likeRaw(new Column(new Table(table), Statuses.SPANS), "'%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%'"), Expression.likeRaw(new Column(new Table(table), Statuses.QUOTED_SPANS), "'%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%'"))); final Expression filterExpression = Expression.or( Expression.notIn(new Column(new Table(table), Statuses._ID), filteredIdsQueryBuilder.build()), Expression.equals(new Column(new Table(table), Statuses.IS_GAP), 1)); if (extraSelection != null) { return Expression.and(filterExpression, extraSelection); } return filterExpression; } public static String getAccountDisplayName(final Context context, final UserKey accountKey, final boolean nameFirst) { final String name; if (nameFirst) { name = getAccountName(context, accountKey); } else { name = String.format("@%s", getAccountScreenName(context, accountKey)); } return name; } public static String getAccountName(final Context context, final UserKey accountKey) { if (context == null) return null; final String cached = sAccountNames.get(accountKey); if (!isEmpty(cached)) return cached; final String[] projection = { Accounts.SCREEN_NAME }; final Cursor cur = getAccountCursor(context, projection, accountKey); if (cur == null) return null; try { if (cur.moveToFirst()) { final String name = cur.getString(0); sAccountNames.put(accountKey, name); return name; } return null; } finally { cur.close(); } } public static String getAccountScreenName(final Context context, final UserKey accountKey) { if (context == null) return null; final String cached = sAccountScreenNames.get(accountKey); if (!isEmpty(cached)) return cached; final String[] projection = { Accounts.SCREEN_NAME }; final Cursor cur = getAccountCursor(context, projection, accountKey); if (cur == null) return null; try { if (cur.moveToFirst()) { final String name = cur.getString(0); sAccountScreenNames.put(accountKey, name); return name; } return null; } finally { cur.close(); } } public static String[] getAccountScreenNames(final Context context) { return getAccountScreenNames(context, null); } public static String[] getAccountScreenNames(@NonNull final Context context, @Nullable final UserKey[] accountKeys) { final String[] cols = new String[] { Accounts.SCREEN_NAME }; final String where; final String[] whereArgs; if (accountKeys != null) { where = Expression.inArgs(new Column(Accounts.ACCOUNT_KEY), accountKeys.length).getSQL(); whereArgs = TwidereArrayUtils.toStringArray(accountKeys); } else { where = null; whereArgs = null; } final Cursor cur = context.getContentResolver().query(Accounts.CONTENT_URI, cols, where, whereArgs, null); if (cur == null) return new String[0]; try { cur.moveToFirst(); final String[] screen_names = new String[cur.getCount()]; int i = 0; while (!cur.isAfterLast()) { screen_names[i++] = cur.getString(0); cur.moveToNext(); } return screen_names; } finally { cur.close(); } } @NonNull public static UserKey[] getActivatedAccountKeys(@NonNull final Context context) { final Cursor cur = context.getContentResolver().query(Accounts.CONTENT_URI, new String[] { Accounts.ACCOUNT_KEY }, Accounts.IS_ACTIVATED + " = 1", null, null); if (cur == null) return new UserKey[0]; try { cur.moveToFirst(); final UserKey[] ids = new UserKey[cur.getCount()]; int i = 0; while (!cur.isAfterLast()) { ids[i++] = UserKey.valueOf(cur.getString(0)); cur.moveToNext(); } return ids; } finally { cur.close(); } } public static int getStatusesCount(@NonNull final Context context, final Uri uri, @Nullable final Bundle extraArgs, final long compare, String compareColumn, boolean greaterThan, @Nullable UserKey[] accountKeys) { if (accountKeys == null) { accountKeys = getActivatedAccountKeys(context); } List<Expression> expressions = new ArrayList<>(); List<String> expressionArgs = new ArrayList<>(); expressions.add(Expression.inArgs(new Column(Statuses.ACCOUNT_KEY), accountKeys.length)); for (UserKey accountKey : accountKeys) { expressionArgs.add(accountKey.toString()); } if (greaterThan) { expressions.add(Expression.greaterThanArgs(compareColumn)); } else { expressions.add(Expression.lesserThanArgs(compareColumn)); } expressionArgs.add(String.valueOf(compare)); expressions.add(buildStatusFilterWhereClause(getTableNameByUri(uri), null)); if (extraArgs != null) { Parcelable extras = extraArgs.getParcelable(EXTRA_EXTRAS); if (extras instanceof HomeTabExtras) { processTabExtras(expressions, expressionArgs, (HomeTabExtras) extras); } } Expression selection = Expression.and(expressions.toArray(new Expression[expressions.size()])); return queryCount(context, uri, selection.getSQL(), expressionArgs.toArray(new String[expressionArgs.size()])); } public static int getActivitiesCount(final Context context, final Uri uri, final long compare, String compareColumn, boolean greaterThan, UserKey... accountKeys) { if (context == null) return 0; if (accountKeys == null) { accountKeys = getActivatedAccountKeys(context); } final Expression selection = Expression.and( Expression.inArgs(new Column(Activities.ACCOUNT_KEY), accountKeys.length), greaterThan ? Expression.greaterThanArgs(compareColumn) : Expression.lesserThanArgs(compareColumn), buildActivityFilterWhereClause(getTableNameByUri(uri), null)); final String[] whereArgs = new String[accountKeys.length + 1]; for (int i = 0; i < accountKeys.length; i++) { whereArgs[i] = accountKeys[i].toString(); } whereArgs[accountKeys.length] = String.valueOf(compare); return queryCount(context, uri, selection.getSQL(), whereArgs); } public static int getActivitiesCount(@NonNull final Context context, final Uri uri, final Expression extraWhere, final String[] extraWhereArgs, final long since, String sinceColumn, boolean followingOnly, @Nullable UserKey[] accountKeys) { if (accountKeys == null) { accountKeys = getActivatedAccountKeys(context); } Expression[] expressions; if (extraWhere != null) { expressions = new Expression[4]; expressions[3] = extraWhere; } else { expressions = new Expression[3]; } expressions[0] = Expression.inArgs(new Column(Activities.ACCOUNT_KEY), accountKeys.length); expressions[1] = Expression.greaterThanArgs(sinceColumn); expressions[2] = buildActivityFilterWhereClause(getTableNameByUri(uri), null); final Expression selection = Expression.and(expressions); String[] selectionArgs; if (extraWhereArgs != null) { selectionArgs = new String[accountKeys.length + 1 + extraWhereArgs.length]; System.arraycopy(extraWhereArgs, 0, selectionArgs, accountKeys.length + 1, extraWhereArgs.length); } else { selectionArgs = new String[accountKeys.length + 1]; } for (int i = 0; i < accountKeys.length; i++) { selectionArgs[i] = accountKeys[i].toString(); } selectionArgs[accountKeys.length] = String.valueOf(since); // If followingOnly option is on, we have to iterate over items if (followingOnly) { final ContentResolver resolver = context.getContentResolver(); final String[] projection = new String[] { Activities.SOURCES }; final Cursor cur = resolver.query(uri, projection, selection.getSQL(), selectionArgs, null); if (cur == null) return -1; try { final JsonMapper<UserFollowState> mapper; try { mapper = LoganSquareMapperFinder.mapperFor(UserFollowState.class); } catch (LoganSquareMapperFinder.ClassLoaderDeadLockException e) { return -1; } int total = 0; cur.moveToFirst(); while (!cur.isAfterLast()) { final String string = cur.getString(0); if (TextUtils.isEmpty(string)) continue; boolean hasFollowing = false; try { for (UserFollowState state : mapper.parseList(string)) { if (state.is_following) { hasFollowing = true; break; } } } catch (IOException e) { continue; } if (hasFollowing) { total++; } cur.moveToNext(); } return total; } finally { cur.close(); } } return queryCount(context, uri, selection.getSQL(), selectionArgs); } public static int getTableId(final Uri uri) { if (uri == null) return -1; return CONTENT_PROVIDER_URI_MATCHER.match(uri); } public static String getTableNameById(final int id) { switch (id) { case TwidereConstants.TABLE_ID_ACCOUNTS: return Accounts.TABLE_NAME; case TwidereConstants.TABLE_ID_STATUSES: return Statuses.TABLE_NAME; case TwidereConstants.TABLE_ID_ACTIVITIES_ABOUT_ME: return Activities.AboutMe.TABLE_NAME; case TwidereConstants.TABLE_ID_ACTIVITIES_BY_FRIENDS: return Activities.ByFriends.TABLE_NAME; case TwidereConstants.TABLE_ID_DRAFTS: return Drafts.TABLE_NAME; case TwidereConstants.TABLE_ID_FILTERED_USERS: return Filters.Users.TABLE_NAME; case TwidereConstants.TABLE_ID_FILTERED_KEYWORDS: return Filters.Keywords.TABLE_NAME; case TwidereConstants.TABLE_ID_FILTERED_SOURCES: return Filters.Sources.TABLE_NAME; case TwidereConstants.TABLE_ID_FILTERED_LINKS: return Filters.Links.TABLE_NAME; case TwidereConstants.TABLE_ID_DIRECT_MESSAGES_INBOX: return DirectMessages.Inbox.TABLE_NAME; case TwidereConstants.TABLE_ID_DIRECT_MESSAGES_OUTBOX: return DirectMessages.Outbox.TABLE_NAME; case TwidereConstants.TABLE_ID_DIRECT_MESSAGES: return DirectMessages.TABLE_NAME; case TwidereConstants.TABLE_ID_DIRECT_MESSAGES_CONVERSATIONS_ENTRIES: return DirectMessages.ConversationEntries.TABLE_NAME; case TwidereConstants.TABLE_ID_TRENDS_LOCAL: return CachedTrends.Local.TABLE_NAME; case TwidereConstants.TABLE_ID_TABS: return Tabs.TABLE_NAME; case TwidereConstants.TABLE_ID_CACHED_STATUSES: return CachedStatuses.TABLE_NAME; case TwidereConstants.TABLE_ID_CACHED_USERS: return CachedUsers.TABLE_NAME; case TwidereConstants.TABLE_ID_CACHED_HASHTAGS: return CachedHashtags.TABLE_NAME; case TwidereConstants.TABLE_ID_CACHED_RELATIONSHIPS: return CachedRelationships.TABLE_NAME; case TwidereConstants.TABLE_ID_SAVED_SEARCHES: return SavedSearches.TABLE_NAME; case TwidereConstants.TABLE_ID_SEARCH_HISTORY: return SearchHistory.TABLE_NAME; default: return null; } } public static String getTableNameByUri(final Uri uri) { if (uri == null) return null; return getTableNameById(getTableId(uri)); } @NonNull public static Expression buildActivityFilterWhereClause(@NonNull final String table, final Expression extraSelection) { final SQLSelectQuery filteredUsersQuery = SQLQueryBuilder .select(new Column(new Table(Filters.Users.TABLE_NAME), Filters.Users.USER_KEY)) .from(new Tables(Filters.Users.TABLE_NAME)).build(); final Expression filteredUsersWhere = Expression.or( Expression.in(new Column(new Table(table), Activities.STATUS_USER_KEY), filteredUsersQuery), Expression.in(new Column(new Table(table), Activities.STATUS_RETWEETED_BY_USER_KEY), filteredUsersQuery), Expression.in(new Column(new Table(table), Activities.STATUS_QUOTED_USER_KEY), filteredUsersQuery)); final SQLSelectQuery.Builder filteredIdsQueryBuilder = SQLQueryBuilder .select(new Column(new Table(table), Activities._ID)).from(new Tables(table)) .where(filteredUsersWhere).union().select(new Columns(new Column(new Table(table), Activities._ID))) .from(new Tables(table, Filters.Sources.TABLE_NAME)).where( Expression.or( Expression.likeRaw(new Column(new Table(table), Activities.STATUS_SOURCE), "'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'"), Expression.likeRaw(new Column(new Table(table), Activities.STATUS_QUOTE_SOURCE), "'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'"))) .union().select(new Columns(new Column(new Table(table), Activities._ID))) .from(new Tables(table, Filters.Keywords.TABLE_NAME)).where( Expression.or( Expression.likeRaw(new Column(new Table(table), Activities.STATUS_TEXT_PLAIN), "'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'"), Expression.likeRaw(new Column(new Table(table), Activities.STATUS_QUOTE_TEXT_PLAIN), "'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'"))) .union().select(new Columns(new Column(new Table(table), Activities._ID))) .from(new Tables(table, Filters.Links.TABLE_NAME)) .where(Expression.or( Expression.likeRaw(new Column(new Table(table), Activities.STATUS_SPANS), "'%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%'"), Expression.likeRaw(new Column(new Table(table), Activities.STATUS_QUOTE_SPANS), "'%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%'"))); final Expression filterExpression = Expression.or( Expression.notIn(new Column(new Table(table), Activities._ID), filteredIdsQueryBuilder.build()), Expression.equals(new Column(new Table(table), Activities.IS_GAP), 1)); if (extraSelection != null) { return Expression.and(filterExpression, extraSelection); } return filterExpression; } @NonNull public static int[] getAccountColors(@NonNull final Context context, @NonNull final UserKey[] accountKeys) { final String[] cols = new String[] { Accounts.ACCOUNT_KEY, Accounts.COLOR }; final String where = Expression.inArgs(new Column(Accounts.ACCOUNT_KEY), accountKeys.length).getSQL(); final String[] whereArgs = TwidereArrayUtils.toStringArray(accountKeys); final int[] colors = new int[accountKeys.length]; final Cursor cur = context.getContentResolver().query(Accounts.CONTENT_URI, cols, where, whereArgs, null); if (cur == null) return colors; try { cur.moveToFirst(); while (!cur.isAfterLast()) { final int idx = ArrayUtils.indexOf(accountKeys, UserKey.valueOf(cur.getString(0))); if (idx >= 0) { colors[idx] = cur.getInt(1); } cur.moveToNext(); } return colors; } finally { cur.close(); } } public static UserKey findAccountKeyByScreenName(@NonNull final Context context, @NonNull final String screenName) { final String[] projection = { Accounts.ACCOUNT_KEY }; final String where = Expression.equalsArgs(Accounts.SCREEN_NAME).getSQL(); final String[] whereArgs = { screenName }; final Cursor cur = context.getContentResolver().query(Accounts.CONTENT_URI, projection, where, whereArgs, null); if (cur == null) return null; try { if (cur.moveToFirst()) { return UserKey.valueOf(cur.getString(0)); } return null; } finally { cur.close(); } } public static UserKey findAccountKey(@NonNull final Context context, final String accountId) { final String[] projection = { Accounts.ACCOUNT_KEY }; final Cursor cur = findAccountCursorsById(context, projection, accountId); if (cur == null) return null; try { if (cur.moveToFirst()) { return UserKey.valueOf(cur.getString(0)); } return null; } finally { cur.close(); } } @NonNull public static UserKey[] getAccountKeys(final Context context) { if (context == null) return new UserKey[0]; final String[] projection = { Accounts.ACCOUNT_KEY }; final Cursor cur = context.getContentResolver().query(Accounts.CONTENT_URI, projection, null, null, null); if (cur == null) return new UserKey[0]; try { cur.moveToFirst(); final UserKey[] ids = new UserKey[cur.getCount()]; int i = 0; while (!cur.isAfterLast()) { ids[i++] = UserKey.valueOf(cur.getString(0)); cur.moveToNext(); } return ids; } finally { cur.close(); } } public static boolean hasAccount(final Context context) { if (context == null) return false; final Cursor cur = context.getContentResolver().query(Accounts.CONTENT_URI, new String[] { SQLFunctions.COUNT() }, null, null, null); if (cur == null) return false; try { cur.moveToFirst(); return cur.getInt(0) > 0; } finally { cur.close(); } } public static synchronized void cleanDatabasesByItemLimit(final Context context) { if (context == null) return; final ContentResolver resolver = context.getContentResolver(); final int itemLimit = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE) .getInt(KEY_DATABASE_ITEM_LIMIT, DEFAULT_DATABASE_ITEM_LIMIT); for (final UserKey accountKey : getAccountKeys(context)) { // Clean statuses. for (final Uri uri : STATUSES_URIS) { if (CachedStatuses.CONTENT_URI.equals(uri)) { continue; } final String table = getTableNameByUri(uri); final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder(); qb.select(new Column(Statuses._ID)).from(new Tables(table)) .where(Expression.equalsArgs(Statuses.ACCOUNT_KEY)) .orderBy(new OrderBy(Statuses.POSITION_KEY, false)).limit(itemLimit); final Expression where = Expression.and(Expression.notIn(new Column(Statuses._ID), qb.build()), Expression.equalsArgs(Statuses.ACCOUNT_KEY)); final String[] whereArgs = { String.valueOf(accountKey), String.valueOf(accountKey) }; resolver.delete(uri, where.getSQL(), whereArgs); } for (final Uri uri : ACTIVITIES_URIS) { final String table = getTableNameByUri(uri); final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder(); qb.select(new Column(Activities._ID)).from(new Tables(table)) .where(Expression.equalsArgs(Activities.ACCOUNT_KEY)) .orderBy(new OrderBy(Activities.TIMESTAMP, false)).limit(itemLimit); final Expression where = Expression.and(Expression.notIn(new Column(Activities._ID), qb.build()), Expression.equalsArgs(Activities.ACCOUNT_KEY)); final String[] whereArgs = { String.valueOf(accountKey), String.valueOf(accountKey) }; resolver.delete(uri, where.getSQL(), whereArgs); } for (final Uri uri : DIRECT_MESSAGES_URIS) { final String table = getTableNameByUri(uri); final Expression accountWhere = Expression.equalsArgs(DirectMessages.ACCOUNT_KEY); final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder(); qb.select(new Column(DirectMessages._ID)).from(new Tables(table)).where(accountWhere) .orderBy(new OrderBy(DirectMessages.MESSAGE_ID, false)).limit(itemLimit * 10); final Expression where = Expression.and( Expression.notIn(new Column(DirectMessages._ID), qb.build()), Expression.equalsArgs(DirectMessages.ACCOUNT_KEY)); final String[] whereArgs = { String.valueOf(accountKey), String.valueOf(accountKey) }; resolver.delete(uri, where.getSQL(), whereArgs); } } // Clean cached values. for (final Uri uri : CACHE_URIS) { final String table = getTableNameByUri(uri); if (table == null) continue; final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder(); qb.select(new Column(BaseColumns._ID)).from(new Tables(table)) .orderBy(new OrderBy(BaseColumns._ID, false)).limit(itemLimit * 20); final Expression where = Expression.notIn(new Column(BaseColumns._ID), qb.build()); resolver.delete(uri, where.getSQL(), null); } } public static void clearAccountName() { sAccountScreenNames.clear(); } public static boolean isFilteringUser(Context context, UserKey userKey) { return isFilteringUser(context, userKey.toString()); } public static boolean isFilteringUser(Context context, String userKey) { final ContentResolver cr = context.getContentResolver(); final Expression where = Expression.equalsArgs(Filters.Users.USER_KEY); final Cursor c = cr.query(Filters.Users.CONTENT_URI, new String[] { SQLFunctions.COUNT() }, where.getSQL(), new String[] { userKey }, null); if (c == null) return false; try { if (c.moveToFirst()) { return c.getLong(0) > 0; } } finally { c.close(); } return false; } @NonNull static String[] getStringFieldArray(@NonNull Context context, @NonNull Uri uri, @NonNull UserKey[] keys, @NonNull String keyField, @NonNull String valueField, @Nullable OrderBy sortExpression) { return getFieldArray(context, uri, keys, keyField, valueField, sortExpression, new FieldArrayCreator<String[]>() { @Override public String[] newArray(int size) { return new String[size]; } @Override public void assign(String[] array, int arrayIdx, Cursor cur, int colIdx) { array[arrayIdx] = cur.getString(colIdx); } }); } @NonNull static long[] getLongFieldArray(@NonNull Context context, @NonNull Uri uri, @NonNull UserKey[] keys, @NonNull String keyField, @NonNull String valueField, @Nullable OrderBy sortExpression) { return getFieldArray(context, uri, keys, keyField, valueField, sortExpression, new FieldArrayCreator<long[]>() { @Override public long[] newArray(int size) { return new long[size]; } @Override public void assign(long[] array, int arrayIdx, Cursor cur, int colIdx) { array[arrayIdx] = cur.getLong(colIdx); } }); } @NonNull static <T> T getFieldArray(@NonNull Context context, @NonNull Uri uri, @NonNull UserKey[] keys, @NonNull String keyField, @NonNull String valueField, @Nullable OrderBy sortExpression, @NonNull FieldArrayCreator<T> creator) { final ContentResolver resolver = context.getContentResolver(); final T messageIds = creator.newArray(keys.length); final String[] selectionArgs = TwidereArrayUtils.toStringArray(keys); final SQLSelectQuery.Builder builder = SQLQueryBuilder.select(new Columns(keyField, valueField)) .from(new Table(getTableNameByUri(uri))).groupBy(new Column(keyField)) .having(Expression.in(new Column(keyField), new ArgsArray(keys.length))); if (sortExpression != null) { builder.orderBy(sortExpression); } final Cursor cur = resolver.query( Uri.withAppendedPath(TwidereDataStore.CONTENT_URI_RAW_QUERY, builder.buildSQL()), null, null, selectionArgs, null); if (cur == null) return messageIds; try { while (cur.moveToNext()) { final UserKey accountKey = UserKey.valueOf(cur.getString(0)); int idx = ArrayUtils.indexOf(keys, accountKey); if (idx < 0) continue; creator.assign(messageIds, idx, cur, 1); } return messageIds; } finally { cur.close(); } } public static void deleteStatus(@NonNull ContentResolver cr, @NonNull UserKey accountKey, @NonNull String statusId, @Nullable ParcelableStatus status) { final String host = accountKey.getHost(); final String deleteWhere, updateWhere; final String[] deleteWhereArgs, updateWhereArgs; if (host != null) { deleteWhere = Expression .and(Expression.likeRaw(new Column(Statuses.ACCOUNT_KEY), "'%@'||?"), Expression.or( Expression.equalsArgs(Statuses.STATUS_ID), Expression.equalsArgs(Statuses.RETWEET_ID))) .getSQL(); deleteWhereArgs = new String[] { host, statusId, statusId }; updateWhere = Expression.and(Expression.likeRaw(new Column(Statuses.ACCOUNT_KEY), "'%@'||?"), Expression.equalsArgs(Statuses.MY_RETWEET_ID)).getSQL(); updateWhereArgs = new String[] { host, statusId }; } else { deleteWhere = Expression .or(Expression.equalsArgs(Statuses.STATUS_ID), Expression.equalsArgs(Statuses.RETWEET_ID)) .getSQL(); deleteWhereArgs = new String[] { statusId, statusId }; updateWhere = Expression.equalsArgs(Statuses.MY_RETWEET_ID).getSQL(); updateWhereArgs = new String[] { statusId }; } for (final Uri uri : STATUSES_URIS) { cr.delete(uri, deleteWhere, deleteWhereArgs); if (status != null) { final ContentValues values = new ContentValues(); values.putNull(Statuses.MY_RETWEET_ID); values.put(Statuses.RETWEET_COUNT, status.retweet_count - 1); cr.update(uri, values, updateWhere, updateWhereArgs); } } } public static void deleteActivityStatus(@NonNull ContentResolver cr, @NonNull UserKey accountKey, @NonNull final String statusId, @Nullable final ParcelableStatus result) { final String host = accountKey.getHost(); final String deleteWhere, updateWhere; final String[] deleteWhereArgs, updateWhereArgs; if (host != null) { deleteWhere = Expression.and(Expression.likeRaw(new Column(Activities.ACCOUNT_KEY), "'%@'||?"), Expression.or(Expression.equalsArgs(Activities.STATUS_ID), Expression.equalsArgs(Activities.STATUS_RETWEET_ID))) .getSQL(); deleteWhereArgs = new String[] { host, statusId, statusId }; updateWhere = Expression.and(Expression.likeRaw(new Column(Activities.ACCOUNT_KEY), "'%@'||?"), Expression.equalsArgs(Activities.STATUS_MY_RETWEET_ID)).getSQL(); updateWhereArgs = new String[] { host, statusId }; } else { deleteWhere = Expression.or(Expression.equalsArgs(Activities.STATUS_ID), Expression.equalsArgs(Activities.STATUS_RETWEET_ID)).getSQL(); deleteWhereArgs = new String[] { statusId, statusId }; updateWhere = Expression.equalsArgs(Activities.STATUS_MY_RETWEET_ID).getSQL(); updateWhereArgs = new String[] { statusId }; } for (final Uri uri : ACTIVITIES_URIS) { cr.delete(uri, deleteWhere, deleteWhereArgs); updateActivity(cr, uri, updateWhere, updateWhereArgs, new UpdateActivityAction() { @Override public void process(ParcelableActivity activity) { activity.status_my_retweet_id = null; ParcelableStatus[][] statusesMatrix = { activity.target_statuses, activity.target_object_statuses }; for (ParcelableStatus[] statusesArray : statusesMatrix) { if (statusesArray == null) continue; for (ParcelableStatus status : statusesArray) { if (statusId.equals(status.id) || statusId.equals(status.retweet_id) || statusId.equals(status.my_retweet_id)) { status.my_retweet_id = null; if (result != null) { status.reply_count = result.reply_count; status.retweet_count = result.retweet_count - 1; status.favorite_count = result.favorite_count; } } } } } }); } } @WorkerThread public static void updateActivity(ContentResolver cr, Uri uri, String where, String[] whereArgs, UpdateActivityAction action) { final Cursor c = cr.query(uri, Activities.COLUMNS, where, whereArgs, null); if (c == null) return; LongSparseArray<ContentValues> values = new LongSparseArray<>(); try { ParcelableActivityCursorIndices ci = new ParcelableActivityCursorIndices(c); c.moveToFirst(); while (!c.isAfterLast()) { final ParcelableActivity activity = ci.newObject(c); action.process(activity); values.put(activity._id, ParcelableActivityValuesCreator.create(activity)); c.moveToNext(); } } finally { c.close(); } String updateWhere = Expression.equalsArgs(Activities._ID).getSQL(); String[] updateWhereArgs = new String[1]; for (int i = 0, j = values.size(); i < j; i++) { updateWhereArgs[0] = String.valueOf(values.keyAt(i)); cr.update(uri, values.valueAt(i), updateWhere, updateWhereArgs); } } static void updateActivityStatus(ContentResolver resolver, UserKey accountKey, String statusId, UpdateActivityAction action) { final String activityWhere = Expression.and(Expression.equalsArgs(Activities.ACCOUNT_KEY), Expression.or( Expression.equalsArgs(Activities.STATUS_ID), Expression.equalsArgs(Activities.STATUS_RETWEET_ID))) .getSQL(); final String[] activityWhereArgs = { accountKey.toString(), statusId, statusId }; for (final Uri uri : ACTIVITIES_URIS) { updateActivity(resolver, uri, activityWhere, activityWhereArgs, action); } } public static void processTabExtras(List<Expression> expressions, List<String> expressionArgs, HomeTabExtras extras) { if (extras.isHideRetweets()) { expressions.add(Expression.equalsArgs(Statuses.IS_RETWEET)); expressionArgs.add("0"); } if (extras.isHideQuotes()) { expressions.add(Expression.equalsArgs(Statuses.IS_QUOTE)); expressionArgs.add("0"); } if (extras.isHideReplies()) { expressions.add(Expression.isNull(new Column(Statuses.IN_REPLY_TO_STATUS_ID))); } } interface FieldArrayCreator<T> { T newArray(int size); void assign(T array, int arrayIdx, Cursor cur, int colIdx); } public static int queryCount(@NonNull final Context context, @NonNull final Uri uri, @Nullable final String selection, @Nullable final String[] selectionArgs) { final ContentResolver resolver = context.getContentResolver(); final String[] projection = new String[] { SQLFunctions.COUNT() }; final Cursor cur = resolver.query(uri, projection, selection, selectionArgs, null); if (cur == null) return -1; try { if (cur.moveToFirst()) { return cur.getInt(0); } return -1; } finally { cur.close(); } } public static List<ParcelableAccount> getAccountsList(final Context context, final boolean activatedOnly) { return getAccountsList(context, activatedOnly, false); } public static List<ParcelableAccount> getAccountsList(final Context context, final boolean activatedOnly, final boolean officialKeyOnly) { if (context == null) return Collections.emptyList(); final ArrayList<ParcelableAccount> accounts = new ArrayList<>(); final String selection = activatedOnly ? Accounts.IS_ACTIVATED + " = 1" : null; final Cursor cur = context.getContentResolver().query(Accounts.CONTENT_URI, Accounts.COLUMNS, selection, null, Accounts.SORT_POSITION); if (cur == null) return accounts; final ParcelableCredentialsCursorIndices indices = new ParcelableCredentialsCursorIndices(cur); cur.moveToFirst(); while (!cur.isAfterLast()) { if (!officialKeyOnly) { accounts.add(indices.newObject(cur)); } else { final String consumerKey = cur.getString(indices.consumer_key); final String consumerSecret = cur.getString(indices.consumer_secret); if (TwitterContentUtils.isOfficialKey(context, consumerKey, consumerSecret)) { accounts.add(indices.newObject(cur)); } } cur.moveToNext(); } cur.close(); return accounts; } @Nullable public static Cursor getAccountCursor(@NonNull final Context context, final String[] columns, @NonNull final UserKey accountKey) { final ContentResolver cr = context.getContentResolver(); final String accountId = accountKey.getId(); final String accountHost = accountKey.getHost(); final String where; final String[] whereArgs; if (TextUtils.isEmpty(accountHost)) { where = Expression.or(Expression.equalsArgs(Accounts.ACCOUNT_KEY), Expression.likeRaw(new Column(Accounts.ACCOUNT_KEY), "?||\'@%\'")).getSQL(); whereArgs = new String[] { accountId, accountId }; } else { where = Expression .or(Expression.equalsArgs(Accounts.ACCOUNT_KEY), Expression.equalsArgs(Accounts.ACCOUNT_KEY)) .getSQL(); whereArgs = new String[] { String.valueOf(accountKey.toString()), accountId }; } return cr.query(Accounts.CONTENT_URI, columns, where, whereArgs, null); } @Nullable public static Cursor findAccountCursorsById(@NonNull final Context context, final String[] columns, final String... ids) { if (ids == null) return null; final ContentResolver cr = context.getContentResolver(); Expression[] expressions = new Expression[ids.length + 1]; for (int i = 0, j = ids.length; i < j; i++) { expressions[i] = Expression.likeRaw(new Column(Accounts.ACCOUNT_KEY), "?||\'@%\'"); } expressions[ids.length] = Expression.inArgs(new Column(Accounts.ACCOUNT_KEY), ids.length); final String where = Expression.or(expressions).getSQL(); final String[] whereArgs = new String[ids.length * 2]; System.arraycopy(TwidereArrayUtils.toStringArray(ids), 0, whereArgs, 0, ids.length); System.arraycopy(whereArgs, 0, whereArgs, ids.length, ids.length); return cr.query(Accounts.CONTENT_URI, columns, where, whereArgs, null); } public static String getAccountType(@NonNull final Context context, @NonNull final UserKey accountKey) { final String[] projection = { Accounts.ACCOUNT_TYPE }; final Cursor cur = getAccountCursor(context, projection, accountKey); if (cur == null) return null; try { if (cur.moveToFirst()) { return cur.getString(0); } } finally { cur.close(); } return null; } public static List<ParcelableCredentials> getCredentialsList(final Context context, final boolean activatedOnly) { return getCredentialsList(context, activatedOnly, false); } public static ParcelableCredentials[] getCredentialsArray(final Context context, final boolean activatedOnly, final boolean officialKeyOnly) { final List<ParcelableCredentials> credentialsList = getCredentialsList(context, activatedOnly, officialKeyOnly); return credentialsList.toArray(new ParcelableCredentials[credentialsList.size()]); } public static List<ParcelableCredentials> getCredentialsList(final Context context, final boolean activatedOnly, final boolean officialKeyOnly) { if (context == null) return Collections.emptyList(); final ArrayList<ParcelableCredentials> accounts = new ArrayList<>(); final String selection = activatedOnly ? Accounts.IS_ACTIVATED + " = 1" : null; final Cursor cur = context.getContentResolver().query(Accounts.CONTENT_URI, Accounts.COLUMNS, selection, null, Accounts.SORT_POSITION); if (cur == null) return accounts; ParcelableCredentialsCursorIndices indices = new ParcelableCredentialsCursorIndices(cur); cur.moveToFirst(); while (!cur.isAfterLast()) { if (officialKeyOnly) { final String consumerKey = cur.getString(indices.consumer_key); final String consumerSecret = cur.getString(indices.consumer_secret); if (TwitterContentUtils.isOfficialKey(context, consumerKey, consumerSecret)) { accounts.add(indices.newObject(cur)); } } else { accounts.add(indices.newObject(cur)); } cur.moveToNext(); } cur.close(); return accounts; } public static int getInteractionsCount(@NonNull final Context context, @Nullable final Bundle extraArgs, final UserKey[] accountIds, final long since, final String sinceColumn) { Expression extraWhere = null; String[] extraWhereArgs = null; boolean followingOnly = false; if (extraArgs != null) { final TabExtras extras = extraArgs.getParcelable(EXTRA_EXTRAS); if (extras instanceof InteractionsTabExtras) { InteractionsTabExtras ite = ((InteractionsTabExtras) extras); if (ite.isMentionsOnly()) { extraWhere = Expression.inArgs(Activities.ACTION, 3); extraWhereArgs = new String[] { Activity.Action.MENTION, Activity.Action.REPLY, Activity.Action.QUOTE }; } if (ite.isMyFollowingOnly()) { followingOnly = true; } } } return getActivitiesCount(context, Activities.AboutMe.CONTENT_URI, extraWhere, extraWhereArgs, since, sinceColumn, followingOnly, accountIds); } public interface UpdateActivityAction { void process(ParcelableActivity activity); } }