A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 220 lines 8.1 kB view raw
1package org.rockbox.Helper; 2 3import java.lang.reflect.Method; 4import org.rockbox.R; 5import org.rockbox.RockboxActivity; 6import android.app.Notification; 7import android.app.NotificationManager; 8import android.app.PendingIntent; 9import android.app.Service; 10import android.content.Intent; 11import android.content.res.Resources; 12import android.graphics.Bitmap; 13import android.graphics.BitmapFactory; 14import android.graphics.drawable.Drawable; 15import android.os.Handler; 16import android.widget.RemoteViews; 17 18public class RunForegroundManager 19{ 20 /* all below is heavily based on the examples found on 21 * http://developer.android.com/reference/android/app/Service.html#setForeground(boolean) 22 */ 23 private Notification mNotification; 24 private NotificationManager mNM; 25 private IRunForeground api; 26 private Service mCurrentService; 27 private Handler mServiceHandler; 28 private Intent mWidgetUpdate; 29 private int iconheight; 30 31 public RunForegroundManager(Service service) 32 { 33 mCurrentService = service; 34 mNM = (NotificationManager) 35 service.getSystemService(Service.NOTIFICATION_SERVICE); 36 RemoteViews views = new RemoteViews(service.getPackageName(), R.layout.statusbar); 37 /* create Intent for clicking on the expanded notifcation area */ 38 Intent intent = new Intent(service, RockboxActivity.class); 39 intent = intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 40 41 /* retrieve height of launcher icon. Used to scale down album art. */ 42 Resources resources = service.getResources(); 43 Drawable draw = resources.getDrawable(R.drawable.launcher); 44 iconheight = draw.getIntrinsicHeight(); 45 46 mNotification = new Notification(); 47 mNotification.tickerText = service.getString(R.string.notification); 48 mNotification.icon = R.drawable.notification; 49 mNotification.contentView = views; 50 mNotification.flags |= Notification.FLAG_ONGOING_EVENT; 51 mNotification.contentIntent = PendingIntent.getActivity(service, 0, intent, 0); 52 53 try { 54 api = new NewForegroundApi(R.string.notification, mNotification); 55 } catch (Throwable t) { 56 /* Throwable includes Exception and the expected 57 * NoClassDefFoundError for Android 1.x */ 58 try { 59 api = new OldForegroundApi(); 60 Logger.i("RunForegroundManager: Falling back to compatibility API"); 61 } catch (Exception e) { 62 Logger.e("Cannot run in foreground: No available API"); 63 } 64 } 65 mServiceHandler = new Handler(service.getMainLooper()); 66 } 67 68 public void startForeground() 69 { 70 /* 71 * Send the notification. 72 * We use a layout id because it is a unique number. 73 * We use it later to cancel. 74 */ 75 mNM.notify(R.string.notification, mNotification); 76 /* 77 * this call makes the service run as foreground, which 78 * provides enough cpu time to do music decoding in the 79 * background 80 */ 81 api.startForeground(); 82 } 83 84 public void stopForeground() 85 { 86 /* Note to cancel BEFORE changing the 87 * foreground state, since we could be killed at that point. 88 */ 89 mNM.cancel(R.string.notification); 90 api.stopForeground(); 91 mWidgetUpdate = null; 92 } 93 94 public void updateNotification(final String title, final String artist, final String album, final String albumart) 95 { 96 /* do this on the main thread for 2 reasons 97 * 1) Don't delay track switching with possibly costly albumart 98 * loading (i.e. off-load from the Rockbox thread) 99 * 2) Work around a bug in Android where decodeFile() fails outside 100 * of the main thread (http://stackoverflow.com/q/7228633) 101 */ 102 mServiceHandler.post(new Runnable() 103 { 104 @Override 105 public void run() 106 { 107 final RemoteViews views = mNotification.contentView; 108 views.setTextViewText(R.id.title, title); 109 views.setTextViewText(R.id.content, artist+"\n"+album); 110 if (artist.equals("")) 111 mNotification.tickerText = title; 112 else 113 mNotification.tickerText = title+" - "+artist; 114 115 if (albumart != null) { 116 /* The notification area doesn't have permissions to access the SD card. 117 * Push the data as Bitmap instead of Uri. Scale down to size of 118 * launcher icon -- broadcasting the unscaled image may yield in 119 * too much data, causing UI hangs of Rockbox. */ 120 Bitmap b = BitmapFactory.decodeFile(albumart); 121 if(b != null) { 122 /* scale width to keep aspect ratio -- height is the constraint */ 123 int scaledwidth = Math.round(iconheight*((float)b.getWidth()/b.getHeight())); 124 views.setImageViewBitmap(R.id.artwork, 125 Bitmap.createScaledBitmap(b, scaledwidth, iconheight, false)); 126 } 127 else { 128 views.setImageViewResource(R.id.artwork, R.drawable.launcher); 129 } 130 } 131 else { 132 views.setImageViewResource(R.id.artwork, R.drawable.launcher); 133 } 134 mWidgetUpdate = new Intent("org.rockbox.TrackUpdateInfo"); 135 mWidgetUpdate.putExtra("title", title); 136 mWidgetUpdate.putExtra("artist", artist); 137 mWidgetUpdate.putExtra("album", album); 138 mWidgetUpdate.putExtra("albumart", albumart); 139 mCurrentService.sendBroadcast(mWidgetUpdate); 140 141 /* notify in this runnable to make sure the notification 142 * has the correct albumart */ 143 mNM.notify(R.string.notification, mNotification); 144 } 145 }); 146 } 147 148 public void resendUpdateNotification() 149 { 150 if (mWidgetUpdate != null) 151 mCurrentService.sendBroadcast(mWidgetUpdate); 152 } 153 154 public void finishNotification() 155 { 156 Logger.d("TrackFinish"); 157 Intent widgetUpdate = new Intent("org.rockbox.TrackFinish"); 158 mCurrentService.sendBroadcast(widgetUpdate); 159 } 160 161 private interface IRunForeground 162 { 163 void startForeground(); 164 void stopForeground(); 165 } 166 167 private class NewForegroundApi implements IRunForeground 168 { 169 int id; 170 Notification mNotification; 171 NewForegroundApi(int _id, Notification _notification) 172 { 173 id = _id; 174 mNotification = _notification; 175 } 176 177 public void startForeground() 178 { 179 mCurrentService.startForeground(id, mNotification); 180 } 181 182 public void stopForeground() 183 { 184 mCurrentService.stopForeground(true); 185 } 186 } 187 188 private class OldForegroundApi implements IRunForeground 189 { 190 /* 191 * Get the new API through reflection because it's unavailable 192 * in honeycomb 193 */ 194 private Method mSetForeground; 195 196 public OldForegroundApi() throws SecurityException, NoSuchMethodException 197 { 198 mSetForeground = getClass().getMethod("setForeground", 199 new Class[] { boolean.class }); 200 } 201 202 public void startForeground() 203 { 204 try { 205 mSetForeground.invoke(mCurrentService, Boolean.TRUE); 206 } catch (Exception e) { 207 Logger.e("startForeground compat error: " + e.getMessage()); 208 e.printStackTrace(); 209 } 210 } 211 public void stopForeground() 212 { 213 try { 214 mSetForeground.invoke(mCurrentService, Boolean.FALSE); 215 } catch (Exception e) { 216 Logger.e("stopForeground compat error: " + e.getMessage()); 217 } 218 } 219 } 220}