diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetActions.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetActions.java index 5be730f8..c6bd8e60 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetActions.java +++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetActions.java @@ -15,47 +15,48 @@ import com.google.common.util.concurrent.MoreExecutors; import java.util.concurrent.ExecutionException; public final class WidgetActions { - public static void dispatchToMediaSession(Context ctx, String action) { - Log.d("TempoWidget", "dispatch action=" + action); - Context appCtx = ctx.getApplicationContext(); - SessionToken token = new SessionToken(appCtx, new ComponentName(appCtx, MediaService.class)); - ListenableFuture future = new MediaController.Builder(appCtx, token).buildAsync(); - future.addListener(() -> { - try { - if (!future.isDone()) return; - MediaController c = future.get(); - Log.d("TempoWidget", "controller connected, isPlaying=" + c.isPlaying()); - switch (action) { - case WidgetProvider.ACT_PLAY_PAUSE: - if (c.isPlaying()) c.pause(); else c.play(); - break; - case WidgetProvider.ACT_NEXT: - c.seekToNext(); - break; - case WidgetProvider.ACT_PREV: - c.seekToPrevious(); - break; - case WidgetProvider.ACT_TOGGLE_SHUFFLE: - c.setShuffleModeEnabled(!c.getShuffleModeEnabled()); - break; - case WidgetProvider.ACT_CYCLE_REPEAT: - int repeatMode = c.getRepeatMode(); - int nextMode; - if (repeatMode == Player.REPEAT_MODE_OFF) { - nextMode = Player.REPEAT_MODE_ALL; - } else if (repeatMode == Player.REPEAT_MODE_ALL) { - nextMode = Player.REPEAT_MODE_ONE; - } else { - nextMode = Player.REPEAT_MODE_OFF; + public static void dispatchToMediaSession(Context ctx, String action) { + Log.d("TempoWidget", "dispatch action=" + action); + Context appCtx = ctx.getApplicationContext(); + SessionToken token = new SessionToken(appCtx, new ComponentName(appCtx, MediaService.class)); + ListenableFuture future = new MediaController.Builder(appCtx, token).buildAsync(); + future.addListener(() -> { + try { + if (!future.isDone()) return; + MediaController c = future.get(); + Log.d("TempoWidget", "controller connected, isPlaying=" + c.isPlaying()); + switch (action) { + case WidgetProvider.ACT_PLAY_PAUSE: + if (c.isPlaying()) c.pause(); + else c.play(); + break; + case WidgetProvider.ACT_NEXT: + c.seekToNext(); + break; + case WidgetProvider.ACT_PREV: + c.seekToPrevious(); + break; + case WidgetProvider.ACT_TOGGLE_SHUFFLE: + c.setShuffleModeEnabled(!c.getShuffleModeEnabled()); + break; + case WidgetProvider.ACT_CYCLE_REPEAT: + int repeatMode = c.getRepeatMode(); + int nextMode; + if (repeatMode == Player.REPEAT_MODE_OFF) { + nextMode = Player.REPEAT_MODE_ALL; + } else if (repeatMode == Player.REPEAT_MODE_ALL) { + nextMode = Player.REPEAT_MODE_ONE; + } 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); - break; - } - WidgetUpdateManager.refreshFromController(ctx); - c.release(); - } catch (ExecutionException | InterruptedException e) { - Log.e("TempoWidget", "dispatch failed", e); - } - }, MoreExecutors.directExecutor()); - } + }, MoreExecutors.directExecutor()); + } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java index 57033c20..06986145 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java +++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java @@ -8,9 +8,12 @@ import android.content.Intent; import android.widget.RemoteViews; import com.cappielloantonio.tempo.R; + import android.app.TaskStackBuilder; import android.app.PendingIntent; + import com.cappielloantonio.tempo.ui.activity.MainActivity; + import android.util.Log; 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_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) { RemoteViews rv = WidgetUpdateManager.chooseBuild(ctx, 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); String a = intent.getAction(); 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); RemoteViews rv = WidgetUpdateManager.chooseBuild(context, appWidgetId); attachIntents(context, rv, appWidgetId); diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider4x1.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider4x1.java index 79ef6af1..b4e5923a 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider4x1.java +++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider4x1.java @@ -4,5 +4,6 @@ package com.cappielloantonio.tempo.widget; * AppWidget provider entry for the 4x1 widget card. Inherits all behavior * from {@link WidgetProvider}. */ -public class WidgetProvider4x1 extends WidgetProvider {} +public class WidgetProvider4x1 extends WidgetProvider { +} diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java index dbbcc6b3..4132511e 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java +++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java @@ -6,263 +6,271 @@ import android.content.Context; import android.graphics.Bitmap; import android.text.TextUtils; import android.graphics.drawable.Drawable; + 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; public final class WidgetUpdateManager { - public static void updateFromState(Context ctx, - String title, - String artist, - String album, - Bitmap art, - boolean playing, - boolean shuffleEnabled, - int repeatMode, - 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); - if (TextUtils.isEmpty(album)) album = ""; + public static void updateFromState(Context ctx, + String title, + String artist, + String album, + Bitmap art, + boolean playing, + boolean shuffleEnabled, + int repeatMode, + 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); + if (TextUtils.isEmpty(album)) album = ""; - final TimingInfo timing = createTimingInfo(positionMs, durationMs); + 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, album, art, playing, - timing.elapsedText, timing.totalText, timing.progress, shuffleEnabled, repeatMode, id); - WidgetProvider.attachIntents(ctx, rv, id); - 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() { - @Override public void onResourceReady(Bitmap resource, Transition 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 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"); - } + 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, album, art, playing, + timing.elapsedText, timing.totalText, timing.progress, shuffleEnabled, repeatMode, id); + WidgetProvider.attachIntents(ctx, rv, id); + mgr.updateAppWidget(id, rv); } - 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) - ? 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; - } + 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); + } } - 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) { - 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); + if (!TextUtils.isEmpty(coverArtId)) { + CustomGlideRequest.loadAlbumArtBitmap( + appCtx, + coverArtId, + com.cappielloantonio.tempo.util.Preferences.getImageSize(), + new CustomTarget() { + @Override + public void onResourceReady(Bitmap resource, Transition 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); + } + } } - } - 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); + public static void refreshFromController(Context ctx) { + final Context appCtx = ctx.getApplicationContext(); + SessionToken token = new SessionToken(appCtx, new ComponentName(appCtx, MediaService.class)); + ListenableFuture 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 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 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; + } - private enum LayoutSize { - COMPACT, - MEDIUM, - LARGE, - EXPANDED - } + String elapsed = (safeDuration > 0 || safePosition > 0) + ? MusicUtil.getReadableDurationString(safePosition, true) + : null; + String total = safeDuration > 0 + ? MusicUtil.getReadableDurationString(safeDuration, true) + : null; - private static final class TimingInfo { - final String elapsedText; - final String totalText; - final int progress; + 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; + } + } - TimingInfo(String elapsedText, String totalText, int progress) { - this.elapsedText = elapsedText; - this.totalText = totalText; - this.progress = progress; + return new TimingInfo(elapsed, total, 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; + } } - } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetViewsFactory.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetViewsFactory.java index ef960aef..c66fd1cb 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetViewsFactory.java +++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetViewsFactory.java @@ -19,62 +19,93 @@ import com.cappielloantonio.tempo.R; public final class WidgetViewsFactory { - static final int PROGRESS_MAX = 1000; - private static final float ALBUM_ART_CORNER_RADIUS_DP = 6f; + static final int PROGRESS_MAX = 1000; + private static final float ALBUM_ART_CORNER_RADIUS_DP = 6f; - 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); + private WidgetViewsFactory() { } - } - 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 subtitle, String album, @@ -85,167 +116,137 @@ public final class WidgetViewsFactory { 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 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); + return populateWithLayout(ctx, title, subtitle, album, art, playing, elapsedText, totalText, + progress, R.layout.widget_layout_large_short, true, true, shuffleEnabled, repeatMode); } - if (art != null) { - Bitmap rounded = maybeRoundBitmap(ctx, art); - rv.setImageViewBitmap(R.id.album_art, rounded != null ? rounded : art); - } else { - rv.setImageViewResource(R.id.album_art, R.drawable.ic_splash_logo); + 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); } - rv.setImageViewResource(R.id.btn_play_pause, - playing ? R.drawable.ic_pause : R.drawable.ic_play); + 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); - 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); + 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); + } - int safeProgress = progress; - if (safeProgress < 0) safeProgress = 0; - if (safeProgress > PROGRESS_MAX) safeProgress = PROGRESS_MAX; + if (art != null) { + Bitmap rounded = maybeRoundBitmap(ctx, art); + 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.setTextViewText(R.id.time_total, total); - rv.setProgressBar(R.id.progress, PROGRESS_MAX, safeProgress, false); + rv.setImageViewResource(R.id.btn_play_pause, + playing ? R.drawable.ic_pause : R.drawable.ic_play); - 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) { - if (source == null || source.isRecycled()) { - return null; + rv.setTextViewText(R.id.time_elapsed, elapsed); + rv.setTextViewText(R.id.time_total, total); + rv.setProgressBar(R.id.progress, PROGRESS_MAX, safeProgress, false); + + applySecondaryControls(ctx, rv, showSecondaryControls, shuffleEnabled, repeatMode); + + return rv; } - try { - int width = source.getWidth(); - int height = source.getHeight(); - if (width <= 0 || height <= 0) { - return null; - } + private static Bitmap maybeRoundBitmap(Context ctx, Bitmap source) { + if (source == null || source.isRecycled()) { + return null; + } - Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(output); + try { + int width = source.getWidth(); + int height = source.getHeight(); + if (width <= 0 || height <= 0) { + return null; + } - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); + Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(output); - float radiusPx = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - ALBUM_ART_CORNER_RADIUS_DP, - ctx.getResources().getDisplayMetrics()); - float maxRadius = Math.min(width, height) / 2f; - float safeRadius = Math.min(radiusPx, maxRadius); + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); - canvas.drawRoundRect(new RectF(0f, 0f, width, height), safeRadius, safeRadius, paint); - return output; - } catch (RuntimeException | OutOfMemoryError e) { - android.util.Log.w("TempoWidget", "Failed to round album art", e); - return null; - } - } + float radiusPx = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + ALBUM_ART_CORNER_RADIUS_DP, + ctx.getResources().getDisplayMetrics()); + float maxRadius = Math.min(width, height) / 2f; + float safeRadius = Math.min(radiusPx, maxRadius); - private static void applySecondaryControls(Context ctx, - 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; + canvas.drawRoundRect(new RectF(0f, 0f, width, height), safeRadius, safeRadius, paint); + return output; + } catch (RuntimeException | OutOfMemoryError e) { + android.util.Log.w("TempoWidget", "Failed to round album art", e); + return null; + } } - int inactiveColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint); - int activeColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint_active); + private static void applySecondaryControls(Context ctx, + 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); - rv.setViewVisibility(R.id.btn_shuffle, View.VISIBLE); - rv.setViewVisibility(R.id.btn_repeat, View.VISIBLE); - rv.setImageViewResource(R.id.btn_shuffle, R.drawable.ic_shuffle); - rv.setImageViewResource(R.id.btn_repeat, - 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); - } + int inactiveColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint); + int activeColor = ContextCompat.getColor(ctx, R.color.widget_icon_tint_active); + + rv.setViewVisibility(R.id.controls_secondary, View.VISIBLE); + rv.setViewVisibility(R.id.btn_shuffle, View.VISIBLE); + rv.setViewVisibility(R.id.btn_repeat, View.VISIBLE); + rv.setImageViewResource(R.id.btn_shuffle, R.drawable.ic_shuffle); + rv.setImageViewResource(R.id.btn_repeat, + 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); + } }