#3248 Adapt volume for playing media if its volume reduction setting is changed
This commit is contained in:
parent
7f0e642069
commit
759ce1b357
|
@ -3,6 +3,7 @@ package de.danoeh.antennapod.fragment;
|
|||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v14.preference.SwitchPreference;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
|
@ -13,6 +14,7 @@ import de.danoeh.antennapod.core.feed.Feed;
|
|||
import de.danoeh.antennapod.core.feed.FeedFilter;
|
||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.dialog.AuthenticationDialog;
|
||||
import de.danoeh.antennapod.dialog.EpisodeFilterDialog;
|
||||
|
@ -132,11 +134,20 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat {
|
|||
}
|
||||
feed.savePreferences();
|
||||
updateVolumeReductionValue();
|
||||
// TODO maxbechtold Check if we can call setVolume for the PlaybackService, if running. Else, show toast?
|
||||
sendVolumeReductionChangedIntent();
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private void sendVolumeReductionChangedIntent() {
|
||||
Context context = getContext();
|
||||
Intent intent = new Intent(PlaybackService.ACTION_VOLUME_REDUCTION_CHANGED).setPackage(context.getPackageName());
|
||||
intent.putExtra(PlaybackService.EXTRA_VOLUME_REDUCTION_AFFECTED_FEED, feed.getIdentifyingValue());
|
||||
intent.putExtra(PlaybackService.EXTRA_VOLUME_REDUCTION_SETTING, feedPreferences.getVolumeReductionSetting());
|
||||
context.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private void updateVolumeReductionValue() {
|
||||
ListPreference volumeReductionPreference = (ListPreference) findPreference("volumeReduction");
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
package de.danoeh.antennapod.core.service.playback;
|
||||
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
|
||||
class PlaybackVolumeAdaptor {
|
||||
|
||||
public void adaptVolumeIfNecessary(PlaybackServiceMediaPlayer mediaPlayer, String affectedFeedIdentifier, FeedPreferences.VolumeReductionSetting volumeReductionSetting) {
|
||||
Playable playable = mediaPlayer.getPlayable();
|
||||
boolean isFeedMedia = playable instanceof FeedMedia;
|
||||
boolean isPlayableLoaded = isPlayableLoaded(mediaPlayer.getPlayerStatus());
|
||||
|
||||
if (isPlayableLoaded && isFeedMedia) {
|
||||
adaptFeedMediaVolumeIfNecessary(mediaPlayer, affectedFeedIdentifier, volumeReductionSetting, (FeedMedia) playable);
|
||||
}
|
||||
}
|
||||
|
||||
private void adaptFeedMediaVolumeIfNecessary(PlaybackServiceMediaPlayer mediaPlayer, String affectedFeedIdentifier, FeedPreferences.VolumeReductionSetting volumeReductionSetting, FeedMedia feedMedia) {
|
||||
if (mediaBelongsToAffectedFeed(feedMedia, affectedFeedIdentifier)) {
|
||||
FeedPreferences preferences = feedMedia.getItem().getFeed().getPreferences();
|
||||
preferences.setVolumeReductionSetting(volumeReductionSetting);
|
||||
|
||||
if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) {
|
||||
forceAdaptVolume(mediaPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPlayableLoaded(PlayerStatus playerStatus) {
|
||||
return playerStatus == PlayerStatus.PLAYING
|
||||
|| playerStatus == PlayerStatus.PAUSED
|
||||
|| playerStatus == PlayerStatus.SEEKING
|
||||
|| playerStatus == PlayerStatus.PREPARING
|
||||
|| playerStatus == PlayerStatus.PREPARED
|
||||
|| playerStatus == PlayerStatus.INITIALIZING;
|
||||
}
|
||||
|
||||
private static boolean mediaBelongsToAffectedFeed(FeedMedia feedMedia, String affectedFeedIdentifier) {
|
||||
return affectedFeedIdentifier != null
|
||||
&& affectedFeedIdentifier.equals(feedMedia.getItem().getFeed().getIdentifyingValue());
|
||||
}
|
||||
|
||||
private static void forceAdaptVolume(PlaybackServiceMediaPlayer mediaPlayer) {
|
||||
mediaPlayer.pause(false, false);
|
||||
mediaPlayer.resume();
|
||||
}
|
||||
|
||||
}
|
|
@ -12,7 +12,6 @@ import android.content.Intent;
|
|||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
|
@ -43,6 +42,7 @@ import com.bumptech.glide.Glide;
|
|||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -54,6 +54,7 @@ import de.danoeh.antennapod.core.feed.Chapter;
|
|||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.feed.SearchResult;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
|
@ -66,7 +67,6 @@ import de.danoeh.antennapod.core.storage.DBReader;
|
|||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.FeedSearcher;
|
||||
import de.danoeh.antennapod.core.util.IntList;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.NetworkUtils;
|
||||
import de.danoeh.antennapod.core.util.QueueAccess;
|
||||
|
@ -80,6 +80,7 @@ import org.greenrobot.eventbus.EventBus;
|
|||
* Controls the MediaPlayer that plays a FeedMedia-file
|
||||
*/
|
||||
public class PlaybackService extends MediaBrowserServiceCompat {
|
||||
|
||||
/**
|
||||
* Logging tag
|
||||
*/
|
||||
|
@ -131,6 +132,12 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
*/
|
||||
public static final String ACTION_PAUSE_PLAY_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.pausePlayCurrentEpisode";
|
||||
|
||||
/**
|
||||
* If the PlaybackService receives this action, it will try to apply the supplied volume reduction setting.
|
||||
*/
|
||||
public static final String ACTION_VOLUME_REDUCTION_CHANGED = "action.de.danoeh.antennapod.core.service.volumedReductionChanged";
|
||||
public static final String EXTRA_VOLUME_REDUCTION_SETTING = "PlaybackService.VolumeReductionSettingExtra";
|
||||
public static final String EXTRA_VOLUME_REDUCTION_AFFECTED_FEED = "PlaybackService.VolumeReductionSettingAffectedFeed";
|
||||
|
||||
/**
|
||||
* If the PlaybackService receives this action, it will resume playback.
|
||||
|
@ -279,6 +286,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(ACTION_SKIP_CURRENT_EPISODE));
|
||||
registerReceiver(pausePlayCurrentEpisodeReceiver, new IntentFilter(ACTION_PAUSE_PLAY_CURRENT_EPISODE));
|
||||
registerReceiver(pauseResumeCurrentEpisodeReceiver, new IntentFilter(ACTION_RESUME_PLAY_CURRENT_EPISODE));
|
||||
registerReceiver(volumeReductionChangedReceiver, new IntentFilter(ACTION_VOLUME_REDUCTION_CHANGED));
|
||||
taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback);
|
||||
|
||||
flavorHelper = new PlaybackServiceFlavorHelper(PlaybackService.this, flavorHelperCallback);
|
||||
|
@ -1517,6 +1525,22 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver volumeReductionChangedReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (TextUtils.equals(intent.getAction(), ACTION_VOLUME_REDUCTION_CHANGED)) {
|
||||
Log.d(TAG, "Received ACTION_VOLUME_REDUCTION_CHANGED intent");
|
||||
|
||||
String affectedFeed = intent.getStringExtra(EXTRA_VOLUME_REDUCTION_AFFECTED_FEED);
|
||||
Serializable volumeReductionExtra = intent.getSerializableExtra(EXTRA_VOLUME_REDUCTION_SETTING);
|
||||
FeedPreferences.VolumeReductionSetting volumeReductionSetting = (FeedPreferences.VolumeReductionSetting) volumeReductionExtra;
|
||||
|
||||
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
|
||||
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, affectedFeed, volumeReductionSetting);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static MediaType getCurrentMediaType() {
|
||||
return currentMediaType;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
package de.danoeh.antennapod.core.service.playback;
|
||||
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.core.feed.FeedPreferences.VolumeReductionSetting;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.mockito.Mockito.anyBoolean;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class PlaybackVolumeAdaptorTest {
|
||||
|
||||
private static final String FEED_ID = "feedId";
|
||||
|
||||
private PlaybackServiceMediaPlayer mediaPlayer;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mediaPlayer = mock(PlaybackServiceMediaPlayer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noChangeIfNoFeedMediaPlaying() {
|
||||
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
|
||||
|
||||
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PAUSED);
|
||||
|
||||
Playable noFeedMedia = mock(Playable.class);
|
||||
when(mediaPlayer.getPlayable()).thenReturn(noFeedMedia);
|
||||
|
||||
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
|
||||
|
||||
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
|
||||
verify(mediaPlayer, never()).resume();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noChangeIfPlayerStatusIsError() {
|
||||
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
|
||||
|
||||
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.ERROR);
|
||||
|
||||
FeedMedia feedMedia = mock(FeedMedia.class);
|
||||
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
|
||||
|
||||
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
|
||||
|
||||
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
|
||||
verify(mediaPlayer, never()).resume();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noChangeIfPlayerStatusIsIndeterminate() {
|
||||
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
|
||||
|
||||
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.INDETERMINATE);
|
||||
|
||||
FeedMedia feedMedia = mock(FeedMedia.class);
|
||||
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
|
||||
|
||||
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
|
||||
|
||||
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
|
||||
verify(mediaPlayer, never()).resume();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noChangeIfPlayerStatusIsStopped() {
|
||||
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
|
||||
|
||||
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.STOPPED);
|
||||
|
||||
FeedMedia feedMedia = mock(FeedMedia.class);
|
||||
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
|
||||
|
||||
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
|
||||
|
||||
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
|
||||
verify(mediaPlayer, never()).resume();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noChangeIfPlayableIsNoItemOfAffectedFeed() {
|
||||
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
|
||||
|
||||
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PLAYING);
|
||||
|
||||
FeedMedia feedMedia = mock(FeedMedia.class);
|
||||
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
|
||||
Feed feed = mockFeed(feedMedia, FEED_ID);
|
||||
when(feed.getIdentifyingValue()).thenReturn("wrongFeedId");
|
||||
|
||||
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.OFF);
|
||||
|
||||
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
|
||||
verify(mediaPlayer, never()).resume();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adaptsPreferencesForLoadedFeedMediaIfPlayerStatusIsPaused() {
|
||||
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
|
||||
|
||||
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PAUSED);
|
||||
|
||||
FeedMedia feedMedia = mock(FeedMedia.class);
|
||||
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
|
||||
FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
|
||||
|
||||
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.LIGHT);
|
||||
|
||||
verify(feedPreferences, times(1)).setVolumeReductionSetting(VolumeReductionSetting.LIGHT);
|
||||
|
||||
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
|
||||
verify(mediaPlayer, never()).resume();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adaptsPreferencesForLoadedFeedMediaIfPlayerStatusIsPrepared() {
|
||||
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
|
||||
|
||||
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PREPARED);
|
||||
|
||||
FeedMedia feedMedia = mock(FeedMedia.class);
|
||||
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
|
||||
FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
|
||||
|
||||
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.LIGHT);
|
||||
|
||||
verify(feedPreferences, times(1)).setVolumeReductionSetting(VolumeReductionSetting.LIGHT);
|
||||
|
||||
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
|
||||
verify(mediaPlayer, never()).resume();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adaptsPreferencesForLoadedFeedMediaIfPlayerStatusIsInitializing() {
|
||||
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
|
||||
|
||||
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.INITIALIZING);
|
||||
|
||||
FeedMedia feedMedia = mock(FeedMedia.class);
|
||||
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
|
||||
FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
|
||||
|
||||
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.LIGHT);
|
||||
|
||||
verify(feedPreferences, times(1)).setVolumeReductionSetting(VolumeReductionSetting.LIGHT);
|
||||
|
||||
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
|
||||
verify(mediaPlayer, never()).resume();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adaptsPreferencesForLoadedFeedMediaIfPlayerStatusIsPreparing() {
|
||||
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
|
||||
|
||||
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PREPARING);
|
||||
|
||||
FeedMedia feedMedia = mock(FeedMedia.class);
|
||||
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
|
||||
FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
|
||||
|
||||
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.LIGHT);
|
||||
|
||||
verify(feedPreferences, times(1)).setVolumeReductionSetting(VolumeReductionSetting.LIGHT);
|
||||
|
||||
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
|
||||
verify(mediaPlayer, never()).resume();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adaptsPreferencesForLoadedFeedMediaIfPlayerStatusIsSeeking() {
|
||||
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
|
||||
|
||||
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.SEEKING);
|
||||
|
||||
FeedMedia feedMedia = mock(FeedMedia.class);
|
||||
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
|
||||
FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
|
||||
|
||||
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.LIGHT);
|
||||
|
||||
verify(feedPreferences, times(1)).setVolumeReductionSetting(VolumeReductionSetting.LIGHT);
|
||||
|
||||
verify(mediaPlayer, never()).pause(anyBoolean(), anyBoolean());
|
||||
verify(mediaPlayer, never()).resume();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adaptsPreferencesAndForcesVolumeChangeForLoadedFeedMediaIfPlayerStatusIsPlaying() {
|
||||
PlaybackVolumeAdaptor playbackVolumeAdaptor = new PlaybackVolumeAdaptor();
|
||||
|
||||
when(mediaPlayer.getPlayerStatus()).thenReturn(PlayerStatus.PLAYING);
|
||||
|
||||
FeedMedia feedMedia = mock(FeedMedia.class);
|
||||
when(mediaPlayer.getPlayable()).thenReturn(feedMedia);
|
||||
FeedPreferences feedPreferences = mockFeedPreferences(feedMedia, FEED_ID);
|
||||
|
||||
playbackVolumeAdaptor.adaptVolumeIfNecessary(mediaPlayer, FEED_ID, VolumeReductionSetting.HEAVY);
|
||||
|
||||
verify(feedPreferences, times(1)).setVolumeReductionSetting(VolumeReductionSetting.HEAVY);
|
||||
|
||||
verify(mediaPlayer, times(1)).pause(false, false);
|
||||
verify(mediaPlayer, times(1)).resume();
|
||||
}
|
||||
|
||||
private FeedPreferences mockFeedPreferences(FeedMedia feedMedia, String feedId) {
|
||||
Feed feed = mockFeed(feedMedia, feedId);
|
||||
FeedPreferences feedPreferences = mock(FeedPreferences.class);
|
||||
when(feed.getPreferences()).thenReturn(feedPreferences);
|
||||
return feedPreferences;
|
||||
}
|
||||
|
||||
private Feed mockFeed(FeedMedia feedMedia, String feedId) {
|
||||
FeedItem feedItem = mock(FeedItem.class);
|
||||
when(feedMedia.getItem()).thenReturn(feedItem);
|
||||
Feed feed = mock(Feed.class);
|
||||
when(feed.getIdentifyingValue()).thenReturn(feedId);
|
||||
when(feedItem.getFeed()).thenReturn(feed);
|
||||
return feed;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue