1 /*
2  * Copyright (C) ${year} The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.threadsample;
18 
19 import com.example.android.threadsample.PhotoDecodeRunnable.TaskRunnableDecodeMethods;
20 
21 import java.io.EOFException;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.net.HttpURLConnection;
25 import java.net.URL;
26 
27 /**
28  * This task downloads bytes from a resource addressed by a URL.  When the task
29  * has finished, it calls handleState to report its results.
30  *
31  * Objects of this class are instantiated and managed by instances of PhotoTask, which
32  * implements the methods of {@link TaskRunnableDecodeMethods}. PhotoTask objects call
33  * {@link #PhotoDownloadRunnable(TaskRunnableDownloadMethods) PhotoDownloadRunnable()} with
34  * themselves as the argument. In effect, an PhotoTask object and a
35  * PhotoDownloadRunnable object communicate through the fields of the PhotoTask.
36  */
37 class PhotoDownloadRunnable implements Runnable {
38     // Sets the size for each read action (bytes)
39     private static final int READ_SIZE = 1024 * 2;
40 
41     // Sets a tag for this class
42     @SuppressWarnings("unused")
43     private static final String LOG_TAG = "PhotoDownloadRunnable";
44 
45     // Constants for indicating the state of the download
46     static final int HTTP_STATE_FAILED = -1;
47     static final int HTTP_STATE_STARTED = 0;
48     static final int HTTP_STATE_COMPLETED = 1;
49 
50     // Defines a field that contains the calling object of type PhotoTask.
51     final TaskRunnableDownloadMethods mPhotoTask;
52 
53     /**
54      *
55      * An interface that defines methods that PhotoTask implements. An instance of
56      * PhotoTask passes itself to an PhotoDownloadRunnable instance through the
57      * PhotoDownloadRunnable constructor, after which the two instances can access each other's
58      * variables.
59      */
60     interface TaskRunnableDownloadMethods {
61 
62         /**
63          * Sets the Thread that this instance is running on
64          * @param currentThread the current Thread
65          */
setDownloadThread(Thread currentThread)66         void setDownloadThread(Thread currentThread);
67 
68         /**
69          * Returns the current contents of the download buffer
70          * @return The byte array downloaded from the URL in the last read
71          */
getByteBuffer()72         byte[] getByteBuffer();
73 
74         /**
75          * Sets the current contents of the download buffer
76          * @param buffer The bytes that were just read
77          */
setByteBuffer(byte[] buffer)78         void setByteBuffer(byte[] buffer);
79 
80         /**
81          * Defines the actions for each state of the PhotoTask instance.
82          * @param state The current state of the task
83          */
handleDownloadState(int state)84         void handleDownloadState(int state);
85 
86         /**
87          * Gets the URL for the image being downloaded
88          * @return The image URL
89          */
getImageURL()90         URL getImageURL();
91     }
92 
93     /**
94      * This constructor creates an instance of PhotoDownloadRunnable and stores in it a reference
95      * to the PhotoTask instance that instantiated it.
96      *
97      * @param photoTask The PhotoTask, which implements TaskRunnableDecodeMethods
98      */
PhotoDownloadRunnable(TaskRunnableDownloadMethods photoTask)99     PhotoDownloadRunnable(TaskRunnableDownloadMethods photoTask) {
100         mPhotoTask = photoTask;
101     }
102 
103     /*
104      * Defines this object's task, which is a set of instructions designed to be run on a Thread.
105      */
106     @SuppressWarnings("resource")
107     @Override
run()108     public void run() {
109 
110         /*
111          * Stores the current Thread in the the PhotoTask instance, so that the instance
112          * can interrupt the Thread.
113          */
114         mPhotoTask.setDownloadThread(Thread.currentThread());
115 
116         // Moves the current Thread into the background
117         android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
118 
119         /*
120          * Gets the image cache buffer object from the PhotoTask instance. This makes the
121          * to both PhotoDownloadRunnable and PhotoTask.
122          */
123         byte[] byteBuffer = mPhotoTask.getByteBuffer();
124 
125         /*
126          * A try block that downloads a Picasa image from a URL. The URL value is in the field
127          * PhotoTask.mImageURL
128          */
129         // Tries to download the picture from Picasa
130         try {
131             // Before continuing, checks to see that the Thread hasn't been
132             // interrupted
133             if (Thread.interrupted()) {
134 
135                 throw new InterruptedException();
136             }
137 
138             // If there's no cache buffer for this image
139             if (null == byteBuffer) {
140 
141                 /*
142                  * Calls the PhotoTask implementation of {@link #handleDownloadState} to
143                  * set the state of the download
144                  */
145                 mPhotoTask.handleDownloadState(HTTP_STATE_STARTED);
146 
147                 // Defines a handle for the byte download stream
148                 InputStream byteStream = null;
149 
150                 // Downloads the image and catches IO errors
151                 try {
152 
153                     // Opens an HTTP connection to the image's URL
154                     HttpURLConnection httpConn =
155                             (HttpURLConnection) mPhotoTask.getImageURL().openConnection();
156 
157                     // Sets the user agent to report to the server
158                     httpConn.setRequestProperty("User-Agent", Constants.USER_AGENT);
159 
160                     // Before continuing, checks to see that the Thread
161                     // hasn't been interrupted
162                     if (Thread.interrupted()) {
163 
164                         throw new InterruptedException();
165                     }
166                     // Gets the input stream containing the image
167                     byteStream = httpConn.getInputStream();
168 
169                     if (Thread.interrupted()) {
170 
171                         throw new InterruptedException();
172                     }
173                     /*
174                      * Gets the size of the file being downloaded. This
175                      * may or may not be returned.
176                      */
177                     int contentSize = httpConn.getContentLength();
178 
179                     /*
180                      * If the size of the image isn't available
181                      */
182                     if (-1 == contentSize) {
183 
184                         // Allocates a temporary buffer
185                         byte[] tempBuffer = new byte[READ_SIZE];
186 
187                         // Records the initial amount of available space
188                         int bufferLeft = tempBuffer.length;
189 
190                         /*
191                          * Defines the initial offset of the next available
192                          * byte in the buffer, and the initial result of
193                          * reading the binary
194                          */
195                         int bufferOffset = 0;
196                         int readResult = 0;
197 
198                         /*
199                          * The "outer" loop continues until all the bytes
200                          * have been downloaded. The inner loop continues
201                          * until the temporary buffer is full, and then
202                          * allocates more buffer space.
203                          */
204                         outer: do {
205                             while (bufferLeft > 0) {
206 
207                                 /*
208                                  * Reads from the URL location into
209                                  * the temporary buffer, starting at the
210                                  * next available free byte and reading as
211                                  * many bytes as are available in the
212                                  * buffer.
213                                  */
214                                 readResult = byteStream.read(tempBuffer, bufferOffset,
215                                         bufferLeft);
216 
217                                 /*
218                                  * InputStream.read() returns zero when the
219                                  * file has been completely read.
220                                  */
221                                 if (readResult < 0) {
222                                     // The read is finished, so this breaks
223                                     // the to "outer" loop
224                                     break outer;
225                                 }
226 
227                                 /*
228                                  * The read isn't finished. This sets the
229                                  * next available open position in the
230                                  * buffer (the buffer index is 0-based).
231                                  */
232                                 bufferOffset += readResult;
233 
234                                 // Subtracts the number of bytes read from
235                                 // the amount of buffer left
236                                 bufferLeft -= readResult;
237 
238                                 if (Thread.interrupted()) {
239 
240                                     throw new InterruptedException();
241                                 }
242                             }
243                             /*
244                              * The temporary buffer is full, so the
245                              * following code creates a new buffer that can
246                              * contain the existing contents plus the next
247                              * read cycle.
248                              */
249 
250                             // Resets the amount of buffer left to be the
251                             // max buffer size
252                             bufferLeft = READ_SIZE;
253 
254                             /*
255                              * Sets a new size that can contain the existing
256                              * buffer's contents plus space for the next
257                              * read cycle.
258                              */
259                             int newSize = tempBuffer.length + READ_SIZE;
260 
261                             /*
262                              * Creates a new temporary buffer, moves the
263                              * contents of the old temporary buffer into it,
264                              * and then points the temporary buffer variable
265                              * to the new buffer.
266                              */
267                             byte[] expandedBuffer = new byte[newSize];
268                             System.arraycopy(tempBuffer, 0, expandedBuffer, 0,
269                                     tempBuffer.length);
270                             tempBuffer = expandedBuffer;
271                         } while (true);
272 
273                         /*
274                          * When the entire image has been read, this creates
275                          * a permanent byte buffer with the same size as
276                          * the number of used bytes in the temporary buffer
277                          * (equal to the next open byte, because tempBuffer
278                          * is 0=based).
279                          */
280                         byteBuffer = new byte[bufferOffset];
281 
282                         // Copies the temporary buffer to the image buffer
283                         System.arraycopy(tempBuffer, 0, byteBuffer, 0, bufferOffset);
284 
285                         /*
286                          * The download size is available, so this creates a
287                          * permanent buffer of that length.
288                          */
289                     } else {
290                         byteBuffer = new byte[contentSize];
291 
292                         // How much of the buffer still remains empty
293                         int remainingLength = contentSize;
294 
295                         // The next open space in the buffer
296                         int bufferOffset = 0;
297 
298                         /*
299                          * Reads into the buffer until the number of bytes
300                          * equal to the length of the buffer (the size of
301                          * the image) have been read.
302                          */
303                         while (remainingLength > 0) {
304                             int readResult = byteStream.read(
305                                     byteBuffer,
306                                     bufferOffset,
307                                     remainingLength);
308                             /*
309                              * EOF should not occur, because the loop should
310                              * read the exact # of bytes in the image
311                              */
312                             if (readResult < 0) {
313 
314                                 // Throws an EOF Exception
315                                 throw new EOFException();
316                             }
317 
318                             // Moves the buffer offset to the next open byte
319                             bufferOffset += readResult;
320 
321                             // Subtracts the # of bytes read from the
322                             // remaining length
323                             remainingLength -= readResult;
324 
325                             if (Thread.interrupted()) {
326 
327                                 throw new InterruptedException();
328                             }
329                         }
330                     }
331 
332                     if (Thread.interrupted()) {
333 
334                         throw new InterruptedException();
335                     }
336 
337                     // If an IO error occurs, returns immediately
338                 } catch (IOException e) {
339                     e.printStackTrace();
340                     return;
341 
342                     /*
343                      * If the input stream is still open, close it
344                      */
345                 } finally {
346                     if (null != byteStream) {
347                         try {
348                             byteStream.close();
349                         } catch (Exception e) {
350 
351                         }
352                     }
353                 }
354             }
355 
356             /*
357              * Stores the downloaded bytes in the byte buffer in the PhotoTask instance.
358              */
359             mPhotoTask.setByteBuffer(byteBuffer);
360 
361             /*
362              * Sets the status message in the PhotoTask instance. This sets the
363              * ImageView background to indicate that the image is being
364              * decoded.
365              */
366             mPhotoTask.handleDownloadState(HTTP_STATE_COMPLETED);
367 
368         // Catches exceptions thrown in response to a queued interrupt
369         } catch (InterruptedException e1) {
370 
371             // Does nothing
372 
373         // In all cases, handle the results
374         } finally {
375 
376             // If the byteBuffer is null, reports that the download failed.
377             if (null == byteBuffer) {
378                 mPhotoTask.handleDownloadState(HTTP_STATE_FAILED);
379             }
380 
381             /*
382              * The implementation of setHTTPDownloadThread() in PhotoTask calls
383              * PhotoTask.setCurrentThread(), which then locks on the static ThreadPool
384              * object and returns the current thread. Locking keeps all references to Thread
385              * objects the same until the reference to the current Thread is deleted.
386              */
387 
388             // Sets the reference to the current Thread to null, releasing its storage
389             mPhotoTask.setDownloadThread(null);
390 
391             // Clears the Thread's interrupt flag
392             Thread.interrupted();
393         }
394     }
395 }
396 
397