diff --git a/.gitignore b/.gitignore index 04388588..7574c77b 100644 --- a/.gitignore +++ b/.gitignore @@ -40,5 +40,3 @@ captures/ # Mac .DS_Store - -/app/google-services.json \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index c9e45d57..ab540a87 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,19 +1,27 @@ buildscript { repositories { + maven { url 'https://maven.fabric.io/public' } mavenCentral() } dependencies { + //noinspection GradleDynamicVersion + classpath 'io.fabric.tools:gradle:1.+' classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1' } } apply plugin: 'com.android.application' +apply plugin: 'io.fabric' apply plugin: 'com.jakewharton.hugo' -apply plugin: 'com.google.gms.google-services' repositories { + maven { url 'https://maven.fabric.io/public' } maven { url "https://jitpack.io" } + maven { + name 'glide-snapshot' + url 'http://oss.sonatype.org/content/repositories/snapshots' + } } android { @@ -27,8 +35,8 @@ android { vectorDrawables.useSupportLibrary = true applicationId "com.kabouzeid.gramophone" - versionCode 128 - versionName "0.14.0" + versionCode 130 + versionName "0.15.0 BETA 1" } buildTypes { release { @@ -39,6 +47,8 @@ android { debug { applicationIdSuffix '.debug' versionNameSuffix ' DEBUG' + + ext.enableCrashlytics = false // Disable fabric build ID generation for debug builds } } packagingOptions { @@ -56,6 +66,10 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' + compile('com.crashlytics.sdk.android:crashlytics:2.6.2@aar') { + transitive = true + } + compile('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.10.4@aar') { transitive = true } @@ -88,13 +102,13 @@ dependencies { compile 'com.github.ksoichiro:android-observablescrollview:1.6.0' compile 'com.github.kabouzeid:SeekArc:1.2-kmod' compile 'com.github.kabouzeid:AndroidSlidingUpPanel:3.3.0-kmod3' - compile 'com.squareup.retrofit2:retrofit:2.1.0' - compile 'com.squareup.retrofit2:converter-gson:2.1.0' + compile 'com.squareup.retrofit2:retrofit:2.2.0' + compile 'com.squareup.retrofit2:converter-gson:2.2.0' //noinspection GradleDynamicVersion compile 'com.anjlab.android.iab.v3:library:1.0.+' compile 'de.psdev.licensesdialog:licensesdialog:1.8.1' - compile 'com.github.bumptech.glide:glide:3.7.0' - compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar' + compile 'com.github.bumptech.glide:glide:3.8.0-SNAPSHOT' + compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0-SNAPSHOT' compile 'com.github.kabouzeid:RecyclerView-FastScroll:1.9-kmod' compile 'com.heinrichreimersoftware:material-intro:1.6' compile 'me.zhanghai.android.materialprogressbar:library:1.3.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8579a7ab..0ff83e28 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - + @@ -173,6 +173,10 @@ + \ No newline at end of file diff --git a/app/src/main/assets/phonograph-changelog.html b/app/src/main/assets/phonograph-changelog.html index 277b918a..bf997b3b 100644 --- a/app/src/main/assets/phonograph-changelog.html +++ b/app/src/main/assets/phonograph-changelog.html @@ -25,6 +25,20 @@

You can view the changelog dialog again at any time from the about section.

+

Version 0.15.0 BETA 1

+ +
    +
  1. NEW: App Shortcuts (Android Nougat only)
  2. +
+ +

Version 0.14.1

+ +
    +
  1. FIX: Notification won't disappear.
  2. +
  3. IMPROVEMENT: Updated libraries.
  4. +
  5. IMPROVEMENT: Synced translations.
  6. +
+

