android: clean-up and some refactoring in java code.
[maemo-rb.git] / android / src / org / rockbox / RockboxService.java
blob5c8f8cfd991bea06961cbe9c9f37fd1d00315236
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.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;
68 @Override
69 public void onCreate()
71 instance = this;
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 */
80 return instance;
83 public boolean isRockboxRunning()
85 return rockbox_running;
87 public Activity getActivity()
89 return mCurrentActivity;
92 public void setActivity(Activity a)
94 mCurrentActivity = 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"))
114 if (rockbox_running)
115 mFgRunner.resendUpdateNotification();
116 return;
119 if (intent.hasExtra("callback"))
120 mResultReceiver = (ResultReceiver) intent.getParcelableExtra("callback");
122 if (!rockbox_running)
123 startService();
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) {
141 doStart(intent);
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 */
148 if (intent == null)
149 intent = new Intent("org.rockbox.ServiceRestarted");
150 doStart(intent);
151 return START_STICKY;
154 private void startService()
156 final Object lock = new Object();
157 Thread rb = new Thread(new Runnable()
159 public void run()
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");
169 lock.notify();
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()) {
183 extractToSd = true;
184 Logger.d("extracting resources to SD card");
186 else {
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();
202 File file;
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));
212 else
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())
222 folder.mkdirs();
224 /* Extract file */
225 BufferedInputStream is = new BufferedInputStream(zipfile.getInputStream(entry), BUFFER);
226 FileOutputStream fos = new FileOutputStream(file);
227 BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);
229 int count;
230 while ((count = is.read(data, 0, BUFFER)) != -1)
231 dest.write(data, 0, count);
233 dest.flush();
234 dest.close();
235 is.close();
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();
245 e.printStackTrace();
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());
255 if (!rbDir.exists())
256 rbDir.mkdirs();
258 OutputStreamWriter strm;
259 try {
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");
264 strm.close();
265 } catch(Exception e) {
266 Logger.d("Exception when writing default config", e);
270 /* Start native code */
271 putResult(RESULT_INVOKING_MAIN);
273 main();
275 putResult(RESULT_ROCKBOX_EXIT);
277 Logger.d("Stop service: main() returned");
278 stopSelf(); /* service is of no use anymore */
280 }, "Rockbox thread");
281 rb.setDaemon(false);
282 /* wait at least until the library is loaded */
283 synchronized (lock)
285 rb.start();
286 while(true)
288 try {
289 lock.wait();
290 } catch (InterruptedException e) {
291 continue;
293 break;
298 private native void main();
300 @Override
301 public IBinder onBind(Intent intent)
303 return null;
306 void startForeground()
308 mFgRunner.startForeground();
311 void stopForeground()
313 mFgRunner.stopForeground();
316 @Override
317 public void onDestroy()
319 super.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
322 * garbage collected.
323 * mMediaButtonReceiver.unregister(); */
324 mMediaButtonReceiver = null;
325 /* Make sure our notification is gone. */
326 stopForeground();
327 instance = null;
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
333 * segment */
334 System.exit(0);