Android-Developers

  • Subscribe to our RSS feed.
  • Twitter
  • StumbleUpon
  • Reddit
  • Facebook
  • Digg

Wednesday, 2 June 2010

Allowing applications to play nice(r) with each other: Handling remote control buttons

Posted on 13:41 by Unknown

[This post is by Jean-Michel Trivi, an engineer working on the Android Media framework, whose T-shirt of the day reads “all your media buttons are belong to you”. — Tim Bray]

Many Android devices come with the Music application used to play audio files stored on the device. Some devices ship with a wired headset that features transport control buttons, so users can for instance conveniently pause and restart music playback, directly from the headset.

But a user might use one application for music listening, and another for listening to podcasts, both of which should be controlled by the headset remote control.

If your media playback application creates a media playback service, just like Music, that responds to the media button events, how will the user know where those events are going to? Music, or your new application?

In this article, we’ll see how to handle this properly in Android 2.2. We’ll first see how to set up intents to receive “MEDIA_BUTTON” intents. We’ll then describe how your application can appropriately become the preferred media button responder in Android 2.2. Since this feature relies on a new API, we’ll revisit the use of reflection to prepare your app to take advantage of Android 2.2, without restricting it to API level 8 (Android 2.2).

An example of the handling of media button intents

In our AndroidManifest.xml for this package we declare the class RemoteControlReceiver to receive MEDIA_BUTTON intents:

<receiver android:name="RemoteControlReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>

Our class to handle those intents can look something like this:

public class RemoteControlReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
/* handle media button intent here by reading contents */
/* of EXTRA_KEY_EVENT to know which key was pressed */
}
}
}

In a media playback application, this is used to react to headset button presses when your activity doesn’t have the focus. For when it does, we override the Activity.onKeyDown() or onKeyUp() methods for the user interface to trap the headset button-related events.

However, this is problematic in the scenario we mentioned earlier. When the user presses “play”, what application should start playing? The Music application? The user’s preferred podcast application?

Becoming the “preferred” media button responder

In Android 2.2, we are introducing two new methods in android.media.AudioManager to declare your intention to become the “preferred” component to receive media button events: registerMediaButtonEventReceiver() and its counterpart, unregisterMediaButtonEventReceiver(). Once the registration call is placed, the designated component will exclusively receive the ACTION_MEDIA_BUTTON intent just as in the example above.

In the activity below were are creating an instance of AudioManager with which we will register our component. We therefore create a ComponentName instance that references our intended media button event responder.

public class MyMediaPlaybackActivity extends Activity {
private AudioManager mAudioManager;
private ComponentName mRemoteControlResponder;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
mRemoteControlResponder = new ComponentName(getPackageName(),
RemoteControlReceiver.class.getName());
}

The system handles the media button registration requests in a “last one wins” manner. This means we need to select where it makes sense for the user to make this request. In a media playback application, proper uses of the registration are for instance:

  • when the UI is displayed: the user is interacting with that application, so (s)he expects it to be the one that will respond to the remote control,


  • when content starts playing (e.g. content finished downloading, or another application caused your service to play content)


Registering is here performed for instance when our UI comes to the foreground:

    @Override
public void onResume() {
super.onResume();
mAudioManager.registerMediaButtonEventReceiver(
mRemoteControlResponder);
}

If we had previously registered our receiver, registering it again will push it up the stack, and doesn’t cause any duplicate registration.

Additionally, it may make sense for your registered component not to be called when your service or application is destroyed (as illustrated below), or under conditions that are specific to your application. For instance, in an application that reads to the user her/his appointments of the day, it could unregister when it’s done speaking the calendar entries of the day.

    @Override
public void onDestroy() {
super.onDestroy();
mAudioManager.unregisterMediaButtonEventReceiver(
mRemoteControlResponder);
}

After “unregistering”, the previous component that requested to receive the media button intents will once again receive them.

Preparing your code for Android 2.2 without restricting it to Android 2.2

While you may appreciate the benefit this new API offers to the users, you might not want to restrict your application to devices that support this feature. Andy McFadden shows us how to use reflection to take advantage of features that are not available on all devices. Let’s use what we learned then to enable your application to use the new media button mechanism when it runs on devices that support this feature.

First we declare in our Activity the two new methods we have used previously for the registration mechanism:

    private static Method mRegisterMediaButtonEventReceiver;
private static Method mUnregisterMediaButtonEventReceiver;

We then add a method that will use reflection on the android.media.AudioManager class to find the two methods when the feature is supported:

private static void initializeRemoteControlRegistrationMethods() {
try {
if (mRegisterMediaButtonEventReceiver == null) {
mRegisterMediaButtonEventReceiver = AudioManager.class.getMethod(
"registerMediaButtonEventReceiver",
new Class[] { ComponentName.class } );
}
if (mUnregisterMediaButtonEventReceiver == null) {
mUnregisterMediaButtonEventReceiver = AudioManager.class.getMethod(
"unregisterMediaButtonEventReceiver",
new Class[] { ComponentName.class } );
}
/* success, this device will take advantage of better remote */
/* control event handling */
} catch (NoSuchMethodException nsme) {
/* failure, still using the legacy behavior, but this app */
/* is future-proof! */
}
}

The method fields will need to be initialized when our Activity class is loaded:

    static {
initializeRemoteControlRegistrationMethods();
}

We’re almost done. Our code will be easier to read and maintain if we wrap the use of our methods initialized through reflection by the following. Note in bold the actual method invocation on our AudioManager instance:

    private void registerRemoteControl() {
try {
if (mRegisterMediaButtonEventReceiver == null) {
return;
}
mRegisterMediaButtonEventReceiver.invoke(mAudioManager,
mRemoteControlResponder);

} catch (InvocationTargetException ite) {
/* unpack original exception when possible */
Throwable cause = ite.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
/* unexpected checked exception; wrap and re-throw */
throw new RuntimeException(ite);
}
} catch (IllegalAccessException ie) {
Log.e(”MyApp”, "unexpected " + ie);
}
}

private void unregisterRemoteControl() {
try {
if (mUnregisterMediaButtonEventReceiver == null) {
return;
}
mUnregisterMediaButtonEventReceiver.invoke(mAudioManager,
mRemoteControlResponder);

} catch (InvocationTargetException ite) {
/* unpack original exception when possible */
Throwable cause = ite.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
/* unexpected checked exception; wrap and re-throw */
throw new RuntimeException(ite);
}
} catch (IllegalAccessException ie) {
System.err.println("unexpected " + ie);
}
}

We are now ready to use our two new methods, registerRemoteControl() and unregisterRemoteControl() in a project that runs on devices supporting API level 1, while still taking advantage of the features found in devices running Android 2.2.

Email ThisBlogThis!Share to XShare to FacebookShare to Pinterest
Posted in Media and Camera | No comments
Newer Post Older Post Home

0 comments:

Post a Comment

Subscribe to: Post Comments (Atom)

