您的位置:首页 > 移动开发 > Android开发

Android按键添加和处理的方案

2013-06-18 18:40 615 查看
Android按键添加和处理的方案

版本号说明作者日期
1.0 Android按键添加和处理的方案

Sky Wang 2013/06/18
需求:Android机器上有个Wifi物理按键,现在需求通过点击“wifi物理按键”能够快速的开启/关闭wifi。

实现方案
经过思考之后,拟出下面几种方案:
方案一,在linux kernel的驱动中捕获“wifi物理按键”。在kernel的按键驱动中截获“wifi”按键,并对其进行处理:若是“wifi”是开启的,则关闭wifi;否则,打开wifi。
方案二,在Android中添加一个服务,监听wifi按键消息。若监听到“wifi”按键,则读取wifi的状态:若是“wifi”是开启的,则关闭wifi;否则,打开wifi。
方案三,在Android的input输入子系统的框架层中捕获wifi按键,并进行相应处理。若捕获到“wifi”按键,则读取wifi的状态:若是“wifi”是开启的,则关闭wifi;否则,打开wifi。

方案一

方案思路: 在linux kernel的驱动中捕获“wifi物理按键”。在kernel的按键驱动中截获“wifi”按键,并对其进行处理:若是“wifi”是开启的,则关闭wifi;否则,打开wifi。

方案分析: 若采用此方案需要解决以下问题
01,在kerne的按键驱动中捕获“wifi”按键。
-- 这个问题很好实现。在kernel的按键驱动中,对按键值进行判断,若是wifi按键,则进行相应处理。
02,在kernel中读取并设置wifi的开/关状态。
-- 这个较难实现。因为wifi驱动的开/关相关的API很难获取到。一般来来说,wifi模组的驱动都是wifi厂家写好并以.ko文件加载的。若需要获取wifi的操作API,需要更厂家一起合作;让它们将接口开放,并让其它设备在kernel中可以读取到。
03,在kernel中将wifi的状态上报到Android系统中。若单单只是实现02步,只是简单的能开/关wifi了;但还需要向办法让Android系统直到wifi的开/关行为。
-- 可以实现,但是太麻烦了。

方案结论: 实现难度太大!

方案二

方案思路: 在Android中添加一个服务,监听wifi按键消息。若监听到“wifi”按键,则读取wifi的状态:若是“wifi”是开启的,则关闭wifi;否则,打开wifi。

方案分析: 若采用此方案需要解决以下问题
01,将kernel的wifi按键上传到Android系统中。
-- 这个可以实现。首先,我们将wifi按键映射到一个sys文件节点上:按下wifi按键时,sys文件节点的值为1;未按下wifi按键时,sys文件节点的值为0。其次,通过NDK编程,读取该sys文件节点,并将读取的接口映射注册到JNI中。最后,通过JNI,将该接口对应注册到Android系统中,使应用程序能够读取该接口。
02,在Android系统中添加一个服务,不断读取wifi按键状态。
-- 这个也可以实现。由于“01”中,我们已经将wifi的按键状态通过JNI注册到Android系统中;我们这里就可以读取到。
03,读取并设置wifi的开/关状态。
-- 这个也可以实现。在Android系统中,我们可以通过WifiManager去读取/设置wifi的开/关状态。通过WifiManager设置的wifi状态,是全局的。

架构图:

public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) {
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final boolean canceled = event.isCanceled();
final int keyCode = event.getKeyCode();

final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0;

final boolean keyguardActive = (mKeyguardMediator == null ? false :
(isScreenOn ?
mKeyguardMediator.isShowingAndNotHidden() :
mKeyguardMediator.isShowing()));

if (!mSystemBooted) {
return 0;
}

if (DEBUG_INPUT) {
Log.d(TAG, "interceptKeyTq keycode=" + keyCode
+ " screenIsOn=" + isScreenOn + " keyguardActive=" + keyguardActive);
}

if (down && (policyFlags & WindowManagerPolicy.FLAG_VIRTUAL) != 0
&& event.getRepeatCount() == 0) {
performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);
}

