2017-03-10 14:53:19 +01:00
/ * Copyright ( C ) 2015 - 2017 Andreas Shimokawa , Carsten Pfeiffer , Daniele
2017-10-16 09:38:02 +02:00
Gobbetti , Frank Slezak , Hasan Ammar , Julien Pivotto , Kevin Richter , Normano64 ,
Steffen Liebergeld
2017-03-10 14:53:19 +01:00
This file is part of Gadgetbridge .
Gadgetbridge is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
Gadgetbridge 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 Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>. */
2015-04-20 22:39:35 +02:00
package nodomain.freeyourgadget.gadgetbridge.externalevents ;
2015-01-07 14:00:18 +01:00
2015-04-04 23:20:28 +02:00
import android.app.ActivityManager ;
2015-01-07 14:00:18 +01:00
import android.app.Notification ;
2016-05-19 16:34:59 +02:00
import android.app.NotificationManager ;
2015-08-31 22:27:25 +02:00
import android.app.PendingIntent ;
2015-07-21 01:25:22 +02:00
import android.content.BroadcastReceiver ;
import android.content.Context ;
2015-01-07 14:00:18 +01:00
import android.content.Intent ;
2015-07-21 01:25:22 +02:00
import android.content.IntentFilter ;
2015-09-25 00:53:40 +02:00
import android.content.pm.ApplicationInfo ;
import android.content.pm.PackageManager ;
2017-09-19 13:24:31 +02:00
import android.graphics.Bitmap ;
import android.graphics.Color ;
import android.graphics.drawable.Drawable ;
Add handling for media session notifications
Since Android 5.0, media players can have interactive notifications that
reside in the notification area, and offer up to 5 control buttons
(play/pause, next, previous, etc), and information about the currentlu
playing media file.
We use these notifications to get information about the currently
playing media file such as:
- artist
- track (title)
- album
- duration (length of the media file)
- play state (playing, paused, stopped)
- position
- play rate (how fast is the media file being played)
We then send this information up to the device.
On Pebble, the music app will display the title and the artist, as
well as a progress bar showing the current position. The progress bar is
animated when the media file is being played, and if it is being paused,
it displays a pause symbol.
This code will be skipped when GadgetBridge is run on a device with
Android version older than 5.0 (lollipop).
2016-06-08 20:33:20 +02:00
import android.media.MediaMetadata ;
import android.media.session.PlaybackState ;
2015-01-07 14:00:18 +01:00
import android.os.Bundle ;
2015-03-04 23:47:47 +01:00
import android.os.PowerManager ;
2017-05-28 18:50:41 +02:00
import android.os.RemoteException ;
2015-01-07 14:00:18 +01:00
import android.service.notification.NotificationListenerService ;
import android.service.notification.StatusBarNotification ;
2018-01-20 21:27:37 +01:00
import android.support.v4.app.NotificationCompat ;
2016-01-09 17:54:17 +01:00
import android.support.v4.app.RemoteInput ;
2015-07-21 01:25:22 +02:00
import android.support.v4.content.LocalBroadcastManager ;
2017-05-28 18:50:41 +02:00
import android.support.v4.media.MediaMetadataCompat ;
import android.support.v4.media.session.MediaControllerCompat ;
import android.support.v4.media.session.MediaSessionCompat ;
import android.support.v4.media.session.PlaybackStateCompat ;
2017-09-19 13:24:31 +02:00
import android.support.v7.graphics.Palette ;
2016-12-09 20:14:17 +01:00
2016-12-09 17:54:19 +01:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
import java.util.List ;
2017-09-19 13:24:31 +02:00
import java.util.Objects ;
2015-04-20 22:39:35 +02:00
2016-12-09 20:14:17 +01:00
import nodomain.freeyourgadget.gadgetbridge.GBApplication ;
2017-01-27 00:23:45 +01:00
import nodomain.freeyourgadget.gadgetbridge.R ;
2017-09-19 13:24:31 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor ;
2016-12-09 20:14:17 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.AppNotificationType ;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec ;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec ;
2016-12-24 22:00:24 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType ;
2016-12-09 20:14:17 +01:00
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService ;
2017-09-19 13:24:31 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil ;
2016-12-09 20:14:17 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue ;
2017-09-19 13:24:31 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils ;
2016-12-09 20:14:17 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.Prefs ;
2018-01-20 21:27:37 +01:00
import static android.support.v4.media.app.NotificationCompat.MediaStyle.getMediaSession ;
2015-01-07 14:00:18 +01:00
public class NotificationListener extends NotificationListenerService {
2015-05-12 06:28:11 +02:00
private static final Logger LOG = LoggerFactory . getLogger ( NotificationListener . class ) ;
2015-01-07 14:00:18 +01:00
2015-07-21 01:25:22 +02:00
public static final String ACTION_DISMISS
= " nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.dismiss " ;
2015-09-01 21:58:36 +02:00
public static final String ACTION_DISMISS_ALL
= " nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.dismiss_all " ;
2015-08-31 22:27:25 +02:00
public static final String ACTION_OPEN
= " nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.open " ;
2015-09-25 00:53:40 +02:00
public static final String ACTION_MUTE
= " nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.mute " ;
2016-01-09 17:54:17 +01:00
public static final String ACTION_REPLY
= " nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.reply " ;
private LimitedQueue mActionLookup = new LimitedQueue ( 16 ) ;
2015-07-21 01:25:22 +02:00
2015-11-23 23:04:46 +01:00
private final BroadcastReceiver mReceiver = new BroadcastReceiver ( ) {
2017-05-28 18:50:41 +02:00
2015-07-21 01:25:22 +02:00
@Override
public void onReceive ( Context context , Intent intent ) {
String action = intent . getAction ( ) ;
2015-09-25 00:53:40 +02:00
switch ( action ) {
2016-04-03 00:49:54 +02:00
case GBApplication . ACTION_QUIT :
stopSelf ( ) ;
break ;
2015-09-25 00:53:40 +02:00
case ACTION_MUTE :
case ACTION_OPEN : {
StatusBarNotification [ ] sbns = NotificationListener . this . getActiveNotifications ( ) ;
int handle = intent . getIntExtra ( " handle " , - 1 ) ;
for ( StatusBarNotification sbn : sbns ) {
2017-05-10 22:26:02 +02:00
if ( ( int ) sbn . getPostTime ( ) = = handle ) {
2015-09-25 00:53:40 +02:00
if ( action . equals ( ACTION_OPEN ) ) {
try {
PendingIntent pi = sbn . getNotification ( ) . contentIntent ;
if ( pi ! = null ) {
pi . send ( ) ;
}
} catch ( PendingIntent . CanceledException e ) {
e . printStackTrace ( ) ;
}
} else {
// ACTION_MUTE
LOG . info ( " going to mute " + sbn . getPackageName ( ) ) ;
2017-08-18 10:30:19 +02:00
GBApplication . addAppToBlacklist ( sbn . getPackageName ( ) ) ;
2015-08-31 22:27:25 +02:00
}
}
}
2015-09-25 00:53:40 +02:00
break ;
2015-08-31 22:27:25 +02:00
}
2015-09-25 00:53:40 +02:00
case ACTION_DISMISS : {
StatusBarNotification [ ] sbns = NotificationListener . this . getActiveNotifications ( ) ;
int handle = intent . getIntExtra ( " handle " , - 1 ) ;
for ( StatusBarNotification sbn : sbns ) {
2017-05-10 22:26:02 +02:00
if ( ( int ) sbn . getPostTime ( ) = = handle ) {
2015-09-25 00:53:40 +02:00
if ( GBApplication . isRunningLollipopOrLater ( ) ) {
String key = sbn . getKey ( ) ;
NotificationListener . this . cancelNotification ( key ) ;
} else {
int id = sbn . getId ( ) ;
String pkg = sbn . getPackageName ( ) ;
String tag = sbn . getTag ( ) ;
NotificationListener . this . cancelNotification ( pkg , tag , id ) ;
}
2015-09-01 21:58:36 +02:00
}
}
2015-09-25 00:53:40 +02:00
break ;
2015-09-01 21:58:36 +02:00
}
2015-09-25 00:53:40 +02:00
case ACTION_DISMISS_ALL :
NotificationListener . this . cancelAllNotifications ( ) ;
break ;
2016-01-09 17:54:17 +01:00
case ACTION_REPLY :
int id = intent . getIntExtra ( " handle " , - 1 ) ;
String reply = intent . getStringExtra ( " reply " ) ;
NotificationCompat . Action replyAction = ( NotificationCompat . Action ) mActionLookup . lookup ( id ) ;
if ( replyAction ! = null & & replyAction . getRemoteInputs ( ) ! = null ) {
RemoteInput [ ] remoteInputs = replyAction . getRemoteInputs ( ) ;
PendingIntent actionIntent = replyAction . getActionIntent ( ) ;
Intent localIntent = new Intent ( ) ;
localIntent . addFlags ( Intent . FLAG_ACTIVITY_NEW_TASK ) ;
Bundle extras = new Bundle ( ) ;
extras . putCharSequence ( remoteInputs [ 0 ] . getResultKey ( ) , reply ) ;
RemoteInput . addResultsToIntent ( remoteInputs , localIntent , extras ) ;
try {
LOG . info ( " will send reply intent to remote application " ) ;
actionIntent . send ( context , 0 , localIntent ) ;
mActionLookup . remove ( id ) ;
} catch ( PendingIntent . CanceledException e ) {
LOG . warn ( " replyToLastNotification error: " + e . getLocalizedMessage ( ) ) ;
}
}
break ;
2015-07-21 01:25:22 +02:00
}
2015-09-01 21:58:36 +02:00
2015-07-21 01:25:22 +02:00
}
} ;
2015-01-07 14:00:18 +01:00
@Override
public void onCreate ( ) {
super . onCreate ( ) ;
2015-07-21 01:25:22 +02:00
IntentFilter filterLocal = new IntentFilter ( ) ;
2016-04-03 00:49:54 +02:00
filterLocal . addAction ( GBApplication . ACTION_QUIT ) ;
2015-08-31 22:27:25 +02:00
filterLocal . addAction ( ACTION_OPEN ) ;
2015-09-01 21:58:36 +02:00
filterLocal . addAction ( ACTION_DISMISS ) ;
filterLocal . addAction ( ACTION_DISMISS_ALL ) ;
2015-09-25 00:53:40 +02:00
filterLocal . addAction ( ACTION_MUTE ) ;
2016-01-09 17:54:17 +01:00
filterLocal . addAction ( ACTION_REPLY ) ;
2015-07-21 01:25:22 +02:00
LocalBroadcastManager . getInstance ( this ) . registerReceiver ( mReceiver , filterLocal ) ;
2015-01-07 14:00:18 +01:00
}
@Override
public void onDestroy ( ) {
2015-07-21 01:25:22 +02:00
LocalBroadcastManager . getInstance ( this ) . unregisterReceiver ( mReceiver ) ;
2015-01-07 14:00:18 +01:00
super . onDestroy ( ) ;
}
@Override
public void onNotificationPosted ( StatusBarNotification sbn ) {
2017-05-28 18:50:41 +02:00
if ( shouldIgnore ( sbn ) )
2015-04-04 23:20:28 +02:00
return ;
2015-03-04 23:47:47 +01:00
2016-05-19 23:58:13 +02:00
switch ( GBApplication . getGrantedInterruptionFilter ( ) ) {
case NotificationManager . INTERRUPTION_FILTER_ALL :
break ;
case NotificationManager . INTERRUPTION_FILTER_ALARMS :
case NotificationManager . INTERRUPTION_FILTER_NONE :
return ;
case NotificationManager . INTERRUPTION_FILTER_PRIORITY :
// FIXME: Handle Reminders and Events if they are enabled in Do Not Disturb
return ;
2016-05-19 16:34:59 +02:00
}
2015-03-04 23:47:47 +01:00
2017-09-19 13:24:31 +02:00
String source = sbn . getPackageName ( ) . toLowerCase ( ) ;
2015-01-07 14:00:18 +01:00
Notification notification = sbn . getNotification ( ) ;
2015-09-24 14:45:21 +02:00
NotificationSpec notificationSpec = new NotificationSpec ( ) ;
2018-01-20 21:27:37 +01:00
notificationSpec . id = ( int ) sbn . getPostTime ( ) ; //FIXME: a truly unique id would be better
2015-09-25 00:53:40 +02:00
// determinate Source App Name ("Label")
PackageManager pm = getPackageManager ( ) ;
ApplicationInfo ai = null ;
try {
ai = pm . getApplicationInfo ( source , 0 ) ;
} catch ( PackageManager . NameNotFoundException e ) {
e . printStackTrace ( ) ;
}
if ( ai ! = null ) {
notificationSpec . sourceName = ( String ) pm . getApplicationLabel ( ai ) ;
}
2015-12-27 19:22:10 +01:00
2017-05-10 22:26:02 +02:00
boolean preferBigText = false ;
2016-10-19 18:38:26 +02:00
2017-09-19 13:24:31 +02:00
// Get the app ID that generated this notification. For now only used by pebble color, but may be more useful later.
notificationSpec . sourceAppId = source ;
2016-12-09 17:54:19 +01:00
notificationSpec . type = AppNotificationType . getInstance ( ) . get ( source ) ;
2015-09-13 00:39:53 +02:00
2017-05-10 22:26:02 +02:00
if ( source . startsWith ( " com.fsck.k9 " ) ) {
preferBigText = true ;
}
2016-12-24 22:00:24 +01:00
if ( notificationSpec . type = = null ) {
notificationSpec . type = NotificationType . UNKNOWN ;
}
2017-09-19 13:24:31 +02:00
// Get color
notificationSpec . pebbleColor = getPebbleColorForNotification ( notificationSpec ) ;
2017-05-28 18:50:41 +02:00
LOG . info ( " Processing notification " + notificationSpec . id + " from source " + source + " with flags: " + notification . flags ) ;
2015-01-22 22:49:50 +01:00
2016-10-19 18:38:26 +02:00
dissectNotificationTo ( notification , notificationSpec , preferBigText ) ;
2016-09-11 00:38:26 +02:00
2017-01-27 00:23:45 +01:00
// ignore Gadgetbridge's very own notifications, except for those from the debug screen
if ( getApplicationContext ( ) . getPackageName ( ) . equals ( source ) ) {
if ( ! getApplicationContext ( ) . getString ( R . string . test_notification ) . equals ( notificationSpec . title ) ) {
return ;
}
}
2016-01-09 17:54:17 +01:00
NotificationCompat . WearableExtender wearableExtender = new NotificationCompat . WearableExtender ( notification ) ;
List < NotificationCompat . Action > actions = wearableExtender . getActions ( ) ;
2016-09-22 09:31:14 +02:00
2016-01-09 17:54:17 +01:00
for ( NotificationCompat . Action act : actions ) {
if ( act ! = null & & act . getRemoteInputs ( ) ! = null ) {
LOG . info ( " found wearable action: " + act . getTitle ( ) + " " + sbn . getTag ( ) ) ;
mActionLookup . add ( notificationSpec . id , act ) ;
notificationSpec . flags | = NotificationSpec . FLAG_WEARABLE_REPLY ;
break ;
}
}
2017-05-28 18:50:41 +02:00
if ( ( notificationSpec . flags & NotificationSpec . FLAG_WEARABLE_REPLY ) = = 0 & & NotificationCompat . isGroupSummary ( notification ) ) { //this could cause #395 to come back
LOG . info ( " Not forwarding notification, FLAG_GROUP_SUMMARY is set and no wearable action present. Notification flags: " + notification . flags ) ;
return ;
}
2015-11-23 22:09:47 +01:00
GBApplication . deviceService ( ) . onNotification ( notificationSpec ) ;
}
2016-10-19 18:38:26 +02:00
private void dissectNotificationTo ( Notification notification , NotificationSpec notificationSpec , boolean preferBigText ) {
2017-05-28 18:50:41 +02:00
Bundle extras = NotificationCompat . getExtras ( notification ) ;
2016-10-18 23:44:00 +02:00
//dumpExtras(extras);
2015-11-23 22:09:47 +01:00
CharSequence title = extras . getCharSequence ( Notification . EXTRA_TITLE ) ;
if ( title ! = null ) {
notificationSpec . title = title . toString ( ) ;
}
2016-10-18 23:44:00 +02:00
CharSequence contentCS = null ;
2017-05-10 22:25:46 +02:00
if ( preferBigText & & extras . containsKey ( Notification . EXTRA_BIG_TEXT ) ) {
2017-05-28 18:50:41 +02:00
contentCS = extras . getCharSequence ( NotificationCompat . EXTRA_BIG_TEXT ) ;
2017-05-10 22:25:46 +02:00
} else if ( extras . containsKey ( Notification . EXTRA_TEXT ) ) {
2017-05-28 18:50:41 +02:00
contentCS = extras . getCharSequence ( NotificationCompat . EXTRA_TEXT ) ;
2016-10-18 23:44:00 +02:00
}
if ( contentCS ! = null ) {
notificationSpec . body = contentCS . toString ( ) ;
2015-01-26 18:52:19 +01:00
}
2016-10-18 23:44:00 +02:00
2015-01-07 14:00:18 +01:00
}
2015-08-06 22:24:44 +02:00
private boolean isServiceRunning ( ) {
ActivityManager manager = ( ActivityManager ) getSystemService ( ACTIVITY_SERVICE ) ;
for ( ActivityManager . RunningServiceInfo service : manager . getRunningServices ( Integer . MAX_VALUE ) ) {
if ( DeviceCommunicationService . class . getName ( ) . equals ( service . service . getClassName ( ) ) ) {
return true ;
}
}
return false ;
}
2016-06-07 19:34:37 +02:00
/ * *
Add handling for media session notifications
Since Android 5.0, media players can have interactive notifications that
reside in the notification area, and offer up to 5 control buttons
(play/pause, next, previous, etc), and information about the currentlu
playing media file.
We use these notifications to get information about the currently
playing media file such as:
- artist
- track (title)
- album
- duration (length of the media file)
- play state (playing, paused, stopped)
- position
- play rate (how fast is the media file being played)
We then send this information up to the device.
On Pebble, the music app will display the title and the artist, as
well as a progress bar showing the current position. The progress bar is
animated when the media file is being played, and if it is being paused,
it displays a pause symbol.
This code will be skipped when GadgetBridge is run on a device with
Android version older than 5.0 (lollipop).
2016-06-08 20:33:20 +02:00
* Try to handle media session notifications that tell info about the current play state .
*
2017-05-28 18:50:41 +02:00
* @param mediaSession The mediasession to handle .
2016-06-07 19:34:37 +02:00
* @return true if notification was handled , false otherwise
* /
2017-05-28 18:50:41 +02:00
public boolean handleMediaSessionNotification ( MediaSessionCompat . Token mediaSession ) {
2016-06-07 19:34:37 +02:00
MusicSpec musicSpec = new MusicSpec ( ) ;
Add handling for media session notifications
Since Android 5.0, media players can have interactive notifications that
reside in the notification area, and offer up to 5 control buttons
(play/pause, next, previous, etc), and information about the currentlu
playing media file.
We use these notifications to get information about the currently
playing media file such as:
- artist
- track (title)
- album
- duration (length of the media file)
- play state (playing, paused, stopped)
- position
- play rate (how fast is the media file being played)
We then send this information up to the device.
On Pebble, the music app will display the title and the artist, as
well as a progress bar showing the current position. The progress bar is
animated when the media file is being played, and if it is being paused,
it displays a pause symbol.
This code will be skipped when GadgetBridge is run on a device with
Android version older than 5.0 (lollipop).
2016-06-08 20:33:20 +02:00
MusicStateSpec stateSpec = new MusicStateSpec ( ) ;
2016-06-07 19:34:37 +02:00
2017-05-28 18:50:41 +02:00
MediaControllerCompat c ;
Add handling for media session notifications
Since Android 5.0, media players can have interactive notifications that
reside in the notification area, and offer up to 5 control buttons
(play/pause, next, previous, etc), and information about the currentlu
playing media file.
We use these notifications to get information about the currently
playing media file such as:
- artist
- track (title)
- album
- duration (length of the media file)
- play state (playing, paused, stopped)
- position
- play rate (how fast is the media file being played)
We then send this information up to the device.
On Pebble, the music app will display the title and the artist, as
well as a progress bar showing the current position. The progress bar is
animated when the media file is being played, and if it is being paused,
it displays a pause symbol.
This code will be skipped when GadgetBridge is run on a device with
Android version older than 5.0 (lollipop).
2016-06-08 20:33:20 +02:00
try {
2017-05-28 18:50:41 +02:00
c = new MediaControllerCompat ( getApplicationContext ( ) , mediaSession ) ;
2016-06-07 19:34:37 +02:00
2017-05-28 18:50:41 +02:00
PlaybackStateCompat s = c . getPlaybackState ( ) ;
2017-02-05 15:13:26 +01:00
stateSpec . position = ( int ) ( s . getPosition ( ) / 1000 ) ;
stateSpec . playRate = Math . round ( 100 * s . getPlaybackSpeed ( ) ) ;
stateSpec . repeat = 1 ;
stateSpec . shuffle = 1 ;
switch ( s . getState ( ) ) {
case PlaybackState . STATE_PLAYING :
stateSpec . state = MusicStateSpec . STATE_PLAYING ;
break ;
case PlaybackState . STATE_STOPPED :
stateSpec . state = MusicStateSpec . STATE_STOPPED ;
break ;
case PlaybackState . STATE_PAUSED :
stateSpec . state = MusicStateSpec . STATE_PAUSED ;
break ;
default :
stateSpec . state = MusicStateSpec . STATE_UNKNOWN ;
break ;
}
2016-06-07 19:34:37 +02:00
2017-05-28 18:50:41 +02:00
MediaMetadataCompat d = c . getMetadata ( ) ;
2017-02-05 15:13:26 +01:00
if ( d = = null )
return false ;
if ( d . containsKey ( MediaMetadata . METADATA_KEY_ARTIST ) )
2017-05-28 18:50:41 +02:00
musicSpec . artist = d . getString ( MediaMetadataCompat . METADATA_KEY_ARTIST ) ;
2017-02-05 15:13:26 +01:00
if ( d . containsKey ( MediaMetadata . METADATA_KEY_ALBUM ) )
2017-05-28 18:50:41 +02:00
musicSpec . album = d . getString ( MediaMetadataCompat . METADATA_KEY_ALBUM ) ;
2017-02-05 15:13:26 +01:00
if ( d . containsKey ( MediaMetadata . METADATA_KEY_TITLE ) )
2017-05-28 18:50:41 +02:00
musicSpec . track = d . getString ( MediaMetadataCompat . METADATA_KEY_TITLE ) ;
2017-02-05 15:13:26 +01:00
if ( d . containsKey ( MediaMetadata . METADATA_KEY_DURATION ) )
2017-05-28 18:50:41 +02:00
musicSpec . duration = ( int ) d . getLong ( MediaMetadataCompat . METADATA_KEY_DURATION ) / 1000 ;
2017-02-05 15:13:26 +01:00
if ( d . containsKey ( MediaMetadata . METADATA_KEY_NUM_TRACKS ) )
2017-05-28 18:50:41 +02:00
musicSpec . trackCount = ( int ) d . getLong ( MediaMetadataCompat . METADATA_KEY_NUM_TRACKS ) ;
2017-02-05 15:13:26 +01:00
if ( d . containsKey ( MediaMetadata . METADATA_KEY_TRACK_NUMBER ) )
2017-05-28 18:50:41 +02:00
musicSpec . trackNr = ( int ) d . getLong ( MediaMetadataCompat . METADATA_KEY_TRACK_NUMBER ) ;
2017-02-05 15:13:26 +01:00
// finally, tell the device about it
GBApplication . deviceService ( ) . onSetMusicInfo ( musicSpec ) ;
GBApplication . deviceService ( ) . onSetMusicState ( stateSpec ) ;
return true ;
2017-05-28 18:50:41 +02:00
} catch ( NullPointerException | RemoteException e ) {
Add handling for media session notifications
Since Android 5.0, media players can have interactive notifications that
reside in the notification area, and offer up to 5 control buttons
(play/pause, next, previous, etc), and information about the currentlu
playing media file.
We use these notifications to get information about the currently
playing media file such as:
- artist
- track (title)
- album
- duration (length of the media file)
- play state (playing, paused, stopped)
- position
- play rate (how fast is the media file being played)
We then send this information up to the device.
On Pebble, the music app will display the title and the artist, as
well as a progress bar showing the current position. The progress bar is
animated when the media file is being played, and if it is being paused,
it displays a pause symbol.
This code will be skipped when GadgetBridge is run on a device with
Android version older than 5.0 (lollipop).
2016-06-08 20:33:20 +02:00
return false ;
2017-02-05 15:13:26 +01:00
}
2016-06-07 19:34:37 +02:00
}
2015-01-07 14:00:18 +01:00
@Override
public void onNotificationRemoved ( StatusBarNotification sbn ) {
2017-05-28 18:50:41 +02:00
if ( shouldIgnore ( sbn ) )
2017-04-02 00:03:41 +02:00
return ;
2017-05-28 18:50:41 +02:00
Prefs prefs = GBApplication . getPrefs ( ) ;
if ( prefs . getBoolean ( " autoremove_notifications " , false ) ) {
LOG . info ( " notification removed, will ask device to delete it " ) ;
2018-02-10 16:52:24 +01:00
GBApplication . deviceService ( ) . onDeleteNotification ( ( int ) sbn . getPostTime ( ) ) ;
2017-04-02 00:03:41 +02:00
}
2017-05-28 18:50:41 +02:00
}
2017-04-02 00:03:41 +02:00
2017-05-28 18:50:41 +02:00
private void dumpExtras ( Bundle bundle ) {
for ( String key : bundle . keySet ( ) ) {
Object value = bundle . get ( key ) ;
if ( value = = null ) {
continue ;
}
LOG . debug ( String . format ( " Notification extra: %s %s (%s) " , key , value . toString ( ) , value . getClass ( ) . getName ( ) ) ) ;
2017-04-28 18:03:19 +02:00
}
2017-05-28 18:50:41 +02:00
}
private boolean shouldIgnore ( StatusBarNotification sbn ) {
/ *
* return early if DeviceCommunicationService is not running ,
* else the service would get started every time we get a notification .
* unfortunately we cannot enable / disable NotificationListener at runtime like we do with
* broadcast receivers because it seems to invalidate the permissions that are
* necessary for NotificationListenerService
* /
if ( ! isServiceRunning ( ) | | sbn = = null ) {
return true ;
}
2017-09-19 13:24:31 +02:00
return shouldIgnoreSource ( sbn . getPackageName ( ) ) | | shouldIgnoreNotification (
2017-08-16 03:58:00 +02:00
sbn . getNotification ( ) , sbn . getPackageName ( ) ) ;
2017-05-28 18:50:41 +02:00
}
private boolean shouldIgnoreSource ( String source ) {
Prefs prefs = GBApplication . getPrefs ( ) ;
/ * do not display messages from " android "
* This includes keyboard selection message , usb connection messages , etc
* Hope it does not filter out too much , we will see . . .
* /
2017-04-28 18:03:19 +02:00
2017-01-09 16:49:11 +01:00
if ( source . equals ( " android " ) | |
source . equals ( " com.android.systemui " ) | |
source . equals ( " com.android.dialer " ) | |
source . equals ( " com.cyanogenmod.eleven " ) ) {
2017-05-28 18:50:41 +02:00
LOG . info ( " Ignoring notification, is a system event " ) ;
return true ;
2017-01-09 16:49:11 +01:00
}
2017-05-28 18:50:41 +02:00
if ( source . equals ( " com.moez.QKSMS " ) | |
source . equals ( " com.android.mms " ) | |
source . equals ( " com.sonyericsson.conversations " ) | |
source . equals ( " com.android.messaging " ) | |
source . equals ( " org.smssecure.smssecure " ) ) {
if ( ! " never " . equals ( prefs . getString ( " notification_mode_sms " , " when_screen_off " ) ) ) {
return true ;
}
}
2017-01-09 16:49:11 +01:00
2017-08-18 10:30:19 +02:00
if ( GBApplication . appIsBlacklisted ( source ) ) {
2017-05-28 18:50:41 +02:00
LOG . info ( " Ignoring notification, application is blacklisted " ) ;
return true ;
2017-01-15 00:10:40 +01:00
}
2017-05-28 18:50:41 +02:00
return false ;
2015-01-07 14:00:18 +01:00
}
2016-10-18 23:44:00 +02:00
2017-08-16 03:58:00 +02:00
private boolean shouldIgnoreNotification ( Notification notification , String source ) {
2017-05-28 18:50:41 +02:00
2018-01-20 21:27:37 +01:00
MediaSessionCompat . Token mediaSession = getMediaSession ( notification ) ;
2017-05-28 18:50:41 +02:00
//try to handle media session notifications
if ( mediaSession ! = null & & handleMediaSessionNotification ( mediaSession ) )
return true ;
2017-08-16 03:58:00 +02:00
NotificationType type = AppNotificationType . getInstance ( ) . get ( source ) ;
2017-05-28 18:50:41 +02:00
//ignore notifications marked as LocalOnly https://developer.android.com/reference/android/app/Notification.html#FLAG_LOCAL_ONLY
2017-11-14 23:27:01 +01:00
//some Apps always mark their notifcations as read-only
2017-08-16 03:58:00 +02:00
if ( NotificationCompat . getLocalOnly ( notification ) & &
2017-11-14 23:27:01 +01:00
type ! = NotificationType . WECHAT & &
type ! = NotificationType . OUTLOOK ) {
2017-05-28 18:50:41 +02:00
return true ;
2017-11-14 23:27:01 +01:00
}
2017-05-28 18:50:41 +02:00
Prefs prefs = GBApplication . getPrefs ( ) ;
if ( ! prefs . getBoolean ( " notifications_generic_whenscreenon " , false ) ) {
PowerManager powermanager = ( PowerManager ) getSystemService ( POWER_SERVICE ) ;
if ( powermanager . isScreenOn ( ) ) {
// LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this");
return true ;
2016-10-18 23:44:00 +02:00
}
}
2017-05-28 18:50:41 +02:00
2017-09-19 13:24:31 +02:00
return ( notification . flags & Notification . FLAG_ONGOING_EVENT ) = = Notification . FLAG_ONGOING_EVENT ;
2017-05-28 18:50:41 +02:00
2016-10-18 23:44:00 +02:00
}
2017-05-28 18:50:41 +02:00
2017-09-19 13:24:31 +02:00
/ * *
* Get the notification color that should be used for this Pebble notification .
*
* Note that this method will * not * edit the NotificationSpec passed in . It will only evaluate the PebbleColor .
*
* See Issue # 815 on GitHub to see how notification colors are set .
*
* @param notificationSpec The NotificationSpec to read from .
* @return Returns a PebbleColor that best represents this notification .
* /
private byte getPebbleColorForNotification ( NotificationSpec notificationSpec ) {
String appId = notificationSpec . sourceAppId ;
NotificationType existingType = notificationSpec . type ;
// If the notification type is known, return the associated color.
if ( existingType ! = NotificationType . UNKNOWN ) {
return existingType . color ;
}
// Otherwise, we go and attempt to find the color from the app icon.
Drawable icon ;
try {
icon = getApplicationContext ( ) . getPackageManager ( ) . getApplicationIcon ( appId ) ;
Objects . requireNonNull ( icon ) ;
} catch ( Exception ex ) {
// If we can't get the icon, we go with the default defined above.
LOG . warn ( " Could not get icon for AppID " + appId , ex ) ;
return PebbleColor . IslamicGreen ;
}
Bitmap bitmapIcon = BitmapUtil . convertDrawableToBitmap ( icon ) ;
int iconPrimaryColor = new Palette . Builder ( bitmapIcon )
. generate ( )
. getVibrantColor ( Color . parseColor ( " #aa0000 " ) ) ;
return PebbleUtils . getPebbleColor ( iconPrimaryColor ) ;
}
2015-09-13 00:39:53 +02:00
}