change application id for release
This commit is contained in:
parent
0cd75de1ee
commit
9d08253655
159 changed files with 801 additions and 801 deletions
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project Licensed under the Apache
|
||||
* License, Version 2.0 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
|
||||
* or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
// Modified for Phonograph by Karim Abou Zeid (kabouzeid).
|
||||
|
||||
package com.dkanada.gramophone.service;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.dkanada.gramophone.BuildConfig;
|
||||
|
||||
/**
|
||||
* Used to control headset playback.
|
||||
* Single press: pause/resume
|
||||
* Double press: next track
|
||||
* Triple press: previous track
|
||||
*/
|
||||
public class MediaButtonIntentReceiver extends BroadcastReceiver {
|
||||
private static final boolean DEBUG = BuildConfig.DEBUG;
|
||||
public static final String TAG = MediaButtonIntentReceiver.class.getSimpleName();
|
||||
|
||||
private static final int MSG_HEADSET_DOUBLE_CLICK_TIMEOUT = 2;
|
||||
|
||||
private static final int DOUBLE_CLICK = 400;
|
||||
|
||||
private static WakeLock mWakeLock = null;
|
||||
private static int mClickCounter = 0;
|
||||
private static long mLastClickTime = 0;
|
||||
|
||||
@SuppressLint("HandlerLeak")
|
||||
private static Handler mHandler = new Handler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(final Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_HEADSET_DOUBLE_CLICK_TIMEOUT:
|
||||
final int clickCount = msg.arg1;
|
||||
final String command;
|
||||
|
||||
if (DEBUG) Log.v(TAG, "Handling headset click, count = " + clickCount);
|
||||
switch (clickCount) {
|
||||
case 1:
|
||||
command = MusicService.ACTION_TOGGLE_PAUSE;
|
||||
break;
|
||||
case 2:
|
||||
command = MusicService.ACTION_SKIP;
|
||||
break;
|
||||
case 3:
|
||||
command = MusicService.ACTION_REWIND;
|
||||
break;
|
||||
default:
|
||||
command = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (command != null) {
|
||||
final Context context = (Context) msg.obj;
|
||||
startService(context, command);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
releaseWakeLockIfHandlerIdle();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
if (DEBUG) Log.v(TAG, "Received intent: " + intent);
|
||||
if (handleIntent(context, intent) && isOrderedBroadcast()) {
|
||||
abortBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean handleIntent(final Context context, final Intent intent) {
|
||||
final String intentAction = intent.getAction();
|
||||
if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
|
||||
final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
|
||||
if (event == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int keycode = event.getKeyCode();
|
||||
final int action = event.getAction();
|
||||
|
||||
// fallback to system time if event time is not available
|
||||
final long eventTime = event.getEventTime() != 0
|
||||
? event.getEventTime()
|
||||
: System.currentTimeMillis();
|
||||
|
||||
String command = null;
|
||||
switch (keycode) {
|
||||
case KeyEvent.KEYCODE_MEDIA_STOP:
|
||||
command = MusicService.ACTION_STOP;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
command = MusicService.ACTION_TOGGLE_PAUSE;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_NEXT:
|
||||
command = MusicService.ACTION_SKIP;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
|
||||
command = MusicService.ACTION_REWIND;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
||||
command = MusicService.ACTION_PAUSE;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
||||
command = MusicService.ACTION_PLAY;
|
||||
break;
|
||||
}
|
||||
|
||||
if (command != null) {
|
||||
if (action == KeyEvent.ACTION_DOWN) {
|
||||
if (event.getRepeatCount() == 0) {
|
||||
// Only consider the first event in a sequence, not the repeat events,
|
||||
// so that we don't trigger in cases where the first event went to
|
||||
// a different app (e.g. when the user ends a phone call by
|
||||
// long pressing the headset button)
|
||||
|
||||
// The service may or may not be running, but we need to send it
|
||||
// a command.
|
||||
if (keycode == KeyEvent.KEYCODE_HEADSETHOOK || keycode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
|
||||
if (eventTime - mLastClickTime >= DOUBLE_CLICK) {
|
||||
mClickCounter = 0;
|
||||
}
|
||||
|
||||
mClickCounter++;
|
||||
if (DEBUG) Log.v(TAG, "Got headset click, count = " + mClickCounter);
|
||||
mHandler.removeMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT);
|
||||
|
||||
Message msg = mHandler.obtainMessage(
|
||||
MSG_HEADSET_DOUBLE_CLICK_TIMEOUT, mClickCounter, 0, context);
|
||||
|
||||
long delay = mClickCounter < 3 ? DOUBLE_CLICK : 0;
|
||||
if (mClickCounter >= 3) {
|
||||
mClickCounter = 0;
|
||||
}
|
||||
|
||||
mLastClickTime = eventTime;
|
||||
acquireWakeLockAndSendMessage(context, msg, delay);
|
||||
} else {
|
||||
startService(context, command);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void startService(Context context, String command) {
|
||||
final Intent intent = new Intent(context, MusicService.class);
|
||||
intent.setAction(command);
|
||||
try {
|
||||
// IMPORTANT NOTE: (kind of a hack)
|
||||
// on Android O and above the following crashes when the app is not running
|
||||
// there is no good way to check whether the app is running so we catch the exception
|
||||
// we do not always want to use startForegroundService() because then one gets an ANR
|
||||
// if no notification is displayed via startForeground()
|
||||
// according to Play analytics this happens a lot, I suppose for example if command = PAUSE
|
||||
context.startService(intent);
|
||||
} catch (IllegalStateException ignored) {
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
}
|
||||
}
|
||||
|
||||
private static void acquireWakeLockAndSendMessage(Context context, Message msg, long delay) {
|
||||
if (mWakeLock == null) {
|
||||
Context appContext = context.getApplicationContext();
|
||||
PowerManager pm = (PowerManager) appContext.getSystemService(Context.POWER_SERVICE);
|
||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Phonograph headset button");
|
||||
mWakeLock.setReferenceCounted(false);
|
||||
}
|
||||
|
||||
if (DEBUG) Log.v(TAG, "Acquiring wake lock and sending " + msg.what);
|
||||
|
||||
mWakeLock.acquire(10000);
|
||||
mHandler.sendMessageDelayed(msg, delay);
|
||||
}
|
||||
|
||||
private static void releaseWakeLockIfHandlerIdle() {
|
||||
if (mHandler.hasMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)) {
|
||||
if (DEBUG) Log.v(TAG, "Handler still has messages pending, not releasing wake lock");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mWakeLock != null) {
|
||||
if (DEBUG) Log.v(TAG, "Releasing wake lock");
|
||||
|
||||
mWakeLock.release();
|
||||
mWakeLock = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
package com.dkanada.gramophone.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.audiofx.AudioEffect;
|
||||
import android.net.Uri;
|
||||
import android.os.PowerManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.service.playback.Playback;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
public class MultiPlayer implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
|
||||
public static final String TAG = MultiPlayer.class.getSimpleName();
|
||||
|
||||
private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
|
||||
private MediaPlayer mNextMediaPlayer;
|
||||
|
||||
private Context context;
|
||||
|
||||
@Nullable
|
||||
private Playback.PlaybackCallbacks callbacks;
|
||||
|
||||
private boolean mIsInitialized = false;
|
||||
|
||||
public MultiPlayer(final Context context) {
|
||||
this.context = context;
|
||||
mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path The path of the file, or the http/rtsp URL of the stream
|
||||
* you want to play
|
||||
* @return True if the <code>player</code> has been prepared and is
|
||||
* ready to play, false otherwise
|
||||
*/
|
||||
@Override
|
||||
public boolean setDataSource(@NonNull final String path) {
|
||||
mIsInitialized = false;
|
||||
mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
|
||||
if (mIsInitialized) {
|
||||
setNextDataSource(null);
|
||||
}
|
||||
|
||||
return mIsInitialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param player The {@link MediaPlayer} to use
|
||||
* @param path The path of the file, or the http/rtsp URL of the stream
|
||||
* you want to play
|
||||
* @return True if the <code>player</code> has been prepared and is
|
||||
* ready to play, false otherwise
|
||||
*/
|
||||
private boolean setDataSourceImpl(@NonNull final MediaPlayer player, @NonNull final String path) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
player.reset();
|
||||
player.setOnPreparedListener(null);
|
||||
if (path.startsWith("content://")) {
|
||||
player.setDataSource(context, Uri.parse(path));
|
||||
} else {
|
||||
player.setDataSource(path);
|
||||
}
|
||||
|
||||
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||
player.prepare();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
player.setOnCompletionListener(this);
|
||||
player.setOnErrorListener(this);
|
||||
|
||||
final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
|
||||
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
|
||||
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
|
||||
intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC);
|
||||
|
||||
context.sendBroadcast(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the MediaPlayer to start when this MediaPlayer finishes playback.
|
||||
*
|
||||
* @param path The path of the file, or the http/rtsp URL of the stream
|
||||
* you want to play
|
||||
*/
|
||||
@Override
|
||||
public void setNextDataSource(@Nullable final String path) {
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mCurrentMediaPlayer.setNextMediaPlayer(null);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.i(TAG, "Next media player is current one, continuing");
|
||||
} catch (IllegalStateException e) {
|
||||
Log.e(TAG, "Media player not initialized!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer.release();
|
||||
mNextMediaPlayer = null;
|
||||
}
|
||||
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PreferenceUtil.getInstance(context).getGaplessPlayback()) {
|
||||
mNextMediaPlayer = new MediaPlayer();
|
||||
mNextMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
|
||||
mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
|
||||
if (setDataSourceImpl(mNextMediaPlayer, path)) {
|
||||
try {
|
||||
mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
|
||||
} catch (@NonNull IllegalArgumentException | IllegalStateException e) {
|
||||
Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e);
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer.release();
|
||||
mNextMediaPlayer = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer.release();
|
||||
mNextMediaPlayer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallbacks(@Nullable Playback.PlaybackCallbacks callbacks) {
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return mIsInitialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
try {
|
||||
mCurrentMediaPlayer.start();
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
mCurrentMediaPlayer.reset();
|
||||
mIsInitialized = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
stop();
|
||||
|
||||
mCurrentMediaPlayer.release();
|
||||
if (mNextMediaPlayer != null) {
|
||||
mNextMediaPlayer.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pause() {
|
||||
try {
|
||||
mCurrentMediaPlayer.pause();
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlaying() {
|
||||
return mIsInitialized && mCurrentMediaPlayer.isPlaying();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int duration() {
|
||||
if (!mIsInitialized) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
return mCurrentMediaPlayer.getDuration();
|
||||
} catch (IllegalStateException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int position() {
|
||||
if (!mIsInitialized) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
return mCurrentMediaPlayer.getCurrentPosition();
|
||||
} catch (IllegalStateException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int seek(final int whereto) {
|
||||
try {
|
||||
mCurrentMediaPlayer.seekTo(whereto);
|
||||
return whereto;
|
||||
} catch (IllegalStateException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setVolume(final float vol) {
|
||||
try {
|
||||
mCurrentMediaPlayer.setVolume(vol, vol);
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setAudioSessionId(final int sessionId) {
|
||||
try {
|
||||
mCurrentMediaPlayer.setAudioSessionId(sessionId);
|
||||
return true;
|
||||
} catch (@NonNull IllegalArgumentException | IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAudioSessionId() {
|
||||
return mCurrentMediaPlayer.getAudioSessionId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onError(final MediaPlayer mp, final int what, final int extra) {
|
||||
mIsInitialized = false;
|
||||
mCurrentMediaPlayer.release();
|
||||
mCurrentMediaPlayer = new MediaPlayer();
|
||||
mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
|
||||
if (context != null) {
|
||||
Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompletion(final MediaPlayer mp) {
|
||||
if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
|
||||
mIsInitialized = false;
|
||||
mCurrentMediaPlayer.release();
|
||||
mCurrentMediaPlayer = mNextMediaPlayer;
|
||||
mIsInitialized = true;
|
||||
mNextMediaPlayer = null;
|
||||
if (callbacks != null) callbacks.onTrackWentToNext();
|
||||
} else {
|
||||
if (callbacks != null) callbacks.onTrackEnded();
|
||||
}
|
||||
}
|
||||
}
|
||||
1342
app/src/main/java/com/dkanada/gramophone/service/MusicService.java
Normal file
1342
app/src/main/java/com/dkanada/gramophone/service/MusicService.java
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,77 @@
|
|||
package com.dkanada.gramophone.service.notification;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.service.MusicService;
|
||||
|
||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
|
||||
public abstract class PlayingNotification {
|
||||
|
||||
private static final int NOTIFICATION_ID = 1;
|
||||
static final String NOTIFICATION_CHANNEL_ID = "playing_notification";
|
||||
|
||||
private static final int NOTIFY_MODE_FOREGROUND = 1;
|
||||
private static final int NOTIFY_MODE_BACKGROUND = 0;
|
||||
|
||||
private int notifyMode = NOTIFY_MODE_BACKGROUND;
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
protected MusicService service;
|
||||
boolean stopped;
|
||||
|
||||
public synchronized void init(MusicService service) {
|
||||
this.service = service;
|
||||
notificationManager = (NotificationManager) service.getSystemService(NOTIFICATION_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void update();
|
||||
|
||||
public synchronized void stop() {
|
||||
stopped = true;
|
||||
service.stopForeground(true);
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
void updateNotifyModeAndPostNotification(Notification notification) {
|
||||
int newNotifyMode;
|
||||
if (service.isPlaying()) {
|
||||
newNotifyMode = NOTIFY_MODE_FOREGROUND;
|
||||
} else {
|
||||
newNotifyMode = NOTIFY_MODE_BACKGROUND;
|
||||
}
|
||||
|
||||
if (notifyMode != newNotifyMode && newNotifyMode == NOTIFY_MODE_BACKGROUND) {
|
||||
service.stopForeground(false);
|
||||
}
|
||||
|
||||
if (newNotifyMode == NOTIFY_MODE_FOREGROUND) {
|
||||
service.startForeground(NOTIFICATION_ID, notification);
|
||||
} else if (newNotifyMode == NOTIFY_MODE_BACKGROUND) {
|
||||
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
notifyMode = newNotifyMode;
|
||||
}
|
||||
|
||||
@RequiresApi(26)
|
||||
private void createNotificationChannel() {
|
||||
NotificationChannel notificationChannel = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID);
|
||||
if (notificationChannel == null) {
|
||||
notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, service.getString(R.string.playing_notification_name), NotificationManager.IMPORTANCE_LOW);
|
||||
notificationChannel.setDescription(service.getString(R.string.playing_notification_description));
|
||||
notificationChannel.enableLights(false);
|
||||
notificationChannel.enableVibration(false);
|
||||
|
||||
notificationManager.createNotificationChannel(notificationChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
package com.dkanada.gramophone.service.notification;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
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.ColorUtil;
|
||||
import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.palette.BitmapPaletteWrapper;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.service.MusicService;
|
||||
import com.dkanada.gramophone.ui.activities.MainActivity;
|
||||
import com.dkanada.gramophone.util.ImageUtil;
|
||||
import com.dkanada.gramophone.util.ThemeUtil;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
public class PlayingNotificationImpl extends PlayingNotification {
|
||||
|
||||
private Target<BitmapPaletteWrapper> target;
|
||||
|
||||
@Override
|
||||
public synchronized void update() {
|
||||
stopped = false;
|
||||
|
||||
final Song song = service.getCurrentSong();
|
||||
|
||||
final boolean isPlaying = service.isPlaying();
|
||||
|
||||
final RemoteViews notificationLayout = new RemoteViews(service.getPackageName(), R.layout.notification);
|
||||
final RemoteViews notificationLayoutBig = new RemoteViews(service.getPackageName(), R.layout.notification_big);
|
||||
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
|
||||
notificationLayout.setViewVisibility(R.id.media_titles, View.INVISIBLE);
|
||||
} else {
|
||||
notificationLayout.setViewVisibility(R.id.media_titles, View.VISIBLE);
|
||||
notificationLayout.setTextViewText(R.id.title, song.title);
|
||||
notificationLayout.setTextViewText(R.id.text, song.artistName);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName) && TextUtils.isEmpty(song.albumName)) {
|
||||
notificationLayoutBig.setViewVisibility(R.id.media_titles, View.INVISIBLE);
|
||||
} else {
|
||||
notificationLayoutBig.setViewVisibility(R.id.media_titles, View.VISIBLE);
|
||||
notificationLayoutBig.setTextViewText(R.id.title, song.title);
|
||||
notificationLayoutBig.setTextViewText(R.id.text, song.artistName);
|
||||
notificationLayoutBig.setTextViewText(R.id.text2, song.albumName);
|
||||
}
|
||||
|
||||
linkButtons(notificationLayout, notificationLayoutBig);
|
||||
|
||||
Intent action = new Intent(service, MainActivity.class);
|
||||
action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
final PendingIntent clickIntent = PendingIntent.getActivity(service, 0, action, 0);
|
||||
final PendingIntent deleteIntent = buildPendingIntent(service, MusicService.ACTION_QUIT, null);
|
||||
|
||||
final Notification notification = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentIntent(clickIntent)
|
||||
.setDeleteIntent(deleteIntent)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContent(notificationLayout)
|
||||
.setCustomBigContentView(notificationLayoutBig)
|
||||
.setOngoing(isPlaying)
|
||||
.build();
|
||||
|
||||
final int bigNotificationImageSize = service.getResources().getDimensionPixelSize(R.dimen.notification_big_image_size);
|
||||
service.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (target != null) {
|
||||
Glide.clear(target);
|
||||
}
|
||||
target = CustomGlideRequest.Builder.from(Glide.with(service), song.primary)
|
||||
.generatePalette(service).build()
|
||||
.into(new SimpleTarget<BitmapPaletteWrapper>(bigNotificationImageSize, bigNotificationImageSize) {
|
||||
@Override
|
||||
public void onResourceReady(BitmapPaletteWrapper resource, GlideAnimation<? super BitmapPaletteWrapper> glideAnimation) {
|
||||
update(resource.getBitmap(), ThemeUtil.getColor(resource.getPalette(), Color.TRANSPARENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFailed(Exception e, Drawable errorDrawable) {
|
||||
super.onLoadFailed(e, errorDrawable);
|
||||
update(null, Color.WHITE);
|
||||
}
|
||||
|
||||
private void update(@Nullable Bitmap bitmap, int bgColor) {
|
||||
if (bitmap != null) {
|
||||
notificationLayout.setImageViewBitmap(R.id.image, bitmap);
|
||||
notificationLayoutBig.setImageViewBitmap(R.id.image, bitmap);
|
||||
} else {
|
||||
notificationLayout.setImageViewResource(R.id.image, R.drawable.default_album_art);
|
||||
notificationLayoutBig.setImageViewResource(R.id.image, R.drawable.default_album_art);
|
||||
}
|
||||
|
||||
if (!PreferenceUtil.getInstance(service).getColoredNotification()) {
|
||||
bgColor = Color.WHITE;
|
||||
}
|
||||
setBackgroundColor(bgColor);
|
||||
setNotificationContent(ColorUtil.isColorLight(bgColor));
|
||||
|
||||
// notification has been stopped before loading was finished
|
||||
if (stopped) return;
|
||||
|
||||
updateNotifyModeAndPostNotification(notification);
|
||||
}
|
||||
|
||||
private void setBackgroundColor(int color) {
|
||||
notificationLayout.setInt(R.id.root, "setBackgroundColor", color);
|
||||
notificationLayoutBig.setInt(R.id.root, "setBackgroundColor", color);
|
||||
}
|
||||
|
||||
private void setNotificationContent(boolean dark) {
|
||||
int primary = MaterialValueHelper.getPrimaryTextColor(service, dark);
|
||||
int secondary = MaterialValueHelper.getSecondaryTextColor(service, dark);
|
||||
|
||||
Bitmap prev = ImageUtil.createBitmap(ImageUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, primary), 1.5f);
|
||||
Bitmap next = ImageUtil.createBitmap(ImageUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, primary), 1.5f);
|
||||
Bitmap playPause = ImageUtil.createBitmap(ImageUtil.getTintedVectorDrawable(service, isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp, primary), 1.5f);
|
||||
|
||||
notificationLayout.setTextColor(R.id.title, primary);
|
||||
notificationLayout.setTextColor(R.id.text, secondary);
|
||||
notificationLayout.setImageViewBitmap(R.id.action_prev, prev);
|
||||
notificationLayout.setImageViewBitmap(R.id.action_next, next);
|
||||
notificationLayout.setImageViewBitmap(R.id.action_play_pause, playPause);
|
||||
|
||||
notificationLayoutBig.setTextColor(R.id.title, primary);
|
||||
notificationLayoutBig.setTextColor(R.id.text, secondary);
|
||||
notificationLayoutBig.setTextColor(R.id.text2, secondary);
|
||||
notificationLayoutBig.setImageViewBitmap(R.id.action_prev, prev);
|
||||
notificationLayoutBig.setImageViewBitmap(R.id.action_next, next);
|
||||
notificationLayoutBig.setImageViewBitmap(R.id.action_play_pause, playPause);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void linkButtons(final RemoteViews notificationLayout, final RemoteViews notificationLayoutBig) {
|
||||
PendingIntent pendingIntent;
|
||||
|
||||
final ComponentName serviceName = new ComponentName(service, MusicService.class);
|
||||
|
||||
// Previous track
|
||||
pendingIntent = buildPendingIntent(service, MusicService.ACTION_REWIND, serviceName);
|
||||
notificationLayout.setOnClickPendingIntent(R.id.action_prev, pendingIntent);
|
||||
notificationLayoutBig.setOnClickPendingIntent(R.id.action_prev, pendingIntent);
|
||||
|
||||
// Play and pause
|
||||
pendingIntent = buildPendingIntent(service, MusicService.ACTION_TOGGLE_PAUSE, serviceName);
|
||||
notificationLayout.setOnClickPendingIntent(R.id.action_play_pause, pendingIntent);
|
||||
notificationLayoutBig.setOnClickPendingIntent(R.id.action_play_pause, pendingIntent);
|
||||
|
||||
// Next track
|
||||
pendingIntent = buildPendingIntent(service, MusicService.ACTION_SKIP, serviceName);
|
||||
notificationLayout.setOnClickPendingIntent(R.id.action_next, pendingIntent);
|
||||
notificationLayoutBig.setOnClickPendingIntent(R.id.action_next, pendingIntent);
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.dkanada.gramophone.service.notification;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.media.app.NotificationCompat.MediaStyle;
|
||||
import androidx.palette.graphics.Palette;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
import com.dkanada.gramophone.R;
|
||||
import com.dkanada.gramophone.glide.CustomGlideRequest;
|
||||
import com.dkanada.gramophone.glide.palette.BitmapPaletteWrapper;
|
||||
import com.dkanada.gramophone.model.Song;
|
||||
import com.dkanada.gramophone.service.MusicService;
|
||||
import com.dkanada.gramophone.ui.activities.MainActivity;
|
||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||
|
||||
import static com.dkanada.gramophone.service.MusicService.ACTION_REWIND;
|
||||
import static com.dkanada.gramophone.service.MusicService.ACTION_SKIP;
|
||||
import static com.dkanada.gramophone.service.MusicService.ACTION_TOGGLE_PAUSE;
|
||||
|
||||
public class PlayingNotificationImpl24 extends PlayingNotification {
|
||||
|
||||
@Override
|
||||
public synchronized void update() {
|
||||
stopped = false;
|
||||
|
||||
final Song song = service.getCurrentSong();
|
||||
|
||||
final boolean isPlaying = service.isPlaying();
|
||||
|
||||
final int playButtonResId = isPlaying
|
||||
? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp;
|
||||
|
||||
Intent action = new Intent(service, MainActivity.class);
|
||||
action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
final PendingIntent clickIntent = PendingIntent.getActivity(service, 0, action, 0);
|
||||
|
||||
final ComponentName serviceName = new ComponentName(service, MusicService.class);
|
||||
Intent intent = new Intent(MusicService.ACTION_QUIT);
|
||||
intent.setComponent(serviceName);
|
||||
final PendingIntent deleteIntent = PendingIntent.getService(service, 0, intent, 0);
|
||||
|
||||
final int bigNotificationImageSize = service.getResources().getDimensionPixelSize(R.dimen.notification_big_image_size);
|
||||
service.runOnUiThread(() -> CustomGlideRequest.Builder
|
||||
.from(Glide.with(service), song.primary)
|
||||
.generatePalette(service).build()
|
||||
.into(new SimpleTarget<BitmapPaletteWrapper>(bigNotificationImageSize, bigNotificationImageSize) {
|
||||
@Override
|
||||
public void onResourceReady(BitmapPaletteWrapper resource, GlideAnimation<? super BitmapPaletteWrapper> glideAnimation) {
|
||||
Palette palette = resource.getPalette();
|
||||
update(resource.getBitmap(), palette.getVibrantColor(palette.getMutedColor(Color.TRANSPARENT)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFailed(Exception e, Drawable errorDrawable) {
|
||||
update(null, Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
void update(Bitmap bitmap, int color) {
|
||||
if (bitmap == null)
|
||||
bitmap = BitmapFactory.decodeResource(service.getResources(), R.drawable.default_album_art);
|
||||
NotificationCompat.Action playPauseAction = new NotificationCompat.Action(playButtonResId,
|
||||
service.getString(R.string.action_play_pause),
|
||||
retrievePlaybackAction(ACTION_TOGGLE_PAUSE));
|
||||
NotificationCompat.Action previousAction = new NotificationCompat.Action(R.drawable.ic_skip_previous_white_24dp,
|
||||
service.getString(R.string.action_previous),
|
||||
retrievePlaybackAction(ACTION_REWIND));
|
||||
NotificationCompat.Action nextAction = new NotificationCompat.Action(R.drawable.ic_skip_next_white_24dp,
|
||||
service.getString(R.string.action_next),
|
||||
retrievePlaybackAction(ACTION_SKIP));
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setSubText(song.albumName)
|
||||
.setLargeIcon(bitmap)
|
||||
.setContentIntent(clickIntent)
|
||||
.setDeleteIntent(deleteIntent)
|
||||
.setContentTitle(song.title)
|
||||
.setContentText(song.artistName)
|
||||
.setOngoing(isPlaying)
|
||||
.setShowWhen(false)
|
||||
.addAction(previousAction)
|
||||
.addAction(playPauseAction)
|
||||
.addAction(nextAction);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
builder.setStyle(new MediaStyle().setMediaSession(service.getMediaSession().getSessionToken()).setShowActionsInCompactView(0, 1, 2))
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O && PreferenceUtil.getInstance(service).getColoredNotification())
|
||||
builder.setColor(color);
|
||||
}
|
||||
|
||||
// notification has been stopped before loading was finished
|
||||
if (stopped) return;
|
||||
|
||||
updateNotifyModeAndPostNotification(builder.build());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private PendingIntent retrievePlaybackAction(final String action) {
|
||||
final ComponentName serviceName = new ComponentName(service, MusicService.class);
|
||||
Intent intent = new Intent(action);
|
||||
intent.setComponent(serviceName);
|
||||
return PendingIntent.getService(service, 0, intent, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.dkanada.gramophone.service.playback;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public interface Playback {
|
||||
boolean setDataSource(String path);
|
||||
|
||||
void setNextDataSource(@Nullable String path);
|
||||
|
||||
void setCallbacks(PlaybackCallbacks callbacks);
|
||||
|
||||
boolean isInitialized();
|
||||
|
||||
boolean start();
|
||||
|
||||
void stop();
|
||||
|
||||
void release();
|
||||
|
||||
boolean pause();
|
||||
|
||||
boolean isPlaying();
|
||||
|
||||
int duration();
|
||||
|
||||
int position();
|
||||
|
||||
int seek(int whereto);
|
||||
|
||||
boolean setVolume(float vol);
|
||||
|
||||
boolean setAudioSessionId(int sessionId);
|
||||
|
||||
int getAudioSessionId();
|
||||
|
||||
interface PlaybackCallbacks {
|
||||
void onTrackWentToNext();
|
||||
|
||||
void onTrackEnded();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue