removed old shit code

This commit is contained in:
Mariotaku Lee 2017-02-16 19:07:39 +08:00
parent eee0227074
commit 251b358918
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
29 changed files with 1499 additions and 2516 deletions

View File

@ -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";

View File

@ -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;
}

View File

@ -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";

View File

@ -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";

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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"

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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;
}

View File

@ -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)
}

View File

@ -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()

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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 ->

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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))

View File

@ -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 {

View File

@ -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("))")
}
}

View File

@ -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()
}
}
}