removed dashclock support

fixed 404 errors when signing in
fixed share in tweet list
fixed white button on pre-lollipop versions
This commit is contained in:
Mariotaku Lee 2015-04-04 22:31:37 +08:00
parent c2dae75dec
commit 7a3d995b0e
39 changed files with 1446 additions and 1887 deletions

View File

@ -99,6 +99,7 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
public static final String AUTHORITY_STATUS_REPLIES = "status_replies";
public static final String AUTHORITY_RETWEETS_OF_ME = "retweets_of_me";
public static final String AUTHORITY_MUTES_USERS = "mutes_users";
public static final String AUTHORITY_NOTIFICATIONS = "notifications";
public static final String QUERY_PARAM_ACCOUNT_ID = "account_id";
public static final String QUERY_PARAM_ACCOUNT_IDS = "account_ids";
@ -120,6 +121,8 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
public static final String QUERY_PARAM_FINISH_ONLY = "finish_only";
public static final String QUERY_PARAM_NEW_ITEMS_COUNT = "new_items_count";
public static final String QUERY_PARAM_RECIPIENT_ID = "recipient_id";
public static final String QUERY_PARAM_READ_POSITION = "param_read_position";
public static final String QUERY_PARAM_READ_POSITIONS = "param_read_positions";
public static final String DEFAULT_PROTOCOL = PROTOCOL_HTTPS;

View File

