Skip to content

Commit 66e5d59

Browse files
gsakakiharajeremyjiang-dev
authored andcommitted
Stop image download by canceling a Future. (#3426)
- Switched to stopping an image download by canceling a Future to interrupt the download thread instead of trying to close the InputStream directly since the underlying library does not appear to be thread safe and can throw a variety of Exceptions when close is called on a different thread (#1830 for a previous issue).
1 parent 8c889dc commit 66e5d59

File tree

5 files changed

+37
-31
lines changed

5 files changed

+37
-31
lines changed

firebase-messaging/src/main/java/com/google/firebase/messaging/DisplayNotification.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import com.google.firebase.messaging.Constants.MessageNotificationKeys;
3333
import java.util.List;
3434
import java.util.concurrent.ExecutionException;
35-
import java.util.concurrent.Executor;
35+
import java.util.concurrent.ExecutorService;
3636
import java.util.concurrent.TimeUnit;
3737
import java.util.concurrent.TimeoutException;
3838

@@ -46,12 +46,12 @@ class DisplayNotification {
4646

4747
private static final int IMAGE_DOWNLOAD_TIMEOUT_SECONDS = 5;
4848

49-
private final Executor networkIoExecutor;
49+
private final ExecutorService networkIoExecutor;
5050
private final Context context;
5151
private final NotificationParams params;
5252

5353
public DisplayNotification(
54-
Context context, NotificationParams params, Executor networkIoExecutor) {
54+
Context context, NotificationParams params, ExecutorService networkIoExecutor) {
5555
this.networkIoExecutor = networkIoExecutor;
5656
this.context = context;
5757
this.params = params;

firebase-messaging/src/main/java/com/google/firebase/messaging/ImageDownload.java

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,30 @@
2222
import android.util.Log;
2323
import androidx.annotation.Nullable;
2424
import com.google.android.gms.tasks.Task;
25-
import com.google.android.gms.tasks.Tasks;
25+
import com.google.android.gms.tasks.TaskCompletionSource;
2626
import com.google.common.io.ByteStreams;
27-
import com.google.common.io.Closeables;
2827
import java.io.Closeable;
2928
import java.io.IOException;
3029
import java.io.InputStream;
3130
import java.net.MalformedURLException;
3231
import java.net.URL;
3332
import java.net.URLConnection;
34-
import java.util.concurrent.Executor;
33+
import java.util.concurrent.ExecutorService;
34+
import java.util.concurrent.Future;
3535

36-
/** Abstraction around downloading an image in a background executor. */
37-
class ImageDownload implements Closeable {
36+
/**
37+
* Abstraction around downloading an image in a background executor.
38+
*
39+
* @hide
40+
*/
41+
public class ImageDownload implements Closeable {
3842

3943
/** Maximum image size to download in bytes (1 MiB). */
4044
private static final int MAX_IMAGE_SIZE_BYTES = 1024 * 1024;
4145

4246
private final URL url;
47+
@Nullable private volatile Future<?> future;
4348
@Nullable private Task<Bitmap> task;
44-
@Nullable private volatile InputStream connectionInputStream;
4549

4650
@Nullable
4751
public static ImageDownload create(String imageUrl) {
@@ -60,16 +64,29 @@ private ImageDownload(URL url) {
6064
this.url = url;
6165
}
6266

63-
public void start(Executor executor) {
64-
task = Tasks.call(executor, this::blockingDownload);
67+
public void start(ExecutorService executor) {
68+
TaskCompletionSource<Bitmap> taskCompletionSource = new TaskCompletionSource<>();
69+
future =
70+
executor.submit(
71+
() -> {
72+
try {
73+
Bitmap bitmap = blockingDownload();
74+
taskCompletionSource.setResult(bitmap);
75+
} catch (Exception e) {
76+
taskCompletionSource.setException(e);
77+
}
78+
});
79+
task = taskCompletionSource.getTask();
6580
}
6681

6782
public Task<Bitmap> getTask() {
6883
return checkNotNull(task); // will throw if getTask() is called without a call to start()
6984
}
7085

7186
public Bitmap blockingDownload() throws IOException {
72-
Log.i(TAG, "Starting download of: " + url);
87+
if (Log.isLoggable(TAG, Log.INFO)) {
88+
Log.i(TAG, "Starting download of: " + url);
89+
}
7390

7491
byte[] imageBytes = blockingDownloadBytes();
7592
Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, /* offset= */ 0, imageBytes.length);
@@ -96,9 +113,6 @@ private byte[] blockingDownloadBytes() throws IOException {
96113
// Now actually try to download the content
97114
byte[] bytes;
98115
try (InputStream connectionInputStream = connection.getInputStream()) {
99-
// Save to a field so that it can be closed on timeout
100-
this.connectionInputStream = connectionInputStream;
101-
102116
// Read one byte over the limit so we can tell if the data is too big, as in many cases
103117
// BitmapFactory will happily decode a partial image.
104118
bytes =
@@ -118,14 +132,6 @@ private byte[] blockingDownloadBytes() throws IOException {
118132

119133
@Override
120134
public void close() {
121-
// Close the stream to prevent downloaded any additional data. This will cause the input stream
122-
// to throw an IOException on read, which will finish the task.
123-
try {
124-
Closeables.closeQuietly(connectionInputStream);
125-
} catch (NullPointerException npe) {
126-
// Older versions of okio don't handle closing on a different thread than the one that it was
127-
// started on, so just catch it for now.
128-
Log.e(TAG, "Failed to close the image download stream.", npe);
129-
}
135+
future.cancel(true);
130136
}
131137
}

firebase-messaging/src/test/java/com/google/firebase/messaging/DisplayNotificationChannelRoboTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import androidx.test.core.app.ApplicationProvider;
3131
import java.util.ArrayList;
3232
import java.util.List;
33-
import java.util.concurrent.Executor;
33+
import java.util.concurrent.ExecutorService;
3434
import java.util.concurrent.Executors;
3535
import org.junit.Before;
3636
import org.junit.Test;
@@ -54,7 +54,7 @@ public final class DisplayNotificationChannelRoboTest {
5454
Context context = ApplicationProvider.getApplicationContext();
5555

5656
NotificationManager notificationManager;
57-
Executor executor;
57+
ExecutorService executor;
5858

5959
@Before
6060
public void setup() throws Exception {
@@ -204,7 +204,7 @@ public void messageProvidedChannel_notExisting() throws Exception {
204204
}
205205

206206
private static DisplayNotification createDisplayNotification(
207-
Context context, Bundle message, Executor executor) {
207+
Context context, Bundle message, ExecutorService executor) {
208208
return new DisplayNotification(context, new NotificationParams(message), executor);
209209
}
210210

firebase-messaging/src/test/java/com/google/firebase/messaging/DisplayNotificationRoboTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
import java.util.HashSet;
5555
import java.util.List;
5656
import java.util.Set;
57-
import java.util.concurrent.Executor;
57+
import java.util.concurrent.ExecutorService;
5858
import java.util.concurrent.Executors;
5959
import org.json.JSONArray;
6060
import org.junit.Before;
@@ -127,7 +127,7 @@ public class DisplayNotificationRoboTest {
127127
private ActivityManager activityManager;
128128
private KeyguardManager keyguardManager;
129129
private NotificationManager notificationManager;
130-
private Executor executor;
130+
private ExecutorService executor;
131131

132132
@Before
133133
public void setUp() throws IOException {

firebase-messaging/src/test/java/com/google/firebase/messaging/ImageDownloadRoboTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import java.io.IOException;
2727
import java.util.Arrays;
2828
import java.util.concurrent.ExecutionException;
29-
import java.util.concurrent.Executor;
29+
import java.util.concurrent.ExecutorService;
3030
import java.util.concurrent.Executors;
3131
import java.util.concurrent.TimeUnit;
3232
import org.junit.Before;
@@ -46,7 +46,7 @@ public class ImageDownloadRoboTest {
4646

4747
@Rule public TestImageServer testImageServer = new TestImageServer();
4848

49-
private Executor executor;
49+
private ExecutorService executor;
5050

5151
@Before
5252
public void setUp() throws IOException {

0 commit comments

Comments
 (0)