何気なく使っているFrameworks 第10回 NavigationBar / ナビゲーションバー
NavigationBar
ICSから追加された、画面下にある仮想キーのことです。
Developerサイトには「Virtual buttons in the System Bar」と記載されていますが、ソースコード中にNavigationBarと書かれています。
NavigationBarの実装方法
NavigationBarはSystemUI/StatusBarの一部として実装されています。
以下の説明を読んでいただくとご理解いただけると思いますが、ただのViewです。
\frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone
- PhoneStatusBar.java
try { boolean showNav = mWindowManager.hasNavigationBar(); if (showNav) { mNavigationBarView = (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null); mNavigationBarView.setDisabledFlags(mDisabled); } } catch (RemoteException ex) { // no window manager? good luck with that }
layoutファイルはこちら。
\frameworks\base\packages\SystemUI\res\layout
- navigation_bar.xml
ちなみにTabletの場合、こちらのソースです。
\frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\tablet
- TabletStatusBar.java
try { // Sanity-check that someone hasn't set up the config wrong and asked for a navigation // bar on a tablet that has only the system bar if (mWindowManager.hasNavigationBar()) { throw new RuntimeException( "Tablet device cannot show navigation bar and system bar"); } } catch (RemoteException ex) { }
Tabletの場合、NavigationBarが有効になっているとRuntimeExceptionとなります。
Systembarとして組み込んでいるからNavigationBarは不要でしょうっということみたいです。
NavigationBarの有無
ICSだからといって、すべての端末でNavigationBarが表示されるわけではありません。
実際、GalaxyNexusは表示あり、Nexus Sでは表示なしとなっています。
では、その切り分けはどこで行っているかというと、
mWindowManager.hasNavigationBar()を見ていくとたどり着きます。
最終的には、PhoneWindowManager#setInitialDisplaySize()で判定を行っています。
// Determine whether the status bar can hide based on the size // of the screen. We assume sizes > 600dp are tablets where we // will use the system bar. int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / DisplayMetrics.DENSITY_DEVICE; mStatusBarCanHide = shortSizeDp < 600; mStatusBarHeight = mContext.getResources().getDimensionPixelSize( mStatusBarCanHide ? com.android.internal.R.dimen.status_bar_height : com.android.internal.R.dimen.system_bar_height); mHasNavigationBar = mContext.getResources().getBoolean( com.android.internal.R.bool.config_showNavigationBar); // Allow a system property to override this. Used by the emulator. // See also hasNavigationBar(). String navBarOverride = SystemProperties.get("qemu.hw.mainkeys"); if (! "".equals(navBarOverride)) { if (navBarOverride.equals("1")) mHasNavigationBar = false; else if (navBarOverride.equals("0")) mHasNavigationBar = true; }
どうやら、com.android.internal.R.bool.config_showNavigationBarという値を参照するようです。
この値は以下のxmlに定義されています。
\frameworks\base\core\res\res\values
- config.xml
<!-- Whether a software navigation bar should be shown. NOTE: in the future this may be autodetected from the Configuration. --> <bool name="config_showNavigationBar">false</bool>
ICS標準としてはNavigationBarは非表示のみたいですね。
では、各端末固有のデータをみてみましょう。
\device\samsung\tuna\overlay\frameworks\base\core\res\res\values
- config.xml
に上書き用の値が定義されていました。これによりGalaxyNexus上でNavigationBar
が表示可能となります。
NavigationBarの表示レイヤー
NavigationBarの表示レイヤーを確認します。
WindowManagerServiceへのaddViewの処理をみると、WindowManager.LayoutParams.TYPE_NAVIGATION_BARというパラメータが指定されています。
\frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone
- PhoneStatusBar.java
private void addNavigationBar() { if (mNavigationBarView == null) return; prepareNavigationBarView(); WindowManagerImpl.getDefault().addView( mNavigationBarView, getNavigationBarLayoutParams()); } private WindowManager.LayoutParams getNavigationBarLayoutParams() { WindowManager.LayoutParams lp = new WindowManager.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, 0 | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_SLIPPERY, PixelFormat.OPAQUE); // this will allow the navbar to run in an overlay on devices that support this if (ActivityManager.isHighEndGfx(mDisplay)) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } lp.setTitle("NavigationBar"); lp.windowAnimations = 0; return lp; }
NAVIGATION_BAR_LAYERはシステムエラー通知(SYSTEM_ERROR_LAYER)のすぐ下と、上位に表示されるレイヤーとなっています。詳しくは以下の記事を参照。
ICSの表示レイヤーについて
http://d.hatena.ne.jp/baroqueworksdev/20111124/1322114804
NavigationBarのキーイベント
NavigationBarのHome/Backなどをタッチすると、ハードキーと同等の動作をします。
Android Frameworksとしてはキーイベントと同じ振る舞いとなるよう、
以下のようにJava層からNative層へイベントを送っていました。
KeyButtonView#sendEvent - WindowManagerService#injectInputEventNoWait - InputManager#injectInputEvent - InputManager#nativeInjectInputEvent - android_server_InputManager_nativeInjectInputEvent - InputDispatcher#injectInputEvent