fix: Include song position and duration in widget

Co-authored-by: Mücahit Kaya <kaya-mucahit@outlook.com>
Co-authored-by: The Firehawk <firehawk@opayq.net>
This commit is contained in:
le-firehawk 2025-09-18 13:57:07 +09:30 committed by mucahit-kaya
parent cc0e264a17
commit e81e1a5356
9 changed files with 210 additions and 20 deletions

View file

@ -10,9 +10,11 @@ import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.R;
import androidx.media3.common.C;
import androidx.media3.session.MediaController;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.ExecutionException;
@ -23,14 +25,19 @@ public final class WidgetUpdateManager {
String title,
String artist,
Bitmap art,
boolean playing) {
boolean playing,
long positionMs,
long durationMs) {
if (TextUtils.isEmpty(title)) title = ctx.getString(R.string.widget_not_playing);
if (TextUtils.isEmpty(artist)) artist = ctx.getString(R.string.widget_placeholder_subtitle);
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
for (int id : ids) {
android.widget.RemoteViews rv = choosePopulate(ctx, title, artist, art, playing, id);
android.widget.RemoteViews rv = choosePopulate(ctx, title, artist, art, playing,
timing.elapsedText, timing.totalText, timing.progress, id);
WidgetProvider.attachIntents(ctx, rv, id);
mgr.updateAppWidget(id, rv);
}
@ -50,11 +57,14 @@ public final class WidgetUpdateManager {
String title,
String artist,
String coverArtId,
boolean playing) {
boolean playing,
long positionMs,
long durationMs) {
final Context appCtx = ctx.getApplicationContext();
final String t = TextUtils.isEmpty(title) ? appCtx.getString(R.string.widget_not_playing) : title;
final String a = TextUtils.isEmpty(artist) ? appCtx.getString(R.string.widget_placeholder_subtitle) : artist;
final boolean p = playing;
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
if (!TextUtils.isEmpty(coverArtId)) {
CustomGlideRequest.loadAlbumArtBitmap(
@ -66,7 +76,8 @@ public final class WidgetUpdateManager {
AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx);
int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class));
for (int id : ids) {
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, resource, p, id);
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, resource, p,
timing.elapsedText, timing.totalText, timing.progress, id);
WidgetProvider.attachIntents(appCtx, rv, id);
mgr.updateAppWidget(id, rv);
}
@ -76,7 +87,8 @@ public final class WidgetUpdateManager {
AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx);
int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class));
for (int id : ids) {
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, null, p, id);
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, null, p,
timing.elapsedText, timing.totalText, timing.progress, id);
WidgetProvider.attachIntents(appCtx, rv, id);
mgr.updateAppWidget(id, rv);
}
@ -87,7 +99,8 @@ public final class WidgetUpdateManager {
AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx);
int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class));
for (int id : ids) {
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, null, p, id);
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, null, p,
timing.elapsedText, timing.totalText, timing.progress, id);
WidgetProvider.attachIntents(appCtx, rv, id);
mgr.updateAppWidget(id, rv);
}
@ -113,25 +126,71 @@ public final class WidgetUpdateManager {
coverId = mi.mediaMetadata.extras.getString("coverArtId");
}
}
long position = c.getCurrentPosition();
long duration = c.getDuration();
if (position == C.TIME_UNSET) position = 0;
if (duration == C.TIME_UNSET) duration = 0;
updateFromState(appCtx,
title != null ? title : appCtx.getString(R.string.widget_not_playing),
artist != null ? artist : appCtx.getString(R.string.widget_placeholder_subtitle),
coverId,
c.isPlaying());
c.isPlaying(),
position,
duration);
c.release();
} catch (ExecutionException | InterruptedException ignored) {
}
}, MoreExecutors.directExecutor());
}
private static TimingInfo createTimingInfo(long positionMs, long durationMs) {
long safePosition = Math.max(0L, positionMs);
long safeDuration = durationMs > 0 ? durationMs : 0L;
if (safeDuration > 0 && safePosition > safeDuration) {
safePosition = safeDuration;
}
String elapsed = (safeDuration > 0 || safePosition > 0)
? MusicUtil.getReadableDurationString(safePosition, true)
: null;
String total = safeDuration > 0
? MusicUtil.getReadableDurationString(safeDuration, true)
: null;
int progress = 0;
if (safeDuration > 0) {
long scaled = safePosition * WidgetViewsFactory.PROGRESS_MAX;
long progressLong = scaled / safeDuration;
if (progressLong < 0) {
progress = 0;
} else if (progressLong > WidgetViewsFactory.PROGRESS_MAX) {
progress = WidgetViewsFactory.PROGRESS_MAX;
} else {
progress = (int) progressLong;
}
}
return new TimingInfo(elapsed, total, progress);
}
public static android.widget.RemoteViews chooseBuild(Context ctx, int appWidgetId) {
if (isLarge(ctx, appWidgetId)) return WidgetViewsFactory.buildLarge(ctx);
return WidgetViewsFactory.buildCompact(ctx);
}
private static android.widget.RemoteViews choosePopulate(Context ctx, String title, String artist, Bitmap art, boolean playing, int appWidgetId) {
if (isLarge(ctx, appWidgetId)) return WidgetViewsFactory.populateLarge(ctx, title, artist, art, playing);
return WidgetViewsFactory.populate(ctx, title, artist, art, playing);
private static android.widget.RemoteViews choosePopulate(Context ctx,
String title,
String artist,
Bitmap art,
boolean playing,
String elapsedText,
String totalText,
int progress,
int appWidgetId) {
if (isLarge(ctx, appWidgetId)) {
return WidgetViewsFactory.populateLarge(ctx, title, artist, art, playing, elapsedText, totalText, progress);
}
return WidgetViewsFactory.populate(ctx, title, artist, art, playing, elapsedText, totalText, progress);
}
private static boolean isLarge(Context ctx, int appWidgetId) {
@ -142,5 +201,16 @@ public final class WidgetUpdateManager {
return minH >= threshold; // dp threshold for 2-row height
}
private static final class TimingInfo {
final String elapsedText;
final String totalText;
final int progress;
TimingInfo(String elapsedText, String totalText, int progress) {
this.elapsedText = elapsedText;
this.totalText = totalText;
this.progress = progress;
}
}
}