CronetDataSource extends BaseDataSource implements HttpDataSource { @Override public long open(DataSpec dataSpec) throws HttpDataSourceException { Assertions.checkNotNull(dataSpec); Assertions.checkState(!opened); operation.close(); resetConnectTimeout(); currentDataSpec = dataSpec; UrlRequest urlRequest; try { urlRequest = buildRequestBuilder(dataSpec).build(); currentUrlRequest = urlRequest; } catch (IOException e) { throw new OpenException(e, dataSpec, Status.IDLE); } urlRequest.start(); transferInitializing(dataSpec); try { boolean connectionOpened = blockUntilConnectTimeout(); if (exception != null) { throw new OpenException(exception, dataSpec, getStatus(urlRequest)); } else if (!connectionOpened) { throw new OpenException(new SocketTimeoutException(), dataSpec, getStatus(urlRequest)); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new OpenException(new InterruptedIOException(e), dataSpec, Status.INVALID); } UrlResponseInfo responseInfo = Assertions.checkNotNull(this.responseInfo); int responseCode = responseInfo.getHttpStatusCode(); if (responseCode < 200 || responseCode > 299) { InvalidResponseCodeException exception = new InvalidResponseCodeException( responseCode, responseInfo.getHttpStatusText(), responseInfo.getAllHeaders(), dataSpec); if (responseCode == 416) { exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); } throw exception; } Predicate<String> contentTypePredicate = this.contentTypePredicate; if (contentTypePredicate != null) { List<String> contentTypeHeaders = responseInfo.getAllHeaders().get(CONTENT_TYPE); String contentType = isEmpty(contentTypeHeaders) ? null : contentTypeHeaders.get(0); if (contentType != null && !contentTypePredicate.evaluate(contentType)) { throw new InvalidContentTypeException(contentType, dataSpec); } } bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0; if (!isCompressed(responseInfo)) { if (dataSpec.length != C.LENGTH_UNSET) { bytesRemaining = dataSpec.length; } else { bytesRemaining = getContentLength(responseInfo); } } else { bytesRemaining = dataSpec.length; } opened = true; transferStarted(dataSpec); return bytesRemaining; } CronetDataSource(CronetEngine cronetEngine, Executor executor);  CronetDataSource(
      CronetEngine cronetEngine,
      Executor executor,
      int connectTimeoutMs,
      int readTimeoutMs,
      boolean resetTimeoutOnRedirects,
      @Nullable RequestProperties defaultRequestProperties);  CronetDataSource(
      CronetEngine cronetEngine,
      Executor executor,
      int connectTimeoutMs,
      int readTimeoutMs,
      boolean resetTimeoutOnRedirects,
      @Nullable RequestProperties defaultRequestProperties,
      boolean handleSetCookieRequests); @Deprecated  CronetDataSource(
      CronetEngine cronetEngine,
      Executor executor,
      @Nullable Predicate<String> contentTypePredicate); @Deprecated  CronetDataSource(
      CronetEngine cronetEngine,
      Executor executor,
      @Nullable Predicate<String> contentTypePredicate,
      int connectTimeoutMs,
      int readTimeoutMs,
      boolean resetTimeoutOnRedirects,
      @Nullable RequestProperties defaultRequestProperties); @Deprecated  CronetDataSource(
      CronetEngine cronetEngine,
      Executor executor,
      @Nullable Predicate<String> contentTypePredicate,
      int connectTimeoutMs,
      int readTimeoutMs,
      boolean resetTimeoutOnRedirects,
      @Nullable RequestProperties defaultRequestProperties,
      boolean handleSetCookieRequests);   CronetDataSource(
      CronetEngine cronetEngine,
      Executor executor,
      int connectTimeoutMs,
      int readTimeoutMs,
      boolean resetTimeoutOnRedirects,
      Clock clock,
      @Nullable RequestProperties defaultRequestProperties,
      boolean handleSetCookieRequests); void setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate); @Override void setRequestProperty(String name, String value); @Override void clearRequestProperty(String name); @Override void clearAllRequestProperties(); @Override int getResponseCode(); @Override Map<String, List<String>> getResponseHeaders(); @Override @Nullable Uri getUri(); @Override long open(DataSpec dataSpec); @Override int read(byte[] buffer, int offset, int readLength); int read(ByteBuffer buffer); @Override synchronized void close(); static final int DEFAULT_CONNECT_TIMEOUT_MILLIS; static final int DEFAULT_READ_TIMEOUT_MILLIS; }
