diff --git a/twidere.component.twitter4j.streaming/src/main/java/twitter4j/conf/StreamConfigurationBuilder.java b/twidere.component.twitter4j.streaming/src/main/java/twitter4j/conf/StreamConfigurationBuilder.java index f4d766736..6d09f7339 100644 --- a/twidere.component.twitter4j.streaming/src/main/java/twitter4j/conf/StreamConfigurationBuilder.java +++ b/twidere.component.twitter4j.streaming/src/main/java/twitter4j/conf/StreamConfigurationBuilder.java @@ -20,6 +20,7 @@ package twitter4j.conf; import twitter4j.http.HostAddressResolverFactory; +import twitter4j.http.HttpClientFactory; /** * A builder that can be used to construct a twitter4j configuration with @@ -31,7 +32,7 @@ import twitter4j.http.HostAddressResolverFactory; * @author John Sirois - john.sirois at gmail.com */ @SuppressWarnings("unused") -public final class StreamConfigurationBuilder { +public final class StreamConfigurationBuilder { private StreamConfigurationBase configuration = new StreamConfigurationBase(); @@ -93,6 +94,10 @@ public final class StreamConfigurationBuilder { return this; } + public void setHttpClientFactory(HttpClientFactory factory) { + configuration.setHttpClientFactory(factory); + } + public StreamConfigurationBuilder setHttpConnectionTimeout(final int httpConnectionTimeout) { checkNotBuilt(); configuration.setHttpConnectionTimeout(httpConnectionTimeout); diff --git a/twidere.extension.streaming/build.gradle b/twidere.extension.streaming/build.gradle index 3108a086f..e1d85315b 100644 --- a/twidere.extension.streaming/build.gradle +++ b/twidere.extension.streaming/build.gradle @@ -27,7 +27,7 @@ android { minSdkVersion 14 targetSdkVersion 22 versionCode 13 - versionName "1.10 (0.3.0-dev)" + versionName "1.11 (0.3.0-dev)" } buildTypes { release { @@ -42,6 +42,7 @@ android { } dependencies { + compile 'com.squareup.okhttp:okhttp:2.3.0' compile project(':twidere.library.extension') compile project(':twidere.component.twitter4j.streaming') compile fileTree(dir: 'libs', include: ['*.jar']) diff --git a/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/Constants.java b/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/Constants.java index ce39d5275..49d5a4135 100644 --- a/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/Constants.java +++ b/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/Constants.java @@ -4,12 +4,9 @@ import org.mariotaku.twidere.TwidereConstants; public interface Constants extends TwidereConstants { - public static final String LOGTAG = "Twidere Stream Extension"; + public static final String LOGTAG = "Twidere.Streaming"; public static final String PREFERENCE_KEY_ACCOUNT_IDS = "account_ids"; public static final String PREFERENCE_KEY_ENABLE_STREAMING = "enable_streaming"; - public static final String TWITTER_CONSUMER_KEY = "uAFVpMhBntJutfVj6abfA"; - public static final String TWITTER_CONSUMER_SECRET = "JARXkJTfxo0F8MyctYy9bUmrLISjo8vXAHsZHYuk2E"; - } diff --git a/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/StreamingService.java b/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/StreamingService.java index f99c6c2f3..9f4cc9b3a 100644 --- a/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/StreamingService.java +++ b/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/StreamingService.java @@ -19,6 +19,7 @@ import android.widget.Toast; import org.mariotaku.twidere.Twidere; import org.mariotaku.twidere.TwidereSharedPreferences; +import org.mariotaku.twidere.extension.streaming.util.OkHttpClientFactory; import org.mariotaku.twidere.extension.streaming.util.TwidereHostAddressResolverFactory; import org.mariotaku.twidere.extension.streaming.util.Utils; import org.mariotaku.twidere.library.twitter4j.streaming.BuildConfig; @@ -51,8 +52,8 @@ import static android.text.TextUtils.isEmpty; public class StreamingService extends Service implements Constants, PrivateConstants { - private static final int NOTIFITION_SERVICE_STARTED = 1; - private static final int NOTIFITION_REQUEST_PERMISSION = 2; + private static final int NOTIFICATION_SERVICE_STARTED = 1; + private static final int NOTIFICATION_REQUEST_PERMISSION = 2; private final List> mTwitterInstances = new ArrayList<>(); private ContentResolver mResolver; @@ -116,7 +117,7 @@ public class StreamingService extends Service implements Constants, PrivateConst new Thread(new ShutdownStreamTwitterRunnable(twitter)).start(); } mTwitterInstances.clear(); - mNotificationManager.cancel(NOTIFITION_SERVICE_STARTED); + mNotificationManager.cancel(NOTIFICATION_SERVICE_STARTED); } @SuppressWarnings("deprecation") @@ -142,9 +143,9 @@ public class StreamingService extends Service implements Constants, PrivateConst notification.icon = R.drawable.ic_stat_twidere; notification.tickerText = getString(R.string.streaming_service_running); notification.setLatestEventInfo(this, contentTitle, contentText, contentIntent); - mNotificationManager.notify(NOTIFITION_SERVICE_STARTED, notification); + mNotificationManager.notify(NOTIFICATION_SERVICE_STARTED, notification); } else { - mNotificationManager.cancel(NOTIFITION_SERVICE_STARTED); + mNotificationManager.cancel(NOTIFICATION_SERVICE_STARTED); } } else { final Intent intent = new Intent(this, SettingsActivity.class); @@ -157,7 +158,7 @@ public class StreamingService extends Service implements Constants, PrivateConst notification.icon = R.drawable.ic_stat_login; notification.tickerText = getString(R.string.request_permission); notification.setLatestEventInfo(this, contentTitle, contentText, contentIntent); - mNotificationManager.notify(NOTIFITION_REQUEST_PERMISSION, notification); + mNotificationManager.notify(NOTIFICATION_REQUEST_PERMISSION, notification); } } @@ -176,6 +177,8 @@ public class StreamingService extends Service implements Constants, PrivateConst final long account_id = account.account_id; mAccountIds[i] = account_id; final StreamConfigurationBuilder cb = new StreamConfigurationBuilder(); + cb.setHttpClientFactory(new OkHttpClientFactory(this)); + cb.setHostAddressResolverFactory(new TwidereHostAddressResolverFactory(this)); cb.setGZIPEnabled(prefs.getBoolean(KEY_GZIP_COMPRESSING, true)); cb.setIncludeEntitiesEnabled(true); if (prefs.getBoolean(KEY_IGNORE_SSL_ERROR, false)) { diff --git a/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/util/OkHttpClientFactory.java b/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/util/OkHttpClientFactory.java new file mode 100644 index 000000000..d87f0dc27 --- /dev/null +++ b/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/util/OkHttpClientFactory.java @@ -0,0 +1,40 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.extension.streaming.util; + +import android.content.Context; + +import twitter4j.http.HttpClient; +import twitter4j.http.HttpClientConfiguration; +import twitter4j.http.HttpClientFactory; + +/** + * Created by mariotaku on 15/1/22. + */ +public class OkHttpClientFactory implements HttpClientFactory { + public OkHttpClientFactory(Context context) { + + } + + @Override + public HttpClient getInstance(HttpClientConfiguration conf) { + return new OkHttpClientImpl(conf); + } +} diff --git a/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/util/OkHttpClientImpl.java b/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/util/OkHttpClientImpl.java new file mode 100644 index 000000000..9501ff26d --- /dev/null +++ b/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/util/OkHttpClientImpl.java @@ -0,0 +1,276 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.extension.streaming.util; + +import android.net.SSLCertificateSocketFactory; +import android.net.Uri; + +import com.squareup.okhttp.Headers; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.MultipartBuilder; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request.Builder; +import com.squareup.okhttp.RequestBody; +import com.squareup.okhttp.Response; +import com.squareup.okhttp.internal.Internal; +import com.squareup.okhttp.internal.Network; + +import org.mariotaku.twidere.TwidereConstants; + +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Proxy.Type; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; + +import javax.net.SocketFactory; + +import okio.BufferedSink; +import twitter4j.TwitterException; +import twitter4j.auth.Authorization; +import twitter4j.http.HostAddressResolver; +import twitter4j.http.HttpClient; +import twitter4j.http.HttpClientConfiguration; +import twitter4j.http.HttpParameter; +import twitter4j.http.HttpRequest; +import twitter4j.http.HttpResponse; +import twitter4j.http.RequestMethod; + +/** + * Created by mariotaku on 15/1/22. + */ +public class OkHttpClientImpl implements HttpClient, TwidereConstants { + + public static final MediaType APPLICATION_FORM_URLENCODED = MediaType.parse("application/x-www-form-urlencoded; charset=UTF-8"); + private final HttpClientConfiguration conf; + private final OkHttpClient client; + private final HostAddressResolver resolver; + + public OkHttpClientImpl(HttpClientConfiguration conf) { + this.conf = conf; + this.resolver = conf.getHostAddressResolverFactory().getInstance(conf); + this.client = createHttpClient(conf); + } + + @Override + public HttpResponse request(HttpRequest req) throws TwitterException { + final Builder builder = new Builder(); + for (Entry headerEntry : req.getRequestHeaders().entrySet()) { + builder.header(headerEntry.getKey(), headerEntry.getValue()); + } + final Authorization authorization = req.getAuthorization(); + if (authorization != null) { + final String authHeader = authorization.getAuthorizationHeader(req); + if (authHeader != null) { + builder.header("Authorization", authHeader); + } + } + try { + setupRequestBuilder(builder, req); + final Response response = client.newCall(builder.build()).execute(); + return new OkHttpResponse(conf, null, response); + } catch (IOException e) { + throw new TwitterException(e); + } + } + + @Override + public void shutdown() { + + } + + private OkHttpClient createHttpClient(HttpClientConfiguration conf) { + final OkHttpClient client = new OkHttpClient(); + final boolean ignoreSSLError = conf.isSSLErrorIgnored(); + if (ignoreSSLError) { + client.setSslSocketFactory(SSLCertificateSocketFactory.getInsecure(0, null)); + } else { + client.setSslSocketFactory(SSLCertificateSocketFactory.getDefault(0, null)); + } + client.setSocketFactory(SocketFactory.getDefault()); + client.setConnectTimeout(conf.getHttpConnectionTimeout(), TimeUnit.MILLISECONDS); + + if (conf.isProxyConfigured()) { + client.setProxy(new Proxy(Type.HTTP, InetSocketAddress.createUnresolved(conf.getHttpProxyHost(), + conf.getHttpProxyPort()))); + } +// client.setHostnameVerifier(new HostResolvedHostnameVerifier()); + Internal.instance.setNetwork(client, new Network() { + @Override + public InetAddress[] resolveInetAddresses(String host) throws UnknownHostException { + try { + return resolver.resolve(host); + } catch (IOException e) { + if (e instanceof UnknownHostException) throw (UnknownHostException) e; + throw new UnknownHostException("Unable to resolve address " + e.getMessage()); + } + } + }); + return client; + } + + private RequestBody getRequestBody(HttpParameter[] params) throws IOException { + if (params == null) return null; + if (!HttpParameter.containsFile(params)) { + return RequestBody.create(APPLICATION_FORM_URLENCODED, HttpParameter.encodeParameters(params)); + } + final MultipartBuilder builder = new MultipartBuilder(); + builder.type(MultipartBuilder.FORM); + for (final HttpParameter param : params) { + if (param.isFile()) { + RequestBody requestBody; + if (param.hasFileBody()) { + requestBody = new StreamRequestBody(MediaType.parse(param.getContentType()), param.getFileBody(), true); + } else { + requestBody = RequestBody.create(MediaType.parse(param.getContentType()), param.getFile()); + } + builder.addFormDataPart(param.getName(), param.getFileName(), requestBody); + } else { + builder.addFormDataPart(param.getName(), param.getValue()); + } + } + return builder.build(); + } + + static class StreamRequestBody extends RequestBody { + + private final MediaType contentType; + private final InputStream stream; + private final boolean closeAfterWrite; + + StreamRequestBody(MediaType contentType, InputStream stream, boolean closeAfterWrite) { + this.contentType = contentType; + this.stream = stream; + this.closeAfterWrite = closeAfterWrite; + } + + @Override + public MediaType contentType() { + return contentType; + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + int len; + byte[] buf = new byte[8192]; + while ((len = stream.read(buf)) != -1) { + sink.write(buf, 0, len); + } + if (closeAfterWrite) { + Utils.closeSilently(stream); + } + } + } + + private void setupRequestBuilder(Builder builder, HttpRequest req) throws IOException { + final Uri.Builder uriBuilder = Uri.parse(req.getURL()).buildUpon(); + final RequestMethod method = req.getMethod(); + if (method != RequestMethod.POST && method != RequestMethod.PUT) { + final HttpParameter[] parameters = req.getParameters(); + if (parameters != null) { + for (HttpParameter param : parameters) { + uriBuilder.appendQueryParameter(param.getName(), param.getValue()); + } + } + } + final Uri uri = uriBuilder.build(); + switch (req.getMethod()) { + case GET: { + builder.get(); + break; + } + case POST: { + builder.post(getRequestBody(req.getParameters())); + break; + } + case DELETE: { + builder.delete(); + break; + } + case HEAD: { + builder.head(); + break; + } + case PUT: { + builder.put(getRequestBody(req.getParameters())); + break; + } + default: { + throw new AssertionError(); + } + } + builder.url(uri.toString()); + } + + private static class OkHttpResponse extends HttpResponse { + + private final Response response; + + public OkHttpResponse(HttpClientConfiguration conf, HttpRequest request, Response response) + throws TwitterException, IOException { + super(conf); + this.response = response; + statusCode = response.code(); + if ("gzip".equals(response.header("Content-Encoding"))) { + is = new GZIPInputStream(response.body().byteStream()); + } else { + is = response.body().byteStream(); + } + if (!response.isSuccessful()) { + throw new TwitterException(response.message(), request, this); + } + } + + @Override + public void disconnect() throws IOException { + if (is != null) { + is.close(); + } + } + + @Override + public String getResponseHeader(String name) { + return response.header(name); + } + + @Override + public Map> getResponseHeaderFields() { + final Headers headers = response.headers(); + final Map> maps = new HashMap<>(); + for (final String name : headers.names()) { + final List values = new ArrayList<>(1); + for (final String value : headers.values(name)) { + values.add(value); + } + maps.put(name, values); + } + return maps; + } + } +} diff --git a/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/util/Utils.java b/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/util/Utils.java index f28521e7f..98bbbb210 100644 --- a/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/util/Utils.java +++ b/twidere.extension.streaming/src/main/java/org/mariotaku/twidere/extension/streaming/util/Utils.java @@ -7,11 +7,23 @@ import org.mariotaku.twidere.TwidereConstants; import org.mariotaku.twidere.TwidereSharedPreferences; import org.mariotaku.twidere.provider.TwidereDataStore.Accounts; +import java.io.Closeable; +import java.io.IOException; + import static android.text.TextUtils.isEmpty; public class Utils implements TwidereConstants { + public static void closeSilently(Closeable closeable) { + if (closeable == null) return; + try { + closeable.close(); + } catch (IOException ignore) { + + } + } + public static long[] getActivatedAccountIds(final Context context) { long[] accounts = new long[0]; if (context == null) return accounts; diff --git a/twidere/build.gradle b/twidere/build.gradle index bfa6b12e1..bb6dae65b 100644 --- a/twidere/build.gradle +++ b/twidere/build.gradle @@ -10,7 +10,7 @@ android { applicationId "org.mariotaku.twidere" minSdkVersion 14 targetSdkVersion 22 - versionCode 100 + versionCode 101 versionName "0.3.0" multiDexEnabled true } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java index e8ba59893..29a905d36 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java @@ -148,7 +148,7 @@ public abstract class AbsStatusesFragment extends BaseSupportFragment impl public void setRefreshing(boolean refreshing) { if (refreshing == mSwipeRefreshLayout.isRefreshing()) return; - if (!refreshing) updateRefreshProgressOffset(); +// if (!refreshing) updateRefreshProgressOffset(); mSwipeRefreshLayout.setRefreshing(refreshing); } @@ -454,11 +454,15 @@ public abstract class AbsStatusesFragment extends BaseSupportFragment impl } private String getCurrentReadPositionTag() { - final String tag = getReadPositionTag(); + final String tag = getReadPositionTagWithAccounts(); if (tag == null) return null; return tag + "_current"; } + private String getReadPositionTagWithAccounts() { + return Utils.getReadPositionTagWithAccounts(getReadPositionTag(), getAccountIds()); + } + private void setListShown(boolean shown) { mProgressContainer.setVisibility(shown ? View.GONE : View.VISIBLE); mSwipeRefreshLayout.setVisibility(shown ? View.VISIBLE : View.GONE); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/DirectMessagesFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/DirectMessagesFragment.java index 4d1957578..d93360225 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/DirectMessagesFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/DirectMessagesFragment.java @@ -330,6 +330,7 @@ public class DirectMessagesFragment extends BaseSupportFragment implements Loade } public void setRefreshing(boolean refreshing) { + if (refreshing == mSwipeRefreshLayout.isRefreshing()) return; mSwipeRefreshLayout.setRefreshing(refreshing); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java b/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java index 08d4f4f73..c184835b8 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java +++ b/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java @@ -821,7 +821,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta getAccountIds(getContext())); for (final AccountPreferences pref : prefs) { if (!pref.isHomeTimelineNotificationEnabled()) continue; - showTimelineNotification(pref, mReadStateManager.getPosition(HomeTimelineFragment.KEY_READ_POSITION_TAG)); + showTimelineNotification(pref, getPositionTag(HomeTimelineFragment.KEY_READ_POSITION_TAG, + pref.getAccountId())); } notifyUnreadCountChanged(NOTIFICATION_ID_HOME_TIMELINE); break; @@ -831,7 +832,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta getAccountIds(getContext())); for (final AccountPreferences pref : prefs) { if (!pref.isMentionsNotificationEnabled()) continue; - showMentionsNotification(pref, mReadStateManager.getPosition(MentionsTimelineFragment.KEY_READ_POSITION_TAG)); + showMentionsNotification(pref, getPositionTag(MentionsTimelineFragment.KEY_READ_POSITION_TAG, + pref.getAccountId())); } notifyUnreadCountChanged(NOTIFICATION_ID_MENTIONS_TIMELINE); break; @@ -853,6 +855,13 @@ public final class TwidereDataProvider extends ContentProvider implements Consta } } + private long getPositionTag(String tag, long accountId) { + final long position = mReadStateManager.getPosition(Utils.getReadPositionTagWithAccounts(tag, + accountId)); + if (position != -1) return position; + return mReadStateManager.getPosition(tag); + } + private void showTimelineNotification(AccountPreferences pref, long position) { final long accountId = pref.getAccountId(); final Context context = getContext(); diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/ReadStateManager.java b/twidere/src/main/java/org/mariotaku/twidere/util/ReadStateManager.java index e621ca779..82debb83b 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/ReadStateManager.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/ReadStateManager.java @@ -63,6 +63,11 @@ public class ReadStateManager implements Constants { return pairs; } + + public long getPosition(final String key, final long keyId) { + return getPosition(key, String.valueOf(keyId)); + } + public long getPosition(final String key, final String keyId) { if (TextUtils.isEmpty(key)) return -1; final Set set = mPreferences.getStringSet(key, null); @@ -82,6 +87,11 @@ public class ReadStateManager implements Constants { mPreferences.unregisterOnSharedPreferenceChangeListener(listener); } + + public boolean setPosition(final String key, final long keyId, final long position, boolean acceptOlder) { + return setPosition(key, String.valueOf(keyId), position, acceptOlder); + } + public boolean setPosition(final String key, final String keyId, final long position, boolean acceptOlder) { if (TextUtils.isEmpty(key)) return false; Set set = mPreferences.getStringSet(key, null); diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java index 088745884..00bde193e 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java @@ -1084,6 +1084,26 @@ public final class Utils implements Constants, TwitterConstants { return intent; } + + public static String getReadPositionTagWithAccounts(String tag, Bundle args) { + final long[] accountIds; + if (args.containsKey(EXTRA_ACCOUNT_IDS)) { + accountIds = args.getLongArray(EXTRA_ACCOUNT_IDS); + } else if (args.containsKey(EXTRA_ACCOUNT_ID)) { + accountIds = new long[]{args.getLong(EXTRA_ACCOUNT_ID, -1)}; + } else { + accountIds = null; + } + return getReadPositionTagWithAccounts(tag, accountIds); + } + + public static String getReadPositionTagWithAccounts(String tag, long... accountIds) { + if (accountIds == null || accountIds.length == 0) return tag; + final long[] accountIdsClone = accountIds.clone(); + Arrays.sort(accountIdsClone); + return tag + "_" + TwidereArrayUtils.toString(accountIdsClone, '_', false); + } + public static String getStatusShareText(final Context context, final ParcelableStatus status) { final Uri link = LinkCreator.getTwitterStatusLink(status.user_screen_name, status.id); return context.getString(R.string.status_share_text_format_with_link,