From ff824d19aabb1df5d46a4b35de00b7d2e7ef913f Mon Sep 17 00:00:00 2001
From: birdbird <6892457-tzugen@users.noreply.gitlab.com>
Date: Sun, 18 Dec 2022 10:55:13 +0000
Subject: [PATCH] Fix some crashes
---
build.gradle | 1 +
gradle/libs.versions.toml | 10 +-
ultrasonic/build.gradle | 2 +
.../DefaultMediaNotificationProvider2.java | 639 ------------------
.../ultrasonic/fragment/SettingsFragment.kt | 29 -
.../playback/CustomNotificationProvider.kt | 7 +-
.../ultrasonic/service/DownloadService.kt | 81 ++-
.../moire/ultrasonic/service/DownloadTask.kt | 3 +
.../org/moire/ultrasonic/util/Settings.kt | 4 +-
.../org/moire/ultrasonic/util/Storage.kt | 2 +
ultrasonic/src/main/res/values-de/strings.xml | 3 +-
ultrasonic/src/main/res/values-es/strings.xml | 3 +-
ultrasonic/src/main/res/values-nl/strings.xml | 3 +-
.../src/main/res/values-pt-rBR/strings.xml | 3 +-
ultrasonic/src/main/res/values/strings.xml | 3 +-
15 files changed, 70 insertions(+), 723 deletions(-)
delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/DefaultMediaNotificationProvider2.java
diff --git a/build.gradle b/build.gradle
index efa20eed..0e2c097b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,6 +11,7 @@ buildscript {
google()
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
+ maven { url 'https://jitpack.io' }
}
dependencies {
classpath libs.gradle
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 3b656efc..06a4ca75 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -9,7 +9,7 @@ ktlint = "0.43.2"
ktlintGradle = "11.0.0"
detekt = "1.22.0"
preferences = "1.2.0"
-media3 = "1.0.0-beta03"
+media3 = "f3e450e783"
androidSupport = "1.5.0"
materialDesign = "1.6.1"
@@ -64,9 +64,11 @@ navigationUiKtx = { module = "androidx.navigation:navigation-ui-ktx", ve
navigationFeature = { module = "androidx.navigation:navigation-dynamic-features-fragment", version.ref = "navigation" }
navigationSafeArgs = { module = "androidx.navigation:navigation-safe-args-gradle-plugin", version.ref = "navigation"}
preferences = { module = "androidx.preference:preference", version.ref = "preferences" }
-media3exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
-media3okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3" }
-media3session = { module = "androidx.media3:media3-session", version.ref = "media3" }
+media3common = { module = "com.github.androidx.media:media3-common", version.ref = "media3" }
+media3exoplayer = { module = "com.github.androidx.media:media3-exoplayer", version.ref = "media3" }
+media3datasource = { module = "com.github.androidx.media:media3-datasource", version.ref = "media3" }
+media3okhttp = { module = "com.github.androidx.media:media3-datasource-okhttp", version.ref = "media3" }
+media3session = { module = "com.github.androidx.media:media3-session", version.ref = "media3" }
swipeRefresh = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swipeRefresh" }
kotlinStdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle
index f5cd922c..15f1a548 100644
--- a/ultrasonic/build.gradle
+++ b/ultrasonic/build.gradle
@@ -104,8 +104,10 @@ dependencies {
implementation libs.viewModelKtx
implementation libs.constraintLayout
implementation libs.preferences
+ implementation libs.media3common
implementation libs.media3exoplayer
implementation libs.media3session
+ implementation libs.media3datasource
implementation libs.media3okhttp
implementation libs.swipeRefresh
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DefaultMediaNotificationProvider2.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DefaultMediaNotificationProvider2.java
deleted file mode 100644
index 405d4fb2..00000000
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DefaultMediaNotificationProvider2.java
+++ /dev/null
@@ -1,639 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package org.moire.ultrasonic.service;
-
-import static androidx.media3.common.C.INDEX_UNSET;
-import static androidx.media3.common.Player.COMMAND_INVALID;
-import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE;
-import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT;
-import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
-import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS;
-import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
-import static androidx.media3.common.Player.COMMAND_STOP;
-import static androidx.media3.common.Player.STATE_ENDED;
-import static androidx.media3.common.util.Assertions.checkState;
-import static androidx.media3.common.util.Assertions.checkStateNotNull;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import androidx.annotation.DoNotInline;
-import androidx.annotation.DrawableRes;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.StringRes;
-import androidx.core.app.NotificationCompat;
-import androidx.core.graphics.drawable.IconCompat;
-import androidx.media3.common.C;
-import androidx.media3.common.MediaMetadata;
-import androidx.media3.common.Player;
-import androidx.media3.common.util.Log;
-import androidx.media3.common.util.UnstableApi;
-import androidx.media3.common.util.Util;
-import androidx.media3.session.CommandButton;
-import androidx.media3.session.MediaController;
-import androidx.media3.session.MediaNotification;
-import androidx.media3.session.MediaSession;
-import androidx.media3.session.MediaStyleNotificationHelper;
-import androidx.media3.session.SessionCommand;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.errorprone.annotations.CanIgnoreReturnValue;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
-import org.moire.ultrasonic.R;
-
-/**
- * The default {@link MediaNotification.Provider}.
- *
- *
Actions
- *
- * The following actions are included in the provided notifications:
- *
- *
- * - {@link MediaController#COMMAND_PLAY_PAUSE} to start or pause playback.
- *
- {@link MediaController#COMMAND_SEEK_TO_PREVIOUS} to seek to the previous item.
- *
- {@link MediaController#COMMAND_SEEK_TO_NEXT} to seek to the next item.
- *
- *
- * Custom commands
- *
- * Custom actions are sent to the session under the hood. You can receive them by overriding the
- * session callback method {@link MediaSession.Callback#onCustomCommand(MediaSession,
- * MediaSession.ControllerInfo, SessionCommand, Bundle)}. This is useful because starting with
- * Android 13, the System UI notification sends commands directly to the session. So handling the
- * custom commands on the session level allows you to handle them at the same callback for all API
- * levels.
- *
- * Drawables
- *
- * The drawables used can be overridden by drawables with the same names defined the application.
- * The drawables are:
- *
- *
- * - {@code media3_notification_play} - The play icon.
- *
- {@code media3_notification_pause} - The pause icon.
- *
- {@code media3_notification_seek_to_previous} - The previous icon.
- *
- {@code media3_notification_seek_to_next} - The next icon.
- *
- {@code media3_notification_small_icon} - The {@link
- * NotificationCompat.Builder#setSmallIcon(int) small icon}. A different icon can be set with
- * {@link #setSmallIcon(int)}.
- *
- *
- * String resources
- *
- * String resources used can be overridden by resources with the same names defined the application.
- * These are:
- *
- *
- * - {@code media3_controls_play_description} - The description of the play icon.
- *
- {@code media3_controls_pause_description} - The description of the pause icon.
- *
- {@code media3_controls_seek_to_previous_description} - The description of the
- * previous icon.
- *
- {@code media3_controls_seek_to_next_description} - The description of the next icon.
- *
- {@code default_notification_channel_name} The name of the {@link
- * NotificationChannel} on which created notifications are posted. A different string resource
- * can be set when constructing the provider with {@link
- * DefaultMediaNotificationProvider2.Builder#setChannelName(int)}.
- *
- */
-@UnstableApi
-public class DefaultMediaNotificationProvider2 implements MediaNotification.Provider {
-
- /** A builder for {@link DefaultMediaNotificationProvider2} instances. */
- public static final class Builder {
- private final Context context;
- private NotificationIdProvider notificationIdProvider;
- private String channelId;
- @StringRes private int channelNameResourceId;
- private boolean built;
-
- /**
- * Creates a builder.
- *
- * @param context Any {@link Context}.
- */
- public Builder(Context context) {
- this.context = context;
- notificationIdProvider = session -> DEFAULT_NOTIFICATION_ID;
- channelId = DEFAULT_CHANNEL_ID;
- channelNameResourceId = DEFAULT_CHANNEL_NAME_RESOURCE_ID;
- }
-
- /**
- * Sets the {@link MediaNotification#notificationId} used for the created notifications. By
- * default, this is set to {@link #DEFAULT_NOTIFICATION_ID}.
- *
- * Overwrites anything set in {@link #setNotificationIdProvider(NotificationIdProvider)}.
- *
- * @param notificationId The notification ID.
- * @return This builder.
- */
- @CanIgnoreReturnValue
- public Builder setNotificationId(int notificationId) {
- this.notificationIdProvider = session -> notificationId;
- return this;
- }
-
- /**
- * Sets the provider for the {@link MediaNotification#notificationId} used for the created
- * notifications. By default, this is set to a provider that always returns {@link
- * #DEFAULT_NOTIFICATION_ID}.
- *
- *
Overwrites anything set in {@link #setNotificationId(int)}.
- *
- * @param notificationIdProvider The notification ID provider.
- * @return This builder.
- */
- @CanIgnoreReturnValue
- public Builder setNotificationIdProvider(NotificationIdProvider notificationIdProvider) {
- this.notificationIdProvider = notificationIdProvider;
- return this;
- }
-
- /**
- * Sets the ID of the {@link NotificationChannel} on which created notifications are posted on.
- * By default, this is set to {@link #DEFAULT_CHANNEL_ID}.
- *
- * @param channelId The channel ID.
- * @return This builder.
- */
- @CanIgnoreReturnValue
- public Builder setChannelId(String channelId) {
- this.channelId = channelId;
- return this;
- }
-
- /**
- * Sets the name of the {@link NotificationChannel} on which created notifications are posted
- * on. By default, this is set to {@link #DEFAULT_CHANNEL_NAME_RESOURCE_ID}.
- *
- * @param channelNameResourceId The string resource ID with the channel name.
- * @return This builder.
- */
- @CanIgnoreReturnValue
- public Builder setChannelName(@StringRes int channelNameResourceId) {
- this.channelNameResourceId = channelNameResourceId;
- return this;
- }
-
- /**
- * Builds the {@link DefaultMediaNotificationProvider2}. The method can be called at most once.
- */
- public DefaultMediaNotificationProvider2 build() {
- checkState(!built);
- DefaultMediaNotificationProvider2 provider = new DefaultMediaNotificationProvider2(this);
- built = true;
- return provider;
- }
- }
-
- /**
- * Provides notification IDs for posting media notifications for given media sessions.
- *
- * @see Builder#setNotificationIdProvider(NotificationIdProvider)
- */
- public interface NotificationIdProvider {
- /** Returns the notification ID for the media notification of the given session. */
- int getNotificationId(MediaSession mediaSession);
- }
-
- /**
- * An extras key that can be used to define the index of a {@link CommandButton} in {@linkplain
- * Notification.MediaStyle#setShowActionsInCompactView(int...) compact view}.
- */
- public static final String COMMAND_KEY_COMPACT_VIEW_INDEX =
- "androidx.media3.session.command.COMPACT_VIEW_INDEX";
-
- /** The default ID used for the {@link MediaNotification#notificationId}. */
- public static final int DEFAULT_NOTIFICATION_ID = 1001;
- /**
- * The default ID used for the {@link NotificationChannel} on which created notifications are
- * posted on.
- */
- public static final String DEFAULT_CHANNEL_ID = "default_channel_id";
- /**
- * The default name used for the {@link NotificationChannel} on which created notifications are
- * posted on.
- */
- @StringRes
- public static final int DEFAULT_CHANNEL_NAME_RESOURCE_ID =
- R.string.default_notification_channel_name;
-
- private static final String TAG = "NotificationProvider";
-
- private final Context context;
- private final NotificationIdProvider notificationIdProvider;
- private final String channelId;
- @StringRes private final int channelNameResourceId;
- private final NotificationManager notificationManager;
- // Cache the last bitmap load request to avoid reloading the bitmap again, particularly useful
- // when showing a notification for the same item (e.g. when switching from playing to paused).
- private final Handler mainHandler;
-
- private @MonotonicNonNull OnBitmapLoadedFutureCallback pendingOnBitmapLoadedFutureCallback;
- @DrawableRes private int smallIconResourceId;
-
- public DefaultMediaNotificationProvider2(Builder builder) {
- this.context = builder.context;
- this.notificationIdProvider = builder.notificationIdProvider;
- this.channelId = builder.channelId;
- this.channelNameResourceId = builder.channelNameResourceId;
- notificationManager =
- checkStateNotNull(
- (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE));
- mainHandler = new Handler(Looper.getMainLooper());
- smallIconResourceId = R.drawable.media3_notification_small_icon;
- }
-
- // MediaNotification.Provider implementation
-
- @Override
- public final MediaNotification createNotification(
- MediaSession mediaSession,
- ImmutableList customLayout,
- MediaNotification.ActionFactory actionFactory,
- Callback onNotificationChangedCallback) {
- ensureNotificationChannel();
-
- Player player = mediaSession.getPlayer();
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);
- int notificationId = notificationIdProvider.getNotificationId(mediaSession);
-
- MediaStyleNotificationHelper.MediaStyle mediaStyle = new MediaStyleNotificationHelper.MediaStyle(mediaSession);
- int[] compactViewIndices =
- addNotificationActions(
- mediaSession,
- getMediaButtons(
- player.getAvailableCommands(),
- customLayout,
- /* showPauseButton= */ player.getPlayWhenReady()
- && player.getPlaybackState() != STATE_ENDED),
- builder,
- actionFactory);
- mediaStyle.setShowActionsInCompactView(compactViewIndices);
-
- // Set metadata info in the notification.
- MediaMetadata metadata = player.getMediaMetadata();
- builder
- .setContentTitle(getNotificationContentTitle(metadata))
- .setContentText(getNotificationContentText(metadata));
- @Nullable
- ListenableFuture bitmapFuture =
- mediaSession.getBitmapLoader().loadBitmapFromMetadata(metadata);
- if (bitmapFuture != null) {
- if (pendingOnBitmapLoadedFutureCallback != null) {
- pendingOnBitmapLoadedFutureCallback.discardIfPending();
- }
- if (bitmapFuture.isDone()) {
- try {
- builder.setLargeIcon(Futures.getDone(bitmapFuture));
- } catch (ExecutionException e) {
- Log.w(TAG, "Failed to load bitmap", e);
- }
- } else {
- pendingOnBitmapLoadedFutureCallback =
- new OnBitmapLoadedFutureCallback(
- notificationId, builder, onNotificationChangedCallback);
- Futures.addCallback(
- bitmapFuture,
- pendingOnBitmapLoadedFutureCallback,
- // This callback must be executed on the next looper iteration, after this method has
- // returned a media notification.
- mainHandler::post);
- }
- }
-
- if (player.isCommandAvailable(COMMAND_STOP) || Util.SDK_INT < 21) {
- // We must include a cancel intent for pre-L devices.
- mediaStyle.setCancelButtonIntent(
- actionFactory.createMediaActionPendingIntent(mediaSession, COMMAND_STOP));
- }
-
- long playbackStartTimeMs = getPlaybackStartTimeEpochMs(player);
- boolean displayElapsedTimeWithChronometer = playbackStartTimeMs != C.TIME_UNSET;
- builder
- .setWhen(playbackStartTimeMs)
- .setShowWhen(displayElapsedTimeWithChronometer)
- .setUsesChronometer(displayElapsedTimeWithChronometer);
-
- Notification notification =
- builder
- .setContentIntent(mediaSession.getSessionActivity())
- .setDeleteIntent(
- actionFactory.createMediaActionPendingIntent(mediaSession, COMMAND_STOP))
- .setOnlyAlertOnce(true)
- .setSmallIcon(smallIconResourceId)
- .setStyle(mediaStyle)
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
- .setOngoing(false)
- .build();
- return new MediaNotification(notificationId, notification);
- }
-
- @Override
- public final boolean handleCustomCommand(MediaSession session, String action, Bundle extras) {
- // Make the custom action being delegated to the session as a custom session command.
- return false;
- }
-
- // Other methods
-
- /**
- * Sets the small icon of the notification which is also shown in the system status bar.
- *
- * @see NotificationCompat.Builder#setSmallIcon(int)
- * @param smallIconResourceId The resource id of the small icon.
- */
- public final void setSmallIcon(@DrawableRes int smallIconResourceId) {
- this.smallIconResourceId = smallIconResourceId;
- }
-
- /**
- * Returns the ordered list of {@linkplain CommandButton command buttons} to be used to build the
- * notification.
- *
- * This method is called each time a new notification is built.
- *
- *
Override this method to customize the buttons on the notification. Commands of the buttons
- * returned by this method must be contained in {@link MediaController#getAvailableCommands()}.
- *
- *
By default, the notification shows {@link Player#COMMAND_PLAY_PAUSE} in {@linkplain
- * Notification.MediaStyle#setShowActionsInCompactView(int...) compact view}. This can be
- * customized by defining the index of the command in compact view of up to 3 commands in their
- * extras with key {@link DefaultMediaNotificationProvider2#COMMAND_KEY_COMPACT_VIEW_INDEX}.
- *
- *
To make the custom layout and commands work, you need to {@linkplain
- * MediaSession#setCustomLayout(List) set the custom layout of commands} and add the custom
- * commands to the available commands when a controller {@linkplain
- * MediaSession.Callback#onConnect(MediaSession, MediaSession.ControllerInfo) connects to the
- * session}. Controllers that connect after you called {@link MediaSession#setCustomLayout(List)}
- * need the custom command set in {@link MediaSession.Callback#onPostConnect(MediaSession,
- * MediaSession.ControllerInfo)} also.
- *
- * @param playerCommands The available player commands.
- * @param customLayout The {@linkplain MediaSession#setCustomLayout(List) custom layout of
- * commands}.
- * @param showPauseButton Whether the notification should show a pause button (e.g., because the
- * player is currently playing content), otherwise show a play button to start playback.
- * @return The ordered list of command buttons to be placed on the notification.
- */
- protected List getMediaButtons(
- Player.Commands playerCommands, List customLayout, boolean showPauseButton) {
- // Skip to previous action.
- List commandButtons = new ArrayList<>();
- if (playerCommands.containsAny(COMMAND_SEEK_TO_PREVIOUS, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)) {
- Bundle commandButtonExtras = new Bundle();
- commandButtonExtras.putInt(COMMAND_KEY_COMPACT_VIEW_INDEX, INDEX_UNSET);
- commandButtons.add(
- new CommandButton.Builder()
- .setPlayerCommand(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
- .setIconResId(R.drawable.media3_notification_seek_to_previous)
- .setDisplayName(
- context.getString(R.string.media3_controls_seek_to_previous_description))
- .setExtras(commandButtonExtras)
- .build());
- }
- if (playerCommands.contains(COMMAND_PLAY_PAUSE)) {
- Bundle commandButtonExtras = new Bundle();
- commandButtonExtras.putInt(COMMAND_KEY_COMPACT_VIEW_INDEX, INDEX_UNSET);
- commandButtons.add(
- new CommandButton.Builder()
- .setPlayerCommand(COMMAND_PLAY_PAUSE)
- .setIconResId(
- showPauseButton
- ? R.drawable.media3_notification_pause
- : R.drawable.media3_notification_play)
- .setExtras(commandButtonExtras)
- .setDisplayName(
- showPauseButton
- ? context.getString(R.string.media3_controls_pause_description)
- : context.getString(R.string.media3_controls_play_description))
- .build());
- }
- // Skip to next action.
- if (playerCommands.containsAny(COMMAND_SEEK_TO_NEXT, COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)) {
- Bundle commandButtonExtras = new Bundle();
- commandButtonExtras.putInt(COMMAND_KEY_COMPACT_VIEW_INDEX, INDEX_UNSET);
- commandButtons.add(
- new CommandButton.Builder()
- .setPlayerCommand(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
- .setIconResId(R.drawable.media3_notification_seek_to_next)
- .setExtras(commandButtonExtras)
- .setDisplayName(context.getString(R.string.media3_controls_seek_to_next_description))
- .build());
- }
- for (int i = 0; i < customLayout.size(); i++) {
- CommandButton button = customLayout.get(i);
- if (button.sessionCommand != null
- && button.sessionCommand.commandCode == SessionCommand.COMMAND_CODE_CUSTOM) {
- commandButtons.add(button);
- }
- }
- return commandButtons;
- }
-
- /**
- * Adds the media buttons to the notification builder for the given action factory.
- *
- * The list of {@code mediaButtons} is the list resulting from {@link #getMediaButtons(
- * Player.Commands, List, boolean)}.
- *
- *
Override this method to customize how the media buttons {@linkplain
- * NotificationCompat.Builder#addAction(NotificationCompat.Action) are added} to the notification
- * and define which actions are shown in compact view by returning the indices of the buttons to
- * be shown in compact view.
- *
- *
By default, {@link Player#COMMAND_PLAY_PAUSE} is shown in compact view, unless some of the
- * buttons are marked with {@link DefaultMediaNotificationProvider2#COMMAND_KEY_COMPACT_VIEW_INDEX}
- * to declare the index in compact view of the given command button in the button extras.
- *
- * @param mediaSession The media session to which the actions will be sent.
- * @param mediaButtons The command buttons to be included in the notification.
- * @param builder The builder to add the actions to.
- * @param actionFactory The actions factory to be used to build notifications.
- * @return The indices of the buttons to be {@linkplain
- * Notification.MediaStyle#setShowActionsInCompactView(int...) used in compact view of the
- * notification}.
- */
- protected int[] addNotificationActions(
- MediaSession mediaSession,
- List mediaButtons,
- NotificationCompat.Builder builder,
- MediaNotification.ActionFactory actionFactory) {
- int[] compactViewIndices = new int[3];
- Arrays.fill(compactViewIndices, INDEX_UNSET);
- int compactViewCommandCount = 0;
- for (int i = 0; i < mediaButtons.size(); i++) {
- CommandButton commandButton = mediaButtons.get(i);
- if (commandButton.sessionCommand != null) {
- builder.addAction(
- actionFactory.createCustomActionFromCustomCommandButton(mediaSession, commandButton));
- } else {
- checkState(commandButton.playerCommand != COMMAND_INVALID);
- builder.addAction(
- actionFactory.createMediaAction(
- mediaSession,
- IconCompat.createWithResource(context, commandButton.iconResId),
- commandButton.displayName,
- commandButton.playerCommand));
- }
- if (compactViewCommandCount == 3) {
- continue;
- }
- int compactViewIndex =
- commandButton.extras.getInt(
- COMMAND_KEY_COMPACT_VIEW_INDEX, /* defaultValue= */ INDEX_UNSET);
- if (compactViewIndex >= 0 && compactViewIndex < compactViewIndices.length) {
- compactViewCommandCount++;
- compactViewIndices[compactViewIndex] = i;
- } else if (commandButton.playerCommand == COMMAND_PLAY_PAUSE
- && compactViewCommandCount == 0) {
- // If there is no custom configuration we use the play/pause action in compact view.
- compactViewIndices[0] = i;
- }
- }
- for (int i = 0; i < compactViewIndices.length; i++) {
- if (compactViewIndices[i] == INDEX_UNSET) {
- compactViewIndices = Arrays.copyOf(compactViewIndices, i);
- break;
- }
- }
- return compactViewIndices;
- }
-
- /**
- * Returns the content title to be used to build the notification.
- *
- * This method is called each time a new notification is built.
- *
- *
Override this method to customize which field of {@link MediaMetadata} is used for content
- * title of the notification.
- *
- *
By default, the notification shows {@link MediaMetadata#title} as content title.
- *
- * @param metadata The media metadata from which content title is fetched.
- * @return Notification content title.
- */
- @Nullable
- protected CharSequence getNotificationContentTitle(MediaMetadata metadata) {
- return metadata.title;
- }
-
- /**
- * Returns the content text to be used to build the notification.
- *
- *
This method is called each time a new notification is built.
- *
- *
Override this method to customize which field of {@link MediaMetadata} is used for content
- * text of the notification.
- *
- *
By default, the notification shows {@link MediaMetadata#artist} as content text.
- *
- * @param metadata The media metadata from which content text is fetched.
- * @return Notification content text.
- */
- @Nullable
- protected CharSequence getNotificationContentText(MediaMetadata metadata) {
- return metadata.artist;
- }
-
- private void ensureNotificationChannel() {
- if (Util.SDK_INT < 26 || notificationManager.getNotificationChannel(channelId) != null) {
- return;
- }
- Api26.createNotificationChannel(
- notificationManager, channelId, context.getString(channelNameResourceId));
- }
-
- private static long getPlaybackStartTimeEpochMs(Player player) {
- // Changing "showWhen" causes notification flicker if SDK_INT < 21.
- if (Util.SDK_INT >= 21
- && player.isPlaying()
- && !player.isPlayingAd()
- && !player.isCurrentMediaItemDynamic()
- && player.getPlaybackParameters().speed == 1f) {
- return System.currentTimeMillis() - player.getContentPosition();
- } else {
- return C.TIME_UNSET;
- }
- }
-
- private static class OnBitmapLoadedFutureCallback implements FutureCallback {
- private final int notificationId;
- private final NotificationCompat.Builder builder;
- private final Callback onNotificationChangedCallback;
-
- private boolean discarded;
-
- public OnBitmapLoadedFutureCallback(
- int notificationId,
- NotificationCompat.Builder builder,
- Callback onNotificationChangedCallback) {
- this.notificationId = notificationId;
- this.builder = builder;
- this.onNotificationChangedCallback = onNotificationChangedCallback;
- }
-
- public void discardIfPending() {
- discarded = true;
- }
-
- @Override
- public void onSuccess(Bitmap result) {
- if (!discarded) {
- builder.setLargeIcon(result);
- onNotificationChangedCallback.onNotificationChanged(
- new MediaNotification(notificationId, builder.build()));
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- if (!discarded) {
- Log.d(TAG, "Failed to load bitmap", t);
- }
- }
- }
-
- @RequiresApi(26)
- private static class Api26 {
- @DoNotInline
- public static void createNotificationChannel(
- NotificationManager notificationManager, String channelId, String channelName) {
- NotificationChannel channel =
- new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW);
- if (Util.SDK_INT <= 27) {
- // API 28+ will automatically hide the app icon 'badge' for notifications using
- // Notification.MediaStyle, but we have to manually hide it for APIs 26 (when badges were
- // added) and 27.
- channel.setShowBadge(false);
- }
- notificationManager.createNotificationChannel(channel);
- }
- }
-}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt
index ccfce111..016765c1 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt
@@ -6,16 +6,11 @@ import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
-import android.graphics.Color
-import android.graphics.Typeface
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.DocumentsContract
import android.provider.SearchRecentSuggestions
-import android.text.SpannableString
-import android.text.style.ForegroundColorSpan
-import android.text.style.StyleSpan
import android.view.View
import androidx.annotation.StringRes
import androidx.preference.CheckBoxPreference
@@ -89,34 +84,11 @@ class SettingsFragment :
customCacheLocation = findPreference(getString(R.string.setting_key_custom_cache_location))
cacheLocation = findPreference(getString(R.string.setting_key_cache_location))
- setupTextColors()
setupClearSearchPreference()
setupCacheLocationPreference()
setupBluetoothDevicePreferences()
}
- private fun setupTextColors(enabled: Boolean = shouldUseId3Tags) {
- val firstPart = getString(R.string.settings_use_id3_offline_warning)
- var secondPart = getString(R.string.settings_use_id3_offline_summary)
-
- // Little hack to circumvent a bug in Android. If we just change the color,
- // the text is not refreshed. If we also change the string, it is refreshed.
- if (enabled) secondPart += " "
-
- val color = if (enabled) "#bd5164" else "#813b48"
-
- Timber.i(color)
-
- val warning = SpannableString(firstPart + "\n" + secondPart)
- warning.setSpan(
- ForegroundColorSpan(Color.parseColor(color)), 0, firstPart.length, 0
- )
- warning.setSpan(
- StyleSpan(Typeface.BOLD), 0, firstPart.length, 0
- )
- useId3TagsOffline?.summary = warning
- }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -205,7 +177,6 @@ class SettingsFragment :
val enabled = sharedPreferences.getBoolean(key, false)
showArtistPicture?.isEnabled = enabled
useId3TagsOffline?.isEnabled = enabled
- setupTextColors(enabled)
}
getString(R.string.setting_key_theme) -> {
RxBus.themeChangedEventPublisher.onNext(Unit)
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/CustomNotificationProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/CustomNotificationProvider.kt
index b6d5c809..10fa2094 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/CustomNotificationProvider.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/CustomNotificationProvider.kt
@@ -12,19 +12,20 @@ import androidx.media3.common.HeartRating
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.CommandButton
+import androidx.media3.session.DefaultMediaNotificationProvider
import androidx.media3.session.MediaNotification
import androidx.media3.session.MediaSession
import androidx.media3.session.SessionCommand
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.R
-import org.moire.ultrasonic.service.DefaultMediaNotificationProvider2
import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.util.toTrack
@UnstableApi
-class CustomNotificationProvider(ctx: Context?) :
- DefaultMediaNotificationProvider2(Builder(ctx)), KoinComponent {
+class CustomNotificationProvider(ctx: Context) :
+ DefaultMediaNotificationProvider(ctx),
+ KoinComponent {
/*
* It is currently not possible to edit a MediaItem after creation so the isRated value
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt
index 8899f382..e574c24a 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt
@@ -23,7 +23,8 @@ import androidx.lifecycle.MutableLiveData
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
import com.google.common.util.concurrent.SettableFuture
-import java.util.PriorityQueue
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.PriorityBlockingQueue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@@ -135,19 +136,19 @@ class DownloadService : Service(), KoinComponent {
var listChanged = false
// Fill up active List with waiting tasks
- while (activelyDownloading.size < Settings.parallelDownloads && downloadQueue.size > 0) {
+ while (activeDownloads.size < Settings.parallelDownloads && downloadQueue.peek() != null) {
// Use poll() instead of remove() which throws an Exception if there is no element.
val track: DownloadableTrack = downloadQueue.poll() ?: continue
val downloadTask = DownloadTask(track, scope!!, ::downloadStateChangedCallback)
- activelyDownloading[track] = downloadTask
+ activeDownloads[track.id] = downloadTask
FileUtil.createDirectoryForParent(track.pinnedFile)
downloadTask.start()
listChanged = true
}
// Stop Executor service when done downloading
- if (activelyDownloading.isEmpty()) {
+ if (activeDownloads.isEmpty()) {
CacheCleaner().cleanSpace()
stopSelf()
}
@@ -175,14 +176,14 @@ class DownloadService : Service(), KoinComponent {
postState(item.track, downloadState, progress)
if (downloadState.isFinalState()) {
- activelyDownloading.remove(item)
+ activeDownloads.remove(item.id)
processNextTracks()
}
when (downloadState) {
DownloadState.FAILED -> {
downloadQueue.remove(item)
- failedList.add(item)
+ failedList[item.id] = item
}
DownloadState.RETRYING -> {
item.tryCount++
@@ -194,7 +195,7 @@ class DownloadService : Service(), KoinComponent {
private fun updateLiveData() {
val temp: MutableList