转自 http://blog.csdn.net/wdaming1986/article/details/7539600
如果想在Android手机要想扩展一个实体键,就我知道而言有两种方法,基于Android4.0的源码来分析的和2.3的源码有点区别,区别不大,下面分享给大家:
转载请标明出处:
(一)可以在frameworks层的KeyEvent.java这个类中定义一个值,在PhoneWindowManager.java这个类中做处理就可以了。(Home键就是这么实现的)。效果图如下:
(二)可以利用广播的形式,frameworks层PhoneWindow.java这个类的onKeyDown( )对这个实体键发广播,上层接受这个广播来处理也可以达到这个效果。耳机键就是利用广播来接受的。无论在哪个界面长按耳机键,都会进入到音乐的界面。(长按耳机键的)效果图如下:
下面我详细展开来说明一下:
一、先说Home键的实现的大致流程,即---->为什么点击Home键,都进入到launcher的待机界面;
(1)Home键的定义在
step1: frameworks/base/core/java/android/view/KeyEvent.java这个类中,在KeyEvent.java这个类中有个static的静态块:
- static {
- populateKeycodeSymbolicNames();
- }
step2: 这个populateKeycodeSymbolicNames()方法其实就是加载了许多键的定义,把这些键对应的值都放到Array数组中。
- <span style="font-size: 16px;">private static void populateKeycodeSymbolicNames() {
- SparseArray<String> names = KEYCODE_SYMBOLIC_NAMES;
- names.append(KEYCODE_UNKNOWN, "KEYCODE_UNKNOWN");
- names.append(KEYCODE_SOFT_LEFT, "KEYCODE_SOFT_LEFT");
- names.append(KEYCODE_SOFT_RIGHT, "KEYCODE_SOFT_RIGHT");
- names.append(KEYCODE_HOME, "KEYCODE_HOME");
- names.append(KEYCODE_BACK, "KEYCODE_BACK");
- names.append(KEYCODE_CALL, "KEYCODE_CALL");
- names.append(KEYCODE_ENDCALL, "KEYCODE_ENDCALL");
- names.append(KEYCODE_0, "KEYCODE_0");
- names.append(KEYCODE_1, "KEYCODE_1");
- names.append(KEYCODE_2, "KEYCODE_2");
- names.append(KEYCODE_3, "KEYCODE_3");
- names.append(KEYCODE_4, "KEYCODE_4");
- names.append(KEYCODE_5, "KEYCODE_5");
- names.append(KEYCODE_6, "KEYCODE_6");
- names.append(KEYCODE_7, "KEYCODE_7");
- names.append(KEYCODE_8, "KEYCODE_8");
- names.append(KEYCODE_9, "KEYCODE_9");</span>
step3: 而Home键对应的值如下:
- <span style="font-size: 16px;">/** Key code constant: Home key.
- * This key is handled by the framework and is never delivered to applications. */
- public static final int KEYCODE_HOME = 3;</span>
(2)Home键的处理如下:在
step1: frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java这个类中:
在这个方法interceptKeyBeforeDispatching(... ... ...)中处理有对Home,Search,menu,音量大小键等等:
- <span style="font-size: 16px;"> /** {@inheritDoc} */
- @Override
- public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
- final boolean keyguardOn = keyguardOn();
- final int keyCode = event.getKeyCode();
- final int repeatCount = event.getRepeatCount();
- final int metaState = event.getMetaState();
- final int flags = event.getFlags();
- final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
- final boolean canceled = event.isCanceled();
- if (false) {
- Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
- + repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed);
- }
- // If we think we might have a volume down & power key chord on the way
- // but we're not sure, then tell the dispatcher to wait a little while and
- // try again later before dispatching.
- if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
- if (mVolumeDownKeyTriggered && !mPowerKeyTriggered) {
- final long now = SystemClock.uptimeMillis();
- final long timeoutTime = mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS;
- if (now < timeoutTime) {
- return timeoutTime - now;
- }
- }
- if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
- && mVolumeDownKeyConsumedByScreenshotChord) {
- if (!down) {
- mVolumeDownKeyConsumedByScreenshotChord = false;
- }
- return -1;
- }
- }
- // First we always handle the home key here, so applications
- // can never break it, although if keyguard is on, we do let
- // it handle it, because that gives us the correct 5 second
- // timeout.
- if (keyCode == KeyEvent.KEYCODE_HOME) {
- // If we have released the home key, and didn't do anything else
- // while it was pressed, then it is time to go home!
- if (mHomePressed && !down) {
- mHomePressed = false;
- if (!canceled) {
- // If an incoming call is ringing, HOME is totally disabled.
- // (The user is already on the InCallScreen at this point,
- // and his ONLY options are to answer or reject the call.)
- boolean incomingRinging = false;
- try {
- ITelephony telephonyService = getTelephonyService();
- if (telephonyService != null) {
- incomingRinging = telephonyService.isRinging();
- }
- } catch (RemoteException ex) {
- Log.w(TAG, "RemoteException from getPhoneInterface()", ex);
- }
- if (incomingRinging) {
- Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");
- } else {
- launchHomeFromHotKey();
- }
- } else {
- Log.i(TAG, "Ignoring HOME; event canceled.");
- }
- return -1;
- }
- // If a system window has focus, then it doesn't make sense
- // right now to interact with applications.
- WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
- if (attrs != null) {
- final int type = attrs.type;
- if (type == WindowManager.LayoutParams.TYPE_KEYGUARD
- || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
- // the "app" is keyguard, so give it the key
- return 0;
- }
- final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
- for (int i=0; i<typeCount; i++) {
- if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
- // don't do anything, but also don't pass it to the app
- return -1;
- }
- }
- }
- if (down) {
- if (repeatCount == 0) {
- mHomePressed = true;
- } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
- if (!keyguardOn) {
- handleLongPressOnHome();
- }
- }
- }
- return -1;
- } else if (keyCode == KeyEvent.KEYCODE_MENU) {
- ........</span>
- <span style="font-size: 16px;"></span>
- <span style="font-size: 24px;"> </span><span style="font-size: 18px;">Step2: <span style="color: rgb(153, 0, 0);"><strong>插曲</strong></span>《<span style="color: rgb(0, 102, 0);"><strong>网上有例子说怎么在自己的应用中屏蔽Home键</strong></span>》--->原理:是在你的应用的Activity中加入了锁屏的type,因为系统对锁屏界面,点击Home键失效!网摘代码如下:</span>
- public class DMActivity extends Activity {
- private boolean flag = true;//true位屏蔽,false位不屏蔽
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
- @Override
- public void onAttachedToWindow() {
- if(flag) {
- this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
- }
- super.onAttachedToWindow();
- }
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if(keyCode == KeyEvent.KEYCODE_HOME){
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
- }
Step3: 真正的原因如下,对锁屏模式的处理:
- <span style="font-size: 16px;"> // If a system window has focus, then it doesn't make sense
- // right now to interact with applications.
- WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
- if (attrs != null) {
- final int type = attrs.type;
- if (type == WindowManager.LayoutParams.TYPE_KEYGUARD
- || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
- // the "app" is keyguard, so give it the key
- return 0;
- }
- final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
- for (int i=0; i<typeCount; i++) {
- if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
- // don't do anything, but also don't pass it to the app
- return -1;
- }
- }
- }</span>
Step4: 我们来看点击home键,为什么进入到launcher的待机界面:
- <span style="font-size: 16px;">// If we have released the home key, and didn't do anything else
- // while it was pressed, then it is time to go home!
- if (mHomePressed && !down) {
- mHomePressed = false;
- if (!canceled) {
- // If an incoming call is ringing, HOME is totally disabled.
- // (The user is already on the InCallScreen at this point,
- // and his ONLY options are to answer or reject the call.)
- boolean incomingRinging = false;
- try {
- ITelephony telephonyService = getTelephonyService();
- if (telephonyService != null) {
- incomingRinging = telephonyService.isRinging();
- }
- } catch (RemoteException ex) {
- Log.w(TAG, "RemoteException from getPhoneInterface()", ex);
- }
- if (incomingRinging) {
- Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");
- } else {
- launchHomeFromHotKey();
- }
- } else {
- Log.i(TAG, "Ignoring HOME; event canceled.");
- }
- return -1;
- }</span>
Step5: 系统会判断,当前点击Home键并且没有电话打入的情况,才对Home键进行处理---->launchHomeFromHotKey();进入到----->launchHomeFromHotKey()方法中:
- /**
- * A home key -> launch home action was detected. Take the appropriate action
- * given the situation with the keyguard.
- */
- void launchHomeFromHotKey() {
- if (mKeyguardMediator.isShowingAndNotHidden()) {
- // don't launch home if keyguard showing
- } else if (!mHideLockScreen && mKeyguardMediator.isInputRestricted()) {
- // when in keyguard restricted mode, must first verify unlock
- // before launching home
- mKeyguardMediator.verifyUnlock(new OnKeyguardExitResult() {
- public void onKeyguardExitResult(boolean success) {
- if (success) {
- try {
- ActivityManagerNative.getDefault().stopAppSwitches();
- } catch (RemoteException e) {
- }
- sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
- startDockOrHome();
- }
- }
- });
- } else {
- // no keyguard stuff to worry about, just launch home!
- try {
- ActivityManagerNative.getDefault().stopAppSwitches();
- } catch (RemoteException e) {
- }
- sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
- startDockOrHome();
- }
- }
Step6: 也是对锁屏模式有个判断,如果不在锁屏模式,就launch Home---->startDockOrHome(),进入——>
- <span style="font-size: 16px;"> void startDockOrHome() {
- Intent dock = createHomeDockIntent();
- if (dock != null) {
- try {
- mContext.startActivity(dock);
- return;
- } catch (ActivityNotFoundException e) {
- }
- }
- mContext.startActivity(mHomeIntent);
- }</span>
Step 7: 其实这个createHomeDockIntent()方法就是对android手机的几种模式进行判断,
The device is not in either car mode or desk mode
The device is in car mode but ENABLE_CAR_DOCK_HOME_CAPTURE is false
The device is in desk mode but ENABLE_DESK_DOCK_HOME_CAPTURE is false
The device is in car mode but there's no CAR_DOCK app with METADATA_DOCK_HOME
The device is in desk mode but there's no DESK_DOCK app with METADATA_DOCK_HOME
如果是以上模式,车载模式或者桌面模式,就返回dock不为空,否则为空。启动这个mHomeIntent。----->mHomeIntent定义如下:
- <span style="font-size: 16px;"> mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
- mHomeIntent.addCategory(Intent.CATEGORY_HOME);
- mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);</span>
Step 8: 这个mHomeIntent就是启动activity中配置Category属性的值为CATEGORY_HOME,启动的时候新启动一个任务,不是在当前的这个任务中启动launcherHome,而是新建一个task。
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记进入前台时(典型的操作是用户在主画面重启它),这个Activity和它之上的都将关闭,以至于用户不能再返回到它们,但是可以回到之前的Activity。
到这为止,Home键的流程已经分析完了。
二 、下面看看长按耳机键接受的广播的处理方式:
(1)这个长按耳机键捕获是在PhoneWindow.java类的onKeyDown()中,然后发送有序的广播---->如下:
- <span xmlns="http://www.w3.org/1999/xhtml" style=""><span xmlns="http://www.w3.org/1999/xhtml" style=""><span xmlns="http://www.w3.org/1999/xhtml" style=""> case KeyEvent.KEYCODE_HEADSETHOOK:
- case KeyEvent.KEYCODE_MEDIA_STOP:
- case KeyEvent.KEYCODE_MEDIA_NEXT:
- case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
- case KeyEvent.KEYCODE_MEDIA_REWIND:
- case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
- Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
- intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
- getContext().sendOrderedBroadcast(intent, null);
- return true;
- }</span></span></span>
接受这个长按耳机键的广播是在Music的app中的----->
public class MediaButtonIntentReceiver extends BroadcastReceiver{ ... ... }
, 需要在Manifest.xml中注册这个广播<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>。---->注册MediaButtonReceiver这个广播,
这个类中onReceive()方法定义的:代码如下--->
- <span style="font-size: 16px;"> @Override
- public void onReceive(Context context, Intent intent) {
- String intentAction = intent.getAction();
- if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intentAction)) {
- Intent i = new Intent(MediaPlaybackService.SERVICECMD);
- i.putExtra(MediaPlaybackService.CMDNAME, MediaPlaybackService.CMDPAUSE);
- context.sendBroadcast(i);
- } else if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) { <