Android NavigationBar中虚拟键调查

NavigationBar调查

NavigationBarAndroid4.0以后出现的新特性,下图2就是NavigationBar,其中包括Back, Home, Recent键。并且对于基于2.3或者更早的版本的app,会在右下角显示一个Menu。这几个键都是虚拟的按键,对于没有实体键的手机或者tablet相当的方便。

 

NavigationBar的显示与隐藏

NavigationBarApp层是由SystemUI控制显示与隐藏的,并且布局文件也在SystemUI中。SystemUI在路径frameworks/base/packages/SystemUI/

SystemUI就是StatusBar的界面部分,随系统启动而启动。SystemUI启动后会加载SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java

之后调用makeStatusBarView构建视图,其中就包括了NavigationBar的创建。代码如下:

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. try {  
  2.     boolean showNav = mWindowManagerService.hasNavigationBar();  
  3.     if (showNav) {  
  4.         mNavigationBarView =  
  5.             (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null);  
  6.   mNavigationBarView.setDisabledFlags(mDisabled);  
  7.         mNavigationBarView.setBar(this);  
  8.   }  
  9. catch (RemoteException ex) {  
  10.   // no window manager? good luck with that  
  11. }  

 

可见NavigationBarView其实是NavigationBar的视图,对应的layout文件为navigation_bar.xmlNavigationBar是否构建显示是通过mWindowManagerServicehasNavigationBar()接口确定的。WindowManagerService会向下调用Policy的接口hasNavigationBar决定NavigationBar显示与否。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public interface WindowManagerPolicy {  
  2.   public boolean hasNavigationBar();  
  3. }  


Policy调用的实际上是PhoneWindowManager的接口hasNavigationBar,获取变量mHasNavigationBar的布尔值,这个变量是setInitialDisplaySize的时候初始化的。

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class PhoneWindowManager implements WindowManagerPolicy {  
  2. ... ...  
  3. // Use this instead of checking config_showNavigationBar so that it can be consistently  
  4.     // overridden by qemu.hw.mainkeys in the emulator.  
  5.     public boolean hasNavigationBar() {  
  6.         return mHasNavigationBar;  
  7.   }  
  8. ... ...  
  9. public void setInitialDisplaySize(Display display, int width, int height, int density) {  
  10. ... ...  
  11. // SystemUI (status bar) layout policy  
  12.         int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / density;  
  13.         String deviceType = SystemProperties.get("sys.device.type");  
  14.         if (! "".equals(deviceType) && deviceType.equals("tablet")) {  
  15.            // if indicate device type is tablet skip the judge for "phone" UI  
  16.         } else if (shortSizeDp < 600) {  
  17.             // 0-599dp: "phone" UI with a separate status & navigation bar  
  18.             mHasSystemNavBar = false;  
  19.             mNavigationBarCanMove = true;  
  20.         } else if (shortSizeDp < 720) {  
  21.             // 600+dp: "phone" UI with modifications for larger screens  
  22.             mHasSystemNavBar = false;  
  23.             mNavigationBarCanMove = false;  
  24.         }  
  25. if (!mHasSystemNavBar) {  
  26.             mHasNavigationBar = mContext.getResources().getBoolean(  
  27.                     com.android.internal.R.bool.config_showNavigationBar);  
  28.             // Allow a system property to override this. Used by the emulator.  
  29.             String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");  
  30.             if (! "".equals(navBarOverride)) {  
  31.                 if      (navBarOverride.equals("1")) mHasNavigationBar = false;  
  32.                 else if (navBarOverride.equals("0")) mHasNavigationBar = true;  
  33.             }  
  34.         } else {  
  35.             mHasNavigationBar = false;  
  36.         }  
  37. }  
  38. }  


com.android.internal.R.bool.config_showNavigationBar这个才是navigationbar显示与否的配置变量,这个变量其实是在Android编译过程中由

frameworks/base/core/res/res/values/config.xml配置的


[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <!-- Whether a software navigation bar should be shown. NOTE: in the future this may be  
  2.          autodetected from the Configuration. -->  
  3.     <bool name="config_showNavigationBar">false</bool>  


WindowManagerPolicy -- frameworks/base/core/java/android/view/WindowManagerPolicy.java

PhoneWindowManager -- frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java

 

另一方面,通过调用SystemProperties.get("qemu.hw.mainkeys");获取到NavigationBar是否被自定义,系统默认是不会设置qemu.hw.mainkeys的值的,所以获取到的是“0”。因此,就算不设定config.xmlconfig_showNavigationBar的值为true,屏幕够大的话,NavigationBar默认还是会显示出来的。

External/qemu/vl-android.c中如下代码会设定qemu.hw.mainkeys的值,下层代码没有继续调查。


[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /* Initialize presence of hardware nav button */  
  2.     boot_property_add("qemu.hw.mainkeys", android_hw->hw_mainKeys ? "1" : "0");  


总的来说NavigationBar的显示与隐藏主要依赖3个方面

1.config.xml的配置config_showNavigationBar为true则显示,false不显示

2.DensityWXGA720/WXGA800/WXGA800-7in三种分辨率显示

3.Qemu.hw.mainkeys的设置,0显示,1不显示

 

NavigationBar Click事件处理

NavigationBarlayout文件是frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml

视图类是Frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java

layout文件看navigation_bar.xml是按方向配置的,rot0/rot90/rot270

每个方向都会包含KeyButtonView类型的4View

id分别是back/home/recent_apps/menu

视图类NavigationBarView.java中会包含获取4button的接口,代码如下:

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public View getRecentsButton() {  
  2.         return mCurrentView.findViewById(R.id.recent_apps);  
  3.   }  
  4. public View getMenuButton() {  
  5.         return mCurrentView.findViewById(R.id.menu);  
  6.     }  
  7.   
  8. public View getBackButton() {  
  9.         return mCurrentView.findViewById(R.id.back);  
  10.     }  
  11.   
  12. public View getHomeButton() {  
  13.   return mCurrentView.findViewById(R.id.home);  
  14.     }  


SystemUI启动后会调用PhoneStatusBar.javastart->addNavigationBar()->

prepareNavigationBarView(),并在prepare中设定了recentHome的处理事件。

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public void start() {  
  3.     ... ...  
  4.     addNavigationBar();  
  5. }  
  6.   
  7. // For small-screen devices (read: phones) that lack hardware navigation buttons  
  8. private void addNavigationBar() {  
  9.     if (DEBUG) Slog.v(TAG, "addNavigationBar: about to add " + mNavigationBarView);  
  10.     if (mNavigationBarView == nullreturn;  
  11.     prepareNavigationBarView();  
  12.   mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());  
  13. }  
  14.   
  15. private void prepareNavigationBarView() {  
  16.     mNavigationBarView.reorient();  
  17.   mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);  
  18.   mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);  
  19.   mNavigationBarView.getHomeButton().setOnTouchListener(mHomeSearchActionListener);  
  20.   mNavigationBarView.getSearchLight().setOnTouchListener(mHomeSearchActionListener);  
  21.   updateSearchPanel();  
  22. }  


接下来先看最简单的Recent Button

Recent Button Click事件处理

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. private View.OnClickListener mRecentsClickListener = new View.OnClickListener() {  
  2.         public void onClick(View v) {  
  3.             toggleRecentApps();  
  4.         }  
  5.     };  


Recent button设定了onClickListener,处理函数是toggleRecentApps()toggleRecentAppsPhoneStatusBar的父类BaseStatusBar中实现。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public void toggleRecentApps() {  
  3.     int msg = MSG_TOGGLE_RECENTS_PANEL;  
  4.     mHandler.removeMessages(msg);  
  5.     mHandler.sendEmptyMessage(msg);  
  6. }  


从函数实现来看,发送了MSG_TOGGLE_RECENTS_PANEL msg给线程。

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. protected class H extends Handler {  
  2.     public void handleMessage(Message m) {  
  3.         switch (m.what) {  
  4.              case MSG_TOGGLE_RECENTS_PANEL:  
  5.                  if (DEBUG) Slog.d(TAG, "toggle recents panel");  
  6.                  toggleRecentsActivity();  
  7.                  break;  
  8.   ... ...  
  9.   }  
  10.     }  
  11. }  


BaseStatusBar.java的内部线程会调用toggleRecentsActivity()处理接收到的消息。

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. protected void toggleRecentsActivity() {  
  2.   try {  
  3. TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask();  
  4.   Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT);  
  5.     intent.setClassName("com.android.systemui",  
  6.         "com.android.systemui.recent.RecentsActivity");  
  7.     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK  
  8.         | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);  
  9.   if (firstTask == null) {  
  10.         if (RecentsActivity.forceOpaqueBackground(mContext)) {  
  11.     ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,  
  12.                             R.anim.recents_launch_from_launcher_enter,  
  13.                             R.anim.recents_launch_from_launcher_exit);  
  14.     mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(  
  15.                             UserHandle.USER_CURRENT));  
  16.   
  17.   } else {  
  18.             // The correct window animation will be applied via the activity's style  
  19.             mContext.startActivityAsUser(intent, new UserHandle(  
  20.                             UserHandle.USER_CURRENT));  
  21.         }  
  22.   } else {  
  23.         Bitmap first = firstTask.getThumbnail();  
  24.   ... ...  
  25.   ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(  
  26.             getStatusBarView(),  
  27.   first, x, y,  
  28.   new ActivityOptions.OnAnimationStartedListener() {  
  29.   public void onAnimationStarted() {  
  30. Intent intent = new Intent(RecentsActivity.WINDOW_ANIMATION_START_INTENT);  
  31.                     intent.setPackage("com.android.systemui");  
  32.                     mContext.sendBroadcastAsUser(intent,   
  33.   new UserHandle(UserHandle.USER_CURRENT));  
  34.                 }  
  35.             });  
  36.                 intent.putExtra(RecentsActivity.WAITING_FOR_WINDOW_ANIMATION_PARAM, true);  
  37.         mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(  
  38.                         UserHandle.USER_CURRENT));  
  39.     }  
  40.     return;  
  41. catch (ActivityNotFoundException e) {  
  42.     Log.e(TAG, "Failed to launch RecentAppsIntent", e);  
  43. }  