if (keyCode == KeyEvent.KEYCODE_POWER) {
policyFlags |= WindowManagerPolicy.FLAG_WAKE;
}
final boolean isWakeKey = (policyFlags & (WindowManagerPolicy.FLAG_WAKE
| WindowManagerPolicy.FLAG_WAKE_DROPPED)) != 0;

int result;
if ((isScreenOn && !mHeadless) || (isInjected && !isWakeKey)) {
// When the screen is on or if the key is injected pass the key to the application.
result = ACTION_PASS_TO_USER;
} else {
// When the screen is off and the key is not injected, determine whether
// to wake the device but don't pass the key to the application.
result = 0;
if (down && isWakeKey) {
if (keyguardActive) {
// If the keyguard is showing, let it decide what to do with the wake key.
mKeyguardMediator.onWakeKeyWhenKeyguardShowingTq(keyCode,
mDockMode != Intent.EXTRA_DOCK_STATE_UNDOCKED);
} else {
// Otherwise, wake the device ourselves.
result |= ACTION_POKE_USER_ACTIVITY;
}
}
}

// Handle special keys.
switch (keyCode) {
case KeyEvent.KEYCODE_SYSRQ: {
if (!down) {
printScreenSysRq();
}
break;
}
case KeyEvent.KEYCODE_AVR_POWER: {

Log.d("##SKYWANG##", "global keycode:"+keyCode);
if (keyCode == KeyEvent.KEYCODE_AVR_POWER && down==false) {
// Wifi按键处理
WifiManager mWM = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
boolean bWifi = mWM.isWifiEnabled();
mWM.setWifiEnabled(!bWifi);
}
break;
}
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (down) {
if (isScreenOn && !mVolumeDownKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mVolumeDownKeyTriggered = true;
mVolumeDownKeyTime = event.getDownTime();
mVolumeDownKeyConsumedByScreenshotChord = false;
cancelPendingPowerKeyAction();
interceptScreenshotChord();
}
} else {
mVolumeDownKeyTriggered = false;
cancelPendingScreenshotChordAction();
}
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
if (down) {
if (isScreenOn && !mVolumeUpKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mVolumeUpKeyTriggered = true;
cancelPendingPowerKeyAction();
cancelPendingScreenshotChordAction();
}
} else {
mVolumeUpKeyTriggered = false;
cancelPendingScreenshotChordAction();
}
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
// add by skywang
if (!down)
handleMuteKey() ;
}

if (down) {
ITelephony telephonyService = getTelephonyService();
if (telephonyService != null) {
try {
if (telephonyService.isRinging()) {
// If an incoming call is ringing, either VOLUME key means
// "silence ringer".  We handle these keys here, rather than
// in the InCallScreen, to make sure we'll respond to them
// even if the InCallScreen hasn't come to the foreground yet.
// Look for the DOWN event here, to agree with the "fallback"
// behavior in the InCallScreen.
Log.i(TAG, "interceptKeyBeforeQueueing:"
+ " VOLUME key-down while ringing: Silence ringer!");

// Silence the ringer.  (It's safe to call this
// even if the ringer has already been silenced.)
telephonyService.silenceRinger();

// And *don't* pass this key thru to the current activity
// (which is probably the InCallScreen.)
result &= ~ACTION_PASS_TO_USER;
break;
}
if (telephonyService.isOffhook()
&& (result & ACTION_PASS_TO_USER) == 0) {
// If we are in call but we decided not to pass the key to
// the application, handle the volume change here.
handleVolumeKey(AudioManager.STREAM_VOICE_CALL, keyCode);
break;
}
} catch (RemoteException ex) {
Log.w(TAG, "ITelephony threw RemoteException", ex);
}
}

if (isMusicActive() && (result & ACTION_PASS_TO_USER) == 0) {
// If music is playing but we decided not to pass the key to the
// application, handle the volume change here.
handleVolumeKey(AudioManager.STREAM_MUSIC, keyCode);
break;
}
}
break;
}

