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
17package com.example.android.threadsample;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.drawable.Drawable;
24import android.net.Uri;
25import android.util.AttributeSet;
26import android.view.View;
27import android.widget.ImageView;
28
29
30import java.lang.ref.WeakReference;
31import 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 */
38public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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
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     */
228    public void setHideView(View view) {
229        this.mThisView = new WeakReference<View>(view);
230    }
231
232    @Override
233    public void setImageBitmap(Bitmap paramBitmap) {
234        super.setImageBitmap(paramBitmap);
235    }
236
237    @Override
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
265    public void setImageResource(int resId) {
266        super.setImageResource(resId);
267    }
268
269    /*
270     * Sets the URI for the Image
271     */
272    @Override
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     */
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     */
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     */
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