diff --git a/app/build.gradle b/app/build.gradle index 75a23d33..3f991f88 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,8 +25,8 @@ android { dependencies { implementation 'androidx.legacy:legacy-support-core-ui:1.0.0' - implementation 'androidx.media:media:1.2.1' - implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.media:media:1.6.0' + implementation 'com.google.android.material:material:1.6.1' implementation 'junit:junit:4.12' compileOnly 'androidx.annotation:annotation:1.0.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3b21a440..c5451e5f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,6 +73,7 @@ THE SOFTWARE. @@ -97,7 +98,8 @@ THE SOFTWARE. android:launchMode="singleInstance" /> + android:label="Vanilla Music 1x1" + android:exported="false"> @@ -107,7 +109,8 @@ THE SOFTWARE. + android:label="Vanilla Music 4x1 A" + android:exported="false"> @@ -117,7 +120,8 @@ THE SOFTWARE. + android:label="Vanilla Music 4x1 White" + android:exported="false"> @@ -127,7 +131,8 @@ THE SOFTWARE. + android:label="Vanilla Music 4x1 B" + android:exported="false"> @@ -137,7 +142,8 @@ THE SOFTWARE. + android:label="Vanilla Music 2x2 A" + android:exported="false"> @@ -147,7 +153,8 @@ THE SOFTWARE. + android:label="Vanilla Music 2x2 B" + android:exported="false"> @@ -155,12 +162,14 @@ THE SOFTWARE. android:name="android.appwidget.provider" android:resource="@xml/widget_d" /> - + - + @@ -173,7 +182,7 @@ THE SOFTWARE. + android:exported="true" /> @@ -191,14 +200,17 @@ THE SOFTWARE. + android:theme="@android:style/Theme.Translucent.NoTitleBar" + android:exported="false"> - + @@ -222,6 +234,7 @@ THE SOFTWARE. diff --git a/app/src/main/assets/about.html b/app/src/main/assets/about.html index 52f8929f..afacfb03 100644 --- a/app/src/main/assets/about.html +++ b/app/src/main/assets/about.html @@ -29,6 +29,9 @@ function show(n) { Changelog [show]
 
+NEXT
+- NEW Upgraded Android SDK level to 33
+
 1.1.0
 - FIX Unbreak plugin integration
 - FIX Always show cover art in notification
diff --git a/app/src/main/java/ch/blinkenlights/android/medialibrary/MediaMetadataExtractor.java b/app/src/main/java/ch/blinkenlights/android/medialibrary/MediaMetadataExtractor.java
index 3c444ce0..109ff8d9 100644
--- a/app/src/main/java/ch/blinkenlights/android/medialibrary/MediaMetadataExtractor.java
+++ b/app/src/main/java/ch/blinkenlights/android/medialibrary/MediaMetadataExtractor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 - 2017 Adrian Ulrich 
+ * Copyright (C) 2016 - 2022 Adrian Ulrich 
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import java.io.FileInputStream;
+import java.io.IOException;
 
 import android.util.Log;
 public class MediaMetadataExtractor extends HashMap> {
@@ -316,7 +317,12 @@ public class MediaMetadataExtractor extends HashMap> {
 		    mediaTags.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO) == null ||
 		    mediaTags.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO) != null ||
 		    mediaTags.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) == null) {
-		    mediaTags.release();
+			try {
+				mediaTags.release();
+			} catch (IOException e) {
+				Log.v("VanillaMusic", "mediaTags.release() failed: " + e);
+			}
+
 			return;
 		}
 
@@ -361,7 +367,11 @@ public class MediaMetadataExtractor extends HashMap> {
 		// if bastp was able to parse it (which is stricter than Android's own parser)
 		mIsMediaFile = (containsKey(TITLE) || containsKey(ALBUM) || containsKey(ARTIST) || !bastpType.equals(""));
 
-		mediaTags.release();
+		try {
+			mediaTags.release();
+		} catch (IOException e) {
+			Log.v("VanillaMusic", "mediaTags.release() failed: " + e);
+		}
 	}
 
 	/**
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/BottomBarControls.java b/app/src/main/java/ch/blinkenlights/android/vanilla/BottomBarControls.java
index 8e5efa3d..ecae0372 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/BottomBarControls.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/BottomBarControls.java
@@ -293,7 +293,8 @@ public class BottomBarControls extends LinearLayout
 	 * Because ...reasons.
 	 */
 	private boolean menuMargin() {
-		return ThemeHelper.usesHoloTheme() == false;
+		// Was false for holo, maybe will be true again in the future? ;-)
+		return true;
 	}
 
 	/**
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/FourLongWidget.java b/app/src/main/java/ch/blinkenlights/android/vanilla/FourLongWidget.java
index 2f689e35..bbc88d5b 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/FourLongWidget.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/FourLongWidget.java
@@ -127,16 +127,16 @@ public class FourLongWidget extends AppWidgetProvider {
 		int flags = Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 
 		intent = new Intent(context, LibraryActivity.class).setAction(Intent.ACTION_MAIN);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.cover, pendingIntent);
 		views.setOnClickPendingIntent(R.id.text_layout, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_TOGGLE_PLAYBACK);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.play_pause, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_NEXT_SONG);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.next, pendingIntent);
 
 		manager.updateAppWidget(new ComponentName(context, FourLongWidget.class), views);
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/FourSquareWidget.java b/app/src/main/java/ch/blinkenlights/android/vanilla/FourSquareWidget.java
index 16020c71..7e85fabb 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/FourSquareWidget.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/FourSquareWidget.java
@@ -129,16 +129,16 @@ public class FourSquareWidget extends AppWidgetProvider {
 		int flags = Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 
 		intent = new Intent(context, LibraryActivity.class).setAction(Intent.ACTION_MAIN);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.title, pendingIntent);
 		views.setOnClickPendingIntent(R.id.artist, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_TOGGLE_PLAYBACK);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.play_pause, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_NEXT_SONG);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.next, pendingIntent);
 
 		manager.updateAppWidget(new ComponentName(context, FourSquareWidget.class), views);
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/FourWhiteWidget.java b/app/src/main/java/ch/blinkenlights/android/vanilla/FourWhiteWidget.java
index b08e7f07..e0333562 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/FourWhiteWidget.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/FourWhiteWidget.java
@@ -131,20 +131,20 @@ public class FourWhiteWidget extends AppWidgetProvider {
 		int flags = Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 
 		intent = new Intent(context, LibraryActivity.class).setAction(Intent.ACTION_MAIN);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.cover, pendingIntent);
 		views.setOnClickPendingIntent(R.id.text_layout, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_TOGGLE_PLAYBACK);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.play_pause, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_NEXT_SONG);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.next, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_PREVIOUS_SONG);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.previous, pendingIntent);
 
 		manager.updateAppWidget(new ComponentName(context, FourWhiteWidget.class), views);
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/OneCellWidget.java b/app/src/main/java/ch/blinkenlights/android/vanilla/OneCellWidget.java
index 7b013856..6765869b 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/OneCellWidget.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/OneCellWidget.java
@@ -103,11 +103,11 @@ public class OneCellWidget extends AppWidgetProvider {
 		int flags = Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 
 		intent = ShortcutPseudoActivity.getIntent(context, doubleTap ? PlaybackService.ACTION_TOGGLE_PLAYBACK_DELAYED : PlaybackService.ACTION_TOGGLE_PLAYBACK);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.play_pause, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, doubleTap ? PlaybackService.ACTION_NEXT_SONG_DELAYED : PlaybackService.ACTION_NEXT_SONG);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.next, pendingIntent);
 
 
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackService.java b/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackService.java
index f23b5d6a..0837c960 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackService.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackService.java
@@ -533,9 +533,7 @@ public final class PlaybackService extends Service
 		mAccelLast = SensorManager.GRAVITY_EARTH;
 		setupSensor();
 
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-			ScheduledLibraryUpdate.scheduleUpdate(this);
-		}
+		ScheduledLibraryUpdate.scheduleUpdate(this);
 	}
 
 	@Override
@@ -806,9 +804,6 @@ public final class PlaybackService extends Service
 		if(mMediaPlayerInitialized != true)
 			return;
 
-		if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
-			return; /* setNextMediaPlayer is supported since JB */
-
 		boolean doGapless = false;
 		int fa = finishAction(mState);
 		Song nextSong = getSong(1);
@@ -2110,11 +2105,11 @@ public final class PlaybackService extends Service
 		case NOT_ACTION_NEXT_SONG: {
 			Intent intent = new Intent(this, PlaybackService.class);
 			intent.setAction(PlaybackService.ACTION_NEXT_SONG_AUTOPLAY);
-			return PendingIntent.getService(this, 0, intent, 0);
+			return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		}
 		case NOT_ACTION_MINI_ACTIVITY: {
 			Intent intent = new Intent(this, MiniPlaybackActivity.class);
-			return PendingIntent.getActivity(this, 0, intent, 0);
+			return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		}
 		default:
 			Log.w("VanillaMusic", "Unknown value for notification_action. Defaulting to 0.");
@@ -2122,12 +2117,12 @@ public final class PlaybackService extends Service
 		case NOT_ACTION_MAIN_ACTIVITY: {
 			Intent intent = new Intent(this, LibraryActivity.class);
 			intent.setAction(Intent.ACTION_MAIN);
-			return PendingIntent.getActivity(this, 0, intent, 0);
+			return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		}
 		case NOT_ACTION_FULL_ACTIVITY: {
 			Intent intent = new Intent(this, FullPlaybackActivity.class);
 			intent.setAction(Intent.ACTION_MAIN);
-			return PendingIntent.getActivity(this, 0, intent, 0);
+			return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		}
 		}
 	}
@@ -2168,11 +2163,11 @@ public final class PlaybackService extends Service
 			.setSubText(song.artist)
 			.setContentIntent(mNotificationAction)
 			.addAction(new NotificationCompat.Action(R.drawable.previous,
-													 getString(R.string.previous_song), PendingIntent.getService(this, 0, previous, 0)))
+													 getString(R.string.previous_song), PendingIntent.getService(this, 0, previous, PendingIntent.FLAG_IMMUTABLE)))
 			.addAction(new NotificationCompat.Action(playButton,
-													 getString(R.string.play_pause), PendingIntent.getService(this, 0, playPause, 0)))
+													 getString(R.string.play_pause), PendingIntent.getService(this, 0, playPause, PendingIntent.FLAG_IMMUTABLE)))
 			.addAction(new NotificationCompat.Action(R.drawable.next,
-													 getString(R.string.next_song), PendingIntent.getService(this, 0, next, 0)))
+													 getString(R.string.next_song), PendingIntent.getService(this, 0, next, PendingIntent.FLAG_IMMUTABLE)))
 			.setStyle(new androidx.media.app.NotificationCompat.MediaStyle()
 					  .setMediaSession(mMediaSessionTracker.getSessionToken())
 					  .setShowActionsInCompactView(0, 1, 2))
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/PlaylistObserver.java b/app/src/main/java/ch/blinkenlights/android/vanilla/PlaylistObserver.java
index bc29526a..2b9017a3 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/PlaylistObserver.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/PlaylistObserver.java
@@ -152,11 +152,7 @@ public class PlaylistObserver extends SQLiteOpenHelper implements Handler.Callba
 		MediaLibrary.unregisterLibraryObserver(mLibraryObserver);
 		mFileObserver.stopWatching();
 
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
-			mHandlerThread.quitSafely();
-		} else {
-			mHandlerThread.quit();
-		}
+		mHandlerThread.quitSafely();
 		mHandlerThread = null;
 		mHandler = null;
 	}
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/PreferencesActivity.java b/app/src/main/java/ch/blinkenlights/android/vanilla/PreferencesActivity.java
index 54455eff..fe3103b3 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/PreferencesActivity.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/PreferencesActivity.java
@@ -86,9 +86,7 @@ public class PreferencesActivity extends PreferenceActivity
 		loadHeadersFromResource(R.xml.preference_headers, tmp);
 
 		for(Header obj : tmp) {
-			// Themes are 5.x only, so do not add PreferencesTheme on holo devices
-			if (!ThemeHelper.usesHoloTheme() || !obj.fragment.equals(PreferencesTheme.class.getName()))
-				target.add(obj);
+			target.add(obj);
 		}
 	}
 
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/RemoteControl.java b/app/src/main/java/ch/blinkenlights/android/vanilla/RemoteControl.java
index e28a73b3..eaec8296 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/RemoteControl.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/RemoteControl.java
@@ -27,10 +27,7 @@ public class RemoteControl {
 	 * Returns a RemoteControl.Client implementation
 	 */
 	public RemoteControl.Client getClient(Context context) {
-		return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
-			new RemoteControlImplLp(context) :
-			new RemoteControlImplICS(context)  // legacy implementation, kept until we drop 4.x support
-		);
+		return new RemoteControlImplLp(context);
 	}
 
 	/**
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/RemoteControlImplICS.java b/app/src/main/java/ch/blinkenlights/android/vanilla/RemoteControlImplICS.java
deleted file mode 100644
index 2d185f32..00000000
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/RemoteControlImplICS.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2015 Adrian Ulrich 
- * Copyright (C) 2012 Christopher Eby 
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-package ch.blinkenlights.android.vanilla;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.graphics.Bitmap;
-import android.media.AudioManager;
-import android.media.MediaMetadataRetriever;
-import android.media.RemoteControlClient;
-
-public class RemoteControlImplICS implements RemoteControl.Client {
-	/**
-	 * Context of this instance
-	 */
-	private final Context mContext;
-	/**
-	 * Used with updateRemote method.
-	 */
-	private RemoteControlClient mRemote;
-	/**
-	 * Whether the cover should be shown. 1 for yes, 0 for no, -1 for
-	 * uninitialized.
-	 */
-	private int mShowCover = -1;
-
-	/**
-	 * Creates a new instance
-	 *
-	 * @param context The context to use
-	 */
-	public RemoteControlImplICS(Context context) {
-		mContext = context;
-	}
-
-	/**
-	 * Perform initialization required for RemoteControlClient.
-	 */
-	public void initializeRemote() {
-		// make sure there is only one registered remote
-		unregisterRemote();
-
-		// Receive 'background' play button events
-		AudioManager audioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
-		ComponentName receiver = new ComponentName(mContext.getPackageName(), MediaButtonReceiver.class.getName());
-		audioManager.registerMediaButtonEventReceiver(receiver);
-
-		Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
-		mediaButtonIntent.setComponent(new ComponentName(mContext.getPackageName(), MediaButtonReceiver.class.getName()));
-		PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(mContext, 0, mediaButtonIntent, 0);
-		RemoteControlClient remote = new RemoteControlClient(mediaPendingIntent);
-
-		// Things we can do (eg: buttons to display on lock screen)
-		int flags = RemoteControlClient.FLAG_KEY_MEDIA_NEXT
-			| RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
-			| RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
-			| RemoteControlClient.FLAG_KEY_MEDIA_PLAY
-			| RemoteControlClient.FLAG_KEY_MEDIA_PAUSE;
-		remote.setTransportControlFlags(flags);
-
-		audioManager.registerRemoteControlClient(remote);
-		mRemote = remote;
-	}
-
-	/**
-	 * Unregisters a remote control client
-	 */
-	public void unregisterRemote() {
-		if (mRemote != null) {
-			AudioManager audioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
-			ComponentName receiver = new ComponentName(mContext.getPackageName(), MediaButtonReceiver.class.getName());
-			audioManager.unregisterMediaButtonEventReceiver(receiver);
-			audioManager.unregisterRemoteControlClient(mRemote);
-			mRemote = null;
-		}
-	}
-
-	/**
-	 * Uninitializes our cached preferences, forcing a reload
-	 */
-	public void reloadPreference()
-	{
-		mShowCover = -1;
-	}
-
-	/**
-	 * Update the remote with new metadata.
-	 * {@link #initializeRemote()} must have been called
-	 * first.
-	 *
-	 * @param song The song containing the new metadata.
-	 * @param state PlaybackService state, used to determine playback state.
-	 * @param keepPaused whether or not to keep the remote updated in paused mode
-	 */
-	public void updateRemote(Song song, int state, boolean keepPaused)
-	{
-		RemoteControlClient remote = mRemote;
-		if (remote == null)
-			return;
-
-		boolean isPlaying = ((state & PlaybackService.FLAG_PLAYING) != 0);
-
-		if (mShowCover == -1) {
-			SharedPreferences settings = SharedPrefHelper.getSettings(mContext);
-			mShowCover = settings.getBoolean(PrefKeys.COVER_ON_LOCKSCREEN, PrefDefaults.COVER_ON_LOCKSCREEN) ? 1 : 0;
-		}
-
-		remote.setPlaybackState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED);
-		RemoteControlClient.MetadataEditor editor = remote.editMetadata(true);
-		if (song != null && song.artist != null && song.album != null) {
-			String artist_album = song.artist + " - " + song.album;
-			artist_album = (song.artist.length() == 0 ? song.album : artist_album); // no artist ? -> only display album
-			artist_album = (song.album.length() == 0 ? song.artist : artist_album); // no album ? -> only display artist
-
-			editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, artist_album);
-			editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, song.title);
-			Bitmap bitmap = song.getMediumCover(mContext);
-			if (bitmap != null  && mShowCover == 1 && (isPlaying || keepPaused)) {
-				// Create a copy of the cover art, since RemoteControlClient likes
-				// to recycle what we give it.
-				bitmap = bitmap.copy(Bitmap.Config.RGB_565, false);
-			} else {
-				// Some lockscreen implementations fail to clear the cover artwork
-				// if we send a null bitmap. We are creating a 16x16 transparent
-				// bitmap to work around this limitation.
-				bitmap = Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888);
-			}
-			editor.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, bitmap);
-		}
-		editor.apply();
-	}
-}
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/ThemeHelper.java b/app/src/main/java/ch/blinkenlights/android/vanilla/ThemeHelper.java
index 87e9a351..a8848695 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/ThemeHelper.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/ThemeHelper.java
@@ -38,31 +38,28 @@ public class ThemeHelper {
 	 * and platform API.
 	 */
 	final public static int getThemeResource(Context context, int theme) {
-		if(usesHoloTheme() == false) {
-			TypedArray ar = null;
-
-			switch (theme) {
-			case R.style.Playback:
-				ar = context.getResources().obtainTypedArray(R.array.theme_category_playback);
-				break;
-			case R.style.Library:
-				ar = context.getResources().obtainTypedArray(R.array.theme_category_library);
-				break;
-			case R.style.BackActionBar:
-				ar = context.getResources().obtainTypedArray(R.array.theme_category_backactionbar);
-				break;
-			case R.style.PopupDialog:
-				ar = context.getResources().obtainTypedArray(R.array.theme_category_popupdialog);
-				break;
-			case R.style.BottomSheetDialog:
-				ar = context.getResources().obtainTypedArray(R.array.theme_category_bottomsheetdialog);
-				break;
-			default:
-				throw new IllegalArgumentException("setTheme() called with unknown theme!");
-			}
-			theme = ar.getResourceId(getSelectedThemeIndex(context), -1);
-			ar.recycle();
+		TypedArray ar = null;
+		switch (theme) {
+		case R.style.Playback:
+			ar = context.getResources().obtainTypedArray(R.array.theme_category_playback);
+			break;
+		case R.style.Library:
+			ar = context.getResources().obtainTypedArray(R.array.theme_category_library);
+			break;
+		case R.style.BackActionBar:
+			ar = context.getResources().obtainTypedArray(R.array.theme_category_backactionbar);
+			break;
+		case R.style.PopupDialog:
+			ar = context.getResources().obtainTypedArray(R.array.theme_category_popupdialog);
+			break;
+		case R.style.BottomSheetDialog:
+			ar = context.getResources().obtainTypedArray(R.array.theme_category_bottomsheetdialog);
+			break;
+		default:
+			throw new IllegalArgumentException("setTheme() called with unknown theme!");
 		}
+		theme = ar.getResourceId(getSelectedThemeIndex(context), -1);
+		ar.recycle();
 		return theme;
 	}
 
@@ -73,14 +70,7 @@ public class ThemeHelper {
 	 */
 	final public static int getPlayButtonResource(boolean playing)
 	{
-		int playButton = 0;
-		if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-			// Android >= 5.0 uses the dark version of this drawable
-			playButton = playing ? R.drawable.widget_pause : R.drawable.widget_play;
-		} else {
-			playButton = playing ? R.drawable.pause : R.drawable.play;
-		}
-		return playButton;
+		return playing ? R.drawable.widget_pause : R.drawable.widget_play;
 	}
 
 	/**
@@ -89,12 +79,9 @@ public class ThemeHelper {
 	 */
 	final private static boolean usesDarkTheme(Context context)
 	{
-		boolean useDark = false;
-		if(usesHoloTheme() == false) {
-			final int idx = getSelectedThemeIndex(context);
-			final String[] variants = context.getResources().getStringArray(R.array.theme_variant);
-			useDark = variants[idx].equals("dark");
-		}
+		final int idx = getSelectedThemeIndex(context);
+		final String[] variants = context.getResources().getStringArray(R.array.theme_variant);
+		boolean useDark = variants[idx].equals("dark");
 		return useDark;
 	}
 
@@ -117,13 +104,6 @@ public class ThemeHelper {
 		return 0;
 	}
 
-	/**
-	 * Returns TRUE if this device uses the HOLO (android 4) theme
-	 */
-	final public static boolean usesHoloTheme() {
-		return (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP);
-	}
-
 	/**
 	 * Fetches a color resource from the current theme
 	 */
@@ -138,9 +118,6 @@ public class ThemeHelper {
 	 * Returns the color to be used to draw the placeholder cover.
 	 */
 	final public static int[] getDefaultCoverColors(Context context) {
-		if (usesHoloTheme()) // pre material device
-			return new int[] { 0xff000000, 0xff404040 };
-
 		int bg = fetchThemeColor(context, android.R.attr.colorBackground);
 		int diff = 0x00171717 * (bg > 0xFF888888 ? -1 : 1);
 		return new int[]{ bg, bg+diff };
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/WidgetD.java b/app/src/main/java/ch/blinkenlights/android/vanilla/WidgetD.java
index 92857b94..6e008c18 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/WidgetD.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/WidgetD.java
@@ -124,27 +124,27 @@ public class WidgetD extends AppWidgetProvider {
 		int flags = Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 
 		intent = new Intent(context, LibraryActivity.class).setAction(Intent.ACTION_MAIN);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.cover, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_TOGGLE_PLAYBACK);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.play_pause, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_NEXT_SONG);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.next, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_PREVIOUS_SONG);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.previous, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_CYCLE_SHUFFLE);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.shuffle, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_CYCLE_REPEAT);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.end_action, pendingIntent);
 
 		manager.updateAppWidget(new ComponentName(context, WidgetD.class), views);
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/WidgetE.java b/app/src/main/java/ch/blinkenlights/android/vanilla/WidgetE.java
index 2ac5d1bd..6431247e 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/WidgetE.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/WidgetE.java
@@ -114,28 +114,28 @@ public class WidgetE extends AppWidgetProvider {
 		int flags = Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 
 		intent = new Intent(context, LibraryActivity.class).setAction(Intent.ACTION_MAIN);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.title, pendingIntent);
 		views.setOnClickPendingIntent(R.id.artist, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_TOGGLE_PLAYBACK);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.play_pause, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_NEXT_SONG);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.next, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_PREVIOUS_SONG);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.previous, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_CYCLE_SHUFFLE);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.shuffle, pendingIntent);
 
 		intent = ShortcutPseudoActivity.getIntent(context, PlaybackService.ACTION_CYCLE_REPEAT);