case KeyEvent.KEYCODE_ENDCALL: {
result &= ~ACTION_PASS_TO_USER;
if (down) {
ITelephony telephonyService = getTelephonyService();
boolean hungUp = false;
if (telephonyService != null) {
try {
hungUp = telephonyService.endCall();
} catch (RemoteException ex) {
Log.w(TAG, "ITelephony threw RemoteException", ex);
}
}
interceptPowerKeyDown(!isScreenOn || hungUp);
} else {
if (interceptPowerKeyUp(canceled)) {
if ((mEndcallBehavior
& Settings.System.END_BUTTON_BEHAVIOR_HOME) != 0) {
if (goHome()) {
break;
}
}
if ((mEndcallBehavior
& Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0) {
result = (result & ~ACTION_POKE_USER_ACTIVITY) | ACTION_GO_TO_SLEEP;
}
}
}
break;
}

case KeyEvent.KEYCODE_POWER: {
if(mHdmiPlugged&&SystemProperties.get("ro.hdmi.power_disable","false").equals("true")){
Log.d("hdmi","power disable---------------");
result=0;
break;
}
result &= ~ACTION_PASS_TO_USER;
if (down) {
if (isScreenOn && !mPowerKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mPowerKeyTriggered = true;
mPowerKeyTime = event.getDownTime();
interceptScreenshotChord();
}

ITelephony telephonyService = getTelephonyService();
boolean hungUp = false;
if (telephonyService != null) {
try {
if (telephonyService.isRinging()) {
// Pressing Power while there's a ringing incoming
// call should silence the ringer.
telephonyService.silenceRinger();
} else if ((mIncallPowerBehavior
& Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
&& telephonyService.isOffhook()) {
// Otherwise, if "Power button ends call" is enabled,
// the Power button will hang up any current active call.
hungUp = telephonyService.endCall();
}
} catch (RemoteException ex) {
Log.w(TAG, "ITelephony threw RemoteException", ex);
}
}
interceptPowerKeyDown(!isScreenOn || hungUp
|| mVolumeDownKeyTriggered || mVolumeUpKeyTriggered);
} else {
mPowerKeyTriggered = false;
cancelPendingScreenshotChordAction();
if (interceptPowerKeyUp(canceled || mPendingPowerKeyUpCanceled)) {
result = (result & ~ACTION_POKE_USER_ACTIVITY) | ACTION_GO_TO_SLEEP;
}
mPendingPowerKeyUpCanceled = false;
}
break;
}

case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
if (down) {
ITelephony telephonyService = getTelephonyService();
if (telephonyService != null) {
try {
if (!telephonyService.isIdle()) {
// Suppress PLAY/PAUSE toggle when phone is ringing or in-call
// to avoid music playback.
break;
}
} catch (RemoteException ex) {
Log.w(TAG, "ITelephony threw RemoteException", ex);
}
}
}
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
if ((result & ACTION_PASS_TO_USER) == 0) {
// Only do this if we would otherwise not pass it to the user. In that
// case, the PhoneWindow class will do the same thing, except it will
// only do it if the showing app doesn't process the key on its own.
// Note that we need to make a copy of the key event here because the
// original key event will be recycled when we return.
mBroadcastWakeLock.acquire();
Message msg = mHandler.obtainMessage(MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK,
new KeyEvent(event));
msg.setAsynchronous(true);
msg.sendToTarget();
}
break;
}

case KeyEvent.KEYCODE_CALL: {
if (down) {
ITelephony telephonyService = getTelephonyService();
if (telephonyService != null) {
try {
if (telephonyService.isRinging()) {
Log.i(TAG, "interceptKeyBeforeQueueing:"
+ " CALL key-down while ringing: Answer the call!");
telephonyService.answerRingingCall();

// And *don't* pass this key thru to the current activity
// (which is presumably the InCallScreen.)
result &= ~ACTION_PASS_TO_USER;
}
} catch (RemoteException ex) {
Log.w(TAG, "ITelephony threw RemoteException", ex);
}
}
}
break;
}
}
return result;
}


View Code
在上面的代码中,我们捕获了KeyEvent.KEYCODE_AVR_POWER,并对其进行处理。

方案结论: 方案可行。而且运行效率比“方案二”高,不会造成功耗很大的问题。

最终总结:方案三最好!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: