removed old shit code
This commit is contained in:
parent
eee0227074
commit
251b358918
|
@ -64,8 +64,6 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
|
|||
String KEYBOARD_SHORTCUTS_PREFERENCES_NAME = "keyboard_shortcuts_preferences";
|
||||
String ETAG_CACHE_PREFERENCES_NAME = "etag_cache";
|
||||
|
||||
String TWITTER_CONSUMER_KEY_LEGACY = "uAFVpMhBntJutfVj6abfA";
|
||||
String TWITTER_CONSUMER_SECRET_LEGACY = "JARXkJTfxo0F8MyctYy9bUmrLISjo8vXAHsZHYuk2E";
|
||||
String TWITTER_CONSUMER_KEY = "0WEJk1x6AlgtjGRhyABXw";
|
||||
String TWITTER_CONSUMER_SECRET = "gWXNqEFhO3fMkAqoIKpTdjK0MOJs68xnOky0FRdDTP8";
|
||||
|
||||
|
@ -192,7 +190,6 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
|
|||
int REQUEST_REQUEST_PERMISSIONS = 30;
|
||||
int REQUEST_PURCHASE_EXTRA_FEATURES = 41;
|
||||
|
||||
int TABLE_ID_ACCOUNTS = 1;
|
||||
int TABLE_ID_STATUSES = 12;
|
||||
int TABLE_ID_MENTIONS = 13;
|
||||
int TABLE_ID_ACTIVITIES_ABOUT_ME = 14;
|
||||
|
@ -213,15 +210,7 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
|
|||
int TABLE_ID_CACHED_STATUSES = 62;
|
||||
int TABLE_ID_CACHED_HASHTAGS = 63;
|
||||
int TABLE_ID_CACHED_RELATIONSHIPS = 64;
|
||||
int VIRTUAL_TABLE_ID_NOTIFICATIONS = 101;
|
||||
int VIRTUAL_TABLE_ID_PREFERENCES = 102;
|
||||
int VIRTUAL_TABLE_ID_ALL_PREFERENCES = 103;
|
||||
int VIRTUAL_TABLE_ID_PERMISSIONS = 104;
|
||||
int VIRTUAL_TABLE_ID_DNS = 105;
|
||||
int VIRTUAL_TABLE_ID_CACHED_IMAGES = 106;
|
||||
int VIRTUAL_TABLE_ID_CACHE_FILES = 107;
|
||||
int VIRTUAL_TABLE_ID_UNREAD_COUNTS = 108;
|
||||
int VIRTUAL_TABLE_ID_UNREAD_COUNTS_BY_TYPE = 109;
|
||||
int VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP = 121;
|
||||
int VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE = 122;
|
||||
int VIRTUAL_TABLE_ID_DRAFTS_UNSENT = 131;
|
||||
|
@ -243,8 +232,6 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
|
|||
int NOTIFICATION_ID_UPDATE_STATUS = 101;
|
||||
int NOTIFICATION_ID_SEND_DIRECT_MESSAGE = 102;
|
||||
|
||||
String ICON_SPECIAL_TYPE_CUSTOMIZE = "_customize";
|
||||
|
||||
|
||||
String METADATA_KEY_EXTENSION = "org.mariotaku.twidere.extension";
|
||||
String METADATA_KEY_EXTENSION_PERMISSIONS = "org.mariotaku.twidere.extension.permissions";
|
||||
|
@ -262,18 +249,12 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
|
|||
String PERMISSION_READ = "read";
|
||||
String PERMISSION_WRITE = "write";
|
||||
String PERMISSION_DIRECT_MESSAGES = "direct_messages";
|
||||
@Deprecated
|
||||
String PERMISSION_ACCOUNTS = "accounts";
|
||||
String PERMISSION_PREFERENCES = "preferences";
|
||||
|
||||
int TAB_CODE_HOME_TIMELINE = 1;
|
||||
int TAB_CODE_NOTIFICATIONS_TIMELINE = 2;
|
||||
int TAB_CODE_DIRECT_MESSAGES = 4;
|
||||
|
||||
int TWITTER_MAX_IMAGE_SIZE = 3145728;
|
||||
int TWITTER_MAX_IMAGE_WIDTH = 1024;
|
||||
int TWITTER_MAX_IMAGE_HEIGHT = 2048;
|
||||
|
||||
String USER_TYPE_TWITTER_COM = "twitter.com";
|
||||
String USER_TYPE_FANFOU_COM = "fanfou.com";
|
||||
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.model;
|
||||
|
||||
import org.mariotaku.commons.objectcursor.LoganSquareCursorFieldConverter;
|
||||
import org.mariotaku.library.objectcursor.annotation.CursorField;
|
||||
import org.mariotaku.library.objectcursor.annotation.CursorObject;
|
||||
import org.mariotaku.twidere.model.util.UserKeyCursorFieldConverter;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
|
||||
|
||||
@CursorObject
|
||||
@Deprecated
|
||||
public class ParcelableDirectMessage {
|
||||
|
||||
@CursorField(value = DirectMessages._ID, type = TwidereDataStore.TYPE_PRIMARY_KEY, excludeWrite = true)
|
||||
public long _id;
|
||||
|
||||
@CursorField(value = DirectMessages.ACCOUNT_KEY, converter = UserKeyCursorFieldConverter.class)
|
||||
public UserKey account_key;
|
||||
|
||||
@CursorField(DirectMessages.MESSAGE_ID)
|
||||
public String id;
|
||||
|
||||
@CursorField(DirectMessages.MESSAGE_TIMESTAMP)
|
||||
public long timestamp;
|
||||
|
||||
@CursorField(DirectMessages.SENDER_ID)
|
||||
public String sender_id;
|
||||
|
||||
@CursorField(DirectMessages.RECIPIENT_ID)
|
||||
public String recipient_id;
|
||||
|
||||
@CursorField(DirectMessages.IS_OUTGOING)
|
||||
public boolean is_outgoing;
|
||||
|
||||
@CursorField(DirectMessages.TEXT_UNESCAPED)
|
||||
public String text_unescaped;
|
||||
|
||||
@CursorField(DirectMessages.TEXT_PLAIN)
|
||||
public String text_plain;
|
||||
|
||||
@CursorField(value = DirectMessages.SPANS, converter = LoganSquareCursorFieldConverter.class)
|
||||
public SpanItem[] spans;
|
||||
|
||||
@CursorField(DirectMessages.SENDER_NAME)
|
||||
public String sender_name;
|
||||
|
||||
@CursorField(DirectMessages.RECIPIENT_NAME)
|
||||
public String recipient_name;
|
||||
|
||||
@CursorField(DirectMessages.SENDER_SCREEN_NAME)
|
||||
public String sender_screen_name;
|
||||
|
||||
@CursorField(DirectMessages.RECIPIENT_SCREEN_NAME)
|
||||
public String recipient_screen_name;
|
||||
|
||||
@CursorField(DirectMessages.SENDER_PROFILE_IMAGE_URL)
|
||||
public String sender_profile_image_url;
|
||||
|
||||
@CursorField(DirectMessages.RECIPIENT_PROFILE_IMAGE_URL)
|
||||
public String recipient_profile_image_url;
|
||||
|
||||
@CursorField(DirectMessages.CONVERSATION_ID)
|
||||
public String conversation_id;
|
||||
|
||||
@CursorField(value = DirectMessages.MEDIA_JSON, converter = LoganSquareCursorFieldConverter.class)
|
||||
public ParcelableMedia[] media;
|
||||
|
||||
}
|
|
@ -44,6 +44,8 @@ import org.mariotaku.twidere.model.util.UserKeyCursorFieldConverter;
|
|||
import org.mariotaku.twidere.provider.TwidereDataStore;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Messages;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
|
@ -159,6 +161,7 @@ public class ParcelableMessage implements Parcelable {
|
|||
|
||||
@StringDef({MessageType.TEXT, MessageType.STICKER, MessageType.CONVERSATION_CREATE,
|
||||
MessageType.JOIN_CONVERSATION, MessageType.PARTICIPANTS_LEAVE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface MessageType {
|
||||
String CONVERSATION_CREATE = "conversation_create";
|
||||
String JOIN_CONVERSATION = "join_conversation";
|
||||
|
|
|
@ -44,6 +44,8 @@ import org.mariotaku.twidere.model.util.UserKeyCursorFieldConverter;
|
|||
import org.mariotaku.twidere.provider.TwidereDataStore;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
|
@ -212,6 +214,7 @@ public class ParcelableMessageConversation implements Parcelable {
|
|||
}
|
||||
|
||||
@StringDef({ExtrasType.FANFOU, ExtrasType.TWITTER_OFFICIAL})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ExtrasType {
|
||||
String FANFOU = "fanfou";
|
||||
String TWITTER_OFFICIAL = "twitter_official";
|
||||
|
|
|
@ -33,6 +33,9 @@ import com.hannesdorfmann.parcelableplease.annotation.ParcelableNoThanks;
|
|||
import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease;
|
||||
import com.hannesdorfmann.parcelableplease.annotation.ParcelableThisPlease;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/3/21.
|
||||
*/
|
||||
|
@ -102,6 +105,7 @@ public class SpanItem implements Parcelable {
|
|||
}
|
||||
|
||||
@IntDef({SpanType.HIDE, SpanType.LINK})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface SpanType {
|
||||
int HIDE = -1;
|
||||
int LINK = 0;
|
||||
|
|
|
@ -168,21 +168,6 @@ public interface TwidereDataStore {
|
|||
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
}
|
||||
|
||||
interface CachedImages extends BaseColumns {
|
||||
String TABLE_NAME = "cached_images";
|
||||
String CONTENT_PATH = TABLE_NAME;
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
|
||||
String URL = "url";
|
||||
|
||||
String PATH = "path";
|
||||
|
||||
String[] MATRIX_COLUMNS = {URL, PATH};
|
||||
|
||||
String[] COLUMNS = {_ID, URL, PATH};
|
||||
}
|
||||
|
||||
interface CachedStatuses extends Statuses {
|
||||
String TABLE_NAME = "cached_statuses";
|
||||
String CONTENT_PATH = TABLE_NAME;
|
||||
|
@ -412,94 +397,6 @@ public interface TwidereDataStore {
|
|||
}
|
||||
}
|
||||
|
||||
interface DirectMessages extends BaseColumns, InsertedDateColumns, AccountSupportColumns {
|
||||
|
||||
String TABLE_NAME = "messages";
|
||||
String CONTENT_PATH = TABLE_NAME;
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
|
||||
String MESSAGE_ID = "message_id";
|
||||
String MESSAGE_TIMESTAMP = "message_timestamp";
|
||||
String SENDER_ID = "sender_id";
|
||||
String RECIPIENT_ID = "recipient_id";
|
||||
String CONVERSATION_ID = "conversation_id";
|
||||
|
||||
String IS_OUTGOING = "is_outgoing";
|
||||
|
||||
String TEXT_PLAIN = "text_plain";
|
||||
String TEXT_UNESCAPED = "text_unescaped";
|
||||
String SPANS = "spans";
|
||||
String SENDER_NAME = "sender_name";
|
||||
String RECIPIENT_NAME = "recipient_name";
|
||||
String SENDER_SCREEN_NAME = "sender_screen_name";
|
||||
String RECIPIENT_SCREEN_NAME = "recipient_screen_name";
|
||||
String SENDER_PROFILE_IMAGE_URL = "sender_profile_image_url";
|
||||
String RECIPIENT_PROFILE_IMAGE_URL = "recipient_profile_image_url";
|
||||
|
||||
String MEDIA_JSON = "media_json";
|
||||
|
||||
String DEFAULT_SORT_ORDER = MESSAGE_ID + " DESC";
|
||||
|
||||
interface Conversation extends DirectMessages {
|
||||
|
||||
}
|
||||
|
||||
interface ConversationEntries extends BaseColumns {
|
||||
|
||||
String TABLE_NAME = "messages_conversation_entries";
|
||||
|
||||
String CONTENT_PATH_SEGMENT = "conversation_entries";
|
||||
String CONTENT_PATH = DirectMessages.CONTENT_PATH + "/" + CONTENT_PATH_SEGMENT;
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(DirectMessages.CONTENT_URI, CONTENT_PATH_SEGMENT);
|
||||
|
||||
String MESSAGE_ID = DirectMessages.MESSAGE_ID;
|
||||
String ACCOUNT_KEY = DirectMessages.ACCOUNT_KEY;
|
||||
String IS_OUTGOING = DirectMessages.IS_OUTGOING;
|
||||
String MESSAGE_TIMESTAMP = DirectMessages.MESSAGE_TIMESTAMP;
|
||||
String NAME = "name";
|
||||
String SCREEN_NAME = "screen_name";
|
||||
String PROFILE_IMAGE_URL = "profile_image_url";
|
||||
String TEXT_UNESCAPED = "text_unescaped";
|
||||
String CONVERSATION_ID = "conversation_id";
|
||||
|
||||
int IDX__ID = 0;
|
||||
int IDX_MESSAGE_TIMESTAMP = 1;
|
||||
int IDX_MESSAGE_ID = 2;
|
||||
int IDX_ACCOUNT_KEY = 3;
|
||||
int IDX_IS_OUTGOING = 4;
|
||||
int IDX_NAME = 5;
|
||||
int IDX_SCREEN_NAME = 6;
|
||||
int IDX_PROFILE_IMAGE_URL = 7;
|
||||
int IDX_TEXT_UNESCAPED = 8;
|
||||
int IDX_CONVERSATION_ID = 9;
|
||||
}
|
||||
|
||||
interface Inbox extends DirectMessages {
|
||||
|
||||
String TABLE_NAME = "messages_inbox";
|
||||
|
||||
String CONTENT_PATH_SEGMENT = "inbox";
|
||||
String CONTENT_PATH = DirectMessages.CONTENT_PATH + "/" + CONTENT_PATH_SEGMENT;
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(DirectMessages.CONTENT_URI, CONTENT_PATH_SEGMENT);
|
||||
|
||||
}
|
||||
|
||||
interface Outbox extends DirectMessages {
|
||||
|
||||
String TABLE_NAME = "messages_outbox";
|
||||
|
||||
String CONTENT_PATH_SEGMENT = "outbox";
|
||||
String CONTENT_PATH = DirectMessages.CONTENT_PATH + "/" + CONTENT_PATH_SEGMENT;
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(DirectMessages.CONTENT_URI, CONTENT_PATH_SEGMENT);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface SearchHistory extends BaseColumns {
|
||||
|
||||
String TABLE_NAME = "search_history";
|
||||
|
@ -516,21 +413,6 @@ public interface TwidereDataStore {
|
|||
String DEFAULT_SORT_ORDER = RECENT_QUERY + " DESC";
|
||||
}
|
||||
|
||||
interface DNS extends BaseColumns {
|
||||
String TABLE_NAME = "dns";
|
||||
String CONTENT_PATH = TABLE_NAME;
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
|
||||
String HOST = "host";
|
||||
|
||||
String ADDRESS = "address";
|
||||
|
||||
String[] MATRIX_COLUMNS = {HOST, ADDRESS};
|
||||
|
||||
String[] COLUMNS = {_ID, HOST, ADDRESS};
|
||||
}
|
||||
|
||||
interface SavedSearches extends BaseColumns, AccountSupportColumns {
|
||||
|
||||
String TABLE_NAME = "saved_searches";
|
||||
|
@ -674,32 +556,6 @@ public interface TwidereDataStore {
|
|||
}
|
||||
}
|
||||
|
||||
interface Mentions extends Statuses {
|
||||
|
||||
String TABLE_NAME = "mentions";
|
||||
String CONTENT_PATH = TABLE_NAME;
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
|
||||
}
|
||||
|
||||
interface Notifications extends BaseColumns {
|
||||
|
||||
String TABLE_NAME = "notifications";
|
||||
|
||||
String CONTENT_PATH = TABLE_NAME;
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
|
||||
String ID = "id";
|
||||
|
||||
String COUNT = "count";
|
||||
|
||||
String[] MATRIX_COLUMNS = {ID, COUNT};
|
||||
|
||||
String[] COLUMNS = {_ID, ID, COUNT};
|
||||
}
|
||||
|
||||
interface Permissions extends BaseColumns {
|
||||
String TABLE_NAME = "permissions";
|
||||
String CONTENT_PATH = TABLE_NAME;
|
||||
|
@ -715,37 +571,6 @@ public interface TwidereDataStore {
|
|||
String[] COLUMNS = {_ID, PACKAGE_NAME, PERMISSION};
|
||||
}
|
||||
|
||||
interface Preferences extends BaseColumns {
|
||||
String TABLE_NAME = "preferences";
|
||||
String CONTENT_PATH = TABLE_NAME;
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
|
||||
int TYPE_INVALID = -1;
|
||||
|
||||
int TYPE_NULL = 0;
|
||||
|
||||
int TYPE_BOOLEAN = 1;
|
||||
|
||||
int TYPE_INTEGER = 2;
|
||||
|
||||
int TYPE_LONG = 3;
|
||||
|
||||
int TYPE_FLOAT = 4;
|
||||
|
||||
int TYPE_STRING = 5;
|
||||
|
||||
String KEY = "key";
|
||||
|
||||
String VALUE = "value";
|
||||
|
||||
String TYPE = "type";
|
||||
|
||||
String[] MATRIX_COLUMNS = {KEY, VALUE, TYPE};
|
||||
|
||||
String[] COLUMNS = {_ID, KEY, VALUE, TYPE};
|
||||
}
|
||||
|
||||
interface Statuses extends BaseColumns, InsertedDateColumns, AccountSupportColumns {
|
||||
|
||||
String TABLE_NAME = "statuses";
|
||||
|
@ -1035,30 +860,4 @@ public interface TwidereDataStore {
|
|||
}
|
||||
|
||||
|
||||
interface UnreadCounts extends BaseColumns {
|
||||
|
||||
String CONTENT_PATH = "unread_counts";
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
|
||||
String TAB_POSITION = "tab_position";
|
||||
|
||||
String TAB_TYPE = "tab_type";
|
||||
|
||||
String COUNT = "count";
|
||||
|
||||
String[] MATRIX_COLUMNS = {TAB_POSITION, TAB_TYPE, COUNT};
|
||||
|
||||
String[] COLUMNS = {_ID, TAB_POSITION, TAB_TYPE, COUNT};
|
||||
|
||||
interface ByType extends UnreadCounts {
|
||||
|
||||
String CONTENT_PATH_SEGMENT = "by_type";
|
||||
|
||||
String CONTENT_PATH = UnreadCounts.CONTENT_PATH + "/" + CONTENT_PATH_SEGMENT;
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(UnreadCounts.CONTENT_URI, CONTENT_PATH_SEGMENT);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,9 +32,7 @@ import android.content.pm.ApplicationInfo;
|
|||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
@ -48,9 +46,6 @@ import org.mariotaku.twidere.model.ParcelableStatus;
|
|||
import org.mariotaku.twidere.model.ParcelableUser;
|
||||
import org.mariotaku.twidere.model.ParcelableUserList;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CacheFiles;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedImages;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.DNS;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Permissions;
|
||||
import org.mariotaku.twidere.util.model.AccountDetailsUtils;
|
||||
|
||||
|
@ -61,7 +56,6 @@ import java.net.Inet4Address;
|
|||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static android.text.TextUtils.isEmpty;
|
||||
|
||||
|
@ -78,40 +72,11 @@ public final class Twidere implements TwidereConstants {
|
|||
activity.finish();
|
||||
}
|
||||
|
||||
public static ParcelFileDescriptor getCachedImageFd(final Context context, final String url) {
|
||||
if (context == null || url == null) return null;
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
final Uri.Builder builder = CachedImages.CONTENT_URI.buildUpon();
|
||||
builder.appendQueryParameter(QUERY_PARAM_URL, url);
|
||||
try {
|
||||
return resolver.openFileDescriptor(builder.build(), "r");
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ParcelFileDescriptor getCacheFileFd(final Context context, final String name) {
|
||||
if (context == null || name == null) return null;
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
final Uri.Builder builder = CacheFiles.CONTENT_URI.buildUpon();
|
||||
builder.appendQueryParameter(QUERY_PARAM_NAME, name);
|
||||
try {
|
||||
return resolver.openFileDescriptor(builder.build(), "r");
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ComposingStatus getComposingStatusFromIntent(final Intent intent) {
|
||||
if (intent == null) return null;
|
||||
return new ComposingStatus(intent);
|
||||
}
|
||||
|
||||
public static TwidereSharedPreferences getSharedPreferences(final Context context) {
|
||||
if (context == null) return null;
|
||||
return new TwidereSharedPreferences(context);
|
||||
}
|
||||
|
||||
public static ParcelableStatus getStatusFromIntent(final Intent intent) {
|
||||
if (intent == null) return null;
|
||||
return intent.getParcelableExtra(EXTRA_STATUS);
|
||||
|
@ -201,31 +166,6 @@ public final class Twidere implements TwidereConstants {
|
|||
throw new UnknownHostException("Bad address " + host + " = " + address);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static InetAddress[] resolveHost(final Context context, final String host) throws UnknownHostException {
|
||||
if (context == null || host == null) return InetAddress.getAllByName(host);
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
final Uri uri = Uri.withAppendedPath(DNS.CONTENT_URI, host);
|
||||
final Cursor cur = resolver.query(uri, DNS.MATRIX_COLUMNS, null, null, null);
|
||||
if (cur == null) return InetAddress.getAllByName(host);
|
||||
try {
|
||||
cur.moveToFirst();
|
||||
final ArrayList<InetAddress> addresses = new ArrayList<>();
|
||||
final int idxHost = cur.getColumnIndex(DNS.HOST), idxAddr = cur.getColumnIndex(DNS.ADDRESS);
|
||||
while (!cur.isAfterLast()) {
|
||||
addresses.add(fromAddressString(cur.getString(idxHost), cur.getString(idxAddr)));
|
||||
cur.moveToNext();
|
||||
}
|
||||
if (addresses.isEmpty()) {
|
||||
throw new UnknownHostException("Unknown host " + host);
|
||||
}
|
||||
return addresses.toArray(new InetAddress[addresses.size()]);
|
||||
} finally {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
@RequiresPermission(allOf = {Manifest.permission.GET_ACCOUNTS}, conditional = true)
|
||||
public static Account findByAccountKey(@NonNull Context context, @NonNull UserKey userKey) {
|
||||
|
|
|
@ -1,213 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public final class TwidereSharedPreferences implements SharedPreferences {
|
||||
|
||||
private final ContentResolver mResolver;
|
||||
|
||||
TwidereSharedPreferences(final Context context) {
|
||||
mResolver = context.getContentResolver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(final String key) {
|
||||
if (key == null) throw new NullPointerException();
|
||||
final Uri uri = Uri.withAppendedPath(TwidereDataStore.Preferences.CONTENT_URI, key);
|
||||
final Cursor cur = mResolver.query(uri, TwidereDataStore.Preferences.MATRIX_COLUMNS, null, null, null);
|
||||
if (cur == null) return false;
|
||||
try {
|
||||
return cur.getCount() > 0;
|
||||
} finally {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor edit() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ?> getAll() {
|
||||
final Cursor cur = mResolver.query(TwidereDataStore.Preferences.CONTENT_URI, TwidereDataStore.Preferences.MATRIX_COLUMNS,
|
||||
null, null, null);
|
||||
if (cur == null) return null;
|
||||
final HashMap<String, Object> map = new HashMap<>();
|
||||
final int key_idx = cur.getColumnIndex(TwidereDataStore.Preferences.KEY);
|
||||
final int value_idx = cur.getColumnIndex(TwidereDataStore.Preferences.VALUE);
|
||||
final int type_idx = cur.getColumnIndex(TwidereDataStore.Preferences.TYPE);
|
||||
cur.moveToFirst();
|
||||
while (!cur.isAfterLast()) {
|
||||
final Object value;
|
||||
switch (cur.getInt(type_idx)) {
|
||||
case TwidereDataStore.Preferences.TYPE_BOOLEAN:
|
||||
value = Boolean.valueOf(cur.getString(value_idx));
|
||||
break;
|
||||
case TwidereDataStore.Preferences.TYPE_INTEGER:
|
||||
value = cur.getInt(value_idx);
|
||||
break;
|
||||
case TwidereDataStore.Preferences.TYPE_LONG:
|
||||
value = cur.getLong(value_idx);
|
||||
break;
|
||||
case TwidereDataStore.Preferences.TYPE_FLOAT:
|
||||
value = cur.getFloat(value_idx);
|
||||
break;
|
||||
case TwidereDataStore.Preferences.TYPE_STRING:
|
||||
value = cur.getString(value_idx);
|
||||
break;
|
||||
case TwidereDataStore.Preferences.TYPE_NULL:
|
||||
value = null;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
map.put(cur.getString(key_idx), value);
|
||||
cur.moveToNext();
|
||||
}
|
||||
cur.close();
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(final String key, final boolean defValue) {
|
||||
if (key == null) throw new NullPointerException();
|
||||
try {
|
||||
return (Boolean) getPreferenceObject(key);
|
||||
} catch (final NullPointerException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(final String key, final float defValue) {
|
||||
if (key == null) throw new NullPointerException();
|
||||
try {
|
||||
return (Float) getPreferenceObject(key);
|
||||
} catch (final NullPointerException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(final String key, final int defValue) {
|
||||
if (key == null) throw new NullPointerException();
|
||||
try {
|
||||
return (Integer) getPreferenceObject(key);
|
||||
} catch (final NullPointerException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(final String key, final long defValue) {
|
||||
if (key == null) throw new NullPointerException();
|
||||
try {
|
||||
return (Long) getPreferenceObject(key);
|
||||
} catch (final NullPointerException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
public Object getPreferenceObject(final String key) {
|
||||
final Uri uri = Uri.withAppendedPath(TwidereDataStore.Preferences.CONTENT_URI, key);
|
||||
final Cursor cur = mResolver.query(uri, TwidereDataStore.Preferences.MATRIX_COLUMNS, null, null, null);
|
||||
if (cur == null) throw new NullPointerException();
|
||||
try {
|
||||
final int key_idx = cur.getColumnIndex(TwidereDataStore.Preferences.KEY);
|
||||
final int value_idx = cur.getColumnIndex(TwidereDataStore.Preferences.VALUE);
|
||||
final int type_idx = cur.getColumnIndex(TwidereDataStore.Preferences.TYPE);
|
||||
cur.moveToFirst();
|
||||
while (!cur.isAfterLast()) {
|
||||
if (!key.equals(cur.getString(key_idx))) {
|
||||
cur.moveToNext();
|
||||
continue;
|
||||
}
|
||||
final Object value;
|
||||
switch (cur.getInt(type_idx)) {
|
||||
case TwidereDataStore.Preferences.TYPE_BOOLEAN:
|
||||
value = Boolean.valueOf(cur.getString(value_idx));
|
||||
break;
|
||||
case TwidereDataStore.Preferences.TYPE_INTEGER:
|
||||
value = cur.getInt(value_idx);
|
||||
break;
|
||||
case TwidereDataStore.Preferences.TYPE_LONG:
|
||||
value = cur.getLong(value_idx);
|
||||
break;
|
||||
case TwidereDataStore.Preferences.TYPE_FLOAT:
|
||||
value = cur.getFloat(value_idx);
|
||||
break;
|
||||
case TwidereDataStore.Preferences.TYPE_STRING:
|
||||
value = cur.getString(value_idx);
|
||||
break;
|
||||
case TwidereDataStore.Preferences.TYPE_NULL:
|
||||
value = null;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
throw new NullPointerException();
|
||||
} finally {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(final String key, final String defValue) {
|
||||
if (key == null) throw new NullPointerException();
|
||||
try {
|
||||
return (String) getPreferenceObject(key);
|
||||
} catch (final NullPointerException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getStringSet(final String key, final Set<String> defValue) {
|
||||
if (key == null) throw new NullPointerException();
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOnSharedPreferenceChangeListener(final OnSharedPreferenceChangeListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterOnSharedPreferenceChangeListener(final OnSharedPreferenceChangeListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
|
@ -79,6 +79,7 @@
|
|||
<uses-permission android:name="org.mariotaku.twidere.permission.SHORTEN_STATUS"/>
|
||||
<uses-permission android:name="org.mariotaku.twidere.permission.UPLOAD_MEDIA"/>
|
||||
<uses-permission android:name="org.mariotaku.twidere.permission.SYNC_TIMELINE"/>
|
||||
<uses-permission android:name="org.mariotaku.twidere.permission.ACCESS_DATABASE"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_PHONE_STATE"
|
||||
tools:node="remove"/>
|
||||
|
@ -87,6 +88,10 @@
|
|||
android:name="org.mariotaku.twidere.permission.PERMISSION_GROUP"
|
||||
android:label="@string/app_name"/>
|
||||
|
||||
<permission
|
||||
android:name="org.mariotaku.twidere.permission.ACCESS_DATABASE"
|
||||
android:permissionGroup="org.mariotaku.twidere.permission.PERMISSION_GROUP"
|
||||
android:protectionLevel="signature"/>
|
||||
<permission
|
||||
android:name="org.mariotaku.twidere.permission.SHORTEN_STATUS"
|
||||
android:description="@string/permission_description_shorten_status"
|
||||
|
@ -603,9 +608,8 @@
|
|||
android:name=".provider.TwidereDataProvider"
|
||||
android:authorities="twidere"
|
||||
android:exported="true"
|
||||
android:grantUriPermissions="true"
|
||||
android:label="@string/label_data_provider"
|
||||
tools:ignore="ExportedContentProvider"/>
|
||||
android:permission="org.mariotaku.twidere.permission.ACCESS_DATABASE"/>
|
||||
<provider
|
||||
android:name=".provider.RecentSearchProvider"
|
||||
android:authorities="org.mariotaku.twidere.provider.SearchRecentSuggestions"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 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.task;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.v4.util.SimpleArrayMap;
|
||||
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.util.TwitterWrapper;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/2/10.
|
||||
*/
|
||||
public final class RemoveUnreadCountsTask extends AsyncTask<Object, Object, Integer> {
|
||||
private final Context context;
|
||||
private final int position;
|
||||
private final SimpleArrayMap<UserKey, Set<String>> counts;
|
||||
|
||||
public RemoveUnreadCountsTask(Context context, final int position, final SimpleArrayMap<UserKey, Set<String>> counts) {
|
||||
this.context = context;
|
||||
this.position = position;
|
||||
this.counts = counts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(final Object... params) {
|
||||
return TwitterWrapper.removeUnreadCounts(context, position, counts);
|
||||
}
|
||||
|
||||
}
|
|
@ -21,7 +21,7 @@ import org.mariotaku.microblog.library.twitter.model.User;
|
|||
import org.mariotaku.twidere.model.ParcelableStatus;
|
||||
import org.mariotaku.twidere.model.SpanItem;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Filters;
|
||||
import org.mariotaku.twidere.util.database.FilterQueryBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -46,83 +46,25 @@ public class InternalTwitterContentUtils {
|
|||
final String textPlain, final String quotedTextPlain,
|
||||
final SpanItem[] spans, final SpanItem[] quotedSpans,
|
||||
final String source, final String quotedSource,
|
||||
final UserKey retweetedById, final UserKey quotedUserId) {
|
||||
final UserKey retweetedByKey, final UserKey quotedUserKey) {
|
||||
return isFiltered(database, userKey, textPlain, quotedTextPlain, spans, quotedSpans, source,
|
||||
quotedSource, retweetedById, quotedUserId, true);
|
||||
quotedSource, retweetedByKey, quotedUserKey, true);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isFiltered(final SQLiteDatabase database, final UserKey userKey,
|
||||
final String textPlain, final String quotedTextPlain,
|
||||
final SpanItem[] spans, final SpanItem[] quotedSpans,
|
||||
final String source, final String quotedSource,
|
||||
final UserKey retweetedByKey, final UserKey quotedUserKey,
|
||||
final boolean filterRts) {
|
||||
if (database == null) return false;
|
||||
if (textPlain == null && spans == null && userKey == null && source == null)
|
||||
return false;
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final List<String> selectionArgs = new ArrayList<>();
|
||||
builder.append("SELECT ");
|
||||
if (textPlain != null) {
|
||||
selectionArgs.add(textPlain);
|
||||
addTextPlainStatement(builder);
|
||||
}
|
||||
if (quotedTextPlain != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ");
|
||||
}
|
||||
selectionArgs.add(quotedTextPlain);
|
||||
addTextPlainStatement(builder);
|
||||
}
|
||||
if (spans != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ");
|
||||
}
|
||||
addSpansStatement(spans, builder, selectionArgs);
|
||||
}
|
||||
if (quotedSpans != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ");
|
||||
}
|
||||
addSpansStatement(quotedSpans, builder, selectionArgs);
|
||||
}
|
||||
if (userKey != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ");
|
||||
}
|
||||
selectionArgs.add(String.valueOf(userKey));
|
||||
createUserKeyStatement(builder);
|
||||
}
|
||||
if (retweetedByKey != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ");
|
||||
}
|
||||
selectionArgs.add(String.valueOf(retweetedByKey));
|
||||
createUserKeyStatement(builder);
|
||||
}
|
||||
if (quotedUserKey != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ");
|
||||
}
|
||||
selectionArgs.add(String.valueOf(quotedUserKey));
|
||||
createUserKeyStatement(builder);
|
||||
}
|
||||
if (source != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ");
|
||||
}
|
||||
selectionArgs.add(source);
|
||||
appendSourceStatement(builder);
|
||||
}
|
||||
if (quotedSource != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ");
|
||||
}
|
||||
selectionArgs.add(quotedSource);
|
||||
appendSourceStatement(builder);
|
||||
}
|
||||
final Cursor cur = database.rawQuery(builder.toString(),
|
||||
selectionArgs.toArray(new String[selectionArgs.size()]));
|
||||
|
||||
final Pair<String, String[]> query = FilterQueryBuilder.INSTANCE.isFilteredQuery(userKey,
|
||||
textPlain, quotedTextPlain, spans, quotedSpans, source, quotedSource, retweetedByKey,
|
||||
quotedUserKey, filterRts);
|
||||
final Cursor cur = database.rawQuery(query.getFirst(), query.getSecond());
|
||||
if (cur == null) return false;
|
||||
try {
|
||||
return cur.moveToFirst() && cur.getInt(0) != 0;
|
||||
|
@ -131,38 +73,6 @@ public class InternalTwitterContentUtils {
|
|||
}
|
||||
}
|
||||
|
||||
static void addTextPlainStatement(StringBuilder builder) {
|
||||
builder.append("(SELECT 1 IN (SELECT ? LIKE '%'||").append(Filters.Keywords.TABLE_NAME).append(".").append(Filters.VALUE).append("||'%' FROM ").append(Filters.Keywords.TABLE_NAME).append("))");
|
||||
}
|
||||
|
||||
static void addSpansStatement(SpanItem[] spans, StringBuilder builder, List<String> selectionArgs) {
|
||||
StringBuilder spansFlat = new StringBuilder();
|
||||
for (SpanItem span : spans) {
|
||||
spansFlat.append(span.link);
|
||||
spansFlat.append(' ');
|
||||
}
|
||||
selectionArgs.add(spansFlat.toString());
|
||||
builder.append("(SELECT 1 IN (SELECT ? LIKE '%'||")
|
||||
.append(Filters.Links.TABLE_NAME)
|
||||
.append(".")
|
||||
.append(Filters.VALUE)
|
||||
.append("||'%' FROM ")
|
||||
.append(Filters.Links.TABLE_NAME)
|
||||
.append("))");
|
||||
}
|
||||
|
||||
static void createUserKeyStatement(StringBuilder builder) {
|
||||
builder.append("(SELECT ").append("?").append(" IN (SELECT ").append(Filters.Users.USER_KEY).append(" FROM ").append(Filters.Users.TABLE_NAME).append("))");
|
||||
}
|
||||
|
||||
static void appendSourceStatement(StringBuilder builder) {
|
||||
builder.append("(SELECT 1 IN (SELECT ? LIKE '%>'||")
|
||||
.append(Filters.Sources.TABLE_NAME).append(".")
|
||||
.append(Filters.VALUE).append("||'</a>%' FROM ")
|
||||
.append(Filters.Sources.TABLE_NAME)
|
||||
.append("))");
|
||||
}
|
||||
|
||||
public static boolean isFiltered(final SQLiteDatabase database, final ParcelableStatus status,
|
||||
final boolean filterRTs) {
|
||||
if (database == null || status == null) return false;
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import org.mariotaku.sqliteqb.library.Columns;
|
||||
import org.mariotaku.sqliteqb.library.Columns.Column;
|
||||
|
@ -27,7 +26,6 @@ import org.mariotaku.sqliteqb.library.Expression;
|
|||
import org.mariotaku.sqliteqb.library.Join;
|
||||
import org.mariotaku.sqliteqb.library.Join.Operation;
|
||||
import org.mariotaku.sqliteqb.library.OrderBy;
|
||||
import org.mariotaku.sqliteqb.library.SQLQueryBuilder;
|
||||
import org.mariotaku.sqliteqb.library.Selectable;
|
||||
import org.mariotaku.sqliteqb.library.Table;
|
||||
import org.mariotaku.sqliteqb.library.Tables;
|
||||
|
@ -35,14 +33,11 @@ import org.mariotaku.sqliteqb.library.query.SQLSelectQuery;
|
|||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedRelationships;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Conversation;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.ConversationEntries;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Inbox;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Outbox;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import kotlin.Pair;
|
||||
|
||||
public class TwidereQueryBuilder {
|
||||
|
||||
public static final class CachedUsersQueryBuilder {
|
||||
|
@ -51,19 +46,19 @@ public class TwidereQueryBuilder {
|
|||
}
|
||||
|
||||
public static Pair<SQLSelectQuery, String[]> withRelationship(final String[] projection,
|
||||
final String selection,
|
||||
final String[] selectionArgs,
|
||||
final String sortOrder,
|
||||
final UserKey accountKey) {
|
||||
final String selection,
|
||||
final String[] selectionArgs,
|
||||
final String sortOrder,
|
||||
final UserKey accountKey) {
|
||||
return withRelationship(Utils.getColumnsFromProjection(projection), selection,
|
||||
selectionArgs, sortOrder, accountKey);
|
||||
}
|
||||
|
||||
public static Pair<SQLSelectQuery, String[]> withRelationship(final Selectable select,
|
||||
final String selection,
|
||||
final String[] selectionArgs,
|
||||
final String sortOrder,
|
||||
final UserKey accountKey) {
|
||||
final String selection,
|
||||
final String[] selectionArgs,
|
||||
final String sortOrder,
|
||||
final UserKey accountKey) {
|
||||
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
|
||||
qb.select(select).from(new Tables(CachedUsers.TABLE_NAME));
|
||||
final Column relationshipsUserId = new Column(new Table(CachedRelationships.TABLE_NAME),
|
||||
|
@ -99,15 +94,15 @@ public class TwidereQueryBuilder {
|
|||
}
|
||||
final String[] mergedArgs = new String[TwidereArrayUtils.arraysLength(accountKeyArgs, selectionArgs)];
|
||||
TwidereArrayUtils.mergeArray(mergedArgs, accountKeyArgs, selectionArgs);
|
||||
return Pair.create(qb.build(), mergedArgs);
|
||||
return new Pair<>(qb.build(), mergedArgs);
|
||||
}
|
||||
|
||||
public static Pair<SQLSelectQuery, String[]> withScore(final String[] projection,
|
||||
final String selection,
|
||||
final String[] selectionArgs,
|
||||
final String sortOrder,
|
||||
final UserKey accountKey,
|
||||
final int limit) {
|
||||
final String selection,
|
||||
final String[] selectionArgs,
|
||||
final String sortOrder,
|
||||
final UserKey accountKey,
|
||||
final int limit) {
|
||||
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
|
||||
final Selectable select = Utils.getColumnsFromProjection(projection);
|
||||
final Column[] columns = new Column[CachedUsers.COLUMNS.length + 1];
|
||||
|
@ -127,9 +122,9 @@ public class TwidereQueryBuilder {
|
|||
qb.select(select);
|
||||
final Pair<SQLSelectQuery, String[]> pair = withRelationship(new Columns(columns), null,
|
||||
null, null, accountKey);
|
||||
qb.from(pair.first);
|
||||
final String[] mergedArgs = new String[TwidereArrayUtils.arraysLength(pair.second, selectionArgs)];
|
||||
TwidereArrayUtils.mergeArray(mergedArgs, pair.second, selectionArgs);
|
||||
qb.from(pair.getFirst());
|
||||
final String[] mergedArgs = new String[TwidereArrayUtils.arraysLength(pair.getSecond(), selectionArgs)];
|
||||
TwidereArrayUtils.mergeArray(mergedArgs, pair.getSecond(), selectionArgs);
|
||||
if (selection != null) {
|
||||
qb.where(new Expression(selection));
|
||||
}
|
||||
|
@ -139,7 +134,7 @@ public class TwidereQueryBuilder {
|
|||
if (limit > 0) {
|
||||
qb.limit(limit);
|
||||
}
|
||||
return Pair.create(qb.build(), mergedArgs);
|
||||
return new Pair<>(qb.build(), mergedArgs);
|
||||
}
|
||||
|
||||
private static Object[] valueOrZero(String... columns) {
|
||||
|
|
|
@ -23,7 +23,6 @@ import android.content.ContentResolver;
|
|||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.util.SimpleArrayMap;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
|
@ -41,34 +40,16 @@ import org.mariotaku.twidere.annotation.AccountType;
|
|||
import org.mariotaku.twidere.model.ListResponse;
|
||||
import org.mariotaku.twidere.model.SingleResponse;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Notifications;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class TwitterWrapper implements Constants {
|
||||
|
||||
public static int clearNotification(final Context context, final int notificationType, final UserKey accountId) {
|
||||
final Uri.Builder builder = Notifications.CONTENT_URI.buildUpon();
|
||||
builder.appendPath(String.valueOf(notificationType));
|
||||
if (accountId != null) {
|
||||
builder.appendPath(String.valueOf(accountId));
|
||||
}
|
||||
return context.getContentResolver().delete(builder.build(), null, null);
|
||||
}
|
||||
|
||||
public static int clearUnreadCount(final Context context, final int position) {
|
||||
if (context == null || position < 0) return 0;
|
||||
final Uri uri = UnreadCounts.CONTENT_URI.buildUpon().appendPath(String.valueOf(position)).build();
|
||||
return context.getContentResolver().delete(uri, null, null);
|
||||
}
|
||||
|
||||
public static SingleResponse<Boolean> deleteProfileBannerImage(final Context context,
|
||||
final UserKey accountKey) {
|
||||
final UserKey accountKey) {
|
||||
final MicroBlog twitter = MicroBlogAPIFactory.getInstance(context, accountKey);
|
||||
if (twitter == null) return SingleResponse.Companion.getInstance(false);
|
||||
try {
|
||||
|
@ -79,38 +60,9 @@ public class TwitterWrapper implements Constants {
|
|||
}
|
||||
}
|
||||
|
||||
public static int removeUnreadCounts(final Context context, final int position, final long account_id,
|
||||
final long... statusIds) {
|
||||
if (context == null || position < 0 || statusIds == null || statusIds.length == 0)
|
||||
return 0;
|
||||
int result = 0;
|
||||
final Uri.Builder builder = UnreadCounts.CONTENT_URI.buildUpon();
|
||||
builder.appendPath(String.valueOf(position));
|
||||
builder.appendPath(String.valueOf(account_id));
|
||||
builder.appendPath(TwidereArrayUtils.toString(statusIds, ',', false));
|
||||
result += context.getContentResolver().delete(builder.build(), null, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int removeUnreadCounts(final Context context, final int position,
|
||||
final SimpleArrayMap<UserKey, Set<String>> counts) {
|
||||
if (context == null || position < 0 || counts == null) return 0;
|
||||
int result = 0;
|
||||
for (int i = 0, j = counts.size(); i < j; i++) {
|
||||
final UserKey key = counts.keyAt(i);
|
||||
final Set<String> value = counts.valueAt(i);
|
||||
final Uri.Builder builder = UnreadCounts.CONTENT_URI.buildUpon();
|
||||
builder.appendPath(String.valueOf(position));
|
||||
builder.appendPath(String.valueOf(key));
|
||||
builder.appendPath(CollectionUtils.toString(value, ',', false));
|
||||
result += context.getContentResolver().delete(builder.build(), null, null);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static User showUser(@NonNull final MicroBlog twitter, final String id, final String screenName,
|
||||
final String accountType) throws MicroBlogException {
|
||||
final String accountType) throws MicroBlogException {
|
||||
if (id != null) {
|
||||
if (AccountType.FANFOU.equals(accountType)) {
|
||||
return twitter.showFanfouUser(id);
|
||||
|
@ -127,7 +79,7 @@ public class TwitterWrapper implements Constants {
|
|||
|
||||
@NonNull
|
||||
public static User showUserAlternative(@NonNull final MicroBlog twitter, final String id,
|
||||
final String screenName)
|
||||
final String screenName)
|
||||
throws MicroBlogException {
|
||||
final String searchScreenName;
|
||||
if (screenName != null) {
|
||||
|
@ -161,7 +113,7 @@ public class TwitterWrapper implements Constants {
|
|||
|
||||
@NonNull
|
||||
public static User tryShowUser(@NonNull final MicroBlog twitter, final String id, final String screenName,
|
||||
String accountType)
|
||||
String accountType)
|
||||
throws MicroBlogException {
|
||||
try {
|
||||
return showUser(twitter, id, screenName, accountType);
|
||||
|
@ -175,9 +127,9 @@ public class TwitterWrapper implements Constants {
|
|||
}
|
||||
|
||||
public static void updateProfileBannerImage(@NonNull final Context context,
|
||||
@NonNull final MicroBlog twitter,
|
||||
@NonNull final Uri imageUri,
|
||||
final boolean deleteImage)
|
||||
@NonNull final MicroBlog twitter,
|
||||
@NonNull final Uri imageUri,
|
||||
final boolean deleteImage)
|
||||
throws IOException, MicroBlogException {
|
||||
FileBody fileBody = null;
|
||||
try {
|
||||
|
@ -192,10 +144,10 @@ public class TwitterWrapper implements Constants {
|
|||
}
|
||||
|
||||
public static void updateProfileBackgroundImage(@NonNull final Context context,
|
||||
@NonNull final MicroBlog twitter,
|
||||
@NonNull final Uri imageUri,
|
||||
final boolean tile,
|
||||
final boolean deleteImage)
|
||||
@NonNull final MicroBlog twitter,
|
||||
@NonNull final Uri imageUri,
|
||||
final boolean tile,
|
||||
final boolean deleteImage)
|
||||
throws IOException, MicroBlogException {
|
||||
FileBody fileBody = null;
|
||||
try {
|
||||
|
@ -210,7 +162,7 @@ public class TwitterWrapper implements Constants {
|
|||
}
|
||||
|
||||
public static User updateProfileImage(@NonNull final Context context, @NonNull final MicroBlog twitter,
|
||||
@NonNull final Uri imageUri, final boolean deleteImage)
|
||||
@NonNull final Uri imageUri, final boolean deleteImage)
|
||||
throws IOException, MicroBlogException {
|
||||
FileBody fileBody = null;
|
||||
try {
|
||||
|
@ -262,12 +214,12 @@ public class TwitterWrapper implements Constants {
|
|||
}
|
||||
|
||||
public MessageListResponse(final UserKey accountKey, final String maxId, final String sinceId,
|
||||
final List<DirectMessage> list) {
|
||||
final List<DirectMessage> list) {
|
||||
this(accountKey, maxId, sinceId, list, null);
|
||||
}
|
||||
|
||||
MessageListResponse(final UserKey accountKey, final String maxId, final String sinceId,
|
||||
final List<DirectMessage> list, final Exception exception) {
|
||||
final List<DirectMessage> list, final Exception exception) {
|
||||
super(accountKey, maxId, sinceId, list, exception);
|
||||
}
|
||||
|
||||
|
@ -286,12 +238,12 @@ public class TwitterWrapper implements Constants {
|
|||
}
|
||||
|
||||
public StatusListResponse(final UserKey accountKey, final String maxId, final String sinceId,
|
||||
final List<Status> list, final boolean truncated) {
|
||||
final List<Status> list, final boolean truncated) {
|
||||
this(accountKey, maxId, sinceId, list, truncated, null);
|
||||
}
|
||||
|
||||
StatusListResponse(final UserKey accountKey, final String maxId, final String sinceId, final List<Status> list,
|
||||
final boolean truncated, final Exception exception) {
|
||||
final boolean truncated, final Exception exception) {
|
||||
super(accountKey, maxId, sinceId, list, exception);
|
||||
this.truncated = truncated;
|
||||
}
|
||||
|
@ -305,17 +257,17 @@ public class TwitterWrapper implements Constants {
|
|||
public final String sinceId;
|
||||
|
||||
public TwitterListResponse(final UserKey accountKey,
|
||||
final Exception exception) {
|
||||
final Exception exception) {
|
||||
this(accountKey, null, null, null, exception);
|
||||
}
|
||||
|
||||
public TwitterListResponse(final UserKey accountKey, final String maxId,
|
||||
final String sinceId, final List<Data> list) {
|
||||
final String sinceId, final List<Data> list) {
|
||||
this(accountKey, maxId, sinceId, list, null);
|
||||
}
|
||||
|
||||
TwitterListResponse(final UserKey accountKey, final String maxId, final String sinceId,
|
||||
final List<Data> list, final Exception exception) {
|
||||
final List<Data> list, final Exception exception) {
|
||||
super(list, exception);
|
||||
this.accountKey = accountKey;
|
||||
this.maxId = maxId;
|
||||
|
|
|
@ -99,7 +99,7 @@ import org.mariotaku.twidere.model.PebbleMessage;
|
|||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.model.util.AccountUtils;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Messages;
|
||||
import org.mariotaku.twidere.view.TabPagerIndicator;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
@ -509,7 +509,7 @@ public final class Utils implements Constants {
|
|||
switch (tableId) {
|
||||
case TABLE_ID_MESSAGES:
|
||||
case TABLE_ID_MESSAGES_CONVERSATIONS:
|
||||
return DirectMessages.CONTENT_URI;
|
||||
return Messages.CONTENT_URI;
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 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.extension
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ContentResolver
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore
|
||||
|
||||
@SuppressLint("Recycle")
|
||||
fun ContentResolver.rawQuery(sql: String, selectionArgs: Array<String>?): Cursor {
|
||||
val rawUri = Uri.withAppendedPath(TwidereDataStore.CONTENT_URI_RAW_QUERY, sql)
|
||||
return query(rawUri, selectionArgs, null, null, null)
|
||||
}
|
||||
|
|
@ -26,7 +26,10 @@ import org.mariotaku.twidere.TwidereConstants.TIMELINE_POSITIONS_PREFERENCES_NAM
|
|||
import org.mariotaku.twidere.provider.TwidereDataStore.*
|
||||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
|
||||
class ClearDatabasesPreference(context: Context, attrs: AttributeSet? = null) : AsyncTaskPreference(context, attrs, R.attr.preferenceStyle) {
|
||||
class ClearDatabasesPreference(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null
|
||||
) : AsyncTaskPreference(context, attrs, R.attr.preferenceStyle) {
|
||||
|
||||
override fun doInBackground() {
|
||||
val context = context ?: return
|
||||
|
@ -45,9 +48,8 @@ class ClearDatabasesPreference(context: Context, attrs: AttributeSet? = null) :
|
|||
}
|
||||
resolver.delete(Activities.AboutMe.CONTENT_URI, null, null)
|
||||
resolver.delete(Activities.ByFriends.CONTENT_URI, null, null)
|
||||
resolver.delete(Notifications.CONTENT_URI, null, null)
|
||||
resolver.delete(UnreadCounts.CONTENT_URI, null, null)
|
||||
resolver.delete(SavedSearches.CONTENT_URI, null, null)
|
||||
// TODO clear all notifications
|
||||
|
||||
val prefs = context.getSharedPreferences(TIMELINE_POSITIONS_PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
val editor = prefs.edit()
|
||||
|
|
|
@ -0,0 +1,573 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 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.provider
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.content.Intent
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.database.SQLException
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteFullException
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Process
|
||||
import android.provider.BaseColumns
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.support.v4.text.BidiFormatter
|
||||
import com.nostra13.universalimageloader.core.ImageLoader
|
||||
import com.squareup.otto.Bus
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
import org.mariotaku.ktextension.isNullOrEmpty
|
||||
import org.mariotaku.ktextension.toNulls
|
||||
import org.mariotaku.sqliteqb.library.Columns.Column
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.sqliteqb.library.RawItemArray
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.*
|
||||
import org.mariotaku.twidere.annotation.CustomTabType
|
||||
import org.mariotaku.twidere.annotation.ReadPositionTag
|
||||
import org.mariotaku.twidere.app.TwidereApplication
|
||||
import org.mariotaku.twidere.model.AccountPreferences
|
||||
import org.mariotaku.twidere.model.Draft
|
||||
import org.mariotaku.twidere.model.DraftCursorIndices
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.event.UnreadCountUpdatedEvent
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.*
|
||||
import org.mariotaku.twidere.service.LengthyOperationsService
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.SQLiteDatabaseWrapper.LazyLoadCallback
|
||||
import org.mariotaku.twidere.util.TwidereQueryBuilder.CachedUsersQueryBuilder
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
|
||||
import org.mariotaku.twidere.util.database.SuggestionsCursorCreator
|
||||
import org.mariotaku.twidere.util.net.TwidereDns
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.Executors
|
||||
import javax.inject.Inject
|
||||
|
||||
class TwidereDataProvider : ContentProvider(), LazyLoadCallback {
|
||||
@Inject
|
||||
lateinit internal var readStateManager: ReadStateManager
|
||||
@Inject
|
||||
lateinit internal var twitterWrapper: AsyncTwitterWrapper
|
||||
@Inject
|
||||
lateinit internal var mediaLoader: ImageLoader
|
||||
@Inject
|
||||
lateinit internal var notificationManager: NotificationManagerWrapper
|
||||
@Inject
|
||||
lateinit internal var preferences: SharedPreferencesWrapper
|
||||
@Inject
|
||||
lateinit internal var dns: TwidereDns
|
||||
@Inject
|
||||
lateinit internal var bus: Bus
|
||||
@Inject
|
||||
lateinit internal var userColorNameManager: UserColorNameManager
|
||||
@Inject
|
||||
lateinit internal var bidiFormatter: BidiFormatter
|
||||
@Inject
|
||||
lateinit internal var permissionsManager: PermissionsManager
|
||||
@Inject
|
||||
lateinit internal var contentNotificationManager: ContentNotificationManager
|
||||
|
||||
private lateinit var databaseWrapper: SQLiteDatabaseWrapper
|
||||
|
||||
private var handler: Handler? = null
|
||||
private var backgroundExecutor: Executor? = null
|
||||
|
||||
override fun bulkInsert(uri: Uri, valuesArray: Array<ContentValues>): Int {
|
||||
try {
|
||||
return bulkInsertInternal(uri, valuesArray)
|
||||
} catch (e: SQLException) {
|
||||
if (handleSQLException(e)) {
|
||||
try {
|
||||
return bulkInsertInternal(uri, valuesArray)
|
||||
} catch (e1: SQLException) {
|
||||
throw IllegalStateException(e1)
|
||||
}
|
||||
|
||||
}
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun handleSQLException(e: SQLException): Boolean {
|
||||
try {
|
||||
if (e is SQLiteFullException) {
|
||||
// Drop cached databases
|
||||
databaseWrapper.delete(CachedUsers.TABLE_NAME, null, null)
|
||||
databaseWrapper.delete(CachedStatuses.TABLE_NAME, null, null)
|
||||
databaseWrapper.delete(CachedHashtags.TABLE_NAME, null, null)
|
||||
databaseWrapper.execSQL("VACUUM")
|
||||
return true
|
||||
}
|
||||
} catch (ee: SQLException) {
|
||||
throw IllegalStateException(ee)
|
||||
}
|
||||
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
|
||||
private fun bulkInsertInternal(uri: Uri, valuesArray: Array<ContentValues>): Int {
|
||||
val tableId = DataStoreUtils.getTableId(uri)
|
||||
val table = DataStoreUtils.getTableNameById(tableId)
|
||||
var result = 0
|
||||
val newIds = LongArray(valuesArray.size)
|
||||
if (table != null && valuesArray.isNotEmpty()) {
|
||||
databaseWrapper.beginTransaction()
|
||||
if (tableId == TABLE_ID_CACHED_USERS) {
|
||||
for (values in valuesArray) {
|
||||
val where = Expression.equalsArgs(CachedUsers.USER_KEY)
|
||||
databaseWrapper.update(table, values, where.sql, arrayOf(values.getAsString(CachedUsers.USER_KEY)))
|
||||
newIds[result++] = databaseWrapper.insertWithOnConflict(table, null,
|
||||
values, SQLiteDatabase.CONFLICT_REPLACE)
|
||||
}
|
||||
} else if (tableId == TABLE_ID_SEARCH_HISTORY) {
|
||||
for (values in valuesArray) {
|
||||
values.put(SearchHistory.RECENT_QUERY, System.currentTimeMillis())
|
||||
val where = Expression.equalsArgs(SearchHistory.QUERY)
|
||||
val args = arrayOf(values.getAsString(SearchHistory.QUERY))
|
||||
databaseWrapper.update(table, values, where.sql, args)
|
||||
newIds[result++] = databaseWrapper.insertWithOnConflict(table, null,
|
||||
values, SQLiteDatabase.CONFLICT_IGNORE)
|
||||
}
|
||||
} else {
|
||||
val conflictAlgorithm = getConflictAlgorithm(tableId)
|
||||
if (conflictAlgorithm != SQLiteDatabase.CONFLICT_NONE) {
|
||||
for (values in valuesArray) {
|
||||
newIds[result++] = databaseWrapper.insertWithOnConflict(table, null,
|
||||
values, conflictAlgorithm)
|
||||
}
|
||||
} else {
|
||||
for (values in valuesArray) {
|
||||
newIds[result++] = databaseWrapper.insert(table, null, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
databaseWrapper.setTransactionSuccessful()
|
||||
databaseWrapper.endTransaction()
|
||||
}
|
||||
if (result > 0) {
|
||||
onDatabaseUpdated(tableId, uri)
|
||||
}
|
||||
onNewItemsInserted(uri, tableId, valuesArray.toNulls())
|
||||
return result
|
||||
}
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
|
||||
try {
|
||||
return deleteInternal(uri, selection, selectionArgs)
|
||||
} catch (e: SQLException) {
|
||||
if (handleSQLException(e)) {
|
||||
try {
|
||||
return deleteInternal(uri, selection, selectionArgs)
|
||||
} catch (e1: SQLException) {
|
||||
throw IllegalStateException(e1)
|
||||
}
|
||||
|
||||
}
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun deleteInternal(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
|
||||
val tableId = DataStoreUtils.getTableId(uri)
|
||||
val table = DataStoreUtils.getTableNameById(tableId) ?: return 0
|
||||
val result = databaseWrapper.delete(table, selection, selectionArgs)
|
||||
if (result > 0) {
|
||||
onDatabaseUpdated(tableId, uri)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun getType(uri: Uri): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? {
|
||||
try {
|
||||
return insertInternal(uri, values)
|
||||
} catch (e: SQLException) {
|
||||
if (handleSQLException(e)) {
|
||||
try {
|
||||
return insertInternal(uri, values)
|
||||
} catch (e1: SQLException) {
|
||||
throw IllegalStateException(e1)
|
||||
}
|
||||
|
||||
}
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun insertInternal(uri: Uri, values: ContentValues?): Uri? {
|
||||
val tableId = DataStoreUtils.getTableId(uri)
|
||||
val table = DataStoreUtils.getTableNameById(tableId)
|
||||
var rowId: Long = -1
|
||||
when (tableId) {
|
||||
TABLE_ID_CACHED_USERS -> {
|
||||
if (values != null) {
|
||||
val where = Expression.equalsArgs(CachedUsers.USER_KEY)
|
||||
val whereArgs = arrayOf(values.getAsString(CachedUsers.USER_KEY))
|
||||
databaseWrapper.update(table, values, where.sql, whereArgs)
|
||||
}
|
||||
rowId = databaseWrapper.insertWithOnConflict(table, null, values,
|
||||
SQLiteDatabase.CONFLICT_IGNORE)
|
||||
}
|
||||
TABLE_ID_SEARCH_HISTORY -> {
|
||||
if (values != null) {
|
||||
values.put(SearchHistory.RECENT_QUERY, System.currentTimeMillis())
|
||||
val where = Expression.equalsArgs(SearchHistory.QUERY)
|
||||
val args = arrayOf(values.getAsString(SearchHistory.QUERY))
|
||||
databaseWrapper.update(table, values, where.sql, args)
|
||||
}
|
||||
rowId = databaseWrapper.insertWithOnConflict(table, null, values,
|
||||
SQLiteDatabase.CONFLICT_IGNORE)
|
||||
}
|
||||
TABLE_ID_CACHED_RELATIONSHIPS -> {
|
||||
var updated = false
|
||||
if (values != null) {
|
||||
val accountKey = values.getAsString(CachedRelationships.ACCOUNT_KEY)
|
||||
val userId = values.getAsString(CachedRelationships.USER_KEY)
|
||||
val where = Expression.and(Expression.equalsArgs(CachedRelationships.ACCOUNT_KEY),
|
||||
Expression.equalsArgs(CachedRelationships.USER_KEY))
|
||||
if (databaseWrapper.update(table, values, where.sql, arrayOf(accountKey, userId)) > 0) {
|
||||
val projection = arrayOf(CachedRelationships._ID)
|
||||
val c = databaseWrapper.query(table, projection, where.sql, null,
|
||||
null, null, null)
|
||||
if (c.moveToFirst()) {
|
||||
rowId = c.getLong(0)
|
||||
}
|
||||
c.close()
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
if (!updated) {
|
||||
rowId = databaseWrapper.insertWithOnConflict(table, null, values,
|
||||
SQLiteDatabase.CONFLICT_IGNORE)
|
||||
}
|
||||
}
|
||||
VIRTUAL_TABLE_ID_DRAFTS_NOTIFICATIONS -> {
|
||||
rowId = showDraftNotification(values)
|
||||
}
|
||||
else -> {
|
||||
val conflictAlgorithm = getConflictAlgorithm(tableId)
|
||||
if (conflictAlgorithm != SQLiteDatabase.CONFLICT_NONE) {
|
||||
rowId = databaseWrapper.insertWithOnConflict(table, null, values,
|
||||
conflictAlgorithm)
|
||||
} else if (table != null) {
|
||||
rowId = databaseWrapper.insert(table, null, values)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
onDatabaseUpdated(tableId, uri)
|
||||
onNewItemsInserted(uri, tableId, arrayOf(values))
|
||||
return Uri.withAppendedPath(uri, rowId.toString())
|
||||
}
|
||||
|
||||
private fun showDraftNotification(values: ContentValues?): Long {
|
||||
val context = context
|
||||
if (values == null || context == null) return -1
|
||||
val draftId = values.getAsLong(BaseColumns._ID) ?: return -1
|
||||
val where = Expression.equals(Drafts._ID, draftId)
|
||||
val c = context.contentResolver.query(Drafts.CONTENT_URI, Drafts.COLUMNS, where.sql, null, null) ?: return -1
|
||||
val i = DraftCursorIndices(c)
|
||||
val item: Draft
|
||||
try {
|
||||
if (!c.moveToFirst()) return -1
|
||||
item = i.newObject(c)
|
||||
} catch (e: IOException) {
|
||||
return -1
|
||||
} finally {
|
||||
c.close()
|
||||
}
|
||||
val title = context.getString(R.string.status_not_updated)
|
||||
val message = context.getString(R.string.status_not_updated_summary)
|
||||
val intent = Intent()
|
||||
intent.`package` = BuildConfig.APPLICATION_ID
|
||||
val uriBuilder = Uri.Builder()
|
||||
uriBuilder.scheme(SCHEME_TWIDERE)
|
||||
uriBuilder.authority(AUTHORITY_DRAFTS)
|
||||
intent.data = uriBuilder.build()
|
||||
val nb = NotificationCompat.Builder(context)
|
||||
nb.setTicker(message)
|
||||
nb.setContentTitle(title)
|
||||
nb.setContentText(item.text)
|
||||
nb.setAutoCancel(true)
|
||||
nb.setWhen(System.currentTimeMillis())
|
||||
nb.setSmallIcon(R.drawable.ic_stat_draft)
|
||||
val discardIntent = Intent(context, LengthyOperationsService::class.java)
|
||||
discardIntent.action = INTENT_ACTION_DISCARD_DRAFT
|
||||
val draftUri = Uri.withAppendedPath(Drafts.CONTENT_URI, draftId.toString())
|
||||
discardIntent.data = draftUri
|
||||
nb.addAction(R.drawable.ic_action_delete, context.getString(R.string.discard), PendingIntent.getService(context, 0,
|
||||
discardIntent, PendingIntent.FLAG_ONE_SHOT))
|
||||
|
||||
val sendIntent = Intent(context, LengthyOperationsService::class.java)
|
||||
sendIntent.action = INTENT_ACTION_SEND_DRAFT
|
||||
sendIntent.data = draftUri
|
||||
nb.addAction(R.drawable.ic_action_send, context.getString(R.string.action_send),
|
||||
PendingIntent.getService(context, 0, sendIntent, PendingIntent.FLAG_ONE_SHOT))
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
|
||||
nb.setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT))
|
||||
notificationManager.notify(draftUri.toString(), NOTIFICATION_ID_DRAFTS,
|
||||
nb.build())
|
||||
return draftId
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
val context = context!!
|
||||
GeneralComponentHelper.build(context).inject(this)
|
||||
handler = Handler(Looper.getMainLooper())
|
||||
databaseWrapper = SQLiteDatabaseWrapper(this)
|
||||
backgroundExecutor = Executors.newSingleThreadExecutor()
|
||||
// final GetWritableDatabaseTask task = new
|
||||
// GetWritableDatabaseTask(context, helper, mDatabaseWrapper);
|
||||
// task.executeTask();
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateSQLiteDatabase(): SQLiteDatabase {
|
||||
val app = TwidereApplication.getInstance(context!!)
|
||||
val helper = app.sqLiteOpenHelper
|
||||
return helper.writableDatabase
|
||||
}
|
||||
|
||||
|
||||
override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?,
|
||||
sortOrder: String?): Cursor? {
|
||||
try {
|
||||
val tableId = DataStoreUtils.getTableId(uri)
|
||||
val table = DataStoreUtils.getTableNameById(tableId)
|
||||
when (tableId) {
|
||||
VIRTUAL_TABLE_ID_DATABASE_PREPARE -> {
|
||||
databaseWrapper.prepare()
|
||||
return MatrixCursor(projection ?: arrayOfNulls<String>(0))
|
||||
}
|
||||
VIRTUAL_TABLE_ID_PERMISSIONS -> {
|
||||
val context = context ?: return null
|
||||
val c = MatrixCursor(Permissions.MATRIX_COLUMNS)
|
||||
val pm = context.packageManager
|
||||
if (Binder.getCallingUid() == Process.myUid()) {
|
||||
val map = permissionsManager.all
|
||||
for ((key, value) in map) {
|
||||
c.addRow(arrayOf<Any>(key, value))
|
||||
}
|
||||
} else {
|
||||
val map = permissionsManager.all
|
||||
val callingPackages = pm.getPackagesForUid(Binder.getCallingUid())
|
||||
for ((key, value) in map) {
|
||||
if (ArrayUtils.contains(callingPackages, key)) {
|
||||
c.addRow(arrayOf<Any>(key, value))
|
||||
}
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP -> {
|
||||
val accountKey = UserKey.valueOf(uri.lastPathSegment)
|
||||
val query = CachedUsersQueryBuilder.withRelationship(projection,
|
||||
selection, selectionArgs, sortOrder, accountKey)
|
||||
val c = databaseWrapper.rawQuery(query.first.sql, query.second)
|
||||
contentNotificationManager.setNotificationUri(c, CachedUsers.CONTENT_URI)
|
||||
return c
|
||||
}
|
||||
VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE -> {
|
||||
val accountKey = UserKey.valueOf(uri.lastPathSegment)
|
||||
val query = CachedUsersQueryBuilder.withScore(projection,
|
||||
selection, selectionArgs, sortOrder, accountKey, 0)
|
||||
val c = databaseWrapper.rawQuery(query.first.sql, query.second)
|
||||
contentNotificationManager.setNotificationUri(c, CachedUsers.CONTENT_URI)
|
||||
return c
|
||||
}
|
||||
VIRTUAL_TABLE_ID_DRAFTS_UNSENT -> {
|
||||
val twitter = twitterWrapper
|
||||
val sendingIds = RawItemArray(twitter.getSendingDraftIds())
|
||||
val where: Expression
|
||||
if (selection != null) {
|
||||
where = Expression.and(Expression(selection),
|
||||
Expression.notIn(Column(Drafts._ID), sendingIds))
|
||||
} else {
|
||||
where = Expression.and(Expression.notIn(Column(Drafts._ID), sendingIds))
|
||||
}
|
||||
val c = databaseWrapper.query(Drafts.TABLE_NAME, projection,
|
||||
where.sql, selectionArgs, null, null, sortOrder)
|
||||
contentNotificationManager.setNotificationUri(c, Utils.getNotificationUri(tableId, uri))
|
||||
return c
|
||||
}
|
||||
VIRTUAL_TABLE_ID_SUGGESTIONS_AUTO_COMPLETE -> {
|
||||
return SuggestionsCursorCreator.forAutoComplete(databaseWrapper,
|
||||
userColorNameManager, uri, projection)
|
||||
}
|
||||
VIRTUAL_TABLE_ID_SUGGESTIONS_SEARCH -> {
|
||||
return SuggestionsCursorCreator.forSearch(databaseWrapper,
|
||||
userColorNameManager, uri, projection)
|
||||
}
|
||||
VIRTUAL_TABLE_ID_NULL -> {
|
||||
return null
|
||||
}
|
||||
VIRTUAL_TABLE_ID_EMPTY -> {
|
||||
return MatrixCursor(projection ?: arrayOfNulls<String>(0))
|
||||
}
|
||||
VIRTUAL_TABLE_ID_RAW_QUERY -> {
|
||||
if (projection != null || selection != null || sortOrder != null) {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
return databaseWrapper.rawQuery(uri.lastPathSegment, selectionArgs)
|
||||
}
|
||||
}
|
||||
if (table == null) return null
|
||||
val c = databaseWrapper.query(table, projection, selection, selectionArgs,
|
||||
null, null, sortOrder)
|
||||
contentNotificationManager.setNotificationUri(c, Utils.getNotificationUri(tableId, uri))
|
||||
return c
|
||||
} catch (e: SQLException) {
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
|
||||
try {
|
||||
return updateInternal(uri, values, selection, selectionArgs)
|
||||
} catch (e: SQLException) {
|
||||
if (handleSQLException(e)) {
|
||||
try {
|
||||
return updateInternal(uri, values, selection, selectionArgs)
|
||||
} catch (e1: SQLException) {
|
||||
throw IllegalStateException(e1)
|
||||
}
|
||||
|
||||
}
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun updateInternal(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
|
||||
val tableId = DataStoreUtils.getTableId(uri)
|
||||
val table = DataStoreUtils.getTableNameById(tableId)
|
||||
var result = 0
|
||||
if (table != null) {
|
||||
result = databaseWrapper.update(table, values, selection, selectionArgs)
|
||||
}
|
||||
if (result > 0) {
|
||||
onDatabaseUpdated(tableId, uri)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun clearNotification() {
|
||||
notificationManager.cancelAll()
|
||||
}
|
||||
|
||||
private fun clearNotification(notificationType: Int, accountId: UserKey) {
|
||||
notificationManager.cancelById(Utils.getNotificationId(notificationType, accountId))
|
||||
}
|
||||
|
||||
|
||||
private fun notifyContentObserver(uri: Uri) {
|
||||
if (!uri.getBooleanQueryParameter(QUERY_PARAM_NOTIFY, true)) return
|
||||
handler!!.post(Runnable {
|
||||
val cr = context.contentResolver ?: return@Runnable
|
||||
cr.notifyChange(uri, null)
|
||||
})
|
||||
}
|
||||
|
||||
private fun notifyUnreadCountChanged(position: Int) {
|
||||
handler!!.post { bus.post(UnreadCountUpdatedEvent(position)) }
|
||||
}
|
||||
|
||||
private fun onDatabaseUpdated(tableId: Int, uri: Uri?) {
|
||||
if (uri == null) return
|
||||
notifyContentObserver(Utils.getNotificationUri(tableId, uri))
|
||||
}
|
||||
|
||||
private fun onNewItemsInserted(uri: Uri, tableId: Int, valuesArray: Array<ContentValues?>?) {
|
||||
val context = context ?: return
|
||||
if (valuesArray.isNullOrEmpty()) return
|
||||
when (tableId) {
|
||||
TABLE_ID_STATUSES -> {
|
||||
backgroundExecutor!!.execute {
|
||||
val prefs = AccountPreferences.getNotificationEnabledPreferences(context,
|
||||
DataStoreUtils.getAccountKeys(context))
|
||||
prefs.filter(AccountPreferences::isHomeTimelineNotificationEnabled).forEach {
|
||||
val positionTag = getPositionTag(CustomTabType.HOME_TIMELINE, it.accountKey)
|
||||
contentNotificationManager.showTimelineNotification(it, positionTag)
|
||||
}
|
||||
notifyUnreadCountChanged(NOTIFICATION_ID_HOME_TIMELINE)
|
||||
}
|
||||
}
|
||||
TABLE_ID_ACTIVITIES_ABOUT_ME -> {
|
||||
backgroundExecutor!!.execute {
|
||||
val prefs = AccountPreferences.getNotificationEnabledPreferences(context,
|
||||
DataStoreUtils.getAccountKeys(context))
|
||||
prefs.filter(AccountPreferences::isInteractionsNotificationEnabled).forEach {
|
||||
contentNotificationManager.showInteractionsNotification(it, getPositionTag(ReadPositionTag.ACTIVITIES_ABOUT_ME,
|
||||
it.accountKey))
|
||||
}
|
||||
notifyUnreadCountChanged(NOTIFICATION_ID_INTERACTIONS_TIMELINE)
|
||||
}
|
||||
}
|
||||
TABLE_ID_MESSAGES -> {
|
||||
val prefs = AccountPreferences.getNotificationEnabledPreferences(context,
|
||||
DataStoreUtils.getAccountKeys(context))
|
||||
prefs.filter(AccountPreferences::isDirectMessagesNotificationEnabled).forEach {
|
||||
val pairs = readStateManager.getPositionPairs(CustomTabType.DIRECT_MESSAGES)
|
||||
// TODO show messages notifications
|
||||
}
|
||||
notifyUnreadCountChanged(NOTIFICATION_ID_DIRECT_MESSAGES)
|
||||
}
|
||||
TABLE_ID_DRAFTS -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPositionTag(tag: String, accountKey: UserKey): Long {
|
||||
val position = readStateManager.getPosition(Utils.getReadPositionTagWithAccount(tag,
|
||||
accountKey))
|
||||
if (position != -1L) return position
|
||||
return readStateManager.getPosition(tag)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private fun getConflictAlgorithm(tableId: Int): Int {
|
||||
when (tableId) {
|
||||
TABLE_ID_CACHED_HASHTAGS, TABLE_ID_CACHED_STATUSES, TABLE_ID_CACHED_USERS, TABLE_ID_CACHED_RELATIONSHIPS, TABLE_ID_SEARCH_HISTORY, TABLE_ID_MESSAGES, TABLE_ID_MESSAGES_CONVERSATIONS -> return SQLiteDatabase.CONFLICT_REPLACE
|
||||
TABLE_ID_FILTERED_USERS, TABLE_ID_FILTERED_KEYWORDS, TABLE_ID_FILTERED_SOURCES, TABLE_ID_FILTERED_LINKS -> return SQLiteDatabase.CONFLICT_IGNORE
|
||||
}
|
||||
return SQLiteDatabase.CONFLICT_NONE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -30,7 +30,6 @@ import org.mariotaku.twidere.model.UserKey
|
|||
import org.mariotaku.twidere.model.account.cred.OAuthCredentials
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.*
|
||||
import org.mariotaku.twidere.util.ContentValuesCreator
|
||||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
import org.mariotaku.twidere.util.DebugLog
|
||||
import org.mariotaku.twidere.util.TwidereArrayUtils
|
||||
|
@ -164,11 +163,9 @@ class StreamingService : Service() {
|
|||
}
|
||||
|
||||
override fun onDirectMessageDeleted(event: DeletionEvent) {
|
||||
val where = Expression.equalsArgs(DirectMessages.MESSAGE_ID).sql
|
||||
val where = Expression.equalsArgs(Messages.MESSAGE_ID).sql
|
||||
val whereArgs = arrayOf(event.id)
|
||||
for (uri in MESSAGES_URIS) {
|
||||
context.contentResolver.delete(uri, where, whereArgs)
|
||||
}
|
||||
context.contentResolver.delete(Messages.CONTENT_URI, where, whereArgs)
|
||||
}
|
||||
|
||||
override fun onStatusDeleted(event: DeletionEvent) {
|
||||
|
@ -183,15 +180,6 @@ class StreamingService : Service() {
|
|||
override fun onDirectMessage(directMessage: DirectMessage) {
|
||||
if (directMessage.id == null) return
|
||||
|
||||
val resolver = context.contentResolver
|
||||
val where = Expression.and(Expression.equalsArgs(DirectMessages.ACCOUNT_KEY),
|
||||
Expression.equalsArgs(DirectMessages.MESSAGE_ID)).sql
|
||||
val whereArgs = arrayOf(account.key.toString(), directMessage.id)
|
||||
for (uri in MESSAGES_URIS) {
|
||||
resolver.delete(uri, where, whereArgs)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onException(ex: Throwable) {
|
||||
|
@ -261,24 +249,7 @@ class StreamingService : Service() {
|
|||
|
||||
@Throws(IOException::class)
|
||||
override fun onStatus(status: Status) {
|
||||
val resolver = context.contentResolver
|
||||
|
||||
val values = ContentValuesCreator.createStatus(status, account.key)
|
||||
if (!statusStreamStarted) {
|
||||
statusStreamStarted = true
|
||||
values.put(Statuses.IS_GAP, true)
|
||||
}
|
||||
val where = Expression.and(Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY),
|
||||
Expression.equalsArgs(Statuses.STATUS_ID)).sql
|
||||
val whereArgs = arrayOf(account.key.toString(), status.id.toString())
|
||||
resolver.delete(Statuses.CONTENT_URI, where, whereArgs)
|
||||
resolver.delete(Mentions.CONTENT_URI, where, whereArgs)
|
||||
resolver.insert(Statuses.CONTENT_URI, values)
|
||||
val rt = status.retweetedStatus
|
||||
if (rt != null && rt.extendedText.contains("@" + account.user.screen_name) ||
|
||||
rt == null && status.extendedText.contains("@" + account.user.screen_name)) {
|
||||
resolver.insert(Mentions.CONTENT_URI, values)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTrackLimitationNotice(numberOfLimitedStatuses: Int) {
|
||||
|
@ -334,7 +305,6 @@ class StreamingService : Service() {
|
|||
|
||||
private val NOTIFICATION_SERVICE_STARTED = 1
|
||||
|
||||
private val MESSAGES_URIS = arrayOf(DirectMessages.Inbox.CONTENT_URI, DirectMessages.Outbox.CONTENT_URI)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -47,8 +47,8 @@ class UpdateAccountInfoTask(
|
|||
|
||||
resolver.update(Statuses.CONTENT_URI, accountKeyValues, accountKeyWhere, accountKeyWhereArgs)
|
||||
resolver.update(Activities.AboutMe.CONTENT_URI, accountKeyValues, accountKeyWhere, accountKeyWhereArgs)
|
||||
resolver.update(DirectMessages.Inbox.CONTENT_URI, accountKeyValues, accountKeyWhere, accountKeyWhereArgs)
|
||||
resolver.update(DirectMessages.Outbox.CONTENT_URI, accountKeyValues, accountKeyWhere, accountKeyWhereArgs)
|
||||
resolver.update(Messages.CONTENT_URI, accountKeyValues, accountKeyWhere, accountKeyWhereArgs)
|
||||
resolver.update(Messages.Conversations.CONTENT_URI, accountKeyValues, accountKeyWhere, accountKeyWhereArgs)
|
||||
resolver.update(CachedRelationships.CONTENT_URI, accountKeyValues, accountKeyWhere, accountKeyWhereArgs)
|
||||
|
||||
updateTabs(resolver, user.key)
|
||||
|
|
|
@ -40,6 +40,7 @@ import org.mariotaku.twidere.model.util.ParcelableUserUtils
|
|||
import org.mariotaku.twidere.model.util.UserKeyUtils
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Messages
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
|
||||
import org.mariotaku.twidere.task.BaseAbstractTask
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils
|
||||
import java.util.*
|
||||
|
||||
|
@ -48,9 +49,9 @@ import java.util.*
|
|||
*/
|
||||
|
||||
class GetMessagesTask(
|
||||
context: android.content.Context
|
||||
) : org.mariotaku.twidere.task.BaseAbstractTask<GetMessagesTask.RefreshMessagesTaskParam, Unit, (Boolean) -> Unit>(context) {
|
||||
override fun doLongOperation(param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam) {
|
||||
context: Context
|
||||
) : BaseAbstractTask<GetMessagesTask.RefreshMessagesTaskParam, Unit, (Boolean) -> Unit>(context) {
|
||||
override fun doLongOperation(param: RefreshMessagesTaskParam) {
|
||||
val accountKeys = param.accountKeys
|
||||
val am = android.accounts.AccountManager.get(context)
|
||||
accountKeys.forEachIndexed { i, accountKey ->
|
||||
|
@ -61,16 +62,16 @@ class GetMessagesTask(
|
|||
} catch (e: org.mariotaku.microblog.library.MicroBlogException) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
org.mariotaku.twidere.task.twitter.message.GetMessagesTask.Companion.storeMessages(context, messages, details)
|
||||
Companion.storeMessages(context, messages, details)
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterExecute(callback: ((Boolean) -> Unit)?, result: Unit) {
|
||||
callback?.invoke(true)
|
||||
bus.post(org.mariotaku.twidere.model.event.GetMessagesTaskEvent(org.mariotaku.twidere.provider.TwidereDataStore.Messages.CONTENT_URI, params?.taskTag, false, null))
|
||||
bus.post(org.mariotaku.twidere.model.event.GetMessagesTaskEvent(Messages.CONTENT_URI, params?.taskTag, false, null))
|
||||
}
|
||||
|
||||
private fun getMessages(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails, param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
|
||||
private fun getMessages(microBlog: org.mariotaku.microblog.library.MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||
when (details.type) {
|
||||
org.mariotaku.twidere.annotation.AccountType.FANFOU -> {
|
||||
// Use fanfou DM api, disabled since it's conversation api is not suitable for paging
|
||||
|
@ -87,8 +88,8 @@ class GetMessagesTask(
|
|||
return getDefaultMessages(microBlog, details, param, index)
|
||||
}
|
||||
|
||||
private fun getTwitterOfficialMessages(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails,
|
||||
param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
|
||||
private fun getTwitterOfficialMessages(microBlog: org.mariotaku.microblog.library.MicroBlog, details: AccountDetails,
|
||||
param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||
val conversationId = param.conversationId
|
||||
if (conversationId == null) {
|
||||
return getTwitterOfficialUserInbox(microBlog, details, param, index)
|
||||
|
@ -97,16 +98,16 @@ class GetMessagesTask(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getFanfouMessages(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails, param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
|
||||
private fun getFanfouMessages(microBlog: org.mariotaku.microblog.library.MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||
val conversationId = param.conversationId
|
||||
if (conversationId == null) {
|
||||
return getFanfouConversations(microBlog, details, param, index)
|
||||
} else {
|
||||
return org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData(emptyList(), emptyList())
|
||||
return DatabaseUpdateData(emptyList(), emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultMessages(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails, param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
|
||||
private fun getDefaultMessages(microBlog: org.mariotaku.microblog.library.MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||
val accountKey = details.key
|
||||
|
||||
val sinceIds = if (param.hasSinceIds) param.sinceIds else null
|
||||
|
@ -137,8 +138,8 @@ class GetMessagesTask(
|
|||
})
|
||||
|
||||
|
||||
val insertMessages = arrayListOf<org.mariotaku.twidere.model.ParcelableMessage>()
|
||||
val conversations = hashMapOf<String, org.mariotaku.twidere.model.ParcelableMessageConversation>()
|
||||
val insertMessages = arrayListOf<ParcelableMessage>()
|
||||
val conversations = hashMapOf<String, ParcelableMessageConversation>()
|
||||
|
||||
val conversationIds = hashSetOf<String>()
|
||||
received.forEach {
|
||||
|
@ -162,23 +163,23 @@ class GetMessagesTask(
|
|||
insertMessages.add(message)
|
||||
conversations.addConversation(message.conversation_id, details, message, setOf(dm.sender, dm.recipient))
|
||||
}
|
||||
return org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData(conversations.values, insertMessages)
|
||||
return DatabaseUpdateData(conversations.values, insertMessages)
|
||||
}
|
||||
|
||||
private fun getTwitterOfficialConversation(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails,
|
||||
conversationId: String, param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
|
||||
val maxId = param.maxIds?.get(index) ?: return org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData(emptyList(), emptyList())
|
||||
private fun getTwitterOfficialConversation(microBlog: org.mariotaku.microblog.library.MicroBlog, details: AccountDetails,
|
||||
conversationId: String, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||
val maxId = param.maxIds?.get(index) ?: return DatabaseUpdateData(emptyList(), emptyList())
|
||||
val paging = org.mariotaku.microblog.library.twitter.model.Paging().apply {
|
||||
maxId(maxId)
|
||||
}
|
||||
|
||||
val response = microBlog.getDmConversation(conversationId, paging).conversationTimeline
|
||||
response.fixMedia(microBlog)
|
||||
return org.mariotaku.twidere.task.twitter.message.GetMessagesTask.Companion.createDatabaseUpdateData(context, details, response)
|
||||
return Companion.createDatabaseUpdateData(context, details, response)
|
||||
}
|
||||
|
||||
private fun getTwitterOfficialUserInbox(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails,
|
||||
param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
|
||||
private fun getTwitterOfficialUserInbox(microBlog: org.mariotaku.microblog.library.MicroBlog, details: AccountDetails,
|
||||
param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||
val maxId = if (param.hasMaxIds) param.maxIds?.get(index) else null
|
||||
val cursor = if (param.hasCursors) param.cursors?.get(index) else null
|
||||
val response = if (cursor != null) {
|
||||
|
@ -191,11 +192,11 @@ class GetMessagesTask(
|
|||
}).userInbox
|
||||
}
|
||||
response.fixMedia(microBlog)
|
||||
return org.mariotaku.twidere.task.twitter.message.GetMessagesTask.Companion.createDatabaseUpdateData(context, details, response)
|
||||
return Companion.createDatabaseUpdateData(context, details, response)
|
||||
}
|
||||
|
||||
|
||||
private fun getFanfouConversations(microBlog: org.mariotaku.microblog.library.MicroBlog, details: org.mariotaku.twidere.model.AccountDetails, param: org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam, index: Int): org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
|
||||
private fun getFanfouConversations(microBlog: org.mariotaku.microblog.library.MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||
val accountKey = details.key
|
||||
val cursor = param.cursors?.get(index)
|
||||
val page = cursor?.substringAfter("page:").toInt(-1)
|
||||
|
@ -205,7 +206,7 @@ class GetMessagesTask(
|
|||
page(page)
|
||||
}
|
||||
})
|
||||
val conversations = hashMapOf<String, org.mariotaku.twidere.model.ParcelableMessageConversation>()
|
||||
val conversations = hashMapOf<String, ParcelableMessageConversation>()
|
||||
|
||||
val conversationIds = hashSetOf<String>()
|
||||
result.mapTo(conversationIds) { "${accountKey.id}-${it.otherId}" }
|
||||
|
@ -219,26 +220,26 @@ class GetMessagesTask(
|
|||
setOf(dm.sender, dm.recipient))
|
||||
mc.request_cursor = "page:$page"
|
||||
}
|
||||
return org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData(conversations.values, emptyList())
|
||||
return DatabaseUpdateData(conversations.values, emptyList())
|
||||
}
|
||||
|
||||
data class DatabaseUpdateData(
|
||||
val conversations: Collection<org.mariotaku.twidere.model.ParcelableMessageConversation>,
|
||||
val messages: Collection<org.mariotaku.twidere.model.ParcelableMessage>,
|
||||
val conversations: Collection<ParcelableMessageConversation>,
|
||||
val messages: Collection<ParcelableMessage>,
|
||||
val deleteConversations: List<String> = emptyList(),
|
||||
val deleteMessages: Map<String, List<String>> = emptyMap(),
|
||||
val conversationRequestCursor: String? = null
|
||||
)
|
||||
|
||||
abstract class RefreshNewTaskParam(
|
||||
context: android.content.Context
|
||||
) : org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam(context) {
|
||||
context: Context
|
||||
) : RefreshMessagesTaskParam(context) {
|
||||
|
||||
override val sinceIds: Array<String?>?
|
||||
get() {
|
||||
val incomingIds = org.mariotaku.twidere.util.DataStoreUtils.getNewestMessageIds(context, org.mariotaku.twidere.provider.TwidereDataStore.Messages.CONTENT_URI,
|
||||
val incomingIds = org.mariotaku.twidere.util.DataStoreUtils.getNewestMessageIds(context, Messages.CONTENT_URI,
|
||||
defaultKeys, false)
|
||||
val outgoingIds = org.mariotaku.twidere.util.DataStoreUtils.getNewestMessageIds(context, org.mariotaku.twidere.provider.TwidereDataStore.Messages.CONTENT_URI,
|
||||
val outgoingIds = org.mariotaku.twidere.util.DataStoreUtils.getNewestMessageIds(context, Messages.CONTENT_URI,
|
||||
defaultKeys, true)
|
||||
return incomingIds + outgoingIds
|
||||
}
|
||||
|
@ -247,7 +248,7 @@ class GetMessagesTask(
|
|||
get() {
|
||||
val cursors = arrayOfNulls<String>(defaultKeys.size)
|
||||
val newestConversations = org.mariotaku.twidere.util.DataStoreUtils.getNewestConversations(context,
|
||||
org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations.CONTENT_URI, twitterOfficialKeys)
|
||||
Conversations.CONTENT_URI, twitterOfficialKeys)
|
||||
newestConversations.forEachIndexed { i, conversation ->
|
||||
cursors[i] = conversation?.request_cursor
|
||||
}
|
||||
|
@ -260,18 +261,18 @@ class GetMessagesTask(
|
|||
}
|
||||
|
||||
abstract class LoadMoreEntriesTaskParam(
|
||||
context: android.content.Context
|
||||
) : org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam(context) {
|
||||
context: Context
|
||||
) : RefreshMessagesTaskParam(context) {
|
||||
|
||||
override val maxIds: Array<String?>? by lazy {
|
||||
val incomingIds = org.mariotaku.twidere.util.DataStoreUtils.getOldestMessageIds(context, org.mariotaku.twidere.provider.TwidereDataStore.Messages.CONTENT_URI,
|
||||
val incomingIds = org.mariotaku.twidere.util.DataStoreUtils.getOldestMessageIds(context, Messages.CONTENT_URI,
|
||||
defaultKeys, false)
|
||||
val outgoingIds = org.mariotaku.twidere.util.DataStoreUtils.getOldestMessageIds(context, org.mariotaku.twidere.provider.TwidereDataStore.Messages.CONTENT_URI,
|
||||
val outgoingIds = org.mariotaku.twidere.util.DataStoreUtils.getOldestMessageIds(context, Messages.CONTENT_URI,
|
||||
defaultKeys, true)
|
||||
val oldestConversations = org.mariotaku.twidere.util.DataStoreUtils.getOldestConversations(context,
|
||||
org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations.CONTENT_URI, twitterOfficialKeys)
|
||||
Conversations.CONTENT_URI, twitterOfficialKeys)
|
||||
oldestConversations.forEachIndexed { i, conversation ->
|
||||
val extras = conversation?.conversation_extras as? org.mariotaku.twidere.model.message.conversation.TwitterOfficialConversationExtras ?: return@forEachIndexed
|
||||
val extras = conversation?.conversation_extras as? TwitterOfficialConversationExtras ?: return@forEachIndexed
|
||||
incomingIds[i] = extras.maxEntryId
|
||||
}
|
||||
return@lazy incomingIds + outgoingIds
|
||||
|
@ -282,19 +283,19 @@ class GetMessagesTask(
|
|||
}
|
||||
|
||||
class LoadMoreMessageTaskParam(
|
||||
context: android.content.Context,
|
||||
accountKey: org.mariotaku.twidere.model.UserKey,
|
||||
context: Context,
|
||||
accountKey: UserKey,
|
||||
override val conversationId: String,
|
||||
maxId: String
|
||||
) : org.mariotaku.twidere.task.twitter.message.GetMessagesTask.RefreshMessagesTaskParam(context) {
|
||||
override val accountKeys: Array<org.mariotaku.twidere.model.UserKey> = arrayOf(accountKey)
|
||||
) : RefreshMessagesTaskParam(context) {
|
||||
override val accountKeys: Array<UserKey> = arrayOf(accountKey)
|
||||
override val maxIds: Array<String?>? = arrayOf(maxId)
|
||||
override val hasMaxIds: Boolean = true
|
||||
}
|
||||
|
||||
abstract class RefreshMessagesTaskParam(
|
||||
val context: android.content.Context
|
||||
) : org.mariotaku.twidere.model.SimpleRefreshTaskParam() {
|
||||
val context: Context
|
||||
) : SimpleRefreshTaskParam() {
|
||||
|
||||
/**
|
||||
* If `conversationId` has value, load messages in conversationId
|
||||
|
@ -303,11 +304,11 @@ class GetMessagesTask(
|
|||
|
||||
var taskTag: String? = null
|
||||
|
||||
protected val accounts: Array<org.mariotaku.twidere.model.AccountDetails?> by lazy {
|
||||
protected val accounts: Array<AccountDetails?> by lazy {
|
||||
org.mariotaku.twidere.model.util.AccountUtils.getAllAccountDetails(android.accounts.AccountManager.get(context), accountKeys, false)
|
||||
}
|
||||
|
||||
protected val defaultKeys: Array<org.mariotaku.twidere.model.UserKey?>by lazy {
|
||||
protected val defaultKeys: Array<UserKey?>by lazy {
|
||||
return@lazy accounts.map { account ->
|
||||
account ?: return@map null
|
||||
if (account.isOfficial(context) || account.type == org.mariotaku.twidere.annotation.AccountType.FANFOU) {
|
||||
|
@ -317,7 +318,7 @@ class GetMessagesTask(
|
|||
}.toTypedArray()
|
||||
}
|
||||
|
||||
protected val twitterOfficialKeys: Array<org.mariotaku.twidere.model.UserKey?> by lazy {
|
||||
protected val twitterOfficialKeys: Array<UserKey?> by lazy {
|
||||
return@lazy accounts.map { account ->
|
||||
account ?: return@map null
|
||||
if (!account.isOfficial(context)) {
|
||||
|
@ -331,22 +332,22 @@ class GetMessagesTask(
|
|||
|
||||
companion object {
|
||||
|
||||
fun createDatabaseUpdateData(context: android.content.Context, account: org.mariotaku.twidere.model.AccountDetails, response: org.mariotaku.microblog.library.twitter.model.DMResponse):
|
||||
org.mariotaku.twidere.task.twitter.message.GetMessagesTask.DatabaseUpdateData {
|
||||
fun createDatabaseUpdateData(context: Context, account: AccountDetails, response: DMResponse):
|
||||
DatabaseUpdateData {
|
||||
val respConversations = response.conversations.orEmpty()
|
||||
val respEntries = response.entries.orEmpty()
|
||||
val respUsers = response.users.orEmpty()
|
||||
|
||||
val conversations = hashMapOf<String, org.mariotaku.twidere.model.ParcelableMessageConversation>()
|
||||
val conversations = hashMapOf<String, ParcelableMessageConversation>()
|
||||
|
||||
conversations.addLocalConversations(context, account.key, respConversations.keys)
|
||||
val messages = java.util.ArrayList<org.mariotaku.twidere.model.ParcelableMessage>()
|
||||
val messageDeletionsMap = java.util.HashMap<String, java.util.ArrayList<String>>()
|
||||
val conversationDeletions = java.util.ArrayList<String>()
|
||||
val messages = ArrayList<ParcelableMessage>()
|
||||
val messageDeletionsMap = HashMap<String, ArrayList<String>>()
|
||||
val conversationDeletions = ArrayList<String>()
|
||||
respEntries.mapNotNullTo(messages) { entry ->
|
||||
when {
|
||||
entry.messageDelete != null -> {
|
||||
val list = messageDeletionsMap.getOrPut(entry.messageDelete.conversationId) { java.util.ArrayList<String>() }
|
||||
val list = messageDeletionsMap.getOrPut(entry.messageDelete.conversationId) { ArrayList<String>() }
|
||||
entry.messageDelete.messages?.forEach {
|
||||
list.add(it.messageId)
|
||||
}
|
||||
|
@ -361,7 +362,7 @@ class GetMessagesTask(
|
|||
}
|
||||
}
|
||||
}
|
||||
val messagesMap = messages.groupBy(org.mariotaku.twidere.model.ParcelableMessage::conversation_id)
|
||||
val messagesMap = messages.groupBy(ParcelableMessage::conversation_id)
|
||||
for ((k, v) in respConversations) {
|
||||
val message = messagesMap[k]?.maxBy(ParcelableMessage::message_timestamp) ?: continue
|
||||
val participants = respUsers.filterKeys { userId ->
|
||||
|
|
|
@ -22,8 +22,6 @@ package org.mariotaku.twidere.util
|
|||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
import android.support.v4.util.SimpleArrayMap
|
||||
import com.squareup.otto.Bus
|
||||
import com.squareup.otto.Subscribe
|
||||
import org.apache.commons.collections.primitives.ArrayIntList
|
||||
|
@ -33,7 +31,6 @@ import org.mariotaku.abstask.library.TaskStarter
|
|||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.ktextension.toNulls
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.http.HttpResponseCode
|
||||
import org.mariotaku.microblog.library.twitter.model.*
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.twidere.R
|
||||
|
@ -57,7 +54,8 @@ class AsyncTwitterWrapper(
|
|||
val context: Context,
|
||||
private val bus: Bus,
|
||||
private val preferences: SharedPreferencesWrapper,
|
||||
private val asyncTaskManager: AsyncTaskManager
|
||||
private val asyncTaskManager: AsyncTaskManager,
|
||||
private val notificationManager: NotificationManagerWrapper
|
||||
) {
|
||||
private val resolver = context.contentResolver
|
||||
|
||||
|
@ -123,13 +121,7 @@ class AsyncTwitterWrapper(
|
|||
}
|
||||
|
||||
fun clearNotificationAsync(notificationId: Int, accountKey: UserKey?) {
|
||||
val task = ClearNotificationTask(context, notificationId, accountKey)
|
||||
AsyncTaskUtils.executeTask(task)
|
||||
}
|
||||
|
||||
fun clearUnreadCountAsync(position: Int) {
|
||||
val task = ClearUnreadCountTask(position)
|
||||
AsyncTaskUtils.executeTask(task)
|
||||
notificationManager.cancelById(Utils.getNotificationId(notificationId, accountKey))
|
||||
}
|
||||
|
||||
fun createBlockAsync(accountKey: UserKey, userKey: UserKey, filterEverywhere: Boolean) {
|
||||
|
@ -313,11 +305,6 @@ class AsyncTwitterWrapper(
|
|||
}
|
||||
}
|
||||
|
||||
fun removeUnreadCountsAsync(position: Int, counts: SimpleArrayMap<UserKey, Set<String>>) {
|
||||
val task = RemoveUnreadCountsTask(context, position, counts)
|
||||
AsyncTaskUtils.executeTask(task)
|
||||
}
|
||||
|
||||
fun reportMultiSpam(accountKey: UserKey, userIds: Array<String>) {
|
||||
// TODO implementation
|
||||
}
|
||||
|
@ -398,26 +385,6 @@ class AsyncTwitterWrapper(
|
|||
return updatingRelationshipIds.contains(ParcelableUser.calculateHashCode(accountId, userId))
|
||||
}
|
||||
|
||||
internal inner class ClearNotificationTask(
|
||||
private val context: Context,
|
||||
private val notificationType: Int,
|
||||
private val accountKey: UserKey?
|
||||
) : AsyncTask<Any, Any, Int>() {
|
||||
|
||||
override fun doInBackground(vararg params: Any): Int {
|
||||
return TwitterWrapper.clearNotification(context, notificationType, accountKey)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal inner class ClearUnreadCountTask(private val position: Int) : AsyncTask<Any, Any, Int>() {
|
||||
|
||||
override fun doInBackground(vararg params: Any): Int {
|
||||
return TwitterWrapper.clearUnreadCount(context, position)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal inner class CreateMultiBlockTask(
|
||||
context: Context,
|
||||
private val accountKey: UserKey,
|
||||
|
@ -611,49 +578,6 @@ class AsyncTwitterWrapper(
|
|||
}
|
||||
|
||||
|
||||
internal inner class DestroyDirectMessageTask(private val mAccountKey: UserKey, private val mMessageId: String) : ManagedAsyncTask<Any, Any, SingleResponse<DirectMessage>>(context) {
|
||||
|
||||
private fun deleteMessages() {
|
||||
val where = Expression.and(Expression.equalsArgs(DirectMessages.ACCOUNT_KEY),
|
||||
Expression.equalsArgs(DirectMessages.MESSAGE_ID)).sql
|
||||
val whereArgs = arrayOf(mAccountKey.toString(), mMessageId)
|
||||
resolver.delete(DirectMessages.Inbox.CONTENT_URI, where, whereArgs)
|
||||
resolver.delete(DirectMessages.Outbox.CONTENT_URI, where, whereArgs)
|
||||
}
|
||||
|
||||
private fun isMessageNotFound(e: Exception?): Boolean {
|
||||
if (e !is MicroBlogException) return false
|
||||
return e.errorCode == ErrorInfo.PAGE_NOT_FOUND || e.statusCode == HttpResponseCode.NOT_FOUND
|
||||
}
|
||||
|
||||
override fun doInBackground(vararg args: Any): SingleResponse<DirectMessage> {
|
||||
val microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey) ?: return SingleResponse.getInstance<DirectMessage>()
|
||||
try {
|
||||
val message = microBlog.destroyDirectMessage(mMessageId)
|
||||
deleteMessages()
|
||||
return SingleResponse.getInstance(message)
|
||||
} catch (e: MicroBlogException) {
|
||||
if (isMessageNotFound(e)) {
|
||||
deleteMessages()
|
||||
}
|
||||
return SingleResponse.getInstance<DirectMessage>(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onPostExecute(result: SingleResponse<DirectMessage>) {
|
||||
super.onPostExecute(result)
|
||||
if (result.hasData() || isMessageNotFound(result.exception)) {
|
||||
Utils.showInfoMessage(context, R.string.message_direct_message_deleted, false)
|
||||
} else {
|
||||
Utils.showErrorMessage(context, R.string.action_deleting, result.exception, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
internal inner class DestroySavedSearchTask(
|
||||
context: Context,
|
||||
private val accountKey: UserKey,
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 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.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.database.Cursor
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.text.TextUtils
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.microblog.library.twitter.model.Activity
|
||||
import org.mariotaku.sqliteqb.library.*
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.*
|
||||
import org.mariotaku.twidere.activity.HomeActivity
|
||||
import org.mariotaku.twidere.annotation.CustomTabType
|
||||
import org.mariotaku.twidere.annotation.NotificationType
|
||||
import org.mariotaku.twidere.constant.IntentConstants
|
||||
import org.mariotaku.twidere.constant.iWantMyStarsBackKey
|
||||
import org.mariotaku.twidere.constant.nameFirstKey
|
||||
import org.mariotaku.twidere.extension.rawQuery
|
||||
import org.mariotaku.twidere.model.*
|
||||
import org.mariotaku.twidere.model.util.ParcelableActivityUtils
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Activities
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
||||
import org.mariotaku.twidere.receiver.NotificationReceiver
|
||||
import org.mariotaku.twidere.util.database.FilterQueryBuilder
|
||||
import org.oshkimaadziig.george.androidutils.SpanFormatter
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/2/16.
|
||||
*/
|
||||
class ContentNotificationManager(
|
||||
val context: Context,
|
||||
val activityTracker: ActivityTracker,
|
||||
val userColorNameManager: UserColorNameManager,
|
||||
val notificationManager: NotificationManagerWrapper,
|
||||
val preferences: SharedPreferences
|
||||
) {
|
||||
|
||||
private var nameFirst: Boolean = false
|
||||
private var useStarForLikes: Boolean = false
|
||||
|
||||
fun showInteractionsNotification(pref: AccountPreferences, position: Long) {
|
||||
val cr = context.contentResolver
|
||||
val accountKey = pref.accountKey
|
||||
val where = Expression.and(
|
||||
Expression.equalsArgs(Activities.ACCOUNT_KEY),
|
||||
Expression.greaterThanArgs(Activities.POSITION_KEY)
|
||||
).sql
|
||||
val whereArgs = arrayOf(accountKey.toString(), position.toString())
|
||||
val c = cr.query(Activities.AboutMe.CONTENT_URI, Activities.COLUMNS, where, whereArgs,
|
||||
OrderBy(Activities.TIMESTAMP, false).sql) ?: return
|
||||
val builder = NotificationCompat.Builder(context)
|
||||
val pebbleNotificationStringBuilder = StringBuilder()
|
||||
try {
|
||||
val count = c.count
|
||||
if (count == 0) return
|
||||
builder.setSmallIcon(R.drawable.ic_stat_notification)
|
||||
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL)
|
||||
applyNotificationPreferences(builder, pref, pref.mentionsNotificationType)
|
||||
|
||||
val resources = context.resources
|
||||
val accountName = DataStoreUtils.getAccountDisplayName(context, accountKey, nameFirst)
|
||||
builder.setContentText(accountName)
|
||||
val style = NotificationCompat.InboxStyle()
|
||||
builder.setStyle(style)
|
||||
builder.setAutoCancel(true)
|
||||
style.setSummaryText(accountName)
|
||||
val ci = ParcelableActivityCursorIndices(c)
|
||||
var messageLines = 0
|
||||
|
||||
var timestamp: Long = -1
|
||||
c.moveToPosition(-1)
|
||||
while (c.moveToNext()) {
|
||||
if (messageLines == 5) {
|
||||
style.addLine(resources.getString(R.string.and_N_more, count - c.position))
|
||||
pebbleNotificationStringBuilder.append(resources.getString(R.string.and_N_more, count - c.position))
|
||||
break
|
||||
}
|
||||
val activity = ci.newObject(c)
|
||||
if (pref.isNotificationMentionsOnly && !ArrayUtils.contains(Activity.Action.MENTION_ACTIONS,
|
||||
activity.action)) {
|
||||
continue
|
||||
}
|
||||
if (activity.status_id != null && FilterQueryBuilder.isFiltered(cr,
|
||||
activity.status_user_key, activity.status_text_plain,
|
||||
activity.status_quote_text_plain, activity.status_spans,
|
||||
activity.status_quote_spans, activity.status_source,
|
||||
activity.status_quote_source, activity.status_retweeted_by_user_key,
|
||||
activity.status_quoted_user_key)) {
|
||||
continue
|
||||
}
|
||||
val filteredUserIds = DataStoreUtils.getFilteredUserIds(context)
|
||||
if (timestamp == -1L) {
|
||||
timestamp = activity.timestamp
|
||||
}
|
||||
ParcelableActivityUtils.initAfterFilteredSourceIds(activity, filteredUserIds,
|
||||
pref.isNotificationFollowingOnly)
|
||||
val sources = ParcelableActivityUtils.getAfterFilteredSources(activity)
|
||||
if (ArrayUtils.isEmpty(sources)) continue
|
||||
val message = ActivityTitleSummaryMessage.get(context,
|
||||
userColorNameManager, activity, sources,
|
||||
0, useStarForLikes, nameFirst)
|
||||
if (message != null) {
|
||||
val summary = message.summary
|
||||
if (TextUtils.isEmpty(summary)) {
|
||||
style.addLine(message.title)
|
||||
pebbleNotificationStringBuilder.append(message.title)
|
||||
pebbleNotificationStringBuilder.append("\n")
|
||||
} else {
|
||||
style.addLine(SpanFormatter.format(resources.getString(R.string.title_summary_line_format),
|
||||
message.title, summary))
|
||||
pebbleNotificationStringBuilder.append(message.title)
|
||||
pebbleNotificationStringBuilder.append(": ")
|
||||
pebbleNotificationStringBuilder.append(message.summary)
|
||||
pebbleNotificationStringBuilder.append("\n")
|
||||
}
|
||||
messageLines++
|
||||
}
|
||||
}
|
||||
if (messageLines == 0) return
|
||||
val displayCount = messageLines + count - c.position
|
||||
val title = resources.getQuantityString(R.plurals.N_new_interactions,
|
||||
displayCount, displayCount)
|
||||
builder.setContentTitle(title)
|
||||
style.setBigContentTitle(title)
|
||||
builder.setNumber(displayCount)
|
||||
builder.setContentIntent(getContentIntent(context, CustomTabType.NOTIFICATIONS_TIMELINE,
|
||||
NotificationType.INTERACTIONS, accountKey, timestamp))
|
||||
if (timestamp != -1L) {
|
||||
builder.setDeleteIntent(getMarkReadDeleteIntent(context,
|
||||
NotificationType.INTERACTIONS, accountKey, timestamp, false))
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
return
|
||||
} finally {
|
||||
c.close()
|
||||
}
|
||||
val notificationId = Utils.getNotificationId(NOTIFICATION_ID_INTERACTIONS_TIMELINE, accountKey)
|
||||
notificationManager.notify("interactions", notificationId, builder.build())
|
||||
|
||||
Utils.sendPebbleNotification(context, context.resources.getString(R.string.interactions), pebbleNotificationStringBuilder.toString())
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun applyNotificationPreferences(builder: NotificationCompat.Builder, pref: AccountPreferences, defaultFlags: Int) {
|
||||
var notificationDefaults = 0
|
||||
if (AccountPreferences.isNotificationHasLight(defaultFlags)) {
|
||||
notificationDefaults = notificationDefaults or NotificationCompat.DEFAULT_LIGHTS
|
||||
}
|
||||
if (isNotificationAudible()) {
|
||||
if (AccountPreferences.isNotificationHasVibration(defaultFlags)) {
|
||||
notificationDefaults = notificationDefaults or NotificationCompat.DEFAULT_VIBRATE
|
||||
} else {
|
||||
notificationDefaults = notificationDefaults and NotificationCompat.DEFAULT_VIBRATE.inv()
|
||||
}
|
||||
if (AccountPreferences.isNotificationHasRingtone(defaultFlags)) {
|
||||
builder.setSound(pref.notificationRingtone, AudioManager.STREAM_NOTIFICATION)
|
||||
}
|
||||
} else {
|
||||
notificationDefaults = notificationDefaults and (NotificationCompat.DEFAULT_VIBRATE or NotificationCompat.DEFAULT_SOUND).inv()
|
||||
}
|
||||
builder.color = pref.notificationLightColor
|
||||
builder.setDefaults(notificationDefaults)
|
||||
builder.setOnlyAlertOnce(true)
|
||||
}
|
||||
|
||||
private fun isNotificationAudible(): Boolean {
|
||||
return !activityTracker.isHomeActivityStarted
|
||||
}
|
||||
|
||||
fun showTimelineNotification(pref: AccountPreferences, position: Long) {
|
||||
val accountKey = pref.accountKey
|
||||
val resources = context.resources
|
||||
val nm = notificationManager
|
||||
val selection = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
|
||||
Expression.greaterThan(Statuses.POSITION_KEY, position))
|
||||
val filteredSelection = buildStatusFilterWhereClause(preferences,
|
||||
Statuses.TABLE_NAME, selection)
|
||||
val selectionArgs = arrayOf(accountKey.toString())
|
||||
val userProjection = arrayOf(Statuses.USER_KEY, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME)
|
||||
val statusProjection = arrayOf(Statuses.POSITION_KEY)
|
||||
val statusCursor = context.contentResolver.query(Statuses.CONTENT_URI, statusProjection,
|
||||
filteredSelection.sql, selectionArgs, Statuses.DEFAULT_SORT_ORDER)
|
||||
|
||||
val userCursor = context.contentResolver.rawQuery(SQLQueryBuilder.select(Columns(*userProjection))
|
||||
.from(Table(Statuses.TABLE_NAME))
|
||||
.where(filteredSelection)
|
||||
.groupBy(Columns.Column(Statuses.USER_KEY))
|
||||
.orderBy(OrderBy(Statuses.DEFAULT_SORT_ORDER)).buildSQL(), selectionArgs)
|
||||
|
||||
try {
|
||||
val usersCount = userCursor.count
|
||||
val statusesCount = statusCursor.count
|
||||
if (statusesCount == 0 || usersCount == 0) return
|
||||
val statusIndices = ParcelableStatusCursorIndices(statusCursor)
|
||||
val userIndices = ParcelableStatusCursorIndices(userCursor)
|
||||
val positionKey = if (statusCursor.moveToFirst()) statusCursor.getLong(statusIndices.position_key) else -1L
|
||||
val notificationTitle = resources.getQuantityString(R.plurals.N_new_statuses,
|
||||
statusesCount, statusesCount)
|
||||
val notificationContent: String
|
||||
userCursor.moveToFirst()
|
||||
val displayName = userColorNameManager.getDisplayName(userCursor.getString(userIndices.user_key),
|
||||
userCursor.getString(userIndices.user_name), userCursor.getString(userIndices.user_screen_name),
|
||||
nameFirst)
|
||||
if (usersCount == 1) {
|
||||
notificationContent = context.getString(R.string.from_name, displayName)
|
||||
} else if (usersCount == 2) {
|
||||
userCursor.moveToPosition(1)
|
||||
val othersName = userColorNameManager.getDisplayName(userCursor.getString(userIndices.user_key),
|
||||
userCursor.getString(userIndices.user_name), userCursor.getString(userIndices.user_screen_name),
|
||||
nameFirst)
|
||||
notificationContent = resources.getString(R.string.from_name_and_name, displayName, othersName)
|
||||
} else {
|
||||
notificationContent = resources.getString(R.string.from_name_and_N_others, displayName, usersCount - 1)
|
||||
}
|
||||
|
||||
// Setup notification
|
||||
val builder = NotificationCompat.Builder(context)
|
||||
builder.setAutoCancel(true)
|
||||
builder.setSmallIcon(R.drawable.ic_stat_twitter)
|
||||
builder.setTicker(notificationTitle)
|
||||
builder.setContentTitle(notificationTitle)
|
||||
builder.setContentText(notificationContent)
|
||||
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL)
|
||||
builder.setContentIntent(getContentIntent(context, CustomTabType.HOME_TIMELINE,
|
||||
NotificationType.HOME_TIMELINE, accountKey, positionKey))
|
||||
builder.setDeleteIntent(getMarkReadDeleteIntent(context, NotificationType.HOME_TIMELINE,
|
||||
accountKey, positionKey, false))
|
||||
builder.setNumber(statusesCount)
|
||||
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL)
|
||||
applyNotificationPreferences(builder, pref, pref.homeTimelineNotificationType)
|
||||
try {
|
||||
nm.notify("home_" + accountKey, Utils.getNotificationId(NOTIFICATION_ID_HOME_TIMELINE, accountKey), builder.build())
|
||||
Utils.sendPebbleNotification(context, null, notificationContent)
|
||||
} catch (e: SecurityException) {
|
||||
// Silently ignore
|
||||
}
|
||||
|
||||
} finally {
|
||||
statusCursor.close()
|
||||
userCursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getContentIntent(context: Context, @CustomTabType type: String,
|
||||
@NotificationType notificationType: String, accountKey: UserKey?, readPosition: Long): PendingIntent {
|
||||
// Setup click intent
|
||||
val homeIntent = Intent(context, HomeActivity::class.java)
|
||||
val homeLinkBuilder = Uri.Builder()
|
||||
homeLinkBuilder.scheme(SCHEME_TWIDERE)
|
||||
homeLinkBuilder.authority(type)
|
||||
if (accountKey != null)
|
||||
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_KEY, accountKey.toString())
|
||||
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_FROM_NOTIFICATION, true.toString())
|
||||
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, System.currentTimeMillis().toString())
|
||||
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE, notificationType)
|
||||
if (readPosition > 0) {
|
||||
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_READ_POSITION, readPosition.toString())
|
||||
}
|
||||
homeIntent.data = homeLinkBuilder.build()
|
||||
homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
return PendingIntent.getActivity(context, 0, homeIntent, 0)
|
||||
}
|
||||
|
||||
|
||||
fun setNotificationUri(c: Cursor?, uri: Uri?) {
|
||||
val cr = context.contentResolver
|
||||
if (cr == null || c == null || uri == null) return
|
||||
c.setNotificationUri(cr, uri)
|
||||
}
|
||||
|
||||
private fun updatePreferences() {
|
||||
nameFirst = preferences[nameFirstKey]
|
||||
useStarForLikes = preferences[iWantMyStarsBackKey]
|
||||
}
|
||||
|
||||
|
||||
private fun getMarkReadDeleteIntent(context: Context, @NotificationType notificationType: String,
|
||||
accountKey: UserKey?, positions: Array<StringLongPair>): PendingIntent {
|
||||
// Setup delete intent
|
||||
val intent = Intent(context, NotificationReceiver::class.java)
|
||||
val linkBuilder = Uri.Builder()
|
||||
linkBuilder.scheme(SCHEME_TWIDERE)
|
||||
linkBuilder.authority(AUTHORITY_INTERACTIONS)
|
||||
linkBuilder.appendPath(notificationType)
|
||||
if (accountKey != null) {
|
||||
linkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_KEY, accountKey.toString())
|
||||
}
|
||||
linkBuilder.appendQueryParameter(QUERY_PARAM_READ_POSITIONS, StringLongPair.toString(positions))
|
||||
linkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, System.currentTimeMillis().toString())
|
||||
linkBuilder.appendQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE, notificationType)
|
||||
intent.data = linkBuilder.build()
|
||||
return PendingIntent.getBroadcast(context, 0, intent, 0)
|
||||
}
|
||||
|
||||
|
||||
private fun getMarkReadDeleteIntent(context: Context, @NotificationType type: String,
|
||||
accountKey: UserKey?, position: Long,
|
||||
extraUserFollowing: Boolean): PendingIntent {
|
||||
return getMarkReadDeleteIntent(context, type, accountKey, position, -1, -1, extraUserFollowing)
|
||||
}
|
||||
|
||||
private fun getMarkReadDeleteIntent(context: Context, @NotificationType type: String,
|
||||
accountKey: UserKey?, position: Long,
|
||||
extraId: Long, extraUserId: Long,
|
||||
extraUserFollowing: Boolean): PendingIntent {
|
||||
// Setup delete intent
|
||||
val intent = Intent(context, NotificationReceiver::class.java)
|
||||
intent.action = IntentConstants.BROADCAST_NOTIFICATION_DELETED
|
||||
val linkBuilder = Uri.Builder()
|
||||
linkBuilder.scheme(SCHEME_TWIDERE)
|
||||
linkBuilder.authority(AUTHORITY_INTERACTIONS)
|
||||
linkBuilder.appendPath(type)
|
||||
if (accountKey != null) {
|
||||
linkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_KEY, accountKey.toString())
|
||||
}
|
||||
linkBuilder.appendQueryParameter(QUERY_PARAM_READ_POSITION, position.toString())
|
||||
linkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, System.currentTimeMillis().toString())
|
||||
linkBuilder.appendQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE, type)
|
||||
|
||||
UriExtraUtils.addExtra(linkBuilder, "item_id", extraId)
|
||||
UriExtraUtils.addExtra(linkBuilder, "item_user_id", extraUserId)
|
||||
UriExtraUtils.addExtra(linkBuilder, "item_user_following", extraUserFollowing)
|
||||
intent.data = linkBuilder.build()
|
||||
return PendingIntent.getBroadcast(context, 0, intent, 0)
|
||||
}
|
||||
}
|
|
@ -24,8 +24,8 @@ import java.io.IOException
|
|||
*/
|
||||
|
||||
fun buildStatusFilterWhereClause(preferences: SharedPreferences,
|
||||
table: String,
|
||||
extraSelection: Expression?): Expression {
|
||||
table: String,
|
||||
extraSelection: Expression?): Expression {
|
||||
val filteredUsersQuery = SQLQueryBuilder
|
||||
.select(Columns.Column(Table(Filters.Users.TABLE_NAME), Filters.Users.USER_KEY))
|
||||
.from(Tables(Filters.Users.TABLE_NAME))
|
||||
|
@ -113,7 +113,6 @@ fun deleteAccountData(resolver: ContentResolver, accountKey: UserKey) {
|
|||
// Also delete tweets related to the account we previously
|
||||
// deleted.
|
||||
resolver.delete(Statuses.CONTENT_URI, where, whereArgs)
|
||||
resolver.delete(Mentions.CONTENT_URI, where, whereArgs)
|
||||
resolver.delete(Activities.AboutMe.CONTENT_URI, where, whereArgs)
|
||||
resolver.delete(Messages.CONTENT_URI, where, whereArgs)
|
||||
resolver.delete(Messages.Conversations.CONTENT_URI, where, whereArgs)
|
||||
|
@ -121,7 +120,7 @@ fun deleteAccountData(resolver: ContentResolver, accountKey: UserKey) {
|
|||
|
||||
|
||||
fun deleteActivityStatus(cr: ContentResolver, accountKey: UserKey,
|
||||
statusId: String, result: ParcelableStatus?) {
|
||||
statusId: String, result: ParcelableStatus?) {
|
||||
|
||||
val host = accountKey.host
|
||||
val deleteWhere: String
|
||||
|
@ -171,9 +170,9 @@ fun deleteActivityStatus(cr: ContentResolver, accountKey: UserKey,
|
|||
}
|
||||
|
||||
fun updateActivityStatus(resolver: ContentResolver,
|
||||
accountKey: UserKey,
|
||||
statusId: String,
|
||||
action: (ParcelableActivity) -> Unit) {
|
||||
accountKey: UserKey,
|
||||
statusId: String,
|
||||
action: (ParcelableActivity) -> Unit) {
|
||||
val activityWhere = Expression.and(
|
||||
Expression.equalsArgs(Activities.ACCOUNT_KEY),
|
||||
Expression.or(
|
||||
|
@ -190,8 +189,8 @@ fun updateActivityStatus(resolver: ContentResolver,
|
|||
|
||||
@WorkerThread
|
||||
fun updateActivity(cr: ContentResolver, uri: Uri,
|
||||
where: String?, whereArgs: Array<String>?,
|
||||
action: (ParcelableActivity) -> Unit) {
|
||||
where: String?, whereArgs: Array<String>?,
|
||||
action: (ParcelableActivity) -> Unit) {
|
||||
val c = cr.query(uri, Activities.COLUMNS, where, whereArgs, null) ?: return
|
||||
val values = LongSparseArray<ContentValues>()
|
||||
try {
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.mariotaku.twidere.extension.model.getAccountKey
|
|||
import org.mariotaku.twidere.extension.model.getAccountUser
|
||||
import org.mariotaku.twidere.extension.model.getColor
|
||||
import org.mariotaku.twidere.extension.model.isActivated
|
||||
import org.mariotaku.twidere.extension.rawQuery
|
||||
import org.mariotaku.twidere.model.*
|
||||
import org.mariotaku.twidere.model.tab.extra.HomeTabExtras
|
||||
import org.mariotaku.twidere.model.tab.extra.InteractionsTabExtras
|
||||
|
@ -112,32 +113,8 @@ object DataStoreUtils {
|
|||
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, CachedUsers.CONTENT_PATH_WITH_RELATIONSHIP + "/*",
|
||||
VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP)
|
||||
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH_WITH_SCORE + "/*",
|
||||
|
@ -698,8 +675,7 @@ object DataStoreUtils {
|
|||
if (sortExpression != null) {
|
||||
builder.orderBy(sortExpression)
|
||||
}
|
||||
val rawUri = Uri.withAppendedPath(TwidereDataStore.CONTENT_URI_RAW_QUERY, builder.buildSQL())
|
||||
resolver.query(rawUri, null, null, bindingArgs, null)?.useCursor { cur ->
|
||||
resolver.rawQuery(builder.buildSQL(), bindingArgs)?.useCursor { cur ->
|
||||
cur.moveToFirst()
|
||||
val colIdx = creator.newIndex(cur)
|
||||
while (!cur.isAfterLast) {
|
||||
|
@ -788,8 +764,7 @@ object DataStoreUtils {
|
|||
fun assign(array: T, arrayIdx: Int, cur: Cursor, colIdx: I)
|
||||
}
|
||||
|
||||
fun queryCount(context: Context, uri: Uri,
|
||||
selection: String?, selectionArgs: Array<String>?): Int {
|
||||
fun queryCount(context: Context, uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
|
||||
val resolver = context.contentResolver
|
||||
val projection = arrayOf(SQLFunctions.COUNT())
|
||||
val cur = resolver.query(uri, projection, selection, selectionArgs, null) ?: return -1
|
||||
|
|
|
@ -82,8 +82,8 @@ class TwidereSQLiteOpenHelper(
|
|||
db.beginTransaction()
|
||||
db.execSQL(createTable(Messages.TABLE_NAME, Messages.COLUMNS, Messages.TYPES, true,
|
||||
messagesConstraint()))
|
||||
db.execSQL(createTable(Conversations.TABLE_NAME, Conversations.COLUMNS,
|
||||
Conversations.TYPES, true, messageConversationsConstraint()))
|
||||
db.execSQL(createTable(Conversations.TABLE_NAME, Conversations.COLUMNS, Conversations.TYPES,
|
||||
true, messageConversationsConstraint()))
|
||||
db.setTransactionSuccessful()
|
||||
db.endTransaction()
|
||||
|
||||
|
@ -210,13 +210,13 @@ class TwidereSQLiteOpenHelper(
|
|||
}
|
||||
}
|
||||
if (oldVersion <= 164) {
|
||||
db.execSQL(SQLQueryBuilder.dropView(true, DirectMessages.TABLE_NAME).sql)
|
||||
db.execSQL(SQLQueryBuilder.dropView(true, DirectMessages.ConversationEntries.TABLE_NAME).sql)
|
||||
db.execSQL(SQLQueryBuilder.dropView(true, "messages").sql)
|
||||
db.execSQL(SQLQueryBuilder.dropView(true, "messages_conversation_entries").sql)
|
||||
db.execSQL(SQLQueryBuilder.dropTrigger(true, "delete_old_received_messages").sql)
|
||||
db.execSQL(SQLQueryBuilder.dropTrigger(true, "delete_old_sent_messages").sql)
|
||||
|
||||
db.execSQL(SQLQueryBuilder.dropTable(true, DirectMessages.Inbox.TABLE_NAME).sql)
|
||||
db.execSQL(SQLQueryBuilder.dropTable(true, DirectMessages.Outbox.TABLE_NAME).sql)
|
||||
db.execSQL(SQLQueryBuilder.dropTable(true, "messages_inbox").sql)
|
||||
db.execSQL(SQLQueryBuilder.dropTable(true, "messages_outbox").sql)
|
||||
|
||||
db.execSQL(SQLQueryBuilder.dropIndex(true, "messages_inbox_index").sql)
|
||||
db.execSQL(SQLQueryBuilder.dropIndex(true, "messages_outbox_index").sql)
|
||||
|
@ -301,7 +301,7 @@ class TwidereSQLiteOpenHelper(
|
|||
}
|
||||
|
||||
private fun createTable(tableName: String, columns: Array<String>, types: Array<String>,
|
||||
createIfNotExists: Boolean, vararg constraints: Constraint): String {
|
||||
createIfNotExists: Boolean, vararg constraints: Constraint): String {
|
||||
val qb = SQLQueryBuilder.createTable(createIfNotExists, tableName)
|
||||
qb.columns(*NewColumn.createNewColumns(columns, types))
|
||||
qb.constraint(*constraints)
|
||||
|
@ -309,7 +309,7 @@ class TwidereSQLiteOpenHelper(
|
|||
}
|
||||
|
||||
private fun createIndex(indexName: String, tableName: String, columns: Array<String>,
|
||||
createIfNotExists: Boolean): String {
|
||||
createIfNotExists: Boolean): String {
|
||||
val qb = SQLQueryBuilder.createIndex(false, createIfNotExists)
|
||||
qb.name(indexName)
|
||||
qb.on(Table(tableName), Columns(*columns))
|
||||
|
|
|
@ -132,7 +132,7 @@ class ApplicationModule(private val application: Application) {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun restHttpClient(prefs: SharedPreferencesWrapper, dns: TwidereDns,
|
||||
connectionPool: ConnectionPool): RestHttpClient {
|
||||
connectionPool: ConnectionPool): RestHttpClient {
|
||||
val conf = HttpClientFactory.HttpClientConfiguration(prefs)
|
||||
return HttpClientFactory.createRestHttpClient(conf, dns, connectionPool)
|
||||
}
|
||||
|
@ -180,8 +180,8 @@ class ApplicationModule(private val application: Application) {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun asyncTwitterWrapper(bus: Bus, preferences: SharedPreferencesWrapper,
|
||||
asyncTaskManager: AsyncTaskManager): AsyncTwitterWrapper {
|
||||
return AsyncTwitterWrapper(application, bus, preferences, asyncTaskManager)
|
||||
asyncTaskManager: AsyncTaskManager, notificationManagerWrapper: NotificationManagerWrapper): AsyncTwitterWrapper {
|
||||
return AsyncTwitterWrapper(application, bus, preferences, asyncTaskManager, notificationManagerWrapper)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
@ -190,6 +190,13 @@ class ApplicationModule(private val application: Application) {
|
|||
return ReadStateManager(application)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun contentNotificationManager(activityTracker: ActivityTracker, userColorNameManager: UserColorNameManager,
|
||||
notificationManagerWrapper: NotificationManagerWrapper, preferences: SharedPreferencesWrapper): ContentNotificationManager {
|
||||
return ContentNotificationManager(application, activityTracker, userColorNameManager, notificationManagerWrapper, preferences)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun mediaLoaderWrapper(loader: ImageLoader, preferences: SharedPreferencesWrapper): MediaLoaderWrapper {
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 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.database
|
||||
|
||||
import android.content.ContentResolver
|
||||
import org.mariotaku.twidere.extension.rawQuery
|
||||
import org.mariotaku.twidere.model.SpanItem
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Filters
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/2/16.
|
||||
*/
|
||||
|
||||
object FilterQueryBuilder {
|
||||
|
||||
fun isFiltered(cr: ContentResolver, userKey: UserKey?, textPlain: String?, quotedTextPlain: String?,
|
||||
spans: Array<SpanItem>?, quotedSpans: Array<SpanItem>?, source: String?, quotedSource: String?,
|
||||
retweetedByKey: UserKey?, quotedUserKey: UserKey?): Boolean {
|
||||
val query = FilterQueryBuilder.isFilteredQuery(userKey,
|
||||
textPlain, quotedTextPlain, spans, quotedSpans, source, quotedSource, retweetedByKey,
|
||||
quotedUserKey, true)
|
||||
val cur = cr.rawQuery(query.first, query.second)
|
||||
try {
|
||||
return cur.moveToFirst() && cur.getInt(0) != 0
|
||||
} finally {
|
||||
cur.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun isFilteredQuery(userKey: UserKey?, textPlain: String?, quotedTextPlain: String?,
|
||||
spans: Array<SpanItem>?, quotedSpans: Array<SpanItem>?, source: String?, quotedSource: String?,
|
||||
retweetedByKey: UserKey?, quotedUserKey: UserKey?, filterRts: Boolean): Pair<String, Array<String>> {
|
||||
val builder = StringBuilder()
|
||||
val selectionArgs = ArrayList<String>()
|
||||
builder.append("SELECT ")
|
||||
if (textPlain != null) {
|
||||
selectionArgs.add(textPlain)
|
||||
addTextPlainStatement(builder)
|
||||
}
|
||||
if (quotedTextPlain != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ")
|
||||
}
|
||||
selectionArgs.add(quotedTextPlain)
|
||||
addTextPlainStatement(builder)
|
||||
}
|
||||
if (spans != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ")
|
||||
}
|
||||
addSpansStatement(spans, builder, selectionArgs)
|
||||
}
|
||||
if (quotedSpans != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ")
|
||||
}
|
||||
addSpansStatement(quotedSpans, builder, selectionArgs)
|
||||
}
|
||||
if (userKey != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ")
|
||||
}
|
||||
selectionArgs.add(userKey.toString())
|
||||
createUserKeyStatement(builder)
|
||||
}
|
||||
if (retweetedByKey != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ")
|
||||
}
|
||||
selectionArgs.add(retweetedByKey.toString())
|
||||
createUserKeyStatement(builder)
|
||||
}
|
||||
if (quotedUserKey != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ")
|
||||
}
|
||||
selectionArgs.add(quotedUserKey.toString())
|
||||
createUserKeyStatement(builder)
|
||||
}
|
||||
if (source != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ")
|
||||
}
|
||||
selectionArgs.add(source)
|
||||
appendSourceStatement(builder)
|
||||
}
|
||||
if (quotedSource != null) {
|
||||
if (!selectionArgs.isEmpty()) {
|
||||
builder.append(" OR ")
|
||||
}
|
||||
selectionArgs.add(quotedSource)
|
||||
appendSourceStatement(builder)
|
||||
}
|
||||
return Pair(builder.toString(), selectionArgs.toTypedArray())
|
||||
}
|
||||
|
||||
|
||||
private fun createUserKeyStatement(builder: StringBuilder) {
|
||||
builder.append("(SELECT ")
|
||||
.append("?")
|
||||
.append(" IN (SELECT ")
|
||||
.append(Filters.Users.USER_KEY)
|
||||
.append(" FROM ")
|
||||
.append(Filters.Users.TABLE_NAME)
|
||||
.append("))")
|
||||
}
|
||||
|
||||
private fun appendSourceStatement(builder: StringBuilder) {
|
||||
builder.append("(SELECT 1 IN (SELECT ? LIKE '%>'||")
|
||||
.append(Filters.Sources.TABLE_NAME).append(".")
|
||||
.append(Filters.VALUE).append("||'</a>%' FROM ")
|
||||
.append(Filters.Sources.TABLE_NAME)
|
||||
.append("))")
|
||||
}
|
||||
|
||||
private fun addTextPlainStatement(builder: StringBuilder) {
|
||||
builder.append("(SELECT 1 IN (SELECT ? LIKE '%'||")
|
||||
.append(Filters.Keywords.TABLE_NAME)
|
||||
.append(".")
|
||||
.append(Filters.VALUE)
|
||||
.append("||'%' FROM ")
|
||||
.append(Filters.Keywords.TABLE_NAME)
|
||||
.append("))")
|
||||
}
|
||||
|
||||
private fun addSpansStatement(spans: Array<SpanItem>, builder: StringBuilder, selectionArgs: MutableList<String>) {
|
||||
val spansFlat = StringBuilder()
|
||||
for (span in spans) {
|
||||
spansFlat.append(span.link)
|
||||
spansFlat.append(' ')
|
||||
}
|
||||
selectionArgs.add(spansFlat.toString())
|
||||
builder.append("(SELECT 1 IN (SELECT ? LIKE '%'||")
|
||||
.append(Filters.Links.TABLE_NAME)
|
||||
.append(".")
|
||||
.append(Filters.VALUE)
|
||||
.append("||'%' FROM ")
|
||||
.append(Filters.Links.TABLE_NAME)
|
||||
.append("))")
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 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.database
|
||||
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.database.MergeCursor
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import org.mariotaku.ktextension.convert
|
||||
import org.mariotaku.sqliteqb.library.*
|
||||
import org.mariotaku.twidere.TwidereConstants.*
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.*
|
||||
import org.mariotaku.twidere.util.SQLiteDatabaseWrapper
|
||||
import org.mariotaku.twidere.util.TwidereQueryBuilder
|
||||
import org.mariotaku.twidere.util.UserColorNameManager
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/2/16.
|
||||
*/
|
||||
|
||||
object SuggestionsCursorCreator {
|
||||
|
||||
private val PATTERN_SCREEN_NAME = Pattern.compile("(?i)[@\uFF20]?([a-z0-9_]{1,20})")
|
||||
|
||||
private val historyProjectionMap = mapOf(
|
||||
Suggestions._ID to Columns.Column(SearchHistory._ID, Suggestions._ID).sql,
|
||||
Suggestions.TYPE to Columns.Column("'${Suggestions.Search.TYPE_SEARCH_HISTORY}'", Suggestions.TYPE).sql,
|
||||
Suggestions.TITLE to Columns.Column(SearchHistory.QUERY, Suggestions.TITLE).sql,
|
||||
Suggestions.SUMMARY to Columns.Column(SQLConstants.NULL, Suggestions.SUMMARY).sql,
|
||||
Suggestions.ICON to Columns.Column(SQLConstants.NULL, Suggestions.ICON).sql,
|
||||
Suggestions.EXTRA_ID to Columns.Column("0", Suggestions.EXTRA_ID).sql,
|
||||
Suggestions.EXTRA to Columns.Column(SQLConstants.NULL, Suggestions.EXTRA).sql,
|
||||
Suggestions.VALUE to Columns.Column(SearchHistory.QUERY, Suggestions.VALUE).sql
|
||||
)
|
||||
|
||||
private val savedSearchesProjectionMap = mapOf(Suggestions._ID to Columns.Column(SavedSearches._ID, Suggestions._ID).sql,
|
||||
Suggestions.TYPE to Columns.Column("'${Suggestions.Search.TYPE_SAVED_SEARCH}'", Suggestions.TYPE).sql,
|
||||
Suggestions.TITLE to Columns.Column(SavedSearches.QUERY, Suggestions.TITLE).sql,
|
||||
Suggestions.SUMMARY to Columns.Column(SQLConstants.NULL, Suggestions.SUMMARY).sql,
|
||||
Suggestions.ICON to Columns.Column(SQLConstants.NULL, Suggestions.ICON).sql,
|
||||
Suggestions.EXTRA_ID to Columns.Column("0", Suggestions.EXTRA_ID).sql,
|
||||
Suggestions.EXTRA to Columns.Column(SQLConstants.NULL, Suggestions.EXTRA).sql,
|
||||
Suggestions.VALUE to Columns.Column(SavedSearches.QUERY, Suggestions.VALUE).sql
|
||||
)
|
||||
private val suggestionUsersProjectionMap = mapOf(Suggestions._ID to Columns.Column(CachedUsers._ID, Suggestions._ID).sql,
|
||||
Suggestions.TYPE to Columns.Column("'${Suggestions.Search.TYPE_USER}'", Suggestions.TYPE).sql,
|
||||
Suggestions.TITLE to Columns.Column(CachedUsers.NAME, Suggestions.TITLE).sql,
|
||||
Suggestions.SUMMARY to Columns.Column(CachedUsers.SCREEN_NAME, Suggestions.SUMMARY).sql,
|
||||
Suggestions.ICON to Columns.Column(CachedUsers.PROFILE_IMAGE_URL, Suggestions.ICON).sql,
|
||||
Suggestions.EXTRA_ID to Columns.Column(CachedUsers.USER_KEY, Suggestions.EXTRA_ID).sql,
|
||||
Suggestions.EXTRA to Columns.Column(SQLConstants.NULL, Suggestions.EXTRA).sql,
|
||||
Suggestions.VALUE to Columns.Column(CachedUsers.SCREEN_NAME, Suggestions.VALUE).sql
|
||||
)
|
||||
|
||||
private val autoCompleteUsersProjectionMap = mapOf(Suggestions._ID to Columns.Column(CachedUsers._ID, Suggestions._ID).sql,
|
||||
Suggestions.TYPE to Columns.Column("'${Suggestions.AutoComplete.TYPE_USERS}'", Suggestions.TYPE).sql,
|
||||
Suggestions.TITLE to Columns.Column(CachedUsers.NAME, Suggestions.TITLE).sql,
|
||||
Suggestions.SUMMARY to Columns.Column(CachedUsers.SCREEN_NAME, Suggestions.SUMMARY).sql,
|
||||
Suggestions.ICON to Columns.Column(CachedUsers.PROFILE_IMAGE_URL, Suggestions.ICON).sql,
|
||||
Suggestions.EXTRA_ID to Columns.Column(CachedUsers.USER_KEY, Suggestions.EXTRA_ID).sql,
|
||||
Suggestions.EXTRA to Columns.Column(SQLConstants.NULL, Suggestions.EXTRA).sql,
|
||||
Suggestions.VALUE to Columns.Column(CachedUsers.SCREEN_NAME, Suggestions.VALUE).sql
|
||||
)
|
||||
|
||||
private val hashtagsProjectionMap = mapOf(Suggestions._ID to Columns.Column(CachedHashtags._ID, Suggestions._ID).sql,
|
||||
Suggestions.TYPE to Columns.Column("'${Suggestions.AutoComplete.TYPE_HASHTAGS}'", Suggestions.TYPE).sql,
|
||||
Suggestions.TITLE to Columns.Column(CachedHashtags.NAME, Suggestions.TITLE).sql,
|
||||
Suggestions.SUMMARY to Columns.Column(SQLConstants.NULL, Suggestions.SUMMARY).sql,
|
||||
Suggestions.ICON to Columns.Column(SQLConstants.NULL, Suggestions.ICON).sql,
|
||||
Suggestions.EXTRA_ID to Columns.Column("0", Suggestions.EXTRA_ID).sql,
|
||||
Suggestions.EXTRA to Columns.Column(SQLConstants.NULL, Suggestions.EXTRA).sql,
|
||||
Suggestions.VALUE to Columns.Column(CachedHashtags.NAME, Suggestions.VALUE).sql
|
||||
)
|
||||
|
||||
|
||||
fun forSearch(db: SQLiteDatabaseWrapper, manager: UserColorNameManager,
|
||||
uri: Uri, projection: Array<String>?): Cursor? {
|
||||
val nonNullProjection = projection ?: Suggestions.COLUMNS
|
||||
val query = uri.getQueryParameter(QUERY_PARAM_QUERY) ?: return null
|
||||
val accountKey = uri.getQueryParameter(QUERY_PARAM_ACCOUNT_KEY)?.convert(UserKey::valueOf) ?: return null
|
||||
val emptyQuery = TextUtils.isEmpty(query)
|
||||
val cursors = mutableListOf(getHistoryCursor(db, nonNullProjection, query))
|
||||
if (emptyQuery) {
|
||||
cursors.add(getSavedSearchCursor(db, nonNullProjection, accountKey))
|
||||
} else {
|
||||
val queryTrimmed = query.replace("_", "^_").substringAfter("@")
|
||||
|
||||
if (!hasName(db, queryTrimmed)) {
|
||||
val m = PATTERN_SCREEN_NAME.matcher(query)
|
||||
if (m.matches()) {
|
||||
val screenName = m.group(1)
|
||||
cursors.add(getScreenNameCursor(nonNullProjection, screenName))
|
||||
}
|
||||
}
|
||||
cursors.add(getUsersCursor(db, manager, accountKey, nonNullProjection, query, queryTrimmed))
|
||||
}
|
||||
return MergeCursor(cursors.toTypedArray())
|
||||
}
|
||||
|
||||
|
||||
fun forAutoComplete(db: SQLiteDatabaseWrapper, manager: UserColorNameManager,
|
||||
uri: Uri, projection: Array<String>?): Cursor? {
|
||||
val nonNullProjection = projection ?: Suggestions.COLUMNS
|
||||
val query = uri.getQueryParameter(QUERY_PARAM_QUERY) ?: return null
|
||||
val type = uri.getQueryParameter(QUERY_PARAM_TYPE) ?: return null
|
||||
val accountKey = uri.getQueryParameter(QUERY_PARAM_ACCOUNT_KEY)?.convert(UserKey::valueOf) ?: return null
|
||||
val queryEscaped = query.replace("_", "^_")
|
||||
when (type) {
|
||||
Suggestions.AutoComplete.TYPE_USERS -> {
|
||||
val nicknameKeys = Utils.getMatchedNicknameKeys(query, manager)
|
||||
val where = Expression.or(Expression.inArgs(Columns.Column(CachedUsers.USER_KEY), nicknameKeys.size),
|
||||
Expression.likeRaw(Columns.Column(CachedUsers.SCREEN_NAME), "?||'%'", "^"),
|
||||
Expression.likeRaw(Columns.Column(CachedUsers.NAME), "?||'%'", "^"))
|
||||
val whereArgs = nicknameKeys + queryEscaped + queryEscaped
|
||||
val orderBy = arrayOf(CachedUsers.SCORE, CachedUsers.LAST_SEEN, CachedUsers.SCREEN_NAME, CachedUsers.NAME)
|
||||
val ascending = booleanArrayOf(false, false, true, true)
|
||||
val mappedProjection = nonNullProjection.map { autoCompleteUsersProjectionMap[it] }.toTypedArray()
|
||||
val (sql, bindingArgs) = TwidereQueryBuilder.CachedUsersQueryBuilder.withScore(mappedProjection,
|
||||
where.sql, whereArgs, OrderBy(orderBy, ascending).sql, accountKey, 0)
|
||||
return db.rawQuery(sql.sql, bindingArgs)
|
||||
}
|
||||
Suggestions.AutoComplete.TYPE_HASHTAGS -> {
|
||||
val where = Expression.likeRaw(Columns.Column(CachedHashtags.NAME), "?||'%'", "^")
|
||||
val whereArgs = arrayOf(queryEscaped)
|
||||
val mappedProjection = nonNullProjection.map { hashtagsProjectionMap[it] }.toTypedArray()
|
||||
return db.query(CachedHashtags.TABLE_NAME, mappedProjection, where.sql, whereArgs, null,
|
||||
null, null)
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUsersCursor(db: SQLiteDatabaseWrapper, manager: UserColorNameManager, accountKey: UserKey,
|
||||
selection: Array<String>, query: String, queryTrimmed: String): Cursor {
|
||||
val nicknameKeys = Utils.getMatchedNicknameKeys(query, manager)
|
||||
val usersSelection = Expression.or(
|
||||
Expression.inArgs(Columns.Column(CachedUsers.USER_KEY), nicknameKeys.size),
|
||||
Expression.likeRaw(Columns.Column(CachedUsers.SCREEN_NAME), "?||'%'", "^"),
|
||||
Expression.likeRaw(Columns.Column(CachedUsers.NAME), "?||'%'", "^")
|
||||
)
|
||||
val selectionArgs = nicknameKeys + queryTrimmed + queryTrimmed
|
||||
val order = arrayOf(CachedUsers.LAST_SEEN, CachedUsers.SCORE, CachedUsers.SCREEN_NAME, CachedUsers.NAME)
|
||||
val ascending = booleanArrayOf(false, false, true, true)
|
||||
val orderBy = OrderBy(order, ascending)
|
||||
val usersProjection = selection.map { suggestionUsersProjectionMap[it] }.toTypedArray()
|
||||
val usersQuery = TwidereQueryBuilder.CachedUsersQueryBuilder.withScore(usersProjection,
|
||||
usersSelection.sql, selectionArgs, orderBy.sql, accountKey, 0)
|
||||
return db.rawQuery(usersQuery.first.sql, usersQuery.second)
|
||||
}
|
||||
|
||||
private fun getSavedSearchCursor(db: SQLiteDatabaseWrapper, projection: Array<String>, accountKey: UserKey): Cursor {
|
||||
val savedSearchesWhere = Expression.equalsArgs(SavedSearches.ACCOUNT_KEY)
|
||||
val whereArgs = arrayOf(accountKey.toString())
|
||||
val savedSearchesProjection = projection.map { savedSearchesProjectionMap[it] }.toTypedArray()
|
||||
return db.query(true, SavedSearches.TABLE_NAME, savedSearchesProjection, savedSearchesWhere.sql,
|
||||
whereArgs, null, null, SavedSearches.DEFAULT_SORT_ORDER, null)
|
||||
}
|
||||
|
||||
private fun getHistoryCursor(db: SQLiteDatabaseWrapper, projection: Array<String>, query: String): Cursor {
|
||||
val queryEscaped = query.replace("_", "^_")
|
||||
val historySelection = Expression.likeRaw(Columns.Column(SearchHistory.QUERY), "?||'%'", "^")
|
||||
val historySelectionArgs = arrayOf(queryEscaped)
|
||||
val historyProjection = projection.map { historyProjectionMap[it] }.toTypedArray()
|
||||
val cursorLimit = if (TextUtils.isEmpty(query)) "3" else "2"
|
||||
return db.query(true, SearchHistory.TABLE_NAME, historyProjection, historySelection.sql,
|
||||
historySelectionArgs, null, null, SearchHistory.DEFAULT_SORT_ORDER, cursorLimit)
|
||||
}
|
||||
|
||||
private fun getScreenNameCursor(projection: Array<String>, screenName: String): Cursor {
|
||||
fun mapSelectionToValue(column: String) = when (column) {
|
||||
Suggestions._ID -> "0"
|
||||
Suggestions.TYPE -> Suggestions.Search.TYPE_SCREEN_NAME
|
||||
Suggestions.TITLE -> screenName
|
||||
Suggestions.EXTRA_ID -> "0"
|
||||
Suggestions.VALUE -> screenName
|
||||
else -> null
|
||||
}
|
||||
|
||||
val cursor = MatrixCursor(projection)
|
||||
cursor.addRow(projection.map(::mapSelectionToValue))
|
||||
return cursor
|
||||
}
|
||||
|
||||
private fun hasName(db: SQLiteDatabaseWrapper, queryTrimmed: String): Boolean {
|
||||
val exactUserSelection = Expression.or(Expression.likeRaw(Columns.Column(CachedUsers.SCREEN_NAME), "?", "^"))
|
||||
val exactUserCursor = db.query(CachedUsers.TABLE_NAME, arrayOf(SQLFunctions.COUNT()),
|
||||
exactUserSelection.sql, arrayOf(queryTrimmed), null, null, null, "1")
|
||||
try {
|
||||
return exactUserCursor.moveToPosition(0) && exactUserCursor.getInt(0) > 0
|
||||
} finally {
|
||||
exactUserCursor.close()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue