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
.FileOutputStream
;
28 import java
.io
.OutputStreamWriter
;
29 import java
.util
.Enumeration
;
30 import java
.util
.zip
.ZipEntry
;
31 import java
.util
.zip
.ZipFile
;
32 import org
.rockbox
.Helper
.Logger
;
33 import org
.rockbox
.Helper
.MediaButtonReceiver
;
34 import org
.rockbox
.Helper
.RunForegroundManager
;
35 import android
.app
.Activity
;
36 import android
.app
.Service
;
37 import android
.content
.Intent
;
38 import android
.os
.Bundle
;
39 import android
.os
.Environment
;
40 import android
.os
.IBinder
;
41 import android
.os
.ResultReceiver
;
42 import android
.view
.KeyEvent
;
44 /* This class is used as the main glue between java and c.
45 * All access should be done through RockboxService.get_instance() for safety.
48 public class RockboxService
extends Service
50 /* this Service is really a singleton class - well almost. */
51 private static RockboxService instance
= null;
53 /* locals needed for the c code and Rockbox state */
54 private static volatile boolean rockbox_running
;
55 private Activity mCurrentActivity
= null;
56 private RunForegroundManager mFgRunner
;
57 private MediaButtonReceiver mMediaButtonReceiver
;
58 private ResultReceiver mResultReceiver
;
60 /* possible result values for intent handling */
61 public static final int RESULT_INVOKING_MAIN
= 0;
62 public static final int RESULT_LIB_LOAD_PROGRESS
= 1;
63 public static final int RESULT_SERVICE_RUNNING
= 3;
64 public static final int RESULT_ERROR_OCCURED
= 4;
65 public static final int RESULT_LIB_LOADED
= 5;
66 public static final int RESULT_ROCKBOX_EXIT
= 6;
69 public void onCreate()
72 mMediaButtonReceiver
= new MediaButtonReceiver(this);
73 mFgRunner
= new RunForegroundManager(this);
76 public static RockboxService
getInstance()
78 /* don't call the constructor here, the instances are managed by
79 * android, so we can't just create a new one */
83 public boolean isRockboxRunning()
85 return rockbox_running
;
87 public Activity
getActivity()
89 return mCurrentActivity
;
92 public void setActivity(Activity a
)
97 private void putResult(int resultCode
)
99 putResult(resultCode
, null);
102 private void putResult(int resultCode
, Bundle resultData
)
104 if (mResultReceiver
!= null)
105 mResultReceiver
.send(resultCode
, resultData
);
108 private void doStart(Intent intent
)
110 Logger
.d("Start RockboxService (Intent: " + intent
.getAction() + ")");
112 if (intent
.getAction().equals("org.rockbox.ResendTrackUpdateInfo"))
115 mFgRunner
.resendUpdateNotification();
119 if (intent
.hasExtra("callback"))
120 mResultReceiver
= (ResultReceiver
) intent
.getParcelableExtra("callback");
122 if (!rockbox_running
)
124 putResult(RESULT_LIB_LOADED
);
126 if (intent
.getAction().equals(Intent
.ACTION_MEDIA_BUTTON
))
128 KeyEvent kev
= intent
.getParcelableExtra(Intent
.EXTRA_KEY_EVENT
);
129 RockboxFramebuffer
.buttonHandler(kev
.getKeyCode(),
130 kev
.getAction() == KeyEvent
.ACTION_DOWN
);
133 /* (Re-)attach the media button receiver, in case it has been lost */
134 mMediaButtonReceiver
.register();
135 putResult(RESULT_SERVICE_RUNNING
);
137 rockbox_running
= true;
140 public void onStart(Intent intent
, int startId
) {
144 public int onStartCommand(Intent intent
, int flags
, int startId
)
146 /* if null, then the service was most likely restarted by android
147 * after getting killed for memory pressure earlier */
149 intent
= new Intent("org.rockbox.ServiceRestarted");
154 private void startService()
156 final Object lock
= new Object();
157 Thread rb
= new Thread(new Runnable()
161 final int BUFFER
= 8*1024;
162 String rockboxDirPath
= "/data/data/org.rockbox/app_rockbox/rockbox";
163 String rockboxCreditsPath
= "/data/data/org.rockbox/app_rockbox/rockbox/rocks/viewers";
164 String rockboxSdDirPath
= "/sdcard/rockbox";
166 /* load library before unzipping which may take a while */
167 synchronized (lock
) {
168 System
.loadLibrary("rockbox");
172 /* the following block unzips libmisc.so, which contains the files
173 * we ship, such as themes. It's needed to put it into a .so file
174 * because there's no other way to ship files and have access
175 * to them from native code
177 File libMisc
= new File("/data/data/org.rockbox/lib/libmisc.so");
178 /* use arbitrary file to determine whether extracting is needed */
179 File arbitraryFile
= new File(rockboxCreditsPath
, "credits.rock");
180 File rockboxInfoFile
= new File(rockboxSdDirPath
, "rockbox-info.txt");
181 boolean extractToSd
= false;
182 if(rockboxInfoFile
.exists()) {
184 Logger
.d("extracting resources to SD card");
187 Logger
.d("extracting resources to internal memory");
189 if (!arbitraryFile
.exists() || (libMisc
.lastModified() > arbitraryFile
.lastModified()))
193 Bundle progressData
= new Bundle();
194 byte data
[] = new byte[BUFFER
];
195 ZipFile zipfile
= new ZipFile(libMisc
);
196 Enumeration
<?
extends ZipEntry
> e
= zipfile
.entries();
197 progressData
.putInt("max", zipfile
.size());
199 while(e
.hasMoreElements())
201 ZipEntry entry
= (ZipEntry
) e
.nextElement();
203 /* strip off /.rockbox when extracting */
204 String fileName
= entry
.getName();
205 int slashIndex
= fileName
.indexOf('/', 1);
206 /* codecs are now stored as libs, only keep rocks on internal */
207 if(extractToSd
== false
208 || fileName
.substring(slashIndex
).startsWith("/rocks"))
210 file
= new File(rockboxDirPath
+ fileName
.substring(slashIndex
));
214 file
= new File(rockboxSdDirPath
+ fileName
.substring(slashIndex
));
217 if (!entry
.isDirectory())
219 /* Create the parent folders if necessary */
220 File folder
= new File(file
.getParent());
221 if (!folder
.exists())
225 BufferedInputStream is
= new BufferedInputStream(zipfile
.getInputStream(entry
), BUFFER
);
226 FileOutputStream fos
= new FileOutputStream(file
);
227 BufferedOutputStream dest
= new BufferedOutputStream(fos
, BUFFER
);
230 while ((count
= is
.read(data
, 0, BUFFER
)) != -1)
231 dest
.write(data
, 0, count
);
238 progressData
.putInt("value", progressData
.getInt("value", 0) + 1);
239 putResult(RESULT_LIB_LOAD_PROGRESS
, progressData
);
241 arbitraryFile
.setLastModified(libMisc
.lastModified());
242 } catch(Exception e
) {
243 Logger
.d("Exception when unzipping", e
);
244 Bundle bundle
= new Bundle();
246 bundle
.putString("error", getString(R
.string
.error_extraction
));
247 putResult(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 Logger
.d("Exception when writing default config", e
);
270 /* Start native code */
271 putResult(RESULT_INVOKING_MAIN
);
275 putResult(RESULT_ROCKBOX_EXIT
);
277 Logger
.d("Stop service: main() returned");
278 stopSelf(); /* service is of no use anymore */
280 }, "Rockbox thread");
282 /* wait at least until the library is loaded */
290 } catch (InterruptedException e
) {
298 private native void main();
301 public IBinder
onBind(Intent intent
)
306 void startForeground()
308 mFgRunner
.startForeground();
311 void stopForeground()
313 mFgRunner
.stopForeground();
317 public void onDestroy()
320 /* Don't unregister so we can receive them (and startup the service)
321 * after idle power-off. Hopefully it's OK if mMediaButtonReceiver is
323 * mMediaButtonReceiver.unregister(); */
324 mMediaButtonReceiver
= null;
325 /* Make sure our notification is gone. */
328 rockbox_running
= false;
329 System
.runFinalization();
330 /* exit() seems unclean but is needed in order to get the .so file garbage
331 * collected, otherwise Android caches this Service and librockbox.so
332 * The library must be reloaded to zero the bss and reset data