Android: Simplify media button intent generation in the widget and cleanup RockboxSer...
[maemo-rb.git] / android / src / org / rockbox / RockboxService.java
blob5465152aa8b05b068843883e42cce40921a039df
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.util.Enumeration;
29 import java.util.Timer;
30 import java.util.TimerTask;
31 import java.util.zip.ZipEntry;
32 import java.util.zip.ZipFile;
34 import org.rockbox.Helper.MediaButtonReceiver;
35 import org.rockbox.Helper.RunForegroundManager;
37 import android.app.Activity;
38 import android.app.Service;
39 import android.content.BroadcastReceiver;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.os.Bundle;
44 import android.os.IBinder;
45 import android.os.ResultReceiver;
46 import android.util.Log;
47 import android.view.KeyEvent;
49 /* This class is used as the main glue between java and c.
50 * All access should be done through RockboxService.get_instance() for safety.
53 public class RockboxService extends Service
55 /* this Service is really a singleton class - well almost.
56 * To do it properly this line should be instance = new RockboxService()
57 * but apparently that doesnt work with the way android Services are created.
59 private static RockboxService instance = null;
61 /* locals needed for the c code and rockbox state */
62 private static volatile boolean rockbox_running;
63 private Activity current_activity = null;
64 private IntentFilter itf;
65 private BroadcastReceiver batt_monitor;
66 private RunForegroundManager fg_runner;
67 private MediaButtonReceiver mMediaButtonReceiver;
68 @SuppressWarnings("unused")
69 private int battery_level;
70 private ResultReceiver resultReceiver;
72 public static final int RESULT_INVOKING_MAIN = 0;
73 public static final int RESULT_LIB_LOAD_PROGRESS = 1;
74 public static final int RESULT_SERVICE_RUNNING = 3;
75 public static final int RESULT_ERROR_OCCURED = 4;
76 public static final int RESULT_LIB_LOADED = 5;
78 @Override
79 public void onCreate()
81 instance = this;
82 mMediaButtonReceiver = new MediaButtonReceiver(this);
83 fg_runner = new RunForegroundManager(this);
86 public static RockboxService get_instance()
88 /* don't call the construtor here, the instances are managed by
89 * android, so we can't just create a new one */
90 return instance;
93 public boolean isRockboxRunning()
95 return rockbox_running;
97 public Activity get_activity()
99 return current_activity;
101 public void set_activity(Activity a)
103 current_activity = a;
106 private void do_start(Intent intent)
108 LOG("Start RockboxService (Intent: " + intent.getAction() + ")");
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 /* give it a bit of time so we can register button presses
120 * sleeping longer doesn't work here, apparently Android
121 * surpresses long sleeps during intent handling */
122 try {
123 Thread.sleep(50);
124 } catch (InterruptedException e) { }
126 KeyEvent kev = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
127 RockboxFramebuffer.buttonHandler(kev.getKeyCode(),
128 kev.getAction() == KeyEvent.ACTION_DOWN);
131 /* (Re-)attach the media button receiver, in case it has been lost */
132 mMediaButtonReceiver.register();
133 if (resultReceiver != null)
134 resultReceiver.send(RESULT_SERVICE_RUNNING, null);
136 rockbox_running = true;
139 private void LOG(CharSequence text)
141 Log.d("Rockbox", (String) text);
144 private void LOG(CharSequence text, Throwable tr)
146 Log.d("Rockbox", (String) text, tr);
149 public void onStart(Intent intent, int startId) {
150 do_start(intent);
153 public int onStartCommand(Intent intent, int flags, int startId)
155 do_start(intent);
156 return 1; /* old API compatibility: 1 == START_STICKY */
159 private void startservice()
161 final Object lock = new Object();
162 Thread rb = new Thread(new Runnable()
164 public void run()
166 final int BUFFER = 8*1024;
167 String rockboxDirPath = "/data/data/org.rockbox/app_rockbox/rockbox";
168 File rockboxDir = new File(rockboxDirPath);
170 /* load library before unzipping which may take a while */
171 synchronized (lock) {
172 System.loadLibrary("rockbox");
173 lock.notify();
176 /* the following block unzips libmisc.so, which contains the files
177 * we ship, such as themes. It's needed to put it into a .so file
178 * because there's no other way to ship files and have access
179 * to them from native code
181 File libMisc = new File("/data/data/org.rockbox/lib/libmisc.so");
182 /* use arbitrary file to determine whether extracting is needed */
183 File arbitraryFile = new File(rockboxDir, "viewers.config");
184 if (!arbitraryFile.exists() || (libMisc.lastModified() > arbitraryFile.lastModified()))
188 Bundle progressData = new Bundle();
189 byte data[] = new byte[BUFFER];
190 ZipFile zipfile = new ZipFile(libMisc);
191 Enumeration<? extends ZipEntry> e = zipfile.entries();
192 progressData.putInt("max", zipfile.size());
194 while(e.hasMoreElements())
196 ZipEntry entry = (ZipEntry) e.nextElement();
197 File file;
198 /* strip off /.rockbox when extracting */
199 String fileName = entry.getName();
200 int slashIndex = fileName.indexOf('/', 1);
201 file = new File(rockboxDirPath + fileName.substring(slashIndex));
203 if (!entry.isDirectory())
205 /* Create the parent folders if necessary */
206 File folder = new File(file.getParent());
207 if (!folder.exists())
208 folder.mkdirs();
210 /* Extract file */
211 BufferedInputStream is = new BufferedInputStream(zipfile.getInputStream(entry), BUFFER);
212 FileOutputStream fos = new FileOutputStream(file);
213 BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);
215 int count;
216 while ((count = is.read(data, 0, BUFFER)) != -1)
217 dest.write(data, 0, count);
219 dest.flush();
220 dest.close();
221 is.close();
224 if (resultReceiver != null) {
225 progressData.putInt("value", progressData.getInt("value", 0) + 1);
226 resultReceiver.send(RESULT_LIB_LOAD_PROGRESS, progressData);
229 } catch(Exception e) {
230 LOG("Exception when unzipping", e);
231 e.printStackTrace();
232 if (resultReceiver != null) {
233 Bundle bundle = new Bundle();
234 bundle.putString("error", getString(R.string.error_extraction));
235 resultReceiver.send(RESULT_ERROR_OCCURED, bundle);
240 if (resultReceiver != null)
241 resultReceiver.send(RESULT_INVOKING_MAIN, null);
243 main();
244 throw new IllegalStateException("native main() returned!");
246 }, "Rockbox thread");
247 rb.setDaemon(false);
248 /* wait at least until the library is loaded */
249 synchronized (lock)
251 rb.start();
252 while(true)
254 try {
255 lock.wait();
256 } catch (InterruptedException e) {
257 continue;
259 break;
264 private native void main();
266 @Override
267 public IBinder onBind(Intent intent)
269 // TODO Auto-generated method stub
270 return null;
274 @SuppressWarnings("unused")
276 * Sets up the battery monitor which receives the battery level
277 * about each 30 seconds
279 private void initBatteryMonitor()
281 itf = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
282 batt_monitor = new BroadcastReceiver()
284 @Override
285 public void onReceive(Context context, Intent intent)
287 /* we get literally spammed with battery statuses
288 * if we don't delay the re-attaching
290 TimerTask tk = new TimerTask()
292 public void run()
294 registerReceiver(batt_monitor, itf);
297 Timer t = new Timer();
298 context.unregisterReceiver(this);
299 int rawlevel = intent.getIntExtra("level", -1);
300 int scale = intent.getIntExtra("scale", -1);
301 if (rawlevel >= 0 && scale > 0)
302 battery_level = (rawlevel * 100) / scale;
303 else
304 battery_level = -1;
305 /* query every 30s should be sufficient */
306 t.schedule(tk, 30000);
309 registerReceiver(batt_monitor, itf);
312 public void startForeground()
314 fg_runner.startForeground();
317 public void stopForeground()
319 fg_runner.stopForeground();
322 @Override
323 public void onDestroy()
325 super.onDestroy();
326 mMediaButtonReceiver.unregister();
327 mMediaButtonReceiver = null;
328 /* Make sure our notification is gone. */
329 stopForeground();
330 instance = null;
331 rockbox_running = false;