;
+}
+
+-keep class !android.support.v7.internal.view.menu.**,** {*;}
+
+-dontwarn
+-ignorewarnings
+
+# ------- FastScrollRecycleView START -------
+-keep class com.simplecityapps.recyclerview_fastscroll.views.FastScrollPopup { *; }
+# ------- FastScrollRecycleView END -------
+
+-keep public class android.support.design.widget.BottomNavigationView { *; }
+-keep public class android.support.design.internal.BottomNavigationMenuView { *; }
+-keep public class android.support.design.internal.BottomNavigationPresenter { *; }
+-keep public class android.support.design.internal.BottomNavigationItemView { *; }
+
+#-dontwarn android.support.v8.renderscript.*
+#-keepclassmembers class android.support.v8.renderscript.RenderScript {
+# native *** rsn*(...);
+# native *** n*(...);
+#}
+
+#-keep class org.jaudiotagger.** { *; }
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..5fa8503e1
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/assets/fonts/circular_std_black.otf b/app/src/main/assets/fonts/circular_std_black.otf
new file mode 100755
index 000000000..c62b210c5
Binary files /dev/null and b/app/src/main/assets/fonts/circular_std_black.otf differ
diff --git a/app/src/main/assets/fonts/circular_std_book.otf b/app/src/main/assets/fonts/circular_std_book.otf
new file mode 100755
index 000000000..3a1f1ad82
Binary files /dev/null and b/app/src/main/assets/fonts/circular_std_book.otf differ
diff --git a/app/src/main/assets/fonts/product_sans_bold.ttf b/app/src/main/assets/fonts/product_sans_bold.ttf
new file mode 100755
index 000000000..d847195c7
Binary files /dev/null and b/app/src/main/assets/fonts/product_sans_bold.ttf differ
diff --git a/app/src/main/assets/fonts/product_sans_regular.ttf b/app/src/main/assets/fonts/product_sans_regular.ttf
new file mode 100755
index 000000000..c0442ee29
Binary files /dev/null and b/app/src/main/assets/fonts/product_sans_regular.ttf differ
diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html
new file mode 100644
index 000000000..115106cb5
--- /dev/null
+++ b/app/src/main/assets/index.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Phonograph by Karim Abou Zeid
+ RxAndroid by RxAndroid authors
+ RxJava by RxJava authors
+ Material Dialogs by Aidan Michael Follestad
+ Calligraphy by RxJava authors
+ Android-Snowfall by JetRadar
+ Android Sliding Up Panelby The Umano Team
+ AOSP Support Librariesby AOSP contributors
+ Butter Knife by Jake Wharton
+ Glide by Sam Judd
+ Retrofit by Square team
+ Material Contextual Action Bar by Aidan Michael Follestad
+ OkHttp by Square team
+ CircleImageView by Henning Dodenhof
+ Transitions Everywhere by Henning Dodenhof
+ MaterialProgressBar by Zhang Hai
+ Android In-App Billing v3 Library by Henning Dodenhof
+ Advanced RecyclerView by Haruki Hasegawa
+ Android-ObservableScrollView by Soichiro Kashima
+ BottomNavigationViewEx by Ittianyu
+ Swipe-Button by EBANX Team
+ Font used(CIRCULAR STD BOOK FONT) by NIELSON CAETANO
+ Icons by Austin Andrews
+ Croller by Harjot Oberai
+ Material Design City Wallpaper
+
+
+
diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png
new file mode 100644
index 000000000..924099d54
Binary files /dev/null and b/app/src/main/ic_launcher-web.png differ
diff --git a/app/src/main/java/code/name/monkey/retromusic/Constants.java b/app/src/main/java/code/name/monkey/retromusic/Constants.java
new file mode 100644
index 000000000..2252315da
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/Constants.java
@@ -0,0 +1,48 @@
+package code.name.monkey.retromusic;
+
+public class Constants {
+
+ public static final String DISCORD_LINK = "https://discord.gg/qTecXXn";
+
+ public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic";
+ public static final String MUSIC_PACKAGE_NAME = "com.android.music";
+ public static final String ACTION_TOGGLE_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".togglepause";
+ public static final String ACTION_PLAY = RETRO_MUSIC_PACKAGE_NAME + ".play";
+ public static final String ACTION_PLAY_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + ".play.playlist";
+ public static final String ACTION_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".pause";
+ public static final String ACTION_STOP = RETRO_MUSIC_PACKAGE_NAME + ".stop";
+ public static final String ACTION_SKIP = RETRO_MUSIC_PACKAGE_NAME + ".skip";
+ public static final String ACTION_REWIND = RETRO_MUSIC_PACKAGE_NAME + ".rewind";
+ public static final String ACTION_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".quitservice";
+ public static final String INTENT_EXTRA_PLAYLIST =
+ RETRO_MUSIC_PACKAGE_NAME + "intentextra.playlist";
+ public static final String INTENT_EXTRA_SHUFFLE_MODE =
+ RETRO_MUSIC_PACKAGE_NAME + ".intentextra.shufflemode";
+ public static final String APP_WIDGET_UPDATE = RETRO_MUSIC_PACKAGE_NAME + ".appwidgetupdate";
+ public static final String EXTRA_APP_WIDGET_NAME = RETRO_MUSIC_PACKAGE_NAME + "app_widget_name";
+ // do not change these three strings as it will break support with other apps (e.g. last.fm scrobbling)
+ public static final String META_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".metachanged";
+ public static final String QUEUE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".queuechanged";
+ public static final String PLAY_STATE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".playstatechanged";
+ public static final String REPEAT_MODE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".repeatmodechanged";
+ public static final String SHUFFLE_MODE_CHANGED =
+ RETRO_MUSIC_PACKAGE_NAME + ".shufflemodechanged";
+ public static final String MEDIA_STORE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".mediastorechanged";
+ public static final String RATE_ON_GOOGLE_PLAY = "https://play.google.com/store/apps/details?id=code.name.monkey.retromusic";
+ public static final String LUIS_GOMZ_GOOGLE_PLUS = "https://plus.google.com/104046235912044592643";
+ public static final String LUIS_GOMZ_TWITTER = "https://www.twitter.com/LuisGmzz";
+ public static final String PAYPAL_ME_URL = "https://www.paypal.me/h4h14";
+ public static final String GOOGLE_PLUS_COMMUNITY = "https://plus.google.com/communities/110811566242871492162";
+ public static final String TRANSLATE = "http://monkeycodeapp.oneskyapp.com/collaboration/project?id=238534";
+ public static final String GITHUB_PROJECT = "https://github.com/h4h13/RetroMusicApp";
+ public static final String BASE_API_URL_KUGOU = "http://lyrics.kugou.com/";
+ public static final String TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog";
+ public static final String USER_PROFILE = "profile.jpg";
+ public static final String USER_BANNER = "banner.jpg";
+ public static final String APP_INSTAGRAM_LINK = "https://www.instagram.com/retromusicapp/";
+ public static final String APP_TELEGRAM_LINK = "https://t.me/retromusicapp/";
+ public static final String APP_TWITTER_LINK = "https://twitter.com/retromusicapp";
+ public static final int CAST_SERVER_PORT = 8080;
+ public static final String FAQ_LINK = "https://github.com/h4h13/RetroMusicApp/blob/master/FAQ.md";
+ public static String LINE_SEPARATOR = System.getProperty("line.separator");
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/Injection.java b/app/src/main/java/code/name/monkey/retromusic/Injection.java
new file mode 100644
index 000000000..481613ff2
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/Injection.java
@@ -0,0 +1,23 @@
+package code.name.monkey.retromusic;
+
+import code.name.monkey.retromusic.providers.RepositoryImpl;
+import code.name.monkey.retromusic.providers.interfaces.Repository;
+import code.name.monkey.retromusic.rest.KogouClient;
+import code.name.monkey.retromusic.rest.service.KuGouApiService;
+import code.name.monkey.retromusic.util.schedulers.BaseSchedulerProvider;
+import code.name.monkey.retromusic.util.schedulers.SchedulerProvider;
+
+public class Injection {
+
+ public static Repository provideRepository() {
+ return RepositoryImpl.getInstance();
+ }
+
+ public static BaseSchedulerProvider provideSchedulerProvider() {
+ return SchedulerProvider.getInstance();
+ }
+
+ public static KuGouApiService provideKuGouApiService() {
+ return new KogouClient().getApiService();
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/RetroApplication.java b/app/src/main/java/code/name/monkey/retromusic/RetroApplication.java
new file mode 100644
index 000000000..cc2abeef8
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/RetroApplication.java
@@ -0,0 +1,77 @@
+package code.name.monkey.retromusic;
+
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.multidex.MultiDexApplication;
+import code.name.monkey.appthemehelper.ThemeStore;
+import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager;
+import com.anjlab.android.iab.v3.BillingProcessor;
+import com.anjlab.android.iab.v3.TransactionDetails;
+import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
+
+public class RetroApplication extends MultiDexApplication {
+
+ public static final String PRO_VERSION_PRODUCT_ID = "pro_version";
+
+ private static RetroApplication app;
+
+ private BillingProcessor billingProcessor;
+
+ public static RetroApplication getInstance() {
+ return app;
+ }
+
+ public static boolean isProVersion() {
+ return BuildConfig.DEBUG || app.billingProcessor.isPurchased(PRO_VERSION_PRODUCT_ID);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ app = this;
+
+ // default theme
+ if (!ThemeStore.isConfigured(this, 1)) {
+ ThemeStore.editTheme(this)
+ .accentColorRes(R.color.md_green_A200)
+ .commit();
+ }
+
+ CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
+ .setDefaultFontPath("fonts/circular_std_book.otf")
+ .setFontAttrId(R.attr.fontPath)
+ .build()
+ );
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
+ new DynamicShortcutManager(this).initDynamicShortcuts();
+ }
+
+ // automatically restores purchases
+ billingProcessor = new BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSE_KEY,
+ new BillingProcessor.IBillingHandler() {
+ @Override
+ public void onProductPurchased(@NonNull String productId, TransactionDetails details) {
+ }
+
+ @Override
+ public void onPurchaseHistoryRestored() {
+ //Toast.makeText(App.this, R.string.restored_previous_purchase_please_restart, Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public void onBillingError(int errorCode, Throwable error) {
+ }
+
+ @Override
+ public void onBillingInitialized() {
+ }
+ });
+ }
+
+ @Override
+ public void onTerminate() {
+ super.onTerminate();
+ billingProcessor.release();
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.java
new file mode 100644
index 000000000..7c9eef617
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.java
@@ -0,0 +1,71 @@
+package code.name.monkey.retromusic.appshortcuts;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.util.TypedValue;
+
+import code.name.monkey.appthemehelper.ThemeStore;
+
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.util.PreferenceUtil;
+import code.name.monkey.retromusic.util.RetroUtil;
+
+/**
+ * @author Adrian Campos
+ */
+@RequiresApi(Build.VERSION_CODES.N_MR1)
+public final class AppShortcutIconGenerator {
+ public static Icon generateThemedIcon(Context context, int iconId) {
+ if (PreferenceUtil.getInstance(context).coloredAppShortcuts()){
+ return generateUserThemedIcon(context, iconId);
+ } else {
+ return generateDefaultThemedIcon(context, iconId);
+ }
+ }
+
+ private static Icon generateDefaultThemedIcon(Context context, int iconId) {
+ // Return an Icon of iconId with default colors
+ return generateThemedIcon(context, iconId,
+ context.getColor(R.color.app_shortcut_default_foreground),
+ context.getColor(R.color.app_shortcut_default_background)
+ );
+ }
+
+ private static Icon generateUserThemedIcon(Context context, int iconId) {
+ // Get background color from context's theme
+ final TypedValue typedColorBackground = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.colorBackground, typedColorBackground, true);
+
+ // Return an Icon of iconId with those colors
+ return generateThemedIcon(context, iconId,
+ ThemeStore.accentColor(context),
+ typedColorBackground.data
+ );
+ }
+
+ private static Icon generateThemedIcon(Context context, int iconId, int foregroundColor, int backgroundColor) {
+ // Get and tint foreground and background drawables
+ Drawable vectorDrawable = RetroUtil.getTintedVectorDrawable(context, iconId, foregroundColor);
+ Drawable backgroundDrawable = RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_app_shortcut_background, backgroundColor);
+
+ // Squash the two drawables together
+ LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{backgroundDrawable, vectorDrawable});
+
+ // Return as an Icon
+ return Icon.createWithBitmap(drawableToBitmap(layerDrawable));
+ }
+
+ private static Bitmap drawableToBitmap(Drawable drawable) {
+ Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.java
new file mode 100644
index 000000000..85e9c4021
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.java
@@ -0,0 +1,77 @@
+package code.name.monkey.retromusic.appshortcuts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import code.name.monkey.retromusic.model.Playlist;
+import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist;
+import code.name.monkey.retromusic.model.smartplaylist.MyTopTracksPlaylist;
+import code.name.monkey.retromusic.model.smartplaylist.ShuffleAllPlaylist;
+
+import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType;
+import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType;
+import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType;
+import code.name.monkey.retromusic.service.MusicService;
+
+import static code.name.monkey.retromusic.Constants.*;
+
+/**
+ * @author Adrian Campos
+ */
+
+public class AppShortcutLauncherActivity extends Activity {
+ public static final String KEY_SHORTCUT_TYPE = "code.name.monkey.retromusic.appshortcuts.ShortcutType";
+
+ public static final int SHORTCUT_TYPE_SHUFFLE_ALL = 0;
+ public static final int SHORTCUT_TYPE_TOP_TRACKS = 1;
+ public static final int SHORTCUT_TYPE_LAST_ADDED = 2;
+ public static final int SHORTCUT_TYPE_NONE = 3;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ int shortcutType = SHORTCUT_TYPE_NONE;
+
+ // Set shortcutType from the intent extras
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ //noinspection WrongConstant
+ shortcutType = extras.getInt(KEY_SHORTCUT_TYPE, SHORTCUT_TYPE_NONE);
+ }
+
+ switch (shortcutType) {
+ case SHORTCUT_TYPE_SHUFFLE_ALL:
+ startServiceWithPlaylist(MusicService.SHUFFLE_MODE_SHUFFLE,
+ new ShuffleAllPlaylist(getApplicationContext()));
+ DynamicShortcutManager.reportShortcutUsed(this, ShuffleAllShortcutType.getId());
+ break;
+ case SHORTCUT_TYPE_TOP_TRACKS:
+ startServiceWithPlaylist(MusicService.SHUFFLE_MODE_NONE,
+ new MyTopTracksPlaylist(getApplicationContext()));
+ DynamicShortcutManager.reportShortcutUsed(this, TopTracksShortcutType.getId());
+ break;
+ case SHORTCUT_TYPE_LAST_ADDED:
+ startServiceWithPlaylist(MusicService.SHUFFLE_MODE_NONE,
+ new LastAddedPlaylist(getApplicationContext()));
+ DynamicShortcutManager.reportShortcutUsed(this, LastAddedShortcutType.getId());
+ break;
+ }
+
+ finish();
+ }
+
+ private void startServiceWithPlaylist(int shuffleMode, Playlist playlist) {
+ Intent intent = new Intent(this, MusicService.class);
+ intent.setAction(ACTION_PLAY_PLAYLIST);
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(INTENT_EXTRA_PLAYLIST, playlist);
+ bundle.putInt(INTENT_EXTRA_SHUFFLE_MODE, shuffleMode);
+
+ intent.putExtras(bundle);
+
+ startService(intent);
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.java
new file mode 100644
index 000000000..5fb280549
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.java
@@ -0,0 +1,63 @@
+package code.name.monkey.retromusic.appshortcuts;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Build;
+
+import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType;
+import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType;
+import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Adrian Campos
+ */
+
+@TargetApi(Build.VERSION_CODES.N_MR1)
+public class DynamicShortcutManager {
+
+ private Context context;
+ private ShortcutManager shortcutManager;
+
+ public DynamicShortcutManager(Context context) {
+ this.context = context;
+ shortcutManager = this.context.getSystemService(ShortcutManager.class);
+ }
+
+ public static ShortcutInfo createShortcut(Context context, String id, String shortLabel, String longLabel, Icon icon, Intent intent) {
+ return new ShortcutInfo.Builder(context, id)
+ .setShortLabel(shortLabel)
+ .setLongLabel(longLabel)
+ .setIcon(icon)
+ .setIntent(intent)
+ .build();
+ }
+
+ public void initDynamicShortcuts() {
+ if (shortcutManager.getDynamicShortcuts().size() == 0) {
+ shortcutManager.setDynamicShortcuts(getDefaultShortcuts());
+ }
+ }
+
+ public void updateDynamicShortcuts() {
+ shortcutManager.updateShortcuts(getDefaultShortcuts());
+ }
+
+ public List getDefaultShortcuts() {
+ return (Arrays.asList(
+ new ShuffleAllShortcutType(context).getShortcutInfo(),
+ new TopTracksShortcutType(context).getShortcutInfo(),
+ new LastAddedShortcutType(context).getShortcutInfo()
+ ));
+ }
+
+ public static void reportShortcutUsed(Context context, String shortcutId){
+ context.getSystemService(ShortcutManager.class).reportShortcutUsed(shortcutId);
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.java
new file mode 100644
index 000000000..28f16a7e4
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.java
@@ -0,0 +1,50 @@
+package code.name.monkey.retromusic.appshortcuts.shortcuttype;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.os.Build;
+import android.os.Bundle;
+
+import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity;
+
+
+/**
+ * @author Adrian Campos
+ */
+@TargetApi(Build.VERSION_CODES.N_MR1)
+public abstract class BaseShortcutType {
+
+ static final String ID_PREFIX = "code.name.monkey.retromusic.appshortcuts.id.";
+
+ Context context;
+
+ public BaseShortcutType(Context context) {
+ this.context = context;
+ }
+
+ static public String getId() {
+ return ID_PREFIX + "invalid";
+ }
+
+ abstract ShortcutInfo getShortcutInfo();
+
+ /**
+ * Creates an Intent that will launch MainActivtiy and immediately play {@param songs} in either shuffle or normal mode
+ *
+ * @param shortcutType Describes the type of shortcut to create (ShuffleAll, TopTracks, custom playlist, etc.)
+ * @return
+ */
+ Intent getPlaySongsIntent(int shortcutType) {
+ Intent intent = new Intent(context, AppShortcutLauncherActivity.class);
+ intent.setAction(Intent.ACTION_VIEW);
+
+ Bundle b = new Bundle();
+ b.putInt(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE, shortcutType);
+
+ intent.putExtras(b);
+
+ return intent;
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.java
new file mode 100644
index 000000000..72db3eeb8
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.java
@@ -0,0 +1,34 @@
+package code.name.monkey.retromusic.appshortcuts.shortcuttype;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.os.Build;
+
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator;
+import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity;
+
+
+/**
+ * @author Adrian Campos
+ */
+@TargetApi(Build.VERSION_CODES.N_MR1)
+public final class LastAddedShortcutType extends BaseShortcutType {
+ public LastAddedShortcutType(Context context) {
+ super(context);
+ }
+
+ public static String getId() {
+ return ID_PREFIX + "last_added";
+ }
+
+ public ShortcutInfo getShortcutInfo() {
+ return new ShortcutInfo.Builder(context, getId())
+ .setShortLabel(context.getString(R.string.app_shortcut_last_added_short))
+ .setLongLabel(context.getString(R.string.app_shortcut_last_added_long))
+ .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_last_added))
+ .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_LAST_ADDED))
+ .build();
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.java
new file mode 100644
index 000000000..be3ba0088
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.java
@@ -0,0 +1,35 @@
+package code.name.monkey.retromusic.appshortcuts.shortcuttype;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.os.Build;
+
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator;
+import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity;
+
+
+
+/**
+ * @author Adrian Campos
+ */
+@TargetApi(Build.VERSION_CODES.N_MR1)
+public final class ShuffleAllShortcutType extends BaseShortcutType {
+ public ShuffleAllShortcutType(Context context) {
+ super(context);
+ }
+
+ public static String getId() {
+ return ID_PREFIX + "shuffle_all";
+ }
+
+ public ShortcutInfo getShortcutInfo() {
+ return new ShortcutInfo.Builder(context, getId())
+ .setShortLabel(context.getString(R.string.app_shortcut_shuffle_all_short))
+ .setLongLabel(context.getString(R.string.app_shortcut_shuffle_all_long))
+ .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_shuffle_all))
+ .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_SHUFFLE_ALL))
+ .build();
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.java
new file mode 100644
index 000000000..3e78db33c
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.java
@@ -0,0 +1,35 @@
+package code.name.monkey.retromusic.appshortcuts.shortcuttype;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.os.Build;
+
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator;
+import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity;
+
+
+
+/**
+ * @author Adrian Campos
+ */
+@TargetApi(Build.VERSION_CODES.N_MR1)
+public final class TopTracksShortcutType extends BaseShortcutType {
+ public TopTracksShortcutType(Context context) {
+ super(context);
+ }
+
+ public static String getId() {
+ return ID_PREFIX + "top_tracks";
+ }
+
+ public ShortcutInfo getShortcutInfo() {
+ return new ShortcutInfo.Builder(context, getId())
+ .setShortLabel(context.getString(R.string.app_shortcut_top_tracks_short))
+ .setLongLabel(context.getString(R.string.app_shortcut_top_tracks_long))
+ .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_top_tracks))
+ .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_TOP_TRACKS))
+ .build();
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.java
new file mode 100644
index 000000000..4b90e941f
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.java
@@ -0,0 +1,171 @@
+package code.name.monkey.retromusic.appwidgets;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.RemoteViews;
+import code.name.monkey.appthemehelper.util.MaterialValueHelper;
+import code.name.monkey.retromusic.Constants;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget;
+import code.name.monkey.retromusic.glide.SongGlideRequest;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.service.MusicService;
+import code.name.monkey.retromusic.ui.activities.MainActivity;
+import code.name.monkey.retromusic.util.RetroUtil;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+import com.bumptech.glide.request.target.Target;
+
+public class AppWidgetBig extends BaseAppWidget {
+
+ public static final String NAME = "app_widget_big";
+
+ private static AppWidgetBig mInstance;
+ private Target target; // for cancellation
+
+ public static synchronized AppWidgetBig getInstance() {
+ if (mInstance == null) {
+ mInstance = new AppWidgetBig();
+ }
+ return mInstance;
+ }
+
+ /**
+ * Initialize given widgets to default state, where we launch Music on default click and hide
+ * actions if service not running.
+ */
+ protected void defaultAppWidget(final Context context, final int[] appWidgetIds) {
+ final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(),
+ R.layout.app_widget_big);
+
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
+ appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art);
+ appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
+ RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp,
+ MaterialValueHelper.getPrimaryTextColor(context, false)), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
+ RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp,
+ MaterialValueHelper.getPrimaryTextColor(context, false)), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(
+ RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp,
+ MaterialValueHelper.getPrimaryTextColor(context, false)), 1f));
+
+ linkButtons(context, appWidgetView);
+ pushUpdate(context, appWidgetIds, appWidgetView);
+ }
+
+ /**
+ * Update all active widget instances by pushing changes
+ */
+ public void performUpdate(final MusicService service, final int[] appWidgetIds) {
+ final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(),
+ R.layout.app_widget_big);
+
+ final boolean isPlaying = service.isPlaying();
+ final Song song = service.getCurrentSong();
+
+ // Set the titles and artwork
+ if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
+ } else {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE);
+ appWidgetView.setTextViewText(R.id.title, song.title);
+ appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song));
+ }
+
+ // Set correct drawable for pause state
+ int playPauseRes =
+ isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp;
+ appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(
+ RetroUtil.getTintedVectorDrawable(service, playPauseRes,
+ MaterialValueHelper.getPrimaryTextColor(service, false)), 1f));
+
+ // Set prev/next button drawables
+ appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
+ RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp,
+ MaterialValueHelper.getPrimaryTextColor(service, false)), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
+ RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp,
+ MaterialValueHelper.getPrimaryTextColor(service, false)), 1f));
+
+ // Link actions buttons to intents
+ linkButtons(service, appWidgetView);
+
+ // Load the album cover async and push the update on completion
+ Point p = RetroUtil.getScreenSize(service);
+ final int widgetImageSize = Math.min(p.x, p.y);
+ final Context appContext = service.getApplicationContext();
+ service.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (target != null) {
+ Glide.clear(target);
+ }
+ target = SongGlideRequest.Builder.from(Glide.with(appContext), song)
+ .checkIgnoreMediaStore(appContext)
+ .asBitmap().build()
+ .into(new SimpleTarget(widgetImageSize, widgetImageSize) {
+ @Override
+ public void onResourceReady(Bitmap resource,
+ GlideAnimation super Bitmap> glideAnimation) {
+ update(resource);
+ }
+
+ @Override
+ public void onLoadFailed(Exception e, Drawable errorDrawable) {
+ super.onLoadFailed(e, errorDrawable);
+ update(null);
+ }
+
+ private void update(@Nullable Bitmap bitmap) {
+ if (bitmap == null) {
+ appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art);
+ } else {
+ appWidgetView.setImageViewBitmap(R.id.image, bitmap);
+ }
+ pushUpdate(appContext, appWidgetIds, appWidgetView);
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Link up various button actions using {@link PendingIntent}.
+ */
+ private void linkButtons(final Context context, final RemoteViews views) {
+ Intent action;
+ PendingIntent pendingIntent;
+
+ final ComponentName serviceName = new ComponentName(context, MusicService.class);
+
+ // Home
+ action = new Intent(context, MainActivity.class);
+ action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ pendingIntent = PendingIntent.getActivity(context, 0, action, 0);
+ views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent);
+
+ // Previous track
+ pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName);
+ views.setOnClickPendingIntent(R.id.button_prev, pendingIntent);
+
+ // Play and pause
+ pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName);
+ views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent);
+
+ // Next track
+ pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName);
+ views.setOnClickPendingIntent(R.id.button_next, pendingIntent);
+
+
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.java
new file mode 100644
index 000000000..d7c080672
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.java
@@ -0,0 +1,194 @@
+package code.name.monkey.retromusic.appwidgets;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+import android.support.v7.graphics.Palette;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.RemoteViews;
+import code.name.monkey.appthemehelper.util.MaterialValueHelper;
+import code.name.monkey.retromusic.Constants;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget;
+import code.name.monkey.retromusic.glide.SongGlideRequest;
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.service.MusicService;
+import code.name.monkey.retromusic.ui.activities.MainActivity;
+import code.name.monkey.retromusic.util.RetroUtil;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+import com.bumptech.glide.request.target.Target;
+
+public class AppWidgetCard extends BaseAppWidget {
+
+ public static final String NAME = "app_widget_card";
+
+ private static AppWidgetCard mInstance;
+ private static int imageSize = 0;
+ private static float cardRadius = 0f;
+ private Target target; // for cancellation
+
+ public static synchronized AppWidgetCard getInstance() {
+ if (mInstance == null) {
+ mInstance = new AppWidgetCard();
+ }
+ return mInstance;
+ }
+
+ /**
+ * Initialize given widgets to default state, where we launch Music on default click and hide
+ * actions if service not running.
+ */
+ protected void defaultAppWidget(final Context context, final int[] appWidgetIds) {
+ final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(),
+ R.layout.app_widget_card);
+
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
+ appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art);
+ appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
+ RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
+ RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(
+ RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
+
+ linkButtons(context, appWidgetView);
+ pushUpdate(context, appWidgetIds, appWidgetView);
+ }
+
+ /**
+ * Update all active widget instances by pushing changes
+ */
+ public void performUpdate(final MusicService service, final int[] appWidgetIds) {
+ final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(),
+ R.layout.app_widget_card);
+
+ final boolean isPlaying = service.isPlaying();
+ final Song song = service.getCurrentSong();
+
+ // Set the titles and artwork
+ if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
+ } else {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE);
+ appWidgetView.setTextViewText(R.id.title, song.title);
+ appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song));
+ }
+
+ // Set correct drawable for pause state
+ int playPauseRes =
+ isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp;
+ appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(
+ RetroUtil.getTintedVectorDrawable(service, playPauseRes,
+ MaterialValueHelper.getSecondaryTextColor(service, true)), 1f));
+
+ // Set prev/next button drawables
+ appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
+ RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp,
+ MaterialValueHelper.getSecondaryTextColor(service, true)), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
+ RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp,
+ MaterialValueHelper.getSecondaryTextColor(service, true)), 1f));
+
+ // Link actions buttons to intents
+ linkButtons(service, appWidgetView);
+
+ if (imageSize == 0) {
+ imageSize = service.getResources().getDimensionPixelSize(R.dimen.app_widget_card_image_size);
+ }
+ if (cardRadius == 0f) {
+ cardRadius = service.getResources().getDimension(R.dimen.app_widget_card_radius);
+ }
+
+ // Load the album cover async and push the update on completion
+ service.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (target != null) {
+ Glide.clear(target);
+ }
+ target = SongGlideRequest.Builder.from(Glide.with(service), song)
+ .checkIgnoreMediaStore(service)
+ .generatePalette(service).build()
+ .centerCrop()
+ .into(new SimpleTarget(imageSize, imageSize) {
+ @Override
+ public void onResourceReady(BitmapPaletteWrapper resource,
+ GlideAnimation super BitmapPaletteWrapper> glideAnimation) {
+ Palette palette = resource.getPalette();
+ update(resource.getBitmap(), palette.getVibrantColor(palette
+ .getMutedColor(MaterialValueHelper.getSecondaryTextColor(service, true))));
+ }
+
+ @Override
+ public void onLoadFailed(Exception e, Drawable errorDrawable) {
+ super.onLoadFailed(e, errorDrawable);
+ update(null, MaterialValueHelper.getSecondaryTextColor(service, true));
+ }
+
+ private void update(@Nullable Bitmap bitmap, int color) {
+ // Set correct drawable for pause state
+ int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp
+ : R.drawable.ic_play_arrow_white_24dp;
+ appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause,
+ createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRes, color), 1f));
+
+ // Set prev/next button drawables
+ appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
+ RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp,
+ color), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
+ RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp,
+ color), 1f));
+
+ final Drawable image = getAlbumArtDrawable(service.getResources(), bitmap);
+ final Bitmap roundedBitmap = createRoundedBitmap(image, imageSize, imageSize,
+ cardRadius, 0, cardRadius, 0);
+ appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap);
+
+ pushUpdate(service, appWidgetIds, appWidgetView);
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Link up various button actions using {@link PendingIntent}.
+ */
+ private void linkButtons(final Context context, final RemoteViews views) {
+ Intent action;
+ PendingIntent pendingIntent;
+
+ final ComponentName serviceName = new ComponentName(context, MusicService.class);
+
+ // Home
+ action = new Intent(context, MainActivity.class);
+ action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ pendingIntent = PendingIntent.getActivity(context, 0, action, 0);
+ views.setOnClickPendingIntent(R.id.image, pendingIntent);
+ views.setOnClickPendingIntent(R.id.media_titles, pendingIntent);
+
+ // Previous track
+ pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName);
+ views.setOnClickPendingIntent(R.id.button_prev, pendingIntent);
+
+ // Play and pause
+ pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName);
+ views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent);
+
+ // Next track
+ pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName);
+ views.setOnClickPendingIntent(R.id.button_next, pendingIntent);
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.java
new file mode 100644
index 000000000..120b9952c
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.java
@@ -0,0 +1,181 @@
+package code.name.monkey.retromusic.appwidgets;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+import android.support.v7.graphics.Palette;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.RemoteViews;
+import code.name.monkey.appthemehelper.util.MaterialValueHelper;
+import code.name.monkey.retromusic.Constants;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget;
+import code.name.monkey.retromusic.glide.SongGlideRequest;
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.service.MusicService;
+import code.name.monkey.retromusic.ui.activities.MainActivity;
+import code.name.monkey.retromusic.util.RetroUtil;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+import com.bumptech.glide.request.target.Target;
+
+public class AppWidgetClassic extends BaseAppWidget {
+
+ public static final String NAME = "app_widget_classic";
+
+ private static AppWidgetClassic mInstance;
+ private static int imageSize = 0;
+ private static float cardRadius = 0f;
+ private Target target; // for cancellation
+
+ public static synchronized AppWidgetClassic getInstance() {
+ if (mInstance == null) {
+ mInstance = new AppWidgetClassic();
+ }
+ return mInstance;
+ }
+
+ /**
+ * Initialize given widgets to default state, where we launch Music on default click and hide
+ * actions if service not running.
+ */
+ protected void defaultAppWidget(final Context context, final int[] appWidgetIds) {
+ final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(),
+ R.layout.app_widget_classic);
+
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
+ appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art);
+ appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
+ RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
+ RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(
+ RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
+
+ linkButtons(context, appWidgetView);
+ pushUpdate(context, appWidgetIds, appWidgetView);
+ }
+
+ /**
+ * Update all active widget instances by pushing changes
+ */
+ public void performUpdate(final MusicService service, final int[] appWidgetIds) {
+ final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(),
+ R.layout.app_widget_classic);
+
+ final boolean isPlaying = service.isPlaying();
+ final Song song = service.getCurrentSong();
+
+ // Set the titles and artwork
+ if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
+ } else {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE);
+ appWidgetView.setTextViewText(R.id.title, song.title);
+ appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song));
+ }
+
+ // Link actions buttons to intents
+ linkButtons(service, appWidgetView);
+
+ if (imageSize == 0) {
+ imageSize = service.getResources()
+ .getDimensionPixelSize(R.dimen.app_widget_classic_image_size);
+ }
+ if (cardRadius == 0f) {
+ cardRadius = service.getResources().getDimension(R.dimen.app_widget_card_radius);
+ }
+
+ // Load the album cover async and push the update on completion
+ final Context appContext = service.getApplicationContext();
+ service.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (target != null) {
+ Glide.clear(target);
+ }
+ target = SongGlideRequest.Builder.from(Glide.with(appContext), song)
+ .checkIgnoreMediaStore(appContext)
+ .generatePalette(service).build()
+ .centerCrop()
+ .into(new SimpleTarget(imageSize, imageSize) {
+ @Override
+ public void onResourceReady(BitmapPaletteWrapper resource,
+ GlideAnimation super BitmapPaletteWrapper> glideAnimation) {
+ Palette palette = resource.getPalette();
+ update(resource.getBitmap(), palette.getVibrantColor(palette
+ .getMutedColor(MaterialValueHelper.getSecondaryTextColor(appContext, true))));
+ }
+
+ @Override
+ public void onLoadFailed(Exception e, Drawable errorDrawable) {
+ super.onLoadFailed(e, errorDrawable);
+ update(null, MaterialValueHelper.getSecondaryTextColor(appContext, true));
+ }
+
+ private void update(@Nullable Bitmap bitmap, int color) {
+ // Set correct drawable for pause state
+ int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp
+ : R.drawable.ic_play_arrow_white_24dp;
+ appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause,
+ createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRes, color), 1f));
+
+ // Set prev/next button drawables
+ appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
+ RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp,
+ color), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
+ RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp,
+ color), 1f));
+
+ final Drawable image = getAlbumArtDrawable(service.getResources(), bitmap);
+ final Bitmap roundedBitmap = createRoundedBitmap(image, imageSize, imageSize,
+ cardRadius, 0, cardRadius, 0);
+ appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap);
+
+ pushUpdate(appContext, appWidgetIds, appWidgetView);
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Link up various button actions using {@link PendingIntent}.
+ */
+ private void linkButtons(final Context context, final RemoteViews views) {
+ Intent action;
+ PendingIntent pendingIntent;
+
+ final ComponentName serviceName = new ComponentName(context, MusicService.class);
+
+ // Home
+ action = new Intent(context, MainActivity.class);
+ action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ pendingIntent = PendingIntent.getActivity(context, 0, action, 0);
+ views.setOnClickPendingIntent(R.id.image, pendingIntent);
+ views.setOnClickPendingIntent(R.id.media_titles, pendingIntent);
+
+ // Previous track
+ pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName);
+ views.setOnClickPendingIntent(R.id.button_prev, pendingIntent);
+
+ // Play and pause
+ pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName);
+ views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent);
+
+ // Next track
+ pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName);
+ views.setOnClickPendingIntent(R.id.button_next, pendingIntent);
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.java
new file mode 100644
index 000000000..1d80a2d13
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.java
@@ -0,0 +1,186 @@
+package code.name.monkey.retromusic.appwidgets;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+import android.support.v7.graphics.Palette;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.RemoteViews;
+import code.name.monkey.appthemehelper.util.MaterialValueHelper;
+import code.name.monkey.retromusic.Constants;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget;
+import code.name.monkey.retromusic.glide.SongGlideRequest;
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.service.MusicService;
+import code.name.monkey.retromusic.ui.activities.MainActivity;
+import code.name.monkey.retromusic.util.RetroUtil;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+import com.bumptech.glide.request.target.Target;
+
+public class AppWidgetSmall extends BaseAppWidget {
+
+ public static final String NAME = "app_widget_small";
+
+ private static AppWidgetSmall mInstance;
+ private static int imageSize = 0;
+ private static float cardRadius = 0f;
+ private Target target; // for cancellation
+
+ public static synchronized AppWidgetSmall getInstance() {
+ if (mInstance == null) {
+ mInstance = new AppWidgetSmall();
+ }
+ return mInstance;
+ }
+
+ /**
+ * Initialize given widgets to default state, where we launch Music on default click and hide
+ * actions if service not running.
+ */
+ protected void defaultAppWidget(final Context context, final int[] appWidgetIds) {
+ final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(),
+ R.layout.app_widget_small);
+
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
+ appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art);
+ appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
+ RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
+ RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(
+ RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp,
+ MaterialValueHelper.getSecondaryTextColor(context, true)), 1f));
+
+ linkButtons(context, appWidgetView);
+ pushUpdate(context, appWidgetIds, appWidgetView);
+ }
+
+ /**
+ * Update all active widget instances by pushing changes
+ */
+ public void performUpdate(final MusicService service, final int[] appWidgetIds) {
+ final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(),
+ R.layout.app_widget_small);
+
+ final boolean isPlaying = service.isPlaying();
+ final Song song = service.getCurrentSong();
+
+ // Set the titles and artwork
+ if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
+ appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE);
+ } else {
+ if (TextUtils.isEmpty(song.title) || TextUtils.isEmpty(song.artistName)) {
+ appWidgetView.setTextViewText(R.id.text_separator, "");
+ } else {
+ appWidgetView.setTextViewText(R.id.text_separator, "•");
+ }
+
+ appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE);
+ appWidgetView.setTextViewText(R.id.title, song.title);
+ appWidgetView.setTextViewText(R.id.text, song.artistName);
+ }
+
+ // Link actions buttons to intents
+ linkButtons(service, appWidgetView);
+
+ if (imageSize == 0) {
+ imageSize = service.getResources().getDimensionPixelSize(R.dimen.app_widget_small_image_size);
+ }
+ if (cardRadius == 0f) {
+ cardRadius = service.getResources().getDimension(R.dimen.app_widget_card_radius);
+ }
+
+ // Load the album cover async and push the update on completion
+ final Context appContext = service.getApplicationContext();
+ service.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (target != null) {
+ Glide.clear(target);
+ }
+ target = SongGlideRequest.Builder.from(Glide.with(appContext), song)
+ .checkIgnoreMediaStore(appContext)
+ .generatePalette(service).build()
+ .centerCrop()
+ .into(new SimpleTarget(imageSize, imageSize) {
+ @Override
+ public void onResourceReady(BitmapPaletteWrapper resource,
+ GlideAnimation super BitmapPaletteWrapper> glideAnimation) {
+ Palette palette = resource.getPalette();
+ update(resource.getBitmap(), palette.getVibrantColor(palette
+ .getMutedColor(MaterialValueHelper.getSecondaryTextColor(appContext, true))));
+ }
+
+ @Override
+ public void onLoadFailed(Exception e, Drawable errorDrawable) {
+ super.onLoadFailed(e, errorDrawable);
+ update(null, MaterialValueHelper.getSecondaryTextColor(appContext, true));
+ }
+
+ private void update(@Nullable Bitmap bitmap, int color) {
+ // Set correct drawable for pause state
+ int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp
+ : R.drawable.ic_play_arrow_white_24dp;
+ appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause,
+ createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRes, color), 1f));
+
+ // Set prev/next button drawables
+ appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(
+ RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp,
+ color), 1f));
+ appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(
+ RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp,
+ color), 1f));
+
+ final Drawable image = getAlbumArtDrawable(service.getResources(), bitmap);
+ final Bitmap roundedBitmap = createRoundedBitmap(image, imageSize, imageSize,
+ cardRadius, 0, 0, 0);
+ appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap);
+
+ pushUpdate(appContext, appWidgetIds, appWidgetView);
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Link up various button actions using {@link PendingIntent}.
+ */
+ private void linkButtons(final Context context, final RemoteViews views) {
+ Intent action;
+ PendingIntent pendingIntent;
+
+ final ComponentName serviceName = new ComponentName(context, MusicService.class);
+
+ // Home
+ action = new Intent(context, MainActivity.class);
+ action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ pendingIntent = PendingIntent.getActivity(context, 0, action, 0);
+ views.setOnClickPendingIntent(R.id.image, pendingIntent);
+ views.setOnClickPendingIntent(R.id.media_titles, pendingIntent);
+
+ // Previous track
+ pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName);
+ views.setOnClickPendingIntent(R.id.button_prev, pendingIntent);
+
+ // Play and pause
+ pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName);
+ views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent);
+
+ // Next track
+ pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName);
+ views.setOnClickPendingIntent(R.id.button_next, pendingIntent);
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.java
new file mode 100644
index 000000000..38fb29895
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.java
@@ -0,0 +1,32 @@
+package code.name.monkey.retromusic.appwidgets;
+
+import android.appwidget.AppWidgetManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+
+import code.name.monkey.retromusic.service.MusicService;
+
+
+/**
+ * @author Eugene Cheung (arkon)
+ */
+public class BootReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
+
+ // Start music service if there are any existing widgets
+ if (widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetBig.class)).length > 0 ||
+ widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetClassic.class)).length > 0 ||
+ widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetSmall.class)).length > 0 ||
+ widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetCard.class)).length > 0) {
+ final Intent serviceIntent = new Intent(context, MusicService.class);
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // not allowed on Oreo
+ context.startService(serviceIntent);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.java
new file mode 100644
index 000000000..b2944cb44
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.java
@@ -0,0 +1,162 @@
+package code.name.monkey.retromusic.appwidgets.base;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.TextUtils;
+import android.widget.RemoteViews;
+import code.name.monkey.retromusic.Constants;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.service.MusicService;
+
+public abstract class BaseAppWidget extends AppWidgetProvider {
+
+ public static final String NAME = "app_widget";
+
+ protected static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) {
+ Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier),
+ (int) (drawable.getIntrinsicHeight() * sizeMultiplier), Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(bitmap);
+ drawable.setBounds(0, 0, c.getWidth(), c.getHeight());
+ drawable.draw(c);
+ return bitmap;
+ }
+
+ protected static Bitmap createRoundedBitmap(Drawable drawable, int width, int height, float tl,
+ float tr, float bl, float br) {
+ if (drawable == null) {
+ return null;
+ }
+
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(bitmap);
+ drawable.setBounds(0, 0, width, height);
+ drawable.draw(c);
+
+ Bitmap rounded = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+
+ Canvas canvas = new Canvas(rounded);
+ Paint paint = new Paint();
+ paint.setShader(
+ new BitmapShader(bitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
+ paint.setAntiAlias(true);
+ canvas.drawPath(composeRoundedRectPath(new RectF(0, 0, width, height), tl, tr, bl, br), paint);
+
+ return rounded;
+ }
+
+ protected static Path composeRoundedRectPath(RectF rect, float tl, float tr, float bl, float br) {
+ Path path = new Path();
+ tl = tl < 0 ? 0 : tl;
+ tr = tr < 0 ? 0 : tr;
+ bl = bl < 0 ? 0 : bl;
+ br = br < 0 ? 0 : br;
+
+ path.moveTo(rect.left + tl, rect.top);
+ path.lineTo(rect.right - tr, rect.top);
+ path.quadTo(rect.right, rect.top, rect.right, rect.top + tr);
+ path.lineTo(rect.right, rect.bottom - br);
+ path.quadTo(rect.right, rect.bottom, rect.right - br, rect.bottom);
+ path.lineTo(rect.left + bl, rect.bottom);
+ path.quadTo(rect.left, rect.bottom, rect.left, rect.bottom - bl);
+ path.lineTo(rect.left, rect.top + tl);
+ path.quadTo(rect.left, rect.top, rect.left + tl, rect.top);
+ path.close();
+
+ return path;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onUpdate(final Context context, final AppWidgetManager appWidgetManager,
+ final int[] appWidgetIds) {
+ defaultAppWidget(context, appWidgetIds);
+ final Intent updateIntent = new Intent(Constants.APP_WIDGET_UPDATE);
+ updateIntent.putExtra(Constants.EXTRA_APP_WIDGET_NAME, NAME);
+ updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
+ updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ context.sendBroadcast(updateIntent);
+ }
+
+ /**
+ * Handle a change notification coming over from {@link MusicService}
+ */
+ public void notifyChange(final MusicService service, final String what) {
+ if (hasInstances(service)) {
+ if (Constants.META_CHANGED.equals(what) || Constants.PLAY_STATE_CHANGED.equals(what)) {
+ performUpdate(service, null);
+ }
+ }
+ }
+
+ protected void pushUpdate(final Context context, final int[] appWidgetIds,
+ final RemoteViews views) {
+ final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ if (appWidgetIds != null) {
+ appWidgetManager.updateAppWidget(appWidgetIds, views);
+ } else {
+ appWidgetManager.updateAppWidget(new ComponentName(context, getClass()), views);
+ }
+ }
+
+ /**
+ * Check against {@link AppWidgetManager} if there are any instances of this widget.
+ */
+ protected boolean hasInstances(final Context context) {
+ final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ final int[] mAppWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context,
+ getClass()));
+ return mAppWidgetIds.length > 0;
+ }
+
+ protected PendingIntent buildPendingIntent(Context context, final String action,
+ final ComponentName serviceName) {
+ Intent intent = new Intent(action);
+ intent.setComponent(serviceName);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ return PendingIntent.getForegroundService(context, 0, intent, 0);
+ } else {
+ return PendingIntent.getService(context, 0, intent, 0);
+ }
+ }
+
+ abstract protected void defaultAppWidget(final Context context, final int[] appWidgetIds);
+
+ abstract public void performUpdate(final MusicService service, final int[] appWidgetIds);
+
+ protected Drawable getAlbumArtDrawable(final Resources resources, final Bitmap bitmap) {
+ Drawable image;
+ if (bitmap == null) {
+ image = resources.getDrawable(R.drawable.default_album_art);
+ } else {
+ image = new BitmapDrawable(resources, bitmap);
+ }
+ return image;
+ }
+
+ protected String getSongArtistAndAlbum(final Song song) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(song.artistName);
+ if (!TextUtils.isEmpty(song.artistName) && !TextUtils.isEmpty(song.albumName)) {
+ builder.append(" • ");
+ }
+ builder.append(song.albumName);
+ return builder.toString();
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.java
new file mode 100644
index 000000000..bd13a4e97
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.java
@@ -0,0 +1,94 @@
+package code.name.monkey.retromusic.dialogs;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.loaders.PlaylistLoader;
+import code.name.monkey.retromusic.model.Playlist;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.util.PlaylistsUtil;
+import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment;
+
+/**
+ * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad)
+ */
+public class AddToPlaylistDialog extends RoundedBottomSheetDialogFragment implements AdapterView.OnItemClickListener {
+
+ @BindView(R.id.playlists)
+ ListView playlist;
+ @BindView(R.id.title)
+ TextView title;
+ List playlists;
+
+ @NonNull
+ public static AddToPlaylistDialog create(Song song) {
+ ArrayList list = new ArrayList<>();
+ list.add(song);
+ return create(list);
+ }
+
+ @NonNull
+ public static AddToPlaylistDialog create(ArrayList songs) {
+ AddToPlaylistDialog dialog = new AddToPlaylistDialog();
+ Bundle args = new Bundle();
+ args.putParcelableArrayList("songs", songs);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View layout = inflater.inflate(R.layout.dialog_add_to_playlist, container, false);
+ ButterKnife.bind(this, layout);
+ return layout;
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ ArrayAdapter playlistAdapter = new ArrayAdapter<>(getActivity(), R.layout.simple_list_item);
+ playlists = PlaylistLoader.getAllPlaylists(getActivity()).blockingFirst();
+ playlistAdapter.add(getActivity().getResources().getString(R.string.action_new_playlist));
+
+ for (int i = 1; i < playlists.size(); i++) {
+ playlistAdapter.add(playlists.get(i - 1).name);
+ playlistAdapter.notifyDataSetChanged();
+ }
+
+ this.playlist.setAdapter(playlistAdapter);
+ this.playlist.setOnItemClickListener(this);
+ }
+
+ @Override
+ public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
+ //noinspection unchecked
+ final ArrayList songs = getArguments().getParcelableArrayList("songs");
+
+ if (songs == null) {
+ return;
+ }
+ if (i == 0) {
+ dismiss();
+ CreatePlaylistDialog.create(songs)
+ .show(getActivity().getSupportFragmentManager(), "ADD_TO_PLAYLIST");
+ } else {
+ dismiss();
+ PlaylistsUtil.addToPlaylist(getActivity(), songs, playlists.get(i - 1).id, true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java
new file mode 100644
index 000000000..4602d50ba
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java
@@ -0,0 +1,159 @@
+package code.name.monkey.retromusic.dialogs;
+
+import android.Manifest;
+import android.app.Dialog;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.DialogFragment;
+import android.view.View;
+
+import com.afollestad.materialdialogs.MaterialDialog;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import code.name.monkey.retromusic.R;
+
+/**
+ * @author Aidan Follestad (afollestad), modified by Karim Abou Zeid
+ */
+public class BlacklistFolderChooserDialog extends DialogFragment implements MaterialDialog.ListCallback {
+
+ private File parentFolder;
+ private File[] parentContents;
+ private boolean canGoUp = false;
+
+ private FolderCallback callback;
+
+ String initialPath = Environment.getExternalStorageDirectory().getAbsolutePath();
+
+ private String[] getContentsArray() {
+ if (parentContents == null) {
+ if (canGoUp) {
+ return new String[]{".."};
+ }
+ return new String[]{};
+ }
+ String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)];
+ if (canGoUp) {
+ results[0] = "..";
+ }
+ for (int i = 0; i < parentContents.length; i++) {
+ results[canGoUp ? i + 1 : i] = parentContents[i].getName();
+ }
+ return results;
+ }
+
+ private File[] listFiles() {
+ File[] contents = parentFolder.listFiles();
+ List results = new ArrayList<>();
+ if (contents != null) {
+ for (File fi : contents) {
+ if (fi.isDirectory()) {
+ results.add(fi);
+ }
+ }
+ Collections.sort(results, new FolderSorter());
+ return results.toArray(new File[results.size()]);
+ }
+ return null;
+ }
+
+ public static BlacklistFolderChooserDialog create() {
+ return new BlacklistFolderChooserDialog();
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ return new MaterialDialog.Builder(getActivity())
+ .title(R.string.md_error_label)
+ .content(R.string.md_storage_perm_error)
+ .positiveText(android.R.string.ok)
+ .build();
+ }
+ if (savedInstanceState == null) {
+ savedInstanceState = new Bundle();
+ }
+ if (!savedInstanceState.containsKey("current_path")) {
+ savedInstanceState.putString("current_path", initialPath);
+ }
+ parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator));
+ checkIfCanGoUp();
+ parentContents = listFiles();
+ MaterialDialog.Builder builder =
+ new MaterialDialog.Builder(getActivity())
+ .title(parentFolder.getAbsolutePath())
+ .items((CharSequence[]) getContentsArray())
+ .itemsCallback(this)
+ .autoDismiss(false)
+ .onPositive((dialog, which) -> {
+ dismiss();
+ callback.onFolderSelection(BlacklistFolderChooserDialog.this, parentFolder);
+ })
+ .onNegative((materialDialog, dialogAction) -> dismiss())
+ .positiveText(R.string.add_action)
+ .negativeText(android.R.string.cancel);
+ return builder.build();
+ }
+
+ @Override
+ public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) {
+ if (canGoUp && i == 0) {
+ parentFolder = parentFolder.getParentFile();
+ if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
+ parentFolder = parentFolder.getParentFile();
+ }
+ checkIfCanGoUp();
+ } else {
+ parentFolder = parentContents[canGoUp ? i - 1 : i];
+ canGoUp = true;
+ if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
+ parentFolder = Environment.getExternalStorageDirectory();
+ }
+ }
+ reload();
+ }
+
+ private void checkIfCanGoUp() {
+ canGoUp = parentFolder.getParent() != null;
+ }
+
+ private void reload() {
+ parentContents = listFiles();
+ MaterialDialog dialog = (MaterialDialog) getDialog();
+ dialog.setTitle(parentFolder.getAbsolutePath());
+ dialog.setItems((CharSequence[]) getContentsArray());
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString("current_path", parentFolder.getAbsolutePath());
+ }
+
+ public void setCallback(FolderCallback callback) {
+ this.callback = callback;
+ }
+
+ public interface FolderCallback {
+ void onFolderSelection(@NonNull BlacklistFolderChooserDialog dialog, @NonNull File folder);
+ }
+
+ private static class FolderSorter implements Comparator {
+
+ @Override
+ public int compare(File lhs, File rhs) {
+ return lhs.getName().compareTo(rhs.getName());
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.java
new file mode 100644
index 000000000..2358c2aad
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.java
@@ -0,0 +1,52 @@
+package code.name.monkey.retromusic.dialogs;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.text.Html;
+
+import com.afollestad.materialdialogs.DialogAction;
+import com.afollestad.materialdialogs.MaterialDialog;
+
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist;
+
+
+public class ClearSmartPlaylistDialog extends DialogFragment {
+
+ @NonNull
+ public static ClearSmartPlaylistDialog create(AbsSmartPlaylist playlist) {
+ ClearSmartPlaylistDialog dialog = new ClearSmartPlaylistDialog();
+ Bundle args = new Bundle();
+ args.putParcelable("playlist", playlist);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ //noinspection unchecked
+ final AbsSmartPlaylist playlist = getArguments().getParcelable("playlist");
+ int title = R.string.clear_playlist_title;
+ //noinspection ConstantConditions
+ CharSequence content = Html.fromHtml(getString(R.string.clear_playlist_x, playlist.name));
+
+ return new MaterialDialog.Builder(getActivity())
+ .title(title)
+ .content(content)
+ .positiveText(R.string.clear_action)
+ .negativeText(android.R.string.cancel)
+ .onPositive(new MaterialDialog.SingleButtonCallback() {
+ @Override
+ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
+ if (getActivity() == null) {
+ return;
+ }
+ playlist.clear(getActivity());
+ }
+ })
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.java
new file mode 100644
index 000000000..a09cf4cfa
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.java
@@ -0,0 +1,103 @@
+package code.name.monkey.retromusic.dialogs;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.widget.BottomSheetDialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import code.name.monkey.appthemehelper.ThemeStore;
+import code.name.monkey.appthemehelper.util.TintHelper;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.util.PlaylistsUtil;
+import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment;
+
+/**
+ * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad)
+ */
+public class CreatePlaylistDialog extends RoundedBottomSheetDialogFragment {
+
+ @BindView(R.id.option_1)
+ EditText playlistName;
+ @BindView(R.id.action_cancel)
+ Button actionCancel;
+ @BindView(R.id.action_create)
+ Button actionCreate;
+
+ @NonNull
+ public static CreatePlaylistDialog create() {
+ return create((Song) null);
+ }
+
+ @NonNull
+ public static CreatePlaylistDialog create(@Nullable Song song) {
+ ArrayList list = new ArrayList<>();
+ if (song != null) {
+ list.add(song);
+ }
+ return create(list);
+ }
+
+ @NonNull
+ public static CreatePlaylistDialog create(ArrayList songs) {
+ CreatePlaylistDialog dialog = new CreatePlaylistDialog();
+ Bundle args = new Bundle();
+ args.putParcelableArrayList("songs", songs);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View layout = inflater.inflate(R.layout.dialog_create_playlist, container, false);
+ ButterKnife.bind(this, layout);
+ return layout;
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ int accentColor = ThemeStore.accentColor(Objects.requireNonNull(getContext()));
+ TintHelper.setTintAuto(playlistName, accentColor, true);
+ TintHelper.setTintAuto(actionCreate, accentColor, true);
+ actionCancel.setTextColor(accentColor);
+ }
+
+ @OnClick({R.id.action_cancel, R.id.action_create})
+ void actions(View view) {
+ switch (view.getId()) {
+ case R.id.action_cancel:
+ dismiss();
+ break;
+ case R.id.action_create:
+ if (getActivity() == null) {
+ return;
+ }
+ if (!playlistName.getText().toString().trim().isEmpty()) {
+ final int playlistId = PlaylistsUtil
+ .createPlaylist(getActivity(), playlistName.getText().toString());
+ if (playlistId != -1 && getActivity() != null) {
+ //noinspection unchecked
+ ArrayList songs = getArguments().getParcelableArrayList("songs");
+ if (songs != null) {
+ PlaylistsUtil.addToPlaylist(getActivity(), songs, playlistId, true);
+ }
+ }
+ }
+ break;
+ }
+ dismiss();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.java
new file mode 100644
index 000000000..5e408ca81
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.java
@@ -0,0 +1,89 @@
+package code.name.monkey.retromusic.dialogs;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.Html;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.model.Playlist;
+import code.name.monkey.retromusic.util.PlaylistsUtil;
+import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment;
+
+
+public class DeletePlaylistDialog extends RoundedBottomSheetDialogFragment {
+
+ @BindView(R.id.action_delete)
+ TextView delete;
+ @BindView(R.id.title)
+ TextView title;
+ @BindView(R.id.action_cancel)
+ TextView cancel;
+
+ @NonNull
+ public static DeletePlaylistDialog create(Playlist playlist) {
+ ArrayList list = new ArrayList<>();
+ list.add(playlist);
+ return create(list);
+ }
+
+ @NonNull
+ public static DeletePlaylistDialog create(ArrayList playlists) {
+ DeletePlaylistDialog dialog = new DeletePlaylistDialog();
+ Bundle args = new Bundle();
+ args.putParcelableArrayList("playlists", playlists);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View layout = inflater.inflate(R.layout.dialog_delete_playlist, container, false);
+ ButterKnife.bind(this, layout);
+ return layout;
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ //noinspection unchecked
+ final ArrayList playlists = getArguments().getParcelableArrayList("playlists");
+ int title;
+ CharSequence content;
+ //noinspection ConstantConditions
+ if (playlists.size() > 1) {
+ title = R.string.delete_playlists_title;
+ content = Html.fromHtml(getString(R.string.delete_x_playlists, playlists.size()));
+ } else {
+ title = R.string.delete_playlist_title;
+ content = Html.fromHtml(getString(R.string.delete_playlist_x, playlists.get(0).name));
+ }
+ this.title.setText(title);
+ this.delete.setText(content);
+ }
+
+ @OnClick({R.id.action_cancel, R.id.action_delete})
+ void actions(View view) {
+ final ArrayList playlists = getArguments().getParcelableArrayList("playlists");
+ switch (view.getId()) {
+ case R.id.action_delete:
+ if (getActivity() == null)
+ return;
+ PlaylistsUtil.deletePlaylists(getActivity(), playlists);
+ break;
+ default:
+ }
+ dismiss();
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.java
new file mode 100644
index 000000000..1bdb83d41
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.java
@@ -0,0 +1,112 @@
+package code.name.monkey.retromusic.dialogs;
+
+/*
+import android.app.Dialog;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.text.Html;
+
+import com.afollestad.materialdialogs.DialogAction;
+import com.afollestad.materialdialogs.MaterialDialog;
+import code.name.monkey.retromusic.model.Song;
+
+import java.util.ArrayList;
+
+import code.name.monkey.retromusic.R;
+
+import code.name.monkey.retromusic.util.MusicUtil;
+
+*/
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.widget.BottomSheetDialogFragment;
+import android.text.Html;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.util.MusicUtil;
+import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment;
+
+/**
+ * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad)
+ */
+
+public class DeleteSongsDialog extends RoundedBottomSheetDialogFragment {
+ @BindView(R.id.action_delete)
+ TextView delete;
+ @BindView(R.id.title)
+ TextView title;
+ @BindView(R.id.action_cancel)
+ TextView cancel;
+
+ @NonNull
+ public static DeleteSongsDialog create(Song song) {
+ ArrayList list = new ArrayList<>();
+ list.add(song);
+ return create(list);
+ }
+
+ @NonNull
+ public static DeleteSongsDialog create(ArrayList songs) {
+ DeleteSongsDialog dialog = new DeleteSongsDialog();
+ Bundle args = new Bundle();
+ args.putParcelableArrayList("songs", songs);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ @OnClick({R.id.action_cancel, R.id.action_delete})
+ void actions(View view) {
+ final ArrayList songs = getArguments().getParcelableArrayList("songs");
+ switch (view.getId()) {
+ case R.id.action_delete:
+ if (getActivity() == null)
+ return;
+ MusicUtil.deleteTracks(getActivity(), songs);
+ break;
+ default:
+ }
+ dismiss();
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ //noinspection unchecked
+ final ArrayList songs = getArguments().getParcelableArrayList("songs");
+ int title;
+ CharSequence content;
+ if (songs != null && songs.size() > 1) {
+ title = R.string.delete_songs_title;
+ content = Html.fromHtml(getString(R.string.delete_x_songs, songs.size()));
+ } else {
+ title = R.string.delete_song_title;
+ content = Html.fromHtml(getString(R.string.delete_song_x, songs.get(0).title));
+ }
+
+ this.title.setText(title);
+ this.delete.setText(content);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View layout = inflater.inflate(R.layout.dialog_delete_songs, container, false);
+ ButterKnife.bind(this, layout);
+ return layout;
+ }
+
+}
+
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/HomeOptionDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/HomeOptionDialog.java
new file mode 100644
index 000000000..11873df99
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/HomeOptionDialog.java
@@ -0,0 +1,125 @@
+package code.name.monkey.retromusic.dialogs;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.AppCompatTextView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.io.File;
+import java.util.Calendar;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.Unbinder;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.util.Compressor;
+import code.name.monkey.retromusic.util.NavigationUtil;
+import code.name.monkey.retromusic.util.PreferenceUtil;
+import code.name.monkey.retromusic.views.CircularImageView;
+import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.schedulers.Schedulers;
+
+import static code.name.monkey.retromusic.Constants.USER_PROFILE;
+
+/**
+ * @author Hemanth S (h4h13).
+ */
+public class HomeOptionDialog extends RoundedBottomSheetDialogFragment {
+ private static final String TAG = "HomeOptionDialog";
+ Unbinder mUnbinder;
+ @BindView(R.id.user_image_bottom)
+ CircularImageView userImageBottom;
+ @BindView(R.id.title_welcome)
+ AppCompatTextView titleWelcome;
+ private CompositeDisposable disposable = new CompositeDisposable();
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View layout = inflater.inflate(R.layout.user_action_details, container, false);
+ mUnbinder = ButterKnife.bind(this, layout);
+ return layout;
+ }
+
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ loadImageFromStorage();
+ titleWelcome.setText(String.format("%s, %s!", getTimeOfTheDay(), PreferenceUtil.getInstance(getContext()).getUserName()));
+ }
+
+ private String getTimeOfTheDay() {
+ String message = getString(R.string.title_good_day);
+ Calendar c = Calendar.getInstance();
+ int timeOfDay = c.get(Calendar.HOUR_OF_DAY);
+
+ if (timeOfDay >= 0 && timeOfDay < 6) {
+ message = getString(R.string.title_good_night);
+ } else if (timeOfDay >= 6 && timeOfDay < 12) {
+ message = getString(R.string.title_good_morning);
+ } else if (timeOfDay >= 12 && timeOfDay < 16) {
+ message = getString(R.string.title_good_afternoon);
+ } else if (timeOfDay >= 16 && timeOfDay < 20) {
+ message = getString(R.string.title_good_evening);
+ } else if (timeOfDay >= 20 && timeOfDay < 24) {
+ message = getString(R.string.title_good_night);
+ }
+ return message;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ disposable.clear();
+ mUnbinder.unbind();
+ }
+
+ @OnClick({R.id.action_about, R.id.user_info_container, R.id.action_folder, R.id.action_settings, R.id.action_sleep_timer})
+ public void onViewClicked(View view) {
+ switch (view.getId()) {
+ case R.id.user_info_container:
+ NavigationUtil.goToUserInfo(getActivity());
+ break;
+ case R.id.action_folder:
+ //getMainActivity().setCurrentFragment(FoldersFragment.newInstance(getContext()), true);
+ break;
+ case R.id.action_settings:
+ NavigationUtil.goToSettings(getActivity());
+ break;
+ case R.id.action_about:
+ NavigationUtil.goToAbout(getActivity());
+ break;
+ case R.id.action_sleep_timer:
+ if (getFragmentManager() != null) {
+ new SleepTimerDialog().show(getFragmentManager(), TAG);
+ }
+ break;
+ }
+ dismiss();
+ }
+
+ private void loadImageFromStorage() {
+ //noinspection ConstantConditions
+ disposable.add(new Compressor(getContext())
+ .setMaxHeight(300)
+ .setMaxWidth(300)
+ .setQuality(75)
+ .setCompressFormat(Bitmap.CompressFormat.WEBP)
+ .compressToBitmapAsFlowable(
+ new File(PreferenceUtil.getInstance(getContext()).getProfileImage(), USER_PROFILE))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(userImageBottom::setImageBitmap,
+ throwable -> userImageBottom.setImageDrawable(ContextCompat
+ .getDrawable(getContext(), R.drawable.ic_person_flat))));
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.java
new file mode 100644
index 000000000..a3a801768
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.java
@@ -0,0 +1,87 @@
+package code.name.monkey.retromusic.dialogs;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.widget.BottomSheetDialogFragment;
+import android.text.Html;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.model.PlaylistSong;
+import code.name.monkey.retromusic.util.PlaylistsUtil;
+import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment;
+
+
+public class RemoveFromPlaylistDialog extends RoundedBottomSheetDialogFragment {
+ @BindView(R.id.action_remove)
+ TextView remove;
+ @BindView(R.id.title)
+ TextView title;
+ @BindView(R.id.action_cancel)
+ TextView cancel;
+
+ @NonNull
+ public static RemoveFromPlaylistDialog create(PlaylistSong song) {
+ ArrayList list = new ArrayList<>();
+ list.add(song);
+ return create(list);
+ }
+
+ @NonNull
+ public static RemoveFromPlaylistDialog create(ArrayList songs) {
+ RemoveFromPlaylistDialog dialog = new RemoveFromPlaylistDialog();
+ Bundle args = new Bundle();
+ args.putParcelableArrayList("songs", songs);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ @OnClick({R.id.action_cancel, R.id.action_remove})
+ void actions(View view) {
+ final ArrayList songs = getArguments().getParcelableArrayList("songs");
+ switch (view.getId()) {
+ case R.id.action_remove:
+ if (getActivity() == null)
+ return;
+ PlaylistsUtil.removeFromPlaylist(getActivity(), songs);
+ break;
+ default:
+ }
+ dismiss();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View layout = inflater.inflate(R.layout.dialog_remove_from_playlist, container, false);
+ ButterKnife.bind(this, layout);
+ return layout;
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ //noinspection unchecked
+ final ArrayList songs = getArguments().getParcelableArrayList("songs");
+ int title;
+ CharSequence content;
+ if (songs.size() > 1) {
+ title = R.string.remove_songs_from_playlist_title;
+ content = Html.fromHtml(getString(R.string.remove_x_songs_from_playlist, songs.size()));
+ } else {
+ title = R.string.remove_song_from_playlist_title;
+ content = Html.fromHtml(getString(R.string.remove_song_x_from_playlist, songs.get(0).title));
+ }
+ this.remove.setText(content);
+ this.title.setText(title);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.java
new file mode 100644
index 000000000..10bdfced4
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.java
@@ -0,0 +1,79 @@
+package code.name.monkey.retromusic.dialogs;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.widget.BottomSheetDialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+
+import java.util.Objects;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import code.name.monkey.appthemehelper.ThemeStore;
+import code.name.monkey.appthemehelper.util.TintHelper;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.util.PlaylistsUtil;
+import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment;
+
+/**
+ * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad)
+ */
+public class RenamePlaylistDialog extends RoundedBottomSheetDialogFragment {
+ @BindView(R.id.option_1)
+ EditText playlistName;
+ @BindView(R.id.action_cancel)
+ Button cancel;
+ @BindView(R.id.action_rename)
+ Button rename;
+
+ @NonNull
+ public static RenamePlaylistDialog create(long playlistId) {
+ RenamePlaylistDialog dialog = new RenamePlaylistDialog();
+ Bundle args = new Bundle();
+ args.putLong("playlist_id", playlistId);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ @OnClick({R.id.action_cancel, R.id.action_rename})
+ void actions(View view) {
+ switch (view.getId()) {
+ case R.id.action_cancel:
+ dismiss();
+ break;
+ case R.id.action_rename:
+ if (!playlistName.toString().trim().equals("")) {
+ long playlistId = getArguments().getLong("playlist_id");
+ PlaylistsUtil.renamePlaylist(getActivity(), playlistId, playlistName.toString());
+ }
+ break;
+ }
+ dismiss();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View layout = inflater.inflate(R.layout.dialog_playlist_rename, container, false);
+ ButterKnife.bind(this, layout);
+ return layout;
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ int accentColor = ThemeStore.accentColor(Objects.requireNonNull(getContext()));
+ TintHelper.setTintAuto(playlistName, accentColor, true);
+ TintHelper.setTintAuto(rename, accentColor, true);
+ cancel.setTextColor(accentColor);
+
+ long playlistId = getArguments().getLong("playlist_id");
+ playlistName.setText(PlaylistsUtil.getNameForPlaylist(getActivity(), playlistId));
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/ScanMediaFolderChooserDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/ScanMediaFolderChooserDialog.java
new file mode 100644
index 000000000..89c8d125e
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/ScanMediaFolderChooserDialog.java
@@ -0,0 +1 @@
+package code.name.monkey.retromusic.dialogs;
import android.Manifest;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaScannerConnection;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
import code.name.monkey.retromusic.ui.fragments.mainactivity.folders.FoldersFragment;
import code.name.monkey.retromusic.util.PreferenceUtil;
/**
* @author Aidan Follestad (afollestad), modified by Karim Abou Zeid
*/
public class ScanMediaFolderChooserDialog extends DialogFragment implements MaterialDialog.ListCallback {
String initialPath = PreferenceUtil.getInstance(getContext()).getStartDirectory().getAbsolutePath();
private File parentFolder;
private File[] parentContents;
private boolean canGoUp = false;
public static ScanMediaFolderChooserDialog create() {
return new ScanMediaFolderChooserDialog();
}
private static void scanPaths(@NonNull WeakReference activityWeakReference, @NonNull Context applicationContext, @Nullable String[] toBeScanned) {
Activity activity = activityWeakReference.get();
if (toBeScanned == null || toBeScanned.length < 1) {
Toast.makeText(applicationContext, R.string.nothing_to_scan, Toast.LENGTH_SHORT).show();
} else {
MediaScannerConnection.scanFile(applicationContext, toBeScanned, null, activity != null ? new UpdateToastMediaScannerCompletionListener(activity, toBeScanned) : null);
}
}
private String[] getContentsArray() {
if (parentContents == null) {
if (canGoUp) {
return new String[]{".."};
}
return new String[]{};
}
String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)];
if (canGoUp) {
results[0] = "..";
}
for (int i = 0; i < parentContents.length; i++) {
results[canGoUp ? i + 1 : i] = parentContents[i].getName();
}
return results;
}
private File[] listFiles() {
File[] contents = parentFolder.listFiles();
List results = new ArrayList<>();
if (contents != null) {
for (File fi : contents) {
if (fi.isDirectory()) {
results.add(fi);
}
}
Collections.sort(results, new FolderSorter());
return results.toArray(new File[results.size()]);
}
return null;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& ActivityCompat.checkSelfPermission(
getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
return new MaterialDialog.Builder(getActivity())
.title(R.string.md_error_label)
.content(R.string.md_storage_perm_error)
.positiveText(android.R.string.ok)
.build();
}
if (savedInstanceState == null) {
savedInstanceState = new Bundle();
}
if (!savedInstanceState.containsKey("current_path")) {
savedInstanceState.putString("current_path", initialPath);
}
parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator));
checkIfCanGoUp();
parentContents = listFiles();
MaterialDialog.Builder builder =
new MaterialDialog.Builder(getActivity())
.title(parentFolder.getAbsolutePath())
.items((CharSequence[]) getContentsArray())
.itemsCallback(this)
.autoDismiss(false)
.onPositive((dialog, which) -> {
final Context applicationContext = getActivity().getApplicationContext();
final WeakReference activityWeakReference = new WeakReference<>(getActivity());
dismiss();
new FoldersFragment.ListPathsAsyncTask(getActivity(), paths ->
scanPaths(activityWeakReference, applicationContext, paths)).execute(new FoldersFragment.ListPathsAsyncTask.LoadingInfo(parentFolder, FoldersFragment.AUDIO_FILE_FILTER));
})
.onNegative((materialDialog, dialogAction) -> dismiss())
.positiveText(R.string.action_scan_directory)
.negativeText(android.R.string.cancel);
return builder.build();
}
@Override
public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) {
if (canGoUp && i == 0) {
parentFolder = parentFolder.getParentFile();
if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
parentFolder = parentFolder.getParentFile();
}
checkIfCanGoUp();
} else {
parentFolder = parentContents[canGoUp ? i - 1 : i];
canGoUp = true;
if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
parentFolder = Environment.getExternalStorageDirectory();
}
}
reload();
}
private void checkIfCanGoUp() {
canGoUp = parentFolder.getParent() != null;
}
private void reload() {
parentContents = listFiles();
MaterialDialog dialog = (MaterialDialog) getDialog();
dialog.setTitle(parentFolder.getAbsolutePath());
dialog.setItems((CharSequence[]) getContentsArray());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("current_path", parentFolder.getAbsolutePath());
}
private static class FolderSorter implements Comparator {
@Override
public int compare(File lhs, File rhs) {
return lhs.getName().compareTo(rhs.getName());
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.java
new file mode 100755
index 000000000..854a06f2a
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.java
@@ -0,0 +1,173 @@
+package code.name.monkey.retromusic.dialogs;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.Locale;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import code.name.monkey.appthemehelper.ThemeStore;
+import code.name.monkey.appthemehelper.util.TintHelper;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.service.MusicService;
+import code.name.monkey.retromusic.util.MusicUtil;
+import code.name.monkey.retromusic.util.PreferenceUtil;
+import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment;
+
+import static code.name.monkey.retromusic.Constants.ACTION_QUIT;
+
+public class SleepTimerDialog extends RoundedBottomSheetDialogFragment {
+ @BindView(R.id.seek_arc)
+ SeekBar seekArc;
+ @BindView(R.id.timer_display)
+ TextView timerDisplay;
+ @BindView(R.id.action_set)
+ Button setButton;
+ @BindView(R.id.action_cancel)
+ Button cancelButton;
+
+ private int seekArcProgress;
+ private TimerUpdater timerUpdater;
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ super.onDismiss(dialog);
+ timerUpdater.cancel();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE) != null) {
+ timerUpdater.start();
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View layout = inflater.inflate(R.layout.dialog_sleep_timer, container, false);
+ ButterKnife.bind(this, layout);
+ return layout;
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ timerUpdater = new TimerUpdater();
+
+ seekArcProgress = PreferenceUtil.getInstance(getActivity()).getLastSleepTimerValue();
+ updateTimeDisplayTime();
+ seekArc.setProgress(seekArcProgress);
+
+ int accentColor = ThemeStore.accentColor(getContext());
+ TintHelper.setTintAuto(seekArc, accentColor, true);
+ setButton.setTextColor(accentColor);
+ cancelButton.setTextColor(accentColor);
+
+ seekArc.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
+ if (i < 1) {
+ seekArc.setProgress(1);
+ return;
+ }
+ seekArcProgress = i;
+ updateTimeDisplayTime();
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ PreferenceUtil.getInstance(getActivity()).setLastSleepTimerValue(seekArcProgress);
+ }
+ });
+ }
+
+ @OnClick({R.id.action_cancel, R.id.action_set})
+ void set(View view) {
+ switch (view.getId()) {
+ case R.id.action_cancel:
+ if (getActivity() == null) {
+ return;
+ }
+ final PendingIntent previous = makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE);
+ if (previous != null) {
+ AlarmManager am = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
+ if (am != null) {
+ am.cancel(previous);
+ }
+ previous.cancel();
+ Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.sleep_timer_canceled), Toast.LENGTH_SHORT).show();
+ }
+ break;
+ case R.id.action_set:
+ if (getActivity() == null) {
+ return;
+ }
+ final int minutes = seekArcProgress;
+ PendingIntent pi = makeTimerPendingIntent(PendingIntent.FLAG_CANCEL_CURRENT);
+ final long nextSleepTimerElapsedTime = SystemClock.elapsedRealtime() + minutes * 60 * 1000;
+ PreferenceUtil.getInstance(getActivity()).setNextSleepTimerElapsedRealtime(nextSleepTimerElapsedTime);
+ AlarmManager am = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
+ if (am != null) {
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextSleepTimerElapsedTime, pi);
+ }
+ Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.sleep_timer_set, minutes), Toast.LENGTH_SHORT).show();
+ break;
+ }
+ dismiss();
+ }
+
+ private void updateTimeDisplayTime() {
+ timerDisplay.setText(String.format(Locale.getDefault(), "%d min", seekArcProgress));
+ }
+
+ private PendingIntent makeTimerPendingIntent(int flag) {
+ return PendingIntent.getService(getActivity(), 0, makeTimerIntent(), flag);
+ }
+
+ private Intent makeTimerIntent() {
+ return new Intent(getActivity(), MusicService.class)
+ .setAction(ACTION_QUIT);
+ }
+
+ private class TimerUpdater extends CountDownTimer {
+ TimerUpdater() {
+ super(PreferenceUtil.getInstance(getActivity()).getNextSleepTimerElapsedRealTime() - SystemClock.elapsedRealtime(), 1000);
+ }
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ cancelButton.setText(String.format("%s (%s)", getString(R.string.cancel_current_timer), MusicUtil.getReadableDurationString(millisUntilFinished)));
+ //materialDialog.setActionButton(DialogAction.NEUTRAL, materialDialog.getContext().getString(R.string.cancel_current_timer) + " (" + MusicUtil.getReadableDurationString(millisUntilFinished) + ")");
+ }
+
+ @Override
+ public void onFinish() {
+ cancelButton.setText(null);
+ //materialDialog.setActionButton(DialogAction.NEUTRAL, null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.java
new file mode 100644
index 000000000..a40104d80
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.java
@@ -0,0 +1,117 @@
+package code.name.monkey.retromusic.dialogs;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.widget.BottomSheetDialogFragment;
+import android.text.Html;
+import android.text.Spanned;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.jaudiotagger.audio.AudioFile;
+import org.jaudiotagger.audio.AudioFileIO;
+import org.jaudiotagger.audio.AudioHeader;
+import org.jaudiotagger.audio.exceptions.CannotReadException;
+import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
+import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
+import org.jaudiotagger.tag.TagException;
+
+import java.io.File;
+import java.io.IOException;
+
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.util.MusicUtil;
+import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment;
+
+/**
+ * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad)
+ */
+public class SongDetailDialog extends RoundedBottomSheetDialogFragment {
+
+ public static final String TAG = SongDetailDialog.class.getSimpleName();
+
+ @NonNull
+ public static SongDetailDialog create(Song song) {
+ SongDetailDialog dialog = new SongDetailDialog();
+ Bundle args = new Bundle();
+ args.putParcelable("song", song);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ private static Spanned makeTextWithTitle(@NonNull Context context, int titleResId, String text) {
+ return Html.fromHtml("" + context.getResources().getString(titleResId) + ": " + "" + text);
+ }
+
+ private static String getFileSizeString(long sizeInBytes) {
+ long fileSizeInKB = sizeInBytes / 1024;
+ long fileSizeInMB = fileSizeInKB / 1024;
+ return fileSizeInMB + " MB";
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View dialogView = inflater.inflate(R.layout.dialog_file_details, container, false);
+ Context context = getContext();
+
+ final TextView fileName = dialogView.findViewById(R.id.file_name);
+ final TextView filePath = dialogView.findViewById(R.id.file_path);
+ final TextView fileSize = dialogView.findViewById(R.id.file_size);
+ final TextView fileFormat = dialogView.findViewById(R.id.file_format);
+ final TextView trackLength = dialogView.findViewById(R.id.track_length);
+ final TextView bitRate = dialogView.findViewById(R.id.bitrate);
+ final TextView samplingRate = dialogView.findViewById(R.id.sampling_rate);
+
+ fileName.setText(makeTextWithTitle(context, R.string.label_file_name, "-"));
+ filePath.setText(makeTextWithTitle(context, R.string.label_file_path, "-"));
+ fileSize.setText(makeTextWithTitle(context, R.string.label_file_size, "-"));
+ fileFormat.setText(makeTextWithTitle(context, R.string.label_file_format, "-"));
+ trackLength.setText(makeTextWithTitle(context, R.string.label_track_length, "-"));
+ bitRate.setText(makeTextWithTitle(context, R.string.label_bit_rate, "-"));
+ samplingRate.setText(makeTextWithTitle(context, R.string.label_sampling_rate, "-"));
+
+ final Song song = getArguments().getParcelable("song");
+ if (song != null) {
+ final File songFile = new File(song.data);
+ if (songFile.exists()) {
+ fileName.setText(makeTextWithTitle(context, R.string.label_file_name, songFile.getName()));
+ filePath.setText(
+ makeTextWithTitle(context, R.string.label_file_path, songFile.getAbsolutePath()));
+ fileSize.setText(makeTextWithTitle(context, R.string.label_file_size,
+ getFileSizeString(songFile.length())));
+ try {
+ AudioFile audioFile = AudioFileIO.read(songFile);
+ AudioHeader audioHeader = audioFile.getAudioHeader();
+
+ fileFormat.setText(
+ makeTextWithTitle(context, R.string.label_file_format, audioHeader.getFormat()));
+ trackLength.setText(makeTextWithTitle(context, R.string.label_track_length, MusicUtil
+ .getReadableDurationString(audioHeader.getTrackLength() * 1000)));
+ bitRate.setText(makeTextWithTitle(context, R.string.label_bit_rate,
+ audioHeader.getBitRate() + " kb/s"));
+ samplingRate.setText(makeTextWithTitle(context, R.string.label_sampling_rate,
+ audioHeader.getSampleRate() + " Hz"));
+ } catch (@NonNull CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
+ Log.e(TAG, "error while reading the song file", e);
+ // fallback
+ trackLength.setText(makeTextWithTitle(context, R.string.label_track_length,
+ MusicUtil.getReadableDurationString(song.duration)));
+ }
+ } else {
+ // fallback
+ fileName.setText(makeTextWithTitle(context, R.string.label_file_name, song.title));
+ trackLength.setText(makeTextWithTitle(context, R.string.label_track_length,
+ MusicUtil.getReadableDurationString(song.duration)));
+ }
+ }
+
+ return dialogView;
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.java
new file mode 100644
index 000000000..0454367c2
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.java
@@ -0,0 +1,71 @@
+package code.name.monkey.retromusic.dialogs;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.widget.BottomSheetDialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.util.MusicUtil;
+import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment;
+
+
+public class SongShareDialog extends RoundedBottomSheetDialogFragment {
+ @BindView(R.id.option_2)
+ TextView currentSong;
+
+ @NonNull
+ public static SongShareDialog create(final Song song) {
+ final SongShareDialog dialog = new SongShareDialog();
+ final Bundle args = new Bundle();
+ args.putParcelable("song", song);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View layout = inflater.inflate(R.layout.dialog_file_share, container, false);
+ ButterKnife.bind(this, layout);
+ return layout;
+ }
+
+ @SuppressLint("StringFormatInvalid")
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ final Song song = getArguments().getParcelable("song");
+ currentSong.setText(getString(R.string.currently_listening_to_x_by_x, song.title, song.artistName));
+ }
+
+ @OnClick({R.id.option_2, R.id.option_1})
+ void onClick(View view) {
+ final Song song = getArguments().getParcelable("song");
+ final String currentlyListening = getString(R.string.currently_listening_to_x_by_x, song.title, song.artistName);
+ switch (view.getId()) {
+ case R.id.option_1:
+ startActivity(
+ Intent.createChooser(
+ MusicUtil.createShareSongFileIntent(song, getContext()), null));
+ break;
+ case R.id.option_2:
+ getActivity().startActivity(Intent.createChooser(
+ new Intent().setAction(Intent.ACTION_SEND)
+ .putExtra(Intent.EXTRA_TEXT, currentlyListening)
+ .setType("text/plain"), null));
+ break;
+ }
+ dismiss();
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java
new file mode 100644
index 000000000..5835bfcda
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java
@@ -0,0 +1,140 @@
+package code.name.monkey.retromusic.glide;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.support.annotation.NonNull;
+
+import com.bumptech.glide.BitmapRequestBuilder;
+import com.bumptech.glide.DrawableRequestBuilder;
+import com.bumptech.glide.DrawableTypeRequest;
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.RequestManager;
+import com.bumptech.glide.load.Key;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+import com.bumptech.glide.request.target.Target;
+
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.RetroApplication;
+import code.name.monkey.retromusic.glide.artistimage.ArtistImage;
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder;
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
+import code.name.monkey.retromusic.model.Artist;
+import code.name.monkey.retromusic.util.ArtistSignatureUtil;
+import code.name.monkey.retromusic.util.CustomArtistImageUtil;
+
+public class ArtistGlideRequest {
+
+ private static final int DEFAULT_ANIMATION = android.R.anim.fade_in;
+ private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.SOURCE;
+ private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_artist_art;
+
+ private static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Artist artist,
+ boolean noCustomImage, boolean forceDownload) {
+ boolean hasCustomImage = CustomArtistImageUtil.getInstance(RetroApplication.getInstance())
+ .hasCustomArtistImage(artist);
+ if (noCustomImage || !hasCustomImage) {
+ return requestManager.load(new ArtistImage(artist.getName(), forceDownload));
+ } else {
+ return requestManager.load(CustomArtistImageUtil.getFile(artist));
+ }
+ }
+
+ private static Key createSignature(Artist artist) {
+ return ArtistSignatureUtil.getInstance(RetroApplication.getInstance())
+ .getArtistSignature(artist.getName());
+ }
+
+ public static class Builder {
+
+ final RequestManager requestManager;
+ final Artist artist;
+ boolean noCustomImage;
+ boolean forceDownload;
+
+ private Builder(@NonNull RequestManager requestManager, Artist artist) {
+ this.requestManager = requestManager;
+ this.artist = artist;
+ }
+
+ public static Builder from(@NonNull RequestManager requestManager, Artist artist) {
+ return new Builder(requestManager, artist);
+ }
+
+ public PaletteBuilder generatePalette(Context context) {
+ return new PaletteBuilder(this, context);
+ }
+
+ public BitmapBuilder asBitmap() {
+ return new BitmapBuilder(this);
+ }
+
+ public Builder noCustomImage(boolean noCustomImage) {
+ this.noCustomImage = noCustomImage;
+ return this;
+ }
+
+ public Builder forceDownload(boolean forceDownload) {
+ this.forceDownload = forceDownload;
+ return this;
+ }
+
+ public DrawableRequestBuilder build() {
+ //noinspection unchecked
+ return createBaseRequest(requestManager, artist, noCustomImage, forceDownload)
+ .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
+ .error(DEFAULT_ERROR_IMAGE)
+ .animate(DEFAULT_ANIMATION)
+ .priority(Priority.LOW)
+ .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
+ .signature(createSignature(artist));
+ }
+ }
+
+ public static class BitmapBuilder {
+
+ private final Builder builder;
+
+ BitmapBuilder(Builder builder) {
+ this.builder = builder;
+ }
+
+ public BitmapRequestBuilder, Bitmap> build() {
+ //noinspection unchecked
+ return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage,
+ builder.forceDownload)
+ .asBitmap()
+ .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
+ .error(DEFAULT_ERROR_IMAGE)
+ .animate(DEFAULT_ANIMATION)
+ .priority(Priority.LOW)
+ .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
+ .signature(createSignature(builder.artist));
+ }
+ }
+
+ public static class PaletteBuilder {
+
+ final Context context;
+ private final Builder builder;
+
+ PaletteBuilder(Builder builder, Context context) {
+ this.builder = builder;
+ this.context = context;
+ }
+
+ public BitmapRequestBuilder, BitmapPaletteWrapper> build() {
+ //noinspection unchecked
+ return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage,
+ builder.forceDownload)
+ .asBitmap()
+ .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class)
+ .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
+ .error(DEFAULT_ERROR_IMAGE)
+ .animate(DEFAULT_ANIMATION)
+ .priority(Priority.LOW)
+ .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
+ .signature(createSignature(builder.artist));
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.java b/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.java
new file mode 100644
index 000000000..b4659b27c
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.java
@@ -0,0 +1,147 @@
+package code.name.monkey.retromusic.glide;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Build;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RSRuntimeException;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicBlur;
+import android.support.annotation.FloatRange;
+import android.support.annotation.NonNull;
+
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
+import code.name.monkey.retromusic.helper.StackBlur;
+
+import code.name.monkey.retromusic.BuildConfig;
+import code.name.monkey.retromusic.util.ImageUtil;
+
+
+public class BlurTransformation extends BitmapTransformation {
+ static final float DEFAULT_BLUR_RADIUS = 5f;
+
+ private Context context;
+ private float blurRadius;
+ private int sampling;
+
+ private BlurTransformation(Builder builder) {
+ super(builder.context);
+ init(builder);
+ }
+
+ private BlurTransformation(Builder builder, BitmapPool bitmapPool) {
+ super(bitmapPool);
+ init(builder);
+ }
+
+ private void init(Builder builder) {
+ this.context = builder.context;
+ this.blurRadius = builder.blurRadius;
+ this.sampling = builder.sampling;
+ }
+
+ @Override
+ protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
+ int sampling;
+ if (this.sampling == 0) {
+ sampling = ImageUtil.calculateInSampleSize(toTransform.getWidth(), toTransform.getHeight(), 100);
+ } else {
+ sampling = this.sampling;
+ }
+
+ int width = toTransform.getWidth();
+ int height = toTransform.getHeight();
+ int scaledWidth = width / sampling;
+ int scaledHeight = height / sampling;
+
+ Bitmap out = pool.get(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
+ if (out == null) {
+ out = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
+ }
+
+ Canvas canvas = new Canvas(out);
+ canvas.scale(1 / (float) sampling, 1 / (float) sampling);
+ Paint paint = new Paint();
+ paint.setFlags(Paint.FILTER_BITMAP_FLAG);
+ canvas.drawBitmap(toTransform, 0, 0, paint);
+
+ if (Build.VERSION.SDK_INT >= 17) {
+ try {
+ final RenderScript rs = RenderScript.create(context.getApplicationContext());
+ final Allocation input = Allocation.createFromBitmap(rs, out, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
+ final Allocation output = Allocation.createTyped(rs, input.getType());
+ final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
+
+ script.setRadius(blurRadius);
+ script.setInput(input);
+ script.forEach(output);
+
+ output.copyTo(out);
+
+ rs.destroy();
+
+ return out;
+
+ } catch (RSRuntimeException e) {
+ // on some devices RenderScript.create() throws: android.support.v8.renderscript.RSRuntimeException: Error loading libRSSupport library
+ if (BuildConfig.DEBUG) e.printStackTrace();
+ }
+ }
+
+ return StackBlur.blur(out, blurRadius);
+ }
+
+ @Override
+ public String getId() {
+ return "BlurTransformation(radius=" + blurRadius + ", sampling=" + sampling + ")";
+ }
+
+ public static class Builder {
+ private Context context;
+ private BitmapPool bitmapPool;
+ private float blurRadius = DEFAULT_BLUR_RADIUS;
+ private int sampling;
+
+ public Builder(@NonNull Context context) {
+ this.context = context;
+ }
+
+ /**
+ * @param blurRadius The radius to use. Must be between 0 and 25. Default is 5.
+ * @return the same Builder
+ */
+ public Builder blurRadius(@FloatRange(from = 0.0f, to = 25.0f) float blurRadius) {
+ this.blurRadius = blurRadius;
+ return this;
+ }
+
+ /**
+ * @param sampling The inSampleSize to use. Must be a power of 2, or 1 for no down sampling or 0 for auto detect sampling. Default is 0.
+ * @return the same Builder
+ */
+ public Builder sampling(int sampling) {
+ this.sampling = sampling;
+ return this;
+ }
+
+ /**
+ * @param bitmapPool The BitmapPool to use.
+ * @return the same Builder
+ */
+ public Builder bitmapPool(BitmapPool bitmapPool) {
+ this.bitmapPool = bitmapPool;
+ return this;
+ }
+
+ public BlurTransformation build() {
+ if (bitmapPool != null) {
+ return new BlurTransformation(this, bitmapPool);
+ }
+ return new BlurTransformation(this);
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.java b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.java
new file mode 100644
index 000000000..79fc23aa8
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.java
@@ -0,0 +1,53 @@
+package code.name.monkey.retromusic.glide;
+
+import android.graphics.drawable.Drawable;
+import android.widget.ImageView;
+
+import com.bumptech.glide.request.animation.GlideAnimation;
+
+import code.name.monkey.appthemehelper.util.ATHUtil;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.RetroApplication;
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteTarget;
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
+import code.name.monkey.retromusic.util.PreferenceUtil;
+
+import static code.name.monkey.retromusic.util.RetroColorUtil.getColor;
+import static code.name.monkey.retromusic.util.RetroColorUtil.getDominantColor;
+
+
+public abstract class RetroMusicColoredTarget extends BitmapPaletteTarget {
+
+ public RetroMusicColoredTarget(ImageView view) {
+ super(view);
+ }
+
+ @Override
+ public void onLoadFailed(Exception e, Drawable errorDrawable) {
+ super.onLoadFailed(e, errorDrawable);
+ onColorReady(getDefaultFooterColor());
+ }
+
+ @Override
+ public void onResourceReady(BitmapPaletteWrapper resource,
+ GlideAnimation super BitmapPaletteWrapper> glideAnimation) {
+ super.onResourceReady(resource, glideAnimation);
+ int defaultColor = getDefaultFooterColor();
+
+ int primaryColor = getColor(resource.getPalette(), defaultColor);
+ int dominantColor = getDominantColor(resource.getBitmap(), defaultColor);
+
+ onColorReady(PreferenceUtil.getInstance(RetroApplication.getInstance()).isDominantColor() ?
+ dominantColor : primaryColor);
+ }
+
+ protected int getDefaultFooterColor() {
+ return ATHUtil.resolveColor(getView().getContext(), R.attr.defaultFooterColor);
+ }
+
+ protected int getAlbumArtistFooterColor() {
+ return ATHUtil.resolveColor(getView().getContext(), R.attr.cardBackgroundColor);
+ }
+
+ public abstract void onColorReady(int color);
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.java b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.java
new file mode 100644
index 000000000..ae2074b17
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.java
@@ -0,0 +1,27 @@
+package code.name.monkey.retromusic.glide;
+
+import android.content.Context;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.GlideBuilder;
+import com.bumptech.glide.module.GlideModule;
+import code.name.monkey.retromusic.glide.artistimage.ArtistImage;
+import code.name.monkey.retromusic.glide.artistimage.ArtistImageLoader;
+import code.name.monkey.retromusic.glide.audiocover.AudioFileCover;
+import code.name.monkey.retromusic.glide.audiocover.AudioFileCoverLoader;
+
+import java.io.InputStream;
+
+
+public class RetroMusicGlideModule implements GlideModule {
+ @Override
+ public void applyOptions(Context context, GlideBuilder builder) {
+
+ }
+
+ @Override
+ public void registerComponents(Context context, Glide glide) {
+ glide.register(AudioFileCover.class, InputStream.class, new AudioFileCoverLoader.Factory());
+ glide.register(ArtistImage.class, InputStream.class, new ArtistImageLoader.Factory(context));
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java
new file mode 100644
index 000000000..24eb8e634
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java
@@ -0,0 +1,124 @@
+package code.name.monkey.retromusic.glide;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.support.annotation.NonNull;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.glide.audiocover.AudioFileCover;
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder;
+import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.util.MusicUtil;
+import code.name.monkey.retromusic.util.PreferenceUtil;
+import com.bumptech.glide.BitmapRequestBuilder;
+import com.bumptech.glide.DrawableRequestBuilder;
+import com.bumptech.glide.DrawableTypeRequest;
+import com.bumptech.glide.RequestManager;
+import com.bumptech.glide.load.Key;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+import com.bumptech.glide.signature.MediaStoreSignature;
+
+
+public class SongGlideRequest {
+
+ static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE;
+ static final int DEFAULT_ANIMATION = android.R.anim.fade_in;
+ static final int DEFAULT_ERROR_IMAGE = R.drawable.default_album_art;
+
+ static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Song song,
+ boolean ignoreMediaStore) {
+ if (ignoreMediaStore) {
+ return requestManager.load(new AudioFileCover(song.data));
+ } else {
+ return requestManager.loadFromMediaStore(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId));
+ }
+ }
+
+ static Key createSignature(Song song) {
+ return new MediaStoreSignature("", song.dateModified, 0);
+ }
+
+ public static class Builder {
+
+ final RequestManager requestManager;
+ final Song song;
+ boolean ignoreMediaStore;
+
+ private Builder(@NonNull RequestManager requestManager, Song song) {
+ this.requestManager = requestManager;
+ this.song = song;
+ }
+
+ public static Builder from(@NonNull RequestManager requestManager, Song song) {
+ return new Builder(requestManager, song);
+ }
+
+ public PaletteBuilder generatePalette(Context context) {
+ return new PaletteBuilder(this, context);
+ }
+
+ public BitmapBuilder asBitmap() {
+ return new BitmapBuilder(this);
+ }
+
+ public Builder checkIgnoreMediaStore(Context context) {
+ return ignoreMediaStore(PreferenceUtil.getInstance(context).ignoreMediaStoreArtwork());
+ }
+
+ Builder ignoreMediaStore(boolean ignoreMediaStore) {
+ this.ignoreMediaStore = ignoreMediaStore;
+ return this;
+ }
+
+ public DrawableRequestBuilder build() {
+ //noinspection unchecked
+ return createBaseRequest(requestManager, song, ignoreMediaStore)
+ .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
+ .error(DEFAULT_ERROR_IMAGE)
+ .animate(DEFAULT_ANIMATION)
+ .signature(createSignature(song));
+ }
+ }
+
+ public static class BitmapBuilder {
+
+ private final Builder builder;
+
+ BitmapBuilder(Builder builder) {
+ this.builder = builder;
+ }
+
+ public BitmapRequestBuilder, Bitmap> build() {
+ //noinspection unchecked
+ return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore)
+ .asBitmap()
+ .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
+ .error(DEFAULT_ERROR_IMAGE)
+ .animate(DEFAULT_ANIMATION)
+ .signature(createSignature(builder.song));
+ }
+ }
+
+ public static class PaletteBuilder {
+
+ final Context context;
+ private final Builder builder;
+
+ PaletteBuilder(Builder builder, Context context) {
+ this.builder = builder;
+ this.context = context;
+ }
+
+ public BitmapRequestBuilder, BitmapPaletteWrapper> build() {
+ //noinspection unchecked
+ return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore)
+ .asBitmap()
+ .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class)
+ .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
+ .error(DEFAULT_ERROR_IMAGE)
+ .animate(DEFAULT_ANIMATION)
+ .signature(createSignature(builder.song));
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImage.java b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImage.java
new file mode 100644
index 000000000..ad8584665
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImage.java
@@ -0,0 +1,12 @@
+package code.name.monkey.retromusic.glide.artistimage;
+
+
+public class ArtistImage {
+ public final String artistName;
+ public final boolean skipOkHttpCache;
+
+ public ArtistImage(String artistName, boolean skipOkHttpCache) {
+ this.artistName = artistName;
+ this.skipOkHttpCache = skipOkHttpCache;
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageFetcher.java b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageFetcher.java
new file mode 100644
index 000000000..5e65535dd
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageFetcher.java
@@ -0,0 +1,83 @@
+package code.name.monkey.retromusic.glide.artistimage;
+
+import android.content.Context;
+
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.model.GlideUrl;
+import com.bumptech.glide.load.model.ModelLoader;
+import code.name.monkey.retromusic.rest.LastFMRestClient;
+import code.name.monkey.retromusic.rest.model.LastFmArtist;
+
+import code.name.monkey.retromusic.util.LastFMUtil;
+import code.name.monkey.retromusic.util.MusicUtil;
+import code.name.monkey.retromusic.util.RetroUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import retrofit2.Response;
+
+
+public class ArtistImageFetcher implements DataFetcher {
+ public static final String TAG = ArtistImageFetcher.class.getSimpleName();
+ private final LastFMRestClient lastFMRestClient;
+ private final ArtistImage model;
+ private final int width;
+ private final int height;
+ private Context context;
+ private ModelLoader urlLoader;
+ private volatile boolean isCancelled;
+ private DataFetcher urlFetcher;
+
+ public ArtistImageFetcher(Context context, LastFMRestClient lastFMRestClient, ArtistImage model, ModelLoader urlLoader, int width, int height) {
+ this.context = context;
+ this.lastFMRestClient = lastFMRestClient;
+ this.model = model;
+ this.urlLoader = urlLoader;
+ this.width = width;
+ this.height = height;
+ }
+
+ @Override
+ public String getId() {
+ // makes sure we never ever return null here
+ return String.valueOf(model.artistName);
+ }
+
+ @Override
+ public InputStream loadData(Priority priority) throws Exception {
+ if (!MusicUtil.isArtistNameUnknown(model.artistName) && RetroUtil.isAllowedToDownloadMetadata(context)) {
+ Response response = lastFMRestClient.getApiService().getArtistInfo(model.artistName, null, model.skipOkHttpCache ? "no-cache" : null).execute();
+
+ if (!response.isSuccessful()) {
+ throw new IOException("Request failed with code: " + response.code());
+ }
+
+ LastFmArtist lastFmArtist = response.body();
+
+ if (isCancelled) return null;
+
+ GlideUrl url = new GlideUrl(LastFMUtil.getLargestArtistImageUrl(lastFmArtist.getArtist().getImage()));
+ urlFetcher = urlLoader.getResourceFetcher(url, width, height);
+
+ return urlFetcher.loadData(priority);
+ }
+ return null;
+ }
+
+ @Override
+ public void cleanup() {
+ if (urlFetcher != null) {
+ urlFetcher.cleanup();
+ }
+ }
+
+ @Override
+ public void cancel() {
+ isCancelled = true;
+ if (urlFetcher != null) {
+ urlFetcher.cancel();
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.java b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.java
new file mode 100644
index 000000000..09392cedc
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.java
@@ -0,0 +1,68 @@
+package code.name.monkey.retromusic.glide.artistimage;
+
+import android.content.Context;
+
+import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.model.GenericLoaderFactory;
+import com.bumptech.glide.load.model.GlideUrl;
+import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.model.ModelLoaderFactory;
+import com.bumptech.glide.load.model.stream.StreamModelLoader;
+import code.name.monkey.retromusic.rest.LastFMRestClient;
+
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.OkHttpClient;
+
+
+
+public class ArtistImageLoader implements StreamModelLoader {
+ // we need these very low values to make sure our artist image loading calls doesn't block the image loading queue
+ private static final int TIMEOUT = 500;
+
+ private Context context;
+ private LastFMRestClient lastFMClient;
+ private ModelLoader urlLoader;
+
+ public ArtistImageLoader(Context context, LastFMRestClient lastFMRestClient, ModelLoader urlLoader) {
+ this.context = context;
+ this.lastFMClient = lastFMRestClient;
+ this.urlLoader = urlLoader;
+ }
+
+ @Override
+ public DataFetcher getResourceFetcher(ArtistImage model, int width, int height) {
+ return new ArtistImageFetcher(context, lastFMClient, model, urlLoader, width, height);
+ }
+
+ public static class Factory implements ModelLoaderFactory {
+ private LastFMRestClient lastFMClient;
+ private OkHttpUrlLoader.Factory okHttpFactory;
+
+ public Factory(Context context) {
+ okHttpFactory = new OkHttpUrlLoader.Factory(new OkHttpClient.Builder()
+ .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
+ .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
+ .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
+ .build());
+ lastFMClient = new LastFMRestClient(LastFMRestClient.createDefaultOkHttpClientBuilder(context)
+ .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
+ .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
+ .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
+ .build());
+ }
+
+ @Override
+ public ModelLoader build(Context context, GenericLoaderFactory factories) {
+ return new ArtistImageLoader(context, lastFMClient, okHttpFactory.build(context, factories));
+ }
+
+ @Override
+ public void teardown() {
+ okHttpFactory.teardown();
+ }
+ }
+}
+
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java
new file mode 100644
index 000000000..3a163bc1a
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java
@@ -0,0 +1,10 @@
+package code.name.monkey.retromusic.glide.audiocover;
+
+
+public class AudioFileCover {
+ public final String filePath;
+
+ public AudioFileCover(String filePath) {
+ this.filePath = filePath;
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java
new file mode 100644
index 000000000..8985da7c3
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java
@@ -0,0 +1,95 @@
+package code.name.monkey.retromusic.glide.audiocover;
+
+import android.media.MediaMetadataRetriever;
+
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.data.DataFetcher;
+
+import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
+import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
+import org.jaudiotagger.audio.mp3.MP3File;
+import org.jaudiotagger.tag.TagException;
+import org.jaudiotagger.tag.images.Artwork;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+public class AudioFileCoverFetcher implements DataFetcher {
+ private static final String[] FALLBACKS = {"cover.jpg", "album.jpg", "folder.jpg", "cover.png", "album.png", "folder.png"};
+ private final AudioFileCover model;
+ private FileInputStream stream;
+
+ public AudioFileCoverFetcher(AudioFileCover model) {
+ this.model = model;
+ }
+
+ @Override
+ public String getId() {
+ // makes sure we never ever return null here
+ return String.valueOf(model.filePath);
+ }
+
+ @Override
+ public InputStream loadData(Priority priority) throws Exception {
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ try {
+ retriever.setDataSource(model.filePath);
+ byte[] picture = retriever.getEmbeddedPicture();
+ if (picture != null) {
+ return new ByteArrayInputStream(picture);
+ } else {
+ return fallback(model.filePath);
+ }
+ } finally {
+ retriever.release();
+ }
+ }
+
+ private InputStream fallback(String path) throws FileNotFoundException {
+ // Method 1: use embedded high resolution album art if there is any
+ try {
+ MP3File mp3File = new MP3File(path);
+ if (mp3File.hasID3v2Tag()) {
+ Artwork art = mp3File.getTag().getFirstArtwork();
+ if (art != null) {
+ byte[] imageData = art.getBinaryData();
+ return new ByteArrayInputStream(imageData);
+ }
+ }
+ // If there are any exceptions, we ignore them and continue to the other fallback method
+ } catch (ReadOnlyFileException | InvalidAudioFrameException | TagException | IOException ignored) {
+ }
+
+ // Method 2: look for album art in external files
+ File parent = new File(path).getParentFile();
+ for (String fallback : FALLBACKS) {
+ File cover = new File(parent, fallback);
+ if (cover.exists()) {
+ return stream = new FileInputStream(cover);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void cleanup() {
+ // already cleaned up in loadData and ByteArrayInputStream will be GC'd
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException ignore) {
+ // can't do much about it
+ }
+ }
+ }
+
+ @Override
+ public void cancel() {
+ // cannot cancel
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java
new file mode 100644
index 000000000..2b02c9b3c
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java
@@ -0,0 +1,32 @@
+package code.name.monkey.retromusic.glide.audiocover;
+
+import android.content.Context;
+
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.model.GenericLoaderFactory;
+import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.model.ModelLoaderFactory;
+import com.bumptech.glide.load.model.stream.StreamModelLoader;
+
+import java.io.InputStream;
+
+
+public class AudioFileCoverLoader implements StreamModelLoader {
+
+ @Override
+ public DataFetcher getResourceFetcher(AudioFileCover model, int width, int height) {
+ return new AudioFileCoverFetcher(model);
+ }
+
+ public static class Factory implements ModelLoaderFactory {
+ @Override
+ public ModelLoader build(Context context, GenericLoaderFactory factories) {
+ return new AudioFileCoverLoader();
+ }
+
+ @Override
+ public void teardown() {
+ }
+ }
+}
+
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java
new file mode 100644
index 000000000..201e43b7b
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java
@@ -0,0 +1,34 @@
+package code.name.monkey.retromusic.glide.palette;
+
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.util.Util;
+
+
+public class BitmapPaletteResource implements Resource {
+
+ private final BitmapPaletteWrapper bitmapPaletteWrapper;
+ private final BitmapPool bitmapPool;
+
+ public BitmapPaletteResource(BitmapPaletteWrapper bitmapPaletteWrapper, BitmapPool bitmapPool) {
+ this.bitmapPaletteWrapper = bitmapPaletteWrapper;
+ this.bitmapPool = bitmapPool;
+ }
+
+ @Override
+ public BitmapPaletteWrapper get() {
+ return bitmapPaletteWrapper;
+ }
+
+ @Override
+ public int getSize() {
+ return Util.getBitmapByteSize(bitmapPaletteWrapper.getBitmap());
+ }
+
+ @Override
+ public void recycle() {
+ if (!bitmapPool.put(bitmapPaletteWrapper.getBitmap())) {
+ bitmapPaletteWrapper.getBitmap().recycle();
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java
new file mode 100644
index 000000000..662db217a
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java
@@ -0,0 +1,17 @@
+package code.name.monkey.retromusic.glide.palette;
+
+import android.widget.ImageView;
+import com.bumptech.glide.request.target.ImageViewTarget;
+
+public class BitmapPaletteTarget extends ImageViewTarget {
+
+ public BitmapPaletteTarget(ImageView view) {
+ super(view);
+ }
+
+ @Override
+ protected void setResource(BitmapPaletteWrapper bitmapPaletteWrapper) {
+ view.setImageBitmap(bitmapPaletteWrapper.getBitmap());
+ }
+
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java
new file mode 100644
index 000000000..ad5cf47a8
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java
@@ -0,0 +1,34 @@
+package code.name.monkey.retromusic.glide.palette;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.engine.Resource;
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
+import code.name.monkey.retromusic.util.RetroColorUtil;
+
+public class BitmapPaletteTranscoder implements ResourceTranscoder {
+ private final BitmapPool bitmapPool;
+
+ public BitmapPaletteTranscoder(Context context) {
+ this(Glide.get(context).getBitmapPool());
+ }
+
+ public BitmapPaletteTranscoder(BitmapPool bitmapPool) {
+ this.bitmapPool = bitmapPool;
+ }
+
+ @Override
+ public Resource transcode(Resource bitmapResource) {
+ Bitmap bitmap = bitmapResource.get();
+ BitmapPaletteWrapper bitmapPaletteWrapper = new BitmapPaletteWrapper(bitmap, RetroColorUtil.generatePalette(bitmap));
+ return new BitmapPaletteResource(bitmapPaletteWrapper, bitmapPool);
+ }
+
+ @Override
+ public String getId() {
+ return "BitmapPaletteTranscoder.code.name.monkey.retromusic.glide.palette";
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java
new file mode 100644
index 000000000..e7c53bea6
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java
@@ -0,0 +1,22 @@
+package code.name.monkey.retromusic.glide.palette;
+
+import android.graphics.Bitmap;
+import android.support.v7.graphics.Palette;
+
+public class BitmapPaletteWrapper {
+ private final Bitmap mBitmap;
+ private final Palette mPalette;
+
+ public BitmapPaletteWrapper(Bitmap bitmap, Palette palette) {
+ mBitmap = bitmap;
+ mPalette = palette;
+ }
+
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ public Palette getPalette() {
+ return mPalette;
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/EqualizerHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/EqualizerHelper.java
new file mode 100644
index 000000000..12f98ba46
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/EqualizerHelper.java
@@ -0,0 +1,171 @@
+package code.name.monkey.retromusic.helper;
+
+import android.media.audiofx.BassBoost;
+import android.media.audiofx.Equalizer;
+import android.media.audiofx.Virtualizer;
+import android.util.Log;
+
+import code.name.monkey.retromusic.interfaces.EqualizerInterface;
+
+/**
+ * @author Hemanth S (h4h13).
+ */
+
+public class EqualizerHelper implements EqualizerInterface {
+ private static final String TAG = "EqualizerHelper";
+ private static volatile EqualizerHelper ourInstance;
+ private Equalizer mEqualizer;
+ private BassBoost mBassBoost;
+ private Virtualizer mVirtualizer;
+
+ private int mMaxLevel, mMinLevel;
+ private boolean isRunning = false;
+
+ private EqualizerHelper() {
+
+ //Prevent form the reflection api.
+ if (ourInstance != null) {
+ throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
+ }
+
+ int i = MusicPlayerRemote.getAudioSessionId();
+
+ mEqualizer = new Equalizer(100, i);
+ if (mEqualizer == null) {
+ Log.i(TAG, "onCreate: Equalizer is null");
+ return;
+ }
+ mEqualizer.setEnabled(true);
+
+
+ mBassBoost = new BassBoost(100, i);
+ if (mBassBoost == null) {
+ Log.i(TAG, "onCreate: BassBoost is null");
+ return;
+ }
+
+ mVirtualizer = new Virtualizer(100, i);
+ if (mVirtualizer == null) {
+ Log.i(TAG, "onCreate: Virtualizer is null");
+ return;
+ }
+
+ mMaxLevel = (int) mEqualizer.getBandLevelRange()[1];
+ mMinLevel = (int) mEqualizer.getBandLevelRange()[0];
+
+ Log.i(TAG, "onCreate: " + mMaxLevel + " " + mMinLevel);
+ isRunning = true;
+ }
+
+ public static EqualizerHelper getInstance() {
+ //Double check locking pattern
+ if (ourInstance == null) {//Check for the first time
+
+ synchronized (EqualizerHelper.class) {//Check for the second time.
+
+ //if there is no instance available... create new one
+ if (ourInstance == null) {
+ ourInstance = new EqualizerHelper();
+ }
+ }
+ }
+ return ourInstance;
+ }
+
+ //Make singleton from serialize and deserialize operation.
+ protected EqualizerHelper readResolve() {
+ return getInstance();
+ }
+
+ @Override
+ public Equalizer getEqualizer() {
+ return mEqualizer;
+ }
+
+ @Override
+ public BassBoost getBassBoost() {
+ return mBassBoost;
+ }
+
+ @Override
+ public Virtualizer getVirtualizer() {
+ return mVirtualizer;
+ }
+
+ @Override
+ public int getBandLevelLow() {
+ return mMinLevel;
+ }
+
+ @Override
+ public int getBandLevelHigh() {
+ return mMaxLevel;
+ }
+
+ @Override
+ public int getNumberOfBands() {
+ return (int) mEqualizer.getNumberOfBands();
+ }
+
+ @Override
+ public int getCenterFreq(int band) {
+ return (int) mEqualizer.getCenterFreq((short) band);
+ }
+
+
+ @Override
+ public int getBandLevel(int band) {
+ return (int) mEqualizer.getBandLevel((short) band);
+ }
+
+ @Override
+ public void setBandLevel(int band, int level) {
+ mEqualizer.setBandLevel((short) band, (short) level);
+ }
+
+ @Override
+ public boolean isBassBoostEnabled() {
+ return mBassBoost.getEnabled();
+ }
+
+ @Override
+ public void setBassBoostEnabled(boolean isEnabled) {
+ mBassBoost.setEnabled(isEnabled);
+ }
+
+ @Override
+ public int getBassBoostStrength() {
+ return (int) mBassBoost.getRoundedStrength();
+ }
+
+ @Override
+ public void setBassBoostStrength(int strength) {
+ mBassBoost.setStrength((short) strength);
+ }
+
+ @Override
+ public boolean isVirtualizerEnabled() {
+ return mVirtualizer.getEnabled();
+ }
+
+ @Override
+ public void setVirtualizerEnabled(boolean isEnabled) {
+ mVirtualizer.setEnabled(isEnabled);
+ }
+
+ @Override
+ public int getVirtualizerStrength() {
+ return mVirtualizer.getRoundedStrength();
+ }
+
+ @Override
+ public void setVirtualizerStrength(int strength) {
+ mVirtualizer.setStrength((short) strength);
+ }
+
+ @Override
+ public boolean isRunning() {
+ return isRunning;
+ }
+
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.java
new file mode 100644
index 000000000..fd1ae91a6
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.java
@@ -0,0 +1,36 @@
+package code.name.monkey.retromusic.helper;
+
+import android.content.Context;
+import android.view.ViewGroup;
+import code.name.monkey.retromusic.R;
+
+
+public class HorizontalAdapterHelper {
+
+ public static final int LAYOUT_RES = R.layout.item_image;
+
+ public static final int TYPE_FIRST = 1;
+ public static final int TYPE_MIDDLE = 2;
+ public static final int TYPE_LAST = 3;
+
+ public static void applyMarginToLayoutParams(Context context,
+ ViewGroup.MarginLayoutParams layoutParams, int viewType) {
+ int listMargin = context.getResources()
+ .getDimensionPixelSize(R.dimen.now_playing_top_margin);
+ if (viewType == TYPE_FIRST) {
+ layoutParams.leftMargin = listMargin;
+ } else if (viewType == TYPE_LAST) {
+ layoutParams.rightMargin = listMargin;
+ }
+ }
+
+ public static int getItemViewtype(int position, int itemCount) {
+ if (position == 0) {
+ return TYPE_FIRST;
+ } else if (position == itemCount - 1) {
+ return TYPE_LAST;
+ } else {
+ return TYPE_MIDDLE;
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/M3UConstants.java b/app/src/main/java/code/name/monkey/retromusic/helper/M3UConstants.java
new file mode 100644
index 000000000..e6ffdf397
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/M3UConstants.java
@@ -0,0 +1,8 @@
+package code.name.monkey.retromusic.helper;
+
+public interface M3UConstants {
+ String EXTENSION = "m3u";
+ String HEADER = "#EXTM3U";
+ String ENTRY = "#EXTINF:";
+ String DURATION_SEPARATOR = ",";
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.java b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.java
new file mode 100644
index 000000000..5e9416eb0
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.java
@@ -0,0 +1,58 @@
+package code.name.monkey.retromusic.helper;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import code.name.monkey.retromusic.loaders.PlaylistSongsLoader;
+import code.name.monkey.retromusic.model.AbsCustomPlaylist;
+import code.name.monkey.retromusic.model.Playlist;
+import code.name.monkey.retromusic.model.Song;
+import io.reactivex.Observable;
+import io.reactivex.ObservableEmitter;
+
+public class M3UWriter implements M3UConstants {
+ public static final String TAG = M3UWriter.class.getSimpleName();
+
+ public static Observable write(@NonNull Context context,
+ @NonNull File dir, @NonNull Playlist playlist) {
+ if (!dir.exists()) //noinspection ResultOfMethodCallIgnored
+ dir.mkdirs();
+ File file = new File(dir, playlist.name.concat("." + EXTENSION));
+
+ if (playlist instanceof AbsCustomPlaylist) {
+ return Observable.create(e -> {
+ ((AbsCustomPlaylist) playlist).getSongs(context).subscribe(songs -> {
+ saveSongsToFile(file, e, songs);
+ });
+ });
+ } else
+ return Observable.create(e ->
+ PlaylistSongsLoader.getPlaylistSongList(context, playlist.id)
+ .subscribe(songs -> {
+ saveSongsToFile(file, e, songs);
+ }));
+ }
+
+ private static void saveSongsToFile(File file, ObservableEmitter e, ArrayList songs) throws IOException {
+ if (songs.size() > 0) {
+ BufferedWriter bw = new BufferedWriter(new FileWriter(file));
+ bw.write(HEADER);
+ for (Song song : songs) {
+ bw.newLine();
+ bw.write(ENTRY + song.duration + DURATION_SEPARATOR + song.artistName + " - " + song.title);
+ bw.newLine();
+ bw.write(song.data);
+ }
+
+ bw.close();
+ }
+ e.onNext(file);
+ e.onComplete();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.java b/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.java
new file mode 100644
index 000000000..ccd21773b
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.java
@@ -0,0 +1,480 @@
+package code.name.monkey.retromusic.helper;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.IBinder;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Random;
+import java.util.WeakHashMap;
+
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.loaders.SongLoader;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.service.MusicService;
+import code.name.monkey.retromusic.util.PreferenceUtil;
+import io.reactivex.schedulers.Schedulers;
+
+
+public class MusicPlayerRemote {
+
+ public static final String TAG = MusicPlayerRemote.class.getSimpleName();
+ private static final WeakHashMap mConnectionMap = new WeakHashMap<>();
+ @Nullable
+ public static MusicService musicService;
+
+ public static ServiceToken bindToService(@NonNull final Context context,
+ final ServiceConnection callback) {
+ Activity realActivity = ((Activity) context).getParent();
+ if (realActivity == null) {
+ realActivity = (Activity) context;
+ }
+
+ final ContextWrapper contextWrapper = new ContextWrapper(realActivity);
+ contextWrapper.startService(new Intent(contextWrapper, MusicService.class));
+
+ final ServiceBinder binder = new ServiceBinder(callback);
+
+ if (contextWrapper.bindService(new Intent().setClass(contextWrapper, MusicService.class), binder, Context.BIND_AUTO_CREATE)) {
+ mConnectionMap.put(contextWrapper, binder);
+ return new ServiceToken(contextWrapper);
+ }
+ return null;
+ }
+
+ public static void unbindFromService(@Nullable final ServiceToken token) {
+ if (token == null) {
+ return;
+ }
+ final ContextWrapper mContextWrapper = token.mWrappedContext;
+ final ServiceBinder mBinder = mConnectionMap.remove(mContextWrapper);
+ if (mBinder == null) {
+ return;
+ }
+ mContextWrapper.unbindService(mBinder);
+ if (mConnectionMap.isEmpty()) {
+ musicService = null;
+ }
+ }
+
+ @Nullable
+ private static String getFilePathFromUri(Context context, Uri uri) {
+ Cursor cursor = null;
+ final String column = "_data";
+ final String[] projection = {
+ column
+ };
+
+ try {
+ cursor = context.getContentResolver().query(uri, projection, null, null,
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int column_index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(column_index);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return null;
+ }
+
+ /**
+ * Async
+ */
+ public static void playSongAt(final int position) {
+ if (musicService != null) {
+ musicService.playSongAt(position);
+ }
+ }
+
+ public static void pauseSong() {
+ if (musicService != null) {
+ musicService.pause();
+ }
+ }
+
+ /**
+ * Async
+ */
+ public static void playNextSong() {
+ if (musicService != null) {
+ musicService.playNextSong(true);
+ }
+ }
+
+ /**
+ * Async
+ */
+ public static void playPreviousSong() {
+ if (musicService != null) {
+ musicService.playPreviousSong(true);
+ }
+ }
+
+ /**
+ * Async
+ */
+ public static void back() {
+ if (musicService != null) {
+ musicService.back(true);
+ }
+ }
+
+ public static boolean isPlaying() {
+ return musicService != null && musicService.isPlaying();
+ }
+
+ public static void resumePlaying() {
+ if (musicService != null) {
+ musicService.play();
+ }
+ }
+
+ /**
+ * Async
+ */
+ public static void openQueue(final ArrayList queue, final int startPosition, final boolean startPlaying) {
+ if (!tryToHandleOpenPlayingQueue(queue, startPosition, startPlaying) && musicService != null) {
+ musicService.openQueue(queue, startPosition, startPlaying);
+ if (PreferenceUtil.getInstance(musicService).isShuffleModeOn())
+ setShuffleMode(MusicService.SHUFFLE_MODE_NONE);
+ }
+ }
+
+ /**
+ * Async
+ */
+ public static void openAndShuffleQueue(final ArrayList queue, boolean startPlaying) {
+ int startPosition = 0;
+ if (!queue.isEmpty()) {
+ startPosition = new Random().nextInt(queue.size());
+ }
+
+ if (!tryToHandleOpenPlayingQueue(queue, startPosition, startPlaying) && musicService != null) {
+ openQueue(queue, startPosition, startPlaying);
+ setShuffleMode(MusicService.SHUFFLE_MODE_SHUFFLE);
+ }
+ }
+
+ private static boolean tryToHandleOpenPlayingQueue(final ArrayList queue, final int startPosition, final boolean startPlaying) {
+ if (getPlayingQueue() == queue) {
+ if (startPlaying) {
+ playSongAt(startPosition);
+ } else {
+ setPosition(startPosition);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public static Song getCurrentSong() {
+ if (musicService != null) {
+ return musicService.getCurrentSong();
+ }
+ return Song.EMPTY_SONG;
+ }
+
+ public static int getPosition() {
+ if (musicService != null) {
+ return musicService.getPosition();
+ }
+ return -1;
+ }
+
+ /**
+ * Async
+ */
+ public static void setPosition(final int position) {
+ if (musicService != null) {
+ musicService.setPosition(position);
+ }
+ }
+
+ public static ArrayList getPlayingQueue() {
+ if (musicService != null) {
+ return musicService.getPlayingQueue();
+ }
+ return new ArrayList<>();
+ }
+
+ public static int getSongProgressMillis() {
+ if (musicService != null) {
+ return musicService.getSongProgressMillis();
+ }
+ return -1;
+ }
+
+ public static int getSongDurationMillis() {
+ if (musicService != null) {
+ return musicService.getSongDurationMillis();
+ }
+ return -1;
+ }
+
+ public static long getQueueDurationMillis(int position) {
+ if (musicService != null) {
+ return musicService.getQueueDurationMillis(position);
+ }
+ return -1;
+ }
+
+ public static int seekTo(int millis) {
+ if (musicService != null) {
+ return musicService.seek(millis);
+ }
+ return -1;
+ }
+
+ public static int getRepeatMode() {
+ if (musicService != null) {
+ return musicService.getRepeatMode();
+ }
+ return MusicService.REPEAT_MODE_NONE;
+ }
+
+ public static int getShuffleMode() {
+ if (musicService != null) {
+ return musicService.getShuffleMode();
+ }
+ return MusicService.SHUFFLE_MODE_NONE;
+ }
+
+ public static boolean cycleRepeatMode() {
+ if (musicService != null) {
+ musicService.cycleRepeatMode();
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean toggleShuffleMode() {
+ if (musicService != null) {
+ musicService.toggleShuffle();
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean setShuffleMode(final int shuffleMode) {
+ if (musicService != null) {
+ musicService.setShuffleMode(shuffleMode);
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean playNext(Song song) {
+ if (musicService != null) {
+ if (getPlayingQueue().size() > 0) {
+ musicService.addSong(getPosition() + 1, song);
+ } else {
+ ArrayList queue = new ArrayList<>();
+ queue.add(song);
+ openQueue(queue, 0, false);
+ }
+ Toast.makeText(musicService, musicService.getResources().getString(R.string.added_title_to_playing_queue), Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean playNext(@NonNull ArrayList songs) {
+ if (musicService != null) {
+ if (getPlayingQueue().size() > 0) {
+ musicService.addSongs(getPosition() + 1, songs);
+ } else {
+ openQueue(songs, 0, false);
+ }
+ final String toast = songs.size() == 1 ? musicService.getResources().getString(R.string.added_title_to_playing_queue) : musicService.getResources().getString(R.string.added_x_titles_to_playing_queue, songs.size());
+ Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean enqueue(Song song) {
+ if (musicService != null) {
+ if (getPlayingQueue().size() > 0) {
+ musicService.addSong(song);
+ } else {
+ ArrayList queue = new ArrayList<>();
+ queue.add(song);
+ openQueue(queue, 0, false);
+ }
+ Toast.makeText(musicService, musicService.getResources().getString(R.string.added_title_to_playing_queue), Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean enqueue(@NonNull ArrayList songs) {
+ if (musicService != null) {
+ if (getPlayingQueue().size() > 0) {
+ musicService.addSongs(songs);
+ } else {
+ openQueue(songs, 0, false);
+ }
+ final String toast = songs.size() == 1 ? musicService.getResources().getString(R.string.added_title_to_playing_queue) : musicService.getResources().getString(R.string.added_x_titles_to_playing_queue, songs.size());
+ Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean removeFromQueue(@NonNull Song song) {
+ if (musicService != null) {
+ musicService.removeSong(song);
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean removeFromQueue(int position) {
+ if (musicService != null && position >= 0 && position < getPlayingQueue().size()) {
+ musicService.removeSong(position);
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean moveSong(int from, int to) {
+ if (musicService != null && from >= 0 && to >= 0 && from < getPlayingQueue().size() && to < getPlayingQueue().size()) {
+ musicService.moveSong(from, to);
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean clearQueue() {
+ if (musicService != null) {
+ musicService.clearQueue();
+ return true;
+ }
+ return false;
+ }
+
+ public static int getAudioSessionId() {
+ if (musicService != null) {
+ return musicService.getAudioSessionId();
+ }
+ return -1;
+ }
+
+ public static void playFromUri(Uri uri) {
+ if (musicService != null) {
+ ArrayList songs = null;
+ if (uri.getScheme() != null && uri.getAuthority() != null) {
+ if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+ String songId = null;
+ if (uri.getAuthority().equals("com.android.providers.media.documents")) {
+ songId = getSongIdFromMediaProvider(uri);
+ } else if (uri.getAuthority().equals("media")) {
+ songId = uri.getLastPathSegment();
+ }
+ if (songId != null) {
+ /* songs = SongLoader.getSongs(SongLoader.makeSongCursor(
+ musicService,
+ MediaStore.Audio.AudioColumns._ID + "=?",
+ new String[]{songId}
+ ));*/
+ songs = SongLoader.getSongs(SongLoader.makeSongCursor(
+ musicService,
+ MediaStore.Audio.AudioColumns._ID + "=?",
+ new String[]{songId}))
+ .subscribeOn(Schedulers.io()).blockingFirst();
+ }
+ }
+ }
+ if (songs == null) {
+ File songFile = null;
+ if (uri.getAuthority() != null && uri.getAuthority().equals("com.android.externalstorage.documents")) {
+ songFile = new File(Environment.getExternalStorageDirectory(), uri.getPath().split(":", 2)[1]);
+ }
+ if (songFile == null) {
+ String path = getFilePathFromUri(musicService, uri);
+ if (path != null)
+ songFile = new File(path);
+ }
+ if (songFile == null && uri.getPath() != null) {
+ songFile = new File(uri.getPath());
+ }
+ if (songFile != null) {
+ songs = SongLoader.getSongs(SongLoader.makeSongCursor(
+ musicService,
+ MediaStore.Audio.AudioColumns.DATA + "=?",
+ new String[]{songFile.getAbsolutePath()}
+ )).blockingFirst();
+ }
+ }
+ if (songs != null && !songs.isEmpty()) {
+ openQueue(songs, 0, true);
+ } else {
+ //TODO the file is not listed in the media store
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ private static String getSongIdFromMediaProvider(Uri uri) {
+ return DocumentsContract.getDocumentId(uri).split(":")[1];
+ }
+
+ public static boolean isServiceConnected() {
+ return musicService != null;
+ }
+
+
+ public static final class ServiceBinder implements ServiceConnection {
+ private final ServiceConnection mCallback;
+
+ ServiceBinder(final ServiceConnection callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onServiceConnected(final ComponentName className, final IBinder service) {
+ MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
+ musicService = binder.getService();
+ if (mCallback != null) {
+ mCallback.onServiceConnected(className, service);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(final ComponentName className) {
+ if (mCallback != null) {
+ mCallback.onServiceDisconnected(className);
+ }
+ musicService = null;
+ }
+ }
+
+ public static final class ServiceToken {
+ ContextWrapper mWrappedContext;
+
+ ServiceToken(final ContextWrapper context) {
+ mWrappedContext = context;
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/MusicProgressViewUpdateHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/MusicProgressViewUpdateHelper.java
new file mode 100644
index 000000000..f3f886c24
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/MusicProgressViewUpdateHelper.java
@@ -0,0 +1,71 @@
+package code.name.monkey.retromusic.helper;
+
+import android.os.Handler;
+import android.os.Message;
+import android.support.annotation.NonNull;
+
+
+public class MusicProgressViewUpdateHelper extends Handler {
+ private static final int CMD_REFRESH_PROGRESS_VIEWS = 1;
+
+ private static final int MIN_INTERVAL = 20;
+ private static final int UPDATE_INTERVAL_PLAYING = 1000;
+ private static final int UPDATE_INTERVAL_PAUSED = 500;
+
+ private Callback callback;
+ private int intervalPlaying;
+ private int intervalPaused;
+
+ public void start() {
+ queueNextRefresh(1);
+ }
+
+ public void stop() {
+ removeMessages(CMD_REFRESH_PROGRESS_VIEWS);
+ }
+
+ public MusicProgressViewUpdateHelper(Callback callback) {
+ this.callback = callback;
+ this.intervalPlaying = UPDATE_INTERVAL_PLAYING;
+ this.intervalPaused = UPDATE_INTERVAL_PAUSED;
+ }
+
+ public MusicProgressViewUpdateHelper(Callback callback, int intervalPlaying, int intervalPaused) {
+ this.callback = callback;
+ this.intervalPlaying = intervalPlaying;
+ this.intervalPaused = intervalPaused;
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ super.handleMessage(msg);
+ if (msg.what == CMD_REFRESH_PROGRESS_VIEWS) {
+ queueNextRefresh(refreshProgressViews());
+ }
+ }
+
+ private int refreshProgressViews() {
+ final int progressMillis = MusicPlayerRemote.getSongProgressMillis();
+ final int totalMillis = MusicPlayerRemote.getSongDurationMillis();
+
+ callback.onUpdateProgressViews(progressMillis, totalMillis);
+
+ if (!MusicPlayerRemote.isPlaying()) {
+ return intervalPaused;
+ }
+
+ final int remainingMillis = intervalPlaying - progressMillis % intervalPlaying;
+
+ return Math.max(MIN_INTERVAL, remainingMillis);
+ }
+
+ private void queueNextRefresh(final long delay) {
+ final Message message = obtainMessage(CMD_REFRESH_PROGRESS_VIEWS);
+ removeMessages(CMD_REFRESH_PROGRESS_VIEWS);
+ sendMessageDelayed(message, delay);
+ }
+
+ public interface Callback {
+ void onUpdateProgressViews(int progress, int total);
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/PlayPauseButtonOnClickHandler.java b/app/src/main/java/code/name/monkey/retromusic/helper/PlayPauseButtonOnClickHandler.java
new file mode 100644
index 000000000..4d69a5a4d
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/PlayPauseButtonOnClickHandler.java
@@ -0,0 +1,15 @@
+package code.name.monkey.retromusic.helper;
+
+import android.view.View;
+
+
+public class PlayPauseButtonOnClickHandler implements View.OnClickListener {
+ @Override
+ public void onClick(View v) {
+ if (MusicPlayerRemote.isPlaying()) {
+ MusicPlayerRemote.pauseSong();
+ } else {
+ MusicPlayerRemote.resumePlaying();
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.java
new file mode 100644
index 000000000..fb8c99e10
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.java
@@ -0,0 +1,91 @@
+package code.name.monkey.retromusic.helper;
+
+import android.app.SearchManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+
+import code.name.monkey.retromusic.loaders.SongLoader;
+import code.name.monkey.retromusic.model.Song;
+
+
+public class SearchQueryHelper {
+ private static final String TITLE_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.TITLE + ") = ?";
+ private static final String ALBUM_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.ALBUM + ") = ?";
+ private static final String ARTIST_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.ARTIST + ") = ?";
+ private static final String AND = " AND ";
+ private static ArrayList songs = new ArrayList<>();
+
+ public static ArrayList getSongs() {
+ return songs;
+ }
+
+ public static void setSongs(ArrayList songs) {
+ SearchQueryHelper.songs = songs;
+ }
+
+ @NonNull
+ public static ArrayList getSongs(@NonNull final Context context, @NonNull final Bundle extras) {
+ final String query = extras.getString(SearchManager.QUERY, null);
+ final String artistName = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST, null);
+ final String albumName = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM, null);
+ final String titleName = extras.getString(MediaStore.EXTRA_MEDIA_TITLE, null);
+
+ ArrayList songs = new ArrayList<>();
+ if (artistName != null && albumName != null && titleName != null) {
+ songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION + AND + ALBUM_SELECTION + AND + TITLE_SELECTION, new String[]{artistName.toLowerCase(), albumName.toLowerCase(), titleName.toLowerCase()})).blockingFirst();
+ }
+ if (!songs.isEmpty()) {
+ return songs;
+ }
+ if (artistName != null && titleName != null) {
+ songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION + AND + TITLE_SELECTION, new String[]{artistName.toLowerCase(), titleName.toLowerCase()})).blockingFirst();
+ }
+ if (!songs.isEmpty()) {
+ return songs;
+ }
+ if (albumName != null && titleName != null) {
+ songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION + AND + TITLE_SELECTION, new String[]{albumName.toLowerCase(), titleName.toLowerCase()})).blockingFirst();
+ }
+ if (!songs.isEmpty()) {
+ return songs;
+ }
+ if (artistName != null) {
+ songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION, new String[]{artistName.toLowerCase()})).blockingFirst();
+ }
+ if (!songs.isEmpty()) {
+ return songs;
+ }
+ if (albumName != null) {
+ songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION, new String[]{albumName.toLowerCase()})).blockingFirst();
+ }
+ if (!songs.isEmpty()) {
+ return songs;
+ }
+ if (titleName != null) {
+ songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, TITLE_SELECTION, new String[]{titleName.toLowerCase()})).blockingFirst();
+ }
+ if (!songs.isEmpty()) {
+ return songs;
+ }
+ songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION, new String[]{query.toLowerCase()})).blockingFirst();
+
+ if (!songs.isEmpty()) {
+ return songs;
+ }
+ songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION, new String[]{query.toLowerCase()})).blockingFirst();
+ if (!songs.isEmpty()) {
+ return songs;
+ }
+ songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, TITLE_SELECTION, new String[]{query.toLowerCase()})).blockingFirst();
+ if (!songs.isEmpty()) {
+ return songs;
+ }
+ return new ArrayList();
+ }
+
+
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/ShuffleHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/ShuffleHelper.java
new file mode 100644
index 000000000..5e7b41821
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/ShuffleHelper.java
@@ -0,0 +1,25 @@
+package code.name.monkey.retromusic.helper;
+
+import android.support.annotation.NonNull;
+
+import code.name.monkey.retromusic.model.Song;
+
+import java.util.Collections;
+import java.util.List;
+
+
+public class ShuffleHelper {
+
+ public static void makeShuffleList(@NonNull List listToShuffle, final int current) {
+ if (listToShuffle.isEmpty()) return;
+ if (current >= 0) {
+ Song song = listToShuffle.remove(current);
+ Collections.shuffle(listToShuffle);
+ listToShuffle.add(0, song);
+ } else {
+ Collections.shuffle(listToShuffle);
+ }
+ }
+
+
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.java b/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.java
new file mode 100644
index 000000000..da99cfc45
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2012 Andrew Neal Licensed under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package code.name.monkey.retromusic.helper;
+
+import android.provider.MediaStore;
+
+/**
+ * Holds all of the sort orders for each list type.
+ *
+ * @author Andrew Neal (andrewdneal@gmail.com)
+ */
+public final class SortOrder {
+
+ /**
+ * This class is never instantiated
+ */
+ public SortOrder() {
+ }
+
+ /**
+ * Artist sort order entries.
+ */
+ public interface ArtistSortOrder {
+
+ /* Artist sort order A-Z */
+ String ARTIST_A_Z = MediaStore.Audio.Artists.DEFAULT_SORT_ORDER;
+
+ /* Artist sort order Z-A */
+ String ARTIST_Z_A = ARTIST_A_Z + " DESC";
+
+ /* Artist sort order number of songs */
+ String ARTIST_NUMBER_OF_SONGS = MediaStore.Audio.Artists.NUMBER_OF_TRACKS
+ + " DESC";
+
+ /* Artist sort order number of albums */
+ String ARTIST_NUMBER_OF_ALBUMS = MediaStore.Audio.Artists.NUMBER_OF_ALBUMS
+ + " DESC";
+ }
+
+ /**
+ * Album sort order entries.
+ */
+ public interface AlbumSortOrder {
+
+ /* Album sort order A-Z */
+ String ALBUM_A_Z = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER;
+
+ /* Album sort order Z-A */
+ String ALBUM_Z_A = ALBUM_A_Z + " DESC";
+
+ /* Album sort order songs */
+ String ALBUM_NUMBER_OF_SONGS = MediaStore.Audio.Albums.NUMBER_OF_SONGS
+ + " DESC";
+
+ /* Album sort order artist */
+ String ALBUM_ARTIST = MediaStore.Audio.Artists.DEFAULT_SORT_ORDER
+ + ", " + MediaStore.Audio.Albums.DEFAULT_SORT_ORDER;
+
+ /* Album sort order year */
+ String ALBUM_YEAR = MediaStore.Audio.Media.YEAR + " DESC";
+ }
+
+ /**
+ * Song sort order entries.
+ */
+ public interface SongSortOrder {
+
+ /* Song sort order A-Z */
+ String SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER;
+
+ /* Song sort order Z-A */
+ String SONG_Z_A = SONG_A_Z + " DESC";
+
+ /* Song sort order artist */
+ String SONG_ARTIST = MediaStore.Audio.Artists.DEFAULT_SORT_ORDER;
+
+ /* Song sort order album */
+ String SONG_ALBUM = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER;
+
+ /* Song sort order year */
+ String SONG_YEAR = MediaStore.Audio.Media.YEAR + " DESC";
+
+ /* Song sort order duration */
+ String SONG_DURATION = MediaStore.Audio.Media.DURATION + " DESC";
+
+ /* Song sort order date */
+ String SONG_DATE = MediaStore.Audio.Media.DATE_ADDED + " DESC";
+ }
+
+ /**
+ * Album song sort order entries.
+ */
+ public interface AlbumSongSortOrder {
+
+ /* Album song sort order A-Z */
+ String SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER;
+
+ /* Album song sort order Z-A */
+ String SONG_Z_A = SONG_A_Z + " DESC";
+
+ /* Album song sort order track list */
+ String SONG_TRACK_LIST = MediaStore.Audio.Media.TRACK + ", "
+ + MediaStore.Audio.Media.DEFAULT_SORT_ORDER;
+
+ /* Album song sort order duration */
+ String SONG_DURATION = SongSortOrder.SONG_DURATION;
+ }
+
+ /**
+ * Artist song sort order entries.
+ */
+ public interface ArtistSongSortOrder {
+
+ /* Artist song sort order A-Z */
+ String SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER;
+
+ /* Artist song sort order Z-A */
+ String SONG_Z_A = SONG_A_Z + " DESC";
+
+ /* Artist song sort order album */
+ String SONG_ALBUM = MediaStore.Audio.Media.ALBUM;
+
+ /* Artist song sort order year */
+ String SONG_YEAR = MediaStore.Audio.Media.YEAR + " DESC";
+
+ /* Artist song sort order duration */
+ String SONG_DURATION = MediaStore.Audio.Media.DURATION + " DESC";
+
+ /* Artist song sort order date */
+ String SONG_DATE = MediaStore.Audio.Media.DATE_ADDED + " DESC";
+ }
+
+ /**
+ * Artist album sort order entries.
+ */
+ public interface ArtistAlbumSortOrder {
+
+ /* Artist album sort order A-Z */
+ String ALBUM_A_Z = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER;
+
+ /* Artist album sort order Z-A */
+ String ALBUM_Z_A = ALBUM_A_Z + " DESC";
+
+ /* Artist album sort order year */
+ String ALBUM_YEAR = MediaStore.Audio.Media.YEAR
+ + " DESC";
+
+ /* Artist album sort order year */
+ String ALBUM_YEAR_ASC = MediaStore.Audio.Media.YEAR
+ + " ASC";
+ }
+
+ /**
+ * Genre sort order entries.
+ */
+ public interface GenreSortOrder {
+
+ /* Genre sort order A-Z */
+ String GENRE_A_Z = MediaStore.Audio.Genres.DEFAULT_SORT_ORDER;
+
+ /* Genre sort order Z-A */
+ String ALBUM_Z_A = GENRE_A_Z + " DESC";
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java b/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java
new file mode 100644
index 000000000..3e812e8a9
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java
@@ -0,0 +1,333 @@
+package code.name.monkey.retromusic.helper;
+
+import android.graphics.Bitmap;
+
+import java.util.ArrayList;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Blur using Java code.
+ *
+ * This is a compromise between Gaussian Blur and Box blur
+ * It creates much better looking blurs than Box Blur, but is
+ * 7x faster than my Gaussian Blur implementation.
+ *
+ * I called it Stack Blur because this describes best how this
+ * filter works internally: it creates a kind of moving stack
+ * of colors whilst scanning through the image. Thereby it
+ * just has to add one new block of color to the right side
+ * of the stack and remove the leftmost color. The remaining
+ * colors on the topmost layer of the stack are either added on
+ * or reduced by one, depending on if they are on the right or
+ * on the left side of the stack.
+ *
+ * @author Enrique López Mañas
+ * http://www.neo-tech.es
+ *
+ * Author of the original algorithm: Mario Klingemann
+ *
+ * Based heavily on http://vitiy.info/Code/stackblur.cpp
+ * See http://vitiy.info/stackblur-algorithm-multi-threaded-blur-for-cpp/
+ * @copyright: Enrique López Mañas
+ * @license: Apache License 2.0
+ */
+public class StackBlur {
+
+ static final int EXECUTOR_THREADS = Runtime.getRuntime().availableProcessors();
+ static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(EXECUTOR_THREADS);
+
+ private static final short[] stackblur_mul = {
+ 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512,
+ 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512,
+ 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456,
+ 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512,
+ 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328,
+ 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456,
+ 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335,
+ 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512,
+ 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405,
+ 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328,
+ 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271,
+ 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456,
+ 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388,
+ 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335,
+ 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292,
+ 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259
+ };
+
+ private static final byte[] stackblur_shr = {
+ 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
+ 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
+ 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24
+ };
+
+ public static Bitmap blur(Bitmap original, float radius) {
+ int w = original.getWidth();
+ int h = original.getHeight();
+ int[] currentPixels = new int[w * h];
+ original.getPixels(currentPixels, 0, w, 0, 0, w, h);
+ int cores = EXECUTOR_THREADS;
+
+ ArrayList horizontal = new ArrayList(cores);
+ ArrayList vertical = new ArrayList(cores);
+ for (int i = 0; i < cores; i++) {
+ horizontal.add(new BlurTask(currentPixels, w, h, (int) radius, cores, i, 1));
+ vertical.add(new BlurTask(currentPixels, w, h, (int) radius, cores, i, 2));
+ }
+
+ try {
+ EXECUTOR.invokeAll(horizontal);
+ } catch (InterruptedException e) {
+ return null;
+ }
+
+ try {
+ EXECUTOR.invokeAll(vertical);
+ } catch (InterruptedException e) {
+ return null;
+ }
+
+ return Bitmap.createBitmap(currentPixels, w, h, Bitmap.Config.ARGB_8888);
+ }
+
+ private static void blurIteration(int[] src, int w, int h, int radius, int cores, int core, int step) {
+ int x, y, xp, yp, i;
+ int sp;
+ int stack_start;
+ int stack_i;
+
+ int src_i;
+ int dst_i;
+
+ long sum_r, sum_g, sum_b,
+ sum_in_r, sum_in_g, sum_in_b,
+ sum_out_r, sum_out_g, sum_out_b;
+
+ int wm = w - 1;
+ int hm = h - 1;
+ int div = (radius * 2) + 1;
+ int mul_sum = stackblur_mul[radius];
+ byte shr_sum = stackblur_shr[radius];
+ int[] stack = new int[div];
+
+ if (step == 1) {
+ int minY = core * h / cores;
+ int maxY = (core + 1) * h / cores;
+
+ for (y = minY; y < maxY; y++) {
+ sum_r = sum_g = sum_b =
+ sum_in_r = sum_in_g = sum_in_b =
+ sum_out_r = sum_out_g = sum_out_b = 0;
+
+ src_i = w * y; // start of line (0,y)
+
+ for (i = 0; i <= radius; i++) {
+ stack_i = i;
+ stack[stack_i] = src[src_i];
+ sum_r += ((src[src_i] >>> 16) & 0xff) * (i + 1);
+ sum_g += ((src[src_i] >>> 8) & 0xff) * (i + 1);
+ sum_b += (src[src_i] & 0xff) * (i + 1);
+ sum_out_r += ((src[src_i] >>> 16) & 0xff);
+ sum_out_g += ((src[src_i] >>> 8) & 0xff);
+ sum_out_b += (src[src_i] & 0xff);
+ }
+
+
+ for (i = 1; i <= radius; i++) {
+ if (i <= wm) src_i += 1;
+ stack_i = i + radius;
+ stack[stack_i] = src[src_i];
+ sum_r += ((src[src_i] >>> 16) & 0xff) * (radius + 1 - i);
+ sum_g += ((src[src_i] >>> 8) & 0xff) * (radius + 1 - i);
+ sum_b += (src[src_i] & 0xff) * (radius + 1 - i);
+ sum_in_r += ((src[src_i] >>> 16) & 0xff);
+ sum_in_g += ((src[src_i] >>> 8) & 0xff);
+ sum_in_b += (src[src_i] & 0xff);
+ }
+
+
+ sp = radius;
+ xp = radius;
+ if (xp > wm) xp = wm;
+ src_i = xp + y * w; // img.pix_ptr(xp, y);
+ dst_i = y * w; // img.pix_ptr(0, y);
+ for (x = 0; x < w; x++) {
+ src[dst_i] = (int)
+ ((src[dst_i] & 0xFFFFFFFF) |
+ ((((sum_r * mul_sum) >>> shr_sum) & 0xff) << 16) |
+ ((((sum_g * mul_sum) >>> shr_sum) & 0xff) << 8) |
+ ((((sum_b * mul_sum) >>> shr_sum) & 0xff)));
+ dst_i += 1;
+
+ sum_r -= sum_out_r;
+ sum_g -= sum_out_g;
+ sum_b -= sum_out_b;
+
+ stack_start = sp + div - radius;
+ if (stack_start >= div) stack_start -= div;
+ stack_i = stack_start;
+
+ sum_out_r -= ((stack[stack_i] >>> 16) & 0xff);
+ sum_out_g -= ((stack[stack_i] >>> 8) & 0xff);
+ sum_out_b -= (stack[stack_i] & 0xff);
+
+ if (xp < wm) {
+ src_i += 1;
+ ++xp;
+ }
+
+ stack[stack_i] = src[src_i];
+
+ sum_in_r += ((src[src_i] >>> 16) & 0xff);
+ sum_in_g += ((src[src_i] >>> 8) & 0xff);
+ sum_in_b += (src[src_i] & 0xff);
+ sum_r += sum_in_r;
+ sum_g += sum_in_g;
+ sum_b += sum_in_b;
+
+ ++sp;
+ if (sp >= div) sp = 0;
+ stack_i = sp;
+
+ sum_out_r += ((stack[stack_i] >>> 16) & 0xff);
+ sum_out_g += ((stack[stack_i] >>> 8) & 0xff);
+ sum_out_b += (stack[stack_i] & 0xff);
+ sum_in_r -= ((stack[stack_i] >>> 16) & 0xff);
+ sum_in_g -= ((stack[stack_i] >>> 8) & 0xff);
+ sum_in_b -= (stack[stack_i] & 0xff);
+ }
+
+ }
+ }
+
+ // step 2
+ else if (step == 2) {
+ int minX = core * w / cores;
+ int maxX = (core + 1) * w / cores;
+
+ for (x = minX; x < maxX; x++) {
+ sum_r = sum_g = sum_b =
+ sum_in_r = sum_in_g = sum_in_b =
+ sum_out_r = sum_out_g = sum_out_b = 0;
+
+ src_i = x; // x,0
+ for (i = 0; i <= radius; i++) {
+ stack_i = i;
+ stack[stack_i] = src[src_i];
+ sum_r += ((src[src_i] >>> 16) & 0xff) * (i + 1);
+ sum_g += ((src[src_i] >>> 8) & 0xff) * (i + 1);
+ sum_b += (src[src_i] & 0xff) * (i + 1);
+ sum_out_r += ((src[src_i] >>> 16) & 0xff);
+ sum_out_g += ((src[src_i] >>> 8) & 0xff);
+ sum_out_b += (src[src_i] & 0xff);
+ }
+ for (i = 1; i <= radius; i++) {
+ if (i <= hm) src_i += w; // +stride
+
+ stack_i = i + radius;
+ stack[stack_i] = src[src_i];
+ sum_r += ((src[src_i] >>> 16) & 0xff) * (radius + 1 - i);
+ sum_g += ((src[src_i] >>> 8) & 0xff) * (radius + 1 - i);
+ sum_b += (src[src_i] & 0xff) * (radius + 1 - i);
+ sum_in_r += ((src[src_i] >>> 16) & 0xff);
+ sum_in_g += ((src[src_i] >>> 8) & 0xff);
+ sum_in_b += (src[src_i] & 0xff);
+ }
+
+ sp = radius;
+ yp = radius;
+ if (yp > hm) yp = hm;
+ src_i = x + yp * w; // img.pix_ptr(x, yp);
+ dst_i = x; // img.pix_ptr(x, 0);
+ for (y = 0; y < h; y++) {
+ src[dst_i] = (int)
+ ((src[dst_i] & 0xFFFFFFFF) |
+ ((((sum_r * mul_sum) >>> shr_sum) & 0xff) << 16) |
+ ((((sum_g * mul_sum) >>> shr_sum) & 0xff) << 8) |
+ ((((sum_b * mul_sum) >>> shr_sum) & 0xff)));
+ dst_i += w;
+
+ sum_r -= sum_out_r;
+ sum_g -= sum_out_g;
+ sum_b -= sum_out_b;
+
+ stack_start = sp + div - radius;
+ if (stack_start >= div) stack_start -= div;
+ stack_i = stack_start;
+
+ sum_out_r -= ((stack[stack_i] >>> 16) & 0xff);
+ sum_out_g -= ((stack[stack_i] >>> 8) & 0xff);
+ sum_out_b -= (stack[stack_i] & 0xff);
+
+ if (yp < hm) {
+ src_i += w; // stride
+ ++yp;
+ }
+
+ stack[stack_i] = src[src_i];
+
+ sum_in_r += ((src[src_i] >>> 16) & 0xff);
+ sum_in_g += ((src[src_i] >>> 8) & 0xff);
+ sum_in_b += (src[src_i] & 0xff);
+ sum_r += sum_in_r;
+ sum_g += sum_in_g;
+ sum_b += sum_in_b;
+
+ ++sp;
+ if (sp >= div) sp = 0;
+ stack_i = sp;
+
+ sum_out_r += ((stack[stack_i] >>> 16) & 0xff);
+ sum_out_g += ((stack[stack_i] >>> 8) & 0xff);
+ sum_out_b += (stack[stack_i] & 0xff);
+ sum_in_r -= ((stack[stack_i] >>> 16) & 0xff);
+ sum_in_g -= ((stack[stack_i] >>> 8) & 0xff);
+ sum_in_b -= (stack[stack_i] & 0xff);
+ }
+ }
+ }
+
+ }
+
+ private static class BlurTask implements Callable {
+ private final int[] _src;
+ private final int _w;
+ private final int _h;
+ private final int _radius;
+ private final int _totalCores;
+ private final int _coreIndex;
+ private final int _round;
+
+ public BlurTask(int[] src, int w, int h, int radius, int totalCores, int coreIndex, int round) {
+ _src = src;
+ _w = w;
+ _h = h;
+ _radius = radius;
+ _totalCores = totalCores;
+ _coreIndex = coreIndex;
+ _round = round;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ blurIteration(_src, _w, _h, _radius, _totalCores, _coreIndex, _round);
+ return null;
+ }
+
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/StopWatch.java b/app/src/main/java/code/name/monkey/retromusic/helper/StopWatch.java
new file mode 100644
index 000000000..896f68ac9
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/StopWatch.java
@@ -0,0 +1,82 @@
+package code.name.monkey.retromusic.helper;
+
+/**
+ * Simple thread safe stop watch.
+ *
+ * @author Karim Abou Zeid (kabouzeid)
+ */
+public class StopWatch {
+
+ /**
+ * The time the stop watch was last started.
+ */
+ private long startTime;
+
+ /**
+ * The time elapsed before the current {@link #startTime}.
+ */
+ private long previousElapsedTime;
+
+ /**
+ * Whether the stop watch is currently running or not.
+ */
+ private boolean isRunning;
+
+ /**
+ * Starts or continues the stop watch.
+ *
+ * @see #pause()
+ * @see #reset()
+ */
+ public void start() {
+ synchronized (this) {
+ startTime = System.currentTimeMillis();
+ isRunning = true;
+ }
+ }
+
+ /**
+ * Pauses the stop watch. It can be continued later from {@link #start()}.
+ *
+ * @see #start()
+ * @see #reset()
+ */
+ public void pause() {
+ synchronized (this) {
+ previousElapsedTime += System.currentTimeMillis() - startTime;
+ isRunning = false;
+ }
+ }
+
+ /**
+ * Stops and resets the stop watch to zero milliseconds.
+ *
+ * @see #start()
+ * @see #pause()
+ */
+ public void reset() {
+ synchronized (this) {
+ startTime = 0;
+ previousElapsedTime = 0;
+ isRunning = false;
+ }
+ }
+
+ /**
+ * @return the total elapsed time in milliseconds
+ */
+ public final long getElapsedTime() {
+ synchronized (this) {
+ long currentElapsedTime = 0;
+ if (isRunning) {
+ currentElapsedTime = System.currentTimeMillis() - startTime;
+ }
+ return previousElapsedTime + currentElapsedTime;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%d millis", getElapsedTime());
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.java
new file mode 100644
index 000000000..0c71de2d0
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.java
@@ -0,0 +1,50 @@
+package code.name.monkey.retromusic.helper.menu;
+
+import android.app.Activity;
+import android.support.annotation.NonNull;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+
+import java.util.ArrayList;
+
+import code.name.monkey.retromusic.loaders.GenreLoader;
+import code.name.monkey.retromusic.model.Genre;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog;
+import code.name.monkey.retromusic.helper.MusicPlayerRemote;
+
+/**
+ * @author Hemanth S (h4h13).
+ */
+
+public class GenreMenuHelper {
+ public static boolean handleMenuClick(@NonNull AppCompatActivity activity,
+ @NonNull Genre genre,
+ @NonNull MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_play:
+ MusicPlayerRemote.openQueue(getGenreSongs(activity, genre), 0, true);
+ return true;
+ case R.id.action_play_next:
+ MusicPlayerRemote.playNext(getGenreSongs(activity, genre));
+ return true;
+ case R.id.action_add_to_playlist:
+ AddToPlaylistDialog.create(getGenreSongs(activity, genre))
+ .show(activity.getSupportFragmentManager(), "ADD_PLAYLIST");
+ return true;
+ case R.id.action_add_to_current_playing:
+ MusicPlayerRemote.enqueue(getGenreSongs(activity, genre));
+ return true;
+ }
+ return false;
+ }
+
+ @NonNull
+ private static ArrayList getGenreSongs(@NonNull Activity activity,
+ @NonNull Genre genre) {
+ ArrayList songs;
+ songs = GenreLoader.getSongs(activity, genre.id).blockingFirst();
+ return songs;
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.java
new file mode 100644
index 000000000..a6944221f
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.java
@@ -0,0 +1,91 @@
+package code.name.monkey.retromusic.helper.menu;
+
+import android.app.Activity;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.RetroApplication;
+import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog;
+import code.name.monkey.retromusic.dialogs.DeletePlaylistDialog;
+import code.name.monkey.retromusic.dialogs.RenamePlaylistDialog;
+import code.name.monkey.retromusic.helper.MusicPlayerRemote;
+import code.name.monkey.retromusic.loaders.PlaylistSongsLoader;
+import code.name.monkey.retromusic.misc.WeakContextAsyncTask;
+import code.name.monkey.retromusic.model.AbsCustomPlaylist;
+import code.name.monkey.retromusic.model.Playlist;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.util.PlaylistsUtil;
+
+
+public class PlaylistMenuHelper {
+
+ public static boolean handleMenuClick(@NonNull AppCompatActivity activity,
+ @NonNull final Playlist playlist, @NonNull MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_play:
+ MusicPlayerRemote.openQueue(getPlaylistSongs(activity, playlist), 9, true);
+ return true;
+ case R.id.action_play_next:
+ MusicPlayerRemote.playNext(getPlaylistSongs(activity, playlist));
+ return true;
+ case R.id.action_add_to_playlist:
+ AddToPlaylistDialog.create(getPlaylistSongs(activity, playlist))
+ .show(activity.getSupportFragmentManager(), "ADD_PLAYLIST");
+ return true;
+ case R.id.action_add_to_current_playing:
+ MusicPlayerRemote.enqueue(getPlaylistSongs(activity, playlist));
+ return true;
+ case R.id.action_rename_playlist:
+ RenamePlaylistDialog.create(playlist.id)
+ .show(activity.getSupportFragmentManager(), "RENAME_PLAYLIST");
+ return true;
+ case R.id.action_delete_playlist:
+ DeletePlaylistDialog.create(playlist)
+ .show(activity.getSupportFragmentManager(), "DELETE_PLAYLIST");
+ return true;
+ case R.id.action_save_playlist:
+ new SavePlaylistAsyncTask(activity).execute(playlist);
+ return true;
+ }
+ return false;
+ }
+
+ @NonNull
+ private static ArrayList getPlaylistSongs(@NonNull Activity activity,
+ @NonNull Playlist playlist) {
+ ArrayList songs;
+ if (playlist instanceof AbsCustomPlaylist) {
+ songs = ((AbsCustomPlaylist) playlist).getSongs(activity).blockingFirst();
+ } else {
+ songs = PlaylistSongsLoader.getPlaylistSongList(activity, playlist).blockingFirst();
+ }
+ return songs;
+ }
+
+ private static class SavePlaylistAsyncTask extends WeakContextAsyncTask {
+ SavePlaylistAsyncTask(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected String doInBackground(Playlist... params) {
+ return String.format(RetroApplication.getInstance().getApplicationContext().getString(R.string
+ .saved_playlist_to), PlaylistsUtil.savePlaylist(RetroApplication.getInstance().getApplicationContext(), params[0]).blockingFirst());
+ }
+
+ @Override
+ protected void onPostExecute(String string) {
+ super.onPostExecute(string);
+ Context context = getContext();
+ if (context != null) {
+ Toast.makeText(context, string, Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.java
new file mode 100644
index 000000000..5d1828867
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.java
@@ -0,0 +1,93 @@
+package code.name.monkey.retromusic.helper.menu;
+
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.v4.app.FragmentActivity;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.PopupMenu;
+
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog;
+import code.name.monkey.retromusic.dialogs.DeleteSongsDialog;
+import code.name.monkey.retromusic.dialogs.SongDetailDialog;
+import code.name.monkey.retromusic.helper.MusicPlayerRemote;
+import code.name.monkey.retromusic.interfaces.PaletteColorHolder;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.ui.activities.tageditor.AbsTagEditorActivity;
+import code.name.monkey.retromusic.ui.activities.tageditor.SongTagEditorActivity;
+import code.name.monkey.retromusic.util.MusicUtil;
+import code.name.monkey.retromusic.util.NavigationUtil;
+
+
+public class SongMenuHelper {
+ public static final int MENU_RES = R.menu.menu_item_song;
+
+ public static boolean handleMenuClick(@NonNull FragmentActivity activity, @NonNull Song song, int menuItemId) {
+ switch (menuItemId) {
+ case R.id.action_set_as_ringtone:
+ MusicUtil.setRingtone(activity, song.id);
+ return true;
+ case R.id.action_share:
+ activity.startActivity(Intent.createChooser(MusicUtil.createShareSongFileIntent(song, activity), null));
+ return true;
+ case R.id.action_delete_from_device:
+ DeleteSongsDialog.create(song).show(activity.getSupportFragmentManager(), "DELETE_SONGS");
+ return true;
+ case R.id.action_add_to_playlist:
+ AddToPlaylistDialog.create(song).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST");
+ return true;
+ case R.id.action_play_next:
+ MusicPlayerRemote.playNext(song);
+ return true;
+ case R.id.action_add_to_current_playing:
+ MusicPlayerRemote.enqueue(song);
+ return true;
+ case R.id.action_tag_editor:
+ Intent tagEditorIntent = new Intent(activity, SongTagEditorActivity.class);
+ tagEditorIntent.putExtra(AbsTagEditorActivity.EXTRA_ID, song.id);
+ if (activity instanceof PaletteColorHolder)
+ tagEditorIntent.putExtra(AbsTagEditorActivity.EXTRA_PALETTE, ((PaletteColorHolder) activity).getPaletteColor());
+ activity.startActivity(tagEditorIntent);
+ return true;
+ case R.id.action_details:
+ SongDetailDialog.create(song).show(activity.getSupportFragmentManager(), "SONG_DETAILS");
+ return true;
+ case R.id.action_go_to_album:
+ NavigationUtil.goToAlbum(activity, song.albumId);
+ return true;
+ case R.id.action_go_to_artist:
+ NavigationUtil.goToArtist(activity, song.artistId);
+ return true;
+ }
+ return false;
+ }
+
+ public static abstract class OnClickSongMenu implements View.OnClickListener, PopupMenu.OnMenuItemClickListener {
+ private AppCompatActivity activity;
+
+ protected OnClickSongMenu(@NonNull AppCompatActivity activity) {
+ this.activity = activity;
+ }
+
+ public int getMenuRes() {
+ return MENU_RES;
+ }
+
+ @Override
+ public void onClick(View v) {
+ PopupMenu popupMenu = new PopupMenu(activity, v);
+ popupMenu.inflate(getMenuRes());
+ popupMenu.setOnMenuItemClickListener(this);
+ popupMenu.show();
+ }
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ return handleMenuClick(activity, getSong(), item.getItemId());
+ }
+
+ public abstract Song getSong();
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.java
new file mode 100644
index 000000000..cb3052439
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.java
@@ -0,0 +1,35 @@
+package code.name.monkey.retromusic.helper.menu;
+
+import android.support.annotation.NonNull;
+import android.support.v4.app.FragmentActivity;
+
+import code.name.monkey.retromusic.model.Song;
+
+import java.util.ArrayList;
+
+import code.name.monkey.retromusic.R;
+import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog;
+import code.name.monkey.retromusic.dialogs.DeleteSongsDialog;
+import code.name.monkey.retromusic.helper.MusicPlayerRemote;
+
+
+
+public class SongsMenuHelper {
+ public static boolean handleMenuClick(@NonNull FragmentActivity activity, @NonNull ArrayList songs, int menuItemId) {
+ switch (menuItemId) {
+ case R.id.action_play_next:
+ MusicPlayerRemote.playNext(songs);
+ return true;
+ case R.id.action_add_to_current_playing:
+ MusicPlayerRemote.enqueue(songs);
+ return true;
+ case R.id.action_add_to_playlist:
+ AddToPlaylistDialog.create(songs).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST");
+ return true;
+ case R.id.action_delete_from_device:
+ DeleteSongsDialog.create(songs).show(activity.getSupportFragmentManager(), "DELETE_SONGS");
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/CabHolder.java b/app/src/main/java/code/name/monkey/retromusic/interfaces/CabHolder.java
new file mode 100644
index 000000000..76ba6f35f
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/CabHolder.java
@@ -0,0 +1,12 @@
+package code.name.monkey.retromusic.interfaces;
+
+import android.support.annotation.NonNull;
+
+import com.afollestad.materialcab.MaterialCab;
+
+
+public interface CabHolder {
+
+ @NonNull
+ MaterialCab openCab(final int menuRes, final MaterialCab.Callback callback);
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/EqualizerInterface.java b/app/src/main/java/code/name/monkey/retromusic/interfaces/EqualizerInterface.java
new file mode 100644
index 000000000..881434181
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/EqualizerInterface.java
@@ -0,0 +1,49 @@
+package code.name.monkey.retromusic.interfaces;
+
+import android.media.audiofx.BassBoost;
+import android.media.audiofx.Equalizer;
+import android.media.audiofx.Virtualizer;
+
+/**
+ * @author Hemanth S (h4h13).
+ */
+
+public interface EqualizerInterface {
+ int getBandLevelLow();
+
+ int getBandLevelHigh();
+
+ int getNumberOfBands();
+
+ int getCenterFreq(int band);
+
+ int getBandLevel(int band);
+
+ void setBandLevel(int band, int level);
+
+ boolean isBassBoostEnabled();
+
+ void setBassBoostEnabled(boolean isEnabled);
+
+ int getBassBoostStrength();
+
+ void setBassBoostStrength(int strength);
+
+ boolean isVirtualizerEnabled();
+
+ void setVirtualizerEnabled(boolean isEnabled);
+
+ int getVirtualizerStrength();
+
+ void setVirtualizerStrength(int strength);
+
+ boolean isRunning();
+
+ Equalizer getEqualizer();
+
+ BassBoost getBassBoost();
+
+ Virtualizer getVirtualizer();
+
+
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/LoaderIds.java b/app/src/main/java/code/name/monkey/retromusic/interfaces/LoaderIds.java
new file mode 100644
index 000000000..e7792818d
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/LoaderIds.java
@@ -0,0 +1,6 @@
+package code.name.monkey.retromusic.interfaces;
+
+
+public interface LoaderIds {
+ int FOLDERS_FRAGMENT = 5;
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/MainActivityFragmentCallbacks.java b/app/src/main/java/code/name/monkey/retromusic/interfaces/MainActivityFragmentCallbacks.java
new file mode 100644
index 000000000..fd165510f
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/MainActivityFragmentCallbacks.java
@@ -0,0 +1,13 @@
+package code.name.monkey.retromusic.interfaces;
+
+import android.support.v4.app.Fragment;
+
+/**
+ * Created by hemanths on 14/08/17.
+ */
+
+public interface MainActivityFragmentCallbacks {
+ boolean handleBackPress();
+
+ //void selectedFragment(Fragment fragment);
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/MusicServiceEventListener.java b/app/src/main/java/code/name/monkey/retromusic/interfaces/MusicServiceEventListener.java
new file mode 100644
index 000000000..ea6090193
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/MusicServiceEventListener.java
@@ -0,0 +1,20 @@
+package code.name.monkey.retromusic.interfaces;
+
+
+public interface MusicServiceEventListener {
+ void onServiceConnected();
+
+ void onServiceDisconnected();
+
+ void onQueueChanged();
+
+ void onPlayingMetaChanged();
+
+ void onPlayStateChanged();
+
+ void onRepeatModeChanged();
+
+ void onShuffleModeChanged();
+
+ void onMediaStoreChanged();
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/PaletteColorHolder.java b/app/src/main/java/code/name/monkey/retromusic/interfaces/PaletteColorHolder.java
new file mode 100644
index 000000000..406ddf603
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/PaletteColorHolder.java
@@ -0,0 +1,12 @@
+package code.name.monkey.retromusic.interfaces;
+
+import android.support.annotation.ColorInt;
+
+/**
+ * @author Aidan Follestad (afollestad)
+ */
+public interface PaletteColorHolder {
+
+ @ColorInt
+ int getPaletteColor();
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/AlbumLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/AlbumLoader.java
new file mode 100644
index 000000000..fb12d1dc1
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/loaders/AlbumLoader.java
@@ -0,0 +1,104 @@
+package code.name.monkey.retromusic.loaders;
+
+import android.content.Context;
+import android.provider.MediaStore.Audio.AudioColumns;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import code.name.monkey.retromusic.model.Album;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.util.PreferenceUtil;
+import io.reactivex.Observable;
+import java.util.ArrayList;
+
+/**
+ * Created by hemanths on 11/08/17.
+ */
+
+public class AlbumLoader {
+
+ public static Observable> getAllAlbums(@NonNull Context context) {
+ Observable> songs = SongLoader.getSongs(SongLoader.makeSongCursor(
+ context,
+ null,
+ null,
+ getSongLoaderSortOrder(context))
+ );
+
+ return splitIntoAlbums(songs);
+ }
+
+ @NonNull
+ public static Observable> getAlbums(@NonNull final Context context,
+ String query) {
+ Observable> songs = SongLoader.getSongs(SongLoader.makeSongCursor(
+ context,
+ AudioColumns.ALBUM + " LIKE ?",
+ new String[]{"%" + query + "%"},
+ getSongLoaderSortOrder(context))
+ );
+ return splitIntoAlbums(songs);
+ }
+
+ @NonNull
+ public static Observable getAlbum(@NonNull final Context context, int albumId) {
+ return Observable.create(e -> {
+ Observable> songs = SongLoader.getSongs(SongLoader
+ .makeSongCursor(context, AudioColumns.ALBUM_ID + "=?",
+ new String[]{String.valueOf(albumId)}, getSongLoaderSortOrder(context)));
+ songs.subscribe(songs1 -> {
+ e.onNext(new Album(songs1));
+ e.onComplete();
+ });
+ });
+ }
+
+ @NonNull
+ public static Observable> splitIntoAlbums(
+ @Nullable final Observable> songs) {
+ return Observable.create(e -> {
+ ArrayList albums = new ArrayList<>();
+ if (songs != null) {
+ songs.subscribe(songs1 -> {
+ for (Song song : songs1) {
+ getOrCreateAlbum(albums, song.albumId).subscribe(album -> album.songs.add(song));
+ }
+ });
+ }
+ e.onNext(albums);
+ e.onComplete();
+ });
+ }
+
+ @NonNull
+ public static ArrayList splitIntoAlbums(@Nullable final ArrayList songs) {
+ ArrayList albums = new ArrayList<>();
+ if (songs != null) {
+ for (Song song : songs) {
+ getOrCreateAlbum(albums, song.albumId).subscribe(album -> album.songs.add(song));
+ }
+ }
+ return albums;
+ }
+
+ private static Observable getOrCreateAlbum(ArrayList albums, int albumId) {
+ return Observable.create(e -> {
+ for (Album album : albums) {
+ if (!album.songs.isEmpty() && album.songs.get(0).albumId == albumId) {
+ e.onNext(album);
+ e.onComplete();
+ return;
+ }
+ }
+ Album album = new Album();
+ albums.add(album);
+ e.onNext(album);
+ e.onComplete();
+ });
+ }
+
+ public static String getSongLoaderSortOrder(Context context) {
+ return PreferenceUtil.getInstance(context).getAlbumSortOrder() + ", " +
+ //PreferenceUtil.getInstance(context).getAlbumSongSortOrder() + "," +
+ PreferenceUtil.getInstance(context).getAlbumDetailSongSortOrder();
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/ArtistLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/ArtistLoader.java
new file mode 100644
index 000000000..500c98a23
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/loaders/ArtistLoader.java
@@ -0,0 +1,119 @@
+package code.name.monkey.retromusic.loaders;
+
+import android.content.Context;
+import android.provider.MediaStore.Audio.AudioColumns;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.ArrayList;
+
+import code.name.monkey.retromusic.model.Album;
+import code.name.monkey.retromusic.model.Artist;
+import code.name.monkey.retromusic.util.PreferenceUtil;
+import io.reactivex.Observable;
+
+public class ArtistLoader {
+ public static String getSongLoaderSortOrder(Context context) {
+ return PreferenceUtil.getInstance(context).getArtistSortOrder() + ", " +
+ PreferenceUtil.getInstance(context).getArtistAlbumSortOrder() + ", " +
+ PreferenceUtil.getInstance(context).getAlbumDetailSongSortOrder() + ", " +
+ PreferenceUtil.getInstance(context).getArtistDetailSongSortOrder();
+ }
+
+ @NonNull
+ public static Observable getArtist(@NonNull final Context context, int artistId) {
+ return Observable.create(e -> SongLoader.getSongs(SongLoader.makeSongCursor(
+ context,
+ AudioColumns.ARTIST_ID + "=?",
+ new String[]{String.valueOf(artistId)},
+ getSongLoaderSortOrder(context)))
+ .subscribe(songs -> {
+ Artist artist = new Artist(AlbumLoader.splitIntoAlbums(songs));
+ e.onNext(artist);
+ e.onComplete();
+ }));
+ }
+
+ @NonNull
+ public static Observable> getAllArtists(@NonNull final Context context) {
+ return Observable.create(e -> SongLoader
+ .getSongs(SongLoader.makeSongCursor(
+ context,
+ null,
+ null,
+ getSongLoaderSortOrder(context))
+ ).subscribe(songs -> {
+ e.onNext(splitIntoArtists(AlbumLoader.splitIntoAlbums(songs)));
+ e.onComplete();
+ }));
+
+ }
+
+ @NonNull
+ public static Observable> getArtists(@NonNull final Context context, String query) {
+ return Observable.create(e -> SongLoader.getSongs(SongLoader.makeSongCursor(
+ context,
+ AudioColumns.ARTIST + " LIKE ?",
+ new String[]{"%" + query + "%"},
+ getSongLoaderSortOrder(context))
+ ).subscribe(songs -> {
+ e.onNext(splitIntoArtists(AlbumLoader.splitIntoAlbums(songs)));
+ e.onComplete();
+ }));
+ }
+
+ @NonNull
+ public static ArrayList splitIntoArtists(@Nullable final ArrayList albums) {
+ ArrayList artists = new ArrayList<>();
+ if (albums != null) {
+ for (Album album : albums) {
+ getOrCreateArtist(artists, album.getArtistId()).albums.add(album);
+ }
+ }
+ return artists;
+ }
+
+ private static Artist getOrCreateArtist(ArrayList artists, int artistId) {
+ for (Artist artist : artists) {
+ if (!artist.albums.isEmpty() && !artist.albums.get(0).songs.isEmpty() && artist.albums.get(0).songs.get(0).artistId == artistId) {
+ return artist;
+ }
+ }
+ Artist album = new Artist();
+ artists.add(album);
+ return album;
+ }
+
+ public static Observable> splitIntoArtists(Observable> albums) {
+ return Observable.create(e -> {
+ ArrayList artists = new ArrayList<>();
+ albums.subscribe(localAlbums -> {
+ if (localAlbums != null) {
+ for (Album album : localAlbums) {
+ getOrCreateArtist(artists, album.getArtistId()).albums.add(album);
+ }
+ }
+ e.onNext(artists);
+ e.onComplete();
+ });
+ });
+ }
+
+ /* public static Observable> getAllArtists(Context context) {
+ return getArtistsForCursor(makeArtistCursor(context, null, null));
+ }
+
+ public static Observable getArtist(Context context, long id) {
+ return getArtist(makeArtistCursor(context, "_id=?", new String[]{String.valueOf(id)}));
+ }
+
+ public static Observable> getArtists(Context context, String paramString) {
+ return getArtistsForCursor(makeArtistCursor(context, "artist LIKE ?", new String[]{"%" + paramString + "%"}));
+ }
+
+ private static Cursor makeArtistCursor(Context context, String selection, String[] paramArrayOfString) {
+ final String artistSortOrder = PreferenceUtil.getInstance(context).getArtistSortOrder();
+ Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, new String[]{"_id", "artist", "number_of_albums", "number_of_tracks"}, selection, paramArrayOfString, artistSortOrder);
+ return cursor;
+ }*/
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/ArtistSongLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/ArtistSongLoader.java
new file mode 100644
index 000000000..e4344ee76
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/loaders/ArtistSongLoader.java
@@ -0,0 +1,37 @@
+package code.name.monkey.retromusic.loaders;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+
+import code.name.monkey.retromusic.model.Song;
+
+import java.util.ArrayList;
+
+import code.name.monkey.retromusic.util.PreferenceUtil;
+import io.reactivex.Observable;
+
+
+public class ArtistSongLoader extends SongLoader {
+
+ @NonNull
+ public static Observable> getArtistSongList(@NonNull final Context context, final int artistId) {
+ return getSongs(makeArtistSongCursor(context, artistId));
+ }
+
+ public static Cursor makeArtistSongCursor(@NonNull final Context context, final int artistId) {
+ try {
+ return makeSongCursor(
+ context,
+ MediaStore.Audio.AudioColumns.ARTIST_ID + "=?",
+ new String[]{
+ String.valueOf(artistId)
+ },
+ PreferenceUtil.getInstance(context).getArtistSongSortOrder()
+ );
+ } catch (SecurityException e) {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/GenreLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/GenreLoader.java
new file mode 100644
index 000000000..5ca28f20b
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/loaders/GenreLoader.java
@@ -0,0 +1,133 @@
+package code.name.monkey.retromusic.loaders;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.provider.MediaStore.Audio.Genres;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.ArrayList;
+
+import code.name.monkey.retromusic.model.Genre;
+import code.name.monkey.retromusic.model.Song;
+import code.name.monkey.retromusic.util.PreferenceUtil;
+import io.reactivex.Observable;
+
+public class GenreLoader {
+
+ @NonNull
+ public static Observable> getAllGenres(@NonNull final Context context) {
+ return getGenresFromCursor(context, makeGenreCursor(context));
+ }
+
+ @NonNull
+ public static Observable> getSongs(@NonNull final Context context, final int genreId) {
+ // The genres table only stores songs that have a genre specified,
+ // so we need to get songs without a genre a different way.
+ if (genreId == -1) {
+ return getSongsWithNoGenre(context);
+ }
+
+ return SongLoader.getSongs(makeGenreSongCursor(context, genreId));
+ }
+
+ @NonNull
+ private static Genre getGenreFromCursor(@NonNull Context context, @NonNull final Cursor cursor) {
+ final int id = cursor.getInt(0);
+ final String name = cursor.getString(1);
+ final int songCount = getSongs(context, id).blockingFirst().size();
+ return new Genre(id, name, songCount);
+
+ }
+
+ @NonNull
+ private static Observable> getSongsWithNoGenre(@NonNull final Context context) {
+ String selection = BaseColumns._ID + " NOT IN " +
+ "(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)";
+ return SongLoader.getSongs(SongLoader.makeSongCursor(context, selection, null));
+ }
+
+ private static boolean hasSongsWithNoGenre(@NonNull final Context context) {
+ final Cursor allSongsCursor = SongLoader.makeSongCursor(context, null, null);
+ final Cursor allSongsWithGenreCursor = makeAllSongsWithGenreCursor(context);
+
+ if (allSongsCursor == null || allSongsWithGenreCursor == null) {
+ return false;
+ }
+
+ final boolean hasSongsWithNoGenre = allSongsCursor.getCount() > allSongsWithGenreCursor.getCount();
+ allSongsCursor.close();
+ allSongsWithGenreCursor.close();
+ return hasSongsWithNoGenre;
+ }
+
+ @Nullable
+ private static Cursor makeAllSongsWithGenreCursor(@NonNull final Context context) {
+ try {
+ return context.getContentResolver().query(
+ Uri.parse("content://media/external/audio/genres/all/members"),
+ new String[]{Genres.Members.AUDIO_ID}, null, null, null);
+ } catch (SecurityException e) {
+ return null;
+ }
+ }
+
+ @Nullable
+ private static Cursor makeGenreSongCursor(@NonNull final Context context, int genreId) {
+ try {
+ return context.getContentResolver().query(
+ Genres.Members.getContentUri("external", genreId),
+ SongLoader.BASE_PROJECTION, SongLoader.BASE_SELECTION, null, PreferenceUtil.getInstance(context).getSongSortOrder());
+ } catch (SecurityException e) {
+ return null;
+ }
+ }
+
+ @NonNull
+ private static Observable> getGenresFromCursor(@NonNull final Context context, @Nullable final Cursor cursor) {
+ return Observable.create(e -> {
+ final ArrayList genres = new ArrayList<>();
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ do {
+ Genre genre = getGenreFromCursor(context, cursor);
+ if (genre.songCount > 0) {
+ genres.add(genre);
+ } else {
+ // try to remove the empty genre from the media store
+ try {
+ context.getContentResolver().delete(Genres.EXTERNAL_CONTENT_URI, Genres._ID + " == " + genre.id, null);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ // nothing we can do then
+ }
+ }
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ }
+ e.onNext(genres);
+ e.onComplete();
+ });
+ }
+
+
+ @Nullable
+ private static Cursor makeGenreCursor(@NonNull final Context context) {
+ final String[] projection = new String[]{
+ Genres._ID,
+ Genres.NAME
+ };
+
+ try {
+ return context.getContentResolver().query(
+ Genres.EXTERNAL_CONTENT_URI,
+ projection, null, null, PreferenceUtil.getInstance(context).getGenreSortOrder());
+ } catch (SecurityException e) {
+ return null;
+ }
+ }
+
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/GenreSongsLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/GenreSongsLoader.java
new file mode 100644
index 000000000..11422e4d0
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/loaders/GenreSongsLoader.java
@@ -0,0 +1,78 @@
+package code.name.monkey.retromusic.loaders;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.AudioColumns;
+import android.support.annotation.NonNull;
+
+import code.name.monkey.retromusic.model.Song;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.reactivex.Observable;
+
+/**
+ * @author Hemanth S (h4h13).
+ */
+
+public class GenreSongsLoader {
+
+ public static Observable> getGenreSongsList(@NonNull Context context, @NonNull int genreId) {
+ return Observable.create(e -> {
+ ArrayList list = new ArrayList<>();
+ Cursor cursor = makeGenreSongCursor(context, genreId);
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ list.add(getGenreSongFromCursorImpl(cursor));
+ } while (cursor.moveToNext());
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ e.onNext((ArrayList) (List) list);
+ e.onComplete();
+ });
+ }
+
+ @NonNull
+ private static Song getGenreSongFromCursorImpl(@NonNull Cursor cursor) {
+ final int id = cursor.getInt(0);
+ final String title = cursor.getString(1);
+ final int trackNumber = cursor.getInt(2);
+ final int year = cursor.getInt(3);
+ final long duration = cursor.getLong(4);
+ final String data = cursor.getString(5);
+ final int dateModified = cursor.getInt(6);
+ final int albumId = cursor.getInt(7);
+ final String albumName = cursor.getString(8);
+ final int artistId = cursor.getInt(9);
+ final String artistName = cursor.getString(10);
+
+ return new Song(id, title, trackNumber, year, duration, data, dateModified, albumId, albumName, artistId, artistName);
+ }
+
+ private static Cursor makeGenreSongCursor(Context context, long genreId) {
+ try {
+ return context.getContentResolver().query(
+ MediaStore.Audio.Genres.Members.getContentUri("external", genreId),
+ new String[]{
+ MediaStore.Audio.Playlists.Members.AUDIO_ID,// 0
+ AudioColumns.TITLE,// 1
+ AudioColumns.TRACK,// 2
+ AudioColumns.YEAR,// 3
+ AudioColumns.DURATION,// 4
+ AudioColumns.DATA,// 5
+ AudioColumns.DATE_MODIFIED,// 6
+ AudioColumns.ALBUM_ID,// 7
+ AudioColumns.ALBUM,// 8
+ AudioColumns.ARTIST_ID,// 9
+ AudioColumns.ARTIST,// 10
+ }, SongLoader.BASE_SELECTION, null,
+ MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER);
+ } catch (SecurityException e) {
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/HomeLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/HomeLoader.java
new file mode 100644
index 000000000..783f39ad7
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/loaders/HomeLoader.java
@@ -0,0 +1,60 @@
+package code.name.monkey.retromusic.loaders;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import code.name.monkey.retromusic.model.Playlist;
+import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist;
+import code.name.monkey.retromusic.model.smartplaylist.HistoryPlaylist;
+import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist;
+import code.name.monkey.retromusic.model.smartplaylist.MyTopTracksPlaylist;
+import io.reactivex.Observable;
+import java.util.ArrayList;
+
+public class HomeLoader {
+
+ public static Observable> getRecentAndTopThings(
+ @NonNull Context context) {
+ ArrayList objects = new ArrayList<>();
+ return Observable.create(e -> {
+
+ new HistoryPlaylist(context).getSongs(context).subscribe(songs -> {
+ if (!songs.isEmpty()) {
+ objects.add(new HistoryPlaylist(context));
+ }
+ });
+ new LastAddedPlaylist(context).getSongs(context).subscribe(songs -> {
+ if (!songs.isEmpty()) {
+ objects.add(new LastAddedPlaylist(context));
+ }
+ });
+ new MyTopTracksPlaylist(context).getSongs(context).subscribe(songs -> {
+ if (!songs.isEmpty()) {
+ objects.add(new MyTopTracksPlaylist(context));
+ }
+ });
+
+ e.onNext(objects);
+ e.onComplete();
+ });
+ }
+
+ public static Observable> getHomeLoader(@NonNull Context context) {
+ ArrayList playlists = new ArrayList<>();
+ PlaylistLoader.getAllPlaylists(context)
+ .subscribe(playlists1 -> {
+ if (playlists1.size() > 0) {
+ for (Playlist playlist : playlists1) {
+ PlaylistSongsLoader.getPlaylistSongList(context, playlist)
+ .subscribe(songs -> {
+ if (songs.size() > 0) {
+ playlists.add(playlist);
+ }
+ });
+ }
+ }
+ });
+ return Observable.just(playlists);
+ }
+
+
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/LastAddedSongsLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/LastAddedSongsLoader.java
new file mode 100644
index 000000000..0e664c2da
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/loaders/LastAddedSongsLoader.java
@@ -0,0 +1,47 @@
+package code.name.monkey.retromusic.loaders;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.MediaStore;
+
+import code.name.monkey.retromusic.model.Album;
+import code.name.monkey.retromusic.model.Artist;
+import code.name.monkey.retromusic.model.Song;
+
+import java.util.ArrayList;
+
+import code.name.monkey.retromusic.util.PreferenceUtil;
+import io.reactivex.Observable;
+import io.reactivex.annotations.NonNull;
+
+/**
+ * Created by hemanths on 16/08/17.
+ */
+
+public class LastAddedSongsLoader {
+
+ @NonNull
+ public static Observable> getLastAddedSongs(@NonNull Context context) {
+ return SongLoader.getSongs(makeLastAddedCursor(context));
+ }
+
+ public static Cursor makeLastAddedCursor(@NonNull final Context context) {
+ long cutoff = PreferenceUtil.getInstance(context).getLastAddedCutoff();
+
+ return SongLoader.makeSongCursor(
+ context,
+ MediaStore.Audio.Media.DATE_ADDED + ">?",
+ new String[]{String.valueOf(cutoff)},
+ MediaStore.Audio.Media.DATE_ADDED + " DESC");
+ }
+
+ @NonNull
+ public static Observable> getLastAddedAlbums(@NonNull Context context) {
+ return AlbumLoader.splitIntoAlbums(getLastAddedSongs(context));
+ }
+
+ @NonNull
+ public static Observable> getLastAddedArtists(@NonNull Context context) {
+ return ArtistLoader.splitIntoArtists(getLastAddedAlbums(context));
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/PlaylistLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/PlaylistLoader.java
new file mode 100644
index 000000000..70ed160ba
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/loaders/PlaylistLoader.java
@@ -0,0 +1,118 @@
+package code.name.monkey.retromusic.loaders;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.PlaylistsColumns;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import code.name.monkey.retromusic.model.Playlist;
+
+import java.util.ArrayList;
+
+import io.reactivex.Observable;
+
+/**
+ * Created by hemanths on 16/08/17.
+ */
+
+public class PlaylistLoader {
+ @Nullable
+ public static Cursor makePlaylistCursor(@NonNull final Context context, final String selection, final String[] values) {
+ try {
+ return context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+ new String[]{
+ /* 0 */
+ BaseColumns._ID,
+ /* 1 */
+ PlaylistsColumns.NAME
+ }, selection, values, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER);
+ } catch (SecurityException e) {
+ return null;
+ }
+ }
+
+ @NonNull
+ public static Observable getPlaylist(@Nullable final Cursor cursor) {
+ return Observable.create(e -> {
+ Playlist playlist = new Playlist();
+
+ if (cursor != null && cursor.moveToFirst()) {
+ playlist = getPlaylistFromCursorImpl(cursor);
+ }
+ if (cursor != null)
+ cursor.close();
+
+ e.onNext(playlist);
+ e.onComplete();
+ });
+
+
+ }
+
+ @NonNull
+ public static Observable getPlaylist(@NonNull final Context context, final String playlistName) {
+ return getPlaylist(makePlaylistCursor(
+ context,
+ PlaylistsColumns.NAME + "=?",
+ new String[]{
+ playlistName
+ }
+ ));
+ }
+
+ @NonNull
+ public static Observable getPlaylist(@NonNull final Context context, final int playlistId) {
+ return getPlaylist(makePlaylistCursor(
+ context,
+ BaseColumns._ID + "=?",
+ new String[]{
+ String.valueOf(playlistId)
+ }
+ ));
+ }
+
+ @NonNull
+ private static Playlist getPlaylistFromCursorImpl(@NonNull final Cursor cursor) {
+
+ final int id = cursor.getInt(0);
+ final String name = cursor.getString(1);
+ return new Playlist(id, name);
+ }
+
+
+ @NonNull
+ public static Observable> getAllPlaylists(@Nullable final Cursor cursor) {
+ return Observable.create(e -> {
+ ArrayList playlists = new ArrayList<>();
+
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ playlists.add(getPlaylistFromCursorImpl(cursor));
+ } while (cursor.moveToNext());
+ }
+ if (cursor != null)
+ cursor.close();
+
+ e.onNext(playlists);
+ e.onComplete();
+ });
+ }
+
+ @NonNull
+ public static Observable> getAllPlaylists(@NonNull final Context context) {
+ return getAllPlaylists(makePlaylistCursor(context, null, null));
+ }
+
+ public static void deletePlaylists(Context context, long playlistId) {
+ Uri localUri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
+ StringBuilder localStringBuilder = new StringBuilder();
+ localStringBuilder.append("_id IN (");
+ localStringBuilder.append((playlistId));
+ localStringBuilder.append(")");
+ context.getContentResolver().delete(localUri, localStringBuilder.toString(), null);
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/PlaylistSongsLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/PlaylistSongsLoader.java
new file mode 100644
index 000000000..3c373b60b
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/loaders/PlaylistSongsLoader.java
@@ -0,0 +1,95 @@
+package code.name.monkey.retromusic.loaders;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.AudioColumns;
+
+import code.name.monkey.retromusic.model.AbsCustomPlaylist;
+import code.name.monkey.retromusic.model.Playlist;
+import code.name.monkey.retromusic.model.PlaylistSong;
+import code.name.monkey.retromusic.model.Song;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.reactivex.Observable;
+import io.reactivex.annotations.NonNull;
+
+/**
+ * Created by hemanths on 16/08/17.
+ */
+
+public class PlaylistSongsLoader {
+
+ @NonNull
+ public static Observable> getPlaylistSongList(@NonNull Context context, Playlist playlist) {
+ if (playlist instanceof AbsCustomPlaylist) {
+ return ((AbsCustomPlaylist) playlist).getSongs(context);
+ } else {
+ //noinspection unchecked
+ return getPlaylistSongList(context, playlist.id);
+ }
+ }
+
+ @NonNull
+ public static Observable> getPlaylistSongList(@NonNull Context context, final int playlistId) {
+ return Observable.create(e -> {
+ ArrayList songs = new ArrayList<>();
+ Cursor cursor = makePlaylistSongCursor(context, playlistId);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ songs.add(getPlaylistSongFromCursorImpl(cursor, playlistId));
+ } while (cursor.moveToNext());
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ e.onNext((ArrayList) (List) songs);
+ e.onComplete();
+ });
+ }
+
+ @NonNull
+ private static PlaylistSong getPlaylistSongFromCursorImpl(@NonNull Cursor cursor, int playlistId) {
+ final int id = cursor.getInt(0);
+ final String title = cursor.getString(1);
+ final int trackNumber = cursor.getInt(2);
+ final int year = cursor.getInt(3);
+ final long duration = cursor.getLong(4);
+ final String data = cursor.getString(5);
+ final int dateModified = cursor.getInt(6);
+ final int albumId = cursor.getInt(7);
+ final String albumName = cursor.getString(8);
+ final int artistId = cursor.getInt(9);
+ final String artistName = cursor.getString(10);
+ final int idInPlaylist = cursor.getInt(11);
+
+ return new PlaylistSong(id, title, trackNumber, year, duration, data, dateModified, albumId, albumName, artistId, artistName, playlistId, idInPlaylist);
+ }
+
+ public static Cursor makePlaylistSongCursor(@NonNull final Context context, final int playlistId) {
+ try {
+ return context.getContentResolver().query(
+ MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
+ new String[]{
+ MediaStore.Audio.Playlists.Members.AUDIO_ID,// 0
+ AudioColumns.TITLE,// 1
+ AudioColumns.TRACK,// 2
+ AudioColumns.YEAR,// 3
+ AudioColumns.DURATION,// 4
+ AudioColumns.DATA,// 5
+ AudioColumns.DATE_MODIFIED,// 6
+ AudioColumns.ALBUM_ID,// 7
+ AudioColumns.ALBUM,// 8
+ AudioColumns.ARTIST_ID,// 9
+ AudioColumns.ARTIST,// 10
+ MediaStore.Audio.Playlists.Members._ID // 11
+ }, SongLoader.BASE_SELECTION, null,
+ MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
+ } catch (SecurityException e) {
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/SearchLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/SearchLoader.java
new file mode 100644
index 000000000..f8bbf7a86
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/loaders/SearchLoader.java
@@ -0,0 +1,44 @@
+package code.name.monkey.retromusic.loaders;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import java.util.ArrayList;
+
+import code.name.monkey.retromusic.R;
+import io.reactivex.Observable;
+
+public class SearchLoader {
+
+ public static Observable> searchAll(@NonNull Context context, @NonNull String query) {
+ ArrayList