diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java index 411dbada..a7b7a52d 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java @@ -206,6 +206,7 @@ public class BookmarkActivity extends SubsonicTabActivity if (!getSelectedSongs(albumListView).isEmpty()) { int position = songs.get(0).getBookmarkPosition(); + if (getDownloadService() == null) return; getDownloadService().restore(songs, 0, position, true, true); selectAll(false, false); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java index 7f5e2222..a3e7954e 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java @@ -97,11 +97,8 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen applyTheme(); super.onCreate(bundle); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - startForegroundService(new Intent(this, DownloadServiceImpl.class)); - } else { - startService(new Intent(this, DownloadServiceImpl.class)); - } + // This should always succeed as it is called when Ultrasonic is in the foreground + startService(new Intent(this, DownloadServiceImpl.class)); setVolumeControlStream(AudioManager.STREAM_MUSIC); if (bundle != null) @@ -778,11 +775,16 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen } Log.w(TAG, "DownloadService not running. Attempting to start it."); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - startForegroundService(new Intent(this, DownloadServiceImpl.class)); - } else { + + try + { startService(new Intent(this, DownloadServiceImpl.class)); } + catch (IllegalStateException exception) + { + Log.w(TAG, "getDownloadService couldn't start DownloadServiceImpl because the application was in the background."); + return null; + } Util.sleepQuietly(50L); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java index cff36151..864d8a14 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java @@ -108,6 +108,13 @@ public class SettingsFragment extends PreferenceFragment setupClearSearchPreference(); setupGaplessControlSettingsV14(); setupFeatureFlagsPreferences(); + + // After API26 foreground services must be used for music playback, and they must have a notification + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + PreferenceCategory notificationsCategory = (PreferenceCategory) findPreference(Constants.PREFERENCES_KEY_CATEGORY_NOTIFICATIONS); + notificationsCategory.removePreference(findPreference(Constants.PREFERENCES_KEY_SHOW_NOTIFICATION)); + notificationsCategory.removePreference(findPreference(Constants.PREFERENCES_KEY_ALWAYS_SHOW_NOTIFICATION)); + } } @Override diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java index 944c6481..d36cab6c 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java @@ -58,19 +58,20 @@ public class MediaButtonIntentReceiver extends BroadcastReceiver Intent serviceIntent = new Intent(context, DownloadServiceImpl.class); serviceIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.startForegroundService(serviceIntent); - } else { - context.startService(serviceIntent); - } try { + context.startService(serviceIntent); + if (isOrderedBroadcast()) { abortBroadcast(); } } + catch (IllegalStateException exception) + { + Log.w(TAG, "MediaButtonIntentReceiver couldn't start DownloadServiceImpl because the application was in the background."); + } catch (Exception x) { // Ignored. diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java index c3d1b3d1..ec3d73e7 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java @@ -276,13 +276,17 @@ public class DownloadServiceImpl extends Service implements DownloadService // We should use a single notification builder, otherwise the notification may not be updated notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID); + + Log.i(TAG, "DownloadServiceImpl created"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); + lifecycleSupport.onStart(intent); + Log.i(TAG, "DownloadServiceImpl started with intent"); return START_NOT_STICKY; } @@ -338,6 +342,8 @@ public class DownloadServiceImpl extends Service implements DownloadService catch (Throwable ignored) { } + + Log.i(TAG, "DownloadServiceImpl stopped"); } public static DownloadService getInstance() @@ -2144,44 +2150,47 @@ public class DownloadServiceImpl extends Service implements DownloadService bigView.setImageViewResource(R.id.control_play, R.drawable.media_pause_normal_dark); } - final Entry song = currentPlaying.getSong(); - final String title = song.getTitle(); - final String text = song.getArtist(); - final String album = song.getAlbum(); - final int rating = song.getUserRating() == null ? 0 : song.getUserRating(); - final int imageSize = Util.getNotificationImageSize(this); + if (currentPlaying != null) { + final Entry song = currentPlaying.getSong(); + final String title = song.getTitle(); + final String text = song.getArtist(); + final String album = song.getAlbum(); + final int rating = song.getUserRating() == null ? 0 : song.getUserRating(); + final int imageSize = Util.getNotificationImageSize(this); - try { - final Bitmap nowPlayingImage = FileUtil.getAlbumArtBitmap(this, currentPlaying.getSong(), imageSize, true); - if (nowPlayingImage == null) { - contentView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); - bigView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); - } else { - contentView.setImageViewBitmap(R.id.notification_image, nowPlayingImage); - bigView.setImageViewBitmap(R.id.notification_image, nowPlayingImage); - } - } catch (Exception x) { - Log.w(TAG, "Failed to get notification cover art", x); - contentView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); - bigView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); - } + try { + final Bitmap nowPlayingImage = FileUtil.getAlbumArtBitmap(this, currentPlaying.getSong(), imageSize, true); + if (nowPlayingImage == null) { + contentView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); + bigView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); + } else { + contentView.setImageViewBitmap(R.id.notification_image, nowPlayingImage); + bigView.setImageViewBitmap(R.id.notification_image, nowPlayingImage); + } + } catch (Exception x) { + Log.w(TAG, "Failed to get notification cover art", x); + contentView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); + bigView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); + } - contentView.setTextViewText(R.id.trackname, title); - bigView.setTextViewText(R.id.trackname, title); - contentView.setTextViewText(R.id.artist, text); - bigView.setTextViewText(R.id.artist, text); - contentView.setTextViewText(R.id.album, album); - bigView.setTextViewText(R.id.album, album); - boolean useFiveStarRating = KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING); - if (!useFiveStarRating) bigView.setViewVisibility(R.id.notification_rating, View.INVISIBLE); - else - { - bigView.setImageViewResource(R.id.notification_five_star_1, rating > 0 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark); - bigView.setImageViewResource(R.id.notification_five_star_2, rating > 1 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark); - bigView.setImageViewResource(R.id.notification_five_star_3, rating > 2 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark); - bigView.setImageViewResource(R.id.notification_five_star_4, rating > 3 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark); - bigView.setImageViewResource(R.id.notification_five_star_5, rating > 4 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark); + contentView.setTextViewText(R.id.trackname, title); + bigView.setTextViewText(R.id.trackname, title); + contentView.setTextViewText(R.id.artist, text); + bigView.setTextViewText(R.id.artist, text); + contentView.setTextViewText(R.id.album, album); + bigView.setTextViewText(R.id.album, album); + + boolean useFiveStarRating = KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING); + if (!useFiveStarRating) + bigView.setViewVisibility(R.id.notification_rating, View.INVISIBLE); + else { + bigView.setImageViewResource(R.id.notification_five_star_1, rating > 0 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark); + bigView.setImageViewResource(R.id.notification_five_star_2, rating > 1 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark); + bigView.setImageViewResource(R.id.notification_five_star_3, rating > 2 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark); + bigView.setImageViewResource(R.id.notification_five_star_4, rating > 3 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark); + bigView.setImageViewResource(R.id.notification_five_star_5, rating > 4 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark); + } } Notification notification = notificationBuilder.build(); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java index d0ca72b7..827891fb 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java @@ -132,6 +132,7 @@ public final class Constants public static final String PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY = "imageLoaderConcurrency"; public static final String PREFERENCES_KEY_FF_IMAGE_LOADER = "ff_new_image_loader"; public static final String PREFERENCES_KEY_USE_FIVE_STAR_RATING = "use_five_star_rating"; + public static final String PREFERENCES_KEY_CATEGORY_NOTIFICATIONS = "notificationsCategory"; // Number of free trial days for non-licensed servers. public static final int FREE_TRIAL_DAYS = 30; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java index 578ae72e..4c455236 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java @@ -36,6 +36,7 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.net.wifi.WifiManager; +import android.os.Build; import android.os.Environment; import android.os.Parcelable; import android.preference.PreferenceManager; @@ -155,12 +156,16 @@ public class Util extends DownloadActivity public static boolean isNotificationEnabled(Context context) { + // After API26 foreground services must be used for music playback, and they must have a notification + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) return true; SharedPreferences preferences = getPreferences(context); return preferences.getBoolean(Constants.PREFERENCES_KEY_SHOW_NOTIFICATION, false); } public static boolean isNotificationAlwaysEnabled(Context context) { + // After API26 foreground services must be used for music playback, and they must have a notification + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) return true; SharedPreferences preferences = getPreferences(context); return preferences.getBoolean(Constants.PREFERENCES_KEY_ALWAYS_SHOW_NOTIFICATION, false); } diff --git a/ultrasonic/src/main/res/xml/settings.xml b/ultrasonic/src/main/res/xml/settings.xml index 27fcff8a..eca14974 100644 --- a/ultrasonic/src/main/res/xml/settings.xml +++ b/ultrasonic/src/main/res/xml/settings.xml @@ -107,7 +107,9 @@ a:summary="@string/settings.playback.resume_play_on_headphones_plug.summary" /> - +