Version 0.14.0

    diff --git a/app/src/main/java/com/kabouzeid/gramophone/App.java b/app/src/main/java/com/kabouzeid/gramophone/App.java index 9279f222..bf8d228e 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/App.java +++ b/app/src/main/java/com/kabouzeid/gramophone/App.java @@ -1,10 +1,33 @@ package com.kabouzeid.gramophone; import android.app.Application; +import android.os.Build; + +import com.crashlytics.android.Crashlytics; +import com.crashlytics.android.core.CrashlyticsCore; +import com.kabouzeid.gramophone.appshortcuts.DynamicShortcutManager; + +import io.fabric.sdk.android.Fabric; /** * @author Karim Abou Zeid (kabouzeid) */ public class App extends Application { public static final String GOOGLE_PLAY_LICENSE_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjMeADN5Ffnt/ml5SYxNPCn8kGcOYGpHEfNSCts99vVxqmCn6C01E94c17j7rUK2aeHur5uxphZylzopPlQ8P8l1fqty0GPUNRSo18FCJzfGH8HZAwZYOcnRFPaXdaq3InyFJhBiODh2oeAcVK/idH6QraQ4r9HIlzigAg6lgwzxl2wJKDh7X/GMdDntCyzDh8xDQ0wIawFgvgojHwqh2Ci8Gnq6EYRwPA9yHiIIksT8Q30QyM5ewl5QcnWepsls7enNqeHarhpmSibRUDgCsxHoOpny7SyuvZvUI3wuLckDR0ds9hrt614scHHqDOBp/qWCZiAgOPVAEQcURbV09qQIDAQAB"; + + @Override + public void onCreate() { + super.onCreate(); + + // Set up Crashlytics, disabled for debug builds + Crashlytics crashlyticsKit = new Crashlytics.Builder() + .core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()) + .build(); + Fabric.with(this, crashlyticsKit); + + //Set up dynamic shortcuts + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + new DynamicShortcutManager(this).initDynamicShortcuts(); + } + } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/AppShortcutIconGenerator.java b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/AppShortcutIconGenerator.java new file mode 100644 index 00000000..6984edf7 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/AppShortcutIconGenerator.java @@ -0,0 +1,71 @@ +package com.kabouzeid.gramophone.appshortcuts; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.graphics.drawable.LayerDrawable; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.util.TypedValue; + +import com.kabouzeid.appthemehelper.ThemeStore; +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.util.PreferenceUtil; +import com.kabouzeid.gramophone.util.Util; + +/** + * @author Adrian Campos + */ + +@RequiresApi(Build.VERSION_CODES.N_MR1) +public final class AppShortcutIconGenerator { + public static Icon generateThemedIcon(Context context, int iconId) { + if (PreferenceUtil.getInstance(context).coloredAppShortcuts()){ + return generateUserThemedIcon(context, iconId); + } else { + return generateDefaultThemedIcon(context, iconId); + } + } + + private static Icon generateDefaultThemedIcon(Context context, int iconId) { + //Return an Icon of iconId with default colors + return generateThemedIcon(context, iconId, + context.getColor(R.color.app_shortcut_default_foreground), + context.getColor(R.color.app_shortcut_default_background) + ); + } + + private static Icon generateUserThemedIcon(Context context, int iconId) { + //Get background color from context's theme + final TypedValue typedColorBackground = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.colorBackground, typedColorBackground, true); + + //Return an Icon of iconId with those colors + return generateThemedIcon(context, iconId, + ThemeStore.primaryColor(context), + typedColorBackground.data + ); + } + + private static Icon generateThemedIcon(Context context, int iconId, int foregroundColor, int backgroundColor) { + //Get and tint foreground and background drawables + Drawable vectorDrawable = Util.getTintedVectorDrawable(context, iconId, foregroundColor); + Drawable backgroundDrawable = Util.getTintedVectorDrawable(context, R.drawable.ic_app_shortcut_background, backgroundColor); + + //Squash the two drawables together + LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{backgroundDrawable, vectorDrawable}); + + //Return as an Icon + return Icon.createWithBitmap(drawableToBitmap(layerDrawable)); + } + + private static Bitmap drawableToBitmap(Drawable drawable) { + Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/AppShortcutLauncherActivity.java b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/AppShortcutLauncherActivity.java new file mode 100644 index 00000000..b6b72cc8 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/AppShortcutLauncherActivity.java @@ -0,0 +1,85 @@ +package com.kabouzeid.gramophone.appshortcuts; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.IntDef; + +import com.kabouzeid.gramophone.appshortcuts.shortcuttype.LastAddedShortcutType; +import com.kabouzeid.gramophone.appshortcuts.shortcuttype.ShuffleAllShortcutType; +import com.kabouzeid.gramophone.appshortcuts.shortcuttype.TopTracksShortcutType; +import com.kabouzeid.gramophone.loader.LastAddedLoader; +import com.kabouzeid.gramophone.loader.SongLoader; +import com.kabouzeid.gramophone.loader.TopAndRecentlyPlayedTracksLoader; +import com.kabouzeid.gramophone.model.Song; +import com.kabouzeid.gramophone.service.MusicService; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +/** + * @author Adrian Campos + */ + +public class AppShortcutLauncherActivity extends Activity { + public static final String KEY_SHORTCUT_TYPE = "com.kabouzeid.gramophone.appshortcuts.ShortcutType"; + + public static final int SHORTCUT_TYPE_SHUFFLE_ALL = 0; + public static final int SHORTCUT_TYPE_TOP_TRACKS = 1; + public static final int SHORTCUT_TYPE_LAST_ADDED = 2; + public static final int SHORTCUT_TYPE_NONE = 3; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + @ShortcutType + int shortcutType = SHORTCUT_TYPE_NONE; + + //Set shortcutType from the intent extras + Bundle extras = getIntent().getExtras(); + if (extras != null) { + //noinspection WrongConstant + shortcutType = extras.getInt(KEY_SHORTCUT_TYPE, SHORTCUT_TYPE_NONE); + } + + switch (shortcutType) { + case SHORTCUT_TYPE_SHUFFLE_ALL: + startServiceWithSongs(MusicService.SHUFFLE_MODE_SHUFFLE, + SongLoader.getAllSongs(getApplicationContext())); + DynamicShortcutManager.reportShortcutUsed(this, ShuffleAllShortcutType.getId()); + break; + case SHORTCUT_TYPE_TOP_TRACKS: + startServiceWithSongs(MusicService.SHUFFLE_MODE_NONE, + TopAndRecentlyPlayedTracksLoader.getTopTracks(getApplicationContext())); + DynamicShortcutManager.reportShortcutUsed(this, TopTracksShortcutType.getId()); + break; + case SHORTCUT_TYPE_LAST_ADDED: + startServiceWithSongs(MusicService.SHUFFLE_MODE_NONE, + LastAddedLoader.getLastAddedSongs(getApplicationContext())); + DynamicShortcutManager.reportShortcutUsed(this, LastAddedShortcutType.getId()); + break; + } + + finish(); + } + + private void startServiceWithSongs(int shuffleMode, ArrayList songs) { + Intent intent = new Intent(this, MusicService.class); + intent.setAction(MusicService.ACTION_PLAY); + + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(MusicService.INTENT_EXTRA_SONGS, songs); + bundle.putInt(MusicService.INTENT_EXTRA_SHUFFLE_MODE, shuffleMode); + + intent.putExtras(bundle); + + startService(intent); + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({SHORTCUT_TYPE_SHUFFLE_ALL, SHORTCUT_TYPE_TOP_TRACKS, SHORTCUT_TYPE_LAST_ADDED, SHORTCUT_TYPE_NONE}) + public @interface ShortcutType { + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/DynamicShortcutManager.java b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/DynamicShortcutManager.java new file mode 100644 index 00000000..a3a77798 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/DynamicShortcutManager.java @@ -0,0 +1,63 @@ +package com.kabouzeid.gramophone.appshortcuts; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; +import android.os.Build; + +import com.kabouzeid.gramophone.appshortcuts.shortcuttype.LastAddedShortcutType; +import com.kabouzeid.gramophone.appshortcuts.shortcuttype.ShuffleAllShortcutType; +import com.kabouzeid.gramophone.appshortcuts.shortcuttype.TopTracksShortcutType; + +import java.util.Arrays; +import java.util.List; + +/** + * @author Adrian Campos + */ + +@TargetApi(Build.VERSION_CODES.N_MR1) +public class DynamicShortcutManager { + + private Context context; + private ShortcutManager shortcutManager; + + public DynamicShortcutManager(Context context) { + this.context = context; + shortcutManager = this.context.getSystemService(ShortcutManager.class); + } + + public static ShortcutInfo createShortcut(Context context, String id, String shortLabel, String longLabel, Icon icon, Intent intent) { + return new ShortcutInfo.Builder(context, id) + .setShortLabel(shortLabel) + .setLongLabel(longLabel) + .setIcon(icon) + .setIntent(intent) + .build(); + } + + public void initDynamicShortcuts() { + if (shortcutManager.getDynamicShortcuts().size() == 0) { + shortcutManager.setDynamicShortcuts(getDefaultShortcuts()); + } + } + + public void updateDynamicShortcuts() { + shortcutManager.updateShortcuts(getDefaultShortcuts()); + } + + public List getDefaultShortcuts() { + return (Arrays.asList( + new ShuffleAllShortcutType(context).getShortcutInfo(), + new TopTracksShortcutType(context).getShortcutInfo(), + new LastAddedShortcutType(context).getShortcutInfo() + )); + } + + public static void reportShortcutUsed(Context context, String shortcutId){ + context.getSystemService(ShortcutManager.class).reportShortcutUsed(shortcutId); + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/shortcuttype/BaseShortcutType.java b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/shortcuttype/BaseShortcutType.java new file mode 100644 index 00000000..ca44f21a --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/shortcuttype/BaseShortcutType.java @@ -0,0 +1,51 @@ +package com.kabouzeid.gramophone.appshortcuts.shortcuttype; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.os.Bundle; + +import com.kabouzeid.gramophone.appshortcuts.AppShortcutLauncherActivity; + +/** + * @author Adrian Campos + */ + +@TargetApi(25) +public abstract class BaseShortcutType { + + static final String ID_PREFIX = "com.kabouzeid.gramophone.appshortcuts.id."; + + Context context; + + public BaseShortcutType(Context context) { + this.context = context; + } + + + abstract ShortcutInfo getShortcutInfo(); + + static public String getId(){ + return ID_PREFIX + "invalid"; + } + + + /** + * Creates an Intent that will launch MainActivtiy and immediately play {@param songs} in either shuffle or normal mode + * + * @param shortcutType Describes the type of shortcut to create (ShuffleAll, TopTracks, custom playlist, etc.) + * @return + */ + Intent getPlaySongsIntent(@AppShortcutLauncherActivity.ShortcutType int shortcutType) { + Intent intent = new Intent(context, AppShortcutLauncherActivity.class); + intent.setAction(Intent.ACTION_VIEW); + + Bundle b = new Bundle(); + b.putInt(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE, shortcutType); + + intent.putExtras(b); + + return intent; + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/shortcuttype/LastAddedShortcutType.java b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/shortcuttype/LastAddedShortcutType.java new file mode 100644 index 00000000..77182f1e --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/shortcuttype/LastAddedShortcutType.java @@ -0,0 +1,33 @@ +package com.kabouzeid.gramophone.appshortcuts.shortcuttype; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.ShortcutInfo; + +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.appshortcuts.AppShortcutIconGenerator; +import com.kabouzeid.gramophone.appshortcuts.AppShortcutLauncherActivity; + +/** + * @author Adrian Campos + */ + +@TargetApi(25) +public final class LastAddedShortcutType extends BaseShortcutType { + public LastAddedShortcutType(Context context) { + super(context); + } + + public ShortcutInfo getShortcutInfo() { + return new ShortcutInfo.Builder(context, getId()) + .setShortLabel(context.getString(R.string.app_shortcut_last_added_short)) + .setLongLabel(context.getString(R.string.app_shortcut_last_added_long)) + .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_last_added)) + .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_LAST_ADDED)) + .build(); + } + + public static String getId(){ + return ID_PREFIX + "last_added"; + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/shortcuttype/ShuffleAllShortcutType.java b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/shortcuttype/ShuffleAllShortcutType.java new file mode 100644 index 00000000..dd952d16 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/shortcuttype/ShuffleAllShortcutType.java @@ -0,0 +1,33 @@ +package com.kabouzeid.gramophone.appshortcuts.shortcuttype; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.ShortcutInfo; + +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.appshortcuts.AppShortcutIconGenerator; +import com.kabouzeid.gramophone.appshortcuts.AppShortcutLauncherActivity; + +/** + * @author Adrian Campos + */ + +@TargetApi(25) +public final class ShuffleAllShortcutType extends BaseShortcutType { + public ShuffleAllShortcutType(Context context) { + super(context); + } + + public ShortcutInfo getShortcutInfo() { + return new ShortcutInfo.Builder(context, getId()) + .setShortLabel(context.getString(R.string.app_shortcut_shuffle_all_short)) + .setLongLabel(context.getString(R.string.app_shortcut_shuffle_all_long)) + .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_shuffle_all)) + .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_SHUFFLE_ALL)) + .build(); + } + + public static String getId() { + return ID_PREFIX + "shuffle_all"; + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/shortcuttype/TopTracksShortcutType.java b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/shortcuttype/TopTracksShortcutType.java new file mode 100644 index 00000000..ef82f306 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/appshortcuts/shortcuttype/TopTracksShortcutType.java @@ -0,0 +1,33 @@ +package com.kabouzeid.gramophone.appshortcuts.shortcuttype; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.ShortcutInfo; + +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.appshortcuts.AppShortcutIconGenerator; +import com.kabouzeid.gramophone.appshortcuts.AppShortcutLauncherActivity; + +/** + * @author Adrian Campos + */ + +@TargetApi(25) +public final class TopTracksShortcutType extends BaseShortcutType { + public TopTracksShortcutType(Context context) { + super(context); + } + + public ShortcutInfo getShortcutInfo() { + return new ShortcutInfo.Builder(context, getId()) + .setShortLabel(context.getString(R.string.app_shortcut_top_tracks_short)) + .setLongLabel(context.getString(R.string.app_shortcut_top_tracks_long)) + .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_top_tracks)) + .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_TOP_TRACKS)) + .build(); + } + + public static String getId() { + return ID_PREFIX + "top_tracks"; + } +} 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 4cdbc318..a5bd9076 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java @@ -30,7 +30,6 @@ import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.media.MediaMetadataCompat; -import android.support.v4.media.session.MediaButtonReceiver; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; import android.widget.Toast; @@ -62,6 +61,7 @@ import com.kabouzeid.gramophone.util.Util; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; +import java.util.Random; /** * @author Karim Abou Zeid (kabouzeid), Andrew Neal @@ -79,6 +79,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP public static final String ACTION_SKIP = PHONOGRAPH_PACKAGE_NAME + ".skip"; public static final String ACTION_REWIND = PHONOGRAPH_PACKAGE_NAME + ".rewind"; public static final String ACTION_QUIT = PHONOGRAPH_PACKAGE_NAME + ".quitservice"; + public static final String INTENT_EXTRA_SONGS = PHONOGRAPH_PACKAGE_NAME + ".intentextra.songs"; + public static final String INTENT_EXTRA_SHUFFLE_MODE = PHONOGRAPH_PACKAGE_NAME + ".intentextra.shufflemode"; 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"; @@ -295,6 +297,20 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP pause(); break; case ACTION_PLAY: + ArrayList songs = intent.getParcelableArrayListExtra(INTENT_EXTRA_SONGS); + if (songs != null) { + int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode()); + if (intent.hasExtra(INTENT_EXTRA_SHUFFLE_MODE) && intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, 0) == SHUFFLE_MODE_SHUFFLE) { + int startPosition = 0; + if (!songs.isEmpty()) { + startPosition = new Random().nextInt(songs.size()); + } + openQueue(songs, startPosition, false); + setShuffleMode(shuffleMode); + } else { + openQueue(songs, 0, false); + } + } play(); break; case ACTION_REWIND: @@ -522,9 +538,29 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP return (getAudioManager().requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); } - private void updateMediaSession() { + private void updateNotification() { + if (getCurrentSong().id != -1) { + playingNotification.update(); + } + } + + private void updateMediaSessionPlaybackState() { + mediaSession.setPlaybackState( + new PlaybackStateCompat.Builder() + .setActions(MEDIA_SESSION_ACTIONS) + .setState(isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, + getPosition(), 1) + .build()); + } + + private void updateMediaSessionMetaData() { final Song song = getCurrentSong(); + if (song.id == -1) { + mediaSession.setMetadata(null); + return; + } + final MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artistName) .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.artistName) @@ -568,7 +604,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } else { mediaSession.setMetadata(metaData.build()); } - } private static Bitmap copy(Bitmap bitmap) { @@ -985,22 +1020,17 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private void handleChangeInternal(@NonNull final String what) { switch (what) { case PLAY_STATE_CHANGED: + updateNotification(); + updateMediaSessionPlaybackState(); final boolean isPlaying = isPlaying(); - playingNotification.update(); - mediaSession.setPlaybackState( - new PlaybackStateCompat.Builder() - .setActions(MEDIA_SESSION_ACTIONS) - .setState(isPlaying ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, - getPosition(), 1) - .build()); if (!isPlaying && getSongProgressMillis() > 0) { savePositionInTrack(); } songPlayCountHelper.notifyPlayStateChanged(isPlaying); break; case META_CHANGED: - playingNotification.update(); - updateMediaSession(); + updateNotification(); + updateMediaSessionMetaData(); savePosition(); savePositionInTrack(); final Song currentSong = getCurrentSong(); @@ -1011,12 +1041,12 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP songPlayCountHelper.notifySongChanged(currentSong); break; case QUEUE_CHANGED: - updateMediaSession(); + updateMediaSessionMetaData(); // because playing queue size might have changed saveState(); if (playingQueue.size() > 0) { prepareNext(); } else { - quit(); + playingNotification.stop(); } break; } @@ -1052,10 +1082,10 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP break; case PreferenceUtil.ALBUM_ART_ON_LOCKSCREEN: case PreferenceUtil.BLURRED_ALBUM_ART: - updateMediaSession(); + updateMediaSessionMetaData(); break; case PreferenceUtil.COLORED_NOTIFICATION: - playingNotification.update(); + updateNotification(); break; } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotification.java b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotification.java index 43558726..858c8486 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotification.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotification.java @@ -7,6 +7,8 @@ import com.kabouzeid.gramophone.service.MusicService; */ public interface PlayingNotification { + int NOTIFICATION_ID = 1; + void init(MusicService service); void update(); diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl.java b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl.java index 5ddbc2aa..2c3e4038 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl.java @@ -133,7 +133,7 @@ public class PlayingNotificationImpl implements PlayingNotification { if (stopped) return; // notification has been stopped before loading was finished - service.startForeground(1, notification); + service.startForeground(NOTIFICATION_ID, notification); } private void setBackgroundColor(int color) { diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java index 056a2eae..283b5c5f 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/notification/PlayingNotificationImpl24.java @@ -157,9 +157,9 @@ public class PlayingNotificationImpl24 implements PlayingNotification { } if (newNotifyMode == NOTIFY_MODE_FOREGROUND) { - service.startForeground(1, notification); + service.startForeground(NOTIFICATION_ID, notification); } else if (newNotifyMode == NOTIFY_MODE_BACKGROUND) { - notificationManager.notify(1, notification); + notificationManager.notify(NOTIFICATION_ID, notification); } notifyMode = newNotifyMode; @@ -169,5 +169,6 @@ public class PlayingNotificationImpl24 implements PlayingNotification { public synchronized void stop() { stopped = true; service.stopForeground(true); + notificationManager.cancel(NOTIFICATION_ID); } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java index 9e219fa9..f2950f5b 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java @@ -296,6 +296,7 @@ public class MainActivity extends AbsSlidingMusicPanelActivity { } else { MusicPlayerRemote.openQueue(songs, 0, true); } + handled = true; } if (uri != null && uri.toString().length() > 0) { diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/SettingsActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/SettingsActivity.java index 002ee600..9730b062 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/SettingsActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/SettingsActivity.java @@ -25,6 +25,7 @@ import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEColorPreference; import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat; import com.kabouzeid.appthemehelper.util.ColorUtil; import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.appshortcuts.DynamicShortcutManager; import com.kabouzeid.gramophone.preferences.NowPlayingScreenPreference; import com.kabouzeid.gramophone.preferences.NowPlayingScreenPreferenceDialog; import com.kabouzeid.gramophone.ui.activities.base.AbsBaseActivity; @@ -82,6 +83,10 @@ public class SettingsActivity extends AbsBaseActivity implements ColorChooserDia .commit(); break; } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + new DynamicShortcutManager(this).updateDynamicShortcuts(); + } recreate(); } @@ -175,6 +180,13 @@ public class SettingsActivity extends AbsBaseActivity implements ColorChooserDia ThemeStore.editTheme(getActivity()) .activityTheme(PreferenceUtil.getThemeResFromPrefValue((String) o)) .commit(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + //Set the new theme so that updateAppShortcuts can pull it + getActivity().setTheme(PreferenceUtil.getThemeResFromPrefValue((String) o)); + new DynamicShortcutManager(getActivity()).updateDynamicShortcuts(); + } + getActivity().recreate(); return true; } @@ -240,6 +252,28 @@ public class SettingsActivity extends AbsBaseActivity implements ColorChooserDia }); } + TwoStatePreference colorAppShortcuts = (TwoStatePreference) findPreference("should_color_app_shortcuts"); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { + colorAppShortcuts.setEnabled(false); + colorAppShortcuts.setSummary(R.string.pref_only_nougat_mr1); + } else { + colorAppShortcuts.setChecked(PreferenceUtil.getInstance(getActivity()).coloredAppShortcuts()); + colorAppShortcuts.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + //Save preference + PreferenceUtil.getInstance(getActivity()).setColoredAppShortcuts((Boolean)newValue); + + //Update app shortcuts + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + new DynamicShortcutManager(getActivity()).updateDynamicShortcuts(); + } + + return true; + } + }); + } + Preference equalizer = findPreference("equalizer"); if (!hasEqualizer()) { equalizer.setEnabled(false); diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/card/CardPlayerFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/card/CardPlayerFragment.java index 38c460a6..739cf30d 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/card/CardPlayerFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/card/CardPlayerFragment.java @@ -319,7 +319,6 @@ public class CardPlayerFragment extends AbsPlayerFragment implements PlayerAlbum try { return AudioFileIO.read(new File(song.data)).getTagOrCreateDefault().getFirst(FieldKey.LYRICS); } catch (Exception e) { - e.printStackTrace(); cancel(false); return null; } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/flat/FlatPlayerFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/flat/FlatPlayerFragment.java index 91c1e78e..679978b7 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/flat/FlatPlayerFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/flat/FlatPlayerFragment.java @@ -319,7 +319,6 @@ public class FlatPlayerFragment extends AbsPlayerFragment implements PlayerAlbum try { return AudioFileIO.read(new File(song.data)).getTagOrCreateDefault().getFirst(FieldKey.LYRICS); } catch (Exception e) { - e.printStackTrace(); cancel(false); return null; } diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java index bd8168ff..63deb986 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java @@ -44,6 +44,7 @@ public final class PreferenceUtil { public static final String FORCE_SQUARE_ALBUM_COVER = "force_square_album_art"; public static final String COLORED_NOTIFICATION = "colored_notification"; + public static final String COLORED_APP_SHORTCUTS = "colored_app_shortcuts"; public static final String AUDIO_DUCKING = "audio_ducking"; public static final String GAPLESS_PLAYBACK = "gapless_playback"; @@ -148,6 +149,16 @@ public final class PreferenceUtil { return mPreferences.getBoolean(COLORED_NOTIFICATION, true); } + public void setColoredAppShortcuts(final boolean value) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putBoolean(COLORED_APP_SHORTCUTS, value); + editor.apply(); + } + + public final boolean coloredAppShortcuts() { + return mPreferences.getBoolean(COLORED_APP_SHORTCUTS, true); + } + public final boolean gaplessPlayback() { return mPreferences.getBoolean(GAPLESS_PLAYBACK, false); } diff --git a/app/src/main/res/drawable/ic_app_shortcut_background.xml b/app/src/main/res/drawable/ic_app_shortcut_background.xml new file mode 100644 index 00000000..914648b4 --- /dev/null +++ b/app/src/main/res/drawable/ic_app_shortcut_background.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_app_shortcut_last_added.xml b/app/src/main/res/drawable/ic_app_shortcut_last_added.xml new file mode 100644 index 00000000..b80363f4 --- /dev/null +++ b/app/src/main/res/drawable/ic_app_shortcut_last_added.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_app_shortcut_shuffle_all.xml b/app/src/main/res/drawable/ic_app_shortcut_shuffle_all.xml new file mode 100644 index 00000000..33fccfc9 --- /dev/null +++ b/app/src/main/res/drawable/ic_app_shortcut_shuffle_all.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_app_shortcut_top_tracks.xml b/app/src/main/res/drawable/ic_app_shortcut_top_tracks.xml new file mode 100644 index 00000000..02fa94e2 --- /dev/null +++ b/app/src/main/res/drawable/ic_app_shortcut_top_tracks.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 277561af..4ce5a3a2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -20,6 +20,9 @@ 搜索 作为下一首播放 播放 + 播放/暂停 + 上一首 + 下一首 加入播放队列 从播放队列中移除 加入播放列表 @@ -45,6 +48,7 @@ 年份 音轨 "音轨(如用 2 表示第 2 首歌或用 3004 表示 CD3 里的第 4 首歌)" + 歌词 标题或艺术家名称为空 正在保存更改… 保存至文件... diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index a422df29..bf20dbcd 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,4 +1,7 @@ #34000000 + + #607d8b + #f5f5f5 \ No newline at end of file diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 417cd4f1..3783fdb5 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -39,4 +39,6 @@ white-space: pre-wrap; } + + com.kabouzeid.gramophone.appshortcuts.ShortcutType \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2160388e..6eef7d46 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -119,6 +119,7 @@ Images Lockscreen Colored navigation bar + Colored app shortcuts Start page Show album cover Auto download artist images @@ -158,6 +159,7 @@ Playlist name Song "Only available on Lollipop." + "Only available on Nougat 7.1." Uses the current songs album cover as lockscreen wallpaper. Blurs the album cover on the lockscreen. Can cause problems with third party apps and widgets. "Colors the notification in the album cover\u2019s vibrant color." @@ -171,6 +173,7 @@ Can increase the album cover quality but causes slower image loading times. Only enable this if you have problems with low resolution artworks. Colors play/pause, shuffle and repeat as well as the progress slider in the album cover\u2019s vibrant color. Colors the navigation bar in the primary color. + Colors the app shortcuts in the primary color. Notifications, navigation etc. "Couldn\u2019t download a matching album cover." Search your library… @@ -275,4 +278,14 @@ Copied device info to clipboard. Your account data is only used for authentication. You will be forwarded to the issue tracker website. + + + @string/action_shuffle_all + Shuffle + + @string/my_top_tracks + Top Tracks + + @string/last_added + @string/last_added diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index bc05af2a..0870ffb7 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -48,4 +48,13 @@ toolbar + + diff --git a/app/src/main/res/xml/pref_colors.xml b/app/src/main/res/xml/pref_colors.xml index d172658f..4ec32d77 100644 --- a/app/src/main/res/xml/pref_colors.xml +++ b/app/src/main/res/xml/pref_colors.xml @@ -27,6 +27,12 @@ android:summary="@string/pref_summary_colored_navigation_bar" android:title="@string/pref_title_navigation_bar" /> + + diff --git a/build.gradle b/build.gradle index b5de8bd0..4a1d32e3 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,6 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:2.3.0' - classpath 'com.google.gms:google-services:3.0.0' } }