-		pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+		pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 		views.setOnClickPendingIntent(R.id.end_action, pendingIntent);
 
 		manager.updateAppWidget(new ComponentName(context, WidgetE.class), views);
diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/ui/FancyMenu.java b/app/src/main/java/ch/blinkenlights/android/vanilla/ui/FancyMenu.java
index ea513abc..3e3059cf 100644
--- a/app/src/main/java/ch/blinkenlights/android/vanilla/ui/FancyMenu.java
+++ b/app/src/main/java/ch/blinkenlights/android/vanilla/ui/FancyMenu.java
@@ -151,13 +151,9 @@ public class FancyMenu {
 	 */
 	private Adapter assembleAdapter(ArrayList> items) {
 	    final Adapter adapter = new Adapter(mContext, 0);
-		// spacers look awful on holo themes
-		final boolean usesSpacers = !ThemeHelper.usesHoloTheme();
 		for (ArrayList sub : items) {
 			for (FancyMenuItem item : sub ) {
-				if (usesSpacers || !item.isSpacer()) {
-					adapter.add(item);
-				}
+				adapter.add(item);
 			}
 		}
 		return adapter;
diff --git a/build.gradle b/build.gradle
index 59803e7f..36d41c83 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@ allprojects {
     }
 }
 ext {
-    compileSdkVersion = 30
-    targetSdkVersion = 30
-    minSdkVersion = 15
+    compileSdkVersion = 33
+    targetSdkVersion = 33
+    minSdkVersion = 26
 }