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 までぐらいしか使えないということなのかな。

ちなみに確認したソースコードAndroid-2.3.7_r1。