Reduce coupling between widget and playback service

Instead of binding to the service, pass the required data. This also
ensures that the widget is updated instantly when calling from
PlaybackService. JobService had the problem that the OS sometimes
took some seconds before actually executing the job.
This commit is contained in:
ByteHamster 2021-01-26 12:40:16 +01:00
parent f3bf708e26
commit b6f72f8847
9 changed files with 146 additions and 151 deletions

View File

@ -6,6 +6,7 @@ import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.LargeTest; import androidx.test.filters.LargeTest;
import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
import de.danoeh.antennapod.core.widget.WidgetUpdater;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.junit.After; import org.junit.After;
@ -187,8 +188,8 @@ public class PlaybackServiceTaskManagerTest {
} }
@Override @Override
public void onWidgetUpdaterTick() { public WidgetUpdater.WidgetState requestWidgetState() {
return null;
} }
@Override @Override
@ -248,8 +249,9 @@ public class PlaybackServiceTaskManagerTest {
} }
@Override @Override
public void onWidgetUpdaterTick() { public WidgetUpdater.WidgetState requestWidgetState() {
countDownLatch.countDown(); countDownLatch.countDown();
return null;
} }
@Override @Override
@ -348,8 +350,8 @@ public class PlaybackServiceTaskManagerTest {
} }
@Override @Override
public void onWidgetUpdaterTick() { public WidgetUpdater.WidgetState requestWidgetState() {
return null;
} }
@Override @Override
@ -391,8 +393,8 @@ public class PlaybackServiceTaskManagerTest {
} }
@Override @Override
public void onWidgetUpdaterTick() { public WidgetUpdater.WidgetState requestWidgetState() {
return null;
} }
@Override @Override
@ -449,8 +451,8 @@ public class PlaybackServiceTaskManagerTest {
} }
@Override @Override
public void onWidgetUpdaterTick() { public WidgetUpdater.WidgetState requestWidgetState() {
return null;
} }
@Override @Override

View File

