mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 09:33:33 +00:00
Merge branch 'feature-widget-playing' into development
This commit is contained in:
commit
d1e247f9e2
5 changed files with 486 additions and 469 deletions
|
|
@ -15,47 +15,48 @@ import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public final class WidgetActions {
|
public final class WidgetActions {
|
||||||
public static void dispatchToMediaSession(Context ctx, String action) {
|
public static void dispatchToMediaSession(Context ctx, String action) {
|
||||||
Log.d("TempoWidget", "dispatch action=" + action);
|
Log.d("TempoWidget", "dispatch action=" + action);
|
||||||
Context appCtx = ctx.getApplicationContext();
|
Context appCtx = ctx.getApplicationContext();
|
||||||
SessionToken token = new SessionToken(appCtx, new ComponentName(appCtx, MediaService.class));
|
SessionToken token = new SessionToken(appCtx, new ComponentName(appCtx, MediaService.class));
|
||||||
ListenableFuture<MediaController> future = new MediaController.Builder(appCtx, token).buildAsync();
|
ListenableFuture<MediaController> future = new MediaController.Builder(appCtx, token).buildAsync();
|
||||||
future.addListener(() -> {
|
future.addListener(() -> {
|
||||||
try {
|
try {
|
||||||
if (!future.isDone()) return;
|
if (!future.isDone()) return;
|
||||||
MediaController c = future.get();
|
MediaController c = future.get();
|
||||||
Log.d("TempoWidget", "controller connected, isPlaying=" + c.isPlaying());
|
Log.d("TempoWidget", "controller connected, isPlaying=" + c.isPlaying());
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case WidgetProvider.ACT_PLAY_PAUSE:
|
case WidgetProvider.ACT_PLAY_PAUSE:
|
||||||
if (c.isPlaying()) c.pause(); else c.play();
|
if (c.isPlaying()) c.pause();
|
||||||
break;
|
else c.play();
|
||||||
case WidgetProvider.ACT_NEXT:
|
break;
|
||||||
c.seekToNext();
|
case WidgetProvider.ACT_NEXT:
|
||||||
break;
|
c.seekToNext();
|
||||||
case WidgetProvider.ACT_PREV:
|
break;
|
||||||
c.seekToPrevious();
|
case WidgetProvider.ACT_PREV:
|
||||||
break;
|
c.seekToPrevious();
|
||||||
case WidgetProvider.ACT_TOGGLE_SHUFFLE:
|
break;
|
||||||
c.setShuffleModeEnabled(!c.getShuffleModeEnabled());
|
case WidgetProvider.ACT_TOGGLE_SHUFFLE:
|
||||||
break;
|
c.setShuffleModeEnabled(!c.getShuffleModeEnabled());
|
||||||
case WidgetProvider.ACT_CYCLE_REPEAT:
|
break;
|
||||||
int repeatMode = c.getRepeatMode();
|
case WidgetProvider.ACT_CYCLE_REPEAT:
|
||||||
int nextMode;
|
int repeatMode = c.getRepeatMode();
|
||||||
if (repeatMode == Player.REPEAT_MODE_OFF) {
|
int nextMode;
|
||||||
nextMode = Player.REPEAT_MODE_ALL;
|
if (repeatMode == Player.REPEAT_MODE_OFF) {
|
||||||
} else if (repeatMode == Player.REPEAT_MODE_ALL) {
|
nextMode = Player.REPEAT_MODE_ALL;
|
||||||
nextMode = Player.REPEAT_MODE_ONE;
|
} else if (repeatMode == Player.REPEAT_MODE_ALL) {
|
||||||
} else {
|
nextMode = Player.REPEAT_MODE_ONE;
|
||||||
nextMode = Player.REPEAT_MODE_OFF;
|
} else {
|
||||||
|
nextMode = Player.REPEAT_MODE_OFF;
|
||||||
|
}
|
||||||
|
c.setRepeatMode(nextMode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
WidgetUpdateManager.refreshFromController(ctx);
|
||||||
|
c.release();
|
||||||
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
Log.e("TempoWidget", "dispatch failed", e);
|
||||||
}
|
}
|
||||||
c.setRepeatMode(nextMode);
|
}, MoreExecutors.directExecutor());
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
WidgetUpdateManager.refreshFromController(ctx);
|
|
||||||
c.release();
|
|
||||||
} catch (ExecutionException | InterruptedException e) {
|
|
||||||
Log.e("TempoWidget", "dispatch failed", e);
|
|
||||||
}
|
|
||||||
}, MoreExecutors.directExecutor());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,12 @@ import android.content.Intent;
|
||||||
import android.widget.RemoteViews;
|
import android.widget.RemoteViews;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
|
|
||||||
import android.app.TaskStackBuilder;
|
import android.app.TaskStackBuilder;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
public class WidgetProvider extends AppWidgetProvider {
|
public class WidgetProvider extends AppWidgetProvider {
|
||||||
|
|
@ -21,7 +24,8 @@ public class WidgetProvider extends AppWidgetProvider {
|
||||||
public static final String ACT_TOGGLE_SHUFFLE = "tempo.widget.SHUFFLE";
|
public static final String ACT_TOGGLE_SHUFFLE = "tempo.widget.SHUFFLE";
|
||||||
public static final String ACT_CYCLE_REPEAT = "tempo.widget.REPEAT";
|
public static final String ACT_CYCLE_REPEAT = "tempo.widget.REPEAT";
|
||||||
|
|
||||||
@Override public void onUpdate(Context ctx, AppWidgetManager mgr, int[] ids) {
|
@Override
|
||||||
|
public void onUpdate(Context ctx, AppWidgetManager mgr, int[] ids) {
|
||||||
for (int id : ids) {
|
for (int id : ids) {
|
||||||
RemoteViews rv = WidgetUpdateManager.chooseBuild(ctx, id);
|
RemoteViews rv = WidgetUpdateManager.chooseBuild(ctx, id);
|
||||||
attachIntents(ctx, rv, id);
|
attachIntents(ctx, rv, id);
|
||||||
|
|
@ -29,7 +33,8 @@ public class WidgetProvider extends AppWidgetProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onReceive(Context ctx, Intent intent) {
|
@Override
|
||||||
|
public void onReceive(Context ctx, Intent intent) {
|
||||||
super.onReceive(ctx, intent);
|
super.onReceive(ctx, intent);
|
||||||
String a = intent.getAction();
|
String a = intent.getAction();
|
||||||
Log.d(TAG, "onReceive action=" + a);
|
Log.d(TAG, "onReceive action=" + a);
|
||||||
|
|
@ -41,7 +46,8 @@ public class WidgetProvider extends AppWidgetProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, android.os.Bundle newOptions) {
|
@Override
|
||||||
|
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, android.os.Bundle newOptions) {
|
||||||
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
|
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
|
||||||
RemoteViews rv = WidgetUpdateManager.chooseBuild(context, appWidgetId);
|
RemoteViews rv = WidgetUpdateManager.chooseBuild(context, appWidgetId);
|
||||||
attachIntents(context, rv, appWidgetId);
|
attachIntents(context, rv, appWidgetId);
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,6 @@ package com.cappielloantonio.tempo.widget;
|
||||||
* AppWidget provider entry for the 4x1 widget card. Inherits all behavior
|
* AppWidget provider entry for the 4x1 widget card. Inherits all behavior
|
||||||
* from {@link WidgetProvider}.
|
* from {@link WidgetProvider}.
|
||||||
*/
|
*/
|
||||||
public class WidgetProvider4x1 extends WidgetProvider {}
|
public class WidgetProvider4x1 extends WidgetProvider {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,263 +6,271 @@ import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
import com.bumptech.glide.request.target.CustomTarget;
|
import com.bumptech.glide.request.target.CustomTarget;
|
||||||
import com.bumptech.glide.request.transition.Transition;
|
import com.bumptech.glide.request.transition.Transition;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
|
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.session.MediaController;
|
import androidx.media3.session.MediaController;
|
||||||
import androidx.media3.session.SessionToken;
|
import androidx.media3.session.SessionToken;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public final class WidgetUpdateManager {
|
public final class WidgetUpdateManager {
|
||||||
|
|
||||||
public static void updateFromState(Context ctx,
|
public static void updateFromState(Context ctx,
|
||||||
String title,
|
String title,
|
||||||
String artist,
|
String artist,
|
||||||
String album,
|
String album,
|
||||||
Bitmap art,
|
Bitmap art,
|
||||||
boolean playing,
|
boolean playing,
|
||||||
boolean shuffleEnabled,
|
boolean shuffleEnabled,
|
||||||
int repeatMode,
|
int repeatMode,
|
||||||
long positionMs,
|
long positionMs,
|
||||||
long durationMs) {
|
long durationMs) {
|
||||||
if (TextUtils.isEmpty(title)) title = ctx.getString(R.string.widget_not_playing);
|
if (TextUtils.isEmpty(title)) title = ctx.getString(R.string.widget_not_playing);
|
||||||
if (TextUtils.isEmpty(artist)) artist = ctx.getString(R.string.widget_placeholder_subtitle);
|
if (TextUtils.isEmpty(artist)) artist = ctx.getString(R.string.widget_placeholder_subtitle);
|
||||||
if (TextUtils.isEmpty(album)) album = "";
|
if (TextUtils.isEmpty(album)) album = "";
|
||||||
|
|
||||||
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
|
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
|
||||||
|
|
||||||
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
|
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
|
||||||
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
|
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
|
||||||
for (int id : ids) {
|
for (int id : ids) {
|
||||||
android.widget.RemoteViews rv = choosePopulate(ctx, title, artist, album, art, playing,
|
android.widget.RemoteViews rv = choosePopulate(ctx, title, artist, album, art, playing,
|
||||||
timing.elapsedText, timing.totalText, timing.progress, shuffleEnabled, repeatMode, id);
|
timing.elapsedText, timing.totalText, timing.progress, shuffleEnabled, repeatMode, id);
|
||||||
WidgetProvider.attachIntents(ctx, rv, id);
|
WidgetProvider.attachIntents(ctx, rv, id);
|
||||||
mgr.updateAppWidget(id, rv);
|
mgr.updateAppWidget(id, rv);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void pushNow(Context ctx) {
|
|
||||||
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
|
|
||||||
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
|
|
||||||
for (int id : ids) {
|
|
||||||
android.widget.RemoteViews rv = chooseBuild(ctx, id);
|
|
||||||
WidgetProvider.attachIntents(ctx, rv, id);
|
|
||||||
mgr.updateAppWidget(id, rv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void updateFromState(Context ctx,
|
|
||||||
String title,
|
|
||||||
String artist,
|
|
||||||
String album,
|
|
||||||
String coverArtId,
|
|
||||||
boolean playing,
|
|
||||||
boolean shuffleEnabled,
|
|
||||||
int repeatMode,
|
|
||||||
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 String alb = !TextUtils.isEmpty(album) ? album : "";
|
|
||||||
final boolean p = playing;
|
|
||||||
final boolean sh = shuffleEnabled;
|
|
||||||
final int rep = repeatMode;
|
|
||||||
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(coverArtId)) {
|
|
||||||
CustomGlideRequest.loadAlbumArtBitmap(
|
|
||||||
appCtx,
|
|
||||||
coverArtId,
|
|
||||||
com.cappielloantonio.tempo.util.Preferences.getImageSize(),
|
|
||||||
new CustomTarget<Bitmap>() {
|
|
||||||
@Override public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
|
|
||||||
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, alb, resource, p,
|
|
||||||
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
|
|
||||||
WidgetProvider.attachIntents(appCtx, rv, id);
|
|
||||||
mgr.updateAppWidget(id, rv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void onLoadCleared(Drawable placeholder) {
|
|
||||||
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, alb, null, p,
|
|
||||||
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
|
|
||||||
WidgetProvider.attachIntents(appCtx, rv, id);
|
|
||||||
mgr.updateAppWidget(id, rv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
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, alb, null, p,
|
|
||||||
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
|
|
||||||
WidgetProvider.attachIntents(appCtx, rv, id);
|
|
||||||
mgr.updateAppWidget(id, rv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void refreshFromController(Context ctx) {
|
|
||||||
final Context appCtx = ctx.getApplicationContext();
|
|
||||||
SessionToken token = new SessionToken(appCtx, new ComponentName(appCtx, MediaService.class));
|
|
||||||
ListenableFuture<MediaController> future = new MediaController.Builder(appCtx, token).buildAsync();
|
|
||||||
future.addListener(() -> {
|
|
||||||
try {
|
|
||||||
if (!future.isDone()) return;
|
|
||||||
MediaController c = future.get();
|
|
||||||
androidx.media3.common.MediaItem mi = c.getCurrentMediaItem();
|
|
||||||
String title = null, artist = null, album = null, coverId = null;
|
|
||||||
if (mi != null && mi.mediaMetadata != null) {
|
|
||||||
if (mi.mediaMetadata.title != null) title = mi.mediaMetadata.title.toString();
|
|
||||||
if (mi.mediaMetadata.artist != null) artist = mi.mediaMetadata.artist.toString();
|
|
||||||
if (mi.mediaMetadata.albumTitle != null) album = mi.mediaMetadata.albumTitle.toString();
|
|
||||||
if (mi.mediaMetadata.extras != null) {
|
|
||||||
if (title == null) title = mi.mediaMetadata.extras.getString("title");
|
|
||||||
if (artist == null) artist = mi.mediaMetadata.extras.getString("artist");
|
|
||||||
if (album == null) album = mi.mediaMetadata.extras.getString("album");
|
|
||||||
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),
|
|
||||||
album,
|
|
||||||
coverId,
|
|
||||||
c.isPlaying(),
|
|
||||||
c.getShuffleModeEnabled(),
|
|
||||||
c.getRepeatMode(),
|
|
||||||
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)
|
public static void pushNow(Context ctx) {
|
||||||
? MusicUtil.getReadableDurationString(safePosition, true)
|
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
|
||||||
: null;
|
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
|
||||||
String total = safeDuration > 0
|
for (int id : ids) {
|
||||||
? MusicUtil.getReadableDurationString(safeDuration, true)
|
android.widget.RemoteViews rv = chooseBuild(ctx, id);
|
||||||
: null;
|
WidgetProvider.attachIntents(ctx, rv, id);
|
||||||
|
mgr.updateAppWidget(id, rv);
|
||||||
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 void updateFromState(Context ctx,
|
||||||
}
|
String title,
|
||||||
|
String artist,
|
||||||
|
String album,
|
||||||
|
String coverArtId,
|
||||||
|
boolean playing,
|
||||||
|
boolean shuffleEnabled,
|
||||||
|
int repeatMode,
|
||||||
|
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 String alb = !TextUtils.isEmpty(album) ? album : "";
|
||||||
|
final boolean p = playing;
|
||||||
|
final boolean sh = shuffleEnabled;
|
||||||
|
final int rep = repeatMode;
|
||||||
|
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
|
||||||
|
|
||||||
public static android.widget.RemoteViews chooseBuild(Context ctx, int appWidgetId) {
|
if (!TextUtils.isEmpty(coverArtId)) {
|
||||||
LayoutSize size = resolveLayoutSize(ctx, appWidgetId);
|
CustomGlideRequest.loadAlbumArtBitmap(
|
||||||
switch (size) {
|
appCtx,
|
||||||
case MEDIUM:
|
coverArtId,
|
||||||
return WidgetViewsFactory.buildMedium(ctx);
|
com.cappielloantonio.tempo.util.Preferences.getImageSize(),
|
||||||
case LARGE:
|
new CustomTarget<Bitmap>() {
|
||||||
return WidgetViewsFactory.buildLarge(ctx);
|
@Override
|
||||||
case EXPANDED:
|
public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
|
||||||
return WidgetViewsFactory.buildExpanded(ctx);
|
AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx);
|
||||||
case COMPACT:
|
int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class));
|
||||||
default:
|
for (int id : ids) {
|
||||||
return WidgetViewsFactory.buildCompact(ctx);
|
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, resource, p,
|
||||||
|
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
|
||||||
|
WidgetProvider.attachIntents(appCtx, rv, id);
|
||||||
|
mgr.updateAppWidget(id, rv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadCleared(Drawable placeholder) {
|
||||||
|
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, alb, null, p,
|
||||||
|
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
|
||||||
|
WidgetProvider.attachIntents(appCtx, rv, id);
|
||||||
|
mgr.updateAppWidget(id, rv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
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, alb, null, p,
|
||||||
|
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
|
||||||
|
WidgetProvider.attachIntents(appCtx, rv, id);
|
||||||
|
mgr.updateAppWidget(id, rv);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static android.widget.RemoteViews choosePopulate(Context ctx,
|
public static void refreshFromController(Context ctx) {
|
||||||
String title,
|
final Context appCtx = ctx.getApplicationContext();
|
||||||
String artist,
|
SessionToken token = new SessionToken(appCtx, new ComponentName(appCtx, MediaService.class));
|
||||||
String album,
|
ListenableFuture<MediaController> future = new MediaController.Builder(appCtx, token).buildAsync();
|
||||||
Bitmap art,
|
future.addListener(() -> {
|
||||||
boolean playing,
|
try {
|
||||||
String elapsedText,
|
if (!future.isDone()) return;
|
||||||
String totalText,
|
MediaController c = future.get();
|
||||||
int progress,
|
androidx.media3.common.MediaItem mi = c.getCurrentMediaItem();
|
||||||
boolean shuffleEnabled,
|
String title = null, artist = null, album = null, coverId = null;
|
||||||
int repeatMode,
|
if (mi != null && mi.mediaMetadata != null) {
|
||||||
int appWidgetId) {
|
if (mi.mediaMetadata.title != null) title = mi.mediaMetadata.title.toString();
|
||||||
LayoutSize size = resolveLayoutSize(ctx, appWidgetId);
|
if (mi.mediaMetadata.artist != null)
|
||||||
switch (size) {
|
artist = mi.mediaMetadata.artist.toString();
|
||||||
case MEDIUM:
|
if (mi.mediaMetadata.albumTitle != null)
|
||||||
return WidgetViewsFactory.populateMedium(ctx, title, artist, album, art, playing,
|
album = mi.mediaMetadata.albumTitle.toString();
|
||||||
elapsedText, totalText, progress, shuffleEnabled, repeatMode);
|
if (mi.mediaMetadata.extras != null) {
|
||||||
case LARGE:
|
if (title == null) title = mi.mediaMetadata.extras.getString("title");
|
||||||
return WidgetViewsFactory.populateLarge(ctx, title, artist, album, art, playing,
|
if (artist == null) artist = mi.mediaMetadata.extras.getString("artist");
|
||||||
elapsedText, totalText, progress, shuffleEnabled, repeatMode);
|
if (album == null) album = mi.mediaMetadata.extras.getString("album");
|
||||||
case EXPANDED:
|
coverId = mi.mediaMetadata.extras.getString("coverArtId");
|
||||||
return WidgetViewsFactory.populateExpanded(ctx, title, artist, album, art, playing,
|
}
|
||||||
elapsedText, totalText, progress, shuffleEnabled, repeatMode);
|
}
|
||||||
case COMPACT:
|
long position = c.getCurrentPosition();
|
||||||
default:
|
long duration = c.getDuration();
|
||||||
return WidgetViewsFactory.populateCompact(ctx, title, artist, album, art, playing,
|
if (position == C.TIME_UNSET) position = 0;
|
||||||
elapsedText, totalText, progress, shuffleEnabled, repeatMode);
|
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),
|
||||||
|
album,
|
||||||
|
coverId,
|
||||||
|
c.isPlaying(),
|
||||||
|
c.getShuffleModeEnabled(),
|
||||||
|
c.getRepeatMode(),
|
||||||
|
position,
|
||||||
|
duration);
|
||||||
|
c.release();
|
||||||
|
} catch (ExecutionException | InterruptedException ignored) {
|
||||||
|
}
|
||||||
|
}, MoreExecutors.directExecutor());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static LayoutSize resolveLayoutSize(Context ctx, int appWidgetId) {
|
private static TimingInfo createTimingInfo(long positionMs, long durationMs) {
|
||||||
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
|
long safePosition = Math.max(0L, positionMs);
|
||||||
android.os.Bundle opts = mgr.getAppWidgetOptions(appWidgetId);
|
long safeDuration = durationMs > 0 ? durationMs : 0L;
|
||||||
int minH = opts != null ? opts.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) : 0;
|
if (safeDuration > 0 && safePosition > safeDuration) {
|
||||||
int expandedThreshold = ctx.getResources().getInteger(R.integer.widget_expanded_min_height_dp);
|
safePosition = safeDuration;
|
||||||
int largeThreshold = ctx.getResources().getInteger(R.integer.widget_large_min_height_dp);
|
}
|
||||||
int mediumThreshold = ctx.getResources().getInteger(R.integer.widget_medium_min_height_dp);
|
|
||||||
if (minH >= expandedThreshold) return LayoutSize.EXPANDED;
|
|
||||||
if (minH >= largeThreshold) return LayoutSize.LARGE;
|
|
||||||
if (minH >= mediumThreshold) return LayoutSize.MEDIUM;
|
|
||||||
return LayoutSize.COMPACT;
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum LayoutSize {
|
String elapsed = (safeDuration > 0 || safePosition > 0)
|
||||||
COMPACT,
|
? MusicUtil.getReadableDurationString(safePosition, true)
|
||||||
MEDIUM,
|
: null;
|
||||||
LARGE,
|
String total = safeDuration > 0
|
||||||
EXPANDED
|
? MusicUtil.getReadableDurationString(safeDuration, true)
|
||||||
}
|
: null;
|
||||||
|
|
||||||
private static final class TimingInfo {
|
int progress = 0;
|
||||||
final String elapsedText;
|
if (safeDuration > 0) {
|
||||||
final String totalText;
|
long scaled = safePosition * WidgetViewsFactory.PROGRESS_MAX;
|
||||||
final int progress;
|
long progressLong = scaled / safeDuration;
|
||||||
|
if (progressLong < 0) {
|
||||||
|
progress = 0;
|
||||||
|
} else if (progressLong > WidgetViewsFactory.PROGRESS_MAX) {
|
||||||
|
progress = WidgetViewsFactory.PROGRESS_MAX;
|
||||||
|
} else {
|
||||||
|
progress = (int) progressLong;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TimingInfo(String elapsedText, String totalText, int progress) {
|
return new TimingInfo(elapsed, total, progress);
|
||||||
this.elapsedText = elapsedText;
|
}
|
||||||
this.totalText = totalText;
|
|
||||||
this.progress = progress;
|
public static android.widget.RemoteViews chooseBuild(Context ctx, int appWidgetId) {
|
||||||
|
LayoutSize size = resolveLayoutSize(ctx, appWidgetId);
|
||||||
|
switch (size) {
|
||||||
|
case MEDIUM:
|
||||||
|
return WidgetViewsFactory.buildMedium(ctx);
|
||||||
|
case LARGE:
|
||||||
|
return WidgetViewsFactory.buildLarge(ctx);
|
||||||
|
case EXPANDED:
|
||||||
|
return WidgetViewsFactory.buildExpanded(ctx);
|
||||||
|
case COMPACT:
|
||||||
|
default:
|
||||||
|
return WidgetViewsFactory.buildCompact(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static android.widget.RemoteViews choosePopulate(Context ctx,
|
||||||
|
String title,
|
||||||
|
String artist,
|
||||||
|
String album,
|
||||||
|
Bitmap art,
|
||||||
|
boolean playing,
|
||||||
|
String elapsedText,
|
||||||
|
String totalText,
|
||||||
|
int progress,
|
||||||
|
boolean shuffleEnabled,
|
||||||
|
int repeatMode,
|
||||||
|
int appWidgetId) {
|
||||||
|
LayoutSize size = resolveLayoutSize(ctx, appWidgetId);
|
||||||
|
switch (size) {
|
||||||
|
case MEDIUM:
|
||||||
|
return WidgetViewsFactory.populateMedium(ctx, title, artist, album, art, playing,
|
||||||
|
elapsedText, totalText, progress, shuffleEnabled, repeatMode);
|
||||||
|
case LARGE:
|
||||||
|
return WidgetViewsFactory.populateLarge(ctx, title, artist, album, art, playing,
|
||||||
|
elapsedText, totalText, progress, shuffleEnabled, repeatMode);
|
||||||
|
case EXPANDED:
|
||||||
|
return WidgetViewsFactory.populateExpanded(ctx, title, artist, album, art, playing,
|
||||||
|
elapsedText, totalText, progress, shuffleEnabled, repeatMode);
|
||||||
|
case COMPACT:
|
||||||
|
default:
|
||||||
|
return WidgetViewsFactory.populateCompact(ctx, title, artist, album, art, playing,
|
||||||
|
elapsedText, totalText, progress, shuffleEnabled, repeatMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LayoutSize resolveLayoutSize(Context ctx, int appWidgetId) {
|
||||||
|
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
|
||||||
|
android.os.Bundle opts = mgr.getAppWidgetOptions(appWidgetId);
|
||||||
|
int minH = opts != null ? opts.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) : 0;
|
||||||
|
int expandedThreshold = ctx.getResources().getInteger(R.integer.widget_expanded_min_height_dp);
|
||||||
|
int largeThreshold = ctx.getResources().getInteger(R.integer.widget_large_min_height_dp);
|
||||||
|
int mediumThreshold = ctx.getResources().getInteger(R.integer.widget_medium_min_height_dp);
|
||||||
|
if (minH >= expandedThreshold) return LayoutSize.EXPANDED;
|
||||||
|
if (minH >= largeThreshold) return LayoutSize.LARGE;
|
||||||
|
if (minH >= mediumThreshold) return LayoutSize.MEDIUM;
|
||||||
|
return LayoutSize.COMPACT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum LayoutSize {
|
||||||
|
COMPACT,
|
||||||
|
MEDIUM,
|
||||||
|
LARGE,
|
||||||
|
EXPANDED
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,62 +19,93 @@ import com.cappielloantonio.tempo.R;
|
||||||
|
|
||||||
public final class WidgetViewsFactory {
|
public final class WidgetViewsFactory {
|
||||||
|
|
||||||
static final int PROGRESS_MAX = 1000;
|
static final int PROGRESS_MAX = 1000;
|
||||||
private static final float ALBUM_ART_CORNER_RADIUS_DP = 6f;
|
private static final float ALBUM_ART_CORNER_RADIUS_DP = 6f;
|
||||||
|
|
||||||
private WidgetViewsFactory() {}
|
private WidgetViewsFactory() {
|
||||||
|
|
||||||
public static RemoteViews buildCompact(Context ctx) {
|
|
||||||
return build(ctx, R.layout.widget_layout_compact, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RemoteViews buildMedium(Context ctx) {
|
|
||||||
return build(ctx, R.layout.widget_layout_medium, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RemoteViews buildLarge(Context ctx) {
|
|
||||||
return build(ctx, R.layout.widget_layout_large_short, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RemoteViews buildExpanded(Context ctx) {
|
|
||||||
return build(ctx, R.layout.widget_layout_large, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RemoteViews build(Context ctx,
|
|
||||||
int layoutRes,
|
|
||||||
boolean showAlbum,
|
|
||||||
boolean showSecondaryControls) {
|
|
||||||
RemoteViews rv = new RemoteViews(ctx.getPackageName(), layoutRes);
|
|
||||||
rv.setTextViewText(R.id.title, ctx.getString(R.string.widget_not_playing));
|
|
||||||
rv.setTextViewText(R.id.subtitle, ctx.getString(R.string.widget_placeholder_subtitle));
|
|
||||||
rv.setTextViewText(R.id.album, "");
|
|
||||||
rv.setViewVisibility(R.id.album, showAlbum ? View.INVISIBLE : View.GONE);
|
|
||||||
rv.setTextViewText(R.id.time_elapsed, ctx.getString(R.string.widget_time_elapsed_placeholder));
|
|
||||||
rv.setTextViewText(R.id.time_total, ctx.getString(R.string.widget_time_duration_placeholder));
|
|
||||||
rv.setProgressBar(R.id.progress, PROGRESS_MAX, 0, false);
|
|
||||||
rv.setImageViewResource(R.id.btn_play_pause, R.drawable.ic_play);
|
|
||||||
rv.setImageViewResource(R.id.album_art, R.drawable.ic_splash_logo);
|
|
||||||
applySecondaryControlsDefaults(ctx, rv, showSecondaryControls);
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void applySecondaryControlsDefaults(Context ctx,
|
|
||||||
RemoteViews rv,
|
|
||||||
boolean show) {
|
|
||||||
int visibility = show ? View.VISIBLE : View.GONE;
|
|
||||||
rv.setViewVisibility(R.id.controls_secondary, visibility);
|
|
||||||
rv.setViewVisibility(R.id.btn_shuffle, visibility);
|
|
||||||
rv.setViewVisibility(R.id.btn_repeat, visibility);
|
|
||||||
if (show) {
|
|
||||||
int defaultColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint);
|
|
||||||
rv.setImageViewResource(R.id.btn_shuffle, R.drawable.ic_shuffle);
|
|
||||||
rv.setImageViewResource(R.id.btn_repeat, R.drawable.ic_repeat);
|
|
||||||
rv.setInt(R.id.btn_shuffle, "setColorFilter", defaultColor);
|
|
||||||
rv.setInt(R.id.btn_repeat, "setColorFilter", defaultColor);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static RemoteViews populateCompact(Context ctx,
|
public static RemoteViews buildCompact(Context ctx) {
|
||||||
|
return build(ctx, R.layout.widget_layout_compact, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RemoteViews buildMedium(Context ctx) {
|
||||||
|
return build(ctx, R.layout.widget_layout_medium, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RemoteViews buildLarge(Context ctx) {
|
||||||
|
return build(ctx, R.layout.widget_layout_large_short, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RemoteViews buildExpanded(Context ctx) {
|
||||||
|
return build(ctx, R.layout.widget_layout_large, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RemoteViews build(Context ctx,
|
||||||
|
int layoutRes,
|
||||||
|
boolean showAlbum,
|
||||||
|
boolean showSecondaryControls) {
|
||||||
|
RemoteViews rv = new RemoteViews(ctx.getPackageName(), layoutRes);
|
||||||
|
rv.setTextViewText(R.id.title, ctx.getString(R.string.widget_not_playing));
|
||||||
|
rv.setTextViewText(R.id.subtitle, ctx.getString(R.string.widget_placeholder_subtitle));
|
||||||
|
rv.setTextViewText(R.id.album, "");
|
||||||
|
rv.setViewVisibility(R.id.album, showAlbum ? View.INVISIBLE : View.GONE);
|
||||||
|
rv.setTextViewText(R.id.time_elapsed, ctx.getString(R.string.widget_time_elapsed_placeholder));
|
||||||
|
rv.setTextViewText(R.id.time_total, ctx.getString(R.string.widget_time_duration_placeholder));
|
||||||
|
rv.setProgressBar(R.id.progress, PROGRESS_MAX, 0, false);
|
||||||
|
rv.setImageViewResource(R.id.btn_play_pause, R.drawable.ic_play);
|
||||||
|
rv.setImageViewResource(R.id.album_art, R.drawable.ic_splash_logo);
|
||||||
|
applySecondaryControlsDefaults(ctx, rv, showSecondaryControls);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applySecondaryControlsDefaults(Context ctx,
|
||||||
|
RemoteViews rv,
|
||||||
|
boolean show) {
|
||||||
|
int visibility = show ? View.VISIBLE : View.GONE;
|
||||||
|
rv.setViewVisibility(R.id.controls_secondary, visibility);
|
||||||
|
rv.setViewVisibility(R.id.btn_shuffle, visibility);
|
||||||
|
rv.setViewVisibility(R.id.btn_repeat, visibility);
|
||||||
|
if (show) {
|
||||||
|
int defaultColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint);
|
||||||
|
rv.setImageViewResource(R.id.btn_shuffle, R.drawable.ic_shuffle);
|
||||||
|
rv.setImageViewResource(R.id.btn_repeat, R.drawable.ic_repeat);
|
||||||
|
rv.setInt(R.id.btn_shuffle, "setColorFilter", defaultColor);
|
||||||
|
rv.setInt(R.id.btn_repeat, "setColorFilter", defaultColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RemoteViews populateCompact(Context ctx,
|
||||||
|
String title,
|
||||||
|
String subtitle,
|
||||||
|
String album,
|
||||||
|
Bitmap art,
|
||||||
|
boolean playing,
|
||||||
|
String elapsedText,
|
||||||
|
String totalText,
|
||||||
|
int progress,
|
||||||
|
boolean shuffleEnabled,
|
||||||
|
int repeatMode) {
|
||||||
|
return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText,
|
||||||
|
progress, R.layout.widget_layout_compact, false, false, shuffleEnabled, repeatMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RemoteViews populateMedium(Context ctx,
|
||||||
|
String title,
|
||||||
|
String subtitle,
|
||||||
|
String album,
|
||||||
|
Bitmap art,
|
||||||
|
boolean playing,
|
||||||
|
String elapsedText,
|
||||||
|
String totalText,
|
||||||
|
int progress,
|
||||||
|
boolean shuffleEnabled,
|
||||||
|
int repeatMode) {
|
||||||
|
return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText,
|
||||||
|
progress, R.layout.widget_layout_medium, true, true, shuffleEnabled, repeatMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RemoteViews populateLarge(Context ctx,
|
||||||
String title,
|
String title,
|
||||||
String subtitle,
|
String subtitle,
|
||||||
String album,
|
String album,
|
||||||
|
|
@ -85,167 +116,137 @@ public final class WidgetViewsFactory {
|
||||||
int progress,
|
int progress,
|
||||||
boolean shuffleEnabled,
|
boolean shuffleEnabled,
|
||||||
int repeatMode) {
|
int repeatMode) {
|
||||||
return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText,
|
return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText,
|
||||||
progress, R.layout.widget_layout_compact, false, false, shuffleEnabled, repeatMode);
|
progress, R.layout.widget_layout_large_short, true, true, shuffleEnabled, repeatMode);
|
||||||
}
|
|
||||||
|
|
||||||
public static RemoteViews populateMedium(Context ctx,
|
|
||||||
String title,
|
|
||||||
String subtitle,
|
|
||||||
String album,
|
|
||||||
Bitmap art,
|
|
||||||
boolean playing,
|
|
||||||
String elapsedText,
|
|
||||||
String totalText,
|
|
||||||
int progress,
|
|
||||||
boolean shuffleEnabled,
|
|
||||||
int repeatMode) {
|
|
||||||
return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText,
|
|
||||||
progress, R.layout.widget_layout_medium, true, true, shuffleEnabled, repeatMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RemoteViews populateLarge(Context ctx,
|
|
||||||
String title,
|
|
||||||
String subtitle,
|
|
||||||
String album,
|
|
||||||
Bitmap art,
|
|
||||||
boolean playing,
|
|
||||||
String elapsedText,
|
|
||||||
String totalText,
|
|
||||||
int progress,
|
|
||||||
boolean shuffleEnabled,
|
|
||||||
int repeatMode) {
|
|
||||||
return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText,
|
|
||||||
progress, R.layout.widget_layout_large_short, true, true, shuffleEnabled, repeatMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RemoteViews populateExpanded(Context ctx,
|
|
||||||
String title,
|
|
||||||
String subtitle,
|
|
||||||
String album,
|
|
||||||
Bitmap art,
|
|
||||||
boolean playing,
|
|
||||||
String elapsedText,
|
|
||||||
String totalText,
|
|
||||||
int progress,
|
|
||||||
boolean shuffleEnabled,
|
|
||||||
int repeatMode) {
|
|
||||||
return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText,
|
|
||||||
progress, R.layout.widget_layout_large, true, true, shuffleEnabled, repeatMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RemoteViews populateWithLayout(Context ctx,
|
|
||||||
String title,
|
|
||||||
String subtitle,
|
|
||||||
String album,
|
|
||||||
Bitmap art,
|
|
||||||
boolean playing,
|
|
||||||
String elapsedText,
|
|
||||||
String totalText,
|
|
||||||
int progress,
|
|
||||||
int layoutRes,
|
|
||||||
boolean showAlbum,
|
|
||||||
boolean showSecondaryControls,
|
|
||||||
boolean shuffleEnabled,
|
|
||||||
int repeatMode) {
|
|
||||||
RemoteViews rv = new RemoteViews(ctx.getPackageName(), layoutRes);
|
|
||||||
rv.setTextViewText(R.id.title, title);
|
|
||||||
rv.setTextViewText(R.id.subtitle, subtitle);
|
|
||||||
|
|
||||||
if (showAlbum && !TextUtils.isEmpty(album)) {
|
|
||||||
rv.setTextViewText(R.id.album, album);
|
|
||||||
rv.setViewVisibility(R.id.album, View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
rv.setTextViewText(R.id.album, "");
|
|
||||||
rv.setViewVisibility(R.id.album, View.GONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (art != null) {
|
public static RemoteViews populateExpanded(Context ctx,
|
||||||
Bitmap rounded = maybeRoundBitmap(ctx, art);
|
String title,
|
||||||
rv.setImageViewBitmap(R.id.album_art, rounded != null ? rounded : art);
|
String subtitle,
|
||||||
} else {
|
String album,
|
||||||
rv.setImageViewResource(R.id.album_art, R.drawable.ic_splash_logo);
|
Bitmap art,
|
||||||
|
boolean playing,
|
||||||
|
String elapsedText,
|
||||||
|
String totalText,
|
||||||
|
int progress,
|
||||||
|
boolean shuffleEnabled,
|
||||||
|
int repeatMode) {
|
||||||
|
return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText,
|
||||||
|
progress, R.layout.widget_layout_large, true, true, shuffleEnabled, repeatMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
rv.setImageViewResource(R.id.btn_play_pause,
|
private static RemoteViews populateWithLayout(Context ctx,
|
||||||
playing ? R.drawable.ic_pause : R.drawable.ic_play);
|
String title,
|
||||||
|
String subtitle,
|
||||||
|
String album,
|
||||||
|
Bitmap art,
|
||||||
|
boolean playing,
|
||||||
|
String elapsedText,
|
||||||
|
String totalText,
|
||||||
|
int progress,
|
||||||
|
int layoutRes,
|
||||||
|
boolean showAlbum,
|
||||||
|
boolean showSecondaryControls,
|
||||||
|
boolean shuffleEnabled,
|
||||||
|
int repeatMode) {
|
||||||
|
RemoteViews rv = new RemoteViews(ctx.getPackageName(), layoutRes);
|
||||||
|
rv.setTextViewText(R.id.title, title);
|
||||||
|
rv.setTextViewText(R.id.subtitle, subtitle);
|
||||||
|
|
||||||
String elapsed = !TextUtils.isEmpty(elapsedText)
|
if (showAlbum && !TextUtils.isEmpty(album)) {
|
||||||
? elapsedText
|
rv.setTextViewText(R.id.album, album);
|
||||||
: ctx.getString(R.string.widget_time_elapsed_placeholder);
|
rv.setViewVisibility(R.id.album, View.VISIBLE);
|
||||||
String total = !TextUtils.isEmpty(totalText)
|
} else {
|
||||||
? totalText
|
rv.setTextViewText(R.id.album, "");
|
||||||
: ctx.getString(R.string.widget_time_duration_placeholder);
|
rv.setViewVisibility(R.id.album, View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
int safeProgress = progress;
|
if (art != null) {
|
||||||
if (safeProgress < 0) safeProgress = 0;
|
Bitmap rounded = maybeRoundBitmap(ctx, art);
|
||||||
if (safeProgress > PROGRESS_MAX) safeProgress = PROGRESS_MAX;
|
rv.setImageViewBitmap(R.id.album_art, rounded != null ? rounded : art);
|
||||||
|
} else {
|
||||||
|
rv.setImageViewResource(R.id.album_art, R.drawable.ic_splash_logo);
|
||||||
|
}
|
||||||
|
|
||||||
rv.setTextViewText(R.id.time_elapsed, elapsed);
|
rv.setImageViewResource(R.id.btn_play_pause,
|
||||||
rv.setTextViewText(R.id.time_total, total);
|
playing ? R.drawable.ic_pause : R.drawable.ic_play);
|
||||||
rv.setProgressBar(R.id.progress, PROGRESS_MAX, safeProgress, false);
|
|
||||||
|
|
||||||
applySecondaryControls(ctx, rv, showSecondaryControls, shuffleEnabled, repeatMode);
|
String elapsed = !TextUtils.isEmpty(elapsedText)
|
||||||
|
? elapsedText
|
||||||
|
: ctx.getString(R.string.widget_time_elapsed_placeholder);
|
||||||
|
String total = !TextUtils.isEmpty(totalText)
|
||||||
|
? totalText
|
||||||
|
: ctx.getString(R.string.widget_time_duration_placeholder);
|
||||||
|
|
||||||
return rv;
|
int safeProgress = progress;
|
||||||
}
|
if (safeProgress < 0) safeProgress = 0;
|
||||||
|
if (safeProgress > PROGRESS_MAX) safeProgress = PROGRESS_MAX;
|
||||||
|
|
||||||
private static Bitmap maybeRoundBitmap(Context ctx, Bitmap source) {
|
rv.setTextViewText(R.id.time_elapsed, elapsed);
|
||||||
if (source == null || source.isRecycled()) {
|
rv.setTextViewText(R.id.time_total, total);
|
||||||
return null;
|
rv.setProgressBar(R.id.progress, PROGRESS_MAX, safeProgress, false);
|
||||||
|
|
||||||
|
applySecondaryControls(ctx, rv, showSecondaryControls, shuffleEnabled, repeatMode);
|
||||||
|
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
private static Bitmap maybeRoundBitmap(Context ctx, Bitmap source) {
|
||||||
int width = source.getWidth();
|
if (source == null || source.isRecycled()) {
|
||||||
int height = source.getHeight();
|
return null;
|
||||||
if (width <= 0 || height <= 0) {
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
try {
|
||||||
Canvas canvas = new Canvas(output);
|
int width = source.getWidth();
|
||||||
|
int height = source.getHeight();
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
|
Canvas canvas = new Canvas(output);
|
||||||
|
|
||||||
float radiusPx = TypedValue.applyDimension(
|
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
TypedValue.COMPLEX_UNIT_DIP,
|
paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
|
||||||
ALBUM_ART_CORNER_RADIUS_DP,
|
|
||||||
ctx.getResources().getDisplayMetrics());
|
|
||||||
float maxRadius = Math.min(width, height) / 2f;
|
|
||||||
float safeRadius = Math.min(radiusPx, maxRadius);
|
|
||||||
|
|
||||||
canvas.drawRoundRect(new RectF(0f, 0f, width, height), safeRadius, safeRadius, paint);
|
float radiusPx = TypedValue.applyDimension(
|
||||||
return output;
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
} catch (RuntimeException | OutOfMemoryError e) {
|
ALBUM_ART_CORNER_RADIUS_DP,
|
||||||
android.util.Log.w("TempoWidget", "Failed to round album art", e);
|
ctx.getResources().getDisplayMetrics());
|
||||||
return null;
|
float maxRadius = Math.min(width, height) / 2f;
|
||||||
}
|
float safeRadius = Math.min(radiusPx, maxRadius);
|
||||||
}
|
|
||||||
|
|
||||||
private static void applySecondaryControls(Context ctx,
|
canvas.drawRoundRect(new RectF(0f, 0f, width, height), safeRadius, safeRadius, paint);
|
||||||
RemoteViews rv,
|
return output;
|
||||||
boolean show,
|
} catch (RuntimeException | OutOfMemoryError e) {
|
||||||
boolean shuffleEnabled,
|
android.util.Log.w("TempoWidget", "Failed to round album art", e);
|
||||||
int repeatMode) {
|
return null;
|
||||||
if (!show) {
|
}
|
||||||
rv.setViewVisibility(R.id.controls_secondary, View.GONE);
|
|
||||||
rv.setViewVisibility(R.id.btn_shuffle, View.GONE);
|
|
||||||
rv.setViewVisibility(R.id.btn_repeat, View.GONE);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int inactiveColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint);
|
private static void applySecondaryControls(Context ctx,
|
||||||
int activeColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint_active);
|
RemoteViews rv,
|
||||||
|
boolean show,
|
||||||
|
boolean shuffleEnabled,
|
||||||
|
int repeatMode) {
|
||||||
|
if (!show) {
|
||||||
|
rv.setViewVisibility(R.id.controls_secondary, View.GONE);
|
||||||
|
rv.setViewVisibility(R.id.btn_shuffle, View.GONE);
|
||||||
|
rv.setViewVisibility(R.id.btn_repeat, View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
rv.setViewVisibility(R.id.controls_secondary, View.VISIBLE);
|
int inactiveColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint);
|
||||||
rv.setViewVisibility(R.id.btn_shuffle, View.VISIBLE);
|
int activeColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint_active);
|
||||||
rv.setViewVisibility(R.id.btn_repeat, View.VISIBLE);
|
|
||||||
rv.setImageViewResource(R.id.btn_shuffle, R.drawable.ic_shuffle);
|
rv.setViewVisibility(R.id.controls_secondary, View.VISIBLE);
|
||||||
rv.setImageViewResource(R.id.btn_repeat,
|
rv.setViewVisibility(R.id.btn_shuffle, View.VISIBLE);
|
||||||
repeatMode == Player.REPEAT_MODE_ONE ? R.drawable.ic_repeat_one : R.drawable.ic_repeat);
|
rv.setViewVisibility(R.id.btn_repeat, View.VISIBLE);
|
||||||
rv.setInt(R.id.btn_shuffle, "setColorFilter", shuffleEnabled ? activeColor : inactiveColor);
|
rv.setImageViewResource(R.id.btn_shuffle, R.drawable.ic_shuffle);
|
||||||
rv.setInt(R.id.btn_repeat, "setColorFilter",
|
rv.setImageViewResource(R.id.btn_repeat,
|
||||||
repeatMode == Player.REPEAT_MODE_OFF ? inactiveColor : activeColor);
|
repeatMode == Player.REPEAT_MODE_ONE ? R.drawable.ic_repeat_one : R.drawable.ic_repeat);
|
||||||
}
|
rv.setInt(R.id.btn_shuffle, "setColorFilter", shuffleEnabled ? activeColor : inactiveColor);
|
||||||
|
rv.setInt(R.id.btn_repeat, "setColorFilter",
|
||||||
|
repeatMode == Player.REPEAT_MODE_OFF ? inactiveColor : activeColor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue