android: Remove hack not needed anymore (since 58e097d).
[maemo-rb.git] / android / src / org / rockbox / RockboxService.java
blob42427263316520230f0e6cd2bc90702c6bf1b42a
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
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 ****************************************************************************/
22 package org.rockbox;
24 import java.io.BufferedInputStream;
25 import java.io.BufferedOutputStream;
26 import java.io.File;
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.MediaButtonReceiver;
33 import org.rockbox.Helper.RunForegroundManager;
34 import android.app.Activity;
35 import android.app.Service;
36 import android.content.Intent;
37 import android.os.Bundle;
38 import android.os.Environment;
39 import android.os.IBinder;
40 import android.os.ResultReceiver;
41 import android.util.Log;
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 * To do it properly this line should be instance = new RockboxService()
52 * but apparently that doesnt work with the way android Services are created.
54 private static RockboxService instance = null;
56 /* locals needed for the c code and rockbox state */
57 private static volatile boolean rockbox_running;
58 private Activity current_activity = null;
59 private RunForegroundManager fg_runner;
60 private MediaButtonReceiver mMediaButtonReceiver;
61 private ResultReceiver resultReceiver;
63 public static final int RESULT_INVOKING_MAIN = 0;
64 public static final int RESULT_LIB_LOAD_PROGRESS = 1;
65 public static final int RESULT_SERVICE_RUNNING = 3;
66 public static final int RESULT_ERROR_OCCURED = 4;
67 public static final int RESULT_LIB_LOADED = 5;
68 public static final int RESULT_ROCKBOX_EXIT = 6;
70 @Override
71 public void onCreate()
73 instance = this;
74 mMediaButtonReceiver = new MediaButtonReceiver(this);
75 fg_runner = new RunForegroundManager(this);
78 public static RockboxService get_instance()
80 /* don't call the construtor here, the instances are managed by
81 * android, so we can't just create a new one */
82 return instance;
85 public boolean isRockboxRunning()
87 return rockbox_running;
89 public Activity get_activity()
91 return current_activity;
93 public void set_activity(Activity a)
95 current_activity = a;
98 private void do_start(Intent intent)
100 LOG("Start RockboxService (Intent: " + intent.getAction() + ")");
102 if (intent.getAction().equals("org.rockbox.ResendTrackUpdateInfo"))
104 if (rockbox_running)
105 fg_runner.resendUpdateNotification();
106 return;
109 if (intent.hasExtra("callback"))
110 resultReceiver = (ResultReceiver) intent.getParcelableExtra("callback");
112 if (!rockbox_running)
113 startservice();
114 if (resultReceiver != null)
115 resultReceiver.send(RESULT_LIB_LOADED, null);
117 if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON))
119 KeyEvent kev = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
120 RockboxFramebuffer.buttonHandler(kev.getKeyCode(),
121 kev.getAction() == KeyEvent.ACTION_DOWN);
124 /* (Re-)attach the media button receiver, in case it has been lost */
125 mMediaButtonReceiver.register();
126 if (resultReceiver != null)
127 resultReceiver.send(RESULT_SERVICE_RUNNING, null);
129 rockbox_running = true;
132 private void LOG(CharSequence text)
134 Log.d("Rockbox", (String) text);
137 private void LOG(CharSequence text, Throwable tr)
139 Log.d("Rockbox", (String) text, tr);
142 public void onStart(Intent intent, int startId) {
143 do_start(intent);
146 public int onStartCommand(Intent intent, int flags, int startId)
148 /* if null, then the service was most likely restarted by android
149 * after getting killed for memory pressure earlier */
150 if (intent == null)
151 intent = new Intent("org.rockbox.ServiceRestarted");
152 do_start(intent);
153 return START_STICKY;
156 private void startservice()
158 final Object lock = new Object();
159 Thread rb = new Thread(new Runnable()
161 public void run()
163 final int BUFFER = 8*1024;
164 String rockboxDirPath = "/data/data/org.rockbox/app_rockbox/rockbox";
165 String rockboxCreditsPath = "/data/data/org.rockbox/app_rockbox/rockbox/rocks/viewers";
166 String rockboxSdDirPath = "/sdcard/rockbox";
167 File rockboxDir = new File(rockboxDirPath);
168 File rockboxSdDir = new File(rockboxSdDirPath);
169 File rockboxCreditsDir = new File(rockboxCreditsPath);
171 /* load library before unzipping which may take a while */
172 synchronized (lock) {
173 System.loadLibrary("rockbox");
174 lock.notify();
177 /* the following block unzips libmisc.so, which contains the files
178 * we ship, such as themes. It's needed to put it into a .so file
179 * because there's no other way to ship files and have access
180 * to them from native code
182 File libMisc = new File("/data/data/org.rockbox/lib/libmisc.so");
183 /* use arbitrary file to determine whether extracting is needed */
184 File arbitraryFile = new File(rockboxCreditsPath, "credits.rock");
185 File rockboxInfoFile = new File(rockboxSdDirPath, "rockbox-info.txt");
186 boolean extractToSd = false;
187 if(rockboxInfoFile.exists()) {
188 extractToSd = true;
189 LOG("extracting resources to SD card");
191 else {
192 LOG("extracting resources to internal memory");
194 if (!arbitraryFile.exists() || (libMisc.lastModified() > arbitraryFile.lastModified()))
198 Bundle progressData = new Bundle();
199 byte data[] = new byte[BUFFER];
200 ZipFile zipfile = new ZipFile(libMisc);
201 Enumeration<? extends ZipEntry> e = zipfile.entries();
202 progressData.putInt("max", zipfile.size());
204 while(e.hasMoreElements())
206 ZipEntry entry = (ZipEntry) e.nextElement();
207 File file;
208 /* strip off /.rockbox when extracting */
209 String fileName = entry.getName();
210 int slashIndex = fileName.indexOf('/', 1);
211 /* codecs are now stored as libs, only keep rocks on internal */
212 if(extractToSd == false
213 || fileName.substring(slashIndex).startsWith("/rocks"))
215 file = new File(rockboxDirPath + fileName.substring(slashIndex));
217 else
219 file = new File(rockboxSdDirPath + fileName.substring(slashIndex));
222 if (!entry.isDirectory())
224 /* Create the parent folders if necessary */
225 File folder = new File(file.getParent());
226 if (!folder.exists())
227 folder.mkdirs();
229 /* Extract file */
230 BufferedInputStream is = new BufferedInputStream(zipfile.getInputStream(entry), BUFFER);
231 FileOutputStream fos = new FileOutputStream(file);
232 BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);
234 int count;
235 while ((count = is.read(data, 0, BUFFER)) != -1)
236 dest.write(data, 0, count);
238 dest.flush();
239 dest.close();
240 is.close();
243 if (resultReceiver != null) {
244 progressData.putInt("value", progressData.getInt("value", 0) + 1);
245 resultReceiver.send(RESULT_LIB_LOAD_PROGRESS, progressData);
248 arbitraryFile.setLastModified(libMisc.lastModified());
249 } catch(Exception e) {
250 LOG("Exception when unzipping", e);
251 e.printStackTrace();
252 if (resultReceiver != null) {
253 Bundle bundle = new Bundle();
254 bundle.putString("error", getString(R.string.error_extraction));
255 resultReceiver.send(RESULT_ERROR_OCCURED, bundle);
260 /* Generate default config if none exists yet */
261 File rockboxConfig = new File(Environment.getExternalStorageDirectory(), "rockbox/config.cfg");
262 if (!rockboxConfig.exists()) {
263 File rbDir = new File(rockboxConfig.getParent());
264 if (!rbDir.exists())
265 rbDir.mkdirs();
267 OutputStreamWriter strm;
268 try {
269 strm = new OutputStreamWriter(new FileOutputStream(rockboxConfig));
270 strm.write("# config generated by RockboxService\n");
271 strm.write("start directory: " + Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "\n");
272 strm.write("lang: /.rockbox/langs/" + getString(R.string.rockbox_language_file) + "\n");
273 strm.close();
274 } catch(Exception e) {
275 LOG("Exception when writing default config", e);
279 /* Start native code */
280 if (resultReceiver != null)
281 resultReceiver.send(RESULT_INVOKING_MAIN, null);
283 main();
285 if (resultReceiver != null)
286 resultReceiver.send(RESULT_ROCKBOX_EXIT, null);
288 LOG("Stop service: main() returned");
289 stopSelf(); /* serivce is of no use anymore */
291 }, "Rockbox thread");
292 rb.setDaemon(false);
293 /* wait at least until the library is loaded */
294 synchronized (lock)
296 rb.start();
297 while(true)
299 try {
300 lock.wait();
301 } catch (InterruptedException e) {
302 continue;
304 break;
309 private native void main();
311 @Override
312 public IBinder onBind(Intent intent)
314 // TODO Auto-generated method stub
315 return null;
318 void startForeground()
320 fg_runner.startForeground();
323 void stopForeground()
325 fg_runner.stopForeground();
328 @Override
329 public void onDestroy()
331 super.onDestroy();
332 /* Don't unregister so we can receive them (and startup the service)
333 * after idle poweroff. Hopefully it's ok if mMediaButtonReceiver is
334 * garbage collected.
335 * mMediaButtonReceiver.unregister(); */
336 mMediaButtonReceiver = null;
337 /* Make sure our notification is gone. */
338 stopForeground();
339 instance = null;
340 rockbox_running = false;
341 System.runFinalization();
342 /* exit() seems unclean but is needed in order to get the .so file garbage
343 * collected, otherwise Android caches this Service and librockbox.so
344 * The library must be reloaded to zero the bss and reset data
345 * segment */
346 System.exit(0);