diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java index 46b73a67..a92f6e41 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java @@ -20,7 +20,6 @@ package org.moire.ultrasonic.activity; import android.app.AlertDialog; import android.app.Dialog; -import android.app.NotificationManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -33,54 +32,23 @@ import android.os.Bundle; import android.os.Environment; import android.support.v7.app.ActionBar; import android.util.Log; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; +import android.view.*; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.RemoteViews; -import android.widget.TextView; - +import android.widget.*; import net.simonvt.menudrawer.MenuDrawer; import net.simonvt.menudrawer.Position; - import org.moire.ultrasonic.R; import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.domain.PlayerState; import org.moire.ultrasonic.domain.Share; -import org.moire.ultrasonic.service.DownloadFile; -import org.moire.ultrasonic.service.DownloadService; -import org.moire.ultrasonic.service.DownloadServiceImpl; -import org.moire.ultrasonic.service.MusicService; -import org.moire.ultrasonic.service.MusicServiceFactory; -import org.moire.ultrasonic.util.BackgroundTask; -import org.moire.ultrasonic.util.Constants; -import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator; -import org.moire.ultrasonic.util.ImageLoader; -import org.moire.ultrasonic.util.ModalBackgroundTask; -import org.moire.ultrasonic.util.ShareDetails; -import org.moire.ultrasonic.util.SilentBackgroundTask; -import org.moire.ultrasonic.util.TabActivityBackgroundTask; -import org.moire.ultrasonic.util.TimeSpan; -import org.moire.ultrasonic.util.TimeSpanPicker; -import org.moire.ultrasonic.util.Util; -import org.moire.ultrasonic.util.VideoPlayerType; +import org.moire.ultrasonic.service.*; +import org.moire.ultrasonic.util.*; import java.io.File; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import java.util.regex.Pattern; /** @@ -829,7 +797,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen { if (IMAGE_LOADER == null || !IMAGE_LOADER.isRunning()) { - IMAGE_LOADER = new ImageLoader(this, Util.getImageLoaderConcurrency(this)); + IMAGE_LOADER = new LegacyImageLoader(this, Util.getImageLoaderConcurrency(this)); IMAGE_LOADER.startImageLoader(); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ImageLoader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ImageLoader.java index 60fe1b49..74202148 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ImageLoader.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ImageLoader.java @@ -1,462 +1,43 @@ -/* - This file is part of Subsonic. - - Subsonic is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Subsonic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Subsonic. If not, see . - - Copyright 2009 (C) Sindre Mehus - */ package org.moire.ultrasonic.util; -import android.content.Context; -import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.TransitionDrawable; -import android.os.Handler; -import android.text.TextUtils; -import android.util.Log; import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import org.moire.ultrasonic.R; import org.moire.ultrasonic.domain.MusicDirectory; -import org.moire.ultrasonic.service.MusicService; -import org.moire.ultrasonic.service.MusicServiceFactory; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicBoolean; +public interface ImageLoader { + boolean isRunning(); -/** - * Asynchronous loading of images, with caching. - *

- * There should normally be only one instance of this class. - * - * @author Sindre Mehus - */ -public class ImageLoader implements Runnable -{ - private static final String TAG = ImageLoader.class.getSimpleName(); + void setConcurrency(int concurrency); - private final LRUCache cache = new LRUCache(150); - private final BlockingQueue queue; - private int imageSizeDefault; - private final int imageSizeLarge; - private Bitmap largeUnknownImage; - private Bitmap unknownAvatarImage; - private Context context; - private Collection threads; - private AtomicBoolean running = new AtomicBoolean(); - private int concurrency; + void startImageLoader(); - public ImageLoader(Context context, int concurrency) - { - this.context = context; - this.concurrency = concurrency; - queue = new LinkedBlockingQueue(1000); + void stopImageLoader(); - Resources resources = context.getResources(); - Drawable drawable = resources.getDrawable(R.drawable.unknown_album); + void loadAvatarImage( + View view, + String username, + boolean large, + int size, + boolean crossFade, + boolean highQuality + ); - // Determine the density-dependent image sizes. - if (drawable != null) - { - imageSizeDefault = drawable.getIntrinsicHeight(); - } + void loadImage( + View view, + MusicDirectory.Entry entry, + boolean large, + int size, + boolean crossFade, + boolean highQuality + ); - imageSizeLarge = Util.getMaxDisplayMetric(context); - createLargeUnknownImage(context); - createUnknownAvatarImage(context); - } + Bitmap getImageBitmap(String username, int size); - public synchronized boolean isRunning() - { - return running.get() && !threads.isEmpty(); - } + Bitmap getImageBitmap(MusicDirectory.Entry entry, boolean large, int size); - public void setConcurrency(int concurrency) - { - this.concurrency = concurrency; - } + void addImageToCache(Bitmap bitmap, MusicDirectory.Entry entry, int size); - public void startImageLoader() - { - running.set(true); + void addImageToCache(Bitmap bitmap, String username, int size); - threads = Collections.synchronizedCollection(new ArrayList(this.concurrency)); - - for (int i = 0; i < this.concurrency; i++) - { - Thread thread = new Thread(this, String.format("ImageLoader_%d", i)); - threads.add(thread); - thread.start(); - } - } - - public synchronized void stopImageLoader() - { - clear(); - - for (Thread thread : threads) - { - thread.interrupt(); - } - - running.set(false); - threads.clear(); - } - - private void createLargeUnknownImage(Context context) - { - BitmapDrawable drawable = (BitmapDrawable) context.getResources().getDrawable(R.drawable.unknown_album_large); - Log.i(TAG, "createLargeUnknownImage"); - - if (drawable != null) - { - largeUnknownImage = Util.scaleBitmap(drawable.getBitmap(), imageSizeLarge); - } - } - - private void createUnknownAvatarImage(Context context) - { - Resources res = context.getResources(); - Drawable contact = res.getDrawable(R.drawable.ic_contact_picture); - unknownAvatarImage = Util.createBitmapFromDrawable(contact); - } - - public void loadAvatarImage(View view, String username, boolean large, int size, boolean crossFade, boolean highQuality) - { - view.invalidate(); - - if (username == null) - { - setUnknownAvatarImage(view); - return; - } - - if (size <= 0) - { - size = large ? imageSizeLarge : imageSizeDefault; - } - - Bitmap bitmap = cache.get(getKey(username, size)); - - if (bitmap != null) - { - setAvatarImageBitmap(view, username, bitmap, crossFade); - return; - } - - setUnknownAvatarImage(view); - - queue.offer(new Task(view, username, size, large, crossFade, highQuality)); - } - - public void loadImage(View view, MusicDirectory.Entry entry, boolean large, int size, boolean crossFade, boolean highQuality) - { - view.invalidate(); - - if (entry == null) - { - setUnknownImage(view, large); - return; - } - - String coverArt = entry.getCoverArt(); - - if (TextUtils.isEmpty(coverArt)) { - setUnknownImage(view, large); - return; - } - - if (size <= 0) - { - size = large ? imageSizeLarge : imageSizeDefault; - } - - Bitmap bitmap = cache.get(getKey(coverArt, size)); - - if (bitmap != null) - { - setImageBitmap(view, entry, bitmap, crossFade); - return; - } - - setUnknownImage(view, large); - - queue.offer(new Task(view, entry, size, large, crossFade, highQuality)); - } - - private static String getKey(String coverArtId, int size) - { - return String.format("%s:%d", coverArtId, size); - } - - public Bitmap getImageBitmap(String username, int size) - { - Bitmap bitmap = cache.get(getKey(username, size)); - - if (bitmap != null && !bitmap.isRecycled()) - { - Bitmap.Config config = bitmap.getConfig(); - return bitmap.copy(config, false); - } - - return null; - } - - public Bitmap getImageBitmap(MusicDirectory.Entry entry, boolean large, int size) - { - if (entry == null) - { - return null; - } - - String coverArt = entry.getCoverArt(); - - if (TextUtils.isEmpty(coverArt)) { - return null; - } - - if (size <= 0) - { - size = large ? imageSizeLarge : imageSizeDefault; - } - - Bitmap bitmap = cache.get(getKey(coverArt, size)); - - if (bitmap != null && !bitmap.isRecycled()) - { - Bitmap.Config config = bitmap.getConfig(); - return bitmap.copy(config, false); - } - - return null; - } - - private void setImageBitmap(View view, MusicDirectory.Entry entry, Bitmap bitmap, boolean crossFade) - { - if (view instanceof ImageView) - { - ImageView imageView = (ImageView) view; - - MusicDirectory.Entry tagEntry = (MusicDirectory.Entry) view.getTag(); - - // Only apply image to the view if the view is intended for this entry - if (entry != null && tagEntry != null && !entry.equals(tagEntry)) - { - Log.i(TAG, "View is no longer valid, not setting ImageBitmap"); - return; - } - - if (crossFade) - { - Drawable existingDrawable = imageView.getDrawable(); - Drawable newDrawable = Util.createDrawableFromBitmap(this.context, bitmap); - - if (existingDrawable == null) - { - Bitmap emptyImage = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); - existingDrawable = new BitmapDrawable(context.getResources(), emptyImage); - } - - Drawable[] layers = new Drawable[]{existingDrawable, newDrawable}; - - TransitionDrawable transitionDrawable = new TransitionDrawable(layers); - imageView.setImageDrawable(transitionDrawable); - transitionDrawable.startTransition(250); - } - else - { - imageView.setImageBitmap(bitmap); - } - } - } - - private void setAvatarImageBitmap(View view, String username, Bitmap bitmap, boolean crossFade) - { - if (view instanceof ImageView) - { - ImageView imageView = (ImageView) view; - - String tagEntry = (String) view.getTag(); - - // Only apply image to the view if the view is intended for this entry - if (username != null && tagEntry != null && !username.equals(tagEntry)) - { - Log.i(TAG, "View is no longer valid, not setting ImageBitmap"); - return; - } - - if (crossFade) - { - Drawable existingDrawable = imageView.getDrawable(); - Drawable newDrawable = Util.createDrawableFromBitmap(this.context, bitmap); - - if (existingDrawable == null) - { - Bitmap emptyImage = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); - existingDrawable = new BitmapDrawable(context.getResources(), emptyImage); - } - - Drawable[] layers = new Drawable[]{existingDrawable, newDrawable}; - - TransitionDrawable transitionDrawable = new TransitionDrawable(layers); - imageView.setImageDrawable(transitionDrawable); - transitionDrawable.startTransition(250); - } - else - { - imageView.setImageBitmap(bitmap); - } - } - } - - public void setUnknownAvatarImage(View view) - { - setAvatarImageBitmap(view, null, unknownAvatarImage, false); - } - - public void setUnknownImage(View view, boolean large) - { - if (large) - { - setImageBitmap(view, null, largeUnknownImage, false); - } - else - { - if (view instanceof TextView) - { - ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(R.drawable.unknown_album, 0, 0, 0); - } - else if (view instanceof ImageView) - { - ((ImageView) view).setImageResource(R.drawable.unknown_album); - } - } - } - - public void addImageToCache(Bitmap bitmap, MusicDirectory.Entry entry, int size) - { - cache.put(getKey(entry.getCoverArt(), size), bitmap); - } - - public void addImageToCache(Bitmap bitmap, String username, int size) - { - cache.put(getKey(username, size), bitmap); - } - - public void clear() - { - queue.clear(); - } - - @Override - public void run() - { - while (running.get()) - { - try - { - Task task = queue.take(); - task.execute(); - } - catch (InterruptedException ignored) - { - running.set(false); - break; - } - catch (Throwable x) - { - Log.e(TAG, "Unexpected exception in ImageLoader.", x); - } - } - } - - private class Task - { - private final View view; - private final MusicDirectory.Entry entry; - private final String username; - private final Handler handler; - private final int size; - private final boolean saveToFile; - private final boolean crossFade; - private final boolean highQuality; - - public Task(View view, MusicDirectory.Entry entry, int size, boolean saveToFile, boolean crossFade, boolean highQuality) - { - this.view = view; - this.entry = entry; - this.username = null; - this.size = size; - this.saveToFile = saveToFile; - this.crossFade = crossFade; - this.highQuality = highQuality; - handler = new Handler(); - } - - public Task(View view, String username, int size, boolean saveToFile, boolean crossFade, boolean highQuality) - { - this.view = view; - this.entry = null; - this.username = username; - this.size = size; - this.saveToFile = saveToFile; - this.crossFade = crossFade; - this.highQuality = highQuality; - handler = new Handler(); - } - - public void execute() - { - try - { - MusicService musicService = MusicServiceFactory.getMusicService(view.getContext()); - final boolean isAvatar = this.username != null && this.entry == null; - final Bitmap bitmap = this.entry != null ? musicService.getCoverArt(view.getContext(), entry, size, saveToFile, highQuality, null) : musicService.getAvatar(view.getContext(), username, size, saveToFile, highQuality, null); - - if (isAvatar) - addImageToCache(bitmap, username, size); - else - addImageToCache(bitmap, entry, size); - - handler.post(new Runnable() - { - @Override - public void run() - { - if (isAvatar) - { - setAvatarImageBitmap(view, username, bitmap, crossFade); - } - else - { - setImageBitmap(view, entry, bitmap, crossFade); - } - } - }); - } - catch (Throwable x) - { - Log.e(TAG, "Failed to download album art.", x); - } - } - } + void clear(); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/LegacyImageLoader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/LegacyImageLoader.java new file mode 100644 index 00000000..ae1fc517 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/LegacyImageLoader.java @@ -0,0 +1,450 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + + Copyright 2009 (C) Sindre Mehus + */ +package org.moire.ultrasonic.util; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.os.Handler; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import org.moire.ultrasonic.R; +import org.moire.ultrasonic.domain.MusicDirectory; +import org.moire.ultrasonic.service.MusicService; +import org.moire.ultrasonic.service.MusicServiceFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Asynchronous loading of images, with caching. + *

+ * There should normally be only one instance of this class. + * + * @author Sindre Mehus + */ +public class LegacyImageLoader implements Runnable, ImageLoader { + private static final String TAG = LegacyImageLoader.class.getSimpleName(); + + private final LRUCache cache = new LRUCache<>(150); + private final BlockingQueue queue; + private int imageSizeDefault; + private final int imageSizeLarge; + private Bitmap largeUnknownImage; + private Bitmap unknownAvatarImage; + private Context context; + private Collection threads; + private AtomicBoolean running = new AtomicBoolean(); + private int concurrency; + + public LegacyImageLoader( + Context context, + int concurrency + ) { + this.context = context; + this.concurrency = concurrency; + queue = new LinkedBlockingQueue<>(1000); + + Resources resources = context.getResources(); + Drawable drawable = resources.getDrawable(R.drawable.unknown_album); + + // Determine the density-dependent image sizes. + if (drawable != null) { + imageSizeDefault = drawable.getIntrinsicHeight(); + } + + imageSizeLarge = Util.getMaxDisplayMetric(context); + createLargeUnknownImage(context); + createUnknownAvatarImage(context); + } + + @Override + public synchronized boolean isRunning() { + return running.get() && !threads.isEmpty(); + } + + @Override + public void setConcurrency(int concurrency) { + this.concurrency = concurrency; + } + + @Override + public void startImageLoader() { + running.set(true); + + threads = Collections.synchronizedCollection(new ArrayList(this.concurrency)); + + for (int i = 0; i < this.concurrency; i++) { + Thread thread = new Thread(this, String.format("ImageLoader_%d", i)); + threads.add(thread); + thread.start(); + } + } + + @Override + public synchronized void stopImageLoader() { + clear(); + + for (Thread thread : threads) { + thread.interrupt(); + } + + running.set(false); + threads.clear(); + } + + private void createLargeUnknownImage(Context context) { + BitmapDrawable drawable = (BitmapDrawable) context.getResources().getDrawable(R.drawable.unknown_album_large); + Log.i(TAG, "createLargeUnknownImage"); + + if (drawable != null) { + largeUnknownImage = Util.scaleBitmap(drawable.getBitmap(), imageSizeLarge); + } + } + + private void createUnknownAvatarImage(Context context) { + Resources res = context.getResources(); + Drawable contact = res.getDrawable(R.drawable.ic_contact_picture); + unknownAvatarImage = Util.createBitmapFromDrawable(contact); + } + + @Override + public void loadAvatarImage( + View view, + String username, + boolean large, + int size, + boolean crossFade, + boolean highQuality + ) { + view.invalidate(); + + if (username == null) { + setUnknownAvatarImage(view); + return; + } + + if (size <= 0) { + size = large ? imageSizeLarge : imageSizeDefault; + } + + Bitmap bitmap = cache.get(getKey(username, size)); + + if (bitmap != null) { + setAvatarImageBitmap(view, username, bitmap, crossFade); + return; + } + + setUnknownAvatarImage(view); + + queue.offer(new Task(view, username, size, large, crossFade, highQuality)); + } + + @Override + public void loadImage( + View view, + MusicDirectory.Entry entry, + boolean large, + int size, + boolean crossFade, + boolean highQuality + ) { + view.invalidate(); + + if (entry == null) { + setUnknownImage(view, large); + return; + } + + String coverArt = entry.getCoverArt(); + + if (TextUtils.isEmpty(coverArt)) { + setUnknownImage(view, large); + return; + } + + if (size <= 0) { + size = large ? imageSizeLarge : imageSizeDefault; + } + + Bitmap bitmap = cache.get(getKey(coverArt, size)); + + if (bitmap != null) { + setImageBitmap(view, entry, bitmap, crossFade); + return; + } + + setUnknownImage(view, large); + + queue.offer(new Task(view, entry, size, large, crossFade, highQuality)); + } + + private static String getKey(String coverArtId, int size) { + return String.format("%s:%d", coverArtId, size); + } + + @Override + public Bitmap getImageBitmap(String username, int size) { + Bitmap bitmap = cache.get(getKey(username, size)); + + if (bitmap != null && !bitmap.isRecycled()) { + Bitmap.Config config = bitmap.getConfig(); + return bitmap.copy(config, false); + } + + return null; + } + + @Override + public Bitmap getImageBitmap(MusicDirectory.Entry entry, boolean large, int size) { + if (entry == null) { + return null; + } + + String coverArt = entry.getCoverArt(); + + if (TextUtils.isEmpty(coverArt)) { + return null; + } + + if (size <= 0) { + size = large ? imageSizeLarge : imageSizeDefault; + } + + Bitmap bitmap = cache.get(getKey(coverArt, size)); + + if (bitmap != null && !bitmap.isRecycled()) { + Bitmap.Config config = bitmap.getConfig(); + return bitmap.copy(config, false); + } + + return null; + } + + private void setImageBitmap( + View view, + MusicDirectory.Entry entry, + Bitmap bitmap, + boolean crossFade + ) { + if (view instanceof ImageView) { + ImageView imageView = (ImageView) view; + + MusicDirectory.Entry tagEntry = (MusicDirectory.Entry) view.getTag(); + + // Only apply image to the view if the view is intended for this entry + if (entry != null && tagEntry != null && !entry.equals(tagEntry)) { + Log.i(TAG, "View is no longer valid, not setting ImageBitmap"); + return; + } + + if (crossFade) { + Drawable existingDrawable = imageView.getDrawable(); + Drawable newDrawable = Util.createDrawableFromBitmap(this.context, bitmap); + + if (existingDrawable == null) { + Bitmap emptyImage = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); + existingDrawable = new BitmapDrawable(context.getResources(), emptyImage); + } + + Drawable[] layers = new Drawable[]{existingDrawable, newDrawable}; + + TransitionDrawable transitionDrawable = new TransitionDrawable(layers); + imageView.setImageDrawable(transitionDrawable); + transitionDrawable.startTransition(250); + } else { + imageView.setImageBitmap(bitmap); + } + } + } + + private void setAvatarImageBitmap( + View view, + String username, + Bitmap bitmap, + boolean crossFade + ) { + if (view instanceof ImageView) { + ImageView imageView = (ImageView) view; + + String tagEntry = (String) view.getTag(); + + // Only apply image to the view if the view is intended for this entry + if (username != null && + tagEntry != null && + !username.equals(tagEntry)) { + Log.i(TAG, "View is no longer valid, not setting ImageBitmap"); + return; + } + + if (crossFade) { + Drawable existingDrawable = imageView.getDrawable(); + Drawable newDrawable = Util.createDrawableFromBitmap(this.context, bitmap); + + if (existingDrawable == null) { + Bitmap emptyImage = Bitmap.createBitmap( + bitmap.getWidth(), + bitmap.getHeight(), + Bitmap.Config.ARGB_8888 + ); + existingDrawable = new BitmapDrawable(context.getResources(), emptyImage); + } + + Drawable[] layers = new Drawable[]{existingDrawable, newDrawable}; + + TransitionDrawable transitionDrawable = new TransitionDrawable(layers); + imageView.setImageDrawable(transitionDrawable); + transitionDrawable.startTransition(250); + } else { + imageView.setImageBitmap(bitmap); + } + } + } + + private void setUnknownAvatarImage(View view) { + setAvatarImageBitmap(view, null, unknownAvatarImage, false); + } + + private void setUnknownImage(View view, boolean large) { + if (large) { + setImageBitmap(view, null, largeUnknownImage, false); + } else { + if (view instanceof TextView) { + ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(R.drawable.unknown_album, 0, 0, 0); + } else if (view instanceof ImageView) { + ((ImageView) view).setImageResource(R.drawable.unknown_album); + } + } + } + + @Override + public void addImageToCache(Bitmap bitmap, MusicDirectory.Entry entry, int size) { + cache.put(getKey(entry.getCoverArt(), size), bitmap); + } + + @Override + public void addImageToCache(Bitmap bitmap, String username, int size) { + cache.put(getKey(username, size), bitmap); + } + + @Override + public void clear() { + queue.clear(); + } + + @Override + public void run() { + while (running.get()) { + try { + Task task = queue.take(); + task.execute(); + } catch (InterruptedException ignored) { + running.set(false); + break; + } catch (Throwable x) { + Log.e(TAG, "Unexpected exception in ImageLoader.", x); + } + } + } + + private class Task { + private final View view; + private final MusicDirectory.Entry entry; + private final String username; + private final Handler handler; + private final int size; + private final boolean saveToFile; + private final boolean crossFade; + private final boolean highQuality; + + Task( + View view, + MusicDirectory.Entry entry, + int size, + boolean saveToFile, + boolean crossFade, + boolean highQuality + ) { + this.view = view; + this.entry = entry; + this.username = null; + this.size = size; + this.saveToFile = saveToFile; + this.crossFade = crossFade; + this.highQuality = highQuality; + handler = new Handler(); + } + + Task( + View view, + String username, + int size, + boolean saveToFile, + boolean crossFade, + boolean highQuality + ) { + this.view = view; + this.entry = null; + this.username = username; + this.size = size; + this.saveToFile = saveToFile; + this.crossFade = crossFade; + this.highQuality = highQuality; + handler = new Handler(); + } + + public void execute() { + try { + MusicService musicService = MusicServiceFactory.getMusicService(view.getContext()); + final boolean isAvatar = this.username != null && this.entry == null; + final Bitmap bitmap = this.entry != null + ? musicService.getCoverArt(view.getContext(), entry, size, saveToFile, highQuality, null) + : musicService.getAvatar(view.getContext(), username, size, saveToFile, highQuality, null); + + if (isAvatar) + addImageToCache(bitmap, username, size); + else + addImageToCache(bitmap, entry, size); + + handler.post(new Runnable() { + @Override + public void run() { + if (isAvatar) { + setAvatarImageBitmap(view, username, bitmap, crossFade); + } else { + setImageBitmap(view, entry, bitmap, crossFade); + } + } + }); + } catch (Throwable x) { + Log.e(TAG, "Failed to download album art.", x); + } + } + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/AlbumView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/AlbumView.java index 23eb0074..1884e201 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/AlbumView.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/AlbumView.java @@ -25,7 +25,6 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; - import org.moire.ultrasonic.R; import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.service.MusicService; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java index 9fc000c2..0361e81f 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java @@ -8,7 +8,6 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; - import org.moire.ultrasonic.R; import org.moire.ultrasonic.activity.SubsonicTabActivity; import org.moire.ultrasonic.domain.ChatMessage; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java index 4b3b5317..ce229d65 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java @@ -24,7 +24,6 @@ import android.widget.ArrayAdapter; import android.widget.CheckedTextView; import android.widget.ImageView; import android.widget.TextView; - import org.moire.ultrasonic.activity.SubsonicTabActivity; import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.util.ImageLoader;