A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
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}