Android Tips #2 ViewRoot#setView() のパーミッションチェック
ViewRoot#setView(View, WindowManager.LayoutParams, View) の WindowManager.LayoutParams で type にいろいろ指定して動作の確認をしていたんだけど、たとえば以下のような例外が出た場合、どういうパーミッションを設定すればよいかがドキュメントにちゃんと記載されていないので、追いかけてみたメモ。たとえば WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL を指定するなど。
02-22 14:10:01.265: E/AndroidRuntime(2257): Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRoot$W@43766e18 -- permission denied for this window type
呼び出しは以下のようになっていて、最終的に PackageManagerService#checkUidPermission(String, int) でチェック処理が行われている。
ViewRoot#setView(View, WindowManager.LayoutParams, View) IWindowSession#add(IWindow, WindowManager.LayoutParams, int, Rect, InputChannel) WindowManagerService#add(IWindow, WindowManager.LayoutParams, int, Rect, InputChannel) WindowManagerService#addWindow(Session session, IWindow, WindowManager.LayoutParams, int, Rect, InputChannel) WindowManagerPolicy#checkAddPermission(WindowManager.LayoutParams) PhoneWindowManager#checkAddPermission(WindowManager.LayoutParams) Context#checkCallingOrSelfPermission(String) ContextWrapper#checkCallingOrSelfPermission(String) ContextImpl#checkCallingOrSelfPermission(String) ContextImpl#checkPermission(String, int, int) IActivityManager#checkPermission(String, int, int) ActivityManagerService#checkPermission(String, int, int) ActivityManagerService#checkComponentPermission(String, int, int, int) IPackageManager#checkUidPermission(String, int) PackageManagerService#checkUidPermission(String, int)
普通のパーミッションならチェックは以下のコードのとおり PhoneWindowManager#checkAddPermission(WindowManger.LayoutParams) で止まる。
711 /** {@inheritDoc} */ 712 public int checkAddPermission(WindowManager.LayoutParams attrs) { 713 int type = attrs.type; 714 715 if (type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW 716 || type > WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { 717 return WindowManagerImpl.ADD_OKAY; 718 } 719 String permission = null; 720 switch (type) { 721 case TYPE_TOAST: 722 // XXX right now the app process has complete control over 723 // this... should introduce a token to let the system 724 // monitor/control what they are doing. 725 break; 726 case TYPE_INPUT_METHOD: 727 case TYPE_WALLPAPER: 728 // The window manager will check these. 729 break; 730 case TYPE_PHONE: 731 case TYPE_PRIORITY_PHONE: 732 case TYPE_SYSTEM_ALERT: 733 case TYPE_SYSTEM_ERROR: 734 case TYPE_SYSTEM_OVERLAY: 735 permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW; 736 break; 737 default: 738 permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; 739 } 740 if (permission != null) { 741 if (mContext.checkCallingOrSelfPermission(permission) 742 != PackageManager.PERMISSION_GRANTED) { 743 return WindowManagerImpl.ADD_PERMISSION_DENIED; 744 } 745 } 746 return WindowManagerImpl.ADD_OKAY; 747 }
このコードからわかることは以下のようなパーミッションがそれぞれ必要であるということ。
- パーミッション必要なし
- TYPE_TOAST
- TYPE_INPUT_METHOD
- TYPE_WALLPAPER
- android.Manifest.permission.SYSTEM_ALERT_WINDOW が必要
- TYPE_PHONE
- TYPE_PRIORITY_PHONE
- TYPE_SYSTEM_ALERT
- TYPE_SYSTEM_ERROR
- TYPE_SYSTEM_OVERLAY
- android.Manifest.permission.INTERNAL_SYSTEM_WINDOW が必要
- 上記以外
最終的にチェックしている箇所は以下のようになっている。
1689 public int checkUidPermission(String permName, int uid) { 1690 synchronized (mPackages) { 1691 Object obj = mSettings.getUserIdLP(uid); 1692 if (obj != null) { 1693 GrantedPermissions gp = (GrantedPermissions)obj; 1694 if (gp.grantedPermissions.contains(permName)) { 1695 return PackageManager.PERMISSION_GRANTED; 1696 } 1697 } else { 1698 HashSet<String> perms = mSystemPermissions.get(uid); 1699 if (perms != null && perms.contains(permName)) { 1700 return PackageManager.PERMISSION_GRANTED; 1701 } 1702 } 1703 } 1704 return PackageManager.PERMISSION_DENIED; 1705 }
gp.grantedPermissions.contains(permName) ってところに "android.permission.INTERNAL_SYSTEM_WINDOW" が渡されるので、あらかじめ grantedPermissions という HashMap にこの文字列が含まれていれば勝ちだ。ただ、AndroidManifest.xml に
実際に動作を確認したわけではないので、ここまで処理が流れてきていない可能性もある。
大体 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL なんて Android 全体を見渡しても StatusBarService でしか使ってないんだから、StatusBarService 専用ということで FA なんだと思うけど、じゃあ何で Android API として公開されているのか。しかし、アプリに直接ステータスバーの中を好きにいじられたらおかしなことになっちゃうから、アプリからは TYPE_SYSTEM_ALERT、TYPE_SYSTEM_ERROR、TYPE_SYSTEM_OVERLAY までぐらいしか使えないということなのかな。