@ -19,7 +19,7 @@ import androidx.core.content.ContextCompat;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.PlayerWidget; import de.danoeh.antennapod.core.receiver.PlayerWidget;
import de.danoeh.antennapod.core.service.PlayerWidgetJobService; import de.danoeh.antennapod.core.widget.WidgetUpdaterJobService;
public class WidgetConfigActivity extends AppCompatActivity { public class WidgetConfigActivity extends AppCompatActivity {
private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
@ -127,7 +127,7 @@ public class WidgetConfigActivity extends AppCompatActivity {
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
setResult(RESULT_OK, resultValue); setResult(RESULT_OK, resultValue);
finish(); finish();
PlayerWidgetJobService.updateWidget(this); WidgetUpdaterJobService.performBackgroundUpdate(this);
} }
private int getColorWithAlpha(int color, int opacity) { private int getColorWithAlpha(int color, int opacity) {

View File

@ -45,6 +45,11 @@
android:label="@string/feed_update_receiver_name" android:label="@string/feed_update_receiver_name"
android:exported="true" android:exported="true"
tools:ignore="ExportedReceiver" /> <!-- allow feeds update to be triggered by external apps --> tools:ignore="ExportedReceiver" /> <!-- allow feeds update to be triggered by external apps -->
<service
android:name=".widget.WidgetUpdaterJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
</application> </application>
</manifest> </manifest>

View File

@ -9,7 +9,7 @@ import android.util.Log;
import java.util.Arrays; import java.util.Arrays;
import de.danoeh.antennapod.core.service.PlayerWidgetJobService; import de.danoeh.antennapod.core.widget.WidgetUpdaterJobService;
public class PlayerWidget extends AppWidgetProvider { public class PlayerWidget extends AppWidgetProvider {
private static final String TAG = "PlayerWidget"; private static final String TAG = "PlayerWidget";
@ -25,7 +25,7 @@ public class PlayerWidget extends AppWidgetProvider {
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive"); Log.d(TAG, "onReceive");
super.onReceive(context, intent); super.onReceive(context, intent);
PlayerWidgetJobService.updateWidget(context); WidgetUpdaterJobService.performBackgroundUpdate(context);
} }
@Override @Override
@ -33,13 +33,14 @@ public class PlayerWidget extends AppWidgetProvider {
super.onEnabled(context); super.onEnabled(context);
Log.d(TAG, "Widget enabled"); Log.d(TAG, "Widget enabled");
setEnabled(context, true); setEnabled(context, true);
PlayerWidgetJobService.updateWidget(context); WidgetUpdaterJobService.performBackgroundUpdate(context);
} }
@Override @Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
Log.d(TAG, "onUpdate() called with: " + "context = [" + context + "], appWidgetManager = [" + appWidgetManager + "], appWidgetIds = [" + Arrays.toString(appWidgetIds) + "]"); Log.d(TAG, "onUpdate() called with: " + "context = [" + context + "], appWidgetManager = ["
PlayerWidgetJobService.updateWidget(context); + appWidgetManager + "], appWidgetIds = [" + Arrays.toString(appWidgetIds) + "]");
WidgetUpdaterJobService.performBackgroundUpdate(context);
} }
@Override @Override

View File

@ -69,7 +69,6 @@ import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.service.PlayerWidgetJobService;
import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DBWriter;
@ -81,6 +80,7 @@ import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import de.danoeh.antennapod.core.util.playback.ExternalMedia; import de.danoeh.antennapod.core.util.playback.ExternalMedia;
import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import de.danoeh.antennapod.core.widget.WidgetUpdater;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
@ -801,8 +801,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
} }
@Override @Override
public void onWidgetUpdaterTick() { public WidgetUpdater.WidgetState requestWidgetState() {
PlayerWidgetJobService.updateWidget(getBaseContext()); return new WidgetUpdater.WidgetState(getPlayable(), getStatus(),
getCurrentPosition(), getDuration(), getCurrentPlaybackSpeed());
} }
@Override @Override
@ -873,9 +874,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
} }
IntentUtils.sendLocalBroadcast(getApplicationContext(), ACTION_PLAYER_STATUS_CHANGED); IntentUtils.sendLocalBroadcast(getApplicationContext(), ACTION_PLAYER_STATUS_CHANGED);
PlayerWidgetJobService.updateWidget(getBaseContext());
bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED); bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED);
bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED); bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED);
taskManager.requestWidgetUpdate();
} }
@Override @Override

View File

