Android: Implement app shutdown and thus, sleep timer.
[kugel-rb.git] / android / src / org / rockbox / RockboxService.java
blobd078fe83b65a85fb12db93b922c31b3845527851
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.Timer;
31 import java.util.TimerTask;
32 import java.util.zip.ZipEntry;
33 import java.util.zip.ZipFile;
35 import org.rockbox.Helper.MediaButtonReceiver;
36 import org.rockbox.Helper.RunForegroundManager;
38 import android.app.Activity;
39 import android.app.Service;
40 import android.content.BroadcastReceiver;
41 import android.content.Context;
42 import android.content.Intent;
43 import android.content.IntentFilter;
44 import android.os.Bundle;
45 import android.os.Environment;
46 import android.os.IBinder;
47 import android.os.ResultReceiver;
48 import android.util.Log;
49 import android.view.KeyEvent;
51 /* This class is used as the main glue between java and c.
52 * All access should be done through RockboxService.get_instance() for safety.
55 public class RockboxService extends Service
57 /* this Service is really a singleton class - well almost.
58 * To do it properly this line should be instance = new RockboxService()
59 * but apparently that doesnt work with the way android Services are created.
61 private static RockboxService instance = null;
63 /* locals needed for the c code and rockbox state */
64 private static volatile boolean rockbox_running;
65 private Activity current_activity = null;
66 private IntentFilter itf;
67 private BroadcastReceiver batt_monitor;
68 private RunForegroundManager fg_runner;
69 private MediaButtonReceiver mMediaButtonReceiver;
70 private int battery_level;
71 private ResultReceiver resultReceiver;
73 public static final int RESULT_INVOKING_MAIN = 0;
74 public static final int RESULT_LIB_LOAD_PROGRESS = 1;
75 public static final int RESULT_SERVICE_RUNNING = 3;
76 public static final int RESULT_ERROR_OCCURED = 4;
77 public static final int RESULT_LIB_LOADED = 5;
78 public static final int RESULT_ROCKBOX_EXIT = 6;
80 @Override
81 public void onCreate()
83 instance = this;
84 mMediaButtonReceiver = new MediaButtonReceiver(this);
85 fg_runner = new RunForegroundManager(this);
88 public static RockboxService get_instance()
90 /* don't call the construtor here, the instances are managed by
91 * android, so we can't just create a new one */
92 return instance;
95 public boolean isRockboxRunning()
97 return rockbox_running;
99 public Activity get_activity()
101 return current_activity;
103 public void set_activity(Activity a)
105 current_activity = a;
108 private void do_start(Intent intent)
110 LOG("Start RockboxService (Intent: " + intent.getAction() + ")");
112 if (intent.getAction().equals("org.rockbox.ResendTrackUpdateInfo"))
114 if (rockbox_running)
115 fg_runner.resendUpdateNotification();
116 return;
119 if (intent.hasExtra("callback"))
120 resultReceiver = (ResultReceiver) intent.getParcelableExtra("callback");
122 if (!rockbox_running)
123 startservice();
124 if (resultReceiver != null)
125 resultReceiver.send(RESULT_LIB_LOADED, null);
127 if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON))
129 /* give it a bit of time so we can register button presses
130 * sleeping longer doesn't work here, apparently Android
131 * surpresses long sleeps during intent handling */
132 try {
133 Thread.sleep(50);
134 } catch (InterruptedException e) { }
136 KeyEvent kev = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
137 RockboxFramebuffer.buttonHandler(kev.getKeyCode(),
138 kev.getAction() == KeyEvent.ACTION_DOWN);
141 /* (Re-)attach the media button receiver, in case it has been lost */
142 mMediaButtonReceiver.register();
143 if (resultReceiver != null)
144 resultReceiver.send(RESULT_SERVICE_RUNNING, null);
146 rockbox_running = true;
149 private void LOG(CharSequence text)
151 Log.d("Rockbox", (String) text);
154 private void LOG(CharSequence text, Throwable tr)
156 Log.d("Rockbox", (String) text, tr);
159 public void onStart(Intent intent, int startId) {
160 do_start(intent);
163 public int onStartCommand(Intent intent, int flags, int startId)
165 do_start(intent);
166 return 1; /* old API compatibility: 1 == START_STICKY */
169 private void startservice()
171 final Object lock = new Object();
172 Thread rb = new Thread(new Runnable()
174 public void run()
176 final int BUFFER = 8*1024;
177 String rockboxDirPath = "/data/data/org.rockbox/app_rockbox/rockbox";
178 File rockboxDir = new File(rockboxDirPath);
180 /* load library before unzipping which may take a while */
181 synchronized (lock) {
182 System.loadLibrary("rockbox");
183 lock.notify();
186 /* the following block unzips libmisc.so, which contains the files
187 * we ship, such as themes. It's needed to put it into a .so file
188 * because there's no other way to ship files and have access
189 * to them from native code
191 File libMisc = new File("/data/data/org.rockbox/lib/libmisc.so");
192 /* use arbitrary file to determine whether extracting is needed */
193 File arbitraryFile = new File(rockboxDir, "viewers.config");
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 file = new File(rockboxDirPath + fileName.substring(slashIndex));
213 if (!entry.isDirectory())
215 /* Create the parent folders if necessary */
216 File folder = new File(file.getParent());
217 if (!folder.exists())
218 folder.mkdirs();
220 /* Extract file */
221 BufferedInputStream is = new BufferedInputStream(zipfile.getInputStream(entry), BUFFER);
222 FileOutputStream fos = new FileOutputStream(file);
223 BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);
225 int count;
226 while ((count = is.read(data, 0, BUFFER)) != -1)
227 dest.write(data, 0, count);
229 dest.flush();
230 dest.close();
231 is.close();
234 if (resultReceiver != null) {
235 progressData.putInt("value", progressData.getInt("value", 0) + 1);
236 resultReceiver.send(RESULT_LIB_LOAD_PROGRESS, progressData);
239 } catch(Exception e) {
240 LOG("Exception when unzipping", e);
241 e.printStackTrace();
242 if (resultReceiver != null) {
243 Bundle bundle = new Bundle();
244 bundle.putString("error", getString(R.string.error_extraction));
245 resultReceiver.send(RESULT_ERROR_OCCURED, bundle);
250 /* Generate default config if none exists yet */
251 File rockboxConfig = new File(Environment.getExternalStorageDirectory(), "rockbox/config.cfg");
252 if (!rockboxConfig.exists()) {
253 File rbDir = new File(rockboxConfig.getParent());
254 if (!rbDir.exists())
255 rbDir.mkdirs();
257 OutputStreamWriter strm;
258 try {
259 strm = new OutputStreamWriter(new FileOutputStream(rockboxConfig));
260 strm.write("# config generated by RockboxService\n");
261 strm.write("start directory: " + Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "\n");
262 strm.write("lang: /.rockbox/langs/" + getString(R.string.rockbox_language_file) + "\n");
263 strm.close();
264 } catch(Exception e) {
265 LOG("Exception when writing default config", e);
269 /* Start native code */
270 if (resultReceiver != null)
271 resultReceiver.send(RESULT_INVOKING_MAIN, null);
273 main();
275 if (resultReceiver != null)
276 resultReceiver.send(RESULT_ROCKBOX_EXIT, null);
278 LOG("Stop service: main() returned");
279 stopSelf(); /* serivce is of no use anymore */
281 }, "Rockbox thread");
282 rb.setDaemon(false);
283 /* wait at least until the library is loaded */
284 synchronized (lock)
286 rb.start();
287 while(true)
289 try {
290 lock.wait();
291 } catch (InterruptedException e) {
292 continue;
294 break;
299 private native void main();
301 @Override
302 public IBinder onBind(Intent intent)
304 // TODO Auto-generated method stub
305 return null;
309 private void initBatteryMonitor()
311 itf = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
312 batt_monitor = new BroadcastReceiver()
314 @Override
315 public void onReceive(Context context, Intent intent)
317 /* we get literally spammed with battery statuses
318 * if we don't delay the re-attaching
320 TimerTask tk = new TimerTask()
322 public void run()
324 registerReceiver(batt_monitor, itf);
327 Timer t = new Timer();
328 context.unregisterReceiver(this);
329 int rawlevel = intent.getIntExtra("level", -1);
330 int scale = intent.getIntExtra("scale", -1);
331 if (rawlevel >= 0 && scale > 0)
332 battery_level = (rawlevel * 100) / scale;
333 else
334 battery_level = -1;
335 /* query every 30s should be sufficient */
336 t.schedule(tk, 30000);
339 registerReceiver(batt_monitor, itf);
342 void startForeground()
344 fg_runner.startForeground();
347 void stopForeground()
349 fg_runner.stopForeground();
352 @Override
353 public void onDestroy()
355 super.onDestroy();
356 mMediaButtonReceiver.unregister();
357 mMediaButtonReceiver = null;
358 /* Make sure our notification is gone. */
359 stopForeground();
360 instance = null;
361 rockbox_running = false;
362 System.runFinalization();
363 /* exit() seems unclean but is needed in order to get the .so file garbage
364 * collected, otherwise Android caches this Service and librockbox.so
365 * The library must be reloaded to zero the bss and reset data
366 * segment */
367 System.exit(0);