Skip to content

Commit fa87eb5

Browse files
ashishkumar468maskaravivek
authored andcommitted
* Fixes commons-app#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
1 parent df426f7 commit fa87eb5

File tree

7 files changed

+231
-56
lines changed

7 files changed

+231
-56
lines changed
Binary file not shown.

app/src/main/java/fr/free/nrw/commons/CommonsApplication.java

+17-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
import com.facebook.drawee.backends.pipeline.Fresco;
1616
import com.facebook.imagepipeline.core.ImagePipeline;
1717
import com.facebook.imagepipeline.core.ImagePipelineConfig;
18+
import com.facebook.imagepipeline.producers.Consumer;
19+
import com.facebook.imagepipeline.producers.FetchState;
20+
import com.facebook.imagepipeline.producers.NetworkFetcher;
21+
import com.facebook.imagepipeline.producers.ProducerContext;
1822
import com.squareup.leakcanary.LeakCanary;
1923
import com.squareup.leakcanary.RefWatcher;
2024

@@ -49,6 +53,7 @@
4953
import io.reactivex.internal.functions.Functions;
5054
import io.reactivex.plugins.RxJavaPlugins;
5155
import io.reactivex.schedulers.Schedulers;
56+
import okhttp3.OkHttpClient;
5257
import timber.log.Timber;
5358

5459
import static org.acra.ReportField.ANDROID_VERSION;
@@ -83,6 +88,9 @@ public class CommonsApplication extends Application {
8388

8489
@Inject @Named("default_preferences") JsonKvStore defaultPrefs;
8590

91+
@Inject
92+
OkHttpClient okHttpClient;
93+
8694
/**
8795
* Constants begin
8896
*/
@@ -134,9 +142,15 @@ public void onCreate() {
134142
initTimber();
135143

136144
// Set DownsampleEnabled to True to downsample the image in case it's heavy
137-
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
138-
.setDownsampleEnabled(true)
139-
.build();
145+
ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(this)
146+
.setDownsampleEnabled(true);
147+
148+
if(ConfigUtils.isBetaFlavour()){
149+
NetworkFetcher networkFetcher=new CustomNetworkFetcher(okHttpClient);
150+
imagePipelineConfigBuilder.setNetworkFetcher(networkFetcher);
151+
}
152+
153+
ImagePipelineConfig config = imagePipelineConfigBuilder.build();
140154
try {
141155
Fresco.initialize(this, config);
142156
} catch (Exception e) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package fr.free.nrw.commons;
2+
3+
import android.net.Uri;
4+
import android.os.Looper;
5+
import android.os.SystemClock;
6+
import com.facebook.imagepipeline.common.BytesRange;
7+
import com.facebook.imagepipeline.image.EncodedImage;
8+
import com.facebook.imagepipeline.producers.BaseNetworkFetcher;
9+
import com.facebook.imagepipeline.producers.BaseProducerContextCallbacks;
10+
import com.facebook.imagepipeline.producers.Consumer;
11+
import com.facebook.imagepipeline.producers.FetchState;
12+
import com.facebook.imagepipeline.producers.NetworkFetcher;
13+
import com.facebook.imagepipeline.producers.ProducerContext;
14+
import java.io.IOException;
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
import java.util.concurrent.Executor;
18+
import okhttp3.CacheControl;
19+
import okhttp3.Call;
20+
import okhttp3.OkHttpClient;
21+
import okhttp3.Request;
22+
import okhttp3.Response;
23+
import okhttp3.ResponseBody;
24+
25+
/** Network fetcher that uses OkHttp 3 as a backend. */
26+
public class CustomNetworkFetcher
27+
extends BaseNetworkFetcher<CustomNetworkFetcher.OkHttpNetworkFetchState> {
28+
29+
public static class OkHttpNetworkFetchState extends FetchState {
30+
31+
public long submitTime;
32+
public long responseTime;
33+
public long fetchCompleteTime;
34+
35+
public OkHttpNetworkFetchState(
36+
Consumer<EncodedImage> consumer, ProducerContext producerContext) {
37+
super(consumer, producerContext);
38+
}
39+
}
40+
41+
private static final String QUEUE_TIME = "queue_time";
42+
private static final String FETCH_TIME = "fetch_time";
43+
private static final String TOTAL_TIME = "total_time";
44+
private static final String IMAGE_SIZE = "image_size";
45+
46+
private final Call.Factory mCallFactory;
47+
private final CacheControl mCacheControl;
48+
49+
private Executor mCancellationExecutor;
50+
51+
/** @param okHttpClient client to use */
52+
public CustomNetworkFetcher(OkHttpClient okHttpClient) {
53+
this(okHttpClient, okHttpClient.dispatcher().executorService());
54+
}
55+
56+
/**
57+
* @param callFactory custom {@link Call.Factory} for fetching image from the network
58+
* @param cancellationExecutor executor on which fetching cancellation is performed if
59+
* cancellation is requested from the UI Thread
60+
*/
61+
public CustomNetworkFetcher(Call.Factory callFactory, Executor cancellationExecutor) {
62+
this(callFactory, cancellationExecutor, true);
63+
}
64+
65+
/**
66+
* @param callFactory custom {@link Call.Factory} for fetching image from the network
67+
* @param cancellationExecutor executor on which fetching cancellation is performed if
68+
* cancellation is requested from the UI Thread
69+
* @param disableOkHttpCache true if network requests should not be cached by OkHttp
70+
*/
71+
public CustomNetworkFetcher(
72+
Call.Factory callFactory, Executor cancellationExecutor, boolean disableOkHttpCache) {
73+
mCallFactory = callFactory;
74+
mCancellationExecutor = cancellationExecutor;
75+
mCacheControl = disableOkHttpCache ? new CacheControl.Builder().noStore().build() : null;
76+
}
77+
78+
@Override
79+
public OkHttpNetworkFetchState createFetchState(
80+
Consumer<EncodedImage> consumer, ProducerContext context) {
81+
return new OkHttpNetworkFetchState(consumer, context);
82+
}
83+
84+
@Override
85+
public void fetch(
86+
final OkHttpNetworkFetchState fetchState, final NetworkFetcher.Callback callback) {
87+
fetchState.submitTime = SystemClock.elapsedRealtime();
88+
final Uri uri = fetchState.getUri();
89+
90+
try {
91+
final Request.Builder requestBuilder = new Request.Builder().url(uri.toString()).get();
92+
93+
if (mCacheControl != null) {
94+
requestBuilder.cacheControl(mCacheControl);
95+
}
96+
97+
final BytesRange bytesRange = fetchState.getContext().getImageRequest().getBytesRange();
98+
if (bytesRange != null) {
99+
requestBuilder.addHeader("Range", bytesRange.toHttpRangeHeaderValue());
100+
}
101+
102+
fetchWithRequest(fetchState, callback, requestBuilder.build());
103+
} catch (Exception e) {
104+
// handle error while creating the request
105+
callback.onFailure(e);
106+
}
107+
}
108+
109+
@Override
110+
public void onFetchCompletion(OkHttpNetworkFetchState fetchState, int byteSize) {
111+
fetchState.fetchCompleteTime = SystemClock.elapsedRealtime();
112+
}
113+
114+
@Override
115+
public Map<String, String> getExtraMap(OkHttpNetworkFetchState fetchState, int byteSize) {
116+
Map<String, String> extraMap = new HashMap<>(4);
117+
extraMap.put(QUEUE_TIME, Long.toString(fetchState.responseTime - fetchState.submitTime));
118+
extraMap.put(FETCH_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.responseTime));
119+
extraMap.put(TOTAL_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.submitTime));
120+
extraMap.put(IMAGE_SIZE, Integer.toString(byteSize));
121+
return extraMap;
122+
}
123+
124+
protected void fetchWithRequest(
125+
final OkHttpNetworkFetchState fetchState,
126+
final NetworkFetcher.Callback callback,
127+
final Request request) {
128+
final Call call = mCallFactory.newCall(request);
129+
130+
fetchState
131+
.getContext()
132+
.addCallbacks(
133+
new BaseProducerContextCallbacks() {
134+
@Override
135+
public void onCancellationRequested() {
136+
if (Looper.myLooper() != Looper.getMainLooper()) {
137+
call.cancel();
138+
} else {
139+
mCancellationExecutor.execute(
140+
new Runnable() {
141+
@Override
142+
public void run() {
143+
call.cancel();
144+
}
145+
});
146+
}
147+
}
148+
});
149+
150+
call.enqueue(
151+
new okhttp3.Callback() {
152+
@Override
153+
public void onResponse(Call call, Response response) throws IOException {
154+
fetchState.responseTime = SystemClock.elapsedRealtime();
155+
final ResponseBody body = response.body();
156+
try {
157+
if (!response.isSuccessful()) {
158+
handleException(
159+
call, new IOException("Unexpected HTTP code " + response), callback);
160+
return;
161+
}
162+
163+
BytesRange responseRange =
164+
BytesRange.fromContentRangeHeader(response.header("Content-Range"));
165+
if (responseRange != null
166+
&& !(responseRange.from == 0
167+
&& responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
168+
// Only treat as a partial image if the range is not all of the content
169+
fetchState.setResponseBytesRange(responseRange);
170+
fetchState.setOnNewResultStatusFlags(Consumer.IS_PARTIAL_RESULT);
171+
}
172+
173+
long contentLength = body.contentLength();
174+
if (contentLength < 0) {
175+
contentLength = 0;
176+
}
177+
callback.onResponse(body.byteStream(), (int) contentLength);
178+
} catch (Exception e) {
179+
handleException(call, e, callback);
180+
} finally {
181+
body.close();
182+
}
183+
}
184+
185+
@Override
186+
public void onFailure(Call call, IOException e) {
187+
handleException(call, e, callback);
188+
}
189+
});
190+
}
191+
192+
/**
193+
* Handles exceptions.
194+
*
195+
* <p>OkHttp notifies callers of cancellations via an IOException. If IOException is caught after
196+
* request cancellation, then the exception is interpreted as successful cancellation and
197+
* onCancellation is called. Otherwise onFailure is called.
198+
*/
199+
private void handleException(final Call call, final Exception e, final Callback callback) {
200+
if (call.isCanceled()) {
201+
callback.onCancellation();
202+
} else {
203+
callback.onFailure(e);
204+
}
205+
}
206+
}

app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ private static OkHttpClient createClient() {
3939
.addInterceptor(new CommonHeaderRequestInterceptor());
4040

4141
if(ConfigUtils.isBetaFlavour()){
42-
builder.sslSocketFactory(SslUtils.INSTANCE.getSslContextForCertificateFile(CommonsApplication.getInstance(), "*.wikimedia.beta.wmflabs.org.cer").getSocketFactory());
42+
builder.sslSocketFactory(SslUtils.INSTANCE.getTrustAllHostsSSLSocketFactory());
4343
}
4444
return builder.build();
4545
}

app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public OkHttpClient provideOkHttpClient(Context context,
6868
.cache(new Cache(dir, OK_HTTP_CACHE_SIZE));
6969

7070
if(ConfigUtils.isBetaFlavour()){
71-
builder.sslSocketFactory(SslUtils.INSTANCE.getSslContextForCertificateFile(context, "*.wikimedia.beta.wmflabs.org.cer").getSocketFactory());
71+
builder.sslSocketFactory(SslUtils.INSTANCE.getTrustAllHostsSSLSocketFactory());
7272
}
7373
return builder.build();
7474
}

app/src/main/java/fr/free/nrw/commons/di/SslUtils.kt

+4-47
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,16 @@
11
package fr.free.nrw.commons.di
22

3-
import android.content.Context
4-
import android.util.Log
53
import java.security.KeyManagementException
6-
import java.security.KeyStore
74
import java.security.NoSuchAlgorithmException
8-
import java.security.SecureRandom
9-
import java.security.cert.Certificate
105
import java.security.cert.CertificateException
11-
import java.security.cert.CertificateFactory
126
import java.security.cert.X509Certificate
13-
import javax.net.ssl.*
7+
import javax.net.ssl.SSLContext
8+
import javax.net.ssl.SSLSocketFactory
9+
import javax.net.ssl.TrustManager
10+
import javax.net.ssl.X509TrustManager
1411

1512
object SslUtils {
1613

17-
fun getSslContextForCertificateFile(context: Context, fileName: String): SSLContext {
18-
try {
19-
val keyStore = SslUtils.getKeyStore(context, fileName)
20-
val sslContext = SSLContext.getInstance("SSL")
21-
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
22-
trustManagerFactory.init(keyStore)
23-
sslContext.init(null, trustManagerFactory.trustManagers, SecureRandom())
24-
return sslContext
25-
} catch (e: Exception) {
26-
val msg = "Error during creating SslContext for certificate from assets"
27-
e.printStackTrace()
28-
throw RuntimeException(msg)
29-
}
30-
}
31-
32-
private fun getKeyStore(context: Context, fileName: String): KeyStore? {
33-
var keyStore: KeyStore? = null
34-
try {
35-
val assetManager = context.assets
36-
val cf = CertificateFactory.getInstance("X.509")
37-
val caInput = assetManager.open(fileName)
38-
val ca: Certificate
39-
try {
40-
ca = cf.generateCertificate(caInput)
41-
Log.d("SslUtilsAndroid", "ca=" + (ca as X509Certificate).subjectDN)
42-
} finally {
43-
caInput.close()
44-
}
45-
46-
val keyStoreType = KeyStore.getDefaultType()
47-
keyStore = KeyStore.getInstance(keyStoreType)
48-
keyStore!!.load(null, null)
49-
keyStore.setCertificateEntry("ca", ca)
50-
} catch (e: Exception) {
51-
e.printStackTrace()
52-
}
53-
54-
return keyStore
55-
}
56-
5714
fun getTrustAllHostsSSLSocketFactory(): SSLSocketFactory? {
5815
try {
5916
// Create a trust manager that does not validate certificate chains

app/src/test/kotlin/fr/free/nrw/commons/TestCommonsApplication.kt

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package fr.free.nrw.commons
22

3+
import android.app.Application
34
import android.content.ContentProviderClient
45
import android.content.Context
56
import androidx.collection.LruCache
@@ -14,7 +15,7 @@ import fr.free.nrw.commons.di.DaggerCommonsApplicationComponent
1415
import fr.free.nrw.commons.kvstore.JsonKvStore
1516
import fr.free.nrw.commons.location.LocationServiceManager
1617

17-
class TestCommonsApplication : CommonsApplication() {
18+
class TestCommonsApplication : Application() {
1819
private var mockApplicationComponent: CommonsApplicationComponent? = null
1920

2021
override fun onCreate() {
@@ -25,9 +26,6 @@ class TestCommonsApplication : CommonsApplication() {
2526
}
2627
super.onCreate()
2728
}
28-
29-
// No leakcanary in unit tests.
30-
override fun setupLeakCanary(): RefWatcher = RefWatcher.DISABLED
3129
}
3230

3331
@Suppress("MemberVisibilityCanBePrivate")

0 commit comments

Comments
 (0)