• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net.test;
6 
7 import android.content.Context;
8 
9 import androidx.annotation.GuardedBy;
10 
11 import org.chromium.net.BidirectionalStream;
12 import org.chromium.net.CronetEngine;
13 import org.chromium.net.ExperimentalBidirectionalStream;
14 import org.chromium.net.NetworkQualityRttListener;
15 import org.chromium.net.NetworkQualityThroughputListener;
16 import org.chromium.net.RequestFinishedInfo;
17 import org.chromium.net.UrlRequest;
18 import org.chromium.net.impl.CronetEngineBase;
19 import org.chromium.net.impl.CronetEngineBuilderImpl;
20 import org.chromium.net.impl.ImplVersion;
21 import org.chromium.net.impl.RefCountDelegate;
22 import org.chromium.net.impl.UrlRequestBase;
23 import org.chromium.net.impl.VersionSafeCallbacks;
24 
25 import java.io.IOException;
26 import java.net.Proxy;
27 import java.net.URL;
28 import java.net.URLConnection;
29 import java.net.URLStreamHandlerFactory;
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.concurrent.Executor;
35 import java.util.concurrent.ExecutorService;
36 import java.util.concurrent.Executors;
37 import java.util.concurrent.LinkedBlockingQueue;
38 import java.util.concurrent.ThreadFactory;
39 import java.util.concurrent.ThreadPoolExecutor;
40 import java.util.concurrent.TimeUnit;
41 
42 /** Fake {@link CronetEngine}. This implements CronetEngine. */
43 final class FakeCronetEngine extends CronetEngineBase {
44     /** Builds a {@link FakeCronetEngine}. This implements CronetEngine.Builder. */
45     static class Builder extends CronetEngineBuilderImpl {
46         private FakeCronetController mController;
47 
48         /**
49          * Builder for {@link FakeCronetEngine}.
50          *
51          * @param context Android {@link Context}.
52          */
Builder(Context context)53         Builder(Context context) {
54             super(context);
55         }
56 
57         @Override
build()58         public FakeCronetEngine build() {
59             return new FakeCronetEngine(this);
60         }
61 
setController(FakeCronetController controller)62         void setController(FakeCronetController controller) {
63             mController = controller;
64         }
65     }
66 
67     private final FakeCronetController mController;
68     private final ExecutorService mExecutorService;
69 
70     private final Object mLock = new Object();
71 
72     @GuardedBy("mLock")
73     private boolean mIsShutdown;
74 
75     /**
76      * The number of started requests where the terminal callback (i.e.
77      * onSucceeded/onCancelled/onFailed) has not yet been called.
78      */
79     @GuardedBy("mLock")
80     private int mRunningRequestCount;
81 
82     /*
83      * The number of started requests where the terminal callbacks (i.e.
84      * onSucceeded/onCancelled/onFailed, request finished listeners) have not
85      * all returned yet.
86      *
87      * By definition this is always greater than or equal to
88      * mRunningRequestCount. The difference between the two is the number of
89      * terminal callbacks that are currently running.
90      */
91     @GuardedBy("mLock")
92     private int mActiveRequestCount;
93 
94     @GuardedBy("mLock")
95     private final Map<
96                     RequestFinishedInfo.Listener, VersionSafeCallbacks.RequestFinishedInfoListener>
97             mFinishedListenerMap = new HashMap<>();
98 
99     /**
100      * Creates a {@link FakeCronetEngine}. Used when {@link FakeCronetEngine} is created with the
101      * {@link FakeCronetEngine.Builder}.
102      *
103      * @param builder a {@link CronetEngineBuilderImpl} to build this {@link CronetEngine}
104      *                implementation from.
105      */
FakeCronetEngine(FakeCronetEngine.Builder builder)106     private FakeCronetEngine(FakeCronetEngine.Builder builder) {
107         if (builder.mController != null) {
108             mController = builder.mController;
109         } else {
110             mController = new FakeCronetController();
111         }
112         mExecutorService =
113                 new ThreadPoolExecutor(
114                         /* corePoolSize= */ 1,
115                         /* maximumPoolSize= */ 5,
116                         /* keepAliveTime= */ 50,
117                         TimeUnit.SECONDS,
118                         new LinkedBlockingQueue<Runnable>(),
119                         new ThreadFactory() {
120                             @Override
121                             public Thread newThread(final Runnable r) {
122                                 return Executors.defaultThreadFactory()
123                                         .newThread(
124                                                 () -> {
125                                                     Thread.currentThread()
126                                                             .setName("FakeCronetEngine");
127                                                     r.run();
128                                                 });
129                             }
130                         });
131         FakeCronetController.addFakeCronetEngine(this);
132     }
133 
134     /**
135      * Gets the controller associated with this instance that will be used for responses to
136      * {@link UrlRequest}s.
137      *
138      * @return the {@link FakeCronetCntroller} that controls this {@link FakeCronetEngine}.
139      */
getController()140     FakeCronetController getController() {
141         return mController;
142     }
143 
144     @Override
newBidirectionalStreamBuilder( String url, BidirectionalStream.Callback callback, Executor executor)145     public ExperimentalBidirectionalStream.Builder newBidirectionalStreamBuilder(
146             String url, BidirectionalStream.Callback callback, Executor executor) {
147         synchronized (mLock) {
148             if (mIsShutdown) {
149                 throw new IllegalStateException(
150                         "This instance of CronetEngine has been shutdown and can no longer be "
151                                 + "used.");
152             }
153             throw new UnsupportedOperationException(
154                     "The bidirectional stream API is not supported by the Fake implementation "
155                             + "of CronetEngine.");
156         }
157     }
158 
159     @Override
getVersionString()160     public String getVersionString() {
161         return "FakeCronet/" + ImplVersion.getCronetVersionWithLastChange();
162     }
163 
164     @Override
shutdown()165     public void shutdown() {
166         synchronized (mLock) {
167             if (mRunningRequestCount != 0) {
168                 throw new IllegalStateException("Cannot shutdown with running requests.");
169             } else {
170                 mIsShutdown = true;
171             }
172         }
173         mExecutorService.shutdown();
174         FakeCronetController.removeFakeCronetEngine(this);
175     }
176 
177     @Override
startNetLogToFile(String fileName, boolean logAll)178     public void startNetLogToFile(String fileName, boolean logAll) {}
179 
180     @Override
startNetLogToDisk(String dirPath, boolean logAll, int maxSize)181     public void startNetLogToDisk(String dirPath, boolean logAll, int maxSize) {}
182 
183     @Override
stopNetLog()184     public void stopNetLog() {}
185 
186     @Override
getGlobalMetricsDeltas()187     public byte[] getGlobalMetricsDeltas() {
188         return new byte[0];
189     }
190 
191     @Override
getEffectiveConnectionType()192     public int getEffectiveConnectionType() {
193         return EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
194     }
195 
196     @Override
getHttpRttMs()197     public int getHttpRttMs() {
198         return CONNECTION_METRIC_UNKNOWN;
199     }
200 
201     @Override
getTransportRttMs()202     public int getTransportRttMs() {
203         return CONNECTION_METRIC_UNKNOWN;
204     }
205 
206     @Override
getDownstreamThroughputKbps()207     public int getDownstreamThroughputKbps() {
208         return CONNECTION_METRIC_UNKNOWN;
209     }
210 
211     @Override
bindToNetwork(long networkHandle)212     public void bindToNetwork(long networkHandle) {
213         throw new UnsupportedOperationException(
214                 "The multi-network API is not supported by the Fake implementation "
215                         + "of Cronet Engine");
216     }
217 
218     @Override
configureNetworkQualityEstimatorForTesting( boolean useLocalHostRequests, boolean useSmallerResponses, boolean disableOfflineCheck)219     public void configureNetworkQualityEstimatorForTesting(
220             boolean useLocalHostRequests,
221             boolean useSmallerResponses,
222             boolean disableOfflineCheck) {}
223 
224     @Override
addRttListener(NetworkQualityRttListener listener)225     public void addRttListener(NetworkQualityRttListener listener) {}
226 
227     @Override
removeRttListener(NetworkQualityRttListener listener)228     public void removeRttListener(NetworkQualityRttListener listener) {}
229 
230     @Override
addThroughputListener(NetworkQualityThroughputListener listener)231     public void addThroughputListener(NetworkQualityThroughputListener listener) {}
232 
233     @Override
removeThroughputListener(NetworkQualityThroughputListener listener)234     public void removeThroughputListener(NetworkQualityThroughputListener listener) {}
235 
236     @Override
addRequestFinishedListener(RequestFinishedInfo.Listener listener)237     public void addRequestFinishedListener(RequestFinishedInfo.Listener listener) {
238         if (listener == null) {
239             throw new IllegalArgumentException("Listener must not be null");
240         }
241         synchronized (mLock) {
242             mFinishedListenerMap.put(
243                     listener, new VersionSafeCallbacks.RequestFinishedInfoListener(listener));
244         }
245     }
246 
247     @Override
removeRequestFinishedListener(RequestFinishedInfo.Listener listener)248     public void removeRequestFinishedListener(RequestFinishedInfo.Listener listener) {
249         if (listener == null) {
250             throw new IllegalArgumentException("Listener must not be null");
251         }
252         synchronized (mLock) {
253             mFinishedListenerMap.remove(listener);
254         }
255     }
256 
hasRequestFinishedListeners()257     boolean hasRequestFinishedListeners() {
258         synchronized (mLock) {
259             return !mFinishedListenerMap.isEmpty();
260         }
261     }
262 
reportRequestFinished( RequestFinishedInfo requestInfo, RefCountDelegate inflightDoneCallbackCount)263     void reportRequestFinished(
264             RequestFinishedInfo requestInfo, RefCountDelegate inflightDoneCallbackCount) {
265         synchronized (mLock) {
266             for (RequestFinishedInfo.Listener listener : mFinishedListenerMap.values()) {
267                 inflightDoneCallbackCount.increment();
268                 listener.getExecutor()
269                         .execute(
270                                 () -> {
271                                     try {
272                                         listener.onRequestFinished(requestInfo);
273                                     } finally {
274                                         inflightDoneCallbackCount.decrement();
275                                     }
276                                 });
277             }
278         }
279     }
280 
281     // TODO(crbug.com/669707) Instantiate a fake CronetHttpUrlConnection wrapping a FakeUrlRequest
282     // here.
283     @Override
openConnection(URL url)284     public URLConnection openConnection(URL url) throws IOException {
285         throw new UnsupportedOperationException(
286                 "The openConnection API is not supported by the Fake implementation of "
287                         + "CronetEngine.");
288     }
289 
290     @Override
openConnection(URL url, Proxy proxy)291     public URLConnection openConnection(URL url, Proxy proxy) throws IOException {
292         throw new UnsupportedOperationException(
293                 "The openConnection API is not supported by the Fake implementation of "
294                         + "CronetEngine.");
295     }
296 
297     @Override
createURLStreamHandlerFactory()298     public URLStreamHandlerFactory createURLStreamHandlerFactory() {
299         throw new UnsupportedOperationException(
300                 "The URLStreamHandlerFactory API is not supported by the Fake implementation of "
301                         + "CronetEngine.");
302     }
303 
304     @Override
createRequest( String url, UrlRequest.Callback callback, Executor userExecutor, int priority, Collection<Object> connectionAnnotations, boolean disableCache, boolean disableConnectionMigration, boolean allowDirectExecutor, boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet, int trafficStatsUid, RequestFinishedInfo.Listener requestFinishedListener, int idempotency, long networkHandle)305     protected UrlRequestBase createRequest(
306             String url,
307             UrlRequest.Callback callback,
308             Executor userExecutor,
309             int priority,
310             Collection<Object> connectionAnnotations,
311             boolean disableCache,
312             boolean disableConnectionMigration,
313             boolean allowDirectExecutor,
314             boolean trafficStatsTagSet,
315             int trafficStatsTag,
316             boolean trafficStatsUidSet,
317             int trafficStatsUid,
318             RequestFinishedInfo.Listener requestFinishedListener,
319             int idempotency,
320             long networkHandle) {
321         if (networkHandle != DEFAULT_NETWORK_HANDLE) {
322             throw new UnsupportedOperationException(
323                     "The multi-network API is not supported by the Fake implementation "
324                             + "of Cronet Engine");
325         }
326 
327         synchronized (mLock) {
328             if (mIsShutdown) {
329                 throw new IllegalStateException(
330                         "This instance of CronetEngine has been shutdown and can no longer be "
331                                 + "used.");
332             }
333             return new FakeUrlRequest(
334                     callback,
335                     userExecutor,
336                     mExecutorService,
337                     url,
338                     allowDirectExecutor,
339                     trafficStatsTagSet,
340                     trafficStatsTag,
341                     trafficStatsUidSet,
342                     trafficStatsUid,
343                     mController,
344                     this,
345                     connectionAnnotations);
346         }
347     }
348 
349     @Override
getActiveRequestCount()350     public int getActiveRequestCount() {
351         synchronized (mLock) {
352             return mActiveRequestCount;
353         }
354     }
355 
356     @Override
createBidirectionalStream( String url, BidirectionalStream.Callback callback, Executor executor, String httpMethod, List<Map.Entry<String, String>> requestHeaders, @StreamPriority int priority, boolean delayRequestHeadersUntilFirstFlush, Collection<Object> connectionAnnotations, boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet, int trafficStatsUid, long networkHandle)357     protected ExperimentalBidirectionalStream createBidirectionalStream(
358             String url,
359             BidirectionalStream.Callback callback,
360             Executor executor,
361             String httpMethod,
362             List<Map.Entry<String, String>> requestHeaders,
363             @StreamPriority int priority,
364             boolean delayRequestHeadersUntilFirstFlush,
365             Collection<Object> connectionAnnotations,
366             boolean trafficStatsTagSet,
367             int trafficStatsTag,
368             boolean trafficStatsUidSet,
369             int trafficStatsUid,
370             long networkHandle) {
371         if (networkHandle != DEFAULT_NETWORK_HANDLE) {
372             throw new UnsupportedOperationException(
373                     "The multi-network API is not supported by the Fake implementation "
374                             + "of Cronet Engine");
375         }
376         synchronized (mLock) {
377             if (mIsShutdown) {
378                 throw new IllegalStateException(
379                         "This instance of CronetEngine has been shutdown and can no longer be "
380                                 + "used.");
381             }
382             throw new UnsupportedOperationException(
383                     "The BidirectionalStream API is not supported by the Fake implementation of "
384                             + "CronetEngine.");
385         }
386     }
387 
388     /**
389      * Mark request as started to prevent shutdown when there are active
390      * requests, only if the engine is not shutdown.
391      *
392      * @return true if the engine is not shutdown and the request is marked as started.
393      */
startRequest()394     boolean startRequest() {
395         synchronized (mLock) {
396             if (!mIsShutdown) {
397                 mActiveRequestCount++;
398                 mRunningRequestCount++;
399                 return true;
400             }
401             return false;
402         }
403     }
404 
405     /**
406      * Mark request as destroyed to allow shutdown when there are no running
407      * requests. Should be called *before* the terminal callback is called, so
408      * that users can call shutdown() from the terminal callback.
409      */
onRequestDestroyed()410     void onRequestDestroyed() {
411         synchronized (mLock) {
412             // Verification check. We should not be able to shutdown if there are still running
413             // requests.
414             if (mIsShutdown) {
415                 throw new IllegalStateException(
416                         "This instance of CronetEngine was shutdown. All requests must have been "
417                                 + "complete.");
418             }
419             mRunningRequestCount--;
420         }
421     }
422 
423     /**
424      * Mark request as finished for the purposes of getActiveRequestCount().
425      * Should be called *after* the terminal callback returns.
426      */
onRequestFinished()427     void onRequestFinished() {
428         synchronized (mLock) {
429             mActiveRequestCount--;
430         }
431     }
432 }
433