1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2010 Thomas Martitz
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
24 import java
.io
.BufferedInputStream
;
25 import java
.io
.BufferedOutputStream
;
27 import java
.io
.FileNotFoundException
;
28 import java
.io
.FileOutputStream
;
29 import java
.io
.IOException
;
30 import java
.lang
.reflect
.InvocationTargetException
;
31 import java
.lang
.reflect
.Method
;
32 import java
.util
.Enumeration
;
33 import java
.util
.Timer
;
34 import java
.util
.TimerTask
;
35 import java
.util
.zip
.ZipEntry
;
36 import java
.util
.zip
.ZipFile
;
38 import android
.app
.Notification
;
39 import android
.app
.NotificationManager
;
40 import android
.app
.PendingIntent
;
41 import android
.app
.Service
;
42 import android
.content
.BroadcastReceiver
;
43 import android
.content
.Context
;
44 import android
.content
.Intent
;
45 import android
.content
.IntentFilter
;
46 import android
.os
.IBinder
;
47 import android
.util
.Log
;
49 public class RockboxService
extends Service
51 /* this Service is really a singleton class */
52 public static RockboxFramebuffer fb
= null;
53 private static RockboxService instance
;
54 private Notification notification
;
55 private static final Class
<?
>[] mStartForegroundSignature
=
56 new Class
[] { int.class, Notification
.class };
57 private static final Class
<?
>[] mStopForegroundSignature
=
58 new Class
[] { boolean.class };
60 private NotificationManager mNM
;
61 private Method mStartForeground
;
62 private Method mStopForeground
;
63 private Object
[] mStartForegroundArgs
= new Object
[2];
64 private Object
[] mStopForegroundArgs
= new Object
[1];
65 private IntentFilter itf
;
66 private BroadcastReceiver batt_monitor
;
67 @SuppressWarnings("unused")
68 private int battery_level
;
71 public void onCreate()
73 mNM
= (NotificationManager
)getSystemService(NOTIFICATION_SERVICE
);
76 mStartForeground
= getClass().getMethod("startForeground",
77 mStartForegroundSignature
);
78 mStopForeground
= getClass().getMethod("stopForeground",
79 mStopForegroundSignature
);
81 catch (NoSuchMethodException e
)
83 /* Running on an older platform: fall back to old API */
84 mStartForeground
= mStopForeground
= null;
90 private void do_start(Intent intent
)
94 /* Display a notification about us starting.
95 * We put an icon in the status bar. */
96 create_notification();
99 private void LOG(CharSequence text
)
101 Log
.d("Rockbox", (String
) text
);
104 private void LOG(CharSequence text
, Throwable tr
)
106 Log
.d("Rockbox", (String
) text
, tr
);
109 public void onStart(Intent intent
, int startId
) {
113 public int onStartCommand(Intent intent
, int flags
, int startId
)
116 return 1; /* old API compatibility: 1 == START_STICKY */
119 private void startservice()
121 final int BUFFER
= 8*1024;
122 Thread rb
= new Thread(new Runnable()
126 /* the following block unzips libmisc.so, which contains the files
127 * we ship, such as themes. It's needed to put it into a .so file
128 * because there's no other way to ship files and have access
129 * to them from native code
133 BufferedOutputStream dest
= null;
134 BufferedInputStream is
= null;
136 File file
= new File("/data/data/org.rockbox/" +
138 /* use arbitrary file to determine whether extracting is needed */
139 File file2
= new File("/data/data/org.rockbox/" +
140 "app_rockbox/rockbox/codecs/mpa.codec");
141 if (!file2
.exists() || (file
.lastModified() > file2
.lastModified()))
143 ZipFile zipfile
= new ZipFile(file
);
144 Enumeration
<?
extends ZipEntry
> e
= zipfile
.entries();
146 while(e
.hasMoreElements())
148 entry
= (ZipEntry
) e
.nextElement();
149 LOG("Extracting: " +entry
);
150 if (entry
.isDirectory())
152 folder
= new File(entry
.getName());
153 LOG("mkdir "+ entry
);
156 } catch (SecurityException ex
) {
157 LOG(ex
.getMessage());
161 is
= new BufferedInputStream(zipfile
.getInputStream(entry
),
164 byte data
[] = new byte[BUFFER
];
165 folder
= new File(new File(entry
.getName()).getParent());
166 LOG("" + folder
.getAbsolutePath());
167 if (!folder
.exists())
169 FileOutputStream fos
= new FileOutputStream(entry
.getName());
170 dest
= new BufferedOutputStream(fos
, BUFFER
);
171 while ((count
= is
.read(data
, 0, BUFFER
)) != -1)
172 dest
.write(data
, 0, count
);
178 } catch(FileNotFoundException e
) {
179 LOG("FileNotFoundException when unzipping", e
);
181 } catch(IOException e
) {
182 LOG("IOException when unzipping", e
);
186 System
.loadLibrary("rockbox");
194 private native void main();
196 public IBinder
onBind(Intent intent
)
198 // TODO Auto-generated method stub
203 @SuppressWarnings("unused")
205 * Sets up the battery monitor which receives the battery level
206 * about each 30 seconds
208 private void initBatteryMonitor()
210 itf
= new IntentFilter(Intent
.ACTION_BATTERY_CHANGED
);
211 batt_monitor
= new BroadcastReceiver()
214 public void onReceive(Context context
, Intent intent
)
216 /* we get literally spammed with battery statuses
217 * if we don't delay the re-attaching
219 TimerTask tk
= new TimerTask()
223 registerReceiver(batt_monitor
, itf
);
226 Timer t
= new Timer();
227 context
.unregisterReceiver(this);
228 int rawlevel
= intent
.getIntExtra("level", -1);
229 int scale
= intent
.getIntExtra("scale", -1);
230 if (rawlevel
>= 0 && scale
> 0)
231 battery_level
= (rawlevel
* 100) / scale
;
234 /* query every 30s should be sufficient */
235 t
.schedule(tk
, 30000);
238 registerReceiver(batt_monitor
, itf
);
241 /* all below is heavily based on the examples found on
242 * http://developer.android.com/reference/android/app/Service.html
245 private void create_notification()
247 /* For now we'll use the same text for the ticker and the
248 * expanded notification */
249 CharSequence text
= getText(R
.string
.notification
);
250 /* Set the icon, scrolling text and timestamp */
251 notification
= new Notification(R
.drawable
.icon
, text
,
252 System
.currentTimeMillis());
254 /* The PendingIntent to launch our activity if the user selects
255 * this notification */
256 Intent intent
= new Intent(this, RockboxActivity
.class);
257 PendingIntent contentIntent
=
258 PendingIntent
.getActivity(this, 0, intent
, 0);
260 /* Set the info for the views that show in the notification panel. */
261 notification
.setLatestEventInfo(this,
262 getText(R
.string
.notification
), text
, contentIntent
);
265 public static void startForeground()
267 if (instance
!= null)
270 * Send the notification.
271 * We use a layout id because it is a unique number.
272 * We use it later to cancel.
274 instance
.mNM
.notify(R
.string
.notification
, instance
.notification
);
276 * this call makes the service run as foreground, which
277 * provides enough cpu time to do music decoding in the
280 instance
.startForegroundCompat(R
.string
.notification
,
281 instance
.notification
);
285 public static void stopForeground()
287 if (instance
.notification
!= null)
289 instance
.stopForegroundCompat(R
.string
.notification
);
290 instance
.mNM
.cancel(R
.string
.notification
);
295 * This is a wrapper around the new startForeground method, using the older
296 * APIs if it is not available.
298 void startForegroundCompat(int id
, Notification notification
)
300 if (mStartForeground
!= null) {
301 mStartForegroundArgs
[0] = Integer
.valueOf(id
);
302 mStartForegroundArgs
[1] = notification
;
304 mStartForeground
.invoke(this, mStartForegroundArgs
);
305 } catch (InvocationTargetException e
) {
306 /* Should not happen. */
307 LOG("Unable to invoke startForeground", e
);
308 } catch (IllegalAccessException e
) {
309 /* Should not happen. */
310 LOG("Unable to invoke startForeground", e
);
315 /* Fall back on the old API.*/
317 mNM
.notify(id
, notification
);
321 * This is a wrapper around the new stopForeground method, using the older
322 * APIs if it is not available.
324 void stopForegroundCompat(int id
)
326 if (mStopForeground
!= null) {
327 mStopForegroundArgs
[0] = Boolean
.TRUE
;
329 mStopForeground
.invoke(this, mStopForegroundArgs
);
330 } catch (InvocationTargetException e
) {
331 /* Should not happen. */
332 LOG("Unable to invoke stopForeground", e
);
333 } catch (IllegalAccessException e
) {
334 /* Should not happen. */
335 LOG("Unable to invoke stopForeground", e
);
340 /* Fall back on the old API. Note to cancel BEFORE changing the
341 * foreground state, since we could be killed at that point. */
343 setForeground(false);
347 public void onDestroy()
350 /* Make sure our notification is gone. */
351 stopForegroundCompat(R
.string
.notification
);