chore: formatting

This commit is contained in:
eddyizm 2025-10-07 21:26:36 -07:00
parent 35af1f9038
commit 45793c343a
No known key found for this signature in database
GPG key ID: CF5F671829E8158A
5 changed files with 486 additions and 469 deletions

View file

@ -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());
}
} }

View file

@ -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);

View file

@ -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 {
}

View file

@ -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;
}
} }
}
} }

View file

@ -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);
}
} }