aboutsummaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/AndroidManifest.xml4
-rw-r--r--app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java52
-rw-r--r--app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java34
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/InternetRadioStationAdapter.java56
-rw-r--r--app/src/main/java/github/daneren2005/dsub/domain/InternetRadioStation.java43
-rw-r--r--app/src/main/java/github/daneren2005/dsub/domain/ServerInfo.java15
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/AdminFragment.java2
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java144
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java157
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java1
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java9
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java17
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java5
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java14
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/DownloadService.java74
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java6
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/MusicService.java3
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java6
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java13
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/Scrobbler.java3
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/parser/AbstractParser.java6
-rw-r--r--app/src/main/java/github/daneren2005/dsub/service/parser/InternetRadioStationParser.java63
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java19
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/Constants.java3
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java1
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/ImageLoader.java8
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/Notifications.java48
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/ThemeUtil.java112
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/UserUtil.java2
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/Util.java85
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/compat/CastCompat.java8
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/InternetRadioStationView.java39
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/SongView.java3
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteChooserDialogFragment.java3
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteControllerDialogFragment.java3
-rw-r--r--app/src/main/res/layout/set_playback_speed.xml22
-rw-r--r--app/src/main/res/menu/drawer_navigation.xml4
-rw-r--r--app/src/main/res/menu/playback_speed_options.xml4
-rw-r--r--app/src/main/res/menu/select_internet_radio_context.xml7
-rw-r--r--app/src/main/res/menu/select_song_context.xml6
-rw-r--r--app/src/main/res/menu/select_song_context_offline.xml9
-rw-r--r--app/src/main/res/values-hu/strings.xml30
-rw-r--r--app/src/main/res/values/arrays.xml6
-rw-r--r--app/src/main/res/values/attrs.xml1
-rw-r--r--app/src/main/res/values/strings.xml11
-rw-r--r--app/src/main/res/values/themes.xml2
-rw-r--r--app/src/main/res/xml/changelog.xml11
-rw-r--r--app/src/main/res/xml/settings_drawer.xml8
-rw-r--r--app/src/main/res/xml/settings_playback.xml2
49 files changed, 1024 insertions, 160 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 999fb150..65305cbd 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,11 +8,11 @@
android:label="Tests" />
<uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" android:maxSdkVersion="22"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+ <uses-permission android:name="android.permission.RECORD_AUDIO" android:maxSdkVersion="22"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
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 ab846ba0..9b14f4f6 100644
--- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java
+++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java
@@ -32,12 +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.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;
@@ -77,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;
@@ -89,6 +95,7 @@ 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;
+ private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
private final List<Runnable> afterServiceAvailable = new ArrayList<>();
private boolean drawerIdle = true;
@@ -117,6 +124,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);
@@ -151,6 +162,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;
@@ -165,6 +179,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
@@ -191,7 +224,7 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
protected void createCustomActionBarView() {
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) || Util.getThemeRes(this) != R.style.Theme_DSub_Light_No_Color)) {
+ 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);
@@ -209,7 +242,7 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
// 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();
@@ -281,6 +314,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;
@@ -556,6 +592,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 +622,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);
}
@@ -897,14 +937,14 @@ public class SubsonicActivity extends AppCompatActivity implements OnItemSelecte
}
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() {
@@ -1161,6 +1201,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 ca6dd168..c7190046 100644
--- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java
+++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java
@@ -66,6 +66,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;
@@ -662,6 +663,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)) {
@@ -922,7 +925,13 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
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);
@@ -939,18 +948,25 @@ public class SubsonicFragmentActivity extends SubsonicActivity implements Downlo
getImageLoader().loadImage(coverArtView, song, false, height, false);
}
- if(currentPlaying != null && currentPlaying.getSong() != null && (currentPlaying.getSong().isPodcast() || currentPlaying.getSong().isAudioBook())) {
+ if(getDownloadService().isCurrentPlayingSingle()) {
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);
+ } else {
+ if (currentPlaying != null && currentPlaying.getSong() != null && (currentPlaying.getSong().isPodcast() || currentPlaying.getSong().isAudioBook())) {
+ 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);
+ }
}
}
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/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/ServerInfo.java b/app/src/main/java/github/daneren2005/dsub/domain/ServerInfo.java
index 7f538484..e41a9503 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;
@@ -215,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"));
@@ -231,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");
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 187f0d55..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, objects.get(0));
+ UserUtil.addNewUser(context, this, (objects.size() > 0) ? objects.get(0) : null);
break;
}
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 7c20dc28..f55dbe1d 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/NowPlayingFragment.java
@@ -400,11 +400,6 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
- DownloadService downloadService = getDownloadService();
- if (downloadService == null) {
- return false;
- }
-
float playbackSpeed = 1.0f;
switch (menuItem.getItemId()) {
case R.id.playback_speed_half:
@@ -419,10 +414,12 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
case R.id.playback_speed_tripple:
playbackSpeed = 3.0f;
break;
+ case R.id.playback_speed_custom:
+ setPlaybackSpeed();
+ return true;
}
- downloadService.setPlaybackSpeed(playbackSpeed);
- updateTitle();
+ setPlaybackSpeed(playbackSpeed);
return true;
}
});
@@ -525,7 +522,7 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
menu.removeItem(R.id.menu_equalizer);
}
- if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isRemoteEnabled) {
+ if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M || isRemoteEnabled) {
playbackSpeedButton.setVisibility(View.GONE);
} else {
playbackSpeedButton.setVisibility(View.VISIBLE);
@@ -538,6 +535,15 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
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)) {
@@ -870,6 +876,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);
@@ -1245,18 +1256,25 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
this.currentPlaying = currentPlaying;
setupSubtitle(currentPlayingIndex);
- if(currentPlaying != null && !currentPlaying.isSong()) {
+ if(getDownloadService().isCurrentPlayingSingle()) {
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);
+ } else {
+ if (currentPlaying != null && !currentPlaying.isSong()) {
+ 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);
+ }
}
updateTitle();
}
@@ -1268,7 +1286,9 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
getImageLoader().loadImage(albumArtImageView, song, true, true);
DownloadService downloadService = getDownloadService();
- if(downloadService.isShufflePlayEnabled()) {
+ 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));
@@ -1317,6 +1337,14 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
} else {
setupSubtitle(currentPlayingIndex);
}
+
+ if(downloadService.isCurrentPlayingSingle()) {
+ toggleListButton.setVisibility(View.GONE);
+ repeatButton.setVisibility(View.GONE);
+ } else {
+ toggleListButton.setVisibility(View.VISIBLE);
+ repeatButton.setVisibility(View.VISIBLE);
+ }
}
@Override
@@ -1371,11 +1399,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);
}
@@ -1485,8 +1518,15 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
stringRes = R.string.download_playback_speed_tripple;
}
+ String playbackSpeedText = null;
if(stringRes != -1) {
- title += " (" + context.getResources().getString(stringRes) + ")";
+ 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);
}
@@ -1504,4 +1544,64 @@ public class NowPlayingFragment extends SubsonicFragment implements OnGestureLis
return entries;
}
+
+ private void setPlaybackSpeed() {
+ View dialogView = context.getLayoutInflater().inflate(R.layout.set_playback_speed, null);
+
+ // Setup playbackSpeed label
+ final TextView playbackSpeedBox = (TextView) dialogView.findViewById(R.id.playback_speed_label);
+ final SharedPreferences prefs = Util.getPreferences(context);
+ String playbackSpeedString = prefs.getString(Constants.PREFERENCES_KEY_CUSTOM_PLAYBACK_SPEED, "17");
+ int playbackSpeed = Integer.parseInt(playbackSpeedString);
+ playbackSpeedBox.setText(new Float(playbackSpeed / 10.0).toString());
+
+ // Setup playbackSpeed slider
+ final SeekBar playbackSpeedBar = (SeekBar) dialogView.findViewById(R.id.playback_speed_bar);
+ playbackSpeedBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+ playbackSpeedBox.setText(new Float((progress + 5) / 10.0).toString());
+ seekBar.setProgress(progress);
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+ });
+ playbackSpeedBar.setProgress(playbackSpeed - 5);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.download_playback_speed_custom)
+ .setView(dialogView)
+ .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ int playbackSpeed = playbackSpeedBar.getProgress() + 5;
+
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(Constants.PREFERENCES_KEY_CUSTOM_PLAYBACK_SPEED, Integer.toString(playbackSpeed));
+ editor.commit();
+
+ setPlaybackSpeed(new Float(playbackSpeed / 10.0));
+ }
+ })
+ .setNegativeButton(R.string.common_cancel, null);
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+ 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/SelectInternetRadioStationFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectInternetRadioStationFragment.java
new file mode 100644
index 00000000..c39e9f61
--- /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");
+ }
+
+ }
+ }
+
+ 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/SettingsFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java
index ad064d01..d5ba25f5 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java
@@ -431,6 +431,7 @@ public class SettingsFragment extends PreferenceCompatFragment implements Shared
for (ServerSettings ss : serverSettings.values()) {
if(!ss.update()) {
serversCategory.removePreference(ss.getScreen());
+ serverCount--;
}
}
}
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 b7478c8a..818324ed 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SubsonicFragment.java
@@ -309,6 +309,12 @@ 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);
+ }
}
} else {
if(Util.isOffline(context)) {
@@ -430,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;
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 1a17dfb3..00fb4624 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/CachedMusicService.java
@@ -39,6 +39,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;
@@ -1218,6 +1219,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 c2007139..2df290cf 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/ChromeCastController.java
@@ -41,6 +41,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;
@@ -465,14 +466,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);
}
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 e4bab798..d1c594ce 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/DownloadFile.java
@@ -29,6 +29,8 @@ 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;
@@ -377,6 +379,18 @@ 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 + ")";
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 cec98865..a6bbc327 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;
@@ -123,7 +125,7 @@ public class DownloadService extends Service {
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;
@@ -382,6 +384,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);
}
@@ -394,7 +400,10 @@ public class DownloadService extends Service {
if (songs.isEmpty()) {
return;
+ } else if(isCurrentPlayingSingle()) {
+ clear();
}
+
if (playNext) {
if (autoplay && getCurrentPlayingIndex() >= 0) {
offset = 0;
@@ -996,6 +1005,21 @@ 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 List<DownloadFile> getDownloads() {
List<DownloadFile> temp = new ArrayList<DownloadFile>();
temp.addAll(downloadList);
@@ -1819,7 +1843,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();
@@ -1835,11 +1859,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);
@@ -1851,19 +1870,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);
@@ -2160,13 +2193,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()) {
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 f8272356..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;
@@ -155,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);
@@ -308,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();
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 22f154c4..1e275108 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/MusicService.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/MusicService.java
@@ -30,6 +30,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.RemoteStatus;
import github.daneren2005.dsub.domain.Lyrics;
@@ -194,6 +195,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 e004101d..2c439ec4 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/OfflineMusicService.java
@@ -41,6 +41,7 @@ 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;
@@ -890,6 +891,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 1f9e5494..6ccf562c 100644
--- a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java
+++ b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java
@@ -80,6 +80,7 @@ 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;
@@ -1765,6 +1766,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", null);
+ 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);
}
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 168a7777..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,7 @@ 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;
@@ -69,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 d6e1a002..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;
@@ -72,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;
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/util/BackgroundTask.java b/app/src/main/java/github/daneren2005/dsub/util/BackgroundTask.java
index 31e83200..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 {
@@ -304,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 4109ea67..2d4301d9 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/Constants.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/Constants.java
@@ -134,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";
@@ -176,6 +177,7 @@ public final class Constants {
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_CUSTOM_PLAYBACK_SPEED = "customPlaybackSpeed";
public static final String OFFLINE_SCROBBLE_COUNT = "scrobbleCount";
public static final String OFFLINE_SCROBBLE_ID = "scrobbleID";
@@ -188,6 +190,7 @@ 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_BLOCK_TOKEN_USE = "blockTokenUse";
public static final String MAIN_BACK_STACK = "backStackIds";
public static final String MAIN_BACK_STACK_SIZE = "backStackIdsSize";
diff --git a/app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java b/app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java
index d8046d1b..710d5232 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/EnvironmentVariables.java
@@ -17,4 +17,5 @@ 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 a85141df..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;
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 2948844b..d6a92b07 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/Notifications.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/Notifications.java
@@ -67,9 +67,10 @@ public final class Notifications {
notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT;
}
boolean remote = downloadService.isRemoteEnabled();
+ boolean isSingle = downloadService.isCurrentPlayingSingle();
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);
notification.bigContentView = expandedContentView;
notification.priority = Notification.PRIORITY_HIGH;
}
@@ -82,7 +83,7 @@ public final class Notifications {
}
RemoteViews smallContentView = new RemoteViews(context.getPackageName(), R.layout.notification);
- setupViews(smallContentView, context, song, false, playing, remote);
+ setupViews(smallContentView, context, song, false, playing, remote, isSingle);
notification.contentView = smallContentView;
Intent notificationIntent = new Intent(context, SubsonicFragmentActivity.class);
@@ -98,7 +99,12 @@ 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 {
@@ -106,13 +112,22 @@ public final class Notifications {
@Override
public void run() {
if (playing) {
- downloadService.startForeground(NOTIFICATION_ID_PLAYING, notification);
+ 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");
+ }
}
}
});
@@ -122,7 +137,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 isLongFile = song.isAudioBook() || song.isPodcast();
// Use the same text for the ticker and the expanded notification
@@ -209,6 +224,27 @@ public final class Notifications {
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;
+ }
+ }
if(previous > 0) {
Intent prevIntent = new Intent("KEYCODE_MEDIA_PREVIOUS");
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/UserUtil.java b/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java
index 24d3906b..db1c628f 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/UserUtil.java
@@ -406,7 +406,7 @@ public final class UserUtil {
}
}
- if(sampleUser.getMusicFolderSettings() != null) {
+ if(sampleUser != null && sampleUser.getMusicFolderSettings() != null) {
for(User.Setting setting: sampleUser.getMusicFolderSettings()) {
User.MusicFolderSetting musicFolderSetting = (User.MusicFolderSetting) setting;
user.addMusicFolder(musicFolderSetting, true);
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 fc7292f6..b69ea55e 100644
--- a/app/src/main/java/github/daneren2005/dsub/util/Util.java
+++ b/app/src/main/java/github/daneren2005/dsub/util/Util.java
@@ -274,65 +274,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);
@@ -480,6 +421,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) {
@@ -1172,7 +1125,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);
@@ -1198,22 +1151,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);
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/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/SongView.java b/app/src/main/java/github/daneren2005/dsub/view/SongView.java
index 8cb0c21c..7a02c91d 100644
--- a/app/src/main/java/github/daneren2005/dsub/view/SongView.java
+++ b/app/src/main/java/github/daneren2005/dsub/view/SongView.java
@@ -30,6 +30,7 @@ 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;
@@ -336,7 +337,7 @@ public class SongView extends UpdateView2<MusicDirectory.Entry, Boolean> {
if(isRated == 1) {
this.setBackgroundColor(Color.RED);
- String theme = Util.getTheme(context);
+ String theme = ThemeUtil.getTheme(context);
if("black".equals(theme)) {
this.getBackground().setAlpha(80);
} else if("dark".equals(theme) || "holo".equals(theme)) {
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
index da9b135f..a2c898b9 100644
--- a/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteChooserDialogFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteChooserDialogFragment.java
@@ -5,11 +5,12 @@ 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, Util.getThemeRes(context));
+ 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
index 7fd54142..ea890b9f 100644
--- a/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteControllerDialogFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/view/compat/CustomMediaRouteControllerDialogFragment.java
@@ -5,11 +5,12 @@ 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, Util.getThemeRes(context));
+ return new MediaRouteControllerDialog(context, ThemeUtil.getThemeRes(context));
}
}
diff --git a/app/src/main/res/layout/set_playback_speed.xml b/app/src/main/res/layout/set_playback_speed.xml
new file mode 100644
index 00000000..42f23a35
--- /dev/null
+++ b/app/src/main/res/layout/set_playback_speed.xml
@@ -0,0 +1,22 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center">
+
+ <TextView
+ android:id="@+id/playback_speed_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dp"
+ android:textSize="20dp"
+ android:paddingRight="10px"
+ android:layout_gravity="center"
+ android:textColor="?android:textColorPrimary"/>
+
+ <SeekBar
+ android:id="@+id/playback_speed_bar"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:max="25"/>
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/menu/drawer_navigation.xml b/app/src/main/res/menu/drawer_navigation.xml
index bd309455..32de5cd5 100644
--- a/app/src/main/res/menu/drawer_navigation.xml
+++ b/app/src/main/res/menu/drawer_navigation.xml
@@ -22,6 +22,10 @@
android:icon="?attr/drawerBookmarks"
android:title="@string/button_bar.bookmarks"/>
<item
+ android:id="@+id/drawer_internet_radio_stations"
+ android:icon="?attr/drawerInternetRadioStations"
+ android:title="@string/button_bar.internet_radio"/>
+ <item
android:id="@+id/drawer_shares"
android:icon="?attr/drawerShares"
android:title="@string/button_bar.shares"/>
diff --git a/app/src/main/res/menu/playback_speed_options.xml b/app/src/main/res/menu/playback_speed_options.xml
index e64ecd04..909d3b3c 100644
--- a/app/src/main/res/menu/playback_speed_options.xml
+++ b/app/src/main/res/menu/playback_speed_options.xml
@@ -19,4 +19,8 @@
<item
android:id="@+id/playback_speed_tripple"
android:title="@string/download.playback_speed_tripple"/>
+
+ <item
+ android:id="@+id/playback_speed_custom"
+ android:title="@string/download.playback_speed_custom"/>
</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/select_internet_radio_context.xml b/app/src/main/res/menu/select_internet_radio_context.xml
new file mode 100644
index 00000000..e739aec5
--- /dev/null
+++ b/app/src/main/res/menu/select_internet_radio_context.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/internet_radio_info"
+ android:title="@string/common.info"/>
+
+</menu>
diff --git a/app/src/main/res/menu/select_song_context.xml b/app/src/main/res/menu/select_song_context.xml
index b68dcad9..46eaaa38 100644
--- a/app/src/main/res/menu/select_song_context.xml
+++ b/app/src/main/res/menu/select_song_context.xml
@@ -15,6 +15,12 @@
android:title="@string/menu.show_artist"/>
</group>
+ <group android:id="@+id/hide_play_now">
+ <item
+ android:id="@+id/song_menu_play_now"
+ android:title="@string/common.play_now"/>
+ </group>
+
<group android:id="@+id/hide_play_next">
<item
android:id="@+id/song_menu_play_next"
diff --git a/app/src/main/res/menu/select_song_context_offline.xml b/app/src/main/res/menu/select_song_context_offline.xml
index d7182068..cc914563 100644
--- a/app/src/main/res/menu/select_song_context_offline.xml
+++ b/app/src/main/res/menu/select_song_context_offline.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:compat="http://schemas.android.com/apk/res-auto">
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/song_menu_info"
@@ -16,6 +15,12 @@
android:title="@string/menu.show_artist"/>
</group>
+ <group android:id="@+id/hide_play_now">
+ <item
+ android:id="@+id/song_menu_play_now"
+ android:title="@string/common.play_now"/>
+ </group>
+
<group android:id="@+id/hide_play_next">
<item
android:id="@+id/song_menu_play_next"
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 8ac830a7..12102043 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -36,6 +36,7 @@
<string name="button_bar.now_playing">Várólista</string>
<string name="button_bar.podcasts">Podcastok</string>
<string name="button_bar.bookmarks">Könyvjelzők</string>
+ <string name="button_bar.internet_radio">Internet rádió</string>
<string name="button_bar.shares">Megosztások</string>
<string name="button_bar.chat">Csevegés (Chat)</string>
<string name="button_bar.admin">Admin</string>
@@ -220,6 +221,12 @@
<string name="download.thumbs_up">Jó</string>
<string name="download.thumbs_down">Nem jó</string>
<string name="download.batch_mode">Kötegelt mód</string>
+ <string name="download.playback_speed_half">0.5x</string>
+ <string name="download.playback_speed_normal">1x</string>
+ <string name="download.playback_speed_one_half">1.5x</string>
+ <string name="download.playback_speed_double">2x</string>
+ <string name="download.playback_speed_tripple">3x</string>
+ <string name="download.playback_speed_custom">Egyéni</string>
<string name="sync.new_podcasts">Új podcastok: \"%s\"</string>
<string name="sync.new_playlists">Új lejátszási listák: \"%s\"</string>
@@ -300,6 +307,8 @@
<string name="settings.theme_dark">Sötét</string>
<string name="settings.theme_black">Fekete</string>
<string name="settings.theme_holo">Holo</string>
+ <string name="settings.theme_day_night">Nappal/Éjszaka</string>
+ <string name="settings.theme_day_black_night">Nappal/Fekete éjszaka</string>
<string name="settings.theme_fullscreen">Teljes képernyős</string>
<string name="settings.theme_fullscreen_summary">Teljes képernyős üzemmód (értesítési sáv elrejtése).</string>
<string name="settings.track_title">Dalsorszám megjelenítése</string>
@@ -402,6 +411,8 @@
<string name="settings.podcasts_enabled_summary">Podcastok menüpont megjelenítése az elhúzható oldalsávon.</string>
<string name="settings.bookmarks_enabled">Könyvjelzők engedélyezése</string>
<string name="settings.bookmarks_enabled_summary">Könyvjelzők menüpont megjelenítése az elhúzható oldalsávon.</string>
+ <string name="settings.internet_radio_enabled">Internet rádió engedélyezése</string>
+ <string name="settings.internet_radio_enabled_summary">Internet rádió menüpont megjelenítése az elhúzható oldalsávon.</string>
<string name="settings.shares_enabled">Megosztások engedélyezése</string>
<string name="settings.shares_enabled_summary">Megosztások menüpont megjelenítése az elhúzható oldalsávon.</string>
<string name="settings.sync_title">Szinkronizálás</string>
@@ -442,6 +453,11 @@
<string name="settings.override_system_language">A rendszer nyelvének felülbírálása</string>
<string name="settings.override_system_language_summary">A Dsub megjelenítése angol nyelven abban az esetben is, ha rendelkezik fordítással. Az alkalmazást törölni kell a memóriából, mert a beállítás csak újraindítás után lép érvénybe!</string>
<string name="settings.drawer_items_title">Oldalsáv elemei</string>
+ <string name="settings.song_press_action">Viselkedés dal megérintésekor</string>
+ <string name="settings.song_press_play_single">Dal lejátszása</string>
+ <string name="settings.song_press_play_all">A teljes album hozzáadása a várólistához</string>
+ <string name="settings.song_press_play_next">Dal sorba állítása következőnek</string>
+ <string name="settings.song_press_play_last">Dal sorba állítása utolsónak</string>
<string name="settings.large_album_art">Nagyméretű albumborítók</string>
<string name="settings.large_album_art_summary">Albumok megjelenítése rácsnézetben és nagyméretű albumborítóval a listanézet helyett.</string>
<string name="settings.admin_enabled">Admin engedélyezése</string>
@@ -454,7 +470,7 @@
<string name="settings.replay_gain_type.track">Dal értékeiből</string>
<string name="settings.replay_gain_bump">Hangerő-kiegyenlítés előerősítése</string>
<string name="settings.replay_gain_untagged">Dalok hangerő-kiegyenlítés nélkül</string>
- <string name="settings.casting">Casting (Tartalmak átküldése)</string>
+ <string name="settings.casting">Casting (tartalmak átküldése)</string>
<string name="settings.casting_proxy">Eszköz használata proxyként</string>
<string name="settings.casting_proxy_summary">Streamelés az eszközön (mint egy proxyn) keresztül. Ez megoldást jelenthet néhány esetben, pl. saját aláírású tanúsítvány használatakor.</string>
<string name="settings.rename_duplicates">Duplikált dalok átnevezése</string>
@@ -530,7 +546,7 @@
<string name="admin.role.jukebox">Jukebox vezérlése</string>
<string name="admin.role.share">Megosztások kezelése</string>
<string name="admin.role.video_conversion">Videók konvertálása</string>
- <string name="admin.role.lastfm">Last.fm funkció használata</string>
+ <string name="admin.role.lastfm">Last.fm szolgáltatás használata</string>
<string name="music_service.retry">Hálózati hiba történt! Újrapróbálkozás %1$d/%2$d.</string>
@@ -555,8 +571,8 @@
<string name="equalizer.label">Equalizer</string>
<string name="equalizer.enabled">Engedélyezve</string>
<string name="equalizer.preset">Profil kiválasztása</string>
- <string name="equalizer.bass_booster">Basszus fokozás</string>
- <string name="equalizer.voice_booster">Beszédhang fokozás</string>
+ <string name="equalizer.bass_booster">Basszus fokozása</string>
+ <string name="equalizer.voice_booster">Beszédhang fokozása</string>
<string name="equalizer.db_size">%d dB</string>
<string name="equalizer.bass_size">%d ezer</string>
@@ -578,7 +594,9 @@
<string name="changelog_ok_button">OK</string>
<string name="changelog_show_full">Továbbiak…</string>
- <string name="chat.send_a_message">Üzenet küldése</string>
+ <string name="chat.send_a_message">Üzenet küldése</string>
+
+ <string name="changelog_version_format">Verzió %s</string>
<string name="tasker.start_playing">Lejátszás indítása</string>
<string name="tasker.start_playing_shuffled">Lejátszás indítása kevert sorrendben</string>
@@ -624,7 +642,7 @@
<string name="details.version">Verzió</string>
<string name="details.files_cached">Gyorsítótárazott fájlok</string>
<string name="details.files_permanent">Megőrzött fájlok</string>
- <string name="details.used_space">Felhasznált tároló</string>
+ <string name="details.used_space">Felhasznált tárhely</string>
<string name="details.available_space">Rendelkezésre álló hely</string>
<string name="details.of">%1$s/%2$s</string>
<string name="details.song">Dal</string>
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 01296e9e..e9aadce7 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -6,6 +6,7 @@
<item>@string/button_bar.playlists</item>
<item>@string/button_bar.podcasts</item>
<item>@string/button_bar.bookmarks</item>
+ <item>@string/button_bar.internet_radio</item>
<item>@string/button_bar.shares</item>
<item>@string/button_bar.chat</item>
</string-array>
@@ -16,6 +17,7 @@
<item>Playlist</item>
<item>Podcast</item>
<item>Bookmark</item>
+ <item>Internet Radio</item>
<item>Share</item>
<item>Chat</item>
</string-array>
@@ -25,6 +27,8 @@
<item>dark</item>
<item>black</item>
<item>holo</item>
+ <item>day/night</item>
+ <item>day/black</item>
</string-array>
<string-array name="themeNames">
@@ -32,6 +36,8 @@
<item>@string/settings.theme_dark</item>
<item>@string/settings.theme_black</item>
<item>@string/settings.theme_holo</item>
+ <item>@string/settings.theme_day_night</item>
+ <item>@string/settings.theme_day_black_night</item>
</string-array>
<string-array name="sleepTimerValues">
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 7cd447f0..055726b8 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -49,6 +49,7 @@
<attr name="drawerPlaylists" format="reference"/>
<attr name="drawerPodcasts" format="reference"/>
<attr name="drawerBookmarks" format="reference"/>
+ <attr name="drawerInternetRadioStations" format="reference"/>
<attr name="drawerShares" format="reference"/>
<attr name="drawerChat" format="reference"/>
<attr name="drawerAdmin" format="reference"/>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d372940f..9ac609c3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -36,6 +36,7 @@
<string name="button_bar.now_playing">Now Playing</string>
<string name="button_bar.podcasts">Podcasts</string>
<string name="button_bar.bookmarks">Bookmarks</string>
+ <string name="button_bar.internet_radio">Internet Radio</string>
<string name="button_bar.shares">Shares</string>
<string name="button_bar.chat">Chat</string>
<string name="button_bar.admin">Admin</string>
@@ -225,6 +226,7 @@
<string name="download.playback_speed_one_half">1.5x</string>
<string name="download.playback_speed_double">2x</string>
<string name="download.playback_speed_tripple">3x</string>
+ <string name="download.playback_speed_custom">Custom</string>
<string name="sync.new_podcasts">New podcasts available</string>
<string name="sync.new_playlists">New songs in playlists</string>
@@ -305,6 +307,8 @@
<string name="settings.theme_dark">Dark</string>
<string name="settings.theme_black">Black</string>
<string name="settings.theme_holo">Holo</string>
+ <string name="settings.theme_day_night">Day/Night</string>
+ <string name="settings.theme_day_black_night">Day/Black Night</string>
<string name="settings.theme_fullscreen">Fullscreen</string>
<string name="settings.theme_fullscreen_summary">Hide as many UI elements as Android will allow</string>
<string name="settings.track_title">Display Track #</string>
@@ -407,6 +411,8 @@
<string name="settings.podcasts_enabled_summary">Whether or not to display the podcast listing in the pull out drawer</string>
<string name="settings.bookmarks_enabled">Bookmarks Enabled</string>
<string name="settings.bookmarks_enabled_summary">Whether or not to display the bookmarks listing in the pull out drawer</string>
+ <string name="settings.internet_radio_enabled">Internet Radio Enabled</string>
+ <string name="settings.internet_radio_enabled_summary">Whether or not to display the internet radio listing in the pull out drawer</string>
<string name="settings.shares_enabled">Shares Enabled</string>
<string name="settings.shares_enabled_summary">Whether or not to display the shares listing in the pull out drawer</string>
<string name="settings.sync_title">Sync</string>
@@ -607,6 +613,7 @@
<string name="details.title.podcast">Podcast Details</string>
<string name="details.title.playlist">Playlist Details</string>
<string name="details.title.artist">Artist Details</string>
+ <string name="details.title.internet_radio_station">Internet Radio Details</string>
<string name="details.podcast">Podcast</string>
<string name="details.status">Status</string>
<string name="details.artist">Artist</string>
@@ -646,6 +653,10 @@
<string name="details.last_played">Last Played</string>
<string name="details.expiration">Expiration</string>
<string name="details.played_count">Played Count</string>
+ <string name="details.stream_url">Stream URL</string>
+ <string name="details.home_page">Home Page</string>
+
+ <string name="permission.external_storage.failed">DSub cannot function without the ability to write to storage</string>
<plurals name="select_album_n_songs">
<item quantity="zero">No songs</item>
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 9e95fe9d..8cccab93 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -49,6 +49,7 @@
<item name="drawerPlaylists">@drawable/ic_menu_playlist_light</item>
<item name="drawerPodcasts">@drawable/ic_menu_podcast_light</item>
<item name="drawerBookmarks">@drawable/ic_menu_bookmark_light</item>
+ <item name="drawerInternetRadioStations">@drawable/ic_menu_radio_light</item>
<item name="drawerShares">@drawable/ic_menu_share_light</item>
<item name="drawerChat">@drawable/ic_menu_chat_light</item>
<item name="drawerAdmin">@drawable/ic_menu_admin_light</item>
@@ -120,6 +121,7 @@
<item name="drawerPlaylists">@drawable/ic_menu_playlist_dark</item>
<item name="drawerPodcasts">@drawable/ic_menu_podcast_dark</item>
<item name="drawerBookmarks">@drawable/ic_menu_bookmark_dark</item>
+ <item name="drawerInternetRadioStations">@drawable/ic_menu_radio_dark</item>
<item name="drawerShares">@drawable/ic_menu_share_dark</item>
<item name="drawerChat">@drawable/ic_menu_chat_dark</item>
<item name="drawerAdmin">@drawable/ic_menu_admin_dark</item>
diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml
index 490c427a..89f076f7 100644
--- a/app/src/main/res/xml/changelog.xml
+++ b/app/src/main/res/xml/changelog.xml
@@ -1,5 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<changelog>
+ <release version="5.3" versioncode="186" releasedate="9/23/2016">
+ <change>Listen to Radio Internet Stations</change>
+ <change>Automatic Day/Night theme</change>
+ <change>Android 6.0 runtime permissions</change>
+ <change>Custom variable playback speed</change>
+ <change>Add Play Now for songs when click action is add to queue</change>
+ <change>More secure connections with tokens (Subsonic 6+)</change>
+ <change>Auto skip uncached songs when no network</change>
+ <change>Shrink install size</change>
+ <change>Fix DLNA casting on Android 7.0+</change>
+ </release>
<release version="5.2.2" versioncode="184" releasedate="8/30/2016">
<change>Fix lagging in landscape view on the Now Playing screen</change>
</release>
diff --git a/app/src/main/res/xml/settings_drawer.xml b/app/src/main/res/xml/settings_drawer.xml
index 4b92737e..f89fb990 100644
--- a/app/src/main/res/xml/settings_drawer.xml
+++ b/app/src/main/res/xml/settings_drawer.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:myns="http://schemas.android.com/apk/res/github.daneren2005.dsub"
+ xmlns:myns="http://schemas.android.com/apk/res-auto"
android:title="@string/settings.drawer_items_title">
<PreferenceCategory
@@ -19,6 +19,12 @@
android:defaultValue="true"/>
<CheckBoxPreference
+ android:title="@string/settings.internet_radio_enabled"
+ android:summary="@string/settings.internet_radio_enabled_summary"
+ android:key="internetRadioEnabled"
+ android:defaultValue="true"/>
+
+ <CheckBoxPreference
android:title="@string/settings.shares_enabled"
android:summary="@string/settings.shares_enabled_summary"
android:key="sharedEnabled"
diff --git a/app/src/main/res/xml/settings_playback.xml b/app/src/main/res/xml/settings_playback.xml
index 566a8218..da31d071 100644
--- a/app/src/main/res/xml/settings_playback.xml
+++ b/app/src/main/res/xml/settings_playback.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:myns="http://schemas.android.com/apk/res/github.daneren2005.dsub"
+ xmlns:myns="http://schemas.android.com/apk/res-auto"
android:title="@string/settings.playback_title">
<PreferenceCategory