1 // Copyright 2015 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.impl; 6 7 import static android.os.Process.THREAD_PRIORITY_BACKGROUND; 8 import static android.os.Process.THREAD_PRIORITY_MORE_FAVORABLE; 9 10 import android.content.Context; 11 import android.os.Build; 12 import android.util.Log; 13 14 import org.chromium.net.BidirectionalStream; 15 import org.chromium.net.ExperimentalBidirectionalStream; 16 import org.chromium.net.NetworkQualityRttListener; 17 import org.chromium.net.NetworkQualityThroughputListener; 18 import org.chromium.net.RequestFinishedInfo; 19 import org.chromium.net.UrlRequest; 20 import org.chromium.net.impl.CronetLogger.CronetEngineBuilderInfo; 21 import org.chromium.net.impl.CronetLogger.CronetSource; 22 import org.chromium.net.impl.CronetLogger.CronetVersion; 23 24 import java.io.IOException; 25 import java.net.Proxy; 26 import java.net.URL; 27 import java.net.URLConnection; 28 import java.net.URLStreamHandler; 29 import java.net.URLStreamHandlerFactory; 30 import java.util.Collection; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.concurrent.Executor; 34 import java.util.concurrent.ExecutorService; 35 import java.util.concurrent.Executors; 36 import java.util.concurrent.LinkedBlockingQueue; 37 import java.util.concurrent.ThreadFactory; 38 import java.util.concurrent.ThreadPoolExecutor; 39 import java.util.concurrent.TimeUnit; 40 import java.util.concurrent.atomic.AtomicInteger; 41 42 /** 43 * {@link java.net.HttpURLConnection} backed CronetEngine. 44 * 45 * <p>Does not support netlogs, transferred data measurement, bidistream, cache, or priority. 46 */ 47 public final class JavaCronetEngine extends CronetEngineBase { 48 private static final String TAG = JavaCronetEngine.class.getSimpleName(); 49 50 private final String mUserAgent; 51 private final ExecutorService mExecutorService; 52 private final int mCronetEngineId; 53 private final CronetLogger mLogger; 54 private final AtomicInteger mActiveRequestCount = new AtomicInteger(); 55 56 /** The network handle to be used for requests that do not explicitly specify one. **/ 57 private long mNetworkHandle = DEFAULT_NETWORK_HANDLE; 58 59 private final Context mContext; 60 JavaCronetEngine(CronetEngineBuilderImpl builder)61 public JavaCronetEngine(CronetEngineBuilderImpl builder) { 62 mContext = builder.getContext(); 63 mCronetEngineId = hashCode(); 64 // On android, all background threads (and all threads that are part 65 // of background processes) are put in a cgroup that is allowed to 66 // consume up to 5% of CPU - these worker threads spend the vast 67 // majority of their time waiting on I/O, so making them contend with 68 // background applications for a slice of CPU doesn't make much sense. 69 // We want to hurry up and get idle. 70 final int threadPriority = 71 builder.threadPriority(THREAD_PRIORITY_BACKGROUND + THREAD_PRIORITY_MORE_FAVORABLE); 72 this.mUserAgent = builder.getUserAgent(); 73 // For unbounded work queues, the effective maximum pool size is 74 // equivalent to the core pool size. 75 this.mExecutorService = 76 new ThreadPoolExecutor( 77 10, 78 10, 79 50, 80 TimeUnit.SECONDS, 81 new LinkedBlockingQueue<Runnable>(), 82 new ThreadFactory() { 83 @Override 84 public Thread newThread(final Runnable r) { 85 return Executors.defaultThreadFactory() 86 .newThread( 87 new Runnable() { 88 @Override 89 public void run() { 90 Thread.currentThread() 91 .setName("JavaCronetEngine"); 92 android.os.Process.setThreadPriority( 93 threadPriority); 94 r.run(); 95 } 96 }); 97 } 98 }); 99 mLogger = CronetLoggerFactory.createNoOpLogger(); 100 try { 101 mLogger.logCronetEngineCreation( 102 mCronetEngineId, 103 new CronetEngineBuilderInfo(builder), 104 buildCronetVersion(), 105 CronetSource.CRONET_SOURCE_FALLBACK); 106 } catch (RuntimeException e) { 107 // Handle any issue gracefully, we should never crash due failures while logging. 108 Log.e(TAG, "Error while trying to log JavaCronetEngine creation: ", e); 109 } 110 Log.w( 111 TAG, 112 "using the fallback Cronet Engine implementation. Performance will suffer " 113 + "and many HTTP client features, including caching, will not work."); 114 } 115 116 /** Increment the number of active requests. */ incrementActiveRequestCount()117 void incrementActiveRequestCount() { 118 mActiveRequestCount.incrementAndGet(); 119 } 120 121 /** Decrement the number of active requests. */ decrementActiveRequestCount()122 void decrementActiveRequestCount() { 123 mActiveRequestCount.decrementAndGet(); 124 } 125 getCronetEngineId()126 int getCronetEngineId() { 127 return mCronetEngineId; 128 } 129 getCronetLogger()130 CronetLogger getCronetLogger() { 131 return mLogger; 132 } 133 getContext()134 Context getContext() { 135 return mContext; 136 } 137 138 @Override createRequest( String url, UrlRequest.Callback callback, Executor executor, 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)139 public UrlRequestBase createRequest( 140 String url, 141 UrlRequest.Callback callback, 142 Executor executor, 143 int priority, 144 Collection<Object> connectionAnnotations, 145 boolean disableCache, 146 boolean disableConnectionMigration, 147 boolean allowDirectExecutor, 148 boolean trafficStatsTagSet, 149 int trafficStatsTag, 150 boolean trafficStatsUidSet, 151 int trafficStatsUid, 152 RequestFinishedInfo.Listener requestFinishedListener, 153 int idempotency, 154 long networkHandle) { 155 if (networkHandle != DEFAULT_NETWORK_HANDLE) { 156 mNetworkHandle = networkHandle; 157 } 158 return new JavaUrlRequest( 159 this, 160 callback, 161 mExecutorService, 162 executor, 163 url, 164 mUserAgent, 165 allowDirectExecutor, 166 trafficStatsTagSet, 167 trafficStatsTag, 168 trafficStatsUidSet, 169 trafficStatsUid, 170 mNetworkHandle); 171 } 172 173 @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)174 protected ExperimentalBidirectionalStream createBidirectionalStream( 175 String url, 176 BidirectionalStream.Callback callback, 177 Executor executor, 178 String httpMethod, 179 List<Map.Entry<String, String>> requestHeaders, 180 @StreamPriority int priority, 181 boolean delayRequestHeadersUntilFirstFlush, 182 Collection<Object> connectionAnnotations, 183 boolean trafficStatsTagSet, 184 int trafficStatsTag, 185 boolean trafficStatsUidSet, 186 int trafficStatsUid, 187 long networkHandle) { 188 throw new UnsupportedOperationException( 189 "Can't create a bidi stream - httpurlconnection doesn't have those APIs"); 190 } 191 192 @Override newBidirectionalStreamBuilder( String url, BidirectionalStream.Callback callback, Executor executor)193 public ExperimentalBidirectionalStream.Builder newBidirectionalStreamBuilder( 194 String url, BidirectionalStream.Callback callback, Executor executor) { 195 throw new UnsupportedOperationException( 196 "The bidirectional stream API is not supported by the Java implementation " 197 + "of Cronet Engine"); 198 } 199 200 @Override getVersionString()201 public String getVersionString() { 202 return "CronetHttpURLConnection/" + ImplVersion.getCronetVersionWithLastChange(); 203 } 204 buildCronetVersion()205 private CronetVersion buildCronetVersion() { 206 String version = getVersionString(); 207 // getVersionString()'s output looks like "Cronet/w.x.y.z@hash". CronetVersion only cares 208 // about the "w.x.y.z" bit. 209 version = version.split("/")[1]; 210 version = version.split("@")[0]; 211 return new CronetVersion(version); 212 } 213 214 @Override shutdown()215 public void shutdown() { 216 mExecutorService.shutdown(); 217 } 218 219 @Override startNetLogToFile(String fileName, boolean logAll)220 public void startNetLogToFile(String fileName, boolean logAll) {} 221 222 @Override startNetLogToDisk(String dirPath, boolean logAll, int maxSize)223 public void startNetLogToDisk(String dirPath, boolean logAll, int maxSize) {} 224 225 @Override stopNetLog()226 public void stopNetLog() {} 227 228 @Override getGlobalMetricsDeltas()229 public byte[] getGlobalMetricsDeltas() { 230 return new byte[0]; 231 } 232 233 @Override getEffectiveConnectionType()234 public int getEffectiveConnectionType() { 235 return EFFECTIVE_CONNECTION_TYPE_UNKNOWN; 236 } 237 238 @Override getHttpRttMs()239 public int getHttpRttMs() { 240 return CONNECTION_METRIC_UNKNOWN; 241 } 242 243 @Override getTransportRttMs()244 public int getTransportRttMs() { 245 return CONNECTION_METRIC_UNKNOWN; 246 } 247 248 @Override getDownstreamThroughputKbps()249 public int getDownstreamThroughputKbps() { 250 return CONNECTION_METRIC_UNKNOWN; 251 } 252 253 @Override getActiveRequestCount()254 public int getActiveRequestCount() { 255 return mActiveRequestCount.get(); 256 } 257 258 @Override bindToNetwork(long networkHandle)259 public void bindToNetwork(long networkHandle) { 260 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 261 throw new UnsupportedOperationException( 262 "This multi-network Java implementation is available starting from Android Pie"); 263 } 264 mNetworkHandle = networkHandle; 265 } 266 267 @Override configureNetworkQualityEstimatorForTesting( boolean useLocalHostRequests, boolean useSmallerResponses, boolean disableOfflineCheck)268 public void configureNetworkQualityEstimatorForTesting( 269 boolean useLocalHostRequests, 270 boolean useSmallerResponses, 271 boolean disableOfflineCheck) {} 272 273 @Override addRttListener(NetworkQualityRttListener listener)274 public void addRttListener(NetworkQualityRttListener listener) {} 275 276 @Override removeRttListener(NetworkQualityRttListener listener)277 public void removeRttListener(NetworkQualityRttListener listener) {} 278 279 @Override addThroughputListener(NetworkQualityThroughputListener listener)280 public void addThroughputListener(NetworkQualityThroughputListener listener) {} 281 282 @Override removeThroughputListener(NetworkQualityThroughputListener listener)283 public void removeThroughputListener(NetworkQualityThroughputListener listener) {} 284 285 @Override addRequestFinishedListener(RequestFinishedInfo.Listener listener)286 public void addRequestFinishedListener(RequestFinishedInfo.Listener listener) {} 287 288 @Override removeRequestFinishedListener(RequestFinishedInfo.Listener listener)289 public void removeRequestFinishedListener(RequestFinishedInfo.Listener listener) {} 290 291 @Override openConnection(URL url)292 public URLConnection openConnection(URL url) throws IOException { 293 return url.openConnection(); 294 } 295 296 @Override openConnection(URL url, Proxy proxy)297 public URLConnection openConnection(URL url, Proxy proxy) throws IOException { 298 return url.openConnection(proxy); 299 } 300 301 @Override createURLStreamHandlerFactory()302 public URLStreamHandlerFactory createURLStreamHandlerFactory() { 303 // Returning null causes this factory to pass though, which ends up using the platform's 304 // implementation. 305 return new URLStreamHandlerFactory() { 306 @Override 307 public URLStreamHandler createURLStreamHandler(String protocol) { 308 return null; 309 } 310 }; 311 } 312 } 313