Clover Coverage Report - Subsonic-Android Coverage Report
Coverage timestamp: ven dic 19 2014 17:57:13 EST
../../../../../img/srcFileCovDistChart10.png 0% of files have more coverage
84   252   28   7
26   171   0,33   6
12     2,33  
2    
This report was generated with an evaluation server license. Purchase Clover or configure your license.
 
  ImageLoader       Line # 52 67 23 92% 0.92
  ImageLoader.Task       Line # 210 17 5 95,5% 0.95454544
 
No Tests
 
1    /*
2    This file is part of Subsonic.
3   
4    Subsonic is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8   
9    Subsonic is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12    GNU General Public License for more details.
13   
14    You should have received a copy of the GNU General Public License
15    along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
16   
17    Copyright 2009 (C) Sindre Mehus
18    */
19    package net.sourceforge.subsonic.androidapp.util;
20   
21    import android.content.Context;
22    import android.graphics.Bitmap;
23    import android.graphics.Canvas;
24    import android.graphics.LinearGradient;
25    import android.graphics.Matrix;
26    import android.graphics.Paint;
27    import android.graphics.Shader;
28    import android.graphics.drawable.BitmapDrawable;
29    import android.graphics.drawable.Drawable;
30    import android.graphics.drawable.TransitionDrawable;
31    import android.os.Handler;
32    import android.util.DisplayMetrics;
33    import android.util.Log;
34    import android.view.View;
35    import android.widget.ImageView;
36    import android.widget.TextView;
37    import net.sourceforge.subsonic.androidapp.R;
38    import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
39    import net.sourceforge.subsonic.androidapp.service.MusicService;
40    import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory;
41   
42    import java.util.concurrent.BlockingQueue;
43    import java.util.concurrent.LinkedBlockingQueue;
44   
45    /**
46    * Asynchronous loading of images, with caching.
47    * <p/>
48    * There should normally be only one instance of this class.
49    *
50    * @author Sindre Mehus
51    */
 
