mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
* Fixes #3345 * Trust all hosts for beta * Added a custom NetworkFetcger for Fresco when on beta * removed unused assets * make TestCommonsApplication extend Application instead of Commons Application
This commit is contained in:
parent
df426f7c42
commit
fa87eb5661
7 changed files with 231 additions and 56 deletions
Binary file not shown.
|
|
@ -15,6 +15,10 @@ import androidx.annotation.NonNull;
|
|||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.imagepipeline.core.ImagePipeline;
|
||||
import com.facebook.imagepipeline.core.ImagePipelineConfig;
|
||||
import com.facebook.imagepipeline.producers.Consumer;
|
||||
import com.facebook.imagepipeline.producers.FetchState;
|
||||
import com.facebook.imagepipeline.producers.NetworkFetcher;
|
||||
import com.facebook.imagepipeline.producers.ProducerContext;
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
import com.squareup.leakcanary.RefWatcher;
|
||||
|
||||
|
|
@ -49,6 +53,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
|||
import io.reactivex.internal.functions.Functions;
|
||||
import io.reactivex.plugins.RxJavaPlugins;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import okhttp3.OkHttpClient;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static org.acra.ReportField.ANDROID_VERSION;
|
||||
|
|
@ -83,6 +88,9 @@ public class CommonsApplication extends Application {
|
|||
|
||||
@Inject @Named("default_preferences") JsonKvStore defaultPrefs;
|
||||
|
||||
@Inject
|
||||
OkHttpClient okHttpClient;
|
||||
|
||||
/**
|
||||
* Constants begin
|
||||
*/
|
||||
|
|
@ -134,9 +142,15 @@ public class CommonsApplication extends Application {
|
|||
initTimber();
|
||||
|
||||
// Set DownsampleEnabled to True to downsample the image in case it's heavy
|
||||
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
|
||||
.setDownsampleEnabled(true)
|
||||
.build();
|
||||
ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(this)
|
||||
.setDownsampleEnabled(true);
|
||||
|
||||
if(ConfigUtils.isBetaFlavour()){
|
||||
NetworkFetcher networkFetcher=new CustomNetworkFetcher(okHttpClient);
|
||||
imagePipelineConfigBuilder.setNetworkFetcher(networkFetcher);
|
||||
}
|
||||
|
||||
ImagePipelineConfig config = imagePipelineConfigBuilder.build();
|
||||
try {
|
||||
Fresco.initialize(this, config);
|
||||
} catch (Exception e) {
|
||||
|
|
|
|||
206
app/src/main/java/fr/free/nrw/commons/CustomNetworkFetcher.java
Normal file
206
app/src/main/java/fr/free/nrw/commons/CustomNetworkFetcher.java
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import com.facebook.imagepipeline.common.BytesRange;
|
||||
import com.facebook.imagepipeline.image.EncodedImage;
|
||||
import com.facebook.imagepipeline.producers.BaseNetworkFetcher;
|
||||
import com.facebook.imagepipeline.producers.BaseProducerContextCallbacks;
|
||||
import com.facebook.imagepipeline.producers.Consumer;
|
||||
import com.facebook.imagepipeline.producers.FetchState;
|
||||
import com.facebook.imagepipeline.producers.NetworkFetcher;
|
||||
import com.facebook.imagepipeline.producers.ProducerContext;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import okhttp3.CacheControl;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/** Network fetcher that uses OkHttp 3 as a backend. */
|
||||
public class CustomNetworkFetcher
|
||||
extends BaseNetworkFetcher<CustomNetworkFetcher.OkHttpNetworkFetchState> {
|
||||
|
||||
public static class OkHttpNetworkFetchState extends FetchState {
|
||||
|
||||
public long submitTime;
|
||||
public long responseTime;
|
||||
public long fetchCompleteTime;
|
||||
|
||||
public OkHttpNetworkFetchState(
|
||||
Consumer<EncodedImage> consumer, ProducerContext producerContext) {
|
||||
super(consumer, producerContext);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String QUEUE_TIME = "queue_time";
|
||||
private static final String FETCH_TIME = "fetch_time";
|
||||
private static final String TOTAL_TIME = "total_time";
|
||||
private static final String IMAGE_SIZE = "image_size";
|
||||
|
||||
private final Call.Factory mCallFactory;
|
||||
private final CacheControl mCacheControl;
|
||||
|
||||
private Executor mCancellationExecutor;
|
||||
|
||||
/** @param okHttpClient client to use */
|
||||
public CustomNetworkFetcher(OkHttpClient okHttpClient) {
|
||||
this(okHttpClient, okHttpClient.dispatcher().executorService());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callFactory custom {@link Call.Factory} for fetching image from the network
|
||||
* @param cancellationExecutor executor on which fetching cancellation is performed if
|
||||
* cancellation is requested from the UI Thread
|
||||
*/
|
||||
public CustomNetworkFetcher(Call.Factory callFactory, Executor cancellationExecutor) {
|
||||
this(callFactory, cancellationExecutor, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callFactory custom {@link Call.Factory} for fetching image from the network
|
||||
* @param cancellationExecutor executor on which fetching cancellation is performed if
|
||||
* cancellation is requested from the UI Thread
|
||||
* @param disableOkHttpCache true if network requests should not be cached by OkHttp
|
||||
*/
|
||||
public CustomNetworkFetcher(
|
||||
Call.Factory callFactory, Executor cancellationExecutor, boolean disableOkHttpCache) {
|
||||
mCallFactory = callFactory;
|
||||
mCancellationExecutor = cancellationExecutor;
|
||||
mCacheControl = disableOkHttpCache ? new CacheControl.Builder().noStore().build() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OkHttpNetworkFetchState createFetchState(
|
||||
Consumer<EncodedImage> consumer, ProducerContext context) {
|
||||
return new OkHttpNetworkFetchState(consumer, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch(
|
||||
final OkHttpNetworkFetchState fetchState, final NetworkFetcher.Callback callback) {
|
||||
fetchState.submitTime = SystemClock.elapsedRealtime();
|
||||
final Uri uri = fetchState.getUri();
|
||||
|
||||
try {
|
||||
final Request.Builder requestBuilder = new Request.Builder().url(uri.toString()).get();
|
||||
|
||||
if (mCacheControl != null) {
|
||||
requestBuilder.cacheControl(mCacheControl);
|
||||
}
|
||||
|
||||
final BytesRange bytesRange = fetchState.getContext().getImageRequest().getBytesRange();
|
||||
if (bytesRange != null) {
|
||||
requestBuilder.addHeader("Range", bytesRange.toHttpRangeHeaderValue());
|
||||
}
|
||||
|
||||
fetchWithRequest(fetchState, callback, requestBuilder.build());
|
||||
} catch (Exception e) {
|
||||
// handle error while creating the request
|
||||
callback.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchCompletion(OkHttpNetworkFetchState fetchState, int byteSize) {
|
||||
fetchState.fetchCompleteTime = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getExtraMap(OkHttpNetworkFetchState fetchState, int byteSize) {
|
||||
Map<String, String> extraMap = new HashMap<>(4);
|
||||
extraMap.put(QUEUE_TIME, Long.toString(fetchState.responseTime - fetchState.submitTime));
|
||||
extraMap.put(FETCH_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.responseTime));
|
||||
extraMap.put(TOTAL_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.submitTime));
|
||||
extraMap.put(IMAGE_SIZE, Integer.toString(byteSize));
|
||||
return extraMap;
|
||||
}
|
||||
|
||||
protected void fetchWithRequest(
|
||||
final OkHttpNetworkFetchState fetchState,
|
||||
final NetworkFetcher.Callback callback,
|
||||
final Request request) {
|
||||
final Call call = mCallFactory.newCall(request);
|
||||
|
||||
fetchState
|
||||
.getContext()
|
||||
.addCallbacks(
|
||||
new BaseProducerContextCallbacks() {
|
||||
@Override
|
||||
public void onCancellationRequested() {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
call.cancel();
|
||||
} else {
|
||||
mCancellationExecutor.execute(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
call.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
call.enqueue(
|
||||
new okhttp3.Callback() {
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
fetchState.responseTime = SystemClock.elapsedRealtime();
|
||||
final ResponseBody body = response.body();
|
||||
try {
|
||||
if (!response.isSuccessful()) {
|
||||
handleException(
|
||||
call, new IOException("Unexpected HTTP code " + response), callback);
|
||||
return;
|
||||
}
|
||||
|
||||
BytesRange responseRange =
|
||||
BytesRange.fromContentRangeHeader(response.header("Content-Range"));
|
||||
if (responseRange != null
|
||||
&& !(responseRange.from == 0
|
||||
&& responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
|
||||
// Only treat as a partial image if the range is not all of the content
|
||||
fetchState.setResponseBytesRange(responseRange);
|
||||
fetchState.setOnNewResultStatusFlags(Consumer.IS_PARTIAL_RESULT);
|
||||
}
|
||||
|
||||
long contentLength = body.contentLength();
|
||||
if (contentLength < 0) {
|
||||
contentLength = 0;
|
||||
}
|
||||
callback.onResponse(body.byteStream(), (int) contentLength);
|
||||
} catch (Exception e) {
|
||||
handleException(call, e, callback);
|
||||
} finally {
|
||||
body.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
handleException(call, e, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles exceptions.
|
||||
*
|
||||
* <p>OkHttp notifies callers of cancellations via an IOException. If IOException is caught after
|
||||
* request cancellation, then the exception is interpreted as successful cancellation and
|
||||
* onCancellation is called. Otherwise onFailure is called.
|
||||
*/
|
||||
private void handleException(final Call call, final Exception e, final Callback callback) {
|
||||
if (call.isCanceled()) {
|
||||
callback.onCancellation();
|
||||
} else {
|
||||
callback.onFailure(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ public final class OkHttpConnectionFactory {
|
|||
.addInterceptor(new CommonHeaderRequestInterceptor());
|
||||
|
||||
if(ConfigUtils.isBetaFlavour()){
|
||||
builder.sslSocketFactory(SslUtils.INSTANCE.getSslContextForCertificateFile(CommonsApplication.getInstance(), "*.wikimedia.beta.wmflabs.org.cer").getSocketFactory());
|
||||
builder.sslSocketFactory(SslUtils.INSTANCE.getTrustAllHostsSSLSocketFactory());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ public class NetworkingModule {
|
|||
.cache(new Cache(dir, OK_HTTP_CACHE_SIZE));
|
||||
|
||||
if(ConfigUtils.isBetaFlavour()){
|
||||
builder.sslSocketFactory(SslUtils.INSTANCE.getSslContextForCertificateFile(context, "*.wikimedia.beta.wmflabs.org.cer").getSocketFactory());
|
||||
builder.sslSocketFactory(SslUtils.INSTANCE.getTrustAllHostsSSLSocketFactory());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,16 @@
|
|||
package fr.free.nrw.commons.di
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import java.security.KeyManagementException
|
||||
import java.security.KeyStore
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.*
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
object SslUtils {
|
||||
|
||||
fun getSslContextForCertificateFile(context: Context, fileName: String): SSLContext {
|
||||
try {
|
||||
val keyStore = SslUtils.getKeyStore(context, fileName)
|
||||
val sslContext = SSLContext.getInstance("SSL")
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
trustManagerFactory.init(keyStore)
|
||||
sslContext.init(null, trustManagerFactory.trustManagers, SecureRandom())
|
||||
return sslContext
|
||||
} catch (e: Exception) {
|
||||
val msg = "Error during creating SslContext for certificate from assets"
|
||||
e.printStackTrace()
|
||||
throw RuntimeException(msg)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getKeyStore(context: Context, fileName: String): KeyStore? {
|
||||
var keyStore: KeyStore? = null
|
||||
try {
|
||||
val assetManager = context.assets
|
||||
val cf = CertificateFactory.getInstance("X.509")
|
||||
val caInput = assetManager.open(fileName)
|
||||
val ca: Certificate
|
||||
try {
|
||||
ca = cf.generateCertificate(caInput)
|
||||
Log.d("SslUtilsAndroid", "ca=" + (ca as X509Certificate).subjectDN)
|
||||
} finally {
|
||||
caInput.close()
|
||||
}
|
||||
|
||||
val keyStoreType = KeyStore.getDefaultType()
|
||||
keyStore = KeyStore.getInstance(keyStoreType)
|
||||
keyStore!!.load(null, null)
|
||||
keyStore.setCertificateEntry("ca", ca)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return keyStore
|
||||
}
|
||||
|
||||
fun getTrustAllHostsSSLSocketFactory(): SSLSocketFactory? {
|
||||
try {
|
||||
// Create a trust manager that does not validate certificate chains
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package fr.free.nrw.commons
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ContentProviderClient
|
||||
import android.content.Context
|
||||
import androidx.collection.LruCache
|
||||
|
|
@ -14,7 +15,7 @@ import fr.free.nrw.commons.di.DaggerCommonsApplicationComponent
|
|||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||
import fr.free.nrw.commons.location.LocationServiceManager
|
||||
|
||||
class TestCommonsApplication : CommonsApplication() {
|
||||
class TestCommonsApplication : Application() {
|
||||
private var mockApplicationComponent: CommonsApplicationComponent? = null
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
@ -25,9 +26,6 @@ class TestCommonsApplication : CommonsApplication() {
|
|||
}
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
// No leakcanary in unit tests.
|
||||
override fun setupLeakCanary(): RefWatcher = RefWatcher.DISABLED
|
||||
}
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue