Remove the use of the instance field in non-anymore-static methods
[kugel-rb.git] / android / src / org / rockbox / RockboxService.java
blob63ebd2756614fa20e6f2b0d5e1c0099e78e1fbb2
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.IOException;
30 import java.lang.reflect.InvocationTargetException;
31 import java.lang.reflect.Method;
32 import java.util.Enumeration;
33 import java.util.Timer;
34 import java.util.TimerTask;
35 import java.util.zip.ZipEntry;
36 import java.util.zip.ZipFile;
38 import android.app.Activity;
39 import android.app.Notification;
40 import android.app.NotificationManager;
41 import android.app.PendingIntent;
42 import android.app.Service;
43 import android.content.BroadcastReceiver;
44 import android.content.Context;
45 import android.content.Intent;
46 import android.content.IntentFilter;
47 import android.os.IBinder;
48 import android.util.Log;
50 /* This class is used as the main glue between java and c.
51 * All access should be done through RockboxService.get_instance() for safety.
54 public class RockboxService extends Service
56 /* this Service is really a singleton class - well almost.
57 * To do it properly this line should be instance = new RockboxService()
58 * but apparently that doesnt work with the way android Services are created.
60 private static RockboxService instance = null;
62 /* locals needed for the c code and rockbox state */
63 private RockboxFramebuffer fb = null;
64 private boolean mRockboxRunning = false;
65 private Activity current_activity = null;
67 private Notification notification;
68 private static final Class<?>[] mStartForegroundSignature =
69 new Class[] { int.class, Notification.class };
70 private static final Class<?>[] mStopForegroundSignature =
71 new Class[] { boolean.class };
73 private NotificationManager mNM;
74 private Method mStartForeground;
75 private Method mStopForeground;
76 private Object[] mStartForegroundArgs = new Object[2];
77 private Object[] mStopForegroundArgs = new Object[1];
78 private IntentFilter itf;
79 private BroadcastReceiver batt_monitor;
80 @SuppressWarnings("unused")
81 private int battery_level;
83 @Override
84 public void onCreate()
86 instance = this;
87 mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
88 try
90 mStartForeground = getClass().getMethod("startForeground",
91 mStartForegroundSignature);
92 mStopForeground = getClass().getMethod("stopForeground",
93 mStopForegroundSignature);
95 catch (NoSuchMethodException e)
97 /* Running on an older platform: fall back to old API */
98 mStartForeground = mStopForeground = null;
100 startservice();
103 public static RockboxService get_instance()
105 return instance;
108 public RockboxFramebuffer get_fb()
110 return fb;
112 /* framebuffer is initialised by the native code(!) so this is needed */
113 public void set_fb(RockboxFramebuffer newfb)
115 fb = newfb;
116 mRockboxRunning = true;
119 public Activity get_activity()
121 return current_activity;
123 public void set_activity(Activity a)
125 current_activity = a;
129 private void do_start(Intent intent)
131 LOG("Start Service");
133 /* Display a notification about us starting.
134 * We put an icon in the status bar. */
135 create_notification();
138 private void LOG(CharSequence text)
140 Log.d("Rockbox", (String) text);
143 private void LOG(CharSequence text, Throwable tr)
145 Log.d("Rockbox", (String) text, tr);
148 public void onStart(Intent intent, int startId) {
149 do_start(intent);
152 public int onStartCommand(Intent intent, int flags, int startId)
154 do_start(intent);
155 return 1; /* old API compatibility: 1 == START_STICKY */
158 private void startservice()
160 final int BUFFER = 8*1024;
161 Thread rb = new Thread(new Runnable()
163 public void run()
165 /* the following block unzips libmisc.so, which contains the files
166 * we ship, such as themes. It's needed to put it into a .so file
167 * because there's no other way to ship files and have access
168 * to them from native code
172 BufferedOutputStream dest = null;
173 BufferedInputStream is = null;
174 ZipEntry entry;
175 File file = new File("/data/data/org.rockbox/" +
176 "lib/libmisc.so");
177 /* use arbitrary file to determine whether extracting is needed */
178 File file2 = new File("/data/data/org.rockbox/" +
179 "app_rockbox/rockbox/codecs/mpa.codec");
180 if (!file2.exists() || (file.lastModified() > file2.lastModified()))
182 ZipFile zipfile = new ZipFile(file);
183 Enumeration<? extends ZipEntry> e = zipfile.entries();
184 File folder;
185 while(e.hasMoreElements())
187 entry = (ZipEntry) e.nextElement();
188 LOG("Extracting: " +entry);
189 if (entry.isDirectory())
191 folder = new File(entry.getName());
192 LOG("mkdir "+ entry);
193 try {
194 folder.mkdirs();
195 } catch (SecurityException ex) {
196 LOG(ex.getMessage());
198 continue;
200 is = new BufferedInputStream(zipfile.getInputStream(entry),
201 BUFFER);
202 int count;
203 byte data[] = new byte[BUFFER];
204 folder = new File(new File(entry.getName()).getParent());
205 LOG("" + folder.getAbsolutePath());
206 if (!folder.exists())
207 folder.mkdirs();
208 FileOutputStream fos = new FileOutputStream(entry.getName());
209 dest = new BufferedOutputStream(fos, BUFFER);
210 while ((count = is.read(data, 0, BUFFER)) != -1)
211 dest.write(data, 0, count);
212 dest.flush();
213 dest.close();
214 is.close();
217 } catch(FileNotFoundException e) {
218 LOG("FileNotFoundException when unzipping", e);
219 e.printStackTrace();
220 } catch(IOException e) {
221 LOG("IOException when unzipping", e);
222 e.printStackTrace();
225 System.loadLibrary("rockbox");
226 main();
228 },"Rockbox thread");
229 rb.setDaemon(false);
230 rb.start();
232 private native void main();
234 /* returns true once rockbox is up and running.
235 * This is considered done once the framebuffer is initialised
237 public boolean isRockboxRunning()
239 return mRockboxRunning;
242 @Override
243 public IBinder onBind(Intent intent)
245 // TODO Auto-generated method stub
246 return null;
250 @SuppressWarnings("unused")
252 * Sets up the battery monitor which receives the battery level
253 * about each 30 seconds
255 private void initBatteryMonitor()
257 itf = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
258 batt_monitor = new BroadcastReceiver()
260 @Override
261 public void onReceive(Context context, Intent intent)
263 /* we get literally spammed with battery statuses
264 * if we don't delay the re-attaching
266 TimerTask tk = new TimerTask()
268 public void run()
270 registerReceiver(batt_monitor, itf);
273 Timer t = new Timer();
274 context.unregisterReceiver(this);
275 int rawlevel = intent.getIntExtra("level", -1);
276 int scale = intent.getIntExtra("scale", -1);
277 if (rawlevel >= 0 && scale > 0)
278 battery_level = (rawlevel * 100) / scale;
279 else
280 battery_level = -1;
281 /* query every 30s should be sufficient */
282 t.schedule(tk, 30000);
285 registerReceiver(batt_monitor, itf);
288 /* all below is heavily based on the examples found on
289 * http://developer.android.com/reference/android/app/Service.html
292 private void create_notification()
294 /* For now we'll use the same text for the ticker and the
295 * expanded notification */
296 CharSequence text = getText(R.string.notification);
297 /* Set the icon, scrolling text and timestamp */
298 notification = new Notification(R.drawable.icon, text,
299 System.currentTimeMillis());
301 /* The PendingIntent to launch our activity if the user selects
302 * this notification */
303 Intent intent = new Intent(this, RockboxActivity.class);
304 PendingIntent contentIntent =
305 PendingIntent.getActivity(this, 0, intent, 0);
307 /* Set the info for the views that show in the notification panel. */
308 notification.setLatestEventInfo(this,
309 getText(R.string.notification), text, contentIntent);
312 public void startForeground()
315 * Send the notification.
316 * We use a layout id because it is a unique number.
317 * We use it later to cancel.
319 mNM.notify(R.string.notification, instance.notification);
321 * this call makes the service run as foreground, which
322 * provides enough cpu time to do music decoding in the
323 * background
325 startForegroundCompat(R.string.notification, notification);
328 public void stopForeground()
330 if (notification != null)
332 stopForegroundCompat(R.string.notification);
333 mNM.cancel(R.string.notification);
338 * This is a wrapper around the new startForeground method, using the older
339 * APIs if it is not available.
341 void startForegroundCompat(int id, Notification notification)
343 if (mStartForeground != null) {
344 mStartForegroundArgs[0] = Integer.valueOf(id);
345 mStartForegroundArgs[1] = notification;
346 try {
347 mStartForeground.invoke(this, mStartForegroundArgs);
348 } catch (InvocationTargetException e) {
349 /* Should not happen. */
350 LOG("Unable to invoke startForeground", e);
351 } catch (IllegalAccessException e) {
352 /* Should not happen. */
353 LOG("Unable to invoke startForeground", e);
355 return;
358 /* Fall back on the old API.*/
359 setForeground(true);
360 mNM.notify(id, notification);
364 * This is a wrapper around the new stopForeground method, using the older
365 * APIs if it is not available.
367 void stopForegroundCompat(int id)
369 if (mStopForeground != null) {
370 mStopForegroundArgs[0] = Boolean.TRUE;
371 try {
372 mStopForeground.invoke(this, mStopForegroundArgs);
373 } catch (InvocationTargetException e) {
374 /* Should not happen. */
375 LOG("Unable to invoke stopForeground", e);
376 } catch (IllegalAccessException e) {
377 /* Should not happen. */
378 LOG("Unable to invoke stopForeground", e);
380 return;
383 /* Fall back on the old API. Note to cancel BEFORE changing the
384 * foreground state, since we could be killed at that point. */
385 mNM.cancel(id);
386 setForeground(false);
389 @Override
390 public void onDestroy()
392 super.onDestroy();
393 /* Make sure our notification is gone. */
394 stopForegroundCompat(R.string.notification);