处理消息的本质其实是通过context启动一个Activity,显示RecentApp。下层调用没有继续调查。从Log中看也确实是这样

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. D/PhoneStatusBar(  360): mRecentsClickListener onClicked call toggleRecentApps.  
  2. I/ActivityManager(  279): START u0 {act=com.android.systemui.recent.action.TOGGLE_RECENTS flg=0x10800000 cmp=com.android.systemui/.recent.RecentsActivity} from pid 360  

Home/Back/Menu Button Click事件处理

其实home/back/recent/menu都是KeyButtonView类型的View,而Home/Back ButtononTouch事件其实也是在KeyButtonView中做的处理。

onTouch会判断发送过来的key_code是否合法,合法就调用sendEvent将事件发送出去。事件是通过InputManagerinjectInputEvent强制插入一个事件

 

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public boolean onTouchEvent(MotionEvent ev) {  
  2.     final int action = ev.getAction();  
  3.   switch (action) {  
  4.         case MotionEvent.ACTION_DOWN:  
  5.   ... ...  
  6.   case MotionEvent.ACTION_MOVE:  
  7.   ... ...  
  8.   case MotionEvent.ACTION_CANCEL:  
  9.   ... ...  
  10.   case MotionEvent.ACTION_UP:  
  11.   final boolean doIt = isPressed();  
  12.   setPressed(false);  
  13.   if (mCode != 0) {  
  14.     if (doIt) {  
  15.         sendEvent(KeyEvent.ACTION_UP, 0);  
  16.         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
  17.                           playSoundEffect(SoundEffectConstants.CLICK);  
  18.     } else {  
  19.         sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);  
  20.     }  
  21.   } else {  
  22.     // no key code, just a regular ImageView  
  23.   if (doIt) {  
  24.         performClick();  
  25.     }  
  26.   }  
  27.   }  
  28. }  
  29. void sendEvent(int action, int flags) {  
  30.     sendEvent(action, flags, SystemClock.uptimeMillis());  
  31. }  
  32.   
  33. void sendEvent(int action, int flags, long when) {  
  34.     final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;  
  35.     final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,  
  36.                 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,  
  37.                 flags | KeyEvent.FLAG_FROM_SYSTEM |   
  38.   KeyEvent.FLAG_VIRTUAL_HARD_KEY,  
  39.                 InputDevice.SOURCE_KEYBOARD);  
  40.         InputManager.getInstance().injectInputEvent(ev,  
  41.                 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);  
  42. }  


