mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-14 00:17:15 +03:00
Extract ImageLoader interface.
This will allow to swap it with a new implementation. Signed-off-by: Yahor Berdnikau <egorr.berd@gmail.com>
This commit is contained in:
parent
36e813bed7
commit
b27ce42d02
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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.
|
||||
* <p/>
|
||||
* 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<String, Bitmap> cache = new LRUCache<String, Bitmap>(150);
|
||||
private final BlockingQueue<Task> queue;
|
||||
private int imageSizeDefault;
|
||||
private final int imageSizeLarge;
|
||||
private Bitmap largeUnknownImage;
|
||||
private Bitmap unknownAvatarImage;
|
||||
private Context context;
|
||||
private Collection<Thread> 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<Task>(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<Thread>(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();
|
||||
}
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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.
|
||||
* <p/>
|
||||
* 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<String, Bitmap> cache = new LRUCache<>(150);
|
||||
private final BlockingQueue<Task> queue;
|
||||
private int imageSizeDefault;
|
||||
private final int imageSizeLarge;
|
||||
private Bitmap largeUnknownImage;
|
||||
private Bitmap unknownAvatarImage;
|
||||
private Context context;
|
||||
private Collection<Thread> 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<Thread>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user