@Test public void testRequestSetsRangeHeader() throws HttpDataSourceException { testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); mockResponseStartSuccess(); dataSourceUnderTest.open(testDataSpec); verify(mockUrlRequestBuilder).addHeader("Range", "bytes=1000-5999"); }
@Test public void testRequestOpen() throws HttpDataSourceException { mockResponseStartSuccess(); assertThat(dataSourceUnderTest.open(testDataSpec)).isEqualTo(TEST_CONTENT_LENGTH); verify(mockTransferListener) .onTransferStart(dataSourceUnderTest, testDataSpec, true); }
@Test public void testRequestOpenGzippedCompressedReturnsDataSpecLength() throws HttpDataSourceException { testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, 5000, null); testResponseHeader.put("Content-Encoding", "gzip"); testResponseHeader.put("Content-Length", Long.toString(50L)); mockResponseStartSuccess(); assertThat(dataSourceUnderTest.open(testDataSpec)).isEqualTo(5000 ); verify(mockTransferListener) .onTransferStart(dataSourceUnderTest, testDataSpec, true); }
@Test public void testRequestOpenFail() { mockResponseStartFailure(); try { dataSourceUnderTest.open(testDataSpec); fail("HttpDataSource.HttpDataSourceException expected"); } catch (HttpDataSourceException e) { assertThat(e.getCause() instanceof UnknownHostException).isFalse(); verify(mockUrlRequest, never()).cancel(); verify(mockTransferListener, never()) .onTransferStart(dataSourceUnderTest, testDataSpec, true); } }
@Test public void open_ifBodyIsSetWithoutContentTypeHeader_fails() { testDataSpec = new DataSpec( Uri.parse(TEST_URL), new byte[1024], 200, 200, 1024, "key", 0); try { dataSourceUnderTest.open(testDataSpec); fail(); } catch (IOException expected) { } }
@Test public void testRequestOpenFailDueToDnsFailure() { mockResponseStartFailure(); when(mockNetworkException.getErrorCode()) .thenReturn(NetworkException.ERROR_HOSTNAME_NOT_RESOLVED); try { dataSourceUnderTest.open(testDataSpec); fail("HttpDataSource.HttpDataSourceException expected"); } catch (HttpDataSourceException e) { assertThat(e.getCause() instanceof UnknownHostException).isTrue(); verify(mockUrlRequest, never()).cancel(); verify(mockTransferListener, never()) .onTransferStart(dataSourceUnderTest, testDataSpec, true); } }
@Test public void testRequestOpenValidatesStatusCode() { mockResponseStartSuccess(); testUrlResponseInfo = createUrlResponseInfo(500); try { dataSourceUnderTest.open(testDataSpec); fail("HttpDataSource.HttpDataSourceException expected"); } catch (HttpDataSourceException e) { assertThat(e instanceof HttpDataSource.InvalidResponseCodeException).isTrue(); verify(mockUrlRequest, never()).cancel(); verify(mockTransferListener, never()) .onTransferStart(dataSourceUnderTest, testDataSpec, true); } }
@Test public void testPostRequestOpenValidatesContentType() { mockResponseStartSuccess(); try { dataSourceUnderTest.open(testPostDataSpec); fail("HttpDataSource.HttpDataSourceException expected"); } catch (HttpDataSourceException e) { verify(mockUrlRequest, never()).start(); } }
@Test public void testConnectTimeout() throws InterruptedException { long startTimeMs = SystemClock.elapsedRealtime(); final ConditionVariable startCondition = buildUrlRequestStartedCondition(); final CountDownLatch timedOutLatch = new CountDownLatch(1); new Thread() { @Override public void run() { try { dataSourceUnderTest.open(testDataSpec); fail(); } catch (HttpDataSourceException e) { assertThat(e instanceof CronetDataSource.OpenException).isTrue(); assertThat(e.getCause() instanceof SocketTimeoutException).isTrue(); assertThat(((CronetDataSource.OpenException) e).cronetConnectionStatus) .isEqualTo(TEST_CONNECTION_STATUS); timedOutLatch.countDown(); } } }.start(); startCondition.block(); assertNotCountedDown(timedOutLatch); SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); assertNotCountedDown(timedOutLatch); SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS + 10); timedOutLatch.await(); verify(mockTransferListener, never()) .onTransferStart(dataSourceUnderTest, testDataSpec, true); }
@Test public void testConnectInterrupted() throws InterruptedException { long startTimeMs = SystemClock.elapsedRealtime(); final ConditionVariable startCondition = buildUrlRequestStartedCondition(); final CountDownLatch timedOutLatch = new CountDownLatch(1); Thread thread = new Thread() { @Override public void run() { try { dataSourceUnderTest.open(testDataSpec); fail(); } catch (HttpDataSourceException e) { assertThat(e instanceof CronetDataSource.OpenException).isTrue(); assertThat(e.getCause() instanceof CronetDataSource.InterruptedIOException).isTrue(); assertThat(((CronetDataSource.OpenException) e).cronetConnectionStatus) .isEqualTo(TEST_INVALID_CONNECTION_STATUS); timedOutLatch.countDown(); } } }; thread.start(); startCondition.block(); assertNotCountedDown(timedOutLatch); SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); assertNotCountedDown(timedOutLatch); thread.interrupt(); timedOutLatch.await(); verify(mockTransferListener, never()) .onTransferStart(dataSourceUnderTest, testDataSpec, true); }
@Test public void testConnectResponseBeforeTimeout() throws Exception { long startTimeMs = SystemClock.elapsedRealtime(); final ConditionVariable startCondition = buildUrlRequestStartedCondition(); final CountDownLatch openLatch = new CountDownLatch(1); AtomicReference<Exception> exceptionOnTestThread = new AtomicReference<>(); new Thread() { @Override public void run() { try { dataSourceUnderTest.open(testDataSpec); } catch (HttpDataSourceException e) { exceptionOnTestThread.set(e); } finally { openLatch.countDown(); } } }.start(); startCondition.block(); assertNotCountedDown(openLatch); SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); assertNotCountedDown(openLatch); dataSourceUnderTest.urlRequestCallback.onResponseStarted(mockUrlRequest, testUrlResponseInfo); openLatch.await(); assertThat(exceptionOnTestThread.get()).isNull(); }
@Test public void testRedirectIncreasesConnectionTimeout() throws Exception { long startTimeMs = SystemClock.elapsedRealtime(); final ConditionVariable startCondition = buildUrlRequestStartedCondition(); final CountDownLatch timedOutLatch = new CountDownLatch(1); final AtomicInteger openExceptions = new AtomicInteger(0); new Thread() { @Override public void run() { try { dataSourceUnderTest.open(testDataSpec); fail(); } catch (HttpDataSourceException e) { assertThat(e instanceof CronetDataSource.OpenException).isTrue(); assertThat(e.getCause() instanceof SocketTimeoutException).isTrue(); openExceptions.getAndIncrement(); timedOutLatch.countDown(); } } }.start(); startCondition.block(); assertNotCountedDown(timedOutLatch); SystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); assertNotCountedDown(timedOutLatch); dataSourceUnderTest.urlRequestCallback.onRedirectReceived( mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl1"); long newTimeoutMs = 2 * TEST_CONNECT_TIMEOUT_MS - 1; SystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs - 1); assertNotCountedDown(timedOutLatch); dataSourceUnderTest.urlRequestCallback.onRedirectReceived( mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl2"); newTimeoutMs = 3 * TEST_CONNECT_TIMEOUT_MS - 2; SystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs - 1); assertNotCountedDown(timedOutLatch); SystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs + 10); timedOutLatch.await(); verify(mockTransferListener, never()) .onTransferStart(dataSourceUnderTest, testDataSpec, true); assertThat(openExceptions.get()).isEqualTo(1); }
@Test public void testRedirectParseAndAttachCookie_dataSourceDoesNotHandleSetCookie_followsRedirect() throws HttpDataSourceException { mockSingleRedirectSuccess(); mockFollowRedirectSuccess(); testResponseHeader.put("Set-Cookie", "testcookie=testcookie; Path=/video"); dataSourceUnderTest.open(testDataSpec); verify(mockUrlRequestBuilder, never()).addHeader(eq("Cookie"), any(String.class)); verify(mockUrlRequest).followRedirect(); }
@Test public void testRedirectNoSetCookieFollowsRedirect() throws HttpDataSourceException { mockSingleRedirectSuccess(); mockFollowRedirectSuccess(); dataSourceUnderTest.open(testDataSpec); verify(mockUrlRequestBuilder, never()).addHeader(eq("Cookie"), any(String.class)); verify(mockUrlRequest).followRedirect(); }
@Test public void testRedirectNoSetCookieFollowsRedirect_dataSourceHandlesSetCookie() throws HttpDataSourceException { dataSourceUnderTest = new CronetDataSource( mockCronetEngine, mockExecutor, TEST_CONNECT_TIMEOUT_MS, TEST_READ_TIMEOUT_MS, true, Clock.DEFAULT, null, true); dataSourceUnderTest.addTransferListener(mockTransferListener); mockSingleRedirectSuccess(); mockFollowRedirectSuccess(); dataSourceUnderTest.open(testDataSpec); verify(mockUrlRequestBuilder, never()).addHeader(eq("Cookie"), any(String.class)); verify(mockUrlRequest).followRedirect(); }
@Test public void testAllowDirectExecutor() throws HttpDataSourceException { testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); mockResponseStartSuccess(); dataSourceUnderTest.open(testDataSpec); verify(mockUrlRequestBuilder).allowDirectExecutor(); }
@Test public void testOpeningTwiceThrows() throws HttpDataSourceException { mockResponseStartSuccess(); dataSourceUnderTest.open(testDataSpec); try { dataSourceUnderTest.open(testDataSpec); fail("Expected IllegalStateException."); } catch (IllegalStateException e) { } }
@Test public void testRequestStartCalled() throws HttpDataSourceException { mockResponseStartSuccess(); dataSourceUnderTest.open(testDataSpec); verify(mockCronetEngine) .newUrlRequestBuilder(eq(TEST_URL), any(UrlRequest.Callback.class), any(Executor.class)); verify(mockUrlRequest).start(); }