52    public class ImageLoader implements Runnable {
53   
54    private static final String TAG = ImageLoader.class.getSimpleName();
55    private static final int CONCURRENCY = 5;
56   
57    private final LRUCache<String, Drawable> cache = new LRUCache<String, Drawable>(100);
58    private final BlockingQueue<Task> queue;
59    private final int imageSizeDefault;
60    private final int imageSizeLarge;
61    private Drawable largeUnknownImage;
62   
 
63  1 toggle public ImageLoader(Context context) {
64  1 queue = new LinkedBlockingQueue<Task>(500);
65   
66    // Determine the density-dependent image sizes.
67  1 imageSizeDefault = context.getResources().getDrawable(R.drawable.unknown_album).getIntrinsicHeight();
68  1 DisplayMetrics metrics = context.getResources().getDisplayMetrics();
69  1 imageSizeLarge = (int) Math.round(Math.min(metrics.widthPixels, metrics.heightPixels) * 0.6);
70   
71  6 for (int i = 0; i < CONCURRENCY; i++) {
72  5 new Thread(this, "ImageLoader").start();
73    }
74   
75  1 createLargeUnknownImage(context);
76    }
77   
 
78  1 toggle private void createLargeUnknownImage(Context context) {
79  1 BitmapDrawable drawable = (BitmapDrawable) context.getResources().getDrawable(R.drawable.unknown_album_large);
80  1 Bitmap bitmap = Bitmap.createScaledBitmap(drawable.getBitmap(), imageSizeLarge, imageSizeLarge, true);
81  1 bitmap = createReflection(bitmap);
82  1 largeUnknownImage = Util.createDrawableFromBitmap(context, bitmap);
83    }
84   
 
85  85 toggle public void loadImage(View view, MusicDirectory.Entry entry, boolean large, boolean crossfade) {
86  85 if (entry == null || entry.getCoverArt() == null) {
87  6 setUnknownImage(view, large);
88  6 return;
89    }
90   
91  79 int size = large ? imageSizeLarge : imageSizeDefault;
92  79 Drawable drawable = cache.get(getKey(entry.getCoverArt(), size));
93  79 if (drawable != null) {
94  44 setImage(view, drawable, large);
95  44 return;
96    }
97   
98  35 if (!large) {
99  23 setUnknownImage(view, large);
100    }
101  35 queue.offer(new Task(view, entry, size, large, large, crossfade));
102    }
103   
 
104  114 toggle private String getKey(String coverArtId, int size) {
105  114 return coverArtId + size;
106    }
107   
 
108  85 toggle private void setImage(View view, Drawable drawable, boolean crossfade) {
109  85 if (view instanceof TextView) {
110    // Cross-fading is not implemented for TextView since it's not in use. It would be easy to add it, though.
111  0 TextView textView = (TextView) view;
112  0 textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
113  85 } else if (view instanceof ImageView) {
114  85 ImageView imageView = (ImageView) view;
115  85 if (crossfade) {
116   
117  65 Drawable existingDrawable = imageView.getDrawable();
118  65 if (existingDrawable == null) {
119  6 Bitmap emptyImage = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
120  6 existingDrawable = new BitmapDrawable(emptyImage);
121    }
122   
123  65 Drawable[] layers = new Drawable[]{existingDrawable, drawable};
124   
125  65 TransitionDrawable transitionDrawable = new TransitionDrawable(layers);
126  65 imageView.setImageDrawable(transitionDrawable);
127  65 transitionDrawable.startTransition(250);
128    } else {
129  20 imageView.setImageDrawable(drawable);
130    }
131    }
132    }
133   
 
134  29 toggle private void setUnknownImage(View view, boolean large) {
135  29 if (large) {
136  6 setImage(view, largeUnknownImage, false);
137    } else {
138  23 if (view instanceof TextView) {
139  0 ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(R.drawable.unknown_album, 0, 0, 0);
140  23 } else if (view instanceof ImageView) {
141  23 ((ImageView) view).setImageResource(R.drawable.unknown_album);
142    }
143    }
144    }
145   
 
146  32 toggle public void clear() {
147  32 queue.clear();
148    }
149   
 
150  5 toggle @Override
151    public void run() {
152  5 while (true) {
153  40 try {
154  40 Task task = queue.take();
155  35 task.execute();
156    } catch (Throwable x) {
157  0 Log.e(TAG, "Unexpected exception in ImageLoader.", x);
158    }
159    }
160    }
161   
 
162  13 toggle private Bitmap createReflection(Bitmap originalImage) {
163   
164  13 int width = originalImage.getWidth();
165  13 int height = originalImage.getHeight();
166   
167    // The gap we want between the reflection and the original image
168  13 final int reflectionGap = 4;
169   
170    // This will not scale but will flip on the Y axis
171  13 Matrix matrix = new Matrix();
172  13 matrix.preScale(1, -1);
173   
174    // Create a Bitmap with the flip matix applied to it.
175    // We only want the bottom half of the image
176  13 Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0, height / 2, width, height / 2, matrix, false);
177   
178    // Create a new bitmap with same width but taller to fit reflection
179  13 Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + height / 2), Bitmap.Config.ARGB_8888);
180   
181    // Create a new Canvas with the bitmap that's big enough for
182    // the image plus gap plus reflection
183  13 Canvas canvas = new Canvas(bitmapWithReflection);
184   
185    // Draw in the original image
186  13 canvas.drawBitmap(originalImage, 0, 0, null);
187   
188    // Draw in the gap
189  13 Paint defaultPaint = new Paint();
190  13 canvas.drawRect(0, height, width, height + reflectionGap, defaultPaint);
191   
192    // Draw in the reflection
193  13 canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null);
194   
195    // Create a shader that is a linear gradient that covers the reflection
196  13 Paint paint = new Paint();
197  13 LinearGradient shader = new LinearGradient(0, originalImage.getHeight(), 0,
198    bitmapWithReflection.getHeight() + reflectionGap, 0x70000000, 0xff000000,
199    Shader.TileMode.CLAMP);
200   
201    // Set the paint to use this shader (linear gradient)
202  13 paint.setShader(shader);
203   
204    // Draw a rectangle using the paint with our linear gradient
205  13 canvas.drawRect(0, height, width, bitmapWithReflection.getHeight() + reflectionGap, paint);
206   
207  13 return bitmapWithReflection;
208    }
209   
 
210    private class Task {
211    private final View view;
212    private final MusicDirectory.Entry entry;
213    private final Handler handler;
214    private final int size;
215    private final boolean reflection;
216    private final boolean saveToFile;
217    private final boolean crossfade;
218   
 
219  35 toggle public Task(View view, MusicDirectory.Entry entry, int size, boolean reflection, boolean saveToFile, boolean crossfade) {
220  35 this.view = view;
221  35 this.entry = entry;
222  35 this.size = size;
223  35 this.reflection = reflection;
224  35 this.saveToFile = saveToFile;
225  35 this.crossfade = crossfade;
226  35 handler = new Handler();
227    }
228   
 
229  35 toggle public void execute() {
230  35 try {
231  35 MusicService musicService = MusicServiceFactory.getMusicService(view.getContext());
232  35 Bitmap bitmap = musicService.getCoverArt(view.getContext(), entry, size, saveToFile, null);
233   
234  35 if (reflection) {
235  12 bitmap = createReflection(bitmap);
236    }
237   
238  35 final Drawable drawable = Util.createDrawableFromBitmap(view.getContext(), bitmap);
239  35 cache.put(getKey(entry.getCoverArt(), size), drawable);
240   
241  35 handler.post(new Runnable() {
 
242  35 toggle @Override
243    public void run() {
244  35 setImage(view, drawable, crossfade);
245    }
246    });
247    } catch (Throwable x) {
248  0 Log.e(TAG, "Failed to download album art.", x);
249    }
250    }
251    }
252    }