@ -8,6 +8,7 @@ import androidx.annotation.NonNull;
import android.util.Log; import android.util.Log;
import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
import de.danoeh.antennapod.core.widget.WidgetUpdater;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
@ -199,17 +200,28 @@ public class PlaybackServiceTaskManager {
*/ */
public synchronized void startWidgetUpdater() { public synchronized void startWidgetUpdater() {
if (!isWidgetUpdaterActive() && !schedExecutor.isShutdown()) { if (!isWidgetUpdaterActive() && !schedExecutor.isShutdown()) {
Runnable widgetUpdater = callback::onWidgetUpdaterTick; Runnable widgetUpdater = this::requestWidgetUpdate;
widgetUpdater = useMainThreadIfNecessary(widgetUpdater); widgetUpdater = useMainThreadIfNecessary(widgetUpdater);
widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL, widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater,
WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS); WIDGET_UPDATER_NOTIFICATION_INTERVAL, WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS);
Log.d(TAG, "Started WidgetUpdater"); Log.d(TAG, "Started WidgetUpdater");
} else { } else {
Log.d(TAG, "Call to startWidgetUpdater was ignored."); Log.d(TAG, "Call to startWidgetUpdater was ignored.");
} }
} }
/**
* Retrieves information about the widget state in the calling thread and then displays it in a background thread.
*/
public synchronized void requestWidgetUpdate() {
WidgetUpdater.WidgetState state = callback.requestWidgetState();
if (!schedExecutor.isShutdown()) {
schedExecutor.execute(() -> WidgetUpdater.updateWidget(context, state));
} else {
Log.d(TAG, "Call to requestWidgetUpdate was ignored.");
}
}
/** /**
* Starts a new sleep timer with the given waiting time. If another sleep timer is already active, it will be * Starts a new sleep timer with the given waiting time. If another sleep timer is already active, it will be
* cancelled first. * cancelled first.
@ -464,7 +476,7 @@ public class PlaybackServiceTaskManager {
void onSleepTimerReset(); void onSleepTimerReset();
void onWidgetUpdaterTick(); WidgetUpdater.WidgetState requestWidgetState();
void onChapterLoaded(Playable media); void onChapterLoaded(Playable media);
} }

View File

@ -21,6 +21,7 @@ import java.util.List;
*/ */
public interface Playable extends Parcelable, public interface Playable extends Parcelable,
ShownotesProvider, ImageResource { ShownotesProvider, ImageResource {
public static final int INVALID_TIME = -1;
/** /**
* Save information about the playable in a preference so that it can be * Save information about the playable in a preference so that it can be

View File

@ -1,17 +1,13 @@
package de.danoeh.antennapod.core.service; package de.danoeh.antennapod.core.widget;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder;
import androidx.annotation.NonNull;
import androidx.core.app.SafeJobIntentService;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
@ -24,7 +20,6 @@ import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.receiver.PlayerWidget; import de.danoeh.antennapod.core.receiver.PlayerWidget;
import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.service.playback.PlaybackService;
@ -35,107 +30,65 @@ import de.danoeh.antennapod.core.util.TimeSpeedConverter;
import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.Playable;
/** /**
* Updates the state of the player widget * Updates the state of the player widget.
*/ */
public class PlayerWidgetJobService extends SafeJobIntentService { public abstract class WidgetUpdater {
private static final String TAG = "WidgetUpdater";
private static final String TAG = "PlayerWidgetJobService"; public static class WidgetState {
final Playable media;
final PlayerStatus status;
final int position;
final int duration;
final float playbackSpeed;
private PlaybackService playbackService; public WidgetState(Playable media, PlayerStatus status, int position, int duration, float playbackSpeed) {
private final Object waitForService = new Object(); this.media = media;
private final Object waitUsingService = new Object(); this.status = status;
this.position = position;
private static final int JOB_ID = -17001; this.duration = duration;
this.playbackSpeed = playbackSpeed;
public static void updateWidget(Context context) {
enqueueWork(context, PlayerWidgetJobService.class, JOB_ID, new Intent(context, PlayerWidgetJobService.class));
}
@Override
protected void onHandleWork(@NonNull Intent intent) {
if (!PlayerWidget.isEnabled(getApplicationContext())) {
return;
} }
synchronized (waitForService) { public WidgetState(PlayerStatus status) {
if (PlaybackService.isRunning && playbackService == null) { this(null, status, Playable.INVALID_TIME, Playable.INVALID_TIME, 1.0f);
bindService(new Intent(this, PlaybackService.class), mConnection, 0);
while (playbackService == null) {
try {
waitForService.wait();
} catch (InterruptedException e) {
return;
}
}
}
}
synchronized (waitUsingService) {
updateViews();
}
if (playbackService != null) {
try {
unbindService(mConnection);
} catch (IllegalArgumentException e) {
Log.w(TAG, "IllegalArgumentException when trying to unbind service");
}
} }
} }
/** /**
* Returns number of cells needed for given size of the widget. * Update the widgets with the given parameters. Must be called in a background thread.
*
* @param size Widget size in dp.
* @return Size in number of cells.
*/ */
private static int getCellsForSize(int size) { public static void updateWidget(Context context, WidgetState widgetState) {
int n = 2; if (!PlayerWidget.isEnabled(context) || widgetState == null) {
while (70 * n - 30 < size) { return;
++n;
} }
return n - 1; ComponentName playerWidget = new ComponentName(context, PlayerWidget.class);
} AppWidgetManager manager = AppWidgetManager.getInstance(context);
private void updateViews() {
ComponentName playerWidget = new ComponentName(this, PlayerWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
int[] widgetIds = manager.getAppWidgetIds(playerWidget); int[] widgetIds = manager.getAppWidgetIds(playerWidget);
final PendingIntent startMediaPlayer = PendingIntent.getActivity(this, R.id.pending_intent_player_activity, final PendingIntent startMediaPlayer = PendingIntent.getActivity(context, R.id.pending_intent_player_activity,
PlaybackService.getPlayerActivityIntent(this), PendingIntent.FLAG_UPDATE_CURRENT); PlaybackService.getPlayerActivityIntent(context), PendingIntent.FLAG_UPDATE_CURRENT);
RemoteViews views; RemoteViews views;
views = new RemoteViews(getPackageName(), R.layout.player_widget); views = new RemoteViews(context.getPackageName(), R.layout.player_widget);
Playable media; if (widgetState.media != null) {
PlayerStatus status;
if (playbackService != null) {
media = playbackService.getPlayable();
status = playbackService.getStatus();
} else {
media = Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext());
status = PlayerStatus.STOPPED;
}
if (media != null) {
Bitmap icon; Bitmap icon;
int iconSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size); int iconSize = context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
views.setOnClickPendingIntent(R.id.layout_left, startMediaPlayer); views.setOnClickPendingIntent(R.id.layout_left, startMediaPlayer);
views.setOnClickPendingIntent(R.id.imgvCover, startMediaPlayer); views.setOnClickPendingIntent(R.id.imgvCover, startMediaPlayer);
try { try {
icon = Glide.with(PlayerWidgetJobService.this) icon = Glide.with(context)
.asBitmap() .asBitmap()
.load(ImageResourceUtils.getImageLocation(media)) .load(ImageResourceUtils.getImageLocation(widgetState.media))
.apply(RequestOptions.diskCacheStrategyOf(ApGlideSettings.AP_DISK_CACHE_STRATEGY)) .apply(RequestOptions.diskCacheStrategyOf(ApGlideSettings.AP_DISK_CACHE_STRATEGY))
.submit(iconSize, iconSize) .submit(iconSize, iconSize)
.get(500, TimeUnit.MILLISECONDS); .get(500, TimeUnit.MILLISECONDS);
views.setImageViewBitmap(R.id.imgvCover, icon); views.setImageViewBitmap(R.id.imgvCover, icon);
} catch (Throwable tr1) { } catch (Throwable tr1) {
try { try {
icon = Glide.with(PlayerWidgetJobService.this) icon = Glide.with(context)
.asBitmap() .asBitmap()
.load(ImageResourceUtils.getFallbackImageLocation(media)) .load(ImageResourceUtils.getFallbackImageLocation(widgetState.media))
.apply(RequestOptions.diskCacheStrategyOf(ApGlideSettings.AP_DISK_CACHE_STRATEGY)) .apply(RequestOptions.diskCacheStrategyOf(ApGlideSettings.AP_DISK_CACHE_STRATEGY))
.submit(iconSize, iconSize) .submit(iconSize, iconSize)
.get(500, TimeUnit.MILLISECONDS); .get(500, TimeUnit.MILLISECONDS);
@ -146,50 +99,44 @@ public class PlayerWidgetJobService extends SafeJobIntentService {
} }
} }
views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle()); views.setTextViewText(R.id.txtvTitle, widgetState.media.getEpisodeTitle());
views.setViewVisibility(R.id.txtvTitle, View.VISIBLE); views.setViewVisibility(R.id.txtvTitle, View.VISIBLE);
views.setViewVisibility(R.id.txtNoPlaying, View.GONE); views.setViewVisibility(R.id.txtNoPlaying, View.GONE);
String progressString; String progressString = getProgressString(widgetState.position,
if (playbackService != null) { widgetState.duration, widgetState.playbackSpeed);
progressString = getProgressString(playbackService.getCurrentPosition(),
playbackService.getDuration(), playbackService.getCurrentPlaybackSpeed());
} else {
progressString = getProgressString(media.getPosition(), media.getDuration(), PlaybackSpeedUtils.getCurrentPlaybackSpeed(media));
}
if (progressString != null) { if (progressString != null) {
views.setViewVisibility(R.id.txtvProgress, View.VISIBLE); views.setViewVisibility(R.id.txtvProgress, View.VISIBLE);
views.setTextViewText(R.id.txtvProgress, progressString); views.setTextViewText(R.id.txtvProgress, progressString);
} }
if (status == PlayerStatus.PLAYING) { if (widgetState.status == PlayerStatus.PLAYING) {
views.setImageViewResource(R.id.butPlay, R.drawable.ic_av_pause_white_48dp); views.setImageViewResource(R.id.butPlay, R.drawable.ic_av_pause_white_48dp);
views.setContentDescription(R.id.butPlay, getString(R.string.pause_label)); views.setContentDescription(R.id.butPlay, context.getString(R.string.pause_label));
views.setImageViewResource(R.id.butPlayExtended, R.drawable.ic_av_pause_white_48dp); views.setImageViewResource(R.id.butPlayExtended, R.drawable.ic_av_pause_white_48dp);
views.setContentDescription(R.id.butPlayExtended, getString(R.string.pause_label)); views.setContentDescription(R.id.butPlayExtended, context.getString(R.string.pause_label));
} else { } else {
views.setImageViewResource(R.id.butPlay, R.drawable.ic_av_play_white_48dp); views.setImageViewResource(R.id.butPlay, R.drawable.ic_av_play_white_48dp);
views.setContentDescription(R.id.butPlay, getString(R.string.play_label)); views.setContentDescription(R.id.butPlay, context.getString(R.string.play_label));
views.setImageViewResource(R.id.butPlayExtended, R.drawable.ic_av_play_white_48dp); views.setImageViewResource(R.id.butPlayExtended, R.drawable.ic_av_play_white_48dp);
views.setContentDescription(R.id.butPlayExtended, getString(R.string.play_label)); views.setContentDescription(R.id.butPlayExtended, context.getString(R.string.play_label));
} }
views.setOnClickPendingIntent(R.id.butPlay, views.setOnClickPendingIntent(R.id.butPlay,
createMediaButtonIntent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); createMediaButtonIntent(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
views.setOnClickPendingIntent(R.id.butPlayExtended, views.setOnClickPendingIntent(R.id.butPlayExtended,
createMediaButtonIntent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); createMediaButtonIntent(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
views.setOnClickPendingIntent(R.id.butRew, views.setOnClickPendingIntent(R.id.butRew,
createMediaButtonIntent(KeyEvent.KEYCODE_MEDIA_REWIND)); createMediaButtonIntent(context, KeyEvent.KEYCODE_MEDIA_REWIND));
views.setOnClickPendingIntent(R.id.butFastForward, views.setOnClickPendingIntent(R.id.butFastForward,
createMediaButtonIntent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD)); createMediaButtonIntent(context, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD));
views.setOnClickPendingIntent(R.id.butSkip, views.setOnClickPendingIntent(R.id.butSkip,
createMediaButtonIntent(KeyEvent.KEYCODE_MEDIA_NEXT)); createMediaButtonIntent(context, KeyEvent.KEYCODE_MEDIA_NEXT));
} else { } else {
// start the app if they click anything // start the app if they click anything
views.setOnClickPendingIntent(R.id.layout_left, startMediaPlayer); views.setOnClickPendingIntent(R.id.layout_left, startMediaPlayer);
views.setOnClickPendingIntent(R.id.butPlay, startMediaPlayer); views.setOnClickPendingIntent(R.id.butPlay, startMediaPlayer);
views.setOnClickPendingIntent(R.id.butPlayExtended, views.setOnClickPendingIntent(R.id.butPlayExtended,
createMediaButtonIntent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); createMediaButtonIntent(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
views.setViewVisibility(R.id.txtvProgress, View.GONE); views.setViewVisibility(R.id.txtvProgress, View.GONE);
views.setViewVisibility(R.id.txtvTitle, View.GONE); views.setViewVisibility(R.id.txtvTitle, View.GONE);
views.setViewVisibility(R.id.txtNoPlaying, View.VISIBLE); views.setViewVisibility(R.id.txtNoPlaying, View.VISIBLE);
@ -201,7 +148,7 @@ public class PlayerWidgetJobService extends SafeJobIntentService {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
for (int id : widgetIds) { for (int id : widgetIds) {
Bundle options = manager.getAppWidgetOptions(id); Bundle options = manager.getAppWidgetOptions(id);
SharedPreferences prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences prefs = context.getSharedPreferences(PlayerWidget.PREFS_NAME, Context.MODE_PRIVATE);
int minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH); int minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
int columns = getCellsForSize(minWidth); int columns = getCellsForSize(minWidth);
if (columns < 3) { if (columns < 3) {
@ -232,18 +179,32 @@ public class PlayerWidgetJobService extends SafeJobIntentService {
} }
/** /**
* Creates an intent which fakes a mediabutton press * Returns number of cells needed for given size of the widget.
*
* @param size Widget size in dp.
* @return Size in number of cells.
*/ */
private PendingIntent createMediaButtonIntent(int eventCode) { private static int getCellsForSize(int size) {
int n = 2;
while (70 * n - 30 < size) {
++n;
}
return n - 1;
}
/**
* Creates an intent which fakes a mediabutton press.
*/
private static PendingIntent createMediaButtonIntent(Context context, int eventCode) {
KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, eventCode); KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, eventCode);
Intent startingIntent = new Intent(getBaseContext(), MediaButtonReceiver.class); Intent startingIntent = new Intent(context, MediaButtonReceiver.class);
startingIntent.setAction(MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER); startingIntent.setAction(MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER);
startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
return PendingIntent.getBroadcast(this, eventCode, startingIntent, 0); return PendingIntent.getBroadcast(context, eventCode, startingIntent, 0);
} }
private String getProgressString(int position, int duration, float speed) { private static String getProgressString(int position, int duration, float speed) {
if (position >= 0 && duration > 0) { if (position >= 0 && duration > 0) {
TimeSpeedConverter converter = new TimeSpeedConverter(speed); TimeSpeedConverter converter = new TimeSpeedConverter(speed);
position = converter.convert(position); position = converter.convert(position);
@ -254,24 +215,4 @@ public class PlayerWidgetJobService extends SafeJobIntentService {
return null; return null;
} }
} }
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
Log.d(TAG, "Connection to service established");
if (service instanceof PlaybackService.LocalBinder) {
synchronized (waitForService) {
playbackService = ((PlaybackService.LocalBinder) service).getService();
waitForService.notifyAll();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (waitUsingService) {
playbackService = null;
}
Log.d(TAG, "Disconnected from service");
}
};
} }

View File

@ -0,0 +1,32 @@
package de.danoeh.antennapod.core.widget;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.core.app.SafeJobIntentService;
import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils;
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.core.util.playback.Playable;
public class WidgetUpdaterJobService extends SafeJobIntentService {
private static final int JOB_ID = -17001;
/**
* Loads the current media from the database and updates the widget in a background job.
*/
public static void performBackgroundUpdate(Context context) {
enqueueWork(context, WidgetUpdaterJobService.class,
WidgetUpdaterJobService.JOB_ID, new Intent(context, WidgetUpdaterJobService.class));
}
@Override
protected void onHandleWork(@NonNull Intent intent) {
Playable media = Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext());
if (media != null) {
WidgetUpdater.updateWidget(this, new WidgetUpdater.WidgetState(media, PlayerStatus.STOPPED,
media.getPosition(), media.getDuration(), PlaybackSpeedUtils.getCurrentPlaybackSpeed(media)));
} else {
WidgetUpdater.updateWidget(this, new WidgetUpdater.WidgetState(PlayerStatus.STOPPED));
}
}
}