根据按键派发策略,派发顺序是Activity,然后是PhoneWindow。
如果activity需要处理音量键,可以在应用中重写Activity 的onKeyDown()方法以截获音量键将其用作其他功能,如相机的音量快捷键。Activity#onKeyDown()方法源码
/** * Called when a key was PRessed down and not handled by any of the views * inside of the activity. So, for example, key presses while the cursor * is inside a TextView will not trigger the event (unless it is a navigation * to another object) because TextView handles its own key presses. * * <p>If the focused view didn't want this event, this method is called. * * <p>The default implementation takes care of {@link KeyEvent#KEYCODE_BACK} * by calling {@link #onBackPressed()}, though the behavior varies based * on the application compatibility mode: for * {@link android.os.Build.VERSION_CODES#ECLAIR} or later applications, * it will set up the dispatch to call {@link #onKeyUp} where the action * will be performed; for earlier applications, it will perform the * action immediately in on-down, as those versions of the platform * behaved. * * <p>Other additional default key handling may be performed * if configured with {@link #setDefaultKeyMode}. * * @return Return <code>true</code> to prevent this event from being propagated * further, or <code>false</code> to indicate that you have not handled * this event and it should continue to be propagated. * @see #onKeyUp * @see android.view.KeyEvent */ public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) { event.startTracking(); } else { onBackPressed(); } return true; } if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) { return false; } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) { Window w = getWindow(); if (w.hasFeature(Window.FEATURE_OPTIONS_PANEL) && w.performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) { return true; } return false; } else { // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_* boolean clearSpannable = false; boolean handled; if ((event.getRepeatCount() != 0) || event.isSystem()) { clearSpannable = true; handled = false; } else { handled = TextKeyListener.getInstance().onKeyDown( null, mDefaultKeySsb, keyCode, event); if (handled && mDefaultKeySsb.length() > 0) { // something useable has been typed - dispatch it now. final String str = mDefaultKeySsb.toString(); clearSpannable = true; switch (mDefaultKeyMode) { case DEFAULT_KEYS_DIALER: Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + str)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); break; case DEFAULT_KEYS_SEARCH_LOCAL: startSearch(str, false, null, false); break; case DEFAULT_KEYS_SEARCH_GLOBAL: startSearch(str, false, null, true); break; } } } if (clearSpannable) { mDefaultKeySsb.clear(); mDefaultKeySsb.clearSpans(); Selection.setSelection(mDefaultKeySsb,0); } return handled; } }Activity.onKeyDown()对一般按键有默认的一些处理,但是我们可以复写这个方法以处理我们想要处理的按键。如果不做处理,会让PhoneWindow的onKeyDown处理,下面是PhoneWindow#onKeyDown()方法源码:
protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) { /* **************************************************************************** * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES. * * If your key handling must happen before the app gets a crack at the event, * it goes in PhoneWindowManager. * * If your key handling should happen in all windows, and does not depend on * the state of the current application, other than that the current * application can override the behavior by handling the event itself, it * should go in PhoneFallbackEventHandler. * * Only if your handling depends on the window, and the fact that it has * a DecorView, should it go here. * ****************************************************************************/ final KeyEvent.DispatcherState dispatcher = mDecor != null ? mDecor.getKeyDispatcherState() : null; //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount() // + " flags=0x" + Integer.toHexString(event.getFlags())); switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: { int direction = 0; switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: direction = AudioManager.ADJUST_RAISE; break; case KeyEvent.KEYCODE_VOLUME_DOWN: direction = AudioManager.ADJUST_LOWER; break; case KeyEvent.KEYCODE_VOLUME_MUTE: direction = AudioManager.ADJUST_TOGGLE_MUTE; break; } // If we have a session send it the volume command, otherwise // use the suggested stream. if (mMediaController != null) { mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI); } else { MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy( mVolumeControlStreamType, direction, AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE | AudioManager.FLAG_FROM_KEY); } return true; } // These are all the recognized media key codes in // KeyEvent.isMediaKey() case KeyEvent.KEYCODE_MEDIA_PLAY: case KeyEvent.KEYCODE_MEDIA_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_MUTE: 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_RECORD: case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { if (mMediaController != null) { if (mMediaController.dispatchMediaButtonEvent(event)) { return true; } } return false; } case KeyEvent.KEYCODE_MENU: { onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event); return true; } case KeyEvent.KEYCODE_BACK: { if (event.getRepeatCount() > 0) break; if (featureId < 0) break; // Currently don't do anything with long press. if (dispatcher != null) { dispatcher.startTracking(event, this); } return true; } } return false; }在PhoneWindow#onKeyDown()中最终调用mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);调节音量,源码如下:/** * Adjust the volume of the output this session is playing on. The direction * must be one of {@link AudioManager#ADJUST_LOWER}, * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}. * The command will be ignored if the session does not support * {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in * {@link AudioManager} may be used to affect the handling. * * @see #getPlaybackInfo() * @param direction The direction to adjust the volume in. * @param flags Any flags to pass with the command. */ public void adjustVolume(int direction, int flags) { try { mSessionBinder.adjustVolume(direction, flags, mContext.getPackageName()); } catch (RemoteException e) { Log.wtf(TAG, "Error calling adjustVolumeBy.", e); } }在PhoneWindow#onKeyDown()的flag有几个,其中AudioManager.FLAG_SHOW_UI告诉AudioService需要弹出一个音量控制板。而PhoneWindow#onKeyUp()中对应的有AudioManager.FLAG_PLAY_SOUND,这是告诉AudioService松开音量键后有提示音,还有更多的flag自己去看。每个流类型(StreamType)都有独立的音量值。而PhoneWindow最终设置的音量所属类型由PhoneWindow#mVolumeControlStream决定,在Activity#setVolumeControlStream()可以设置绑定的PhoneWindow的mVolumeControlStream。
音量按键有三个值,分别是KeyEvent.KEYCODE_VOLUME_UP(加), KeyEvent.KEYCODE_VOLUME_DOWN(减), KeyEvent.KEYCODE_VOLUME_MUTE(静音)
音量改变后,会发广播,下面是AudioService#setIndex()源码:
mVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex); mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS, mStreamVolumeAlias[mStreamType]); sendBroadcastToAll(mVolumeChanged);
新闻热点
疑难解答