1 /*
2  * Copyright (C) 2012 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 android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Bitmap;
22 import android.graphics.Canvas;
23 import android.graphics.drawable.Drawable;
24 import android.net.Uri;
25 import android.util.AttributeSet;
26 import android.view.View;
27 import android.widget.ImageView;
28 
29 
30 import java.lang.ref.WeakReference;
31 import java.net.URL;
32 
33 /**
34  * This class extends the standard Android ImageView View class with some features
35  * that are useful for downloading, decoding, and displaying Picasa images.
36  *
37  */
38 public class PhotoView extends ImageView {
39 
40     // Indicates if caching should be used
41     private boolean mCacheFlag;
42 
43     // Status flag that indicates if onDraw has completed
44     private boolean mIsDrawn;
45 
46     /*
47      * Creates a weak reference to the ImageView in this object. The weak
48      * reference prevents memory leaks and crashes, because it automatically tracks the "state" of
49      * the variable it backs. If the reference becomes invalid, the weak reference is garbage-
50      * collected.
51      * This technique is important for referring to objects that are part of a component lifecycle.
52      * Using a hard reference may cause memory leaks as the value continues to change; even worse,
53      * it can cause crashes if the underlying component is destroyed. Using a weak reference to
54      * a View ensures that the reference is more transitory in nature.
55      */
56     private WeakReference<View> mThisView;
57 
58     // Contains the ID of the internal View
59     private int mHideShowResId = -1;
60 
61     // The URL that points to the source of the image for this ImageView
62     private URL mImageURL;
63 
64     // The Thread that will be used to download the image for this ImageView
65     private PhotoTask mDownloadThread;
66 
67     /**
68      * Creates an ImageDownloadView with no settings
69      * @param context A context for the View
70      */
PhotoView(Context context)71     public PhotoView(Context context) {
72         super(context);
73     }
74 
75     /**
76      * Creates an ImageDownloadView and gets attribute values
77      * @param context A Context to use with the View
78      * @param attributeSet The entire set of attributes for the View
79      */
PhotoView(Context context, AttributeSet attributeSet)80     public PhotoView(Context context, AttributeSet attributeSet) {
81         super(context, attributeSet);
82 
83         // Gets attributes associated with the attribute set
84         getAttributes(attributeSet);
85     }
86 
87     /**
88      * Creates an ImageDownloadView, gets attribute values, and applies a default style
89      * @param context A context for the View
90      * @param attributeSet The entire set of attributes for the View
91      * @param defaultStyle The default style to use with the View
92      */
PhotoView(Context context, AttributeSet attributeSet, int defaultStyle)93     public PhotoView(Context context, AttributeSet attributeSet, int defaultStyle) {
94         super(context, attributeSet, defaultStyle);
95 
96         // Gets attributes associated with the attribute set
97         getAttributes(attributeSet);
98     }
99 
100     /**
101      * Gets the resource ID for the hideShowSibling resource
102      * @param attributeSet The entire set of attributes for the View
103      */
getAttributes(AttributeSet attributeSet)104     private void getAttributes(AttributeSet attributeSet) {
105 
106         // Gets an array of attributes for the View
107         TypedArray attributes =
108                 getContext().obtainStyledAttributes(attributeSet, R.styleable.ImageDownloaderView);
109 
110         // Gets the resource Id of the View to hide or show
111         mHideShowResId =
112                 attributes.getResourceId(R.styleable.ImageDownloaderView_hideShowSibling, -1);
113 
114         // Returns the array for re-use
115         attributes.recycle();
116     }
117 
118     /**
119      * Sets the visibility of the PhotoView
120      * @param visState The visibility state (see View.setVisibility)
121      */
showView(int visState)122     private void showView(int visState) {
123         // If the View contains something
124         if (mThisView != null) {
125 
126             // Gets a local hard reference to the View
127             View localView = mThisView.get();
128 
129             // If the weak reference actually contains something, set the visibility
130             if (localView != null)
131                 localView.setVisibility(visState);
132         }
133     }
134 
135     /**
136      * Sets the image in this ImageView to null, and makes the View visible
137      */
clearImage()138     public void clearImage() {
139         setImageDrawable(null);
140         showView(View.VISIBLE);
141     }
142 
143     /**
144      * Returns the URL of the picture associated with this ImageView
145      * @return a URL
146      */
getLocation()147     final URL getLocation() {
148         return mImageURL;
149     }
150 
151     /*
152      * This callback is invoked when the system attaches the ImageView to a Window. The callback
153      * is invoked before onDraw(), but may be invoked after onMeasure()
154      */
155     @Override
onAttachedToWindow()156     protected void onAttachedToWindow() {
157         // Always call the supermethod first
158         super.onAttachedToWindow();
159 
160         // If the sibling View is set and the parent of the ImageView is itself a View
161         if ((this.mHideShowResId != -1) && ((getParent() instanceof View))) {
162 
163             // Gets a handle to the sibling View
164             View localView = ((View) getParent()).findViewById(this.mHideShowResId);
165 
166             // If the sibling View contains something, make it the weak reference for this View
167             if (localView != null) {
168                 this.mThisView = new WeakReference<View>(localView);
169             }
170         }
171     }
172 
173     /*
174      * This callback is invoked when the ImageView is removed from a Window. It "unsets" variables
175      * to prevent memory leaks.
176      */
177     @Override
onDetachedFromWindow()178     protected void onDetachedFromWindow() {
179 
180         // Clears out the image drawable, turns off the cache, disconnects the view from a URL
181         setImageURL(null, false, null);
182 
183         // Gets the current Drawable, or null if no Drawable is attached
184         Drawable localDrawable = getDrawable();
185 
186         // if the Drawable is null, unbind it from this VIew
187         if (localDrawable != null)
188             localDrawable.setCallback(null);
189 
190         // If this View still exists, clears the weak reference, then sets the reference to null
191         if (mThisView != null) {
192             mThisView.clear();
193             mThisView = null;
194         }
195 
196         // Sets the downloader thread to null
197         this.mDownloadThread = null;
198 
199         // Always call the super method last
200         super.onDetachedFromWindow();
201     }
202 
203     /*
204      * This callback is invoked when the system tells the View to draw itself. If the View isn't
205      * already drawn, and its URL isn't null, it invokes a Thread to download the image. Otherwise,
206      * it simply passes the existing Canvas to the super method
207      */
208     @Override
onDraw(Canvas canvas)209     protected void onDraw(Canvas canvas) {
210         // If the image isn't already drawn, and the URL is set
211         if ((!mIsDrawn) && (mImageURL != null)) {
212 
213             // Starts downloading this View, using the current cache setting
214             mDownloadThread = PhotoManager.startDownload(this, mCacheFlag);
215 
216             // After successfully downloading the image, this marks that it's available.
217             mIsDrawn = true;
218         }
219         // Always call the super method last
220         super.onDraw(canvas);
221     }
222 
223     /**
224      * Sets the current View weak reference to be the incoming View. See the definition of
225      * mThisView
226      * @param view the View to use as the new WeakReference
227      */
setHideView(View view)228     public void setHideView(View view) {
229         this.mThisView = new WeakReference<View>(view);
230     }
231 
232     @Override
setImageBitmap(Bitmap paramBitmap)233     public void setImageBitmap(Bitmap paramBitmap) {
234         super.setImageBitmap(paramBitmap);
235     }
236 
237     @Override
setImageDrawable(Drawable drawable)238     public void setImageDrawable(Drawable drawable) {
239         // The visibility of the View
240         int viewState;
241 
242         /*
243          * Sets the View state to visible if the method is called with a null argument (the
244          * image is being cleared). Otherwise, sets the View state to invisible before refreshing
245          * it.
246          */
247         if (drawable == null) {
248 
249             viewState = View.VISIBLE;
250         } else {
251 
252             viewState = View.INVISIBLE;
253         }
254         // Either hides or shows the View, depending on the view state
255         showView(viewState);
256 
257         // Invokes the supermethod with the provided drawable
258         super.setImageDrawable(drawable);
259     }
260 
261     /*
262      * Displays a drawable in the View
263      */
264     @Override
setImageResource(int resId)265     public void setImageResource(int resId) {
266         super.setImageResource(resId);
267     }
268 
269     /*
270      * Sets the URI for the Image
271      */
272     @Override
setImageURI(Uri uri)273     public void setImageURI(Uri uri) {
274         super.setImageURI(uri);
275     }
276 
277     /**
278      * Attempts to set the picture URL for this ImageView and then download the picture.
279      * <p>
280      * If the picture URL for this view is already set, and the input URL is not the same as the
281      * stored URL, then the picture has moved and any existing downloads are stopped.
282      * <p>
283      * If the input URL is the same as the stored URL, then nothing needs to be done.
284      * <p>
285      * If the stored URL is null, then this method starts a download and decode of the picture
286      * @param pictureURL An incoming URL for a Picasa picture
287      * @param cacheFlag Whether to use caching when doing downloading and decoding
288      * @param imageDrawable The Drawable to use for this ImageView
289      */
setImageURL(URL pictureURL, boolean cacheFlag, Drawable imageDrawable)290     public void setImageURL(URL pictureURL, boolean cacheFlag, Drawable imageDrawable) {
291         // If the picture URL for this ImageView is already set
292         if (mImageURL != null) {
293 
294             // If the stored URL doesn't match the incoming URL, then the picture has changed.
295             if (!mImageURL.equals(pictureURL)) {
296 
297                 // Stops any ongoing downloads for this ImageView
298                 PhotoManager.removeDownload(mDownloadThread, mImageURL);
299             } else {
300 
301                 // The stored URL matches the incoming URL. Returns without doing any work.
302                 return;
303             }
304         }
305 
306         // Sets the Drawable for this ImageView
307         setImageDrawable(imageDrawable);
308 
309         // Stores the picture URL for this ImageView
310         mImageURL = pictureURL;
311 
312         // If the draw operation for this ImageVIew has completed, and the picture URL isn't empty
313         if ((mIsDrawn) && (pictureURL != null)) {
314 
315             // Sets the cache flag
316             mCacheFlag = cacheFlag;
317 
318             /*
319              * Starts a download of the picture file. Notice that if caching is on, the picture
320              * file's contents may be taken from the cache.
321              */
322             mDownloadThread = PhotoManager.startDownload(this, cacheFlag);
323         }
324     }
325 
326     /**
327      * Sets the Drawable for this ImageView
328      * @param drawable A Drawable to use for the ImageView
329      */
setStatusDrawable(Drawable drawable)330     public void setStatusDrawable(Drawable drawable) {
331 
332         // If the View is empty, sets a Drawable as its content
333         if (mThisView == null) {
334             setImageDrawable(drawable);
335         }
336     }
337 
338     /**
339      * Sets the content of this ImageView to be a Drawable resource
340      * @param resId
341      */
setStatusResource(int resId)342     public void setStatusResource(int resId) {
343 
344         // If the View is empty, provides it with a Drawable resource as its content
345         if (mThisView == null) {
346             setImageResource(resId);
347         }
348     }
349 }
350