parent
7f31aff144
commit
c16c95d7a7
@ -1,27 +0,0 @@ |
|||||||
/* Copyright 2017 Andrew Dawson |
|
||||||
* |
|
||||||
* This file is a part of Tusky. |
|
||||||
* |
|
||||||
* 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. |
|
||||||
* |
|
||||||
* Tusky 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 Tusky; if not, |
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky; |
|
||||||
|
|
||||||
/** Android Studio complains about built-in assertions so this is an alternative. */ |
|
||||||
class Assert { |
|
||||||
private static boolean ENABLED = BuildConfig.DEBUG; |
|
||||||
|
|
||||||
static void expect(boolean expression) { |
|
||||||
if (ENABLED && !expression) { |
|
||||||
throw new AssertionError(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,40 +0,0 @@ |
|||||||
/* Copyright 2017 Andrew Dawson |
|
||||||
* |
|
||||||
* This file is a part of Tusky. |
|
||||||
* |
|
||||||
* 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. |
|
||||||
* |
|
||||||
* Tusky 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 Tusky; if not, |
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky; |
|
||||||
|
|
||||||
class CountUpDownLatch { |
|
||||||
private int count; |
|
||||||
|
|
||||||
CountUpDownLatch() { |
|
||||||
this.count = 0; |
|
||||||
} |
|
||||||
|
|
||||||
synchronized void countDown() { |
|
||||||
count--; |
|
||||||
notifyAll(); |
|
||||||
} |
|
||||||
|
|
||||||
synchronized void countUp() { |
|
||||||
count++; |
|
||||||
notifyAll(); |
|
||||||
} |
|
||||||
|
|
||||||
synchronized void await() throws InterruptedException { |
|
||||||
while (count != 0) { |
|
||||||
wait(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,50 +0,0 @@ |
|||||||
/* Copyright 2017 Andrew Dawson |
|
||||||
* |
|
||||||
* This file is a part of Tusky. |
|
||||||
* |
|
||||||
* 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. |
|
||||||
* |
|
||||||
* Tusky 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 Tusky; if not, |
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky; |
|
||||||
|
|
||||||
class DateUtils { |
|
||||||
/* This is a rough duplicate of android.text.format.DateUtils.getRelativeTimeSpanString, |
|
||||||
* but even with the FORMAT_ABBREV_RELATIVE flag it wasn't abbreviating enough. */ |
|
||||||
static String getRelativeTimeSpanString(long then, long now) { |
|
||||||
final long MINUTE = 60; |
|
||||||
final long HOUR = 60 * MINUTE; |
|
||||||
final long DAY = 24 * HOUR; |
|
||||||
final long YEAR = 365 * DAY; |
|
||||||
long span = (now - then) / 1000; |
|
||||||
String prefix = ""; |
|
||||||
if (span < 0) { |
|
||||||
prefix = "in "; |
|
||||||
span = -span; |
|
||||||
} |
|
||||||
String unit; |
|
||||||
if (span < MINUTE) { |
|
||||||
unit = "s"; |
|
||||||
} else if (span < HOUR) { |
|
||||||
span /= MINUTE; |
|
||||||
unit = "m"; |
|
||||||
} else if (span < DAY) { |
|
||||||
span /= HOUR; |
|
||||||
unit = "h"; |
|
||||||
} else if (span < YEAR) { |
|
||||||
span /= DAY; |
|
||||||
unit = "d"; |
|
||||||
} else { |
|
||||||
span /= YEAR; |
|
||||||
unit = "y"; |
|
||||||
} |
|
||||||
return prefix + span + unit; |
|
||||||
} |
|
||||||
} |
|
@ -1,54 +0,0 @@ |
|||||||
/* Copyright 2017 Andrew Dawson |
|
||||||
* |
|
||||||
* This file is a part of Tusky. |
|
||||||
* |
|
||||||
* 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. |
|
||||||
* |
|
||||||
* Tusky 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 Tusky; if not, |
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky; |
|
||||||
|
|
||||||
import android.os.Build; |
|
||||||
import android.text.Html; |
|
||||||
import android.text.Spanned; |
|
||||||
|
|
||||||
public class HtmlUtils { |
|
||||||
private static CharSequence trimTrailingWhitespace(CharSequence s) { |
|
||||||
int i = s.length(); |
|
||||||
do { |
|
||||||
i--; |
|
||||||
} while (i >= 0 && Character.isWhitespace(s.charAt(i))); |
|
||||||
return s.subSequence(0, i + 1); |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("deprecation") |
|
||||||
public static Spanned fromHtml(String html) { |
|
||||||
Spanned result; |
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
|
||||||
result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY); |
|
||||||
} else { |
|
||||||
result = Html.fromHtml(html); |
|
||||||
} |
|
||||||
/* Html.fromHtml returns trailing whitespace if the html ends in a </p> tag, which |
|
||||||
* all status contents do, so it should be trimmed. */ |
|
||||||
return (Spanned) trimTrailingWhitespace(result); |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("deprecation") |
|
||||||
public static String toHtml(Spanned text) { |
|
||||||
String result; |
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
|
||||||
result = Html.toHtml(text, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE); |
|
||||||
} else { |
|
||||||
result = Html.toHtml(text); |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
} |
|
@ -1,44 +0,0 @@ |
|||||||
/* Copyright 2017 Andrew Dawson |
|
||||||
* |
|
||||||
* This file is a part of Tusky. |
|
||||||
* |
|
||||||
* 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. |
|
||||||
* |
|
||||||
* Tusky 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 Tusky; if not, |
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky; |
|
||||||
|
|
||||||
import android.support.annotation.Nullable; |
|
||||||
|
|
||||||
import java.io.IOException; |
|
||||||
import java.io.InputStream; |
|
||||||
import java.io.OutputStream; |
|
||||||
|
|
||||||
class IOUtils { |
|
||||||
static void closeQuietly(@Nullable InputStream stream) { |
|
||||||
try { |
|
||||||
if (stream != null) { |
|
||||||
stream.close(); |
|
||||||
} |
|
||||||
} catch (IOException e) { |
|
||||||
// intentionally unhandled
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static void closeQuietly(@Nullable OutputStream stream) { |
|
||||||
try { |
|
||||||
if (stream != null) { |
|
||||||
stream.close(); |
|
||||||
} |
|
||||||
} catch (IOException e) { |
|
||||||
// intentionally unhandled
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,51 +0,0 @@ |
|||||||
/* Copyright 2017 Andrew Dawson |
|
||||||
* |
|
||||||
* This file is a part of Tusky. |
|
||||||
* |
|
||||||
* 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. |
|
||||||
* |
|
||||||
* Tusky 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 Tusky; if not, |
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky; |
|
||||||
|
|
||||||
/**A wrapper for android.util.Log that allows for disabling logging, such as for release builds.*/ |
|
||||||
public class Log { |
|
||||||
private static final boolean LOGGING_ENABLED = BuildConfig.DEBUG; |
|
||||||
|
|
||||||
public static void i(String tag, String string) { |
|
||||||
if (LOGGING_ENABLED) { |
|
||||||
android.util.Log.i(tag, string); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static void e(String tag, String string) { |
|
||||||
if (LOGGING_ENABLED) { |
|
||||||
android.util.Log.e(tag, string); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static void d(String tag, String string) { |
|
||||||
if (LOGGING_ENABLED) { |
|
||||||
android.util.Log.d(tag, string); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static void v(String tag, String string) { |
|
||||||
if (LOGGING_ENABLED) { |
|
||||||
android.util.Log.v(tag, string); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static void w(String tag, String string) { |
|
||||||
if (LOGGING_ENABLED) { |
|
||||||
android.util.Log.w(tag, string); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,242 +0,0 @@ |
|||||||
/* Copyright 2017 Andrew Dawson |
|
||||||
* |
|
||||||
* This file is part of Tusky. |
|
||||||
* |
|
||||||
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU |
|
||||||
* Lesser General Public License as published by the Free Software Foundation, either version 3 of |
|
||||||
* the License, or (at your option) any later version. |
|
||||||
* |
|
||||||
* Tusky 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 Lesser |
|
||||||
* General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU Lesser General Public License along with Tusky. If |
|
||||||
* not, see <http://www.gnu.org/licenses/>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky; |
|
||||||
|
|
||||||
import android.os.Build; |
|
||||||
import android.support.annotation.NonNull; |
|
||||||
|
|
||||||
import java.io.IOException; |
|
||||||
import java.net.InetAddress; |
|
||||||
import java.net.Socket; |
|
||||||
import java.security.KeyManagementException; |
|
||||||
import java.security.KeyStore; |
|
||||||
import java.security.KeyStoreException; |
|
||||||
import java.security.NoSuchAlgorithmException; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.Arrays; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext; |
|
||||||
import javax.net.ssl.SSLSocket; |
|
||||||
import javax.net.ssl.SSLSocketFactory; |
|
||||||
import javax.net.ssl.TrustManager; |
|
||||||
import javax.net.ssl.TrustManagerFactory; |
|
||||||
import javax.net.ssl.X509TrustManager; |
|
||||||
|
|
||||||
import okhttp3.ConnectionSpec; |
|
||||||
import okhttp3.Interceptor; |
|
||||||
import okhttp3.OkHttpClient; |
|
||||||
import okhttp3.Request; |
|
||||||
import okhttp3.Response; |
|
||||||
|
|
||||||
public class OkHttpUtils { |
|
||||||
static final String TAG = "OkHttpUtils"; // logging tag
|
|
||||||
|
|
||||||
/** |
|
||||||
* Makes a Builder with the maximum range of TLS versions and cipher suites enabled. |
|
||||||
* |
|
||||||
* It first tries the "approved" list of cipher suites given in OkHttp (the default in |
|
||||||
* ConnectionSpec.MODERN_TLS) and if that doesn't work falls back to the set of ALL enabled, |
|
||||||
* then falls back to plain http. |
|
||||||
* |
|
||||||
* API level 24 has a regression in elliptic curves where it only supports secp256r1, so this |
|
||||||
* first tries a fallback without elliptic curves at all, and then tries them after. |
|
||||||
* |
|
||||||
* TLS 1.1 and 1.2 have to be manually enabled on API levels 16-20. |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
public static OkHttpClient.Builder getCompatibleClientBuilder() { |
|
||||||
ConnectionSpec fallback = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) |
|
||||||
.allEnabledCipherSuites() |
|
||||||
.supportsTlsExtensions(true) |
|
||||||
.build(); |
|
||||||
|
|
||||||
List<ConnectionSpec> specList = new ArrayList<>(); |
|
||||||
specList.add(ConnectionSpec.MODERN_TLS); |
|
||||||
addNougatFixConnectionSpec(specList); |
|
||||||
specList.add(fallback); |
|
||||||
specList.add(ConnectionSpec.CLEARTEXT); |
|
||||||
|
|
||||||
OkHttpClient.Builder builder = new OkHttpClient.Builder() |
|
||||||
.addInterceptor(getUserAgentInterceptor()) |
|
||||||
.connectionSpecs(specList); |
|
||||||
|
|
||||||
return enableHigherTlsOnPreLollipop(builder); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
public static OkHttpClient getCompatibleClient() { |
|
||||||
return getCompatibleClientBuilder().build(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Add a custom User-Agent that contains Tusky & Android Version to all requests |
|
||||||
* Example: |
|
||||||
* User-Agent: Tusky/1.1.2 Android/5.0.2 |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
private static Interceptor getUserAgentInterceptor() { |
|
||||||
return new Interceptor() { |
|
||||||
@Override |
|
||||||
public Response intercept(Chain chain) throws IOException { |
|
||||||
Request originalRequest = chain.request(); |
|
||||||
Request requestWithUserAgent = originalRequest.newBuilder() |
|
||||||
.header("User-Agent", "Tusky/"+BuildConfig.VERSION_NAME+" Android/"+Build.VERSION.RELEASE) |
|
||||||
.build(); |
|
||||||
return chain.proceed(requestWithUserAgent); |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Android version Nougat has a regression where elliptic curve cipher suites are supported, but |
|
||||||
* only the curve secp256r1 is allowed. So, first it's best to just disable all elliptic |
|
||||||
* ciphers, try the connection, and fall back to the all cipher suites enabled list after. |
|
||||||
*/ |
|
||||||
private static void addNougatFixConnectionSpec(List<ConnectionSpec> specList) { |
|
||||||
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.N) { |
|
||||||
return; |
|
||||||
} |
|
||||||
SSLSocketFactory socketFactory; |
|
||||||
try { |
|
||||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( |
|
||||||
TrustManagerFactory.getDefaultAlgorithm()); |
|
||||||
trustManagerFactory.init((KeyStore) null); |
|
||||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); |
|
||||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { |
|
||||||
throw new IllegalStateException("Unexpected default trust managers:" |
|
||||||
+ Arrays.toString(trustManagers)); |
|
||||||
} |
|
||||||
|
|
||||||
X509TrustManager trustManager = (X509TrustManager) trustManagers[0]; |
|
||||||
|
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS"); |
|
||||||
sslContext.init(null, new TrustManager[] { trustManager }, null); |
|
||||||
socketFactory = sslContext.getSocketFactory(); |
|
||||||
} catch (NoSuchAlgorithmException|KeyStoreException|KeyManagementException e) { |
|
||||||
Log.e(TAG, "Failed obtaining the SSL socket factory."); |
|
||||||
return; |
|
||||||
} |
|
||||||
String[] cipherSuites = socketFactory.getDefaultCipherSuites(); |
|
||||||
ArrayList<String> allowedList = new ArrayList<>(); |
|
||||||
for (String suite : cipherSuites) { |
|
||||||
if (!suite.contains("ECDH")) { |
|
||||||
allowedList.add(suite); |
|
||||||
} |
|
||||||
} |
|
||||||
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) |
|
||||||
.cipherSuites(allowedList.toArray(new String[0])) |
|
||||||
.supportsTlsExtensions(true) |
|
||||||
.build(); |
|
||||||
specList.add(spec); |
|
||||||
} |
|
||||||
|
|
||||||
private static OkHttpClient.Builder enableHigherTlsOnPreLollipop(OkHttpClient.Builder builder) { |
|
||||||
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) { |
|
||||||
try { |
|
||||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( |
|
||||||
TrustManagerFactory.getDefaultAlgorithm()); |
|
||||||
trustManagerFactory.init((KeyStore) null); |
|
||||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); |
|
||||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { |
|
||||||
throw new IllegalStateException("Unexpected default trust managers:" |
|
||||||
+ Arrays.toString(trustManagers)); |
|
||||||
} |
|
||||||
|
|
||||||
X509TrustManager trustManager = (X509TrustManager) trustManagers[0]; |
|
||||||
|
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS"); |
|
||||||
sslContext.init(null, new TrustManager[] { trustManager }, null); |
|
||||||
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); |
|
||||||
|
|
||||||
builder.sslSocketFactory(new SSLSocketFactoryCompat(sslSocketFactory), |
|
||||||
trustManager); |
|
||||||
} catch (NoSuchAlgorithmException|KeyStoreException|KeyManagementException e) { |
|
||||||
Log.e(TAG, "Failed enabling TLS 1.1 & 1.2. " + e.getMessage()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return builder; |
|
||||||
} |
|
||||||
|
|
||||||
private static class SSLSocketFactoryCompat extends SSLSocketFactory { |
|
||||||
private static final String[] DESIRED_TLS_VERSIONS = { "TLSv1", "TLSv1.1", "TLSv1.2", |
|
||||||
"TLSv1.3" }; |
|
||||||
|
|
||||||
final SSLSocketFactory delegate; |
|
||||||
|
|
||||||
SSLSocketFactoryCompat(SSLSocketFactory base) { |
|
||||||
this.delegate = base; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String[] getDefaultCipherSuites() { |
|
||||||
return delegate.getDefaultCipherSuites(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String[] getSupportedCipherSuites() { |
|
||||||
return delegate.getSupportedCipherSuites(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) |
|
||||||
throws IOException { |
|
||||||
return patch(delegate.createSocket(s, host, port, autoClose)); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public Socket createSocket(String host, int port) throws IOException { |
|
||||||
return patch(delegate.createSocket(host, port)); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) |
|
||||||
throws IOException { |
|
||||||
return patch(delegate.createSocket(host, port, localHost, localPort)); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public Socket createSocket(InetAddress host, int port) throws IOException { |
|
||||||
return patch(delegate.createSocket(host, port)); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, |
|
||||||
int localPort) throws IOException { |
|
||||||
return patch(delegate.createSocket(address, port, localAddress, localPort)); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
private static String[] getMatches(String[] wanted, String[] have) { |
|
||||||
List<String> a = new ArrayList<>(Arrays.asList(wanted)); |
|
||||||
List<String> b = Arrays.asList(have); |
|
||||||
a.retainAll(b); |
|
||||||
return a.toArray(new String[0]); |
|
||||||
} |
|
||||||
|
|
||||||
private Socket patch(Socket socket) { |
|
||||||
if (socket instanceof SSLSocket) { |
|
||||||
SSLSocket sslSocket = (SSLSocket) socket; |
|
||||||
String[] protocols = getMatches(DESIRED_TLS_VERSIONS, |
|
||||||
sslSocket.getSupportedProtocols()); |
|
||||||
sslSocket.setEnabledProtocols(protocols); |
|
||||||
} |
|
||||||
return socket; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,129 +0,0 @@ |
|||||||
/* Copyright 2017 Andrew Dawson |
|
||||||
* |
|
||||||
* This file is a part of Tusky. |
|
||||||
* |
|
||||||
* 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. |
|
||||||
* |
|
||||||
* Tusky 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 Tusky; if not, |
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky; |
|
||||||
|
|
||||||
import android.text.Spannable; |
|
||||||
import android.text.Spanned; |
|
||||||
import android.text.style.ForegroundColorSpan; |
|
||||||
|
|
||||||
class SpanUtils { |
|
||||||
private static class FindCharsResult { |
|
||||||
int charIndex; |
|
||||||
int stringIndex; |
|
||||||
|
|
||||||
FindCharsResult() { |
|
||||||
charIndex = -1; |
|
||||||
stringIndex = -1; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private static FindCharsResult findChars(String string, int fromIndex, char[] chars) { |
|
||||||
FindCharsResult result = new FindCharsResult(); |
|
||||||
final int length = string.length(); |
|
||||||
for (int i = fromIndex; i < length; i++) { |
|
||||||
char c = string.charAt(i); |
|
||||||
for (int j = 0; j < chars.length; j++) { |
|
||||||
if (chars[j] == c) { |
|
||||||
result.charIndex = j; |
|
||||||
result.stringIndex = i; |
|
||||||
return result; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
private static FindCharsResult findStart(String string, int fromIndex, char[] chars) { |
|
||||||
final int length = string.length(); |
|
||||||
while (fromIndex < length) { |
|
||||||
FindCharsResult found = findChars(string, fromIndex, chars); |
|
||||||
int i = found.stringIndex; |
|
||||||
if (i < 0) { |
|
||||||
break; |
|
||||||
} else if (i == 0 || i >= 1 && Character.isWhitespace(string.codePointBefore(i))) { |
|
||||||
return found; |
|
||||||
} else { |
|
||||||
fromIndex = i + 1; |
|
||||||
} |
|
||||||
} |
|
||||||
return new FindCharsResult(); |
|
||||||
} |
|
||||||
|
|
||||||
private static int findEndOfHashtag(String string, int fromIndex) { |
|
||||||
final int length = string.length(); |
|
||||||
for (int i = fromIndex + 1; i < length;) { |
|
||||||
int codepoint = string.codePointAt(i); |
|
||||||
if (Character.isWhitespace(codepoint)) { |
|
||||||
return i; |
|
||||||
} else if (codepoint == '#') { |
|
||||||
return -1; |
|
||||||
} |
|
||||||
i += Character.charCount(codepoint); |
|
||||||
} |
|
||||||
return length; |
|
||||||
} |
|
||||||
|
|
||||||
private static int findEndOfMention(String string, int fromIndex) { |
|
||||||
int atCount = 0; |
|
||||||
final int length = string.length(); |
|
||||||
for (int i = fromIndex + 1; i < length;) { |
|
||||||
int codepoint = string.codePointAt(i); |
|
||||||
if (Character.isWhitespace(codepoint)) { |
|
||||||
return i; |
|
||||||
} else if (codepoint == '@') { |
|
||||||
atCount += 1; |
|
||||||
if (atCount >= 2) { |
|
||||||
return -1; |
|
||||||
} |
|
||||||
} |
|
||||||
i += Character.charCount(codepoint); |
|
||||||
} |
|
||||||
return length; |
|
||||||
} |
|
||||||
|
|
||||||
static void highlightSpans(Spannable text, int colour) { |
|
||||||
// Strip all existing colour spans.
|
|
||||||
int n = text.length(); |
|
||||||
ForegroundColorSpan[] oldSpans = text.getSpans(0, n, ForegroundColorSpan.class); |
|
||||||
for (int i = oldSpans.length - 1; i >= 0; i--) { |
|
||||||
text.removeSpan(oldSpans[i]); |
|
||||||
} |
|
||||||
// Colour the mentions and hashtags.
|
|
||||||
String string = text.toString(); |
|
||||||
int start; |
|
||||||
int end = 0; |
|
||||||
while (end < n) { |
|
||||||
char[] chars = { '#', '@' }; |
|
||||||
FindCharsResult found = findStart(string, end, chars); |
|
||||||
start = found.stringIndex; |
|
||||||
if (start < 0) { |
|
||||||
break; |
|
||||||
} |
|
||||||
if (found.charIndex == 0) { |
|
||||||
end = findEndOfHashtag(string, start); |
|
||||||
} else if (found.charIndex == 1) { |
|
||||||
end = findEndOfMention(string, start); |
|
||||||
} else { |
|
||||||
break; |
|
||||||
} |
|
||||||
if (end < 0) { |
|
||||||
break; |
|
||||||
} |
|
||||||
text.setSpan(new ForegroundColorSpan(colour), start, end, |
|
||||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,68 +0,0 @@ |
|||||||
/* Copyright 2017 Andrew Dawson |
|
||||||
* |
|
||||||
* This file is a part of Tusky. |
|
||||||
* |
|
||||||
* 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. |
|
||||||
* |
|
||||||
* Tusky 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 Tusky; if not, |
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky; |
|
||||||
|
|
||||||
import android.content.Context; |
|
||||||
import android.graphics.Color; |
|
||||||
import android.graphics.PorterDuff; |
|
||||||
import android.graphics.drawable.Drawable; |
|
||||||
import android.support.annotation.AttrRes; |
|
||||||
import android.support.annotation.ColorInt; |
|
||||||
import android.support.annotation.DrawableRes; |
|
||||||
import android.support.v4.content.ContextCompat; |
|
||||||
import android.util.TypedValue; |
|
||||||
import android.widget.ImageView; |
|
||||||
|
|
||||||
class ThemeUtils { |
|
||||||
static Drawable getDrawable(Context context, @AttrRes int attribute, |
|
||||||
@DrawableRes int fallbackDrawable) { |
|
||||||
TypedValue value = new TypedValue(); |
|
||||||
@DrawableRes int resourceId; |
|
||||||
if (context.getTheme().resolveAttribute(attribute, value, true)) { |
|
||||||
resourceId = value.resourceId; |
|
||||||
} else { |
|
||||||
resourceId = fallbackDrawable; |
|
||||||
} |
|
||||||
return ContextCompat.getDrawable(context, resourceId); |
|
||||||
} |
|
||||||
|
|
||||||
static @DrawableRes int getDrawableId(Context context, @AttrRes int attribute, |
|
||||||
@DrawableRes int fallbackDrawableId) { |
|
||||||
TypedValue value = new TypedValue(); |
|
||||||
if (context.getTheme().resolveAttribute(attribute, value, true)) { |
|
||||||
return value.resourceId; |
|
||||||
} else { |
|
||||||
return fallbackDrawableId; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static @ColorInt int getColor(Context context, @AttrRes int attribute) { |
|
||||||
TypedValue value = new TypedValue(); |
|
||||||
if (context.getTheme().resolveAttribute(attribute, value, true)) { |
|
||||||
return value.data; |
|
||||||
} else { |
|
||||||
return Color.BLACK; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static void setImageViewTint(ImageView view, @AttrRes int attribute) { |
|
||||||
view.setColorFilter(getColor(view.getContext(), attribute), PorterDuff.Mode.SRC_IN); |
|
||||||
} |
|
||||||
|
|
||||||
static void setDrawableTint(Context context, Drawable drawable, @AttrRes int attribute) { |
|
||||||
drawable.setColorFilter(getColor(context, attribute), PorterDuff.Mode.SRC_IN); |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue