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
.OutputStreamWriter
;
30 import java
.util
.Enumeration
;
31 import java
.util
.Timer
;
32 import java
.util
.TimerTask
;
33 import java
.util
.zip
.ZipEntry
;
34 import java
.util
.zip
.ZipFile
;
36 import org
.rockbox
.Helper
.MediaButtonReceiver
;
37 import org
.rockbox
.Helper
.RunForegroundManager
;
39 import android
.app
.Activity
;
40 import android
.app
.Service
;
41 import android
.content
.BroadcastReceiver
;
42 import android
.content
.Context
;
43 import android
.content
.Intent
;
44 import android
.content
.IntentFilter
;
45 import android
.os
.Bundle
;
46 import android
.os
.Environment
;
47 import android
.os
.IBinder
;
48 import android
.os
.ResultReceiver
;
49 import android
.util
.Log
;
50 import android
.view
.KeyEvent
;
52 /* This class is used as the main glue between java and c.
53 * All access should be done through RockboxService.get_instance() for safety.
56 public class RockboxService
extends Service
58 /* this Service is really a singleton class - well almost.
59 * To do it properly this line should be instance = new RockboxService()
60 * but apparently that doesnt work with the way android Services are created.
62 private static RockboxService instance
= null;
64 /* locals needed for the c code and rockbox state */
65 private static volatile boolean rockbox_running
;
66 private Activity current_activity
= null;
67 private IntentFilter itf
;
68 private BroadcastReceiver batt_monitor
;
69 private RunForegroundManager fg_runner
;
70 private MediaButtonReceiver mMediaButtonReceiver
;
71 @SuppressWarnings("unused")
72 private int battery_level
;
73 private ResultReceiver resultReceiver
;
75 public static final int RESULT_INVOKING_MAIN
= 0;
76 public static final int RESULT_LIB_LOAD_PROGRESS
= 1;
77 public static final int RESULT_SERVICE_RUNNING
= 3;
78 public static final int RESULT_ERROR_OCCURED
= 4;
79 public static final int RESULT_LIB_LOADED
= 5;
82 public void onCreate()
85 mMediaButtonReceiver
= new MediaButtonReceiver(this);
86 fg_runner
= new RunForegroundManager(this);
89 public static RockboxService
get_instance()
91 /* don't call the construtor here, the instances are managed by
92 * android, so we can't just create a new one */
96 public boolean isRockboxRunning()
98 return rockbox_running
;
100 public Activity
get_activity()
102 return current_activity
;
104 public void set_activity(Activity a
)
106 current_activity
= a
;
109 private void do_start(Intent intent
)
111 LOG("Start RockboxService (Intent: " + intent
.getAction() + ")");
113 if (intent
.getAction().equals("org.rockbox.ResendTrackUpdateInfo"))
116 fg_runner
.resendUpdateNotification();
120 if (intent
.hasExtra("callback"))
121 resultReceiver
= (ResultReceiver
) intent
.getParcelableExtra("callback");
123 if (!rockbox_running
)
125 if (resultReceiver
!= null)
126 resultReceiver
.send(RESULT_LIB_LOADED
, null);
128 if (intent
.getAction().equals(Intent
.ACTION_MEDIA_BUTTON
))
130 /* give it a bit of time so we can register button presses
131 * sleeping longer doesn't work here, apparently Android
132 * surpresses long sleeps during intent handling */
135 } catch (InterruptedException e
) { }
137 KeyEvent kev
= intent
.getParcelableExtra(Intent
.EXTRA_KEY_EVENT
);
138 RockboxFramebuffer
.buttonHandler(kev
.getKeyCode(),
139 kev
.getAction() == KeyEvent
.ACTION_DOWN
);
142 /* (Re-)attach the media button receiver, in case it has been lost */
143 mMediaButtonReceiver
.register();
144 if (resultReceiver
!= null)
145 resultReceiver
.send(RESULT_SERVICE_RUNNING
, null);
147 rockbox_running
= true;
150 private void LOG(CharSequence text
)
152 Log
.d("Rockbox", (String
) text
);
155 private void LOG(CharSequence text
, Throwable tr
)
157 Log
.d("Rockbox", (String
) text
, tr
);
160 public void onStart(Intent intent
, int startId
) {
164 public int onStartCommand(Intent intent
, int flags
, int startId
)
167 return 1; /* old API compatibility: 1 == START_STICKY */
170 private void startservice()
172 final Object lock
= new Object();
173 Thread rb
= new Thread(new Runnable()
177 final int BUFFER
= 8*1024;
178 String rockboxDirPath
= "/data/data/org.rockbox/app_rockbox/rockbox";
179 File rockboxDir
= new File(rockboxDirPath
);
181 /* load library before unzipping which may take a while */
182 synchronized (lock
) {
183 System
.loadLibrary("rockbox");
187 /* the following block unzips libmisc.so, which contains the files
188 * we ship, such as themes. It's needed to put it into a .so file
189 * because there's no other way to ship files and have access
190 * to them from native code
192 File libMisc
= new File("/data/data/org.rockbox/lib/libmisc.so");
193 /* use arbitrary file to determine whether extracting is needed */
194 File arbitraryFile
= new File(rockboxDir
, "viewers.config");
195 if (!arbitraryFile
.exists() || (libMisc
.lastModified() > arbitraryFile
.lastModified()))
199 Bundle progressData
= new Bundle();
200 byte data
[] = new byte[BUFFER
];
201 ZipFile zipfile
= new ZipFile(libMisc
);
202 Enumeration
<?
extends ZipEntry
> e
= zipfile
.entries();
203 progressData
.putInt("max", zipfile
.size());
205 while(e
.hasMoreElements())
207 ZipEntry entry
= (ZipEntry
) e
.nextElement();
209 /* strip off /.rockbox when extracting */
210 String fileName
= entry
.getName();
211 int slashIndex
= fileName
.indexOf('/', 1);
212 file
= new File(rockboxDirPath
+ fileName
.substring(slashIndex
));
214 if (!entry
.isDirectory())
216 /* Create the parent folders if necessary */
217 File folder
= new File(file
.getParent());
218 if (!folder
.exists())
222 BufferedInputStream is
= new BufferedInputStream(zipfile
.getInputStream(entry
), BUFFER
);
223 FileOutputStream fos
= new FileOutputStream(file
);
224 BufferedOutputStream dest
= new BufferedOutputStream(fos
, BUFFER
);
227 while ((count
= is
.read(data
, 0, BUFFER
)) != -1)
228 dest
.write(data
, 0, count
);
235 if (resultReceiver
!= null) {
236 progressData
.putInt("value", progressData
.getInt("value", 0) + 1);
237 resultReceiver
.send(RESULT_LIB_LOAD_PROGRESS
, progressData
);
240 } catch(Exception e
) {
241 LOG("Exception when unzipping", e
);
243 if (resultReceiver
!= null) {
244 Bundle bundle
= new Bundle();
245 bundle
.putString("error", getString(R
.string
.error_extraction
));
246 resultReceiver
.send(RESULT_ERROR_OCCURED
, bundle
);
251 /* Generate default config if none exists yet */
252 File rockboxConfig
= new File(Environment
.getExternalStorageDirectory(), "rockbox/config.cfg");
253 if (!rockboxConfig
.exists()) {
254 File rbDir
= new File(rockboxConfig
.getParent());
258 OutputStreamWriter strm
;
260 strm
= new OutputStreamWriter(new FileOutputStream(rockboxConfig
));
261 strm
.write("# config generated by RockboxService\n");
262 strm
.write("start directory: " + Environment
.getExternalStorageDirectory().getAbsolutePath() + "/" + "\n");
263 strm
.write("lang: /.rockbox/langs/" + getString(R
.string
.rockbox_language_file
) + "\n");
265 } catch(Exception e
) {
266 LOG("Exception when writing default config", e
);
270 /* Start native code */
271 if (resultReceiver
!= null)
272 resultReceiver
.send(RESULT_INVOKING_MAIN
, null);
275 throw new IllegalStateException("native main() returned!");
277 }, "Rockbox thread");
279 /* wait at least until the library is loaded */
287 } catch (InterruptedException e
) {
295 private native void main();
298 public IBinder
onBind(Intent intent
)
300 // TODO Auto-generated method stub
305 @SuppressWarnings("unused")
307 * Sets up the battery monitor which receives the battery level
308 * about each 30 seconds
310 private void initBatteryMonitor()
312 itf
= new IntentFilter(Intent
.ACTION_BATTERY_CHANGED
);
313 batt_monitor
= new BroadcastReceiver()
316 public void onReceive(Context context
, Intent intent
)
318 /* we get literally spammed with battery statuses
319 * if we don't delay the re-attaching
321 TimerTask tk
= new TimerTask()
325 registerReceiver(batt_monitor
, itf
);
328 Timer t
= new Timer();
329 context
.unregisterReceiver(this);
330 int rawlevel
= intent
.getIntExtra("level", -1);
331 int scale
= intent
.getIntExtra("scale", -1);
332 if (rawlevel
>= 0 && scale
> 0)
333 battery_level
= (rawlevel
* 100) / scale
;
336 /* query every 30s should be sufficient */
337 t
.schedule(tk
, 30000);
340 registerReceiver(batt_monitor
, itf
);
343 void startForeground()
345 fg_runner
.startForeground();
348 void stopForeground()
350 fg_runner
.stopForeground();
354 public void onDestroy()
357 mMediaButtonReceiver
.unregister();
358 mMediaButtonReceiver
= null;
359 /* Make sure our notification is gone. */
362 rockbox_running
= false;