Popular Posts

  • Bring Your Apps into the Classroom, with Google Play for Education
    Posted by Shazia Makhdumi, Head of Strategic EDU Partnerships, Google Play team Google Play for Education has officially launched . It’s an ...
  • A Faster Emulator with Better Hardware Support
    [This post is by Xavier Ducrohet and Reto Meier of the Android engineering team. — Tim Bray.] The Android emulator is a key tool for Android...
  • Powering Chrome to Phone with Android Cloud to Device Messaging
    [This post is by Dave Burke, who's an Engineering Manager 80% of the time. — Tim Bray] Android Cloud to Device Messaging (C2DM) was lau...
  • Android 1.5 is here!
    I've got some good news today: the Android 1.5 SDK, release 1 is ready! Grab it from the download page . For an overview of the new Andr...
  • Memory Analysis for Android Applications
    [This post is by Patrick Dubroy, an Android engineer who writes about programming, usability, and interaction on his personal blog . — Tim B...
  • Preview of Google TV Add-on for the Android SDK
    [This post is by Ambarish Kenghe, who’s a Product Manager for Google TV — Tim Bray] At Google I/O , we announced that Android Market is comi...
  • Android SDK Tools, Revision 20
    [This post is by Xavier Ducrohet , Tech Lead for the Android developer tools] Along with the preview of the Android 4.1 (Jelly Bean) platfo...
  • RenderScript Intrinsics
    Posted by R. Jason Sams , Android RenderScript Tech Lead RenderScript has a very powerful ability called Intrinsics . Intrinsics are built-...
  • In-App Billing on Android Market: Ready for Testing
    [This post is by Eric Chu, Android Developer Ecosystem. —Dirk Dougherty] Back in January we announced our plan to introduce Android Market ...
  • Twitter for Android: A closer look at Android’s evolving UI patterns
    [This post is by Chris Nesladek, Interaction Designer, Richard Fulcher, Interaction Designer, and Virgil Dobjanschi, Software Engineer — Ti...

Categories

  • accessibility
  • Action Bar
  • Administration
  • Android
  • Android 1.5
  • Android 1.6
  • Android 2.0
  • Android 2.1
  • Android 2.2
  • Android 2.3
  • Android 2.3.3
  • Android 3.0
  • Android 3.2
  • Android 4.0
  • Android 4.2
  • Android 4.3
  • Android 4.4
  • Android Design
  • Android Developer Challenge
  • Android Developer Phone
  • Android Market
  • Android SDK
  • Android Studio
  • Animation and Graphics
  • Announcements
  • App Components
  • App Resources
  • Apps
  • Audio
  • Authentication
  • Best Practices
  • Boston
  • Code Day
  • Connectivity
  • Content Provider
  • Cool Stuff
  • Dashboard
  • Daydream
  • Debugging
  • Developer Console
  • Developer Days
  • Developer Labs
  • Developer profiles
  • Developer Story
  • Education
  • Games
  • GCM
  • Gestures
  • Google Analytics
  • Google Cloud Messaging
  • Google Cloud Platform
  • Google I/O
  • Google Play
  • Google Play game services
  • Google Play services
  • Google Services
  • Google Wallet
  • Google+
  • Guidelines
  • How-to
  • Image Processing
  • IME
  • In-app Billing
  • Input methods
  • Intents
  • io2010
  • IO2013
  • JNI
  • Layout
  • Localization
  • Location
  • Location and Sensors
  • London
  • Maps
  • Media and Camera
  • Mountain View
  • Munich
  • NDK
  • Open source
  • OpenGL ES
  • Optimization
  • Performance
  • Photo Sphere
  • Promo Graphics
  • Quality
  • Quick Search Box
  • Renderscript
  • Resources
  • RTL
  • Sample code
  • SDK Tools
  • SDK updates
  • Security
  • Sensors
  • Speech Input
  • Support Library
  • Survey
  • Tablets
  • Tel Aviv
  • Telephony
  • Testing
  • Text and Input
  • Text-to-Speech
  • Tools
  • Touch
  • User Interface
  • User Support
  • WebView
  • Widgets

Blog Archive

  • ►  2013 (45)
    • ►  November (2)
    • ►  October (7)
    • ►  September (2)
    • ►  August (5)
    • ►  July (5)
    • ►  June (4)
    • ►  May (9)
    • ►  April (3)
    • ►  March (2)
    • ►  February (3)
    • ►  January (3)
  • ►  2012 (43)
    • ►  December (5)
    • ►  November (3)
    • ►  October (3)
    • ►  September (1)
    • ►  August (1)
    • ►  July (2)
    • ►  June (5)
    • ►  May (1)
    • ►  April (5)
    • ►  March (6)
    • ►  February (5)
    • ►  January (6)
  • ►  2011 (67)
    • ►  December (7)
    • ►  November (7)
    • ►  October (5)
    • ►  September (5)
    • ►  August (3)
    • ►  July (7)
    • ►  June (3)
    • ►  May (5)
    • ►  April (6)
    • ►  March (8)
    • ►  February (7)
    • ►  January (4)
  • ▼  2010 (72)
    • ►  December (8)
    • ►  November (3)
    • ►  October (4)
    • ►  September (8)
    • ►  August (6)
    • ►  July (9)
    • ▼  June (11)
      • Exercising Our Remote Application Removal Feature
      • Android Market Problem
      • The Froyo Code Drop
      • Hands-on at OSCON
      • Future-Proofing Your App
      • Game Development for Android: A Quick Primer
      • Download Count Problems
      • Blogging Round the World
      • Making Sense of Multitouch
      • Application Visibility Issues
      • Allowing applications to play nice(r) with each ot...
    • ►  May (11)
    • ►  April (2)
    • ►  March (3)
    • ►  February (2)
    • ►  January (5)
  • ►  2009 (63)
    • ►  December (7)
    • ►  November (5)
    • ►  October (5)
    • ►  September (8)
    • ►  August (2)
    • ►  July (1)
    • ►  June (2)
    • ►  May (5)
    • ►  April (12)
    • ►  March (5)
    • ►  February (8)
    • ►  January (3)
  • ►  2008 (40)
    • ►  December (3)
    • ►  November (1)
    • ►  October (4)
    • ►  September (6)
    • ►  August (4)
    • ►  June (1)
    • ►  May (5)
    • ►  April (4)
    • ►  March (5)
    • ►  February (2)
    • ►  January (5)
  • ►  2007 (8)
    • ►  December (3)
    • ►  November (5)
Powered by Blogger.

About Me

Unknown
View my complete profile