@ -18,12 +18,11 @@ package twitter4j.conf;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import twitter4j.TwitterConstants;
import twitter4j.Version;
import twitter4j.http.HeaderMap;
import twitter4j.http.HostAddressResolverFactory;
import twitter4j.http.HttpClientFactory;
@ -98,7 +97,7 @@ class ConfigurationBase implements TwitterConstants, Configuration {
private HostAddressResolverFactory hostAddressResolverFactory;
// method for HttpRequestFactoryConfiguration
Map<String, String> requestHeaders;
HeaderMap requestHeaders;
private static final List<ConfigurationBase> instances = new ArrayList<ConfigurationBase>();
private boolean includeCards;
@ -394,7 +393,7 @@ class ConfigurationBase implements TwitterConstants, Configuration {
}
@Override
public Map<String, String> getRequestHeaders() {
public HeaderMap getRequestHeaders() {
return requestHeaders;
}
@ -714,6 +713,10 @@ class ConfigurationBase implements TwitterConstants, Configuration {
oAuthAccessTokenSecret = accessTokenSecret;
}
public void setOAuthAuthorizationURL(String oAuthAuthorizationURL) {
// this.oAuthAuthorizationURL = oAuthAuthorizationURL;
}
protected final void setOAuthBaseURL(String oAuthBaseURL) {
if (isNullOrEmpty(oAuthBaseURL)) {
oAuthBaseURL = DEFAULT_OAUTH_BASE_URL;
@ -869,23 +872,23 @@ class ConfigurationBase implements TwitterConstants, Configuration {
}
final void initRequestHeaders() {
requestHeaders = new HashMap<String, String>();
requestHeaders = new HeaderMap();
if (includeTwitterClientHeader) {
requestHeaders.put("X-Twitter-Client-Version", getClientVersion());
requestHeaders.put("X-Twitter-Client-URL", getClientURL());
requestHeaders.put("X-Twitter-Client", getClientName());
requestHeaders.addHeader("X-Twitter-Client-Version", getClientVersion());
requestHeaders.addHeader("X-Twitter-Client-URL", getClientURL());
requestHeaders.addHeader("X-Twitter-Client", getClientName());
}
requestHeaders.put("User-Agent", getHttpUserAgent());
requestHeaders.addHeader("User-Agent", getHttpUserAgent());
if (gzipEnabled) {
requestHeaders.put("Accept-Encoding", "gzip");
requestHeaders.addHeader("Accept-Encoding", "gzip");
}
// I found this may cause "Socket is closed" error in Android, so I
// changed it to "keep-alive".
if (!isNullOrEmpty(httpProxyHost) && httpProxyPort > 0) {
requestHeaders.put("Connection", "keep-alive");
requestHeaders.addHeader("Connection", "keep-alive");
} else {
requestHeaders.put("Connection", "close");
requestHeaders.addHeader("Connection", "close");
}
}

View File

@ -253,6 +253,13 @@ public final class ConfigurationBuilder {
return this;
}
public ConfigurationBuilder setOAuthAuthorizationURL(final String oAuthAuthorizationURL) {
checkNotBuilt();
configuration.setOAuthAuthorizationURL(oAuthAuthorizationURL);
return this;
}
public ConfigurationBuilder setOAuthConsumerKey(final String oAuthConsumerKey) {
checkNotBuilt();
configuration.setOAuthConsumerKey(oAuthConsumerKey);

View File

@ -0,0 +1,38 @@
/*
* 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 twitter4j.http;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Created by mariotaku on 15/4/4.
*/
public class HeaderMap extends HashMap<String, List<String>> {
public void addHeader(String key, String value) {
List<String> values = get(key);
if (values == null) values = new ArrayList<>();
values.add(value);
put(key, values);
}
}

View File

@ -20,6 +20,7 @@
package twitter4j.http;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import twitter4j.TwitterException;
@ -35,190 +36,190 @@ import static twitter4j.http.RequestMethod.PUT;
/**
* HTTP Client wrapper with handy request methods, ResponseListener mechanism
*
*
* @author Yusuke Yamamoto - yusuke at mac.com
*/
public final class HttpClientWrapper {
private final Configuration wrapperConf;
private final HttpClient http;
private final Configuration wrapperConf;
private final HttpClient http;
private final Map<String, String> requestHeaders;
private final Map<String, List<String>> requestHeaders;
private HttpResponseListener httpResponseListener;
private HttpResponseListener httpResponseListener;
// never used with this project. Just for handiness for those using this
// class.
public HttpClientWrapper() {
wrapperConf = ConfigurationContext.getInstance();
requestHeaders = wrapperConf.getRequestHeaders();
http = FactoryUtils.getHttpClient(wrapperConf);
}
// never used with this project. Just for handiness for those using this
// class.
public HttpClientWrapper() {
wrapperConf = ConfigurationContext.getInstance();
requestHeaders = wrapperConf.getRequestHeaders();
http = FactoryUtils.getHttpClient(wrapperConf);
}
public HttpClientWrapper(final Configuration wrapperConf) {
this.wrapperConf = wrapperConf;
requestHeaders = wrapperConf.getRequestHeaders();
http = FactoryUtils.getHttpClient(wrapperConf);
}
public HttpClientWrapper(final Configuration wrapperConf) {
this.wrapperConf = wrapperConf;
requestHeaders = wrapperConf.getRequestHeaders();
http = FactoryUtils.getHttpClient(wrapperConf);
}
public HttpResponse delete(final String url, final String signUrl) throws TwitterException {
return delete(url, signUrl, null, null);
}
public HttpResponse delete(final String url, final String signUrl) throws TwitterException {
return delete(url, signUrl, null, null);
}
public HttpResponse delete(final String url, final String signUrl, final Authorization authorization)
throws TwitterException {
return delete(url, signUrl, null, authorization);
}
public HttpResponse delete(final String url, final String signUrl, final Authorization authorization)
throws TwitterException {
return delete(url, signUrl, null, authorization);
}
public HttpResponse delete(final String url, final String signUrl, final HttpParameter[] parameters)
throws TwitterException {
return delete(url, signUrl, parameters, null);
}
public HttpResponse delete(final String url, final String signUrl, final HttpParameter[] parameters)
throws TwitterException {
return delete(url, signUrl, parameters, null);
}
public HttpResponse delete(final String url, final String signUrl, final HttpParameter[] parameters,
final Authorization authorization) throws TwitterException {
return request(new HttpRequest(DELETE, url, signUrl, parameters, authorization, requestHeaders));
}
public HttpResponse delete(final String url, final String signUrl, final HttpParameter[] parameters,
final Authorization authorization) throws TwitterException {
return request(new HttpRequest(DELETE, url, signUrl, parameters, authorization, requestHeaders));
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final HttpClientWrapper that = (HttpClientWrapper) o;
final HttpClientWrapper that = (HttpClientWrapper) o;
if (!http.equals(that.http)) return false;
if (!requestHeaders.equals(that.requestHeaders)) return false;
if (!wrapperConf.equals(that.wrapperConf)) return false;
if (!http.equals(that.http)) return false;
if (!requestHeaders.equals(that.requestHeaders)) return false;
if (!wrapperConf.equals(that.wrapperConf)) return false;
return true;
}
return true;
}
public HttpResponse get(final String url, final String signUrl) throws TwitterException {
return get(url, signUrl, null, null);
}
public HttpResponse get(final String url, final String signUrl) throws TwitterException {
return get(url, signUrl, null, null);
}
public HttpResponse get(final String url, final String signUrl, final Authorization authorization)
throws TwitterException {
return get(url, signUrl, null, authorization);
}
public HttpResponse get(final String url, final String signUrl, final Authorization authorization)
throws TwitterException {
return get(url, signUrl, null, authorization);
}
public HttpResponse get(final String url, final String signUrl, final HttpParameter[] parameters)
throws TwitterException {
return get(url, signUrl, parameters, null);
}
public HttpResponse get(final String url, final String signUrl, final HttpParameter[] parameters)
throws TwitterException {
return get(url, signUrl, parameters, null);
}
public HttpResponse get(final String url, final String signUrl, final HttpParameter[] parameters,
final Authorization authorization) throws TwitterException {
return request(new HttpRequest(GET, url, signUrl, parameters, authorization, requestHeaders));
}
public HttpResponse get(final String url, final String signUrl, final HttpParameter[] parameters,
final Authorization authorization) throws TwitterException {
return request(new HttpRequest(GET, url, signUrl, parameters, authorization, requestHeaders));
}
@Override
public int hashCode() {
int result = wrapperConf.hashCode();
result = 31 * result + http.hashCode();
result = 31 * result + requestHeaders.hashCode();
return result;
}
@Override
public int hashCode() {
int result = wrapperConf.hashCode();
result = 31 * result + http.hashCode();
result = 31 * result + requestHeaders.hashCode();
return result;
}
public HttpResponse head(final String url, final String signUrl) throws TwitterException {
return head(url, signUrl, null, null);
}
public HttpResponse head(final String url, final String signUrl) throws TwitterException {
return head(url, signUrl, null, null);
}
public HttpResponse head(final String url, final String signUrl, final Authorization authorization)
throws TwitterException {
return head(url, signUrl, null, authorization);
}
public HttpResponse head(final String url, final String signUrl, final Authorization authorization)
throws TwitterException {
return head(url, signUrl, null, authorization);
}
public HttpResponse head(final String url, final String signUrl, final HttpParameter[] parameters)
throws TwitterException {
return head(url, signUrl, parameters, null);
}
public HttpResponse head(final String url, final String signUrl, final HttpParameter[] parameters)
throws TwitterException {
return head(url, signUrl, parameters, null);
}
public HttpResponse head(final String url, final String signUrl, final HttpParameter[] parameters,
final Authorization authorization) throws TwitterException {
return request(new HttpRequest(HEAD, url, signUrl, parameters, authorization, requestHeaders));
}
public HttpResponse head(final String url, final String signUrl, final HttpParameter[] parameters,
final Authorization authorization) throws TwitterException {
return request(new HttpRequest(HEAD, url, signUrl, parameters, authorization, requestHeaders));
}
public HttpResponse post(final String url, final String signUrl) throws TwitterException {
return post(url, signUrl, null, null, null);
}
public HttpResponse post(final String url, final String signUrl) throws TwitterException {
return post(url, signUrl, null, null, null);
}
public HttpResponse post(final String url, final String signUrl, final Authorization authorization)
throws TwitterException {
return post(url, signUrl, null, authorization, null);
}
public HttpResponse post(final String url, final String signUrl, final Authorization authorization)
throws TwitterException {
return post(url, signUrl, null, authorization, null);
}
public HttpResponse post(final String url, final String signUrl, final HttpParameter[] parameters)
throws TwitterException {
return post(url, signUrl, parameters, null, null);
}
public HttpResponse post(final String url, final String signUrl, final HttpParameter[] parameters)
throws TwitterException {
return post(url, signUrl, parameters, null, null);
}
public HttpResponse post(final String url, final String signUrl, final HttpParameter[] parameters,
final Authorization authorization) throws TwitterException {
return post(url, signUrl, parameters, authorization, null);
}
public HttpResponse post(final String url, final String signUrl, final HttpParameter[] parameters,
final Authorization authorization) throws TwitterException {
return post(url, signUrl, parameters, authorization, null);
}
public HttpResponse post(final String url, final String signUrl, final HttpParameter[] parameters,
final Authorization authorization, final Map<String, String> requestHeaders) throws TwitterException {
final Map<String, String> headers = new HashMap<String, String>(this.requestHeaders);
if (requestHeaders != null) {
headers.putAll(requestHeaders);
}
return request(new HttpRequest(POST, url, signUrl, parameters, authorization, headers));
}
public HttpResponse post(final String url, final String signUrl, final HttpParameter[] parameters,
final Authorization authorization, final Map<String, List<String>> requestHeaders) throws TwitterException {
final Map<String, List<String>> headers = new HashMap<>(this.requestHeaders);
if (requestHeaders != null) {
headers.putAll(requestHeaders);
}
return request(new HttpRequest(POST, url, signUrl, parameters, authorization, headers));
}
public HttpResponse post(final String url, final String signUrl, final HttpParameter[] parameters,
final Map<String, String> requestHeaders) throws TwitterException {
return post(url, signUrl, parameters, null, requestHeaders);
}
public HttpResponse post(final String url, final String signUrl, final HttpParameter[] parameters,
final Map<String, List<String>> requestHeaders) throws TwitterException {
return post(url, signUrl, parameters, null, requestHeaders);
}
public HttpResponse put(final String url, final String signUrl) throws TwitterException {
return put(url, signUrl, null, null);
}
public HttpResponse put(final String url, final String signUrl) throws TwitterException {
return put(url, signUrl, null, null);
}
public HttpResponse put(final String url, final String signUrl, final Authorization authorization)
throws TwitterException {
return put(url, signUrl, null, authorization);
}
public HttpResponse put(final String url, final String signUrl, final Authorization authorization)
throws TwitterException {
return put(url, signUrl, null, authorization);
}
public HttpResponse put(final String url, final String signUrl, final HttpParameter[] parameters)
throws TwitterException {
return put(url, signUrl, parameters, null);
}
public HttpResponse put(final String url, final String signUrl, final HttpParameter[] parameters)
throws TwitterException {
return put(url, signUrl, parameters, null);
}
public HttpResponse put(final String url, final String signUrl, final HttpParameter[] parameters,
final Authorization authorization) throws TwitterException {
return request(new HttpRequest(PUT, url, signUrl, parameters, authorization, requestHeaders));
}
public HttpResponse put(final String url, final String signUrl, final HttpParameter[] parameters,
final Authorization authorization) throws TwitterException {
return request(new HttpRequest(PUT, url, signUrl, parameters, authorization, requestHeaders));
}
public void setHttpResponseListener(final HttpResponseListener listener) {
httpResponseListener = listener;
}
public void setHttpResponseListener(final HttpResponseListener listener) {
httpResponseListener = listener;
}
public void shutdown() {
http.shutdown();
}
public void shutdown() {
http.shutdown();
}
@Override
public String toString() {
return "HttpClientWrapper{" + "wrapperConf=" + wrapperConf + ", http=" + http + ", requestHeaders="
+ requestHeaders + ", httpResponseListener=" + httpResponseListener + '}';
}
@Override
public String toString() {
return "HttpClientWrapper{" + "wrapperConf=" + wrapperConf + ", http=" + http + ", requestHeaders="
+ requestHeaders + ", httpResponseListener=" + httpResponseListener + '}';
}
private HttpResponse request(final HttpRequest req) throws TwitterException {
HttpResponse res;
try {
res = http.request(req);
// fire HttpResponseEvent
if (httpResponseListener != null) {
httpResponseListener.httpResponseReceived(new HttpResponseEvent(req, res, null));
}
} catch (final TwitterException te) {
if (httpResponseListener != null) {
httpResponseListener.httpResponseReceived(new HttpResponseEvent(req, null, te));
}
throw te;
}
return res;
}
private HttpResponse request(final HttpRequest req) throws TwitterException {
HttpResponse res;
try {
res = http.request(req);
// fire HttpResponseEvent
if (httpResponseListener != null) {
httpResponseListener.httpResponseReceived(new HttpResponseEvent(req, res, null));
}
} catch (final TwitterException te) {
if (httpResponseListener != null) {
httpResponseListener.httpResponseReceived(new HttpResponseEvent(req, null, te));
}
throw te;
}
return res;
}
}

View File

@ -16,11 +16,12 @@
package twitter4j.http;
import java.util.List;
import java.util.Map;
public interface HttpClientWrapperConfiguration extends HttpClientConfiguration {
/**
* @return request headers
*/
Map<String, String> getRequestHeaders();
HeaderMap getRequestHeaders();
}

View File

@ -20,113 +20,114 @@
package twitter4j.http;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import twitter4j.auth.Authorization;
/**
* HTTP Request parameter object
*
*
* @author Yusuke Yamamoto - yusuke at mac.com
*/
public final class HttpRequest {
private final RequestMethod method;
private final RequestMethod method;
private final String url, signUrl;
private final String url, signUrl;
private final HttpParameter[] parameters;
private final HttpParameter[] parameters;
private final Authorization authorization;
private final Authorization authorization;
private final Map<String, String> requestHeaders;
private final Map<String, List<String>> requestHeaders;
private static final HttpParameter[] NULL_PARAMETERS = new HttpParameter[0];
private static final HttpParameter[] NULL_PARAMETERS = new HttpParameter[0];
/**
* @param method Specifies the HTTP method
* @param url the request to request
* @param parameters parameters
* @param authorization Authentication implementation. Currently
* BasicAuthentication, OAuthAuthentication and
* NullAuthentication are supported.
* @param requestHeaders
*/
public HttpRequest(final RequestMethod method, final String url, final String signUrl,
final HttpParameter[] parameters, final Authorization authorization,
final Map<String, String> requestHeaders) {
this.method = method;
if (method != RequestMethod.POST && parameters != null && parameters.length != 0) {
final String paramString = HttpParameter.encodeParameters(parameters);
this.url = url + "?" + paramString;
this.signUrl = signUrl + "?" + paramString;
this.parameters = NULL_PARAMETERS;
} else {
this.url = url;
this.signUrl = signUrl;
this.parameters = parameters;
}
this.authorization = authorization;
this.requestHeaders = requestHeaders;
}
/**
* @param method Specifies the HTTP method
* @param url the request to request
* @param parameters parameters
* @param authorization Authentication implementation. Currently
* BasicAuthentication, OAuthAuthentication and
* NullAuthentication are supported.
* @param requestHeaders
*/
public HttpRequest(final RequestMethod method, final String url, final String signUrl,
final HttpParameter[] parameters, final Authorization authorization,
final Map<String, List<String>> requestHeaders) {
this.method = method;
if (method != RequestMethod.POST && parameters != null && parameters.length != 0) {
final String paramString = HttpParameter.encodeParameters(parameters);
this.url = url + "?" + paramString;
this.signUrl = signUrl + "?" + paramString;
this.parameters = NULL_PARAMETERS;
} else {
this.url = url;
this.signUrl = signUrl;
this.parameters = parameters;
}
this.authorization = authorization;
this.requestHeaders = requestHeaders;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final HttpRequest that = (HttpRequest) o;
final HttpRequest that = (HttpRequest) o;
if (authorization != null ? !authorization.equals(that.authorization) : that.authorization != null)
return false;
if (!Arrays.equals(parameters, that.parameters)) return false;
if (requestHeaders != null ? !requestHeaders.equals(that.requestHeaders) : that.requestHeaders != null)
return false;
if (method != null ? !method.equals(that.method) : that.method != null) return false;
if (url != null ? !url.equals(that.url) : that.url != null) return false;
if (authorization != null ? !authorization.equals(that.authorization) : that.authorization != null)
return false;
if (!Arrays.equals(parameters, that.parameters)) return false;
if (requestHeaders != null ? !requestHeaders.equals(that.requestHeaders) : that.requestHeaders != null)
return false;
if (method != null ? !method.equals(that.method) : that.method != null) return false;
if (url != null ? !url.equals(that.url) : that.url != null) return false;
return true;
}
return true;
}
public Authorization getAuthorization() {
return authorization;
}
public Authorization getAuthorization() {
return authorization;
}
public RequestMethod getMethod() {
return method;
}
public RequestMethod getMethod() {
return method;
}
public HttpParameter[] getParameters() {
return parameters;
}
public HttpParameter[] getParameters() {
return parameters;
}
public Map<String, String> getRequestHeaders() {
return requestHeaders;
}
public Map<String, List<String>> getRequestHeaders() {
return requestHeaders;
}
public String getSignURL() {
return signUrl != null ? signUrl : url;
}
public String getSignURL() {
return signUrl != null ? signUrl : url;
}
public String getURL() {
return url;
}
public String getURL() {
return url;
}
@Override
public int hashCode() {
int result = method != null ? method.hashCode() : 0;
result = 31 * result + (url != null ? url.hashCode() : 0);
result = 31 * result + (signUrl != null ? signUrl.hashCode() : 0);
result = 31 * result + (parameters != null ? Arrays.hashCode(parameters) : 0);
result = 31 * result + (authorization != null ? authorization.hashCode() : 0);
result = 31 * result + (requestHeaders != null ? requestHeaders.hashCode() : 0);
return result;
}
@Override
public int hashCode() {
int result = method != null ? method.hashCode() : 0;
result = 31 * result + (url != null ? url.hashCode() : 0);
result = 31 * result + (signUrl != null ? signUrl.hashCode() : 0);
result = 31 * result + (parameters != null ? Arrays.hashCode(parameters) : 0);
result = 31 * result + (authorization != null ? authorization.hashCode() : 0);
result = 31 * result + (requestHeaders != null ? requestHeaders.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "HttpRequest{" + "requestMethod=" + method + ", url='" + url + '\'' + ", signUrl='" + signUrl + '\''
+ ", postParams=" + (parameters == null ? null : Arrays.asList(parameters)) + ", authentication="
+ authorization + ", requestHeaders=" + requestHeaders + '}';
}
@Override
public String toString() {
return "HttpRequest{" + "requestMethod=" + method + ", url='" + url + '\'' + ", signUrl='" + signUrl + '\''
+ ", postParams=" + (parameters == null ? null : Arrays.asList(parameters)) + ", authentication="
+ authorization + ", requestHeaders=" + requestHeaders + '}';
}
}

View File

@ -206,6 +206,8 @@ public abstract class HttpResponse {
public abstract String getResponseHeader(String name);
public abstract List<String> getResponseHeaders(String name);
public abstract Map<String, List<String>> getResponseHeaderFields();
public int getStatusCode() {

View File

@ -1,68 +0,0 @@
/*
* Copyright 2007 Yusuke Yamamoto
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package twitter4j.http;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.List;
import java.util.Map;
/**
* @author Yusuke Yamamoto - yusuke at mac.com
* @since Twitter4J 2.1.2
*/
public class HttpResponseImpl extends HttpResponse {
private HttpURLConnection con;
HttpResponseImpl(final HttpURLConnection con, final HttpClientConfiguration conf) throws IOException {
super(conf);
this.con = con;
if (con == null) return;
statusCode = con.getResponseCode();
if (null == (is = con.getErrorStream())) {
is = con.getInputStream();
}
if (is != null && "gzip".equals(con.getContentEncoding())) {
// the response is gzipped
is = new StreamingGZIPInputStream(is);
}
}
// for test purpose
/* package */HttpResponseImpl(final String content) {
super();
responseAsString = content;
}
/**
* {@inheritDoc}
*/
@Override
public void disconnect() {
con.disconnect();
}
@Override
public String getResponseHeader(final String name) {
return con.getHeaderField(name);
}
@Override
public Map<String, List<String>> getResponseHeaderFields() {
return con.getHeaderFields();
}
}

View File

@ -81,8 +81,11 @@ public class OkHttpClientImpl implements HttpClient, TwidereConstants {
@Override
public HttpResponse request(HttpRequest req) throws TwitterException {
final Builder builder = new Builder();
for (Entry<String, String> headerEntry : req.getRequestHeaders().entrySet()) {
builder.header(headerEntry.getKey(), headerEntry.getValue());
for (Entry<String, List<String>> headerEntry : req.getRequestHeaders().entrySet()) {
final String name = headerEntry.getKey();
for (String value : headerEntry.getValue()) {
builder.addHeader(name, value);
}
}
final Authorization authorization = req.getAuthorization();
if (authorization != null) {
@ -259,6 +262,11 @@ public class OkHttpClientImpl implements HttpClient, TwidereConstants {
return response.header(name);
}
@Override
public List<String> getResponseHeaders(String name) {
return response.headers(name);
}
@Override
public Map<String, List<String>> getResponseHeaderFields() {
final Headers headers = response.headers();

View File

@ -72,7 +72,6 @@ dependencies {
compile 'com.sothree.slidinguppanel:library:3.0.0'
compile 'com.twitter:twitter-text:1.11.1'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3'
compile 'com.google.android.apps.dashclock:dashclock-api:2.0.0'
compile 'com.squareup:otto:1.3.6'
compile 'dnsjava:dnsjava:2.1.7'
compile 'com.commonsware.cwac:merge:1.1.1'

View File

@ -599,63 +599,6 @@
<service
android:name=".service.BackgroundOperationService"
android:label="@string/label_background_operation_service"/>
<service
android:name=".service.DashClockHomeUnreadCountService"
android:icon="@drawable/ic_extension_twidere"
android:label="@string/dashclock_home_unread_count_name"
android:permission="com.google.android.apps.dashclock.permission.READ_EXTENSION_DATA">
<intent-filter>
<action android:name="com.google.android.apps.dashclock.Extension"/>
</intent-filter>
<meta-data
android:name="protocolVersion"
android:value="2"/>
<meta-data
android:name="worldReadable"
android:value="true"/>
<meta-data
android:name="description"
android:value="@string/dashclock_home_unread_count_description"/>
</service>
<service
android:name=".service.DashClockMentionsUnreadCountService"
android:icon="@drawable/ic_extension_mentions"
android:label="@string/dashclock_mentions_unread_count_name"
android:permission="com.google.android.apps.dashclock.permission.READ_EXTENSION_DATA">
<intent-filter>
<action android:name="com.google.android.apps.dashclock.Extension"/>
</intent-filter>
<meta-data
android:name="protocolVersion"
android:value="2"/>
<meta-data
android:name="worldReadable"
android:value="true"/>
<meta-data
android:name="description"
android:value="@string/dashclock_mentions_unread_count_description"/>
</service>
<service
android:name=".service.DashClockMessagesUnreadCountService"
android:icon="@drawable/ic_extension_messages"
android:label="@string/dashclock_messages_unread_count_name"
android:permission="com.google.android.apps.dashclock.permission.READ_EXTENSION_DATA">
<intent-filter>
<action android:name="com.google.android.apps.dashclock.Extension"/>
</intent-filter>
<meta-data
android:name="protocolVersion"
android:value="2"/>
<meta-data
android:name="worldReadable"
android:value="true"/>
<meta-data
android:name="description"
android:value="@string/dashclock_messages_unread_count_description"/>
</service>
<service
android:name=".nyan.NyanWallpaperService"
android:enabled="false"

View File

@ -28,6 +28,7 @@ public interface IThemedActivity {
public Resources getDefaultResources();
public int getThemeBackgroundAlpha();
public int getCurrentThemeBackgroundAlpha();
public int getThemeColor();

View File

@ -34,6 +34,8 @@ import android.view.View;
import android.view.Window;
import android.webkit.JavascriptInterface;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@ -170,6 +172,11 @@ public class BrowserSignInActivity extends BaseSupportDialogActivity implements
mActivity.setLoadProgressShown(true);
}
@Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
}
@Override
public void onReceivedError(final WebView view, final int errorCode, final String description,
final String failingUrl) {

View File

@ -31,7 +31,6 @@ import android.database.ContentObserver;
import android.graphics.Canvas;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
@ -724,9 +723,21 @@ public class HomeActivity extends BaseActionBarActivity implements OnClickListen
final int actionBarAlpha = isTransparent ? ThemeUtils.getUserThemeBackgroundAlpha(this) : 0xFF;
final IHomeActionButton homeActionButton = (IHomeActionButton) mActionsButton;
mTabIndicator.setItemContext(ThemeUtils.getActionBarContext(this));
if (ThemeUtils.isColoredActionBar(themeResId)) {
ViewAccessor.setBackground(mActionBar, ThemeUtils.getActionBarBackground(this, themeResId, themeColor, true));
if (ThemeUtils.isDarkTheme(themeResId)) {
final int backgroundColor = ThemeUtils.getThemeBackgroundColor(mTabIndicator.getItemContext());
final int foregroundColor = ThemeUtils.getThemeForegroundColor(mTabIndicator.getItemContext());
homeActionButton.setButtonColor(backgroundColor);
homeActionButton.setIconColor(foregroundColor, Mode.SRC_ATOP);
mTabIndicator.setStripColor(themeColor);
mTabIndicator.setIconColor(foregroundColor);
mTabIndicator.setLabelColor(foregroundColor);
mColorStatusFrameLayout.setDrawColor(true);
mColorStatusFrameLayout.setDrawShadow(false);
mColorStatusFrameLayout.setColor(getResources().getColor(R.color.background_color_action_bar_dark), actionBarAlpha);
mColorStatusFrameLayout.setFactor(1);
} else {
final int contrastColor = ColorUtils.getContrastYIQ(themeColor, 192);
ViewAccessor.setBackground(mActionBar, new ColorDrawable(themeColor));
homeActionButton.setButtonColor(themeColor);
homeActionButton.setIconColor(contrastColor, Mode.SRC_ATOP);
mTabIndicator.setStripColor(contrastColor);
@ -737,17 +748,6 @@ public class HomeActivity extends BaseActionBarActivity implements OnClickListen
mColorStatusFrameLayout.setDrawShadow(false);
mColorStatusFrameLayout.setColor(themeColor, actionBarAlpha);
mColorStatusFrameLayout.setFactor(1);
} else {
final int backgroundColor = ThemeUtils.getThemeBackgroundColor(mTabIndicator.getItemContext());
final int foregroundColor = ThemeUtils.getThemeForegroundColor(mTabIndicator.getItemContext());
ViewAccessor.setBackground(mActionBar, ThemeUtils.getActionBarBackground(this, themeResId));
homeActionButton.setButtonColor(backgroundColor);
homeActionButton.setIconColor(foregroundColor, Mode.SRC_ATOP);
mTabIndicator.setStripColor(themeColor);
mTabIndicator.setIconColor(foregroundColor);
mTabIndicator.setLabelColor(foregroundColor);
mColorStatusFrameLayout.setDrawColor(false);
mColorStatusFrameLayout.setDrawShadow(false);
}
mTabIndicator.setAlpha(actionBarAlpha / 255f);
mActionsButton.setAlpha(actionBarAlpha / 255f);

View File

@ -255,16 +255,20 @@ public class LinkHandlerActivity extends BaseActionBarActivity implements OnClic
case LINK_ID_USER: {
mMainContent.setShadowColor(0xA0000000);
mMainContent.setDrawShadow(false);
mMainContent.setDrawColor(!ThemeUtils.isDarkTheme(getCurrentThemeResourceId()));
mMainContent.setDrawColor(true);
break;
}
default: {
mMainContent.setDrawShadow(false);
mMainContent.setDrawColor(!ThemeUtils.isDarkTheme(getCurrentThemeResourceId()));
mMainContent.setDrawColor(true);
mMainContent.setFactor(1);
final int color = getCurrentThemeColor();
final int alpha = getCurrentThemeBackgroundAlpha();
mMainContent.setColor(color, alpha);
if (ThemeUtils.isDarkTheme(getCurrentThemeResourceId())) {
mMainContent.setColor(getResources().getColor(R.color.background_color_action_bar_dark), alpha);
} else {
mMainContent.setColor(color, alpha);
}
break;
}
}

View File

@ -38,6 +38,7 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBar;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.MenuItem;
@ -95,6 +96,7 @@ public class SignInActivity extends BaseActionBarActivity implements TwitterCons
private static final String TWITTER_SIGNUP_URL = "https://twitter.com/signup";
private static final String EXTRA_API_LAST_CHANGE = "api_last_change";
public static final String FRAGMENT_TAG_SIGN_IN_PROGRESS = "sign_in_progress";
private static final String DEFAULT_TWITTER_API_URL_FORMAT = "https://[DOMAIN.]twitter.com/";
private String mAPIUrlFormat;
private int mAuthType;
@ -367,16 +369,17 @@ public class SignInActivity extends BaseActionBarActivity implements TwitterCons
} else {
Utils.setUserAgent(this, cb);
}
if (!isEmpty(mAPIUrlFormat)) {
final String versionSuffix = mNoVersionSuffix ? null : "/1.1/";
cb.setRestBaseURL(Utils.getApiUrl(mAPIUrlFormat, "api", versionSuffix));
cb.setOAuthBaseURL(Utils.getApiUrl(mAPIUrlFormat, "api", "/oauth/"));
cb.setUploadBaseURL(Utils.getApiUrl(mAPIUrlFormat, "upload", versionSuffix));
if (!mSameOAuthSigningUrl) {
cb.setSigningRestBaseURL(DEFAULT_SIGNING_REST_BASE_URL);
cb.setSigningOAuthBaseURL(DEFAULT_SIGNING_OAUTH_BASE_URL);
cb.setSigningUploadBaseURL(DEFAULT_SIGNING_UPLOAD_BASE_URL);
}
final String apiUrlFormat = TextUtils.isEmpty(mAPIUrlFormat) ? DEFAULT_TWITTER_API_URL_FORMAT : mAPIUrlFormat;
final String versionSuffix = mNoVersionSuffix ? null : "/1.1/";
cb.setRestBaseURL(Utils.getApiUrl(apiUrlFormat, "api", versionSuffix));
cb.setOAuthBaseURL(Utils.getApiUrl(apiUrlFormat, "api", "/oauth/"));
cb.setUploadBaseURL(Utils.getApiUrl(apiUrlFormat, "upload", versionSuffix));
cb.setOAuthAuthorizationURL(Utils.getApiUrl(apiUrlFormat, null, "/oauth/authorize"));
cb.setHttpUserAgent(Utils.generateBrowserUserAgent());
if (!mSameOAuthSigningUrl) {
cb.setSigningRestBaseURL(DEFAULT_SIGNING_REST_BASE_URL);
cb.setSigningOAuthBaseURL(DEFAULT_SIGNING_OAUTH_BASE_URL);
cb.setSigningUploadBaseURL(DEFAULT_SIGNING_UPLOAD_BASE_URL);
}
if (isEmpty(mConsumerKey) || isEmpty(mConsumerSecret)) {
cb.setOAuthConsumerKey(TWITTER_CONSUMER_KEY_3);
@ -501,6 +504,7 @@ public class SignInActivity extends BaseActionBarActivity implements TwitterCons
}
void onSignInStart() {
if (isFinishing()) return;
final SupportProgressDialogFragment fragment = SupportProgressDialogFragment.show(this, FRAGMENT_TAG_SIGN_IN_PROGRESS);
fragment.setCancelable(false);
}

View File

@ -45,6 +45,8 @@ import static org.mariotaku.twidere.util.Utils.announceForAccessibilityCompat;
public class SupportTabsAdapter extends SupportFixedFragmentStatePagerAdapter implements TabProvider, TabListener,
Constants {
private static final String EXTRA_ADAPTER_POSITION = "adapter_position";
private final ArrayList<SupportTabSpec> mTabs = new ArrayList<>();
private final Context mContext;
@ -105,7 +107,7 @@ public class SupportTabsAdapter extends SupportFixedFragmentStatePagerAdapter im
if (args == null) {
args = new Bundle();
}
args.putInt(EXTRA_TAB_POSITION, position);
args.putInt(EXTRA_ADAPTER_POSITION, position);
return args;
}
@ -155,7 +157,7 @@ public class SupportTabsAdapter extends SupportFixedFragmentStatePagerAdapter im
if (!(object instanceof Fragment)) return POSITION_NONE;
final Bundle args = ((Fragment) object).getArguments();
if (args == null) return POSITION_NONE;
return args.getInt(EXTRA_TAB_POSITION, POSITION_NONE);
return args.getInt(EXTRA_ADAPTER_POSITION, POSITION_NONE);
}
@Override

View File

@ -83,6 +83,11 @@ public abstract class AbsStatusesFragment<Data> extends BaseSupportFragment impl
public boolean onMenuItemClick(MenuItem item) {
final ParcelableStatus status = mSelectedStatus;
if (status == null) return false;
if (item.getItemId() == MENU_SHARE) {
final Intent shareIntent = Utils.createStatusShareIntent(getActivity(), status);
startActivity(Intent.createChooser(shareIntent, getString(R.string.share_status)));
return true;
}
return Utils.handleMenuItemClick(getActivity(), AbsStatusesFragment.this,
getFragmentManager(), getTwitterWrapper(), status, item);
}
@ -461,7 +466,7 @@ public abstract class AbsStatusesFragment<Data> extends BaseSupportFragment impl
protected abstract AbsStatusesAdapter<Data> onCreateAdapter(Context context, boolean compact);
protected void saveReadPosition() {
final String readPositionTag = getReadPositionTag();
final String readPositionTag = getReadPositionTagWithAccounts();
if (readPositionTag == null) return;
final int position = mLayoutManager.findFirstVisibleItemPosition();
if (position == RecyclerView.NO_POSITION) return;

View File

@ -115,9 +115,12 @@ public class SearchFragment extends BaseSupportFragment implements RefreshScroll
private void updateTabOffset() {
final int controlBarHeight = getControlBarHeight();
final int translationY = controlBarHeight - mControlBarOffsetPixels;
final View view = getActivity().getWindow().findViewById(android.support.v7.appcompat.R.id.action_bar);
if (view != null && controlBarHeight != 0) {
view.setAlpha(translationY / (float) controlBarHeight);
final FragmentActivity activity = getActivity();
if (activity instanceof LinkHandlerActivity) {
final View view = activity.getWindow().findViewById(android.support.v7.appcompat.R.id.action_bar);
if (view != null && controlBarHeight != 0) {
view.setAlpha(translationY / (float) controlBarHeight);
}
}
mPagerIndicator.setTranslationY(translationY);
mPagerWindowOverlay.setTranslationY(translationY);

View File

@ -30,12 +30,8 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.RectF;
@ -61,6 +57,7 @@ import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.internal.widget.TintButton;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@ -197,7 +194,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
private TabPagerIndicator mPagerIndicator;
private View mUuckyFooter;
private View mProfileBannerContainer;
private Button mFollowButton;
private TintButton mFollowButton;
private ProgressBar mFollowProgress;
private View mPagesContent, mPagesErrorContainer;
private ImageView mPagesErrorIcon;
@ -389,13 +386,8 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
mFollowButton.setCompoundDrawablePadding(Math.round(mFollowButton.getTextSize() * 0.25f));
final ContentResolver resolver = getContentResolver();
final String where = Expression.equals(CachedUsers.USER_ID, user.id).getSQL();
final ContentValues cachedValues = ParcelableUser.makeCachedUserContentValues(user);
resolver.insert(CachedUsers.CONTENT_URI, cachedValues);
// I bet you don't want to see blocked user in your auto
// complete list.
if (!data.getData().isSourceBlockingTarget()) {
}
mFollowButton.setVisibility(View.VISIBLE);
} else {
mFollowButton.setText(null);
@ -1132,7 +1124,6 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
mErrorMessageView = (TextView) headerView.findViewById(R.id.error_message);
mProfileBannerView = (ProfileBannerImageView) view.findViewById(R.id.profile_banner);
mProfileBannerContainer = view.findViewById(R.id.profile_banner_container);
// mCardView = (CardView) headerView.findViewById(R.id.card);
mNameView = (TextView) headerView.findViewById(R.id.name);
mScreenNameView = (TextView) headerView.findViewById(R.id.screen_name);
mDescriptionView = (TextView) headerView.findViewById(R.id.description);
@ -1154,7 +1145,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
mProfileBannerSpace = headerView.findViewById(R.id.profile_banner_space);
mViewPager = (ViewPager) contentView.findViewById(R.id.view_pager);
mPagerIndicator = (TabPagerIndicator) contentView.findViewById(R.id.view_pager_tabs);
mFollowButton = (Button) headerView.findViewById(R.id.follow);
mFollowButton = (TintButton) headerView.findViewById(R.id.follow);
mFollowProgress = (ProgressBar) headerView.findViewById(R.id.follow_progress);
mUuckyFooter = headerView.findViewById(R.id.uucky_footer);
mPagesContent = view.findViewById(R.id.pages_content);
@ -1183,11 +1174,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
public void setListShown(boolean shown) {
final TintedStatusFrameLayout tintedStatus = mTintedStatusContent;
if (tintedStatus == null) return;
final FragmentActivity activity = getActivity();
final LinkHandlerActivity linkHandler = (LinkHandlerActivity) activity;
final boolean drawColor = !ThemeUtils.isDarkTheme(linkHandler.getCurrentThemeResourceId());
tintedStatus.setDrawShadow(shown);
tintedStatus.setDrawColor(drawColor);
}
private void getFriendship() {
@ -1253,10 +1240,8 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
final LinkHandlerActivity linkHandler = (LinkHandlerActivity) activity;
final ActionBar actionBar = linkHandler.getSupportActionBar();
if (actionBar == null) return;
final int themeResId = linkHandler.getCurrentThemeResourceId();
final Drawable shadow = ResourcesCompat.getDrawable(activity.getResources(), R.drawable.shadow_user_banner_action_bar, null);
final Drawable background = ThemeUtils.getActionBarBackground(activity, themeResId);
mActionBarBackground = new ActionBarDrawable(getResources(), shadow, background, ThemeUtils.isDarkTheme(themeResId));
mActionBarBackground = new ActionBarDrawable(getResources(), shadow);
mActionBarBackground.setAlpha(linkHandler.getCurrentThemeBackgroundAlpha());
mProfileBannerView.setAlpha(linkHandler.getCurrentThemeBackgroundAlpha() / 255f);
actionBar.setBackgroundDrawable(mActionBarBackground);
@ -1266,19 +1251,22 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
if (mActionBarBackground == null) {
setupBaseActionBar();
}
mActionBarBackground.setColor(color);
final FragmentActivity activity = getActivity();
mTintedStatusContent.setColor(color, ThemeUtils.getThemeAlpha(activity));
final IThemedActivity themed = (IThemedActivity) activity;
final int themeRes = themed.getCurrentThemeResourceId();
if (ThemeUtils.isDarkTheme(themeRes)) {
final int actionBarColor = getResources().getColor(R.color.background_color_action_bar_dark);
mTintedStatusContent.setColor(actionBarColor, themed.getCurrentThemeBackgroundAlpha());
mActionBarBackground.setColor(actionBarColor);
} else {
mTintedStatusContent.setColor(color, themed.getCurrentThemeBackgroundAlpha());
mActionBarBackground.setColor(color);
}
mDescriptionView.setLinkTextColor(color);
mProfileBannerView.setBackgroundColor(color);
mLocationView.setLinkTextColor(color);
mURLView.setLinkTextColor(color);
if (activity instanceof IThemedActivity) {
final int themeRes = ((IThemedActivity) activity).getCurrentThemeResourceId();
ViewAccessor.setBackground(mPagerIndicator, ThemeUtils.getActionBarStackedBackground(activity, themeRes, color, true));
} else {
mPagerIndicator.setBackgroundColor(color);
}
ViewAccessor.setBackground(mPagerIndicator, ThemeUtils.getActionBarStackedBackground(activity, themeRes, color, true));
final HeaderDrawerLayout drawer = mHeaderDrawerLayout;
if (drawer != null) {
@ -1445,22 +1433,17 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
private static class ActionBarDrawable extends LayerDrawable {
private final Drawable mShadowDrawable;
private final Drawable mBackgroundDrawable;
private final ColorDrawable mColorDrawable;
private final boolean mColorLineOnly;
private float mFactor;
private int mColor;
private int mAlpha;
private float mOutlineAlphaFactor;
public ActionBarDrawable(Resources resources, Drawable shadow, Drawable background,
boolean colorLineOnly) {
super(new Drawable[]{shadow, background, new ActionBarColorDrawable(true)});
public ActionBarDrawable(Resources resources, Drawable shadow) {
super(new Drawable[]{shadow, new ActionBarColorDrawable(true)});
mShadowDrawable = getDrawable(0);
mBackgroundDrawable = getDrawable(1);
mColorDrawable = (ColorDrawable) getDrawable(2);
mColorLineOnly = colorLineOnly;
mColorDrawable = (ColorDrawable) getDrawable(1);
setAlpha(0xFF);
setOutlineAlphaFactor(1);
}
@ -1472,12 +1455,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void getOutline(Outline outline) {
final boolean showColor = !mColorLineOnly && mColor != 0;
if (showColor) {
mColorDrawable.getOutline(outline);
} else {
mBackgroundDrawable.getOutline(outline);
}
mColorDrawable.getOutline(outline);
outline.setAlpha(mFactor * mOutlineAlphaFactor * 0.99f);
}
@ -1494,22 +1472,12 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
@Override
public int getIntrinsicWidth() {
final boolean showColor = !mColorLineOnly && mColor != 0;
if (showColor) {
return mColorDrawable.getIntrinsicWidth();
} else {
return mBackgroundDrawable.getIntrinsicWidth();
}
return mColorDrawable.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
final boolean showColor = !mColorLineOnly && mColor != 0;
if (showColor) {
return mColorDrawable.getIntrinsicHeight();
} else {
return mBackgroundDrawable.getIntrinsicHeight();
}
return mColorDrawable.getIntrinsicHeight();
}
public void setColor(int color) {
@ -1522,81 +1490,9 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
mFactor = f;
mShadowDrawable.setAlpha(Math.round(mAlpha * MathUtils.clamp(1 - f, 0, 1)));
final boolean hasColor = mColor != 0;
final boolean showBackground = mColorLineOnly || !hasColor;
final boolean showLine = mColorLineOnly && hasColor;
final boolean showColor = !mColorLineOnly && hasColor;
mBackgroundDrawable.setAlpha(showBackground ? Math.round(mAlpha * MathUtils.clamp(f, 0, 1)) : 0);
mColorDrawable.setAlpha(showColor ? Math.round(mAlpha * MathUtils.clamp(f, 0, 1)) : 0);
mColorDrawable.setAlpha(hasColor ? Math.round(mAlpha * MathUtils.clamp(f, 0, 1)) : 0);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static class LineBackgroundDrawable extends Drawable {
private final Rect mBounds;
private final Paint mPaint;
private final float mLineSize;
private int mAlpha;
private int mColor;
LineBackgroundDrawable(Resources resources, float lineSizeDp) {
mBounds = new Rect();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLineSize = resources.getDisplayMetrics().density * lineSizeDp;
setColor(Color.TRANSPARENT);
}
@Override
public void draw(Canvas canvas) {
canvas.drawRect(mBounds.left, mBounds.bottom - mLineSize, mBounds.right,
mBounds.bottom, mPaint);
}
public int getColor() {
return mColor;
}
public void setColor(int color) {
mColor = color;
updatePaint();
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mBounds.set(bounds);
}
private void updatePaint() {
mPaint.setColor(mColor);
mPaint.setAlpha(Color.alpha(mColor) * mAlpha / 0xFF);
invalidateSelf();
}
@Override
public int getAlpha() {
return mAlpha;
}
@Override
public void setAlpha(int alpha) {
mAlpha = alpha;
updatePaint();
}
@Override
public void setColorFilter(ColorFilter cf) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
}

View File

@ -21,6 +21,8 @@ package org.mariotaku.twidere.model;
import android.support.annotation.NonNull;
import org.mariotaku.twidere.util.TwidereArrayUtils;
/**
* Created by mariotaku on 15/3/25.
*/
@ -69,9 +71,25 @@ public class StringLongPair {
return key + ":" + value;
}
public static StringLongPair valueOf(String s) {
public static StringLongPair valueOf(String s) throws NumberFormatException {
if (s == null) return null;
final String[] segs = s.split(":");
if (segs.length != 2) throw new NumberFormatException();
return new StringLongPair(segs[0], Long.parseLong(segs[1]));
}
public static String toString(StringLongPair[] pairs) {
if (pairs==null)return null;
return TwidereArrayUtils.toString(pairs, ';', false);
}
public static StringLongPair[] valuesOf(String s) throws NumberFormatException {
if (s == null) return null;
final String[] segs = s.split(";");
final StringLongPair[] pairs = new StringLongPair[segs.length];
for (int i = 0, j = segs.length; i < j; i++) {
pairs[i] = valueOf(segs[i]);
}
return pairs;
}
}

View File

@ -50,6 +50,7 @@ import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.InboxStyle;
import android.support.v4.util.LongSparseArray;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.StyleSpan;
@ -83,6 +84,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Preferences;
import org.mariotaku.twidere.provider.TwidereDataStore.SearchHistory;
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts;
import org.mariotaku.twidere.receiver.NotificationReceiver;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ImagePreloader;
import org.mariotaku.twidere.util.MediaPreviewUtils;
@ -873,7 +875,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
final String filteredSelection = Utils.buildStatusFilterWhereClause(Statuses.TABLE_NAME,
selection, true).getSQL();
final String[] userProjection = {Statuses.USER_ID, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME};
final String[] statusProjection = new String[0];
final String[] statusProjection = {Statuses.STATUS_ID};
final Cursor statusCursor = mDatabaseWrapper.query(Statuses.TABLE_NAME, statusProjection,
filteredSelection, null, null, null, Statuses.SORT_ORDER_TIMESTAMP_DESC);
final Cursor userCursor = mDatabaseWrapper.query(Statuses.TABLE_NAME, userProjection,
@ -882,9 +884,11 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
final int usersCount = userCursor.getCount();
final int statusesCount = statusCursor.getCount();
if (statusesCount == 0 || usersCount == 0) return;
final int idxUserName = userCursor.getColumnIndex(Statuses.USER_NAME),
final int idxStatusId = statusCursor.getColumnIndex(Statuses.STATUS_ID),
idxUserName = userCursor.getColumnIndex(Statuses.USER_NAME),
idxUserScreenName = userCursor.getColumnIndex(Statuses.USER_NAME),
idxUserId = userCursor.getColumnIndex(Statuses.USER_NAME);
final long statusId = statusCursor.moveToFirst() ? statusCursor.getLong(idxStatusId) : -1;
final String notificationTitle = resources.getQuantityString(R.plurals.N_new_statuses,
statusesCount, statusesCount);
final String notificationContent;
@ -906,15 +910,6 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
notificationContent = resources.getString(R.string.from_name_and_N_others, othersName, usersCount - 1);
}
// Setup on click intent
final Intent homeIntent = new Intent(context, HomeActivity.class);
final Uri.Builder homeLinkBuilder = new Uri.Builder();
homeLinkBuilder.scheme(SCHEME_TWIDERE);
homeLinkBuilder.authority(AUTHORITY_HOME);
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
homeIntent.setData(homeLinkBuilder.build());
final PendingIntent clickIntent = PendingIntent.getActivity(context, 0, homeIntent, 0);
// Setup notification
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setAutoCancel(true);
@ -923,7 +918,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
builder.setContentTitle(notificationTitle);
builder.setContentText(notificationContent);
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
builder.setContentIntent(clickIntent);
builder.setContentIntent(getContentIntent(context, AUTHORITY_HOME, accountId));
builder.setDeleteIntent(getDeleteIntent(context, AUTHORITY_HOME, accountId, statusId));
builder.setNumber(statusesCount);
builder.setColor(pref.getNotificationLightColor());
setNotificationPreferences(builder, pref, pref.getHomeTimelineNotificationType());
@ -951,7 +947,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
final String filteredSelection = Utils.buildStatusFilterWhereClause(Mentions.TABLE_NAME,
selection, true).getSQL();
final String[] userProjection = {Statuses.USER_ID, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME};
final String[] statusProjection = {Statuses.USER_ID, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME,
final String[] statusProjection = {Statuses.STATUS_ID, Statuses.USER_ID, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME,
Statuses.TEXT_UNESCAPED, Statuses.STATUS_TIMESTAMP};
final Cursor statusCursor = mDatabaseWrapper.query(Mentions.TABLE_NAME, statusProjection,
filteredSelection, null, null, null, Statuses.SORT_ORDER_TIMESTAMP_DESC);
@ -964,6 +960,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
final String accountName = Utils.getAccountName(context, accountId);
final String accountScreenName = Utils.getAccountScreenName(context, accountId);
final int idxStatusText = statusCursor.getColumnIndex(Statuses.TEXT_UNESCAPED),
idxStatusId = statusCursor.getColumnIndex(Statuses.STATUS_ID),
idxStatusTimestamp = statusCursor.getColumnIndex(Statuses.STATUS_TIMESTAMP),
idxStatusUserName = statusCursor.getColumnIndex(Statuses.USER_NAME),
idxStatusUserScreenName = statusCursor.getColumnIndex(Statuses.USER_SCREEN_NAME),
@ -985,12 +982,15 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
}
// Add rich notification and get latest tweet timestamp
long when = -1;
long when = -1, statusId = -1;
final InboxStyle style = new InboxStyle();
for (int i = 0, j = Math.min(statusesCount, 5); statusCursor.moveToPosition(i) && i < j; i++) {
if (when < 0) {
if (when == -1) {
when = statusCursor.getLong(idxStatusTimestamp);
}
if (statusId == -1) {
statusId = statusCursor.getLong(idxStatusId);
}
final SpannableStringBuilder sb = new SpannableStringBuilder();
sb.append(UserColorNameUtils.getUserNickname(context, statusCursor.getLong(idxUserId),
mNameFirst ? statusCursor.getString(idxStatusUserName) : statusCursor.getString(idxStatusUserScreenName)));
@ -1005,15 +1005,6 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
style.setSummaryText("@" + accountScreenName);
}
// Setup on click intent
final Intent homeIntent = new Intent(context, HomeActivity.class);
final Uri.Builder homeLinkBuilder = new Uri.Builder();
homeLinkBuilder.scheme(SCHEME_TWIDERE);
homeLinkBuilder.authority(AUTHORITY_MENTIONS);
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
homeIntent.setData(homeLinkBuilder.build());
final PendingIntent clickIntent = PendingIntent.getActivity(context, 0, homeIntent, 0);
// Setup notification
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setAutoCancel(true);
@ -1022,7 +1013,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
builder.setContentTitle(notificationTitle);
builder.setContentText(notificationContent);
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
builder.setContentIntent(clickIntent);
builder.setContentIntent(getContentIntent(context, AUTHORITY_MENTIONS, accountId));
builder.setDeleteIntent(getDeleteIntent(context, AUTHORITY_MENTIONS, accountId, statusId));
builder.setNumber(statusesCount);
builder.setWhen(when);
builder.setStyle(style);
@ -1036,6 +1028,43 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
}
}
private PendingIntent getContentIntent(Context context, String type, long accountId) {
// Setup click intent
final Intent homeIntent = new Intent(context, HomeActivity.class);
final Uri.Builder homeLinkBuilder = new Uri.Builder();
homeLinkBuilder.scheme(SCHEME_TWIDERE);
homeLinkBuilder.authority(type);
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
homeIntent.setData(homeLinkBuilder.build());
return PendingIntent.getActivity(context, 0, homeIntent, 0);
}
private static PendingIntent getDeleteIntent(Context context, String type, long accountId, long position) {
// Setup delete intent
final Intent recvIntent = new Intent(context, NotificationReceiver.class);
final Uri.Builder recvLinkBuilder = new Uri.Builder();
recvLinkBuilder.scheme(SCHEME_TWIDERE);
recvLinkBuilder.authority(AUTHORITY_NOTIFICATIONS);
recvLinkBuilder.appendPath(type);
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_READ_POSITION, String.valueOf(position));
recvIntent.setData(recvLinkBuilder.build());
return PendingIntent.getBroadcast(context, 0, recvIntent, 0);
}
private static PendingIntent getDeleteIntent(Context context, String type, long accountId, StringLongPair[] positions) {
// Setup delete intent
final Intent recvIntent = new Intent(context, NotificationReceiver.class);
final Uri.Builder recvLinkBuilder = new Uri.Builder();
recvLinkBuilder.scheme(SCHEME_TWIDERE);
recvLinkBuilder.authority(AUTHORITY_NOTIFICATIONS);
recvLinkBuilder.appendPath(type);
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_READ_POSITIONS, StringLongPair.toString(positions));
recvIntent.setData(recvLinkBuilder.build());
return PendingIntent.getBroadcast(context, 0, recvIntent, 0);
}
private void setNotificationPreferences(NotificationCompat.Builder builder, AccountPreferences pref, int defaultFlags) {
int notificationDefaults = 0;
if (AccountPreferences.isNotificationHasLight(defaultFlags)) {
@ -1089,8 +1118,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
final String filteredSelection = selection.getSQL();
final String[] userProjection = {DirectMessages.SENDER_ID, DirectMessages.SENDER_NAME,
DirectMessages.SENDER_SCREEN_NAME};
final String[] messageProjection = {DirectMessages.SENDER_ID, DirectMessages.SENDER_NAME,
DirectMessages.SENDER_SCREEN_NAME, DirectMessages.TEXT_UNESCAPED,
final String[] messageProjection = {DirectMessages.MESSAGE_ID, DirectMessages.SENDER_ID,
DirectMessages.SENDER_NAME, DirectMessages.SENDER_SCREEN_NAME, DirectMessages.TEXT_UNESCAPED,
DirectMessages.MESSAGE_TIMESTAMP};
final Cursor messageCursor = mDatabaseWrapper.query(DirectMessages.Inbox.TABLE_NAME, messageProjection,
filteredSelection, null, null, null, DirectMessages.DEFAULT_SORT_ORDER);
@ -1104,6 +1133,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
final String accountScreenName = Utils.getAccountScreenName(context, accountId);
final int idxMessageText = messageCursor.getColumnIndex(DirectMessages.TEXT_UNESCAPED),
idxMessageTimestamp = messageCursor.getColumnIndex(DirectMessages.MESSAGE_TIMESTAMP),
idxMessageId = messageCursor.getColumnIndex(DirectMessages.MESSAGE_ID),
idxMessageUserId = messageCursor.getColumnIndex(DirectMessages.SENDER_ID),
idxMessageUserName = messageCursor.getColumnIndex(DirectMessages.SENDER_NAME),
idxMessageUserScreenName = messageCursor.getColumnIndex(DirectMessages.SENDER_SCREEN_NAME),
idxUserName = userCursor.getColumnIndex(DirectMessages.SENDER_NAME),
@ -1128,35 +1159,36 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
displayName, usersCount - 1, messagesCount);
}
final LongSparseArray<Long> idsMap = new LongSparseArray<>();
// Add rich notification and get latest tweet timestamp
long when = -1;
final InboxStyle style = new InboxStyle();
for (int i = 0, j = Math.min(messagesCount, 5); messageCursor.moveToPosition(i) && i < j; i++) {
for (int i = 0; messageCursor.moveToPosition(i) && i < messagesCount; i++) {
if (when < 0) {
when = messageCursor.getLong(idxMessageTimestamp);
}
final SpannableStringBuilder sb = new SpannableStringBuilder();
sb.append(UserColorNameUtils.getUserNickname(context, messageCursor.getLong(idxUserId),
mNameFirst ? messageCursor.getString(idxMessageUserName) : messageCursor.getString(idxMessageUserScreenName)));
sb.setSpan(new StyleSpan(Typeface.BOLD), 0, sb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.append(' ');
sb.append(messageCursor.getString(idxMessageText));
style.addLine(sb);
if (i < 5) {
final SpannableStringBuilder sb = new SpannableStringBuilder();
sb.append(UserColorNameUtils.getUserNickname(context, messageCursor.getLong(idxUserId),
mNameFirst ? messageCursor.getString(idxMessageUserName) : messageCursor.getString(idxMessageUserScreenName)));
sb.setSpan(new StyleSpan(Typeface.BOLD), 0, sb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.append(' ');
sb.append(messageCursor.getString(idxMessageText));
style.addLine(sb);
}
final long userId = messageCursor.getLong(idxMessageUserId);
final long messageId = messageCursor.getLong(idxMessageId);
idsMap.put(userId, Math.max(idsMap.get(userId, -1L), messageId));
}
if (mNameFirst) {
style.setSummaryText(accountName);
} else {
style.setSummaryText("@" + accountScreenName);
}
// Setup on click intent
final Intent homeIntent = new Intent(context, HomeActivity.class);
final Uri.Builder homeLinkBuilder = new Uri.Builder();
homeLinkBuilder.scheme(SCHEME_TWIDERE);
homeLinkBuilder.authority(AUTHORITY_DIRECT_MESSAGES);
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
homeIntent.setData(homeLinkBuilder.build());
final PendingIntent clickIntent = PendingIntent.getActivity(context, 0, homeIntent, 0);
final StringLongPair[] positions = new StringLongPair[idsMap.size()];
for (int i = 0, j = idsMap.size(); i < j; i++) {
positions[i] = new StringLongPair(String.valueOf(idsMap.keyAt(i)), idsMap.valueAt(i));
}
// Setup notification
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
@ -1166,7 +1198,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
builder.setContentTitle(notificationTitle);
builder.setContentText(notificationContent);
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
builder.setContentIntent(clickIntent);
builder.setContentIntent(getContentIntent(context, AUTHORITY_DIRECT_MESSAGES, accountId));
builder.setContentIntent(getDeleteIntent(context, AUTHORITY_DIRECT_MESSAGES, accountId, positions));
builder.setNumber(messagesCount);
builder.setWhen(when);
builder.setStyle(style);

View File

@ -0,0 +1,85 @@
/*
* 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.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.fragment.support.DirectMessagesFragment;
import org.mariotaku.twidere.fragment.support.HomeTimelineFragment;
import org.mariotaku.twidere.fragment.support.MentionsTimelineFragment;
import org.mariotaku.twidere.model.StringLongPair;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.ReadStateManager;
import org.mariotaku.twidere.util.Utils;
/**
* Created by mariotaku on 15/4/4.
*/
public class NotificationReceiver extends BroadcastReceiver implements Constants {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case BROADCAST_NOTIFICATION_DELETED: {
final Uri uri = intent.getData();
final String tag = getPositionTag(uri.getLastPathSegment());
if (tag == null) return;
final long accountId = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_ACCOUNT_ID), -1);
final TwidereApplication app = TwidereApplication.getInstance(context);
final ReadStateManager manager = app.getReadStateManager();
final String paramReadPosition, paramReadPositions;
if (!TextUtils.isEmpty(paramReadPosition = uri.getQueryParameter(QUERY_PARAM_READ_POSITION))) {
manager.setPosition(Utils.getReadPositionTagWithAccounts(tag, accountId),
ParseUtils.parseLong(paramReadPosition, -1));
} else if (!TextUtils.isEmpty(paramReadPositions = uri.getQueryParameter(QUERY_PARAM_READ_POSITIONS))) {
try {
final StringLongPair[] pairs = StringLongPair.valuesOf(paramReadPositions);
for (StringLongPair pair : pairs) {
manager.setPosition(tag, pair.getKey(), pair.getValue());
}
} catch (NumberFormatException ignore) {
}
}
break;
}
}
}
private String getPositionTag(String type) {
switch (type) {
case AUTHORITY_HOME: {
return HomeTimelineFragment.KEY_READ_POSITION_TAG;
}
case AUTHORITY_MENTIONS: {
return MentionsTimelineFragment.KEY_READ_POSITION_TAG;
}
case AUTHORITY_DIRECT_MESSAGES: {
return DirectMessagesFragment.KEY_READ_POSITION_TAG;
}
}
return null;
}
}

View File

@ -1,56 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 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.service;
import android.content.Intent;
import android.content.res.Resources;
import com.google.android.apps.dashclock.api.DashClockExtension;
import com.google.android.apps.dashclock.api.ExtensionData;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.activity.support.HomeActivity;
import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts;
import org.mariotaku.twidere.util.UnreadCountUtils;
public class DashClockHomeUnreadCountService extends DashClockExtension implements TwidereConstants {
private static final String[] URIS = { UnreadCounts.CONTENT_URI.toString() };
@Override
protected void onInitialize(final boolean isReconnect) {
super.onInitialize(isReconnect);
addWatchContentUris(URIS);
}
@Override
protected void onUpdateData(final int reason) {
final ExtensionData data = new ExtensionData();
final int count = UnreadCountUtils.getUnreadCount(this, TAB_TYPE_HOME_TIMELINE);
final Resources res = getResources();
data.visible(count > 0);
data.icon(R.drawable.ic_extension_twidere);
data.status(Integer.toString(count));
data.expandedTitle(res.getQuantityString(R.plurals.N_new_statuses, count, count));
data.clickIntent(new Intent(this, HomeActivity.class));
publishUpdate(data);
}
}

View File

@ -1,56 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 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.service;
import android.content.Intent;
import android.content.res.Resources;
import com.google.android.apps.dashclock.api.DashClockExtension;
import com.google.android.apps.dashclock.api.ExtensionData;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.activity.support.HomeActivity;
import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts;
import org.mariotaku.twidere.util.UnreadCountUtils;
public class DashClockMentionsUnreadCountService extends DashClockExtension implements TwidereConstants {
private static final String[] URIS = { UnreadCounts.CONTENT_URI.toString() };
@Override
protected void onInitialize(final boolean isReconnect) {
super.onInitialize(isReconnect);
addWatchContentUris(URIS);
}
@Override
protected void onUpdateData(final int reason) {
final ExtensionData data = new ExtensionData();
final int count = UnreadCountUtils.getUnreadCount(this, TAB_TYPE_MENTIONS_TIMELINE);
final Resources res = getResources();
data.visible(count > 0);
data.icon(R.drawable.ic_extension_mentions);
data.status(Integer.toString(count));
data.expandedTitle(res.getQuantityString(R.plurals.N_new_mentions, count, count));
data.clickIntent(new Intent(this, HomeActivity.class));
publishUpdate(data);
}
}

View File

@ -1,56 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 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.service;
import android.content.Intent;
import android.content.res.Resources;
import com.google.android.apps.dashclock.api.DashClockExtension;
import com.google.android.apps.dashclock.api.ExtensionData;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.activity.support.HomeActivity;
import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts;
import org.mariotaku.twidere.util.UnreadCountUtils;
public class DashClockMessagesUnreadCountService extends DashClockExtension implements TwidereConstants {
private static final String[] URIS = { UnreadCounts.CONTENT_URI.toString() };
@Override
protected void onInitialize(final boolean isReconnect) {
super.onInitialize(isReconnect);
addWatchContentUris(URIS);
}
@Override
protected void onUpdateData(final int reason) {
final ExtensionData data = new ExtensionData();
final int count = UnreadCountUtils.getUnreadCount(this, TAB_TYPE_DIRECT_MESSAGES);
final Resources res = getResources();
data.visible(count > 0);
data.icon(R.drawable.ic_extension_messages);
data.status(Integer.toString(count));
data.expandedTitle(res.getQuantityString(R.plurals.N_new_messages, count, count));
data.clickIntent(new Intent(this, HomeActivity.class));
publishUpdate(data);
}
}

View File

@ -53,265 +53,257 @@ import static org.mariotaku.twidere.util.Utils.shouldStopAutoRefreshOnBatteryLow
public class RefreshService extends Service implements Constants {
private SharedPreferencesWrapper mPreferences;
private SharedPreferencesWrapper mPreferences;
private AlarmManager mAlarmManager;
private AsyncTwitterWrapper mTwitterWrapper;
private PendingIntent mPendingRefreshHomeTimelineIntent, mPendingRefreshMentionsIntent,
mPendingRefreshDirectMessagesIntent, mPendingRefreshTrendsIntent;
private AlarmManager mAlarmManager;
private AsyncTwitterWrapper mTwitterWrapper;
private PendingIntent mPendingRefreshHomeTimelineIntent, mPendingRefreshMentionsIntent,
mPendingRefreshDirectMessagesIntent, mPendingRefreshTrendsIntent;
private final BroadcastReceiver mStateReceiver = new BroadcastReceiver() {
private final BroadcastReceiver mStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
if (isDebugBuild()) {
Log.d(LOGTAG, String.format("Refresh service received action %s", action));
}
if (BROADCAST_NOTIFICATION_DELETED.equals(action)) {
final int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
final long accountId = intent.getLongExtra(EXTRA_NOTIFICATION_ACCOUNT, -1);
clearNotification(notificationId, accountId);
} else if (BROADCAST_RESCHEDULE_HOME_TIMELINE_REFRESHING.equals(action)) {
rescheduleHomeTimelineRefreshing();
} else if (BROADCAST_RESCHEDULE_MENTIONS_REFRESHING.equals(action)) {
rescheduleMentionsRefreshing();
} else if (BROADCAST_RESCHEDULE_DIRECT_MESSAGES_REFRESHING.equals(action)) {
rescheduleDirectMessagesRefreshing();
} else if (BROADCAST_RESCHEDULE_TRENDS_REFRESHING.equals(action)) {
rescheduleTrendsRefreshing();
} else if (isAutoRefreshAllowed()) {
final long[] accountIds = getAccountIds(context);
final AccountPreferences[] accountPrefs = AccountPreferences.getAccountPreferences(context, accountIds);
if (BROADCAST_REFRESH_HOME_TIMELINE.equals(action)) {
final long[] refreshIds = getRefreshableIds(accountPrefs, new HomeRefreshableFilter());
final long[] sinceIds = getNewestStatusIdsFromDatabase(context, Statuses.CONTENT_URI, refreshIds);
if (isDebugBuild()) {
Log.d(LOGTAG, String.format("Auto refreshing home for %s", Arrays.toString(refreshIds)));
}
if (!isHomeTimelineRefreshing()) {
getHomeTimeline(refreshIds, null, sinceIds);
}
} else if (BROADCAST_REFRESH_MENTIONS.equals(action)) {
final long[] refreshIds = getRefreshableIds(accountPrefs, new MentionsRefreshableFilter());
final long[] sinceIds = getNewestStatusIdsFromDatabase(context, Mentions.CONTENT_URI, refreshIds);
if (isDebugBuild()) {
Log.d(LOGTAG, String.format("Auto refreshing mentions for %s", Arrays.toString(refreshIds)));
}
if (!isMentionsRefreshing()) {
getMentions(refreshIds, null, sinceIds);
}
} else if (BROADCAST_REFRESH_DIRECT_MESSAGES.equals(action)) {
final long[] refreshIds = getRefreshableIds(accountPrefs, new MessagesRefreshableFilter());
final long[] sinceIds = getNewestMessageIdsFromDatabase(context, DirectMessages.Inbox.CONTENT_URI,
refreshIds);
if (isDebugBuild()) {
Log.d(LOGTAG, String.format("Auto refreshing messages for %s", Arrays.toString(refreshIds)));
}
if (!isReceivedDirectMessagesRefreshing()) {
getReceivedDirectMessages(refreshIds, null, sinceIds);
}
} else if (BROADCAST_REFRESH_TRENDS.equals(action)) {
final long[] refreshIds = getRefreshableIds(accountPrefs, new TrendsRefreshableFilter());
if (isDebugBuild()) {
Log.d(LOGTAG, String.format("Auto refreshing trends for %s", Arrays.toString(refreshIds)));
}
if (!isLocalTrendsRefreshing()) {
getLocalTrends(refreshIds);
}
}
}
}
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
if (isDebugBuild()) {
Log.d(LOGTAG, String.format("Refresh service received action %s", action));
}
if (BROADCAST_RESCHEDULE_HOME_TIMELINE_REFRESHING.equals(action)) {
rescheduleHomeTimelineRefreshing();
} else if (BROADCAST_RESCHEDULE_MENTIONS_REFRESHING.equals(action)) {
rescheduleMentionsRefreshing();
} else if (BROADCAST_RESCHEDULE_DIRECT_MESSAGES_REFRESHING.equals(action)) {
rescheduleDirectMessagesRefreshing();
} else if (BROADCAST_RESCHEDULE_TRENDS_REFRESHING.equals(action)) {
rescheduleTrendsRefreshing();
} else if (isAutoRefreshAllowed()) {
final long[] accountIds = getAccountIds(context);
final AccountPreferences[] accountPrefs = AccountPreferences.getAccountPreferences(context, accountIds);
if (BROADCAST_REFRESH_HOME_TIMELINE.equals(action)) {
final long[] refreshIds = getRefreshableIds(accountPrefs, new HomeRefreshableFilter());
final long[] sinceIds = getNewestStatusIdsFromDatabase(context, Statuses.CONTENT_URI, refreshIds);
if (isDebugBuild()) {
Log.d(LOGTAG, String.format("Auto refreshing home for %s", Arrays.toString(refreshIds)));
}
if (!isHomeTimelineRefreshing()) {
getHomeTimeline(refreshIds, null, sinceIds);
}
} else if (BROADCAST_REFRESH_MENTIONS.equals(action)) {
final long[] refreshIds = getRefreshableIds(accountPrefs, new MentionsRefreshableFilter());
final long[] sinceIds = getNewestStatusIdsFromDatabase(context, Mentions.CONTENT_URI, refreshIds);
if (isDebugBuild()) {
Log.d(LOGTAG, String.format("Auto refreshing mentions for %s", Arrays.toString(refreshIds)));
}
if (!isMentionsRefreshing()) {
getMentions(refreshIds, null, sinceIds);
}
} else if (BROADCAST_REFRESH_DIRECT_MESSAGES.equals(action)) {
final long[] refreshIds = getRefreshableIds(accountPrefs, new MessagesRefreshableFilter());
final long[] sinceIds = getNewestMessageIdsFromDatabase(context, DirectMessages.Inbox.CONTENT_URI,
refreshIds);
if (isDebugBuild()) {
Log.d(LOGTAG, String.format("Auto refreshing messages for %s", Arrays.toString(refreshIds)));
}
if (!isReceivedDirectMessagesRefreshing()) {
getReceivedDirectMessages(refreshIds, null, sinceIds);
}
} else if (BROADCAST_REFRESH_TRENDS.equals(action)) {
final long[] refreshIds = getRefreshableIds(accountPrefs, new TrendsRefreshableFilter());
if (isDebugBuild()) {
Log.d(LOGTAG, String.format("Auto refreshing trends for %s", Arrays.toString(refreshIds)));
}
if (!isLocalTrendsRefreshing()) {
getLocalTrends(refreshIds);
}
}
}
}
};
};
@Override
public IBinder onBind(final Intent intent) {
return null;
}
@Override
public IBinder onBind(final Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
final TwidereApplication app = TwidereApplication.getInstance(this);
mTwitterWrapper = app.getTwitterWrapper();
mPreferences = SharedPreferencesWrapper.getInstance(app, SHARED_PREFERENCES_NAME, MODE_PRIVATE);
mPendingRefreshHomeTimelineIntent = PendingIntent.getBroadcast(this, 0, new Intent(
BROADCAST_REFRESH_HOME_TIMELINE), 0);
mPendingRefreshMentionsIntent = PendingIntent.getBroadcast(this, 0, new Intent(BROADCAST_REFRESH_MENTIONS), 0);
mPendingRefreshDirectMessagesIntent = PendingIntent.getBroadcast(this, 0, new Intent(
BROADCAST_REFRESH_DIRECT_MESSAGES), 0);
mPendingRefreshTrendsIntent = PendingIntent.getBroadcast(this, 0, new Intent(BROADCAST_REFRESH_TRENDS), 0);
final IntentFilter filter = new IntentFilter(BROADCAST_NOTIFICATION_DELETED);
filter.addAction(BROADCAST_REFRESH_HOME_TIMELINE);
filter.addAction(BROADCAST_REFRESH_MENTIONS);
filter.addAction(BROADCAST_REFRESH_DIRECT_MESSAGES);
filter.addAction(BROADCAST_RESCHEDULE_HOME_TIMELINE_REFRESHING);
filter.addAction(BROADCAST_RESCHEDULE_MENTIONS_REFRESHING);
filter.addAction(BROADCAST_RESCHEDULE_DIRECT_MESSAGES_REFRESHING);
registerReceiver(mStateReceiver, filter);
startAutoRefresh();
}
@Override
public void onCreate() {
super.onCreate();
mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
final TwidereApplication app = TwidereApplication.getInstance(this);
mTwitterWrapper = app.getTwitterWrapper();
mPreferences = SharedPreferencesWrapper.getInstance(app, SHARED_PREFERENCES_NAME, MODE_PRIVATE);
mPendingRefreshHomeTimelineIntent = PendingIntent.getBroadcast(this, 0, new Intent(
BROADCAST_REFRESH_HOME_TIMELINE), 0);
mPendingRefreshMentionsIntent = PendingIntent.getBroadcast(this, 0, new Intent(BROADCAST_REFRESH_MENTIONS), 0);
mPendingRefreshDirectMessagesIntent = PendingIntent.getBroadcast(this, 0, new Intent(
BROADCAST_REFRESH_DIRECT_MESSAGES), 0);
mPendingRefreshTrendsIntent = PendingIntent.getBroadcast(this, 0, new Intent(BROADCAST_REFRESH_TRENDS), 0);
final IntentFilter filter = new IntentFilter(BROADCAST_NOTIFICATION_DELETED);
filter.addAction(BROADCAST_REFRESH_HOME_TIMELINE);
filter.addAction(BROADCAST_REFRESH_MENTIONS);
filter.addAction(BROADCAST_REFRESH_DIRECT_MESSAGES);
filter.addAction(BROADCAST_RESCHEDULE_HOME_TIMELINE_REFRESHING);
filter.addAction(BROADCAST_RESCHEDULE_MENTIONS_REFRESHING);
filter.addAction(BROADCAST_RESCHEDULE_DIRECT_MESSAGES_REFRESHING);
registerReceiver(mStateReceiver, filter);
startAutoRefresh();
}
@Override
public void onDestroy() {
unregisterReceiver(mStateReceiver);
if (hasAutoRefreshAccounts(this)) {
// Auto refresh enabled, so I will try to start service after it was
// stopped.
startService(new Intent(this, getClass()));
}
super.onDestroy();
}
@Override
public void onDestroy() {
unregisterReceiver(mStateReceiver);
if (hasAutoRefreshAccounts(this)) {
// Auto refresh enabled, so I will try to start service after it was
// stopped.
startService(new Intent(this, getClass()));
}
super.onDestroy();
}
protected boolean isAutoRefreshAllowed() {
return isNetworkAvailable(this) && (isBatteryOkay(this) || !shouldStopAutoRefreshOnBatteryLow(this));
}
protected boolean isAutoRefreshAllowed() {
return isNetworkAvailable(this) && (isBatteryOkay(this) || !shouldStopAutoRefreshOnBatteryLow(this));
}
private void clearNotification(final int notificationId, final long notificationAccount) {
mTwitterWrapper.clearNotificationAsync(notificationId, notificationAccount);
}
private int getHomeTimeline(final long[] accountIds, final long[] maxIds, final long[] sinceIds) {
return mTwitterWrapper.getHomeTimelineAsync(accountIds, maxIds, sinceIds);
}
private int getHomeTimeline(final long[] accountIds, final long[] maxIds, final long[] sinceIds) {
return mTwitterWrapper.getHomeTimelineAsync(accountIds, maxIds, sinceIds);
}
private int getLocalTrends(final long[] accountIds) {
final long account_id = getDefaultAccountId(this);
final int woeid = mPreferences.getInt(KEY_LOCAL_TRENDS_WOEID, 1);
return mTwitterWrapper.getLocalTrendsAsync(account_id, woeid);
}
private int getLocalTrends(final long[] accountIds) {
final long account_id = getDefaultAccountId(this);
final int woeid = mPreferences.getInt(KEY_LOCAL_TRENDS_WOEID, 1);
return mTwitterWrapper.getLocalTrendsAsync(account_id, woeid);
}
private int getMentions(final long[] accountIds, final long[] maxIds, final long[] sinceIds) {
return mTwitterWrapper.getMentionsTimelineAsync(accountIds, maxIds, sinceIds);
}
private int getMentions(final long[] accountIds, final long[] maxIds, final long[] sinceIds) {
return mTwitterWrapper.getMentionsTimelineAsync(accountIds, maxIds, sinceIds);
}
private int getReceivedDirectMessages(final long[] accountIds, final long[] maxIds, final long[] sinceIds) {
return mTwitterWrapper.getReceivedDirectMessagesAsync(accountIds, maxIds, sinceIds);
}
private int getReceivedDirectMessages(final long[] accountIds, final long[] maxIds, final long[] sinceIds) {
return mTwitterWrapper.getReceivedDirectMessagesAsync(accountIds, maxIds, sinceIds);
}
private long[] getRefreshableIds(final AccountPreferences[] prefs, final RefreshableAccountFilter filter) {
if (prefs == null) return null;
final long[] temp = new long[prefs.length];
int i = 0;
for (final AccountPreferences pref : prefs) {
if (pref.isAutoRefreshEnabled() && filter.isRefreshable(pref)) {
temp[i++] = pref.getAccountId();
}
}
final long[] result = new long[i];
System.arraycopy(temp, 0, result, 0, i);
return result;
}
private long[] getRefreshableIds(final AccountPreferences[] prefs, final RefreshableAccountFilter filter) {
if (prefs == null) return null;
final long[] temp = new long[prefs.length];
int i = 0;
for (final AccountPreferences pref : prefs) {
if (pref.isAutoRefreshEnabled() && filter.isRefreshable(pref)) {
temp[i++] = pref.getAccountId();
}
}
final long[] result = new long[i];
System.arraycopy(temp, 0, result, 0, i);
return result;
}
private long getRefreshInterval() {
if (mPreferences == null) return 0;
final int prefValue = parseInt(mPreferences.getString(KEY_REFRESH_INTERVAL, DEFAULT_REFRESH_INTERVAL));
return Math.max(prefValue, 3) * 60 * 1000;
}
private long getRefreshInterval() {
if (mPreferences == null) return 0;
final int prefValue = parseInt(mPreferences.getString(KEY_REFRESH_INTERVAL, DEFAULT_REFRESH_INTERVAL));
return Math.max(prefValue, 3) * 60 * 1000;
}
private boolean isHomeTimelineRefreshing() {
return mTwitterWrapper.isHomeTimelineRefreshing();
}
private boolean isHomeTimelineRefreshing() {
return mTwitterWrapper.isHomeTimelineRefreshing();
}
private boolean isLocalTrendsRefreshing() {
return mTwitterWrapper.isLocalTrendsRefreshing();
}
private boolean isLocalTrendsRefreshing() {
return mTwitterWrapper.isLocalTrendsRefreshing();
}
private boolean isMentionsRefreshing() {
return mTwitterWrapper.isMentionsTimelineRefreshing();
}
private boolean isMentionsRefreshing() {
return mTwitterWrapper.isMentionsTimelineRefreshing();
}
private boolean isReceivedDirectMessagesRefreshing() {
return mTwitterWrapper.isReceivedDirectMessagesRefreshing();
}
private boolean isReceivedDirectMessagesRefreshing() {
return mTwitterWrapper.isReceivedDirectMessagesRefreshing();
}
private void rescheduleDirectMessagesRefreshing() {
mAlarmManager.cancel(mPendingRefreshDirectMessagesIntent);
final long refreshInterval = getRefreshInterval();
if (refreshInterval > 0) {
mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
refreshInterval, mPendingRefreshDirectMessagesIntent);
}
}
private void rescheduleDirectMessagesRefreshing() {
mAlarmManager.cancel(mPendingRefreshDirectMessagesIntent);
final long refreshInterval = getRefreshInterval();
if (refreshInterval > 0) {
mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
refreshInterval, mPendingRefreshDirectMessagesIntent);
}
}
private void rescheduleHomeTimelineRefreshing() {
mAlarmManager.cancel(mPendingRefreshHomeTimelineIntent);
final long refreshInterval = getRefreshInterval();
if (refreshInterval > 0) {
mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
refreshInterval, mPendingRefreshHomeTimelineIntent);
}
}
private void rescheduleHomeTimelineRefreshing() {
mAlarmManager.cancel(mPendingRefreshHomeTimelineIntent);
final long refreshInterval = getRefreshInterval();
if (refreshInterval > 0) {
mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
refreshInterval, mPendingRefreshHomeTimelineIntent);
}
}
private void rescheduleMentionsRefreshing() {
mAlarmManager.cancel(mPendingRefreshMentionsIntent);
final long refreshInterval = getRefreshInterval();
if (refreshInterval > 0) {
mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
refreshInterval, mPendingRefreshMentionsIntent);
}
}
private void rescheduleMentionsRefreshing() {
mAlarmManager.cancel(mPendingRefreshMentionsIntent);
final long refreshInterval = getRefreshInterval();
if (refreshInterval > 0) {
mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
refreshInterval, mPendingRefreshMentionsIntent);
}
}
private void rescheduleTrendsRefreshing() {
mAlarmManager.cancel(mPendingRefreshTrendsIntent);
final long refreshInterval = getRefreshInterval();
if (refreshInterval > 0) {
mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
refreshInterval, mPendingRefreshTrendsIntent);
}
}
private void rescheduleTrendsRefreshing() {
mAlarmManager.cancel(mPendingRefreshTrendsIntent);
final long refreshInterval = getRefreshInterval();
if (refreshInterval > 0) {
mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
refreshInterval, mPendingRefreshTrendsIntent);
}
}
private boolean startAutoRefresh() {
stopAutoRefresh();
final long refreshInterval = getRefreshInterval();
if (refreshInterval <= 0) return false;
rescheduleHomeTimelineRefreshing();
rescheduleMentionsRefreshing();
rescheduleDirectMessagesRefreshing();
rescheduleTrendsRefreshing();
return true;
}
private boolean startAutoRefresh() {
stopAutoRefresh();
final long refreshInterval = getRefreshInterval();
if (refreshInterval <= 0) return false;
rescheduleHomeTimelineRefreshing();
rescheduleMentionsRefreshing();
rescheduleDirectMessagesRefreshing();
rescheduleTrendsRefreshing();
return true;
}
private void stopAutoRefresh() {
mAlarmManager.cancel(mPendingRefreshHomeTimelineIntent);
mAlarmManager.cancel(mPendingRefreshMentionsIntent);
mAlarmManager.cancel(mPendingRefreshDirectMessagesIntent);
mAlarmManager.cancel(mPendingRefreshTrendsIntent);
}
private void stopAutoRefresh() {
mAlarmManager.cancel(mPendingRefreshHomeTimelineIntent);
mAlarmManager.cancel(mPendingRefreshMentionsIntent);
mAlarmManager.cancel(mPendingRefreshDirectMessagesIntent);
mAlarmManager.cancel(mPendingRefreshTrendsIntent);
}
private static class HomeRefreshableFilter implements RefreshableAccountFilter {
@Override
public boolean isRefreshable(final AccountPreferences pref) {
return pref.isAutoRefreshHomeTimelineEnabled();
}
}
private static class HomeRefreshableFilter implements RefreshableAccountFilter {
@Override
public boolean isRefreshable(final AccountPreferences pref) {
return pref.isAutoRefreshHomeTimelineEnabled();
}
}
private static class MentionsRefreshableFilter implements RefreshableAccountFilter {
private static class MentionsRefreshableFilter implements RefreshableAccountFilter {
@Override
public boolean isRefreshable(final AccountPreferences pref) {
return pref.isAutoRefreshMentionsEnabled();
}
@Override
public boolean isRefreshable(final AccountPreferences pref) {
return pref.isAutoRefreshMentionsEnabled();
}
}
}
private static class MessagesRefreshableFilter implements RefreshableAccountFilter {
@Override
public boolean isRefreshable(final AccountPreferences pref) {
return pref.isAutoRefreshDirectMessagesEnabled();
}
}
private static class MessagesRefreshableFilter implements RefreshableAccountFilter {
@Override
public boolean isRefreshable(final AccountPreferences pref) {
return pref.isAutoRefreshDirectMessagesEnabled();
}
}
private static interface RefreshableAccountFilter {
boolean isRefreshable(AccountPreferences pref);
}
private static interface RefreshableAccountFilter {
boolean isRefreshable(AccountPreferences pref);
}
private static class TrendsRefreshableFilter implements RefreshableAccountFilter {
@Override
public boolean isRefreshable(final AccountPreferences pref) {
return pref.isAutoRefreshTrendsEnabled();
}
}
private static class TrendsRefreshableFilter implements RefreshableAccountFilter {
@Override
public boolean isRefreshable(final AccountPreferences pref) {
return pref.isAutoRefreshTrendsEnabled();
}
}
}

View File

@ -22,6 +22,7 @@ package org.mariotaku.twidere.util;
import android.text.TextUtils;
import android.util.Xml;
import org.apache.commons.lang3.ArrayUtils;
import org.mariotaku.twidere.Constants;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@ -29,19 +30,28 @@ import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.auth.AccessToken;
import twitter4j.auth.RequestToken;
import twitter4j.conf.Configuration;
import twitter4j.http.HeaderMap;
import twitter4j.http.HttpClientWrapper;
import twitter4j.http.HttpParameter;
import twitter4j.http.HttpResponse;
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 final Twitter twitter;
private final HttpClientWrapper client;
@ -62,18 +72,27 @@ public class OAuthPasswordAuthenticator implements Constants {
try {
final String oauthToken = requestToken.getToken();
final String authorizationUrl = requestToken.getAuthorizationURL();
final String authenticityToken = readAuthenticityTokenFromHtml(client.get(authorizationUrl,
authorizationUrl, null, null).asReader());
if (authenticityToken == null) throw new AuthenticityTokenException();
final HashMap<String, String> inputMap = new HashMap<>();
final HttpResponse authorizePage = client.get(authorizationUrl, authorizationUrl, null, null);
final List<String> cookieHeaders = authorizePage.getResponseHeaders("Set-Cookie");
readInputFromHtml(authorizePage.asReader(),
inputMap, INPUT_AUTHENTICITY_TOKEN, INPUT_REDIRECT_AFTER_LOGIN);
final Configuration conf = twitter.getConfiguration();
final HttpParameter[] params = new HttpParameter[4];
params[0] = new HttpParameter("authenticity_token", authenticityToken);
params[1] = new HttpParameter("oauth_token", oauthToken);
params[2] = new HttpParameter("session[username_or_email]", username);
params[3] = new HttpParameter("session[password]", password);
final List<HttpParameter> params = new ArrayList<>();
params.add(new HttpParameter("oauth_token", oauthToken));
params.add(new HttpParameter(INPUT_AUTHENTICITY_TOKEN, inputMap.get(INPUT_AUTHENTICITY_TOKEN)));
if (inputMap.containsKey(INPUT_REDIRECT_AFTER_LOGIN)) {
params.add(new HttpParameter(INPUT_REDIRECT_AFTER_LOGIN, inputMap.get(INPUT_REDIRECT_AFTER_LOGIN)));
}
params.add(new HttpParameter("session[username_or_email]", username));
params.add(new HttpParameter("session[password]", password));
final HeaderMap requestHeaders = new HeaderMap();
requestHeaders.addHeader("Origin", "https://twitter.com");
requestHeaders.addHeader("Referer", "https://twitter.com/oauth/authorize?oauth_token=" + requestToken.getToken());
requestHeaders.put("Cookie", cookieHeaders);
final String oAuthAuthorizationUrl = conf.getOAuthAuthorizationURL();
final String oauthPin = readOAuthPINFromHtml(client.post(oAuthAuthorizationUrl, oAuthAuthorizationUrl,
params).asReader());
params.toArray(new HttpParameter[params.size()]), requestHeaders).asReader());
if (isEmpty(oauthPin)) throw new WrongUserPassException();
return twitter.getOAuthAccessToken(requestToken, oauthPin);
} catch (final IOException | TwitterException | NullPointerException | XmlPullParserException e) {
@ -81,7 +100,7 @@ public class OAuthPasswordAuthenticator implements Constants {
}
}
public static String readAuthenticityTokenFromHtml(final Reader in) throws IOException, XmlPullParserException {
public 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);
@ -90,12 +109,14 @@ public class OAuthPasswordAuthenticator implements Constants {
final String tag = parser.getName();
switch (parser.getEventType()) {
case XmlPullParser.START_TAG: {
if ("input".equals(tag) && "authenticity_token".equals(parser.getAttributeValue(null, "name")))
return parser.getAttributeValue(null, "value");
final String name = parser.getAttributeValue(null, "name");
if ("input".equalsIgnoreCase(tag) && ArrayUtils.contains(desiredNames, name)) {
map.put(name, parser.getAttributeValue(null, "value"));
}
break;
}
}
}
return null;
}
public static String readOAuthPINFromHtml(final Reader in) throws XmlPullParserException, IOException {

View File

@ -83,6 +83,10 @@ public class ReadStateManager implements Constants {
mPreferences.registerOnSharedPreferenceChangeListener(listener);
}
public boolean setPosition(String key, String keyId, long position) {
return setPosition(key, keyId, position, false);
}
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
mPreferences.unregisterOnSharedPreferenceChangeListener(listener);
}

View File

@ -142,8 +142,8 @@ import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.fragment.iface.IBaseFragment.SystemWindowsInsetsCallback;
import org.mariotaku.twidere.fragment.support.AddStatusFilterDialogFragment;
import org.mariotaku.twidere.fragment.support.DestroyStatusDialogFragment;
import org.mariotaku.twidere.fragment.support.MessagesConversationFragment;
import org.mariotaku.twidere.fragment.support.IncomingFriendshipsFragment;
import org.mariotaku.twidere.fragment.support.MessagesConversationFragment;
import org.mariotaku.twidere.fragment.support.MutesUsersListFragment;
import org.mariotaku.twidere.fragment.support.SavedSearchesListFragment;
import org.mariotaku.twidere.fragment.support.SearchFragment;
@ -1078,8 +1078,8 @@ public final class Utils implements Constants, TwitterConstants {
public static Intent createStatusShareIntent(final Context context, final ParcelableStatus status) {
final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, getStatusShareText(context, status));
intent.putExtra(Intent.EXTRA_TEXT, getStatusShareSubject(context, status));
intent.putExtra(Intent.EXTRA_SUBJECT, getStatusShareSubject(context, status));
intent.putExtra(Intent.EXTRA_TEXT, getStatusShareText(context, status));
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
return intent;
}
@ -2331,6 +2331,7 @@ public final class Utils implements Constants, TwitterConstants {
cb.setRestBaseURL(getApiUrl(account.api_url_format, "api", versionSuffix));
cb.setOAuthBaseURL(getApiUrl(account.api_url_format, "api", "/oauth/"));
cb.setUploadBaseURL(getApiUrl(account.api_url_format, "upload", versionSuffix));
cb.setOAuthAuthorizationURL(getApiUrl(account.api_url_format, null, null));
if (!account.same_oauth_signing_url) {
cb.setSigningRestBaseURL(DEFAULT_SIGNING_REST_BASE_URL);
cb.setSigningOAuthBaseURL(DEFAULT_SIGNING_OAUTH_BASE_URL);
@ -2393,6 +2394,7 @@ public final class Utils implements Constants, TwitterConstants {
cb.setRestBaseURL(getApiUrl(apiUrlFormat, "api", "/1.1/"));
cb.setOAuthBaseURL(getApiUrl(apiUrlFormat, "api", "/oauth/"));
cb.setUploadBaseURL(getApiUrl(apiUrlFormat, "upload", "/1.1/"));
cb.setOAuthAuthorizationURL(getApiUrl(apiUrlFormat, null, null));
if (!sameOAuthSigningUrl) {
cb.setSigningRestBaseURL(DEFAULT_SIGNING_REST_BASE_URL);
cb.setSigningOAuthBaseURL(DEFAULT_SIGNING_OAUTH_BASE_URL);
@ -2521,6 +2523,7 @@ public final class Utils implements Constants, TwitterConstants {
cb.setRestBaseURL(getApiUrl(apiUrlFormat, "api", versionSuffix));
cb.setOAuthBaseURL(getApiUrl(apiUrlFormat, "api", "/oauth/"));
cb.setUploadBaseURL(getApiUrl(apiUrlFormat, "upload", versionSuffix));
cb.setOAuthAuthorizationURL(getApiUrl(apiUrlFormat, null, null));
if (!sameOAuthSigningUrl) {
cb.setSigningRestBaseURL(DEFAULT_SIGNING_REST_BASE_URL);
cb.setSigningOAuthBaseURL(DEFAULT_SIGNING_OAUTH_BASE_URL);

View File

@ -52,7 +52,6 @@ import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import javax.net.SocketFactory;
import javax.net.ssl.TrustManager;
import okio.BufferedSink;
import twitter4j.TwitterException;
@ -86,8 +85,11 @@ public class OkHttpClientImpl implements HttpClient, TwidereConstants {
@Override
public HttpResponse request(HttpRequest req) throws TwitterException {
final Builder builder = new Builder();
for (Entry<String, String> headerEntry : req.getRequestHeaders().entrySet()) {
builder.header(headerEntry.getKey(), headerEntry.getValue());
for (Entry<String, List<String>> headerEntry : req.getRequestHeaders().entrySet()) {
final String name = headerEntry.getKey();
for (String value : headerEntry.getValue()) {
builder.addHeader(name, value);
}
}
final Authorization authorization = req.getAuthorization();
if (authorization != null) {
@ -280,5 +282,10 @@ public class OkHttpClientImpl implements HttpClient, TwidereConstants {
}
return maps;
}
@Override
public List<String> getResponseHeaders(String name) {
return response.headers(name);
}
}
}

View File

@ -59,7 +59,7 @@
android:layout_centerInParent="true"
android:minWidth="48dp">
<Button
<android.support.v7.internal.widget.TintButton
android:id="@+id/follow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -1,35 +0,0 @@
<?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/>.
-->
<android.support.v7.widget.CardView
android:id="@+id/card"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/element_spacing_normal"
app:cardBackgroundColor="?cardItemBackgroundColor"
app:cardCornerRadius="@dimen/corner_radius_card"
app:cardElevation="@dimen/elevation_card">
<include layout="@layout/layout_user_details_common"/>
</android.support.v7.widget.CardView>

View File

@ -1,252 +0,0 @@
<?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/>.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:id="@+id/card_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:splitMotionEvents="false">
<LinearLayout
android:id="@+id/description_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:orientation="vertical"
android:padding="@dimen/element_spacing_small">
<org.mariotaku.twidere.view.themed.ThemedTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/element_spacing_small"
android:singleLine="true"
android:text="@string/description"
android:textAllCaps="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:textColorPrimary"
android:textStyle="bold"
android:visibility="gone"/>
<org.mariotaku.twidere.view.HandleSpanClickTextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/element_spacing_small"
android:paddingRight="@dimen/element_spacing_small"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?android:textColorPrimary"
tools:text="Line 1\nLine 2\nLine 3"/>
</LinearLayout>
<LinearLayout
android:id="@+id/location_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="@dimen/element_spacing_small">
<org.mariotaku.twidere.view.ActionIconView
android:layout_width="@dimen/element_size_small"
android:layout_height="@dimen/element_size_small"
android:color="?android:textColorPrimary"
android:src="@drawable/ic_action_location"/>
<org.mariotaku.twidere.view.themed.ThemedTextView
android:id="@+id/location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/element_spacing_small"
android:paddingRight="@dimen/element_spacing_small"
android:singleLine="true"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?android:textColorPrimary"
tools:text="Zhengzhou China"/>
</LinearLayout>
<LinearLayout
android:id="@+id/url_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="@dimen/element_spacing_small">
<org.mariotaku.twidere.view.ActionIconView
android:layout_width="@dimen/element_size_small"
android:layout_height="@dimen/element_size_small"
android:color="?android:textColorPrimary"
android:src="@drawable/ic_action_link"/>
<org.mariotaku.twidere.view.HandleSpanClickTextView
android:id="@+id/url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:paddingLeft="@dimen/element_spacing_small"
android:paddingRight="@dimen/element_spacing_small"
android:singleLine="true"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?android:textColorPrimary"
tools:text="mariotaku.org"/>
</LinearLayout>
<LinearLayout
android:id="@+id/created_at_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="@dimen/element_spacing_small">
<org.mariotaku.twidere.view.ActionIconView
android:layout_width="@dimen/element_size_small"
android:layout_height="@dimen/element_size_small"
android:color="?android:textColorPrimary"
android:src="@drawable/ic_action_portal_cake"/>
<org.mariotaku.twidere.view.themed.ThemedTextView
android:id="@+id/created_at"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/element_spacing_small"
android:paddingRight="@dimen/element_spacing_small"
android:singleLine="true"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?android:textColorPrimary"
tools:text="July 17, 2009 18:35 (20 tweets per day)"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.2dp"
android:background="?android:dividerVertical"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/action_button_size"
android:baselineAligned="false"
android:divider="?android:dividerHorizontal"
android:orientation="horizontal"
android:showDividers="middle"
android:splitMotionEvents="false">
<LinearLayout
android:id="@+id/followers_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/element_spacing_small">
<org.mariotaku.twidere.view.themed.ThemedTextView
android:id="@+id/followers_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:textAppearanceMedium"
android:textColor="?android:textColorPrimary"
tools:text="255"/>
<org.mariotaku.twidere.view.themed.ThemedTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/followers"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?android:textColorSecondary"/>
</LinearLayout>
<LinearLayout
android:id="@+id/friends_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/element_spacing_small">
<org.mariotaku.twidere.view.themed.ThemedTextView
android:id="@+id/friends_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:textAppearanceMedium"
android:textColor="?android:textColorPrimary"
tools:text="255"/>
<org.mariotaku.twidere.view.themed.ThemedTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/following"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?android:textColorSecondary"/>
</LinearLayout>
<LinearLayout
android:id="@+id/listed_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/element_spacing_small">
<org.mariotaku.twidere.view.themed.ThemedTextView
android:id="@+id/listed_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:textAppearanceMedium"
android:textColor="?android:textColorPrimary"
tools:text="255"/>
<org.mariotaku.twidere.view.themed.ThemedTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/listed"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?android:textColorSecondary"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<include layout="@layout/layout_content_fragment_common"/>
</merge>

View File

@ -1,35 +0,0 @@
<?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/>.
-->
<android.support.v7.widget.CardView
android:id="@+id/card"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/element_spacing_normal"
app:cardBackgroundColor="?cardItemBackgroundColor"
app:cardCornerRadius="0dp"
app:cardElevation="0dp">
<include layout="@layout/layout_user_details_common"/>
</android.support.v7.widget.CardView>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<menu xmlns:android="http://schemas.android.com/apk/res/android"
>
<item
android:id="@id/reply"

View File

@ -733,6 +733,7 @@
<string name="user_type_verified">Verified</string>
<string name="user_type_protected">Protected</string>
<string name="tweet_hashtag">Tweet #<xliff:g id="text">%1$s</xliff:g></string>
<string name="share_status">Share tweet</string>
<!-- 'Tweet' here is a verb -->
<string name="tweet_from_name">Tweet from <xliff:g id="text">%1$s</xliff:g></string>