2015-05-26 13:19:03 +02:00
|
|
|
package org.mariotaku.twidere.util;
|
|
|
|
|
2015-11-25 01:47:30 +01:00
|
|
|
import android.annotation.SuppressLint;
|
2015-05-26 13:19:03 +02:00
|
|
|
import android.content.Context;
|
|
|
|
import android.content.SharedPreferences;
|
2015-05-27 18:31:48 +02:00
|
|
|
import android.content.pm.PackageInfo;
|
|
|
|
import android.content.pm.PackageManager;
|
2015-05-26 13:19:03 +02:00
|
|
|
import android.net.SSLCertificateSocketFactory;
|
2015-12-26 10:58:07 +01:00
|
|
|
import android.os.Build;
|
2015-05-27 18:31:48 +02:00
|
|
|
import android.support.annotation.NonNull;
|
2015-05-26 13:19:03 +02:00
|
|
|
import android.support.annotation.Nullable;
|
2015-12-26 10:58:07 +01:00
|
|
|
import android.support.annotation.WorkerThread;
|
2015-05-27 18:31:48 +02:00
|
|
|
import android.text.TextUtils;
|
2015-06-30 01:59:22 +02:00
|
|
|
import android.webkit.URLUtil;
|
2015-05-26 13:19:03 +02:00
|
|
|
|
2015-12-30 12:34:40 +01:00
|
|
|
import com.squareup.okhttp.Authenticator;
|
|
|
|
import com.squareup.okhttp.Credentials;
|
2015-05-26 13:19:03 +02:00
|
|
|
import com.squareup.okhttp.OkHttpClient;
|
2015-12-30 12:34:40 +01:00
|
|
|
import com.squareup.okhttp.Request;
|
|
|
|
import com.squareup.okhttp.Response;
|
2015-05-26 13:19:03 +02:00
|
|
|
|
2015-11-25 03:30:37 +01:00
|
|
|
import org.apache.commons.lang3.math.NumberUtils;
|
2015-05-27 18:31:48 +02:00
|
|
|
import org.mariotaku.restfu.ExceptionFactory;
|
|
|
|
import org.mariotaku.restfu.HttpRequestFactory;
|
2015-11-14 13:36:56 +01:00
|
|
|
import org.mariotaku.restfu.Pair;
|
2015-05-27 18:31:48 +02:00
|
|
|
import org.mariotaku.restfu.RequestInfoFactory;
|
2015-05-26 13:19:03 +02:00
|
|
|
import org.mariotaku.restfu.RestAPIFactory;
|
2015-05-27 18:31:48 +02:00
|
|
|
import org.mariotaku.restfu.RestMethodInfo;
|
|
|
|
import org.mariotaku.restfu.RestRequestInfo;
|
|
|
|
import org.mariotaku.restfu.annotation.RestMethod;
|
2015-06-23 16:50:53 +02:00
|
|
|
import org.mariotaku.restfu.annotation.param.MethodExtra;
|
2015-05-26 13:19:03 +02:00
|
|
|
import org.mariotaku.restfu.http.Authorization;
|
|
|
|
import org.mariotaku.restfu.http.Endpoint;
|
2015-05-27 18:31:48 +02:00
|
|
|
import org.mariotaku.restfu.http.FileValue;
|
2015-05-26 13:19:03 +02:00
|
|
|
import org.mariotaku.restfu.http.RestHttpClient;
|
2015-05-27 18:31:48 +02:00
|
|
|
import org.mariotaku.restfu.http.RestHttpRequest;
|
|
|
|
import org.mariotaku.restfu.http.RestHttpResponse;
|
|
|
|
import org.mariotaku.restfu.http.mime.StringTypedData;
|
|
|
|
import org.mariotaku.restfu.http.mime.TypedData;
|
2015-11-14 13:36:56 +01:00
|
|
|
import org.mariotaku.restfu.okhttp.OkHttpRestClient;
|
2015-12-26 10:58:07 +01:00
|
|
|
import org.mariotaku.twidere.BuildConfig;
|
2015-05-26 13:19:03 +02:00
|
|
|
import org.mariotaku.twidere.TwidereConstants;
|
|
|
|
import org.mariotaku.twidere.api.twitter.Twitter;
|
2015-12-30 14:48:27 +01:00
|
|
|
import org.mariotaku.twidere.api.twitter.TwitterCaps;
|
2015-05-27 18:31:48 +02:00
|
|
|
import org.mariotaku.twidere.api.twitter.TwitterException;
|
|
|
|
import org.mariotaku.twidere.api.twitter.TwitterOAuth;
|
|
|
|
import org.mariotaku.twidere.api.twitter.TwitterUpload;
|
|
|
|
import org.mariotaku.twidere.api.twitter.TwitterUserStream;
|
|
|
|
import org.mariotaku.twidere.api.twitter.auth.BasicAuthorization;
|
|
|
|
import org.mariotaku.twidere.api.twitter.auth.EmptyAuthorization;
|
2015-05-26 13:19:03 +02:00
|
|
|
import org.mariotaku.twidere.api.twitter.auth.OAuthAuthorization;
|
2015-05-27 18:31:48 +02:00
|
|
|
import org.mariotaku.twidere.api.twitter.auth.OAuthEndpoint;
|
|
|
|
import org.mariotaku.twidere.api.twitter.auth.OAuthToken;
|
2015-05-26 13:19:03 +02:00
|
|
|
import org.mariotaku.twidere.api.twitter.util.TwitterConverter;
|
|
|
|
import org.mariotaku.twidere.model.ConsumerKeyType;
|
|
|
|
import org.mariotaku.twidere.model.ParcelableCredentials;
|
2015-06-24 17:13:03 +02:00
|
|
|
import org.mariotaku.twidere.model.RequestType;
|
2015-10-05 16:19:47 +02:00
|
|
|
import org.mariotaku.twidere.util.dagger.ApplicationModule;
|
2015-12-31 09:32:27 +01:00
|
|
|
import org.mariotaku.twidere.util.dagger.DependencyHolder;
|
2015-12-20 12:54:05 +01:00
|
|
|
import org.mariotaku.twidere.util.net.NetworkUsageUtils;
|
2015-12-31 09:32:27 +01:00
|
|
|
import org.mariotaku.twidere.util.net.TwidereProxySelector;
|
2015-05-26 13:19:03 +02:00
|
|
|
|
2015-12-30 12:34:40 +01:00
|
|
|
import java.io.IOException;
|
2015-05-26 13:19:03 +02:00
|
|
|
import java.net.InetSocketAddress;
|
|
|
|
import java.net.Proxy;
|
|
|
|
import java.net.SocketAddress;
|
2015-05-27 18:31:48 +02:00
|
|
|
import java.nio.charset.Charset;
|
|
|
|
import java.util.ArrayList;
|
2015-12-25 16:09:06 +01:00
|
|
|
import java.util.Collections;
|
2015-06-23 16:50:53 +02:00
|
|
|
import java.util.HashMap;
|
2015-05-27 18:31:48 +02:00
|
|
|
import java.util.List;
|
2015-12-26 10:58:07 +01:00
|
|
|
import java.util.Locale;
|
2015-05-27 18:31:48 +02:00
|
|
|
import java.util.Map;
|
2015-05-26 13:19:03 +02:00
|
|
|
import java.util.concurrent.TimeUnit;
|
2015-05-27 18:31:48 +02:00
|
|
|
import java.util.regex.Matcher;
|
|
|
|
import java.util.regex.Pattern;
|
2015-05-26 13:19:03 +02:00
|
|
|
|
2015-06-28 21:32:51 +02:00
|
|
|
import javax.net.ssl.SSLSocketFactory;
|
|
|
|
|
2015-05-26 13:19:03 +02:00
|
|
|
import static android.text.TextUtils.isEmpty;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Created by mariotaku on 15/5/7.
|
|
|
|
*/
|
|
|
|
public class TwitterAPIFactory implements TwidereConstants {
|
|
|
|
|
2015-12-31 09:32:27 +01:00
|
|
|
public static final String CARDS_PLATFORM_ANDROID_12 = "Android-12";
|
|
|
|
|
2015-12-26 10:58:07 +01:00
|
|
|
@WorkerThread
|
2015-05-26 13:19:03 +02:00
|
|
|
public static Twitter getDefaultTwitterInstance(final Context context, final boolean includeEntities) {
|
|
|
|
if (context == null) return null;
|
|
|
|
return getDefaultTwitterInstance(context, includeEntities, true);
|
|
|
|
}
|
|
|
|
|
2015-12-26 10:58:07 +01:00
|
|
|
@WorkerThread
|
2015-05-26 13:19:03 +02:00
|
|
|
public static Twitter getDefaultTwitterInstance(final Context context, final boolean includeEntities,
|
|
|
|
final boolean includeRetweets) {
|
|
|
|
if (context == null) return null;
|
|
|
|
return getTwitterInstance(context, Utils.getDefaultAccountId(context), includeEntities, includeRetweets);
|
|
|
|
}
|
|
|
|
|
2015-12-26 10:58:07 +01:00
|
|
|
@WorkerThread
|
2015-05-26 13:19:03 +02:00
|
|
|
public static Twitter getTwitterInstance(final Context context, final long accountId,
|
|
|
|
final boolean includeEntities) {
|
|
|
|
return getTwitterInstance(context, accountId, includeEntities, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable
|
2015-12-26 10:58:07 +01:00
|
|
|
@WorkerThread
|
2015-05-26 13:19:03 +02:00
|
|
|
public static Twitter getTwitterInstance(final Context context, final long accountId,
|
|
|
|
final boolean includeEntities,
|
|
|
|
final boolean includeRetweets) {
|
|
|
|
return getTwitterInstance(context, accountId, includeEntities, includeRetweets, Twitter.class);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable
|
2015-12-26 10:58:07 +01:00
|
|
|
@WorkerThread
|
2015-05-26 13:19:03 +02:00
|
|
|
public static <T> T getTwitterInstance(final Context context, final long accountId,
|
|
|
|
final boolean includeEntities,
|
|
|
|
final boolean includeRetweets, Class<T> cls) {
|
|
|
|
if (context == null) return null;
|
|
|
|
final ParcelableCredentials credentials = ParcelableCredentials.getCredentials(context, accountId);
|
2015-12-25 16:09:06 +01:00
|
|
|
final HashMap<String, String> extraParams = new HashMap<>();
|
|
|
|
extraParams.put("include_entities", String.valueOf(includeEntities));
|
|
|
|
extraParams.put("include_retweets", String.valueOf(includeRetweets));
|
|
|
|
return getInstance(context, credentials, extraParams, cls);
|
2015-05-26 13:19:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static RestHttpClient getDefaultHttpClient(final Context context) {
|
2015-12-31 09:32:27 +01:00
|
|
|
if (context == null) return null;
|
2015-05-26 13:19:03 +02:00
|
|
|
final SharedPreferencesWrapper prefs = SharedPreferencesWrapper.getInstance(context, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
2015-12-31 09:32:27 +01:00
|
|
|
return createHttpClient(context, prefs);
|
2015-05-26 13:19:03 +02:00
|
|
|
}
|
|
|
|
|
2015-12-31 09:32:27 +01:00
|
|
|
public static RestHttpClient createHttpClient(final Context context, final SharedPreferences prefs) {
|
2015-10-06 15:36:51 +02:00
|
|
|
final OkHttpClient client = new OkHttpClient();
|
2015-12-31 09:32:27 +01:00
|
|
|
updateHttpClientConfiguration(context, prefs, client);
|
2015-12-25 10:54:13 +01:00
|
|
|
DebugModeUtils.initForHttpClient(client);
|
2015-12-20 12:54:05 +01:00
|
|
|
NetworkUsageUtils.initForHttpClient(context, client);
|
2015-11-14 13:36:56 +01:00
|
|
|
return new OkHttpRestClient(client);
|
2015-10-06 15:36:51 +02:00
|
|
|
}
|
|
|
|
|
2015-11-25 01:47:30 +01:00
|
|
|
@SuppressLint("SSLCertificateSocketFactoryGetInsecure")
|
2015-12-31 09:32:27 +01:00
|
|
|
public static void updateHttpClientConfiguration(final Context context,
|
|
|
|
final SharedPreferences prefs, final OkHttpClient client) {
|
2015-05-26 13:19:03 +02:00
|
|
|
final int connectionTimeout = prefs.getInt(KEY_CONNECTION_TIMEOUT, 10);
|
|
|
|
final boolean ignoreSslError = prefs.getBoolean(KEY_IGNORE_SSL_ERROR, false);
|
|
|
|
final boolean enableProxy = prefs.getBoolean(KEY_ENABLE_PROXY, false);
|
|
|
|
|
|
|
|
client.setConnectTimeout(connectionTimeout, TimeUnit.SECONDS);
|
2015-12-29 08:26:52 +01:00
|
|
|
client.setReadTimeout(0, TimeUnit.SECONDS);
|
|
|
|
client.setWriteTimeout(0, TimeUnit.SECONDS);
|
2015-06-28 21:32:51 +02:00
|
|
|
final SSLSocketFactory sslSocketFactory;
|
2015-05-26 13:19:03 +02:00
|
|
|
if (ignoreSslError) {
|
2015-11-25 01:47:30 +01:00
|
|
|
// We intentionally use insecure connections
|
2015-12-29 08:26:52 +01:00
|
|
|
sslSocketFactory = SSLCertificateSocketFactory.getInsecure(0, null);
|
2015-06-28 21:32:51 +02:00
|
|
|
if (sslSocketFactory instanceof SSLCertificateSocketFactory) {
|
|
|
|
|
|
|
|
}
|
|
|
|
client.setSslSocketFactory(sslSocketFactory);
|
2015-10-06 18:45:39 +02:00
|
|
|
} else {
|
|
|
|
client.setSslSocketFactory(null);
|
2015-05-26 13:19:03 +02:00
|
|
|
}
|
|
|
|
if (enableProxy) {
|
2015-12-30 12:34:40 +01:00
|
|
|
final String proxyType = prefs.getString(KEY_PROXY_TYPE, null);
|
|
|
|
final String proxyHost = prefs.getString(KEY_PROXY_HOST, null);
|
|
|
|
final int proxyPort = NumberUtils.toInt(prefs.getString(KEY_PROXY_PORT, null), -1);
|
|
|
|
if (!isEmpty(proxyHost) && TwidereMathUtils.inRangeInclusiveInclusive(proxyPort, 0, 65535)) {
|
2015-12-31 09:32:27 +01:00
|
|
|
client.setProxySelector(new TwidereProxySelector(context, getProxyType(proxyType),
|
|
|
|
proxyHost, proxyPort));
|
2015-12-30 12:34:40 +01:00
|
|
|
}
|
|
|
|
final String username = prefs.getString(KEY_PROXY_USERNAME, null);
|
|
|
|
final String password = prefs.getString(KEY_PROXY_PASSWORD, null);
|
|
|
|
client.setAuthenticator(new Authenticator() {
|
|
|
|
@Override
|
|
|
|
public Request authenticate(Proxy proxy, Response response) throws IOException {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
|
|
|
|
final Request.Builder builder = response.request().newBuilder();
|
|
|
|
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
|
|
|
|
final String credential = Credentials.basic(username, password);
|
|
|
|
builder.header("Proxy-Authorization", credential);
|
|
|
|
}
|
|
|
|
return builder.build();
|
|
|
|
}
|
|
|
|
});
|
2015-10-06 18:45:39 +02:00
|
|
|
} else {
|
2015-12-30 14:48:27 +01:00
|
|
|
client.setProxySelector(null);
|
|
|
|
client.setAuthenticator(null);
|
2015-05-26 13:19:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static Proxy getProxy(final SharedPreferences prefs) {
|
2015-12-30 12:34:40 +01:00
|
|
|
final String proxyType = prefs.getString(KEY_PROXY_TYPE, null);
|
2015-05-26 13:19:03 +02:00
|
|
|
final String proxyHost = prefs.getString(KEY_PROXY_HOST, null);
|
2015-12-30 12:34:40 +01:00
|
|
|
final int proxyPort = NumberUtils.toInt(prefs.getString(KEY_PROXY_PORT, null), -1);
|
|
|
|
if (!isEmpty(proxyHost) && TwidereMathUtils.inRangeInclusiveInclusive(proxyPort, 0, 65535)) {
|
2015-05-26 13:19:03 +02:00
|
|
|
final SocketAddress addr = InetSocketAddress.createUnresolved(proxyHost, proxyPort);
|
2015-12-30 12:34:40 +01:00
|
|
|
return new Proxy(getProxyType(proxyType), addr);
|
2015-05-26 13:19:03 +02:00
|
|
|
}
|
|
|
|
return Proxy.NO_PROXY;
|
|
|
|
}
|
|
|
|
|
2015-12-30 12:34:40 +01:00
|
|
|
private static Proxy.Type getProxyType(String proxyType) {
|
|
|
|
if ("socks".equalsIgnoreCase(proxyType)) return Proxy.Type.SOCKS;
|
|
|
|
return Proxy.Type.HTTP;
|
|
|
|
}
|
|
|
|
|
2015-12-26 10:58:07 +01:00
|
|
|
@WorkerThread
|
2015-12-25 16:09:06 +01:00
|
|
|
public static <T> T getInstance(final Context context, final Endpoint endpoint,
|
|
|
|
final Authorization auth, final Map<String, String> extraRequestParams,
|
|
|
|
final Class<T> cls) {
|
2015-05-26 13:19:03 +02:00
|
|
|
final RestAPIFactory factory = new RestAPIFactory();
|
|
|
|
final String userAgent;
|
|
|
|
if (auth instanceof OAuthAuthorization) {
|
|
|
|
final String consumerKey = ((OAuthAuthorization) auth).getConsumerKey();
|
|
|
|
final String consumerSecret = ((OAuthAuthorization) auth).getConsumerSecret();
|
|
|
|
final ConsumerKeyType officialKeyType = TwitterContentUtils.getOfficialKeyType(context, consumerKey, consumerSecret);
|
|
|
|
if (officialKeyType != ConsumerKeyType.UNKNOWN) {
|
2015-12-26 10:58:07 +01:00
|
|
|
userAgent = getUserAgentName(context, officialKeyType);
|
2015-05-26 13:19:03 +02:00
|
|
|
} else {
|
2015-05-27 18:31:48 +02:00
|
|
|
userAgent = getTwidereUserAgent(context);
|
2015-05-26 13:19:03 +02:00
|
|
|
}
|
|
|
|
} else {
|
2015-05-27 18:31:48 +02:00
|
|
|
userAgent = getTwidereUserAgent(context);
|
2015-05-26 13:19:03 +02:00
|
|
|
}
|
2015-12-31 09:32:27 +01:00
|
|
|
DependencyHolder holder = new DependencyHolder(context);
|
|
|
|
factory.setClient(holder.getRestHttpClient());
|
2015-05-26 13:19:03 +02:00
|
|
|
factory.setConverter(new TwitterConverter());
|
|
|
|
factory.setEndpoint(endpoint);
|
|
|
|
factory.setAuthorization(auth);
|
2015-12-25 16:09:06 +01:00
|
|
|
factory.setRequestInfoFactory(new TwidereRequestInfoFactory(extraRequestParams));
|
2015-05-27 18:31:48 +02:00
|
|
|
factory.setHttpRequestFactory(new TwidereHttpRequestFactory(userAgent));
|
|
|
|
factory.setExceptionFactory(new TwidereExceptionFactory());
|
2015-05-26 13:19:03 +02:00
|
|
|
return factory.build(cls);
|
|
|
|
}
|
|
|
|
|
2015-12-26 10:58:07 +01:00
|
|
|
@WorkerThread
|
2015-12-25 16:09:06 +01:00
|
|
|
public static <T> T getInstance(final Context context, final Endpoint endpoint,
|
|
|
|
final Authorization auth, final Class<T> cls) {
|
|
|
|
return getInstance(context, endpoint, auth, null, cls);
|
2015-05-26 13:19:03 +02:00
|
|
|
}
|
|
|
|
|
2015-12-26 10:58:07 +01:00
|
|
|
@WorkerThread
|
2015-12-25 16:09:06 +01:00
|
|
|
public static <T> T getInstance(final Context context, final Endpoint endpoint,
|
|
|
|
final ParcelableCredentials credentials,
|
|
|
|
final Class<T> cls) {
|
|
|
|
return getInstance(context, endpoint, credentials, null, cls);
|
|
|
|
}
|
|
|
|
|
2015-12-26 10:58:07 +01:00
|
|
|
@WorkerThread
|
2015-12-25 16:09:06 +01:00
|
|
|
public static <T> T getInstance(final Context context, final Endpoint endpoint,
|
|
|
|
final ParcelableCredentials credentials,
|
|
|
|
final Map<String, String> extraRequestParams, final Class<T> cls) {
|
|
|
|
return TwitterAPIFactory.getInstance(context, endpoint, getAuthorization(credentials),
|
|
|
|
extraRequestParams, cls);
|
|
|
|
}
|
|
|
|
|
2015-12-26 10:58:07 +01:00
|
|
|
@WorkerThread
|
2015-12-25 16:09:06 +01:00
|
|
|
static <T> T getInstance(final Context context, final ParcelableCredentials credentials,
|
|
|
|
final Class<T> cls) {
|
|
|
|
return getInstance(context, credentials, null, cls);
|
|
|
|
}
|
|
|
|
|
2015-12-26 10:58:07 +01:00
|
|
|
@WorkerThread
|
2015-12-25 16:09:06 +01:00
|
|
|
static <T> T getInstance(final Context context, final ParcelableCredentials credentials,
|
|
|
|
final Map<String, String> extraRequestParams, final Class<T> cls) {
|
2015-05-26 13:19:03 +02:00
|
|
|
if (credentials == null) return null;
|
2015-12-25 16:09:06 +01:00
|
|
|
return TwitterAPIFactory.getInstance(context, getEndpoint(credentials, cls), credentials,
|
|
|
|
extraRequestParams, cls);
|
2015-05-27 18:31:48 +02:00
|
|
|
}
|
|
|
|
|
2015-06-30 02:45:42 +02:00
|
|
|
public static Endpoint getEndpoint(ParcelableCredentials credentials, Class<?> cls) {
|
2015-05-27 18:31:48 +02:00
|
|
|
final String apiUrlFormat;
|
|
|
|
final boolean sameOAuthSigningUrl = credentials.same_oauth_signing_url;
|
|
|
|
final boolean noVersionSuffix = credentials.no_version_suffix;
|
|
|
|
if (!isEmpty(credentials.api_url_format)) {
|
|
|
|
apiUrlFormat = credentials.api_url_format;
|
|
|
|
} else {
|
|
|
|
apiUrlFormat = DEFAULT_TWITTER_API_URL_FORMAT;
|
|
|
|
}
|
|
|
|
final String domain, versionSuffix;
|
|
|
|
if (Twitter.class.isAssignableFrom(cls)) {
|
|
|
|
domain = "api";
|
|
|
|
versionSuffix = noVersionSuffix ? null : "/1.1/";
|
|
|
|
} else if (TwitterUpload.class.isAssignableFrom(cls)) {
|
|
|
|
domain = "upload";
|
|
|
|
versionSuffix = noVersionSuffix ? null : "/1.1/";
|
|
|
|
} else if (TwitterOAuth.class.isAssignableFrom(cls)) {
|
|
|
|
domain = "api";
|
|
|
|
versionSuffix = "oauth";
|
|
|
|
} else if (TwitterUserStream.class.isAssignableFrom(cls)) {
|
|
|
|
domain = "userstream";
|
|
|
|
versionSuffix = noVersionSuffix ? null : "/1.1/";
|
2015-12-30 14:48:27 +01:00
|
|
|
} else if (TwitterCaps.class.isAssignableFrom(cls)) {
|
|
|
|
domain = "caps";
|
|
|
|
versionSuffix = null;
|
2015-05-27 18:31:48 +02:00
|
|
|
} else {
|
|
|
|
throw new TwitterConverter.UnsupportedTypeException(cls);
|
|
|
|
}
|
|
|
|
final String endpointUrl;
|
|
|
|
endpointUrl = getApiUrl(apiUrlFormat, domain, versionSuffix);
|
|
|
|
if (credentials.auth_type == ParcelableCredentials.AUTH_TYPE_XAUTH || credentials.auth_type == ParcelableCredentials.AUTH_TYPE_OAUTH) {
|
|
|
|
final String signEndpointUrl;
|
|
|
|
if (!sameOAuthSigningUrl) {
|
|
|
|
signEndpointUrl = getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, domain, versionSuffix);
|
|
|
|
} else {
|
|
|
|
signEndpointUrl = endpointUrl;
|
|
|
|
}
|
|
|
|
return new OAuthEndpoint(endpointUrl, signEndpointUrl);
|
|
|
|
}
|
|
|
|
return new Endpoint(endpointUrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Authorization getAuthorization(ParcelableCredentials credentials) {
|
|
|
|
switch (credentials.auth_type) {
|
|
|
|
case ParcelableCredentials.AUTH_TYPE_OAUTH:
|
|
|
|
case ParcelableCredentials.AUTH_TYPE_XAUTH: {
|
|
|
|
final String consumerKey = TextUtils.isEmpty(credentials.consumer_key) ?
|
|
|
|
TWITTER_CONSUMER_KEY_LEGACY : credentials.consumer_key;
|
|
|
|
final String consumerSecret = TextUtils.isEmpty(credentials.consumer_secret) ?
|
|
|
|
TWITTER_CONSUMER_SECRET_LEGACY : credentials.consumer_secret;
|
2015-09-03 09:33:00 +02:00
|
|
|
final OAuthToken accessToken = new OAuthToken(credentials.oauth_token,
|
|
|
|
credentials.oauth_token_secret);
|
|
|
|
if (isValidConsumerKeySecret(consumerKey) && isValidConsumerKeySecret(consumerSecret))
|
|
|
|
return new OAuthAuthorization(consumerKey, consumerSecret, accessToken);
|
|
|
|
return new OAuthAuthorization(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, accessToken);
|
2015-05-27 18:31:48 +02:00
|
|
|
}
|
|
|
|
case ParcelableCredentials.AUTH_TYPE_BASIC: {
|
|
|
|
final String screenName = credentials.screen_name;
|
|
|
|
final String username = credentials.basic_auth_username;
|
|
|
|
final String loginName = username != null ? username : screenName;
|
|
|
|
final String password = credentials.basic_auth_password;
|
|
|
|
if (isEmpty(loginName) || isEmpty(password)) return null;
|
|
|
|
return new BasicAuthorization(loginName, password);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new EmptyAuthorization();
|
|
|
|
}
|
|
|
|
|
2015-12-25 16:09:06 +01:00
|
|
|
private static void addParam(List<Pair<String, String>> params, String name, Object value) {
|
2015-05-27 18:31:48 +02:00
|
|
|
params.add(Pair.create(name, String.valueOf(value)));
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void addPart(List<Pair<String, TypedData>> params, String name, Object value) {
|
|
|
|
final TypedData typedData = new StringTypedData(String.valueOf(value), Charset.defaultCharset());
|
|
|
|
params.add(Pair.create(name, typedData));
|
|
|
|
}
|
|
|
|
|
2015-11-14 13:36:56 +01:00
|
|
|
public static boolean verifyApiFormat(@NonNull String format) {
|
2015-06-30 01:59:22 +02:00
|
|
|
return URLUtil.isValidUrl(getApiBaseUrl(format, "test"));
|
|
|
|
}
|
|
|
|
|
2015-11-14 13:36:56 +01:00
|
|
|
@NonNull
|
|
|
|
public static String getApiBaseUrl(@NonNull String format, @Nullable final String domain) {
|
2015-06-30 01:59:22 +02:00
|
|
|
final Matcher matcher = Pattern.compile("\\[(\\.?)DOMAIN(\\.?)\\]", Pattern.CASE_INSENSITIVE).matcher(format);
|
2015-05-27 18:31:48 +02:00
|
|
|
if (!matcher.find()) {
|
|
|
|
// For backward compatibility
|
|
|
|
format = substituteLegacyApiBaseUrl(format, domain);
|
|
|
|
if (!format.endsWith("/1.1") && !format.endsWith("/1.1/")) {
|
|
|
|
return format;
|
|
|
|
}
|
|
|
|
final String versionSuffix = "/1.1";
|
|
|
|
final int suffixLength = versionSuffix.length();
|
|
|
|
final int lastIndex = format.lastIndexOf(versionSuffix);
|
|
|
|
return format.substring(0, lastIndex) + format.substring(lastIndex + suffixLength);
|
|
|
|
}
|
|
|
|
if (TextUtils.isEmpty(domain)) return matcher.replaceAll("");
|
|
|
|
return matcher.replaceAll(String.format("$1%s$2", domain));
|
|
|
|
}
|
|
|
|
|
|
|
|
private static String substituteLegacyApiBaseUrl(@NonNull String format, String domain) {
|
|
|
|
final int startOfHost = format.indexOf("://") + 3, endOfHost = format.indexOf('/', startOfHost);
|
|
|
|
final String host = endOfHost != -1 ? format.substring(startOfHost, endOfHost) : format.substring(startOfHost);
|
|
|
|
if (!host.equalsIgnoreCase("api.twitter.com")) return format;
|
|
|
|
return format.substring(0, startOfHost) + domain + ".twitter.com" + format.substring(endOfHost);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String getApiUrl(final String pattern, final String domain, final String appendPath) {
|
|
|
|
final String urlBase = getApiBaseUrl(pattern, domain);
|
|
|
|
if (appendPath == null) return urlBase.endsWith("/") ? urlBase : urlBase + "/";
|
|
|
|
final StringBuilder sb = new StringBuilder(urlBase);
|
|
|
|
if (urlBase.endsWith("/")) {
|
|
|
|
sb.append(appendPath.startsWith("/") ? appendPath.substring(1) : appendPath);
|
|
|
|
} else {
|
|
|
|
if (appendPath.startsWith("/")) {
|
|
|
|
sb.append(appendPath);
|
|
|
|
} else {
|
|
|
|
sb.append('/');
|
|
|
|
sb.append(appendPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
2015-12-26 10:58:07 +01:00
|
|
|
@WorkerThread
|
|
|
|
public static String getUserAgentName(Context context, ConsumerKeyType type) {
|
2015-05-27 18:31:48 +02:00
|
|
|
switch (type) {
|
|
|
|
case TWITTER_FOR_ANDROID: {
|
2015-12-26 10:58:07 +01:00
|
|
|
final String versionName = "5.2.4";
|
|
|
|
final String internalVersionName = "524-r1";
|
|
|
|
final String model = Build.MODEL;
|
|
|
|
final String manufacturer = Build.MANUFACTURER;
|
|
|
|
final int sdkInt = Build.VERSION.SDK_INT;
|
|
|
|
final String device = Build.DEVICE;
|
|
|
|
final String brand = Build.BRAND;
|
|
|
|
final String product = Build.PRODUCT;
|
|
|
|
final int debug = BuildConfig.DEBUG ? 1 : 0;
|
|
|
|
return String.format(Locale.ROOT, "TwitterAndroid/%s (%s) %s/%d (%s;%s;%s;%s;%d)",
|
|
|
|
versionName, internalVersionName, model, sdkInt, manufacturer, device, brand,
|
|
|
|
product, debug);
|
2015-05-27 18:31:48 +02:00
|
|
|
}
|
|
|
|
case TWITTER_FOR_IPHONE: {
|
|
|
|
return "Twitter-iPhone";
|
|
|
|
}
|
|
|
|
case TWITTER_FOR_IPAD: {
|
|
|
|
return "Twitter-iPad";
|
|
|
|
}
|
|
|
|
case TWITTER_FOR_MAC: {
|
|
|
|
return "Twitter-Mac";
|
|
|
|
}
|
2015-07-17 16:30:41 +02:00
|
|
|
case TWEETDECK: {
|
2015-12-26 10:58:07 +01:00
|
|
|
return UserAgentUtils.getDefaultUserAgentStringSafe(context);
|
2015-07-17 16:30:41 +02:00
|
|
|
}
|
2015-05-27 18:31:48 +02:00
|
|
|
}
|
|
|
|
return "Twitter";
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String getTwidereUserAgent(final Context context) {
|
|
|
|
final PackageManager pm = context.getPackageManager();
|
|
|
|
try {
|
2015-12-26 10:58:07 +01:00
|
|
|
final PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
|
|
|
|
return String.format("%s %s / %s", TWIDERE_APP_NAME, TWIDERE_PROJECT_URL, pi.versionName);
|
2015-05-27 18:31:48 +02:00
|
|
|
} catch (final PackageManager.NameNotFoundException e) {
|
2015-12-26 10:58:07 +01:00
|
|
|
throw new AssertionError(e);
|
2015-05-27 18:31:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-03 09:15:11 +02:00
|
|
|
public static Endpoint getOAuthRestEndpoint(String apiUrlFormat, boolean sameOAuthSigningUrl, boolean noVersionSuffix) {
|
|
|
|
return getOAuthEndpoint(apiUrlFormat, "api", noVersionSuffix ? null : "1.1", sameOAuthSigningUrl);
|
2015-07-17 16:30:41 +02:00
|
|
|
}
|
|
|
|
|
2015-11-14 13:36:56 +01:00
|
|
|
public static Endpoint getOAuthEndpoint(String apiUrlFormat, @Nullable String domain,
|
|
|
|
@Nullable String versionSuffix, boolean sameOAuthSigningUrl) {
|
2015-07-17 16:30:41 +02:00
|
|
|
String endpointUrl, signEndpointUrl;
|
2015-09-03 09:15:11 +02:00
|
|
|
endpointUrl = getApiUrl(apiUrlFormat, domain, versionSuffix);
|
2015-07-17 16:30:41 +02:00
|
|
|
if (!sameOAuthSigningUrl) {
|
2015-09-03 09:15:11 +02:00
|
|
|
signEndpointUrl = getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, domain, versionSuffix);
|
2015-07-17 16:30:41 +02:00
|
|
|
} else {
|
|
|
|
signEndpointUrl = endpointUrl;
|
|
|
|
}
|
|
|
|
return new OAuthEndpoint(endpointUrl, signEndpointUrl);
|
|
|
|
}
|
|
|
|
|
2015-09-03 09:33:00 +02:00
|
|
|
public static OAuthToken getOAuthToken(String consumerKey, String consumerSecret) {
|
|
|
|
if (isValidConsumerKeySecret(consumerKey) && isValidConsumerKeySecret(consumerSecret))
|
|
|
|
return new OAuthToken(consumerKey, consumerSecret);
|
|
|
|
return new OAuthToken(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean isValidConsumerKeySecret(@NonNull CharSequence text) {
|
|
|
|
for (int i = 0, j = text.length(); i < j; i++) {
|
|
|
|
if (!isAsciiLetterOrDigit(text.charAt(i))) return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean isAsciiLetterOrDigit(int codePoint) {
|
|
|
|
return ('A' <= codePoint && codePoint <= 'Z') || ('a' <= codePoint && codePoint <= 'z') || '0' <= codePoint && codePoint <= '9';
|
|
|
|
}
|
|
|
|
|
2015-12-31 09:32:27 +01:00
|
|
|
public static class Options {
|
|
|
|
|
|
|
|
final HashMap<String, String> extras = new HashMap<>();
|
|
|
|
|
|
|
|
public void putExtra(String key, String value) {
|
|
|
|
extras.put(key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-27 18:31:48 +02:00
|
|
|
public static class TwidereRequestInfoFactory implements RequestInfoFactory {
|
2015-06-23 16:50:53 +02:00
|
|
|
|
2015-12-25 16:09:06 +01:00
|
|
|
private static Map<String, String> sDefaultRequestParams;
|
2015-06-23 16:50:53 +02:00
|
|
|
|
|
|
|
static {
|
2015-12-25 16:09:06 +01:00
|
|
|
final HashMap<String, String> map = new HashMap<>();
|
|
|
|
try {
|
|
|
|
map.put("include_cards", "true");
|
2015-12-31 09:32:27 +01:00
|
|
|
map.put("cards_platform", CARDS_PLATFORM_ANDROID_12);
|
2015-12-25 16:09:06 +01:00
|
|
|
map.put("include_entities", "true");
|
|
|
|
map.put("include_my_retweet", "true");
|
|
|
|
map.put("include_rts", "true");
|
|
|
|
map.put("include_reply_count", "true");
|
|
|
|
map.put("include_descendent_reply_count", "true");
|
|
|
|
map.put("full_text", "true");
|
|
|
|
map.put("model_version", "7");
|
|
|
|
} finally {
|
|
|
|
sDefaultRequestParams = Collections.unmodifiableMap(map);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private final Map<String, String> extraRequestParams;
|
|
|
|
|
|
|
|
TwidereRequestInfoFactory(Map<String, String> extraRequestParams) {
|
|
|
|
this.extraRequestParams = extraRequestParams;
|
2015-06-23 16:50:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-05-27 18:31:48 +02:00
|
|
|
@Override
|
|
|
|
public RestRequestInfo create(RestMethodInfo methodInfo) {
|
|
|
|
final RestMethod method = methodInfo.getMethod();
|
|
|
|
final String path = methodInfo.getPath();
|
|
|
|
final List<Pair<String, String>> queries = new ArrayList<>(methodInfo.getQueries());
|
|
|
|
final List<Pair<String, String>> forms = new ArrayList<>(methodInfo.getForms());
|
|
|
|
final List<Pair<String, String>> headers = methodInfo.getHeaders();
|
|
|
|
final List<Pair<String, TypedData>> parts = methodInfo.getParts();
|
|
|
|
final FileValue file = methodInfo.getFile();
|
|
|
|
final Map<String, Object> extras = methodInfo.getExtras();
|
2015-06-23 16:50:53 +02:00
|
|
|
final MethodExtra methodExtra = methodInfo.getMethodExtra();
|
|
|
|
if (methodExtra != null && "extra_params".equals(methodExtra.name())) {
|
|
|
|
final String[] extraParamKeys = methodExtra.values();
|
|
|
|
if (parts.isEmpty()) {
|
|
|
|
final List<Pair<String, String>> params = method.hasBody() ? forms : queries;
|
|
|
|
for (String key : extraParamKeys) {
|
2015-12-25 16:09:06 +01:00
|
|
|
if (extraRequestParams != null && extraRequestParams.containsKey(key)) {
|
|
|
|
addParam(params, key, extraRequestParams.get(key));
|
|
|
|
} else {
|
|
|
|
addParam(params, key, sDefaultRequestParams.get(key));
|
|
|
|
}
|
2015-06-23 16:50:53 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (String key : extraParamKeys) {
|
2015-12-25 16:09:06 +01:00
|
|
|
if (extraRequestParams != null && extraRequestParams.containsKey(key)) {
|
|
|
|
addPart(parts, key, extraRequestParams.get(key));
|
|
|
|
} else {
|
|
|
|
addPart(parts, key, sDefaultRequestParams.get(key));
|
|
|
|
}
|
2015-06-23 16:50:53 +02:00
|
|
|
}
|
|
|
|
}
|
2015-05-27 18:31:48 +02:00
|
|
|
}
|
|
|
|
return new RestRequestInfo(method.value(), path, queries, forms, headers, parts, file,
|
|
|
|
methodInfo.getBody(), extras);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class TwidereHttpRequestFactory implements HttpRequestFactory {
|
|
|
|
|
|
|
|
private final String userAgent;
|
|
|
|
|
|
|
|
public TwidereHttpRequestFactory(final String userAgent) {
|
|
|
|
this.userAgent = userAgent;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public RestHttpRequest create(@NonNull Endpoint endpoint, @NonNull RestRequestInfo info,
|
|
|
|
@Nullable Authorization authorization) {
|
|
|
|
final String restMethod = info.getMethod();
|
|
|
|
final String url = Endpoint.constructUrl(endpoint.getUrl(), info);
|
|
|
|
final ArrayList<Pair<String, String>> headers = new ArrayList<>(info.getHeaders());
|
|
|
|
|
|
|
|
if (authorization != null && authorization.hasAuthorization()) {
|
|
|
|
headers.add(Pair.create("Authorization", authorization.getHeader(endpoint, info)));
|
|
|
|
}
|
|
|
|
headers.add(Pair.create("User-Agent", userAgent));
|
2015-06-24 17:13:03 +02:00
|
|
|
return new RestHttpRequest(restMethod, url, headers, info.getBody(), RequestType.API);
|
2015-05-27 18:31:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class TwidereExceptionFactory implements ExceptionFactory {
|
|
|
|
@Override
|
|
|
|
public Exception newException(Throwable cause, RestHttpRequest request, RestHttpResponse response) {
|
2015-06-12 10:36:40 +02:00
|
|
|
final TwitterException te;
|
|
|
|
if (cause != null) {
|
|
|
|
te = new TwitterException(cause);
|
|
|
|
} else {
|
2015-09-11 04:13:50 +02:00
|
|
|
te = TwitterConverter.parseTwitterException(response);
|
2015-06-12 10:36:40 +02:00
|
|
|
}
|
2015-11-22 04:26:59 +01:00
|
|
|
te.setHttpResponse(response);
|
2015-05-27 18:31:48 +02:00
|
|
|
return te;
|
|
|
|
}
|
2015-05-26 13:19:03 +02:00
|
|
|
}
|
2015-06-30 01:59:22 +02:00
|
|
|
|
2015-05-26 13:19:03 +02:00
|
|
|
}
|