MediaButtonReceiver.java revision d3c5347b3ec0025ec906e2053eaa9b97287c46a5
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.media.session;
18
19import android.app.Service;
20import android.content.BroadcastReceiver;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.support.v4.media.MediaBrowserServiceCompat;
27import android.support.v4.media.session.MediaControllerCompat;
28import android.support.v4.media.session.MediaSessionCompat;
29import android.view.KeyEvent;
30
31import java.util.List;
32
33/**
34 * A media button receiver receives and helps translate hardware media playback buttons,
35 * such as those found on wired and wireless headsets, into the appropriate callbacks
36 * in your app.
37 * <p />
38 * You can add this MediaButtonReceiver to your app by adding it directly to your
39 * AndroidManifest.xml:
40 * <pre>
41 * &lt;receiver android:name="android.support.v4.media.session.MediaButtonReceiver" &gt;
42 *   &lt;intent-filter&gt;
43 *     &lt;action android:name="android.intent.action.MEDIA_BUTTON" /&gt;
44 *   &lt;/intent-filter&gt;
45 * &lt;/receiver&gt;
46 * </pre>
47 * This class assumes you have a {@link Service} in your app that controls
48 * media playback via a {@link MediaSessionCompat} - all {@link Intent}s received by
49 * the MediaButtonReceiver will be forwarded to that service.
50 * <p />
51 * First priority is given to a {@link Service}
52 * that includes an intent filter that handles {@link Intent#ACTION_MEDIA_BUTTON}:
53 * <pre>
54 * &lt;service android:name="com.example.android.MediaPlaybackService" &gt;
55 *   &lt;intent-filter&gt;
56 *     &lt;action android:name="android.intent.action.MEDIA_BUTTON" /&gt;
57 *   &lt;/intent-filter&gt;
58 * &lt;/service&gt;
59 * </pre>
60 *
61 * If such a {@link Service} is not found, MediaButtonReceiver will attempt to
62 * find a media browser service implementation.
63 * If neither is available or more than one valid service/media browser service is found, an
64 * {@link IllegalStateException} will be thrown.
65 * <p />
66 * Events can then be handled in {@link Service#onStartCommand(Intent, int, int)} by calling
67 * {@link MediaButtonReceiver#handleIntent(MediaSessionCompat, Intent)}, passing in
68 * your current {@link MediaSessionCompat}:
69 * <pre>
70 * private MediaSessionCompat mMediaSessionCompat = ...;
71 *
72 * public int onStartCommand(Intent intent, int flags, int startId) {
73 *   MediaButtonReceiver.handleIntent(mMediaSessionCompat, intent);
74 *   return super.onStartCommand(intent, flags, startId);
75 * }
76 * </pre>
77 *
78 * This ensures that the correct callbacks to {@link MediaSessionCompat.Callback}
79 * will be triggered based on the incoming {@link KeyEvent}.
80 */
81public class MediaButtonReceiver extends BroadcastReceiver {
82    @Override
83    public void onReceive(Context context, Intent intent) {
84        Intent queryIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
85        queryIntent.setPackage(context.getPackageName());
86        PackageManager pm = context.getPackageManager();
87        List<ResolveInfo> resolveInfos = pm.queryIntentServices(queryIntent, 0);
88        if (resolveInfos.isEmpty()) {
89            // Fall back to looking for any available media browser service
90            queryIntent.setAction(MediaBrowserServiceCompat.SERVICE_INTERFACE);
91            resolveInfos = pm.queryIntentServices(queryIntent, 0);
92        }
93        if (resolveInfos.isEmpty()) {
94            throw new IllegalStateException("Could not find any Service that handles " +
95                    Intent.ACTION_MEDIA_BUTTON + " or a media browser service implementation");
96        } else if (resolveInfos.size() != 1) {
97            throw new IllegalStateException("Expected 1 Service that handles " +
98                    queryIntent.getAction() + ", found " + resolveInfos.size() );
99        }
100        ResolveInfo resolveInfo = resolveInfos.get(0);
101        ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName,
102                resolveInfo.serviceInfo.name);
103        intent.setComponent(componentName);
104        context.startService(intent);
105    }
106
107    /**
108     * Extracts any available {@link KeyEvent} from an {@link Intent#ACTION_MEDIA_BUTTON}
109     * intent, passing it onto the {@link MediaSessionCompat} using
110     * {@link MediaControllerCompat#dispatchMediaButtonEvent(KeyEvent)}, which in turn
111     * will trigger callbacks to the {@link MediaSessionCompat.Callback} registered via
112     * {@link MediaSessionCompat#setCallback(MediaSessionCompat.Callback)}.
113     * <p />
114     * The returned {@link KeyEvent} is non-null if any {@link KeyEvent} is found and can
115     * be used if any additional processing is needed beyond what is done in the
116     * {@link MediaSessionCompat.Callback}. An example of is to prevent redelivery of a
117     * {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE} Intent in the case of the Service being
118     * restarted (which, by default, will redeliver the last received Intent).
119     * <pre>
120     * KeyEvent keyEvent = MediaButtonReceiver.handleIntent(mediaSession, intent);
121     * if (keyEvent != null && keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
122     *   Intent emptyIntent = new Intent(intent);
123     *   emptyIntent.setAction("");
124     *   startService(emptyIntent);
125     * }
126     * </pre>
127     * @param mediaSessionCompat A {@link MediaSessionCompat} that has a
128     *            {@link MediaSessionCompat.Callback} set.
129     * @param intent The intent to parse.
130     * @return The extracted {@link KeyEvent} if found, or null.
131     */
132    public static KeyEvent handleIntent(MediaSessionCompat mediaSessionCompat, Intent intent) {
133        if (mediaSessionCompat == null || intent == null
134                || !Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
135                || !intent.hasExtra(Intent.EXTRA_KEY_EVENT)) {
136            return null;
137        }
138        KeyEvent ke = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
139        MediaControllerCompat mediaController = mediaSessionCompat.getController();
140        mediaController.dispatchMediaButtonEvent(ke);
141        return ke;
142    }
143}
144
145