当你用耳机听音乐的时候,想通过耳机上的按钮来快速切换下一首、上一首音乐是不是很方便呢!如果,你的手机有这样一个功能当然是不错的!下面来看看我是如何实现的!
对于这个功能的开发,首先要知道两点:(1)耳机按键事件如何获取(短按和长按事件);(2)如果切换上一首音乐、切换下一首音乐。所以我们先来解决这两个问题:
(1)耳机按键事件如何获取。
首先来看看耳机按钮的事件是否有传递到上层framwork。查上耳机、连接USB,然后通过log查看工具,我们通过打印的LOG信息可以很快知道,其事件是会上传上来的。
01-01 01:07:44.524: D/WindowManager(514): interceptKeyTi keyCode=85 down=false repeatCount=0 keyguardOn=false mHomePressed=false canceled=false
01-01 01:07:47.644: D/WindowManager(514): interceptKeyTi keyCode=86 down=true repeatCount=0 keyguardOn=false mHomePressed=false canceled=false
通过上面的LOG信息,你可以知道长按耳机上的按钮,其 keyCode=86;短按耳机上的按钮,其 keyCode=85;
PhoneWindowManager.java里面的这方法interceptKeyBeforeQueueing就是按键时间的接口函数!
(2)如果切换上一首音乐、切换下一首音乐。
我们知道,音乐的播放其实是运行一个service的!在Music里面,我们可以很快知道这个service其实就是:MediaPlaybackService。通过这个service 的onStartCommand方法可以知道,在onStartCommand里面其实是根据intent带回来的不同参数来控制音乐播放的,比如暂停、播放、下一首、上一首等。
上面两个问题已经解决了!接下来的问题就是如果把这两个问题关联起来!也就是耳机事件如何传递到这个music的app里面来!说的具体一点,就是耳机按键时间如果传递到这个播放音乐的service里面来!!我们知道service的直接父类其实是ContextWrapper,想对于activity,并没有实现Window.Callback, KeyEvent.Callback,这两个接口,所以更本上是无法接受这个耳机按键事件的!
那怎么解决这个问题呢!很快想到了BroadcastReceiver,用BroadcastReceiver来接受按键事件! 在music这个app里面定义一个BroadcastReceiver,在PhoneWindowManager里面把检测到的事件发送到我们再music里面定义的BroadcastReceiver。这样问题就解决了!
以上就是解决这个问题的基本思路!不过,一些细微的问题需要注意,下面一个个来说:
(1)在music定义的BroadcastReceiver由于是监听这个app外面的广播事件,所以只是在代码里面动态注册是不可取的!必须AndroidManifest.xml这样定义:
<receiver android:name="com.android.music.HEADSET_key_Receiver"
android:exported="true" >
<intent-filter>
<action android:name="com.android.music.HEADSET_key_input" />
</intent-filter>
</receiver>
这里的android:exported="true" 是必须的。
(2)在PhoneWindowManager中,检测到耳机按键事件,如下方式发送广播:
Intent intent = new Intent();
intent.putExtra("music_change", music_change);
intent.setClassName("com.android.music", "com.android.music.HEADSET_key_Receiver");//
intent.setAction("com.android.music.HEADSET_key_input");
这里最好setClassName,这样一来效率高。
(3)在PhoneWindowManager中,检测到耳机按键的事件是down事件。
(4)在PhoneWindowManager中,检测到耳机按键事件时候最好能检测一下手机是否在播放音乐:
boolean isMusicActive() {
final AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
if (am == null) {
Log.w(TAG, "isMusicActive: couldn‘t get AudioManager reference");
return false;
}
return am.isMusicActive();
}
还需要检测一下在在通话过程中incall
ITelephony telephonyService = getTelephonyService();
if (telephonyService != null) {// 这里检测一下是否当前正在通常 incall
try {
if (!telephonyService.isIdle()) {
// Suppress PLAY/PAUSE toggle when phone is ringing or in-call
// to avoid music playback.
return;
}
} catch (RemoteException ex) {
Log.w(TAG, "ITelephony threw RemoteException", ex);
}
}
(5)在music定义的BroadcastReceiver中在start播放音乐的service的时候,最好能够检测一下这个service是否处于运行当中:
private boolean isMusicServiceRunning(Context context) {
boolean isServiceRuning = false;
ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
final int maxServciesNum = 100;
List<RunningServiceInfo> list = am.getRunningServices(maxServciesNum);
for (RunningServiceInfo info : list) {
if (MediaPlaybackService.class.getName().equals(info.service.getClassName())) {
isServiceRuning = true;
break;
}
}
// MusicLogUtils.d("yuyongjun", "isMusicServiceRunning " + isServiceRuning + ", Runing service num is " + list.size());
return isServiceRuning;
}
(6)尽管播放音乐的service是一个运行的service,但是并不代表现在正在播放音乐(比如 暂停播放等)。所以,是否正在播放音乐其实需要在这个service(MediaPlaybackService)中才能确认。所以,我们start这个service的时候需要带一个标识:
Intent i = new Intent(context, MediaPlaybackService.class);
i.setAction(MediaPlaybackService.SERVICECMD);
i.putExtra(MediaPlaybackService.CMDNAME, command);
i.putExtra(MediaPlaybackService.DELTATIME, deltaTime);
i.putExtra(HEADSET_key_input, "true");
context.startService(i);
上面的 i.putExtra(HEADSET_key_input, "true");用于标示是耳机按钮事件触发。所以在MediaPlaybackService的onStartCommand有如下代码判断。
// yuyongjun _start 2014-4-16 FeatureOption.BASICOM_HEADSET_KEY_CONTROL_MUSIC_CHANGE_SONG
String HEADSET_key_input = intent.getStringExtra(HEADSET_key_Receiver.HEADSET_key_input);
if(HEADSET_key_input != null && HEADSET_key_input.equals("true"))
{
if(false == mIsSupposedToBePlaying)
{
return START_STICKY;
}
MusicLogUtils.d("yuyongjun", "onStartCommand: HEADSET_key_input");
}// yuyongjun _end 2014-4-16