log看也确实是这样处理的,其中action 0代表ACTION_DOWN1代表ACTION_UPmCode是键值,82代表KEYCODE_MENU3代表KEYCODE_HOME4代表KEYCODE_BACK。具体的KeyCodeAction可以参考

http://developer.android.com/intl/zh-cn/reference/android/view/KeyEvent.html

 

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. D/KeyButtonView(  351): onTouchEvent MotionEvent = MotionEvent { action=ACTION_DOWN, id[0]=0, x[0]=40.0, y[0]=52.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=170128, downTime=170128, deviceId=0, source=0x1002 }  
  2. D/KeyButtonView(  351): sendEvent mCode = 82  
  3. D/KeyButtonView(  351): sendEvent, action = 0  
  4.   
  5. D/KeyButtonView(  351): onTouchEvent MotionEvent = MotionEvent { action=ACTION_UP, id[0]=0, x[0]=40.0, y[0]=52.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=170195, downTime=170128, deviceId=0, source=0x1002 }  
  6. D/KeyButtonView(  351): sendEvent mCode = 82  
  7. D/KeyButtonView(  351): sendEvent, action = 1  
  8.   
  9. E/StrictMode(  360):    at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)  
  10. D/KeyButtonView(  360): onTouchEvent MotionEvent = MotionEvent { action=ACTION_DOWN, id[0]=0, x[0]=95.0, y[0]=47.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=160035, downTime=160035, deviceId=0, source=0x1002 }  
  11. D/KeyButtonView(  360): sendEvent mCode = 3  
  12. D/KeyButtonView(  360): sendEvent, action = 0  
  13. D/KeyButtonView(  360): onTouchEvent MotionEvent = MotionEvent { action=ACTION_UP, id[0]=0, x[0]=95.0, y[0]=47.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=160137, downTime=160035, deviceId=0, source=0x1002 }  
  14. D/KeyButtonView(  360): sendEvent mCode = 3  
  15. D/KeyButtonView(  360): sendEvent, action = 1  
  16.   
  17. D/KeyButtonView(  360): onTouchEvent MotionEvent = MotionEvent { action=ACTION_DOWN, id[0]=0, x[0]=74.0, y[0]=61.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=177529, downTime=177529, deviceId=0, source=0x1002 }  
  18. D/KeyButtonView(  360): sendEvent mCode = 4  
  19. D/KeyButtonView(  360): sendEvent, action = 0  
  20. D/KeyButtonView(  360): onTouchEvent MotionEvent = MotionEvent { action=ACTION_UP, id[0]=0, x[0]=74.0, y[0]=61.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=177649, downTime=177529, deviceId=0, source=0x1002 }  
  21. D/KeyButtonView(  360): sendEvent mCode = 4  
  22. D/KeyButtonView(  360): sendEvent, action = 1  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值