Android: Rework RunForegroundManager (again)

Restore pre-r29562 way of doing compatibility and adapt
it to what MediaButtonReceiver looks like. I.e. assume the new API is there,
and fall back to the old API if an exception is raised because the API isn't there.
The old API still needs to be discovered through reflection because it's removed
entirely from Honeycomb.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30165 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Thomas Martitz 2011-07-18 22:04:08 +00:00
parent 8fba4fb937
commit 64b5f1c625
3 changed files with 92 additions and 70 deletions

View file

@ -36,6 +36,10 @@ public class Logger
{ {
Log.d(TAG, s); Log.d(TAG, s);
} }
public static void d(String s, Throwable t)
{
Log.d(TAG, s, t);
}
public static void e(String s) public static void e(String s)
{ {

View file

@ -58,6 +58,7 @@ public class MediaButtonReceiver
/* Throwable includes Exception and the expected /* Throwable includes Exception and the expected
* NoClassDefFoundError */ * NoClassDefFoundError */
api = new OldApi(c); api = new OldApi(c);
Logger.i("MediaButtonReceiver: Falling back to compatibility API");
} }
} }

View file

@ -1,22 +1,23 @@
package org.rockbox.Helper; package org.rockbox.Helper;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.rockbox.R; import org.rockbox.R;
import org.rockbox.RockboxActivity; import org.rockbox.RockboxActivity;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.util.Log;
import android.widget.RemoteViews; import android.widget.RemoteViews;
public class RunForegroundManager public class RunForegroundManager
{ {
/* all below is heavily based on the examples found on
* http://developer.android.com/reference/android/app/Service.html#setForeground(boolean)
*/
private Notification mNotification; private Notification mNotification;
private NotificationManager mNM; private NotificationManager mNM;
private IRunForeground api;
private Service mCurrentService; private Service mCurrentService;
private Intent mWidgetUpdate; private Intent mWidgetUpdate;
@ -37,17 +38,43 @@ public class RunForegroundManager
mNotification.flags |= Notification.FLAG_ONGOING_EVENT; mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
mNotification.contentIntent = PendingIntent.getActivity(service, 0, intent, 0); mNotification.contentIntent = PendingIntent.getActivity(service, 0, intent, 0);
initForegroundCompat(); try {
api = new NewForegroundApi(R.string.notification, mNotification);
} catch (Throwable t) {
/* Throwable includes Exception and the expected
* NoClassDefFoundError for Android 1.x */
try {
api = new OldForegroundApi();
Logger.i("RunForegroundManager: Falling back to compatibility API");
} catch (Exception e) {
Logger.e("Cannot run in foreground: No available API");
}
}
} }
public void startForeground() public void startForeground()
{ {
startForegroundCompat(R.string.notification, mNotification); /*
* Send the notification.
* We use a layout id because it is a unique number.
* We use it later to cancel.
*/
mNM.notify(R.string.notification, mNotification);
/*
* this call makes the service run as foreground, which
* provides enough cpu time to do music decoding in the
* background
*/
api.startForeground();
} }
public void stopForeground() public void stopForeground()
{ {
stopForegroundCompat(R.string.notification); /* Note to cancel BEFORE changing the
* foreground state, since we could be killed at that point.
*/
mNM.cancel(R.string.notification);
api.stopForeground();
mWidgetUpdate = null; mWidgetUpdate = null;
} }
@ -78,78 +105,68 @@ public class RunForegroundManager
public void finishNotification() public void finishNotification()
{ {
Log.d("Rockbox", "TrackFinish"); Logger.d("TrackFinish");
Intent widgetUpdate = new Intent("org.rockbox.TrackFinish"); Intent widgetUpdate = new Intent("org.rockbox.TrackFinish");
mCurrentService.sendBroadcast(widgetUpdate); mCurrentService.sendBroadcast(widgetUpdate);
} }
/* Loosely based on http://developer.android.com/reference/android/app/Service.html#startForeground(int, android.app.Notification) */ private interface IRunForeground
private static final Class<?>[] mSetForegroundSignature = new Class[] {boolean.class}; {
private static final Class<?>[] mStartForegroundSignature = new Class[] {int.class, Notification.class}; void startForeground();
private static final Class<?>[] mStopForegroundSignature = new Class[] {boolean.class}; void stopForeground();
}
private Method mSetForeground; private class NewForegroundApi implements IRunForeground
private Method mStartForeground; {
private Method mStopForeground; int id;
Notification mNotification;
NewForegroundApi(int _id, Notification _notification)
{
id = _id;
mNotification = _notification;
}
private void initForegroundCompat() { public void startForeground()
Class<?> serviceClass = mCurrentService.getClass(); {
try { mCurrentService.startForeground(id, mNotification);
mStartForeground = serviceClass.getMethod("startForeground", mStartForegroundSignature); }
mStopForeground = serviceClass.getMethod("stopForeground", mStopForegroundSignature);
} catch (NoSuchMethodException e) { public void stopForeground()
// Running on an older platform. {
mStartForeground = mStopForeground = null; mCurrentService.stopForeground(true);
}
}
private class OldForegroundApi implements IRunForeground
{
/*
* Get the new API through reflection because it's unavailable
* in honeycomb
*/
private Method mSetForeground;
public OldForegroundApi() throws SecurityException, NoSuchMethodException
{
mSetForeground = getClass().getMethod("setForeground",
new Class[] { boolean.class });
}
public void startForeground()
{
try { try {
mSetForeground = serviceClass.getMethod("setForeground", mSetForegroundSignature); mSetForeground.invoke(mCurrentService, Boolean.TRUE);
} catch (NoSuchMethodException e2) { } catch (Exception e) {
throw new IllegalStateException("OS doesn't have Service.startForeground nor Service.setForeground!", e2); Logger.e("startForeground compat error: " + e.getMessage());
e.printStackTrace();
}
}
public void stopForeground()
{
try {
mSetForeground.invoke(mCurrentService, Boolean.FALSE);
} catch (Exception e) {
Logger.e("stopForeground compat error: " + e.getMessage());
} }
} }
} }
private void invokeMethod(Method method, Object[] args) {
try {
method.invoke(mCurrentService, args);
} catch (Exception e) {
// Should not happen.
Log.w("Rockbox", "Unable to invoke method", e);
}
}
/**
* This is a wrapper around the new startForeground method, using the older
* APIs if it is not available.
*/
private void startForegroundCompat(int id, Notification notification) {
// If we have the new startForeground API, then use it.
if (mStartForeground != null) {
Object[] startForeGroundArgs = new Object[] {Integer.valueOf(id), notification};
invokeMethod(mStartForeground, startForeGroundArgs);
} else {
// Fall back on the old API.
Object[] setForegroundArgs = new Object[] {Boolean.TRUE};
invokeMethod(mSetForeground, setForegroundArgs);
mNM.notify(id, notification);
}
}
/**
* This is a wrapper around the new stopForeground method, using the older
* APIs if it is not available.
*/
private void stopForegroundCompat(int id) {
// If we have the new stopForeground API, then use it.
if (mStopForeground != null) {
Object[] stopForegroundArgs = new Object[] {Boolean.TRUE};
invokeMethod(mStopForeground, stopForegroundArgs);
} else {
// Fall back on the old API. Note to cancel BEFORE changing the
// foreground state, since we could be killed at that point.
mNM.cancel(id);
Object[] setForegroundArgs = new Object[] {Boolean.FALSE};
invokeMethod(mSetForeground, setForegroundArgs);
}
}
} }