diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f4c60a18..c1000c8a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2,8 +2,8 @@ + a:versionCode="44" + a:versionName="1.3.0.4"> diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index f347b258..c1193303 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -374,6 +374,19 @@ Afficher l\'artiste Scan Media After Download Automatically scan media after download + Image Loader Concurrency + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 Aucun titre diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 71d3210b..46d0cc20 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -374,6 +374,19 @@ Ugrás az előadóhoz Scan Media After Download Automatically scan media after download + Image Loader Concurrency + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 Nincsenek dalok diff --git a/res/values/arrays.xml b/res/values/arrays.xml index a7e3feee..56d6ecb8 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -263,5 +263,33 @@ @string/settings.share_hours @string/settings.share_days + + @string/settings.image_loader_concurrency_1 + @string/settings.image_loader_concurrency_2 + @string/settings.image_loader_concurrency_3 + @string/settings.image_loader_concurrency_4 + @string/settings.image_loader_concurrency_5 + @string/settings.image_loader_concurrency_6 + @string/settings.image_loader_concurrency_7 + @string/settings.image_loader_concurrency_8 + @string/settings.image_loader_concurrency_9 + @string/settings.image_loader_concurrency_10 + @string/settings.image_loader_concurrency_11 + @string/settings.image_loader_concurrency_12 + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index f1ec7eea..61d9d5fa 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -374,6 +374,19 @@ Show Artist Scan Media After Download Automatically scan media after download + Image Loader Concurrency + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 No songs diff --git a/res/xml/settings.xml b/res/xml/settings.xml index c2e89b19..cf5b0bee 100644 --- a/res/xml/settings.xml +++ b/res/xml/settings.xml @@ -49,6 +49,12 @@ a:entryValues="@array/viewRefreshValues" a:key="viewRefresh" a:title="@string/settings.view_refresh"/> + newActivity) + public void startActivityForResultWithoutTransition(Activity currentActivity, Class newActivity) { startActivityForResultWithoutTransition(currentActivity, new Intent(currentActivity, newActivity)); } - protected void startActivityForResultWithoutTransition(Activity currentActivity, Intent intent) + public void startActivityForResultWithoutTransition(Activity currentActivity, Intent intent) { startActivityForResult(intent, 0); Util.disablePendingTransition(currentActivity); diff --git a/src/com/thejoshwa/ultrasonic/androidapp/activity/SettingsActivity.java b/src/com/thejoshwa/ultrasonic/androidapp/activity/SettingsActivity.java index b9239923..5dc04b3e 100644 --- a/src/com/thejoshwa/ultrasonic/androidapp/activity/SettingsActivity.java +++ b/src/com/thejoshwa/ultrasonic/androidapp/activity/SettingsActivity.java @@ -88,6 +88,7 @@ public class SettingsActivity extends PreferenceResultActivity implements Shared private CheckBoxPreference sendBluetoothNotifications; private CheckBoxPreference sendBluetoothAlbumArt; private ListPreference viewRefresh; + private ListPreference imageLoaderConcurrency; private EditTextPreference sharingDefaultDescription; private EditTextPreference sharingDefaultGreeting; private TimeSpanPreference sharingDefaultExpiration; @@ -181,6 +182,7 @@ public class SettingsActivity extends PreferenceResultActivity implements Shared sendBluetoothAlbumArt = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART); sendBluetoothNotifications = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS); viewRefresh = (ListPreference) findPreference(Constants.PREFERENCES_KEY_VIEW_REFRESH); + imageLoaderConcurrency = (ListPreference) findPreference(Constants.PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY); sharingDefaultDescription = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION); sharingDefaultGreeting = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING); sharingDefaultExpiration = (TimeSpanPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION); @@ -542,6 +544,7 @@ public class SettingsActivity extends PreferenceResultActivity implements Shared chatRefreshInterval.setSummary(chatRefreshInterval.getEntry()); directoryCacheTime.setSummary(directoryCacheTime.getEntry()); viewRefresh.setSummary(viewRefresh.getEntry()); + imageLoaderConcurrency.setSummary(imageLoaderConcurrency.getEntry()); sharingDefaultExpiration.setSummary(sharingDefaultExpiration.getText()); sharingDefaultDescription.setSummary(sharingDefaultDescription.getText()); sharingDefaultGreeting.setSummary(sharingDefaultGreeting.getText()); diff --git a/src/com/thejoshwa/ultrasonic/androidapp/activity/SubsonicTabActivity.java b/src/com/thejoshwa/ultrasonic/androidapp/activity/SubsonicTabActivity.java index a0bb09da..82847ddd 100644 --- a/src/com/thejoshwa/ultrasonic/androidapp/activity/SubsonicTabActivity.java +++ b/src/com/thejoshwa/ultrasonic/androidapp/activity/SubsonicTabActivity.java @@ -94,7 +94,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen { private static final String TAG = SubsonicTabActivity.class.getSimpleName(); private static final Pattern COMPILE = Pattern.compile(":"); - private static ImageLoader IMAGE_LOADER; + protected static ImageLoader IMAGE_LOADER; protected static String theme; private static SubsonicTabActivity instance; @@ -228,7 +228,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen super.onDestroy(); destroyed = true; nowPlayingView = null; - getImageLoader().clear(); + clearImageLoader(); } @Override @@ -957,12 +957,19 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen } } + public synchronized void clearImageLoader() + { + if (IMAGE_LOADER != null && IMAGE_LOADER.isRunning()) IMAGE_LOADER.clear(); + } + public synchronized ImageLoader getImageLoader() { - if (IMAGE_LOADER == null) + if (IMAGE_LOADER == null || !IMAGE_LOADER.isRunning()) { - IMAGE_LOADER = new ImageLoader(this); + IMAGE_LOADER = new ImageLoader(this, Util.getImageLoaderConcurrency(this)); + IMAGE_LOADER.startImageLoader(); } + return IMAGE_LOADER; } diff --git a/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadService.java b/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadService.java index fb66a56a..c4e62da5 100644 --- a/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadService.java +++ b/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadService.java @@ -140,4 +140,8 @@ public interface DownloadService void swap(boolean mainList, int from, int to); void restore(List songs, int currentPlayingIndex, int currentPlayingPosition, boolean autoPlay, boolean newPlaylist); + + void stopJukeboxService(); + + void startJukeboxService(); } diff --git a/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadServiceImpl.java b/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadServiceImpl.java index 4995b63c..4d370391 100644 --- a/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadServiceImpl.java +++ b/src/com/thejoshwa/ultrasonic/androidapp/service/DownloadServiceImpl.java @@ -466,6 +466,18 @@ public class DownloadServiceImpl extends Service implements DownloadService } } + @Override + public void stopJukeboxService() + { + jukeboxService.stopJukeboxService(); + } + + @Override + public void startJukeboxService() + { + jukeboxService.startJukeboxService(); + } + @Override public synchronized void setShufflePlayEnabled(boolean enabled) { @@ -1414,8 +1426,11 @@ public class DownloadServiceImpl extends Service implements DownloadService { this.jukeboxEnabled = jukeboxEnabled; jukeboxService.setEnabled(jukeboxEnabled); + if (jukeboxEnabled) { + jukeboxService.startJukeboxService(); + reset(); // Cancel current download, if necessary. @@ -1424,6 +1439,10 @@ public class DownloadServiceImpl extends Service implements DownloadService currentDownloading.cancelDownload(); } } + else + { + jukeboxService.stopJukeboxService(); + } } @Override @@ -1505,6 +1524,10 @@ public class DownloadServiceImpl extends Service implements DownloadService audioManager.unregisterRemoteControlClient(remoteControlClient); audioManager.registerRemoteControlClient(remoteControlClient); } + else + { + setUpRemoteControlClient(); + } Log.i(TAG, String.format("In updateRemoteControl, playerState: %s [%d]", playerState, getPlayerPosition())); diff --git a/src/com/thejoshwa/ultrasonic/androidapp/service/JukeboxService.java b/src/com/thejoshwa/ultrasonic/androidapp/service/JukeboxService.java index dbaa184b..f78834de 100644 --- a/src/com/thejoshwa/ultrasonic/androidapp/service/JukeboxService.java +++ b/src/com/thejoshwa/ultrasonic/androidapp/service/JukeboxService.java @@ -64,6 +64,8 @@ public class JukeboxService private JukeboxStatus jukeboxStatus; private float gain = 0.5f; private VolumeToast volumeToast; + private boolean running = false; + private Thread serviceThread; // TODO: Report warning if queue fills up. // TODO: Create shutdown method? @@ -74,19 +76,42 @@ public class JukeboxService public JukeboxService(DownloadServiceImpl downloadService) { this.downloadService = downloadService; - new Thread() + } + + public void startJukeboxService() + { + if (running) return; + + running = true; + startProcessTasks(); + } + + public void stopJukeboxService() + { + running = false; + Util.sleepQuietly(1000); + + if (serviceThread != null) serviceThread.interrupt(); + } + + private void startProcessTasks() + { + serviceThread = new Thread() { @Override public void run() { processTasks(); } - }.start(); + }; + + serviceThread.start(); } private synchronized void startStatusUpdate() { stopStatusUpdate(); + Runnable updateTask = new Runnable() { @Override @@ -96,6 +121,7 @@ public class JukeboxService tasks.add(new GetStatus()); } }; + statusUpdateFuture = executorService.scheduleWithFixedDelay(updateTask, STATUS_UPDATE_INTERVAL_SECONDS, STATUS_UPDATE_INTERVAL_SECONDS, TimeUnit.SECONDS); } @@ -110,9 +136,10 @@ public class JukeboxService private void processTasks() { - while (true) + while (running) { JukeboxTask task = null; + try { if (!Util.isOffline(downloadService)) @@ -121,11 +148,17 @@ public class JukeboxService JukeboxStatus status = task.execute(); onStatusUpdate(status); } + } + catch (InterruptedException ignored) + { + } catch (Throwable x) { onError(task, x); } + + Util.sleepQuietly(1); } } @@ -136,6 +169,7 @@ public class JukeboxService // Track change? Integer index = jukeboxStatus.getCurrentPlayingIndex(); + if (index != null && index != -1 && index != downloadService.getCurrentPlayingIndex()) { downloadService.setCurrentPlaying(index); @@ -165,6 +199,7 @@ public class JukeboxService private void disableJukeboxOnError(Throwable x, final int resourceId) { Log.w(TAG, x.toString()); + handler.post(new Runnable() { @Override @@ -173,6 +208,7 @@ public class JukeboxService Util.toast(downloadService, resourceId, false); } }); + downloadService.setJukeboxEnabled(false); } @@ -187,6 +223,7 @@ public class JukeboxService { ids.add(file.getSong().getId()); } + tasks.add(new SetPlaylist(ids)); } @@ -213,6 +250,7 @@ public class JukeboxService tasks.remove(Start.class); stopStatusUpdate(); + tasks.add(new Stop()); } @@ -266,17 +304,19 @@ public class JukeboxService public void setEnabled(boolean enabled) { tasks.clear(); + if (enabled) { updatePlaylist(); } + stop(); + downloadService.setPlayerState(PlayerState.IDLE); } private static class TaskQueue { - private final LinkedBlockingQueue queue = new LinkedBlockingQueue(); void add(JukeboxTask jukeboxTask) @@ -289,15 +329,17 @@ public class JukeboxService return queue.take(); } - void remove(Class clazz) + void remove(Class taskClass) { try { Iterator iterator = queue.iterator(); + while (iterator.hasNext()) { JukeboxTask task = iterator.next(); - if (clazz.equals(task.getClass())) + + if (taskClass.equals(task.getClass())) { iterator.remove(); } @@ -317,7 +359,6 @@ public class JukeboxService private abstract class JukeboxTask { - abstract JukeboxStatus execute() throws Exception; @Override @@ -338,7 +379,6 @@ public class JukeboxService private class SetPlaylist extends JukeboxTask { - private final List ids; SetPlaylist(List ids) diff --git a/src/com/thejoshwa/ultrasonic/androidapp/util/Constants.java b/src/com/thejoshwa/ultrasonic/androidapp/util/Constants.java index 15058088..ac959abe 100644 --- a/src/com/thejoshwa/ultrasonic/androidapp/util/Constants.java +++ b/src/com/thejoshwa/ultrasonic/androidapp/util/Constants.java @@ -128,6 +128,7 @@ public final class Constants public static final String PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION = "sharingDefaultExpiration"; public static final String PREFERENCES_KEY_SHOW_ALL_SONGS_BY_ARTIST = "showAllSongsByArtist"; public static final String PREFERENCES_KEY_SCAN_MEDIA = "scanMedia"; + public static final String PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY = "imageLoaderConcurrency"; // Name of the preferences file. public static final String PREFERENCES_FILE_NAME = "com.thejoshwa.ultrasonic.androidapp_preferences"; diff --git a/src/com/thejoshwa/ultrasonic/androidapp/util/ImageLoader.java b/src/com/thejoshwa/ultrasonic/androidapp/util/ImageLoader.java index 56585e48..ba2435ac 100644 --- a/src/com/thejoshwa/ultrasonic/androidapp/util/ImageLoader.java +++ b/src/com/thejoshwa/ultrasonic/androidapp/util/ImageLoader.java @@ -36,6 +36,8 @@ import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory; import com.thejoshwa.ultrasonic.androidapp.service.MusicService; import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory; +import java.util.ArrayList; +import java.util.Collection; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -50,7 +52,6 @@ public class ImageLoader implements Runnable { private static final String TAG = ImageLoader.class.getSimpleName(); - private static final int CONCURRENCY = 5; private final LRUCache cache = new LRUCache(150); private final BlockingQueue queue; @@ -58,10 +59,14 @@ public class ImageLoader implements Runnable private final int imageSizeLarge; private Bitmap largeUnknownImage; private Context context; + private Collection threads = new ArrayList(); + private boolean running = false; + private int concurrency; - public ImageLoader(Context context) + public ImageLoader(Context context, int concurrency) { this.context = context; + this.concurrency = concurrency; queue = new LinkedBlockingQueue(1000); Resources resources = context.getResources(); @@ -76,12 +81,36 @@ public class ImageLoader implements Runnable DisplayMetrics metrics = context.getResources().getDisplayMetrics(); imageSizeLarge = Math.round(Math.min(metrics.widthPixels, metrics.heightPixels)); - for (int i = 0; i < CONCURRENCY; i++) + createLargeUnknownImage(context); + } + + public synchronized boolean isRunning() + { + return running && !threads.isEmpty(); + } + + public void startImageLoader() + { + running = true; + + for (int i = 0; i < this.concurrency; i++) { - new Thread(this, "ImageLoader").start(); + Thread thread = new Thread(this, "ImageLoader"); + threads.add(thread); + thread.start(); + } + } + + public synchronized void stopImageLoader() + { + clear(); + + for (Thread thread : threads) + { + thread.interrupt(); } - createLargeUnknownImage(context); + running = false; } private void createLargeUnknownImage(Context context) @@ -131,7 +160,7 @@ public class ImageLoader implements Runnable private static String getKey(String coverArtId, int size) { - return coverArtId + ":" + size; + return String.format("%s:%d", coverArtId, size); } public Bitmap getImageBitmap(MusicDirectory.Entry entry, boolean large, int size) @@ -226,12 +255,16 @@ public class ImageLoader implements Runnable @Override public void run() { - while (true) + while (running) { try { Task task = queue.take(); task.execute(); + } + catch (InterruptedException ignored) + { + } catch (Throwable x) { diff --git a/src/com/thejoshwa/ultrasonic/androidapp/util/Util.java b/src/com/thejoshwa/ultrasonic/androidapp/util/Util.java index fc478a01..27470e31 100644 --- a/src/com/thejoshwa/ultrasonic/androidapp/util/Util.java +++ b/src/com/thejoshwa/ultrasonic/androidapp/util/Util.java @@ -52,6 +52,7 @@ import android.widget.Toast; import com.thejoshwa.ultrasonic.androidapp.R; import com.thejoshwa.ultrasonic.androidapp.activity.DownloadActivity; import com.thejoshwa.ultrasonic.androidapp.activity.MainActivity; +import com.thejoshwa.ultrasonic.androidapp.activity.SettingsActivity; import com.thejoshwa.ultrasonic.androidapp.domain.Bookmark; import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory; import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory.Entry; @@ -127,7 +128,7 @@ public class Util extends DownloadActivity public static boolean isOffline(Context context) { - return context != null && getActiveServer(context) == 0; + return context == null || getActiveServer(context) == 0; } public static boolean isScreenLitOnDownload(Context context) @@ -257,6 +258,7 @@ public class Util extends DownloadActivity { return false; } + SharedPreferences preferences = getPreferences(context); return preferences.getBoolean(Constants.PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + instance, false); } @@ -806,6 +808,19 @@ public class Util extends DownloadActivity showDialog(context, android.R.drawable.ic_dialog_info, titleId, messageId); } + public static void showWelcomeDialog(final Context context, final MainActivity activity, int titleId, int messageId) + { + new AlertDialog.Builder(context).setIcon(android.R.drawable.ic_dialog_info).setTitle(titleId).setMessage(messageId).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int i) + { + dialog.dismiss(); + activity.startActivityForResultWithoutTransition(activity, SettingsActivity.class); + } + }).show(); + } + private static void showDialog(Context context, int icon, int titleId, int messageId) { new AlertDialog.Builder(context).setIcon(icon).setTitle(titleId).setMessage(messageId).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() @@ -1631,4 +1646,10 @@ public class Util extends DownloadActivity Intent scanFileIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri); context.sendBroadcast(scanFileIntent); } + + public static int getImageLoaderConcurrency(Context context) + { + SharedPreferences preferences = getPreferences(context); + return Integer.parseInt(preferences.getString(Constants.PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY, "5")); + } }