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;