From 62e498a1a90a19ed97fa48d372f601c8da9b0a3d Mon Sep 17 00:00:00 2001 From: Adrian Ulrich Date: Fri, 26 Oct 2018 20:20:15 +0200 Subject: [PATCH] initial shortcut support --- app/src/main/AndroidManifest.xml | 2 + .../android/vanilla/LibraryActivity.java | 13 ++- .../android/vanilla/PlaybackService.java | 11 +++ .../vanilla/ShortcutPseudoActivity.java | 13 ++- .../android/vanilla/SystemUtils.java | 99 +++++++++++++++++++ app/src/main/res/values/translatable.xml | 1 + 6 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/ch/blinkenlights/android/vanilla/SystemUtils.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 188ea8cf..4d249942 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,6 +31,8 @@ THE SOFTWARE. + + = MediaUtils.TYPE_ARTIST && type <= MediaUtils.TYPE_COMPOSER && type != MediaUtils.TYPE_SONG) + menu.add(0, CTX_MENU_ADD_TO_HOMESCREEN, 0, R.string.add_to_homescreen).setIntent(rowData); if (type == MediaUtils.TYPE_ALBUM || type == MediaUtils.TYPE_SONG) menu.add(0, CTX_MENU_MORE_FROM_ARTIST, 0, R.string.more_from_artist).setIntent(rowData); if (type == MediaUtils.TYPE_SONG) { @@ -762,11 +765,19 @@ public class LibraryActivity setLimiter(MediaUtils.TYPE_ALBUM, "_id=" + intent.getLongExtra(LibraryAdapter.DATA_ID, LibraryAdapter.INVALID_ID)); updateLimiterViews(); break; - case CTX_MENU_ADD_TO_PLAYLIST: + case CTX_MENU_ADD_TO_PLAYLIST: { long id = intent.getLongExtra("id", LibraryAdapter.INVALID_ID); PlaylistDialog plDialog = PlaylistDialog.newInstance(this, intent, (id == LibraryAdapter.HEADER_ID ? mCurrentAdapter : null)); plDialog.show(getFragmentManager(), "PlaylistDialog"); break; + } + case CTX_MENU_ADD_TO_HOMESCREEN: { + int type = intent.getIntExtra(LibraryAdapter.DATA_TYPE, MediaUtils.TYPE_INVALID); + long id = intent.getLongExtra(LibraryAdapter.DATA_ID, LibraryAdapter.INVALID_ID); + String label = intent.getStringExtra(LibraryAdapter.DATA_TITLE); + SystemUtils.installLauncherShortcut(this, label, type, id); + break; + } default: return super.onContextItemSelected(item); } diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackService.java b/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackService.java index b784d109..03faa1fb 100644 --- a/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackService.java +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/PlaybackService.java @@ -167,6 +167,10 @@ public final class PlaybackService extends Service * Flushes the queue, switches to random mode and starts playing. */ public static final String ACTION_RANDOM_MIX_AUTOPLAY = "ch.blinkenlights.android.vanilla.action.RANDOM_MIX_AUTOPLAY"; + /** + * Flushes the queue and plays everything of the passed type/id combination. + */ + public static final String ACTION_FROM_TYPE_ID_AUTOPLAY = "ch.blinkenlights.android.vanilla.action.FROM_TYPE_ID_AUTOPLAY"; /** * Change the shuffle mode. */ @@ -600,6 +604,13 @@ public final class PlaybackService extends Service // We therefore send a GO message to the same queue, so it will get handled as // soon as the queue is ready. mHandler.sendEmptyMessage(MSG_CALL_GO); + } else if (ACTION_FROM_TYPE_ID_AUTOPLAY.equals(action)) { + int type = intent.getIntExtra(LibraryAdapter.DATA_TYPE, MediaUtils.TYPE_INVALID); + long id = intent.getLongExtra(LibraryAdapter.DATA_ID, LibraryAdapter.INVALID_ID); + QueryTask query = MediaUtils.buildQuery(type, id, Song.FILLED_PROJECTION, null); + // Flush the queue and start playing: + query.mode = SongTimeline.MODE_PLAY; + addSongs(query); } else if (ACTION_CLOSE_NOTIFICATION.equals(action)) { mForceNotificationVisible = false; pause(); diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/ShortcutPseudoActivity.java b/app/src/main/java/ch/blinkenlights/android/vanilla/ShortcutPseudoActivity.java index 3843a220..5adab1bb 100644 --- a/app/src/main/java/ch/blinkenlights/android/vanilla/ShortcutPseudoActivity.java +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/ShortcutPseudoActivity.java @@ -57,11 +57,22 @@ public class ShortcutPseudoActivity extends Activity { case PlaybackService.ACTION_PREVIOUS_SONG: case PlaybackService.ACTION_PREVIOUS_SONG_AUTOPLAY: case PlaybackService.ACTION_CYCLE_SHUFFLE: - case PlaybackService.ACTION_CYCLE_REPEAT: + case PlaybackService.ACTION_CYCLE_REPEAT: { Intent intent = new Intent(this, PlaybackService.class); intent.setAction(action); startService(intent); break; + } + case PlaybackService.ACTION_FROM_TYPE_ID_AUTOPLAY: { + // From pinned shortcuts: Same as other actions, but this + // includes some extras. + Intent intent = new Intent(this, PlaybackService.class); + intent.setAction(action); + intent.putExtra(LibraryAdapter.DATA_TYPE, getIntent().getIntExtra(LibraryAdapter.DATA_TYPE, MediaUtils.TYPE_INVALID)); + intent.putExtra(LibraryAdapter.DATA_ID, getIntent().getLongExtra(LibraryAdapter.DATA_ID, LibraryAdapter.INVALID_ID)); + startService(intent); + break; + } default: throw new IllegalArgumentException("No such action: " + action); } diff --git a/app/src/main/java/ch/blinkenlights/android/vanilla/SystemUtils.java b/app/src/main/java/ch/blinkenlights/android/vanilla/SystemUtils.java new file mode 100644 index 00000000..a95d7461 --- /dev/null +++ b/app/src/main/java/ch/blinkenlights/android/vanilla/SystemUtils.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 Adrian Ulrich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package ch.blinkenlights.android.vanilla; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutManager; +import android.content.pm.ShortcutInfo; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Icon; + + +/** + * Provides some static System-related utility functions. + */ +public class SystemUtils { + + /** + * Installs (or prompts user to install) a shortcut launcher pointing to the given type and id + * combination. Launching the shortcut will cause vanilla to play the type / id combination. + * + * @param context the context to use. + * @param label the label of the shortcut. + * @param type the media type of the passed in id. + * @param id an id of a media type + */ + public static void installLauncherShortcut(Context context, String label, int type, long id) { + Intent shortcut = new Intent(context, ShortcutPseudoActivity.class); + shortcut.setAction(PlaybackService.ACTION_FROM_TYPE_ID_AUTOPLAY); + shortcut.putExtra(LibraryAdapter.DATA_TYPE, type); + shortcut.putExtra(LibraryAdapter.DATA_ID, id); + + Bitmap cover = null; + Song song = MediaUtils.getSongByTypeId(context, type, id); + if (song != null) { + cover = song.getSmallCover(context); + } + if (cover == null) { + cover = BitmapFactory.decodeResource(context.getResources(), R.drawable.fallback_cover); + } + + // TODO: Support pre api 26. + installShortcut(context, label, cover, shortcut); + } + + /** + * Prompts the user to install a shortcut. This is only available on API >= 26. + * + * @param context the context to use. + * @param label the label to use for this shortcut. + * @param cover the icon to use for this shortcut. + * @param intent intent launched by the shortcut. + * + * @return true if the shortcut MAY have been created. + */ + private static boolean installShortcut(Context context, String label, Bitmap cover, Intent intent) { + ShortcutManager manager = context.getSystemService(ShortcutManager.class); + if (manager == null || !manager.isRequestPinShortcutSupported()) + return false; + + String uniqueId = "vanilla:shortcut:" + System.currentTimeMillis(); + ShortcutInfo pin = new ShortcutInfo.Builder(context, uniqueId) + .setIntent(intent) + .setShortLabel(label) + .setIcon(Icon.createWithBitmap(cover)) + .build(); + + manager.requestPinShortcut(pin, null); + return true; + } + + // TODO: Test this on kitkat. + private static boolean installShortcutLegacy(Context context, String label, Intent intent) { + Intent add = new Intent(); + add.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent); + add.putExtra(Intent.EXTRA_SHORTCUT_NAME, label); + add.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, + Intent.ShortcutIconResource.fromContext(context, R.drawable.repeat_active)); + add.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + context.sendBroadcast(add); + return true; + } +} diff --git a/app/src/main/res/values/translatable.xml b/app/src/main/res/values/translatable.xml index 3f9805cd..33208c83 100644 --- a/app/src/main/res/values/translatable.xml +++ b/app/src/main/res/values/translatable.xml @@ -68,6 +68,7 @@ THE SOFTWARE. Rename Save as playlist… Add to playlist… + Add to homescreen… Add to favorites Remove from favorites Favorites