diff --git a/.gitignore b/.gitignore index 4838c7c33..8dc679d0d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ proguard_logs/ .gradle/* /build/ local.properties -*Thumbs.db \ No newline at end of file +*Thumbs.db +app/google/release \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 78351890a..dafc4fbc6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,11 +6,18 @@ android { defaultConfig { applicationId "github.daneren2005.dsub" minSdkVersion 15 - targetSdkVersion 26 - versionCode 203 - versionName '5.5.0' + targetSdkVersion 29 + versionCode 206 + versionName '5.5.2' setProperty("archivesBaseName", "DSub $versionName") resConfigs "de", "es", "fr", "hu", "nl", "pt-rPT", "ru", "sv" + vectorDrawables.useSupportLibrary = true + resValue 'string', 'account_type.subsonic', applicationId + ".subsonic" + resValue 'string', 'provider.search', applicationId + ".provider.DSubSearchProvider" + resValue 'string', 'provider.playlist', applicationId + ".playlists.provider" + resValue 'string', 'provider.podcast', applicationId + ".podcasts.provider" + resValue 'string', 'provider.starred', applicationId + ".starred.provider" + resValue 'string', 'provider.recently_added', applicationId + ".mostrecent.provider" } buildTypes { release { diff --git a/app/src/google/java/github/daneren2005/dsub/service/ChromeCastController.java b/app/src/google/java/github/daneren2005/dsub/service/ChromeCastController.java index b2405705d..2bd4cb593 100644 --- a/app/src/google/java/github/daneren2005/dsub/service/ChromeCastController.java +++ b/app/src/google/java/github/daneren2005/dsub/service/ChromeCastController.java @@ -49,6 +49,8 @@ import github.daneren2005.serverproxy.ServerProxy; import github.daneren2005.serverproxy.WebProxy; +import static github.daneren2005.dsub.util.compat.GoogleCompat.castApplicationId; + /** * Created by owner on 2/9/14. */ @@ -421,14 +423,14 @@ public void onConnectionSuspended(int cause) { void launchApplication() { try { - Cast.CastApi.launchApplication(apiClient, EnvironmentVariables.CAST_APPLICATION_ID, false).setResultCallback(resultCallback); + Cast.CastApi.launchApplication(apiClient, castApplicationId(), false).setResultCallback(resultCallback); } catch (Exception e) { Log.e(TAG, "Failed to launch application", e); } } void reconnectApplication() { try { - Cast.CastApi.joinApplication(apiClient, EnvironmentVariables.CAST_APPLICATION_ID, sessionId).setResultCallback(resultCallback); + Cast.CastApi.joinApplication(apiClient, castApplicationId(), sessionId).setResultCallback(resultCallback); } catch (Exception e) { Log.e(TAG, "Failed to reconnect application", e); } diff --git a/app/src/google/java/github/daneren2005/dsub/util/compat/GoogleCompat.java b/app/src/google/java/github/daneren2005/dsub/util/compat/GoogleCompat.java index 63992b0f9..f8bd4ee77 100644 --- a/app/src/google/java/github/daneren2005/dsub/util/compat/GoogleCompat.java +++ b/app/src/google/java/github/daneren2005/dsub/util/compat/GoogleCompat.java @@ -9,6 +9,7 @@ import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.security.ProviderInstaller; +import static com.google.android.gms.cast.CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID; import github.daneren2005.dsub.service.ChromeCastController; import github.daneren2005.dsub.service.DownloadService; @@ -32,10 +33,17 @@ public static void installProvider(Context context) throws Exception{ ProviderInstaller.installIfNeeded(context); } + public static String castApplicationId() { + if (EnvironmentVariables.CAST_APPLICATION_ID != null) { + return EnvironmentVariables.CAST_APPLICATION_ID; + } else { + return DEFAULT_MEDIA_RECEIVER_APPLICATION_ID; + } + } + public static boolean castAvailable() { - if (EnvironmentVariables.CAST_APPLICATION_ID == null) { - Log.w(TAG, "CAST_APPLICATION_ID not provided"); - return false; + if (castApplicationId() == DEFAULT_MEDIA_RECEIVER_APPLICATION_ID) { + Log.i(TAG, "Using DEFAULT_MEDIA_RECEIVER_APPLICATION_ID for casting"); } try { Class.forName("com.google.android.gms.cast.CastDevice"); @@ -56,6 +64,6 @@ public static RemoteController getController(DownloadService downloadService, Me } public static String getCastControlCategory() { - return CastMediaControlIntent.categoryForCast(EnvironmentVariables.CAST_APPLICATION_ID); + return CastMediaControlIntent.categoryForCast(castApplicationId()); } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 527565343..2572bbb53 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,14 +16,14 @@ - - + + @@ -39,7 +39,8 @@ android:backupAgent="github.daneren2005.dsub.util.SettingsBackupAgent" android:icon="@drawable/launch" android:theme="@style/Theme.DSub.Light" - android:largeHeap="true"> + android:largeHeap="true" + android:usesCleartextTraffic="true"> @@ -89,10 +90,11 @@ - + - @@ -214,24 +216,24 @@ + android:authorities="@string/provider.search"/> 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 5948064d1..d475be7b5 100644 --- a/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java +++ b/app/src/main/java/github/daneren2005/dsub/activity/SubsonicActivity.java @@ -18,6 +18,7 @@ */ package github.daneren2005.dsub.activity; +import android.Manifest; import android.app.UiModeManager; import android.content.Context; import android.content.DialogInterface; @@ -184,6 +185,17 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin 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); } + + SharedPreferences prefs = Util.getPreferences(this); + int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1); + String expectedSSID = prefs.getString(Constants.PREFERENCES_KEY_SERVER_LOCAL_NETWORK_SSID + instance, ""); + if(!expectedSSID.isEmpty()) { + String currentSSID = Util.getSSID(this); + + if(currentSSID == "" && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, SubsonicActivity.PERMISSIONS_REQUEST_LOCATION); + } + } } @Override @@ -198,6 +210,14 @@ public void onRequestPermissionsResult(int requestCode, String permissions[], in finish(); } } + case PERMISSIONS_REQUEST_LOCATION: { + // 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_location_failed); + } + } } } @@ -1248,7 +1268,7 @@ public void uncaughtException(Thread thread, Throwable throwable) { PrintWriter printWriter = null; try { - PackageInfo packageInfo = context.getPackageManager().getPackageInfo("github.daneren2005.dsub", 0); + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); file = new File(Environment.getExternalStorageDirectory(), "dsub-stacktrace.txt"); printWriter = new PrintWriter(file); printWriter.println("Android API level: " + Build.VERSION.SDK); 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 674ce98a4..c5463f383 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SettingsFragment.java @@ -214,7 +214,7 @@ else if(Constants.PREFERENCES_KEY_SYNC_MOST_RECENT.equals(key)) { if(downloadService != null) { MediaRouteManager mediaRouter = downloadService.getMediaRouter(); - Boolean enabled = sharedPreferences.getBoolean(key, true); + Boolean enabled = sharedPreferences.getBoolean(key, false); if (enabled) { mediaRouter.addDLNAProvider(); } else { @@ -528,6 +528,10 @@ protected void onAddEditTextToDialogView(View dialogView, final EditText editTex super.onAddEditTextToDialogView(dialogView, editText); ViewGroup root = (ViewGroup) ((ViewGroup) dialogView).getChildAt(0); + if(internalSSID == "" && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, SubsonicActivity.PERMISSIONS_REQUEST_LOCATION); + } + Button defaultButton = new Button(getContext()); defaultButton.setText(internalSSIDDisplay); defaultButton.setOnClickListener(new View.OnClickListener() { @@ -575,6 +579,12 @@ public void onClick(View v) { serverSyncPreference.setSummary(R.string.settings_server_sync_summary); serverSyncPreference.setTitle(R.string.settings_server_sync); + final CheckBoxPreference serverAuthHeaderPreference = new CheckBoxPreference(context); + serverAuthHeaderPreference.setKey(Constants.PREFERENCES_KEY_SERVER_AUTHHEADER + instance); + serverAuthHeaderPreference.setChecked(Util.isAuthHeaderEnabled(context, instance)); + serverAuthHeaderPreference.setSummary(R.string.settings_server_authheaders_summary); + serverAuthHeaderPreference.setTitle(R.string.settings_server_authheaders); + final Preference serverOpenBrowser = new Preference(context); serverOpenBrowser.setKey(Constants.PREFERENCES_KEY_OPEN_BROWSER); serverOpenBrowser.setPersistent(false); @@ -649,6 +659,7 @@ public boolean onPreferenceClick(Preference preference) { screen.addPreference(serverPasswordPreference); screen.addPreference(serverTagPreference); screen.addPreference(serverSyncPreference); + screen.addPreference(serverAuthHeaderPreference); screen.addPreference(serverTestConnectionPreference); screen.addPreference(serverOpenBrowser); screen.addPreference(serverRemoveServerPreference); @@ -803,7 +814,7 @@ public boolean onPreferenceChange(Preference preference, Object value) { try { String url = (String) value; new URL(url); - if (url.contains(" ") || url.contains("@")) { + if (url.contains(" ")) { throw new Exception(); } } catch (Exception x) { @@ -824,7 +835,7 @@ public boolean onPreferenceChange(Preference preference, Object value) { } new URL(url); - if (url.contains(" ") || url.contains("@")) { + if (url.contains(" ")) { throw new Exception(); } } catch (Exception x) { 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 e90969007..67a0f36fe 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/DownloadService.java @@ -310,7 +310,9 @@ public boolean onError(MediaPlayer mediaPlayer, int what, int more) { public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); lifecycleSupport.onStart(intent); - if(Build.VERSION.SDK_INT >= 26 && !this.isForeground()) { + + String action = intent.getAction(); + if(Build.VERSION.SDK_INT >= 26 && !this.isForeground() && !"KEYCODE_MEDIA_START".equals(action)) { Notifications.shutGoogleUpNotification(this); } return START_NOT_STICKY; @@ -1081,7 +1083,7 @@ public synchronized List getDownloads() { public synchronized List getRecentDownloads() { int from = Math.max(currentPlayingIndex - 10, 0); - int songsToKeep = Math.max(Util.getPreloadCount(this), 20); + int songsToKeep = Math.min(Math.max(Util.getPreloadCount(this), 20), downloadList.size()); int to = Math.min(currentPlayingIndex + songsToKeep, Math.max(downloadList.size() - 1, 0)); List temp = downloadList.subList(from, to); temp.addAll(backgroundDownloadList); @@ -1519,12 +1521,14 @@ public synchronized void setPlayerState(final PlayerState playerState) { Util.requestAudioFocus(this, audioManager); } + SharedPreferences prefs = Util.getPreferences(this); + boolean usingMediaStyleNotification = prefs.getBoolean(Constants.PREFERENCES_KEY_MEDIA_STYLE_NOTIFICATION, true) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + if (show) { - Notifications.showPlayingNotification(this, this, handler, currentPlaying.getSong()); + Notifications.showPlayingNotification(this, this, handler, currentPlaying.getSong(), usingMediaStyleNotification); } else if (pause) { - SharedPreferences prefs = Util.getPreferences(this); - if(prefs.getBoolean(Constants.PREFERENCES_KEY_PERSISTENT_NOTIFICATION, false)) { - Notifications.showPlayingNotification(this, this, handler, currentPlaying.getSong()); + if (prefs.getBoolean(Constants.PREFERENCES_KEY_PERSISTENT_NOTIFICATION, false)) { + Notifications.showPlayingNotification(this, this, handler, currentPlaying.getSong(), usingMediaStyleNotification); } else { Notifications.hidePlayingNotification(this, this, handler); } 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 100224e8c..8fdafc111 100644 --- a/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java +++ b/app/src/main/java/github/daneren2005/dsub/service/RESTMusicService.java @@ -1911,11 +1911,16 @@ private HttpURLConnection getConnectionDirect(Context context, String url, Map= 500) { 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 017ba2f37..0ce58d738 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/Constants.java +++ b/app/src/main/java/github/daneren2005/dsub/util/Constants.java @@ -18,6 +18,8 @@ */ package github.daneren2005.dsub.util; +import github.daneren2005.dsub.BuildConfig; + /** * @author Sindre Mehus * @version $Id$ @@ -85,6 +87,7 @@ public final class Constants { public static final String PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId"; public static final String PREFERENCES_KEY_USERNAME = "username"; public static final String PREFERENCES_KEY_PASSWORD = "password"; + public static final String PREFERENCES_KEY_SERVER_AUTHHEADER = "authHeader"; public static final String PREFERENCES_KEY_ENCRYPTED_PASSWORD = "encryptedPassword"; public static final String PREFERENCES_KEY_INSTALL_TIME = "installTime"; public static final String PREFERENCES_KEY_THEME = "theme"; @@ -117,6 +120,7 @@ public final class Constants { public static final String PREFERENCES_EQUALIZER_ON = "equalizerOn"; public static final String PREFERENCES_EQUALIZER_SETTINGS = "equalizerSettings"; public static final String PREFERENCES_KEY_PERSISTENT_NOTIFICATION = "persistentNotification"; + public static final String PREFERENCES_KEY_MEDIA_STYLE_NOTIFICATION = "mediaStyleNotification"; public static final String PREFERENCES_KEY_GAPLESS_PLAYBACK = "gaplessPlayback"; public static final String PREFERENCES_KEY_REMOVE_PLAYED = "removePlayed"; public static final String PREFERENCES_KEY_KEEP_PLAYED_CNT = "keepPlayedCount"; @@ -212,17 +216,17 @@ public final class Constants { public static final String FRAGMENT_POSITION = "fragmentPosition"; // Name of the preferences file. - public static final String PREFERENCES_FILE_NAME = "github.daneren2005.dsub_preferences"; - public static final String OFFLINE_SYNC_NAME = "github.daneren2005.dsub.offline"; + public static final String PREFERENCES_FILE_NAME = BuildConfig.APPLICATION_ID + "_preferences"; + public static final String OFFLINE_SYNC_NAME = BuildConfig.APPLICATION_ID + ".offline"; public static final String OFFLINE_SYNC_DEFAULT = "syncDefaults"; // Account prefs public static final String SYNC_ACCOUNT_NAME = "Subsonic Account"; - public static final String SYNC_ACCOUNT_TYPE = "subsonic.org"; - public static final String SYNC_ACCOUNT_PLAYLIST_AUTHORITY = "github.daneren2005.dsub.playlists.provider"; - public static final String SYNC_ACCOUNT_PODCAST_AUTHORITY = "github.daneren2005.dsub.podcasts.provider"; - public static final String SYNC_ACCOUNT_STARRED_AUTHORITY = "github.daneren2005.dsub.starred.provider"; - public static final String SYNC_ACCOUNT_MOST_RECENT_AUTHORITY = "github.daneren2005.dsub.mostrecent.provider"; + public static final String SYNC_ACCOUNT_TYPE = BuildConfig.APPLICATION_ID + ".subsonic"; + public static final String SYNC_ACCOUNT_PLAYLIST_AUTHORITY = BuildConfig.APPLICATION_ID + ".playlists.provider"; + public static final String SYNC_ACCOUNT_PODCAST_AUTHORITY = BuildConfig.APPLICATION_ID + ".podcasts.provider"; + public static final String SYNC_ACCOUNT_STARRED_AUTHORITY = BuildConfig.APPLICATION_ID + ".starred.provider"; + public static final String SYNC_ACCOUNT_MOST_RECENT_AUTHORITY = BuildConfig.APPLICATION_ID + ".mostrecent.provider"; public static final String TASKER_EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"; diff --git a/app/src/main/java/github/daneren2005/dsub/util/DownloadFileItemHelperCallback.java b/app/src/main/java/github/daneren2005/dsub/util/DownloadFileItemHelperCallback.java index e1e2dc63c..0e80295a2 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/DownloadFileItemHelperCallback.java +++ b/app/src/main/java/github/daneren2005/dsub/util/DownloadFileItemHelperCallback.java @@ -4,9 +4,9 @@ import android.support.v7.widget.helper.ItemTouchHelper; import android.util.Log; -import org.eclipse.jetty.util.ArrayQueue; import java.util.Queue; +import java.util.ArrayDeque; import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.fragments.SubsonicFragment; @@ -22,7 +22,7 @@ public class DownloadFileItemHelperCallback extends ItemTouchHelper.SimpleCallba private boolean mainList; private BackgroundTask pendingTask = null; - private Queue pendingOperations = new ArrayQueue(); + private Queue pendingOperations = new ArrayDeque(); public DownloadFileItemHelperCallback(SubsonicFragment fragment, boolean mainList) { super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT); diff --git a/app/src/main/java/github/daneren2005/dsub/util/MediaRouteManager.java b/app/src/main/java/github/daneren2005/dsub/util/MediaRouteManager.java index e19cc1567..00e02dab1 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/MediaRouteManager.java +++ b/app/src/main/java/github/daneren2005/dsub/util/MediaRouteManager.java @@ -141,7 +141,7 @@ private void addProviders() { addOnlineProviders(); } - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && Util.getPreferences(downloadService).getBoolean(Constants.PREFERENCES_KEY_DLNA_CASTING_ENABLED, true)) { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && Util.getPreferences(downloadService).getBoolean(Constants.PREFERENCES_KEY_DLNA_CASTING_ENABLED, false)) { addDLNAProvider(); } } 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 a8f7add0a..88bc8138f 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/Notifications.java +++ b/app/src/main/java/github/daneren2005/dsub/util/Notifications.java @@ -43,6 +43,7 @@ import github.daneren2005.dsub.provider.DSubWidgetProvider; import github.daneren2005.dsub.service.DownloadFile; import github.daneren2005.dsub.service.DownloadService; +import github.daneren2005.dsub.util.compat.RemoteControlClientLP; import github.daneren2005.dsub.view.UpdateView; public final class Notifications { @@ -65,43 +66,110 @@ public final class Notifications { private final static Pair NOTIFICATION_TEXT_COLORS = new Pair(); - public static void showPlayingNotification(final Context context, final DownloadService downloadService, final Handler handler, MusicDirectory.Entry song) { + public static void showPlayingNotification(final Context context, final DownloadService downloadService, final Handler handler, MusicDirectory.Entry song, boolean usingMediaStyleNotification) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { getPlayingNotificationChannel(context); } - // Set the icon, scrolling text and timestamp - final Notification notification = new NotificationCompat.Builder(context) - .setSmallIcon(R.drawable.stat_notify_playing) - .setTicker(song.getTitle()) - .setWhen(System.currentTimeMillis()) - .setChannelId("now-playing-channel") - .build(); + final Notification notification; final boolean playing = downloadService.getPlayerState() == PlayerState.STARTED; - if(playing) { - notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; - } + boolean remote = downloadService.isRemoteEnabled(); boolean isSingle = downloadService.isCurrentPlayingSingle(); boolean shouldFastForward = downloadService.shouldFastForward(); - if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.JELLY_BEAN){ - RemoteViews expandedContentView = new RemoteViews(context.getPackageName(), R.layout.notification_expanded); - setupViews(expandedContentView ,context, song, true, playing, remote, isSingle, shouldFastForward); - notification.bigContentView = expandedContentView; - notification.priority = Notification.PRIORITY_HIGH; - } - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - notification.visibility = Notification.VISIBILITY_PUBLIC; - if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_HEADS_UP_NOTIFICATION, false) && !UpdateView.hasActiveActivity()) { - notification.vibrate = new long[0]; + if (usingMediaStyleNotification) { + RemoteControlClientLP remoteControlClient = (RemoteControlClientLP) downloadService.getRemoteControlClient(); + + android.support.v4.media.app.NotificationCompat.MediaStyle mediaStyle = new android.support.v4.media.app.NotificationCompat.MediaStyle() + .setMediaSession(remoteControlClient.getMediaSession().getSessionToken()); + + if (isSingle) { + mediaStyle.setShowActionsInCompactView(1); + } else { + mediaStyle.setShowActionsInCompactView(0, 2, 4); } - } - RemoteViews smallContentView = new RemoteViews(context.getPackageName(), R.layout.notification); - setupViews(smallContentView, context, song, false, playing, remote, isSingle, shouldFastForward); - notification.contentView = smallContentView; + String title = song.getTitle(); + String artist = song.getArtist(); + String album = song.getAlbum(); + + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, "now-playing-channel") + .setSmallIcon(R.drawable.stat_notify_playing) + .setContentTitle(title) + .setContentText(artist + " - " + album) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setStyle(mediaStyle); + + // Set the album art. + try { + ImageLoader imageLoader = SubsonicActivity.getStaticImageLoader(context); + if(imageLoader != null) { + Bitmap bitmap = imageLoader.getCachedImage(context, song, false); + + if(bitmap != null) { + notificationBuilder.setLargeIcon(bitmap); + } + } + } catch (Exception x) { + Log.w(TAG, "Failed to get notification cover art", x); + } + + PendingIntent prevIntent = null; + PendingIntent nextIntent = null; + if (!isSingle) { + prevIntent = getMediaPendingIntent("KEYCODE_MEDIA_PREVIOUS", context); + nextIntent = getMediaPendingIntent("KEYCODE_MEDIA_NEXT", context); + } + + PendingIntent rewindIntent = getMediaPendingIntent("KEYCODE_MEDIA_REWIND", context); + PendingIntent playIntent = getMediaPendingIntent(playing ? "KEYCODE_MEDIA_PLAY_PAUSE" : "KEYCODE_MEDIA_START", context); + PendingIntent forwardIntent = getMediaPendingIntent("KEYCODE_MEDIA_FAST_FORWARD", context); + + if (!isSingle) { + notificationBuilder.addAction(R.drawable.ic_baseline_skip_previous_32, "Previous", prevIntent); + } + + notificationBuilder.addAction(R.drawable.ic_baseline_fast_rewind_32, "Rewind", rewindIntent); + notificationBuilder.addAction(playing ? R.drawable.ic_baseline_pause_48 : R.drawable.ic_baseline_play_arrow_48, playing ? "Pause" : "Play", playIntent); + notificationBuilder.addAction(R.drawable.ic_baseline_fast_forward_32, "Forward", forwardIntent); + + if (!isSingle) { + notificationBuilder.addAction(R.drawable.ic_baseline_skip_next_32, "Next", nextIntent); + } + + notification = notificationBuilder.build(); + } else { + // Set the icon, scrolling text and timestamp + notification = new NotificationCompat.Builder(context, "now-playing-channel") + .setSmallIcon(R.drawable.stat_notify_playing) + .setTicker(song.getTitle()) + .setWhen(System.currentTimeMillis()) + .build(); + + if(playing) { + notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; + } + + 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, isSingle, shouldFastForward); + notification.bigContentView = expandedContentView; + notification.priority = Notification.PRIORITY_HIGH; + } + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + notification.visibility = Notification.VISIBILITY_PUBLIC; + + if(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_HEADS_UP_NOTIFICATION, false) && !UpdateView.hasActiveActivity()) { + notification.vibrate = new long[0]; + } + } + + RemoteViews smallContentView = new RemoteViews(context.getPackageName(), R.layout.notification); + setupViews(smallContentView, context, song, false, playing, remote, isSingle, shouldFastForward); + notification.contentView = smallContentView; + } Intent notificationIntent = new Intent(context, SubsonicFragmentActivity.class); notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true); @@ -154,10 +222,52 @@ public void run() { DSubWidgetProvider.notifyInstances(context, downloadService, playing); } + private static PendingIntent getMediaPendingIntent(String action, Context context) { + Intent intent = new Intent(action); + intent.setComponent(new ComponentName(context, DownloadService.class)); + + int keyCode = 0; + switch (action) { + case "KEYCODE_MEDIA_PREVIOUS": + keyCode = KeyEvent.KEYCODE_MEDIA_PREVIOUS; + break; + + case "KEYCODE_MEDIA_REWIND": + keyCode = KeyEvent.KEYCODE_MEDIA_REWIND; + break; + + case "KEYCODE_MEDIA_PLAY_PAUSE": + keyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE; + break; + + case "KEYCODE_MEDIA_START": + keyCode = KeyEvent.KEYCODE_MEDIA_PLAY; + break; + + case "KEYCODE_MEDIA_NEXT": + keyCode = KeyEvent.KEYCODE_MEDIA_NEXT; + break; + + case "KEYCODE_MEDIA_FAST_FORWARD": + keyCode = KeyEvent.KEYCODE_MEDIA_FAST_FORWARD; + break; + + case "KEYCODE_MEDIA_STOP": + keyCode = KeyEvent.KEYCODE_MEDIA_STOP; + break; + } + + if (keyCode != 0) { + intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, keyCode)); + } + + return PendingIntent.getService(context, 0, intent, 0); + } + private static void setupViews(RemoteViews rv, Context context, MusicDirectory.Entry song, boolean expanded, boolean playing, boolean remote, boolean isSingleFile, boolean shouldFastForward) { // Use the same text for the ticker and the expanded notification String title = song.getTitle(); - String arist = song.getArtist(); + String artist = song.getArtist(); String album = song.getAlbum(); // Set the album art. @@ -181,7 +291,7 @@ private static void setupViews(RemoteViews rv, Context context, MusicDirectory.E // set the text for the notifications rv.setTextViewText(R.id.notification_title, title); - rv.setTextViewText(R.id.notification_artist, arist); + rv.setTextViewText(R.id.notification_artist, artist); rv.setTextViewText(R.id.notification_album, album); boolean persistent = Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_PERSISTENT_NOTIFICATION, false); @@ -276,53 +386,32 @@ private static void setupViews(RemoteViews rv, Context context, MusicDirectory.E PendingIntent pendingIntent; if(previous > 0) { - Intent prevIntent = new Intent("KEYCODE_MEDIA_PREVIOUS"); - prevIntent.setComponent(new ComponentName(context, DownloadService.class)); - prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS)); - pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); + pendingIntent = getMediaPendingIntent("KEYCODE_MEDIA_PREVIOUS", context); rv.setOnClickPendingIntent(previous, pendingIntent); } if(rewind > 0) { - Intent rewindIntent = new Intent("KEYCODE_MEDIA_REWIND"); - rewindIntent.setComponent(new ComponentName(context, DownloadService.class)); - rewindIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_REWIND)); - pendingIntent = PendingIntent.getService(context, 0, rewindIntent, 0); + pendingIntent = getMediaPendingIntent("KEYCODE_MEDIA_REWIND", context); rv.setOnClickPendingIntent(rewind, pendingIntent); } if(pause > 0) { if(playing) { - Intent pauseIntent = new Intent("KEYCODE_MEDIA_PLAY_PAUSE"); - pauseIntent.setComponent(new ComponentName(context, DownloadService.class)); - pauseIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); - pendingIntent = PendingIntent.getService(context, 0, pauseIntent, 0); + pendingIntent = getMediaPendingIntent("KEYCODE_MEDIA_PLAY_PAUSE", context); rv.setOnClickPendingIntent(pause, pendingIntent); } else { - Intent prevIntent = new Intent("KEYCODE_MEDIA_START"); - prevIntent.setComponent(new ComponentName(context, DownloadService.class)); - prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY)); - pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); + pendingIntent = getMediaPendingIntent("KEYCODE_MEDIA_START", context); rv.setOnClickPendingIntent(pause, pendingIntent); } } if(next > 0) { - Intent nextIntent = new Intent("KEYCODE_MEDIA_NEXT"); - nextIntent.setComponent(new ComponentName(context, DownloadService.class)); - nextIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT)); - pendingIntent = PendingIntent.getService(context, 0, nextIntent, 0); + pendingIntent = getMediaPendingIntent("KEYCODE_MEDIA_NEXT", context); rv.setOnClickPendingIntent(next, pendingIntent); } if(fastForward > 0) { - Intent fastForwardIntent = new Intent("KEYCODE_MEDIA_FAST_FORWARD"); - fastForwardIntent.setComponent(new ComponentName(context, DownloadService.class)); - fastForwardIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD)); - pendingIntent = PendingIntent.getService(context, 0, fastForwardIntent, 0); + pendingIntent = getMediaPendingIntent("KEYCODE_MEDIA_FAST_FORWARD", context); rv.setOnClickPendingIntent(fastForward, pendingIntent); } if(close > 0) { - Intent prevIntent = new Intent("KEYCODE_MEDIA_STOP"); - prevIntent.setComponent(new ComponentName(context, DownloadService.class)); - prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP)); - pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); + pendingIntent = getMediaPendingIntent("KEYCODE_MEDIA_STOP", context); rv.setOnClickPendingIntent(close, pendingIntent); } } 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 b58a81e4d..872e95612 100644 --- a/app/src/main/java/github/daneren2005/dsub/util/Util.java +++ b/app/src/main/java/github/daneren2005/dsub/util/Util.java @@ -460,6 +460,11 @@ public static boolean isSyncEnabled(Context context, int instance) { return prefs.getBoolean(Constants.PREFERENCES_KEY_SERVER_SYNC + instance, true); } + public static boolean isAuthHeaderEnabled(Context context, int instance) { + SharedPreferences prefs = getPreferences(context); + return prefs.getBoolean(Constants.PREFERENCES_KEY_SERVER_AUTHHEADER + instance, true); + } + public static String getParentFromEntry(Context context, MusicDirectory.Entry entry) { if(Util.isTagBrowsing(context)) { if(!entry.isDirectory()) { diff --git a/app/src/main/res/drawable/ic_baseline_fast_forward_32.xml b/app/src/main/res/drawable/ic_baseline_fast_forward_32.xml new file mode 100644 index 000000000..2ed771186 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_fast_forward_32.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_fast_rewind_32.xml b/app/src/main/res/drawable/ic_baseline_fast_rewind_32.xml new file mode 100644 index 000000000..d25463685 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_fast_rewind_32.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_pause_48.xml b/app/src/main/res/drawable/ic_baseline_pause_48.xml new file mode 100644 index 000000000..8d5e417e9 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_pause_48.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_pause_64.xml b/app/src/main/res/drawable/ic_baseline_pause_64.xml new file mode 100644 index 000000000..8de9e59e9 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_pause_64.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_play_arrow_48.xml b/app/src/main/res/drawable/ic_baseline_play_arrow_48.xml new file mode 100644 index 000000000..c775d2c09 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_play_arrow_48.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_play_arrow_64.xml b/app/src/main/res/drawable/ic_baseline_play_arrow_64.xml new file mode 100644 index 000000000..f42648b89 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_play_arrow_64.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_skip_next_32.xml b/app/src/main/res/drawable/ic_baseline_skip_next_32.xml new file mode 100644 index 000000000..23a02ee02 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_skip_next_32.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_skip_previous_32.xml b/app/src/main/res/drawable/ic_baseline_skip_previous_32.xml new file mode 100644 index 000000000..e01d66665 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_skip_previous_32.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/abstract_activity.xml b/app/src/main/res/layout/abstract_activity.xml index 56db14393..7ca9b9365 100644 --- a/app/src/main/res/layout/abstract_activity.xml +++ b/app/src/main/res/layout/abstract_activity.xml @@ -4,8 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" android:layout_width="match_parent" - android:layout_height="match_parent" - android:fitsSystemWindows="true"> + android:layout_height="match_parent"> Tags de pistas Abrir en pestaña Abrir directamente a esta pestaña + Notificaciones nativas multimedia de Android (5.0+) + Mostrar las notificaciones de reproducción como notificaciones de estilo multimedia nativas (solo en Android Lollipop y superior) Expira: %s Nunca expira diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5c576865e..50909e1e1 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -8,7 +8,7 @@ Jouer Jouer au hasard Suivant - Précédent + Jouer en dernier Mettre en cache Mettre en cache permanent Supprimer diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index be1a7bfff..87ebbe071 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -284,6 +284,8 @@ Open in browser Whether or not syncing is enabled for this server Sync Enabled + If this is enabled the request headers will include Authorization: Basic + Authorization Basic headers Music cache Songs to preload (Wifi) Songs to preload (Mobile) @@ -495,6 +497,8 @@ If you are having battery drain problems on Android 7.0 try turning this off Rewind Interval Fast Forward Interval + Native Android media notification (5.0+) + Show playing notifications as native media style notifications (Android Lollipop+ only) Shuffle By Start Year: @@ -665,6 +669,7 @@ Home Page DSub cannot function without the ability to write to storage + This apps uses location data to get the WIFI SSID to match against your local network settings. To get rid of this message, change the local network SSID to blank. No songs diff --git a/app/src/main/res/xml/authenticator.xml b/app/src/main/res/xml/authenticator.xml index 3055240be..23e4621ad 100644 --- a/app/src/main/res/xml/authenticator.xml +++ b/app/src/main/res/xml/authenticator.xml @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml index e8654e6f5..4be2ae360 100644 --- a/app/src/main/res/xml/changelog.xml +++ b/app/src/main/res/xml/changelog.xml @@ -1,5 +1,17 @@ + + Update to Android 10 target level + Request location permissions if needed for local network check + Show new notification on secure lockscreen + Fix fullscreen not being fullscreen after upgrading to Android 9 target level + + + Update to Android 9 target level + Optionally use system default media style notification (on by default - thanks avm99963) + Fix not being able to use basic server auth with @ in network connection string via server setting (thanks popeen) + Turned DLNA casting off by default since it causes battery drain I have not been able to fix + Fix saving files being broken on Android 11+ Upgrade to Android 8 target level again diff --git a/app/src/main/res/xml/mostrecent_syncadapter.xml b/app/src/main/res/xml/mostrecent_syncadapter.xml index 0195edebb..a01725015 100644 --- a/app/src/main/res/xml/mostrecent_syncadapter.xml +++ b/app/src/main/res/xml/mostrecent_syncadapter.xml @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/app/src/main/res/xml/settings_cast.xml b/app/src/main/res/xml/settings_cast.xml index 78bafdd4d..2170d7ed8 100644 --- a/app/src/main/res/xml/settings_cast.xml +++ b/app/src/main/res/xml/settings_cast.xml @@ -33,9 +33,9 @@ android:title="@string/settings.other_title"> + android:summary="@string/settings.casting.dlna_casting_enabled.summary" + android:title="@string/settings.casting.dlna_casting_enabled" /> \ No newline at end of file diff --git a/app/src/main/res/xml/settings_playback.xml b/app/src/main/res/xml/settings_playback.xml index fb3501f0c..fd6a7dda5 100644 --- a/app/src/main/res/xml/settings_playback.xml +++ b/app/src/main/res/xml/settings_playback.xml @@ -46,6 +46,12 @@ android:key="headsUpNotification" android:defaultValue="false"/> + +