diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6a1ae96f..e0b1e821 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -126,14 +126,17 @@ - + + android:resource="@xml/app_widget_small_info" /> restoredQueue = MusicPlaybackQueueStore.getInstance(context).getSavedPlayingQueue(); - int restoredPosition = PreferenceManager.getDefaultSharedPreferences(context).getInt(MusicService.SAVED_POSITION, -1); - if (!restoredQueue.isEmpty() && restoredPosition >= 0 && restoredPosition < restoredQueue.size()) { - song = restoredQueue.get(restoredPosition); - } - } - linkButtons(context, widgetLayout); - widgetLayout.setTextViewText(R.id.title, song.title); - String separator = TextUtils.isEmpty(song.artistName) || TextUtils.isEmpty(song.albumName) ? "" : " | "; - widgetLayout.setTextViewText(R.id.song_secondary_information, song.artistName + separator + song.albumName); - - updateWidgetsPlayState(context, isPlaying); - loadAlbumCover(context, song); - } - - public static void updateWidgetsPlayState(@NonNull final Context context, boolean isPlaying) { - initLayoutIfNecessary(context); - int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp; - widgetLayout.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(Util.getTintedDrawable(context, playPauseRes, MaterialValueHelper.getPrimaryTextColor(context, true)), 1.5f)); - updateWidgets(context); - } - - private static void initLayoutIfNecessary(Context context) { - if (widgetLayout == null) - widgetLayout = new RemoteViews(context.getPackageName(), R.layout.widget_medium); - widgetLayout.setImageViewBitmap(R.id.button_next, createBitmap(Util.getTintedDrawable(context, R.drawable.ic_skip_next_white_24dp, MaterialValueHelper.getPrimaryTextColor(context, true)), 1.5f)); - widgetLayout.setImageViewBitmap(R.id.button_prev, createBitmap(Util.getTintedDrawable(context, R.drawable.ic_skip_previous_white_24dp, MaterialValueHelper.getPrimaryTextColor(context, true)), 1.5f)); - } - - private static void updateWidgets(@NonNull final Context context) { - // this is only a temporary solution until we rewrote this whole class. - try { - AppWidgetManager man = AppWidgetManager.getInstance(context); - int[] ids = man.getAppWidgetIds( - new ComponentName(context, WidgetMedium.class)); - for (int widgetId : ids) { - man.updateAppWidget(widgetId, widgetLayout); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - private static Handler uiThreadHandler; - private static Target target; - - private static void loadAlbumCover(@NonNull final Context context, @Nullable final Song song) { - if (song == null) return; - - if (uiThreadHandler == null) { - uiThreadHandler = new Handler(Looper.getMainLooper()); - } - if (target == null) { - int widgetImageSize = context.getResources().getDimensionPixelSize(R.dimen.widget_medium_image_size); - target = new SimpleTarget(widgetImageSize, widgetImageSize) { - @Override - public void onLoadFailed(Exception e, Drawable errorDrawable) { - super.onLoadFailed(e, errorDrawable); - setAlbumCover(context, null); - } - - @Override - public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { - setAlbumCover(context, resource); - } - }; - } - - uiThreadHandler.post(new Runnable() { - @Override - public void run() { - SongGlideRequest.Builder.from(Glide.with(context), song) - .checkIgnoreMediaStore(context) - .asBitmap().build() - .into(target); - } - }); - } - - private static void setAlbumCover(@NonNull final Context context, @Nullable final Bitmap albumArt) { - if (albumArt != null) { - widgetLayout.setImageViewBitmap(R.id.image, albumArt); - } else { - widgetLayout.setImageViewResource(R.id.image, R.drawable.default_album_art); - } - updateWidgets(context); - } - - private static void linkButtons(@NonNull final Context context, @NonNull final RemoteViews views) { - views.setOnClickPendingIntent(R.id.content, retrievePlaybackActions(context, 0)); - views.setOnClickPendingIntent(R.id.button_toggle_play_pause, retrievePlaybackActions(context, 1)); - views.setOnClickPendingIntent(R.id.button_next, retrievePlaybackActions(context, 2)); - views.setOnClickPendingIntent(R.id.button_prev, retrievePlaybackActions(context, 3)); - } - - private static PendingIntent retrievePlaybackActions(@NonNull final Context context, final int which) { - final ComponentName serviceName = new ComponentName(context, MusicService.class); - Intent intent; - switch (which) { - case 0: - intent = new Intent(context, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - case 1: - intent = new Intent(MusicService.ACTION_TOGGLE_PAUSE); - intent.setComponent(serviceName); - return PendingIntent.getService(context, 1, intent, 0); - case 2: - intent = new Intent(MusicService.ACTION_SKIP); - intent.setComponent(serviceName); - return PendingIntent.getService(context, 2, intent, 0); - case 3: - intent = new Intent(MusicService.ACTION_REWIND); - intent.setComponent(serviceName); - return PendingIntent.getService(context, 3, intent, 0); - } - return null; - } - - @Override - public void onUpdate(@NonNull Context context, @NonNull AppWidgetManager appWidgetManager, @NonNull int[] appWidgetIds) { - updateWidgets(context, MusicPlayerRemote.getCurrentSong(), MusicPlayerRemote.isPlaying()); - } - - private static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) { - Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier), (int) (drawable.getIntrinsicHeight() * sizeMultiplier), Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(bitmap); - drawable.setBounds(0, 0, c.getWidth(), c.getHeight()); - drawable.draw(c); - return bitmap; - } -} - - diff --git a/app/src/main/java/com/kabouzeid/gramophone/appwidgets/AppWidgetSmall.java b/app/src/main/java/com/kabouzeid/gramophone/appwidgets/AppWidgetSmall.java new file mode 100644 index 00000000..ca0f3be8 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/appwidgets/AppWidgetSmall.java @@ -0,0 +1,189 @@ +package com.kabouzeid.gramophone.appwidgets; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.view.View; +import android.widget.RemoteViews; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.Target; +import com.kabouzeid.appthemehelper.util.MaterialValueHelper; +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.glide.SongGlideRequest; +import com.kabouzeid.gramophone.model.Song; +import com.kabouzeid.gramophone.service.MusicService; +import com.kabouzeid.gramophone.ui.activities.MainActivity; +import com.kabouzeid.gramophone.util.Util; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public class AppWidgetSmall extends BaseAppWidget { + public static final String NAME = "app_widget_small"; + + private static AppWidgetSmall mInstance; + + public static synchronized AppWidgetSmall getInstance() { + if (mInstance == null) { + mInstance = new AppWidgetSmall(); + } + return mInstance; + } + + /** + * {@inheritDoc} + */ + @Override + public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, + final int[] appWidgetIds) { + defaultAppWidget(context, appWidgetIds); + final Intent updateIntent = new Intent(MusicService.APP_WIDGET_UPDATE); + updateIntent.putExtra(MusicService.EXTRA_APP_WIDGET_NAME, NAME); + updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); + updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + context.sendBroadcast(updateIntent); + } + + /** + * Initialize given widgets to default state, where we launch Music on + * default click and hide actions if service not running. + */ + private void defaultAppWidget(final Context context, final int[] appWidgetIds) { + final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(), R.layout.app_widget_small); + + appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE); + appWidgetView.setViewVisibility(R.id.image, View.INVISIBLE); + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(Util.getTintedDrawable(context, R.drawable.ic_skip_next_white_24dp, MaterialValueHelper.getPrimaryTextColor(context, true)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(Util.getTintedDrawable(context, R.drawable.ic_skip_previous_white_24dp, MaterialValueHelper.getPrimaryTextColor(context, true)), 1f)); + + linkButtons(context, appWidgetView); + pushUpdate(context, appWidgetIds, appWidgetView); + } + + private void pushUpdate(final Context context, final int[] appWidgetIds, final RemoteViews views) { + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + if (appWidgetIds != null) { + appWidgetManager.updateAppWidget(appWidgetIds, views); + } else { + appWidgetManager.updateAppWidget(new ComponentName(context, getClass()), views); + } + } + + /** + * Check against {@link AppWidgetManager} if there are any instances of this + * widget. + */ + private boolean hasInstances(final Context context) { + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + final int[] mAppWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, + getClass())); + return mAppWidgetIds.length > 0; + } + + /** + * Handle a change notification coming over from + * {@link MusicService} + */ + public void notifyChange(final MusicService service, final String what) { + if (hasInstances(service)) { + if (MusicService.META_CHANGED.equals(what) || MusicService.PLAY_STATE_CHANGED.equals(what)) { + performUpdate(service, null); + } + } + } + + /** + * Update all active widget instances by pushing changes + */ + public void performUpdate(final MusicService service, final int[] appWidgetIds) { + final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(), R.layout.app_widget_small); + + final boolean isPlaying = service.isPlaying(); + final Song song = service.getCurrentSong(); + + // Set the titles and artwork + if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { + appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE); + } else { + appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE); + appWidgetView.setTextViewText(R.id.title, song.title); + appWidgetView.setTextViewText(R.id.text, song.artistName); + } + + // Set correct drawable for pause state + int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp; + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(Util.getTintedDrawable(service, playPauseRes, MaterialValueHelper.getPrimaryTextColor(service, true)), 1f)); + + // Link actions buttons to intents + linkButtons(service, appWidgetView); + + // load the album cover async and push the update on completion + final Context appContext = service.getApplicationContext(); + final int widgetImageSize = service.getResources().getDimensionPixelSize(R.dimen.app_widget_small_image_size); + service.runOnUiThread(new Runnable() { + @Override + public void run() { + if (target != null) { + Glide.clear(target); + } + target = SongGlideRequest.Builder.from(Glide.with(appContext), song) + .checkIgnoreMediaStore(appContext) + .asBitmap().build() + .into(new SimpleTarget(widgetImageSize, widgetImageSize) { + @Override + public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { + appWidgetView.setViewVisibility(R.id.image, View.VISIBLE); + appWidgetView.setImageViewBitmap(R.id.image, resource); + pushUpdate(appContext, appWidgetIds, appWidgetView); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + appWidgetView.setViewVisibility(R.id.image, View.VISIBLE); + appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art); + pushUpdate(appContext, appWidgetIds, appWidgetView); + } + }); + } + }); + } + + private Target target; // for cancellation + + /** + * Link up various button actions using {@link PendingIntent}. + */ + private void linkButtons(final Context context, final RemoteViews views) { + Intent action; + PendingIntent pendingIntent; + + final ComponentName serviceName = new ComponentName(context, MusicService.class); + + // Home + action = new Intent(context, MainActivity.class); + action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + pendingIntent = PendingIntent.getActivity(context, 0, action, 0); + views.setOnClickPendingIntent(R.id.content, pendingIntent); + + // Previous track + pendingIntent = buildPendingIntent(context, MusicService.ACTION_REWIND, serviceName); + views.setOnClickPendingIntent(R.id.button_prev, pendingIntent); + + // Play and pause + pendingIntent = buildPendingIntent(context, MusicService.ACTION_TOGGLE_PAUSE, serviceName); + views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent); + + // Next track + pendingIntent = buildPendingIntent(context, MusicService.ACTION_SKIP, serviceName); + views.setOnClickPendingIntent(R.id.button_next, pendingIntent); + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/appwidgets/BaseAppWidget.java b/app/src/main/java/com/kabouzeid/gramophone/appwidgets/BaseAppWidget.java new file mode 100644 index 00000000..c44f0bf6 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/appwidgets/BaseAppWidget.java @@ -0,0 +1,30 @@ +package com.kabouzeid.gramophone.appwidgets; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetProvider; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public class BaseAppWidget extends AppWidgetProvider { + + protected PendingIntent buildPendingIntent(Context context, final String action, final ComponentName serviceName) { + Intent intent = new Intent(action); + intent.setComponent(serviceName); + return PendingIntent.getService(context, 0, intent, 0); + } + + protected static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) { + Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier), (int) (drawable.getIntrinsicHeight() * sizeMultiplier), Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bitmap); + drawable.setBounds(0, 0, c.getWidth(), c.getHeight()); + drawable.draw(c); + return bitmap; + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java index f76b0bf9..bcd9b734 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java @@ -2,6 +2,7 @@ package com.kabouzeid.gramophone.service; import android.app.PendingIntent; import android.app.Service; +import android.appwidget.AppWidgetManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -36,7 +37,7 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import com.kabouzeid.gramophone.R; -import com.kabouzeid.gramophone.appwidget.WidgetMedium; +import com.kabouzeid.gramophone.appwidgets.AppWidgetSmall; import com.kabouzeid.gramophone.glide.BlurTransformation; import com.kabouzeid.gramophone.glide.SongGlideRequest; import com.kabouzeid.gramophone.helper.PlayingNotificationHelper; @@ -72,6 +73,9 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP public static final String ACTION_REWIND = PHONOGRAPH_PACKAGE_NAME + ".rewind"; public static final String ACTION_QUIT = PHONOGRAPH_PACKAGE_NAME + ".quitservice"; + public static final String APP_WIDGET_UPDATE = PHONOGRAPH_PACKAGE_NAME + ".appwidgetupdate"; + public static final String EXTRA_APP_WIDGET_NAME = PHONOGRAPH_PACKAGE_NAME + "app_widget_name"; + // do not change these three strings as it will break support with other apps (e.g. last.fm scrobbling) public static final String META_CHANGED = PHONOGRAPH_PACKAGE_NAME + ".metachanged"; public static final String QUEUE_CHANGED = PHONOGRAPH_PACKAGE_NAME + ".queuechanged"; @@ -108,6 +112,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private final IBinder musicBind = new MusicBinder(); + private AppWidgetSmall appWidgetSmall = AppWidgetSmall.getInstance(); + private Playback playback; private ArrayList playingQueue = new ArrayList<>(); private ArrayList originalPlayingQueue = new ArrayList<>(); @@ -115,6 +121,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private int nextPosition = -1; private int shuffleMode; private int repeatMode; + private boolean queuesRestored; private boolean audioDucking; private boolean pausedByTransientLossOfFocus; private boolean receiversAndRemoteControlClientRegistered; @@ -181,6 +188,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP registerReceiversAndRemoteControlClient(); + registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE)); + mediaStoreObserver = new MediaStoreObserver(playerHandler); throttledPublicPlayStateChangedNotifier = new ThrottledPublicPlayStateChangedNotifier(playerHandler); getContentResolver().registerContentObserver( @@ -196,7 +205,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private void registerReceiversAndRemoteControlClient() { if (!receiversAndRemoteControlClientRegistered) { registerReceiver(becomingNoisyReceiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); - //noinspection deprecation getAudioManager().registerMediaButtonEventReceiver(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); initRemoteControlClient(); receiversAndRemoteControlClientRegistered = true; @@ -227,6 +235,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP public int onStartCommand(@Nullable Intent intent, int flags, int startId) { if (intent != null) { if (intent.getAction() != null) { + restoreQueuesAndPositionIfNecessary(); String action = intent.getAction(); switch (action) { case ACTION_TOGGLE_PAUSE: @@ -261,6 +270,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP @Override public void onDestroy() { + unregisterReceiver(widgetIntentReceiver); quit(); releaseResources(); getContentResolver().unregisterContentObserver(mediaStoreObserver); @@ -291,9 +301,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private void unregisterReceiversAndRemoteControlClient() { if (receiversAndRemoteControlClientRegistered) { unregisterReceiver(becomingNoisyReceiver); - //noinspection deprecation getAudioManager().unregisterRemoteControlClient(remoteControlClient); - //noinspection deprecation getAudioManager().unregisterMediaButtonEventReceiver(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); receiversAndRemoteControlClientRegistered = false; } @@ -345,34 +353,36 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private void restoreState() { shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_SHUFFLE_MODE, 0); repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_REPEAT_MODE, 0); - notifyPrivateChange(SHUFFLE_MODE_CHANGED); - notifyPrivateChange(REPEAT_MODE_CHANGED); + handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); + handleAndSendChangeInternal(REPEAT_MODE_CHANGED); playerHandler.removeMessages(RESTORE_QUEUES); playerHandler.sendEmptyMessage(RESTORE_QUEUES); } - private void restoreQueuesAndPositionImpl() { - ArrayList restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue(); - ArrayList restoredOriginalQueue = MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue(); - int restoredPosition = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1); - int restoredPositionInTrack = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1); + private synchronized void restoreQueuesAndPositionIfNecessary() { + if (!queuesRestored && playingQueue.isEmpty()) { + ArrayList restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue(); + ArrayList restoredOriginalQueue = MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue(); + int restoredPosition = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1); + int restoredPositionInTrack = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1); - if (restoredQueue.size() > 0 && restoredQueue.size() == restoredOriginalQueue.size() && restoredPosition != -1) { - this.originalPlayingQueue = restoredOriginalQueue; - this.playingQueue = restoredQueue; + if (restoredQueue.size() > 0 && restoredQueue.size() == restoredOriginalQueue.size() && restoredPosition != -1) { + this.originalPlayingQueue = restoredOriginalQueue; + this.playingQueue = restoredQueue; - position = restoredPosition; - openCurrent(); - prepareNext(); + position = restoredPosition; + openCurrent(); + prepareNext(); - if (restoredPositionInTrack > 0) seek(restoredPositionInTrack); + if (restoredPositionInTrack > 0) seek(restoredPositionInTrack); - notHandledMetaChangedForCurrentTrack = true; - sendPrivateIntent(META_CHANGED); - sendPrivateIntent(QUEUE_CHANGED); - updateWidgets(); + notHandledMetaChangedForCurrentTrack = true; + sendChangeInternal(META_CHANGED); + sendChangeInternal(QUEUE_CHANGED); + } } + queuesRestored = true; } private int quit() { @@ -415,7 +425,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } public boolean isPlaying() { - return playback.isPlaying(); + return playback != null && playback.isPlaying(); } public int getPosition() { @@ -567,10 +577,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP playingNotificationHelper.updateNotification(); } - private void updateWidgets() { - WidgetMedium.updateWidgets(this, getCurrentSong(), isPlaying()); - } - public int getNextPosition(boolean force) { int position = getPosition() + 1; switch (getRepeatMode()) { @@ -620,7 +626,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP .putInt(SAVED_REPEAT_MODE, repeatMode) .apply(); prepareNext(); - notifyPrivateChange(REPEAT_MODE_CHANGED); + handleAndSendChangeInternal(REPEAT_MODE_CHANGED); break; } } @@ -775,7 +781,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP registerReceiversAndRemoteControlClient(); playback.start(); if (notHandledMetaChangedForCurrentTrack) { - handleChange(META_CHANGED); + handleChangeInternal(META_CHANGED); notHandledMetaChangedForCurrentTrack = false; } notifyChange(PLAY_STATE_CHANGED); @@ -900,18 +906,18 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP position = newPosition; break; } - notifyPrivateChange(SHUFFLE_MODE_CHANGED); + handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); notifyChange(QUEUE_CHANGED); } private void notifyChange(@NonNull final String what) { - notifyPrivateChange(what); + handleAndSendChangeInternal(what); sendPublicIntent(what); } - private void notifyPrivateChange(@NonNull final String what) { - handleChange(what); - sendPrivateIntent(what); + private void handleAndSendChangeInternal(@NonNull final String what) { + handleChangeInternal(what); + sendChangeInternal(what); } // to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch @@ -936,16 +942,16 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP sendStickyBroadcast(intent); } - private void sendPrivateIntent(final String what) { + private void sendChangeInternal(final String what) { sendBroadcast(new Intent(what)); + appWidgetSmall.notifyChange(this, what); } - private void handleChange(@NonNull final String what) { + private void handleChangeInternal(@NonNull final String what) { switch (what) { case PLAY_STATE_CHANGED: final boolean isPlaying = isPlaying(); playingNotificationHelper.updatePlayState(isPlaying); - WidgetMedium.updateWidgetsPlayState(this, isPlaying); //noinspection deprecation remoteControlClient.setPlaybackState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED); if (!isPlaying && getSongProgressMillis() > 0) { @@ -955,7 +961,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP break; case META_CHANGED: updateNotification(); - updateWidgets(); updateRemoteControlClient(); savePosition(); savePositionInTrack(); @@ -1110,7 +1115,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP break; case RESTORE_QUEUES: - service.restoreQueuesAndPositionImpl(); + service.restoreQueuesAndPositionIfNecessary(); break; case FOCUS_CHANGE: @@ -1159,6 +1164,18 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } } + private final BroadcastReceiver widgetIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + final String command = intent.getStringExtra(EXTRA_APP_WIDGET_NAME); + + if (AppWidgetSmall.NAME.equals(command)) { + final int[] small = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); + appWidgetSmall.performUpdate(MusicService.this, small); + } + } + }; + private class MediaStoreObserver extends ContentObserver implements Runnable { // milliseconds to delay before calling refresh to aggregate events private static final long REFRESH_DELAY = 500; @@ -1182,7 +1199,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP public void run() { // actually call refresh when the delayed callback fires // do not send a sticky broadcast here - notifyPrivateChange(MEDIA_STORE_CHANGED); + handleAndSendChangeInternal(MEDIA_STORE_CHANGED); } } diff --git a/app/src/main/res/layout/widget_medium.xml b/app/src/main/res/layout/app_widget_small.xml similarity index 80% rename from app/src/main/res/layout/widget_medium.xml rename to app/src/main/res/layout/app_widget_small.xml index d8515d25..6b5bdaca 100644 --- a/app/src/main/res/layout/widget_medium.xml +++ b/app/src/main/res/layout/app_widget_small.xml @@ -2,14 +2,15 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="96dp" + android:layout_height="@dimen/app_widget_small_image_size" android:background="#AAFFFFFF" + android:orientation="horizontal" tools:ignore="ContentDescription"> @@ -18,6 +19,7 @@ android:layout_height="match_parent"> + android:background="@drawable/notification_selector" + tools:src="@drawable/ic_skip_previous_white_24dp" + tools:tint="#000" /> + android:background="@drawable/notification_selector" + tools:src="@drawable/ic_play_arrow_white_24dp" + tools:tint="#000" /> + android:background="@drawable/notification_selector" + tools:src="@drawable/ic_skip_next_white_24dp" + tools:tint="#000" /> @@ -65,17 +73,19 @@ android:gravity="center_vertical" android:singleLine="true" android:textAppearance="@style/TextAppearance.AppCompat.Subhead" - android:textColor="@color/ate_primary_text_light" /> + android:textColor="@color/ate_primary_text_light" + tools:text="Title" /> + android:textColor="@color/ate_primary_text_light" + tools:text="Text" /> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index d29838fe..bc5f7e22 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -33,7 +33,6 @@ http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout -17dp 2dp - 96dp 128dp 8dp @@ -59,4 +58,9 @@ http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout 56dp + + 96dp + 250dp + 40dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9239b489..20048c40 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -245,4 +245,5 @@ Could not scan %d files. Listing files %s is the new start directory. + small diff --git a/app/src/main/res/xml/app_widget_small_info.xml b/app/src/main/res/xml/app_widget_small_info.xml new file mode 100644 index 00000000..98dacbab --- /dev/null +++ b/app/src/main/res/xml/app_widget_small_info.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/widget_medium_info.xml b/app/src/main/res/xml/widget_medium_info.xml deleted file mode 100644 index b260851e..00000000 --- a/app/src/main/res/xml/widget_medium_info.xml +++ /dev/null @@ -1,10 +0,0 @@ - - \ No newline at end of file