Implement DownloadClient using HttpURLConnection
The version of OkHttp used in AOSP doesn't handle dynamic table size updates [1] properly [2]. Instead of fixing OkHttp or importing a prebuilt updated version, implement a new download client only using HttpURLConnection, which seems to work properly. [1] https://tools.ietf.org/html/rfc7541#section-6.3 [2] https://trac.nginx.org/nginx/ticket/1397 Change-Id: I3eedf7326f2017812c4a12d41f9ea028d255f7a8
This commit is contained in:
@@ -12,8 +12,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
android-support-v7-appcompat \
|
||||
android-support-v7-cardview \
|
||||
android-support-v7-preference \
|
||||
android-support-v7-recyclerview \
|
||||
okhttp
|
||||
android-support-v7-recyclerview
|
||||
|
||||
LOCAL_RESOURCE_DIR := \
|
||||
$(TOP)/frameworks/support/design/res \
|
||||
|
@@ -385,11 +385,18 @@ public class UpdatesActivity extends UpdatesListActivity {
|
||||
}
|
||||
};
|
||||
|
||||
final DownloadClient downloadClient = new DownloadClient.Builder()
|
||||
final DownloadClient downloadClient;
|
||||
try {
|
||||
downloadClient = new DownloadClient.Builder()
|
||||
.setUrl(url)
|
||||
.setDestination(jsonFileTmp)
|
||||
.setDownloadCallback(callback)
|
||||
.build();
|
||||
} catch (IOException exception) {
|
||||
Log.e(TAG, "Could not build download client");
|
||||
showSnackbar(R.string.snack_updates_check_failed, Snackbar.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
|
@@ -105,12 +105,17 @@ public class UpdatesCheckReceiver extends BroadcastReceiver {
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
DownloadClient downloadClient = new DownloadClient.Builder()
|
||||
.setUrl(url)
|
||||
.setDestination(jsonNew)
|
||||
.setDownloadCallback(callback)
|
||||
.build();
|
||||
downloadClient.start();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Could not fetch list, scheduling new check", e);
|
||||
scheduleUpdatesCheck(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static void showNotification(Context context) {
|
||||
|
@@ -31,6 +31,7 @@ import org.lineageos.updater.model.UpdateInfo;
|
||||
import org.lineageos.updater.model.UpdateStatus;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -365,12 +366,20 @@ public class UpdaterController implements Controller {
|
||||
Log.d(TAG, "Changing name with " + destination.getName());
|
||||
}
|
||||
update.setFile(destination);
|
||||
DownloadClient downloadClient = new DownloadClient.Builder()
|
||||
DownloadClient downloadClient;
|
||||
try {
|
||||
downloadClient = new DownloadClient.Builder()
|
||||
.setUrl(update.getDownloadUrl())
|
||||
.setDestination(update.getFile())
|
||||
.setDownloadCallback(getDownloadCallback(downloadId))
|
||||
.setProgressListener(getProgressListener(downloadId))
|
||||
.build();
|
||||
} catch (IOException exception) {
|
||||
Log.e(TAG, "Could not build download client");
|
||||
update.setStatus(UpdateStatus.PAUSED_ERROR);
|
||||
notifyUpdateChange(downloadId);
|
||||
return false;
|
||||
}
|
||||
addDownloadClient(mDownloads.get(downloadId), downloadClient);
|
||||
update.setStatus(UpdateStatus.STARTING);
|
||||
notifyUpdateChange(downloadId);
|
||||
@@ -399,12 +408,20 @@ public class UpdaterController implements Controller {
|
||||
verifyUpdateAsync(downloadId);
|
||||
notifyUpdateChange(downloadId);
|
||||
} else {
|
||||
DownloadClient downloadClient = new DownloadClient.Builder()
|
||||
DownloadClient downloadClient;
|
||||
try {
|
||||
downloadClient = new DownloadClient.Builder()
|
||||
.setUrl(update.getDownloadUrl())
|
||||
.setDestination(update.getFile())
|
||||
.setDownloadCallback(getDownloadCallback(downloadId))
|
||||
.setProgressListener(getProgressListener(downloadId))
|
||||
.build();
|
||||
} catch (IOException exception) {
|
||||
Log.e(TAG, "Could not build download client");
|
||||
update.setStatus(UpdateStatus.PAUSED_ERROR);
|
||||
notifyUpdateChange(downloadId);
|
||||
return false;
|
||||
}
|
||||
addDownloadClient(mDownloads.get(downloadId), downloadClient);
|
||||
update.setStatus(UpdateStatus.STARTING);
|
||||
notifyUpdateChange(downloadId);
|
||||
|
@@ -16,6 +16,7 @@
|
||||
package org.lineageos.updater.download;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -63,7 +64,7 @@ public interface DownloadClient {
|
||||
private DownloadClient.DownloadCallback mCallback;
|
||||
private DownloadClient.ProgressListener mProgressListener;
|
||||
|
||||
public DownloadClient build() {
|
||||
public DownloadClient build() throws IOException {
|
||||
if (mUrl == null) {
|
||||
throw new IllegalStateException("No download URL defined");
|
||||
} else if (mDestination == null) {
|
||||
@@ -71,7 +72,7 @@ public interface DownloadClient {
|
||||
} else if (mCallback == null) {
|
||||
throw new IllegalStateException("No download callback defined");
|
||||
}
|
||||
return new OkHttpDownloadClient(mUrl, mDestination, mProgressListener, mCallback);
|
||||
return new HttpURLConnectionClient(mUrl, mDestination, mProgressListener, mCallback);
|
||||
}
|
||||
|
||||
public Builder setUrl(String url) {
|
||||
|
211
src/org/lineageos/updater/download/HttpURLConnectionClient.java
Normal file
211
src/org/lineageos/updater/download/HttpURLConnectionClient.java
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.lineageos.updater.download;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpURLConnectionClient implements DownloadClient {
|
||||
|
||||
private final static String TAG = "HttpURLConnectionClient";
|
||||
|
||||
private final HttpURLConnection mClient;
|
||||
|
||||
private final File mDestination;
|
||||
private final DownloadClient.ProgressListener mProgressListener;
|
||||
private final DownloadClient.DownloadCallback mCallback;
|
||||
|
||||
private DownloadThread mDownloadThread;
|
||||
|
||||
public class Headers implements DownloadClient.Headers {
|
||||
@Override
|
||||
public String get(String name) {
|
||||
return mClient.getHeaderField(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAll() {
|
||||
return mClient.getHeaderFields();
|
||||
}
|
||||
}
|
||||
|
||||
HttpURLConnectionClient(String url, File destination,
|
||||
DownloadClient.ProgressListener progressListener,
|
||||
DownloadClient.DownloadCallback callback) throws IOException {
|
||||
mClient = (HttpURLConnection) new URL(url).openConnection();
|
||||
mDestination = destination;
|
||||
mProgressListener = progressListener;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (mDownloadThread != null) {
|
||||
Log.e(TAG, "Already downloading");
|
||||
return;
|
||||
}
|
||||
downloadFileInternalCommon(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
if (mDownloadThread != null) {
|
||||
Log.e(TAG, "Already downloading");
|
||||
return;
|
||||
}
|
||||
downloadFileResumeInternal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (mDownloadThread == null) {
|
||||
Log.e(TAG, "Not downloading");
|
||||
return;
|
||||
}
|
||||
mDownloadThread.interrupt();
|
||||
mDownloadThread = null;
|
||||
}
|
||||
|
||||
private void downloadFileResumeInternal() {
|
||||
if (!mDestination.exists()) {
|
||||
mCallback.onFailure(false);
|
||||
return;
|
||||
}
|
||||
long offset = mDestination.length();
|
||||
mClient.setRequestProperty("Range", "bytes=" + offset + "-");
|
||||
downloadFileInternalCommon(true);
|
||||
}
|
||||
|
||||
private void downloadFileInternalCommon(boolean resume) {
|
||||
if (mDownloadThread != null) {
|
||||
Log.wtf(TAG, "Already downloading");
|
||||
return;
|
||||
}
|
||||
|
||||
mDownloadThread = new DownloadThread(resume);
|
||||
mDownloadThread.start();
|
||||
}
|
||||
|
||||
private static boolean isSuccessCode(int statusCode) {
|
||||
return (statusCode / 100) == 2;
|
||||
}
|
||||
|
||||
private static boolean isPartialContentCode(int statusCode) {
|
||||
return statusCode == 206;
|
||||
}
|
||||
|
||||
private class DownloadThread extends Thread {
|
||||
|
||||
private long mTotalBytes = 0;
|
||||
private long mTotalBytesRead = 0;
|
||||
|
||||
private long mCurSampleBytes = 0;
|
||||
private long mLastMillis = 0;
|
||||
private long mSpeed = -1;
|
||||
private long mEta = -1;
|
||||
|
||||
private final boolean mResume;
|
||||
|
||||
private DownloadThread(boolean resume) {
|
||||
mResume = resume;
|
||||
}
|
||||
|
||||
private void calculateSpeed() {
|
||||
final long millis = SystemClock.elapsedRealtime();
|
||||
final long delta = millis - mLastMillis;
|
||||
if (delta > 500) {
|
||||
final long curSpeed = ((mTotalBytesRead - mCurSampleBytes) * 1000) / delta;
|
||||
if (mSpeed == -1) {
|
||||
mSpeed = curSpeed;
|
||||
} else {
|
||||
mSpeed = ((mSpeed * 3) + curSpeed) / 4;
|
||||
}
|
||||
|
||||
mLastMillis = millis;
|
||||
mCurSampleBytes = mTotalBytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateEta() {
|
||||
if (mSpeed > 0) {
|
||||
mEta = (mTotalBytes - mTotalBytesRead) / mSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
mClient.connect();
|
||||
int responseCode = mClient.getResponseCode();
|
||||
mCallback.onResponse(responseCode, mClient.getURL().toString(), new Headers());
|
||||
|
||||
if (mResume && isPartialContentCode(responseCode)) {
|
||||
mTotalBytesRead = mDestination.length();
|
||||
Log.d(TAG, "The server fulfilled the partial content request");
|
||||
} else if (mResume || !isSuccessCode(responseCode)) {
|
||||
Log.e(TAG, "The server replied with code " + responseCode);
|
||||
mCallback.onFailure(isInterrupted());
|
||||
return;
|
||||
}
|
||||
|
||||
try (
|
||||
InputStream inputStream = mClient.getInputStream();
|
||||
OutputStream outputStream = new FileOutputStream(mDestination, mResume)
|
||||
) {
|
||||
mTotalBytes = mClient.getContentLength() + mTotalBytesRead;
|
||||
byte[] b = new byte[8192];
|
||||
int count;
|
||||
while (!isInterrupted() && (count = inputStream.read(b)) > 0) {
|
||||
outputStream.write(b, 0, count);
|
||||
mTotalBytesRead += count;
|
||||
calculateSpeed();
|
||||
calculateEta();
|
||||
if (mProgressListener != null) {
|
||||
mProgressListener.update(mTotalBytesRead, mTotalBytes, mSpeed, mEta,
|
||||
false);
|
||||
}
|
||||
}
|
||||
if (mProgressListener != null) {
|
||||
mProgressListener.update(mTotalBytesRead, mTotalBytes, mSpeed, mEta, true);
|
||||
}
|
||||
|
||||
outputStream.flush();
|
||||
|
||||
if (isInterrupted()) {
|
||||
mCallback.onFailure(true);
|
||||
} else {
|
||||
mCallback.onSuccess(mDestination);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error downloading file", e);
|
||||
mCallback.onFailure(isInterrupted());
|
||||
} finally {
|
||||
mClient.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,297 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.lineageos.updater.download;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.okhttp.Callback;
|
||||
import com.android.okhttp.Interceptor;
|
||||
import com.android.okhttp.MediaType;
|
||||
import com.android.okhttp.OkHttpClient;
|
||||
import com.android.okhttp.Request;
|
||||
import com.android.okhttp.Response;
|
||||
import com.android.okhttp.ResponseBody;
|
||||
import com.android.okhttp.okio.Buffer;
|
||||
import com.android.okhttp.okio.BufferedSink;
|
||||
import com.android.okhttp.okio.BufferedSource;
|
||||
import com.android.okhttp.okio.ForwardingSource;
|
||||
import com.android.okhttp.okio.Okio;
|
||||
import com.android.okhttp.okio.Source;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class OkHttpDownloadClient implements DownloadClient {
|
||||
|
||||
private static final String TAG = "DownloadClient";
|
||||
|
||||
private final Object DOWNLOAD_TAG = new Object();
|
||||
|
||||
private final OkHttpClient mClient = new OkHttpClient();
|
||||
|
||||
private final String mUrl;
|
||||
private final File mDestination;
|
||||
private final DownloadClient.ProgressListener mProgressListener;
|
||||
private final DownloadClient.DownloadCallback mCallback;
|
||||
private long mResumeOffset = 0;
|
||||
|
||||
private boolean mDownloading = false;
|
||||
private boolean mCancelled = false;
|
||||
|
||||
public class Headers implements DownloadClient.Headers {
|
||||
private com.android.okhttp.Headers mHeaders;
|
||||
|
||||
private Headers(com.android.okhttp.Headers headers) {
|
||||
mHeaders = headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(String name) {
|
||||
return mHeaders.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAll() {
|
||||
return mHeaders.toMultimap();
|
||||
}
|
||||
}
|
||||
|
||||
OkHttpDownloadClient(String url, File destination,
|
||||
DownloadClient.ProgressListener progressListener,
|
||||
DownloadClient.DownloadCallback callback) {
|
||||
mUrl = url;
|
||||
mDestination = destination;
|
||||
mProgressListener = progressListener;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (mDownloading) {
|
||||
Log.e(TAG, "Already downloading");
|
||||
return;
|
||||
}
|
||||
mCancelled = false;
|
||||
mDownloading = true;
|
||||
downloadFileInternal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
if (mDownloading) {
|
||||
Log.e(TAG, "Already downloading");
|
||||
return;
|
||||
}
|
||||
mCancelled = false;
|
||||
mDownloading = true;
|
||||
downloadFileResumeInternal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (!mDownloading) {
|
||||
Log.e(TAG, "Not downloading");
|
||||
return;
|
||||
}
|
||||
mDownloading = false;
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCancelled = true;
|
||||
mClient.cancel(DOWNLOAD_TAG);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void downloadFileInternal() {
|
||||
final Request request = new Request.Builder()
|
||||
.url(mUrl)
|
||||
.tag(DOWNLOAD_TAG)
|
||||
.build();
|
||||
downloadFileInternalCommon(request, false);
|
||||
}
|
||||
|
||||
private void downloadFileResumeInternal() {
|
||||
final Request.Builder requestBuilder = new Request.Builder()
|
||||
.url(mUrl)
|
||||
.tag(DOWNLOAD_TAG);
|
||||
if (!mDestination.exists()) {
|
||||
mCallback.onFailure(mCancelled);
|
||||
return;
|
||||
}
|
||||
long offset = mDestination.length();
|
||||
requestBuilder.addHeader("Range", "bytes=" + offset + "-");
|
||||
final Request request = requestBuilder.build();
|
||||
downloadFileInternalCommon(request, true);
|
||||
}
|
||||
|
||||
private static boolean isSuccessCode(int statusCode) {
|
||||
return (statusCode / 100) == 2;
|
||||
}
|
||||
|
||||
private static boolean isPartialContentCode(int statusCode) {
|
||||
return statusCode == 206;
|
||||
}
|
||||
|
||||
private void downloadFileInternalCommon(final Request request, final boolean resume) {
|
||||
|
||||
mClient.networkInterceptors().add(new Interceptor() {
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Response originalResponse = chain.proceed(chain.request());
|
||||
ProgressResponseBody progressResponseBody =
|
||||
new ProgressResponseBody(originalResponse.body(), mProgressListener);
|
||||
return originalResponse.newBuilder()
|
||||
.body(progressResponseBody)
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
mClient.newCall(request).enqueue(new Callback() {
|
||||
|
||||
@Override
|
||||
public void onFailure(Request request, IOException e) {
|
||||
Log.d(TAG, "Download failed", e);
|
||||
mCallback.onFailure(mCancelled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Response response) {
|
||||
Log.d(TAG, "Downloading");
|
||||
|
||||
final ResponseBody body = response.body();
|
||||
if (resume && isPartialContentCode(response.code())) {
|
||||
mResumeOffset = mDestination.length();
|
||||
Log.d(TAG, "The server fulfilled the partial content request");
|
||||
} else if (resume || !isSuccessCode(response.code())) {
|
||||
Log.e(TAG, "The server replied with code " + response.code());
|
||||
mCallback.onFailure(mCancelled);
|
||||
try {
|
||||
body.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Could not close response body", e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mCallback.onResponse(response.code(), response.request().urlString(),
|
||||
new Headers(response.headers()));
|
||||
try (BufferedSink sink = Okio.buffer(resume ?
|
||||
Okio.appendingSink(mDestination) : Okio.sink(mDestination))) {
|
||||
sink.writeAll(body.source());
|
||||
Log.d(TAG, "Download complete");
|
||||
sink.flush();
|
||||
mCallback.onSuccess(mDestination);
|
||||
} catch (IOException e) {
|
||||
onFailure(request, e);
|
||||
} finally {
|
||||
try {
|
||||
body.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Could not close response body", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class ProgressResponseBody extends ResponseBody {
|
||||
|
||||
private final ResponseBody mResponseBody;
|
||||
private final DownloadClient.ProgressListener mProgressListener;
|
||||
private BufferedSource mBufferedSource;
|
||||
|
||||
ProgressResponseBody(ResponseBody responseBody,
|
||||
DownloadClient.ProgressListener progressListener) {
|
||||
mResponseBody = responseBody;
|
||||
mProgressListener = progressListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return mResponseBody.contentType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() throws IOException {
|
||||
return mResponseBody.contentLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedSource source() throws IOException {
|
||||
if (mBufferedSource == null) {
|
||||
mBufferedSource = Okio.buffer(source(mResponseBody.source()));
|
||||
}
|
||||
return mBufferedSource;
|
||||
}
|
||||
|
||||
private Source source(Source source) {
|
||||
return new ForwardingSource(source) {
|
||||
private long mTotalBytes = 0;
|
||||
private long mTotalBytesRead = mResumeOffset;
|
||||
|
||||
private long mCurSampleBytes = 0;
|
||||
private long mLastMillis = 0;
|
||||
private long mSpeed = -1;
|
||||
private long mEta = -1;
|
||||
|
||||
private void calculateSpeed() {
|
||||
final long millis = SystemClock.elapsedRealtime();
|
||||
final long delta = millis - mLastMillis;
|
||||
if (delta > 500) {
|
||||
final long curSpeed = ((mTotalBytesRead - mCurSampleBytes) * 1000) / delta;
|
||||
if (mSpeed == -1) {
|
||||
mSpeed = curSpeed;
|
||||
} else {
|
||||
mSpeed = ((mSpeed * 3) + curSpeed) / 4;
|
||||
}
|
||||
|
||||
mLastMillis = millis;
|
||||
mCurSampleBytes = mTotalBytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateEta() {
|
||||
if (mSpeed > 0) {
|
||||
mEta = (mTotalBytes - mTotalBytesRead) / mSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long read(Buffer sink, long byteCount) throws IOException {
|
||||
long bytesRead = super.read(sink, byteCount);
|
||||
|
||||
mTotalBytes = mResponseBody.contentLength() + mResumeOffset;
|
||||
// read() returns the number of bytes read, or -1 if this source is exhausted.
|
||||
mTotalBytesRead += bytesRead != -1 ? bytesRead : 0;
|
||||
|
||||
calculateSpeed();
|
||||
calculateEta();
|
||||
|
||||
if (mProgressListener != null) {
|
||||
mProgressListener.update(mTotalBytesRead, mTotalBytes,
|
||||
mSpeed, mEta, bytesRead == -1);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user