aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java119
-rw-r--r--app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java195
-rw-r--r--app/src/main/java/github/daneren2005/dsub/activity/VoiceQueryReceiverActivity.java16
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java41
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/DownloadFileAdapter.java25
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/EntryGridAdapter.java24
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/EntryInfiniteGridAdapter.java9
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/ExpandableSectionAdapter.java150
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/InternetRadioStationAdapter.java56
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/MainAdapter.java5
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java67
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/SearchAdapter.java35
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/SectionAdapter.java9
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/SettingsAdapter.java92
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/SimilarArtistAdapter.java53
-rw-r--r--app/src/main/java/github/daneren2005/dsub/domain/Artist.java32
-rw-r--r--app/src/main/java/github/daneren2005/dsub/domain/Indexes.java7
-rw-r--r--app/src/main/java/github/daneren2005/dsub/domain/InternetRadioStation.java43
-rw-r--r--app/src/main/java/github/daneren2005/dsub/domain/MusicDirectory.java59
-rw-r--r--app/src/main/java/github/daneren2005/dsub/domain/MusicFolder.java57
-rw-r--r--app/src/main/java/github/daneren2005/dsub/domain/SearchCritera.java94
-rw-r--r--app/src/main/java/github/daneren2005/dsub/domain/ServerInfo.java30
-rw-r--r--app/src/main/java/github/daneren2005/dsub/domain/User.java45
-rw-r--r--app/src/main/java/github/daneren2005/dsub/domain/Version.java236
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java2
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/ChatFragment.java8
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/DownloadFragment.java8
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/EqualizerFragment.java8
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/MainFragment.java136
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java327
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java122
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SelectArtistFragment.java119
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SelectBookmarkFragment.java38
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java232
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java157
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java38
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java42
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java6
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SelectShareFragment.java33
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java47
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SimilarArtistFragment.java78
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java197
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/UserFragment.java4
-rw-r--r--app/src/main/java/github/daneren2005/dsub/provider/DLNARouteProvider.java17
-rw-r--r--app/src/main/java/github/daneren2005/dsub/provider/DSubSearchProvider.java4
-rw-r--r--app/src/main/java/github/daneren2005/dsub/provider/DSubWidgetProvider.java2
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java314
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java107
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java93
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/DLNAController.java59
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java66
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/DownloadService.java432
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java41
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/JukeboxController.java7
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/MusicService.java12
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java49
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java977
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/RemoteController.java75
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/Scrobbler.java16
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/parser/AbstractParser.java36
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/parser/EntryListParser.java (renamed from app/src/main/java/github/daneren2005/dsub/service/parser/AlbumListParser.java)11
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/parser/ErrorParser.java2
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/parser/InternetRadioStationParser.java63
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/parser/ScanStatusParser.java18
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/parser/TopSongsParser.java58
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/parser/UserParser.java45
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/ssl/SSLSocketFactory.java553
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/ssl/TrustManagerDecorator.java65
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/ssl/TrustSelfSignedStrategy.java44
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/ssl/TrustStrategy.java57
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/sync/MostRecentSyncAdapter.java2
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/sync/PlaylistSyncAdapter.java9
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/sync/PodcastSyncAdapter.java10
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/sync/StarredSyncAdapter.java3
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/sync/SubsonicSyncAdapter.java78
-rw-r--r--app/src/main/java/github/daneren2005/dsub/updates/Updater.java7
-rw-r--r--app/src/main/java/github/daneren2005/dsub/updates/Updater403.java58
-rw-r--r--app/src/main/java/github/daneren2005/dsub/updates/UpdaterNoDLNA.java41
-rw-r--r--app/src/main/java/github/daneren2005/dsub/updates/UpdaterSongPress.java42
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/ArtistRadioBuffer.java3
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java35
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/Constants.java16
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/DrawableTint.java14
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/DroppySpeedControl.java82
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java21
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java79
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/MediaRouteManager.java23
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/Notifications.java154
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/ProgressListener.java2
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/SettingsBackupAgent.java7
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/SongDBHandler.java41
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/ThemeUtil.java112
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/UpdateHelper.java86
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/UserUtil.java58
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/Util.java307
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/compat/CastCompat.java8
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java2
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java2
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientJB.java13
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java169
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/tags/ID3v2File.java4
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/AlbumView.java14
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/CacheLocationPreference.java146
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/CardView.java67
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/FastScroller.java2
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/GridSpacingDecoration.java51
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/InternetRadioStationView.java39
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/MyViewFlipper.java53
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/SettingView.java9
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/SongView.java89
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/UpdateView.java4
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteChooserDialogFragment.java16
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteControllerDialogFragment.java16
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteDialogFactory.java17
114 files changed, 5491 insertions, 2844 deletions
diff --git a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java
index a58f169b..a1c5ceef 100644
--- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java
+++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java
@@ -32,13 +32,15 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.support.design.widget.NavigationView;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBarDrawerToggle;
-import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
+import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.KeyEvent;
@@ -67,7 +69,9 @@ import java.util.List;
import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.ServerInfo;
+import github.daneren2005.dsub.fragments.AdminFragment;
import github.daneren2005.dsub.fragments.SubsonicFragment;
+import github.daneren2005.dsub.fragments.UserFragment;
import github.daneren2005.dsub.service.DownloadService;
import github.daneren2005.dsub.service.HeadphoneListenerService;
import github.daneren2005.dsub.service.MusicService;
@@ -76,10 +80,13 @@ import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.DrawableTint;
import github.daneren2005.dsub.util.ImageLoader;
import github.daneren2005.dsub.util.SilentBackgroundTask;
+import github.daneren2005.dsub.util.ThemeUtil;
import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.view.UpdateView;
import github.daneren2005.dsub.util.UserUtil;
+import static android.Manifest.*;
+
public class SubsonicActivity extends AppCompatActivity implements OnItemSelectedListener {
private static final String TAG = SubsonicActivity.class.getSimpleName();
private static ImageLoader IMAGE_LOADER;
@@ -88,6 +95,8 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
protected static boolean actionbarColored;
private static final int MENU_GROUP_SERVER = 10;
private static final int MENU_ITEM_SERVER_BASE = 100;
+ public static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
+ public static final int PERMISSIONS_REQUEST_LOCATION = 2;
private final List<Runnable> afterServiceAvailable = new ArrayList<>();
private boolean drawerIdle = true;
@@ -116,6 +125,10 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
boolean drawerOpen = false;
SharedPreferences.OnSharedPreferenceChangeListener preferencesListener;
+ static {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO);
+ }
+
@Override
protected void onCreate(Bundle bundle) {
UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
@@ -150,6 +163,9 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
case Constants.PREFERENCES_KEY_BOOKMARKS_ENABLED:
setDrawerItemVisible(R.id.drawer_bookmarks, false);
break;
+ case Constants.PREFERENCES_KEY_INTERNET_RADIO_ENABLED:
+ setDrawerItemVisible(R.id.drawer_internet_radio_stations, false);
+ break;
case Constants.PREFERENCES_KEY_SHARED_ENABLED:
setDrawerItemVisible(R.id.drawer_shares, false);
break;
@@ -164,6 +180,25 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
};
Util.getPreferences(this).registerOnSharedPreferenceChangeListener(preferencesListener);
}
+
+ if (ContextCompat.checkSelfPermission(this, permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, new String[]{ permission.WRITE_EXTERNAL_STORAGE }, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
+ switch (requestCode) {
+ case PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE: {
+ // If request is cancelled, the result arrays are empty.
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+
+ } else {
+ Util.toast(this, R.string.permission_external_storage_failed);
+ finish();
+ }
+ }
+ }
}
@Override
@@ -189,40 +224,48 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
}
protected void createCustomActionBarView() {
- View customActionbar = getLayoutInflater().inflate(R.layout.actionbar_spinner, null);
- actionBarSpinner = (Spinner)customActionbar.findViewById(R.id.spinner);
- if(Util.getThemeRes(this) == R.style.Theme_DSub_Light_No_Actionbar) {
- actionBarSpinner.setBackgroundResource(R.drawable.abc_spinner_mtrl_am_alpha);
+ actionBarSpinner = (Spinner) getLayoutInflater().inflate(R.layout.actionbar_spinner, null);
+ if((this instanceof SubsonicFragmentActivity || this instanceof SettingsActivity) && (Util.getPreferences(this).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true) || ThemeUtil.getThemeRes(this) != R.style.Theme_DSub_Light_No_Color)) {
+ actionBarSpinner.setBackgroundDrawable(DrawableTint.getTintedDrawableFromColor(this, R.drawable.abc_spinner_mtrl_am_alpha, android.R.color.white));
}
spinnerAdapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
actionBarSpinner.setOnItemSelectedListener(this);
actionBarSpinner.setAdapter(spinnerAdapter);
- getSupportActionBar().setCustomView(customActionbar);
+ getSupportActionBar().setCustomView(actionBarSpinner);
}
@Override
- protected void onResume() {
- super.onResume();
+ protected void onStart() {
+ super.onStart();
Util.registerMediaButtonEventReceiver(this);
// Make sure to update theme
SharedPreferences prefs = Util.getPreferences(this);
- if (theme != null && !theme.equals(Util.getTheme(this)) || fullScreen != prefs.getBoolean(Constants.PREFERENCES_KEY_FULL_SCREEN, false) || actionbarColored != prefs.getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) {
+ if (theme != null && !theme.equals(ThemeUtil.getTheme(this)) || fullScreen != prefs.getBoolean(Constants.PREFERENCES_KEY_FULL_SCREEN, false) || actionbarColored != prefs.getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) {
restart();
overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
- DrawableTint.wipeTintCache();
+ DrawableTint.clearCache();
+ return;
}
- populateTabs();
getImageLoader().onUIVisible();
UpdateView.addActiveActivity();
}
@Override
- protected void onPause() {
- super.onPause();
+ protected void onResume() {
+ super.onResume();
+
+ // If this is in onStart is causes crashes when rotating screen in offline mode
+ // Actual root cause of error is `drawerItemSelected(newFragment);` in the offline mode branch of code
+ populateTabs();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
UpdateView.removeActiveActivity();
}
@@ -281,6 +324,9 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
case R.id.drawer_bookmarks:
drawerItemSelected("Bookmark");
return true;
+ case R.id.drawer_internet_radio_stations:
+ drawerItemSelected("Internet Radio");
+ return true;
case R.id.drawer_shares:
drawerItemSelected("Share");
return true;
@@ -538,7 +584,7 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
int top = spinnerAdapter.getCount() - 1;
if(position < top) {
- for(int i = top; i > position; i--) {
+ for(int i = top; i > position && i >= 0; i--) {
removeCurrent();
}
}
@@ -556,6 +602,7 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
SharedPreferences prefs = Util.getPreferences(this);
boolean podcastsEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_PODCASTS_ENABLED, true);
boolean bookmarksEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_BOOKMARKS_ENABLED, true) && !Util.isOffline(this) && ServerInfo.canBookmark(this);
+ boolean internetRadioEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_INTERNET_RADIO_ENABLED, true) && !Util.isOffline(this) && ServerInfo.canInternetRadio(this);
boolean sharedEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_SHARED_ENABLED, true) && !Util.isOffline(this);
boolean chatEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_CHAT_ENABLED, true) && !Util.isOffline(this);
boolean adminEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_ADMIN_ENABLED, true) && !Util.isOffline(this);
@@ -585,6 +632,9 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
if(!bookmarksEnabled) {
setDrawerItemVisible(R.id.drawer_bookmarks, false);
}
+ if(!internetRadioEnabled) {
+ setDrawerItemVisible(R.id.drawer_internet_radio_stations, false);
+ }
if(!sharedEnabled) {
setDrawerItemVisible(R.id.drawer_shares, false);
}
@@ -752,6 +802,11 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
recreateSpinner();
}
public void removeCurrent() {
+ // Don't try to remove current if there is no backstack to remove from
+ if(backStack.isEmpty()) {
+ return;
+ }
+
if(currentFragment != null) {
currentFragment.setPrimaryFragment(false);
}
@@ -819,7 +874,11 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
removeCurrent();
}
- currentFragment.invalidate();
+ if(currentFragment instanceof UserFragment || currentFragment instanceof AdminFragment) {
+ restart(false);
+ } else {
+ currentFragment.invalidate();
+ }
populateTabs();
}
@@ -853,6 +912,7 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
spinnerAdapter.notifyDataSetChanged();
actionBarSpinner.setSelection(spinnerAdapter.getCount() - 1);
if(!isTv()) {
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setDisplayShowCustomEnabled(true);
}
@@ -862,6 +922,7 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
} else if(!isTv()) {
+ getSupportActionBar().setDisplayShowTitleEnabled(true);
getSupportActionBar().setTitle(currentFragment.getTitle());
getSupportActionBar().setDisplayShowCustomEnabled(false);
drawerToggle.setDrawerIndicatorEnabled(true);
@@ -869,22 +930,31 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
}
protected void restart() {
- Intent intent = new Intent(this, ((Object) this).getClass());
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ restart(true);
+ }
+ protected void restart(boolean resumePosition) {
+ Intent intent = new Intent(this, this.getClass());
intent.putExtras(getIntent());
- intent.putExtra(Constants.FRAGMENT_POSITION, lastSelectedPosition);
+ if(resumePosition) {
+ intent.putExtra(Constants.FRAGMENT_POSITION, lastSelectedPosition);
+ } else {
+ String fragmentType = Util.openToTab(this);
+ intent.putExtra(Constants.INTENT_EXTRA_FRAGMENT_TYPE, fragmentType);
+ intent.putExtra(Constants.FRAGMENT_POSITION, getDrawerItemId(fragmentType));
+ }
+ finish();
Util.startActivityWithoutTransition(this, intent);
}
private void applyTheme() {
- theme = Util.getTheme(this);
+ theme = ThemeUtil.getTheme(this);
if(theme != null && theme.indexOf("fullscreen") != -1) {
theme = theme.substring(0, theme.indexOf("_fullscreen"));
- Util.setTheme(this, theme);
+ ThemeUtil.setTheme(this, theme);
}
- Util.applyTheme(this, theme);
+ ThemeUtil.applyTheme(this, theme);
actionbarColored = Util.getPreferences(this).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true);
}
private void applyFullscreen() {
@@ -1044,6 +1114,7 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
UserUtil.seedCurrentUser(this);
this.updateDrawerHeader();
+ drawer.closeDrawers();
}
private void showOfflineSyncDialog(final int scrobbleCount, final int starsCount) {
@@ -1125,6 +1196,10 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
}
public int getDrawerItemId(String fragmentType) {
+ if(fragmentType == null) {
+ return R.id.drawer_home;
+ }
+
switch(fragmentType) {
case "Home":
return R.id.drawer_home;
@@ -1136,6 +1211,8 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
return R.id.drawer_podcasts;
case "Bookmark":
return R.id.drawer_bookmarks;
+ case "Internet Radio":
+ return R.id.drawer_internet_radio_stations;
case "Share":
return R.id.drawer_shares;
case "Chat":
diff --git a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java
index 87a67e80..803e6f72 100644
--- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java
+++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java
@@ -28,8 +28,8 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.os.Bundle;
-import android.os.Handler;
import android.preference.PreferenceManager;
+import android.provider.MediaStore;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AlertDialog;
@@ -37,8 +37,6 @@ import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckBox;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -48,9 +46,6 @@ import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import java.io.File;
import java.util.Date;
import java.util.List;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.MusicDirectory;
@@ -66,6 +61,7 @@ import github.daneren2005.dsub.fragments.SearchFragment;
import github.daneren2005.dsub.fragments.SelectArtistFragment;
import github.daneren2005.dsub.fragments.SelectBookmarkFragment;
import github.daneren2005.dsub.fragments.SelectDirectoryFragment;
+import github.daneren2005.dsub.fragments.SelectInternetRadioStationFragment;
import github.daneren2005.dsub.fragments.SelectPlaylistFragment;
import github.daneren2005.dsub.fragments.SelectPodcastsFragment;
import github.daneren2005.dsub.fragments.SelectShareFragment;
@@ -76,6 +72,7 @@ import github.daneren2005.dsub.service.MusicService;
import github.daneren2005.dsub.service.MusicServiceFactory;
import github.daneren2005.dsub.updates.Updater;
import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.DrawableTint;
import github.daneren2005.dsub.util.FileUtil;
import github.daneren2005.dsub.util.SilentBackgroundTask;
import github.daneren2005.dsub.util.UserUtil;
@@ -93,6 +90,7 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
private SlidingUpPanelLayout slideUpPanel;
private SlidingUpPanelLayout.PanelSlideListener panelSlideListener;
+ private boolean isPanelClosing = false;
private NowPlayingFragment nowPlayingFragment;
private SubsonicFragment secondaryFragment;
private Toolbar mainToolbar;
@@ -106,6 +104,10 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
private long lastBackPressTime = 0;
private DownloadFile currentPlaying;
private PlayerState currentState;
+ private ImageButton previousButton;
+ private ImageButton nextButton;
+ private ImageButton rewindButton;
+ private ImageButton fastforwardButton;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -132,6 +134,7 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
stopService(new Intent(this, DownloadService.class));
finish();
getImageLoader().clearCache();
+ DrawableTint.clearCache();
} else if(getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD_VIEW)) {
getIntent().putExtra(Constants.INTENT_EXTRA_FRAGMENT_TYPE, "Download");
lastSelectedPosition = R.id.drawer_downloading;
@@ -153,8 +156,19 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
if(item != null) {
item.setChecked(true);
}
+ } else {
+ lastSelectedPosition = getDrawerItemId(fragmentType);
}
+
currentFragment = getNewFragment(fragmentType);
+ if(getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_ID)) {
+ Bundle currentArguments = currentFragment.getArguments();
+ if(currentArguments == null) {
+ currentArguments = new Bundle();
+ }
+ currentArguments.putString(Constants.INTENT_EXTRA_NAME_ID, getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID));
+ currentFragment.setArguments(currentArguments);
+ }
currentFragment.setPrimaryFragment(true);
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, currentFragment, currentFragment.getSupportTag() + "").commit();
@@ -187,24 +201,34 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
@Override
public void onPanelCollapsed(View panel) {
- bottomBar.setVisibility(View.VISIBLE);
- nowPlayingToolbar.setVisibility(View.GONE);
- nowPlayingFragment.setPrimaryFragment(false);
- setSupportActionBar(mainToolbar);
- recreateSpinner();
+ isPanelClosing = false;
+ if(bottomBar.getVisibility() == View.GONE) {
+ bottomBar.setVisibility(View.VISIBLE);
+ nowPlayingToolbar.setVisibility(View.GONE);
+ nowPlayingFragment.setPrimaryFragment(false);
+ setSupportActionBar(mainToolbar);
+ recreateSpinner();
+ }
}
@Override
public void onPanelExpanded(View panel) {
+ isPanelClosing = false;
currentFragment.stopActionMode();
// Disable custom view before switching
getSupportActionBar().setDisplayShowCustomEnabled(false);
+ getSupportActionBar().setDisplayShowTitleEnabled(true);
bottomBar.setVisibility(View.GONE);
nowPlayingToolbar.setVisibility(View.VISIBLE);
setSupportActionBar(nowPlayingToolbar);
- nowPlayingFragment.setPrimaryFragment(true);
+
+ if(secondaryFragment == null) {
+ nowPlayingFragment.setPrimaryFragment(true);
+ } else {
+ secondaryFragment.setPrimaryFragment(true);
+ }
drawerToggle.setDrawerIndicatorEnabled(false);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@@ -230,6 +254,8 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
openNowPlaying();
}
}, 200);
+
+ getIntent().removeExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD);
}
bottomBar = findViewById(R.id.bottom_bar);
@@ -248,7 +274,25 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
trans.commit();
}
- ImageButton previousButton = (ImageButton) findViewById(R.id.download_previous);
+ rewindButton = (ImageButton) findViewById(R.id.download_rewind);
+ rewindButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ new SilentBackgroundTask<Void>(SubsonicFragmentActivity.this) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ if (getDownloadService() == null) {
+ return null;
+ }
+
+ getDownloadService().rewind();
+ return null;
+ }
+ }.execute();
+ }
+ });
+
+ previousButton = (ImageButton) findViewById(R.id.download_previous);
previousButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -276,6 +320,8 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
PlayerState state = getDownloadService().getPlayerState();
if(state == PlayerState.STARTED) {
getDownloadService().pause();
+ } else if(state == PlayerState.IDLE) {
+ getDownloadService().play();
} else {
getDownloadService().start();
}
@@ -286,7 +332,7 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
}
});
- ImageButton nextButton = (ImageButton) findViewById(R.id.download_next);
+ nextButton = (ImageButton) findViewById(R.id.download_next);
nextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -303,6 +349,24 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
}.execute();
}
});
+
+ fastforwardButton = (ImageButton) findViewById(R.id.download_fastforward);
+ fastforwardButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ new SilentBackgroundTask<Void>(SubsonicFragmentActivity.this) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ if (getDownloadService() == null) {
+ return null;
+ }
+
+ getDownloadService().fastForward();
+ return null;
+ }
+ }.execute();
+ }
+ });
}
@Override
@@ -330,17 +394,19 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
super.onNewIntent(intent);
if(currentFragment != null && intent.getStringExtra(Constants.INTENT_EXTRA_NAME_QUERY) != null) {
+ if(slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
+ closeNowPlaying();
+ }
+
if(currentFragment instanceof SearchFragment) {
String query = intent.getStringExtra(Constants.INTENT_EXTRA_NAME_QUERY);
boolean autoplay = intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false);
- boolean requestsearch = intent.getBooleanExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, false);
+ String artist = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ARTIST);
+ String album = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ALBUM);
+ String title = intent.getStringExtra(MediaStore.EXTRA_MEDIA_TITLE);
if (query != null) {
- ((SearchFragment)currentFragment).search(query, autoplay);
- } else {
- if (requestsearch) {
- onSearchRequested();
- }
+ ((SearchFragment)currentFragment).search(query, autoplay, artist, album, title);
}
getIntent().removeExtra(Constants.INTENT_EXTRA_NAME_QUERY);
} else {
@@ -349,7 +415,14 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
SearchFragment fragment = new SearchFragment();
replaceFragment(fragment, fragment.getSupportTag());
}
+ } else if(intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, false)) {
+ if(slideUpPanel.getPanelState() != SlidingUpPanelLayout.PanelState.EXPANDED) {
+ openNowPlaying();
+ }
} else {
+ if(slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
+ closeNowPlaying();
+ }
setIntent(intent);
}
if(drawer != null) {
@@ -402,6 +475,9 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putString(Constants.MAIN_NOW_PLAYING, nowPlayingFragment.getTag());
+ if(secondaryFragment != null) {
+ savedInstanceState.putString(Constants.MAIN_NOW_PLAYING_SECONDARY, secondaryFragment.getTag());
+ }
savedInstanceState.putInt(Constants.MAIN_SLIDE_PANEL_STATE, slideUpPanel.getPanelState().hashCode());
}
@Override
@@ -411,6 +487,19 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
String id = savedInstanceState.getString(Constants.MAIN_NOW_PLAYING);
FragmentManager fm = getSupportFragmentManager();
nowPlayingFragment = (NowPlayingFragment) fm.findFragmentByTag(id);
+
+ String secondaryId = savedInstanceState.getString(Constants.MAIN_NOW_PLAYING_SECONDARY);
+ if(secondaryId != null) {
+ secondaryFragment = (SubsonicFragment) fm.findFragmentByTag(secondaryId);
+
+ nowPlayingFragment.setPrimaryFragment(false);
+ secondaryFragment.setPrimaryFragment(true);
+
+ FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
+ trans.hide(nowPlayingFragment);
+ trans.commit();
+ }
+
if(drawerToggle != null && backStack.size() > 0) {
drawerToggle.setDrawerIndicatorEnabled(false);
}
@@ -472,7 +561,7 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
@Override
public void replaceFragment(SubsonicFragment fragment, int tag, boolean replaceCurrent) {
- if(slideUpPanel != null && slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
+ if(slideUpPanel != null && slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED && !isPanelClosing) {
secondaryFragment = fragment;
nowPlayingFragment.setPrimaryFragment(false);
secondaryFragment.setPrimaryFragment(true);
@@ -562,6 +651,7 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
@Override
public void closeNowPlaying() {
slideUpPanel.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
+ isPanelClosing = true;
}
private SubsonicFragment getNewFragment(String fragmentType) {
@@ -575,6 +665,8 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
return new SelectPodcastsFragment();
} else if("Bookmark".equals(fragmentType)) {
return new SelectBookmarkFragment();
+ } else if("Internet Radio".equals(fragmentType)) {
+ return new SelectInternetRadioStationFragment();
} else if("Share".equals(fragmentType)) {
return new SelectShareFragment();
} else if("Admin".equals(fragmentType)) {
@@ -637,7 +729,7 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + 1, "Demo Server");
editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + 1, "http://demo.subsonic.org");
- editor.putString(Constants.PREFERENCES_KEY_USERNAME + 1, "guest");
+ editor.putString(Constants.PREFERENCES_KEY_USERNAME + 1, "guest2");
editor.putString(Constants.PREFERENCES_KEY_PASSWORD + 1, "guest");
editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
editor.commit();
@@ -828,14 +920,20 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
}
@Override
- public void onSongChanged(DownloadFile currentPlaying, int currentPlayingIndex) {
+ public void onSongChanged(DownloadFile currentPlaying, int currentPlayingIndex, boolean shouldFastForward) {
this.currentPlaying = currentPlaying;
MusicDirectory.Entry song = null;
if (currentPlaying != null) {
song = currentPlaying.getSong();
trackView.setText(song.getTitle());
- artistView.setText(song.getArtist());
+
+ if(song.getArtist() != null) {
+ artistView.setVisibility(View.VISIBLE);
+ artistView.setText(song.getArtist());
+ } else {
+ artistView.setVisibility(View.GONE);
+ }
} else {
trackView.setText(R.string.main_title);
artistView.setText(R.string.main_artist);
@@ -851,13 +949,40 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
}
getImageLoader().loadImage(coverArtView, song, false, height, false);
}
+
+ updateMediaButtons(shouldFastForward);
+ }
+
+ private void updateMediaButtons(boolean shouldFastForward) {
+ DownloadService downloadService = getDownloadService();
+ if(downloadService.isCurrentPlayingSingle()) {
+ previousButton.setVisibility(View.GONE);
+ nextButton.setVisibility(View.GONE);
+ rewindButton.setVisibility(View.GONE);
+ fastforwardButton.setVisibility(View.GONE);
+ } else {
+ if (shouldFastForward) {
+ previousButton.setVisibility(View.GONE);
+ nextButton.setVisibility(View.GONE);
+
+ rewindButton.setVisibility(View.VISIBLE);
+ fastforwardButton.setVisibility(View.VISIBLE);
+ } else {
+ previousButton.setVisibility(View.VISIBLE);
+ nextButton.setVisibility(View.VISIBLE);
+
+ rewindButton.setVisibility(View.GONE);
+ fastforwardButton.setVisibility(View.GONE);
+ }
+ }
}
@Override
- public void onSongsChanged(List<DownloadFile> songs, DownloadFile currentPlaying, int currentPlayingIndex) {
+ public void onSongsChanged(List<DownloadFile> songs, DownloadFile currentPlaying, int currentPlayingIndex, boolean shouldFastForward) {
if(this.currentPlaying != currentPlaying || this.currentPlaying == null) {
- onSongChanged(currentPlaying, currentPlayingIndex);
- onMetadataUpdate(currentPlaying != null ? currentPlaying.getSong() : null, DownloadService.METADATA_UPDATED_ALL);
+ onSongChanged(currentPlaying, currentPlayingIndex, shouldFastForward);
+ } else {
+ updateMediaButtons(shouldFastForward);
}
}
@@ -875,7 +1000,21 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
}
@Override
- public void onMetadataUpdate(MusicDirectory.Entry entry, int fieldChange) {
+ public void onMetadataUpdate(MusicDirectory.Entry song, int fieldChange) {
+ if(song != null && coverArtView != null && fieldChange == DownloadService.METADATA_UPDATED_COVER_ART) {
+ int height = coverArtView.getHeight();
+ if (height <= 0) {
+ int[] attrs = new int[]{R.attr.actionBarSize};
+ TypedArray typedArray = this.obtainStyledAttributes(attrs);
+ height = typedArray.getDimensionPixelSize(0, 0);
+ typedArray.recycle();
+ }
+ getImageLoader().loadImage(coverArtView, song, false, height, false);
+ // We need to update it immediately since it won't update if updater is not running for it
+ if(nowPlayingFragment != null && slideUpPanel.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
+ nowPlayingFragment.onMetadataUpdate(song, fieldChange);
+ }
+ }
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/activity/VoiceQueryReceiverActivity.java b/app/src/main/java/github/daneren2005/dsub/activity/VoiceQueryReceiverActivity.java
index c0effe27..641b118f 100644
--- a/app/src/main/java/github/daneren2005/dsub/activity/VoiceQueryReceiverActivity.java
+++ b/app/src/main/java/github/daneren2005/dsub/activity/VoiceQueryReceiverActivity.java
@@ -55,6 +55,22 @@ public class VoiceQueryReceiverActivity extends Activity {
if(!GMS_SEARCH_ACTION.equals(getIntent().getAction())) {
intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
}
+
+ String artist = getIntent().getStringExtra(MediaStore.EXTRA_MEDIA_ARTIST);
+ if(artist != null) {
+ intent.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist);
+ }
+
+ String album = getIntent().getStringExtra(MediaStore.EXTRA_MEDIA_ALBUM);
+ if(album != null) {
+ intent.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album);
+ }
+
+ String title = getIntent().getStringExtra(MediaStore.EXTRA_MEDIA_TITLE);
+ if(title != null) {
+ intent.putExtra(MediaStore.EXTRA_MEDIA_TITLE, title);
+ }
+
intent.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, getIntent().getStringExtra(MediaStore.EXTRA_MEDIA_FOCUS));
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
Util.startActivityWithoutTransition(VoiceQueryReceiverActivity.this, intent);
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java
index 5feaa482..5ed79e82 100644
--- a/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java
@@ -23,27 +23,32 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import java.io.Serializable;
import java.util.List;
import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.Artist;
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.domain.MusicDirectory.Entry;
import github.daneren2005.dsub.domain.MusicFolder;
import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.view.ArtistView;
import github.daneren2005.dsub.view.FastScroller;
+import github.daneren2005.dsub.view.SongView;
import github.daneren2005.dsub.view.UpdateView;
-public class ArtistAdapter extends SectionAdapter<Artist> implements FastScroller.BubbleTextGetter {
+public class ArtistAdapter extends SectionAdapter<Serializable> implements FastScroller.BubbleTextGetter {
+ public static int VIEW_TYPE_SONG = 3;
public static int VIEW_TYPE_ARTIST = 4;
private List<MusicFolder> musicFolders;
private OnMusicFolderChanged onMusicFolderChanged;
- public ArtistAdapter(Context context, List<Artist> artists, OnItemClickedListener listener) {
+ public ArtistAdapter(Context context, List<Serializable> artists, OnItemClickedListener listener) {
this(context, artists, null, listener, null);
}
- public ArtistAdapter(Context context, List<Artist> artists, List<MusicFolder> musicFolders, OnItemClickedListener onItemClickedListener, OnMusicFolderChanged onMusicFolderChanged) {
+ public ArtistAdapter(Context context, List<Serializable> artists, List<MusicFolder> musicFolders, OnItemClickedListener onItemClickedListener, OnMusicFolderChanged onMusicFolderChanged) {
super(context, artists);
this.musicFolders = musicFolders;
this.onItemClickedListener = onItemClickedListener;
@@ -92,7 +97,7 @@ public class ArtistAdapter extends SectionAdapter<Artist> implements FastScrolle
return new UpdateView.UpdateViewHolder(header, false);
}
@Override
- public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String header) {
+ public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String header, int sectionIndex) {
TextView folderName = (TextView) holder.getView().findViewById(R.id.select_artist_folder_2);
String musicFolderId = Util.getSelectedMusicFolderId(context);
@@ -110,17 +115,35 @@ public class ArtistAdapter extends SectionAdapter<Artist> implements FastScrolle
@Override
public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
- return new UpdateView.UpdateViewHolder(new ArtistView(context));
+ UpdateView updateView = null;
+ if(viewType == VIEW_TYPE_ARTIST) {
+ updateView = new ArtistView(context);
+ } else if(viewType == VIEW_TYPE_SONG) {
+ updateView = new SongView(context);
+ }
+
+ return new UpdateView.UpdateViewHolder(updateView);
}
@Override
- public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Artist item, int viewType) {
- holder.getUpdateView().setObject(item);
+ public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Serializable item, int viewType) {
+ UpdateView view = holder.getUpdateView();
+ if(viewType == VIEW_TYPE_ARTIST) {
+ view.setObject(item);
+ } else if(viewType == VIEW_TYPE_SONG) {
+ SongView songView = (SongView) view;
+ Entry entry = (Entry) item;
+ songView.setObject(entry, checkable && !entry.isVideo());
+ }
}
@Override
- public int getItemViewType(Artist item) {
- return VIEW_TYPE_ARTIST;
+ public int getItemViewType(Serializable item) {
+ if(item instanceof Artist) {
+ return VIEW_TYPE_ARTIST;
+ } else {
+ return VIEW_TYPE_SONG;
+ }
}
@Override
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/DownloadFileAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/DownloadFileAdapter.java
index d4613994..5b3dc289 100644
--- a/app/src/main/java/github/daneren2005/dsub/adapter/DownloadFileAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/DownloadFileAdapter.java
@@ -16,13 +16,18 @@
package github.daneren2005.dsub.adapter;
import android.content.Context;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import java.util.List;
+import github.daneren2005.dsub.R;
import github.daneren2005.dsub.service.DownloadFile;
+import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.view.FastScroller;
import github.daneren2005.dsub.view.SongView;
import github.daneren2005.dsub.view.UpdateView;
@@ -33,6 +38,7 @@ public class DownloadFileAdapter extends SectionAdapter<DownloadFile> implements
public DownloadFileAdapter(Context context, List<DownloadFile> entries, OnItemClickedListener onItemClickedListener) {
super(context, entries);
this.onItemClickedListener = onItemClickedListener;
+ this.checkable = true;
}
@Override
@@ -43,7 +49,7 @@ public class DownloadFileAdapter extends SectionAdapter<DownloadFile> implements
@Override
public void onBindViewHolder(UpdateView.UpdateViewHolder holder, DownloadFile item, int viewType) {
SongView songView = (SongView) holder.getUpdateView();
- songView.setObject(item.getSong(), false);
+ songView.setObject(item.getSong(), Util.isBatchMode(context));
songView.setDownloadFile(item);
}
@@ -56,4 +62,21 @@ public class DownloadFileAdapter extends SectionAdapter<DownloadFile> implements
public String getTextToShowInBubble(int position) {
return null;
}
+
+ @Override
+ public void onCreateActionModeMenu(Menu menu, MenuInflater menuInflater) {
+ if(Util.isOffline(context)) {
+ menuInflater.inflate(R.menu.multiselect_nowplaying_offline, menu);
+ } else {
+ menuInflater.inflate(R.menu.multiselect_nowplaying, menu);
+ }
+
+ if(!selected.isEmpty()) {
+ MenuItem starItem = menu.findItem(R.id.menu_star);
+ if(starItem != null) {
+ boolean isStarred = selected.get(0).getSong().isStarred();
+ starItem.setTitle(isStarred ? R.string.common_unstar : R.string.common_star);
+ }
+ }
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/EntryGridAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/EntryGridAdapter.java
index 38931482..e75a5104 100644
--- a/app/src/main/java/github/daneren2005/dsub/adapter/EntryGridAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/EntryGridAdapter.java
@@ -16,13 +16,12 @@
package github.daneren2005.dsub.adapter;
import android.content.Context;
-import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import java.util.ArrayList;
import java.util.List;
import github.daneren2005.dsub.R;
@@ -45,8 +44,8 @@ public class EntryGridAdapter extends SectionAdapter<Entry> {
private ImageLoader imageLoader;
private boolean largeAlbums;
private boolean showArtist = false;
+ private boolean showAlbum = false;
private boolean removeFromPlaylist = false;
- private boolean removeStarred = true;
private View header;
public EntryGridAdapter(Context context, List<Entry> entries, ImageLoader imageLoader, boolean largeCell) {
@@ -89,6 +88,7 @@ public class EntryGridAdapter extends SectionAdapter<Entry> {
albumView.setObject(entry, imageLoader);
} else if(viewType == VIEW_TYPE_SONG) {
SongView songView = (SongView) view;
+ songView.setShowAlbum(showAlbum);
songView.setObject(entry, checkable && !entry.isVideo());
}
}
@@ -96,7 +96,7 @@ public class EntryGridAdapter extends SectionAdapter<Entry> {
public UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) {
return new UpdateViewHolder(header, false);
}
- public void onBindHeaderHolder(UpdateViewHolder holder, String header) {
+ public void onBindHeaderHolder(UpdateViewHolder holder, String header, int sectionIndex) {
}
@@ -125,6 +125,10 @@ public class EntryGridAdapter extends SectionAdapter<Entry> {
this.showArtist = showArtist;
}
+ public void setShowAlbum(boolean showAlbum) {
+ this.showAlbum = showAlbum;
+ }
+
public void removeAt(int index) {
sections.get(0).remove(index);
if(header != null) {
@@ -136,9 +140,6 @@ public class EntryGridAdapter extends SectionAdapter<Entry> {
public void setRemoveFromPlaylist(boolean removeFromPlaylist) {
this.removeFromPlaylist = removeFromPlaylist;
}
- public void setRemoveStarred(boolean removeStarred) {
- this.removeStarred = removeStarred;
- }
@Override
public void onCreateActionModeMenu(Menu menu, MenuInflater menuInflater) {
@@ -151,8 +152,13 @@ public class EntryGridAdapter extends SectionAdapter<Entry> {
if(!removeFromPlaylist) {
menu.removeItem(R.id.menu_remove_playlist);
}
- if(removeStarred) {
- menu.removeItem(R.id.menu_unstar);
+
+ if(!selected.isEmpty()) {
+ MenuItem starItem = menu.findItem(R.id.menu_star);
+ if(starItem != null) {
+ boolean isStarred = selected.get(0).isStarred();
+ starItem.setTitle(isStarred ? R.string.common_unstar : R.string.common_star);
+ }
}
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/EntryInfiniteGridAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/EntryInfiniteGridAdapter.java
index 2c4f75dc..6c1c14da 100644
--- a/app/src/main/java/github/daneren2005/dsub/adapter/EntryInfiniteGridAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/EntryInfiniteGridAdapter.java
@@ -26,6 +26,7 @@ import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.MusicDirectory.Entry;
import github.daneren2005.dsub.domain.ServerInfo;
+import github.daneren2005.dsub.fragments.MainFragment;
import github.daneren2005.dsub.service.MusicService;
import github.daneren2005.dsub.service.MusicServiceFactory;
import github.daneren2005.dsub.util.ImageLoader;
@@ -88,6 +89,10 @@ public class EntryInfiniteGridAdapter extends EntryGridAdapter {
this.type = type;
this.extra = extra;
this.size = size;
+
+ if(super.getItemCount() < size) {
+ allLoaded = true;
+ }
}
public void loadMore() {
@@ -110,7 +115,7 @@ public class EntryInfiniteGridAdapter extends EntryGridAdapter {
appendCachedData(newData);
loading = false;
- if(newData.isEmpty()) {
+ if(newData.size() < size) {
allLoaded = true;
notifyDataSetChanged();
}
@@ -126,6 +131,8 @@ public class EntryInfiniteGridAdapter extends EntryGridAdapter {
result = service.getAlbumList(type, extra, size, offset, false, context, null);
} else if("genres".equals(type) || "genres-songs".equals(type)) {
result = service.getSongsByGenre(extra, size, offset, context, null);
+ }else if(type.indexOf(MainFragment.SONGS_LIST_PREFIX) != -1) {
+ result = service.getSongList(type, size, offset, context, null);
} else {
result = service.getAlbumList(type, size, offset, false, context, null);
}
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/ExpandableSectionAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/ExpandableSectionAdapter.java
new file mode 100644
index 00000000..6ebb34e3
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/ExpandableSectionAdapter.java
@@ -0,0 +1,150 @@
+/*
+ This file is part of Subsonic.
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+ Copyright 2015 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.adapter;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.util.DrawableTint;
+import github.daneren2005.dsub.view.BasicHeaderView;
+import github.daneren2005.dsub.view.UpdateView;
+
+public abstract class ExpandableSectionAdapter<T> extends SectionAdapter<T> {
+ private static final String TAG = ExpandableSectionAdapter.class.getSimpleName();
+ private static final int DEFAULT_VISIBLE = 4;
+ private static final int EXPAND_TOGGLE = R.attr.select_server;
+ private static final int COLLAPSE_TOGGLE = R.attr.select_tabs;
+
+ protected List<Integer> sectionsDefaultVisible;
+ protected List<List<T>> sectionsExtras;
+ protected int expandToggleRes;
+ protected int collapseToggleRes;
+
+ protected ExpandableSectionAdapter() {}
+ public ExpandableSectionAdapter(Context context, List<T> section) {
+ List<List<T>> sections = new ArrayList<>();
+ sections.add(section);
+
+ init(context, Arrays.asList("Section"), sections, Arrays.asList((Integer) null));
+ }
+ public ExpandableSectionAdapter(Context context, List<String> headers, List<List<T>> sections) {
+ init(context, headers, sections, null);
+ }
+ public ExpandableSectionAdapter(Context context, List<String> headers, List<List<T>> sections, List<Integer> sectionsDefaultVisible) {
+ init(context, headers, sections, sectionsDefaultVisible);
+ }
+ protected void init(Context context, List<String> headers, List<List<T>> fullSections, List<Integer> sectionsDefaultVisible) {
+ this.context = context;
+ this.headers = headers;
+ this.sectionsDefaultVisible = sectionsDefaultVisible;
+ if(sectionsDefaultVisible == null) {
+ sectionsDefaultVisible = new ArrayList<>(fullSections.size());
+ for(int i = 0; i < fullSections.size(); i++) {
+ sectionsDefaultVisible.add(DEFAULT_VISIBLE);
+ }
+ }
+
+ this.sections = new ArrayList<>();
+ this.sectionsExtras = new ArrayList<>();
+ int i = 0;
+ for(List<T> fullSection: fullSections) {
+ List<T> visibleSection = new ArrayList<>();
+
+ Integer defaultVisible = sectionsDefaultVisible.get(i);
+ if(defaultVisible == null || defaultVisible >= fullSection.size()) {
+ visibleSection.addAll(fullSection);
+ this.sectionsExtras.add(null);
+ } else {
+ visibleSection.addAll(fullSection.subList(0, defaultVisible));
+ this.sectionsExtras.add(fullSection.subList(defaultVisible, fullSection.size()));
+ }
+ this.sections.add(visibleSection);
+
+ i++;
+ }
+
+ expandToggleRes = DrawableTint.getDrawableRes(context, EXPAND_TOGGLE);
+ collapseToggleRes = DrawableTint.getDrawableRes(context, COLLAPSE_TOGGLE);
+ }
+
+ @Override
+ public UpdateView.UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) {
+ return new UpdateView.UpdateViewHolder(new BasicHeaderView(context, R.layout.expandable_header));
+ }
+
+ @Override
+ public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String header, final int sectionIndex) {
+ UpdateView view = holder.getUpdateView();
+ ImageView toggleSelectionView = (ImageView) view.findViewById(R.id.item_select);
+
+ List<T> visibleSelection = sections.get(sectionIndex);
+ List<T> sectionExtras = sectionsExtras.get(sectionIndex);
+
+ if(sectionExtras != null && !sectionExtras.isEmpty()) {
+ toggleSelectionView.setVisibility(View.VISIBLE);
+ toggleSelectionView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ List<T> visibleSelection = sections.get(sectionIndex);
+ List<T> sectionExtras = sectionsExtras.get(sectionIndex);
+
+ // Update icon
+ int selectToggleAttr;
+ if (!visibleSelection.contains(sectionExtras.get(0))) {
+ selectToggleAttr = COLLAPSE_TOGGLE;
+
+ // Update how many are displayed
+ int lastIndex = getItemPosition(visibleSelection.get(visibleSelection.size() - 1));
+ visibleSelection.addAll(sectionExtras);
+ notifyItemRangeInserted(lastIndex, sectionExtras.size());
+ } else {
+ selectToggleAttr = EXPAND_TOGGLE;
+
+ // Update how many are displayed
+ visibleSelection.removeAll(sectionExtras);
+ int lastIndex = getItemPosition(visibleSelection.get(visibleSelection.size() - 1));
+ notifyItemRangeRemoved(lastIndex, sectionExtras.size());
+ }
+
+ ((ImageView) v).setImageResource(DrawableTint.getDrawableRes(context, selectToggleAttr));
+ }
+ });
+
+ int selectToggleAttr;
+ if (!visibleSelection.contains(sectionExtras.get(0))) {
+ selectToggleAttr = EXPAND_TOGGLE;
+ } else {
+ selectToggleAttr = COLLAPSE_TOGGLE;
+ }
+
+ toggleSelectionView.setImageResource(DrawableTint.getDrawableRes(context, selectToggleAttr));
+ } else {
+ toggleSelectionView.setVisibility(View.GONE);
+ }
+
+ if(view != null) {
+ view.setObject(header);
+ }
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/InternetRadioStationAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/InternetRadioStationAdapter.java
new file mode 100644
index 00000000..9d47d70c
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/InternetRadioStationAdapter.java
@@ -0,0 +1,56 @@
+/*
+ This file is part of Subsonic.
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+ Copyright 2016 (C) Scott Jackson
+*/
+package github.daneren2005.dsub.adapter;
+
+import android.content.Context;
+import android.view.ViewGroup;
+
+import java.util.List;
+
+import github.daneren2005.dsub.domain.InternetRadioStation;
+import github.daneren2005.dsub.view.FastScroller;
+import github.daneren2005.dsub.view.InternetRadioStationView;
+import github.daneren2005.dsub.view.UpdateView;
+
+public class InternetRadioStationAdapter extends SectionAdapter<InternetRadioStation> implements FastScroller.BubbleTextGetter {
+ public static int VIEW_TYPE_INTERNET_RADIO_STATION = 1;
+
+ public InternetRadioStationAdapter(Context context, List<InternetRadioStation> stations, OnItemClickedListener listener) {
+ super(context, stations);
+ this.onItemClickedListener = listener;
+ }
+
+ @Override
+ public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
+ return new UpdateView.UpdateViewHolder(new InternetRadioStationView(context));
+ }
+
+ @Override
+ public void onBindViewHolder(UpdateView.UpdateViewHolder holder, InternetRadioStation station, int viewType) {
+ holder.getUpdateView().setObject(station);
+ holder.setItem(station);
+ }
+
+ @Override
+ public int getItemViewType(InternetRadioStation station) {
+ return VIEW_TYPE_INTERNET_RADIO_STATION;
+ }
+
+ @Override
+ public String getTextToShowInBubble(int position) {
+ InternetRadioStation item = getItemForPosition(position);
+ return item.getTitle();
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/MainAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/MainAdapter.java
index 8f1f1c38..dd70aa99 100644
--- a/app/src/main/java/github/daneren2005/dsub/adapter/MainAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/MainAdapter.java
@@ -77,7 +77,7 @@ public class MainAdapter extends SectionAdapter<Integer> {
return new UpdateView.UpdateViewHolder(new BasicHeaderView(context, R.layout.album_list_header));
}
@Override
- public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String header) {
+ public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String header, int sectionIndex) {
UpdateView view = holder.getUpdateView();
CheckBox checkBox = (CheckBox) view.findViewById(R.id.item_checkbox);
@@ -100,6 +100,9 @@ public class MainAdapter extends SectionAdapter<Integer> {
} else if("videos".equals(header)) {
display = context.getResources().getString(R.string.main_videos);
checkBox.setVisibility(View.GONE);
+ } else if("songs".equals(header)) {
+ display = context.getResources().getString(R.string.search_songs);
+ checkBox.setVisibility(View.GONE);
} else {
display = header;
checkBox.setVisibility(View.GONE);
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java
index a95abeda..f843a722 100644
--- a/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java
@@ -34,21 +34,17 @@ import github.daneren2005.dsub.view.SongView;
import github.daneren2005.dsub.view.UpdateView;
import java.io.Serializable;
+import java.util.Arrays;
import java.util.List;
-public class PodcastChannelAdapter extends SectionAdapter<Serializable> implements FastScroller.BubbleTextGetter {
+public class PodcastChannelAdapter extends ExpandableSectionAdapter<Serializable> implements FastScroller.BubbleTextGetter {
public static final int VIEW_TYPE_PODCAST_LEGACY = 1;
public static final int VIEW_TYPE_PODCAST_LINE = 2;
public static final int VIEW_TYPE_PODCAST_CELL = 3;
public static final int VIEW_TYPE_PODCAST_EPISODE = 4;
- public static final String EPISODE_HEADER = "episodes";
- public static final String CHANNEL_HEADER = "channels";
-
private ImageLoader imageLoader;
private boolean largeCell;
- private int selectToggleAttr = R.attr.select_server;
- private List<Serializable> extraEpisodes;
public PodcastChannelAdapter(Context context, List<Serializable> podcasts, ImageLoader imageLoader, OnItemClickedListener listener, boolean largeCell) {
super(context, podcasts);
@@ -56,9 +52,8 @@ public class PodcastChannelAdapter extends SectionAdapter<Serializable> implemen
this.onItemClickedListener = listener;
this.largeCell = largeCell;
}
- public PodcastChannelAdapter(Context context, List<String> headers, List<List<Serializable>> sections, List<Serializable> extraEpisodes, ImageLoader imageLoader, OnItemClickedListener listener, boolean largeCell) {
- super(context, headers, sections);
- this.extraEpisodes = extraEpisodes;
+ public PodcastChannelAdapter(Context context, List<String> headers, List<List<Serializable>> sections, ImageLoader imageLoader, OnItemClickedListener listener, boolean largeCell) {
+ super(context, headers, sections, Arrays.asList(3, null));
this.imageLoader = imageLoader;
this.onItemClickedListener = listener;
this.largeCell = largeCell;
@@ -126,58 +121,6 @@ public class PodcastChannelAdapter extends SectionAdapter<Serializable> implemen
}
menu.removeItem(R.id.menu_remove_playlist);
- menu.removeItem(R.id.menu_unstar);
- }
-
- @Override
- public UpdateView.UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) {
- return new UpdateView.UpdateViewHolder(new BasicHeaderView(context, R.layout.newest_episode_header));
- }
-
- @Override
- public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String header) {
- UpdateView view = holder.getUpdateView();
- ImageView toggleSelectionView = (ImageView) view.findViewById(R.id.item_select);
-
- String display;
- if(EPISODE_HEADER.equals(header)) {
- display = context.getResources().getString(R.string.main_albums_newest);
-
- if(extraEpisodes != null && !extraEpisodes.isEmpty()) {
- toggleSelectionView.setVisibility(View.VISIBLE);
- toggleSelectionView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Update icon
- if (selectToggleAttr == R.attr.select_server) {
- selectToggleAttr = R.attr.select_tabs;
-
- // Update how many are displayed
- sections.get(0).addAll(extraEpisodes);
- notifyItemRangeInserted(4, extraEpisodes.size());
- } else {
- selectToggleAttr = R.attr.select_server;
-
- // Update how many are displayed
- sections.get(0).removeAll(extraEpisodes);
- notifyItemRangeRemoved(4, extraEpisodes.size());
- }
-
- ((ImageView) v).setImageResource(DrawableTint.getDrawableRes(context, selectToggleAttr));
-
- }
- });
- toggleSelectionView.setImageResource(DrawableTint.getDrawableRes(context, selectToggleAttr));
- } else {
- toggleSelectionView.setVisibility(View.GONE);
- }
- } else {
- display = context.getResources().getString(R.string.select_podcasts_channels);
- toggleSelectionView.setVisibility(View.GONE);
- }
-
- if(view != null) {
- view.setObject(display);
- }
+ menu.removeItem(R.id.menu_star);
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/SearchAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/SearchAdapter.java
index 66f2db21..69e5d56d 100644
--- a/app/src/main/java/github/daneren2005/dsub/adapter/SearchAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/SearchAdapter.java
@@ -19,7 +19,9 @@ import android.content.Context;
import android.content.res.Resources;
import android.view.Menu;
import android.view.MenuInflater;
+import android.view.View;
import android.view.ViewGroup;
+import android.widget.ImageView;
import java.io.Serializable;
import java.util.ArrayList;
@@ -28,10 +30,12 @@ import java.util.List;
import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.MusicDirectory.Entry;
import github.daneren2005.dsub.domain.SearchResult;
+import github.daneren2005.dsub.util.DrawableTint;
import github.daneren2005.dsub.util.ImageLoader;
import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.view.AlbumView;
import github.daneren2005.dsub.view.ArtistView;
+import github.daneren2005.dsub.view.BasicHeaderView;
import github.daneren2005.dsub.view.SongView;
import github.daneren2005.dsub.view.UpdateView;
@@ -40,32 +44,39 @@ import static github.daneren2005.dsub.adapter.EntryGridAdapter.VIEW_TYPE_ALBUM_C
import static github.daneren2005.dsub.adapter.EntryGridAdapter.VIEW_TYPE_ALBUM_LINE;
import static github.daneren2005.dsub.adapter.EntryGridAdapter.VIEW_TYPE_SONG;
-public class SearchAdapter extends SectionAdapter<Serializable> {
- private SearchResult searchResult;
+public class SearchAdapter extends ExpandableSectionAdapter<Serializable> {
private ImageLoader imageLoader;
private boolean largeAlbums;
+ private static final int MAX_ARTISTS = 10;
+ private static final int MAX_ALBUMS = 4;
+ private static final int MAX_SONGS = 10;
+
public SearchAdapter(Context context, SearchResult searchResult, ImageLoader imageLoader, boolean largeAlbums, OnItemClickedListener listener) {
- this.context = context;
- this.searchResult = searchResult;
this.imageLoader = imageLoader;
this.largeAlbums = largeAlbums;
- this.sections = new ArrayList<>();
- this.headers = new ArrayList<>();
+ List<List<Serializable>> sections = new ArrayList<>();
+ List<String> headers = new ArrayList<>();
+ List<Integer> defaultVisible = new ArrayList<>();
Resources res = context.getResources();
if(!searchResult.getArtists().isEmpty()) {
- this.sections.add((List<Serializable>) (List<?>) searchResult.getArtists());
- this.headers.add(res.getString(R.string.search_artists));
+ sections.add((List<Serializable>) (List<?>) searchResult.getArtists());
+ headers.add(res.getString(R.string.search_artists));
+ defaultVisible.add(MAX_ARTISTS);
}
if(!searchResult.getAlbums().isEmpty()) {
- this.sections.add((List<Serializable>) (List<?>) searchResult.getAlbums());
- this.headers.add(res.getString(R.string.search_albums));
+ sections.add((List<Serializable>) (List<?>) searchResult.getAlbums());
+ headers.add(res.getString(R.string.search_albums));
+ defaultVisible.add(MAX_ALBUMS);
}
if(!searchResult.getSongs().isEmpty()) {
- this.sections.add((List<Serializable>) (List<?>) searchResult.getSongs());
- this.headers.add(res.getString(R.string.search_songs));
+ sections.add((List<Serializable>) (List<?>) searchResult.getSongs());
+ headers.add(res.getString(R.string.search_songs));
+ defaultVisible.add(MAX_SONGS);
}
+ init(context, headers, sections, defaultVisible);
+
this.onItemClickedListener = listener;
checkable = true;
}
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/SectionAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/SectionAdapter.java
index d5f9a6ea..33bbb384 100644
--- a/app/src/main/java/github/daneren2005/dsub/adapter/SectionAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/SectionAdapter.java
@@ -194,7 +194,7 @@ public abstract class SectionAdapter<T> extends RecyclerView.Adapter<UpdateViewH
for(List<T> section: sections) {
boolean validHeader = headers.get(subHeader) != null;
if(position == subPosition && validHeader) {
- onBindHeaderHolder(holder, headers.get(subHeader));
+ onBindHeaderHolder(holder, headers.get(subHeader), subHeader);
return;
}
@@ -289,7 +289,7 @@ public abstract class SectionAdapter<T> extends RecyclerView.Adapter<UpdateViewH
public UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) {
return new UpdateViewHolder(new BasicHeaderView(context));
}
- public void onBindHeaderHolder(UpdateViewHolder holder, String header) {
+ public void onBindHeaderHolder(UpdateViewHolder holder, String header, int sectionIndex) {
UpdateView view = holder.getUpdateView();
if(view != null) {
view.setObject(header);
@@ -414,14 +414,15 @@ public abstract class SectionAdapter<T> extends RecyclerView.Adapter<UpdateViewH
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
currentActionMode = mode;
- onCreateActionModeMenu(menu, mode.getMenuInflater());
- MenuUtil.hideMenuItems(context, menu, updateView);
T item = holder.getItem();
selected.add(item);
selectedViews.add(updateView);
setChecked(updateView, true);
+ onCreateActionModeMenu(menu, mode.getMenuInflater());
+ MenuUtil.hideMenuItems(context, menu, updateView);
+
mode.setTitle(context.getResources().getString(R.string.select_album_n_selected, selected.size()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) {
TypedValue typedValue = new TypedValue();
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/SettingsAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/SettingsAdapter.java
index 1cb9c34e..4e75a2f7 100644
--- a/app/src/main/java/github/daneren2005/dsub/adapter/SettingsAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/SettingsAdapter.java
@@ -16,18 +16,20 @@
package github.daneren2005.dsub.adapter;
import android.content.Context;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
import android.widget.TextView;
+import java.util.ArrayList;
import java.util.List;
import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.ServerInfo;
import github.daneren2005.dsub.domain.User;
import github.daneren2005.dsub.util.ImageLoader;
+import github.daneren2005.dsub.util.UserUtil;
+import github.daneren2005.dsub.view.BasicHeaderView;
import github.daneren2005.dsub.view.RecyclingImageView;
import github.daneren2005.dsub.view.SettingView;
import github.daneren2005.dsub.view.UpdateView;
@@ -37,56 +39,81 @@ import static github.daneren2005.dsub.domain.User.Setting;
public class SettingsAdapter extends SectionAdapter<Setting> {
private static final String TAG = SettingsAdapter.class.getSimpleName();
public final int VIEW_TYPE_SETTING = 1;
+ public final int VIEW_TYPE_SETTING_HEADER = 2;
private final User user;
private final boolean editable;
private final ImageLoader imageLoader;
- public SettingsAdapter(Context context, User user, ImageLoader imageLoader, boolean editable, OnItemClickedListener<Setting> onItemClickedListener) {
- super(context, user.getSettings(), imageLoader != null);
+ public SettingsAdapter(Context context, User user, List<String> headers, List<List<User.Setting>> settingSections, ImageLoader imageLoader, boolean editable, OnItemClickedListener<Setting> onItemClickedListener) {
+ super(context, headers, settingSections, imageLoader != null);
this.user = user;
this.imageLoader = imageLoader;
this.editable = editable;
this.onItemClickedListener = onItemClickedListener;
- List<Setting> settings = sections.get(0);
- for(Setting setting: settings) {
- if(setting.getValue()) {
- addSelected(setting);
+ for(List<Setting> settings: sections) {
+ for (Setting setting : settings) {
+ if (setting.getValue()) {
+ addSelected(setting);
+ }
}
}
}
+ @Override
+ public int getItemViewType(int position) {
+ int viewType = super.getItemViewType(position);
+ if(viewType == SectionAdapter.VIEW_TYPE_HEADER) {
+ if(position == 0 && imageLoader != null) {
+ return VIEW_TYPE_HEADER;
+ } else {
+ return VIEW_TYPE_SETTING_HEADER;
+ }
+ } else {
+ return viewType;
+ }
+ }
+
public UpdateView.UpdateViewHolder onCreateHeaderHolder(ViewGroup parent) {
View header = LayoutInflater.from(context).inflate(R.layout.user_header, parent, false);
return new UpdateView.UpdateViewHolder(header, false);
}
- public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String description) {
+ public void onBindHeaderHolder(UpdateView.UpdateViewHolder holder, String description, int sectionIndex) {
View header = holder.getView();
RecyclingImageView coverArtView = (RecyclingImageView) header.findViewById(R.id.user_avatar);
- imageLoader.loadAvatar(context, coverArtView, user.getUsername());
- coverArtView.setOnInvalidated(new RecyclingImageView.OnInvalidated() {
- @Override
- public void onInvalidated(RecyclingImageView imageView) {
- imageLoader.loadAvatar(context, imageView, user.getUsername());
+ if(coverArtView != null) {
+ imageLoader.loadAvatar(context, coverArtView, user.getUsername());
+ coverArtView.setOnInvalidated(new RecyclingImageView.OnInvalidated() {
+ @Override
+ public void onInvalidated(RecyclingImageView imageView) {
+ imageLoader.loadAvatar(context, imageView, user.getUsername());
+ }
+ });
+
+ TextView usernameView = (TextView) header.findViewById(R.id.user_username);
+ usernameView.setText(user.getUsername());
+
+ final TextView emailView = (TextView) header.findViewById(R.id.user_email);
+ if (user.getEmail() != null) {
+ emailView.setText(user.getEmail());
+ } else {
+ emailView.setVisibility(View.GONE);
}
- });
-
- TextView usernameView = (TextView) header.findViewById(R.id.user_username);
- usernameView.setText(user.getUsername());
-
- final TextView emailView = (TextView) header.findViewById(R.id.user_email);
- if(user.getEmail() != null) {
- emailView.setText(user.getEmail());
} else {
- emailView.setVisibility(View.GONE);
+ TextView nameView = (TextView) header.findViewById(R.id.item_name);
+ nameView.setText(description);
}
}
@Override
public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
- return new UpdateView.UpdateViewHolder(new SettingView(context));
+ if(viewType == VIEW_TYPE_SETTING_HEADER) {
+ return new UpdateView.UpdateViewHolder(new BasicHeaderView(context));
+ } else {
+ return new UpdateView.UpdateViewHolder(new SettingView(context));
+ }
}
@Override
@@ -105,4 +132,21 @@ public class SettingsAdapter extends SectionAdapter<Setting> {
updateView.setChecked(checked);
}
}
+
+ public static SettingsAdapter getSettingsAdapter(Context context, User user, ImageLoader imageLoader, OnItemClickedListener<Setting> onItemClickedListener) {
+ return getSettingsAdapter(context, user, imageLoader, UserUtil.isCurrentAdmin() && ServerInfo.checkServerVersion(context, "1.10"), onItemClickedListener);
+ }
+ public static SettingsAdapter getSettingsAdapter(Context context, User user, ImageLoader imageLoader, boolean isEditable, OnItemClickedListener<Setting> onItemClickedListener) {
+ List<String> headers = new ArrayList<>();
+ List<List<User.Setting>> settingsSections = new ArrayList<>();
+ headers.add(context.getResources().getString(R.string.admin_permissions));
+ settingsSections.add(user.getSettings());
+
+ if(user.getMusicFolderSettings() != null) {
+ headers.add(context.getResources().getString(R.string.admin_musicFolders));
+ settingsSections.add(user.getMusicFolderSettings());
+ }
+
+ return new SettingsAdapter(context, user, headers, settingsSections, imageLoader, isEditable, onItemClickedListener);
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/SimilarArtistAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/SimilarArtistAdapter.java
new file mode 100644
index 00000000..2234d4cd
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/SimilarArtistAdapter.java
@@ -0,0 +1,53 @@
+/*
+ This file is part of Subsonic.
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+ Copyright 2016 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.adapter;
+
+import android.content.Context;
+import android.view.ViewGroup;
+
+import java.util.List;
+
+import github.daneren2005.dsub.domain.Artist;
+import github.daneren2005.dsub.view.ArtistView;
+import github.daneren2005.dsub.view.UpdateView;
+
+public class SimilarArtistAdapter extends SectionAdapter<Artist> {
+ public static int VIEW_TYPE_ARTIST = 4;
+
+ public SimilarArtistAdapter(Context context, List<Artist> artists, OnItemClickedListener onItemClickedListener) {
+ super(context, artists);
+ this.onItemClickedListener = onItemClickedListener;
+ }
+ public SimilarArtistAdapter(Context context, List<String> headers, List<List<Artist>> sections, OnItemClickedListener onItemClickedListener) {
+ super(context, headers, sections);
+ this.onItemClickedListener = onItemClickedListener;
+ }
+
+ @Override
+ public UpdateView.UpdateViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
+ return new UpdateView.UpdateViewHolder(new ArtistView(context));
+ }
+
+ @Override
+ public void onBindViewHolder(UpdateView.UpdateViewHolder holder, Artist item, int viewType) {
+ holder.getUpdateView().setObject(item);
+ }
+
+ @Override
+ public int getItemViewType(Artist item) {
+ return VIEW_TYPE_ARTIST;
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/domain/Artist.java b/app/src/main/java/github/daneren2005/dsub/domain/Artist.java
index 56e8f92e..ff4d86ce 100644
--- a/app/src/main/java/github/daneren2005/dsub/domain/Artist.java
+++ b/app/src/main/java/github/daneren2005/dsub/domain/Artist.java
@@ -21,15 +21,19 @@ package github.daneren2005.dsub.domain;
import android.util.Log;
import java.io.Serializable;
+import java.text.Collator;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Locale;
/**
* @author Sindre Mehus
*/
public class Artist implements Serializable {
private static final String TAG = Artist.class.getSimpleName();
+ public static final String ROOT_ID = "-1";
+ public static final String MISSING_ID = "-2";
private String id;
private String name;
@@ -38,6 +42,14 @@ public class Artist implements Serializable {
private Integer rating;
private int closeness;
+ public Artist() {
+
+ }
+ public Artist(String id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
public String getId() {
return id;
}
@@ -109,30 +121,18 @@ public class Artist implements Serializable {
public static class ArtistComparator implements Comparator<Artist> {
private String[] ignoredArticles;
+ private Collator collator;
public ArtistComparator(String[] ignoredArticles) {
this.ignoredArticles = ignoredArticles;
+ this.collator = Collator.getInstance(Locale.US);
+ this.collator.setStrength(Collator.PRIMARY);
}
public int compare(Artist lhsArtist, Artist rhsArtist) {
- if("root".equals(lhsArtist.getId())) {
- return 1;
- } else if("root".equals(rhsArtist.getId())) {
- return -1;
- }
-
String lhs = lhsArtist.getName().toLowerCase();
String rhs = rhsArtist.getName().toLowerCase();
- char lhs1 = lhs.charAt(0);
- char rhs1 = rhs.charAt(0);
-
- if (Character.isDigit(lhs1) && !Character.isDigit(rhs1)) {
- return 1;
- } else if (Character.isDigit(rhs1) && !Character.isDigit(lhs1)) {
- return -1;
- }
-
for (String article : ignoredArticles) {
int index = lhs.indexOf(article.toLowerCase() + " ");
if (index == 0) {
@@ -144,7 +144,7 @@ public class Artist implements Serializable {
}
}
- return lhs.compareTo(rhs);
+ return collator.compare(lhs, rhs);
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/domain/Indexes.java b/app/src/main/java/github/daneren2005/dsub/domain/Indexes.java
index e15ccf9f..05e686ca 100644
--- a/app/src/main/java/github/daneren2005/dsub/domain/Indexes.java
+++ b/app/src/main/java/github/daneren2005/dsub/domain/Indexes.java
@@ -52,13 +52,6 @@ public class Indexes implements Serializable {
this.shortcuts = shortcuts;
this.artists = artists;
this.entries = entries;
- if(!entries.isEmpty()) {
- Artist root = new Artist();
- root.setId("root");
- root.setName("Root");
- root.setIndex("#");
- artists.add(root);
- }
}
public long getLastModified() {
diff --git a/app/src/main/java/github/daneren2005/dsub/domain/InternetRadioStation.java b/app/src/main/java/github/daneren2005/dsub/domain/InternetRadioStation.java
new file mode 100644
index 00000000..47d79b99
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/domain/InternetRadioStation.java
@@ -0,0 +1,43 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2016 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.domain;
+
+public class InternetRadioStation extends MusicDirectory.Entry {
+ private String streamUrl;
+ private String homePageUrl;
+
+ public InternetRadioStation() {}
+
+ public String getStreamUrl() {
+ return streamUrl;
+ }
+
+ public void setStreamUrl(String streamUrl) {
+ this.streamUrl = streamUrl;
+ }
+
+ public String getHomePageUrl() {
+ return homePageUrl;
+ }
+
+ public void setHomePageUrl(String homePageUrl) {
+ this.homePageUrl = homePageUrl;
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/domain/MusicDirectory.java b/app/src/main/java/github/daneren2005/dsub/domain/MusicDirectory.java
index 3c022cea..5f7b2412 100644
--- a/app/src/main/java/github/daneren2005/dsub/domain/MusicDirectory.java
+++ b/app/src/main/java/github/daneren2005/dsub/domain/MusicDirectory.java
@@ -24,6 +24,8 @@ import android.content.SharedPreferences;
import android.media.MediaMetadataRetriever;
import android.os.Build;
import android.util.Log;
+
+import java.text.Collator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -31,8 +33,11 @@ import java.io.File;
import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Locale;
+import github.daneren2005.dsub.service.DownloadService;
import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.UpdateHelper;
import github.daneren2005.dsub.util.Util;
/**
@@ -135,13 +140,14 @@ public class MusicDirectory implements Serializable {
EntryComparator.sort(children, byYear);
}
- public synchronized void updateMetadata(MusicDirectory refreshedDirectory) {
+ public synchronized boolean updateMetadata(MusicDirectory refreshedDirectory) {
+ boolean metadataUpdated = false;
Iterator<Entry> it = children.iterator();
while(it.hasNext()) {
Entry entry = it.next();
int index = refreshedDirectory.children.indexOf(entry);
if(index != -1) {
- Entry refreshed = refreshedDirectory.children.get(index);
+ final Entry refreshed = refreshedDirectory.children.get(index);
entry.setTitle(refreshed.getTitle());
entry.setAlbum(refreshed.getAlbum());
@@ -155,8 +161,36 @@ public class MusicDirectory implements Serializable {
entry.setStarred(refreshed.isStarred());
entry.setRating(refreshed.getRating());
entry.setType(refreshed.getType());
+ if(!Util.equals(entry.getCoverArt(), refreshed.getCoverArt())) {
+ metadataUpdated = true;
+ entry.setCoverArt(refreshed.getCoverArt());
+ }
+
+ new UpdateHelper.EntryInstanceUpdater(entry) {
+ @Override
+ public void update(Entry found) {
+ found.setTitle(refreshed.getTitle());
+ found.setAlbum(refreshed.getAlbum());
+ found.setArtist(refreshed.getArtist());
+ found.setTrack(refreshed.getTrack());
+ found.setYear(refreshed.getYear());
+ found.setGenre(refreshed.getGenre());
+ found.setTranscodedContentType(refreshed.getTranscodedContentType());
+ found.setTranscodedSuffix(refreshed.getTranscodedSuffix());
+ found.setDiscNumber(refreshed.getDiscNumber());
+ found.setStarred(refreshed.isStarred());
+ found.setRating(refreshed.getRating());
+ found.setType(refreshed.getType());
+ if(!Util.equals(found.getCoverArt(), refreshed.getCoverArt())) {
+ found.setCoverArt(refreshed.getCoverArt());
+ metadataUpdate = DownloadService.METADATA_UPDATED_COVER_ART;
+ }
+ }
+ }.execute();
}
}
+
+ return metadataUpdated;
}
public synchronized boolean updateEntriesList(Context context, int instance, MusicDirectory refreshedDirectory) {
boolean changed = false;
@@ -202,6 +236,7 @@ public class MusicDirectory implements Serializable {
private String album;
private String artist;
private Integer track;
+ private Integer customOrder;
private Integer year;
private String genre;
private String contentType;
@@ -275,6 +310,10 @@ public class MusicDirectory implements Serializable {
public void rebaseTitleOffPath() {
try {
String filename = getPath();
+ if(filename == null) {
+ return;
+ }
+
int index = filename.lastIndexOf('/');
if (index != -1) {
filename = filename.substring(index + 1);
@@ -386,6 +425,13 @@ public class MusicDirectory implements Serializable {
this.track = track;
}
+ public Integer getCustomOrder() {
+ return customOrder;
+ }
+ public void setCustomOrder(Integer customOrder) {
+ this.customOrder = customOrder;
+ }
+
public Integer getYear() {
return year;
}
@@ -586,9 +632,12 @@ public class MusicDirectory implements Serializable {
public static class EntryComparator implements Comparator<Entry> {
private boolean byYear;
+ private Collator collator;
public EntryComparator(boolean byYear) {
this.byYear = byYear;
+ this.collator = Collator.getInstance(Locale.US);
+ this.collator.setStrength(Collator.PRIMARY);
}
public int compare(Entry lhs, Entry rhs) {
@@ -608,8 +657,8 @@ public class MusicDirectory implements Serializable {
return 1;
}
}
-
- return lhs.getAlbumDisplay().compareToIgnoreCase(rhs.getAlbumDisplay());
+
+ return collator.compare(lhs.getAlbumDisplay(), rhs.getAlbumDisplay());
}
Integer lhsDisc = lhs.getDiscNumber();
@@ -633,7 +682,7 @@ public class MusicDirectory implements Serializable {
return 1;
}
- return lhs.getTitle().compareToIgnoreCase(rhs.getTitle());
+ return collator.compare(lhs.getTitle(), rhs.getTitle());
}
public static void sort(List<Entry> entries) {
diff --git a/app/src/main/java/github/daneren2005/dsub/domain/MusicFolder.java b/app/src/main/java/github/daneren2005/dsub/domain/MusicFolder.java
index 99e86e23..37f76249 100644
--- a/app/src/main/java/github/daneren2005/dsub/domain/MusicFolder.java
+++ b/app/src/main/java/github/daneren2005/dsub/domain/MusicFolder.java
@@ -18,7 +18,12 @@
*/
package github.daneren2005.dsub.domain;
+import android.util.Log;
+
import java.io.Serializable;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
/**
* Represents a top level directory in which music or other media is stored.
@@ -27,23 +32,49 @@ import java.io.Serializable;
* @version $Id$
*/
public class MusicFolder implements Serializable {
-
- private String id;
- private String name;
+ private static final String TAG = MusicFolder.class.getSimpleName();
+ private String id;
+ private String name;
+ private boolean enabled;
public MusicFolder() {
}
- public MusicFolder(String id, String name) {
- this.id = id;
- this.name = name;
- }
+ public MusicFolder(String id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+ public boolean getEnabled() {
+ return enabled;
+ }
- public String getId() {
- return id;
- }
+ public static class MusicFolderComparator implements Comparator<MusicFolder> {
+ public int compare(MusicFolder lhsMusicFolder, MusicFolder rhsMusicFolder) {
+ if(lhsMusicFolder == rhsMusicFolder || lhsMusicFolder.getName().equals(rhsMusicFolder.getName())) {
+ return 0;
+ } else {
+ return lhsMusicFolder.getName().compareToIgnoreCase(rhsMusicFolder.getName());
+ }
+ }
+ }
- public String getName() {
- return name;
- }
+ public static void sort(List<MusicFolder> musicFolders) {
+ try {
+ Collections.sort(musicFolders, new MusicFolderComparator());
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to sort music folders", e);
+ }
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/domain/SearchCritera.java b/app/src/main/java/github/daneren2005/dsub/domain/SearchCritera.java
index 20d46aa0..ed2400ef 100644
--- a/app/src/main/java/github/daneren2005/dsub/domain/SearchCritera.java
+++ b/app/src/main/java/github/daneren2005/dsub/domain/SearchCritera.java
@@ -18,6 +18,8 @@
*/
package github.daneren2005.dsub.domain;
+import java.util.regex.Pattern;
+
/**
* The criteria for a music search.
*
@@ -25,31 +27,67 @@ package github.daneren2005.dsub.domain;
*/
public class SearchCritera {
- private final String query;
- private final int artistCount;
- private final int albumCount;
- private final int songCount;
-
- public SearchCritera(String query, int artistCount, int albumCount, int songCount) {
- this.query = query;
- this.artistCount = artistCount;
- this.albumCount = albumCount;
- this.songCount = songCount;
- }
-
- public String getQuery() {
- return query;
- }
-
- public int getArtistCount() {
- return artistCount;
- }
-
- public int getAlbumCount() {
- return albumCount;
- }
-
- public int getSongCount() {
- return songCount;
- }
-} \ No newline at end of file
+ private final String query;
+ private final int artistCount;
+ private final int albumCount;
+ private final int songCount;
+ private Pattern pattern;
+
+ public SearchCritera(String query, int artistCount, int albumCount, int songCount) {
+ this.query = query;
+ this.artistCount = artistCount;
+ this.albumCount = albumCount;
+ this.songCount = songCount;
+ }
+
+ public String getQuery() {
+ return query;
+ }
+
+ public int getArtistCount() {
+ return artistCount;
+ }
+
+ public int getAlbumCount() {
+ return albumCount;
+ }
+
+ public int getSongCount() {
+ return songCount;
+ }
+
+ /**
+ * Returns and caches a pattern instance that can be used to check if a
+ * string matches the query.
+ */
+ public Pattern getPattern() {
+
+ // If the pattern wasn't already cached, create a new regular expression
+ // from the search string :
+ // * Surround the search string with ".*" (match anything)
+ // * Replace spaces and wildcard '*' characters with ".*"
+ // * All other characters are properly quoted
+ if (this.pattern == null) {
+ String regex = ".*";
+ String currentPart = "";
+ for (int i = 0; i < query.length(); i++) {
+ char c = query.charAt(i);
+ if (c == '*' || c == ' ') {
+ regex += Pattern.quote(currentPart);
+ regex += ".*";
+ currentPart = "";
+ } else {
+ currentPart += c;
+ }
+ }
+ if (currentPart.length() > 0) {
+ regex += Pattern.quote(currentPart);
+ }
+
+ regex += ".*";
+ this.pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
+ }
+
+ return this.pattern;
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/domain/ServerInfo.java b/app/src/main/java/github/daneren2005/dsub/domain/ServerInfo.java
index 73037c4a..5852210e 100644
--- a/app/src/main/java/github/daneren2005/dsub/domain/ServerInfo.java
+++ b/app/src/main/java/github/daneren2005/dsub/domain/ServerInfo.java
@@ -24,6 +24,7 @@ import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.FileUtil;
import github.daneren2005.dsub.util.Util;
@@ -35,6 +36,7 @@ import github.daneren2005.dsub.util.Util;
public class ServerInfo implements Serializable {
public static final int TYPE_SUBSONIC = 1;
public static final int TYPE_MADSONIC = 2;
+ public static final int TYPE_AMPACHE = 3;
private static final Map<Integer, ServerInfo> SERVERS = new ConcurrentHashMap<Integer, ServerInfo>();
private boolean isLicenseValid;
@@ -189,13 +191,20 @@ public class ServerInfo implements Serializable {
public static boolean isMadsonic6(Context context, int instance) {
return getServerType(context, instance) == TYPE_MADSONIC && checkServerVersion(context, "2.0", instance);
}
+
+ public static boolean isAmpache(Context context) {
+ return isAmpache(context, Util.getActiveServer(context));
+ }
+ public static boolean isAmpache(Context context, int instance) {
+ return getServerType(context, instance) == TYPE_AMPACHE;
+ }
private static String getCacheName(Context context, int instance) {
return "server-" + Util.getRestUrl(context, null, instance, false).hashCode() + ".ser";
}
public static boolean hasArtistInfo(Context context) {
- if(isStockSubsonic(context) && ServerInfo.checkServerVersion(context, "1.11")) {
+ if(!isMadsonic(context) && ServerInfo.checkServerVersion(context, "1.11")) {
return true;
} else if(isMadsonic(context)) {
return checkServerVersion(context, "2.0");
@@ -207,6 +216,9 @@ public class ServerInfo implements Serializable {
public static boolean canBookmark(Context context) {
return checkServerVersion(context, "1.9");
}
+ public static boolean canInternetRadio(Context context) {
+ return checkServerVersion(context, "1.9");
+ }
public static boolean canSavePlayQueue(Context context) {
return ServerInfo.checkServerVersion(context, "1.12") && (!ServerInfo.isMadsonic(context) || checkServerVersion(context, "2.0"));
@@ -223,8 +235,15 @@ public class ServerInfo implements Serializable {
return canUseToken(context, Util.getActiveServer(context));
}
public static boolean canUseToken(Context context, int instance) {
- return false; /*isStockSubsonic(context, instance) && checkServerVersion(context, "1.13", instance) ||
- isMadsonic(context, instance) && checkServerVersion(context, "2.0", instance);*/
+ if(isStockSubsonic(context, instance) && checkServerVersion(context, "1.14", instance)) {
+ if(Util.getBlockTokenUse(context, instance)) {
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ return false;
+ }
}
public static boolean hasSimilarArtists(Context context) {
return !ServerInfo.isMadsonic(context) || ServerInfo.checkServerVersion(context, "2.0");
@@ -232,4 +251,9 @@ public class ServerInfo implements Serializable {
public static boolean hasNewestPodcastEpisodes(Context context) {
return ServerInfo.checkServerVersion(context, "1.13");
}
+
+ public static boolean canRescanServer(Context context) {
+ return ServerInfo.isMadsonic(context) ||
+ (ServerInfo.isStockSubsonic(context) && ServerInfo.checkServerVersion(context, "1.15"));
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/domain/User.java b/app/src/main/java/github/daneren2005/dsub/domain/User.java
index 797a1271..5307828a 100644
--- a/app/src/main/java/github/daneren2005/dsub/domain/User.java
+++ b/app/src/main/java/github/daneren2005/dsub/domain/User.java
@@ -15,6 +15,8 @@
package github.daneren2005.dsub.domain;
+import android.util.Pair;
+
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@@ -31,8 +33,9 @@ public class User implements Serializable {
public static final String STREAM = "streamRole";
public static final String JUKEBOX = "jukeboxRole";
public static final String SHARE = "shareRole";
+ public static final String VIDEO_CONVERSION = "videoConversionRole";
public static final String LASTFM = "lastFMRole";
- public static final List<String> ROLES = new ArrayList<String>();
+ public static final List<String> ROLES = new ArrayList<>();
static {
ROLES.add(ADMIN);
@@ -45,6 +48,7 @@ public class User implements Serializable {
ROLES.add(PODCAST);
ROLES.add(JUKEBOX);
ROLES.add(SHARE);
+ ROLES.add(VIDEO_CONVERSION);
}
private String username;
@@ -52,6 +56,7 @@ public class User implements Serializable {
private String email;
private List<Setting> settings = new ArrayList<Setting>();
+ private List<Setting> musicFolders;
public User() {
@@ -92,9 +97,27 @@ public class User implements Serializable {
settings.add(new Setting(name, value));
}
+ public void addMusicFolder(MusicFolder musicFolder) {
+ if(musicFolders == null) {
+ musicFolders = new ArrayList<>();
+ }
+
+ musicFolders.add(new MusicFolderSetting(musicFolder.getId(), musicFolder.getName(), false));
+ }
+ public void addMusicFolder(MusicFolderSetting musicFolderSetting, boolean defaultValue) {
+ if(musicFolders == null) {
+ musicFolders = new ArrayList<>();
+ }
+
+ musicFolders.add(new MusicFolderSetting(musicFolderSetting.getName(), musicFolderSetting.getLabel(), defaultValue));
+ }
+ public List<Setting> getMusicFolderSettings() {
+ return musicFolders;
+ }
+
public static class Setting implements Serializable {
- String name;
- Boolean value;
+ private String name;
+ private Boolean value;
public Setting() {
@@ -114,4 +137,20 @@ public class User implements Serializable {
this.value = value;
}
}
+
+ public static class MusicFolderSetting extends Setting {
+ private String label;
+
+ public MusicFolderSetting() {
+
+ }
+ public MusicFolderSetting(String name, String label, Boolean value) {
+ super(name, value);
+ this.label = label;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/domain/Version.java b/app/src/main/java/github/daneren2005/dsub/domain/Version.java
index 97246ecf..9df0dbb4 100644
--- a/app/src/main/java/github/daneren2005/dsub/domain/Version.java
+++ b/app/src/main/java/github/daneren2005/dsub/domain/Version.java
@@ -27,41 +27,41 @@ import java.io.Serializable;
* @version $Revision: 1.3 $ $Date: 2006/01/20 21:25:16 $
*/
public class Version implements Comparable<Version>, Serializable {
- private int major;
- private int minor;
- private int beta;
- private int bugfix;
+ private int major;
+ private int minor;
+ private int beta;
+ private int bugfix;
public Version() {
// For Kryo
}
- /**
- * Creates a new version instance by parsing the given string.
- * @param version A string of the format "1.27", "1.27.2" or "1.27.beta3".
- */
- public Version(String version) {
- String[] s = version.split("\\.");
- major = Integer.valueOf(s[0]);
- minor = Integer.valueOf(s[1]);
-
- if (s.length > 2) {
- if (s[2].contains("beta")) {
- beta = Integer.valueOf(s[2].replace("beta", ""));
- } else {
- bugfix = Integer.valueOf(s[2]);
- }
- }
- }
-
- public int getMajor() {
- return major;
- }
-
- public int getMinor() {
- return minor;
- }
-
+ /**
+ * Creates a new version instance by parsing the given string.
+ * @param version A string of the format "1.27", "1.27.2" or "1.27.beta3".
+ */
+ public Version(String version) {
+ String[] s = version.split("\\.");
+ major = Integer.valueOf(s[0]);
+ minor = Integer.valueOf(s[1]);
+
+ if (s.length > 2) {
+ if (s[2].contains("beta")) {
+ beta = Integer.valueOf(s[2].replace("beta", ""));
+ } else {
+ bugfix = Integer.valueOf(s[2]);
+ }
+ }
+ }
+
+ public int getMajor() {
+ return major;
+ }
+
+ public int getMinor() {
+ return minor;
+ }
+
public String getVersion() {
switch(major) {
case 1:
@@ -90,96 +90,98 @@ public class Version implements Comparable<Version>, Serializable {
return "4.9";
case 11:
return "5.1";
- case 12:
- return "5.2";
- case 13:
- return "5.3";
+ case 12:
+ return "5.2";
+ case 13:
+ return "5.3";
+ case 14:
+ return "6.0";
}
}
return "";
}
- /**
- * Return whether this object is equal to another.
- * @param o Object to compare to.
- * @return Whether this object is equals to another.
- */
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- final Version version = (Version) o;
-
- if (beta != version.beta) return false;
- if (bugfix != version.bugfix) return false;
- if (major != version.major) return false;
- return minor == version.minor;
- }
-
- /**
- * Returns a hash code for this object.
- * @return A hash code for this object.
- */
- public int hashCode() {
- int result;
- result = major;
- result = 29 * result + minor;
- result = 29 * result + beta;
- result = 29 * result + bugfix;
- return result;
- }
-
- /**
- * Returns a string representation of the form "1.27", "1.27.2" or "1.27.beta3".
- * @return A string representation of the form "1.27", "1.27.2" or "1.27.beta3".
- */
- public String toString() {
- StringBuffer buf = new StringBuffer();
- buf.append(major).append('.').append(minor);
- if (beta != 0) {
- buf.append(".beta").append(beta);
- } else if (bugfix != 0) {
- buf.append('.').append(bugfix);
- }
-
- return buf.toString();
- }
-
- /**
- * Compares this object with the specified object for order.
- * @param version The object to compare to.
- * @return A negative integer, zero, or a positive integer as this object is less than, equal to, or
- * greater than the specified object.
- */
- @Override
- public int compareTo(Version version) {
- if (major < version.major) {
- return -1;
- } else if (major > version.major) {
- return 1;
- }
-
- if (minor < version.minor) {
- return -1;
- } else if (minor > version.minor) {
- return 1;
- }
-
- if (bugfix < version.bugfix) {
- return -1;
- } else if (bugfix > version.bugfix) {
- return 1;
- }
-
- int thisBeta = beta == 0 ? Integer.MAX_VALUE : beta;
- int otherBeta = version.beta == 0 ? Integer.MAX_VALUE : version.beta;
-
- if (thisBeta < otherBeta) {
- return -1;
- } else if (thisBeta > otherBeta) {
- return 1;
- }
-
- return 0;
- }
+ /**
+ * Return whether this object is equal to another.
+ * @param o Object to compare to.
+ * @return Whether this object is equals to another.
+ */
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final Version version = (Version) o;
+
+ if (beta != version.beta) return false;
+ if (bugfix != version.bugfix) return false;
+ if (major != version.major) return false;
+ return minor == version.minor;
+ }
+
+ /**
+ * Returns a hash code for this object.
+ * @return A hash code for this object.
+ */
+ public int hashCode() {
+ int result;
+ result = major;
+ result = 29 * result + minor;
+ result = 29 * result + beta;
+ result = 29 * result + bugfix;
+ return result;
+ }
+
+ /**
+ * Returns a string representation of the form "1.27", "1.27.2" or "1.27.beta3".
+ * @return A string representation of the form "1.27", "1.27.2" or "1.27.beta3".
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append(major).append('.').append(minor);
+ if (beta != 0) {
+ buf.append(".beta").append(beta);
+ } else if (bugfix != 0) {
+ buf.append('.').append(bugfix);
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * Compares this object with the specified object for order.
+ * @param version The object to compare to.
+ * @return A negative integer, zero, or a positive integer as this object is less than, equal to, or
+ * greater than the specified object.
+ */
+ @Override
+ public int compareTo(Version version) {
+ if (major < version.major) {
+ return -1;
+ } else if (major > version.major) {
+ return 1;
+ }
+
+ if (minor < version.minor) {
+ return -1;
+ } else if (minor > version.minor) {
+ return 1;
+ }
+
+ if (bugfix < version.bugfix) {
+ return -1;
+ } else if (bugfix > version.bugfix) {
+ return 1;
+ }
+
+ int thisBeta = beta == 0 ? Integer.MAX_VALUE : beta;
+ int otherBeta = version.beta == 0 ? Integer.MAX_VALUE : version.beta;
+
+ if (thisBeta < otherBeta) {
+ return -1;
+ } else if (thisBeta > otherBeta) {
+ return 1;
+ }
+
+ return 0;
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java
index 630acf2c..552712f7 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java
@@ -47,7 +47,7 @@ public class AdminFragment extends SelectRecyclerFragment<User> {
switch (item.getItemId()) {
case R.id.menu_add_user:
- UserUtil.addNewUser(context, this);
+ UserUtil.addNewUser(context, this, (objects.size() > 0) ? objects.get(0) : null);
break;
}
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/ChatFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/ChatFragment.java
index febf22de..3208ffb7 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/ChatFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/ChatFragment.java
@@ -135,8 +135,8 @@ public class ChatFragment extends SubsonicFragment {
}
@Override
- public void onResume() {
- super.onResume();
+ public void onStart() {
+ super.onStart();
final Handler handler = new Handler();
Runnable runnable = new Runnable() {
@@ -164,8 +164,8 @@ public class ChatFragment extends SubsonicFragment {
}
@Override
- public void onPause() {
- super.onPause();
+ public void onStop() {
+ super.onStop();
if(executorService != null) {
executorService.shutdown();
executorService = null;
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/DownloadFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/DownloadFragment.java
index 7594a99e..9e8f8c5b 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/DownloadFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/DownloadFragment.java
@@ -65,8 +65,8 @@ public class DownloadFragment extends SelectRecyclerFragment<DownloadFile> imple
}
@Override
- public void onResume() {
- super.onResume();
+ public void onStart() {
+ super.onStart();
final Handler handler = new Handler();
Runnable runnable = new Runnable() {
@@ -86,8 +86,8 @@ public class DownloadFragment extends SelectRecyclerFragment<DownloadFile> imple
}
@Override
- public void onPause() {
- super.onPause();
+ public void onStop() {
+ super.onStop();
executorService.shutdown();
}
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/EqualizerFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/EqualizerFragment.java
index 9ee98cb4..948c59cf 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/EqualizerFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/EqualizerFragment.java
@@ -110,8 +110,8 @@ public class EqualizerFragment extends SubsonicFragment {
}
@Override
- public void onPause() {
- super.onPause();
+ public void onStop() {
+ super.onStop();
try {
equalizerController.saveSettings();
@@ -125,8 +125,8 @@ public class EqualizerFragment extends SubsonicFragment {
}
@Override
- public void onResume() {
- super.onResume();
+ public void onStart() {
+ super.onStart();
equalizerController = DownloadService.getInstance().getEqualizerController();
equalizer = equalizerController.getEqualizer();
bass = equalizerController.getBassBoost();
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/MainFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/MainFragment.java
index 5daf3d7a..82e50b76 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/MainFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/MainFragment.java
@@ -1,11 +1,12 @@
package github.daneren2005.dsub.fragments;
-import android.content.res.Resources;
-import android.os.Environment;
import android.content.Intent;
-import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
+import android.os.Environment;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.StatFs;
import android.util.Log;
@@ -18,6 +19,7 @@ import github.daneren2005.dsub.adapter.MainAdapter;
import github.daneren2005.dsub.adapter.SectionAdapter;
import github.daneren2005.dsub.domain.ServerInfo;
import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.EnvironmentVariables;
import github.daneren2005.dsub.util.FileUtil;
import github.daneren2005.dsub.util.LoadingTask;
import github.daneren2005.dsub.util.ProgressListener;
@@ -28,13 +30,28 @@ import github.daneren2005.dsub.service.MusicServiceFactory;
import github.daneren2005.dsub.view.ChangeLog;
import github.daneren2005.dsub.view.UpdateView;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.URL;
+import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import javax.net.ssl.HttpsURLConnection;
+
public class MainFragment extends SelectRecyclerFragment<Integer> {
private static final String TAG = MainFragment.class.getSimpleName();
+ public static final String SONGS_LIST_PREFIX = "songs-";
+ public static final String SONGS_NEWEST = SONGS_LIST_PREFIX + "newest";
+ public static final String SONGS_TOP_PLAYED = SONGS_LIST_PREFIX + "topPlayed";
+ public static final String SONGS_RECENT = SONGS_LIST_PREFIX + "recent";
+ public static final String SONGS_FREQUENT = SONGS_LIST_PREFIX + "frequent";
public MainFragment() {
super();
@@ -47,10 +64,11 @@ public class MainFragment extends SelectRecyclerFragment<Integer> {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
menuInflater.inflate(R.menu.main, menu);
+ onFinishSetupOptionsMenu(menu);
try {
- if (!ServerInfo.isMadsonic(context) || !UserUtil.isCurrentAdmin()) {
- menu.setGroupVisible(R.id.madsonic, false);
+ if (!ServerInfo.canRescanServer(context) || !UserUtil.isCurrentAdmin()) {
+ menu.setGroupVisible(R.id.rescan_server, false);
}
} catch(Exception e) {
Log.w(TAG, "Error on setting madsonic invisible", e);
@@ -113,6 +131,22 @@ public class MainFragment extends SelectRecyclerFragment<Integer> {
sections.add(albums);
headers.add("albums");
+ if(ServerInfo.isMadsonic6(context)) {
+ List<Integer> songs = new ArrayList<>();
+
+ songs.add(R.string.main_songs_newest);
+ if(ServerInfo.checkServerVersion(context, "2.0.1")) {
+ songs.add(R.string.main_songs_top_played);
+ }
+ songs.add(R.string.main_songs_recent);
+ if(ServerInfo.checkServerVersion(context, "2.0.1")) {
+ songs.add(R.string.main_songs_frequent);
+ }
+
+ sections.add(songs);
+ headers.add("songs");
+ }
+
if(ServerInfo.checkServerVersion(context, "1.8")) {
List<Integer> videos = Arrays.asList(R.string.main_videos);
sections.add(videos);
@@ -237,10 +271,10 @@ public class MainFragment extends SelectRecyclerFragment<Integer> {
private void getLogs() {
try {
- final String version = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
- new LoadingTask<File>(context) {
+ final PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
+ new LoadingTask<String>(context) {
@Override
- protected File doInBackground() throws Throwable {
+ protected String doInBackground() throws Throwable {
updateProgress("Gathering Logs");
File logcat = new File(Environment.getExternalStorageDirectory(), "dsub-logcat.txt");
Util.delete(logcat);
@@ -258,30 +292,94 @@ public class MainFragment extends SelectRecyclerFragment<Integer> {
logcatProc = Runtime.getRuntime().exec(progs.toArray(new String[progs.size()]));
logcatProc.waitFor();
- } catch(Exception e) {
- Util.toast(context, "Failed to gather logs");
} finally {
if(logcatProc != null) {
logcatProc.destroy();
}
}
- return logcat;
+ URL url = new URL("https://pastebin.com/api/api_post.php");
+ HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
+ StringBuffer responseBuffer = new StringBuffer();
+ try {
+ urlConnection.setReadTimeout(10000);
+ urlConnection.setConnectTimeout(15000);
+ urlConnection.setRequestMethod("POST");
+ urlConnection.setDoInput(true);
+ urlConnection.setDoOutput(true);
+
+ OutputStream os = urlConnection.getOutputStream();
+ BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, Constants.UTF_8));
+ writer.write("api_dev_key=" + URLEncoder.encode(EnvironmentVariables.PASTEBIN_DEV_KEY, Constants.UTF_8) + "&api_option=paste&api_paste_private=1&api_paste_code=");
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(new FileInputStream(logcat)));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ writer.write(URLEncoder.encode(line + "\n", Constants.UTF_8));
+ }
+ } finally {
+ Util.close(reader);
+ }
+
+ File stacktrace = new File(Environment.getExternalStorageDirectory(), "dsub-stacktrace.txt");
+ if(stacktrace.exists() && stacktrace.isFile()) {
+ writer.write("\n\nMost Recent Stacktrace:\n\n");
+
+ reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(new FileInputStream(stacktrace)));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ writer.write(URLEncoder.encode(line + "\n", Constants.UTF_8));
+ }
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ writer.flush();
+ writer.close();
+ os.close();
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
+ String inputLine;
+ while ((inputLine = in.readLine()) != null) {
+ responseBuffer.append(inputLine);
+ }
+ in.close();
+ } finally {
+ urlConnection.disconnect();
+ }
+
+ String response = responseBuffer.toString();
+ if(response.indexOf("http") == 0) {
+ return response.replace("http:", "https:");
+ } else {
+ throw new Exception("Pastebin Error: " + response);
+ }
+ }
+
+ @Override
+ protected void error(Throwable error) {
+ Log.e(TAG, "Failed to gather logs", error);
+ Util.toast(context, "Failed to gather logs");
}
@Override
- protected void done(File logcat) {
+ protected void done(String logcat) {
String footer = "Android SDK: " + Build.VERSION.SDK;
footer += "\nDevice Model: " + Build.MODEL;
footer += "\nDevice Name: " + Build.MANUFACTURER + " " + Build.PRODUCT;
footer += "\nROM: " + Build.DISPLAY;
+ footer += "\nLogs: " + logcat;
+ footer += "\nBuild Number: " + packageInfo.versionCode;
Intent email = new Intent(Intent.ACTION_SENDTO,
Uri.fromParts("mailto", "dsub.android@gmail.com", null));
- email.putExtra(Intent.EXTRA_SUBJECT, "DSub " + version + " Error Logs");
+ email.putExtra(Intent.EXTRA_SUBJECT, "DSub " + packageInfo.versionName + " Error Logs");
email.putExtra(Intent.EXTRA_TEXT, "Describe the problem here\n\n\n" + footer);
- Uri attachment = Uri.fromFile(logcat);
- email.putExtra(Intent.EXTRA_STREAM, attachment);
startActivity(email);
}
}.execute();
@@ -310,6 +408,14 @@ public class MainFragment extends SelectRecyclerFragment<Integer> {
showAlbumList("alphabeticalByName");
} else if(item == R.string.main_videos) {
showVideos();
+ } else if (item == R.string.main_songs_newest) {
+ showAlbumList(SONGS_NEWEST);
+ } else if (item == R.string.main_songs_top_played) {
+ showAlbumList(SONGS_TOP_PLAYED);
+ } else if (item == R.string.main_songs_recent) {
+ showAlbumList(SONGS_RECENT);
+ } else if (item == R.string.main_songs_frequent) {
+ showAlbumList(SONGS_FREQUENT);
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java
index c557a174..10623b4e 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java
@@ -51,15 +51,18 @@ import android.view.animation.AnimationUtils;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.PopupMenu;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.ViewFlipper;
+import com.shehabic.droppy.DroppyClickCallbackInterface;
+import com.shehabic.droppy.DroppyMenuPopup;
+import com.shehabic.droppy.animations.DroppyFadeInAnimation;
import github.daneren2005.dsub.R;
import github.daneren2005.dsub.activity.SubsonicFragmentActivity;
import github.daneren2005.dsub.adapter.SectionAdapter;
import github.daneren2005.dsub.audiofx.EqualizerController;
import github.daneren2005.dsub.domain.Bookmark;
-import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.PlayerState;
import github.daneren2005.dsub.domain.RepeatMode;
import github.daneren2005.dsub.domain.ServerInfo;
@@ -73,6 +76,7 @@ import github.daneren2005.dsub.service.ServerTooOldException;
import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.SilentBackgroundTask;
import github.daneren2005.dsub.adapter.DownloadFileAdapter;
+import github.daneren2005.dsub.view.compat.CustomMediaRouteDialogFactory;
import github.daneren2005.dsub.view.FadeOutAnimation;
import github.daneren2005.dsub.view.FastScroller;
import github.daneren2005.dsub.view.UpdateView;
@@ -88,7 +92,6 @@ import java.util.concurrent.ScheduledFuture;
public class NowPlayingFragment extends SubsonicFragment implements OnGestureListener, SectionAdapter.OnItemClickedListener<DownloadFile>, OnSongChangedListener {
private static final String TAG = NowPlayingFragment.class.getSimpleName();
private static final int PERCENTAGE_OF_SCREEN_FOR_SWIPE = 10;
- private static final int INCREMENT_TIME = 5000;
private static final int ACTION_PREVIOUS = 1;
private static final int ACTION_NEXT = 2;
@@ -106,6 +109,8 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
private SeekBar progressBar;
private AutoRepeatButton previousButton;
private AutoRepeatButton nextButton;
+ private AutoRepeatButton rewindButton;
+ private AutoRepeatButton fastforwardButton;
private View pauseButton;
private View stopButton;
private View startButton;
@@ -115,6 +120,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
private ImageButton bookmarkButton;
private ImageButton rateBadButton;
private ImageButton rateGoodButton;
+ private ImageButton playbackSpeedButton;
private ScheduledExecutorService executorService;
private DownloadFile currentPlaying;
@@ -129,6 +135,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
private int lastY = 0;
private int currentPlayingSize = 0;
private MenuItem timerMenu;
+ private DroppySpeedControl speed;
/**
* Called when the activity is first created.
@@ -172,6 +179,8 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
progressBar = (SeekBar)rootView.findViewById(R.id.download_progress_bar);
previousButton = (AutoRepeatButton)rootView.findViewById(R.id.download_previous);
nextButton = (AutoRepeatButton)rootView.findViewById(R.id.download_next);
+ rewindButton = (AutoRepeatButton) rootView.findViewById(R.id.download_rewind);
+ fastforwardButton = (AutoRepeatButton) rootView.findViewById(R.id.download_fastforward);
pauseButton =rootView.findViewById(R.id.download_pause);
stopButton =rootView.findViewById(R.id.download_stop);
startButton =rootView.findViewById(R.id.download_start);
@@ -179,6 +188,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
bookmarkButton = (ImageButton) rootView.findViewById(R.id.download_bookmark);
rateBadButton = (ImageButton) rootView.findViewById(R.id.download_rating_bad);
rateGoodButton = (ImageButton) rootView.findViewById(R.id.download_rating_good);
+ playbackSpeedButton = (ImageButton) rootView.findViewById(R.id.download_playback_speed);
toggleListButton =rootView.findViewById(R.id.download_toggle_list);
playlistView = (RecyclerView)rootView.findViewById(R.id.download_list);
@@ -194,6 +204,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
@Override
public void onClick(View v) {
getDownloadService().toggleStarred();
+ setControlsVisible(true);
}
});
} else {
@@ -212,6 +223,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
bookmarkButton.setOnTouchListener(touchListener);
rateBadButton.setOnTouchListener(touchListener);
rateGoodButton.setOnTouchListener(touchListener);
+ playbackSpeedButton.setOnTouchListener(touchListener);
emptyTextView.setOnTouchListener(touchListener);
albumArtImageView.setOnTouchListener(new View.OnTouchListener() {
@Override
@@ -239,7 +251,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
});
previousButton.setOnRepeatListener(new Runnable() {
public void run() {
- changeProgress(-INCREMENT_TIME);
+ changeProgress(true);
}
});
@@ -259,10 +271,35 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
});
nextButton.setOnRepeatListener(new Runnable() {
public void run() {
- changeProgress(INCREMENT_TIME);
+ changeProgress(false);
+ }
+ });
+
+ rewindButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ changeProgress(true);
+ }
+ });
+ rewindButton.setOnRepeatListener(new Runnable() {
+ public void run() {
+ changeProgress(true);
+ }
+ });
+
+ fastforwardButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ changeProgress(false);
+ }
+ });
+ fastforwardButton.setOnRepeatListener(new Runnable() {
+ public void run() {
+ changeProgress(false);
}
});
+
pauseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@@ -330,6 +367,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
@Override
public void onClick(View view) {
createBookmark();
+ setControlsVisible(true);
}
});
@@ -341,6 +379,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
return;
}
downloadService.toggleRating(1);
+ setControlsVisible(true);
}
});
rateGoodButton.setOnClickListener(new View.OnClickListener() {
@@ -351,9 +390,16 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
return;
}
downloadService.toggleRating(5);
+ setControlsVisible(true);
}
});
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ setPlaybackSpeed();
+ } else {
+ playbackSpeedButton.setVisibility(View.GONE);
+ }
+
toggleListButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@@ -405,11 +451,6 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
}
});
- if(Build.MODEL.equals("Nexus 4") || Build.MODEL.equals("GT-I9100")) {
- View slider = rootView.findViewById(R.id.download_slider);
- slider.setPadding(0, 0, 0, 0);
- }
-
return rootView;
}
@@ -438,7 +479,8 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
}
boolean equalizerAvailable = downloadService != null && downloadService.getEqualizerAvailable();
- if(equalizerAvailable && !downloadService.isRemoteEnabled()) {
+ boolean isRemoteEnabled = downloadService != null && downloadService.isRemoteEnabled();
+ if(equalizerAvailable && !isRemoteEnabled) {
SharedPreferences prefs = Util.getPreferences(context);
boolean equalizerOn = prefs.getBoolean(Constants.PREFERENCES_EQUALIZER_ON, false);
if (equalizerOn && downloadService != null) {
@@ -450,12 +492,32 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
menu.removeItem(R.id.menu_equalizer);
}
+ if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M || isRemoteEnabled) {
+ playbackSpeedButton.setVisibility(View.GONE);
+ } else {
+ playbackSpeedButton.setVisibility(View.VISIBLE);
+ }
+
if(downloadService != null) {
MenuItem mediaRouteItem = menu.findItem(R.id.menu_mediaroute);
if(mediaRouteItem != null) {
MediaRouteButton mediaRouteButton = (MediaRouteButton) MenuItemCompat.getActionView(mediaRouteItem);
+ mediaRouteButton.setDialogFactory(new CustomMediaRouteDialogFactory());
mediaRouteButton.setRouteSelector(downloadService.getRemoteSelector());
}
+
+ if(downloadService.isCurrentPlayingSingle()) {
+ if(!Util.isOffline(context)) {
+ menu.removeItem(R.id.menu_save_playlist);
+ }
+
+ menu.removeItem(R.id.menu_batch_mode);
+ menu.removeItem(R.id.menu_remove_played);
+ }
+ }
+
+ if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_BATCH_MODE, false)) {
+ menu.findItem(R.id.menu_batch_mode).setChecked(true);
}
}
@@ -474,7 +536,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
menuInflater.inflate(R.menu.nowplaying_context_offline, menu);
} else {
menuInflater.inflate(R.menu.nowplaying_context, menu);
- menu.findItem(R.id.menu_star).setTitle(downloadFile.getSong().isStarred() ? R.string.common_unstar : R.string.common_star);
+ menu.findItem(R.id.song_menu_star).setTitle(downloadFile.getSong().isStarred() ? R.string.common_unstar : R.string.common_star);
}
if (downloadFile.getSong().getParent() == null) {
@@ -620,9 +682,6 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
}
createNewPlaylist(entries, true);
return true;
- case R.id.menu_star:
- UpdateHelper.toggleStarred(context, song.getSong());
- return true;
case R.id.menu_rate:
UpdateHelper.setRating(context, song.getSong());
return true;
@@ -634,11 +693,6 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
startTimer();
}
return true;
- case R.id.menu_add_playlist:
- songs = new ArrayList<Entry>(1);
- songs.add(song.getSong());
- addToPlaylist(songs);
- return true;
case R.id.menu_info:
displaySongInfo(song.getSong());
return true;
@@ -663,14 +717,25 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
// Any failed condition will get here
Util.toast(context, "Failed to start equalizer. Try restarting.");
return true;
- } default:
+ }case R.id.menu_batch_mode:
+ if(Util.isBatchMode(context)) {
+ Util.setBatchMode(context, false);
+ songListAdapter.notifyDataSetChanged();
+ } else {
+ Util.setBatchMode(context, true);
+ songListAdapter.notifyDataSetChanged();
+ }
+ context.supportInvalidateOptionsMenu();
+
+ return true;
+ default:
return false;
}
}
@Override
- public void onResume() {
- super.onResume();
+ public void onStart() {
+ super.onStart();
if(this.primaryFragment) {
onResumeHandlers();
} else {
@@ -678,13 +743,13 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
}
}
private void onResumeHandlers() {
- final Handler handler = new Handler();
executorService = Executors.newSingleThreadScheduledExecutor();
setControlsVisible(true);
final DownloadService downloadService = getDownloadService();
if (downloadService == null || downloadService.getCurrentPlaying() == null || startFlipped) {
playlistFlipper.setDisplayedChild(1);
+ startFlipped = false;
}
if (downloadService != null && downloadService.getKeepScreenOn()) {
context.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -701,19 +766,20 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
context.runWhenServiceAvailable(new Runnable() {
@Override
public void run() {
- if(primaryFragment) {
+ if (primaryFragment) {
DownloadService downloadService = getDownloadService();
downloadService.startRemoteScan();
downloadService.addOnSongChangedListener(NowPlayingFragment.this, true);
}
updateRepeatButton();
+ updateTitle();
}
});
}
@Override
- public void onPause() {
- super.onPause();
+ public void onStop() {
+ super.onStop();
onPauseHandlers();
}
private void onPauseHandlers() {
@@ -780,6 +846,11 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
}
private void setControlsVisible(boolean visible) {
+ DownloadService downloadService = getDownloadService();
+ if(downloadService != null && downloadService.isCurrentPlayingSingle()) {
+ return;
+ }
+
try {
long duration = 1700L;
FadeOutAnimation.createAndStart(rootView.findViewById(R.id.download_overlay_buttons), !visible, duration);
@@ -899,10 +970,14 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
private int getMinutes(int progress) {
if(progress < 30) {
return progress + 1;
- } else if(progress < 61) {
+ } else if(progress < 49) {
return (progress - 30) * 5 + getMinutes(29);
+ } else if(progress < 57) {
+ return (progress - 48) * 30 + getMinutes(48);
+ } else if(progress < 81) {
+ return (progress - 56) * 60 + getMinutes(56);
} else {
- return (progress - 61) * 15 + getMinutes(60);
+ return (progress - 80) * 150 + getMinutes(80);
}
}
@@ -938,31 +1013,22 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
}
}
- private void changeProgress(final int ms) {
+ private void changeProgress(final boolean rewind) {
final DownloadService downloadService = getDownloadService();
if(downloadService == null) {
return;
}
new SilentBackgroundTask<Void>(context) {
- boolean isJukeboxEnabled;
- int msPlayed;
- Integer duration;
- PlayerState playerState;
int seekTo;
@Override
protected Void doInBackground() throws Throwable {
- msPlayed = Math.max(0, downloadService.getPlayerPosition());
- duration = downloadService.getPlayerDuration();
- playerState = getDownloadService().getPlayerState();
- int msTotal = duration == null ? 0 : duration;
- if(msPlayed + ms > msTotal) {
- seekTo = msTotal;
+ if(rewind) {
+ seekTo = downloadService.rewind();
} else {
- seekTo = msPlayed + ms;
+ seekTo = downloadService.fastForward();
}
- downloadService.seekTo(seekTo);
return null;
}
@@ -1156,13 +1222,55 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
}
@Override
- public void onSongChanged(DownloadFile currentPlaying, int currentPlayingIndex) {
+ public void onSongChanged(DownloadFile currentPlaying, int currentPlayingIndex, boolean shouldFastForward) {
this.currentPlaying = currentPlaying;
+ setupSubtitle(currentPlayingIndex);
+
+ updateMediaButton(shouldFastForward);
+ updateTitle();
+ setPlaybackSpeed();
+ }
+
+ private void updateMediaButton(boolean shouldFastForward) {
+ DownloadService downloadService = getDownloadService();
+ if(downloadService.isCurrentPlayingSingle()) {
+ previousButton.setVisibility(View.GONE);
+ nextButton.setVisibility(View.GONE);
+ rewindButton.setVisibility(View.GONE);
+ fastforwardButton.setVisibility(View.GONE);
+ } else {
+ if (downloadService.shouldFastForward()) {
+ previousButton.setVisibility(View.GONE);
+ nextButton.setVisibility(View.GONE);
+
+ rewindButton.setVisibility(View.VISIBLE);
+ fastforwardButton.setVisibility(View.VISIBLE);
+ } else {
+ previousButton.setVisibility(View.VISIBLE);
+ nextButton.setVisibility(View.VISIBLE);
+
+ rewindButton.setVisibility(View.GONE);
+ fastforwardButton.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private void setupSubtitle(int currentPlayingIndex) {
if (currentPlaying != null) {
Entry song = currentPlaying.getSong();
songTitleTextView.setText(song.getTitle());
getImageLoader().loadImage(albumArtImageView, song, true, true);
- setSubtitle(context.getResources().getString(R.string.download_playing_out_of, currentPlayingIndex + 1, currentPlayingSize));
+
+ DownloadService downloadService = getDownloadService();
+ if(downloadService.isCurrentPlayingSingle()) {
+ setSubtitle(null);
+ } else if(downloadService.isShufflePlayEnabled()) {
+ setSubtitle(context.getResources().getString(R.string.download_playerstate_playing_shuffle));
+ } else if(downloadService.isArtistRadio()) {
+ setSubtitle(context.getResources().getString(R.string.download_playerstate_playing_artist_radio));
+ } else {
+ setSubtitle(context.getResources().getString(R.string.download_playing_out_of, currentPlayingIndex + 1, currentPlayingSize));
+ }
} else {
songTitleTextView.setText(null);
getImageLoader().loadImage(albumArtImageView, (Entry) null, true, false);
@@ -1171,7 +1279,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
}
@Override
- public void onSongsChanged(List<DownloadFile> songs, DownloadFile currentPlaying, int currentPlayingIndex) {
+ public void onSongsChanged(List<DownloadFile> songs, DownloadFile currentPlaying, int currentPlayingIndex, boolean shouldFastForward) {
currentPlayingSize = songs.size();
DownloadService downloadService = getDownloadService();
@@ -1199,11 +1307,22 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
scrollWhenLoaded = false;
}
- setSubtitle(context.getResources().getString(R.string.download_playing_out_of, currentPlayingIndex + 1, currentPlayingSize));
if(this.currentPlaying != currentPlaying) {
- onSongChanged(currentPlaying, currentPlayingIndex);
+ onSongChanged(currentPlaying, currentPlayingIndex, shouldFastForward);
onMetadataUpdate(currentPlaying != null ? currentPlaying.getSong() : null, DownloadService.METADATA_UPDATED_ALL);
+ } else {
+ updateMediaButton(shouldFastForward);
+ setupSubtitle(currentPlayingIndex);
+ }
+
+ if(downloadService.isCurrentPlayingSingle()) {
+ toggleListButton.setVisibility(View.GONE);
+ repeatButton.setVisibility(View.GONE);
+ } else {
+ toggleListButton.setVisibility(View.VISIBLE);
+ repeatButton.setVisibility(View.VISIBLE);
}
+ setPlaybackSpeed();
}
@Override
@@ -1258,11 +1377,16 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
break;
default:
if(currentPlaying != null) {
- String artist = "";
- if(currentPlaying.getSong().getArtist() != null) {
- artist = currentPlaying.getSong().getArtist() + " - ";
+ Entry entry = currentPlaying.getSong();
+ if(entry.getAlbum() != null) {
+ String artist = "";
+ if (entry.getArtist() != null) {
+ artist = currentPlaying.getSong().getArtist() + " - ";
+ }
+ statusTextView.setText(artist + entry.getAlbum());
+ } else {
+ statusTextView.setText(null);
}
- statusTextView.setText(artist + currentPlaying.getSong().getAlbum());
} else {
statusTextView.setText(null);
}
@@ -1334,6 +1458,10 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
}
bookmarkButton.setImageResource(bookmark);
}
+
+ if(song != null && albumArtImageView != null && fieldChange == DownloadService.METADATA_UPDATED_COVER_ART) {
+ getImageLoader().loadImage(albumArtImageView, song, true, true);
+ }
}
public void updateRepeatButton() {
@@ -1352,4 +1480,103 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
break;
}
}
+ private void updateTitle() {
+ DownloadService downloadService = getDownloadService();
+ float playbackSpeed = downloadService.getPlaybackSpeed();
+
+ String title = context.getResources().getString(R.string.button_bar_now_playing);
+ int stringRes = -1;
+ if(playbackSpeed == 0.5f) {
+ stringRes = R.string.download_playback_speed_half;
+ } else if(playbackSpeed == 1.5f) {
+ stringRes = R.string.download_playback_speed_one_half;
+ } else if(playbackSpeed == 2.0f) {
+ stringRes = R.string.download_playback_speed_double;
+ } else if(playbackSpeed == 3.0f) {
+ stringRes = R.string.download_playback_speed_tripple;
+ }
+
+ String playbackSpeedText = null;
+ if(stringRes != -1) {
+ playbackSpeedText = context.getResources().getString(stringRes);
+ } else if(Math.abs(playbackSpeed - 1.0) > 0.01) {
+ playbackSpeedText = Float.toString(playbackSpeed) + "x";
+ }
+
+ if(playbackSpeedText != null) {
+ title += " (" + playbackSpeedText + ")";
+ }
+ setTitle(title);
+ }
+
+ @Override
+ protected List<Entry> getSelectedEntries() {
+ List<DownloadFile> selected = getCurrentAdapter().getSelected();
+ List<Entry> entries = new ArrayList<>();
+
+ for(DownloadFile downloadFile: selected) {
+ if(downloadFile.getSong() != null) {
+ entries.add(downloadFile.getSong());
+ }
+ }
+
+ return entries;
+ }
+
+ private void setPlaybackSpeed() {
+ if (playbackSpeedButton.getVisibility() == View.GONE)
+ return;
+ speed = new DroppySpeedControl(R.layout.set_playback_speed);
+ DroppyMenuPopup.Builder builder = new DroppyMenuPopup.Builder(context,playbackSpeedButton);
+ speed.setClickable(true);
+ float playbackSpeed;
+
+ playbackSpeed = getDownloadService() != null ? getDownloadService().getPlaybackSpeed() : 1.0f;
+
+ final DroppyMenuPopup popup = builder.triggerOnAnchorClick(true).addMenuItem(speed).setPopupAnimation(new DroppyFadeInAnimation()).build();
+ speed.setOnSeekBarChangeListener(context, new DroppyClickCallbackInterface() {
+ @Override
+ public void call(View v, int id) {
+ SeekBar playbackSpeedBar = (SeekBar) v;
+ int playbackSpeed = playbackSpeedBar.getProgress() +5 ;
+ setPlaybackSpeed(playbackSpeed/10f);
+ }
+ },R.id.playback_speed_bar,R.id.playback_speed_label,playbackSpeed);
+ speed.setOnClicks(context,
+ new DroppyClickCallbackInterface() {
+ @Override
+ public void call(View v, int id) {
+ float playbackSpeed = 1.0f;
+ switch (id) {
+ case R.id.playback_speed_one_half:
+ playbackSpeed = 1.5f;
+ break;
+ case R.id.playback_speed_double:
+ playbackSpeed = 2.0f;
+ break;
+ case R.id.playback_speed_triple:
+ playbackSpeed = 3.0f;
+ break;
+ default:
+ break;
+ }
+ setPlaybackSpeed(playbackSpeed);
+ speed.updateSeekBar(playbackSpeed);
+ popup.dismiss(true);
+ }
+ }
+ ,R.id.playback_speed_normal,R.id.playback_speed_one_half,R.id.playback_speed_double,
+ R.id.playback_speed_triple);
+ speed.updateSeekBar(playbackSpeed);
+
+ }
+ private void setPlaybackSpeed(float playbackSpeed) {
+ DownloadService downloadService = getDownloadService();
+ if (downloadService == null) {
+ return;
+ }
+
+ downloadService.setPlaybackSpeed(playbackSpeed);
+ updateTitle();
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java
index d21b82e0..dfff45cd 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SearchFragment.java
@@ -3,10 +3,14 @@ package github.daneren2005.dsub.fragments;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
import android.content.Intent;
import android.os.Bundle;
+import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -25,6 +29,7 @@ import github.daneren2005.dsub.adapter.SearchAdapter;
import github.daneren2005.dsub.adapter.SectionAdapter;
import github.daneren2005.dsub.domain.Artist;
import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.domain.MusicDirectory.Entry;
import github.daneren2005.dsub.domain.SearchCritera;
import github.daneren2005.dsub.domain.SearchResult;
import github.daneren2005.dsub.service.MusicService;
@@ -39,9 +44,9 @@ import github.daneren2005.dsub.view.UpdateView;
public class SearchFragment extends SubsonicFragment implements SectionAdapter.OnItemClickedListener<Serializable> {
private static final String TAG = SearchFragment.class.getSimpleName();
- private static final int MAX_ARTISTS = 10;
- private static final int MAX_ALBUMS = 10;
- private static final int MAX_SONGS = 25;
+ private static final int MAX_ARTISTS = 20;
+ private static final int MAX_ALBUMS = 20;
+ private static final int MAX_SONGS = 50;
private static final int MIN_CLOSENESS = 1;
protected RecyclerView recyclerView;
@@ -108,13 +113,13 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O
}
@Override
- public GridLayoutManager.SpanSizeLookup getSpanSizeLookup() {
+ public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final GridLayoutManager gridLayoutManager) {
return new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int viewType = adapter.getItemViewType(position);
if(viewType == EntryGridAdapter.VIEW_TYPE_SONG || viewType == EntryGridAdapter.VIEW_TYPE_HEADER || viewType == ArtistAdapter.VIEW_TYPE_ARTIST) {
- return getRecyclerColumnCount();
+ return gridLayoutManager.getSpanCount();
} else {
return 1;
}
@@ -125,24 +130,13 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
menuInflater.inflate(R.menu.search, menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_search:
- context.startSearch(currentQuery, false, null, false);
- return true;
- }
-
- return super.onOptionsItemSelected(item);
-
+ onFinishSetupOptionsMenu(menu);
}
@Override
public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<Serializable> updateView, Serializable item) {
onCreateContextMenuSupport(menu, menuInflater, updateView, item);
- if(item instanceof MusicDirectory.Entry && !((MusicDirectory.Entry) item).isVideo() && !Util.isOffline(context)) {
+ if(item instanceof Entry && !((Entry) item).isVideo() && !Util.isOffline(context)) {
menu.removeItem(R.id.song_menu_remove_playlist);
}
recreateContextMenu(menu);
@@ -162,8 +156,8 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O
public void onItemClicked(UpdateView<Serializable> updateView, Serializable item) {
if (item instanceof Artist) {
onArtistSelected((Artist) item, false);
- } else if (item instanceof MusicDirectory.Entry) {
- MusicDirectory.Entry entry = (MusicDirectory.Entry) item;
+ } else if (item instanceof Entry) {
+ Entry entry = (Entry) item;
if (entry.isDirectory()) {
onAlbumSelected(entry, false);
} else if (entry.isVideo()) {
@@ -175,19 +169,24 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O
}
@Override
- protected List<MusicDirectory.Entry> getSelectedEntries() {
+ protected List<Entry> getSelectedEntries() {
List<Serializable> selected = adapter.getSelected();
- List<MusicDirectory.Entry> selectedMedia = new ArrayList<>();
+ List<Entry> selectedMedia = new ArrayList<>();
for(Serializable ser: selected) {
- if(ser instanceof MusicDirectory.Entry) {
- selectedMedia.add((MusicDirectory.Entry) ser);
+ if(ser instanceof Entry) {
+ selectedMedia.add((Entry) ser);
}
}
return selectedMedia;
}
- public void search(final String query, final boolean autoplay) {
+ @Override
+ protected boolean isShowArtistEnabled() {
+ return true;
+ }
+
+ public void search(final String query, final boolean autoplay, final String artist, final String album, final String title) {
if(skipSearch) {
skipSearch = false;
return;
@@ -207,12 +206,20 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O
searchResult = result;
recyclerView.setAdapter(adapter = new SearchAdapter(context, searchResult, getImageLoader(), largeAlbums, SearchFragment.this));
if (autoplay) {
- autoplay(query);
+ autoplay(query, artist, album, title);
}
}
};
task.execute();
+
+ if(searchItem != null) {
+ MenuItemCompat.collapseActionView(searchItem);
+ }
+ }
+
+ protected String getCurrentQuery() {
+ return currentQuery;
}
private void onArtistSelected(Artist artist, boolean autoplay) {
@@ -229,7 +236,7 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O
replaceFragment(fragment);
}
- private void onAlbumSelected(MusicDirectory.Entry album, boolean autoplay) {
+ private void onAlbumSelected(Entry album, boolean autoplay) {
SubsonicFragment fragment = new SelectDirectoryFragment();
Bundle args = new Bundle();
args.putString(Constants.INTENT_EXTRA_NAME_ID, album.getId());
@@ -242,7 +249,7 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O
replaceFragment(fragment);
}
- private void onSongSelected(MusicDirectory.Entry song, boolean save, boolean append, boolean autoplay, boolean playNext) {
+ private void onSongSelected(Entry song, boolean save, boolean append, boolean autoplay, boolean playNext) {
DownloadService downloadService = getDownloadService();
if (downloadService != null) {
if (!append) {
@@ -257,7 +264,7 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O
}
}
- private void onVideoSelected(MusicDirectory.Entry entry) {
+ private void onVideoSelected(Entry entry) {
int maxBitrate = Util.getMaxVideoBitrate(context);
Intent intent = new Intent(Intent.ACTION_VIEW);
@@ -265,6 +272,55 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O
startActivity(intent);
}
+ private void autoplay(String query, String artistQuery, String albumQuery, String titleQuery) {
+ Log.i(TAG, "Query: '" + query + "' ( Artist: '" + artistQuery + "', Album: '" + albumQuery + "', Title: '" + titleQuery + "')");
+
+ if(titleQuery != null && !searchResult.getSongs().isEmpty()) {
+ titleQuery = titleQuery.toLowerCase();
+
+ TreeMap<Integer, Entry> tree = new TreeMap<>();
+ for(Entry song: searchResult.getSongs()) {
+ tree.put(Util.getStringDistance(song.getTitle().toLowerCase(), titleQuery), song);
+ }
+
+ Map.Entry<Integer, Entry> entry = tree.firstEntry();
+ if(entry.getKey() <= MIN_CLOSENESS) {
+ onSongSelected(entry.getValue(), false, false, true, false);
+ } else {
+ autoplay(query);
+ }
+ } else if(albumQuery != null && !searchResult.getAlbums().isEmpty()) {
+ albumQuery = albumQuery.toLowerCase();
+
+ TreeMap<Integer, Entry> tree = new TreeMap<>();
+ for(Entry album: searchResult.getAlbums()) {
+ tree.put(Util.getStringDistance(album.getTitle().toLowerCase(), albumQuery), album);
+ }
+
+ Map.Entry<Integer, Entry> entry = tree.firstEntry();
+ if(entry.getKey() <= MIN_CLOSENESS) {
+ onAlbumSelected(entry.getValue(), true);
+ } else {
+ autoplay(query);
+ }
+ } else if(artistQuery != null && !searchResult.getArtists().isEmpty()) {
+ artistQuery = artistQuery.toLowerCase();
+
+ TreeMap<Integer, Artist> tree = new TreeMap<>();
+ for(Artist artist: searchResult.getArtists()) {
+ tree.put(Util.getStringDistance(artist.getName().toLowerCase(), artistQuery), artist);
+ }
+ Map.Entry<Integer, Artist> entry = tree.firstEntry();
+ if(entry.getKey() <= MIN_CLOSENESS) {
+ onArtistSelected(entry.getValue(), true);
+ } else {
+ autoplay(query);
+ }
+ } else {
+ autoplay(query);
+ }
+ }
+
private void autoplay(String query) {
query = query.toLowerCase();
@@ -273,12 +329,12 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O
artist = searchResult.getArtists().get(0);
artist.setCloseness(Util.getStringDistance(artist.getName().toLowerCase(), query));
}
- MusicDirectory.Entry album = null;
+ Entry album = null;
if(!searchResult.getAlbums().isEmpty()) {
album = searchResult.getAlbums().get(0);
album.setCloseness(Util.getStringDistance(album.getTitle().toLowerCase(), query));
}
- MusicDirectory.Entry song = null;
+ Entry song = null;
if(!searchResult.getSongs().isEmpty()) {
song = searchResult.getSongs().get(0);
song.setCloseness(Util.getStringDistance(song.getTitle().toLowerCase(), query));
@@ -286,10 +342,10 @@ public class SearchFragment extends SubsonicFragment implements SectionAdapter.O
if(artist != null && (artist.getCloseness() <= MIN_CLOSENESS ||
(album == null || artist.getCloseness() <= album.getCloseness()) &&
- (song == null || artist.getCloseness() <= song.getCloseness()))) {
+ (song == null || artist.getCloseness() <= song.getCloseness()))) {
onArtistSelected(artist, true);
} else if(album != null && (album.getCloseness() <= MIN_CLOSENESS ||
- song == null || album.getCloseness() <= song.getCloseness())) {
+ song == null || album.getCloseness() <= song.getCloseness())) {
onAlbumSelected(album, true);
} else if(song != null) {
onSongSelected(song, false, false, true, false);
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectArtistFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectArtistFragment.java
index 3df0a9a9..e971bfb6 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectArtistFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectArtistFragment.java
@@ -17,6 +17,7 @@ import github.daneren2005.dsub.adapter.SectionAdapter;
import github.daneren2005.dsub.domain.Artist;
import github.daneren2005.dsub.domain.Indexes;
import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.domain.MusicDirectory.Entry;
import github.daneren2005.dsub.domain.MusicFolder;
import github.daneren2005.dsub.domain.ServerInfo;
import github.daneren2005.dsub.service.MusicService;
@@ -29,12 +30,11 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
-public class SelectArtistFragment extends SelectRecyclerFragment<Artist> implements ArtistAdapter.OnMusicFolderChanged {
+public class SelectArtistFragment extends SelectRecyclerFragment<Serializable> implements ArtistAdapter.OnMusicFolderChanged {
private static final String TAG = SelectArtistFragment.class.getSimpleName();
- private static final int MENU_GROUP_MUSIC_FOLDER = 10;
private List<MusicFolder> musicFolders = null;
- private List<MusicDirectory.Entry> entries;
+ private List<Entry> entries;
private String groupId;
private String groupName;
@@ -63,12 +63,14 @@ public class SelectArtistFragment extends SelectRecyclerFragment<Artist> impleme
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
Bundle args = getArguments();
if(args != null) {
- groupId = args.getString(Constants.INTENT_EXTRA_NAME_ID);
- groupName = args.getString(Constants.INTENT_EXTRA_NAME_NAME);
+ if(args.getBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, false)) {
+ groupId = args.getString(Constants.INTENT_EXTRA_NAME_ID);
+ groupName = args.getString(Constants.INTENT_EXTRA_NAME_NAME);
- if(groupName != null) {
- setTitle(groupName);
- context.invalidateOptionsMenu();
+ if (groupName != null) {
+ setTitle(groupName);
+ context.invalidateOptionsMenu();
+ }
}
}
@@ -78,47 +80,56 @@ public class SelectArtistFragment extends SelectRecyclerFragment<Artist> impleme
}
@Override
- public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<Artist> updateView, Artist item) {
+ public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<Serializable> updateView, Serializable item) {
onCreateContextMenuSupport(menu, menuInflater, updateView, item);
recreateContextMenu(menu);
}
@Override
- public boolean onContextItemSelected(MenuItem menuItem, UpdateView<Artist> updateView, Artist item) {
+ public boolean onContextItemSelected(MenuItem menuItem, UpdateView<Serializable> updateView, Serializable item) {
return onContextItemSelected(menuItem, item);
}
@Override
- public void onItemClicked(UpdateView<Artist> updateView, Artist artist) {
+ public void onItemClicked(UpdateView<Serializable> updateView, Serializable item) {
SubsonicFragment fragment;
- if((Util.isFirstLevelArtist(context) || Util.isOffline(context) || Util.isTagBrowsing(context)) || "root".equals(artist.getId()) || groupId != null) {
- fragment = new SelectDirectoryFragment();
- Bundle args = new Bundle();
- args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId());
- args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName());
-
- if ("root".equals(artist.getId())) {
- args.putSerializable(Constants.FRAGMENT_LIST, (Serializable) entries);
- }
- if(ServerInfo.checkServerVersion(context, "1.13") && !Util.isOffline(context)) {
- args.putSerializable(Constants.INTENT_EXTRA_NAME_DIRECTORY, new MusicDirectory.Entry(artist));
+ if(item instanceof Artist) {
+ Artist artist = (Artist) item;
+
+ if ((Util.isFirstLevelArtist(context) || Util.isOffline(context) || Util.isTagBrowsing(context)) || groupId != null) {
+ fragment = new SelectDirectoryFragment();
+ Bundle args = new Bundle();
+ args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId());
+ args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName());
+
+ if (ServerInfo.checkServerVersion(context, "1.13") && !Util.isOffline(context)) {
+ args.putSerializable(Constants.INTENT_EXTRA_NAME_DIRECTORY, new Entry(artist));
+ }
+ args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
+
+ fragment.setArguments(args);
+ } else {
+ fragment = new SelectArtistFragment();
+ Bundle args = new Bundle();
+ args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId());
+ args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName());
+ args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
+ if (ServerInfo.checkServerVersion(context, "1.13") && !Util.isOffline(context)) {
+ args.putSerializable(Constants.INTENT_EXTRA_NAME_DIRECTORY, new Entry(artist));
+ }
+
+ fragment.setArguments(args);
}
- args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
- fragment.setArguments(args);
+ replaceFragment(fragment);
} else {
- fragment = new SelectArtistFragment();
- Bundle args = new Bundle();
- args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId());
- args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName());
- if(ServerInfo.checkServerVersion(context, "1.13") && !Util.isOffline(context)) {
- args.putSerializable(Constants.INTENT_EXTRA_NAME_DIRECTORY, new MusicDirectory.Entry(artist));
+ Entry entry = (Entry) item;
+ if (entry.isVideo()) {
+ playVideo(entry);
+ } else {
+ onSongPress(entries, entry);
}
-
- fragment.setArguments(args);
}
-
- replaceFragment(fragment);
}
@Override
@@ -155,15 +166,15 @@ public class SelectArtistFragment extends SelectRecyclerFragment<Artist> impleme
}
@Override
- public SectionAdapter getAdapter(List<Artist> objects) {
+ public SectionAdapter getAdapter(List<Serializable> objects) {
return new ArtistAdapter(context, objects, musicFolders, this, this);
}
@Override
- public List<Artist> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception {
- List<Artist> artists;
+ public List<Serializable> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception {
+ List<Serializable> items;
if(groupId == null) {
- if (!Util.isOffline(context) && !Util.isTagBrowsing(context)) {
+ if (!Util.isOffline(context) && (!Util.isTagBrowsing(context) || ServerInfo.checkServerVersion(context, "1.14"))) {
musicFolders = musicService.getMusicFolders(refresh, context, listener);
// Hide folders option if there is only one
@@ -178,14 +189,16 @@ public class SelectArtistFragment extends SelectRecyclerFragment<Artist> impleme
Indexes indexes = musicService.getIndexes(musicFolderId, refresh, context, listener);
indexes.sortChildren(context);
- artists = new ArrayList<>(indexes.getShortcuts().size() + indexes.getArtists().size());
- artists.addAll(indexes.getShortcuts());
- artists.addAll(indexes.getArtists());
+ items = new ArrayList<>(indexes.getShortcuts().size() + indexes.getArtists().size());
+ items.addAll(indexes.getShortcuts());
+ items.addAll(indexes.getArtists());
entries = indexes.getEntries();
+ items.addAll(entries);
} else {
- artists = new ArrayList<>();
+ List<Artist> artists = new ArrayList<>();
+ items = new ArrayList<>();
MusicDirectory dir = musicService.getMusicDirectory(groupId, groupName, refresh, context, listener);
- for(MusicDirectory.Entry entry: dir.getChildren(true, false)) {
+ for(Entry entry: dir.getChildren(true, false)) {
Artist artist = new Artist();
artist.setId(entry.getId());
artist.setName(entry.getTitle());
@@ -193,21 +206,17 @@ public class SelectArtistFragment extends SelectRecyclerFragment<Artist> impleme
artists.add(artist);
}
- entries = new ArrayList<>();
- entries.addAll(dir.getChildren(false, true));
- if(!entries.isEmpty()) {
- Artist root = new Artist();
- root.setId("root");
- root.setName("Root");
- root.setIndex("#");
- artists.add(root);
- }
-
- Indexes indexes = new Indexes(0, artists, new ArrayList<Artist>());
+ Indexes indexes = new Indexes(0, new ArrayList<Artist>(), artists);
indexes.sortChildren(context);
+ items.addAll(indexes.getArtists());
+
+ entries = dir.getChildren(false, true);
+ for(Entry entry: entries) {
+ items.add(entry);
+ }
}
- return artists;
+ return items;
}
@Override
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectBookmarkFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectBookmarkFragment.java
index 5f3ca38b..c320f3c1 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectBookmarkFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectBookmarkFragment.java
@@ -18,6 +18,7 @@
*/
package github.daneren2005.dsub.fragments;
+import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -28,6 +29,7 @@ import github.daneren2005.dsub.domain.Bookmark;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.service.DownloadService;
import github.daneren2005.dsub.service.MusicService;
+import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.MenuUtil;
import github.daneren2005.dsub.util.ProgressListener;
import github.daneren2005.dsub.util.SilentBackgroundTask;
@@ -89,19 +91,29 @@ public class SelectBookmarkFragment extends SelectRecyclerFragment<MusicDirector
return;
}
- new SilentBackgroundTask<Void>(context) {
- @Override
- protected Void doInBackground() throws Throwable {
- downloadService.clear();
- downloadService.download(Arrays.asList(bookmark), false, true, false, false, 0, bookmark.getBookmark().getPosition());
- return null;
- }
-
- @Override
- protected void done(Void result) {
- context.openNowPlaying();
- }
- }.execute();
+ boolean allowPlayAll = ((!Util.isTagBrowsing(context) && bookmark.getParent() != null) || (Util.isTagBrowsing(context) && bookmark.getAlbumId() != null)) && !bookmark.isPodcast();
+ if(allowPlayAll && "all".equals(Util.getSongPressAction(context))) {
+ new RecursiveLoader(context) {
+ @Override
+ protected Boolean doInBackground() throws Throwable {
+ getSiblingsRecursively(bookmark);
+
+ if(songs.isEmpty() || !songs.contains(bookmark)) {
+ playNowInTask(Arrays.asList(bookmark), bookmark, bookmark.getBookmark().getPosition());
+ } else {
+ playNowInTask(songs, bookmark, bookmark.getBookmark().getPosition());
+ }
+ return null;
+ }
+
+ @Override
+ protected void done(Boolean result) {
+ context.openNowPlaying();
+ }
+ }.execute();
+ } else {
+ onSongPress(Arrays.asList(bookmark), bookmark, bookmark.getBookmark().getPosition(), false);
+ }
}
private void displayBookmarkInfo(final MusicDirectory.Entry entry) {
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java
index d2282117..d3a0bfe8 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java
@@ -39,6 +39,7 @@ import github.daneren2005.dsub.domain.ArtistInfo;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.ServerInfo;
import github.daneren2005.dsub.domain.Share;
+import github.daneren2005.dsub.service.CachedMusicService;
import github.daneren2005.dsub.service.DownloadService;
import github.daneren2005.dsub.util.DrawableTint;
import github.daneren2005.dsub.util.ImageLoader;
@@ -86,6 +87,11 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
private ArtistInfo artistInfo;
private String artistInfoDelayed;
+ private SilentBackgroundTask updateCoverArtTask;
+ private ImageView coverArtView;
+ private Entry coverArtRep;
+ private String coverArtId;
+
String id;
String name;
Entry directory;
@@ -184,27 +190,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
recyclerView.setHasFixedSize(true);
fastScroller = (FastScroller) rootView.findViewById(R.id.fragment_fast_scroller);
setupScrollList(recyclerView);
-
- if(largeAlbums) {
- GridLayoutManager gridLayoutManager = new GridLayoutManager(context, getRecyclerColumnCount());
- gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
- @Override
- public int getSpanSize(int position) {
- int viewType = entryGridAdapter.getItemViewType(position);
- if(viewType == EntryGridAdapter.VIEW_TYPE_SONG || viewType == EntryGridAdapter.VIEW_TYPE_HEADER || viewType == EntryInfiniteGridAdapter.VIEW_TYPE_LOADING) {
- return getRecyclerColumnCount();
- } else {
- return 1;
- }
- }
- });
- recyclerView.addItemDecoration(new GridSpacingDecoration());
- recyclerView.setLayoutManager(gridLayoutManager);
- } else {
- LinearLayoutManager layoutManager = new LinearLayoutManager(context);
- layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
- recyclerView.setLayoutManager(layoutManager);
- }
+ setupLayoutManager(recyclerView, largeAlbums);
if(entries == null) {
if(primaryFragment || secondaryFragment) {
@@ -248,7 +234,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
if(!ServerInfo.hasTopSongs(context)) {
menu.removeItem(R.id.menu_top_tracks);
}
- if(!ServerInfo.checkServerVersion(context, "1.11") || (id != null && "root".equals(id))) {
+ if(!ServerInfo.checkServerVersion(context, "1.11")) {
menu.removeItem(R.id.menu_radio);
menu.removeItem(R.id.menu_similar_artists);
} else if(!ServerInfo.hasSimilarArtists(context)) {
@@ -305,9 +291,6 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
case R.id.menu_show_all:
setShowAll();
return true;
- case R.id.menu_unstar:
- unstarSelected();
- return true;
case R.id.menu_top_tracks:
showTopTracks();
return true;
@@ -329,10 +312,6 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
if(!entry.isVideo() && !Util.isOffline(context) && (playlistId == null || !playlistOwner) && (podcastId == null || Util.isOffline(context) && podcastId != null)) {
menu.removeItem(R.id.song_menu_remove_playlist);
}
- // Remove show artists if parent is not set and if not on a album list
- if((albumListType == null || (entry.getParent() == null && entry.getArtistId() == null)) && !Util.isOffline(context)) {
- menu.removeItem(R.id.album_menu_show_artist);
- }
recreateContextMenu(menu);
}
@@ -380,28 +359,20 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
return;
}
- playNow(Arrays.asList(entry));
+ onSongPress(Arrays.asList(entry), entry, false);
} else {
- List<Entry> songs = new ArrayList<Entry>();
-
- if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_PLAY_NOW_AFTER, true)) {
- Iterator it = entries.listIterator(entries.indexOf(entry));
- while(it.hasNext()) {
- songs.add((Entry) it.next());
- }
- } else {
- songs.add(entry);
- }
-
- playNow(songs);
+ onSongPress(entries, entry, albumListType == null || "starred".equals(albumListType));
}
}
@Override
protected void refresh(boolean refresh) {
- if(!"root".equals(id)) {
- load(refresh);
- }
+ load(refresh);
+ }
+
+ @Override
+ protected boolean isShowArtistEnabled() {
+ return albumListType != null;
}
private void load(boolean refresh) {
@@ -488,8 +459,12 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
}
List<Entry> songs = new ArrayList<Entry>();
getSongsRecursively(root, songs);
- root.replaceChildren(songs);
- return root;
+
+ // CachedMusicService is refreshing this data in the background, so will wipe out the songs list from root
+ MusicDirectory clonedRoot = new MusicDirectory(songs);
+ clonedRoot.setId(root.getId());
+ clonedRoot.setName(root.getName());
+ return clonedRoot;
}
private void getSongsRecursively(MusicDirectory parent, List<Entry> songs) throws Exception {
@@ -577,6 +552,14 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
setTitle(albumListExtra);
} else if("alphabeticalByName".equals(albumListType)) {
setTitle(R.string.main_albums_alphabetical);
+ } if (MainFragment.SONGS_NEWEST.equals(albumListType)) {
+ setTitle(R.string.main_songs_newest);
+ } else if (MainFragment.SONGS_TOP_PLAYED.equals(albumListType)) {
+ setTitle(R.string.main_songs_top_played);
+ } else if (MainFragment.SONGS_RECENT.equals(albumListType)) {
+ setTitle(R.string.main_songs_recent);
+ } else if (MainFragment.SONGS_FREQUENT.equals(albumListType)) {
+ setTitle(R.string.main_songs_frequent);
}
new LoadTask(true) {
@@ -593,6 +576,8 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
}
} else if("genres".equals(albumListType) || "genres-songs".equals(albumListType)) {
result = service.getSongsByGenre(albumListExtra, size, 0, context, this);
+ } else if(albumListType.indexOf(MainFragment.SONGS_LIST_PREFIX) != -1) {
+ result = service.getSongList(albumListType, size, 0, context, this);
} else {
result = service.getAlbumList(albumListType, size, 0, refresh, context, this);
}
@@ -657,9 +642,19 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
}
@Override
- public void updateCache() {
- if(entryGridAdapter != null) {
+ public void updateCache(int changeCode) {
+ if(entryGridAdapter != null && changeCode == CachedMusicService.CACHE_UPDATE_LIST) {
entryGridAdapter.notifyDataSetChanged();
+ } else if(changeCode == CachedMusicService.CACHE_UPDATE_METADATA) {
+ if(coverArtView != null && coverArtRep != null && !Util.equals(coverArtRep.getCoverArt(), coverArtId)) {
+ synchronized (coverArtRep) {
+ if (updateCoverArtTask != null && updateCoverArtTask.isRunning()) {
+ updateCoverArtTask.cancel();
+ }
+ updateCoverArtTask = getImageLoader().loadImage(coverArtView, coverArtRep, false, true);
+ coverArtId = coverArtRep.getCoverArt();
+ }
+ }
}
}
}
@@ -669,6 +664,21 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
return entryGridAdapter;
}
+ @Override
+ public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final GridLayoutManager gridLayoutManager) {
+ return new GridLayoutManager.SpanSizeLookup() {
+ @Override
+ public int getSpanSize(int position) {
+ int viewType = entryGridAdapter.getItemViewType(position);
+ if(viewType == EntryGridAdapter.VIEW_TYPE_SONG || viewType == EntryGridAdapter.VIEW_TYPE_HEADER || viewType == EntryInfiniteGridAdapter.VIEW_TYPE_LOADING) {
+ return gridLayoutManager.getSpanCount();
+ } else {
+ return 1;
+ }
+ }
+ };
+ }
+
private void finishLoading() {
boolean validData = !entries.isEmpty() || !albums.isEmpty();
if(!validData) {
@@ -682,7 +692,6 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
if(albumListType == null || "starred".equals(albumListType)) {
entryGridAdapter = new EntryGridAdapter(context, entries, getImageLoader(), largeAlbums);
entryGridAdapter.setRemoveFromPlaylist(playlistId != null);
- entryGridAdapter.setRemoveStarred(albumListType == null);
} else {
if("alphabeticalByName".equals(albumListType)) {
entryGridAdapter = new AlphabeticalAlbumAdapter(context, entries, getImageLoader(), largeAlbums);
@@ -728,11 +737,14 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
if(!artist) {
entryGridAdapter.setShowArtist(true);
}
+ if(topTracks || showAll) {
+ entryGridAdapter.setShowAlbum(true);
+ }
// Show header if not album list type and not root and not artist
// For Subsonic 5.1+ display a header for artists with getArtistInfo data if it exists
boolean addedHeader = false;
- if(albumListType == null && !"root".equals(id) && (!artist || artistInfo != null || artistInfoDelayed != null) && (share == null || entries.size() != albums.size())) {
+ if(albumListType == null && (!artist || artistInfo != null || artistInfoDelayed != null) && (share == null || entries.size() != albums.size())) {
View header = createHeader();
if(header != null) {
@@ -792,7 +804,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
Bundle args = getArguments();
boolean playAll = args.getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false);
if (playAll && !restoredInstance) {
- playAll(args.getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, false), false);
+ playAll(args.getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, false), false, false);
}
}
@@ -802,20 +814,19 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
if(!songs.isEmpty()) {
download(songs, append, false, !append, playNext, shuffle);
entryGridAdapter.clearSelected();
- }
- else {
- playAll(shuffle, append);
+ } else {
+ playAll(shuffle, append, playNext);
}
}
- private void playAll(final boolean shuffle, final boolean append) {
+ private void playAll(final boolean shuffle, final boolean append, final boolean playNext) {
boolean hasSubFolders = albums != null && !albums.isEmpty();
if (hasSubFolders && (id != null || share != null || "starred".equals(albumListType))) {
- downloadRecursively(id, false, append, !append, shuffle, false);
+ downloadRecursively(id, false, append, !append, shuffle, false, playNext);
} else if(hasSubFolders && albumListType != null) {
- downloadRecursively(albums, shuffle, append);
+ downloadRecursively(albums, shuffle, append, playNext);
} else {
- download(entries, append, false, !append, false, shuffle);
+ download(entries, append, false, !append, playNext, shuffle);
}
}
@@ -909,7 +920,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
for(Integer index: indexes) {
entryGridAdapter.removeAt(index);
}
- Util.toast(context, context.getResources().getString(R.string.removed_playlist, indexes.size(), name));
+ Util.toast(context, context.getResources().getString(R.string.removed_playlist, String.valueOf(indexes.size()), name));
}
@Override
@@ -953,70 +964,28 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
}.execute();
}
- public void unstarSelected() {
- List<Entry> selected = getSelectedEntries();
- if(selected.size() == 0) {
- selected = entries;
- }
- if(selected.size() == 0) {
- return;
- }
- final List<Entry> unstar = new ArrayList<Entry>();
- unstar.addAll(selected);
+ @Override
+ protected void toggleSelectedStarred() {
+ UpdateHelper.OnStarChange onStarChange = null;
+ if(albumListType != null && "starred".equals(albumListType)) {
+ onStarChange = new UpdateHelper.OnStarChange() {
+ @Override
+ public void starChange(boolean starred) {
- new LoadingTask<Void>(context, true) {
- @Override
- protected Void doInBackground() throws Throwable {
- MusicService musicService = MusicServiceFactory.getMusicService(context);
- List<Entry> entries = new ArrayList<Entry>();
- List<Entry> artists = new ArrayList<Entry>();
- List<Entry> albums = new ArrayList<Entry>();
- for(Entry entry: unstar) {
- if(entry.isDirectory() && Util.isTagBrowsing(context)) {
- if(entry.isAlbum()) {
- albums.add(entry);
- } else {
- artists.add(entry);
- }
- } else {
- entries.add(entry);
- }
}
- musicService.setStarred(entries, artists, albums, false, this, context);
- for(Entry entry: unstar) {
- new UpdateHelper.EntryInstanceUpdater(entry) {
- @Override
- public void update(Entry found) {
- found.setStarred(false);
+ @Override
+ public void starCommited(boolean starred) {
+ if(!starred) {
+ for (Entry entry : entries) {
+ entryGridAdapter.removeItem(entry);
}
- }.execute();
- }
-
- return null;
- }
-
- @Override
- protected void done(Void result) {
- Util.toast(context, context.getResources().getString(R.string.starring_content_unstarred, Integer.toString(unstar.size())));
-
- for(Entry entry: unstar) {
- entryGridAdapter.removeItem(entry);
- }
- }
-
- @Override
- protected void error(Throwable error) {
- String msg;
- if (error instanceof OfflineException || error instanceof ServerTooOldException) {
- msg = getErrorMessage(error);
- } else {
- msg = context.getResources().getString(R.string.starring_content_error, Integer.toString(unstar.size())) + " " + getErrorMessage(error);
+ }
}
+ };
+ }
- Util.toast(context, msg, false);
- }
- }.execute();
+ UpdateHelper.toggleStarred(context, getSelectedEntries(), onStarChange);
}
private void checkLicenseAndTrialPeriod(LoadingTask onValid) {
@@ -1105,11 +1074,8 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
@Override
protected Void doInBackground() throws Throwable {
DownloadService downloadService = getDownloadService();
+ downloadService.clear();
downloadService.setArtistRadio(artistId);
- if(downloadService.size() == 0) {
- Log.e(TAG, "Failed to create artist radio");
- throw new Exception("Failed to create artist radio");
- }
return null;
}
@@ -1159,22 +1125,22 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
});
imageLoader.loadImage(coverArtView, url, false);
} else if(entries.size() > 0) {
- Entry coverArt = null;
- for (int i = 0; (i < 3) && (coverArt == null || coverArt.getCoverArt() == null); i++) {
- coverArt = entries.get(random.nextInt(entries.size()));
+ coverArtRep = null;
+ this.coverArtView = coverArtView;
+ for (int i = 0; (i < 3) && (coverArtRep == null || coverArtRep.getCoverArt() == null); i++) {
+ coverArtRep = entries.get(random.nextInt(entries.size()));
}
- final Entry albumRep = coverArt;
coverArtView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (albumRep.getCoverArt() == null) {
+ if (coverArtRep == null || coverArtRep.getCoverArt() == null) {
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
ImageView fullScreenView = new ImageView(context);
- imageLoader.loadImage(fullScreenView, albumRep, true, true);
+ imageLoader.loadImage(fullScreenView, coverArtRep, true, true);
builder.setCancelable(true);
AlertDialog imageDialog = builder.create();
@@ -1183,7 +1149,10 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
imageDialog.show();
}
});
- imageLoader.loadImage(coverArtView, albumRep, false, true);
+ synchronized (coverArtRep) {
+ coverArtId = coverArtRep.getCoverArt();
+ updateCoverArtTask = imageLoader.loadImage(coverArtView, coverArtRep, false, true);
+ }
}
coverArtView.setOnInvalidated(new RecyclingImageView.OnInvalidated() {
@@ -1349,6 +1318,11 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Section
starButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.star_outline));
}
}
+
+ @Override
+ public void starCommited(boolean starred) {
+
+ }
});
}
});
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java
new file mode 100644
index 00000000..74c4b269
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java
@@ -0,0 +1,157 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2016 (C) Scott Jackson
+*/
+package github.daneren2005.dsub.fragments;
+
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.adapter.InternetRadioStationAdapter;
+import github.daneren2005.dsub.adapter.SectionAdapter;
+import github.daneren2005.dsub.domain.InternetRadioStation;
+import github.daneren2005.dsub.service.DownloadService;
+import github.daneren2005.dsub.service.MusicService;
+import github.daneren2005.dsub.util.ProgressListener;
+import github.daneren2005.dsub.util.TabBackgroundTask;
+import github.daneren2005.dsub.util.Util;
+import github.daneren2005.dsub.view.UpdateView;
+
+public class SelectInternetRadioStationFragment extends SelectRecyclerFragment<InternetRadioStation> {
+ private static final String TAG = SelectInternetRadioStationFragment.class.getSimpleName();
+
+ @Override
+ public int getOptionsMenu() {
+ return R.menu.abstract_top_menu;
+ }
+
+ @Override
+ public SectionAdapter<InternetRadioStation> getAdapter(List<InternetRadioStation> objs) {
+ return new InternetRadioStationAdapter(context, objs, this);
+ }
+
+ @Override
+ public List<InternetRadioStation> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception {
+ return musicService.getInternetRadioStations(refresh, context, listener);
+ }
+
+ @Override
+ public int getTitleResource() {
+ return R.string.button_bar_internet_radio;
+ }
+
+ @Override
+ public void onItemClicked(UpdateView<InternetRadioStation> updateView, final InternetRadioStation item) {
+ new TabBackgroundTask<Void>(this) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ DownloadService downloadService = getDownloadService();
+ if(downloadService == null) {
+ return null;
+ }
+
+ getStreamFromPlaylist(item);
+ downloadService.download(item);
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ context.openNowPlaying();
+ }
+ }.execute();
+ }
+
+ @Override
+ public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<InternetRadioStation> updateView, InternetRadioStation item) {
+ menuInflater.inflate(R.menu.select_internet_radio_context, menu);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem menuItem, UpdateView<InternetRadioStation> updateView, InternetRadioStation item) {
+ switch (menuItem.getItemId()) {
+ case R.id.internet_radio_info:
+ displayInternetRadioStationInfo(item);
+ break;
+ }
+
+ return false;
+ }
+
+ private void getStreamFromPlaylist(InternetRadioStation internetRadioStation) {
+ if(internetRadioStation.getStreamUrl() != null && (internetRadioStation.getStreamUrl().indexOf(".m3u") != -1 || internetRadioStation.getStreamUrl().indexOf(".pls") != -1)) {
+ try {
+ URL url = new URL(internetRadioStation.getStreamUrl());
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+
+ try {
+ BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+ String line;
+ while((line = in.readLine()) != null) {
+ // Not blank line or comment
+ if(line.length() > 0 && line.indexOf('#') != 0) {
+ if(internetRadioStation.getStreamUrl().indexOf(".m3u") != -1) {
+ internetRadioStation.setStreamUrl(line);
+ break;
+ } else {
+ if(line.indexOf("File1=") == 0) {
+ internetRadioStation.setStreamUrl(line.replace("File1=", ""));
+ } else if(line.indexOf("Title1=") == 0) {
+ internetRadioStation.setTitle(line.replace("Title1=", ""));
+ }
+ }
+ }
+ }
+ } finally {
+ connection.disconnect();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get stream data from playlist", e);
+ }
+
+ }
+ }
+
+ private void displayInternetRadioStationInfo(final InternetRadioStation station) {
+ List<Integer> headers = new ArrayList<>();
+ List<String> details = new ArrayList<>();
+
+ headers.add(R.string.details_title);
+ details.add(station.getTitle());
+
+ headers.add(R.string.details_home_page);
+ details.add(station.getHomePageUrl());
+
+ headers.add(R.string.details_stream_url);
+ details.add(station.getStreamUrl());
+
+ Util.showDetailsDialog(context, R.string.details_title_internet_radio_station, headers, details);
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java
index 6e2c9da5..5cb413fe 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java
@@ -1,5 +1,8 @@
package github.daneren2005.dsub.fragments;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import android.support.v7.app.AlertDialog;
import android.content.DialogInterface;
import android.content.res.Resources;
@@ -184,6 +187,22 @@ public class SelectPlaylistFragment extends SelectRecyclerFragment<Playlist> {
replaceFragment(fragment);
}
+ @Override
+ public void onFinishRefresh() {
+ Bundle args = getArguments();
+ if(args != null) {
+ String playlistId = args.getString(Constants.INTENT_EXTRA_NAME_ID, null);
+ if (playlistId != null && objects != null) {
+ for (Playlist playlist : objects) {
+ if (playlistId.equals(playlist.getId())) {
+ onItemClicked(null, playlist);
+ break;
+ }
+ }
+ }
+ }
+ }
+
private void deletePlaylist(final Playlist playlist) {
Util.confirmDialog(context, R.string.common_delete, playlist.getName(), new DialogInterface.OnClickListener() {
@Override
@@ -326,7 +345,24 @@ public class SelectPlaylistFragment extends SelectRecyclerFragment<Playlist> {
private void syncPlaylist(Playlist playlist) {
SyncUtil.addSyncedPlaylist(context, playlist.getId());
- downloadPlaylist(playlist.getId(), playlist.getName(), true, true, false, false, true);
+
+ boolean syncImmediately;
+ if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_SYNC_WIFI, true)) {
+ ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = manager.getActiveNetworkInfo();
+
+ if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+ syncImmediately = true;
+ } else {
+ syncImmediately = false;
+ }
+ } else {
+ syncImmediately = true;
+ }
+
+ if(syncImmediately) {
+ downloadPlaylist(playlist.getId(), playlist.getName(), true, true, false, false, true);
+ }
}
private void stopSyncPlaylist(final Playlist playlist) {
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java
index 3f8f7844..9011b4c5 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectPodcastsFragment.java
@@ -154,25 +154,18 @@ public class SelectPodcastsFragment extends SelectRecyclerFragment<Serializable>
if(newestEpisodes == null || newestEpisodes.getChildrenSize() == 0) {
return new PodcastChannelAdapter(context, channels, hasCoverArt ? getImageLoader() : null, this, largeAlbums);
} else {
- List<String> headers = Arrays.asList(PodcastChannelAdapter.EPISODE_HEADER, PodcastChannelAdapter.CHANNEL_HEADER);
+ Resources res = context.getResources();
+ List<String> headers = Arrays.asList(res.getString(R.string.main_albums_newest), res.getString(R.string.select_podcasts_channels));
List<MusicDirectory.Entry> episodes = newestEpisodes.getChildren(false, true);
List<Serializable> serializableEpisodes = new ArrayList<>();
-
- // Put 3 in current list
- while(serializableEpisodes.size() < 3 && !episodes.isEmpty()) {
- serializableEpisodes.add(episodes.remove(0));
- }
-
- // Put rest in extra set
- List<Serializable> extraEpisodes = new ArrayList<>();
- extraEpisodes.addAll(episodes);
+ serializableEpisodes.addAll(episodes);
List<List<Serializable>> sections = new ArrayList<>();
sections.add(serializableEpisodes);
sections.add(channels);
- return new PodcastChannelAdapter(context, headers, sections, extraEpisodes, ServerInfo.checkServerVersion(context, "1.13") ? getImageLoader() : null, this, largeAlbums);
+ return new PodcastChannelAdapter(context, headers, sections, ServerInfo.checkServerVersion(context, "1.13") ? getImageLoader() : null, this, largeAlbums);
}
}
@@ -182,7 +175,7 @@ public class SelectPodcastsFragment extends SelectRecyclerFragment<Serializable>
if(!Util.isOffline(context) && ServerInfo.hasNewestPodcastEpisodes(context)) {
try {
- newestEpisodes = musicService.getNewestPodcastEpisodes(10, context, listener);
+ newestEpisodes = musicService.getNewestPodcastEpisodes(refresh, context, listener, 10);
for(MusicDirectory.Entry entry: newestEpisodes.getChildren()) {
for(PodcastChannel channel: channels) {
@@ -246,12 +239,12 @@ public class SelectPodcastsFragment extends SelectRecyclerFragment<Serializable>
return;
}
- playNow(Arrays.asList((MusicDirectory.Entry) episode));
+ onSongPress(Arrays.asList((MusicDirectory.Entry) episode), episode, false);
}
}
@Override
- public GridLayoutManager.SpanSizeLookup getSpanSizeLookup() {
+ public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final GridLayoutManager gridLayoutManager) {
return new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
@@ -259,7 +252,7 @@ public class SelectPodcastsFragment extends SelectRecyclerFragment<Serializable>
if(adapter != null) {
int viewType = getCurrentAdapter().getItemViewType(position);
if (viewType == SectionAdapter.VIEW_TYPE_HEADER || viewType == PodcastChannelAdapter.VIEW_TYPE_PODCAST_EPISODE || viewType == PodcastChannelAdapter.VIEW_TYPE_PODCAST_LEGACY) {
- return getRecyclerColumnCount();
+ return gridLayoutManager.getSpanCount();
} else {
return 1;
}
@@ -270,6 +263,25 @@ public class SelectPodcastsFragment extends SelectRecyclerFragment<Serializable>
};
}
+ @Override
+ public void onFinishRefresh() {
+ Bundle args = getArguments();
+ if(args != null) {
+ String podcastId = args.getString(Constants.INTENT_EXTRA_NAME_ID, null);
+ if (podcastId != null && objects != null) {
+ for (Serializable ser : objects) {
+ if (ser instanceof PodcastChannel) {
+ PodcastChannel podcast = (PodcastChannel) ser;
+ if (podcastId.equals(podcast.getId())) {
+ onItemClicked(null, podcast);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
public void refreshPodcasts() {
new SilentBackgroundTask<Void>(context) {
@Override
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java
index 7ae7fff8..0d4506ac 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java
@@ -15,10 +15,15 @@
package github.daneren2005.dsub.fragments;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.Context;
import android.os.Bundle;
+import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -102,6 +107,7 @@ public abstract class SelectRecyclerFragment<T> extends SubsonicFragment impleme
}
menuInflater.inflate(getOptionsMenu(), menu);
+ onFinishSetupOptionsMenu(menu);
}
@Override
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectShareFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectShareFragment.java
index cb0e48b9..f231fa33 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectShareFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectShareFragment.java
@@ -27,6 +27,7 @@ import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.EditText;
+import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -104,10 +105,34 @@ public class SelectShareFragment extends SelectRecyclerFragment<Share> {
}
private void displayShareInfo(final Share share) {
- String message = context.getResources().getString(R.string.share_info,
- share.getUsername(), (share.getDescription() != null) ? share.getDescription() : "", share.getUrl(),
- Util.formatDate(share.getCreated()), Util.formatDate(share.getLastVisited()), Util.formatDate(share.getExpires()), share.getVisitCount());
- Util.info(context, share.getName(), message);
+ List<Integer> headers = new ArrayList<>();
+ List<String> details = new ArrayList<>();
+
+ headers.add(R.string.details_title);
+ details.add(share.getName());
+
+ headers.add(R.string.details_owner);
+ details.add(share.getUsername());
+
+ headers.add(R.string.details_description);
+ details.add(share.getDescription());
+
+ headers.add(R.string.details_url);
+ details.add(share.getUrl());
+
+ headers.add(R.string.details_created);
+ details.add(Util.formatDate(share.getCreated()));
+
+ headers.add(R.string.details_last_played);
+ details.add(Util.formatDate(share.getLastVisited()));
+
+ headers.add(R.string.details_expiration);
+ details.add(Util.formatDate(share.getExpires(), false));
+
+ headers.add(R.string.details_played_count);
+ details.add(Long.toString(share.getVisitCount()));
+
+ Util.showDetailsDialog(context, R.string.details_title_playlist, headers, details);
}
private void updateShareInfo(final Share share) {
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java
index 9853e046..fa2ca340 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java
@@ -15,12 +15,14 @@
package github.daneren2005.dsub.fragments;
+import android.Manifest;
import android.accounts.Account;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
@@ -29,6 +31,8 @@ import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
import android.text.InputType;
import android.util.Log;
import android.view.LayoutInflater;
@@ -47,6 +51,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.activity.SubsonicActivity;
import github.daneren2005.dsub.service.DownloadService;
import github.daneren2005.dsub.service.HeadphoneListenerService;
import github.daneren2005.dsub.service.MusicService;
@@ -54,8 +59,10 @@ import github.daneren2005.dsub.service.MusicServiceFactory;
import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.FileUtil;
import github.daneren2005.dsub.util.LoadingTask;
+import github.daneren2005.dsub.util.MediaRouteManager;
import github.daneren2005.dsub.util.SyncUtil;
import github.daneren2005.dsub.util.Util;
+import github.daneren2005.dsub.view.CacheLocationPreference;
import github.daneren2005.dsub.view.ErrorDialog;
public class SettingsFragment extends PreferenceCompatFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -69,7 +76,7 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared
private ListPreference maxVideoBitrateWifi;
private ListPreference maxVideoBitrateMobile;
private ListPreference networkTimeout;
- private EditTextPreference cacheLocation;
+ private CacheLocationPreference cacheLocation;
private ListPreference preloadCountWifi;
private ListPreference preloadCountMobile;
private ListPreference keepPlayedCount;
@@ -77,6 +84,7 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared
private ListPreference pauseDisconnect;
private Preference addServerPreference;
private PreferenceCategory serversCategory;
+ private ListPreference songPressAction;
private ListPreference videoPlayer;
private ListPreference syncInterval;
private CheckBoxPreference syncEnabled;
@@ -137,6 +145,8 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared
xml = R.xml.settings_playback;
} else if("servers".equals(name)) {
xml = R.xml.settings_servers;
+ } else if ("cast".equals(name)) {
+ xml = R.xml.settings_cast;
}
if(xml != 0) {
@@ -184,6 +194,25 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared
} else {
context.stopService(serviceIntent);
}
+ } else if(Constants.PREFERENCES_KEY_THEME.equals(key)) {
+ String value = sharedPreferences.getString(key, null);
+ if("day/night".equals(value) || "day/black".equals(value)) {
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(context, new String[]{ Manifest.permission.ACCESS_COARSE_LOCATION }, SubsonicActivity.PERMISSIONS_REQUEST_LOCATION);
+ }
+ }
+ } else if(Constants.PREFERENCES_KEY_DLNA_CASTING_ENABLED.equals(key)) {
+ DownloadService downloadService = DownloadService.getInstance();
+ if(downloadService != null) {
+ MediaRouteManager mediaRouter = downloadService.getMediaRouter();
+
+ Boolean enabled = sharedPreferences.getBoolean(key, true);
+ if (enabled) {
+ mediaRouter.addDLNAProvider();
+ } else {
+ mediaRouter.removeDLNAProvider();
+ }
+ }
}
scheduleBackup();
@@ -205,7 +234,7 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared
maxVideoBitrateWifi = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_WIFI);
maxVideoBitrateMobile = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_MOBILE);
networkTimeout = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT);
- cacheLocation = (EditTextPreference) this.findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION);
+ cacheLocation = (CacheLocationPreference) this.findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION);
preloadCountWifi = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT_WIFI);
preloadCountMobile = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT_MOBILE);
keepPlayedCount = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_KEEP_PLAYED_CNT);
@@ -214,6 +243,7 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared
serversCategory = (PreferenceCategory) this.findPreference(Constants.PREFERENCES_KEY_SERVER_KEY);
addServerPreference = this.findPreference(Constants.PREFERENCES_KEY_SERVER_ADD);
videoPlayer = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_VIDEO_PLAYER);
+ songPressAction = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_SONG_PRESS_ACTION);
syncInterval = (ListPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_INTERVAL);
syncEnabled = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_ENABLED);
syncWifi = (CheckBoxPreference) this.findPreference(Constants.PREFERENCES_KEY_SYNC_WIFI);
@@ -350,6 +380,8 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared
if(theme != null) {
theme.setSummary(theme.getEntry());
+ }
+ if(openToTab != null) {
openToTab.setSummary(openToTab.getEntry());
}
@@ -379,6 +411,7 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared
keepPlayedCount.setSummary(keepPlayedCount.getEntry());
tempLoss.setSummary(tempLoss.getEntry());
pauseDisconnect.setSummary(pauseDisconnect.getEntry());
+ songPressAction.setSummary(songPressAction.getEntry());
videoPlayer.setSummary(videoPlayer.getEntry());
if(replayGain.isChecked()) {
@@ -423,6 +456,7 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared
for (ServerSettings ss : serverSettings.values()) {
if(!ss.update()) {
serversCategory.removePreference(ss.getScreen());
+ serverCount--;
}
}
}
@@ -633,7 +667,7 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared
} catch(Exception e) {
Log.w(TAG, "Failed to create " + musicNoMedia, e);
}
- } else if (nomediaDir.exists()) {
+ } else if (!hide && nomediaDir.exists()) {
if (!nomediaDir.delete()) {
Log.w(TAG, "Failed to delete " + nomediaDir);
}
@@ -664,8 +698,11 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared
SharedPreferences.Editor editor = prefs.edit();
editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, defaultPath);
editor.commit();
- cacheLocation.setSummary(defaultPath);
- cacheLocation.setText(defaultPath);
+
+ if(cacheLocation != null) {
+ cacheLocation.setSummary(defaultPath);
+ cacheLocation.setText(defaultPath);
+ }
}
// Clear download queue.
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SimilarArtistFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SimilarArtistFragment.java
index 93e3a93a..a41b9d6f 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SimilarArtistFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SimilarArtistFragment.java
@@ -15,14 +15,16 @@
package github.daneren2005.dsub.fragments;
+import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import github.daneren2005.dsub.R;
-import github.daneren2005.dsub.adapter.ArtistAdapter;
import github.daneren2005.dsub.adapter.SectionAdapter;
+import github.daneren2005.dsub.adapter.SimilarArtistAdapter;
import github.daneren2005.dsub.domain.Artist;
import github.daneren2005.dsub.domain.ArtistInfo;
import github.daneren2005.dsub.domain.MusicDirectory;
@@ -35,6 +37,8 @@ import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.view.UpdateView;
import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
@@ -52,18 +56,6 @@ public class SimilarArtistFragment extends SelectRecyclerFragment<Artist> {
}
@Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
- super.onCreateOptionsMenu(menu, menuInflater);
- if(!primaryFragment) {
- return;
- }
-
- if(info.getMissingArtists().isEmpty()) {
- menu.removeItem(R.id.menu_show_missing);
- }
- }
-
- @Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_play_now:
@@ -72,9 +64,6 @@ public class SimilarArtistFragment extends SelectRecyclerFragment<Artist> {
case R.id.menu_shuffle:
playAll(true);
return true;
- case R.id.menu_show_missing:
- showMissingArtists();
- break;
}
return super.onOptionsItemSelected(item);
@@ -82,8 +71,10 @@ public class SimilarArtistFragment extends SelectRecyclerFragment<Artist> {
@Override
public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<Artist> updateView, Artist item) {
- onCreateContextMenuSupport(menu, menuInflater, updateView, item);
- recreateContextMenu(menu);
+ if(!Artist.MISSING_ID.equals(item.getId())) {
+ onCreateContextMenuSupport(menu, menuInflater, updateView, item);
+ recreateContextMenu(menu);
+ }
}
@Override
@@ -93,14 +84,21 @@ public class SimilarArtistFragment extends SelectRecyclerFragment<Artist> {
@Override
public void onItemClicked(UpdateView<Artist> updateView, Artist artist) {
- SubsonicFragment fragment = new SelectDirectoryFragment();
- Bundle args = new Bundle();
- args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId());
- args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName());
- args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
- fragment.setArguments(args);
-
- replaceFragment(fragment);
+ if(Artist.MISSING_ID.equals(artist.getId())) {
+ String url = "http://www.last.fm/music/" + URLEncoder.encode(artist.getName());
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(url));
+ startActivity(intent);
+ } else {
+ SubsonicFragment fragment = new SelectDirectoryFragment();
+ Bundle args = new Bundle();
+ args.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId());
+ args.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getName());
+ args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
+ fragment.setArguments(args);
+
+ replaceFragment(fragment);
+ }
}
@Override
@@ -109,8 +107,22 @@ public class SimilarArtistFragment extends SelectRecyclerFragment<Artist> {
}
@Override
- public SectionAdapter getAdapter(List<Artist> objects) {
- return new ArtistAdapter(context, objects, this);
+ public SectionAdapter getAdapter(List<Artist> artists) {
+ if(info.getMissingArtists().isEmpty()) {
+ return new SimilarArtistAdapter(context, artists, this);
+ } else {
+ List<String> headers = new ArrayList<>();
+ headers.add(null);
+ headers.add(context.getResources().getString(R.string.menu_similar_artists_missing));
+
+ List<Artist> missingArtists = new ArrayList<>();
+ for(String artistName: info.getMissingArtists()) {
+ Artist artist = new Artist(Artist.MISSING_ID, artistName);
+ missingArtists.add(artist);
+ }
+
+ return new SimilarArtistAdapter(context, headers, Arrays.asList(artists, missingArtists), this);
+ }
}
@Override
@@ -124,16 +136,6 @@ public class SimilarArtistFragment extends SelectRecyclerFragment<Artist> {
return R.string.menu_similar_artists;
}
- private void showMissingArtists() {
- StringBuilder b = new StringBuilder();
-
- for(String name: info.getMissingArtists()) {
- b.append("<h3><a href=\"https://www.google.com/#q=" + URLEncoder.encode(name) + "\">" + name + "</a></h3> ");
- }
-
- Util.showHTMLDialog(context, R.string.menu_similar_artists, b.toString());
- }
-
private void playAll(final boolean shuffle) {
new RecursiveLoader(context) {
@Override
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java
index c503ec6c..de230309 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java
@@ -20,6 +20,9 @@ package github.daneren2005.dsub.fragments;
import android.annotation.TargetApi;
import android.app.Activity;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -37,6 +40,7 @@ import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Menu;
@@ -125,6 +129,8 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
protected boolean artistOverride = false;
protected SwipeRefreshLayout refreshLayout;
protected boolean firstRun;
+ protected MenuItem searchItem;
+ protected SearchView searchView;
public SubsonicFragment() {
super();
@@ -177,15 +183,36 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
this.context = context;
}
+ protected void onFinishSetupOptionsMenu(final Menu menu) {
+ searchItem = menu.findItem(R.id.menu_global_search);
+ if(searchItem != null) {
+ searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
+ SearchManager searchManager = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
+ SearchableInfo searchableInfo = searchManager.getSearchableInfo(context.getComponentName());
+ if(searchableInfo == null) {
+ Log.w(TAG, "Failed to get SearchableInfo");
+ } else {
+ searchView.setSearchableInfo(searchableInfo);
+ }
+
+ String currentQuery = getCurrentQuery();
+ if(currentQuery != null) {
+ searchView.setOnSearchClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ searchView.setQuery(getCurrentQuery(), false);
+ }
+ });
+ }
+ }
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_global_shuffle:
onShuffleRequested();
return true;
- case R.id.menu_global_search:
- context.onSearchRequested();
- return true;
case R.id.menu_exit:
exit();
return true;
@@ -221,6 +248,9 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
addToPlaylist(songs);
clearSelected();
return true;
+ case R.id.menu_star:case R.id.menu_unstar:
+ toggleSelectedStarred();
+ return true;
}
return false;
@@ -269,7 +299,6 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
menu.removeItem(R.id.menu_rate);
}
}
- menu.findItem(entry.isDirectory() ? R.id.album_menu_star : R.id.song_menu_star).setTitle(entry.isStarred() ? R.string.common_unstar : R.string.common_star);
} else if(!entry.isVideo()) {
if(Util.isOffline(context)) {
menuInflater.inflate(R.menu.select_song_context_offline, menu);
@@ -280,8 +309,13 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
if(entry.getBookmark() == null) {
menu.removeItem(R.id.bookmark_menu_delete);
}
+
+
+ String songPressAction = Util.getSongPressAction(context);
+ if(!"next".equals(songPressAction) && !"last".equals(songPressAction)) {
+ menu.setGroupVisible(R.id.hide_play_now, false);
+ }
}
- menu.findItem(entry.isDirectory() ? R.id.album_menu_star : R.id.song_menu_star).setTitle(entry.isStarred() ? R.string.common_unstar : R.string.common_star);
} else {
if(Util.isOffline(context)) {
menuInflater.inflate(R.menu.select_video_context_offline, menu);
@@ -290,6 +324,15 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
menuInflater.inflate(R.menu.select_video_context, menu);
}
}
+
+ MenuItem starMenu = menu.findItem(entry.isDirectory() ? R.id.album_menu_star : R.id.song_menu_star);
+ if(starMenu != null) {
+ starMenu.setTitle(entry.isStarred() ? R.string.common_unstar : R.string.common_star);
+ }
+
+ if(!isShowArtistEnabled() || (!Util.isTagBrowsing(context) && entry.getParent() == null) || (Util.isTagBrowsing(context) && entry.getArtistId() == null)) {
+ menu.setGroupVisible(R.id.hide_show_artist, false);
+ }
} else if(selected instanceof Artist) {
Artist artist = (Artist) selected;
if(Util.isOffline(context)) {
@@ -323,6 +366,9 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
public boolean onContextItemSelected(MenuItem menuItem, Object selectedItem) {
Artist artist = selectedItem instanceof Artist ? (Artist) selectedItem : null;
Entry entry = selectedItem instanceof Entry ? (Entry) selectedItem : null;
+ if(selectedItem instanceof DownloadFile) {
+ entry = ((DownloadFile) selectedItem).getSong();
+ }
List<Entry> songs = new ArrayList<Entry>(1);
songs.add(entry);
@@ -390,6 +436,9 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
case R.id.album_menu_share:
createShare(songs);
break;
+ case R.id.song_menu_play_now:
+ playNow(songs);
+ break;
case R.id.song_menu_play_next:
getDownloadService().download(songs, false, false, true, false);
break;
@@ -611,7 +660,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
}
});
- refreshLayout.setColorScheme(
+ refreshLayout.setColorSchemeResources(
R.color.holo_blue_light,
R.color.holo_orange_light,
R.color.holo_green_light,
@@ -634,7 +683,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
}
});
- refreshLayout.setColorScheme(
+ refreshLayout.setColorSchemeResources(
R.color.holo_blue_light,
R.color.holo_orange_light,
R.color.holo_green_light,
@@ -656,7 +705,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
final int columns = getRecyclerColumnCount();
GridLayoutManager gridLayoutManager = new GridLayoutManager(context, columns);
- GridLayoutManager.SpanSizeLookup spanSizeLookup = getSpanSizeLookup();
+ GridLayoutManager.SpanSizeLookup spanSizeLookup = getSpanSizeLookup(gridLayoutManager);
if(spanSizeLookup != null) {
gridLayoutManager.setSpanSizeLookup(spanSizeLookup);
}
@@ -671,15 +720,15 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
return layoutManager;
}
- public GridLayoutManager.SpanSizeLookup getSpanSizeLookup() {
+ public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final GridLayoutManager gridLayoutManager) {
return new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
SectionAdapter adapter = getCurrentAdapter();
if(adapter != null) {
- int viewType = getCurrentAdapter().getItemViewType(position);
+ int viewType = adapter.getItemViewType(position);
if (viewType == SectionAdapter.VIEW_TYPE_HEADER) {
- return getRecyclerColumnCount();
+ return gridLayoutManager.getSpanCount();
} else {
return 1;
}
@@ -722,6 +771,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
if(downloadService == null) {
return;
}
+ downloadService.clear();
downloadService.setShufflePlayEnabled(true);
context.openNowPlaying();
return;
@@ -825,6 +875,8 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
if (downloadService == null) {
return;
}
+
+ downloadService.clear();
downloadService.setShufflePlayEnabled(true);
context.openNowPlaying();
}
@@ -909,7 +961,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
}.execute();
}
- protected void downloadRecursively(final List<Entry> albums, final boolean shuffle, final boolean append) {
+ protected void downloadRecursively(final List<Entry> albums, final boolean shuffle, final boolean append, final boolean playNext) {
new RecursiveLoader(context) {
@Override
protected Boolean doInBackground() throws Throwable {
@@ -937,7 +989,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
downloadService.clear();
}
- downloadService.download(songs, false, true, false, false);
+ downloadService.download(songs, false, true, playNext, false);
if(!append) {
transition = true;
}
@@ -965,6 +1017,14 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
}
protected void addToPlaylist(final List<Entry> songs) {
+ Iterator<Entry> it = songs.iterator();
+ while(it.hasNext()) {
+ Entry entry = it.next();
+ if(entry.isDirectory()) {
+ it.remove();
+ }
+ }
+
if(songs.isEmpty()) {
Util.toast(context, "No songs selected");
return;
@@ -1053,7 +1113,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
@Override
protected void done(Void result) {
- Util.toast(context, context.getResources().getString(R.string.updated_playlist, songs.size(), playlist.getName()));
+ Util.toast(context, context.getResources().getString(R.string.updated_playlist, String.valueOf(songs.size()), playlist.getName()));
}
@Override
@@ -1075,16 +1135,24 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
final EditText playlistNameView = (EditText) layout.findViewById(R.id.save_playlist_name);
final CheckBox overwriteCheckBox = (CheckBox) layout.findViewById(R.id.save_playlist_overwrite);
if(getSuggestion) {
- String playlistName = (getDownloadService() != null) ? getDownloadService().getSuggestedPlaylistName() : null;
+ DownloadService downloadService = getDownloadService();
+ String playlistName = null;
+ String playlistId = null;
+ if(downloadService != null) {
+ playlistName = downloadService.getSuggestedPlaylistName();
+ playlistId = downloadService.getSuggestedPlaylistId();
+ }
if (playlistName != null) {
playlistNameView.setText(playlistName);
- try {
- if(ServerInfo.checkServerVersion(context, "1.8.0") && Integer.parseInt(getDownloadService().getSuggestedPlaylistId()) != -1) {
- overwriteCheckBox.setChecked(true);
- overwriteCheckBox.setVisibility(View.VISIBLE);
+ if(playlistId != null) {
+ try {
+ if (ServerInfo.checkServerVersion(context, "1.8.0") && Integer.parseInt(playlistId) != -1) {
+ overwriteCheckBox.setChecked(true);
+ overwriteCheckBox.setVisibility(View.VISIBLE);
+ }
+ } catch (Exception e) {
+ Log.i(TAG, "Playlist id isn't a integer, probably MusicCabinet", e);
}
- } catch(Exception e) {
- Log.i(TAG, "Playlist id isn't a integer, probably MusicCabinet");
}
} else {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
@@ -1145,6 +1213,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
@Override
protected void error(Throwable error) {
String msg = context.getResources().getString(R.string.download_playlist_error) + " " + getErrorMessage(error);
+ Log.e(TAG, "Failed to create playlist", error);
Util.toast(context, msg);
}
}.execute();
@@ -1174,6 +1243,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
msg = context.getResources().getString(R.string.download_playlist_error) + " " + getErrorMessage(error);
}
+ Log.e(TAG, "Failed to overwrite playlist", error);
Util.toast(context, msg, false);
}
}.execute();
@@ -1572,7 +1642,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
.setPositiveButton(R.string.bookmark_action_resume, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
- playNow(songs, song, position);
+ playNow(songs, song, position, playlistName, playlistId);
}
})
.setNegativeButton(R.string.bookmark_action_start_over, new DialogInterface.OnClickListener() {
@@ -1605,13 +1675,40 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
}
}.execute();
- playNow(songs, 0);
+ playNow(songs, 0, playlistName, playlistId);
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
+ protected void onSongPress(List<Entry> entries, Entry entry) {
+ onSongPress(entries, entry, 0, true);
+ }
+ protected void onSongPress(List<Entry> entries, Entry entry, boolean allowPlayAll) {
+ onSongPress(entries, entry, 0, allowPlayAll);
+ }
+ protected void onSongPress(List<Entry> entries, Entry entry, int position, boolean allowPlayAll) {
+ List<Entry> songs = new ArrayList<Entry>();
+
+ String songPressAction = Util.getSongPressAction(context);
+ if("all".equals(songPressAction) && allowPlayAll) {
+ for(Entry song: entries) {
+ if(!song.isDirectory() && !song.isVideo()) {
+ songs.add(song);
+ }
+ }
+ playNow(songs, entry, position);
+ } else if("next".equals(songPressAction)) {
+ getDownloadService().download(Arrays.asList(entry), false, false, true, false);
+ } else if("last".equals(songPressAction)) {
+ getDownloadService().download(Arrays.asList(entry), false, false, false, false);
+ } else {
+ songs.add(entry);
+ playNow(songs);
+ }
+ }
+
protected void playNow(List<Entry> entries) {
playNow(entries, null, null);
}
@@ -1659,15 +1756,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
new LoadingTask<Void>(context) {
@Override
protected Void doInBackground() throws Throwable {
- DownloadService downloadService = getDownloadService();
- if(downloadService == null) {
- return null;
- }
-
- downloadService.clear();
- downloadService.download(entries, false, true, true, false, entries.indexOf(song), position);
- downloadService.setSuggestedPlaylistName(playlistName, playlistId);
-
+ playNowInTask(entries, song, position, playlistName, playlistId);
return null;
}
@@ -1677,6 +1766,19 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
}
}.execute();
}
+ protected void playNowInTask(final List<Entry> entries, final Entry song, final int position) {
+ playNowInTask(entries, song, position, null, null);
+ }
+ protected void playNowInTask(final List<Entry> entries, final Entry song, final int position, final String playlistName, final String playlistId) {
+ DownloadService downloadService = getDownloadService();
+ if(downloadService == null) {
+ return;
+ }
+
+ downloadService.clear();
+ downloadService.download(entries, false, true, true, false, entries.indexOf(song), position);
+ downloadService.setSuggestedPlaylistName(playlistName, playlistId);
+ }
protected void deleteBookmark(final MusicDirectory.Entry entry, final SectionAdapter adapter) {
Util.confirmDialog(context, R.string.bookmark_delete_title, entry.getTitle(), new DialogInterface.OnClickListener() {
@@ -1691,7 +1793,7 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
MusicService musicService = MusicServiceFactory.getMusicService(context);
musicService.deleteBookmark(entry, context, null);
- new UpdateHelper.EntryInstanceUpdater(entry) {
+ new UpdateHelper.EntryInstanceUpdater(entry, DownloadService.METADATA_UPDATED_BOOKMARK) {
@Override
public void update(Entry found) {
found.setBookmark(null);
@@ -1898,6 +2000,18 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
}
}
+ protected void toggleSelectedStarred() {
+ UpdateHelper.toggleStarred(context, getSelectedEntries());
+ }
+
+ protected boolean isShowArtistEnabled() {
+ return false;
+ }
+
+ protected String getCurrentQuery() {
+ return null;
+ }
+
public abstract class RecursiveLoader extends LoadingTask<Boolean> {
protected MusicService musicService;
protected static final int MAX_SONGS = 500;
@@ -1909,6 +2023,23 @@ public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnR
musicService = MusicServiceFactory.getMusicService(context);
}
+ protected void getSiblingsRecursively(Entry entry) throws Exception {
+ MusicDirectory parent = new MusicDirectory();
+ if(Util.isTagBrowsing(context) && !Util.isOffline(context)) {
+ parent.setId(entry.getAlbumId());
+ } else {
+ parent.setId(entry.getParent());
+ }
+
+ if(parent.getId() == null) {
+ songs.add(entry);
+ } else {
+ MusicDirectory.Entry dir = new Entry(parent.getId());
+ dir.setDirectory(true);
+ parent.addChild(dir);
+ getSongsRecursively(parent, songs);
+ }
+ }
protected void getSongsRecursively(List<Entry> entry) throws Exception {
getSongsRecursively(entry, false);
}
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/UserFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/UserFragment.java
index dab104bd..dde76624 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/UserFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/UserFragment.java
@@ -20,6 +20,8 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import github.daneren2005.dsub.R;
@@ -78,7 +80,7 @@ public class UserFragment extends SelectRecyclerFragment<User.Setting>{
@Override
public SectionAdapter<User.Setting> getAdapter(List<User.Setting> objs) {
- return new SettingsAdapter(context, user, getImageLoader(), UserUtil.isCurrentAdmin() && ServerInfo.checkServerVersion(context, "1.10"), this);
+ return SettingsAdapter.getSettingsAdapter(context, user, getImageLoader(), this);
}
@Override
diff --git a/app/src/main/java/github/daneren2005/dsub/provider/DLNARouteProvider.java b/app/src/main/java/github/daneren2005/dsub/provider/DLNARouteProvider.java
index c5632362..0ee16723 100644
--- a/app/src/main/java/github/daneren2005/dsub/provider/DLNARouteProvider.java
+++ b/app/src/main/java/github/daneren2005/dsub/provider/DLNARouteProvider.java
@@ -71,6 +71,7 @@ public class DLNARouteProvider extends MediaRouteProvider {
private List<String> removing = new ArrayList<String>();
private AndroidUpnpService dlnaService;
private ServiceConnection dlnaServiceConnection;
+ private RegistryListener registryListener;
private boolean searchOnConnect = false;
public DLNARouteProvider(Context context) {
@@ -84,7 +85,7 @@ public class DLNARouteProvider extends MediaRouteProvider {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
dlnaService = (AndroidUpnpService) service;
- dlnaService.getRegistry().addListener(new RegistryListener() {
+ dlnaService.getRegistry().addListener(registryListener = new RegistryListener() {
@Override
public void remoteDeviceDiscoveryStarted(Registry registry, RemoteDevice remoteDevice) {
@@ -142,6 +143,7 @@ public class DLNARouteProvider extends MediaRouteProvider {
@Override
public void onServiceDisconnected(ComponentName name) {
dlnaService = null;
+ registryListener = null;
}
};
@@ -166,7 +168,7 @@ public class DLNARouteProvider extends MediaRouteProvider {
DLNADevice device = deviceEntry.getValue();
int volume;
- if(device.volumeMax == 0) {
+ if(device.volumeMax <= 0) {
volume = 5;
} else {
int increments = (int) Math.ceil(device.volumeMax / 10.0);
@@ -290,6 +292,17 @@ public class DLNARouteProvider extends MediaRouteProvider {
}
}
+ public void destroy() {
+ if(dlnaService != null) {
+ dlnaService.getRegistry().removeListener(registryListener);
+ registryListener = null;
+ }
+
+ if(dlnaServiceConnection != null) {
+ getContext().getApplicationContext().unbindService(dlnaServiceConnection);
+ }
+ }
+
private class DLNARouteController extends RouteController {
private DLNADevice device;
diff --git a/app/src/main/java/github/daneren2005/dsub/provider/DSubSearchProvider.java b/app/src/main/java/github/daneren2005/dsub/provider/DSubSearchProvider.java
index f91c364e..ba8c80c1 100644
--- a/app/src/main/java/github/daneren2005/dsub/provider/DSubSearchProvider.java
+++ b/app/src/main/java/github/daneren2005/dsub/provider/DSubSearchProvider.java
@@ -58,6 +58,10 @@ public class DSubSearchProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ if(selectionArgs[0].isEmpty()) {
+ return null;
+ }
+
String query = selectionArgs[0] + "*";
SearchResult searchResult = search(query);
return createCursor(selectionArgs[0], searchResult);
diff --git a/app/src/main/java/github/daneren2005/dsub/provider/DSubWidgetProvider.java b/app/src/main/java/github/daneren2005/dsub/provider/DSubWidgetProvider.java
index 18660fa2..5c90c250 100644
--- a/app/src/main/java/github/daneren2005/dsub/provider/DSubWidgetProvider.java
+++ b/app/src/main/java/github/daneren2005/dsub/provider/DSubWidgetProvider.java
@@ -278,7 +278,7 @@ public class DSubWidgetProvider extends AppWidgetProvider {
private void linkButtons(Context context, RemoteViews views, boolean playerActive) {
Intent intent = new Intent(context, SubsonicFragmentActivity.class);
intent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.appwidget_coverart, pendingIntent);
views.setOnClickPendingIntent(R.id.appwidget_top, pendingIntent);
diff --git a/app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java b/app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java
index d579ef54..35f6d37a 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/AutoMediaBrowserService.java
@@ -21,6 +21,7 @@ package github.daneren2005.dsub.service;
import android.annotation.TargetApi;
import android.content.Intent;
import android.media.MediaDescription;
+import android.media.MediaMetadata;
import android.media.browse.MediaBrowser;
import android.os.Build;
import android.os.Bundle;
@@ -33,7 +34,14 @@ import java.util.ArrayList;
import java.util.List;
import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.Artist;
+import github.daneren2005.dsub.domain.Indexes;
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.domain.MusicDirectory.Entry;
+import github.daneren2005.dsub.domain.MusicFolder;
import github.daneren2005.dsub.domain.Playlist;
+import github.daneren2005.dsub.domain.PodcastChannel;
+import github.daneren2005.dsub.domain.PodcastEpisode;
import github.daneren2005.dsub.domain.ServerInfo;
import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.SilentBackgroundTask;
@@ -48,8 +56,14 @@ public class AutoMediaBrowserService extends MediaBrowserService {
private static final String BROWSER_ALBUM_LISTS = "albumLists";
private static final String BROWSER_LIBRARY = "library";
private static final String BROWSER_PLAYLISTS = "playlists";
+ private static final String BROWSER_PODCASTS = "podcasts";
+ private static final String BROWSER_BOOKMARKS = "bookmarks";
private static final String PLAYLIST_PREFIX = "pl-";
+ private static final String PODCAST_PREFIX = "po-";
private static final String ALBUM_TYPE_PREFIX = "ty-";
+ private static final String MUSIC_DIRECTORY_PREFIX = "md-";
+ private static final String MUSIC_FOLDER_PREFIX = "mf-";
+ private static final String MUSIC_DIRECTORY_CONTENTS_PREFIX = "mdc-";
private DownloadService downloadService;
private Handler handler = new Handler();
@@ -76,12 +90,29 @@ public class AutoMediaBrowserService extends MediaBrowserService {
} else if(parentId.startsWith(ALBUM_TYPE_PREFIX)) {
int id = Integer.valueOf(parentId.substring(ALBUM_TYPE_PREFIX.length()));
getAlbumList(result, id);
+ } else if(parentId.startsWith(MUSIC_DIRECTORY_PREFIX)) {
+ String id = parentId.substring(MUSIC_DIRECTORY_PREFIX.length());
+ getPlayOptions(result, id, Constants.INTENT_EXTRA_NAME_ID);
} else if(BROWSER_LIBRARY.equals(parentId)) {
getLibrary(result);
+ } else if(parentId.startsWith(MUSIC_FOLDER_PREFIX)) {
+ String id = parentId.substring(MUSIC_FOLDER_PREFIX.length());
+ getIndexes(result, id);
+ } else if(parentId.startsWith(MUSIC_DIRECTORY_CONTENTS_PREFIX)) {
+ String id = parentId.substring(MUSIC_DIRECTORY_CONTENTS_PREFIX.length());
+ getMusicDirectory(result, id);
} else if(BROWSER_PLAYLISTS.equals(parentId)) {
getPlaylists(result);
} else if(parentId.startsWith(PLAYLIST_PREFIX)) {
- getPlayOptions(result, parentId.substring(PLAYLIST_PREFIX.length()), Constants.INTENT_EXTRA_NAME_PLAYLIST_ID);
+ String id = parentId.substring(PLAYLIST_PREFIX.length());
+ getPlayOptions(result, id, Constants.INTENT_EXTRA_NAME_PLAYLIST_ID);
+ } else if(BROWSER_PODCASTS.equals(parentId)) {
+ getPodcasts(result);
+ } else if(parentId.startsWith(PODCAST_PREFIX)) {
+ String id = parentId.substring(PODCAST_PREFIX.length());
+ getPodcastEpisodes(result, id);
+ } else if(BROWSER_BOOKMARKS.equals(parentId)) {
+ getBookmarks(result);
} else {
// No idea what it is, send empty result
result.sendResult(new ArrayList<MediaBrowser.MediaItem>());
@@ -91,7 +122,7 @@ public class AutoMediaBrowserService extends MediaBrowserService {
private void getRootFolders(Result<List<MediaBrowser.MediaItem>> result) {
List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
- /*MediaDescription.Builder albumLists = new MediaDescription.Builder();
+ MediaDescription.Builder albumLists = new MediaDescription.Builder();
albumLists.setTitle(downloadService.getString(R.string.main_albums_title))
.setMediaId(BROWSER_ALBUM_LISTS);
mediaItems.add(new MediaBrowser.MediaItem(albumLists.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE));
@@ -99,13 +130,27 @@ public class AutoMediaBrowserService extends MediaBrowserService {
MediaDescription.Builder library = new MediaDescription.Builder();
library.setTitle(downloadService.getString(R.string.button_bar_browse))
.setMediaId(BROWSER_LIBRARY);
- mediaItems.add(new MediaBrowser.MediaItem(library.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE));*/
+ mediaItems.add(new MediaBrowser.MediaItem(library.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE));
MediaDescription.Builder playlists = new MediaDescription.Builder();
playlists.setTitle(downloadService.getString(R.string.button_bar_playlists))
.setMediaId(BROWSER_PLAYLISTS);
mediaItems.add(new MediaBrowser.MediaItem(playlists.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE));
+ if(Util.getPreferences(downloadService).getBoolean(Constants.PREFERENCES_KEY_PODCASTS_ENABLED, true)) {
+ MediaDescription.Builder podcasts = new MediaDescription.Builder();
+ podcasts.setTitle(downloadService.getString(R.string.button_bar_podcasts))
+ .setMediaId(BROWSER_PODCASTS);
+ mediaItems.add(new MediaBrowser.MediaItem(podcasts.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE));
+ }
+
+ if(Util.getPreferences(downloadService).getBoolean(Constants.PREFERENCES_KEY_BOOKMARKS_ENABLED, true)) {
+ MediaDescription.Builder podcasts = new MediaDescription.Builder();
+ podcasts.setTitle(downloadService.getString(R.string.button_bar_bookmarks))
+ .setMediaId(BROWSER_BOOKMARKS);
+ mediaItems.add(new MediaBrowser.MediaItem(podcasts.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE));
+ }
+
result.sendResult(mediaItems);
}
@@ -113,15 +158,10 @@ public class AutoMediaBrowserService extends MediaBrowserService {
List<Integer> albums = new ArrayList<>();
albums.add(R.string.main_albums_newest);
albums.add(R.string.main_albums_random);
- if(ServerInfo.checkServerVersion(downloadService, "1.8")) {
- albums.add(R.string.main_albums_alphabetical);
- }
if(!Util.isTagBrowsing(downloadService)) {
albums.add(R.string.main_albums_highest);
}
- // albums.add(R.string.main_albums_starred);
- // albums.add(R.string.main_albums_genres);
- // albums.add(R.string.main_albums_year);
+ albums.add(R.string.main_albums_starred);
albums.add(R.string.main_albums_recent);
albums.add(R.string.main_albums_frequent);
@@ -138,12 +178,158 @@ public class AutoMediaBrowserService extends MediaBrowserService {
result.sendResult(mediaItems);
}
- private void getAlbumList(Result<List<MediaBrowser.MediaItem>> result, int id) {
+ private void getAlbumList(final Result<List<MediaBrowser.MediaItem>> result, final int id) {
+ new SilentServiceTask<MusicDirectory>(downloadService) {
+ @Override
+ protected MusicDirectory doInBackground(MusicService musicService) throws Throwable {
+ String albumListType;
+ switch(id) {
+ case R.string.main_albums_newest:
+ albumListType = "newest";
+ break;
+ case R.string.main_albums_random:
+ albumListType = "random";
+ break;
+ case R.string.main_albums_highest:
+ albumListType = "highest";
+ break;
+ case R.string.main_albums_starred:
+ albumListType = "starred";
+ break;
+ case R.string.main_albums_recent:
+ albumListType = "recent";
+ break;
+ case R.string.main_albums_frequent:
+ albumListType = "frequent";
+ break;
+ default:
+ albumListType = "newest";
+ }
+
+ return musicService.getAlbumList(albumListType, 20, 0, true, downloadService, null);
+ }
+
+ @Override
+ protected void done(MusicDirectory albumSet) {
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ for(Entry album: albumSet.getChildren(true, false)) {
+ MediaDescription description = new MediaDescription.Builder()
+ .setTitle(album.getAlbumDisplay())
+ .setSubtitle(album.getArtist())
+ .setMediaId(MUSIC_DIRECTORY_PREFIX + album.getId())
+ .build();
+
+ mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE));
+ }
+ result.sendResult(mediaItems);
+ }
+ }.execute();
+
+ result.detach();
}
- private void getLibrary(Result<List<MediaBrowser.MediaItem>> result) {
+ private void getLibrary(final Result<List<MediaBrowser.MediaItem>> result) {
+ new SilentServiceTask<List<MusicFolder>>(downloadService) {
+ @Override
+ protected List<MusicFolder> doInBackground(MusicService musicService) throws Throwable {
+ return musicService.getMusicFolders(false, downloadService, null);
+ }
+
+ @Override
+ protected void done(List<MusicFolder> folders) {
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ for(MusicFolder folder: folders) {
+ MediaDescription description = new MediaDescription.Builder()
+ .setTitle(folder.getName())
+ .setMediaId(MUSIC_FOLDER_PREFIX + folder.getId())
+ .build();
+
+ mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE));
+ }
+
+ result.sendResult(mediaItems);
+ }
+ }.execute();
+
+ result.detach();
+ }
+ private void getIndexes(final Result<List<MediaBrowser.MediaItem>> result, final String musicFolderId) {
+ new SilentServiceTask<Indexes>(downloadService) {
+ @Override
+ protected Indexes doInBackground(MusicService musicService) throws Throwable {
+ return musicService.getIndexes(musicFolderId, false, downloadService, null);
+ }
+
+ @Override
+ protected void done(Indexes indexes) {
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ // music directories
+ for(Artist artist : indexes.getArtists()) {
+ MediaDescription description = new MediaDescription.Builder()
+ .setTitle(artist.getName())
+ .setMediaId(MUSIC_DIRECTORY_CONTENTS_PREFIX + artist.getId())
+ .build();
+
+ mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE));
+ }
+
+ // music files
+ for(Entry entry: indexes.getEntries()) {
+ MediaDescription description = new MediaDescription.Builder()
+ .setTitle(entry.getTitle())
+ .setMediaId(MUSIC_DIRECTORY_PREFIX + entry.getId())
+ .build();
+
+ mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE));
+ }
+
+ result.sendResult(mediaItems);
+ }
+ }.execute();
+
+ result.detach();
+ }
+
+ private void getMusicDirectory(final Result<List<MediaBrowser.MediaItem>> result, final String musicDirectoryId) {
+ new SilentServiceTask<MusicDirectory>(downloadService) {
+ @Override
+ protected MusicDirectory doInBackground(MusicService musicService) throws Throwable {
+ return musicService.getMusicDirectory(musicDirectoryId, "", false, downloadService, null);
+ }
+
+ @Override
+ protected void done(MusicDirectory directory) {
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ addPlayOptions(mediaItems, musicDirectoryId, Constants.INTENT_EXTRA_NAME_ID);
+
+ for(Entry entry : directory.getChildren()) {
+ MediaDescription description;
+ if (entry.isDirectory()) {
+ // browse deeper
+ description = new MediaDescription.Builder()
+ .setTitle(entry.getTitle())
+ .setMediaId(MUSIC_DIRECTORY_CONTENTS_PREFIX + entry.getId())
+ .build();
+ } else {
+ // playback options for a single item
+ description = new MediaDescription.Builder()
+ .setTitle(entry.getTitle())
+ .setMediaId(MUSIC_DIRECTORY_PREFIX + entry.getId())
+ .build();
+ }
+ mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE));
+ }
+ result.sendResult(mediaItems);
+ }
+ }.execute();
+
+ result.detach();
}
private void getPlaylists(final Result<List<MediaBrowser.MediaItem>> result) {
@@ -172,9 +358,101 @@ public class AutoMediaBrowserService extends MediaBrowserService {
result.detach();
}
- private void getPlayOptions(Result<List<MediaBrowser.MediaItem>> result, String id, String idConstant) {
- List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+ private void getPodcasts(final Result<List<MediaBrowser.MediaItem>> result) {
+ new SilentServiceTask<List<PodcastChannel>>(downloadService) {
+ @Override
+ protected List<PodcastChannel> doInBackground(MusicService musicService) throws Throwable {
+ return musicService.getPodcastChannels(false, downloadService, null);
+ }
+
+ @Override
+ protected void done(List<PodcastChannel> podcasts) {
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ for(PodcastChannel podcast: podcasts) {
+ MediaDescription description = new MediaDescription.Builder()
+ .setTitle(podcast.getName())
+ .setMediaId(PODCAST_PREFIX + podcast.getId())
+ .build();
+
+ mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_BROWSABLE));
+ }
+
+ result.sendResult(mediaItems);
+ }
+ }.execute();
+
+ result.detach();
+ }
+ private void getPodcastEpisodes(final Result<List<MediaBrowser.MediaItem>> result, final String podcastId) {
+ new SilentServiceTask<MusicDirectory>(downloadService) {
+ @Override
+ protected MusicDirectory doInBackground(MusicService musicService) throws Throwable {
+ return musicService.getPodcastEpisodes(false, podcastId, downloadService, null);
+ }
+
+ @Override
+ protected void done(MusicDirectory podcasts) {
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ for(Entry entry: podcasts.getChildren(false, true)) {
+ PodcastEpisode podcast = (PodcastEpisode) entry;
+ Bundle podcastExtras = new Bundle();
+ podcastExtras.putSerializable(Constants.INTENT_EXTRA_ENTRY, podcast);
+ podcastExtras.putString(Constants.INTENT_EXTRA_NAME_PODCAST_ID, podcast.getId());
+
+ MediaDescription description = new MediaDescription.Builder()
+ .setTitle(podcast.getTitle())
+ .setSubtitle(Util.formatDate(downloadService, podcast.getDate(), false))
+ .setMediaId(PODCAST_PREFIX + podcast.getId())
+ .setExtras(podcastExtras)
+ .build();
+
+ mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_PLAYABLE));
+ }
+
+ result.sendResult(mediaItems);
+ }
+ }.execute();
+
+ result.detach();
+ }
+
+ private void getBookmarks(final Result<List<MediaBrowser.MediaItem>> result) {
+ new SilentServiceTask<MusicDirectory>(downloadService) {
+ @Override
+ protected MusicDirectory doInBackground(MusicService musicService) throws Throwable {
+ return musicService.getBookmarks(false, downloadService, null);
+ }
+
+ @Override
+ protected void done(MusicDirectory bookmarkList) {
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ for(Entry entry: bookmarkList.getChildren(false, true)) {
+ Bundle extras = new Bundle();
+ extras.putSerializable(Constants.INTENT_EXTRA_ENTRY, entry);
+ extras.putString(Constants.INTENT_EXTRA_NAME_CHILD_ID, entry.getId());
+
+ MediaDescription description = new MediaDescription.Builder()
+ .setTitle(entry.getTitle())
+ .setSubtitle(Util.formatDuration(entry.getBookmark().getPosition() / 1000))
+ .setMediaId(entry.getId())
+ .setExtras(extras)
+ .build();
+
+ mediaItems.add(new MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_PLAYABLE));
+ }
+
+ result.sendResult(mediaItems);
+ }
+ }.execute();
+
+ result.detach();
+ }
+
+ private void addPlayOptions(List<MediaBrowser.MediaItem> mediaItems, String id, String idConstant) {
Bundle playAllExtras = new Bundle();
playAllExtras.putString(idConstant, id);
@@ -194,7 +472,7 @@ public class AutoMediaBrowserService extends MediaBrowserService {
.setExtras(shuffleExtras);
mediaItems.add(new MediaBrowser.MediaItem(shuffle.build(), MediaBrowser.MediaItem.FLAG_PLAYABLE));
- /*Bundle playLastExtras = new Bundle();
+ Bundle playLastExtras = new Bundle();
playLastExtras.putString(idConstant, id);
playLastExtras.putBoolean(Constants.INTENT_EXTRA_PLAY_LAST, true);
@@ -202,7 +480,13 @@ public class AutoMediaBrowserService extends MediaBrowserService {
playLast.setTitle(downloadService.getString(R.string.menu_play_last))
.setMediaId("playLast-" + id)
.setExtras(playLastExtras);
- mediaItems.add(new MediaBrowser.MediaItem(playLast.build(), MediaBrowser.MediaItem.FLAG_PLAYABLE));*/
+ mediaItems.add(new MediaBrowser.MediaItem(playLast.build(), MediaBrowser.MediaItem.FLAG_PLAYABLE));
+ }
+
+ private void getPlayOptions(Result<List<MediaBrowser.MediaItem>> result, String id, String idConstant) {
+ List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();
+
+ addPlayOptions(mediaItems, id, idConstant);
result.sendResult(mediaItems);
}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java
index 53433f5c..9fd26fe5 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java
@@ -20,6 +20,7 @@ package github.daneren2005.dsub.service;
import java.io.File;
import java.io.IOException;
+import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
@@ -27,8 +28,6 @@ import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.TimeUnit;
-import org.apache.http.HttpResponse;
-
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
@@ -39,6 +38,7 @@ import github.daneren2005.dsub.domain.Bookmark;
import github.daneren2005.dsub.domain.ChatMessage;
import github.daneren2005.dsub.domain.Genre;
import github.daneren2005.dsub.domain.Indexes;
+import github.daneren2005.dsub.domain.InternetRadioStation;
import github.daneren2005.dsub.domain.PlayerQueue;
import github.daneren2005.dsub.domain.PodcastEpisode;
import github.daneren2005.dsub.domain.RemoteStatus;
@@ -69,6 +69,9 @@ public class CachedMusicService implements MusicService {
private static final int MUSIC_DIR_CACHE_SIZE = 20;
private static final int TTL_MUSIC_DIR = 5 * 60; // Five minutes
+ public static final int CACHE_UPDATE_LIST = 1;
+ public static final int CACHE_UPDATE_METADATA = 2;
+ private static final int CACHED_LAST_FM = 24 * 60;
private final RESTMusicService musicService;
private final TimeLimitedCache<Boolean> cachedLicenseValid = new TimeLimitedCache<Boolean>(120, TimeUnit.SECONDS);
@@ -121,11 +124,13 @@ public class CachedMusicService implements MusicService {
if(!refresh) {
result = FileUtil.deserialize(context, getCacheName(context, "musicFolders"), ArrayList.class);
}
-
+
if(result == null) {
result = musicService.getMusicFolders(refresh, context, progressListener);
FileUtil.serialize(context, new ArrayList<MusicFolder>(result), getCacheName(context, "musicFolders"));
}
+
+ MusicFolder.sort(result);
cachedMusicFolders.set(result);
}
return result;
@@ -150,7 +155,7 @@ public class CachedMusicService implements MusicService {
if(!refresh) {
result = FileUtil.deserialize(context, name, Indexes.class);
}
-
+
if(result == null) {
result = musicService.getIndexes(musicFolderId, refresh, context, progressListener);
FileUtil.serialize(context, result, name);
@@ -169,12 +174,13 @@ public class CachedMusicService implements MusicService {
new SilentBackgroundTask<Void>(context) {
MusicDirectory refreshed;
+ private boolean metadataUpdated;
@Override
protected Void doInBackground() throws Throwable {
refreshed = musicService.getMusicDirectory(id, name, true, context, null);
updateAllSongs(context, refreshed);
- cached.updateMetadata(refreshed);
+ metadataUpdated = cached.updateMetadata(refreshed);
deleteRemovedEntries(context, refreshed, cached);
FileUtil.serialize(context, refreshed, getCacheName(context, "directory", id));
return null;
@@ -185,7 +191,10 @@ public class CachedMusicService implements MusicService {
public void done(Void result) {
if(progressListener != null) {
if(cached.updateEntriesList(context, musicService.getInstance(context), refreshed)) {
- progressListener.updateCache();
+ progressListener.updateCache(CACHE_UPDATE_LIST);
+ }
+ if(metadataUpdated) {
+ progressListener.updateCache(CACHE_UPDATE_METADATA);
}
}
}
@@ -201,7 +210,7 @@ public class CachedMusicService implements MusicService {
dir = musicService.getMusicDirectory(id, name, refresh, context, progressListener);
updateAllSongs(context, dir);
FileUtil.serialize(context, dir, getCacheName(context, "directory", id));
-
+
// If a cached copy exists to check against, look for removes
deleteRemovedEntries(context, dir, cached);
}
@@ -234,7 +243,7 @@ public class CachedMusicService implements MusicService {
public void done(Void result) {
if(progressListener != null) {
if(cached.updateEntriesList(context, musicService.getInstance(context), refreshed)) {
- progressListener.updateCache();
+ progressListener.updateCache(CACHE_UPDATE_LIST);
}
}
}
@@ -267,12 +276,13 @@ public class CachedMusicService implements MusicService {
new SilentBackgroundTask<Void>(context) {
MusicDirectory refreshed;
+ private boolean metadataUpdated;
@Override
protected Void doInBackground() throws Throwable {
refreshed = musicService.getAlbum(id, name, refresh, context, null);
updateAllSongs(context, refreshed);
- cached.updateMetadata(refreshed);
+ metadataUpdated = cached.updateMetadata(refreshed);
deleteRemovedEntries(context, refreshed, cached);
FileUtil.serialize(context, refreshed, getCacheName(context, "album", id));
return null;
@@ -283,7 +293,10 @@ public class CachedMusicService implements MusicService {
public void done(Void result) {
if(progressListener != null) {
if(cached.updateEntriesList(context, musicService.getInstance(context), refreshed)) {
- progressListener.updateCache();
+ progressListener.updateCache(CACHE_UPDATE_LIST);
+ }
+ if(metadataUpdated) {
+ progressListener.updateCache(CACHE_UPDATE_METADATA);
}
}
}
@@ -656,6 +669,11 @@ public class CachedMusicService implements MusicService {
}
@Override
+ public MusicDirectory getSongList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception {
+ return musicService.getSongList(type, size, offset, context, progressListener);
+ }
+
+ @Override
public MusicDirectory getRandomSongs(int size, String artistId, Context context, ProgressListener progressListener) throws Exception {
return musicService.getRandomSongs(size, artistId, context, progressListener);
}
@@ -714,11 +732,16 @@ public class CachedMusicService implements MusicService {
@Override
public Bitmap getCoverArt(Context context, Entry entry, int size, ProgressListener progressListener, SilentBackgroundTask task) throws Exception {
- return musicService.getCoverArt(context, entry, size, progressListener, task);
+ Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, entry, size);
+ if (bitmap != null) {
+ return bitmap;
+ } else {
+ return musicService.getCoverArt(context, entry, size, progressListener, task);
+ }
}
@Override
- public HttpResponse getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception {
+ public HttpURLConnection getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception {
return musicService.getDownloadInputStream(context, song, offset, maxBitrate, task);
}
@@ -905,8 +928,18 @@ public class CachedMusicService implements MusicService {
}
@Override
- public MusicDirectory getNewestPodcastEpisodes(int count, Context context, ProgressListener progressListener) throws Exception {
- return musicService.getNewestPodcastEpisodes(count, context, progressListener);
+ public MusicDirectory getNewestPodcastEpisodes(boolean refresh, Context context, ProgressListener progressListener, int count) throws Exception {
+ MusicDirectory result = null;
+
+ String cacheName = getCacheName(context, "newestPodcastEpisodes");
+ try {
+ result = musicService.getNewestPodcastEpisodes(refresh, context, progressListener, count);
+ FileUtil.serialize(context, result, cacheName);
+ } catch(IOException e) {
+ result = FileUtil.deserialize(context, cacheName, MusicDirectory.class, 24);
+ } finally {
+ return result;
+ }
}
@Override
@@ -1128,7 +1161,12 @@ public class CachedMusicService implements MusicService {
@Override
public Bitmap getAvatar(String username, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception {
- return musicService.getAvatar(username, size, context, progressListener, task);
+ Bitmap bitmap = FileUtil.getAvatarBitmap(context, username, size);
+ if(bitmap != null) {
+ return bitmap;
+ } else {
+ return musicService.getAvatar(username, size, context, progressListener, task);
+ }
}
@Override
@@ -1136,12 +1174,22 @@ public class CachedMusicService implements MusicService {
String cacheName = getCacheName(context, "artistInfo", id);
ArtistInfo info = null;
if(!refresh) {
- info = FileUtil.deserialize(context, cacheName, ArtistInfo.class);
+ info = FileUtil.deserialize(context, cacheName, ArtistInfo.class, CACHED_LAST_FM);
}
if(info == null && allowNetwork) {
- info = musicService.getArtistInfo(id, refresh, allowNetwork, context, progressListener);
- FileUtil.serialize(context, info, cacheName);
+ try {
+ info = musicService.getArtistInfo(id, refresh, allowNetwork, context, progressListener);
+ FileUtil.serialize(context, info, cacheName);
+ } catch(Exception e) {
+ Log.w(TAG, "Failed to refresh Artist Info");
+ info = FileUtil.deserialize(context, cacheName, ArtistInfo.class);
+
+ // Nothing ever cached, throw error further upstream
+ if(info == null) {
+ throw e;
+ }
+ }
}
return info;
@@ -1149,7 +1197,12 @@ public class CachedMusicService implements MusicService {
@Override
public Bitmap getBitmap(String url, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception {
- return musicService.getBitmap(url, size, context, progressListener, task);
+ Bitmap bitmap = FileUtil.getMiscBitmap(context, url, size);
+ if(bitmap != null) {
+ return bitmap;
+ } else {
+ return musicService.getBitmap(url, size, context, progressListener, task);
+ }
}
@Override
@@ -1180,6 +1233,22 @@ public class CachedMusicService implements MusicService {
}
@Override
+ public List<InternetRadioStation> getInternetRadioStations(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+ List<InternetRadioStation> result = null;
+
+ if(!refresh) {
+ result = FileUtil.deserialize(context, getCacheName(context, "internetRadioStations"), ArrayList.class);
+ }
+
+ if(result == null) {
+ result = musicService.getInternetRadioStations(refresh, context, progressListener);
+ FileUtil.serialize(context, new ArrayList<>(result), getCacheName(context, "internetRadioStations"));
+ }
+
+ return result;
+ }
+
+ @Override
public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{
return musicService.processOfflineSyncs(context, progressListener);
}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java b/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java
index 670ea7d2..f9e2bfb1 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java
@@ -23,6 +23,7 @@ import android.util.Log;
import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.cast.Cast;
import com.google.android.gms.cast.CastDevice;
+import com.google.android.gms.cast.CastStatusCodes;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.cast.MediaStatus;
@@ -41,6 +42,7 @@ import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.PlayerState;
import github.daneren2005.dsub.domain.RemoteControlState;
import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.EnvironmentVariables;
import github.daneren2005.dsub.util.FileUtil;
import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.util.compat.CastCompat;
@@ -62,18 +64,15 @@ public class ChromeCastController extends RemoteController {
private boolean error = false;
private boolean ignoreNextPaused = false;
private String sessionId;
+ private boolean isStopping = false;
+ private Runnable afterUpdateComplete = null;
- private ServerProxy proxy;
- private String rootLocation;
private RemoteMediaPlayer mediaPlayer;
private double gain = 0.5;
public ChromeCastController(DownloadService downloadService, CastDevice castDevice) {
- this.downloadService = downloadService;
+ super(downloadService);
this.castDevice = castDevice;
-
- SharedPreferences prefs = Util.getPreferences(downloadService);
- rootLocation = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null);
}
@Override
@@ -247,66 +246,44 @@ public class ChromeCastController extends RemoteController {
}
}
- void startSong(DownloadFile currentPlaying, boolean autoStart, int position) {
+ void startSong(final DownloadFile currentPlaying, final boolean autoStart, final int position) {
if(currentPlaying == null) {
try {
- if (mediaPlayer != null && !error) {
- mediaPlayer.stop(apiClient);
+ if (mediaPlayer != null && !error && !isStopping) {
+ isStopping = true;
+ mediaPlayer.stop(apiClient).setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
+ @Override
+ public void onResult(RemoteMediaPlayer.MediaChannelResult mediaChannelResult) {
+ isStopping = false;
+
+ if(afterUpdateComplete != null) {
+ afterUpdateComplete.run();
+ afterUpdateComplete = null;
+ }
+ }
+ });
}
} catch(Exception e) {
// Just means it didn't need to be stopped
}
downloadService.setPlayerState(PlayerState.IDLE);
return;
+ } else if(isStopping) {
+ afterUpdateComplete = new Runnable() {
+ @Override
+ public void run() {
+ startSong(currentPlaying, autoStart, position);
+ }
+ };
+ return;
}
+
downloadService.setPlayerState(PlayerState.PREPARING);
MusicDirectory.Entry song = currentPlaying.getSong();
try {
MusicService musicService = MusicServiceFactory.getMusicService(downloadService);
- String url;
- // Offline, use file proxy
- if(Util.isOffline(downloadService) || song.getId().indexOf(rootLocation) != -1) {
- if(proxy == null) {
- proxy = new FileProxy(downloadService);
- proxy.start();
- }
-
- // Offline song
- if(song.getId().indexOf(rootLocation) != -1) {
- url = proxy.getPublicAddress(song.getId());
- } else {
- // Playing online song in offline mode
- url = proxy.getPublicAddress(currentPlaying.getCompleteFile().getPath());
- }
- } else {
- // Check if we want a proxy going still
- if(Util.isCastProxy(downloadService)) {
- if(proxy instanceof FileProxy) {
- proxy.stop();
- proxy = null;
- }
-
- if(proxy == null) {
- proxy = createWebProxy();
- proxy.start();
- }
- } else if(proxy != null) {
- proxy.stop();
- proxy = null;
- }
-
- if(song.isVideo()) {
- url = musicService.getHlsUrl(song.getId(), currentPlaying.getBitRate(), downloadService);
- } else {
- url = musicService.getMusicUrl(downloadService, song, currentPlaying.getBitRate());
- }
-
- // If proxy is going, it is a WebProxy
- if(proxy != null) {
- url = proxy.getPublicAddress(url);
- }
- }
+ String url = getStreamUrl(musicService, currentPlaying);
// Setup song/video information
MediaMetadata meta = new MediaMetadata(song.isVideo() ? MediaMetadata.MEDIA_TYPE_MOVIE : MediaMetadata.MEDIA_TYPE_MUSIC_TRACK);
@@ -367,6 +344,8 @@ public class ChromeCastController extends RemoteController {
public void onResult(RemoteMediaPlayer.MediaChannelResult result) {
if (result.getStatus().isSuccess()) {
// Handled in other handler
+ } else if(result.getStatus().getStatusCode() == CastStatusCodes.REPLACED) {
+ Log.w(TAG, "Request was replaced: " + currentPlaying.toString());
} else {
Log.e(TAG, "Failed to load: " + result.getStatus().toString());
failedLoad();
@@ -443,14 +422,14 @@ public class ChromeCastController extends RemoteController {
void launchApplication() {
try {
- Cast.CastApi.launchApplication(apiClient, CastCompat.APPLICATION_ID, false).setResultCallback(resultCallback);
+ Cast.CastApi.launchApplication(apiClient, EnvironmentVariables.CAST_APPLICATION_ID, false).setResultCallback(resultCallback);
} catch (Exception e) {
Log.e(TAG, "Failed to launch application", e);
}
}
void reconnectApplication() {
try {
- Cast.CastApi.joinApplication(apiClient, CastCompat.APPLICATION_ID, sessionId).setResultCallback(resultCallback);
+ Cast.CastApi.joinApplication(apiClient, EnvironmentVariables.CAST_APPLICATION_ID, sessionId).setResultCallback(resultCallback);
} catch (Exception e) {
Log.e(TAG, "Failed to reconnect application", e);
}
@@ -483,7 +462,9 @@ public class ChromeCastController extends RemoteController {
break;
case MediaStatus.PLAYER_STATE_IDLE:
if (mediaStatus.getIdleReason() == MediaStatus.IDLE_REASON_FINISHED) {
- downloadService.onSongCompleted();
+ if(downloadService.getPlayerState() != PlayerState.PREPARING) {
+ downloadService.onSongCompleted();
+ }
} else if (mediaStatus.getIdleReason() == MediaStatus.IDLE_REASON_INTERRUPTED) {
if (downloadService.getPlayerState() != PlayerState.PREPARING) {
downloadService.setPlayerState(PlayerState.PREPARING);
@@ -503,7 +484,7 @@ public class ChromeCastController extends RemoteController {
try {
Cast.CastApi.setMessageReceivedCallbacks(apiClient, mediaPlayer.getNamespace(), mediaPlayer);
- } catch (IOException e) {
+ } catch (Exception e) {
Log.e(TAG, "Exception while creating channel", e);
}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/DLNAController.java b/app/src/main/java/github/daneren2005/dsub/service/DLNAController.java
index 0673cdeb..143be330 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/DLNAController.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/DLNAController.java
@@ -83,9 +83,6 @@ public class DLNAController extends RemoteController {
SubscriptionCallback callback;
boolean supportsSeek = false;
boolean supportsSetupNext = false;
-
- private ServerProxy proxy;
- String rootLocation = "";
boolean error = false;
final AtomicLong lastUpdate = new AtomicLong();
@@ -108,12 +105,9 @@ public class DLNAController extends RemoteController {
};
public DLNAController(DownloadService downloadService, ControlPoint controlPoint, DLNADevice device) {
- this.downloadService = downloadService;
+ super(downloadService);
this.controlPoint = controlPoint;
this.device = device;
-
- SharedPreferences prefs = Util.getPreferences(downloadService);
- rootLocation = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null);
nextSupported = true;
}
@@ -163,7 +157,10 @@ public class DLNAController extends RemoteController {
protected void eventReceived(GENASubscription genaSubscription) {
Map<String, StateVariableValue> m = genaSubscription.getCurrentValues();
try {
- LastChange lastChange = new LastChange(new AVTransportLastChangeParser(), m.get("LastChange").toString());
+ String lastChangeText = m.get("LastChange").toString();
+ lastChangeText = lastChangeText.replace(",X_DLNA_SeekTime","").replace(",X_DLNA_SeekByte", "");
+ LastChange lastChange = new LastChange(new AVTransportLastChangeParser(), lastChangeText);
+
if (lastChange.getEventedValue(0, AVTransportVariable.TransportState.class) == null) {
return;
}
@@ -470,49 +467,7 @@ public class DLNAController extends RemoteController {
// Get url for entry
MusicService musicService = MusicServiceFactory.getMusicService(downloadService);
- String url;
- // In offline mode or playing offline song
- if(Util.isOffline(downloadService) || song.getId().indexOf(rootLocation) != -1) {
- if(proxy == null) {
- proxy = new FileProxy(downloadService);
- proxy.start();
- }
-
- // Offline song
- if(song.getId().indexOf(rootLocation) != -1) {
- url = proxy.getPublicAddress(song.getId());
- } else {
- // Playing online song in offline mode
- url = proxy.getPublicAddress(downloadFile.getCompleteFile().getPath());
- }
- } else {
- // Check if we want a proxy going still
- if(Util.isCastProxy(downloadService)) {
- if(proxy instanceof FileProxy) {
- proxy.stop();
- proxy = null;
- }
-
- if(proxy == null) {
- proxy = createWebProxy();
- proxy.start();
- }
- } else if(proxy != null) {
- proxy.stop();
- proxy = null;
- }
-
- if(song.isVideo()) {
- url = musicService.getHlsUrl(song.getId(), downloadFile.getBitRate(), downloadService);
- } else {
- url = musicService.getMusicUrl(downloadService, song, downloadFile.getBitRate());
- }
-
- // If proxy is going, it is a WebProxy
- if(proxy != null) {
- url = proxy.getPublicAddress(url);
- }
- }
+ String url = getStreamUrl(musicService, downloadFile);
// Create metadata for entry
Item track;
@@ -582,7 +537,7 @@ public class DLNAController extends RemoteController {
Log.w(TAG, "Metadata generation failed", e);
}
- return new Pair<String, String>(url, metadata);
+ return new Pair<>(url, metadata);
}
private void failedLoad() {
diff --git a/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java b/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java
index 3febfaea..30e06982 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java
@@ -24,11 +24,14 @@ import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.HttpURLConnection;
import android.content.Context;
import android.net.wifi.WifiManager;
import android.os.PowerManager;
import android.util.Log;
+
+import github.daneren2005.dsub.domain.InternetRadioStation;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.SilentBackgroundTask;
@@ -37,15 +40,6 @@ import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.util.CacheCleaner;
import github.daneren2005.serverproxy.BufferFile;
-import org.apache.http.Header;
-
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-
-/**
- * @author Sindre Mehus
- * @version $Id$
- */
public class DownloadFile implements BufferFile {
private static final String TAG = DownloadFile.class.getSimpleName();
private static final int MAX_FAILURES = 5;
@@ -84,6 +78,9 @@ public class DownloadFile implements BufferFile {
public MusicDirectory.Entry getSong() {
return song;
}
+ public boolean isSong() {
+ return song.isSong();
+ }
public Context getContext() {
return context;
@@ -111,7 +108,7 @@ public class DownloadFile implements BufferFile {
}
} else if(song.getSuffix() != null && (song.getTranscodedSuffix() == null || song.getSuffix().equals(song.getTranscodedSuffix()))) {
// If just downsampling, don't try to upsample (ie: 128 kpbs -> 192 kpbs)
- if(song.getBitRate() != null && br > song.getBitRate()) {
+ if(song.getBitRate() != null && (br == 0 || br > song.getBitRate())) {
br = song.getBitRate();
}
}
@@ -374,11 +371,37 @@ public class DownloadFile implements BufferFile {
}
}
+ public boolean isStream() {
+ return song != null && song instanceof InternetRadioStation;
+ }
+ public String getStream() {
+ if(song != null && song instanceof InternetRadioStation) {
+ InternetRadioStation station = (InternetRadioStation) song;
+ return station.getStreamUrl();
+ } else {
+ return null;
+ }
+ }
+
@Override
public String toString() {
return "DownloadFile (" + song + ")";
}
+ // Don't do this. Causes infinite loop if two instances of same song
+ /*@Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DownloadFile downloadFile = (DownloadFile) o;
+ return Util.equals(this.getSong(), downloadFile.getSong());
+ }*/
+
private class DownloadTask extends SilentBackgroundTask<Void> {
private MusicService musicService;
@@ -436,17 +459,15 @@ public class DownloadFile implements BufferFile {
}
if(compare) {
// Attempt partial HTTP GET, appending to the file if it exists.
- HttpResponse response = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this);
- Header contentLengthHeader = response.getFirstHeader("Content-Length");
- if(contentLengthHeader != null) {
- String contentLengthString = contentLengthHeader.getValue();
- if(contentLengthString != null) {
- Log.i(TAG, "Content Length: " + contentLengthString);
- contentLength = Long.parseLong(contentLengthString);
- }
+ HttpURLConnection connection = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this);
+ long contentLength = connection.getContentLength();
+ if(contentLength > 0) {
+ Log.i(TAG, "Content Length: " + contentLength);
+ DownloadFile.this.contentLength = contentLength;
}
- in = response.getEntity().getContent();
- boolean partial = response.getStatusLine().getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT;
+
+ in = connection.getInputStream();
+ boolean partial = connection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL;
if (partial) {
Log.i(TAG, "Executed partial HTTP GET, skipping " + partialFile.length() + " bytes");
}
@@ -515,8 +536,9 @@ public class DownloadFile implements BufferFile {
}
// Only run these if not interrupted, ie: cancelled
- if(!isCancelled()) {
- new CacheCleaner(context, DownloadService.getInstance()).cleanSpace();
+ DownloadService downloadService = DownloadService.getInstance();
+ if(downloadService != null && !isCancelled()) {
+ new CacheCleaner(context, downloadService).cleanSpace();
checkDownloads();
}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java b/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java
index 6641d040..8213a7d4 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java
@@ -35,6 +35,7 @@ import github.daneren2005.dsub.activity.SubsonicActivity;
import github.daneren2005.dsub.audiofx.AudioEffectsController;
import github.daneren2005.dsub.audiofx.EqualizerController;
import github.daneren2005.dsub.domain.Bookmark;
+import github.daneren2005.dsub.domain.InternetRadioStation;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.PlayerState;
import github.daneren2005.dsub.domain.PodcastEpisode;
@@ -60,6 +61,7 @@ import github.daneren2005.serverproxy.BufferProxy;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
@@ -76,6 +78,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.media.MediaPlayer;
+import android.media.PlaybackParams;
import android.media.audiofx.AudioEffect;
import android.net.wifi.WifiManager;
import android.os.Build;
@@ -87,6 +90,7 @@ import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.util.Log;
import android.support.v4.util.LruCache;
+import android.view.KeyEvent;
/**
* @author Sindre Mehus
@@ -105,6 +109,7 @@ public class DownloadService extends Service {
public static final String START_PLAY = "github.daneren2005.dsub.START_PLAYING";
public static final int FAST_FORWARD = 30000;
public static final int REWIND = 10000;
+ private static final long DEFAULT_DELAY_UPDATE_PROGRESS = 1000L;
private static final double DELETE_CUTOFF = 0.84;
private static final int REQUIRED_ALBUM_MATCHES = 4;
private static final int REMOTE_PLAYLIST_TOTAL = 3;
@@ -116,10 +121,11 @@ public class DownloadService extends Service {
public static final int METADATA_UPDATED_STAR = 1;
public static final int METADATA_UPDATED_RATING = 2;
public static final int METADATA_UPDATED_BOOKMARK = 4;
+ public static final int METADATA_UPDATED_COVER_ART = 8;
private RemoteControlClientBase mRemoteControl;
- private final IBinder binder = new SimpleServiceBinder<DownloadService>(this);
+ private final IBinder binder = new SimpleServiceBinder<>(this);
private Looper mediaPlayerLooper;
private MediaPlayer mediaPlayer;
private MediaPlayer nextMediaPlayer;
@@ -160,6 +166,7 @@ public class DownloadService extends Service {
private int cachedPosition = 0;
private boolean downloadOngoing = false;
private float volume = 1.0f;
+ private long delayUpdateProgress = DEFAULT_DELAY_UPDATE_PROGRESS;
private AudioEffectsController effectsController;
private RemoteControlState remoteState = LOCAL;
@@ -190,13 +197,16 @@ public class DownloadService extends Service {
mediaPlayer = new MediaPlayer();
mediaPlayer.setWakeMode(DownloadService.this, PowerManager.PARTIAL_WAKE_LOCK);
+ // We want to change audio session id's between upgrading Android versions. Upgrading to Android 7.0 is broken (probably updated session id format)
audioSessionId = -1;
- Integer id = prefs.getInt(Constants.CACHE_AUDIO_SESSION_ID, -1);
- if(id != -1) {
+ int id = prefs.getInt(Constants.CACHE_AUDIO_SESSION_ID, -1);
+ int versionCode = prefs.getInt(Constants.CACHE_AUDIO_SESSION_VERSION_CODE, -1);
+ if(versionCode == Build.VERSION.SDK_INT && id != -1) {
try {
audioSessionId = id;
mediaPlayer.setAudioSessionId(audioSessionId);
} catch (Throwable e) {
+ Log.w(TAG, "Failed to use cached audio session", e);
audioSessionId = -1;
}
}
@@ -205,7 +215,11 @@ public class DownloadService extends Service {
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
audioSessionId = mediaPlayer.getAudioSessionId();
- prefs.edit().putInt(Constants.CACHE_AUDIO_SESSION_ID, audioSessionId).commit();
+
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putInt(Constants.CACHE_AUDIO_SESSION_ID, audioSessionId);
+ editor.putInt(Constants.CACHE_AUDIO_SESSION_VERSION_CODE, Build.VERSION.SDK_INT);
+ editor.commit();
} catch (Throwable t) {
// Froyo or lower
}
@@ -219,14 +233,14 @@ public class DownloadService extends Service {
}
});
- try {
+ /*try {
Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId);
i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
sendBroadcast(i);
} catch(Throwable e) {
// Froyo or lower
- }
+ }*/
effectsController = new AudioEffectsController(DownloadService.this, audioSessionId);
if(prefs.getBoolean(Constants.PREFERENCES_EQUALIZER_ON, false)) {
@@ -377,6 +391,10 @@ public class DownloadService extends Service {
handler.postDelayed(r, millis);
}
+ public synchronized void download(InternetRadioStation station) {
+ clear();
+ download(Arrays.asList((MusicDirectory.Entry) station), false, true, false, false);
+ }
public synchronized void download(List<MusicDirectory.Entry> songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle) {
download(songs, save, autoplay, playNext, shuffle, 0, 0);
}
@@ -389,7 +407,10 @@ public class DownloadService extends Service {
if (songs.isEmpty()) {
return;
+ } else if(isCurrentPlayingSingle()) {
+ clear();
}
+
if (playNext) {
if (autoplay && getCurrentPlayingIndex() >= 0) {
offset = 0;
@@ -573,7 +594,6 @@ public class DownloadService extends Service {
public synchronized void setShufflePlayEnabled(boolean enabled) {
shufflePlay = enabled;
if (shufflePlay) {
- clear();
checkDownloads();
}
SharedPreferences.Editor editor = Util.getPreferences(this).edit();
@@ -600,6 +620,9 @@ public class DownloadService extends Service {
}
editor.commit();
}
+ public boolean isArtistRadio() {
+ return artistRadio;
+ }
public synchronized void shuffle() {
Collections.shuffle(downloadList);
@@ -739,7 +762,7 @@ public class DownloadService extends Service {
int position = getPlayerPosition();
int duration = getPlayerDuration();
boolean cutoff = isPastCutoff(position, duration, true);
- if(currentPlaying != null && currentPlaying.getSong() instanceof PodcastEpisode) {
+ if(currentPlaying != null && currentPlaying.getSong() instanceof PodcastEpisode && !currentPlaying.isSaved()) {
if(cutoff) {
currentPlaying.delete();
}
@@ -757,7 +780,7 @@ public class DownloadService extends Service {
checkAddBookmark();
}
if(currentPlaying != null) {
- scrobbler.conditionalScrobble(this, currentPlaying, position, duration);
+ scrobbler.conditionalScrobble(this, currentPlaying, position, duration, cutoff);
}
reset();
@@ -781,6 +804,10 @@ public class DownloadService extends Service {
suggestedPlaylistName = null;
suggestedPlaylistId = null;
+
+ setShufflePlayEnabled(false);
+ setArtistRadio(null);
+ checkDownloads();
}
public synchronized void remove(int which) {
@@ -807,6 +834,8 @@ public class DownloadService extends Service {
if(downloadFile == nextPlaying) {
setNextPlaying();
}
+
+ checkDownloads();
}
public synchronized void removeBackground(DownloadFile downloadFile) {
if (downloadFile == currentDownloading && downloadFile != currentPlaying && downloadFile != nextPlaying) {
@@ -843,6 +872,9 @@ public class DownloadService extends Service {
if(this.currentPlaying != null) {
this.currentPlaying.setPlaying(false);
}
+ if(delayUpdateProgress != DEFAULT_DELAY_UPDATE_PROGRESS && !isNextPlayingSameAlbum(currentPlaying, this.currentPlaying)) {
+// resetPlaybackSpeed();
+ }
this.currentPlaying = currentPlaying;
if(currentPlaying == null) {
currentPlayingIndex = -1;
@@ -853,7 +885,10 @@ public class DownloadService extends Service {
if (currentPlaying != null && currentPlaying.getSong() != null) {
Util.broadcastNewTrackInfo(this, currentPlaying.getSong());
- mRemoteControl.updateMetadata(this, currentPlaying.getSong());
+
+ if(mRemoteControl != null) {
+ mRemoteControl.updateMetadata(this, currentPlaying.getSong());
+ }
} else {
Util.broadcastNewTrackInfo(this, null);
Notifications.hidePlayingNotification(this, this, handler);
@@ -977,6 +1012,25 @@ public class DownloadService extends Service {
public List<DownloadFile> getToDelete() { return toDelete; }
+ public boolean isCurrentPlayingSingle() {
+ if(currentPlaying != null && currentPlaying.getSong() instanceof InternetRadioStation) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ public boolean isCurrentPlayingStream() {
+ if(currentPlaying != null) {
+ return currentPlaying.isStream();
+ } else {
+ return false;
+ }
+ }
+
+ public synchronized boolean shouldFastForward() {
+ return size() == 1 || (currentPlaying != null && !currentPlaying.isSong());
+ }
+
public synchronized List<DownloadFile> getDownloads() {
List<DownloadFile> temp = new ArrayList<DownloadFile>();
temp.addAll(downloadList);
@@ -1033,6 +1087,8 @@ public class DownloadService extends Service {
bufferAndPlay(position, start);
checkDownloads();
setNextPlaying();
+ } else {
+ checkDownloads();
}
}
}
@@ -1070,6 +1126,7 @@ public class DownloadService extends Service {
setCurrentPlaying(nextPlaying, true);
setPlayerState(PlayerState.STARTED);
setupHandlers(currentPlaying, false, start);
+ applyPlaybackParamsMain();
setNextPlaying();
// Proxy should not be being used here since the next player was already setup to play
@@ -1120,6 +1177,27 @@ public class DownloadService extends Service {
handleError(x);
}
}
+ public synchronized int rewind() {
+ return seekToWrapper(-REWIND);
+ }
+ public synchronized int fastForward() {
+ return seekToWrapper(FAST_FORWARD);
+ }
+ protected int seekToWrapper(int difference) {
+ int msPlayed = Math.max(0, getPlayerPosition());
+ Integer duration = getPlayerDuration();
+ int msTotal = duration == null ? 0 : duration;
+
+ int seekTo;
+ if(msPlayed + difference > msTotal) {
+ seekTo = msTotal;
+ } else {
+ seekTo = msPlayed + difference;
+ }
+ seekTo(seekTo);
+
+ return seekTo;
+ }
public synchronized void previous() {
int index = getCurrentPlayingIndex();
@@ -1128,12 +1206,13 @@ public class DownloadService extends Service {
}
// If only one song, just skip within song
- if(size() == 1) {
- seekTo(getPlayerPosition() - REWIND);
+ if(shouldFastForward()) {
+ rewind();
+ return;
+ } else if(playerState == PREPARING || playerState == PREPARED) {
return;
}
-
// Restart song if played more than five seconds.
if (getPlayerPosition() > 5000 || (index == 0 && getRepeatMode() != RepeatMode.ALL)) {
seekTo(0);
@@ -1154,8 +1233,8 @@ public class DownloadService extends Service {
}
public synchronized void next(boolean forceCutoff, boolean forceStart) {
// If only one song, just skip within song
- if(size() == 1) {
- seekTo(getPlayerPosition() + FAST_FORWARD);
+ if(shouldFastForward()) {
+ fastForward();
return;
} else if(playerState == PREPARING || playerState == PREPARED) {
return;
@@ -1170,7 +1249,7 @@ public class DownloadService extends Service {
} else {
cutoff = isPastCutoff(position, duration);
}
- if(currentPlaying != null && currentPlaying.getSong() instanceof PodcastEpisode) {
+ if(currentPlaying != null && currentPlaying.getSong() instanceof PodcastEpisode && !currentPlaying.isSaved()) {
if(cutoff) {
toDelete.add(currentPlaying);
}
@@ -1179,7 +1258,7 @@ public class DownloadService extends Service {
clearCurrentBookmark(true);
}
if(currentPlaying != null) {
- scrobbler.conditionalScrobble(this, currentPlaying, position, duration);
+ scrobbler.conditionalScrobble(this, currentPlaying, position, duration, cutoff);
}
int index = getCurrentPlayingIndex();
@@ -1258,6 +1337,7 @@ public class DownloadService extends Service {
// Only start if done preparing
if(playerState != PREPARING) {
mediaPlayer.start();
+ applyPlaybackParamsMain();
} else {
// Otherwise, we need to set it up to start when done preparing
autoPlayStart = true;
@@ -1372,6 +1452,9 @@ public class DownloadService extends Service {
if (playerState == PAUSED) {
lifecycleSupport.serializeDownloadQueue();
+ if(!isPastCutoff()) {
+ checkAddBookmark(true);
+ }
}
boolean show = playerState == PlayerState.STARTED;
@@ -1398,13 +1481,13 @@ public class DownloadService extends Service {
Notifications.hidePlayingNotification(this, this, handler);
}
if(mRemoteControl != null) {
- mRemoteControl.setPlaybackState(playerState.getRemoteControlClientPlayState());
+ mRemoteControl.setPlaybackState(playerState.getRemoteControlClientPlayState(), getCurrentPlayingIndex(), size());
}
if (playerState == STARTED) {
- scrobbler.scrobble(this, currentPlaying, false);
+ scrobbler.scrobble(this, currentPlaying, false, false);
} else if (playerState == COMPLETED) {
- scrobbler.scrobble(this, currentPlaying, true);
+ scrobbler.scrobble(this, currentPlaying, true, true);
}
if(playerState == STARTED && positionCache == null) {
@@ -1450,7 +1533,7 @@ public class DownloadService extends Service {
positionCache.stop();
positionCache = null;
}
- scrobbler.scrobble(this, currentPlaying, true);
+ scrobbler.scrobble(this, currentPlaying, true, true);
onStateUpdate();
}
@@ -1468,7 +1551,7 @@ public class DownloadService extends Service {
while(isRunning) {
try {
onSongProgress();
- Thread.sleep(1000L);
+ Thread.sleep(delayUpdateProgress);
}
catch(Exception e) {
isRunning = false;
@@ -1510,7 +1593,7 @@ public class DownloadService extends Service {
}
}
onSongProgress(cachedPosition < 2000 ? true: false);
- Thread.sleep(1000L);
+ Thread.sleep(delayUpdateProgress);
}
catch(Exception e) {
Log.w(TAG, "Crashed getting current position", e);
@@ -1587,6 +1670,9 @@ public class DownloadService extends Service {
return controller;
}
+ public MediaRouteManager getMediaRouter() {
+ return mediaRouter;
+ }
public MediaRouteSelector getRemoteSelector() {
return mediaRouter.getSelector();
}
@@ -1699,11 +1785,12 @@ public class DownloadService extends Service {
nextPlayingTask.cancel();
nextPlayingTask = null;
}
- }
- if(remoteState == LOCAL) {
- checkDownloads();
+ if(nextPlayerState != IDLE) {
+ setNextPlayerState(IDLE);
+ }
}
+ checkDownloads();
if(routeId != null) {
final Runnable delayedReconnect = new Runnable() {
@@ -1771,7 +1858,7 @@ public class DownloadService extends Service {
bufferAndPlay(position, true);
}
private synchronized void bufferAndPlay(int position, boolean start) {
- if(!currentPlaying.isCompleteFileAvailable()) {
+ if(!currentPlaying.isCompleteFileAvailable() && !currentPlaying.isStream()) {
if(Util.isAllowedToDownload(this)) {
reset();
@@ -1787,11 +1874,6 @@ public class DownloadService extends Service {
private synchronized void doPlay(final DownloadFile downloadFile, final int position, final boolean start) {
try {
- downloadFile.setPlaying(true);
- final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
- boolean isPartial = file.equals(downloadFile.getPartialFile());
- downloadFile.updateModificationDate();
-
subtractPosition = 0;
mediaPlayer.setOnCompletionListener(null);
mediaPlayer.setOnPreparedListener(null);
@@ -1803,19 +1885,33 @@ public class DownloadService extends Service {
} catch(Throwable e) {
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
- String dataSource = file.getAbsolutePath();
- if(isPartial && !Util.isOffline(this)) {
- if (proxy == null) {
- proxy = new BufferProxy(this);
- proxy.start();
- }
- proxy.setBufferFile(downloadFile);
- dataSource = proxy.getPrivateAddress(dataSource);
+
+ String dataSource;
+ boolean isPartial = false;
+ if(downloadFile.isStream()) {
+ dataSource = downloadFile.getStream();
Log.i(TAG, "Data Source: " + dataSource);
- } else if(proxy != null) {
- proxy.stop();
- proxy = null;
+ } else {
+ downloadFile.setPlaying(true);
+ final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
+ isPartial = file.equals(downloadFile.getPartialFile());
+ downloadFile.updateModificationDate();
+
+ dataSource = file.getAbsolutePath();
+ if (isPartial && !Util.isOffline(this)) {
+ if (proxy == null) {
+ proxy = new BufferProxy(this);
+ proxy.start();
+ }
+ proxy.setBufferFile(downloadFile);
+ dataSource = proxy.getPrivateAddress(dataSource);
+ Log.i(TAG, "Data Source: " + dataSource);
+ } else if (proxy != null) {
+ proxy.stop();
+ proxy = null;
+ }
}
+
mediaPlayer.setDataSource(dataSource);
setPlayerState(PREPARING);
@@ -1844,6 +1940,7 @@ public class DownloadService extends Service {
if (start || autoPlayStart) {
mediaPlayer.start();
+ applyPlaybackParamsMain();
setPlayerState(STARTED);
// Disable autoPlayStart after done
@@ -2111,13 +2208,16 @@ public class DownloadService extends Service {
checkArtistRadio();
}
- if (!Util.isNetworkConnected(this, true) || Util.isOffline(this)) {
+ if (!Util.isAllowedToDownload(this)) {
return;
}
if (downloadList.isEmpty() && backgroundDownloadList.isEmpty()) {
return;
}
+ if(currentPlaying != null && currentPlaying.isStream()) {
+ return;
+ }
// Need to download current playing and not casting?
if (currentPlaying != null && remoteState == LOCAL && currentPlaying != currentDownloading && !currentPlaying.isWorkDone()) {
@@ -2138,7 +2238,7 @@ public class DownloadService extends Service {
int preloaded = 0;
- if(n != 0 && remoteState == LOCAL) {
+ if(n != 0 && (remoteState == LOCAL || Util.shouldCacheDuringCasting(this))) {
int start = currentPlaying == null ? 0 : getCurrentPlayingIndex();
if(start == -1) {
start = 0;
@@ -2342,7 +2442,7 @@ public class DownloadService extends Service {
}
// Make cutoff a maximum of 10 minutes
- int cutoffPoint = Math.min((int) (duration * DELETE_CUTOFF), 10 * 60 * 1000);
+ int cutoffPoint = Math.max((int) (duration * DELETE_CUTOFF), duration - 10 * 60 * 1000);
boolean isPastCutoff = duration > 0 && position > cutoffPoint;
// Check to make sure song isn't within 10 seconds of where it was created
@@ -2423,8 +2523,11 @@ public class DownloadService extends Service {
}.execute();
}
}
-
+
private void checkAddBookmark() {
+ checkAddBookmark(false);
+ }
+ private void checkAddBookmark(final boolean updateMetadata) {
// Don't do anything if no current playing
if(currentPlaying == null || !ServerInfo.canBookmark(this)) {
return;
@@ -2454,6 +2557,9 @@ public class DownloadService extends Service {
if(found != null) {
found.setBookmark(new Bookmark(position));
}
+ if(updateMetadata) {
+ onMetadataUpdate(METADATA_UPDATED_BOOKMARK);
+ }
return null;
}
@@ -2482,9 +2588,9 @@ public class DownloadService extends Service {
SharedPreferences prefs = Util.getPreferences(this);
try {
- float[] rg = BastpUtil.getReplayGainValues(downloadFile.getFile().getCanonicalPath()); /* track, album */
float adjust = 0f;
if (prefs.getBoolean(Constants.PREFERENCES_KEY_REPLAY_GAIN, false)) {
+ float[] rg = BastpUtil.getReplayGainValues(downloadFile.getFile().getCanonicalPath()); /* track, album */
boolean singleAlbum = false;
String replayGainType = prefs.getString(Constants.PREFERENCES_KEY_REPLAY_GAIN_TYPE, "1");
@@ -2558,6 +2664,63 @@ public class DownloadService extends Service {
}
}
+ public void setPlaybackSpeed(float playbackSpeed) {
+ if(currentPlaying.isSong())
+ Util.getPreferences(this).edit().putFloat(Constants.PREFERENCES_KEY_SONG_PLAYBACK_SPEED, playbackSpeed).commit();
+ else
+ Util.getPreferences(this).edit().putFloat(Constants.PREFERENCES_KEY_PLAYBACK_SPEED, playbackSpeed).commit();
+ if(mediaPlayer != null && (playerState == PREPARED || playerState == STARTED || playerState == PAUSED || playerState == PAUSED_TEMP)) {
+ applyPlaybackParamsMain();
+ }
+
+ delayUpdateProgress = Math.round(DEFAULT_DELAY_UPDATE_PROGRESS / playbackSpeed);
+ }
+ private void resetPlaybackSpeed() {
+ Util.getPreferences(this).edit().remove(Constants.PREFERENCES_KEY_PLAYBACK_SPEED).commit();
+ Util.getPreferences(this).edit().remove(Constants.PREFERENCES_KEY_SONG_PLAYBACK_SPEED).commit();
+ }
+
+ public float getPlaybackSpeed() {
+ if (currentPlaying == null)
+ return 1.0f;
+ else {
+ if (currentPlaying.isSong())
+ return Util.getPreferences(this).getFloat(Constants.PREFERENCES_KEY_SONG_PLAYBACK_SPEED, 1.0f);
+ else
+ return Util.getPreferences(this).getFloat(Constants.PREFERENCES_KEY_PLAYBACK_SPEED, 1.0f);
+ }
+ }
+
+ private synchronized void applyPlaybackParamsMain() {
+ applyPlaybackParams(mediaPlayer);
+ }
+ private synchronized boolean isNextPlayingSameAlbum() {
+ return isNextPlayingSameAlbum(currentPlaying, nextPlaying);
+ }
+ private synchronized boolean isNextPlayingSameAlbum(DownloadFile currentPlaying, DownloadFile nextPlaying) {
+ if(currentPlaying == null || nextPlaying == null) {
+ return false;
+ } else {
+ return currentPlaying.getSong().getAlbum().equals(nextPlaying.getSong().getAlbum());
+ }
+ }
+
+ private synchronized void applyPlaybackParams(MediaPlayer mediaPlayer) {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ float playbackSpeed = getPlaybackSpeed();
+
+ try {
+ if (Math.abs(playbackSpeed - 1.0) > 0.01 || mediaPlayer.getPlaybackParams() != null) {
+ PlaybackParams playbackParams = new PlaybackParams();
+ playbackParams.setSpeed(playbackSpeed);
+ mediaPlayer.setPlaybackParams(playbackParams);
+ }
+ } catch(Exception e) {
+ Log.e(TAG, "Error while applying media player params", e);
+ }
+ }
+ }
+
public void toggleStarred() {
final DownloadFile currentPlaying = this.currentPlaying;
if(currentPlaying == null) {
@@ -2571,6 +2734,11 @@ public class DownloadService extends Service {
onMetadataUpdate(METADATA_UPDATED_STAR);
}
}
+
+ @Override
+ public void starCommited(boolean starred) {
+
+ }
});
}
public void toggleRating(int rating) {
@@ -2600,7 +2768,7 @@ public class DownloadService extends Service {
UpdateHelper.setRating(this, entry, rating, new UpdateHelper.OnRatingChange() {
@Override
public void ratingChange(int rating) {
- if(currentPlaying == DownloadService.this.currentPlaying) {
+ if (currentPlaying == DownloadService.this.currentPlaying) {
onMetadataUpdate(METADATA_UPDATED_RATING);
}
}
@@ -2613,13 +2781,19 @@ public class DownloadService extends Service {
wakeLock.acquire(ms);
}
+ public void handleKeyEvent(KeyEvent keyEvent) {
+ lifecycleSupport.handleKeyEvent(keyEvent);
+ }
+
public void addOnSongChangedListener(OnSongChangedListener listener) {
addOnSongChangedListener(listener, false);
}
- public synchronized void addOnSongChangedListener(OnSongChangedListener listener, boolean run) {
- int index = onSongChangedListeners.indexOf(listener);
- if(index == -1) {
- onSongChangedListeners.add(listener);
+ public void addOnSongChangedListener(OnSongChangedListener listener, boolean run) {
+ synchronized(onSongChangedListeners) {
+ int index = onSongChangedListeners.indexOf(listener);
+ if (index == -1) {
+ onSongChangedListeners.add(listener);
+ }
}
if(run) {
@@ -2630,6 +2804,7 @@ public class DownloadService extends Service {
onSongsChanged();
onSongProgress();
onStateUpdate();
+ onMetadataUpdate(METADATA_UPDATED_ALL);
}
});
} else {
@@ -2637,53 +2812,61 @@ public class DownloadService extends Service {
}
}
}
- public synchronized void removeOnSongChangeListener(OnSongChangedListener listener) {
- int index = onSongChangedListeners.indexOf(listener);
- if(index != -1) {
- onSongChangedListeners.remove(index);
+ public void removeOnSongChangeListener(OnSongChangedListener listener) {
+ synchronized(onSongChangedListeners) {
+ int index = onSongChangedListeners.indexOf(listener);
+ if (index != -1) {
+ onSongChangedListeners.remove(index);
+ }
}
}
- private synchronized void onSongChanged() {
+ private void onSongChanged() {
final long atRevision = revision;
- for(final OnSongChangedListener listener: onSongChangedListeners) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- if(revision == atRevision && instance != null) {
- listener.onSongChanged(currentPlaying, currentPlayingIndex);
+ synchronized(onSongChangedListeners) {
+ final boolean shouldFastForward = shouldFastForward();
+ for (final OnSongChangedListener listener : onSongChangedListeners) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (revision == atRevision && instance != null) {
+ listener.onSongChanged(currentPlaying, currentPlayingIndex, shouldFastForward);
- MusicDirectory.Entry entry = currentPlaying != null ? currentPlaying.getSong() : null;
- listener.onMetadataUpdate(entry, METADATA_UPDATED_ALL);
+ MusicDirectory.Entry entry = currentPlaying != null ? currentPlaying.getSong() : null;
+ listener.onMetadataUpdate(entry, METADATA_UPDATED_ALL);
+ }
}
- }
- });
- }
+ });
+ }
- if(mediaPlayerHandler != null && !onSongChangedListeners.isEmpty()) {
- mediaPlayerHandler.post(new Runnable() {
- @Override
- public void run() {
- onSongProgress();
- }
- });
+ if (mediaPlayerHandler != null && !onSongChangedListeners.isEmpty()) {
+ mediaPlayerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onSongProgress();
+ }
+ });
+ }
}
}
- private synchronized void onSongsChanged() {
+ private void onSongsChanged() {
final long atRevision = revision;
- for(final OnSongChangedListener listener: onSongChangedListeners) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- if(revision == atRevision && instance != null) {
- listener.onSongsChanged(downloadList, currentPlaying, currentPlayingIndex);
+ synchronized(onSongChangedListeners) {
+ final boolean shouldFastForward = shouldFastForward();
+ for (final OnSongChangedListener listener : onSongChangedListeners) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (revision == atRevision && instance != null) {
+ listener.onSongsChanged(downloadList, currentPlaying, currentPlayingIndex, shouldFastForward);
+ }
}
- }
- });
+ });
+ }
}
}
- private synchronized void onSongProgress() {
+ private void onSongProgress() {
onSongProgress(true);
}
private synchronized void onSongProgress(boolean manual) {
@@ -2691,22 +2874,29 @@ public class DownloadService extends Service {
final Integer duration = getPlayerDuration();
final boolean isSeekable = isSeekable();
final int position = getPlayerPosition();
- for(final OnSongChangedListener listener: onSongChangedListeners) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- if(revision == atRevision && instance != null) {
- listener.onSongProgress(currentPlaying, position, duration, isSeekable);
+ final int index = getCurrentPlayingIndex();
+ final int queueSize = size();
+
+ synchronized(onSongChangedListeners) {
+ for (final OnSongChangedListener listener : onSongChangedListeners) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (revision == atRevision && instance != null) {
+ listener.onSongProgress(currentPlaying, position, duration, isSeekable);
+ }
}
- }
- });
+ });
+ }
}
if(manual) {
handler.post(new Runnable() {
@Override
public void run() {
- mRemoteControl.setPlaybackState(playerState.getRemoteControlClientPlayState());
+ if(mRemoteControl != null) {
+ mRemoteControl.setPlaybackState(playerState.getRemoteControlClientPlayState(), index, queueSize);
+ }
}
});
}
@@ -2718,39 +2908,45 @@ public class DownloadService extends Service {
}
}
}
- private synchronized void onStateUpdate() {
+ private void onStateUpdate() {
final long atRevision = revision;
- for(final OnSongChangedListener listener: onSongChangedListeners) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- if(revision == atRevision && instance != null) {
- listener.onStateUpdate(currentPlaying, playerState);
+ synchronized(onSongChangedListeners) {
+ for (final OnSongChangedListener listener : onSongChangedListeners) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (revision == atRevision && instance != null) {
+ listener.onStateUpdate(currentPlaying, playerState);
+ }
}
- }
- });
+ });
+ }
}
}
- private synchronized void onMetadataUpdate() {
+ public void onMetadataUpdate() {
onMetadataUpdate(METADATA_UPDATED_ALL);
}
- private synchronized void onMetadataUpdate(final int updateType) {
- for(final OnSongChangedListener listener: onSongChangedListeners) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- if(instance != null) {
- MusicDirectory.Entry entry = currentPlaying != null ? currentPlaying.getSong() : null;
- listener.onMetadataUpdate(entry, updateType);
+ public void onMetadataUpdate(final int updateType) {
+ synchronized(onSongChangedListeners) {
+ for (final OnSongChangedListener listener : onSongChangedListeners) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (instance != null) {
+ MusicDirectory.Entry entry = currentPlaying != null ? currentPlaying.getSong() : null;
+ listener.onMetadataUpdate(entry, updateType);
+ }
}
- }
- });
+ });
+ }
}
handler.post(new Runnable() {
@Override
public void run() {
- mRemoteControl.metadataChanged(currentPlaying.getSong());
+ if(currentPlaying != null) {
+ mRemoteControl.metadataChanged(currentPlaying.getSong());
+ }
}
});
}
@@ -2862,8 +3058,8 @@ public class DownloadService extends Service {
}
public interface OnSongChangedListener {
- void onSongChanged(DownloadFile currentPlaying, int currentPlayingIndex);
- void onSongsChanged(List<DownloadFile> songs, DownloadFile currentPlaying, int currentPlayingIndex);
+ void onSongChanged(DownloadFile currentPlaying, int currentPlayingIndex, boolean shouldFastForward);
+ void onSongsChanged(List<DownloadFile> songs, DownloadFile currentPlaying, int currentPlayingIndex, boolean shouldFastForward);
void onSongProgress(DownloadFile currentPlaying, int millisPlayed, Integer duration, boolean isSeekable);
void onStateUpdate(DownloadFile downloadFile, PlayerState playerState);
void onMetadataUpdate(MusicDirectory.Entry entry, int fieldChange);
diff --git a/app/src/main/java/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java b/app/src/main/java/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java
index 4989db40..1c80b622 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java
@@ -36,6 +36,8 @@ import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
+
+import github.daneren2005.dsub.domain.InternetRadioStation;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.PlayerQueue;
import github.daneren2005.dsub.domain.PlayerState;
@@ -56,6 +58,7 @@ import static github.daneren2005.dsub.domain.PlayerState.PREPARING;
public class DownloadServiceLifecycleSupport {
private static final String TAG = DownloadServiceLifecycleSupport.class.getSimpleName();
public static final String FILENAME_DOWNLOADS_SER = "downloadstate2.ser";
+ private static final int DEBOUNCE_TIME = 200;
private final DownloadService downloadService;
private Looper eventLooper;
@@ -154,6 +157,8 @@ public class DownloadServiceLifecycleSupport {
// Pause temporarily on incoming phone calls.
phoneStateListener = new MyPhoneStateListener();
+
+ // Android 6.0 removes requirement for android.Manifest.permission.READ_PHONE_STATE;
TelephonyManager telephonyManager = (TelephonyManager) downloadService.getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
@@ -225,6 +230,7 @@ public class DownloadServiceLifecycleSupport {
}
editor.commit();
+ downloadService.clear();
downloadService.setShufflePlayEnabled(true);
} else {
downloadService.start();
@@ -306,7 +312,7 @@ public class DownloadServiceLifecycleSupport {
FileUtil.serialize(downloadService, state, FILENAME_DOWNLOADS_SER);
// If we are on Subsonic 5.2+, save play queue
- if(serializeRemote && ServerInfo.canSavePlayQueue(downloadService) && !Util.isOffline(downloadService) && state.songs.size() > 0) {
+ if(serializeRemote && ServerInfo.canSavePlayQueue(downloadService) && !Util.isOffline(downloadService) && state.songs.size() > 0 && !(state.songs.get(0) instanceof InternetRadioStation)) {
// Cancel any currently running tasks
if(currentSavePlayQueueTask != null) {
currentSavePlayQueueTask.cancel();
@@ -384,14 +390,25 @@ public class DownloadServiceLifecycleSupport {
return lastChange;
}
- private void handleKeyEvent(KeyEvent event) {
- if(event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ public void handleKeyEvent(KeyEvent event) {
+ if(event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() > 0) {
+ switch (event.getKeyCode()) {
+ case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ downloadService.fastForward();
+ break;
+ case RemoteControlClient.FLAG_KEY_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ downloadService.rewind();
+ break;
+ }
+ } else if(event.getAction() == KeyEvent.ACTION_UP) {
switch (event.getKeyCode()) {
case RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE:
- case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
downloadService.togglePlayPause();
break;
case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
if(lastPressTime < (System.currentTimeMillis() - 500)) {
lastPressTime = System.currentTimeMillis();
downloadService.togglePlayPause();
@@ -401,11 +418,23 @@ public class DownloadServiceLifecycleSupport {
break;
case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
- downloadService.previous();
+ if(lastPressTime < (System.currentTimeMillis() - DEBOUNCE_TIME)) {
+ lastPressTime = System.currentTimeMillis();
+ downloadService.previous();
+ }
break;
case RemoteControlClient.FLAG_KEY_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_NEXT:
- downloadService.next();
+ if(lastPressTime < (System.currentTimeMillis() - DEBOUNCE_TIME)) {
+ lastPressTime = System.currentTimeMillis();
+ downloadService.next();
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ downloadService.rewind();
+ break;
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ downloadService.fastForward();
break;
case RemoteControlClient.FLAG_KEY_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_STOP:
diff --git a/app/src/main/java/github/daneren2005/dsub/service/JukeboxController.java b/app/src/main/java/github/daneren2005/dsub/service/JukeboxController.java
index e9d7cbc8..b9f40f32 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/JukeboxController.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/JukeboxController.java
@@ -22,6 +22,7 @@ import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.RemoteStatus;
import github.daneren2005.dsub.domain.PlayerState;
import github.daneren2005.dsub.domain.RemoteControlState;
+import github.daneren2005.dsub.domain.RepeatMode;
import github.daneren2005.dsub.service.parser.SubsonicRESTException;
import github.daneren2005.dsub.util.Util;
@@ -47,7 +48,7 @@ public class JukeboxController extends RemoteController {
private float gain = 0.5f;
public JukeboxController(DownloadService downloadService, Handler handler) {
- this.downloadService = downloadService;
+ super(downloadService);
this.handler = handler;
}
@@ -200,11 +201,15 @@ public class JukeboxController extends RemoteController {
// Track change?
Integer index = jukeboxStatus.getCurrentPlayingIndex();
+ int currentPlayingIndex = downloadService.getCurrentPlayingIndex();
if (index != null && index != -1 && index != downloadService.getCurrentPlayingIndex()) {
downloadService.setPlayerState(PlayerState.COMPLETED);
downloadService.setCurrentPlaying(index, true);
if(jukeboxStatus.isPlaying()) {
downloadService.setPlayerState(PlayerState.STARTED);
+ } else if(index == 0 && currentPlayingIndex == downloadService.size() - 1 && downloadService.getRepeatMode() == RepeatMode.ALL) {
+ // Jukebox does not support any form of auto repeat
+ start();
}
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/MusicService.java b/app/src/main/java/github/daneren2005/dsub/service/MusicService.java
index 2972bb7c..6a58e340 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/MusicService.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/MusicService.java
@@ -18,18 +18,18 @@
*/
package github.daneren2005.dsub.service;
+import java.net.HttpURLConnection;
import java.util.List;
-import org.apache.http.HttpResponse;
import android.content.Context;
import android.graphics.Bitmap;
import github.daneren2005.dsub.domain.ArtistInfo;
-import github.daneren2005.dsub.domain.Bookmark;
import github.daneren2005.dsub.domain.ChatMessage;
import github.daneren2005.dsub.domain.Genre;
import github.daneren2005.dsub.domain.Indexes;
+import github.daneren2005.dsub.domain.InternetRadioStation;
import github.daneren2005.dsub.domain.PlayerQueue;
import github.daneren2005.dsub.domain.RemoteStatus;
import github.daneren2005.dsub.domain.Lyrics;
@@ -94,6 +94,8 @@ public interface MusicService {
MusicDirectory getAlbumList(String type, String extra, int size, int offset, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
+ MusicDirectory getSongList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception;
+
MusicDirectory getRandomSongs(int size, String artistId, Context context, ProgressListener progressListener) throws Exception;
MusicDirectory getRandomSongs(int size, String folder, String genre, String startYear, String endYear, Context context, ProgressListener progressListener) throws Exception;
@@ -101,7 +103,7 @@ public interface MusicService {
Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, ProgressListener progressListener, SilentBackgroundTask task) throws Exception;
- HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception;
+ HttpURLConnection getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception;
String getMusicUrl(Context context, MusicDirectory.Entry song, int maxBitrate) throws Exception;
@@ -147,7 +149,7 @@ public interface MusicService {
MusicDirectory getPodcastEpisodes(boolean refresh, String id, Context context, ProgressListener progressListener) throws Exception;
- MusicDirectory getNewestPodcastEpisodes(int count, Context context, ProgressListener progressListener) throws Exception;
+ MusicDirectory getNewestPodcastEpisodes(boolean refresh, Context context, ProgressListener progressListener, int count) throws Exception;
void refreshPodcasts(Context context, ProgressListener progressListener) throws Exception;
@@ -192,6 +194,8 @@ public interface MusicService {
void savePlayQueue(List<MusicDirectory.Entry> songs, MusicDirectory.Entry currentPlaying, int position, Context context, ProgressListener progressListener) throws Exception;
PlayerQueue getPlayQueue(Context context, ProgressListener progressListener) throws Exception;
+
+ List<InternetRadioStation> getInternetRadioStations(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception;
diff --git a/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java
index b8633624..da6c37f1 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java
@@ -21,6 +21,7 @@ package github.daneren2005.dsub.service;
import java.io.File;
import java.io.Reader;
import java.io.FileReader;
+import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -34,13 +35,12 @@ import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.util.Log;
-import org.apache.http.HttpResponse;
-
import github.daneren2005.dsub.domain.Artist;
import github.daneren2005.dsub.domain.ArtistInfo;
import github.daneren2005.dsub.domain.ChatMessage;
import github.daneren2005.dsub.domain.Genre;
import github.daneren2005.dsub.domain.Indexes;
+import github.daneren2005.dsub.domain.InternetRadioStation;
import github.daneren2005.dsub.domain.MusicDirectory.Entry;
import github.daneren2005.dsub.domain.PlayerQueue;
import github.daneren2005.dsub.domain.PodcastEpisode;
@@ -95,7 +95,7 @@ public class OfflineMusicService implements MusicService {
artist.setIndex(file.getName().substring(0, 1));
artist.setName(file.getName());
artists.add(artist);
- } else {
+ } else if(!file.getName().equals("albumart.jpg") && !file.getName().equals(".nomedia")) {
entries.add(createEntry(context, file));
}
}
@@ -226,7 +226,7 @@ public class OfflineMusicService implements MusicService {
}
@Override
- public HttpResponse getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception {
+ public HttpURLConnection getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception {
throw new OfflineException(ERRORMSG);
}
@@ -307,7 +307,15 @@ public class OfflineMusicService implements MusicService {
}
}
});
-
+
+ // Respect counts in search criteria
+ int artistCount = Math.min(artists.size(), criteria.getArtistCount());
+ int albumCount = Math.min(albums.size(), criteria.getAlbumCount());
+ int songCount = Math.min(songs.size(), criteria.getSongCount());
+ artists = artists.subList(0, artistCount);
+ albums = albums.subList(0, albumCount);
+ songs = songs.subList(0, songCount);
+
return new SearchResult(artists, albums, songs);
}
@@ -359,20 +367,13 @@ public class OfflineMusicService implements MusicService {
}
}
private int matchCriteria(SearchCritera criteria, String name) {
- String query = criteria.getQuery().toLowerCase();
- String[] queryParts = query.split(" ");
- String[] nameParts = name.toLowerCase().split(" ");
-
- int closeness = 0;
- for(String queryPart : queryParts) {
- for(String namePart : nameParts) {
- if(namePart.equals(queryPart)) {
- closeness++;
- }
- }
+ if (criteria.getPattern().matcher(name).matches()) {
+ return Util.getStringDistance(
+ criteria.getQuery().toLowerCase(),
+ name.toLowerCase());
+ } else {
+ return 0;
}
-
- return closeness;
}
@Override
@@ -587,6 +588,11 @@ public class OfflineMusicService implements MusicService {
}
@Override
+ public MusicDirectory getSongList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException(ERRORMSG);
+ }
+
+ @Override
public MusicDirectory getRandomSongs(int size, String artistId, Context context, ProgressListener progressListener) throws Exception {
throw new OfflineException(ERRORMSG);
}
@@ -769,7 +775,7 @@ public class OfflineMusicService implements MusicService {
}
@Override
- public MusicDirectory getNewestPodcastEpisodes(int count, Context context, ProgressListener progressListener) throws Exception {
+ public MusicDirectory getNewestPodcastEpisodes(boolean refresh, Context context, ProgressListener progressListener, int count) throws Exception {
throw new OfflineException(ERRORMSG);
}
@@ -884,6 +890,11 @@ public class OfflineMusicService implements MusicService {
}
@Override
+ public List<InternetRadioStation> getInternetRadioStations(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException(ERRORMSG);
+ }
+
+ @Override
public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{
throw new OfflineException(ERRORMSG);
}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java
index 4a6e5108..657ac4a9 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java
@@ -24,61 +24,37 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
+import java.net.HttpURLConnection;
+import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpResponse;
-import org.apache.http.NameValuePair;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.entity.UrlEncodedFormEntity;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.conn.params.ConnManagerParams;
-import org.apache.http.conn.params.ConnPerRouteBean;
-import org.apache.http.conn.scheme.PlainSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.conn.scheme.SocketFactory;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
-import org.apache.http.message.BasicHeader;
-import org.apache.http.message.BasicNameValuePair;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpConnectionParams;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.BasicHttpContext;
-import org.apache.http.protocol.ExecutionContext;
-import org.apache.http.protocol.HttpContext;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
-import android.os.Looper;
+import android.util.Base64;
import android.util.Log;
+import com.google.android.gms.security.ProviderInstaller;
+
import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.*;
-import github.daneren2005.dsub.service.parser.AlbumListParser;
+import github.daneren2005.dsub.fragments.MainFragment;
+import github.daneren2005.dsub.service.parser.EntryListParser;
import github.daneren2005.dsub.service.parser.ArtistInfoParser;
import github.daneren2005.dsub.service.parser.BookmarkParser;
import github.daneren2005.dsub.service.parser.ChatMessageParser;
import github.daneren2005.dsub.service.parser.ErrorParser;
import github.daneren2005.dsub.service.parser.GenreParser;
import github.daneren2005.dsub.service.parser.IndexesParser;
+import github.daneren2005.dsub.service.parser.InternetRadioStationParser;
import github.daneren2005.dsub.service.parser.JukeboxStatusParser;
import github.daneren2005.dsub.service.parser.LicenseParser;
import github.daneren2005.dsub.service.parser.LyricsParser;
@@ -95,11 +71,9 @@ import github.daneren2005.dsub.service.parser.SearchResult2Parser;
import github.daneren2005.dsub.service.parser.SearchResultParser;
import github.daneren2005.dsub.service.parser.ShareParser;
import github.daneren2005.dsub.service.parser.StarredListParser;
+import github.daneren2005.dsub.service.parser.TopSongsParser;
import github.daneren2005.dsub.service.parser.UserParser;
import github.daneren2005.dsub.service.parser.VideosParser;
-import github.daneren2005.dsub.service.ssl.SSLSocketFactory;
-import github.daneren2005.dsub.service.ssl.TrustSelfSignedStrategy;
-import github.daneren2005.dsub.util.BackgroundTask;
import github.daneren2005.dsub.util.Pair;
import github.daneren2005.dsub.util.SilentBackgroundTask;
import github.daneren2005.dsub.util.Constants;
@@ -108,19 +82,23 @@ import github.daneren2005.dsub.util.ProgressListener;
import github.daneren2005.dsub.util.SongDBHandler;
import github.daneren2005.dsub.util.Util;
import java.io.*;
+import java.util.Map;
import java.util.zip.GZIPInputStream;
-/**
- * @author Sindre Mehus
- */
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
public class RESTMusicService implements MusicService {
private static final String TAG = RESTMusicService.class.getSimpleName();
- private static final int SOCKET_CONNECT_TIMEOUT = 10 * 1000;
private static final int SOCKET_READ_TIMEOUT_DEFAULT = 10 * 1000;
private static final int SOCKET_READ_TIMEOUT_DOWNLOAD = 30 * 1000;
- private static final int SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS = 60 * 1000;
private static final int SOCKET_READ_TIMEOUT_GET_PLAYLIST = 60 * 1000;
// Allow 20 seconds extra timeout per MB offset.
@@ -129,51 +107,46 @@ public class RESTMusicService implements MusicService {
private static final int HTTP_REQUEST_MAX_ATTEMPTS = 5;
private static final long REDIRECTION_CHECK_INTERVAL_MILLIS = 60L * 60L * 1000L;
- private final DefaultHttpClient httpClient;
+ private SSLSocketFactory sslSocketFactory;
+ private HostnameVerifier selfSignedHostnameVerifier;
private long redirectionLastChecked;
private int redirectionNetworkType = -1;
private String redirectFrom;
private String redirectTo;
- private final ThreadSafeClientConnManager connManager;
private Integer instance;
+ private boolean hasInstalledGoogleSSL = false;
public RESTMusicService() {
+ TrustManager[] trustAllCerts = new TrustManager[]{
+ new X509TrustManager() {
+ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ public void checkClientTrusted(
+ java.security.cert.X509Certificate[] certs, String authType) {
+ }
+ public void checkServerTrusted(
+ java.security.cert.X509Certificate[] certs, String authType) {
+ }
+ }
+ };
+ try {
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
+ sslSocketFactory = sslContext.getSocketFactory();
+ } catch (Exception e) {
+ }
- // Create and initialize default HTTP parameters
- HttpParams params = new BasicHttpParams();
- ConnManagerParams.setMaxTotalConnections(params, 20);
- ConnManagerParams.setMaxConnectionsPerRoute(params, new ConnPerRouteBean(20));
- HttpConnectionParams.setConnectionTimeout(params, SOCKET_CONNECT_TIMEOUT);
- HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_DEFAULT);
-
- // Turn off stale checking. Our connections break all the time anyway,
- // and it's not worth it to pay the penalty of checking every time.
- HttpConnectionParams.setStaleCheckingEnabled(params, false);
-
- // Create and initialize scheme registry
- SchemeRegistry schemeRegistry = new SchemeRegistry();
- schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
- schemeRegistry.register(new Scheme("https", createSSLSocketFactory(), 443));
-
- // Create an HttpClient with the ThreadSafeClientConnManager.
- // This connection manager must be used if more than one thread will
- // be using the HttpClient.
- connManager = new ThreadSafeClientConnManager(params, schemeRegistry);
- httpClient = new DefaultHttpClient(connManager, params);
- }
-
- private SocketFactory createSSLSocketFactory() {
- try {
- return new SSLSocketFactory(new TrustSelfSignedStrategy(), SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
- } catch (Throwable x) {
- Log.e(TAG, "Failed to create custom SSL socket factory, using default.", x);
- return org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory();
- }
+ selfSignedHostnameVerifier = new HostnameVerifier() {
+ public boolean verify(String hostname, SSLSession session) {
+ return true;
+ }
+ };
}
@Override
public void ping(Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "ping", null);
+ Reader reader = getReader(context, progressListener, "ping");
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -184,7 +157,7 @@ public class RESTMusicService implements MusicService {
@Override
public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getLicense", null);
+ Reader reader = getReader(context, progressListener, "getLicense");
try {
ServerInfo serverInfo = new LicenseParser(context, getInstance(context)).parse(reader);
return serverInfo.isLicenseValid();
@@ -194,7 +167,7 @@ public class RESTMusicService implements MusicService {
}
public List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getMusicFolders", null);
+ Reader reader = getReader(context, progressListener, "getMusicFolders");
try {
return new MusicFoldersParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -204,7 +177,17 @@ public class RESTMusicService implements MusicService {
@Override
public void startRescan(Context context, ProgressListener listener) throws Exception {
- Reader reader = getReader(context, listener, "startRescan", null);
+ String startMethod = ServerInfo.isMadsonic(context, getInstance(context)) ? "startRescan" : "startScan";
+ String refreshMethod = null;
+ if(ServerInfo.isMadsonic(context, getInstance(context))) {
+ startMethod = "startRescan";
+ refreshMethod = "scanstatus";
+ } else {
+ startMethod = "startScan";
+ refreshMethod = "getScanStatus";
+ }
+
+ Reader reader = getReader(context, listener, startMethod);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -214,7 +197,7 @@ public class RESTMusicService implements MusicService {
// Now check if still running
boolean done = false;
while(!done) {
- reader = getReader(context, null, "scanstatus", null);
+ reader = getReader(context, null, refreshMethod);
try {
boolean running = new ScanStatusParser(context, getInstance(context)).parse(reader, listener);
if(running) {
@@ -241,7 +224,7 @@ public class RESTMusicService implements MusicService {
parameterValues.add(musicFolderId);
}
- Reader reader = getReader(context, progressListener, Util.isTagBrowsing(context, getInstance(context)) ? "getArtists" : "getIndexes", null, parameterNames, parameterValues);
+ Reader reader = getReader(context, progressListener, Util.isTagBrowsing(context, getInstance(context)) ? "getArtists" : "getIndexes", parameterNames, parameterValues);
try {
return new IndexesParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -287,7 +270,7 @@ public class RESTMusicService implements MusicService {
}
private MusicDirectory getMusicDirectoryImpl(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getMusicDirectory", null, "id", id);
+ Reader reader = getReader(context, progressListener, "getMusicDirectory", "id", id);
try {
return new MusicDirectoryParser(context, getInstance(context)).parse(name, reader, progressListener);
} finally {
@@ -297,7 +280,7 @@ public class RESTMusicService implements MusicService {
@Override
public MusicDirectory getArtist(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getArtist", null, "id", id);
+ Reader reader = getReader(context, progressListener, "getArtist", "id", id);
try {
return new MusicDirectoryParser(context, getInstance(context)).parse(name, reader, progressListener);
} finally {
@@ -307,7 +290,7 @@ public class RESTMusicService implements MusicService {
@Override
public MusicDirectory getAlbum(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getAlbum", null, "id", id);
+ Reader reader = getReader(context, progressListener, "getAlbum", "id", id);
try {
return new MusicDirectoryParser(context, getInstance(context)).parse(name, reader, progressListener);
} finally {
@@ -331,7 +314,7 @@ public class RESTMusicService implements MusicService {
private SearchResult searchOld(SearchCritera critera, Context context, ProgressListener progressListener) throws Exception {
List<String> parameterNames = Arrays.asList("any", "songCount");
List<Object> parameterValues = Arrays.<Object>asList(critera.getQuery(), critera.getSongCount());
- Reader reader = getReader(context, progressListener, "search", null, parameterNames, parameterValues);
+ Reader reader = getReader(context, progressListener, "search", parameterNames, parameterValues);
try {
return new SearchResultParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -363,7 +346,7 @@ public class RESTMusicService implements MusicService {
method = "search2";
}
}
- Reader reader = getReader(context, progressListener, method, null, parameterNames, parameterValues);
+ Reader reader = getReader(context, progressListener, method, parameterNames, parameterValues);
try {
return new SearchResult2Parser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -373,10 +356,7 @@ public class RESTMusicService implements MusicService {
@Override
public MusicDirectory getPlaylist(boolean refresh, String id, String name, Context context, ProgressListener progressListener) throws Exception {
- HttpParams params = new BasicHttpParams();
- HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_PLAYLIST);
-
- Reader reader = getReader(context, progressListener, "getPlaylist", params, "id", id);
+ Reader reader = getReader(context, progressListener, "getPlaylist", "id", id, SOCKET_READ_TIMEOUT_GET_PLAYLIST);
try {
return new PlaylistParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -386,7 +366,7 @@ public class RESTMusicService implements MusicService {
@Override
public List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getPlaylists", null);
+ Reader reader = getReader(context, progressListener, "getPlaylists");
try {
return new PlaylistsParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -412,7 +392,7 @@ public class RESTMusicService implements MusicService {
parameterValues.add(getOfflineSongId(entry.getId(), context, progressListener));
}
- Reader reader = getReader(context, progressListener, "createPlaylist", null, parameterNames, parameterValues);
+ Reader reader = getReader(context, progressListener, "createPlaylist", parameterNames, parameterValues);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -422,7 +402,7 @@ public class RESTMusicService implements MusicService {
@Override
public void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "deletePlaylist", null, "id", id);
+ Reader reader = getReader(context, progressListener, "deletePlaylist", "id", id);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -441,7 +421,7 @@ public class RESTMusicService implements MusicService {
names.add("songIdToAdd");
values.add(getOfflineSongId(song.getId(), context, progressListener));
}
- Reader reader = getReader(context, progressListener, "updatePlaylist", null, names, values);
+ Reader reader = getReader(context, progressListener, "updatePlaylist", names, values);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -460,7 +440,7 @@ public class RESTMusicService implements MusicService {
names.add("songIndexToRemove");
values.add(song);
}
- Reader reader = getReader(context, progressListener, "updatePlaylist", null, names, values);
+ Reader reader = getReader(context, progressListener, "updatePlaylist", names, values);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -485,7 +465,7 @@ public class RESTMusicService implements MusicService {
names.add("songIndexToRemove");
values.add(i);
}
- Reader reader = getReader(context, progressListener, "updatePlaylist", null, names, values);
+ Reader reader = getReader(context, progressListener, "updatePlaylist", names, values);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -496,7 +476,7 @@ public class RESTMusicService implements MusicService {
@Override
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.8", "Updating playlists is not supported.");
- Reader reader = getReader(context, progressListener, "updatePlaylist", null, Arrays.asList("playlistId", "name", "comment", "public"), Arrays.<Object>asList(id, name, comment, pub));
+ Reader reader = getReader(context, progressListener, "updatePlaylist", Arrays.asList("playlistId", "name", "comment", "public"), Arrays.<Object>asList(id, name, comment, pub));
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -506,7 +486,7 @@ public class RESTMusicService implements MusicService {
@Override
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getLyrics", null, Arrays.asList("artist", "title"), Arrays.<Object>asList(artist, title));
+ Reader reader = getReader(context, progressListener, "getLyrics", Arrays.asList("artist", "title"), Arrays.<Object>asList(artist, title));
try {
return new LyricsParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -525,10 +505,10 @@ public class RESTMusicService implements MusicService {
Reader reader;
if(time > 0){
checkServerVersion(context, "1.8", "Scrobbling with a time not supported.");
- reader = getReader(context, progressListener, "scrobble", null, Arrays.asList("id", "submission", "time"), Arrays.<Object>asList(id, submission, time));
+ reader = getReader(context, progressListener, "scrobble", Arrays.asList("id", "submission", "time"), Arrays.<Object>asList(id, submission, time));
}
else
- reader = getReader(context, progressListener, "scrobble", null, Arrays.asList("id", "submission"), Arrays.<Object>asList(id, submission));
+ reader = getReader(context, progressListener, "scrobble", Arrays.asList("id", "submission"), Arrays.<Object>asList(id, submission));
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -569,9 +549,9 @@ public class RESTMusicService implements MusicService {
method = "getAlbumList";
}
- Reader reader = getReader(context, progressListener, method, null, names, values, true);
+ Reader reader = getReader(context, progressListener, method, names, values, true);
try {
- return new AlbumListParser(context, getInstance(context)).parse(reader, progressListener);
+ return new EntryListParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
Util.close(reader);
}
@@ -606,7 +586,7 @@ public class RESTMusicService implements MusicService {
int decade = Integer.parseInt(extra);
// Reverse chronological order only supported in 5.3+
- if(ServerInfo.checkServerVersion(context, "1.13", instance) && ServerInfo.isStockSubsonic(context, instance)) {
+ if(ServerInfo.checkServerVersion(context, "1.13", instance) && !ServerInfo.isMadsonic(context, instance)) {
values.add(decade + 9);
values.add(decade);
} else {
@@ -635,15 +615,51 @@ public class RESTMusicService implements MusicService {
method = "getAlbumList";
}
- Reader reader = getReader(context, progressListener, method, null, names, values, true);
+ Reader reader = getReader(context, progressListener, method, names, values, true);
try {
- return new AlbumListParser(context, instance).parse(reader, progressListener);
+ return new EntryListParser(context, instance).parse(reader, progressListener);
} finally {
Util.close(reader);
}
}
- @Override
+ @Override
+ public MusicDirectory getSongList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception {
+ List<String> names = new ArrayList<String>();
+ List<Object> values = new ArrayList<Object>();
+
+ names.add("size");
+ values.add(size);
+ names.add("offset");
+ values.add(offset);
+
+ String method;
+ switch(type) {
+ case MainFragment.SONGS_NEWEST:
+ method = "getNewaddedSongs";
+ break;
+ case MainFragment.SONGS_TOP_PLAYED:
+ method = "getTopplayedSongs";
+ break;
+ case MainFragment.SONGS_RECENT:
+ method = "getLastplayedSongs";
+ break;
+ case MainFragment.SONGS_FREQUENT:
+ method = "getMostplayedSongs";
+ break;
+ default:
+ method = "getNewaddedSongs";
+ }
+
+ Reader reader = getReader(context, progressListener, method, names, values, true);
+ try {
+ return new EntryListParser(context, getInstance(context)).parse(reader, progressListener);
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
public MusicDirectory getRandomSongs(int size, String artistId, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.11", "Artist radio is not supported");
@@ -658,7 +674,13 @@ public class RESTMusicService implements MusicService {
int instance = getInstance(context);
String method;
- if(ServerInfo.isMadsonic(context, instance)) {
+ if(ServerInfo.isMadsonic6(context, instance)) {
+ if (Util.isTagBrowsing(context, instance)) {
+ method = "getSimilarSongsID3";
+ } else {
+ method = "getSimilarSongs";
+ }
+ } else if(ServerInfo.isMadsonic(context, instance)) {
method = "getPandoraSongs";
} else {
if (Util.isTagBrowsing(context, instance)) {
@@ -668,7 +690,7 @@ public class RESTMusicService implements MusicService {
}
}
- Reader reader = getReader(context, progressListener, method, null, names, values);
+ Reader reader = getReader(context, progressListener, method, names, values);
try {
return new RandomSongsParser(context, instance).parse(reader, progressListener);
} finally {
@@ -702,7 +724,7 @@ public class RESTMusicService implements MusicService {
method = "getStarred";
}
- Reader reader = getReader(context, progressListener, method, null, names, values, true);
+ Reader reader = getReader(context, progressListener, method, names, values, true);
try {
return new StarredListParser(context, instance).parse(reader, progressListener);
} finally {
@@ -712,10 +734,7 @@ public class RESTMusicService implements MusicService {
@Override
public MusicDirectory getRandomSongs(int size, String musicFolderId, String genre, String startYear, String endYear, Context context, ProgressListener progressListener) throws Exception {
- HttpParams params = new BasicHttpParams();
- HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS);
-
- List<String> names = new ArrayList<String>();
+ List<String> names = new ArrayList<String>();
List<Object> values = new ArrayList<Object>();
names.add("size");
@@ -754,7 +773,7 @@ public class RESTMusicService implements MusicService {
values.add(endYear);
}
- Reader reader = getReader(context, progressListener, "getRandomSongs", params, names, values);
+ Reader reader = getReader(context, progressListener, "getRandomSongs", names, values);
try {
return new RandomSongsParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -784,87 +803,24 @@ public class RESTMusicService implements MusicService {
@Override
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, ProgressListener progressListener, SilentBackgroundTask task) throws Exception {
-
// Synchronize on the entry so that we don't download concurrently for the same song.
synchronized (entry) {
+ String url = getRestUrl(context, "getCoverArt");
+ List<String> parameterNames = Arrays.asList("id");
+ List<Object> parameterValues = Arrays.<Object>asList(entry.getCoverArt());
- // Use cached file, if existing.
- Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, entry, size);
- if (bitmap != null) {
- return bitmap;
- }
-
- String url = getRestUrl(context, "getCoverArt");
-
- InputStream in = null;
- try {
- List<String> parameterNames = Arrays.asList("id");
- List<Object> parameterValues = Arrays.<Object>asList(entry.getCoverArt());
- HttpEntity entity = getEntityForURL(context, url, null, parameterNames, parameterValues, progressListener, task);
-
- in = entity.getContent();
- Header contentEncoding = entity.getContentEncoding();
- if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
- in = new GZIPInputStream(in);
- }
-
- // If content type is XML, an error occured. Get it.
- String contentType = Util.getContentType(entity);
- if (contentType != null && (contentType.startsWith("text/xml") || contentType.startsWith("text/html"))) {
- new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8));
- return null; // Never reached.
- }
-
- byte[] bytes = Util.toByteArray(in);
-
- // Handle case where partial was downloaded before being cancelled
- if(task != null && task.isCancelled()) {
- return null;
- }
-
- OutputStream out = null;
- try {
- out = new FileOutputStream(FileUtil.getAlbumArtFile(context, entry));
- out.write(bytes);
- } finally {
- Util.close(out);
- }
-
- // Size == 0 -> only want to download
- if(size == 0) {
- return null;
- } else {
- return FileUtil.getSampledBitmap(bytes, size);
- }
- } finally {
- Util.close(in);
- }
+ return getBitmapFromUrl(context, url, parameterNames, parameterValues, size, FileUtil.getAlbumArtFile(context, entry), true, progressListener, task);
}
}
@Override
- public HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception {
-
+ public HttpURLConnection getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception {
String url = getRestUrl(context, "stream");
-
- // Set socket read timeout. Note: The timeout increases as the offset gets larger. This is
- // to avoid the thrashing effect seen when offset is combined with transcoding/downsampling on the server.
- // In that case, the server uses a long time before sending any data, causing the client to time out.
- HttpParams params = new BasicHttpParams();
- int timeout = (int) (SOCKET_READ_TIMEOUT_DOWNLOAD + offset * TIMEOUT_MILLIS_PER_OFFSET_BYTE);
- HttpConnectionParams.setSoTimeout(params, timeout);
-
- // Add "Range" header if offset is given.
- List<Header> headers = new ArrayList<Header>();
- if (offset > 0) {
- headers.add(new BasicHeader("Range", "bytes=" + offset + "-"));
- }
-
List<String> parameterNames = new ArrayList<String>();
parameterNames.add("id");
parameterNames.add("maxBitRate");
- List<Object> parameterValues = new ArrayList<Object>();
+ List<Object> parameterValues = new ArrayList<>();
parameterValues.add(song.getId());
parameterValues.add(maxBitrate);
@@ -884,24 +840,32 @@ public class RESTMusicService implements MusicService {
parameterValues.add("raw");
}
}
- HttpResponse response = getResponseForURL(context, url, params, parameterNames, parameterValues, headers, null, task, false);
- // If content type is XML, an error occurred. Get it.
- String contentType = Util.getContentType(response.getEntity());
- if (contentType != null && (contentType.startsWith("text/xml") || contentType.startsWith("text/html"))) {
- InputStream in = response.getEntity().getContent();
- Header contentEncoding = response.getEntity().getContentEncoding();
- if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
- in = new GZIPInputStream(in);
+ // Add "Range" header if offset is given
+ Map<String, String> headers = new HashMap<>();
+ if (offset > 0) {
+ headers.put("Range", "bytes=" + offset + "-");
+ }
+
+ // Set socket read timeout. Note: The timeout increases as the offset gets larger. This is
+ // to avoid the thrashing effect seen when offset is combined with transcoding/downsampling on the server.
+ // In that case, the server uses a long time before sending any data, causing the client to time out.
+ int timeout = (int) (SOCKET_READ_TIMEOUT_DOWNLOAD + offset * TIMEOUT_MILLIS_PER_OFFSET_BYTE);
+ HttpURLConnection connection = getConnection(context, url, parameterNames, parameterValues, headers, timeout);
+
+ // If content type is XML, an error occurred. Get it.
+ String contentType = connection.getContentType();
+ if (contentType != null && (contentType.startsWith("text/xml") || contentType.startsWith("text/html"))) {
+ InputStream in = getInputStreamFromConnection(connection);
+
+ try {
+ new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8));
+ } finally {
+ Util.close(in);
}
- try {
- new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8));
- } finally {
- Util.close(in);
- }
- }
+ }
- return response;
+ return connection;
}
@Override
@@ -909,10 +873,9 @@ public class RESTMusicService implements MusicService {
StringBuilder builder = new StringBuilder(getRestUrl(context, "stream"));
builder.append("&id=").append(song.getId());
- // If we are doing mp3 to mp3, just specify raw so that stuff works better
- if("mp3".equals(song.getSuffix()) && (song.getTranscodedSuffix() == null || "mp3".equals(song.getTranscodedSuffix())) && ServerInfo.checkServerVersion(context, "1.9", getInstance(context))) {
+ // Allow user to specify to stream raw formats if available
+ if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_CAST_STREAM_ORIGINAL, true) && ("mp3".equals(song.getSuffix()) || "flac".equals(song.getSuffix()) || "wav".equals(song.getSuffix()) || "aac".equals(song.getSuffix())) && ServerInfo.checkServerVersion(context, "1.9", getInstance(context))) {
builder.append("&format=raw");
- builder.append("&estimateContentLength=true");
} else {
builder.append("&maxBitRate=").append(maxBitrate);
}
@@ -1013,7 +976,7 @@ public class RESTMusicService implements MusicService {
private RemoteStatus executeJukeboxCommand(Context context, ProgressListener progressListener, List<String> parameterNames, List<Object> parameterValues) throws Exception {
checkServerVersion(context, "1.7", "Jukebox not supported.");
- Reader reader = getReader(context, progressListener, "jukeboxControl", null, parameterNames, parameterValues);
+ Reader reader = getReader(context, progressListener, "jukeboxControl", parameterNames, parameterValues);
try {
return new JukeboxStatusParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1052,7 +1015,7 @@ public class RESTMusicService implements MusicService {
}
}
- Reader reader = getReader(context, progressListener, starred ? "star" : "unstar", null, names, values);
+ Reader reader = getReader(context, progressListener, starred ? "star" : "unstar", names, values);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1064,7 +1027,7 @@ public class RESTMusicService implements MusicService {
public List<Share> getShares(Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.6", "Shares not supported.");
- Reader reader = getReader(context, progressListener, "getShares", null);
+ Reader reader = getReader(context, progressListener, "getShares");
try {
return new ShareParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -1092,7 +1055,7 @@ public class RESTMusicService implements MusicService {
parameterValues.add(expires);
}
- Reader reader = getReader(context, progressListener, "createShare", null, parameterNames, parameterValues);
+ Reader reader = getReader(context, progressListener, "createShare", parameterNames, parameterValues);
try {
return new ShareParser(context, getInstance(context)).parse(reader, progressListener);
}
@@ -1105,16 +1068,13 @@ public class RESTMusicService implements MusicService {
public void deleteShare(String id, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.6", "Shares not supported.");
- HttpParams params = new BasicHttpParams();
- HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS);
-
List<String> parameterNames = new ArrayList<String>();
List<Object> parameterValues = new ArrayList<Object>();
parameterNames.add("id");
parameterValues.add(id);
- Reader reader = getReader(context, progressListener, "deleteShare", params, parameterNames, parameterValues);
+ Reader reader = getReader(context, progressListener, "deleteShare", parameterNames, parameterValues);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
@@ -1128,9 +1088,6 @@ public class RESTMusicService implements MusicService {
public void updateShare(String id, String description, Long expires, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.6", "Updating share not supported.");
- HttpParams params = new BasicHttpParams();
- HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS);
-
List<String> parameterNames = new ArrayList<String>();
List<Object> parameterValues = new ArrayList<Object>();
@@ -1145,7 +1102,7 @@ public class RESTMusicService implements MusicService {
parameterNames.add("expires");
parameterValues.add(expires);
- Reader reader = getReader(context, progressListener, "updateShare", params, parameterNames, parameterValues);
+ Reader reader = getReader(context, progressListener, "updateShare", parameterNames, parameterValues);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
}
@@ -1158,16 +1115,13 @@ public class RESTMusicService implements MusicService {
public List<ChatMessage> getChatMessages(Long since, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.2", "Chat not supported.");
- HttpParams params = new BasicHttpParams();
- HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS);
-
List<String> parameterNames = new ArrayList<String>();
List<Object> parameterValues = new ArrayList<Object>();
parameterNames.add("since");
parameterValues.add(since);
- Reader reader = getReader(context, progressListener, "getChatMessages", params, parameterNames, parameterValues);
+ Reader reader = getReader(context, progressListener, "getChatMessages", parameterNames, parameterValues);
try {
return new ChatMessageParser(context, getInstance(context)).parse(reader, progressListener);
@@ -1180,16 +1134,13 @@ public class RESTMusicService implements MusicService {
public void addChatMessage(String message, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.2", "Chat not supported.");
- HttpParams params = new BasicHttpParams();
- HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS);
-
List<String> parameterNames = new ArrayList<String>();
List<Object> parameterValues = new ArrayList<Object>();
parameterNames.add("message");
parameterValues.add(message);
- Reader reader = getReader(context, progressListener, "addChatMessage", params, parameterNames, parameterValues);
+ Reader reader = getReader(context, progressListener, "addChatMessage", parameterNames, parameterValues);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
@@ -1202,7 +1153,7 @@ public class RESTMusicService implements MusicService {
public List<Genre> getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.9", "Genres not supported.");
- Reader reader = getReader(context, progressListener, "getGenres", null);
+ Reader reader = getReader(context, progressListener, "getGenres");
try {
return new GenreParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -1214,9 +1165,6 @@ public class RESTMusicService implements MusicService {
public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.9", "Genres not supported.");
- HttpParams params = new BasicHttpParams();
- HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS);
-
List<String> parameterNames = new ArrayList<String>();
List<Object> parameterValues = new ArrayList<Object>();
@@ -1237,7 +1185,7 @@ public class RESTMusicService implements MusicService {
}
}
- Reader reader = getReader(context, progressListener, "getSongsByGenre", params, parameterNames, parameterValues, true);
+ Reader reader = getReader(context, progressListener, "getSongsByGenre", parameterNames, parameterValues, true);
try {
return new RandomSongsParser(context, instance).parse(reader, progressListener);
} finally {
@@ -1256,9 +1204,9 @@ public class RESTMusicService implements MusicService {
parameterValues.add(size);
String method = ServerInfo.isMadsonic(context, getInstance(context)) ? "getTopTrackSongs" : "getTopSongs";
- Reader reader = getReader(context, progressListener, method, null, parameterNames, parameterValues);
+ Reader reader = getReader(context, progressListener, method, parameterNames, parameterValues);
try {
- return new RandomSongsParser(context, getInstance(context)).parse(reader, progressListener);
+ return new TopSongsParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
Util.close(reader);
}
@@ -1268,7 +1216,7 @@ public class RESTMusicService implements MusicService {
public List<PodcastChannel> getPodcastChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.6", "Podcasts not supported.");
- Reader reader = getReader(context, progressListener, "getPodcasts", null, Arrays.asList("includeEpisodes"), Arrays.<Object>asList("false"));
+ Reader reader = getReader(context, progressListener, "getPodcasts", Arrays.asList("includeEpisodes"), Arrays.<Object>asList("false"));
try {
List<PodcastChannel> channels = new PodcastChannelParser(context, getInstance(context)).parse(reader, progressListener);
@@ -1290,7 +1238,7 @@ public class RESTMusicService implements MusicService {
@Override
public MusicDirectory getPodcastEpisodes(boolean refresh, String id, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getPodcasts", null, Arrays.asList("id"), Arrays.<Object>asList(id));
+ Reader reader = getReader(context, progressListener, "getPodcasts", Arrays.asList("id"), Arrays.<Object>asList(id));
try {
return new PodcastEntryParser(context, getInstance(context)).parse(id, reader, progressListener);
} finally {
@@ -1299,8 +1247,8 @@ public class RESTMusicService implements MusicService {
}
@Override
- public MusicDirectory getNewestPodcastEpisodes(int count, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getNewestPodcasts", null, Arrays.asList("count"), Arrays.<Object>asList(count));
+ public MusicDirectory getNewestPodcastEpisodes(boolean refresh, Context context, ProgressListener progressListener, int count) throws Exception {
+ Reader reader = getReader(context, progressListener, "getNewestPodcasts", Arrays.asList("count"), Arrays.<Object>asList(count), true);
try {
return new PodcastEntryParser(context, getInstance(context)).parse(null, reader, progressListener);
@@ -1313,7 +1261,7 @@ public class RESTMusicService implements MusicService {
public void refreshPodcasts(Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.9", "Refresh podcasts not supported.");
- Reader reader = getReader(context, progressListener, "refreshPodcasts", null);
+ Reader reader = getReader(context, progressListener, "refreshPodcasts");
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1325,7 +1273,7 @@ public class RESTMusicService implements MusicService {
public void createPodcastChannel(String url, Context context, ProgressListener progressListener) throws Exception{
checkServerVersion(context, "1.9", "Creating podcasts not supported.");
- Reader reader = getReader(context, progressListener, "createPodcastChannel", null, "url", url);
+ Reader reader = getReader(context, progressListener, "createPodcastChannel", "url", url);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1337,7 +1285,7 @@ public class RESTMusicService implements MusicService {
public void deletePodcastChannel(String id, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.9", "Deleting podcasts not supported.");
- Reader reader = getReader(context, progressListener, "deletePodcastChannel", null, "id", id);
+ Reader reader = getReader(context, progressListener, "deletePodcastChannel", "id", id);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1349,7 +1297,7 @@ public class RESTMusicService implements MusicService {
public void downloadPodcastEpisode(String id, Context context, ProgressListener progressListener) throws Exception{
checkServerVersion(context, "1.9", "Downloading podcasts not supported.");
- Reader reader = getReader(context, progressListener, "downloadPodcastEpisode", null, "id", id);
+ Reader reader = getReader(context, progressListener, "downloadPodcastEpisode", "id", id);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1361,7 +1309,7 @@ public class RESTMusicService implements MusicService {
public void deletePodcastEpisode(String id, String parent, ProgressListener progressListener, Context context) throws Exception{
checkServerVersion(context, "1.9", "Deleting podcasts not supported.");
- Reader reader = getReader(context, progressListener, "deletePodcastEpisode", null, "id", id);
+ Reader reader = getReader(context, progressListener, "deletePodcastEpisode", "id", id);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1373,7 +1321,7 @@ public class RESTMusicService implements MusicService {
public void setRating(MusicDirectory.Entry entry, int rating, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.6", "Setting ratings not supported.");
- Reader reader = getReader(context, progressListener, "setRating", null, Arrays.asList("id", "rating"), Arrays.<Object>asList(entry.getId(), rating));
+ Reader reader = getReader(context, progressListener, "setRating", Arrays.asList("id", "rating"), Arrays.<Object>asList(entry.getId(), rating));
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1385,7 +1333,7 @@ public class RESTMusicService implements MusicService {
public MusicDirectory getBookmarks(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.9", "Bookmarks not supported.");
- Reader reader = getReader(context, progressListener, "getBookmarks", null);
+ Reader reader = getReader(context, progressListener, "getBookmarks");
try {
return new BookmarkParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -1397,7 +1345,7 @@ public class RESTMusicService implements MusicService {
public void createBookmark(MusicDirectory.Entry entry, int position, String comment, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.9", "Creating bookmarks not supported.");
- Reader reader = getReader(context, progressListener, "createBookmark", null, Arrays.asList("id", "position", "comment"), Arrays.<Object>asList(entry.getId(), position, comment));
+ Reader reader = getReader(context, progressListener, "createBookmark", Arrays.asList("id", "position", "comment"), Arrays.<Object>asList(entry.getId(), position, comment));
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1409,7 +1357,7 @@ public class RESTMusicService implements MusicService {
public void deleteBookmark(MusicDirectory.Entry entry, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.9", "Deleting bookmarks not supported.");
- Reader reader = getReader(context, progressListener, "deleteBookmark", null, Arrays.asList("id"), Arrays.<Object>asList(entry.getId()));
+ Reader reader = getReader(context, progressListener, "deleteBookmark", Arrays.asList("id"), Arrays.<Object>asList(entry.getId()));
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1419,7 +1367,7 @@ public class RESTMusicService implements MusicService {
@Override
public User getUser(boolean refresh, String username, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getUser", null, Arrays.asList("username"), Arrays.<Object>asList(username));
+ Reader reader = getReader(context, progressListener, "getUser", Arrays.asList("username"), Arrays.<Object>asList(username));
try {
List<User> users = new UserParser(context, getInstance(context)).parse(reader, progressListener);
if(users.size() > 0) {
@@ -1437,7 +1385,7 @@ public class RESTMusicService implements MusicService {
public List<User> getUsers(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
checkServerVersion(context, "1.8", "Getting user list is not supported");
- Reader reader = getReader(context, progressListener, "getUsers", null);
+ Reader reader = getReader(context, progressListener, "getUsers");
try {
return new UserParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -1462,7 +1410,16 @@ public class RESTMusicService implements MusicService {
values.add(setting.getValue());
}
- Reader reader = getReader(context, progressListener, "createUser", null, names, values);
+ if(user.getMusicFolderSettings() != null) {
+ for(User.Setting setting: user.getMusicFolderSettings()) {
+ if(setting.getValue()) {
+ names.add("musicFolderId");
+ values.add(setting.getName());
+ }
+ }
+ }
+
+ Reader reader = getReader(context, progressListener, "createUser", names, values);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1487,7 +1444,16 @@ public class RESTMusicService implements MusicService {
}
}
- Reader reader = getReader(context, progressListener, "updateUser", null, names, values);
+ if(user.getMusicFolderSettings() != null) {
+ for(User.Setting setting: user.getMusicFolderSettings()) {
+ if(setting.getValue()) {
+ names.add("musicFolderId");
+ values.add(setting.getName());
+ }
+ }
+ }
+
+ Reader reader = getReader(context, progressListener, "updateUser", names, values);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1497,7 +1463,7 @@ public class RESTMusicService implements MusicService {
@Override
public void deleteUser(String username, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "deleteUser", null, Arrays.asList("username"), Arrays.<Object>asList(username));
+ Reader reader = getReader(context, progressListener, "deleteUser", Arrays.asList("username"), Arrays.<Object>asList(username));
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1507,7 +1473,7 @@ public class RESTMusicService implements MusicService {
@Override
public void changeEmail(String username, String email, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "updateUser", null, Arrays.asList("username", "email"), Arrays.<Object>asList(username, email));
+ Reader reader = getReader(context, progressListener, "updateUser", Arrays.asList("username", "email"), Arrays.<Object>asList(username, email));
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1517,7 +1483,7 @@ public class RESTMusicService implements MusicService {
@Override
public void changePassword(String username, String password, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "changePassword", null, Arrays.asList("username", "password"), Arrays.<Object>asList(username, password));
+ Reader reader = getReader(context, progressListener, "changePassword", Arrays.asList("username", "password"), Arrays.<Object>asList(username, password));
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1535,55 +1501,11 @@ public class RESTMusicService implements MusicService {
// Synchronize on the username so that we don't download concurrently for
// the same user.
synchronized (username) {
- // Use cached file, if existing.
- Bitmap bitmap = FileUtil.getAvatarBitmap(context, username, size);
- if(bitmap != null) {
- return bitmap;
- }
-
String url = Util.getRestUrl(context, "getAvatar");
- InputStream in = null;
- try
- {
- List<String> parameterNames;
- List<Object> parameterValues;
-
- parameterNames = Collections.singletonList("username");
- parameterValues = Arrays.<Object>asList(username);
-
- HttpEntity entity = getEntityForURL(context, url, null, parameterNames, parameterValues, progressListener, task);
- in = entity.getContent();
- Header contentEncoding = entity.getContentEncoding();
- if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
- in = new GZIPInputStream(in);
- }
-
- // If content type is XML, an error occurred. Get it.
- String contentType = Util.getContentType(entity);
- if (contentType != null && (contentType.startsWith("text/xml") || contentType.startsWith("text/html"))) {
- new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8));
- return null; // Never reached.
- }
-
- byte[] bytes = Util.toByteArray(in);
- if(task != null && task.isCancelled()) {
- // Handle case where partial is downloaded and cancelled
- return null;
- }
-
- OutputStream out = null;
- try {
- out = new FileOutputStream(FileUtil.getAvatarFile(context, username));
- out.write(bytes);
- } finally {
- Util.close(out);
- }
+ List<String> parameterNames = Collections.singletonList("username");
+ List<Object> parameterValues = Arrays.<Object>asList(username);
- return FileUtil.getSampledBitmap(bytes, size, false);
- }
- finally {
- Util.close(in);
- }
+ return getBitmapFromUrl(context, url, parameterNames, parameterValues, size, FileUtil.getAvatarFile(context, username), false, progressListener, task);
}
}
@@ -1603,7 +1525,7 @@ public class RESTMusicService implements MusicService {
method = "getArtistInfo";
}
- Reader reader = getReader(context, progressListener, method, null, Arrays.asList("id", "includeNotPresent"), Arrays.<Object>asList(id, "true"));
+ Reader reader = getReader(context, progressListener, method, Arrays.asList("id", "includeNotPresent"), Arrays.<Object>asList(id, "true"));
try {
return new ArtistInfoParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -1615,53 +1537,13 @@ public class RESTMusicService implements MusicService {
public Bitmap getBitmap(String url, int size, Context context, ProgressListener progressListener, SilentBackgroundTask task) throws Exception {
// Synchronize on the url so that we don't download concurrently
synchronized (url) {
- // Use cached file, if existing.
- Bitmap bitmap = FileUtil.getMiscBitmap(context, url, size);
- if(bitmap != null) {
- return bitmap;
- }
-
- InputStream in = null;
- try {
- HttpEntity entity = getEntityForURL(context, url, null, null, null, progressListener, task);
- in = entity.getContent();
- Header contentEncoding = entity.getContentEncoding();
- if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
- in = new GZIPInputStream(in);
- }
-
- // If content type is XML, an error occurred. Get it.
- String contentType = Util.getContentType(entity);
- if (contentType != null && (contentType.startsWith("text/xml") || contentType.startsWith("text/html"))) {
- new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8));
- return null; // Never reached.
- }
-
- byte[] bytes = Util.toByteArray(in);
- if(task != null && task.isCancelled()) {
- // Handle case where partial is downloaded and cancelled
- return null;
- }
-
- OutputStream out = null;
- try {
- out = new FileOutputStream(FileUtil.getMiscFile(context, url));
- out.write(bytes);
- } finally {
- Util.close(out);
- }
-
- return FileUtil.getSampledBitmap(bytes, size, false);
- }
- finally {
- Util.close(in);
- }
+ return getBitmapFromUrl(context, url, null, null, size, FileUtil.getMiscFile(context, url), false, progressListener, task);
}
}
@Override
public MusicDirectory getVideos(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getVideos", null, true);
+ Reader reader = getReader(context, progressListener, "getVideos");
try {
return new VideosParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -1685,7 +1567,7 @@ public class RESTMusicService implements MusicService {
parameterNames.add("position");
parameterValues.add(position);
- Reader reader = getReader(context, progressListener, "savePlayQueue", null, parameterNames, parameterValues);
+ Reader reader = getReader(context, progressListener, "savePlayQueue", parameterNames, parameterValues);
try {
new ErrorParser(context, getInstance(context)).parse(reader);
} finally {
@@ -1695,7 +1577,7 @@ public class RESTMusicService implements MusicService {
@Override
public PlayerQueue getPlayQueue(Context context, ProgressListener progressListener) throws Exception {
- Reader reader = getReader(context, progressListener, "getPlayQueue", null);
+ Reader reader = getReader(context, progressListener, "getPlayQueue");
try {
return new PlayQueueParser(context, getInstance(context)).parse(reader, progressListener);
} finally {
@@ -1704,6 +1586,18 @@ public class RESTMusicService implements MusicService {
}
@Override
+ public List<InternetRadioStation> getInternetRadioStations(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+ checkServerVersion(context, "1.9", null);
+
+ Reader reader = getReader(context, progressListener, "getInternetRadioStations");
+ try {
+ return new InternetRadioStationParser(context, getInstance(context)).parse(reader, progressListener);
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{
return processOfflineScrobbles(context, progressListener) + processOfflineStars(context, progressListener);
}
@@ -1714,13 +1608,13 @@ public class RESTMusicService implements MusicService {
int count = offline.getInt(Constants.OFFLINE_SCROBBLE_COUNT, 0);
int retry = 0;
for(int i = 1; i <= count; i++) {
- String id = offline.getString(Constants.OFFLINE_SCROBBLE_ID + i, null);
- long time = offline.getLong(Constants.OFFLINE_SCROBBLE_TIME + i, 0);
- if(id != null) {
- scrobble(id, true, time, context, progressListener);
- } else {
- String search = offline.getString(Constants.OFFLINE_SCROBBLE_SEARCH + i, "");
- try{
+ try {
+ String id = offline.getString(Constants.OFFLINE_SCROBBLE_ID + i, null);
+ long time = offline.getLong(Constants.OFFLINE_SCROBBLE_TIME + i, 0);
+ if(id != null) {
+ scrobble(id, true, time, context, progressListener);
+ } else {
+ String search = offline.getString(Constants.OFFLINE_SCROBBLE_SEARCH + i, "");
SearchCritera critera = new SearchCritera(search, 0, 0, 1);
SearchResult result = searchNew(critera, context, progressListener);
if(result.getSongs().size() == 1){
@@ -1732,10 +1626,10 @@ public class RESTMusicService implements MusicService {
throw new Exception("Song not found on server");
}
}
- catch(Exception e){
- Log.e(TAG, e.toString());
- retry++;
- }
+ }
+ catch(Exception e){
+ Log.e(TAG, e.toString());
+ retry++;
}
}
@@ -1815,210 +1709,244 @@ public class RESTMusicService implements MusicService {
this.instance = instance;
}
- private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams) throws Exception {
- return getReader(context, progressListener, method, requestParams, false);
+ protected Bitmap getBitmapFromUrl(Context context, String url, List<String> parameterNames, List<Object> parameterValues, int size, File saveToFile, boolean allowUnscaled, ProgressListener progressListener, SilentBackgroundTask task) throws Exception {
+ InputStream in = null;
+ try {
+ HttpURLConnection connection = getConnection(context, url, parameterNames, parameterValues, progressListener, true);
+ in = getInputStreamFromConnection(connection);
+
+ String contentType = connection.getContentType();
+ if (contentType != null && (contentType.startsWith("text/xml") || contentType.startsWith("text/html"))) {
+ new ErrorParser(context, getInstance(context)).parse(new InputStreamReader(in, Constants.UTF_8));
+ }
+
+ byte[] bytes = Util.toByteArray(in);
+
+ // Handle case where partial was downloaded before being cancelled
+ if(task != null && task.isCancelled()) {
+ return null;
+ }
+
+ OutputStream out = null;
+ try {
+ out = new FileOutputStream(saveToFile);
+ out.write(bytes);
+ } finally {
+ Util.close(out);
+ }
+
+ // Size == 0 -> only want to download
+ if(size == 0) {
+ return null;
+ } else {
+ return FileUtil.getSampledBitmap(bytes, size, allowUnscaled);
+ }
+ } finally {
+ Util.close(in);
+ }
}
- private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams, boolean throwsError) throws Exception {
- return getReader(context, progressListener, method, requestParams, Collections.<String>emptyList(), Collections.emptyList(), throwsError);
- }
- private Reader getReader(Context context, ProgressListener progressListener, String method,
- HttpParams requestParams, String parameterName, Object parameterValue) throws Exception {
- return getReader(context, progressListener, method, requestParams, Arrays.asList(parameterName), Arrays.<Object>asList(parameterValue));
- }
+ // Helper classes to get a reader for the request
+ private Reader getReader(Context context, ProgressListener progressListener, String method) throws Exception {
+ return getReader(context, progressListener, method, (List<String>)null, null);
+ }
- private Reader getReader(Context context, ProgressListener progressListener, String method,
- HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues) throws Exception {
- return getReader(context, progressListener, method, requestParams, parameterNames, parameterValues, false);
+ private Reader getReader(Context context, ProgressListener progressListener, String method, String parameterName, Object parameterValue) throws Exception {
+ return getReader(context, progressListener, method, parameterName, parameterValue, 0);
}
- private Reader getReader(Context context, ProgressListener progressListener, String method,
- HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, boolean throwErrors) throws Exception {
+ private Reader getReader(Context context, ProgressListener progressListener, String method, String parameterName, Object parameterValue, int minNetworkTimeout) throws Exception {
+ return getReader(context, progressListener, method, Arrays.asList(parameterName), Arrays.asList(parameterValue), minNetworkTimeout);
+ }
+ private Reader getReader(Context context, ProgressListener progressListener, String method, List<String> parameterNames, List<Object> parameterValues) throws Exception {
+ return getReader(context, progressListener, method, parameterNames, parameterValues, 0);
+ }
+ private Reader getReader(Context context, ProgressListener progressListener, String method, List<String> parameterNames, List<Object> parameterValues, int minNetworkTimeout) throws Exception {
+ return getReader(context, progressListener, method, parameterNames, parameterValues, minNetworkTimeout, false);
+ }
+ private Reader getReader(Context context, ProgressListener progressListener, String method, List<String> parameterNames, List<Object> parameterValues, boolean throwErrors) throws Exception {
+ return getReader(context, progressListener, method, parameterNames, parameterValues, 0, throwErrors);
+ }
+ private Reader getReader(Context context, ProgressListener progressListener, String method, List<String> parameterNames, List<Object> parameterValues, int minNetworkTimeout, boolean throwErrors) throws Exception {
if (progressListener != null) {
progressListener.updateProgress(R.string.service_connecting);
}
String url = getRestUrl(context, method);
- return getReaderForURL(context, url, requestParams, parameterNames, parameterValues, progressListener, throwErrors);
+ return getReaderForURL(context, url, parameterNames, parameterValues, minNetworkTimeout, progressListener, throwErrors);
}
- private Reader getReaderForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames,
- List<Object> parameterValues, ProgressListener progressListener) throws Exception {
- return getReaderForURL(context, url, requestParams, parameterNames, parameterValues, progressListener, true);
+ private Reader getReaderForURL(Context context, String url, List<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener) throws Exception {
+ return getReaderForURL(context, url, parameterNames, parameterValues, progressListener, true);
}
- private Reader getReaderForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames,
- List<Object> parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception {
- HttpEntity entity = getEntityForURL(context, url, requestParams, parameterNames, parameterValues, progressListener, throwErrors);
- if (entity == null) {
- throw new RuntimeException("No entity received for URL " + url);
- }
+ private Reader getReaderForURL(Context context, String url, List<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception {
+ return getReaderForURL(context, url, parameterNames, parameterValues, 0, progressListener, throwErrors);
+ }
+ private Reader getReaderForURL(Context context, String url, List<String> parameterNames, List<Object> parameterValues, int minNetworkTimeout, ProgressListener progressListener, boolean throwErrors) throws Exception {
+ InputStream in = getInputStream(context, url, parameterNames, parameterValues, minNetworkTimeout, progressListener, throwErrors);
+ return new InputStreamReader(in, Constants.UTF_8);
+ }
- InputStream in = entity.getContent();
- Header contentEncoding = entity.getContentEncoding();
- if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
+ // Helper classes to open a connection to a server
+ private InputStream getInputStream(Context context, String url, List<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener, boolean throwsErrors) throws Exception {
+ return getInputStream(context, url, parameterNames, parameterValues, 0, progressListener, throwsErrors);
+ }
+ private InputStream getInputStream(Context context, String url, List<String> parameterNames, List<Object> parameterValues, int minNetworkTimeout, ProgressListener progressListener, boolean throwsErrors) throws Exception {
+ HttpURLConnection connection = getConnection(context, url, parameterNames, parameterValues, minNetworkTimeout, progressListener, throwsErrors);
+ return getInputStreamFromConnection(connection);
+ }
+ private InputStream getInputStreamFromConnection(HttpURLConnection connection) throws Exception {
+ InputStream in = connection.getInputStream();
+ if("gzip".equals(connection.getContentEncoding())) {
in = new GZIPInputStream(in);
}
- return new InputStreamReader(in, Constants.UTF_8);
- }
- private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames,
- List<Object> parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception {
+ return in;
+ }
- return getEntityForURL(context, url, requestParams, parameterNames, parameterValues, progressListener, null, throwErrors);
+ private HttpURLConnection getConnection(Context context, String url, List<String> parameterNames, List<Object> parameterValues, Map<String, String> headers, int minNetworkTimeout) throws Exception {
+ return getConnection(context, url, parameterNames, parameterValues, headers, minNetworkTimeout, null, true);
+ }
+ private HttpURLConnection getConnection(Context context, String url, List<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener, boolean throwErrors) throws Exception {
+ return getConnection(context, url, parameterNames, parameterValues, 0, progressListener, throwErrors);
+ }
+ private HttpURLConnection getConnection(Context context, String url, List<String> parameterNames, List<Object> parameterValues, int minNetworkTimeout, ProgressListener progressListener, boolean throwErrors) throws Exception {
+ return getConnection(context, url, parameterNames, parameterValues, null, minNetworkTimeout, progressListener, throwErrors);
+ }
+ private HttpURLConnection getConnection(Context context, String url, List<String> parameterNames, List<Object> parameterValues, Map<String, String> headers, int minNetworkTimeout, ProgressListener progressListener, boolean throwErrors) throws Exception {
+ if(throwErrors) {
+ SharedPreferences prefs = Util.getPreferences(context);
+ int networkTimeout = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, SOCKET_READ_TIMEOUT_DEFAULT + ""));
+ return getConnectionDirect(context, url, parameterNames, parameterValues, headers, Math.max(minNetworkTimeout, networkTimeout));
+ } else {
+ return getConnection(context, url, parameterNames, parameterValues, headers, minNetworkTimeout, progressListener, HTTP_REQUEST_MAX_ATTEMPTS, 0);
+ }
}
- private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames,
- List<Object> parameterValues, ProgressListener progressListener, SilentBackgroundTask task) throws Exception {
- return getResponseForURL(context, url, requestParams, parameterNames, parameterValues, null, progressListener, task, false).getEntity();
+ private HttpURLConnection getConnection(Context context, String url, List<String> parameterNames, List<Object> parameterValues, Map<String, String> headers, int minNetworkTimeout, ProgressListener progressListener, int retriesLeft, int attempts) throws Exception {
+ SharedPreferences prefs = Util.getPreferences(context);
+ int networkTimeout = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, SOCKET_READ_TIMEOUT_DEFAULT + ""));
+ minNetworkTimeout = Math.max(minNetworkTimeout, networkTimeout);
+ attempts++;
+ retriesLeft--;
+
+ try {
+ return getConnectionDirect(context, url, parameterNames, parameterValues, headers, minNetworkTimeout);
+ } catch (IOException x) {
+ if(retriesLeft > 0) {
+ if (progressListener != null) {
+ String msg = context.getResources().getString(R.string.music_service_retry, attempts, HTTP_REQUEST_MAX_ATTEMPTS - 1);
+ progressListener.updateProgress(msg);
+ }
+
+ Log.w(TAG, "Got IOException " + x + " (" + attempts + "), will retry");
+ Thread.sleep(2000L);
+
+ minNetworkTimeout = (int) (minNetworkTimeout * 1.3);
+ return getConnection(context, url, parameterNames, parameterValues, headers, minNetworkTimeout, progressListener, retriesLeft, attempts);
+ } else {
+ throw x;
+ }
+ }
}
- private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames,
- List<Object> parameterValues, ProgressListener progressListener, SilentBackgroundTask task, boolean throwsError) throws Exception {
- return getResponseForURL(context, url, requestParams, parameterNames, parameterValues, null, progressListener, task, throwsError).getEntity();
- }
- private HttpResponse getResponseForURL(Context context, String url, HttpParams requestParams,
- List<String> parameterNames, List<Object> parameterValues,
- List<Header> headers, ProgressListener progressListener, SilentBackgroundTask task, boolean throwsErrors) throws Exception {
- // If not too many parameters, extract them to the URL rather than relying on the HTTP POST request being
- // received intact. Remember, HTTP POST requests are converted to GET requests during HTTP redirects, thus
- // loosing its entity.
- if (parameterNames != null && parameterNames.size() < 10) {
- StringBuilder builder = new StringBuilder(url);
- for (int i = 0; i < parameterNames.size(); i++) {
- builder.append("&").append(parameterNames.get(i)).append("=");
+ private HttpURLConnection getConnectionDirect(Context context, String url, List<String> parameterNames, List<Object> parameterValues, Map<String, String> headers, int minNetworkTimeout) throws Exception {
+ // Add params to query
+ if (parameterNames != null) {
+ StringBuilder builder = new StringBuilder(url);
+ for (int i = 0; i < parameterNames.size(); i++) {
+ builder.append("&").append(parameterNames.get(i)).append("=");
String part = URLEncoder.encode(String.valueOf(parameterValues.get(i)), "UTF-8");
- part = part.replaceAll("\\%27", "&#39;");
- builder.append(part);
- }
- url = builder.toString();
- parameterNames = null;
- parameterValues = null;
- }
+ part = part.replaceAll("\\%27", "'");
+ builder.append(part);
+ }
+ url = builder.toString();
+ }
- String rewrittenUrl = rewriteUrlWithRedirect(context, url);
- return executeWithRetry(context, rewrittenUrl, url, requestParams, parameterNames, parameterValues, headers, progressListener, task, throwsErrors);
- }
+ // Rewrite url based on redirects
+ String rewrittenUrl = rewriteUrlWithRedirect(context, url);
+ if(rewrittenUrl.indexOf("scanstatus") == -1) {
+ Log.i(TAG, stripUrlInfo(rewrittenUrl));
+ }
+
+ return getConnectionDirect(context, rewrittenUrl, headers, minNetworkTimeout);
+ }
+
+ private HttpURLConnection getConnectionDirect(Context context, String url, Map<String, String> headers, int minNetworkTimeout) throws Exception {
+ if(!hasInstalledGoogleSSL) {
+ try {
+ ProviderInstaller.installIfNeeded(context);
+ } catch(Exception e) {
+ // Just continue on anyways, doesn't really harm anything if this fails
+ Log.w(TAG, "Failed to update to use Google Play SSL", e);
+ }
+ hasInstalledGoogleSSL = true;
+ }
- private HttpResponse executeWithRetry(final Context context, String url, String originalUrl, HttpParams requestParams,
- List<String> parameterNames, List<Object> parameterValues,
- List<Header> headers, ProgressListener progressListener, SilentBackgroundTask task, boolean throwErrors) throws Exception {
- // Strip out sensitive information from log
- if(url.indexOf("scanstatus") == -1) {
- Log.i(TAG, stripUrlInfo(url));
+ // Connect and add headers
+ URL urlObj = new URL(url);
+ HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
+ if(url.indexOf("getCoverArt") == -1 && url.indexOf("stream") == -1 && url.indexOf("getAvatar") == -1) {
+ connection.addRequestProperty("Accept-Encoding", "gzip");
+ }
+ connection.addRequestProperty("User-Agent", Constants.REST_CLIENT_ID);
+
+ // Set timeout
+ connection.setConnectTimeout(minNetworkTimeout);
+ connection.setReadTimeout(minNetworkTimeout);
+
+ // Add headers
+ if(headers != null) {
+ for(Map.Entry<String, String> header: headers.entrySet()) {
+ connection.setRequestProperty(header.getKey(), header.getValue());
+ }
+ }
+
+ if(connection instanceof HttpsURLConnection) {
+ HttpsURLConnection sslConnection = (HttpsURLConnection) connection;
+ sslConnection.setSSLSocketFactory(sslSocketFactory);
+ sslConnection.setHostnameVerifier(selfSignedHostnameVerifier);
}
SharedPreferences prefs = Util.getPreferences(context);
- int networkTimeout = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, "15000"));
- HttpParams newParams = httpClient.getParams();
- HttpConnectionParams.setSoTimeout(newParams, networkTimeout);
- httpClient.setParams(newParams);
-
- final AtomicReference<Boolean> isCancelled = new AtomicReference<Boolean>(false);
- int attempts = 0;
- while (true) {
- attempts++;
- HttpContext httpContext = new BasicHttpContext();
- final HttpRequestBase request = (url.indexOf("rest") == -1) ? new HttpGet(url) : new HttpPost(url);
-
- if (task != null) {
- // Attempt to abort the HTTP request if the task is cancelled.
- task.setOnCancelListener(new BackgroundTask.OnCancelListener() {
- @Override
- public void onCancel() {
- try {
- isCancelled.set(true);
- if(Thread.currentThread() == Looper.getMainLooper().getThread()) {
- new SilentBackgroundTask<Void>(context) {
- @Override
- protected Void doInBackground() throws Throwable {
- request.abort();
- return null;
- }
- }.execute();
- } else {
- request.abort();
- }
- } catch(Exception e) {
- Log.e(TAG, "Failed to stop http task", e);
- }
- }
- });
- }
-
- if (parameterNames != null && request instanceof HttpPost) {
- List<NameValuePair> params = new ArrayList<NameValuePair>();
- for (int i = 0; i < parameterNames.size(); i++) {
- params.add(new BasicNameValuePair(parameterNames.get(i), String.valueOf(parameterValues.get(i))));
- }
- ((HttpPost) request).setEntity(new UrlEncodedFormEntity(params, Constants.UTF_8));
- }
-
- if (requestParams != null) {
- request.setParams(requestParams);
- }
-
- if (headers != null) {
- for (Header header : headers) {
- request.addHeader(header);
- }
- }
- if(url.indexOf("getCoverArt") == -1 && url.indexOf("stream") == -1 && url.indexOf("getAvatar") == -1) {
- request.addHeader("Accept-Encoding", "gzip");
+ int instance = getInstance(context);
+ String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
+ String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
+ String encoded = Base64.encodeToString((username + ":" + password).getBytes("UTF-8"), Base64.NO_WRAP);;
+ connection.setRequestProperty("Authorization", "Basic " + encoded);
+
+ // Force the connection to initiate
+ if(connection.getResponseCode() >= 500) {
+ throw new IOException("Error code: " + connection.getResponseCode());
+ }
+ if(detectRedirect(context, urlObj, connection)) {
+ String rewrittenUrl = rewriteUrlWithRedirect(context, url);
+ if(!rewrittenUrl.equals(url)) {
+ connection.disconnect();
+ return getConnectionDirect(context, rewrittenUrl, headers, minNetworkTimeout);
}
- request.addHeader("User-Agent", Constants.REST_CLIENT_ID);
-
- // Set credentials to get through apache proxies that require authentication.
- int instance = getInstance(context);
- String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
- String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
- httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),
- new UsernamePasswordCredentials(username, password));
-
- try {
- HttpResponse response = httpClient.execute(request, httpContext);
- detectRedirect(originalUrl, context, httpContext);
- return response;
- } catch (IOException x) {
- request.abort();
- if (attempts >= HTTP_REQUEST_MAX_ATTEMPTS || isCancelled.get() || throwErrors) {
- throw x;
- }
- if (progressListener != null) {
- String msg = context.getResources().getString(R.string.music_service_retry, attempts, HTTP_REQUEST_MAX_ATTEMPTS - 1);
- progressListener.updateProgress(msg);
- }
- Log.w(TAG, "Got IOException " + x + " (" + attempts + "), will retry");
- increaseTimeouts(requestParams);
- Thread.sleep(2000L);
- }
- }
- }
+ }
- private void increaseTimeouts(HttpParams requestParams) {
- if (requestParams != null) {
- int connectTimeout = HttpConnectionParams.getConnectionTimeout(requestParams);
- if (connectTimeout != 0) {
- HttpConnectionParams.setConnectionTimeout(requestParams, (int) (connectTimeout * 1.3F));
- }
- int readTimeout = HttpConnectionParams.getSoTimeout(requestParams);
- if (readTimeout != 0) {
- HttpConnectionParams.setSoTimeout(requestParams, (int) (readTimeout * 1.5F));
- }
- }
- }
+ return connection;
+ }
- private void detectRedirect(String originalUrl, Context context, HttpContext httpContext) throws Exception {
- HttpUriRequest request = (HttpUriRequest) httpContext.getAttribute(ExecutionContext.HTTP_REQUEST);
- HttpHost host = (HttpHost) httpContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
-
- // Sometimes the request doesn't contain the "http://host" part
- String redirectedUrl;
- if (request.getURI().getScheme() == null) {
- redirectedUrl = host.toURI() + request.getURI();
- } else {
- redirectedUrl = request.getURI().toString();
+ // Returns true when we should immediately retry with the redirect
+ private boolean detectRedirect(Context context, URL originalUrl, HttpURLConnection connection) throws Exception {
+ if(connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP || connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM) {
+ String redirectLocation = connection.getHeaderField("Location");
+ if(redirectLocation != null) {
+ detectRedirect(context, originalUrl.toExternalForm(), redirectLocation);
+ return true;
+ }
}
+ detectRedirect(context, originalUrl, connection.getURL());
+ return false;
+ }
+ private void detectRedirect(Context context, URL originalUrl, URL redirectedUrl) throws Exception {
+ detectRedirect(context, originalUrl.toExternalForm(), redirectedUrl.toExternalForm());
+ }
+ private void detectRedirect(Context context, String originalUrl, String redirectedUrl) throws Exception {
if(redirectedUrl != null && "http://subsonic.org/pages/".equals(redirectedUrl)) {
throw new Exception("Invalid url, redirects to http://subsonic.org/pages/");
}
@@ -2035,7 +1963,7 @@ public class RESTMusicService implements MusicService {
redirectionLastChecked = System.currentTimeMillis();
redirectionNetworkType = getCurrentNetworkType(context);
}
- }
+ }
private String rewriteUrlWithRedirect(Context context, String url) {
@@ -2084,7 +2012,10 @@ public class RESTMusicService implements MusicService {
}
}
- public HttpClient getHttpClient() {
- return httpClient;
+ public SSLSocketFactory getSSLSocketFactory() {
+ return sslSocketFactory;
+ }
+ public HostnameVerifier getHostNameVerifier() {
+ return selfSignedHostnameVerifier;
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java b/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java
index 99502f5e..617144d7 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/RemoteController.java
@@ -19,18 +19,32 @@
package github.daneren2005.dsub.service;
+import android.content.SharedPreferences;
import android.util.Log;
import java.util.Iterator;
import java.util.concurrent.LinkedBlockingQueue;
+import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.RemoteStatus;
+import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.Util;
+import github.daneren2005.serverproxy.FileProxy;
+import github.daneren2005.serverproxy.ServerProxy;
import github.daneren2005.serverproxy.WebProxy;
public abstract class RemoteController {
private static final String TAG = RemoteController.class.getSimpleName();
protected DownloadService downloadService;
protected boolean nextSupported = false;
+ protected ServerProxy proxy;
+ protected String rootLocation = "";
+
+ public RemoteController(DownloadService downloadService) {
+ this.downloadService = downloadService;
+ SharedPreferences prefs = Util.getPreferences(downloadService);
+ rootLocation = prefs.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, null);
+ }
public abstract void create(boolean playing, int seconds);
public abstract void start();
@@ -43,7 +57,11 @@ public abstract class RemoteController {
// Really is abstract, just don't want to require RemoteController's support it
public void changeNextTrack(DownloadFile song) {}
public boolean isNextSupported() {
- return this.nextSupported;
+ if(Util.getPreferences(downloadService).getBoolean(Constants.PREFERENCES_KEY_CAST_GAPLESS_PLAYBACK, true)) {
+ return this.nextSupported;
+ } else {
+ return false;
+ }
}
public abstract void setVolume(int volume);
public abstract void updateVolume(boolean up);
@@ -99,9 +117,62 @@ public abstract class RemoteController {
protected WebProxy createWebProxy() {
MusicService musicService = MusicServiceFactory.getMusicService(downloadService);
if(musicService instanceof CachedMusicService) {
- return new WebProxy(downloadService, ((CachedMusicService)musicService).getMusicService().getHttpClient());
+ RESTMusicService restMusicService = ((CachedMusicService)musicService).getMusicService();
+ return new WebProxy(downloadService, restMusicService.getSSLSocketFactory(), restMusicService.getHostNameVerifier());
} else {
return new WebProxy(downloadService);
}
}
+
+ protected String getStreamUrl(MusicService musicService, DownloadFile downloadFile) throws Exception {
+ MusicDirectory.Entry song = downloadFile.getSong();
+
+ String url;
+ // In offline mode or playing offline song
+ if(downloadFile.isStream()) {
+ url = downloadFile.getStream();
+ } else if(Util.isOffline(downloadService) || song.getId().indexOf(rootLocation) != -1) {
+ if(proxy == null) {
+ proxy = new FileProxy(downloadService);
+ proxy.start();
+ }
+
+ // Offline song
+ if(song.getId().indexOf(rootLocation) != -1) {
+ url = proxy.getPublicAddress(song.getId());
+ } else {
+ // Playing online song in offline mode
+ url = proxy.getPublicAddress(downloadFile.getCompleteFile().getPath());
+ }
+ } else {
+ // Check if we want a proxy going still
+ if(Util.isCastProxy(downloadService)) {
+ if(proxy instanceof FileProxy) {
+ proxy.stop();
+ proxy = null;
+ }
+
+ if(proxy == null) {
+ proxy = createWebProxy();
+ proxy.start();
+ }
+ } else if(proxy != null) {
+ proxy.stop();
+ proxy = null;
+ }
+
+ if(song.isVideo()) {
+ url = musicService.getHlsUrl(song.getId(), downloadFile.getBitRate(), downloadService);
+ } else {
+ url = musicService.getMusicUrl(downloadService, song, downloadFile.getBitRate());
+ }
+
+ // If proxy is going, it is a WebProxy
+ if(proxy != null) {
+ url = proxy.getPublicAddress(url);
+ }
+ }
+
+ return url;
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/Scrobbler.java b/app/src/main/java/github/daneren2005/dsub/service/Scrobbler.java
index 1d9fecef..c7ad639e 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/Scrobbler.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/Scrobbler.java
@@ -3,6 +3,8 @@ package github.daneren2005.dsub.service;
import android.content.Context;
import android.util.Log;
+import github.daneren2005.dsub.domain.InternetRadioStation;
+import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.PodcastEpisode;
import github.daneren2005.dsub.util.SilentBackgroundTask;
import github.daneren2005.dsub.util.SongDBHandler;
@@ -21,18 +23,18 @@ public class Scrobbler {
private String lastSubmission;
private String lastNowPlaying;
- public void conditionalScrobble(Context context, DownloadFile song, int playerPosition, int duration) {
+ public void conditionalScrobble(Context context, DownloadFile song, int playerPosition, int duration, boolean isPastCutoff) {
// More than 4 minutes
if(playerPosition > FOUR_MINUTES) {
- scrobble(context, song, true);
+ scrobble(context, song, true, isPastCutoff);
}
// More than 50% played
else if(duration > 0 && playerPosition > (duration / 2)) {
- scrobble(context, song, true);
+ scrobble(context, song, true, isPastCutoff);
}
}
- public void scrobble(final Context context, final DownloadFile song, final boolean submission) {
+ public void scrobble(final Context context, final DownloadFile song, final boolean submission, final boolean isPastCutoff) {
if(song == null) {
return;
}
@@ -55,7 +57,9 @@ public class Scrobbler {
new SilentBackgroundTask<Void>(context) {
@Override
protected Void doInBackground() {
- SongDBHandler.getHandler(context).setSongPlayed(song, submission);
+ if(isPastCutoff) {
+ SongDBHandler.getHandler(context).setSongPlayed(song, submission);
+ }
// Scrobbling disabled
if (!Util.isScrobblingEnabled(context)) {
@@ -66,7 +70,7 @@ public class Scrobbler {
return null;
}
// Ignore podcasts
- else if(song.getSong() instanceof PodcastEpisode) {
+ else if(song.getSong() instanceof PodcastEpisode || song.getSong() instanceof InternetRadioStation) {
return null;
}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/parser/AbstractParser.java b/app/src/main/java/github/daneren2005/dsub/service/parser/AbstractParser.java
index 664adcfb..d4c090c1 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/parser/AbstractParser.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/parser/AbstractParser.java
@@ -18,6 +18,7 @@
*/
package github.daneren2005.dsub.service.parser;
+import java.io.IOException;
import java.io.Reader;
import org.xmlpull.v1.XmlPullParser;
@@ -36,6 +37,12 @@ import github.daneren2005.dsub.util.Util;
*/
public abstract class AbstractParser {
private static final String TAG = AbstractParser.class.getSimpleName();
+ private static final String SUBSONIC_RESPONSE = "subsonic-response";
+ private static final String MADSONIC_RESPONSE = "madsonic-response";
+ private static final String SUBSONIC = "subsonic";
+ private static final String MADSONIC = "madsonic";
+ private static final String AMPACHE = "ampache";
+
protected final Context context;
protected final int instance;
private XmlPullParser parser;
@@ -66,6 +73,11 @@ public abstract class AbstractParser {
case 40:
message = context.getResources().getString(R.string.parser_not_authenticated);
break;
+ case 41:
+ Util.setBlockTokenUse(context, instance, true);
+
+ // Throw IOException so RESTMusicService knows to retry
+ throw new IOException();
case 50:
message = context.getResources().getString(R.string.parser_not_authorized);
break;
@@ -127,21 +139,32 @@ public abstract class AbstractParser {
}
protected int nextParseEvent() throws Exception {
- return parser.next();
+ try {
+ return parser.next();
+ } catch(Exception e) {
+ if(ServerInfo.isMadsonic6(context, instance)) {
+ ServerInfo overrideInfo = new ServerInfo();
+ overrideInfo.saveServerInfo(context, instance);
+ }
+
+ throw e;
+ }
}
protected String getElementName() {
String name = parser.getName();
- if ("subsonic-response".equals(name) || "madsonic-response".equals(name)) {
+ if (SUBSONIC_RESPONSE.equals(name) || MADSONIC_RESPONSE.equals(name)) {
rootElementFound = true;
String version = get("version");
if (version != null) {
ServerInfo server = new ServerInfo();
server.setRestVersion(new Version(version));
- if("madsonic".equals(get("type")) || "madsonic-response".equals(name)) {
+ if(MADSONIC.equals(get("type")) || MADSONIC_RESPONSE.equals(name)) {
server.setRestType(ServerInfo.TYPE_MADSONIC);
- } else if("subsonic".equals(get("type")) && server.checkServerVersion(context, "1.13")) {
+ } if(AMPACHE.equals(get("type"))) {
+ server.setRestType(ServerInfo.TYPE_AMPACHE);
+ } else if(SUBSONIC.equals(get("type")) && server.checkServerVersion(context, "1.13")) {
// Oh am I going to regret this
server.setRestType(ServerInfo.TYPE_MADSONIC);
server.setRestVersion(new Version("2.0.0"));
@@ -154,6 +177,11 @@ public abstract class AbstractParser {
protected void validate() throws Exception {
if (!rootElementFound) {
+ if(ServerInfo.isMadsonic6(context, instance)) {
+ ServerInfo overrideInfo = new ServerInfo();
+ overrideInfo.saveServerInfo(context, instance);
+ }
+
throw new Exception(context.getResources().getString(R.string.background_task_parse_error));
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/parser/AlbumListParser.java b/app/src/main/java/github/daneren2005/dsub/service/parser/EntryListParser.java
index 773c241b..f91aaae1 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/parser/AlbumListParser.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/parser/EntryListParser.java
@@ -29,9 +29,9 @@ import java.io.Reader;
/**
* @author Sindre Mehus
*/
-public class AlbumListParser extends MusicDirectoryEntryParser {
+public class EntryListParser extends MusicDirectoryEntryParser {
- public AlbumListParser(Context context, int instance) {
+ public EntryListParser(Context context, int instance) {
super(context, instance);
}
@@ -46,7 +46,12 @@ public class AlbumListParser extends MusicDirectoryEntryParser {
String name = getElementName();
if ("album".equals(name)) {
MusicDirectory.Entry entry = parseEntry("");
- entry.setDirectory(true);
+ if(get("isDir") == null) {
+ entry.setDirectory(true);
+ }
+ dir.addChild(entry);
+ } else if ("song".equals(name)) {
+ MusicDirectory.Entry entry = parseEntry("");
dir.addChild(entry);
} else if ("error".equals(name)) {
handleError();
diff --git a/app/src/main/java/github/daneren2005/dsub/service/parser/ErrorParser.java b/app/src/main/java/github/daneren2005/dsub/service/parser/ErrorParser.java
index afb05928..1b389f80 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/parser/ErrorParser.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/parser/ErrorParser.java
@@ -27,7 +27,6 @@ import java.io.Reader;
* @author Sindre Mehus
*/
public class ErrorParser extends AbstractParser {
-
public ErrorParser(Context context, int instance) {
super(context, instance);
}
@@ -45,5 +44,6 @@ public class ErrorParser extends AbstractParser {
} while (eventType != XmlPullParser.END_DOCUMENT);
validate();
+ reader.close();
}
} \ No newline at end of file
diff --git a/app/src/main/java/github/daneren2005/dsub/service/parser/InternetRadioStationParser.java b/app/src/main/java/github/daneren2005/dsub/service/parser/InternetRadioStationParser.java
new file mode 100644
index 00000000..77d7bc4a
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/service/parser/InternetRadioStationParser.java
@@ -0,0 +1,63 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2016 (C) Scott Jackson
+*/
+package github.daneren2005.dsub.service.parser;
+
+import android.content.Context;
+import github.daneren2005.dsub.domain.InternetRadioStation;
+import github.daneren2005.dsub.util.ProgressListener;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class InternetRadioStationParser extends ErrorParser {
+ public InternetRadioStationParser(Context context, int instance) {
+ super(context, instance);
+ }
+
+ public List<InternetRadioStation> parse(Reader reader, ProgressListener progressListener) throws Exception {
+ init(reader);
+
+ List<InternetRadioStation> result = new ArrayList<>();
+ int eventType;
+ do {
+ eventType = nextParseEvent();
+ if (eventType == XmlPullParser.START_TAG) {
+ String name = getElementName();
+ if ("internetRadioStation".equals(name)) {
+ InternetRadioStation station = new InternetRadioStation();
+
+ station.setId(get("id"));
+ station.setTitle(get("name"));
+ station.setStreamUrl(get("streamUrl"));
+ station.setHomePageUrl(get("homePageUrl"));
+
+ result.add(station);
+ } else if ("error".equals(name)) {
+ handleError();
+ }
+ }
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ validate();
+ return result;
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/java/github/daneren2005/dsub/service/parser/ScanStatusParser.java b/app/src/main/java/github/daneren2005/dsub/service/parser/ScanStatusParser.java
index ffb3ba05..acd00661 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/parser/ScanStatusParser.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/parser/ScanStatusParser.java
@@ -21,6 +21,7 @@ import org.xmlpull.v1.XmlPullParser;
import java.io.Reader;
import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.ServerInfo;
import github.daneren2005.dsub.util.ProgressListener;
public class ScanStatusParser extends AbstractParser {
@@ -32,14 +33,23 @@ public class ScanStatusParser extends AbstractParser {
public boolean parse(Reader reader, ProgressListener progressListener) throws Exception {
init(reader);
- Boolean started = null;
+ String scanName, scanningName;
+ if(ServerInfo.isMadsonic(context, instance)) {
+ scanName = "status";
+ scanningName = "started";
+ } else {
+ scanName = "scanStatus";
+ scanningName = "scanning";
+ }
+
+ Boolean scanning = null;
int eventType;
do {
eventType = nextParseEvent();
if (eventType == XmlPullParser.START_TAG) {
String name = getElementName();
- if("status".equals(name)) {
- started = getBoolean("started");
+ if(scanName.equals(name)) {
+ scanning = getBoolean(scanningName);
String msg = context.getResources().getString(R.string.parser_scan_count, getInteger("count"));
progressListener.updateProgress(msg);
@@ -51,6 +61,6 @@ public class ScanStatusParser extends AbstractParser {
validate();
- return started != null && started;
+ return scanning != null && scanning;
}
} \ No newline at end of file
diff --git a/app/src/main/java/github/daneren2005/dsub/service/parser/TopSongsParser.java b/app/src/main/java/github/daneren2005/dsub/service/parser/TopSongsParser.java
new file mode 100644
index 00000000..2d1f43dc
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/service/parser/TopSongsParser.java
@@ -0,0 +1,58 @@
+/*
+ This file is part of Subsonic.
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+ Copyright 2016 (C) Scott Jackson
+*/
+package github.daneren2005.dsub.service.parser;
+
+import android.content.Context;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.Reader;
+
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.util.ProgressListener;
+
+public class TopSongsParser extends MusicDirectoryEntryParser {
+
+ public TopSongsParser(Context context, int instance) {
+ super(context, instance);
+ }
+
+ public MusicDirectory parse(Reader reader, ProgressListener progressListener) throws Exception {
+ init(reader);
+
+ MusicDirectory dir = new MusicDirectory();
+ int eventType;
+ int customOrder = 1;
+ do {
+ eventType = nextParseEvent();
+ if (eventType == XmlPullParser.START_TAG) {
+ String name = getElementName();
+ if ("song".equals(name)) {
+ MusicDirectory.Entry entry = parseEntry("");
+ entry.setCustomOrder(customOrder);
+ dir.addChild(entry);
+
+ customOrder++;
+ } else if ("error".equals(name)) {
+ handleError();
+ }
+ }
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ validate();
+
+ return dir;
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/github/daneren2005/dsub/service/parser/UserParser.java b/app/src/main/java/github/daneren2005/dsub/service/parser/UserParser.java
index e20556c0..fc2ddd7e 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/parser/UserParser.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/parser/UserParser.java
@@ -16,6 +16,7 @@
package github.daneren2005.dsub.service.parser;
import android.content.Context;
+import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
@@ -23,10 +24,16 @@ import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
+import github.daneren2005.dsub.domain.MusicFolder;
import github.daneren2005.dsub.domain.User;
+import github.daneren2005.dsub.domain.User.MusicFolderSetting;
+import github.daneren2005.dsub.domain.User.Setting;
+import github.daneren2005.dsub.service.MusicService;
+import github.daneren2005.dsub.service.MusicServiceFactory;
import github.daneren2005.dsub.util.ProgressListener;
public class UserParser extends AbstractParser {
+ private static final String TAG = UserParser.class.getSimpleName();
public UserParser(Context context, int instance) {
super(context, instance);
@@ -35,14 +42,17 @@ public class UserParser extends AbstractParser {
public List<User> parse(Reader reader, ProgressListener progressListener) throws Exception {
init(reader);
List<User> result = new ArrayList<User>();
+ List<MusicFolder> musicFolders = null;
+ User user = null;
int eventType;
+ String tagName = null;
do {
eventType = nextParseEvent();
if (eventType == XmlPullParser.START_TAG) {
- String name = getElementName();
- if ("user".equals(name)) {
- User user = new User();
+ tagName = getElementName();
+ if ("user".equals(tagName)) {
+ user = new User();
user.setUsername(get("username"));
user.setEmail(get("email"));
@@ -53,9 +63,31 @@ public class UserParser extends AbstractParser {
parseSetting(user, User.LASTFM);
result.add(user);
- } else if ("error".equals(name)) {
+ } else if ("error".equals(tagName)) {
handleError();
}
+ } else if(eventType == XmlPullParser.TEXT) {
+ if("folder".equals(tagName)) {
+ String id = getText();
+ if(musicFolders == null) {
+ musicFolders = getMusicFolders();
+ }
+
+ if(user != null) {
+ if(user.getMusicFolderSettings() == null) {
+ for (MusicFolder musicFolder : musicFolders) {
+ user.addMusicFolder(musicFolder);
+ }
+ }
+
+ for(Setting musicFolder: user.getMusicFolderSettings()) {
+ if(musicFolder.getName().equals(id)) {
+ musicFolder.setValue(true);
+ break;
+ }
+ }
+ }
+ }
}
} while (eventType != XmlPullParser.END_DOCUMENT);
@@ -63,6 +95,11 @@ public class UserParser extends AbstractParser {
return result;
}
+
+ private List<MusicFolder> getMusicFolders() throws Exception{
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ return musicService.getMusicFolders(false, context, null);
+ }
private void parseSetting(User user, String name) {
String value = get(name);
diff --git a/app/src/main/java/github/daneren2005/dsub/service/ssl/SSLSocketFactory.java b/app/src/main/java/github/daneren2005/dsub/service/ssl/SSLSocketFactory.java
deleted file mode 100644
index 830950c8..00000000
--- a/app/src/main/java/github/daneren2005/dsub/service/ssl/SSLSocketFactory.java
+++ /dev/null
@@ -1,553 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package github.daneren2005.dsub.service.ssl;
-
-import android.os.Build;
-import android.util.Log;
-
-import org.apache.http.conn.ConnectTimeoutException;
-import org.apache.http.conn.scheme.HostNameResolver;
-import org.apache.http.conn.scheme.LayeredSocketFactory;
-import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
-import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
-import org.apache.http.conn.ssl.StrictHostnameVerifier;
-import org.apache.http.conn.ssl.X509HostnameVerifier;
-import org.apache.http.params.HttpConnectionParams;
-import org.apache.http.params.HttpParams;
-
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
-
-import java.io.IOException;
-import java.lang.reflect.Array;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.SocketTimeoutException;
-import java.net.UnknownHostException;
-import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Provider;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.UnrecoverableKeyException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Layered socket factory for TLS/SSL connections.
- * <p>
- * SSLSocketFactory can be used to validate the identity of the HTTPS server against a list of
- * trusted certificates and to authenticate to the HTTPS server using a private key.
- * <p>
- * SSLSocketFactory will enable server authentication when supplied with
- * a {@link KeyStore trust-store} file containing one or several trusted certificates. The client
- * secure socket will reject the connection during the SSL session handshake if the target HTTPS
- * server attempts to authenticate itself with a non-trusted certificate.
- * <p>
- * Use JDK keytool utility to import a trusted certificate and generate a trust-store file:
- * <pre>
- * keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
- * </pre>
- * <p>
- * In special cases the standard trust verification process can be bypassed by using a custom
- * {@link TrustStrategy}. This interface is primarily intended for allowing self-signed
- * certificates to be accepted as trusted without having to add them to the trust-store file.
- * <p>
- * The following parameters can be used to customize the behavior of this
- * class:
- * <ul>
- * <li>{@link org.apache.http.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li>
- * <li>{@link org.apache.http.params.CoreConnectionPNames#SO_TIMEOUT}</li>
- * </ul>
- * <p>
- * SSLSocketFactory will enable client authentication when supplied with
- * a {@link KeyStore key-store} file containing a private key/public certificate
- * pair. The client secure socket will use the private key to authenticate
- * itself to the target HTTPS server during the SSL session handshake if
- * requested to do so by the server.
- * The target HTTPS server will in its turn verify the certificate presented
- * by the client in order to establish client's authenticity
- * <p>
- * Use the following sequence of actions to generate a key-store file
- * </p>
- * <ul>
- * <li>
- * <p>
- * Use JDK keytool utility to generate a new key
- * <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre>
- * For simplicity use the same password for the key as that of the key-store
- * </p>
- * </li>
- * <li>
- * <p>
- * Issue a certificate signing request (CSR)
- * <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre>
- * </p>
- * </li>
- * <li>
- * <p>
- * Send the certificate request to the trusted Certificate Authority for signature.
- * One may choose to act as her own CA and sign the certificate request using a PKI
- * tool, such as OpenSSL.
- * </p>
- * </li>
- * <li>
- * <p>
- * Import the trusted CA root certificate
- * <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre>
- * </p>
- * </li>
- * <li>
- * <p>
- * Import the PKCS#7 file containg the complete certificate chain
- * <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre>
- * </p>
- * </li>
- * <li>
- * <p>
- * Verify the content the resultant keystore file
- * <pre>keytool -list -v -keystore my.keystore</pre>
- * </p>
- * </li>
- * </ul>
- *
- * @since 4.0
- */
-public class SSLSocketFactory implements LayeredSocketFactory {
- private static final String TAG = SSLSocketFactory.class.getSimpleName();
- public static final String TLS = "TLS";
-
- public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER
- = new AllowAllHostnameVerifier();
-
- public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
- = new BrowserCompatHostnameVerifier();
-
- public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER
- = new StrictHostnameVerifier();
-
- /**
- * The default factory using the default JVM settings for secure connections.
- */
- private static final SSLSocketFactory DEFAULT_FACTORY = new SSLSocketFactory();
-
- /**
- * Gets the default factory, which uses the default JVM settings for secure
- * connections.
- *
- * @return the default factory
- */
- public static SSLSocketFactory getSocketFactory() {
- return DEFAULT_FACTORY;
- }
-
- private final javax.net.ssl.SSLSocketFactory socketfactory;
- private final HostNameResolver nameResolver;
- // TODO: make final
- private volatile X509HostnameVerifier hostnameVerifier;
-
- private static SSLContext createSSLContext(
- String algorithm,
- final KeyStore keystore,
- final String keystorePassword,
- final KeyStore truststore,
- final SecureRandom random,
- final TrustStrategy trustStrategy)
- throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException {
- if (algorithm == null) {
- algorithm = TLS;
- }
- KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
- KeyManagerFactory.getDefaultAlgorithm());
- kmfactory.init(keystore, keystorePassword != null ? keystorePassword.toCharArray(): null);
- KeyManager[] keymanagers = kmfactory.getKeyManagers();
- TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
- TrustManagerFactory.getDefaultAlgorithm());
- tmfactory.init(keystore);
- TrustManager[] trustmanagers = tmfactory.getTrustManagers();
- if (trustmanagers != null && trustStrategy != null) {
- for (int i = 0; i < trustmanagers.length; i++) {
- TrustManager tm = trustmanagers[i];
- if (tm instanceof X509TrustManager) {
- trustmanagers[i] = new TrustManagerDecorator(
- (X509TrustManager) tm, trustStrategy);
- }
- }
- }
-
- SSLContext sslcontext = SSLContext.getInstance(algorithm);
- sslcontext.init(keymanagers, trustmanagers, random);
- return sslcontext;
- }
-
- /**
- * @deprecated Use {@link #SSLSocketFactory(String, KeyStore, String, KeyStore, SecureRandom, X509HostnameVerifier)}
- */
- @Deprecated
- public SSLSocketFactory(
- final String algorithm,
- final KeyStore keystore,
- final String keystorePassword,
- final KeyStore truststore,
- final SecureRandom random,
- final HostNameResolver nameResolver)
- throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
- this(createSSLContext(
- algorithm, keystore, keystorePassword, truststore, random, null),
- nameResolver);
- }
-
- /**
- * @since 4.1
- */
- public SSLSocketFactory(
- String algorithm,
- final KeyStore keystore,
- final String keystorePassword,
- final KeyStore truststore,
- final SecureRandom random,
- final X509HostnameVerifier hostnameVerifier)
- throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
- this(createSSLContext(
- algorithm, keystore, keystorePassword, truststore, random, null),
- hostnameVerifier);
- }
-
- /**
- * @since 4.1
- */
- public SSLSocketFactory(
- String algorithm,
- final KeyStore keystore,
- final String keystorePassword,
- final KeyStore truststore,
- final SecureRandom random,
- final TrustStrategy trustStrategy,
- final X509HostnameVerifier hostnameVerifier)
- throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
- this(createSSLContext(
- algorithm, keystore, keystorePassword, truststore, random, trustStrategy),
- hostnameVerifier);
- }
-
- public SSLSocketFactory(
- final KeyStore keystore,
- final String keystorePassword,
- final KeyStore truststore)
- throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
- this(TLS, keystore, keystorePassword, truststore, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
- }
-
- public SSLSocketFactory(
- final KeyStore keystore,
- final String keystorePassword)
- throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException{
- this(TLS, keystore, keystorePassword, null, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
- }
-
- public SSLSocketFactory(
- final KeyStore truststore)
- throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
- this(TLS, null, null, truststore, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
- }
-
- /**
- * @since 4.1
- */
- public SSLSocketFactory(
- final TrustStrategy trustStrategy,
- final X509HostnameVerifier hostnameVerifier)
- throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
- this(TLS, null, null, null, null, trustStrategy, hostnameVerifier);
- }
-
- /**
- * @since 4.1
- */
- public SSLSocketFactory(
- final TrustStrategy trustStrategy)
- throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
- this(TLS, null, null, null, null, trustStrategy, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
- }
-
- public SSLSocketFactory(final SSLContext sslContext) {
- this(sslContext, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
- }
-
- /**
- * @deprecated Use {@link #SSLSocketFactory(SSLContext)}
- */
- @Deprecated
- public SSLSocketFactory(
- final SSLContext sslContext, final HostNameResolver nameResolver) {
- super();
- this.socketfactory = sslContext.getSocketFactory();
- this.hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
- this.nameResolver = nameResolver;
- }
-
- /**
- * @since 4.1
- */
- public SSLSocketFactory(
- final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) {
- super();
- this.socketfactory = sslContext.getSocketFactory();
- this.hostnameVerifier = hostnameVerifier;
- this.nameResolver = null;
- }
-
- private SSLSocketFactory() {
- super();
- this.socketfactory = HttpsURLConnection.getDefaultSSLSocketFactory();
- this.hostnameVerifier = null;
- this.nameResolver = null;
- }
-
- /**
- * @param params Optional parameters. Parameters passed to this method will have no effect.
- * This method will create a unconnected instance of {@link Socket} class
- * using {@link javax.net.ssl.SSLSocketFactory#createSocket()} method.
- * @since 4.1
- */
- @SuppressWarnings("cast")
- public Socket createSocket(final HttpParams params) throws IOException {
- // the cast makes sure that the factory is working as expected
- SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket();
- sslSocket.setEnabledProtocols(getProtocols(sslSocket));
- sslSocket.setEnabledCipherSuites(getCiphers(sslSocket));
- return sslSocket;
- }
-
- @SuppressWarnings("cast")
- public Socket createSocket() throws IOException {
- // the cast makes sure that the factory is working as expected
- SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket();
- sslSocket.setEnabledProtocols(getProtocols(sslSocket));
- sslSocket.setEnabledCipherSuites(getCiphers(sslSocket));
- return sslSocket;
- }
-
- /**
- * @since 4.1
- */
- public Socket connectSocket(
- final Socket sock,
- final InetSocketAddress remoteAddress,
- final InetSocketAddress localAddress,
- final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
- if (remoteAddress == null) {
- throw new IllegalArgumentException("Remote address may not be null");
- }
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- SSLSocket sslsock = (SSLSocket) (sock != null ? sock : createSocket());
- if (localAddress != null) {
-// sslsock.setReuseAddress(HttpConnectionParams.getSoReuseaddr(params));
- sslsock.bind(localAddress);
- }
-
- setHostName(sslsock, remoteAddress.getHostName());
- int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
- int soTimeout = HttpConnectionParams.getSoTimeout(params);
-
- try {
- sslsock.connect(remoteAddress, connTimeout);
- } catch (SocketTimeoutException ex) {
- throw new ConnectTimeoutException("Connect to " + remoteAddress.getHostName() + "/"
- + remoteAddress.getAddress() + " timed out");
- }
- sslsock.setSoTimeout(soTimeout);
- if (this.hostnameVerifier != null) {
- try {
- this.hostnameVerifier.verify(remoteAddress.getHostName(), sslsock);
- // verifyHostName() didn't blowup - good!
- } catch (IOException iox) {
- // close the socket before re-throwing the exception
- try { sslsock.close(); } catch (Exception x) { /*ignore*/ }
- throw iox;
- }
- }
- return sslsock;
- }
-
-
- /**
- * Checks whether a socket connection is secure.
- * This factory creates TLS/SSL socket connections
- * which, by default, are considered secure.
- * <br/>
- * Derived classes may override this method to perform
- * runtime checks, for example based on the cypher suite.
- *
- * @param sock the connected socket
- *
- * @return <code>true</code>
- *
- * @throws IllegalArgumentException if the argument is invalid
- */
- public boolean isSecure(final Socket sock) throws IllegalArgumentException {
- if (sock == null) {
- throw new IllegalArgumentException("Socket may not be null");
- }
- // This instanceof check is in line with createSocket() above.
- if (!(sock instanceof SSLSocket)) {
- throw new IllegalArgumentException("Socket not created by this factory");
- }
- // This check is performed last since it calls the argument object.
- if (sock.isClosed()) {
- throw new IllegalArgumentException("Socket is closed");
- }
- return true;
- }
-
- /**
- * @since 4.1
- */
- public Socket createLayeredSocket(
- final Socket socket,
- final String host,
- final int port,
- final boolean autoClose) throws IOException, UnknownHostException {
- SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(
- socket,
- host,
- port,
- autoClose
- );
- sslSocket.setEnabledProtocols(getProtocols(sslSocket));
- sslSocket.setEnabledCipherSuites(getCiphers(sslSocket));
- if (this.hostnameVerifier != null) {
- this.hostnameVerifier.verify(host, sslSocket);
- }
- // verifyHostName() didn't blowup - good!
- return sslSocket;
- }
-
- @Deprecated
- public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) {
- if ( hostnameVerifier == null ) {
- throw new IllegalArgumentException("Hostname verifier may not be null");
- }
- this.hostnameVerifier = hostnameVerifier;
- }
-
- public X509HostnameVerifier getHostnameVerifier() {
- return this.hostnameVerifier;
- }
-
- /**
- * @deprecated Use {@link #connectSocket(Socket, InetSocketAddress, InetSocketAddress, HttpParams)}
- */
- @Deprecated
- public Socket connectSocket(
- final Socket socket,
- final String host, int port,
- final InetAddress localAddress, int localPort,
- final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
- InetSocketAddress local = null;
- if (localAddress != null || localPort > 0) {
- // we need to bind explicitly
- if (localPort < 0) {
- localPort = 0; // indicates "any"
- }
- local = new InetSocketAddress(localAddress, localPort);
- }
- InetAddress remoteAddress;
- if (this.nameResolver != null) {
- remoteAddress = this.nameResolver.resolve(host);
- } else {
- remoteAddress = InetAddress.getByName(host);
- }
- InetSocketAddress remote = new InetSocketAddress(remoteAddress, port);
- return connectSocket(socket, remote, local, params);
- }
-
- /**
- * @deprecated Use {@link #createLayeredSocket(Socket, String, int, boolean)}
- */
- @Deprecated
- public Socket createSocket(
- final Socket socket,
- final String host, int port,
- boolean autoClose) throws IOException, UnknownHostException {
- SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(socket, host, port, autoClose);
- sslSocket.setEnabledProtocols(getProtocols(sslSocket));
- sslSocket.setEnabledCipherSuites(getCiphers(sslSocket));
- setHostName(sslSocket, host);
- return sslSocket;
- }
-
- private void setHostName(SSLSocket sslsock, String hostname){
- try {
- java.lang.reflect.Method setHostnameMethod = sslsock.getClass().getMethod("setHostname", String.class);
- setHostnameMethod.invoke(sslsock, hostname);
- } catch (Exception e) {
- Log.w(TAG, "SNI not useable", e);
- }
- }
-
- private String[] getProtocols(SSLSocket sslSocket) {
- String[] protocols = sslSocket.getEnabledProtocols();
-
- // Remove SSLv3 if it is not the only option
- if(protocols.length > 1) {
- List<String> protocolList = new ArrayList(Arrays.asList(protocols));
- protocolList.remove("SSLv3");
- protocols = protocolList.toArray(new String[protocolList.size()]);
- }
-
- return protocols;
- }
-
- private String[] getCiphers(SSLSocket sslSocket) {
- String[] ciphers = sslSocket.getEnabledCipherSuites();
-
- List<String> enabledCiphers = new ArrayList(Arrays.asList(ciphers));
- // On Android 5.0 release, Jetty doesn't seem to play nice with these ciphers
- // Issue seems to have been fixed in M, and now won't work without them. Because Google
- if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
- enabledCiphers.remove("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");
- enabledCiphers.remove("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA");
- }
-
- ciphers = enabledCiphers.toArray(new String[enabledCiphers.size()]);
- return ciphers;
- }
-}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustManagerDecorator.java b/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustManagerDecorator.java
deleted file mode 100644
index f2364368..00000000
--- a/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustManagerDecorator.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package github.daneren2005.dsub.service.ssl;
-
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-
-import javax.net.ssl.X509TrustManager;
-
-
-/**
- * @since 4.1
- */
-class TrustManagerDecorator implements X509TrustManager {
-
- private final X509TrustManager trustManager;
- private final TrustStrategy trustStrategy;
-
- TrustManagerDecorator(final X509TrustManager trustManager, final TrustStrategy trustStrategy) {
- super();
- this.trustManager = trustManager;
- this.trustStrategy = trustStrategy;
- }
-
- public void checkClientTrusted(
- final X509Certificate[] chain, final String authType) throws CertificateException {
- this.trustManager.checkClientTrusted(chain, authType);
- }
-
- public void checkServerTrusted(
- final X509Certificate[] chain, final String authType) throws CertificateException {
- if (!this.trustStrategy.isTrusted(chain, authType)) {
- this.trustManager.checkServerTrusted(chain, authType);
- }
- }
-
- public X509Certificate[] getAcceptedIssuers() {
- return this.trustManager.getAcceptedIssuers();
- }
-
-}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustSelfSignedStrategy.java b/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustSelfSignedStrategy.java
deleted file mode 100644
index 637a8931..00000000
--- a/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustSelfSignedStrategy.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package github.daneren2005.dsub.service.ssl;
-
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-
-/**
- * A trust strategy that accepts self-signed certificates as trusted. Verification of all other
- * certificates is done by the trust manager configured in the SSL context.
- *
- * @since 4.1
- */
-public class TrustSelfSignedStrategy implements TrustStrategy {
-
- public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
- return true;
- }
-
-}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustStrategy.java b/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustStrategy.java
deleted file mode 100644
index 334a97c5..00000000
--- a/app/src/main/java/github/daneren2005/dsub/service/ssl/TrustStrategy.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package github.daneren2005.dsub.service.ssl;
-
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-
-/**
- * A strategy to establish trustworthiness of certificates without consulting the trust manager
- * configured in the actual SSL context. This interface can be used to override the standard
- * JSSE certificate verification process.
- *
- * @since 4.1
- */
-public interface TrustStrategy {
-
- /**
- * Determines whether the certificate chain can be trusted without consulting the trust manager
- * configured in the actual SSL context. This method can be used to override the standard JSSE
- * certificate verification process.
- * <p>
- * Please note that, if this method returns <code>false</code>, the trust manager configured
- * in the actual SSL context can still clear the certificate as trusted.
- *
- * @param chain the peer certificate chain
- * @param authType the authentication type based on the client certificate
- * @return <code>true</code> if the certificate can be trusted without verification by
- * the trust manager, <code>false</code> otherwise.
- * @throws CertificateException thrown if the certificate is not trusted or invalid.
- */
- boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException;
-
-}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/sync/MostRecentSyncAdapter.java b/app/src/main/java/github/daneren2005/dsub/service/sync/MostRecentSyncAdapter.java
index 8da83be1..bcb7b92f 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/sync/MostRecentSyncAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/sync/MostRecentSyncAdapter.java
@@ -49,7 +49,7 @@ public class MostRecentSyncAdapter extends SubsonicSyncAdapter {
}
@Override
- public void onExecuteSync(Context context, int instance) {
+ public void onExecuteSync(Context context, int instance) throws NetworkNotValidException {
try {
ArrayList<String> syncedList = SyncUtil.getSyncedMostRecent(context, instance);
MusicDirectory albumList = musicService.getAlbumList("newest", 20, 0, tagBrowsing, context, null);
diff --git a/app/src/main/java/github/daneren2005/dsub/service/sync/PlaylistSyncAdapter.java b/app/src/main/java/github/daneren2005/dsub/service/sync/PlaylistSyncAdapter.java
index a0996628..cb3c3877 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/sync/PlaylistSyncAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/sync/PlaylistSyncAdapter.java
@@ -56,7 +56,7 @@ public class PlaylistSyncAdapter extends SubsonicSyncAdapter {
}
@Override
- public void onExecuteSync(Context context, int instance) {
+ public void onExecuteSync(Context context, int instance) throws NetworkNotValidException {
String serverName = Util.getServerName(context, instance);
List<Playlist> remainder = null;
@@ -69,6 +69,7 @@ public class PlaylistSyncAdapter extends SubsonicSyncAdapter {
ArrayList<SyncSet> playlistList = SyncUtil.getSyncedPlaylists(context, instance);
List<String> updated = new ArrayList<String>();
+ String updatedId = null;
boolean removed = false;
for(int i = 0; i < playlistList.size(); i++) {
SyncSet cachedPlaylist = playlistList.get(i);
@@ -94,9 +95,13 @@ public class PlaylistSyncAdapter extends SubsonicSyncAdapter {
DownloadFile file = new DownloadFile(context, entry, true);
String path = file.getCompleteFile().getPath();
while(!file.isSaved() && !file.isFailedMax()) {
+ throwIfNetworkInvalid();
file.downloadNow(musicService);
if(file.isSaved() && !updated.contains(playlist.getName())) {
updated.add(playlist.getName());
+ if(updatedId == null) {
+ updatedId = playlist.getId();
+ }
}
}
@@ -147,7 +152,7 @@ public class PlaylistSyncAdapter extends SubsonicSyncAdapter {
}
if(updated.size() > 0) {
- Notifications.showSyncNotification(context, R.string.sync_new_playlists, SyncUtil.joinNames(updated));
+ Notifications.showSyncNotification(context, R.string.sync_new_playlists, SyncUtil.joinNames(updated), updatedId);
}
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/service/sync/PodcastSyncAdapter.java b/app/src/main/java/github/daneren2005/dsub/service/sync/PodcastSyncAdapter.java
index 985a7267..7afcad25 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/sync/PodcastSyncAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/sync/PodcastSyncAdapter.java
@@ -23,7 +23,6 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.util.Log;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
@@ -54,7 +53,7 @@ public class PodcastSyncAdapter extends SubsonicSyncAdapter {
}
@Override
- public void onExecuteSync(Context context, int instance) {
+ public void onExecuteSync(Context context, int instance) throws NetworkNotValidException {
ArrayList<SyncSet> podcastList = SyncUtil.getSyncedPodcasts(context, instance);
try {
@@ -68,6 +67,7 @@ public class PodcastSyncAdapter extends SubsonicSyncAdapter {
}
List<String> updated = new ArrayList<String>();
+ String updatedId = null;
for(int i = 0; i < podcastList.size(); i++) {
SyncSet set = podcastList.get(i);
String id = set.id;
@@ -80,6 +80,7 @@ public class PodcastSyncAdapter extends SubsonicSyncAdapter {
if(entry.getId() != null && "completed".equals(((PodcastEpisode)entry).getStatus()) && !existingEpisodes.contains(entry.getId())) {
DownloadFile file = new DownloadFile(context, entry, false);
while(!file.isCompleteFileAvailable() && !file.isFailedMax()) {
+ throwIfNetworkInvalid();
file.downloadNow(musicService);
}
// Only add if actualy downloaded correctly
@@ -87,6 +88,9 @@ public class PodcastSyncAdapter extends SubsonicSyncAdapter {
existingEpisodes.add(entry.getId());
if(!updated.contains(podcasts.getName())) {
updated.add(podcasts.getName());
+ if(updatedId == null) {
+ updatedId = podcasts.getId();
+ }
}
}
}
@@ -104,7 +108,7 @@ public class PodcastSyncAdapter extends SubsonicSyncAdapter {
// Make sure there are is at least one change before re-syncing
if(updated.size() > 0) {
FileUtil.serialize(context, podcastList, SyncUtil.getPodcastSyncFile(context, instance));
- Notifications.showSyncNotification(context, R.string.sync_new_podcasts, SyncUtil.joinNames(updated));
+ Notifications.showSyncNotification(context, R.string.sync_new_podcasts, SyncUtil.joinNames(updated), updatedId);
}
} catch(Exception e) {
Log.w(TAG, "Failed to get podcasts for " + Util.getServerName(context, instance));
diff --git a/app/src/main/java/github/daneren2005/dsub/service/sync/StarredSyncAdapter.java b/app/src/main/java/github/daneren2005/dsub/service/sync/StarredSyncAdapter.java
index cf985227..0af8886b 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/sync/StarredSyncAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/sync/StarredSyncAdapter.java
@@ -20,7 +20,6 @@
package github.daneren2005.dsub.service.sync;
import android.annotation.TargetApi;
-import android.app.Notification;
import android.content.Context;
import android.util.Log;
@@ -50,7 +49,7 @@ public class StarredSyncAdapter extends SubsonicSyncAdapter {
}
@Override
- public void onExecuteSync(Context context, int instance) {
+ public void onExecuteSync(Context context, int instance) throws NetworkNotValidException {
try {
ArrayList<String> syncedList = new ArrayList<String>();
MusicDirectory starredList = musicService.getStarredList(context, null);
diff --git a/app/src/main/java/github/daneren2005/dsub/service/sync/SubsonicSyncAdapter.java b/app/src/main/java/github/daneren2005/dsub/service/sync/SubsonicSyncAdapter.java
index 661f126d..4879d032 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/sync/SubsonicSyncAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/sync/SubsonicSyncAdapter.java
@@ -65,39 +65,54 @@ public class SubsonicSyncAdapter extends AbstractThreadedSyncAdapter {
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
- ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = manager.getActiveNetworkInfo();
-
- // Don't try to sync if no network!
- if(networkInfo == null || !networkInfo.isConnected() || Util.isOffline(context)) {
- Log.w(TAG, "Not running sync, not connected to network");
+ String invalidMessage = isNetworkValid();
+ if(invalidMessage != null) {
+ Log.w(TAG, "Not running sync: " + invalidMessage);
return;
}
-
+
// Make sure battery > x% or is charging
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, intentFilter);
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
- if(status != BatteryManager.BATTERY_STATUS_CHARGING && status != BatteryManager.BATTERY_STATUS_FULL) {
+ if (status != BatteryManager.BATTERY_STATUS_CHARGING && status != BatteryManager.BATTERY_STATUS_FULL) {
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
-
- if((level / (float)scale) < 0.15) {
+
+ if ((level / (float) scale) < 0.15) {
Log.w(TAG, "Not running sync, battery too low");
return;
}
}
+ executeSync(context);
+ }
+
+ private String isNetworkValid() {
+ ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = manager.getActiveNetworkInfo();
+
+ // Don't try to sync if no network!
+ if(networkInfo == null || !networkInfo.isConnected() || Util.isOffline(context)) {
+ return "Not connected to any network";
+ }
+
// Check if user wants to only sync on wifi
SharedPreferences prefs = Util.getPreferences(context);
if(prefs.getBoolean(Constants.PREFERENCES_KEY_SYNC_WIFI, true)) {
if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
- executeSync(context);
+ return null;
} else {
- Log.w(TAG, "Not running sync, not connected to wifi");
+ return "Not connected to WIFI";
}
} else {
- executeSync(context);
+ return null;
+ }
+ }
+ protected void throwIfNetworkInvalid() throws NetworkNotValidException {
+ String invalidMessage = isNetworkValid();
+ if(invalidMessage != null) {
+ throw new NetworkNotValidException(invalidMessage);
}
}
@@ -106,32 +121,39 @@ public class SubsonicSyncAdapter extends AbstractThreadedSyncAdapter {
Log.i(TAG, "Running sync for " + className);
long start = System.currentTimeMillis();
int servers = Util.getServerCount(context);
- for(int i = 1; i <= servers; i++) {
- try {
- if(isValidServer(context, i) && Util.isSyncEnabled(context, i)) {
- tagBrowsing = Util.isTagBrowsing(context, i);
- musicService.setInstance(i);
- onExecuteSync(context, i);
- } else {
- Log.i(TAG, "Skipped sync for " + i);
+ try {
+ for (int i = 1; i <= servers; i++) {
+ try {
+ throwIfNetworkInvalid();
+
+ if (isValidServer(context, i) && Util.isSyncEnabled(context, i)) {
+ tagBrowsing = Util.isTagBrowsing(context, i);
+ musicService.setInstance(i);
+ onExecuteSync(context, i);
+ } else {
+ Log.i(TAG, "Skipped sync for " + i);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed sync for " + className + "(" + i + ")", e);
}
- } catch(Exception e) {
- Log.e(TAG, "Failed sync for " + className + "(" + i + ")", e);
}
+ } catch (NetworkNotValidException e) {
+ Log.e(TAG, "Stopped sync due to network loss", e);
}
Log.i(TAG, className + " executed in " + (System.currentTimeMillis() - start) + " ms");
}
- public void onExecuteSync(Context context, int instance) {
+ public void onExecuteSync(Context context, int instance) throws NetworkNotValidException {
}
- protected boolean downloadRecursively(List<String> paths, MusicDirectory parent, Context context, boolean save) throws Exception {
+ protected boolean downloadRecursively(List<String> paths, MusicDirectory parent, Context context, boolean save) throws Exception,NetworkNotValidException {
boolean downloaded = false;
for (MusicDirectory.Entry song: parent.getChildren(false, true)) {
if (!song.isVideo()) {
DownloadFile file = new DownloadFile(context, song, save);
while(!(save && file.isSaved() || !save && file.isCompleteFileAvailable()) && !file.isFailedMax()) {
+ throwIfNetworkInvalid();
file.downloadNow(musicService);
if(!file.isFailed()) {
downloaded = true;
@@ -171,4 +193,10 @@ public class SubsonicSyncAdapter extends AbstractThreadedSyncAdapter {
String url = Util.getRestUrl(context, "null", instance, false);
return !(url.contains("demo.subsonic.org") || url.contains("yourhost"));
}
+
+ public class NetworkNotValidException extends Throwable {
+ public NetworkNotValidException(String reason) {
+ super("Not running sync: " + reason);
+ }
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/updates/Updater.java b/app/src/main/java/github/daneren2005/dsub/updates/Updater.java
index a2870941..2dabb624 100644
--- a/app/src/main/java/github/daneren2005/dsub/updates/Updater.java
+++ b/app/src/main/java/github/daneren2005/dsub/updates/Updater.java
@@ -37,13 +37,18 @@ public class Updater {
protected Context context;
public Updater(int version) {
+ // 5.2 should show as 520 instead of 52
+ if(version < 100) {
+ version *= 10;
+ }
this.version = version;
}
public void checkUpdates(Context context) {
this.context = context;
List<Updater> updaters = new ArrayList<Updater>();
- updaters.add(new Updater403());
+ updaters.add(new UpdaterSongPress());
+ updaters.add(new UpdaterNoDLNA());
SharedPreferences prefs = Util.getPreferences(context);
int lastVersion = prefs.getInt(Constants.LAST_VERSION, 0);
diff --git a/app/src/main/java/github/daneren2005/dsub/updates/Updater403.java b/app/src/main/java/github/daneren2005/dsub/updates/Updater403.java
deleted file mode 100644
index 4f2cbf43..00000000
--- a/app/src/main/java/github/daneren2005/dsub/updates/Updater403.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- This file is part of Subsonic.
-
- Subsonic is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Subsonic is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
-
- Copyright 2009 (C) Sindre Mehus
- */
-package github.daneren2005.dsub.updates;
-
-import android.content.Context;
-import android.util.Log;
-import github.daneren2005.dsub.updates.Updater;
-import github.daneren2005.dsub.util.Constants;
-import github.daneren2005.dsub.util.FileUtil;
-import java.io.File;
-
-/**
- *
- * @author Scott
- */
-public class Updater403 extends Updater {
- public Updater403() {
- super(403);
- TAG = Updater403.class.getSimpleName();
- }
-
- @Override
- public void update(Context context) {
- // Rename cover.jpeg to cover.jpg
- Log.i(TAG, "Running Updater403: updating cover.jpg to albumart.jpg");
- File dir = FileUtil.getMusicDirectory(context);
- if(dir != null) {
- moveArt(dir);
- }
- }
-
- private void moveArt(File dir) {
- for(File file: dir.listFiles()) {
- if(file.isDirectory()) {
- moveArt(file);
- } else if("cover.jpg".equals(file.getName()) || "cover.jpeg".equals(file.getName())) {
- File renamed = new File(dir, Constants.ALBUM_ART_FILE);
- file.renameTo(renamed);
- }
- }
- }
-}
diff --git a/app/src/main/java/github/daneren2005/dsub/updates/UpdaterNoDLNA.java b/app/src/main/java/github/daneren2005/dsub/updates/UpdaterNoDLNA.java
new file mode 100644
index 00000000..a060c4fd
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/updates/UpdaterNoDLNA.java
@@ -0,0 +1,41 @@
+/*
+ This file is part of Subsonic.
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+ Copyright 2016 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.updates;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build;
+
+import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.Util;
+
+public class UpdaterNoDLNA extends Updater {
+ public UpdaterNoDLNA() {
+ super(534);
+ TAG = this.getClass().getSimpleName();
+ }
+
+ @Override
+ public void update(Context context) {
+ SharedPreferences prefs = Util.getPreferences(context);
+
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(Constants.PREFERENCES_KEY_DLNA_CASTING_ENABLED, false);
+ editor.commit();
+ }
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/updates/UpdaterSongPress.java b/app/src/main/java/github/daneren2005/dsub/updates/UpdaterSongPress.java
new file mode 100644
index 00000000..7efa18e4
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/updates/UpdaterSongPress.java
@@ -0,0 +1,42 @@
+/*
+ This file is part of Subsonic.
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+ Copyright 2016 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.updates;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.Util;
+
+public class UpdaterSongPress extends Updater {
+ public UpdaterSongPress() {
+ super(521);
+ TAG = this.getClass().getSimpleName();
+ }
+
+ @Override
+ public void update(Context context) {
+ SharedPreferences prefs = Util.getPreferences(context);
+ boolean playNowAfter = prefs.getBoolean("playNowAfter", true);
+
+ // Migrate the old preference so behavior stays the same
+ if(playNowAfter == false) {
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(Constants.PREFERENCES_KEY_SONG_PRESS_ACTION, "single");
+ editor.commit();
+ }
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/ArtistRadioBuffer.java b/app/src/main/java/github/daneren2005/dsub/util/ArtistRadioBuffer.java
index 2af468f6..bdd961b4 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/ArtistRadioBuffer.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/ArtistRadioBuffer.java
@@ -66,7 +66,6 @@ public class ArtistRadioBuffer {
buffer.clear();
}
- context.clear();
this.artistId = artistId;
awaitingResults = true;
refill();
@@ -108,7 +107,7 @@ public class ArtistRadioBuffer {
}
private void refill() {
- if (buffer != null && (buffer.size() > refillThreshold || (!Util.isNetworkConnected(context) && !Util.isOffline(context)) || lastCount == 0)) {
+ if (buffer != null && executorService != null && (buffer.size() > refillThreshold || (!Util.isNetworkConnected(context) && !Util.isOffline(context)) || lastCount == 0)) {
executorService.shutdown();
return;
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java b/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java
index 18f245d5..2b0c6279 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java
@@ -27,6 +27,7 @@ import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
import org.xmlpull.v1.XmlPullParserException;
@@ -54,6 +55,7 @@ public abstract class BackgroundTask<T> implements ProgressListener {
private static final Collection<Thread> threads = Collections.synchronizedCollection(new ArrayList<Thread>());
protected static final BlockingQueue<BackgroundTask.Task> queue = new LinkedBlockingQueue<BackgroundTask.Task>(10);
private static Handler handler = null;
+ private static AtomicInteger currentlyRunning = new AtomicInteger(0);
static {
try {
handler = new Handler(Looper.getMainLooper());
@@ -71,6 +73,11 @@ public abstract class BackgroundTask<T> implements ProgressListener {
threads.add(thread);
thread.start();
}
+ } else if(currentlyRunning.get() >= threads.size()) {
+ Log.w(TAG, "Emergency add new thread: " + (threads.size() + 1));
+ Thread thread = new Thread(new TaskRunnable(), String.format("BackgroundTask_%d", threads.size()));
+ threads.add(thread);
+ thread.start();
}
if(handler == null) {
try {
@@ -176,7 +183,7 @@ public abstract class BackgroundTask<T> implements ProgressListener {
}
@Override
- public void updateCache() {
+ public void updateCache(int changeCode) {
}
@@ -208,8 +215,18 @@ public abstract class BackgroundTask<T> implements ProgressListener {
handler.post(new Runnable() {
@Override
public void run() {
- if(!isCancelled()) {
- onDone(result);
+ if (!isCancelled()) {
+ try {
+ onDone(result);
+ } catch (Throwable t) {
+ if(!isCancelled()) {
+ try {
+ onError(t);
+ } catch(Exception e) {
+ // Don't care
+ }
+ }
+ }
}
taskStart.set(false);
@@ -294,22 +311,30 @@ public abstract class BackgroundTask<T> implements ProgressListener {
@Override
public void run() {
Looper.prepare();
+ final Thread currentThread = Thread.currentThread();
while(running) {
try {
Task task = queue.take();
+ currentlyRunning.incrementAndGet();
task.execute();
} catch(InterruptedException stop) {
Log.e(TAG, "Thread died");
running = false;
- threads.remove(Thread.currentThread());
} catch(Throwable t) {
Log.e(TAG, "Unexpected crash in BackgroundTask thread", t);
+ running = false;
}
+
+ currentlyRunning.decrementAndGet();
+ }
+
+ if(threads.contains(currentThread)) {
+ threads.remove(currentThread);
}
}
}
- public static interface OnCancelListener {
+ public interface OnCancelListener {
void onCancel();
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/Constants.java b/app/src/main/java/github/daneren2005/dsub/util/Constants.java
index 89e7de3b..21adce8c 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/Constants.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/Constants.java
@@ -67,6 +67,7 @@ public final class Constants {
public static final String INTENT_EXTRA_TOP_TRACKS = "topTracks";
public static final String INTENT_EXTRA_SHOW_ALL = "showAll";
public static final String INTENT_EXTRA_PLAY_LAST = "playLast";
+ public static final String INTENT_EXTRA_ENTRY = "passedEntry";
// Preferences keys.
public static final String PREFERENCES_KEY_SERVER_KEY = "server";
@@ -133,6 +134,7 @@ public final class Constants {
public static final String PREFERENCES_KEY_HIDE_WIDGET = "hideWidget";
public static final String PREFERENCES_KEY_PODCASTS_ENABLED = "podcastsEnabled";
public static final String PREFERENCES_KEY_BOOKMARKS_ENABLED = "bookmarksEnabled";
+ public static final String PREFERENCES_KEY_INTERNET_RADIO_ENABLED = "internetRadioEnabled";
public static final String PREFERENCES_KEY_CUSTOM_SORT_ENABLED = "customSortEnabled";
public static final String PREFERENCES_KEY_MENU_PLAY_NOW = "showPlayNow";
public static final String PREFERENCES_KEY_MENU_PLAY_SHUFFLED = "showPlayShuffled";
@@ -147,7 +149,8 @@ public final class Constants {
public static final String PREFERENCES_KEY_BROWSE_TAGS = "browseTags";
public static final String PREFERENCES_KEY_OPEN_TO_TAB = "openToTab";
public static final String PREFERENCES_KEY_OVERRIDE_SYSTEM_LANGUAGE = "overrideSystemLanguage";
- public static final String PREFERENCES_KEY_PLAY_NOW_AFTER = "playNowAfter";
+ // public static final String PREFERENCES_KEY_PLAY_NOW_AFTER = "playNowAfter";
+ public static final String PREFERENCES_KEY_SONG_PRESS_ACTION = "songPressAction";
public static final String PREFERENCES_KEY_LARGE_ALBUM_ART = "largeAlbumArt";
public static final String PREFERENCES_KEY_ADMIN_ENABLED = "adminEnabled";
public static final String PREFERENCES_KEY_PLAYLIST_NAME = "suggestedPlaylistName";
@@ -168,6 +171,14 @@ public final class Constants {
public static final String PREFERENCES_KEY_COLOR_ACTION_BAR = "colorActionBar";
public static final String PREFERENCES_KEY_SHUFFLE_BY_ALBUM = "shuffleByAlbum";
public static final String PREFERENCES_KEY_RESUME_PLAY_QUEUE_NEVER = "neverResumePlayQueue";
+ public static final String PREFERENCES_KEY_BATCH_MODE = "batchMode";
+ public static final String PREFERENCES_KEY_CAST_GAPLESS_PLAYBACK = "castingGaplessPlayback";
+ public static final String PREFERENCES_KEY_CAST_STREAM_ORIGINAL = "castStreamOriginal";
+ public static final String PREFERENCES_KEY_HEADS_UP_NOTIFICATION = "headsUpNotification";
+ public static final String PREFERENCES_KEY_CAST_CACHE = "castCache";
+ public static final String PREFERENCES_KEY_PLAYBACK_SPEED = "playbackSpeed";
+ public static final String PREFERENCES_KEY_SONG_PLAYBACK_SPEED = "songPlaybackSpeed";
+ public static final String PREFERENCES_KEY_DLNA_CASTING_ENABLED = "dlnaCastingEnabled";
public static final String OFFLINE_SCROBBLE_COUNT = "scrobbleCount";
public static final String OFFLINE_SCROBBLE_ID = "scrobbleID";
@@ -180,10 +191,13 @@ public final class Constants {
public static final String CACHE_KEY_IGNORE = "ignoreArticles";
public static final String CACHE_AUDIO_SESSION_ID = "audioSessionId";
+ public static final String CACHE_AUDIO_SESSION_VERSION_CODE = "audioSessionVersionCode";
+ public static final String CACHE_BLOCK_TOKEN_USE = "blockTokenUse";
public static final String MAIN_BACK_STACK = "backStackIds";
public static final String MAIN_BACK_STACK_SIZE = "backStackIdsSize";
public static final String MAIN_NOW_PLAYING = "nowPlayingId";
+ public static final String MAIN_NOW_PLAYING_SECONDARY = "nowPlayingSecondaryId";
public static final String MAIN_SLIDE_PANEL_STATE = "slidePanelState";
public static final String FRAGMENT_LIST = "fragmentList";
public static final String FRAGMENT_LIST2 = "fragmentList2";
diff --git a/app/src/main/java/github/daneren2005/dsub/util/DrawableTint.java b/app/src/main/java/github/daneren2005/dsub/util/DrawableTint.java
index 2da72579..f03906a8 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/DrawableTint.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/DrawableTint.java
@@ -21,6 +21,7 @@ import android.content.res.TypedArray;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.support.annotation.AttrRes;
+import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.util.TypedValue;
@@ -48,6 +49,17 @@ public class DrawableTint {
tintedDrawables.put(drawableRes, background);
return background;
}
+ public static Drawable getTintedDrawableFromColor(Context context, @DrawableRes int drawableRes, @ColorRes int colorRes) {
+ if(tintedDrawables.containsKey(drawableRes)) {
+ return tintedDrawables.get(drawableRes);
+ }
+
+ int color = context.getResources().getColor(colorRes);
+ Drawable background = context.getResources().getDrawable(drawableRes);
+ background.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+ tintedDrawables.put(drawableRes, background);
+ return background;
+ }
public static int getColorRes(Context context, @AttrRes int colorAttr) {
int color;
if(attrMap.containsKey(colorAttr)) {
@@ -83,7 +95,7 @@ public class DrawableTint {
return getTintedDrawable(context, drawableRes, colorAttr);
}
- public static void wipeTintCache() {
+ public static void clearCache() {
attrMap.clear();
tintedDrawables.clear();
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/DroppySpeedControl.java b/app/src/main/java/github/daneren2005/dsub/util/DroppySpeedControl.java
new file mode 100644
index 00000000..8f58e60e
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/util/DroppySpeedControl.java
@@ -0,0 +1,82 @@
+package github.daneren2005.dsub.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import com.shehabic.droppy.DroppyClickCallbackInterface;
+import com.shehabic.droppy.DroppyMenuCustomItem;
+
+/**
+ * Created by marcus on 2/14/2017.
+ */
+public class DroppySpeedControl extends DroppyMenuCustomItem {
+
+ private Context context;
+ private SeekBar seekBar;
+ private DroppyClickCallbackInterface updateBarCallback;
+ public DroppySpeedControl(int customResourceId) {
+ super(customResourceId);
+
+ }
+
+ @Override
+ public View render(Context context) {
+ return super.render(context);
+
+
+ }
+
+ public DroppySpeedControl setOnClicks(Context context, final DroppyClickCallbackInterface callback, int ... elementsByID){
+ render(context);
+ View.OnClickListener listener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ callback.call(v, v.getId());
+ }
+ };
+ for (Integer element : elementsByID) {
+ renderedView.findViewById(element).setOnClickListener(listener);
+ }
+ return this;
+ }
+
+
+ public void updateSeekBar(float playbackSpeed){
+ TextView tv = (TextView)seekBar.getTag();
+ tv.setText(Float.toString(playbackSpeed));
+ seekBar.setProgress((int)(playbackSpeed*10)-5);
+ }
+
+ public DroppySpeedControl setOnSeekBarChangeListener(Context context, final DroppyClickCallbackInterface callback, int seekBarByID, int textViewByID, float playbackSpeed) {
+ updateBarCallback = callback;
+ render(context);
+ final TextView textBox = (TextView) renderedView.findViewById(textViewByID);
+ textBox.setText(Float.toString(playbackSpeed));
+ SeekBar seekBar = ((SeekBar) renderedView.findViewById(seekBarByID));
+ this.seekBar = seekBar;
+ seekBar.setTag(textBox);
+ seekBar.setProgress((int)(playbackSpeed*10)-5);
+ seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+ textBox.setText(new Float((progress + 5) / 10.0).toString());
+ seekBar.setProgress(progress);
+ callback.call(seekBar,seekBar.getId());
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+ });
+ seekBar.setProgress((int)((playbackSpeed/10.0) - 5));
+ return this;
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java b/app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java
new file mode 100644
index 00000000..710d5232
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java
@@ -0,0 +1,21 @@
+/*
+ This file is part of Subsonic.
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+ Copyright 2016 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.util;
+
+public final class EnvironmentVariables {
+ public static final String PASTEBIN_DEV_KEY = "";
+ public static final String CAST_APPLICATION_ID = "";
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java b/app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java
index 85844360..2321e69e 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java
@@ -43,6 +43,7 @@ import java.lang.ref.WeakReference;
import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.ArtistInfo;
+import github.daneren2005.dsub.domain.InternetRadioStation;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.Playlist;
import github.daneren2005.dsub.domain.PodcastChannel;
@@ -221,8 +222,11 @@ public class ImageLoader {
return loadImage(view, entry, large, size, crossfade);
}
public SilentBackgroundTask loadImage(View view, MusicDirectory.Entry entry, boolean large, int size, boolean crossfade) {
- // TODO: If we know this a artist, try to load artist info instead
- if(entry != null && !entry.isAlbum() && ServerInfo.checkServerVersion(context, "1.11") && !Util.isOffline(context)) {
+ if(entry != null && entry instanceof InternetRadioStation) {
+ // Continue on and load a null bitmap
+ }
+ // If we know this a artist, try to load artist info instead
+ else if(entry != null && !entry.isAlbum() && ServerInfo.checkServerVersion(context, "1.11") && !Util.isOffline(context)) {
SilentBackgroundTask task = new ArtistImageTask(view.getContext(), entry, size, imageSizeLarge, large, view, crossfade);
task.execute();
return task;
@@ -524,44 +528,49 @@ public class ImageLoader {
@Override
protected Void doInBackground() throws Throwable {
- MusicService musicService = MusicServiceFactory.getMusicService(mContext);
- ArtistInfo artistInfo = musicService.getArtistInfo(mEntry.getId(), false, true, mContext, null);
- String url = artistInfo.getImageUrl();
-
- // Figure out whether we are going to get a artist image or the standard image
- if(url != null && !"".equals(url.trim())) {
- // If getting the artist image fails for any reason, retry for the standard version
- subTask = new ViewUrlTask(mContext, mView, url, mSize) {
- @Override
- protected void failedToDownload() {
- // Call loadImage so we can take advantage of all of it's logic checks
- loadImage(mView, mEntry, mSize == imageSizeLarge, mCrossfade);
-
- // Delete subTask so it doesn't get called in done
- subTask = null;
+ try {
+ MusicService musicService = MusicServiceFactory.getMusicService(mContext);
+ ArtistInfo artistInfo = musicService.getArtistInfo(mEntry.getId(), false, true, mContext, null);
+ String url = artistInfo.getImageUrl();
+
+ // Figure out whether we are going to get a artist image or the standard image
+ if (url != null && !"".equals(url.trim())) {
+ // If getting the artist image fails for any reason, retry for the standard version
+ subTask = new ViewUrlTask(mContext, mView, url, mSize) {
+ @Override
+ protected void failedToDownload() {
+ // Call loadImage so we can take advantage of all of it's logic checks
+ loadImage(mView, mEntry, mSize == imageSizeLarge, mCrossfade);
+
+ // Delete subTask so it doesn't get called in done
+ subTask = null;
+ }
+ };
+ } else {
+ if (mEntry != null && mEntry.getCoverArt() == null && mEntry.isDirectory() && !Util.isOffline(context)) {
+ // Try to lookup child cover art
+ MusicDirectory.Entry firstChild = FileUtil.lookupChild(context, mEntry, true);
+ if (firstChild != null) {
+ mEntry.setCoverArt(firstChild.getCoverArt());
+ }
}
- };
- } else {
- if (mEntry != null && mEntry.getCoverArt() == null && mEntry.isDirectory() && !Util.isOffline(context)) {
- // Try to lookup child cover art
- MusicDirectory.Entry firstChild = FileUtil.lookupChild(context, mEntry, true);
- if (firstChild != null) {
- mEntry.setCoverArt(firstChild.getCoverArt());
+
+ if (mEntry != null && mEntry.getCoverArt() != null) {
+ subTask = new ViewImageTask(mContext, mEntry, mSize, mSaveSize, mIsNowPlaying, mView, mCrossfade);
+ } else {
+ // If entry is null as well, we need to just set as a blank image
+ Bitmap bitmap = getUnknownImage(mEntry, mSize);
+ mDrawable = Util.createDrawableFromBitmap(mContext, bitmap);
+ return null;
}
}
- if (mEntry != null && mEntry.getCoverArt() != null) {
- subTask = new ViewImageTask(mContext, mEntry, mSize, mSaveSize, mIsNowPlaying, mView, mCrossfade);
- } else {
- // If entry is null as well, we need to just set as a blank image
- Bitmap bitmap = getUnknownImage(mEntry, mSize);
- mDrawable = Util.createDrawableFromBitmap(mContext, bitmap);
- return null;
- }
+ // Execute whichever way we decided to go
+ subTask.doInBackground();
+ } catch (Throwable x) {
+ Log.e(TAG, "Failed to get artist info", x);
+ cancelled.set(true);
}
-
- // Execute whichever way we decided to go
- subTask.doInBackground();
return null;
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/MediaRouteManager.java b/app/src/main/java/github/daneren2005/dsub/util/MediaRouteManager.java
index 9aa54c4b..73ec6aec 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/MediaRouteManager.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/MediaRouteManager.java
@@ -48,6 +48,7 @@ public class MediaRouteManager extends MediaRouter.Callback {
private MediaRouteSelector selector;
private List<MediaRouteProvider> providers = new ArrayList<MediaRouteProvider>();
private List<MediaRouteProvider> onlineProviders = new ArrayList<MediaRouteProvider>();
+ private DLNARouteProvider dlnaProvider;
static {
try {
@@ -159,10 +160,8 @@ public class MediaRouteManager extends MediaRouter.Callback {
addOnlineProviders();
}
- if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- DLNARouteProvider dlnaProvider = new DLNARouteProvider(downloadService);
- router.addProvider(dlnaProvider);
- providers.add(dlnaProvider);
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && Util.getPreferences(downloadService).getBoolean(Constants.PREFERENCES_KEY_DLNA_CASTING_ENABLED, true)) {
+ addDLNAProvider();
}
}
public void buildSelector() {
@@ -178,4 +177,20 @@ public class MediaRouteManager extends MediaRouter.Callback {
}
selector = builder.build();
}
+
+ public void addDLNAProvider() {
+ if(dlnaProvider == null) {
+ dlnaProvider = new DLNARouteProvider(downloadService);
+ router.addProvider(dlnaProvider);
+ providers.add(dlnaProvider);
+ }
+ }
+ public void removeDLNAProvider() {
+ if(dlnaProvider != null) {
+ router.removeProvider(dlnaProvider);
+ providers.remove(dlnaProvider);
+ dlnaProvider.destroy();
+ dlnaProvider = null;
+ }
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/Notifications.java b/app/src/main/java/github/daneren2005/dsub/util/Notifications.java
index 375c9966..750ab40c 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/Notifications.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/Notifications.java
@@ -41,6 +41,7 @@ import github.daneren2005.dsub.domain.PlayerState;
import github.daneren2005.dsub.provider.DSubWidgetProvider;
import github.daneren2005.dsub.service.DownloadFile;
import github.daneren2005.dsub.service.DownloadService;
+import github.daneren2005.dsub.view.UpdateView;
public final class Notifications {
private static final String TAG = Notifications.class.getSimpleName();
@@ -66,23 +67,29 @@ public final class Notifications {
notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT;
}
boolean remote = downloadService.isRemoteEnabled();
+ boolean isSingle = downloadService.isCurrentPlayingSingle();
+ boolean shouldFastForward = downloadService.shouldFastForward();
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.JELLY_BEAN){
RemoteViews expandedContentView = new RemoteViews(context.getPackageName(), R.layout.notification_expanded);
- setupViews(expandedContentView ,context, song, true, playing, remote);
+ setupViews(expandedContentView ,context, song, true, playing, remote, isSingle, shouldFastForward);
notification.bigContentView = expandedContentView;
notification.priority = Notification.PRIORITY_HIGH;
}
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notification.visibility = Notification.VISIBILITY_PUBLIC;
+
+ if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_HEADS_UP_NOTIFICATION, false) && !UpdateView.hasActiveActivity()) {
+ notification.vibrate = new long[0];
+ }
}
RemoteViews smallContentView = new RemoteViews(context.getPackageName(), R.layout.notification);
- setupViews(smallContentView, context, song, false, playing, remote);
+ setupViews(smallContentView, context, song, false, playing, remote, isSingle, shouldFastForward);
notification.contentView = smallContentView;
Intent notificationIntent = new Intent(context, SubsonicFragmentActivity.class);
notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true);
- notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
notification.contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
playShowing = true;
@@ -93,21 +100,35 @@ public final class Notifications {
public void run() {
downloadService.stopForeground(true);
showDownloadingNotification(context, downloadService, handler, downloadService.getCurrentDownloading(), downloadService.getBackgroundDownloads().size());
- downloadService.startForeground(NOTIFICATION_ID_PLAYING, notification);
+
+ try {
+ downloadService.startForeground(NOTIFICATION_ID_PLAYING, notification);
+ } catch(Exception e) {
+ Log.e(TAG, "Failed to start notifications after stopping foreground download");
+ }
}
});
} else {
handler.post(new Runnable() {
@Override
public void run() {
- if(playing) {
- downloadService.startForeground(NOTIFICATION_ID_PLAYING, notification);
+ if (playing) {
+ try {
+ downloadService.startForeground(NOTIFICATION_ID_PLAYING, notification);
+ } catch(Exception e) {
+ Log.e(TAG, "Failed to start notifications while playing");
+ }
} else {
playShowing = false;
persistentPlayingShowing = true;
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
downloadService.stopForeground(false);
- notificationManager.notify(NOTIFICATION_ID_PLAYING, notification);
+
+ try {
+ notificationManager.notify(NOTIFICATION_ID_PLAYING, notification);
+ } catch(Exception e) {
+ Log.e(TAG, "Failed to start notifications while paused");
+ }
}
}
});
@@ -117,8 +138,7 @@ public final class Notifications {
DSubWidgetProvider.notifyInstances(context, downloadService, playing);
}
- private static void setupViews(RemoteViews rv, Context context, MusicDirectory.Entry song, boolean expanded, boolean playing, boolean remote){
-
+ private static void setupViews(RemoteViews rv, Context context, MusicDirectory.Entry song, boolean expanded, boolean playing, boolean remote, boolean isSingleFile, boolean shouldFastForward) {
// Use the same text for the ticker and the expanded notification
String title = song.getTitle();
String arist = song.getArtist();
@@ -152,49 +172,112 @@ public final class Notifications {
if(persistent) {
if(expanded) {
rv.setImageViewResource(R.id.control_pause, playing ? R.drawable.notification_pause : R.drawable.notification_start);
+
+ if(shouldFastForward) {
+ rv.setImageViewResource(R.id.control_previous, R.drawable.notification_rewind);
+ rv.setImageViewResource(R.id.control_next, R.drawable.notification_fastforward);
+ } else {
+ rv.setImageViewResource(R.id.control_previous, R.drawable.notification_backward);
+ rv.setImageViewResource(R.id.control_next, R.drawable.notification_forward);
+ }
} else {
rv.setImageViewResource(R.id.control_previous, playing ? R.drawable.notification_pause : R.drawable.notification_start);
- rv.setImageViewResource(R.id.control_pause, R.drawable.notification_forward);
+ if(shouldFastForward) {
+ rv.setImageViewResource(R.id.control_pause, R.drawable.notification_fastforward);
+ } else {
+ rv.setImageViewResource(R.id.control_pause, R.drawable.notification_forward);
+ }
rv.setImageViewResource(R.id.control_next, R.drawable.notification_close);
}
+ } else if(shouldFastForward) {
+ rv.setImageViewResource(R.id.control_previous, R.drawable.notification_rewind);
+ rv.setImageViewResource(R.id.control_next, R.drawable.notification_fastforward);
+ } else {
+ // Necessary for switching back since it appears to re-use the same layout
+ rv.setImageViewResource(R.id.control_previous, R.drawable.notification_backward);
+ rv.setImageViewResource(R.id.control_next, R.drawable.notification_forward);
}
// Create actions for media buttons
- PendingIntent pendingIntent;
- int previous = 0, pause = 0, next = 0, close = 0;
- if(persistent && !expanded) {
- pause = R.id.control_previous;
- next = R.id.control_pause;
- close = R.id.control_next;
+ int previous = 0, pause = 0, next = 0, close = 0, rewind = 0, fastForward = 0;
+ if (expanded) {
+ if (shouldFastForward) {
+ rewind = R.id.control_previous;
+ pause = R.id.control_pause;
+ fastForward = R.id.control_next;
+ } else {
+ previous = R.id.control_previous;
+ pause = R.id.control_pause;
+ next = R.id.control_next;
+ }
+
+ if (remote || persistent) {
+ close = R.id.notification_close;
+ rv.setViewVisibility(close, View.VISIBLE);
+ }
} else {
- previous = R.id.control_previous;
- pause = R.id.control_pause;
- next = R.id.control_next;
+ if (persistent) {
+ pause = R.id.control_previous;
+ if(shouldFastForward) {
+ fastForward = R.id.control_pause;
+ } else {
+ next = R.id.control_pause;
+ }
+ close = R.id.control_next;
+ } else {
+ rewind = R.id.control_previous;
+ pause = R.id.control_pause;
+ fastForward = R.id.control_next;
+ }
}
- if((remote || persistent) && close == 0 && expanded) {
- close = R.id.notification_close;
- rv.setViewVisibility(close, View.VISIBLE);
+ if(isSingleFile) {
+ if(previous > 0) {
+ rv.setViewVisibility(previous, View.GONE);
+ previous = 0;
+ }
+ if(rewind > 0) {
+ rv.setViewVisibility(rewind, View.GONE);
+ rewind = 0;
+ }
+
+ if(next > 0) {
+ rv.setViewVisibility(next, View.GONE);
+ next = 0;
+ }
+
+ if(fastForward > 0) {
+ rv.setViewVisibility(fastForward, View.GONE);
+ fastForward = 0;
+ }
}
+ PendingIntent pendingIntent;
if(previous > 0) {
Intent prevIntent = new Intent("KEYCODE_MEDIA_PREVIOUS");
prevIntent.setComponent(new ComponentName(context, DownloadService.class));
- prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS));
+ prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS));
pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0);
rv.setOnClickPendingIntent(previous, pendingIntent);
}
+ if(rewind > 0) {
+ Intent rewindIntent = new Intent("KEYCODE_MEDIA_REWIND");
+ rewindIntent.setComponent(new ComponentName(context, DownloadService.class));
+ rewindIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_REWIND));
+ pendingIntent = PendingIntent.getService(context, 0, rewindIntent, 0);
+ rv.setOnClickPendingIntent(rewind, pendingIntent);
+ }
if(pause > 0) {
if(playing) {
Intent pauseIntent = new Intent("KEYCODE_MEDIA_PLAY_PAUSE");
pauseIntent.setComponent(new ComponentName(context, DownloadService.class));
- pauseIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+ pauseIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
pendingIntent = PendingIntent.getService(context, 0, pauseIntent, 0);
rv.setOnClickPendingIntent(pause, pendingIntent);
} else {
Intent prevIntent = new Intent("KEYCODE_MEDIA_START");
prevIntent.setComponent(new ComponentName(context, DownloadService.class));
- prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY));
+ prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY));
pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0);
rv.setOnClickPendingIntent(pause, pendingIntent);
}
@@ -202,14 +285,21 @@ public final class Notifications {
if(next > 0) {
Intent nextIntent = new Intent("KEYCODE_MEDIA_NEXT");
nextIntent.setComponent(new ComponentName(context, DownloadService.class));
- nextIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT));
+ nextIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT));
pendingIntent = PendingIntent.getService(context, 0, nextIntent, 0);
rv.setOnClickPendingIntent(next, pendingIntent);
}
+ if(fastForward > 0) {
+ Intent fastForwardIntent = new Intent("KEYCODE_MEDIA_FAST_FORWARD");
+ fastForwardIntent.setComponent(new ComponentName(context, DownloadService.class));
+ fastForwardIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD));
+ pendingIntent = PendingIntent.getService(context, 0, fastForwardIntent, 0);
+ rv.setOnClickPendingIntent(fastForward, pendingIntent);
+ }
if(close > 0) {
Intent prevIntent = new Intent("KEYCODE_MEDIA_STOP");
prevIntent.setComponent(new ComponentName(context, DownloadService.class));
- prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP));
+ prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP));
pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0);
rv.setOnClickPendingIntent(close, pendingIntent);
}
@@ -270,7 +360,7 @@ public final class Notifications {
Intent notificationIntent = new Intent(context, SubsonicFragmentActivity.class);
notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD_VIEW, true);
- notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
builder.setContentIntent(PendingIntent.getActivity(context, 2, notificationIntent, 0));
final Notification notification = builder.build();
@@ -306,6 +396,9 @@ public final class Notifications {
}
public static void showSyncNotification(final Context context, int stringId, String extra) {
+ showSyncNotification(context, stringId, extra, null);
+ }
+ public static void showSyncNotification(final Context context, int stringId, String extra, String extraId) {
if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_SYNC_NOTIFICATION, true)) {
if(extra == null) {
extra = "";
@@ -323,7 +416,7 @@ public final class Notifications {
.setAutoCancel(true);
Intent notificationIntent = new Intent(context, SubsonicFragmentActivity.class);
- notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
String tab = null, type = null;
switch(stringId) {
@@ -346,6 +439,9 @@ public final class Notifications {
if(type != null) {
notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type);
}
+ if(extraId != null) {
+ notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_ID, extraId);
+ }
builder.setContentIntent(PendingIntent.getActivity(context, stringId, notificationIntent, 0));
diff --git a/app/src/main/java/github/daneren2005/dsub/util/ProgressListener.java b/app/src/main/java/github/daneren2005/dsub/util/ProgressListener.java
index 22f35efc..603b1ccb 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/ProgressListener.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/ProgressListener.java
@@ -24,5 +24,5 @@ package github.daneren2005.dsub.util;
public interface ProgressListener {
void updateProgress(String message);
void updateProgress(int messageId);
- void updateCache();
+ void updateCache(int changeCode);
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/SettingsBackupAgent.java b/app/src/main/java/github/daneren2005/dsub/util/SettingsBackupAgent.java
index def97cac..e57658c4 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/SettingsBackupAgent.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/SettingsBackupAgent.java
@@ -21,6 +21,7 @@ package github.daneren2005.dsub.util;
import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInput;
import android.app.backup.SharedPreferencesBackupHelper;
+import android.content.SharedPreferences;
import android.os.ParcelFileDescriptor;
import java.io.IOError;
@@ -39,6 +40,10 @@ public class SettingsBackupAgent extends BackupAgentHelper {
@Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException{
super.onRestore(data, appVersionCode, newState);
- Util.getPreferences(this).edit().remove(Constants.PREFERENCES_KEY_CACHE_LOCATION).apply();
+
+ SharedPreferences.Editor editor = Util.getPreferences(this).edit();
+ editor.remove(Constants.PREFERENCES_KEY_CACHE_LOCATION);
+ editor.remove(Constants.CACHE_AUDIO_SESSION_ID);
+ editor.apply();
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/SongDBHandler.java b/app/src/main/java/github/daneren2005/dsub/util/SongDBHandler.java
index 8d91a251..1309ee69 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/SongDBHandler.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/SongDBHandler.java
@@ -150,11 +150,23 @@ public class SongDBHandler extends SQLiteOpenHelper {
db.close();
}
+ public boolean hasBeenPlayed(MusicDirectory.Entry entry) {
+ Long[] lastPlayed = getLastPlayed(entry);
+ return lastPlayed != null && lastPlayed[0] != null && lastPlayed[0] > 0;
+ }
+ public boolean hasBeenCompleted(MusicDirectory.Entry entry) {
+ Long[] lastPlayed = getLastPlayed(entry);
+ return lastPlayed != null && lastPlayed[1] != null && lastPlayed[1] > 0;
+ }
public synchronized Long[] getLastPlayed(MusicDirectory.Entry entry) {
return getLastPlayed(getOnlineSongId(entry));
}
protected synchronized Long[] getLastPlayed(Pair<Integer, String> pair) {
- return getLastPlayed(pair.getFirst(), pair.getSecond());
+ if(pair == null) {
+ return null;
+ } else {
+ return getLastPlayed(pair.getFirst(), pair.getSecond());
+ }
}
public synchronized Long[] getLastPlayed(int serverKey, String id) {
SQLiteDatabase db = this.getReadableDatabase();
@@ -169,9 +181,12 @@ public class SongDBHandler extends SQLiteOpenHelper {
dates[0] = cursor.getLong(0);
dates[1] = cursor.getLong(1);
return dates;
- } catch(Exception e) {}
-
- return null;
+ } catch(Exception e) {
+ return null;
+ }
+ finally {
+ db.close();
+ }
}
public synchronized Pair<Integer, String> getOnlineSongId(MusicDirectory.Entry entry) {
@@ -210,9 +225,12 @@ public class SongDBHandler extends SQLiteOpenHelper {
try {
cursor.moveToFirst();
return new Pair(cursor.getInt(0), cursor.getString(1));
- } catch(Exception e) {}
-
- return null;
+ } catch(Exception e) {
+ return null;
+ }
+ finally {
+ db.close();
+ }
}
public synchronized Pair<Integer, String> getIdFromPath(int serverKey, String path) {
SQLiteDatabase db = this.getReadableDatabase();
@@ -223,9 +241,12 @@ public class SongDBHandler extends SQLiteOpenHelper {
try {
cursor.moveToFirst();
return new Pair(cursor.getInt(0), cursor.getString(1));
- } catch(Exception e) {}
-
- return null;
+ } catch(Exception e) {
+ return null;
+ }
+ finally {
+ db.close();
+ }
}
public static SongDBHandler getHandler(Context context) {
diff --git a/app/src/main/java/github/daneren2005/dsub/util/ThemeUtil.java b/app/src/main/java/github/daneren2005/dsub/util/ThemeUtil.java
new file mode 100644
index 00000000..7de4f928
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/util/ThemeUtil.java
@@ -0,0 +1,112 @@
+/*
+ This file is part of Subsonic.
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+ Copyright 2016 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+
+import java.util.Locale;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.activity.SettingsActivity;
+import github.daneren2005.dsub.activity.SubsonicFragmentActivity;
+
+public final class ThemeUtil {
+ public static final String THEME_DARK = "dark";
+ public static final String THEME_BLACK = "black";
+ public static final String THEME_LIGHT = "light";
+ public static final String THEME_HOLO = "holo";
+ public static final String THEME_DAY_NIGHT = "day/night";
+ public static final String THEME_DAY_BLACK_NIGHT = "day/black";
+
+ public static String getTheme(Context context) {
+ SharedPreferences prefs = Util.getPreferences(context);
+ String theme = prefs.getString(Constants.PREFERENCES_KEY_THEME, null);
+
+ if(THEME_DAY_NIGHT.equals(theme)) {
+ int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ if(currentNightMode == Configuration.UI_MODE_NIGHT_YES) {
+ theme = THEME_DARK;
+ } else {
+ theme = THEME_LIGHT;
+ }
+ } else if(THEME_DAY_BLACK_NIGHT.equals(theme)) {
+ int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ if(currentNightMode == Configuration.UI_MODE_NIGHT_YES) {
+ theme = THEME_BLACK;
+ } else {
+ theme = THEME_LIGHT;
+ }
+ }
+
+ return theme;
+ }
+ public static int getThemeRes(Context context) {
+ return getThemeRes(context, getTheme(context));
+ }
+ public static int getThemeRes(Context context, String theme) {
+ if(context instanceof SubsonicFragmentActivity || context instanceof SettingsActivity) {
+ if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) {
+ if (THEME_DARK.equals(theme)) {
+ return R.style.Theme_DSub_Dark_No_Actionbar;
+ } else if (THEME_BLACK.equals(theme)) {
+ return R.style.Theme_DSub_Black_No_Actionbar;
+ } else if (THEME_HOLO.equals(theme)) {
+ return R.style.Theme_DSub_Holo_No_Actionbar;
+ } else {
+ return R.style.Theme_DSub_Light_No_Actionbar;
+ }
+ } else {
+ if (THEME_DARK.equals(theme)) {
+ return R.style.Theme_DSub_Dark_No_Color;
+ } else if (THEME_BLACK.equals(theme)) {
+ return R.style.Theme_DSub_Black_No_Color;
+ } else if (THEME_HOLO.equals(theme)) {
+ return R.style.Theme_DSub_Holo_No_Color;
+ } else {
+ return R.style.Theme_DSub_Light_No_Color;
+ }
+ }
+ } else {
+ if (THEME_DARK.equals(theme)) {
+ return R.style.Theme_DSub_Dark;
+ } else if (THEME_BLACK.equals(theme)) {
+ return R.style.Theme_DSub_Black;
+ } else if (THEME_HOLO.equals(theme)) {
+ return R.style.Theme_DSub_Holo;
+ } else {
+ return R.style.Theme_DSub_Light;
+ }
+ }
+ }
+ public static void setTheme(Context context, String theme) {
+ SharedPreferences.Editor editor = Util.getPreferences(context).edit();
+ editor.putString(Constants.PREFERENCES_KEY_THEME, theme);
+ editor.commit();
+ }
+
+ public static void applyTheme(Context context, String theme) {
+ context.setTheme(getThemeRes(context, theme));
+
+ SharedPreferences prefs = Util.getPreferences(context);
+ if(prefs.getBoolean(Constants.PREFERENCES_KEY_OVERRIDE_SYSTEM_LANGUAGE, false)) {
+ Configuration config = new Configuration();
+ config.locale = Locale.ENGLISH;
+ context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
+ }
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/UpdateHelper.java b/app/src/main/java/github/daneren2005/dsub/util/UpdateHelper.java
index c7e0a04b..4cf25b30 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/UpdateHelper.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/UpdateHelper.java
@@ -27,6 +27,7 @@ import android.util.Log;
import android.view.View;
import android.widget.RatingBar;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -51,9 +52,24 @@ public final class UpdateHelper {
}
public static void toggleStarred(final Context context, final Entry entry, final OnStarChange onStarChange) {
- final boolean starred = !entry.isStarred();
- entry.setStarred(starred);
+ toggleStarred(context, Arrays.asList(entry), onStarChange);
+ }
+
+ public static void toggleStarred(Context context, List<Entry> entries) {
+ toggleStarred(context, entries, null);
+ }
+ public static void toggleStarred(final Context context, final List<Entry> entries, final OnStarChange onStarChange) {
+ if(entries.isEmpty()) {
+ return;
+ }
+
+ final Entry firstEntry = entries.get(0);
+ final boolean starred = !firstEntry.isStarred();
+ for(Entry entry: entries) {
+ entry.setStarred(starred);
+ }
if(onStarChange != null) {
+ onStarChange.entries = entries;
onStarChange.starChange(starred);
}
@@ -61,22 +77,30 @@ public final class UpdateHelper {
@Override
protected Void doInBackground() throws Throwable {
MusicService musicService = MusicServiceFactory.getMusicService(context);
- if(entry.isDirectory() && Util.isTagBrowsing(context) && !Util.isOffline(context)) {
- if(entry.isAlbum()) {
- musicService.setStarred(null, null, Arrays.asList(entry), starred, null, context);
+ List<Entry> songs = new ArrayList<Entry>();
+ List<Entry> artists = new ArrayList<Entry>();
+ List<Entry> albums = new ArrayList<Entry>();
+ for(Entry entry: entries) {
+ if(entry.isDirectory() && Util.isTagBrowsing(context)) {
+ if(entry.isAlbum()) {
+ albums.add(entry);
+ } else {
+ artists.add(entry);
+ }
} else {
- musicService.setStarred(null, Arrays.asList(entry), null, starred, null, context);
+ songs.add(entry);
}
- } else {
- musicService.setStarred(Arrays.asList(entry), null, null, starred, null, context);
}
-
- new EntryInstanceUpdater(entry) {
- @Override
- public void update(Entry found) {
- found.setStarred(starred);
- }
- }.execute();
+ musicService.setStarred(songs, artists, albums, starred, this, context);
+
+ for(Entry entry: entries) {
+ new UpdateHelper.EntryInstanceUpdater(entry) {
+ @Override
+ public void update(Entry found) {
+ found.setStarred(starred);
+ }
+ }.execute();
+ }
return null;
}
@@ -84,13 +108,21 @@ public final class UpdateHelper {
@Override
protected void done(Void result) {
// UpdateView
- Util.toast(context, context.getResources().getString(starred ? R.string.starring_content_starred : R.string.starring_content_unstarred, entry.getTitle()));
+ int starMsgId = starred ? R.string.starring_content_starred : R.string.starring_content_unstarred;
+ String starMsgBody = (entries.size() > 1) ? Integer.toString(entries.size()) : firstEntry.getTitle();
+ Util.toast(context, context.getResources().getString(starMsgId, starMsgBody));
+
+ if(onStarChange != null) {
+ onStarChange.starCommited(starred);
+ }
}
@Override
protected void error(Throwable error) {
Log.w(TAG, "Failed to star", error);
- entry.setStarred(!starred);
+ for(Entry entry: entries) {
+ entry.setStarred(!starred);
+ }
if(onStarChange != null) {
onStarChange.starChange(!starred);
}
@@ -99,7 +131,8 @@ public final class UpdateHelper {
if (error instanceof OfflineException || error instanceof ServerTooOldException) {
msg = getErrorMessage(error);
} else {
- msg = context.getResources().getString(R.string.starring_content_error, entry.getTitle()) + " " + getErrorMessage(error);
+ String errorBody = (entries.size() > 1) ? Integer.toString(entries.size()) : firstEntry.getTitle();
+ msg = context.getResources().getString(R.string.starring_content_error, errorBody) + " " + getErrorMessage(error);
}
Util.toast(context, msg, false);
@@ -215,6 +248,7 @@ public final class UpdateHelper {
msg = context.getResources().getString(rating > 0 ? R.string.rating_set_rating_failed : R.string.rating_remove_rating_failed, entry.getTitle()) + " " + getErrorMessage(error);
}
+ Log.e(TAG, "Failed to setRating", error);
Util.toast(context, msg, false);
}
}.execute();
@@ -222,10 +256,15 @@ public final class UpdateHelper {
public static abstract class EntryInstanceUpdater {
private Entry entry;
+ protected int metadataUpdate = DownloadService.METADATA_UPDATED_ALL;
public EntryInstanceUpdater(Entry entry) {
this.entry = entry;
}
+ public EntryInstanceUpdater(Entry entry, int metadataUpdate) {
+ this.entry = entry;
+ this.metadataUpdate = metadataUpdate;
+ }
public abstract void update(Entry found);
@@ -234,11 +273,17 @@ public final class UpdateHelper {
if(downloadService != null && !entry.isDirectory()) {
boolean serializeChanges = false;
List<DownloadFile> downloadFiles = downloadService.getDownloads();
+ DownloadFile currentPlaying = downloadService.getCurrentPlaying();
+
for(DownloadFile file: downloadFiles) {
Entry check = file.getSong();
if(entry.getId().equals(check.getId())) {
- update(entry);
+ update(check);
serializeChanges = true;
+
+ if(currentPlaying != null && currentPlaying.getSong() != null && currentPlaying.getSong().getId().equals(entry.getId())) {
+ downloadService.onMetadataUpdate(metadataUpdate);
+ }
}
}
@@ -255,7 +300,10 @@ public final class UpdateHelper {
}
public static abstract class OnStarChange {
+ protected List<Entry> entries;
+
public abstract void starChange(boolean starred);
+ public abstract void starCommited(boolean starred);
}
public static abstract class OnRatingChange {
public abstract void ratingChange(int rating);
diff --git a/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java b/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java
index 6417dc81..db1c628f 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java
@@ -29,8 +29,6 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
import android.widget.TextView;
import github.daneren2005.dsub.R;
@@ -159,8 +157,11 @@ public final class UserUtil {
return defaultValue;
}
-
- public static void confirmCredentials(final Activity context, final Runnable onSuccess) {
+
+ public static void confirmCredentials(Activity context, Runnable onSuccess) {
+ confirmCredentials(context, onSuccess, null);
+ }
+ public static void confirmCredentials(final Activity context, final Runnable onSuccess, final Runnable onCancel) {
final long currentTime = System.currentTimeMillis();
// If already ran this check within last x time, just go ahead and auth
if((currentTime - lastVerifiedTime) < MIN_VERIFY_DURATION) {
@@ -175,12 +176,7 @@ public final class UserUtil {
.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
- String password = passwordView.getText().toString();
-
- SharedPreferences prefs = Util.getPreferences(context);
- String correctPassword = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + Util.getActiveServer(context), null);
-
- if(password != null && password.equals(correctPassword)) {
+ if(isPasswordCorrect(context, passwordView)) {
lastVerifiedTime = currentTime;
onSuccess.run();
} else {
@@ -188,7 +184,14 @@ public final class UserUtil {
}
}
})
- .setNegativeButton(R.string.common_cancel, null)
+ .setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if(onCancel != null) {
+ onCancel.run();
+ }
+ }
+ })
.setCancelable(true);
AlertDialog dialog = builder.create();
@@ -199,8 +202,14 @@ public final class UserUtil {
public static void changePassword(final Activity context, final User user) {
View layout = context.getLayoutInflater().inflate(R.layout.change_password, null);
+ View currentPasswordLayout = layout.findViewById(R.id.current_password_layout);
+ final TextView currentPasswordView = (TextView) layout.findViewById(R.id.current_password);
final TextView passwordView = (TextView) layout.findViewById(R.id.new_password);
+ if(isCurrentAdmin()) {
+ currentPasswordLayout.setVisibility(View.GONE);
+ }
+
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.admin_change_password)
.setView(layout)
@@ -215,8 +224,12 @@ public final class UserUtil {
@Override
public void onClick(View v) {
final String password = passwordView.getText().toString();
+ if(!isCurrentAdmin() && !isPasswordCorrect(context, currentPasswordView)) {
+ Util.toast(context, R.string.admin_confirm_password_bad);
+ return;
+ }
// Don't allow blank passwords
- if ("".equals(password)) {
+ else if ("".equals(password)) {
Util.toast(context, R.string.admin_change_password_invalid);
return;
}
@@ -252,6 +265,16 @@ public final class UserUtil {
});
}
+ private static boolean isPasswordCorrect(Context context, TextView passwordView) {
+ return isPasswordCorrect(context, passwordView.getText().toString());
+ }
+ private static boolean isPasswordCorrect(Context context, String password) {
+ SharedPreferences prefs = Util.getPreferences(context);
+ String correctPassword = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + Util.getActiveServer(context), null);
+
+ return password != null && password.equals(correctPassword);
+ }
+
public static void updateSettings(final Context context, final User user) {
new SilentBackgroundTask<Void>(context) {
@Override
@@ -373,7 +396,7 @@ public final class UserUtil {
});
}
- public static void addNewUser(final Activity context, final SubsonicFragment fragment) {
+ public static void addNewUser(final Activity context, final SubsonicFragment fragment, User sampleUser) {
final User user = new User();
for(String role: User.ROLES) {
if(role.equals(User.SETTINGS) || role.equals(User.STREAM)) {
@@ -383,6 +406,13 @@ public final class UserUtil {
}
}
+ if(sampleUser != null && sampleUser.getMusicFolderSettings() != null) {
+ for(User.Setting setting: sampleUser.getMusicFolderSettings()) {
+ User.MusicFolderSetting musicFolderSetting = (User.MusicFolderSetting) setting;
+ user.addMusicFolder(musicFolderSetting, true);
+ }
+ }
+
View layout = context.getLayoutInflater().inflate(R.layout.create_user, null);
final TextView usernameView = (TextView) layout.findViewById(R.id.username);
final TextView emailView = (TextView) layout.findViewById(R.id.email);
@@ -391,7 +421,7 @@ public final class UserUtil {
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
- recyclerView.setAdapter(new SettingsAdapter(context, user, null, true, new SectionAdapter.OnItemClickedListener<User.Setting>() {
+ recyclerView.setAdapter(SettingsAdapter.getSettingsAdapter(context, user, null, true, new SectionAdapter.OnItemClickedListener<User.Setting>() {
@Override
public void onItemClicked(UpdateView<User.Setting> updateView, User.Setting item) {
if(updateView.isCheckable()) {
diff --git a/app/src/main/java/github/daneren2005/dsub/util/Util.java b/app/src/main/java/github/daneren2005/dsub/util/Util.java
index bf4af20d..7f8a168d 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/Util.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/Util.java
@@ -19,15 +19,15 @@ package github.daneren2005.dsub.util;
import android.annotation.TargetApi;
import android.app.Activity;
-import android.graphics.Color;
import android.support.annotation.StringRes;
import android.support.v7.app.AlertDialog;
+import android.content.ClipboardManager;
+import android.content.ClipData;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -46,15 +46,13 @@ import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.util.Log;
import android.util.SparseArray;
+import android.view.View;
import android.view.Gravity;
-import android.view.Window;
-import android.view.WindowManager;
+import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import github.daneren2005.dsub.R;
-import github.daneren2005.dsub.activity.SettingsActivity;
-import github.daneren2005.dsub.activity.SubsonicFragmentActivity;
import github.daneren2005.dsub.adapter.DetailsAdapter;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.PlayerState;
@@ -63,8 +61,6 @@ import github.daneren2005.dsub.domain.ServerInfo;
import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver;
import github.daneren2005.dsub.service.DownloadService;
-import org.apache.http.HttpEntity;
-
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
@@ -76,7 +72,6 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.DecimalFormat;
import java.text.NumberFormat;
@@ -107,6 +102,7 @@ public final class Util {
private static DecimalFormat BYTE_LOCALIZED_FORMAT = null;
private static SimpleDateFormat DATE_FORMAT_SHORT = new SimpleDateFormat("MMM d h:mm a");
private static SimpleDateFormat DATE_FORMAT_LONG = new SimpleDateFormat("MMM d, yyyy h:mm a");
+ private static SimpleDateFormat DATE_FORMAT_NO_TIME = new SimpleDateFormat("MMM d, yyyy");
private static int CURRENT_YEAR = new Date().getYear();
public static final String EVENT_META_CHANGED = "github.daneren2005.dsub.EVENT_META_CHANGED";
@@ -269,65 +265,6 @@ public final class Util {
editor.putBoolean(Constants.PREFERENCES_KEY_ALBUMS_PER_FOLDER + instance, perFolder);
editor.commit();
}
-
- public static String getTheme(Context context) {
- SharedPreferences prefs = getPreferences(context);
- return prefs.getString(Constants.PREFERENCES_KEY_THEME, null);
- }
- public static int getThemeRes(Context context) {
- return getThemeRes(context, getTheme(context));
- }
- public static int getThemeRes(Context context, String theme) {
- if(context instanceof SubsonicFragmentActivity || context instanceof SettingsActivity) {
- if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_COLOR_ACTION_BAR, true)) {
- if ("dark".equals(theme)) {
- return R.style.Theme_DSub_Dark_No_Actionbar;
- } else if ("black".equals(theme)) {
- return R.style.Theme_DSub_Black_No_Actionbar;
- } else if ("holo".equals(theme)) {
- return R.style.Theme_DSub_Holo_No_Actionbar;
- } else {
- return R.style.Theme_DSub_Light_No_Actionbar;
- }
- } else {
- if ("dark".equals(theme)) {
- return R.style.Theme_DSub_Dark_No_Color;
- } else if ("black".equals(theme)) {
- return R.style.Theme_DSub_Black_No_Color;
- } else if ("holo".equals(theme)) {
- return R.style.Theme_DSub_Holo_No_Color;
- } else {
- return R.style.Theme_DSub_Light_No_Color;
- }
- }
- } else {
- if ("dark".equals(theme)) {
- return R.style.Theme_DSub_Dark;
- } else if ("black".equals(theme)) {
- return R.style.Theme_DSub_Black;
- } else if ("holo".equals(theme)) {
- return R.style.Theme_DSub_Holo;
- } else {
- return R.style.Theme_DSub_Light;
- }
- }
- }
- public static void setTheme(Context context, String theme) {
- SharedPreferences.Editor editor = getPreferences(context).edit();
- editor.putString(Constants.PREFERENCES_KEY_THEME, theme);
- editor.commit();
- }
-
- public static void applyTheme(Context context, String theme) {
- context.setTheme(getThemeRes(context, theme));
-
- SharedPreferences prefs = Util.getPreferences(context);
- if(prefs.getBoolean(Constants.PREFERENCES_KEY_OVERRIDE_SYSTEM_LANGUAGE, false)) {
- Configuration config = new Configuration();
- config.locale = Locale.ENGLISH;
- context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
- }
- }
public static boolean getDisplayTrack(Context context) {
SharedPreferences prefs = getPreferences(context);
@@ -376,6 +313,12 @@ public final class Util {
int cacheSize = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_CACHE_SIZE, "-1"));
return cacheSize == -1 ? Integer.MAX_VALUE : cacheSize;
}
+ public static boolean isBatchMode(Context context) {
+ return Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_BATCH_MODE, false);
+ }
+ public static void setBatchMode(Context context, boolean batchMode) {
+ Util.getPreferences(context).edit().putBoolean(Constants.PREFERENCES_KEY_BATCH_MODE, batchMode).commit();
+ }
public static String getRestUrl(Context context, String method) {
return getRestUrl(context, method, true);
@@ -401,13 +344,15 @@ public final class Util {
String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null);
if(allowAltAddress && Util.isWifiConnected(context)) {
String SSID = prefs.getString(Constants.PREFERENCES_KEY_SERVER_LOCAL_NETWORK_SSID + instance, "");
- String currentSSID = Util.getSSID(context);
-
- String[] ssidParts = SSID.split(",");
- if("".equals(SSID) || SSID.equals(currentSSID) || Arrays.asList(ssidParts).contains(currentSSID)) {
- String internalUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_INTERNAL_URL + instance, null);
- if(internalUrl != null && !"".equals(internalUrl) && !"http://".equals(internalUrl)) {
- serverUrl = internalUrl;
+ if(!SSID.isEmpty()) {
+ String currentSSID = Util.getSSID(context);
+
+ String[] ssidParts = SSID.split(",");
+ if ("".equals(SSID) || SSID.equals(currentSSID) || Arrays.asList(ssidParts).contains(currentSSID)) {
+ String internalUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_INTERNAL_URL + instance, null);
+ if (internalUrl != null && !"".equals(internalUrl) && !"http://".equals(internalUrl)) {
+ serverUrl = internalUrl;
+ }
}
}
}
@@ -467,6 +412,18 @@ public final class Util {
return builder.toString().hashCode();
}
+ public static String getBlockTokenUsePref(Context context, int instance) {
+ return Constants.CACHE_BLOCK_TOKEN_USE + Util.getRestUrl(context, null, instance, false);
+ }
+ public static boolean getBlockTokenUse(Context context, int instance) {
+ return getPreferences(context).getBoolean(getBlockTokenUsePref(context, instance), false);
+ }
+ public static void setBlockTokenUse(Context context, int instance, boolean block) {
+ SharedPreferences.Editor editor = getPreferences(context).edit();
+ editor.putBoolean(getBlockTokenUsePref(context, instance), block);
+ editor.commit();
+ }
+
public static String replaceInternalUrl(Context context, String url) {
// Only change to internal when using https
if(url.indexOf("https") != -1) {
@@ -637,13 +594,6 @@ public final class Util {
}
}
- public static String getContentType(HttpEntity entity) {
- if (entity == null || entity.getContentType() == null) {
- return null;
- }
- return entity.getContentType().getValue();
- }
-
public static int getRemainingTrialDays(Context context) {
SharedPreferences prefs = getPreferences(context);
long installTime = prefs.getLong(Constants.PREFERENCES_KEY_INSTALL_TIME, 0L);
@@ -683,11 +633,19 @@ public final class Util {
editor.commit();
}
+ public static boolean shouldCacheDuringCasting(Context context) {
+ return Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_CAST_CACHE, false);
+ }
+
public static boolean shouldStartOnHeadphones(Context context) {
SharedPreferences prefs = getPreferences(context);
return prefs.getBoolean(Constants.PREFERENCES_KEY_START_ON_HEADPHONES, false);
}
+ public static String getSongPressAction(Context context) {
+ return getPreferences(context).getString(Constants.PREFERENCES_KEY_SONG_PRESS_ACTION, "all");
+ }
+
/**
* Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
* <p/>
@@ -901,26 +859,42 @@ public final class Util {
}
public static String formatDate(Context context, String dateString) {
+ return formatDate(context, dateString, true);
+ }
+ public static String formatDate(Context context, String dateString, boolean includeTime) {
+ if(dateString == null) {
+ return "";
+ }
+
try {
+ dateString = dateString.replace(' ', 'T');
boolean isDateNormalized = ServerInfo.checkServerVersion(context, "1.11");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH);
if (isDateNormalized) {
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
- return formatDate(dateFormat.parse(dateString));
+ return formatDate(dateFormat.parse(dateString), includeTime);
} catch(ParseException e) {
+ Log.e(TAG, "Failed to parse date string", e);
return dateString;
}
}
public static String formatDate(Date date) {
+ return formatDate(date, true);
+ }
+ public static String formatDate(Date date, boolean includeTime) {
if(date == null) {
return "Never";
} else {
- if(date.getYear() != CURRENT_YEAR) {
- return DATE_FORMAT_LONG.format(date);
+ if(includeTime) {
+ if (date.getYear() != CURRENT_YEAR) {
+ return DATE_FORMAT_LONG.format(date);
+ } else {
+ return DATE_FORMAT_SHORT.format(date);
+ }
} else {
- return DATE_FORMAT_SHORT.format(date);
+ return DATE_FORMAT_NO_TIME.format(date);
}
}
}
@@ -1135,7 +1109,7 @@ public final class Util {
}
public static boolean isAllowedToDownload(Context context) {
- return !isWifiRequiredForDownload(context) || isWifiConnected(context);
+ return isNetworkConnected(context, true) && !isOffline(context);
}
public static boolean isWifiRequiredForDownload(Context context) {
SharedPreferences prefs = getPreferences(context);
@@ -1161,22 +1135,22 @@ public final class Util {
showDialog(context, android.R.drawable.ic_dialog_info, title, message, linkify);
}
- private static void showDialog(Context context, int icon, int titleId, int messageId) {
+ public static void showDialog(Context context, int icon, int titleId, int messageId) {
showDialog(context, icon, titleId, messageId, true);
}
- private static void showDialog(Context context, int icon, int titleId, String message) {
+ public static void showDialog(Context context, int icon, int titleId, String message) {
showDialog(context, icon, titleId, message, true);
}
- private static void showDialog(Context context, int icon, String title, String message) {
+ public static void showDialog(Context context, int icon, String title, String message) {
showDialog(context, icon, title, message, true);
}
- private static void showDialog(Context context, int icon, int titleId, int messageId, boolean linkify) {
+ public static void showDialog(Context context, int icon, int titleId, int messageId, boolean linkify) {
showDialog(context, icon, context.getResources().getString(titleId), context.getResources().getString(messageId), linkify);
}
- private static void showDialog(Context context, int icon, int titleId, String message, boolean linkify) {
+ public static void showDialog(Context context, int icon, int titleId, String message, boolean linkify) {
showDialog(context, icon, context.getResources().getString(titleId), message, linkify);
}
- private static void showDialog(Context context, int icon, String title, String message, boolean linkify) {
+ public static void showDialog(Context context, int icon, String title, String message, boolean linkify) {
SpannableString ss = new SpannableString(message);
if(linkify) {
Linkify.addLinks(ss, Linkify.ALL);
@@ -1222,12 +1196,36 @@ public final class Util {
}
showDetailsDialog(context, context.getResources().getString(title), headerStrings, details);
}
- public static void showDetailsDialog(Context context, String title, List<String> headers, List<String> details) {
+ public static void showDetailsDialog(Context context, String title, List<String> headers, final List<String> details) {
ListView listView = new ListView(context);
listView.setAdapter(new DetailsAdapter(context, R.layout.details_item, headers, details));
listView.setDivider(null);
listView.setScrollbarFadingEnabled(false);
+ // Let the user long-click on a row to copy its value to the clipboard
+ final Context contextRef = context;
+ listView.setOnItemLongClickListener(new ListView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view, int pos, long id) {
+ TextView nameView = (TextView) view.findViewById(R.id.detail_name);
+ TextView detailsView = (TextView) view.findViewById(R.id.detail_value);
+ if(nameView == null || detailsView == null) {
+ return false;
+ }
+
+ CharSequence name = nameView.getText();
+ CharSequence value = detailsView.getText();
+
+ ClipboardManager clipboard = (ClipboardManager) contextRef.getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText(name, value);
+ clipboard.setPrimaryClip(clip);
+
+ toast(contextRef, "Copied " + name + " to clipboard");
+
+ return true;
+ }
+ });
+
new AlertDialog.Builder(context)
// .setIcon(android.R.drawable.ic_dialog_info)
.setTitle(title)
@@ -1367,72 +1365,79 @@ public final class Util {
* <p>Broadcasts the given song info as the new song being played.</p>
*/
public static void broadcastNewTrackInfo(Context context, MusicDirectory.Entry song) {
- DownloadService downloadService = (DownloadService)context;
- Intent intent = new Intent(EVENT_META_CHANGED);
- Intent avrcpIntent = new Intent(AVRCP_METADATA_CHANGED);
-
- if (song != null) {
- intent.putExtra("title", song.getTitle());
- intent.putExtra("artist", song.getArtist());
- intent.putExtra("album", song.getAlbum());
-
- File albumArtFile = FileUtil.getAlbumArtFile(context, song);
- intent.putExtra("coverart", albumArtFile.getAbsolutePath());
- avrcpIntent.putExtra("playing", true);
- } else {
- intent.putExtra("title", "");
- intent.putExtra("artist", "");
- intent.putExtra("album", "");
- intent.putExtra("coverart", "");
- avrcpIntent.putExtra("playing", false);
- }
- addTrackInfo(context, song, avrcpIntent);
+ try {
+ Intent intent = new Intent(EVENT_META_CHANGED);
+ Intent avrcpIntent = new Intent(AVRCP_METADATA_CHANGED);
+
+ if (song != null) {
+ intent.putExtra("title", song.getTitle());
+ intent.putExtra("artist", song.getArtist());
+ intent.putExtra("album", song.getAlbum());
- context.sendBroadcast(intent);
- context.sendBroadcast(avrcpIntent);
+ File albumArtFile = FileUtil.getAlbumArtFile(context, song);
+ intent.putExtra("coverart", albumArtFile.getAbsolutePath());
+ avrcpIntent.putExtra("playing", true);
+ } else {
+ intent.putExtra("title", "");
+ intent.putExtra("artist", "");
+ intent.putExtra("album", "");
+ intent.putExtra("coverart", "");
+ avrcpIntent.putExtra("playing", false);
+ }
+ addTrackInfo(context, song, avrcpIntent);
+
+ context.sendBroadcast(intent);
+ context.sendBroadcast(avrcpIntent);
+ } catch(Exception e) {
+ Log.e(TAG, "Failed to broadcastNewTrackInfo", e);
+ }
}
/**
* <p>Broadcasts the given player state as the one being set.</p>
*/
public static void broadcastPlaybackStatusChange(Context context, MusicDirectory.Entry song, PlayerState state) {
- Intent intent = new Intent(EVENT_PLAYSTATE_CHANGED);
- Intent avrcpIntent = new Intent(AVRCP_PLAYSTATE_CHANGED);
-
- switch (state) {
- case STARTED:
- intent.putExtra("state", "play");
- avrcpIntent.putExtra("playing", true);
- break;
- case STOPPED:
- intent.putExtra("state", "stop");
- avrcpIntent.putExtra("playing", false);
- break;
- case PAUSED:
- intent.putExtra("state", "pause");
- avrcpIntent.putExtra("playing", false);
- break;
- case PREPARED:
- // Only send quick pause event for samsung devices, causes issues for others
- if(Build.MANUFACTURER.toLowerCase().indexOf("samsung") != -1) {
+ try {
+ Intent intent = new Intent(EVENT_PLAYSTATE_CHANGED);
+ Intent avrcpIntent = new Intent(AVRCP_PLAYSTATE_CHANGED);
+
+ switch (state) {
+ case STARTED:
+ intent.putExtra("state", "play");
+ avrcpIntent.putExtra("playing", true);
+ break;
+ case STOPPED:
+ intent.putExtra("state", "stop");
avrcpIntent.putExtra("playing", false);
- } else {
- return; // Don't broadcast anything
- }
- break;
- case COMPLETED:
- intent.putExtra("state", "complete");
- avrcpIntent.putExtra("playing", false);
- break;
- default:
- return; // No need to broadcast.
- }
- addTrackInfo(context, song, avrcpIntent);
+ break;
+ case PAUSED:
+ intent.putExtra("state", "pause");
+ avrcpIntent.putExtra("playing", false);
+ break;
+ case PREPARED:
+ // Only send quick pause event for samsung devices, causes issues for others
+ if (Build.MANUFACTURER.toLowerCase().indexOf("samsung") != -1) {
+ avrcpIntent.putExtra("playing", false);
+ } else {
+ return; // Don't broadcast anything
+ }
+ break;
+ case COMPLETED:
+ intent.putExtra("state", "complete");
+ avrcpIntent.putExtra("playing", false);
+ break;
+ default:
+ return; // No need to broadcast.
+ }
+ addTrackInfo(context, song, avrcpIntent);
- if(state != PlayerState.PREPARED) {
- context.sendBroadcast(intent);
+ if (state != PlayerState.PREPARED) {
+ context.sendBroadcast(intent);
+ }
+ context.sendBroadcast(avrcpIntent);
+ } catch(Exception e) {
+ Log.e(TAG, "Failed to broadcastPlaybackStatusChange", e);
}
- context.sendBroadcast(avrcpIntent);
}
private static void addTrackInfo(Context context, MusicDirectory.Entry song, Intent intent) {
@@ -1448,6 +1453,7 @@ public final class Util {
intent.putExtra("duration", (long) downloadService.getPlayerDuration());
intent.putExtra("position", (long) downloadService.getPlayerPosition());
intent.putExtra("coverart", albumArtFile.getAbsolutePath());
+ intent.putExtra("package","github.daneren2005.dsub");
} else {
intent.putExtra("track", "");
intent.putExtra("artist", "");
@@ -1457,6 +1463,7 @@ public final class Util {
intent.putExtra("duration", (long) 0);
intent.putExtra("position", (long) 0);
intent.putExtra("coverart", "");
+ intent.putExtra("package","github.daneren2005.dsub");
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/CastCompat.java b/app/src/main/java/github/daneren2005/dsub/util/compat/CastCompat.java
index ab64bca9..415106db 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/compat/CastCompat.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/compat/CastCompat.java
@@ -23,13 +23,9 @@ import com.google.android.gms.cast.CastMediaControlIntent;
import github.daneren2005.dsub.service.ChromeCastController;
import github.daneren2005.dsub.service.DownloadService;
import github.daneren2005.dsub.service.RemoteController;
+import github.daneren2005.dsub.util.EnvironmentVariables;
-/**
- * Created by owner on 2/9/14.
- */
public final class CastCompat {
- public static final String APPLICATION_ID = "5F85EBEB";
-
static {
try {
Class.forName("com.google.android.gms.cast.CastDevice");
@@ -52,6 +48,6 @@ public final class CastCompat {
}
public static String getCastControlCategory() {
- return CastMediaControlIntent.categoryForCast(APPLICATION_ID);
+ return CastMediaControlIntent.categoryForCast(EnvironmentVariables.CAST_APPLICATION_ID);
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java
index 1f7035dc..4f9a27f0 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientBase.java
@@ -29,7 +29,7 @@ public abstract class RemoteControlClientBase {
public abstract void register(final Context context, final ComponentName mediaButtonReceiverComponent);
public abstract void unregister(final Context context);
- public abstract void setPlaybackState(int state);
+ public abstract void setPlaybackState(int state, int index, int queueSize);
public abstract void updateMetadata(Context context, MusicDirectory.Entry currentSong);
public abstract void metadataChanged(MusicDirectory.Entry currentSong);
public abstract void updateAlbumArt(MusicDirectory.Entry currentSong, Bitmap bitmap);
diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java
index 2a06e798..74076afb 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java
@@ -54,7 +54,7 @@ public class RemoteControlClientICS extends RemoteControlClientBase {
audioManager.unregisterRemoteControlClient(mRemoteControl);
}
- public void setPlaybackState(final int state) {
+ public void setPlaybackState(final int state, int index, int queueSize) {
if(mRemoteControl == null) {
return;
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientJB.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientJB.java
index e61e9a47..d10c8594 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientJB.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientJB.java
@@ -1,17 +1,10 @@
package github.daneren2005.dsub.util.compat;
-import github.daneren2005.dsub.domain.MusicDirectory;
-import github.daneren2005.dsub.util.ImageLoader;
import android.annotation.TargetApi;
-import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.media.MediaMetadataRetriever;
import android.media.RemoteControlClient;
-import github.daneren2005.dsub.activity.SubsonicActivity;
-import github.daneren2005.dsub.service.DownloadService;
+
import github.daneren2005.dsub.util.SilentBackgroundTask;
@TargetApi(18)
@@ -36,13 +29,13 @@ public class RemoteControlClientJB extends RemoteControlClientICS {
return null;
}
}.execute();
- setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
+ setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING, 0, 0);
}
});
}
@Override
- public void setPlaybackState(final int state) {
+ public void setPlaybackState(final int state, int index, int queueSize) {
if(mRemoteControl == null) {
return;
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java
index 456446f3..00bca833 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/compat/RemoteControlClientLP.java
@@ -34,7 +34,10 @@ import android.media.session.PlaybackState;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
+import android.support.annotation.NonNull;
import android.support.v7.media.MediaRouter;
+import android.util.Log;
+import android.view.KeyEvent;
import java.util.ArrayList;
import java.util.List;
@@ -42,7 +45,9 @@ import java.util.List;
import github.daneren2005.dsub.R;
import github.daneren2005.dsub.activity.SubsonicActivity;
import github.daneren2005.dsub.activity.SubsonicFragmentActivity;
+import github.daneren2005.dsub.domain.Bookmark;
import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.domain.MusicDirectory.Entry;
import github.daneren2005.dsub.domain.Playlist;
import github.daneren2005.dsub.domain.SearchCritera;
import github.daneren2005.dsub.domain.SearchResult;
@@ -87,7 +92,7 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
Intent activityIntent = new Intent(context, SubsonicFragmentActivity.class);
activityIntent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true);
- activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ activityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent activityPendingIntent = PendingIntent.getActivity(context, 0, activityIntent, 0);
mediaSession.setSessionActivity(activityPendingIntent);
@@ -116,8 +121,12 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
mediaSession.release();
}
+ private void setPlaybackState(int state) {
+ setPlaybackState(state, downloadService.getCurrentPlayingIndex(), downloadService.size());
+ }
+
@Override
- public void setPlaybackState(int state) {
+ public void setPlaybackState(int state, int index, int queueSize) {
PlaybackState.Builder builder = new PlaybackState.Builder();
int newState = PlaybackState.STATE_NONE;
@@ -141,11 +150,17 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
position = downloadService.getPlayerPosition();
}
builder.setState(newState, position, 1.0f);
- builder.setActions(getPlaybackActions());
-
DownloadFile downloadFile = downloadService.getCurrentPlaying();
+ Entry entry = null;
+ boolean isSong = true;
if(downloadFile != null) {
- MusicDirectory.Entry entry = downloadFile.getSong();
+ entry = downloadFile.getSong();
+ isSong = entry.isSong();
+ }
+
+ builder.setActions(getPlaybackActions(isSong, index, queueSize));
+
+ if(entry != null) {
addCustomActions(entry, builder);
builder.setActiveQueueItemId(entry.getId().hashCode());
}
@@ -156,7 +171,7 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
}
@Override
- public void updateMetadata(Context context, MusicDirectory.Entry currentSong) {
+ public void updateMetadata(Context context, Entry currentSong) {
setMetadata(currentSong, null);
if(currentSong != null && imageLoader != null) {
@@ -165,11 +180,11 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
}
@Override
- public void metadataChanged(MusicDirectory.Entry currentSong) {
+ public void metadataChanged(Entry currentSong) {
setPlaybackState(previousState);
}
- public void setMetadata(MusicDirectory.Entry currentSong, Bitmap bitmap) {
+ public void setMetadata(Entry currentSong, Bitmap bitmap) {
MediaMetadata.Builder builder = new MediaMetadata.Builder();
builder.putString(MediaMetadata.METADATA_KEY_ARTIST, (currentSong == null) ? null : currentSong.getArtist())
.putString(MediaMetadata.METADATA_KEY_ALBUM, (currentSong == null) ? null : currentSong.getAlbum())
@@ -189,7 +204,7 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
}
@Override
- public void updateAlbumArt(MusicDirectory.Entry currentSong, Bitmap bitmap) {
+ public void updateAlbumArt(Entry currentSong, Bitmap bitmap) {
setMetadata(currentSong, bitmap);
}
@@ -208,7 +223,7 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
List<MediaSession.QueueItem> queue = new ArrayList<>();
for(DownloadFile file: playlist) {
- MusicDirectory.Entry entry = file.getSong();
+ Entry entry = file.getSong();
MediaDescription description = new MediaDescription.Builder()
.setMediaId(entry.getId())
@@ -227,24 +242,27 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
return mediaSession;
}
- protected long getPlaybackActions() {
+ protected long getPlaybackActions(boolean isSong, int currentIndex, int size) {
long actions = PlaybackState.ACTION_PLAY |
PlaybackState.ACTION_PAUSE |
PlaybackState.ACTION_SEEK_TO |
PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM;
- int currentIndex = downloadService.getCurrentPlayingIndex();
- int size = downloadService.size();
- if(currentIndex > 0) {
+ if(isSong) {
+ if (currentIndex > 0) {
+ actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+ }
+ if (currentIndex < size - 1) {
+ actions |= PlaybackState.ACTION_SKIP_TO_NEXT;
+ }
+ } else {
actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS;
- }
- if(currentIndex < size - 1) {
actions |= PlaybackState.ACTION_SKIP_TO_NEXT;
}
return actions;
}
- protected void addCustomActions(MusicDirectory.Entry currentSong, PlaybackState.Builder builder) {
+ protected void addCustomActions(Entry currentSong, PlaybackState.Builder builder) {
Bundle showOnWearExtras = new Bundle();
showOnWearExtras.putBoolean(SHOW_ON_WEAR, true);
@@ -296,7 +314,7 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
SearchResult results = musicService.search(searchCritera, downloadService, null);
if(results.hasArtists()) {
- playFromParent(new MusicDirectory.Entry(results.getArtists().get(0)));
+ playFromParent(new Entry(results.getArtists().get(0)));
} else if(results.hasAlbums()) {
playFromParent(results.getAlbums().get(0));
} else if(results.hasSongs()) {
@@ -307,13 +325,13 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
return null;
}
-
- private void playFromParent(MusicDirectory.Entry parent) throws Exception {
- List<MusicDirectory.Entry> songs = new ArrayList<>();
+
+ private void playFromParent(Entry parent) throws Exception {
+ List<Entry> songs = new ArrayList<>();
getSongsRecursively(parent, songs);
playSongs(songs);
}
- private void getSongsRecursively(MusicDirectory.Entry parent, List<MusicDirectory.Entry> songs) throws Exception {
+ private void getSongsRecursively(Entry parent, List<Entry> songs) throws Exception {
MusicDirectory musicDirectory;
if(Util.isTagBrowsing(downloadService) && !Util.isOffline(downloadService)) {
musicDirectory = musicService.getAlbum(parent.getId(), parent.getTitle(), false, downloadService, this);
@@ -321,7 +339,7 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
musicDirectory = musicService.getMusicDirectory(parent.getId(), parent.getTitle(), false, downloadService, this);
}
- for (MusicDirectory.Entry dir : musicDirectory.getChildren(true, false)) {
+ for (Entry dir : musicDirectory.getChildren(true, false)) {
if (dir.getRating() == 1) {
continue;
}
@@ -329,7 +347,7 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
getSongsRecursively(dir, songs);
}
- for (MusicDirectory.Entry song : musicDirectory.getChildren(false, true)) {
+ for (Entry song : musicDirectory.getChildren(false, true)) {
if (!song.isVideo() && song.getRating() != 1) {
songs.add(song);
}
@@ -349,24 +367,75 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
}
}.execute();
}
+ private void playMusicDirectory(Entry dir, boolean shuffle, boolean append, boolean playFromBookmark) {
+ playMusicDirectory(dir.getId(), shuffle, append, playFromBookmark);
+ }
+ private void playMusicDirectory(final String dirId, final boolean shuffle, final boolean append, final boolean playFromBookmark) {
+ new SilentServiceTask<Void>(downloadService) {
+ @Override
+ protected Void doInBackground(MusicService musicService) throws Throwable {
+ MusicDirectory musicDirectory;
+ if(Util.isTagBrowsing(downloadService) && !Util.isOffline(downloadService)) {
+ musicDirectory = musicService.getAlbum(dirId, "dir", false, downloadService, null);
+ } else {
+ musicDirectory = musicService.getMusicDirectory(dirId, "dir", false, downloadService, null);
+ }
+
+ List<Entry> playEntries = new ArrayList<>();
+ List<Entry> allEntries = musicDirectory.getChildren(false, true);
+ for(Entry song: allEntries) {
+ if (!song.isVideo() && song.getRating() != 1) {
+ playEntries.add(song);
+ }
+ }
+ playSongs(playEntries, shuffle, append, playFromBookmark);
+
+ return null;
+ }
+ }.execute();
+ }
+
+ private void playSong(Entry entry) {
- private void playSong(MusicDirectory.Entry entry) {
- List<MusicDirectory.Entry> entries = new ArrayList<>();
+ }
+ private void playSong(Entry entry, boolean resumeFromBookmark) {
+ List<Entry> entries = new ArrayList<>();
entries.add(entry);
- playSongs(entries);
+ playSongs(entries, false, false, resumeFromBookmark);
}
- private void playSongs(List<MusicDirectory.Entry> entries) {
+ private void playSongs(List<Entry> entries) {
playSongs(entries, false, false);
}
- private void playSongs(List<MusicDirectory.Entry> entries, boolean shuffle, boolean append) {
+ private void playSongs(List<Entry> entries, boolean shuffle, boolean append) {
+ playSongs(entries, shuffle, append, false);
+ }
+ private void playSongs(List<Entry> entries, boolean shuffle, boolean append, boolean resumeFromBookmark) {
if(!append) {
downloadService.clear();
}
- downloadService.download(entries, false, true, false, shuffle);
+
+ int startIndex = 0;
+ int startPosition = 0;
+ if(resumeFromBookmark) {
+ int bookmarkIndex = 0;
+ for(Entry entry: entries) {
+ if(entry.getBookmark() != null) {
+ Bookmark bookmark = entry.getBookmark();
+ startIndex = bookmarkIndex;
+ startPosition = bookmark.getPosition();
+ break;
+ }
+ bookmarkIndex++;
+ }
+ }
+
+ downloadService.download(entries, false, !append, false, shuffle, startIndex, startPosition);
}
private void noResults() {
-
+ // Keep getting emails from Google that not playing something with no results is bad
+ downloadService.clear();
+ downloadService.setShufflePlayEnabled(true);
}
private class EventCallback extends MediaSession.Callback {
@@ -415,6 +484,7 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
public void onPlayFromSearch (String query, Bundle extras) {
// User just asked to playing something
if("".equals(query)) {
+ downloadService.clear();
downloadService.setShufflePlayEnabled(true);
} else {
String mediaFocus = extras.getString(MediaStore.EXTRA_MEDIA_FOCUS);
@@ -435,6 +505,7 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, genre);
editor.commit();
+ downloadService.clear();
downloadService.setShufflePlayEnabled(true);
}
else {
@@ -467,6 +538,7 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
}
}
+ @Override
public void onPlayFromMediaId (String mediaId, Bundle extras) {
if(extras == null) {
return;
@@ -474,11 +546,33 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
boolean shuffle = extras.getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, false);
boolean playLast = extras.getBoolean(Constants.INTENT_EXTRA_PLAY_LAST, false);
+ Entry entry = (Entry) extras.getSerializable(Constants.INTENT_EXTRA_ENTRY);
+
String playlistId = extras.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, null);
if(playlistId != null) {
Playlist playlist = new Playlist(playlistId, null);
playPlaylist(playlist, shuffle, playLast);
}
+ String musicDirectoryId = extras.getString(Constants.INTENT_EXTRA_NAME_ID);
+ if(musicDirectoryId != null) {
+ Entry dir = new Entry(musicDirectoryId);
+ playMusicDirectory(dir, shuffle, playLast, true);
+ }
+
+ String podcastId = extras.getString(Constants.INTENT_EXTRA_NAME_PODCAST_ID, null);
+ if(podcastId != null) {
+ playSong(entry, true);
+ }
+
+ // Currently only happens when playing bookmarks so we should be looking up parent
+ String childId = extras.getString(Constants.INTENT_EXTRA_NAME_CHILD_ID, null);
+ if(childId != null) {
+ if(Util.isTagBrowsing(downloadService) && !Util.isOffline(downloadService)) {
+ playMusicDirectory(entry.getAlbumId(), shuffle, playLast, true);
+ } else {
+ playMusicDirectory(entry.getParent(), shuffle, playLast, true);
+ }
+ }
}
@Override
@@ -491,5 +585,18 @@ public class RemoteControlClientLP extends RemoteControlClientBase {
downloadService.toggleStarred();
}
}
+
+ @Override
+ public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
+ if (getMediaSession() != null && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
+ KeyEvent keyEvent = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ if (keyEvent != null) {
+ downloadService.handleKeyEvent(keyEvent);
+ return true;
+ }
+ }
+
+ return super.onMediaButtonEvent(mediaButtonIntent);
+ }
}
}
diff --git a/app/src/main/java/github/daneren2005/dsub/util/tags/ID3v2File.java b/app/src/main/java/github/daneren2005/dsub/util/tags/ID3v2File.java
index ea61f36c..69668475 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/tags/ID3v2File.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/tags/ID3v2File.java
@@ -134,7 +134,11 @@ public class ID3v2File extends Common {
if(endValue != -1) {
parts.add(txData[1].substring(endName + 1, endValue));
nextStartIndex = endValue + 1;
+ } else {
+ break;
}
+ } else {
+ break;
}
startName = txData[1].toLowerCase(Locale.US).indexOf("replaygain_", nextStartIndex);
diff --git a/app/src/main/java/github/daneren2005/dsub/view/AlbumView.java b/app/src/main/java/github/daneren2005/dsub/view/AlbumView.java
index 048d5a75..3084e962 100644
--- a/app/src/main/java/github/daneren2005/dsub/view/AlbumView.java
+++ b/app/src/main/java/github/daneren2005/dsub/view/AlbumView.java
@@ -30,6 +30,7 @@ import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.util.FileUtil;
import github.daneren2005.dsub.util.ImageLoader;
+import github.daneren2005.dsub.util.Util;
import java.io.File;
@@ -40,6 +41,7 @@ public class AlbumView extends UpdateView2<MusicDirectory.Entry, ImageLoader> {
private TextView titleView;
private TextView artistView;
private boolean showArtist = true;
+ private String coverArtId;
public AlbumView(Context context, boolean cell) {
super(context);
@@ -82,12 +84,13 @@ public class AlbumView extends UpdateView2<MusicDirectory.Entry, ImageLoader> {
artist += album.getYear();
}
artistView.setText(album.getArtist() == null ? "" : artist);
- imageTask = imageLoader.loadImage(coverArtView, album, false, true);
+ onUpdateImageView();
file = null;
}
public void onUpdateImageView() {
imageTask = item2.loadImage(coverArtView, item, false, true);
+ coverArtId = item.getCoverArt();
}
@Override
@@ -101,6 +104,15 @@ public class AlbumView extends UpdateView2<MusicDirectory.Entry, ImageLoader> {
isRated = item.getRating();
}
+ @Override
+ public void update() {
+ super.update();
+
+ if(!Util.equals(item.getCoverArt(), coverArtId)) {
+ onUpdateImageView();
+ }
+ }
+
public MusicDirectory.Entry getEntry() {
return item;
}
diff --git a/app/src/main/java/github/daneren2005/dsub/view/CacheLocationPreference.java b/app/src/main/java/github/daneren2005/dsub/view/CacheLocationPreference.java
new file mode 100644
index 00000000..35ce71bc
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/view/CacheLocationPreference.java
@@ -0,0 +1,146 @@
+/*
+ This file is part of Subsonic.
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+ Copyright 2015 (C) Scott Jackson
+*/
+package github.daneren2005.dsub.view;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Environment;
+import android.preference.DialogPreference;
+import android.preference.EditTextPreference;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.io.File;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.util.FileUtil;
+
+public class CacheLocationPreference extends EditTextPreference {
+ private static final String TAG = CacheLocationPreference.class.getSimpleName();
+ private Context context;
+
+ public CacheLocationPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ this.context = context;
+ }
+ public CacheLocationPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ this.context = context;
+ }
+ public CacheLocationPreference(Context context) {
+ super(context);
+ this.context = context;
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ view.setLayoutParams(new ViewGroup.LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ final EditText editText = (EditText) view.findViewById(android.R.id.edit);
+ ViewGroup vg = (ViewGroup) editText.getParent();
+
+ LinearLayout cacheButtonsWrapper = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.cache_location_buttons, vg, true);
+ Button internalLocation = (Button) cacheButtonsWrapper.findViewById(R.id.location_internal);
+ Button externalLocation = (Button) cacheButtonsWrapper.findViewById(R.id.location_external);
+
+ File[] dirs;
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ dirs = context.getExternalMediaDirs();
+ } else {
+ dirs = ContextCompat.getExternalFilesDirs(context, null);
+ }
+
+ // Past 5.0 we can query directly for SD Card
+ File internalDir = null, externalDir = null;
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ for(int i = 0; i < dirs.length; i++) {
+ try {
+ if (dirs[i] != null) {
+ if(Environment.isExternalStorageRemovable(dirs[i])) {
+ if(externalDir != null) {
+ externalDir = dirs[i];
+ }
+ } else {
+ internalDir = dirs[i];
+ }
+
+ if(internalDir != null && externalDir != null) {
+ break;
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to check if is external", e);
+ }
+ }
+ }
+
+ // Before 5.0, we have to guess. Most of the time the SD card is last
+ if(externalDir == null) {
+ for (int i = dirs.length - 1; i >= 0; i--) {
+ if (dirs[i] != null) {
+ externalDir = dirs[i];
+ break;
+ }
+ }
+ }
+ if(internalDir == null) {
+ for (int i = 0; i < dirs.length; i++) {
+ if (dirs[i] != null) {
+ internalDir = dirs[i];
+ break;
+ }
+ }
+ }
+ final File finalInternalDir = new File(internalDir, "music");
+ final File finalExternalDir = new File(externalDir, "music");
+
+ final EditText editTextBox = (EditText)view.findViewById(android.R.id.edit);
+ if(finalInternalDir != null && (finalInternalDir.exists() || finalInternalDir.mkdirs())) {
+ internalLocation.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String path = finalInternalDir.getPath();
+ editTextBox.setText(path);
+ }
+ });
+ } else {
+ internalLocation.setEnabled(false);
+ }
+
+ if(finalExternalDir != null && !finalInternalDir.equals(finalExternalDir) && (finalExternalDir.exists() || finalExternalDir.mkdirs())) {
+ externalLocation.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String path = finalExternalDir.getPath();
+ editTextBox.setText(path);
+ }
+ });
+ } else {
+ externalLocation.setEnabled(false);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/view/CardView.java b/app/src/main/java/github/daneren2005/dsub/view/CardView.java
new file mode 100644
index 00000000..d6bca330
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/view/CardView.java
@@ -0,0 +1,67 @@
+package github.daneren2005.dsub.view;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.util.DrawableTint;
+
+public class CardView extends FrameLayout{
+ private static final String TAG = CardView.class.getSimpleName();
+
+ public CardView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public CardView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public CardView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public CardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ try {
+ Path clipPath = new Path();
+ float roundedDp = getResources().getDimension(R.dimen.Card_Radius);
+ clipPath.addRoundRect(new RectF(canvas.getClipBounds()), roundedDp, roundedDp, Path.Direction.CW);
+ canvas.clipPath(clipPath);
+ } catch(Exception e) {
+ Log.e(TAG, "Failed to clip path on canvas", e);
+ }
+ super.onDraw(canvas);
+ }
+
+ private void init(Context context) {
+ setClipChildren(true);
+ setBackgroundResource(DrawableTint.getDrawableRes(context, R.attr.cardBackgroundDrawable));
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ setElevation(getResources().getInteger(R.integer.Card_Elevation));
+ }
+
+ // clipPath is not supported with Hardware Acceleration before API 18
+ // http://stackoverflow.com/questions/8895677/work-around-canvas-clippath-that-is-not-supported-in-android-any-more/8895894#8895894
+ if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2 && isHardwareAccelerated()) {
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/view/FastScroller.java b/app/src/main/java/github/daneren2005/dsub/view/FastScroller.java
index 7cb29835..d3eacefc 100644
--- a/app/src/main/java/github/daneren2005/dsub/view/FastScroller.java
+++ b/app/src/main/java/github/daneren2005/dsub/view/FastScroller.java
@@ -95,7 +95,7 @@ public class FastScroller extends LinearLayout {
switch(action)
{
case MotionEvent.ACTION_DOWN:
- if(event.getX() < (handle.getX() - 20)) {
+ if(event.getX() < (handle.getX() - 30)) {
return false;
}
diff --git a/app/src/main/java/github/daneren2005/dsub/view/GridSpacingDecoration.java b/app/src/main/java/github/daneren2005/dsub/view/GridSpacingDecoration.java
index b59e7157..45b34b9f 100644
--- a/app/src/main/java/github/daneren2005/dsub/view/GridSpacingDecoration.java
+++ b/app/src/main/java/github/daneren2005/dsub/view/GridSpacingDecoration.java
@@ -18,10 +18,17 @@ package github.daneren2005.dsub.view;
import android.graphics.Rect;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
+import android.util.Log;
import android.util.TypedValue;
import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import static android.widget.LinearLayout.*;
public class GridSpacingDecoration extends RecyclerView.ItemDecoration {
+ private static final String TAG = GridSpacingDecoration.class.getSimpleName();
public static final int SPACING = 10;
@Override
@@ -39,30 +46,52 @@ public class GridSpacingDecoration extends RecyclerView.ItemDecoration {
}
int spanCount = getTotalSpan(view, parent);
int spanIndex = childIndex % spanCount;
+
+ // If we can, use the SpanSizeLookup since headers screw up the index calculation
+ RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+ if(layoutManager instanceof GridLayoutManager) {
+ GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
+ GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
+ if(spanSizeLookup != null) {
+ spanIndex = spanSizeLookup.getSpanIndex(childIndex, spanCount);
+ }
+ }
int spanSize = getSpanSize(parent, childIndex);
/* INVALID SPAN */
if (spanCount < 1 || spanSize > 1) return;
- outRect.top = halfSpacing;
- outRect.bottom = halfSpacing;
- outRect.left = halfSpacing;
- outRect.right = halfSpacing;
+ int margins = 0;
+ if(view instanceof UpdateView) {
+ View firstChild = ((ViewGroup) view).getChildAt(0);
+ ViewGroup.LayoutParams layoutParams = firstChild.getLayoutParams();
+ if (layoutParams instanceof LinearLayout.LayoutParams) {
+ margins = ((LinearLayout.LayoutParams) layoutParams).bottomMargin;
+ } else if (layoutParams instanceof FrameLayout.LayoutParams) {
+ margins = ((FrameLayout.LayoutParams) layoutParams).bottomMargin;
+ }
+ }
+ int doubleMargins = margins * 2;
+
+ outRect.top = halfSpacing - margins;
+ outRect.bottom = halfSpacing - margins;
+ outRect.left = halfSpacing - margins;
+ outRect.right = halfSpacing - margins;
- if (isTopEdge(childIndex, spanCount)) {
- outRect.top = spacing;
+ if (isTopEdge(childIndex, spanIndex, spanCount)) {
+ outRect.top = spacing - doubleMargins;
}
if (isLeftEdge(spanIndex, spanCount)) {
- outRect.left = spacing;
+ outRect.left = spacing - doubleMargins;
}
if (isRightEdge(spanIndex, spanCount)) {
- outRect.right = spacing;
+ outRect.right = spacing - doubleMargins;
}
if (isBottomEdge(childIndex, childCount, spanCount)) {
- outRect.bottom = spacing;
+ outRect.bottom = spacing - doubleMargins;
}
}
@@ -94,8 +123,8 @@ public class GridSpacingDecoration extends RecyclerView.ItemDecoration {
return spanIndex == spanCount - 1;
}
- protected boolean isTopEdge(int childIndex, int spanCount) {
- return childIndex < spanCount;
+ protected boolean isTopEdge(int childIndex, int spanIndex, int spanCount) {
+ return childIndex < spanCount && childIndex == spanIndex;
}
protected boolean isBottomEdge(int childIndex, int childCount, int spanCount) {
diff --git a/app/src/main/java/github/daneren2005/dsub/view/InternetRadioStationView.java b/app/src/main/java/github/daneren2005/dsub/view/InternetRadioStationView.java
new file mode 100644
index 00000000..36aaa8af
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/view/InternetRadioStationView.java
@@ -0,0 +1,39 @@
+/*
+ This file is part of Subsonic.
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+ Copyright 2016 (C) Scott Jackson
+*/
+package github.daneren2005.dsub.view;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.InternetRadioStation;
+
+public class InternetRadioStationView extends UpdateView<InternetRadioStation> {
+ private TextView titleView;
+
+ public InternetRadioStationView(Context context) {
+ super(context);
+ LayoutInflater.from(context).inflate(R.layout.basic_list_item, this, true);
+
+ titleView = (TextView) findViewById(R.id.item_name);
+ moreButton = (ImageView) findViewById(R.id.item_more);
+ }
+
+ protected void setObjectImpl(InternetRadioStation station) {
+ titleView.setText(station.getTitle());
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/view/MyViewFlipper.java b/app/src/main/java/github/daneren2005/dsub/view/MyViewFlipper.java
deleted file mode 100644
index 26a3de08..00000000
--- a/app/src/main/java/github/daneren2005/dsub/view/MyViewFlipper.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- This file is part of Subsonic.
-
- Subsonic is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Subsonic is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
-
- Copyright 2009 (C) Sindre Mehus
- */
-package github.daneren2005.dsub.view;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.ViewFlipper;
-
-/**
- * Work-around for Android Issue 6191 (http://code.google.com/p/android/issues/detail?id=6191)
- *
- * @author Sindre Mehus
- * @version $Id$
- */
-public class MyViewFlipper extends ViewFlipper {
-
- public MyViewFlipper(Context context) {
- super(context);
- }
-
- public MyViewFlipper(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
-
- @Override
- protected void onDetachedFromWindow() {
- try {
- super.onDetachedFromWindow();
- }
- catch (IllegalArgumentException e) {
- // Call stopFlipping() in order to kick off updateRunning()
- stopFlipping();
- }
- }
-}
-
diff --git a/app/src/main/java/github/daneren2005/dsub/view/SettingView.java b/app/src/main/java/github/daneren2005/dsub/view/SettingView.java
index d46dc5d2..6dc116f8 100644
--- a/app/src/main/java/github/daneren2005/dsub/view/SettingView.java
+++ b/app/src/main/java/github/daneren2005/dsub/view/SettingView.java
@@ -23,6 +23,7 @@ import android.widget.TextView;
import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.User;
+import github.daneren2005.dsub.domain.User.MusicFolderSetting;
import static github.daneren2005.dsub.domain.User.Setting;
@@ -51,12 +52,14 @@ public class SettingView extends UpdateView2<Setting, Boolean> {
protected void setObjectImpl(Setting setting, Boolean isEditable) {
// Can't edit non-role parts
String name = setting.getName();
- if(name.indexOf("Role") == -1) {
+ if(name.indexOf("Role") == -1 && !(setting instanceof MusicFolderSetting)) {
item2 = false;
}
int res = -1;
- if(User.SCROBBLING.equals(name)) {
+ if(setting instanceof MusicFolderSetting) {
+ titleView.setText(((MusicFolderSetting) setting).getLabel());
+ } else if(User.SCROBBLING.equals(name)) {
res = R.string.admin_scrobblingEnabled;
} else if(User.ADMIN.equals(name)) {
res = R.string.admin_role_admin;
@@ -78,6 +81,8 @@ public class SettingView extends UpdateView2<Setting, Boolean> {
res = R.string.admin_role_jukebox;
} else if(User.SHARE.equals(name)) {
res = R.string.admin_role_share;
+ } else if(User.VIDEO_CONVERSION.equals(name)) {
+ res = R.string.admin_role_video_conversion;
} else if(User.LASTFM.equals(name)) {
res = R.string.admin_role_lastfm;
} else {
diff --git a/app/src/main/java/github/daneren2005/dsub/view/SongView.java b/app/src/main/java/github/daneren2005/dsub/view/SongView.java
index 625303b7..320c5933 100644
--- a/app/src/main/java/github/daneren2005/dsub/view/SongView.java
+++ b/app/src/main/java/github/daneren2005/dsub/view/SongView.java
@@ -29,6 +29,8 @@ import github.daneren2005.dsub.domain.PodcastEpisode;
import github.daneren2005.dsub.service.DownloadService;
import github.daneren2005.dsub.service.DownloadFile;
import github.daneren2005.dsub.util.DrawableTint;
+import github.daneren2005.dsub.util.SongDBHandler;
+import github.daneren2005.dsub.util.ThemeUtil;
import github.daneren2005.dsub.util.Util;
import java.io.File;
@@ -41,12 +43,15 @@ import java.io.File;
public class SongView extends UpdateView2<MusicDirectory.Entry, Boolean> {
private static final String TAG = SongView.class.getSimpleName();
+ private TextView trackTextView;
private TextView titleTextView;
+ private TextView playingTextView;
private TextView artistTextView;
private TextView durationTextView;
private TextView statusTextView;
private ImageView statusImageView;
private ImageView bookmarkButton;
+ private ImageView playedButton;
private View bottomRowView;
private DownloadService downloadService;
@@ -63,13 +68,17 @@ public class SongView extends UpdateView2<MusicDirectory.Entry, Boolean> {
private boolean partialFileExists = false;
private boolean loaded = false;
private boolean isBookmarked = false;
- private boolean bookmarked = false;
+ private boolean isBookmarkedShown = false;
private boolean showPodcast = false;
+ private boolean isPlayed = false;
+ private boolean isPlayedShown = false;
+ private boolean showAlbum = false;
public SongView(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.song_list_item, this, true);
+ trackTextView = (TextView) findViewById(R.id.song_track);
titleTextView = (TextView) findViewById(R.id.song_title);
artistTextView = (TextView) findViewById(R.id.song_artist);
durationTextView = (TextView) findViewById(R.id.song_duration);
@@ -80,6 +89,7 @@ public class SongView extends UpdateView2<MusicDirectory.Entry, Boolean> {
starButton.setFocusable(false);
bookmarkButton = (ImageButton) findViewById(R.id.song_bookmark);
bookmarkButton.setFocusable(false);
+ playedButton = (ImageButton) findViewById(R.id.song_played);
moreButton = (ImageView) findViewById(R.id.item_more);
bottomRowView = findViewById(R.id.song_bottom);
}
@@ -102,12 +112,15 @@ public class SongView extends UpdateView2<MusicDirectory.Entry, Boolean> {
if(artist.length() != 0) {
artist.append(" - ");
}
- int index = date.indexOf(" ");
- artist.append(date.substring(0, index != -1 ? index : date.length()));
+ artist.append(Util.formatDate(context, date, false));
}
}
else if(song.getArtist() != null) {
- artist.append(song.getArtist());
+ if(showAlbum) {
+ artist.append(song.getAlbum());
+ } else {
+ artist.append(song.getArtist());
+ }
}
if(isPodcast) {
@@ -138,8 +151,26 @@ public class SongView extends UpdateView2<MusicDirectory.Entry, Boolean> {
String title = song.getTitle();
Integer track = song.getTrack();
+ if(song.getCustomOrder() != null) {
+ track = song.getCustomOrder();
+ }
+ TextView newPlayingTextView;
if(track != null && Util.getDisplayTrack(context)) {
- title = String.format("%02d", track) + " " + title;
+ trackTextView.setText(String.format("%02d", track));
+ trackTextView.setVisibility(View.VISIBLE);
+ newPlayingTextView = trackTextView;
+ } else {
+ trackTextView.setVisibility(View.GONE);
+ newPlayingTextView = titleTextView;
+ }
+
+ if(newPlayingTextView != playingTextView || playingTextView == null) {
+ if(playing) {
+ playingTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+ playing = false;
+ }
+
+ playingTextView = newPlayingTextView;
}
titleTextView.setText(title);
@@ -191,6 +222,10 @@ public class SongView extends UpdateView2<MusicDirectory.Entry, Boolean> {
item.loadMetadata(downloadFile.getCompleteFile());
loaded = true;
}
+
+ if(item instanceof PodcastEpisode || item.isAudioBook() || item.isPodcast()) {
+ isPlayed = SongDBHandler.getHandler(context).hasBeenCompleted(item);
+ }
}
@Override
@@ -242,32 +277,48 @@ public class SongView extends UpdateView2<MusicDirectory.Entry, Boolean> {
rightImage = false;
}
- boolean playing = downloadService.getCurrentPlaying() == downloadFile;
+ boolean playing = Util.equals(downloadService.getCurrentPlaying(), downloadFile);
if (playing) {
if(!this.playing) {
this.playing = playing;
- titleTextView.setCompoundDrawablesWithIntrinsicBounds(DrawableTint.getDrawableRes(context, R.attr.playing), 0, 0, 0);
+ playingTextView.setCompoundDrawablesWithIntrinsicBounds(DrawableTint.getDrawableRes(context, R.attr.playing), 0, 0, 0);
}
} else {
if(this.playing) {
this.playing = playing;
- titleTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+ playingTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
}
if(isBookmarked) {
- if(!bookmarked) {
+ if(!isBookmarkedShown) {
if(bookmarkButton.getDrawable() == null) {
bookmarkButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_menu_bookmark_selected));
}
bookmarkButton.setVisibility(View.VISIBLE);
- bookmarked = true;
+ isBookmarkedShown = true;
}
} else {
- if(bookmarked) {
+ if(isBookmarkedShown) {
bookmarkButton.setVisibility(View.GONE);
- bookmarked = false;
+ isBookmarkedShown = false;
+ }
+ }
+
+ if(isPlayed) {
+ if(!isPlayedShown) {
+ if(playedButton.getDrawable() == null) {
+ playedButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_toggle_played));
+ }
+
+ playedButton.setVisibility(View.VISIBLE);
+ isPlayedShown = true;
+ }
+ } else {
+ if(isPlayedShown) {
+ playedButton.setVisibility(View.GONE);
+ isPlayedShown = false;
}
}
@@ -288,7 +339,15 @@ public class SongView extends UpdateView2<MusicDirectory.Entry, Boolean> {
// Still highlight red if a 1-star
if(isRated == 1) {
this.setBackgroundColor(Color.RED);
- this.getBackground().setAlpha(20);
+
+ String theme = ThemeUtil.getTheme(context);
+ if("black".equals(theme)) {
+ this.getBackground().setAlpha(80);
+ } else if("dark".equals(theme) || "holo".equals(theme)) {
+ this.getBackground().setAlpha(60);
+ } else {
+ this.getBackground().setAlpha(20);
+ }
} else if(rating == 1) {
this.setBackgroundColor(0x00000000);
}
@@ -304,4 +363,8 @@ public class SongView extends UpdateView2<MusicDirectory.Entry, Boolean> {
public void setShowPodcast(boolean showPodcast) {
this.showPodcast = showPodcast;
}
+
+ public void setShowAlbum(boolean showAlbum) {
+ this.showAlbum = showAlbum;
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/view/UpdateView.java b/app/src/main/java/github/daneren2005/dsub/view/UpdateView.java
index 8f3b5271..0041eac5 100644
--- a/app/src/main/java/github/daneren2005/dsub/view/UpdateView.java
+++ b/app/src/main/java/github/daneren2005/dsub/view/UpdateView.java
@@ -201,6 +201,10 @@ public abstract class UpdateView<T> extends LinearLayout {
});
}
+ public static boolean hasActiveActivity() {
+ return activeActivities > 0;
+ }
+
public static void addActiveActivity() {
activeActivities++;
diff --git a/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteChooserDialogFragment.java b/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteChooserDialogFragment.java
new file mode 100644
index 00000000..a2c898b9
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteChooserDialogFragment.java
@@ -0,0 +1,16 @@
+package github.daneren2005.dsub.view.compat;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.app.MediaRouteChooserDialog;
+import android.support.v7.app.MediaRouteChooserDialogFragment;
+
+import github.daneren2005.dsub.util.ThemeUtil;
+import github.daneren2005.dsub.util.Util;
+
+public class CustomMediaRouteChooserDialogFragment extends MediaRouteChooserDialogFragment {
+ @Override
+ public MediaRouteChooserDialog onCreateChooserDialog(Context context, Bundle savedInstanceState) {
+ return new MediaRouteChooserDialog(context, ThemeUtil.getThemeRes(context));
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteControllerDialogFragment.java b/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteControllerDialogFragment.java
new file mode 100644
index 00000000..ea890b9f
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteControllerDialogFragment.java
@@ -0,0 +1,16 @@
+package github.daneren2005.dsub.view.compat;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.app.MediaRouteControllerDialog;
+import android.support.v7.app.MediaRouteControllerDialogFragment;
+
+import github.daneren2005.dsub.util.ThemeUtil;
+import github.daneren2005.dsub.util.Util;
+
+public class CustomMediaRouteControllerDialogFragment extends MediaRouteControllerDialogFragment {
+ @Override
+ public MediaRouteControllerDialog onCreateControllerDialog(Context context, Bundle savedInstanceState) {
+ return new MediaRouteControllerDialog(context, ThemeUtil.getThemeRes(context));
+ }
+}
diff --git a/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteDialogFactory.java b/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteDialogFactory.java
new file mode 100644
index 00000000..8bc890cb
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteDialogFactory.java
@@ -0,0 +1,17 @@
+package github.daneren2005.dsub.view.compat;
+
+import android.support.v7.app.MediaRouteChooserDialogFragment;
+import android.support.v7.app.MediaRouteControllerDialogFragment;
+import android.support.v7.app.MediaRouteDialogFactory;
+
+public class CustomMediaRouteDialogFactory extends MediaRouteDialogFactory {
+ @Override
+ public MediaRouteChooserDialogFragment onCreateChooserDialogFragment() {
+ return new CustomMediaRouteChooserDialogFragment();
+ }
+
+ @Override
+ public MediaRouteControllerDialogFragment onCreateControllerDialogFragment() {
+ return new CustomMediaRouteControllerDialogFragment();
+ }
+}