mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-17 04:00:48 +01:00
implemented 2FA login
This commit is contained in:
parent
d854cf180d
commit
3b16568474
@ -44,7 +44,7 @@ dependencies {
|
||||
compile 'com.android.support:support-v4:23.1.0'
|
||||
compile 'com.bluelinelabs:logansquare:1.1.0'
|
||||
compile 'org.apache.commons:commons-lang3:3.4'
|
||||
compile 'com.github.mariotaku:RestFu:0.9.2'
|
||||
compile 'com.github.mariotaku.RestFu:library:0.9.6'
|
||||
compile 'com.hannesdorfmann.parcelableplease:annotation:1.0.1'
|
||||
compile 'com.github.mariotaku:SQLiteQB:88291f3a28'
|
||||
compile 'com.github.mariotaku.LoganSquareExtension:core:b6f53c9a4d'
|
||||
|
@ -19,9 +19,10 @@
|
||||
|
||||
package org.mariotaku.twidere.api.twitter.auth;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Base64;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.mariotaku.restfu.Pair;
|
||||
import org.mariotaku.restfu.RestRequestInfo;
|
||||
import org.mariotaku.restfu.Utils;
|
||||
import org.mariotaku.restfu.http.Authorization;
|
||||
@ -79,8 +80,8 @@ public class OAuthAuthorization implements Authorization, OAuthSupport {
|
||||
private String generateOAuthSignature(String method, String url,
|
||||
String oauthNonce, long timestamp,
|
||||
String oauthToken, String oauthTokenSecret,
|
||||
List<Pair<String, String>> queries,
|
||||
List<Pair<String, String>> forms) {
|
||||
@Nullable List<Pair<String, String>> queries,
|
||||
@Nullable List<Pair<String, String>> forms) {
|
||||
final List<String> encodeParams = new ArrayList<>();
|
||||
encodeParams.add(encodeParameter("oauth_consumer_key", consumerKey));
|
||||
encodeParams.add(encodeParameter("oauth_nonce", oauthNonce));
|
||||
@ -133,11 +134,6 @@ public class OAuthAuthorization implements Authorization, OAuthSupport {
|
||||
public String getHeader(Endpoint endpoint, RestRequestInfo request) {
|
||||
if (!(endpoint instanceof OAuthEndpoint))
|
||||
throw new IllegalArgumentException("OAuthEndpoint required");
|
||||
final OAuthEndpoint oauthEndpoint = (OAuthEndpoint) endpoint;
|
||||
final String method = request.getMethod();
|
||||
final String url = Endpoint.constructUrl(oauthEndpoint.getSignUrl(), request);
|
||||
final String oauthNonce = generateOAuthNonce();
|
||||
final long timestamp = System.currentTimeMillis() / 1000;
|
||||
final Map<String, Object> extras = request.getExtras();
|
||||
final String oauthToken, oauthTokenSecret;
|
||||
if (this.oauthToken != null) {
|
||||
@ -147,8 +143,36 @@ public class OAuthAuthorization implements Authorization, OAuthSupport {
|
||||
oauthToken = (String) extras.get("oauth_token");
|
||||
oauthTokenSecret = (String) extras.get("oauth_token_secret");
|
||||
}
|
||||
final OAuthEndpoint oauthEndpoint = (OAuthEndpoint) endpoint;
|
||||
final String method = request.getMethod();
|
||||
final String url = Endpoint.constructUrl(oauthEndpoint.getSignUrl(), request);
|
||||
final List<Pair<String, String>> queries = request.getQueries();
|
||||
final List<Pair<String, String>> forms = request.getForms();
|
||||
final List<Pair<String, String>> encodeParams = generateOAuthParams(oauthToken, oauthTokenSecret,
|
||||
method, url, queries, forms);
|
||||
final StringBuilder headerBuilder = new StringBuilder();
|
||||
headerBuilder.append("OAuth ");
|
||||
for (int i = 0, j = encodeParams.size(); i < j; i++) {
|
||||
if (i != 0) {
|
||||
headerBuilder.append(", ");
|
||||
}
|
||||
final Pair<String, String> keyValuePair = encodeParams.get(i);
|
||||
headerBuilder.append(keyValuePair.first);
|
||||
headerBuilder.append("=\"");
|
||||
headerBuilder.append(keyValuePair.second);
|
||||
headerBuilder.append('\"');
|
||||
}
|
||||
return headerBuilder.toString();
|
||||
}
|
||||
|
||||
public List<Pair<String, String>> generateOAuthParams(String oauthToken,
|
||||
String oauthTokenSecret, String method,
|
||||
String url, List<Pair<String, String>> queries,
|
||||
List<Pair<String, String>> forms) {
|
||||
final String oauthNonce = generateOAuthNonce();
|
||||
final long timestamp = System.currentTimeMillis() / 1000;
|
||||
final String oauthSignature = generateOAuthSignature(method, url, oauthNonce, timestamp, oauthToken,
|
||||
oauthTokenSecret, request.getQueries(), request.getForms());
|
||||
oauthTokenSecret, queries, forms);
|
||||
final List<Pair<String, String>> encodeParams = new ArrayList<>();
|
||||
encodeParams.add(Pair.create("oauth_consumer_key", consumerKey));
|
||||
encodeParams.add(Pair.create("oauth_nonce", oauthNonce));
|
||||
@ -165,19 +189,7 @@ public class OAuthAuthorization implements Authorization, OAuthSupport {
|
||||
return lhs.first.compareTo(rhs.first);
|
||||
}
|
||||
});
|
||||
final StringBuilder headerBuilder = new StringBuilder();
|
||||
headerBuilder.append("OAuth ");
|
||||
for (int i = 0, j = encodeParams.size(); i < j; i++) {
|
||||
if (i != 0) {
|
||||
headerBuilder.append(", ");
|
||||
}
|
||||
final Pair<String, String> keyValuePair = encodeParams.get(i);
|
||||
headerBuilder.append(keyValuePair.first);
|
||||
headerBuilder.append("=\"");
|
||||
headerBuilder.append(keyValuePair.second);
|
||||
headerBuilder.append('\"');
|
||||
}
|
||||
return headerBuilder.toString();
|
||||
return encodeParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -19,8 +19,8 @@
|
||||
|
||||
package org.mariotaku.twidere.api.twitter.auth;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import org.mariotaku.restfu.Pair;
|
||||
import org.mariotaku.restfu.Utils;
|
||||
import org.mariotaku.restfu.http.ValueMap;
|
||||
|
||||
|
@ -331,6 +331,80 @@ public class StatusImpl extends TwitterResponseImpl implements Status {
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
StatusImpl status = (StatusImpl) o;
|
||||
|
||||
if (id != status.id) return false;
|
||||
if (truncated != status.truncated) return false;
|
||||
if (inReplyToStatusId != status.inReplyToStatusId) return false;
|
||||
if (inReplyToUserId != status.inReplyToUserId) return false;
|
||||
if (retweetCount != status.retweetCount) return false;
|
||||
if (favoriteCount != status.favoriteCount) return false;
|
||||
if (replyCount != status.replyCount) return false;
|
||||
if (favorited != status.favorited) return false;
|
||||
if (retweeted != status.retweeted) return false;
|
||||
if (descendentReplyCount != status.descendentReplyCount) return false;
|
||||
if (possiblySensitive != status.possiblySensitive) return false;
|
||||
if (createdAt != null ? !createdAt.equals(status.createdAt) : status.createdAt != null)
|
||||
return false;
|
||||
if (text != null ? !text.equals(status.text) : status.text != null) return false;
|
||||
if (source != null ? !source.equals(status.source) : status.source != null) return false;
|
||||
if (entities != null ? !entities.equals(status.entities) : status.entities != null)
|
||||
return false;
|
||||
if (extendedEntities != null ? !extendedEntities.equals(status.extendedEntities) : status.extendedEntities != null)
|
||||
return false;
|
||||
if (inReplyToScreenName != null ? !inReplyToScreenName.equals(status.inReplyToScreenName) : status.inReplyToScreenName != null)
|
||||
return false;
|
||||
if (user != null ? !user.equals(status.user) : status.user != null) return false;
|
||||
if (geo != null ? !geo.equals(status.geo) : status.geo != null) return false;
|
||||
if (place != null ? !place.equals(status.place) : status.place != null) return false;
|
||||
if (currentUserRetweet != null ? !currentUserRetweet.equals(status.currentUserRetweet) : status.currentUserRetweet != null)
|
||||
return false;
|
||||
if (!Arrays.equals(contributors, status.contributors)) return false;
|
||||
if (lang != null ? !lang.equals(status.lang) : status.lang != null) return false;
|
||||
if (retweetedStatus != null ? !retweetedStatus.equals(status.retweetedStatus) : status.retweetedStatus != null)
|
||||
return false;
|
||||
if (quotedStatus != null ? !quotedStatus.equals(status.quotedStatus) : status.quotedStatus != null)
|
||||
return false;
|
||||
return !(card != null ? !card.equals(status.card) : status.card != null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = createdAt != null ? createdAt.hashCode() : 0;
|
||||
result = 31 * result + (int) (id ^ (id >>> 32));
|
||||
result = 31 * result + (text != null ? text.hashCode() : 0);
|
||||
result = 31 * result + (source != null ? source.hashCode() : 0);
|
||||
result = 31 * result + (truncated ? 1 : 0);
|
||||
result = 31 * result + (entities != null ? entities.hashCode() : 0);
|
||||
result = 31 * result + (extendedEntities != null ? extendedEntities.hashCode() : 0);
|
||||
result = 31 * result + (int) (inReplyToStatusId ^ (inReplyToStatusId >>> 32));
|
||||
result = 31 * result + (int) (inReplyToUserId ^ (inReplyToUserId >>> 32));
|
||||
result = 31 * result + (inReplyToScreenName != null ? inReplyToScreenName.hashCode() : 0);
|
||||
result = 31 * result + (user != null ? user.hashCode() : 0);
|
||||
result = 31 * result + (geo != null ? geo.hashCode() : 0);
|
||||
result = 31 * result + (place != null ? place.hashCode() : 0);
|
||||
result = 31 * result + (currentUserRetweet != null ? currentUserRetweet.hashCode() : 0);
|
||||
result = 31 * result + (contributors != null ? Arrays.hashCode(contributors) : 0);
|
||||
result = 31 * result + (int) (retweetCount ^ (retweetCount >>> 32));
|
||||
result = 31 * result + (int) (favoriteCount ^ (favoriteCount >>> 32));
|
||||
result = 31 * result + (int) (replyCount ^ (replyCount >>> 32));
|
||||
result = 31 * result + (favorited ? 1 : 0);
|
||||
result = 31 * result + (retweeted ? 1 : 0);
|
||||
result = 31 * result + (lang != null ? lang.hashCode() : 0);
|
||||
result = 31 * result + (int) (descendentReplyCount ^ (descendentReplyCount >>> 32));
|
||||
result = 31 * result + (retweetedStatus != null ? retweetedStatus.hashCode() : 0);
|
||||
result = 31 * result + (quotedStatus != null ? quotedStatus.hashCode() : 0);
|
||||
result = 31 * result + (card != null ? card.hashCode() : 0);
|
||||
result = 31 * result + (possiblySensitive ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@OnJsonParseComplete
|
||||
void onJsonParseComplete() throws IOException {
|
||||
if (id <= 0 || text == null) throw new IOException("Malformed Status object");
|
||||
|
@ -19,17 +19,20 @@
|
||||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import com.bluelinelabs.logansquare.LoganSquare;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||
|
||||
import org.mariotaku.restfu.Pair;
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.http.Endpoint;
|
||||
import org.mariotaku.restfu.http.RestHttpClient;
|
||||
import org.mariotaku.restfu.http.RestHttpRequest;
|
||||
import org.mariotaku.restfu.http.RestHttpResponse;
|
||||
import org.mariotaku.twidere.api.twitter.model.MediaEntity;
|
||||
import org.mariotaku.twidere.api.twitter.model.Status;
|
||||
import org.mariotaku.twidere.api.twitter.model.UrlEntity;
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
import org.mariotaku.twidere.model.RequestType;
|
||||
import org.mariotaku.twidere.util.HtmlLinkExtractor.HtmlLink;
|
||||
@ -42,10 +45,6 @@ import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.mariotaku.twidere.api.twitter.model.MediaEntity;
|
||||
import org.mariotaku.twidere.api.twitter.model.Status;
|
||||
import org.mariotaku.twidere.api.twitter.model.UrlEntity;
|
||||
|
||||
import static android.text.TextUtils.isEmpty;
|
||||
|
||||
public class MediaPreviewUtils {
|
||||
|
@ -93,7 +93,8 @@ dependencies {
|
||||
compile 'com.soundcloud.android:android-crop:1.0.1@aar'
|
||||
compile 'com.hannesdorfmann.parcelableplease:annotation:1.0.1'
|
||||
compile 'com.github.mariotaku:PickNCrop:1dff3ed574'
|
||||
compile 'com.github.mariotaku:RestFu:0.9.2'
|
||||
compile 'com.github.mariotaku.RestFu:library:0.9.6'
|
||||
compile 'com.github.mariotaku.RestFu:okhttp:0.9.6'
|
||||
compile 'com.diogobernardino:williamchart:2.0.1'
|
||||
compile 'com.lnikkila:extendedtouchview:0.1.0'
|
||||
compile 'com.google.dagger:dagger:2.0.1'
|
||||
|
@ -22,14 +22,15 @@ package edu.tsinghua.hotmobi;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.mariotaku.restfu.Pair;
|
||||
import org.mariotaku.restfu.annotation.method.POST;
|
||||
import org.mariotaku.restfu.http.RestHttpClient;
|
||||
import org.mariotaku.restfu.http.RestHttpRequest;
|
||||
import org.mariotaku.restfu.http.RestHttpResponse;
|
||||
import org.mariotaku.restfu.http.mime.FileTypedData;
|
||||
import org.mariotaku.twidere.util.AbsLogger;
|
||||
import org.mariotaku.twidere.util.TwitterAPIFactory;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
|
||||
@ -43,6 +44,7 @@ import java.util.List;
|
||||
import edu.tsinghua.spice.Utilies.SpiceProfilingUtil;
|
||||
|
||||
/**
|
||||
* Upload logs to target server
|
||||
* Created by mariotaku on 15/8/27.
|
||||
*/
|
||||
public class UploadLogsTask implements Runnable {
|
||||
@ -119,7 +121,7 @@ public class UploadLogsTask implements Runnable {
|
||||
}
|
||||
}
|
||||
if (succeeded) {
|
||||
dayLogsDir.delete();
|
||||
AbsLogger.logIfFalse(dayLogsDir.delete(), "Unable to delete log dir");
|
||||
}
|
||||
}
|
||||
return hasErrors;
|
||||
|
@ -37,6 +37,7 @@ import org.mariotaku.twidere.util.ActivityTracker;
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
|
||||
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback;
|
||||
import org.mariotaku.twidere.util.NotificationManagerWrapper;
|
||||
import org.mariotaku.twidere.util.ReadStateManager;
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
|
||||
import org.mariotaku.twidere.util.ThemeUtils;
|
||||
@ -66,6 +67,8 @@ public class BaseAppCompatActivity extends ThemedAppCompatActivity implements Co
|
||||
protected Bus mBus;
|
||||
@Inject
|
||||
protected SharedPreferencesWrapper mPreferences;
|
||||
@Inject
|
||||
protected NotificationManagerWrapper mNotificationManager;
|
||||
|
||||
// Registered listeners
|
||||
private ArrayList<ControlBarOffsetListener> mControlBarOffsetListeners = new ArrayList<>();
|
||||
|
@ -29,7 +29,6 @@ import android.net.http.SslError;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Pair;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
@ -40,6 +39,8 @@ import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.attoparser.AttoParseException;
|
||||
import org.mariotaku.restfu.Pair;
|
||||
import org.mariotaku.restfu.http.Authorization;
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.api.twitter.TwitterOAuth;
|
||||
@ -51,7 +52,6 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
|
||||
import org.mariotaku.twidere.util.AsyncTaskUtils;
|
||||
import org.mariotaku.twidere.util.OAuthPasswordAuthenticator;
|
||||
import org.mariotaku.twidere.util.TwitterAPIFactory;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
@ -130,8 +130,10 @@ public class BrowserSignInActivity extends BaseSupportDialogActivity {
|
||||
|
||||
private String readOAuthPin(final String html) {
|
||||
try {
|
||||
return OAuthPasswordAuthenticator.readOAuthPINFromHtml(new StringReader(html));
|
||||
} catch (final XmlPullParserException | IOException e) {
|
||||
OAuthPasswordAuthenticator.OAuthPinData data = new OAuthPasswordAuthenticator.OAuthPinData();
|
||||
OAuthPasswordAuthenticator.readOAuthPINFromHtml(new StringReader(html), data);
|
||||
return data.oauthPin;
|
||||
} catch (final AttoParseException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
package org.mariotaku.twidere.activity.support;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.SearchManager;
|
||||
import android.content.ContentResolver;
|
||||
@ -127,10 +126,6 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
|
||||
|
||||
private ParcelableAccount mSelectedAccountToSearch;
|
||||
|
||||
private SharedPreferences mPreferences;
|
||||
|
||||
|
||||
private NotificationManager mNotificationManager;
|
||||
|
||||
private MultiSelectEventHandler mMultiSelectHandler;
|
||||
|
||||
@ -348,8 +343,6 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
mPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
mMultiSelectHandler = new MultiSelectEventHandler(this);
|
||||
mMultiSelectHandler.dispatchOnCreate();
|
||||
if (!Utils.hasAccount(this)) {
|
||||
@ -716,7 +709,6 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
|
||||
}
|
||||
}
|
||||
if (initialTab != -1 && mViewPager != null) {
|
||||
// clearNotification(initial_tab);
|
||||
}
|
||||
final Intent extraIntent = intent.getParcelableExtra(EXTRA_EXTRA_INTENT);
|
||||
if (extraIntent != null && firstCreate) {
|
||||
|
@ -33,7 +33,6 @@ import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
@ -92,6 +91,7 @@ import org.mariotaku.twidere.util.ThemeUtils;
|
||||
import org.mariotaku.twidere.util.TwidereActionModeForChildListener;
|
||||
import org.mariotaku.twidere.util.TwidereColorUtils;
|
||||
import org.mariotaku.twidere.util.TwitterAPIFactory;
|
||||
import org.mariotaku.twidere.util.UserAgentUtils;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
import org.mariotaku.twidere.util.support.ViewSupport;
|
||||
import org.mariotaku.twidere.util.support.view.ViewOutlineProviderCompat;
|
||||
@ -113,7 +113,6 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
|
||||
private static final String TWITTER_SIGNUP_URL = "https://twitter.com/signup";
|
||||
private static final String EXTRA_API_LAST_CHANGE = "api_last_change";
|
||||
private static final String DEFAULT_TWITTER_API_URL_FORMAT = "https://[DOMAIN.]twitter.com/";
|
||||
private final Handler mHandler = new Handler();
|
||||
@Nullable
|
||||
private String mAPIUrlFormat;
|
||||
private int mAuthType;
|
||||
@ -485,7 +484,7 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
private void dismissDialogFragment(final String tag) {
|
||||
void dismissDialogFragment(final String tag) {
|
||||
mResumeFragmentRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -503,7 +502,11 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
|
||||
}
|
||||
|
||||
void onSignInStart() {
|
||||
mResumeFragmentRunnable = new Runnable() {
|
||||
showSignInProgressDialog();
|
||||
}
|
||||
|
||||
void showSignInProgressDialog() {
|
||||
postAfterFragmentResumed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isFinishing()) return;
|
||||
@ -514,7 +517,11 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
|
||||
fragment.show(ft, FRAGMENT_TAG_SIGN_IN_PROGRESS);
|
||||
mResumeFragmentRunnable = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
void postAfterFragmentResumed(Runnable runnable) {
|
||||
mResumeFragmentRunnable = runnable;
|
||||
if (mFragmentsResumed) {
|
||||
mResumeFragmentRunnable.run();
|
||||
}
|
||||
@ -563,25 +570,32 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class AbstractSignInTask extends AsyncTask<Object, Object, SignInResponse> {
|
||||
public static abstract class AbstractSignInTask extends AsyncTask<Object, Runnable, SignInResponse> {
|
||||
|
||||
protected final SignInActivity callback;
|
||||
protected final SignInActivity activity;
|
||||
|
||||
public AbstractSignInTask(final SignInActivity callback) {
|
||||
this.callback = callback;
|
||||
public AbstractSignInTask(final SignInActivity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final SignInResponse result) {
|
||||
if (callback != null) {
|
||||
callback.onSignInResult(result);
|
||||
if (activity != null) {
|
||||
activity.onSignInResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (callback != null) {
|
||||
callback.onSignInStart();
|
||||
if (activity != null) {
|
||||
activity.onSignInStart();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Runnable... values) {
|
||||
for (Runnable value : values) {
|
||||
value.run();
|
||||
}
|
||||
}
|
||||
|
||||
@ -665,17 +679,17 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
|
||||
private final String username, password;
|
||||
private final int authType;
|
||||
|
||||
private final Context context;
|
||||
@NonNull
|
||||
private final String apiUrlFormat;
|
||||
private final boolean sameOAuthSigningUrl, noVersionSuffix;
|
||||
private final OAuthToken consumerKey;
|
||||
private final InputLoginVerificationCallback verificationCallback;
|
||||
private final String userAgent;
|
||||
|
||||
public SignInTask(final SignInActivity context, final String username, final String password, final int authType,
|
||||
public SignInTask(final SignInActivity activity, final String username, final String password, final int authType,
|
||||
final OAuthToken consumerKey, @NonNull final String apiUrlFormat, final boolean sameOAuthSigningUrl,
|
||||
final boolean noVersionSuffix) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
super(activity);
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.authType = authType;
|
||||
@ -683,6 +697,8 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
|
||||
this.apiUrlFormat = apiUrlFormat;
|
||||
this.sameOAuthSigningUrl = sameOAuthSigningUrl;
|
||||
this.noVersionSuffix = noVersionSuffix;
|
||||
verificationCallback = new InputLoginVerificationCallback();
|
||||
userAgent = UserAgentUtils.getDefaultUserAgentString(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -712,31 +728,31 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
|
||||
final String versionSuffix = noVersionSuffix ? null : "1.1";
|
||||
final Endpoint endpoint = new Endpoint(TwitterAPIFactory.getApiUrl(apiUrlFormat, "api", versionSuffix));
|
||||
final Authorization auth = new BasicAuthorization(username, password);
|
||||
final Twitter twitter = TwitterAPIFactory.getInstance(context, endpoint, auth, Twitter.class);
|
||||
final Twitter twitter = TwitterAPIFactory.getInstance(activity, endpoint, auth, Twitter.class);
|
||||
final User user = twitter.verifyCredentials();
|
||||
final long userId = user.getId();
|
||||
if (userId <= 0) return new SignInResponse(false, false, null);
|
||||
if (isUserLoggedIn(context, userId)) return new SignInResponse(true, false, null);
|
||||
if (isUserLoggedIn(activity, userId)) return new SignInResponse(true, false, null);
|
||||
final int color = analyseUserProfileColor(user);
|
||||
return new SignInResponse(isUserLoggedIn(context, userId), username, password, user,
|
||||
return new SignInResponse(isUserLoggedIn(activity, userId), username, password, user,
|
||||
color, apiUrlFormat, noVersionSuffix);
|
||||
}
|
||||
|
||||
private SignInResponse authOAuth() throws AuthenticationException, TwitterException {
|
||||
Endpoint endpoint = TwitterAPIFactory.getOAuthEndpoint(apiUrlFormat, "api", null, sameOAuthSigningUrl);
|
||||
OAuthAuthorization auth = new OAuthAuthorization(consumerKey.getOauthToken(), consumerKey.getOauthTokenSecret());
|
||||
final TwitterOAuth oauth = TwitterAPIFactory.getInstance(context, endpoint, auth, TwitterOAuth.class);
|
||||
final OAuthPasswordAuthenticator authenticator = new OAuthPasswordAuthenticator(oauth);
|
||||
final TwitterOAuth oauth = TwitterAPIFactory.getInstance(activity, endpoint, auth, TwitterOAuth.class);
|
||||
final OAuthPasswordAuthenticator authenticator = new OAuthPasswordAuthenticator(oauth, verificationCallback, userAgent);
|
||||
final OAuthToken accessToken = authenticator.getOAuthAccessToken(username, password);
|
||||
final long userId = accessToken.getUserId();
|
||||
if (userId <= 0) return new SignInResponse(false, false, null);
|
||||
endpoint = TwitterAPIFactory.getOAuthRestEndpoint(apiUrlFormat, sameOAuthSigningUrl, noVersionSuffix);
|
||||
auth = new OAuthAuthorization(consumerKey.getOauthToken(), consumerKey.getOauthTokenSecret(), accessToken);
|
||||
final Twitter twitter = TwitterAPIFactory.getInstance(context, endpoint,
|
||||
final Twitter twitter = TwitterAPIFactory.getInstance(activity, endpoint,
|
||||
auth, Twitter.class);
|
||||
final User user = twitter.verifyCredentials();
|
||||
final int color = analyseUserProfileColor(user);
|
||||
return new SignInResponse(isUserLoggedIn(context, userId), auth, user, ParcelableCredentials.AUTH_TYPE_OAUTH, color,
|
||||
return new SignInResponse(isUserLoggedIn(activity, userId), auth, user, ParcelableCredentials.AUTH_TYPE_OAUTH, color,
|
||||
apiUrlFormat, sameOAuthSigningUrl, noVersionSuffix);
|
||||
}
|
||||
|
||||
@ -744,30 +760,126 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
|
||||
final String versionSuffix = noVersionSuffix ? null : "1.1";
|
||||
final Endpoint endpoint = new Endpoint(TwitterAPIFactory.getApiUrl(apiUrlFormat, "api", versionSuffix));
|
||||
final Authorization auth = new EmptyAuthorization();
|
||||
final Twitter twitter = TwitterAPIFactory.getInstance(context, endpoint, auth, Twitter.class);
|
||||
final Twitter twitter = TwitterAPIFactory.getInstance(activity, endpoint, auth, Twitter.class);
|
||||
final User user = twitter.verifyCredentials();
|
||||
final long userId = user.getId();
|
||||
if (userId <= 0) return new SignInResponse(false, false, null);
|
||||
final int color = analyseUserProfileColor(user);
|
||||
return new SignInResponse(isUserLoggedIn(context, userId), user, color, apiUrlFormat, noVersionSuffix);
|
||||
return new SignInResponse(isUserLoggedIn(activity, userId), user, color, apiUrlFormat, noVersionSuffix);
|
||||
}
|
||||
|
||||
private SignInResponse authxAuth() throws TwitterException {
|
||||
Endpoint endpoint = TwitterAPIFactory.getOAuthEndpoint(apiUrlFormat, "api", null, sameOAuthSigningUrl);
|
||||
OAuthAuthorization auth = new OAuthAuthorization(consumerKey.getOauthToken(), consumerKey.getOauthTokenSecret());
|
||||
final TwitterOAuth oauth = TwitterAPIFactory.getInstance(context, endpoint, auth, TwitterOAuth.class);
|
||||
final TwitterOAuth oauth = TwitterAPIFactory.getInstance(activity, endpoint, auth, TwitterOAuth.class);
|
||||
final OAuthToken accessToken = oauth.getAccessToken(username, password, TwitterOAuth.XAuthMode.CLIENT);
|
||||
final long userId = accessToken.getUserId();
|
||||
if (userId <= 0) return new SignInResponse(false, false, null);
|
||||
auth = new OAuthAuthorization(consumerKey.getOauthToken(), consumerKey.getOauthTokenSecret(), accessToken);
|
||||
endpoint = TwitterAPIFactory.getOAuthRestEndpoint(apiUrlFormat, sameOAuthSigningUrl, noVersionSuffix);
|
||||
final Twitter twitter = TwitterAPIFactory.getInstance(context, endpoint, auth, Twitter.class);
|
||||
final Twitter twitter = TwitterAPIFactory.getInstance(activity, endpoint, auth, Twitter.class);
|
||||
final User user = twitter.verifyCredentials();
|
||||
final int color = analyseUserProfileColor(user);
|
||||
return new SignInResponse(isUserLoggedIn(context, userId), auth, user, ParcelableCredentials.AUTH_TYPE_XAUTH, color, apiUrlFormat,
|
||||
return new SignInResponse(isUserLoggedIn(activity, userId), auth, user, ParcelableCredentials.AUTH_TYPE_XAUTH, color, apiUrlFormat,
|
||||
sameOAuthSigningUrl, noVersionSuffix);
|
||||
}
|
||||
|
||||
class InputLoginVerificationCallback implements OAuthPasswordAuthenticator.LoginVerificationCallback {
|
||||
|
||||
boolean isChallengeFinished;
|
||||
String challengeResponse;
|
||||
|
||||
@Override
|
||||
public String getLoginVerification() {
|
||||
// Dismiss current progress dialog
|
||||
publishProgress(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
activity.dismissDialogFragment(FRAGMENT_TAG_SIGN_IN_PROGRESS);
|
||||
}
|
||||
});
|
||||
// Show verification input dialog and wait for user input
|
||||
publishProgress(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
activity.postAfterFragmentResumed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
InputLoginVerificationDialogFragment df = new InputLoginVerificationDialogFragment();
|
||||
df.setCallback(InputLoginVerificationCallback.this);
|
||||
df.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
while (!isChallengeFinished) {
|
||||
// Wait for 50ms
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
// Show progress dialog
|
||||
publishProgress(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
activity.showSignInProgressDialog();
|
||||
}
|
||||
});
|
||||
return challengeResponse;
|
||||
}
|
||||
|
||||
public void setChallengeResponse(String challengeResponse) {
|
||||
isChallengeFinished = true;
|
||||
this.challengeResponse = challengeResponse;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class InputLoginVerificationDialogFragment extends BaseSupportDialogFragment implements DialogInterface.OnClickListener {
|
||||
|
||||
private SignInTask.InputLoginVerificationCallback callback;
|
||||
|
||||
public void setCallback(SignInTask.InputLoginVerificationCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
callback.setChallengeResponse(null);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder.setTitle(R.string.login_verification);
|
||||
builder.setView(R.layout.dialog_login_verification_code);
|
||||
builder.setPositiveButton(android.R.string.ok, this);
|
||||
builder.setNegativeButton(android.R.string.cancel, this);
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE: {
|
||||
final AlertDialog alertDialog = (AlertDialog) dialog;
|
||||
final EditText editVerification = (EditText) alertDialog.findViewById(R.id.edit_verification_code);
|
||||
callback.setChallengeResponse(ParseUtils.parseString(editVerification.getText()));
|
||||
break;
|
||||
}
|
||||
case DialogInterface.BUTTON_NEGATIVE: {
|
||||
callback.setChallengeResponse(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class SignInResponse {
|
||||
|
@ -47,6 +47,7 @@ import org.mariotaku.twidere.util.AsyncTaskManager;
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||
import org.mariotaku.twidere.util.MediaLoaderWrapper;
|
||||
import org.mariotaku.twidere.util.MultiSelectManager;
|
||||
import org.mariotaku.twidere.util.NotificationManagerWrapper;
|
||||
import org.mariotaku.twidere.util.ReadStateManager;
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
|
||||
import org.mariotaku.twidere.util.ThemedLayoutInflaterFactory;
|
||||
@ -78,6 +79,8 @@ public class BaseSupportFragment extends Fragment implements IBaseFragment, Cons
|
||||
protected UserColorNameManager mUserColorNameManager;
|
||||
@Inject
|
||||
protected SharedPreferencesWrapper mPreferences;
|
||||
@Inject
|
||||
protected NotificationManagerWrapper mNotificationManager;
|
||||
|
||||
public BaseSupportFragment() {
|
||||
|
||||
|
@ -267,6 +267,16 @@ public abstract class CursorStatusesFragment extends AbsStatusesFragment<List<Pa
|
||||
|
||||
protected abstract int getNotificationType();
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (isVisibleToUser) {
|
||||
for (long accountId : getAccountIds()) {
|
||||
mTwitterWrapper.clearNotificationAsync(getNotificationType(), accountId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected long[] getOldestStatusIds(long[] accountIds) {
|
||||
return getOldestStatusIdsFromDatabase(getActivity(), getContentUri(), accountIds);
|
||||
}
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
package org.mariotaku.twidere.fragment.support;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
@ -299,10 +298,9 @@ public class DirectMessagesFragment extends AbsContentListRecyclerViewFragment<M
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
final FragmentActivity activity = getActivity();
|
||||
if (isVisibleToUser && activity != null) {
|
||||
final NotificationManager nm = (NotificationManager) activity.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
for (long accountId : getAccountIds()) {
|
||||
final String tag = "messages_" + accountId;
|
||||
nm.cancel(tag, NOTIFICATION_ID_DIRECT_MESSAGES);
|
||||
mNotificationManager.cancel(tag, NOTIFICATION_ID_DIRECT_MESSAGES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,6 @@
|
||||
|
||||
package org.mariotaku.twidere.fragment.support;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
|
||||
@ -73,10 +71,9 @@ public class HomeTimelineFragment extends CursorStatusesFragment {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
final FragmentActivity activity = getActivity();
|
||||
if (isVisibleToUser && activity != null) {
|
||||
final NotificationManager nm = (NotificationManager) activity.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
for (long accountId : getAccountIds()) {
|
||||
final String tag = "home_" + accountId;
|
||||
nm.cancel(tag, NOTIFICATION_ID_HOME_TIMELINE);
|
||||
mNotificationManager.cancel(tag, NOTIFICATION_ID_HOME_TIMELINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
package org.mariotaku.twidere.fragment.support;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
@ -82,10 +81,9 @@ public class MentionsTimelineFragment extends CursorStatusesFragment {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
final FragmentActivity activity = getActivity();
|
||||
if (isVisibleToUser && activity != null) {
|
||||
final NotificationManager nm = (NotificationManager) activity.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
for (long accountId : getAccountIds()) {
|
||||
final String tag = "mentions_" + accountId;
|
||||
nm.cancel(tag, NOTIFICATION_ID_MENTIONS_TIMELINE);
|
||||
mNotificationManager.cancel(tag, NOTIFICATION_ID_MENTIONS_TIMELINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,6 @@ import android.support.v7.widget.PopupMenu.OnMenuItemClickListener;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.LayoutParams;
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
@ -1158,7 +1157,6 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
|
||||
private List<ParcelableStatus> mConversation, mReplies;
|
||||
private StatusAdapterListener mStatusAdapterListener;
|
||||
private RecyclerView mRecyclerView;
|
||||
private DetailStatusViewHolder mStatusViewHolder;
|
||||
private CharSequence mReplyError;
|
||||
|
||||
public StatusAdapter(StatusFragment fragment, boolean compact) {
|
||||
@ -1369,22 +1367,6 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(ViewHolder holder) {
|
||||
if (holder instanceof DetailStatusViewHolder) {
|
||||
mStatusViewHolder = (DetailStatusViewHolder) holder;
|
||||
}
|
||||
super.onViewDetachedFromWindow(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAttachedToWindow(ViewHolder holder) {
|
||||
if (holder == mStatusViewHolder) {
|
||||
mStatusViewHolder = null;
|
||||
}
|
||||
super.onViewAttachedToWindow(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoadMoreIndicatorVisible() {
|
||||
return mLoadMoreIndicatorVisible;
|
||||
@ -1417,9 +1399,6 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
case VIEW_TYPE_DETAIL_STATUS: {
|
||||
if (mStatusViewHolder != null) {
|
||||
return mStatusViewHolder;
|
||||
}
|
||||
final View view;
|
||||
if (mIsCompact) {
|
||||
view = mInflater.inflate(R.layout.header_status_compact, parent, false);
|
||||
|
@ -20,7 +20,6 @@
|
||||
package org.mariotaku.twidere.provider;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentProvider;
|
||||
@ -50,7 +49,6 @@ import android.os.Looper;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.BaseColumns;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationCompat.InboxStyle;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
@ -109,6 +107,7 @@ import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||
import org.mariotaku.twidere.util.DatabaseQueryUtils;
|
||||
import org.mariotaku.twidere.util.ImagePreloader;
|
||||
import org.mariotaku.twidere.util.MediaPreviewUtils;
|
||||
import org.mariotaku.twidere.util.NotificationManagerWrapper;
|
||||
import org.mariotaku.twidere.util.ParseUtils;
|
||||
import org.mariotaku.twidere.util.PermissionsManager;
|
||||
import org.mariotaku.twidere.util.ReadStateManager;
|
||||
@ -159,14 +158,10 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
AsyncTwitterWrapper mTwitterWrapper;
|
||||
@Inject
|
||||
ImageLoader mMediaLoader;
|
||||
private ContentResolver mContentResolver;
|
||||
private SQLiteDatabaseWrapper mDatabaseWrapper;
|
||||
private PermissionsManager mPermissionsManager;
|
||||
@Nullable
|
||||
private NotificationManager mNotificationManager;
|
||||
@Inject
|
||||
NotificationManagerWrapper mNotificationManager;
|
||||
@Inject
|
||||
SharedPreferencesWrapper mPreferences;
|
||||
private ImagePreloader mImagePreloader;
|
||||
@Inject
|
||||
Network mNetwork;
|
||||
@Inject
|
||||
@ -174,6 +169,10 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
@Inject
|
||||
UserColorNameManager mUserColorNameManager;
|
||||
private Handler mHandler;
|
||||
private ContentResolver mContentResolver;
|
||||
private SQLiteDatabaseWrapper mDatabaseWrapper;
|
||||
private PermissionsManager mPermissionsManager;
|
||||
private ImagePreloader mImagePreloader;
|
||||
|
||||
private boolean mHomeActivityInBackground;
|
||||
private final BroadcastReceiver mHomeActivityStateReceiver = new BroadcastReceiver() {
|
||||
@ -576,7 +575,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
PendingIntent.getService(context, 0, sendIntent, PendingIntent.FLAG_ONE_SHOT));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
|
||||
notificationBuilder.setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT));
|
||||
getNotificationManager().notify(Uri.withAppendedPath(Drafts.CONTENT_URI, String.valueOf(draftId)).toString(),
|
||||
mNotificationManager.notify(Uri.withAppendedPath(Drafts.CONTENT_URI, String.valueOf(draftId)).toString(),
|
||||
NOTIFICATION_ID_DRAFTS, notificationBuilder.build());
|
||||
return draftId;
|
||||
}
|
||||
@ -1040,11 +1039,11 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
}
|
||||
|
||||
private void clearNotification() {
|
||||
getNotificationManager().cancelAll();
|
||||
mNotificationManager.cancelAll();
|
||||
}
|
||||
|
||||
private void clearNotification(final int notificationType, final long accountId) {
|
||||
|
||||
mNotificationManager.cancelById(Utils.getNotificationId(notificationType, accountId));
|
||||
}
|
||||
|
||||
private Cursor getCachedImageCursor(final String url) {
|
||||
@ -1100,13 +1099,6 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
return c;
|
||||
}
|
||||
|
||||
private NotificationManager getNotificationManager() {
|
||||
if (mNotificationManager != null) return mNotificationManager;
|
||||
final Context context = getContext();
|
||||
assert context != null;
|
||||
return mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
private Cursor getNotificationsCursor() {
|
||||
final MatrixCursor c = new MatrixCursor(TwidereDataStore.Notifications.MATRIX_COLUMNS);
|
||||
return c;
|
||||
@ -1252,7 +1244,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
final long accountId = pref.getAccountId();
|
||||
final Context context = getContext();
|
||||
final Resources resources = context.getResources();
|
||||
final NotificationManager nm = getNotificationManager();
|
||||
final NotificationManagerWrapper nm = mNotificationManager;
|
||||
final Expression selection = Expression.and(Expression.equals(Statuses.ACCOUNT_ID, accountId),
|
||||
Expression.greaterThan(Statuses.STATUS_ID, position));
|
||||
final String filteredSelection = Utils.buildStatusFilterWhereClause(Statuses.TABLE_NAME,
|
||||
@ -1308,7 +1300,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
builder.setColor(pref.getNotificationLightColor());
|
||||
setNotificationPreferences(builder, pref, pref.getHomeTimelineNotificationType());
|
||||
try {
|
||||
nm.notify("home_" + accountId, NOTIFICATION_ID_HOME_TIMELINE, builder.build());
|
||||
nm.notify("home_" + accountId, Utils.getNotificationId(NOTIFICATION_ID_HOME_TIMELINE, accountId), builder.build());
|
||||
Utils.sendPebbleNotification(context, notificationContent);
|
||||
} catch (SecurityException e) {
|
||||
// Silently ignore
|
||||
@ -1323,7 +1315,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
final long accountId = pref.getAccountId();
|
||||
final Context context = getContext();
|
||||
final Resources resources = context.getResources();
|
||||
final NotificationManager nm = getNotificationManager();
|
||||
final NotificationManagerWrapper nm = mNotificationManager;
|
||||
final Expression selection;
|
||||
if (pref.isNotificationFollowingOnly()) {
|
||||
selection = Expression.and(Expression.equals(Statuses.ACCOUNT_ID, accountId),
|
||||
@ -1360,8 +1352,10 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
}
|
||||
|
||||
private void displaySeparateMentionsNotifications(AccountPreferences pref, long accountId,
|
||||
Context context, Resources resources, NotificationManager nm,
|
||||
String filteredSelection, Cursor statusCursor, int statusesCount) {
|
||||
Context context, Resources resources,
|
||||
NotificationManagerWrapper nm,
|
||||
String filteredSelection, Cursor statusCursor,
|
||||
int statusesCount) {
|
||||
//noinspection TryFinallyCanBeTryWithResources
|
||||
if (statusCursor.getCount() == 0 || statusesCount == 0) return;
|
||||
final String accountName = Utils.getAccountName(context, accountId);
|
||||
@ -1399,7 +1393,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
style.setSummaryText(mNameFirst ? accountName : accountScreenName);
|
||||
//TODO show account info
|
||||
try {
|
||||
nm.notify("mentions_" + accountId + "_" + statusId, NOTIFICATION_ID_MENTIONS_TIMELINE,
|
||||
nm.notify("mentions_" + accountId + "_" + statusId,
|
||||
Utils.getNotificationId(NOTIFICATION_ID_MENTIONS_TIMELINE, accountId),
|
||||
builder.build());
|
||||
Utils.sendPebbleNotification(context, text);
|
||||
} catch (SecurityException e) {
|
||||
@ -1412,8 +1407,10 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
}
|
||||
|
||||
private void displayGroupedMentionsNotifications(AccountPreferences pref, long accountId,
|
||||
Context context, Resources resources, NotificationManager nm,
|
||||
String filteredSelection, Cursor statusCursor, int statusesCount) {
|
||||
Context context, Resources resources,
|
||||
NotificationManagerWrapper nm,
|
||||
String filteredSelection, Cursor statusCursor,
|
||||
int statusesCount) {
|
||||
final String[] userProjection = {Statuses.USER_ID, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME};
|
||||
final Cursor userCursor = mDatabaseWrapper.query(Mentions.TABLE_NAME, userProjection,
|
||||
filteredSelection, null, Statuses.USER_ID, null, Statuses.SORT_ORDER_TIMESTAMP_DESC,
|
||||
@ -1566,7 +1563,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
||||
}
|
||||
mReadStateManager.setPosition(TAG_OLDEST_MESSAGES, String.valueOf(accountId), oldestId, false);
|
||||
final Resources resources = context.getResources();
|
||||
final NotificationManager nm = getNotificationManager();
|
||||
final NotificationManagerWrapper nm = mNotificationManager;
|
||||
final ArrayList<Expression> orExpressions = new ArrayList<>();
|
||||
final String prefix = accountId + "-";
|
||||
final int prefixLength = prefix.length();
|
||||
|
@ -21,7 +21,6 @@ package org.mariotaku.twidere.service;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
@ -79,6 +78,7 @@ import org.mariotaku.twidere.util.BitmapUtils;
|
||||
import org.mariotaku.twidere.util.ContentValuesCreator;
|
||||
import org.mariotaku.twidere.util.ListUtils;
|
||||
import org.mariotaku.twidere.util.MediaUploaderInterface;
|
||||
import org.mariotaku.twidere.util.NotificationManagerWrapper;
|
||||
import org.mariotaku.twidere.util.ParseUtils;
|
||||
import org.mariotaku.twidere.util.StatusCodeMessageUtils;
|
||||
import org.mariotaku.twidere.util.StatusShortenerInterface;
|
||||
@ -117,9 +117,10 @@ public class BackgroundOperationService extends IntentService implements Constan
|
||||
private Handler mHandler;
|
||||
private SharedPreferences mPreferences;
|
||||
private ContentResolver mResolver;
|
||||
private NotificationManager mNotificationManager;
|
||||
@Inject
|
||||
AsyncTwitterWrapper mTwitter;
|
||||
@Inject
|
||||
NotificationManagerWrapper mNotificationManager;
|
||||
|
||||
private MediaUploaderInterface mUploader;
|
||||
private StatusShortenerInterface mShortener;
|
||||
@ -139,7 +140,6 @@ public class BackgroundOperationService extends IntentService implements Constan
|
||||
mPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE);
|
||||
mValidator = new TwidereValidator(this);
|
||||
mResolver = getContentResolver();
|
||||
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
final String uploaderComponent = mPreferences.getString(KEY_MEDIA_UPLOADER, null);
|
||||
final String shortenerComponent = mPreferences.getString(KEY_STATUS_SHORTENER, null);
|
||||
mUseUploader = !ServicePickerPreference.isNoneValue(uploaderComponent);
|
||||
@ -671,14 +671,14 @@ public class BackgroundOperationService extends IntentService implements Constan
|
||||
|
||||
static class MessageMediaUploadListener implements ReadListener {
|
||||
private final Context context;
|
||||
private final NotificationManager manager;
|
||||
private final NotificationManagerWrapper manager;
|
||||
|
||||
int percent;
|
||||
|
||||
private final Builder builder;
|
||||
private final String message;
|
||||
|
||||
MessageMediaUploadListener(final Context context, final NotificationManager manager,
|
||||
MessageMediaUploadListener(final Context context, final NotificationManagerWrapper manager,
|
||||
final NotificationCompat.Builder builder, final String message) {
|
||||
this.context = context;
|
||||
this.manager = manager;
|
||||
@ -715,14 +715,14 @@ public class BackgroundOperationService extends IntentService implements Constan
|
||||
|
||||
static class StatusMediaUploadListener implements ReadListener {
|
||||
private final Context context;
|
||||
private final NotificationManager manager;
|
||||
private final NotificationManagerWrapper manager;
|
||||
|
||||
int percent;
|
||||
|
||||
private final Builder builder;
|
||||
private final ParcelableStatusUpdate statusUpdate;
|
||||
|
||||
StatusMediaUploadListener(final Context context, final NotificationManager manager,
|
||||
StatusMediaUploadListener(final Context context, final NotificationManagerWrapper manager,
|
||||
final NotificationCompat.Builder builder, final ParcelableStatusUpdate statusUpdate) {
|
||||
this.context = context;
|
||||
this.manager = manager;
|
||||
|
@ -7,7 +7,6 @@ import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
@ -42,7 +41,6 @@ import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Mentions;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
|
||||
import org.mariotaku.twidere.util.ContentValuesCreator;
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
|
||||
import org.mariotaku.twidere.util.TwidereArrayUtils;
|
||||
import org.mariotaku.twidere.util.TwitterAPIFactory;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
@ -59,7 +57,6 @@ public class StreamingService extends Service implements Constants {
|
||||
private final LongSparseArray<UserStreamCallback> mCallbacks = new LongSparseArray<>();
|
||||
private ContentResolver mResolver;
|
||||
|
||||
private SharedPreferences mPreferences;
|
||||
private NotificationManager mNotificationManager;
|
||||
|
||||
private long[] mAccountIds;
|
||||
@ -92,7 +89,6 @@ public class StreamingService extends Service implements Constants {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mPreferences = SharedPreferencesWrapper.getInstance(this, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||
mResolver = getContentResolver();
|
||||
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
if (BuildConfig.DEBUG) {
|
||||
|
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
|
||||
import org.mariotaku.twidere.app.TwidereApplication;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/11/13.
|
||||
*/
|
||||
public class NotificationManagerWrapper {
|
||||
private final NotificationManager notificationManager;
|
||||
private final List<PostedNotification> notifications = new CopyOnWriteArrayList<>();
|
||||
|
||||
public NotificationManagerWrapper(TwidereApplication application) {
|
||||
notificationManager = (NotificationManager) application.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
public void notify(String tag, int id, Notification notification) {
|
||||
notificationManager.notify(tag, id, notification);
|
||||
notifications.add(new PostedNotification(tag, id));
|
||||
}
|
||||
|
||||
public void notify(int id, Notification notification) {
|
||||
notificationManager.notify(id, notification);
|
||||
notifications.add(new PostedNotification(null, id));
|
||||
}
|
||||
|
||||
public void cancel(String tag, int id) {
|
||||
notificationManager.cancel(tag, id);
|
||||
notifications.removeAll(find(tag, id));
|
||||
}
|
||||
|
||||
private List<PostedNotification> find(String tag, int id) {
|
||||
final ArrayList<PostedNotification> result = new ArrayList<>();
|
||||
for (PostedNotification notification : notifications) {
|
||||
if (notification.equals(tag, id)) {
|
||||
result.add(notification);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<PostedNotification> findByTag(String tag) {
|
||||
final ArrayList<PostedNotification> result = new ArrayList<>();
|
||||
for (PostedNotification notification : notifications) {
|
||||
if ((tag != null ? tag.equals(notification.tag) : null == notification.tag)) {
|
||||
result.add(notification);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<PostedNotification> findById(int id) {
|
||||
final ArrayList<PostedNotification> result = new ArrayList<>();
|
||||
for (PostedNotification notification : notifications) {
|
||||
if (id == notification.id) {
|
||||
result.add(notification);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void cancel(int id) {
|
||||
notificationManager.cancel(id);
|
||||
notifications.removeAll(find(null, id));
|
||||
}
|
||||
|
||||
public void cancelById(int id) {
|
||||
final List<PostedNotification> collection = findById(id);
|
||||
for (PostedNotification notification : collection) {
|
||||
notificationManager.cancel(notification.tag, notification.id);
|
||||
}
|
||||
notificationManager.cancel(id);
|
||||
notifications.removeAll(collection);
|
||||
}
|
||||
|
||||
public void cancelByTag(String tag) {
|
||||
final List<PostedNotification> collection = findByTag(tag);
|
||||
for (PostedNotification notification : collection) {
|
||||
notificationManager.cancel(notification.tag, notification.id);
|
||||
}
|
||||
notifications.removeAll(collection);
|
||||
}
|
||||
|
||||
public void cancelAll() {
|
||||
notificationManager.cancelAll();
|
||||
}
|
||||
|
||||
public void cancelByTag() {
|
||||
|
||||
}
|
||||
|
||||
private class PostedNotification {
|
||||
private final String tag;
|
||||
private final int id;
|
||||
|
||||
public PostedNotification(String tag, int id) {
|
||||
this.tag = tag;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public boolean equals(String tag, int id) {
|
||||
return id == this.id && (tag != null ? tag.equals(this.tag) : this.tag == null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = tag != null ? tag.hashCode() : 0;
|
||||
result = 31 * result + id;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -20,55 +20,94 @@
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.nostra13.universalimageloader.utils.IoUtils;
|
||||
import com.squareup.okhttp.HttpUrl;
|
||||
import com.squareup.okhttp.Interceptor;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.Response;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.attoparser.AttoParseException;
|
||||
import org.attoparser.IAttoHandler;
|
||||
import org.attoparser.IAttoParser;
|
||||
import org.attoparser.markup.MarkupAttoParser;
|
||||
import org.attoparser.markup.html.AbstractStandardNonValidatingHtmlAttoHandler;
|
||||
import org.attoparser.markup.html.HtmlParsingConfiguration;
|
||||
import org.attoparser.markup.html.elements.IHtmlElement;
|
||||
import org.mariotaku.restfu.Pair;
|
||||
import org.mariotaku.restfu.RestAPIFactory;
|
||||
import org.mariotaku.restfu.RestClient;
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.annotation.method.POST;
|
||||
import org.mariotaku.restfu.http.Endpoint;
|
||||
import org.mariotaku.restfu.http.RestHttpClient;
|
||||
import org.mariotaku.restfu.http.RestHttpRequest;
|
||||
import org.mariotaku.restfu.http.RestHttpResponse;
|
||||
import org.mariotaku.restfu.http.mime.BaseTypedData;
|
||||
import org.mariotaku.restfu.http.mime.FormTypedBody;
|
||||
import org.mariotaku.restfu.okhttp.OkHttpRestClient;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.api.twitter.TwitterException;
|
||||
import org.mariotaku.twidere.api.twitter.TwitterOAuth;
|
||||
import org.mariotaku.twidere.api.twitter.auth.OAuthToken;
|
||||
import org.mariotaku.twidere.model.RequestType;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.CookieManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static android.text.TextUtils.isEmpty;
|
||||
|
||||
public class OAuthPasswordAuthenticator implements Constants {
|
||||
|
||||
private static final String INPUT_AUTHENTICITY_TOKEN = "authenticity_token";
|
||||
private static final String INPUT_REDIRECT_AFTER_LOGIN = "redirect_after_login";
|
||||
private static final IAttoParser PARSER = new MarkupAttoParser();
|
||||
|
||||
private final TwitterOAuth oauth;
|
||||
private final RestHttpClient client;
|
||||
private final OkHttpRestClient client;
|
||||
private final Endpoint endpoint;
|
||||
private final LoginVerificationCallback loginVerificationCallback;
|
||||
private final String userAgent;
|
||||
|
||||
public OAuthPasswordAuthenticator(final TwitterOAuth oauth) {
|
||||
public OAuthPasswordAuthenticator(final TwitterOAuth oauth,
|
||||
final LoginVerificationCallback loginVerificationCallback,
|
||||
final String userAgent) {
|
||||
final RestClient restClient = RestAPIFactory.getRestClient(oauth);
|
||||
this.oauth = oauth;
|
||||
this.client = restClient.getRestClient();
|
||||
this.client = (OkHttpRestClient) restClient.getRestClient();
|
||||
final OkHttpClient okhttp = client.getClient();
|
||||
okhttp.setCookieHandler(new CookieManager());
|
||||
okhttp.networkInterceptors().add(new Interceptor() {
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
final Response response = chain.proceed(chain.request());
|
||||
if (!response.isRedirect()) {
|
||||
return response;
|
||||
}
|
||||
final String location = response.header("Location");
|
||||
final Response.Builder builder = response.newBuilder();
|
||||
if (!TextUtils.isEmpty(location)) {
|
||||
final HttpUrl originalLocation = HttpUrl.parse(location);
|
||||
final HttpUrl.Builder locationBuilder = HttpUrl.parse(endpoint.getUrl()).newBuilder();
|
||||
for (String pathSegments : originalLocation.pathSegments()) {
|
||||
locationBuilder.addPathSegment(pathSegments);
|
||||
}
|
||||
for (int i = 0, j = originalLocation.querySize(); i < j; i++) {
|
||||
final String name = originalLocation.queryParameterName(i);
|
||||
final String value = originalLocation.queryParameterValue(i);
|
||||
locationBuilder.addQueryParameter(name, value);
|
||||
}
|
||||
final String encodedFragment = originalLocation.encodedFragment();
|
||||
if (encodedFragment != null) {
|
||||
locationBuilder.encodedFragment(encodedFragment);
|
||||
}
|
||||
final HttpUrl newLocation = locationBuilder.build();
|
||||
builder.header("Location", newLocation.toString());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
});
|
||||
this.endpoint = restClient.getEndpoint();
|
||||
this.loginVerificationCallback = loginVerificationCallback;
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
public OAuthToken getOAuthAccessToken(final String username, final String password) throws AuthenticationException {
|
||||
@ -79,126 +118,333 @@ public class OAuthPasswordAuthenticator implements Constants {
|
||||
if (e.isCausedByNetworkIssue()) throw new AuthenticationException(e);
|
||||
throw new AuthenticityTokenException(e);
|
||||
}
|
||||
RestHttpResponse authorizePage = null, authorizeResult = null;
|
||||
try {
|
||||
final String oauthToken = requestToken.getOauthToken();
|
||||
final HashMap<String, String> inputMap = new HashMap<>();
|
||||
final RestHttpRequest.Builder authorizePageBuilder = new RestHttpRequest.Builder();
|
||||
authorizePageBuilder.method(GET.METHOD);
|
||||
authorizePageBuilder.url(endpoint.construct("/oauth/authorize", Pair.create("oauth_token",
|
||||
requestToken.getOauthToken())));
|
||||
authorizePageBuilder.extra(RequestType.API);
|
||||
final RestHttpRequest authorizePageRequest = authorizePageBuilder.build();
|
||||
authorizePage = client.execute(authorizePageRequest);
|
||||
final String[] cookieHeaders = authorizePage.getHeaders("Set-Cookie");
|
||||
readInputFromHtml(BaseTypedData.reader(authorizePage.getBody()), inputMap,
|
||||
INPUT_AUTHENTICITY_TOKEN, INPUT_REDIRECT_AFTER_LOGIN);
|
||||
final List<Pair<String, String>> params = new ArrayList<>();
|
||||
params.add(Pair.create("oauth_token", oauthToken));
|
||||
params.add(Pair.create(INPUT_AUTHENTICITY_TOKEN, inputMap.get(INPUT_AUTHENTICITY_TOKEN)));
|
||||
if (inputMap.containsKey(INPUT_REDIRECT_AFTER_LOGIN)) {
|
||||
params.add(Pair.create(INPUT_REDIRECT_AFTER_LOGIN, inputMap.get(INPUT_REDIRECT_AFTER_LOGIN)));
|
||||
final AuthorizeRequestData authorizeRequestData = getAuthorizeRequestData(requestToken);
|
||||
AuthorizeResponseData authorizeResponseData = getAuthorizeResponseData(requestToken,
|
||||
authorizeRequestData, username, password);
|
||||
if (!TextUtils.isEmpty(authorizeResponseData.oauthPin)) {
|
||||
// Here we got OAuth PIN, just get access token directly
|
||||
return oauth.getAccessToken(requestToken, authorizeResponseData.oauthPin);
|
||||
} else if (authorizeResponseData.verification == null) {
|
||||
// No OAuth pin, or verification challenge, so treat as wrong password
|
||||
throw new WrongUserPassException();
|
||||
}
|
||||
params.add(Pair.create("session[username_or_email]", username));
|
||||
params.add(Pair.create("session[password]", password));
|
||||
final FormTypedBody authorizationResultBody = new FormTypedBody(params);
|
||||
final ArrayList<Pair<String, String>> requestHeaders = new ArrayList<>();
|
||||
requestHeaders.add(Pair.create("Origin", "https://twitter.com"));
|
||||
requestHeaders.add(Pair.create("Referer", Endpoint.constructUrl("https://twitter.com/oauth/authorize",
|
||||
Pair.create("oauth_token", requestToken.getOauthToken()))));
|
||||
// Go to password verification flow
|
||||
final AuthorizeRequestData verificationData = getVerificationData(authorizeResponseData,
|
||||
loginVerificationCallback.getLoginVerification());
|
||||
authorizeResponseData = getAuthorizeResponseData(requestToken,
|
||||
verificationData, username, password);
|
||||
if (TextUtils.isEmpty(authorizeResponseData.oauthPin)) {
|
||||
throw new VerificationCodeException();
|
||||
}
|
||||
return oauth.getAccessToken(requestToken, authorizeResponseData.oauthPin);
|
||||
} catch (final IOException | NullPointerException | TwitterException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
final String host = parseUrlHost(endpoint.getUrl());
|
||||
for (String cookieHeader : cookieHeaders) {
|
||||
for (HttpCookie cookie : HttpCookie.parse(cookieHeader)) {
|
||||
if (HttpCookie.domainMatches(cookie.getDomain(), host)) {
|
||||
cookie.setVersion(1);
|
||||
cookie.setDomain("twitter.com");
|
||||
private AuthorizeRequestData getVerificationData(AuthorizeResponseData authorizeResponseData,
|
||||
String challengeResponse) throws IOException, VerificationCodeException {
|
||||
RestHttpResponse response = null;
|
||||
try {
|
||||
final AuthorizeRequestData data = new AuthorizeRequestData();
|
||||
final List<Pair<String, String>> params = new ArrayList<>();
|
||||
final AuthorizeResponseData.Verification verification = authorizeResponseData.verification;
|
||||
params.add(Pair.create("authenticity_token", verification.authenticityToken));
|
||||
params.add(Pair.create("user_id", verification.userId));
|
||||
params.add(Pair.create("challenge_id", verification.challengeId));
|
||||
params.add(Pair.create("challenge_type", verification.challengeType));
|
||||
params.add(Pair.create("platform", verification.platform));
|
||||
params.add(Pair.create("redirect_after_login", verification.redirectAfterLogin));
|
||||
final ArrayList<Pair<String, String>> requestHeaders = new ArrayList<>();
|
||||
requestHeaders.add(Pair.create("User-Agent", userAgent));
|
||||
|
||||
params.add(Pair.create("challenge_response", challengeResponse));
|
||||
final FormTypedBody authorizationResultBody = new FormTypedBody(params);
|
||||
|
||||
final RestHttpRequest.Builder authorizeResultBuilder = new RestHttpRequest.Builder();
|
||||
authorizeResultBuilder.method(POST.METHOD);
|
||||
authorizeResultBuilder.url(endpoint.construct("/account/login_verification"));
|
||||
authorizeResultBuilder.headers(requestHeaders);
|
||||
authorizeResultBuilder.body(authorizationResultBody);
|
||||
authorizeResultBuilder.extra(RequestType.API);
|
||||
response = client.execute(authorizeResultBuilder.build());
|
||||
parseAuthorizeRequestData(response, data);
|
||||
return data;
|
||||
} catch (AttoParseException e) {
|
||||
throw new VerificationCodeException();
|
||||
} finally {
|
||||
Utils.closeSilently(response);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseAuthorizeRequestData(RestHttpResponse response, final AuthorizeRequestData data) throws AttoParseException, IOException {
|
||||
final HtmlParsingConfiguration conf = new HtmlParsingConfiguration();
|
||||
final IAttoHandler handler = new AbstractStandardNonValidatingHtmlAttoHandler(conf) {
|
||||
boolean isOAuthFormOpened;
|
||||
|
||||
@Override
|
||||
public void handleHtmlStandaloneElement(IHtmlElement element, boolean minimized,
|
||||
String elementName, Map<String, String> attributes,
|
||||
int line, int col) throws AttoParseException {
|
||||
handleHtmlOpenElement(element, elementName, attributes, line, col);
|
||||
handleHtmlCloseElement(element, elementName, line, col);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlOpenElement(IHtmlElement element, String elementName,
|
||||
Map<String, String> attributes, int line, int col)
|
||||
throws AttoParseException {
|
||||
switch (elementName) {
|
||||
case "form": {
|
||||
if (attributes != null && "oauth_form".equals(attributes.get("id"))) {
|
||||
isOAuthFormOpened = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "input": {
|
||||
if (isOAuthFormOpened && attributes != null) {
|
||||
final String name = attributes.get("name");
|
||||
if (TextUtils.isEmpty(name)) break;
|
||||
final String value = attributes.get("value");
|
||||
if (name.equals("authenticity_token")) {
|
||||
data.authenticityToken = value;
|
||||
} else if (name.equals("redirect_after_login")) {
|
||||
data.redirectAfterLogin = value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
requestHeaders.add(Pair.create("Cookie", cookie.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlCloseElement(IHtmlElement element, String elementName, int line, int col) throws AttoParseException {
|
||||
if ("form".equals(elementName)) {
|
||||
isOAuthFormOpened = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
PARSER.parse(BaseTypedData.reader(response.getBody()), handler);
|
||||
}
|
||||
|
||||
private AuthorizeResponseData getAuthorizeResponseData(OAuthToken requestToken,
|
||||
AuthorizeRequestData authorizeRequestData,
|
||||
String username, String password) throws IOException, AuthenticationException {
|
||||
RestHttpResponse response = null;
|
||||
try {
|
||||
final AuthorizeResponseData data = new AuthorizeResponseData();
|
||||
final List<Pair<String, String>> params = new ArrayList<>();
|
||||
params.add(Pair.create("oauth_token", requestToken.getOauthToken()));
|
||||
params.add(Pair.create("authenticity_token", authorizeRequestData.authenticityToken));
|
||||
params.add(Pair.create("redirect_after_login", authorizeRequestData.redirectAfterLogin));
|
||||
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
|
||||
params.add(Pair.create("session[username_or_email]", username));
|
||||
params.add(Pair.create("session[password]", password));
|
||||
}
|
||||
final FormTypedBody authorizationResultBody = new FormTypedBody(params);
|
||||
final ArrayList<Pair<String, String>> requestHeaders = new ArrayList<>();
|
||||
requestHeaders.add(Pair.create("User-Agent", userAgent));
|
||||
data.referer = authorizeRequestData.referer;
|
||||
|
||||
final RestHttpRequest.Builder authorizeResultBuilder = new RestHttpRequest.Builder();
|
||||
authorizeResultBuilder.method(POST.METHOD);
|
||||
authorizeResultBuilder.url(endpoint.construct("/oauth/authorize"));
|
||||
authorizeResultBuilder.headers(requestHeaders);
|
||||
authorizeResultBuilder.body(authorizationResultBody);
|
||||
authorizeResultBuilder.extra(RequestType.API);
|
||||
authorizeResult = client.execute(authorizeResultBuilder.build());
|
||||
final String oauthPin = readOAuthPINFromHtml(BaseTypedData.reader(authorizeResult.getBody()));
|
||||
if (isEmpty(oauthPin)) throw new WrongUserPassException();
|
||||
return oauth.getAccessToken(requestToken, oauthPin);
|
||||
} catch (final IOException | NullPointerException | XmlPullParserException | TwitterException e) {
|
||||
response = client.execute(authorizeResultBuilder.build());
|
||||
final HtmlParsingConfiguration conf = new HtmlParsingConfiguration();
|
||||
final IAttoHandler handler = new AbstractStandardNonValidatingHtmlAttoHandler(conf) {
|
||||
boolean isOAuthPinDivOpened;
|
||||
boolean isLoginVerificationFormOpened;
|
||||
|
||||
@Override
|
||||
public void handleHtmlStandaloneElement(IHtmlElement element, boolean minimized,
|
||||
String elementName, Map<String, String> attributes,
|
||||
int line, int col) throws AttoParseException {
|
||||
handleHtmlOpenElement(element, elementName, attributes, line, col);
|
||||
handleHtmlCloseElement(element, elementName, line, col);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlCloseElement(IHtmlElement element, String elementName, int line, int col) throws AttoParseException {
|
||||
switch (elementName) {
|
||||
case "div": {
|
||||
isOAuthPinDivOpened = false;
|
||||
break;
|
||||
}
|
||||
case "form": {
|
||||
isLoginVerificationFormOpened = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlOpenElement(IHtmlElement element, String elementName,
|
||||
Map<String, String> attributes, int line, int col)
|
||||
throws AttoParseException {
|
||||
switch (elementName) {
|
||||
case "div": {
|
||||
if (attributes != null && "oauth_pin".equals(attributes.get("id"))) {
|
||||
isOAuthPinDivOpened = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "form": {
|
||||
if (attributes != null && "login-verification-form".equals(attributes.get("id"))) {
|
||||
isLoginVerificationFormOpened = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "input":
|
||||
if (isLoginVerificationFormOpened && attributes != null) {
|
||||
final String name = attributes.get("name");
|
||||
if (TextUtils.isEmpty(name)) break;
|
||||
final String value = attributes.get("value");
|
||||
switch (name) {
|
||||
case "authenticity_token": {
|
||||
ensureVerification();
|
||||
data.verification.authenticityToken = value;
|
||||
break;
|
||||
}
|
||||
case "challenge_id": {
|
||||
ensureVerification();
|
||||
data.verification.challengeId = value;
|
||||
break;
|
||||
}
|
||||
case "challenge_type": {
|
||||
ensureVerification();
|
||||
data.verification.challengeType = value;
|
||||
break;
|
||||
}
|
||||
case "platform": {
|
||||
ensureVerification();
|
||||
data.verification.platform = value;
|
||||
break;
|
||||
}
|
||||
case "user_id": {
|
||||
ensureVerification();
|
||||
data.verification.userId = value;
|
||||
break;
|
||||
}
|
||||
case "redirect_after_login": {
|
||||
ensureVerification();
|
||||
data.verification.redirectAfterLogin = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureVerification() {
|
||||
if (data.verification == null) {
|
||||
data.verification = new AuthorizeResponseData.Verification();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleText(char[] buffer, int offset, int len, int line, int col) throws AttoParseException {
|
||||
if (isOAuthPinDivOpened) {
|
||||
final String s = new String(buffer, offset, len);
|
||||
if (TextUtils.isDigitsOnly(s)) {
|
||||
data.oauthPin = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
PARSER.parse(BaseTypedData.reader(response.getBody()), handler);
|
||||
return data;
|
||||
} catch (AttoParseException e) {
|
||||
throw new AuthenticationException(e);
|
||||
} finally {
|
||||
if (authorizePage != null) {
|
||||
IoUtils.closeSilently(authorizePage);
|
||||
}
|
||||
if (authorizeResult != null) {
|
||||
IoUtils.closeSilently(authorizeResult);
|
||||
}
|
||||
Utils.closeSilently(response);
|
||||
}
|
||||
}
|
||||
|
||||
private static void readInputFromHtml(final Reader in, Map<String, String> map, String... desiredNames) throws IOException, XmlPullParserException {
|
||||
final XmlPullParserFactory f = XmlPullParserFactory.newInstance();
|
||||
final XmlPullParser parser = f.newPullParser();
|
||||
parser.setFeature(Xml.FEATURE_RELAXED, true);
|
||||
parser.setInput(in);
|
||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
final String tag = parser.getName();
|
||||
switch (parser.getEventType()) {
|
||||
case XmlPullParser.START_TAG: {
|
||||
final String name = parser.getAttributeValue(null, "name");
|
||||
if ("input".equalsIgnoreCase(tag) && ArrayUtils.contains(desiredNames, name)) {
|
||||
map.put(name, parser.getAttributeValue(null, "value"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
private AuthorizeRequestData getAuthorizeRequestData(OAuthToken requestToken) throws IOException,
|
||||
AuthenticationException {
|
||||
RestHttpResponse response = null;
|
||||
try {
|
||||
final AuthorizeRequestData data = new AuthorizeRequestData();
|
||||
final RestHttpRequest.Builder authorizePageBuilder = new RestHttpRequest.Builder();
|
||||
authorizePageBuilder.method(GET.METHOD);
|
||||
authorizePageBuilder.url(endpoint.construct("/oauth/authorize",
|
||||
Pair.create("oauth_token", requestToken.getOauthToken())));
|
||||
data.referer = Endpoint.constructUrl("https://api.twitter.com/oauth/authorize",
|
||||
Pair.create("oauth_token", requestToken.getOauthToken()));
|
||||
final ArrayList<Pair<String, String>> requestHeaders = new ArrayList<>();
|
||||
requestHeaders.add(Pair.create("User-Agent", userAgent));
|
||||
authorizePageBuilder.headers(requestHeaders);
|
||||
authorizePageBuilder.extra(RequestType.API);
|
||||
final RestHttpRequest authorizePageRequest = authorizePageBuilder.build();
|
||||
response = client.execute(authorizePageRequest);
|
||||
parseAuthorizeRequestData(response, data);
|
||||
if (TextUtils.isEmpty(data.authenticityToken)) {
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
return data;
|
||||
} catch (AttoParseException e) {
|
||||
throw new AuthenticationException(e);
|
||||
} finally {
|
||||
Utils.closeSilently(response);
|
||||
}
|
||||
}
|
||||
|
||||
public static String readOAuthPINFromHtml(final Reader in) throws XmlPullParserException, IOException {
|
||||
boolean start_div = false, start_code = false;
|
||||
final XmlPullParserFactory f = XmlPullParserFactory.newInstance();
|
||||
final XmlPullParser parser = f.newPullParser();
|
||||
parser.setFeature(Xml.FEATURE_RELAXED, true);
|
||||
parser.setInput(in);
|
||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
final String tag = parser.getName();
|
||||
final int type = parser.getEventType();
|
||||
if (type == XmlPullParser.START_TAG) {
|
||||
if ("div".equalsIgnoreCase(tag)) {
|
||||
start_div = "oauth_pin".equals(parser.getAttributeValue(null, "id"));
|
||||
} else if ("code".equalsIgnoreCase(tag)) {
|
||||
if (start_div) {
|
||||
start_code = true;
|
||||
public static void readOAuthPINFromHtml(Reader reader, final OAuthPinData data) throws AttoParseException, IOException {
|
||||
final HtmlParsingConfiguration conf = new HtmlParsingConfiguration();
|
||||
final IAttoHandler handler = new AbstractStandardNonValidatingHtmlAttoHandler(conf) {
|
||||
boolean isOAuthPinDivOpened;
|
||||
|
||||
@Override
|
||||
public void handleHtmlStandaloneElement(IHtmlElement element, boolean minimized,
|
||||
String elementName, Map<String, String> attributes,
|
||||
int line, int col) throws AttoParseException {
|
||||
handleHtmlOpenElement(element, elementName, attributes, line, col);
|
||||
handleHtmlCloseElement(element, elementName, line, col);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlOpenElement(IHtmlElement element, String elementName, Map<String, String> attributes, int line, int col) throws AttoParseException {
|
||||
switch (elementName) {
|
||||
case "div": {
|
||||
if (attributes != null && "oauth_pin".equals(attributes.get("id"))) {
|
||||
isOAuthPinDivOpened = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (type == XmlPullParser.END_TAG) {
|
||||
if ("div".equalsIgnoreCase(tag)) {
|
||||
start_div = false;
|
||||
} else if ("code".equalsIgnoreCase(tag)) {
|
||||
start_code = false;
|
||||
}
|
||||
} else if (type == XmlPullParser.TEXT) {
|
||||
final String text = parser.getText();
|
||||
if (start_code && !TextUtils.isEmpty(text) && TextUtils.isDigitsOnly(text))
|
||||
return text;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@Override
|
||||
public void handleHtmlCloseElement(IHtmlElement element, String elementName, int line, int col) throws AttoParseException {
|
||||
if ("div".equals(elementName)) {
|
||||
isOAuthPinDivOpened = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleText(char[] buffer, int offset, int len, int line, int col) throws AttoParseException {
|
||||
if (isOAuthPinDivOpened) {
|
||||
final String s = new String(buffer, offset, len);
|
||||
if (TextUtils.isDigitsOnly(s)) {
|
||||
data.oauthPin = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
PARSER.parse(reader, handler);
|
||||
}
|
||||
|
||||
private static String parseUrlHost(String url) {
|
||||
final int startOfHost = url.indexOf("://") + 3, endOfHost = url.indexOf('/', startOfHost);
|
||||
return url.substring(startOfHost, endOfHost);
|
||||
public interface LoginVerificationCallback {
|
||||
String getLoginVerification();
|
||||
}
|
||||
|
||||
public static class AuthenticationException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -5629194721838256378L;
|
||||
|
||||
AuthenticationException() {
|
||||
}
|
||||
|
||||
@ -213,8 +459,6 @@ public class OAuthPasswordAuthenticator implements Constants {
|
||||
|
||||
public static final class AuthenticityTokenException extends AuthenticationException {
|
||||
|
||||
private static final long serialVersionUID = -1840298989316218380L;
|
||||
|
||||
public AuthenticityTokenException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
@ -222,8 +466,41 @@ public class OAuthPasswordAuthenticator implements Constants {
|
||||
|
||||
public static final class WrongUserPassException extends AuthenticationException {
|
||||
|
||||
private static final long serialVersionUID = -4880737459768513029L;
|
||||
|
||||
}
|
||||
|
||||
public static final class VerificationCodeException extends AuthenticationException {
|
||||
|
||||
|
||||
}
|
||||
|
||||
static class AuthorizeResponseData {
|
||||
|
||||
String referer;
|
||||
|
||||
public String oauthPin;
|
||||
public Verification verification;
|
||||
|
||||
static class Verification {
|
||||
|
||||
String authenticityToken;
|
||||
String challengeId;
|
||||
String challengeType;
|
||||
String platform;
|
||||
String userId;
|
||||
String redirectAfterLogin;
|
||||
}
|
||||
}
|
||||
|
||||
static class AuthorizeRequestData {
|
||||
public String authenticityToken;
|
||||
public String redirectAfterLogin;
|
||||
|
||||
public String referer;
|
||||
}
|
||||
|
||||
public static class OAuthPinData {
|
||||
|
||||
public String oauthPin;
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import android.net.SSLCertificateSocketFactory;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.webkit.URLUtil;
|
||||
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
@ -17,6 +16,7 @@ import com.squareup.okhttp.internal.Network;
|
||||
|
||||
import org.mariotaku.restfu.ExceptionFactory;
|
||||
import org.mariotaku.restfu.HttpRequestFactory;
|
||||
import org.mariotaku.restfu.Pair;
|
||||
import org.mariotaku.restfu.RequestInfoFactory;
|
||||
import org.mariotaku.restfu.RestAPIFactory;
|
||||
import org.mariotaku.restfu.RestMethodInfo;
|
||||
@ -31,6 +31,7 @@ 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;
|
||||
import org.mariotaku.restfu.okhttp.OkHttpRestClient;
|
||||
import org.mariotaku.twidere.TwidereConstants;
|
||||
import org.mariotaku.twidere.api.twitter.Twitter;
|
||||
import org.mariotaku.twidere.api.twitter.TwitterException;
|
||||
@ -47,7 +48,6 @@ import org.mariotaku.twidere.model.ConsumerKeyType;
|
||||
import org.mariotaku.twidere.model.ParcelableCredentials;
|
||||
import org.mariotaku.twidere.model.RequestType;
|
||||
import org.mariotaku.twidere.util.dagger.ApplicationModule;
|
||||
import org.mariotaku.twidere.util.net.OkHttpRestClient;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
@ -116,7 +116,7 @@ public class TwitterAPIFactory implements TwidereConstants {
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
updateHttpClientConfiguration(prefs, client);
|
||||
Internal.instance.setNetwork(client, network);
|
||||
return new OkHttpRestClient(context, client);
|
||||
return new OkHttpRestClient(client);
|
||||
}
|
||||
|
||||
public static void updateHttpClientConfiguration(final SharedPreferences prefs, final OkHttpClient client) {
|
||||
@ -262,12 +262,12 @@ public class TwitterAPIFactory implements TwidereConstants {
|
||||
params.add(Pair.create(name, typedData));
|
||||
}
|
||||
|
||||
public static boolean verifyApiFormat(String format) {
|
||||
public static boolean verifyApiFormat(@NonNull String format) {
|
||||
return URLUtil.isValidUrl(getApiBaseUrl(format, "test"));
|
||||
}
|
||||
|
||||
public static String getApiBaseUrl(String format, final String domain) {
|
||||
if (format == null) return null;
|
||||
@NonNull
|
||||
public static String getApiBaseUrl(@NonNull String format, @Nullable final String domain) {
|
||||
final Matcher matcher = Pattern.compile("\\[(\\.?)DOMAIN(\\.?)\\]", Pattern.CASE_INSENSITIVE).matcher(format);
|
||||
if (!matcher.find()) {
|
||||
// For backward compatibility
|
||||
@ -344,7 +344,8 @@ public class TwitterAPIFactory implements TwidereConstants {
|
||||
return getOAuthEndpoint(apiUrlFormat, "api", noVersionSuffix ? null : "1.1", sameOAuthSigningUrl);
|
||||
}
|
||||
|
||||
public static Endpoint getOAuthEndpoint(String apiUrlFormat, String domain, String versionSuffix, boolean sameOAuthSigningUrl) {
|
||||
public static Endpoint getOAuthEndpoint(String apiUrlFormat, @Nullable String domain,
|
||||
@Nullable String versionSuffix, boolean sameOAuthSigningUrl) {
|
||||
String endpointUrl, signEndpointUrl;
|
||||
endpointUrl = getApiUrl(apiUrlFormat, domain, versionSuffix);
|
||||
if (!sameOAuthSigningUrl) {
|
||||
|
@ -3991,6 +3991,12 @@ public final class Utils implements Constants {
|
||||
return isOfficialCredentials(context, account);
|
||||
}
|
||||
|
||||
public static int getNotificationId(int baseId, long accountId) {
|
||||
int result = baseId;
|
||||
result = 31 * result + (int) (accountId ^ (accountId >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
static class UtilsL {
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
|
@ -33,6 +33,7 @@ import com.squareup.otto.Bus;
|
||||
import com.squareup.otto.ThreadEnforcer;
|
||||
|
||||
import org.mariotaku.restfu.http.RestHttpClient;
|
||||
import org.mariotaku.restfu.okhttp.OkHttpRestClient;
|
||||
import org.mariotaku.twidere.BuildConfig;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.app.TwidereApplication;
|
||||
@ -43,13 +44,13 @@ import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
|
||||
import org.mariotaku.twidere.util.MediaLoaderWrapper;
|
||||
import org.mariotaku.twidere.util.MultiSelectManager;
|
||||
import org.mariotaku.twidere.util.NotificationManagerWrapper;
|
||||
import org.mariotaku.twidere.util.ReadStateManager;
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
|
||||
import org.mariotaku.twidere.util.TwitterAPIFactory;
|
||||
import org.mariotaku.twidere.util.UserColorNameManager;
|
||||
import org.mariotaku.twidere.util.VideoLoader;
|
||||
import org.mariotaku.twidere.util.imageloader.TwidereImageDownloader;
|
||||
import org.mariotaku.twidere.util.net.OkHttpRestClient;
|
||||
import org.mariotaku.twidere.util.net.TwidereNetwork;
|
||||
|
||||
import dagger.Module;
|
||||
@ -79,6 +80,7 @@ public class ApplicationModule {
|
||||
private final UserColorNameManager userColorNameManager;
|
||||
private final KeyboardShortcutsHandler keyboardShortcutsHandler;
|
||||
private final HotMobiLogger hotMobiLogger;
|
||||
private final NotificationManagerWrapper notificationManagerWrapper;
|
||||
|
||||
public ApplicationModule(TwidereApplication application) {
|
||||
if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
|
||||
@ -96,6 +98,7 @@ public class ApplicationModule {
|
||||
asyncTaskManager = AsyncTaskManager.getInstance();
|
||||
readStateManager = new ReadStateManager(application);
|
||||
network = new TwidereNetwork(application);
|
||||
notificationManagerWrapper = new NotificationManagerWrapper(application);
|
||||
|
||||
|
||||
asyncTwitterWrapper = new AsyncTwitterWrapper(application, asyncTaskManager, sharedPreferences, bus);
|
||||
@ -133,6 +136,11 @@ public class ApplicationModule {
|
||||
return keyboardShortcutsHandler;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public NotificationManagerWrapper getNotificationManagerWrapper() {
|
||||
return notificationManagerWrapper;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public SharedPreferencesWrapper getSharedPreferences() {
|
||||
return sharedPreferences;
|
||||
|
@ -24,7 +24,6 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.webkit.URLUtil;
|
||||
|
||||
import com.nostra13.universalimageloader.core.assist.ContentLengthInputStream;
|
||||
@ -32,6 +31,7 @@ import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
|
||||
import com.squareup.pollexor.Thumbor;
|
||||
import com.squareup.pollexor.ThumborUrlBuilder;
|
||||
|
||||
import org.mariotaku.restfu.Pair;
|
||||
import org.mariotaku.restfu.RestRequestInfo;
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.http.Authorization;
|
||||
|
@ -1,315 +0,0 @@
|
||||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util.net;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.squareup.okhttp.Call;
|
||||
import com.squareup.okhttp.Callback;
|
||||
import com.squareup.okhttp.Headers;
|
||||
import com.squareup.okhttp.HttpUrl;
|
||||
import com.squareup.okhttp.MediaType;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.Request;
|
||||
import com.squareup.okhttp.RequestBody;
|
||||
import com.squareup.okhttp.Response;
|
||||
import com.squareup.okhttp.ResponseBody;
|
||||
|
||||
import org.mariotaku.restfu.http.ContentType;
|
||||
import org.mariotaku.restfu.http.RestHttpCallback;
|
||||
import org.mariotaku.restfu.http.RestHttpClient;
|
||||
import org.mariotaku.restfu.http.RestHttpRequest;
|
||||
import org.mariotaku.restfu.http.RestHttpResponse;
|
||||
import org.mariotaku.restfu.http.RestQueuedRequest;
|
||||
import org.mariotaku.restfu.http.mime.TypedData;
|
||||
import org.mariotaku.twidere.util.DebugModeUtils;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import okio.BufferedSink;
|
||||
import okio.Okio;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/5/5.
|
||||
*/
|
||||
public class OkHttpRestClient implements RestHttpClient {
|
||||
|
||||
private final OkHttpClient client;
|
||||
|
||||
public OkHttpRestClient(Context context, OkHttpClient client) {
|
||||
this.client = client;
|
||||
NetworkUsageUtils.initForHttpClient(context, client);
|
||||
DebugModeUtils.initForHttpClient(client);
|
||||
}
|
||||
|
||||
public OkHttpClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@Override
|
||||
public RestHttpResponse execute(RestHttpRequest restHttpRequest) throws IOException {
|
||||
final Call call = newCall(restHttpRequest);
|
||||
return new OkRestHttpResponse(call.execute());
|
||||
}
|
||||
|
||||
private Call newCall(final RestHttpRequest restHttpRequest) throws MalformedURLException {
|
||||
final Request.Builder builder = new Request.Builder();
|
||||
builder.method(restHttpRequest.getMethod(), RestToOkBody.wrap(restHttpRequest.getBody()));
|
||||
final HttpUrl httpUrl = HttpUrl.parse(restHttpRequest.getUrl());
|
||||
if (httpUrl == null) {
|
||||
throw new MalformedURLException();
|
||||
}
|
||||
builder.url(httpUrl);
|
||||
final List<Pair<String, String>> headers = restHttpRequest.getHeaders();
|
||||
if (headers != null) {
|
||||
for (Pair<String, String> header : headers) {
|
||||
builder.addHeader(header.first, header.second);
|
||||
}
|
||||
}
|
||||
builder.tag(restHttpRequest.getExtra());
|
||||
return client.newCall(builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestQueuedRequest enqueue(final RestHttpRequest request, final RestHttpCallback callback) {
|
||||
final Call call;
|
||||
try {
|
||||
call = newCall(request);
|
||||
} catch (final MalformedURLException e) {
|
||||
final DummyRequest dummyCall = new DummyRequest();
|
||||
client.getDispatcher().getExecutorService().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (dummyCall.isCancelled()) {
|
||||
callback.cancelled();
|
||||
return;
|
||||
}
|
||||
callback.exception(e);
|
||||
}
|
||||
});
|
||||
return dummyCall;
|
||||
}
|
||||
call.enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(final Request request, final IOException e) {
|
||||
if (call.isCanceled()) {
|
||||
callback.cancelled();
|
||||
return;
|
||||
}
|
||||
callback.exception(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(final Response response) throws IOException {
|
||||
if (call.isCanceled()) {
|
||||
callback.cancelled();
|
||||
return;
|
||||
}
|
||||
callback.callback(new OkRestHttpResponse(response));
|
||||
}
|
||||
});
|
||||
return new OkHttpQueuedRequest(client, call);
|
||||
}
|
||||
|
||||
private static class RestToOkBody extends RequestBody {
|
||||
private final TypedData body;
|
||||
|
||||
public RestToOkBody(TypedData body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
final ContentType contentType = body.contentType();
|
||||
if (contentType == null) return null;
|
||||
return MediaType.parse(contentType.toHeader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(BufferedSink sink) throws IOException {
|
||||
body.writeTo(sink.outputStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() throws IOException {
|
||||
return body.length();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static RequestBody wrap(@Nullable TypedData body) {
|
||||
if (body == null) return null;
|
||||
return new RestToOkBody(body);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OkRestHttpResponse extends RestHttpResponse {
|
||||
private final Response response;
|
||||
private TypedData body;
|
||||
|
||||
public OkRestHttpResponse(Response response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatus() {
|
||||
return response.code();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, String>> getHeaders() {
|
||||
final Headers headers = response.headers();
|
||||
final ArrayList<Pair<String, String>> headersList = new ArrayList<>();
|
||||
for (int i = 0, j = headers.size(); i < j; i++) {
|
||||
headersList.add(Pair.create(headers.name(i), headers.value(i)));
|
||||
}
|
||||
return headersList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
return response.header(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getHeaders(String name) {
|
||||
final List<String> values = response.headers(name);
|
||||
return values.toArray(new String[values.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedData getBody() {
|
||||
if (body != null) return body;
|
||||
return body = new OkToRestBody(response.body());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (body != null) {
|
||||
body.close();
|
||||
body = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class OkToRestBody implements TypedData {
|
||||
|
||||
private final ResponseBody body;
|
||||
|
||||
public OkToRestBody(ResponseBody body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentType contentType() {
|
||||
final MediaType mediaType = body.contentType();
|
||||
if (mediaType == null) return null;
|
||||
return ContentType.parse(mediaType.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String contentEncoding() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() throws IOException {
|
||||
return body.contentLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long writeTo(@NonNull OutputStream os) throws IOException {
|
||||
final BufferedSink sink = Okio.buffer(Okio.sink(os));
|
||||
final long result = sink.writeAll(body.source());
|
||||
sink.flush();
|
||||
return result;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public InputStream stream() throws IOException {
|
||||
return body.byteStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
body.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static class DummyRequest implements RestQueuedRequest {
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class OkHttpQueuedRequest implements RestQueuedRequest {
|
||||
private final OkHttpClient client;
|
||||
private final Call call;
|
||||
private boolean cancelled;
|
||||
|
||||
public OkHttpQueuedRequest(final OkHttpClient client, final Call call) {
|
||||
this.client = client;
|
||||
this.call = call;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled || call.isCanceled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
cancelled = true;
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
call.cancel();
|
||||
} else {
|
||||
client.getDispatcher().getExecutorService().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
call.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -17,10 +17,10 @@
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<org.mariotaku.twidere.view.TintedStatusNativeActionModeAwareLayout
|
||||
android:id="@+id/main_content"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:setPadding="true">
|
||||
@ -57,7 +57,7 @@
|
||||
android:hint="@string/username"
|
||||
android:inputType="textEmailAddress"
|
||||
android:singleLine="true"
|
||||
android:typeface="normal"/>
|
||||
android:typeface="normal" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
@ -68,7 +68,7 @@
|
||||
android:hint="@string/password"
|
||||
android:inputType="textPassword"
|
||||
android:singleLine="true"
|
||||
android:typeface="normal"/>
|
||||
android:typeface="normal" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
@ -85,7 +85,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:minHeight="48dp"
|
||||
android:text="@string/register"/>
|
||||
android:text="@string/register" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/sign_in"
|
||||
@ -95,7 +95,7 @@
|
||||
android:layout_weight="1"
|
||||
android:minHeight="48dp"
|
||||
android:onClick="onClick"
|
||||
android:text="@string/sign_in"/>
|
||||
android:text="@string/sign_in" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -110,7 +110,7 @@
|
||||
android:minHeight="36dp"
|
||||
android:onClick="onClick"
|
||||
android:text="@string/sign_in_method_introduction_title"
|
||||
android:textAppearance="?android:textAppearanceSmall"/>
|
||||
android:textAppearance="?android:textAppearanceSmall" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
@ -121,14 +121,14 @@
|
||||
android:layout_height="?actionBarSize"
|
||||
android:layout_alignParentTop="true"
|
||||
android:touchscreenBlocksFocus="true"
|
||||
tools:ignore="UnusedAttribute"/>
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
<View
|
||||
android:id="@+id/window_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/twidere_action_bar_container"
|
||||
android:background="?android:windowContentOverlay"/>
|
||||
android:background="?android:windowContentOverlay" />
|
||||
</RelativeLayout>
|
||||
|
||||
</org.mariotaku.twidere.view.TintedStatusNativeActionModeAwareLayout>
|
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Twidere - Twitter client for Android
|
||||
~
|
||||
~ Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/element_spacing_normal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/element_spacing_normal"
|
||||
android:text="@string/login_verification_hint" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edit_verification_code"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/element_spacing_normal"
|
||||
android:inputType="textVisiblePassword|number"
|
||||
android:textAppearance="?android:textAppearanceMedium"
|
||||
android:typeface="monospace">
|
||||
|
||||
<requestFocus />
|
||||
</EditText>
|
||||
</LinearLayout>
|
@ -819,4 +819,6 @@
|
||||
<string name="i_want_my_stars_back_summary">Show use favorite (star) instead of like (heart)</string>
|
||||
<string name="copy_link">Copy link</string>
|
||||
<string name="link_copied_to_clipboard">Link copied to clipboard</string>
|
||||
<string name="login_verification">Login verification</string>
|
||||
<string name="login_verification_hint">Please check your phone for a PIN code and enter it to log in.</string>
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user