/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v4.media.session; import android.app.Service; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.support.v4.media.session.MediaControllerCompat; import android.support.v4.media.session.MediaSessionCompat; import android.view.KeyEvent; import java.util.List; /** * A media button receiver receives and helps translate hardware media playback buttons, * such as those found on wired and wireless headsets, into the appropriate callbacks * in your app. * <p /> * You can add this MediaButtonReceiver to your app by adding it directly to your * AndroidManifest.xml: * <pre> * <receiver android:name="android.support.v4.media.session.MediaButtonReceiver" > * <intent-filter> * <action android:name="android.intent.action.MEDIA_BUTTON" /> * </intent-filter> * </receiver> * </pre> * This class assumes you have a {@link Service} in your app that controls * media playback via a {@link MediaSessionCompat}. That {@link Service} must * include an intent filter that also handles {@link Intent#ACTION_MEDIA_BUTTON}: * <pre> * <service android:name="com.example.android.MediaPlaybackService" > * <intent-filter> * <action android:name="android.intent.action.MEDIA_BUTTON" /> * </intent-filter> * </service> * </pre> * * All {@link Intent}s sent to this MediaButtonReceiver will then be forwarded * to the {@link Service}. Events can then be handled in * {@link Service#onStartCommand(Intent, int, int)} by calling * {@link MediaButtonReceiver#handleIntent(MediaSessionCompat, Intent)}, passing in * your current {@link MediaSessionCompat}: * <pre> * private MediaSessionCompat mMediaSessionCompat = ...; * * public int onStartCommand(Intent intent, int flags, int startId) { * MediaButtonReceiver.handleIntent(mMediaSessionCompat, intent); * return super.onStartCommand(intent, flags, startId); * } * </pre> * * This ensures that the correct callbacks to {@link MediaSessionCompat.Callback} * will be triggered based on the incoming {@link KeyEvent}. */ public class MediaButtonReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Intent queryIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); queryIntent.setPackage(context.getPackageName()); PackageManager pm = context.getPackageManager(); List<ResolveInfo> resolveInfos = pm.queryIntentServices(queryIntent, 0); if (resolveInfos.size() != 1) { throw new IllegalStateException("Expected 1 Service that handles " + Intent.ACTION_MEDIA_BUTTON + ", found " + resolveInfos.size()); } ResolveInfo resolveInfo = resolveInfos.get(0); ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); intent.setComponent(componentName); context.startService(intent); } /** * Extracts any available {@link KeyEvent} from an {@link Intent#ACTION_MEDIA_BUTTON} * intent, passing it onto the {@link MediaSessionCompat} using * {@link MediaControllerCompat#dispatchMediaButtonEvent(KeyEvent)}, which in turn * will trigger callbacks to the {@link MediaSessionCompat.Callback} registered via * {@link MediaSessionCompat#setCallback(MediaSessionCompat.Callback)}. * <p /> * The returned {@link KeyEvent} is non-null if any {@link KeyEvent} is found and can * be used if any additional processing is needed beyond what is done in the * {@link MediaSessionCompat.Callback}. An example of is to prevent redelivery of a * {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE} Intent in the case of the Service being * restarted (which, by default, will redeliver the last received Intent). * <pre> * KeyEvent keyEvent = MediaButtonReceiver.handleIntent(mediaSession, intent); * if (keyEvent != null && keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { * Intent emptyIntent = new Intent(intent); * emptyIntent.setAction(""); * startService(emptyIntent); * } * </pre> * @param mediaSessionCompat A {@link MediaSessionCompat} that has a * {@link MediaSessionCompat.Callback} set. * @param intent The intent to parse. * @return The extracted {@link KeyEvent} if found, or null. */ public static KeyEvent handleIntent(MediaSessionCompat mediaSessionCompat, Intent intent) { if (mediaSessionCompat == null || intent == null || !Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) || !intent.hasExtra(Intent.EXTRA_KEY_EVENT)) { return null; } KeyEvent ke = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); MediaControllerCompat mediaController = mediaSessionCompat.getController(); mediaController.dispatchMediaButtonEvent(ke); return ke; } }