Android port: generate initial config when none is present with dynamically
[maemo-rb.git] / android / src / org / rockbox / RockboxService.java
blobc474044c0b7867e3934cfda0eef049b3fa9154c9
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.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;
81 @Override
82 public void onCreate()
84 instance = this;
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 */
93 return instance;
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"))
115 if (rockbox_running)
116 fg_runner.resendUpdateNotification();
117 return;
120 if (intent.hasExtra("callback"))
121 resultReceiver = (ResultReceiver) intent.getParcelableExtra("callback");
123 if (!rockbox_running)
124 startservice();
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 */
133 try {
134 Thread.sleep(50);
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) {
161 do_start(intent);
164 public int onStartCommand(Intent intent, int flags, int startId)
166 do_start(intent);
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()
175 public void run()
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");
184 lock.notify();
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();
208 File file;
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())
219 folder.mkdirs();
221 /* Extract file */
222 BufferedInputStream is = new BufferedInputStream(zipfile.getInputStream(entry), BUFFER);
223 FileOutputStream fos = new FileOutputStream(file);
224 BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);
226 int count;
227 while ((count = is.read(data, 0, BUFFER)) != -1)
228 dest.write(data, 0, count);
230 dest.flush();
231 dest.close();
232 is.close();
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);
242 e.printStackTrace();
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());
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 LOG("Exception when writing default config", e);
270 /* Start native code */
271 if (resultReceiver != null)
272 resultReceiver.send(RESULT_INVOKING_MAIN, null);
274 main();
275 throw new IllegalStateException("native main() returned!");
277 }, "Rockbox thread");
278 rb.setDaemon(false);
279 /* wait at least until the library is loaded */
280 synchronized (lock)
282 rb.start();
283 while(true)
285 try {
286 lock.wait();
287 } catch (InterruptedException e) {
288 continue;
290 break;
295 private native void main();
297 @Override
298 public IBinder onBind(Intent intent)
300 // TODO Auto-generated method stub
301 return null;
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()
315 @Override
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()
323 public void run()
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;
334 else
335 battery_level = -1;
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();
353 @Override
354 public void onDestroy()
356 super.onDestroy();
357 mMediaButtonReceiver.unregister();
358 mMediaButtonReceiver = null;
359 /* Make sure our notification is gone. */
360 stopForeground();
361 instance = null;
362 rockbox_running = false;