本文共 17236 字,大约阅读时间需要 57 分钟。
在用户对软按键或者某些ui操作的时候会反馈振动,达到让用户感知操作ok的效果。 在情景模式(Audio Profile)的选取之后,将会出现对特定情景模式设置的界面(Edit Profile),在这里面就可以设置是否启动振动器 和反馈功能。 情景模式对于的代码在:packages/apps/Settings/src/com/android/settings/audioprofile下, 其中文件:AudioProfileSettings.java是情景模式选择;Editprofile.java是情景模式编辑界面。 如果用户在界面上点击选中了反馈的功能,那么将会调用到函数:setHapticFeedbackEnabled() @ frameworks/base/core/java/android/view/View.java public void setHapticFeedbackEnabled(boolean hapticFeedbackEnabled) { setFlags(hapticFeedbackEnabled ? HAPTIC_FEEDBACK_ENABLED: 0, HAPTIC_FEEDBACK_ENABLED); } 先来看看打电话界面的软键盘的触摸反馈功能的实现: @packages/apps/Phone/src/com/android/phone 进入这个目录一眼就可以看到文件HapticFeedback.java,很显然,它就是实现振动反馈功能的,该文件代码比较简单: 该文件有一段较为详细的注释,说明了如何在代码中添加这个功能,使用它们的函数接口 /** * Handles the haptic feedback: a light buzz happening when the user * presses a soft key (UI button or capacitive key(电容虚拟按键)). * * The haptic feedback is controlled by: * - a system resource for the pattern * The pattern used is tuned per device and stored in an internal * resource (config_virtualKeyVibePattern.) * * - a system setting HAPTIC_FEEDBACK_ENABLED.(这个就是情景模式中设置的反馈使能) * HAPTIC_FEEDBACK_ENABLED can be changed by the user using the * system Settings activity. It must be rechecked each time the * activity comes in the foreground (onResume). * * //后面的代码可以看出,必须要这两个条件同时满足才可以调用振动器的接口。 * * This class is not thread safe. It assumes it'll be called from the * UI thead. * * Typical usage(典型用法): * -------------- * static private final boolean HAPTIC_ENABLED = true; * private HapticFeedback mHaptic = new HapticFeedback(); * * protected void onCreate(Bundle icicle) { * mHaptic.init((Context)this, HAPTIC_ENABLED); * } * * protected void onResume() { * // Refresh the system setting. * mHaptic.checkSystemSetting(); * } * * public void foo() { * mHaptic.vibrate(); * } * */ public class HapticFeedback { private static final int VIBRATION_PATTERN_ID = com.android.internal.R.array.config_virtualKeyVibePattern; /** If no pattern was found, vibrate for a small amount of time. */ private static final long DURATION = 10; // millisec. /** Play the haptic pattern only once. */ private static final int NO_REPEAT = -1; ... private Context mContext; private long[] mHapticPattern; private Vibrator mVibrator;private boolean mEnabled;
private Settings.System mSystemSettings; private ContentResolver mContentResolver; private boolean mSettingEnabled; public void init(Context context, boolean enabled) { mEnabled = enabled; if (enabled) { mVibrator = new Vibrator(); if (!loadHapticSystemPattern(context.getResources())) { mHapticPattern = new long[] {0, DURATION, 2 * DURATION, 3 * DURATION}; // 设置一个默认的振动模式 } mSystemSettings = new Settings.System(); mContentResolver = context.getContentResolver(); } } // Reload the system settings to check if the user enabled the haptic feedback. // called in onResume() public void checkSystemSetting() { if (!mEnabled) { return; } try { int val = mSystemSettings.getInt(mContentResolver, System.HAPTIC_FEEDBACK_ENABLED, 0); mSettingEnabled = val != 0; / } catch (Resources.NotFoundException nfe) { Log.e(TAG, "Could not retrieve system setting.", nfe); mSettingEnabled = false; } } public void vibrate() { if (!mEnabled || !mSettingEnabled) { return; // 看得出,只有调用init时传入enable和用户设置使能反馈后才能调用振动器的接口 } mVibrator.vibrate(mHapticPattern, NO_REPEAT); } /** * @return true If the system haptic pattern was found. * @加载振动模式 */ private boolean loadHapticSystemPattern(Resources r) { int[] pattern;mHapticPattern = null;
try { pattern = r.getIntArray(VIBRATION_PATTERN_ID); } catch (Resources.NotFoundException nfe) { Log.e(TAG, "Vibrate pattern missing.", nfe); return false; }if (null == pattern || pattern.length == 0) {
Log.e(TAG, "Haptic pattern is null or empty."); return false; }// int[] to long[] conversion.
mHapticPattern = new long[pattern.length]; for (int i = 0; i < pattern.length; i++) { mHapticPattern[i] = pattern[i]; } return true; } } 在打电话界面有两个文件中使用到这类API:EmergencyDialer.java (packages\apps\phone\src\com\android\phone) TwelveKeyDialer.java (packages\apps\contacts\src\com\android\contacts) 现在以拨号键盘为例:TwelveKeyDialer.java,搜索mHaptic.字符串即可找到。 mHaptic.init(this, r.getBoolean(R.bool.config_enable_dialer_key_vibration)) config_enable_dialer_key_vibration在下列文件中定义: Config.xml (packages\apps\phone\res\values): <bool name="config_enable_dialer_key_vibration">true</bool> public void onClick(View view) { switch (view.getId()) { case R.id.one: { keyPressed(KeyEvent.KEYCODE_1); playTone(ToneGenerator.TONE_DTMF_1); mHaptic.vibrate(); return; } ... } } protected void onResume() { ... // Retrieve the haptic feedback setting. mHaptic.checkSystemSetting(); ... } 从上面的init函数调用和Config.xml可以看出,config_virtualKeyVibePattern模式并没有在phone的资源中定义, 直接使用的默认模式。 那么全局搜索了config_virtualKeyVibePattern这个字符串,发现只有在文件 Config.xml (frameworks\base\core\res\res\values)中有定义,而起还有另外4个类似的振动模式 <!-- Vibrator pattern for feedback about a long screen/key press --> <integer-array name="config_longPressVibePattern"> <item>0</item> <item>1</item> <item>20</item> <item>21</item> </integer-array><!-- Vibrator pattern for feedback about touching a virtual key -->
<integer-array name="config_virtualKeyVibePattern"> <item>0</item> <item>10</item> <item>20</item> <item>30</item> </integer-array><!-- Vibrator pattern for a very short but reliable vibration for soft keyboard tap -->
<integer-array name="config_keyboardTapVibePattern"> <item>40</item> </integer-array><!-- Vibrator pattern for feedback about booting with safe mode disabled -->
<integer-array name="config_safeModeDisabledVibePattern"> <item>0</item> <item>1</item> <item>20</item> <item>21</item> </integer-array><!-- Vibrator pattern for feedback about booting with safe mode disabled -->
<integer-array name="config_safeModeEnabledVibePattern"> <item>0</item> <item>1</item> <item>20</item> <item>21</item> <item>500</item> <item>600</item> </integer-array> 那么上面类似的5中模式在哪里有使用呢?在刚刚的搜索结果中可以看到赫然出现一个文件: PhoneWindowManager.java (frameworks\policies\base\phone\com\android\internal\policy\impl) 这个文件中的代码实现系统所有窗口的统一管理,那么显而易见大部分窗口振动都是在这里操作的。 public void init(Context context, IWindowManager windowManager, LocalPowerManager powerManager) { ... mVibrator = new Vibrator(); mLongPressVibePattern = getLongIntArray(mContext.getResources(), com.android.internal.R.array.config_longPressVibePattern); mVirtualKeyVibePattern = getLongIntArray(mContext.getResources(), com.android.internal.R.array.config_virtualKeyVibePattern); mKeyboardTapVibePattern = getLongIntArray(mContext.getResources(), com.android.internal.R.array.config_keyboardTapVibePattern); mSafeModeDisabledVibePattern = getLongIntArray(mContext.getResources(), com.android.internal.R.array.config_safeModeDisabledVibePattern); mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(), com.android.internal.R.array.config_safeModeEnabledVibePattern); // 取出振动模式数据,放在long []类型数组中。 } public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always) { final boolean hapticsDisabled = Settings.System.getInt(mContext .getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 0; // 再次获取用户设置 if (!always && (hapticsDisabled || mKeyguardMediator .isShowingAndNotHidden())) { return false; // } long[] pattern = null; switch (effectId) { // effectId这个值是觉得采用哪种模式的,长按,还是按了软按键产生等等。 case HapticFeedbackConstants.LONG_PRESS: pattern = mLongPressVibePattern; break; case HapticFeedbackConstants.VIRTUAL_KEY: pattern = mVirtualKeyVibePattern; break; case HapticFeedbackConstants.KEYBOARD_TAP: pattern = mKeyboardTapVibePattern; break; case HapticFeedbackConstants.SAFE_MODE_DISABLED: pattern = mSafeModeDisabledVibePattern; break; case HapticFeedbackConstants.SAFE_MODE_ENABLED: pattern = mSafeModeEnabledVibePattern; break; default: return false; } // 调用振动器接口开始振动 if (pattern.length == 1) { // One-shot vibration mVibrator.vibrate(pattern[0]); } else { // Pattern vibration mVibrator.vibrate(pattern, -1); } return true; } public void keyFeedbackFromInput(KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN && (event.getFlags() & KeyEvent.FLAG_VIRTUAL_HARD_KEY) != 0) { // 这里有对按键类型做判断看是否是虚拟的硬按键,就对于这虚拟的软按键。 performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false); } } 到这里,基本上大致明白了振动反馈的原理,现在要引入一个实际面临的问题。 本来Virtual Key是只的触摸屏上划出一块区域来做几个按键的这类按键,这类按键在原始系统上就是可以进行振动反馈的, 不过呢,现在我们的系统上有2个触摸按键MENU和BACK,他们虽然也是在触摸屏上,不过他们是按照按键上报,而不是按触摸 坐标上报之后生成的虚拟keyevent,所以这样的话,上层就会将他们视为物理按键,但是他们确实是触摸型的。 所以为了让这类按键触摸之后也有振动反馈,所以就需要查找按键上报的流程,然后进行修改。 获取按键的类型flags: event.getFlags(), KeyEvent.java (frameworks\base\core\java\android\view) 想要知道这个flags是如何来的,那么就需要从kernel内核之上开始顺藤摸瓜将按键上报流程缕一下: EventHub.cpp (frameworks\base\libs\ui) 这个文件中的函数EventHub::getEvent()会从内核中获取input子系统的event事件,其中也包括key事件。 bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, int32_t* outValue, nsecs_t* outWhen) { ... while(1) { ... pollres = poll(mFDs, mFDCount, -1); ... for(i = 1; i < mFDCount; i++) { if(mFDs[i].revents) { if(mFDs[i].revents & POLLIN) { res = read(mFDs[i].fd, &iev, sizeof(iev)); if (res == sizeof(iev)) { *outDeviceId = mDevices[i]->id; if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0; *outType = iev.type; *outScancode = iev.code; if (iev.type == EV_KEY) { err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags); // 从*.kl文件中进行键值映射,也得到想要flags值。kernel键值映射到android键值。...
} else { *outKeycode = iev.code; } *outValue = iev.value; *outWhen = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec); return true; } ... } } } } ... } } *.kl文件格式: key 158 BACK ... key 229 MENU key 139 MENU key 59 MENU ... key 116 POWER WAKE ... 其中第一列是key关键字,第二列是kernel的键值,第3列是android键值,第4列是flag 其中flags有以下几种,可以从文件:KeycodeLabels.h (frameworks\base\include\ui)中查看得到: static const KeycodeLabel FLAGS[] = { { "WAKE", 0x00000001 }, { "WAKE_DROPPED", 0x00000002 }, { "SHIFT", 0x00000004 }, { "CAPS_LOCK", 0x00000008 }, { "ALT", 0x00000010 }, { "ALT_GR", 0x00000020 }, { "MENU", 0x00000040 }, { "LAUNCHER", 0x00000080 }, { NULL, 0 } }; 其中也可以看到MENU和BACK在android空间中对于的键值分别是:82和4. static const KeycodeLabel KEYCODES[] = { ... { "BACK", 4 }, ... { "MENU", 82 }, ... } 上面这些值都是KeyLayoutMap.cpp (frameworks\base\libs\ui)文件中函数 status_t KeyLayoutMap::load(const char* filename)在加载*.kl文件的时候根据文件和上面两个数组设置好了的,最后只需要用map()函数来获取这些值即可。 接下来按键值将会上传至JNI层:com_android_server_KeyInputQueue.cpp (frameworks\base\services\jni) static jboolean android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz, jobject event) { gLock.lock(); sp<EventHub> hub = gHub; if (hub == NULL) { hub = new EventHub; gHub = hub; } gLock.unlock();int32_t deviceId;
int32_t type; int32_t scancode, keycode; uint32_t flags; int32_t value; nsecs_t when; bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode, &flags, &value, &when); // getEventenv->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId);
env->SetIntField(event, gInputOffsets.mType, (jint)type); env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode); env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode); env->SetIntField(event, gInputOffsets.mFlags, (jint)flags); env->SetIntField(event, gInputOffsets.mValue, value); env->SetLongField(event, gInputOffsets.mWhen, (jlong)(nanoseconds_to_milliseconds(when))); // 将这些值传入java空间。return res;
} 最后在java代码中只需要调用jni接口readEvent即可。KeyInputQueue.java (frameworks\base\services\java\com\android\server)文件中会创建一个线程来专门读取inputevent值
Thread mThread = new Thread("InputDeviceReader") {
public void run() { ... RawInputEvent ev = new RawInputEvent(); while (true) { try { InputDevice di;// block, doesn't release the monitor
readEvent(ev); ... } } ... // Is it a key event? if (type == RawInputEvent.EV_KEY && (classes&RawInputEvent.CLASS_KEYBOARD) != 0 && (scancode < RawInputEvent.BTN_FIRST || scancode > RawInputEvent.BTN_LAST)) { boolean down; if (ev.value != 0) { down = true; di.mKeyDownTime = curTime; } else { down = false; } int keycode = rotateKeyCodeLocked(ev.keycode); addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, newKeyEvent(di, di.mKeyDownTime, curTime, down, keycode, 0, scancode, ((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0) ? KeyEvent.FLAG_WOKE_HERE : 0)); } ... } }generateVirtualKeyDown()根据上报的触摸点坐标等信息生成虚拟硬按键事件。
KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, true, vk.lastKeycode, 0, vk.scancode, KeyEvent.FLAG_VIRTUAL_HARD_KEY); mHapticFeedbackCallback.virtualKeyFeedback(event); addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, event); newKeyEvent()生成一个KeyEvent实例,如下: public static KeyEvent newKeyEvent(InputDevice device, long downTime, long eventTime, boolean down, int keycode, int repeatCount, int scancode, int flags) { return new KeyEvent( downTime, eventTime, down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, keycode, repeatCount, device != null ? device.mMetaKeysState : 0, device != null ? device.id : -1, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM); } ***********************88 错误的修改 *******************************/ 所以为了实现MENU和BACK按键能产生震动反馈,那么可以直接修改这个函数newKeyEvent(),如下: public static KeyEvent newKeyEvent(InputDevice device, long downTime, long eventTime, boolean down, int keycode, int repeatCount, int scancode, int flags) { + int nflags = flags; + if(keycode == 4 || keycode == 82) // BACK and MENU + nflags |= KeyEvent.FLAG_VIRTUAL_HARD_KEY; return new KeyEvent( downTime, eventTime, down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, keycode, repeatCount, device != null ? device.mMetaKeysState : 0, device != null ? device.id : -1, scancode, +/- nflags | KeyEvent.FLAG_FROM_SYSTEM); } ***********************88 错误的修改 *******************************/ int keycode = rotateKeyCodeLocked(ev.keycode); /* Begin: lizhiguo, 2011-10-20, modifiled for BACK and MENU key HapticFeedback.*/ if(1){ int nflags = ((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0) ? KeyEvent.FLAG_WOKE_HERE : 0; if(keycode == 4 || keycode == 82) // BACK and MENU nflags |= KeyEvent.FLAG_VIRTUAL_HARD_KEY; KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, down, keycode, 0, scancode, nflags); if(keycode == 4 || keycode == 82) // BACK and MENU mHapticFeedbackCallback.virtualKeyFeedback(event); addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, event); } else{ addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, newKeyEvent(di, di.mKeyDownTime, curTime, down, keycode, 0, scancode, ((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0) ? KeyEvent.FLAG_WOKE_HERE : 0)); } /* End: lizhiguo, 2011-10-20 */ 完! 源码列表: 1 KeyInputQueue.java (frameworks\base\services\java\com\android\server) 2 RawInputEvent.java (frameworks\base\core\java\android\view) 3 KeyEvent.java (frameworks\base\core\java\android\view) 4 KeyInputQueue.java (frameworks\base\services\java\com\android\server):2 5 WindowManagerPolicy.java (frameworks\base\core\java\android\view) 6 com_android_server_KeyInputQueue.cpp (frameworks\base\services\jni) 7 EventHub.cpp (frameworks\base\libs\ui) 8 EventHub.cpp (frameworks\base\libs\ui):2 9 KeyLayoutMap.cpp (frameworks\base\libs\ui) 0 KeycodeLabels.h (frameworks\base\include\ui) A TwelveKeyDialer.java (packages\apps\contacts\src\com\android\contacts) B PhoneWindowManager.java (frameworks\policies\base\phone\com\android\internal\policy\impl) C KeyEvent.java (frameworks\base\awt\java\awt\event) D HapticFeedbackConstants.java (frameworks\base\core\java\android\view) E PasswordUnlockScreen.java (frameworks\policies\base\phone\com\android\internal\policy\impl) F LockPatternView.java (frameworks\base\core\java\com\android\internal\widget) G HapticFeedback.java (packages\apps\phone\src\com\android\phone) H Donottranslate_config.xml (packages\apps\contacts\res\values) I EmergencyDialer.java (packages\apps\phone\src\com\android\phone) J Config.xml (frameworks\base\core\res\res\values) K Vibrator.java (frameworks\base\core\java\android\os) L View.java (frameworks\base\core\java\android\view) M Settings.java (frameworks\base\core\java\android\provider) N ContentResolver.java (frameworks\base\core\java\android\content) O System.java (dalvik\libcore\luni-kernel\src\main\java\java\lang) P Editprofile.java (packages\apps\settings\src\com\android\settings\audioprofile) Q CheckBoxPreference.java (frameworks\base\core\java\android\preference) R AudioProfileImpl.java (frameworks\mtk\extensions\media\audio\java\com\mediatek\audioprofile) S ProfileState.java (frameworks\mtk\extensions\media\audio\java\com\mediatek\audioprofile) T AudioProfileSettings.java (packages\apps\settings\src\com\android\settings\audioprofile) U Config.xml (packages\apps\phone\res\values) V TextKeyListenerTest.java (cts\tests\tests\text\src\android\text\method\cts) W WindowManagerService.java (frameworks\base\services\java\com\android\server) X KeyEventTest.java (cts\tests\tests\view\src\android\view\cts) Y KeyLayoutMap.h (frameworks\base\libs\ui) Z User_comments_for_4_to_5.xml (frameworks\base\docs\html\sdk\api_diff\5) missingSinces.txt (frameworks\base\docs\html\sdk\api_diff\5) 5.xml (frameworks\base\api) MidWindowManager.java (frameworks\policies\base\mid\com\android\internal\policy\impl) EventHub.h (frameworks\base\include\ui) Evdev.c (kernel\drivers\input) Input.h (kernel\include\linux) Input.c (kernel\drivers\input) Preference.java (frameworks\base\core\java\android\preference)转载地址:http://cefsi.baihongyu.com/