implemented 2FA login

This commit is contained in:
Mariotaku Lee 2015-11-14 20:36:56 +08:00
parent d854cf180d
commit 3b16568474
30 changed files with 919 additions and 584 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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