AndroidのWindowManagerでViewを表示するときのIMEの制約
Androidのアプリを作っている時にどの画面よりも手前にViewを表示したい時にWindowManagerを使って特定の指定レイヤーに対してaddViewを行うケースがあります。
しかし、このaddView時に発生するIMEの表示の制約があるのでそれをご説明します。
WindowManagerのaddViewで追加したViewでIME表示ができるか
まずAndroidのWindowManagerにaddViewをする処理が次のような処理になります。
WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); windowManager.addView(new View(context), params);
ここで指定されるWindowManager.LayoutParamsの第3引数は追加するViewのレイヤーを指定します。
Android6.0、Android7.0において指定できるレイヤーは次のものになります。
- TYPE_TOAST
- TYPE_DREAM
- TYPE_INPUT_METHOD
- TYPE_WALLPAPER
- TYPE_PRIVATE_PRESENTATION
- TYPE_VOICE_INTERACTION
- TYPE_ACCESSIBILITY_OVERLAY
- TYPE_QS_DIALOG
- TYPE_PHONE
- TYPE_PRIORITY_PHONE
- TYPE_SYSTEM_ALERT
- TYPE_SYSTEM_ERROR
- TYPE_SYSTEM_OVERLAY
他にもありますが他のレイヤーはシステム内部で利用する向けなので割愛します。 このうちAndroid6.0、Android7.0でアプリ開発者が利用できるレイヤーはTYPE_TOASTだけです。 端末開発を行っている場合にはシステムのUIDと同一のUIDを持つアプリの場合には次のレイヤーに追加することができます。
- TYPE_PHONE
- TYPE_PRIORITY_PHONE
- TYPE_SYSTEM_ALERT
- TYPE_SYSTEM_ERROR
- TYPE_SYSTEM_OVERLAY
これらの追加できるレイヤーにaddViewできるかを判断する処理はAndroidのソースコード(AOSP)の「frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java」のcheckAddPermissionにてTYPE_TOASTなどの設定されているかどうかを確認されています。
もし違うレイヤーに追加した場合にはエラーになります。
TYPE_TOASTでIMEを表示する
addViewで表示するView上にEditTextを表示することができます。
しかし、EditTextに対してフォーカスを当てたとしてもIMEが表示されることはありません。
IMEを表示するにはフラグとレイヤーで条件があります。
PhoneWindowManager.java内でIMEを表示することができるかを確認するメソッドcanReceiveInputは下記のように設定されています。
3812 private boolean canReceiveInput(WindowState win) { 3813 boolean notFocusable = 3814 (win.getAttrs().flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0; 3815 boolean altFocusableIm = 3816 (win.getAttrs().flags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0; 3817 boolean notFocusableForIm = notFocusable ^ altFocusableIm; 3818 return !notFocusableForIm; 3819 }
このメソッドがtrueで返却されればIMEが表示できる扱いになります。
よくやる手はLayoutParamsのflagにFLAG_ALT_FOCUSABLE_IMを追加してこのメソッドの返り値をtrueにします。
しかしこれの確認している処理の部分は下記の部分になります。
4297 @Override 4298 public void layoutWindowLw(WindowState win, WindowState attached) { 4299 // We've already done the navigation bar and status bar. If the status bar can receive 4300 // input, we need to layout it again to accomodate for the IME window. 4301 if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar) { 4302 return; 4303 }
ここを確認するとステータスバーのレイヤーかナビゲーションバーのレイヤーでないと表示でないと4301のif文でreturnで返却されてしまうようです。
このif文を抜けたあとにIME表示時のViewの位置計算や表示などを行っているためにIMEを表示しているためIMEが表示できません。
また、TYPE_TOASTでしか一般的なアプリではaddViewできないことから一般的なアプリではIMEを表示することができませんので注意しましょう。
対策
対策方法としては1種類しかない状態です。
FacebookのMessengerアプリでチャットヘッド機能がありどの画面上でも表示される機能を持っています。
そこでは丸いチャットヘッド時はWindowManagerでaddViewでViewを追加したものを表示しそれをタップしてメッセージ入力画面を表示した時点でActivityを表示して文字入力を行って実現しているようです。
このやり方の弊害はActivityがオーバーレイしてしまうためもともと動いていたアプリがonPauseに入ってしまうので挙動がおかしくなる可能性があります。
注意しましょう。