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
.Activity
;
39 import android
.app
.Notification
;
40 import android
.app
.NotificationManager
;
41 import android
.app
.PendingIntent
;
42 import android
.app
.Service
;
43 import android
.content
.BroadcastReceiver
;
44 import android
.content
.Context
;
45 import android
.content
.Intent
;
46 import android
.content
.IntentFilter
;
47 import android
.os
.IBinder
;
48 import android
.util
.Log
;
50 /* This class is used as the main glue between java and c.
51 * All access should be done through RockboxService.get_instance() for safety.
54 public class RockboxService
extends Service
56 /* this Service is really a singleton class - well almost.
57 * To do it properly this line should be instance = new RockboxService()
58 * but apparently that doesnt work with the way android Services are created.
60 private static RockboxService instance
= null;
62 /* locals needed for the c code and rockbox state */
63 private RockboxFramebuffer fb
= null;
64 private boolean mRockboxRunning
= false;
65 private Activity current_activity
= null;
67 private Notification notification
;
68 private static final Class
<?
>[] mStartForegroundSignature
=
69 new Class
[] { int.class, Notification
.class };
70 private static final Class
<?
>[] mStopForegroundSignature
=
71 new Class
[] { boolean.class };
73 private NotificationManager mNM
;
74 private Method mStartForeground
;
75 private Method mStopForeground
;
76 private Object
[] mStartForegroundArgs
= new Object
[2];
77 private Object
[] mStopForegroundArgs
= new Object
[1];
78 private IntentFilter itf
;
79 private BroadcastReceiver batt_monitor
;
80 @SuppressWarnings("unused")
81 private int battery_level
;
84 public void onCreate()
87 mNM
= (NotificationManager
)getSystemService(NOTIFICATION_SERVICE
);
90 mStartForeground
= getClass().getMethod("startForeground",
91 mStartForegroundSignature
);
92 mStopForeground
= getClass().getMethod("stopForeground",
93 mStopForegroundSignature
);
95 catch (NoSuchMethodException e
)
97 /* Running on an older platform: fall back to old API */
98 mStartForeground
= mStopForeground
= null;
103 public static RockboxService
get_instance()
108 public RockboxFramebuffer
get_fb()
112 /* framebuffer is initialised by the native code(!) so this is needed */
113 public void set_fb(RockboxFramebuffer newfb
)
116 mRockboxRunning
= true;
119 public Activity
get_activity()
121 return current_activity
;
123 public void set_activity(Activity a
)
125 current_activity
= a
;
129 private void do_start(Intent intent
)
131 LOG("Start Service");
133 /* Display a notification about us starting.
134 * We put an icon in the status bar. */
135 create_notification();
138 private void LOG(CharSequence text
)
140 Log
.d("Rockbox", (String
) text
);
143 private void LOG(CharSequence text
, Throwable tr
)
145 Log
.d("Rockbox", (String
) text
, tr
);
148 public void onStart(Intent intent
, int startId
) {
152 public int onStartCommand(Intent intent
, int flags
, int startId
)
155 return 1; /* old API compatibility: 1 == START_STICKY */
158 private void startservice()
160 final int BUFFER
= 8*1024;
161 Thread rb
= new Thread(new Runnable()
165 /* the following block unzips libmisc.so, which contains the files
166 * we ship, such as themes. It's needed to put it into a .so file
167 * because there's no other way to ship files and have access
168 * to them from native code
172 BufferedOutputStream dest
= null;
173 BufferedInputStream is
= null;
175 File file
= new File("/data/data/org.rockbox/" +
177 /* use arbitrary file to determine whether extracting is needed */
178 File file2
= new File("/data/data/org.rockbox/" +
179 "app_rockbox/rockbox/codecs/mpa.codec");
180 if (!file2
.exists() || (file
.lastModified() > file2
.lastModified()))
182 ZipFile zipfile
= new ZipFile(file
);
183 Enumeration
<?
extends ZipEntry
> e
= zipfile
.entries();
185 while(e
.hasMoreElements())
187 entry
= (ZipEntry
) e
.nextElement();
188 LOG("Extracting: " +entry
);
189 if (entry
.isDirectory())
191 folder
= new File(entry
.getName());
192 LOG("mkdir "+ entry
);
195 } catch (SecurityException ex
) {
196 LOG(ex
.getMessage());
200 is
= new BufferedInputStream(zipfile
.getInputStream(entry
),
203 byte data
[] = new byte[BUFFER
];
204 folder
= new File(new File(entry
.getName()).getParent());
205 LOG("" + folder
.getAbsolutePath());
206 if (!folder
.exists())
208 FileOutputStream fos
= new FileOutputStream(entry
.getName());
209 dest
= new BufferedOutputStream(fos
, BUFFER
);
210 while ((count
= is
.read(data
, 0, BUFFER
)) != -1)
211 dest
.write(data
, 0, count
);
217 } catch(FileNotFoundException e
) {
218 LOG("FileNotFoundException when unzipping", e
);
220 } catch(IOException e
) {
221 LOG("IOException when unzipping", e
);
225 System
.loadLibrary("rockbox");
232 private native void main();
234 /* returns true once rockbox is up and running.
235 * This is considered done once the framebuffer is initialised
237 public boolean isRockboxRunning()
239 return mRockboxRunning
;
243 public IBinder
onBind(Intent intent
)
245 // TODO Auto-generated method stub
250 @SuppressWarnings("unused")
252 * Sets up the battery monitor which receives the battery level
253 * about each 30 seconds
255 private void initBatteryMonitor()
257 itf
= new IntentFilter(Intent
.ACTION_BATTERY_CHANGED
);
258 batt_monitor
= new BroadcastReceiver()
261 public void onReceive(Context context
, Intent intent
)
263 /* we get literally spammed with battery statuses
264 * if we don't delay the re-attaching
266 TimerTask tk
= new TimerTask()
270 registerReceiver(batt_monitor
, itf
);
273 Timer t
= new Timer();
274 context
.unregisterReceiver(this);
275 int rawlevel
= intent
.getIntExtra("level", -1);
276 int scale
= intent
.getIntExtra("scale", -1);
277 if (rawlevel
>= 0 && scale
> 0)
278 battery_level
= (rawlevel
* 100) / scale
;
281 /* query every 30s should be sufficient */
282 t
.schedule(tk
, 30000);
285 registerReceiver(batt_monitor
, itf
);
288 /* all below is heavily based on the examples found on
289 * http://developer.android.com/reference/android/app/Service.html
292 private void create_notification()
294 /* For now we'll use the same text for the ticker and the
295 * expanded notification */
296 CharSequence text
= getText(R
.string
.notification
);
297 /* Set the icon, scrolling text and timestamp */
298 notification
= new Notification(R
.drawable
.icon
, text
,
299 System
.currentTimeMillis());
301 /* The PendingIntent to launch our activity if the user selects
302 * this notification */
303 Intent intent
= new Intent(this, RockboxActivity
.class);
304 PendingIntent contentIntent
=
305 PendingIntent
.getActivity(this, 0, intent
, 0);
307 /* Set the info for the views that show in the notification panel. */
308 notification
.setLatestEventInfo(this,
309 getText(R
.string
.notification
), text
, contentIntent
);
312 public void startForeground()
315 * Send the notification.
316 * We use a layout id because it is a unique number.
317 * We use it later to cancel.
319 mNM
.notify(R
.string
.notification
, instance
.notification
);
321 * this call makes the service run as foreground, which
322 * provides enough cpu time to do music decoding in the
325 startForegroundCompat(R
.string
.notification
, notification
);
328 public void stopForeground()
330 if (notification
!= null)
332 stopForegroundCompat(R
.string
.notification
);
333 mNM
.cancel(R
.string
.notification
);
338 * This is a wrapper around the new startForeground method, using the older
339 * APIs if it is not available.
341 void startForegroundCompat(int id
, Notification notification
)
343 if (mStartForeground
!= null) {
344 mStartForegroundArgs
[0] = Integer
.valueOf(id
);
345 mStartForegroundArgs
[1] = notification
;
347 mStartForeground
.invoke(this, mStartForegroundArgs
);
348 } catch (InvocationTargetException e
) {
349 /* Should not happen. */
350 LOG("Unable to invoke startForeground", e
);
351 } catch (IllegalAccessException e
) {
352 /* Should not happen. */
353 LOG("Unable to invoke startForeground", e
);
358 /* Fall back on the old API.*/
360 mNM
.notify(id
, notification
);
364 * This is a wrapper around the new stopForeground method, using the older
365 * APIs if it is not available.
367 void stopForegroundCompat(int id
)
369 if (mStopForeground
!= null) {
370 mStopForegroundArgs
[0] = Boolean
.TRUE
;
372 mStopForeground
.invoke(this, mStopForegroundArgs
);
373 } catch (InvocationTargetException e
) {
374 /* Should not happen. */
375 LOG("Unable to invoke stopForeground", e
);
376 } catch (IllegalAccessException e
) {
377 /* Should not happen. */
378 LOG("Unable to invoke stopForeground", e
);
383 /* Fall back on the old API. Note to cancel BEFORE changing the
384 * foreground state, since we could be killed at that point. */
386 setForeground(false);
390 public void onDestroy()
393 /* Make sure our notification is gone. */
394 stopForegroundCompat(R
.string
.notification
);