Android - Unable to add window android.view.ViewRootImpl$W@6518342 -- permission denied for window type 2133


因為跟博主碰到了一樣的問題,所以記錄一下分析原理

原文鏈接:https://www.jianshu.com/p/b0364074288a

首先,先介紹下背景環境,第一,是Android7.0,其次,要屏蔽home鍵,先上下出問題的代碼

private void testWindow() { AlertDialog d = new AlertDialog.Builder(this) .setPositiveButton("ok", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .setTitle("i am a test").create(); d.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); d.setOnKeyListener(new DialogInterface.OnKeyListener() { @Override public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_HOME) { Log.i(TAG, "onKey: key home press"); return true; } return false; } }); d.show(); } 

代碼很簡單,出問題的罪魁禍首就是這貨了

d.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 

設置這貨就是為了能夠捕獲到home鍵,當然,調用這句話前提是申請了權限。

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (Build.VERSION.SDK_INT >= 23) { if (Settings.canDrawOverlays(this)) { testWindow(); } else { Uri uri = Uri.parse("package:" + MainActivity.this.getPackageName()); Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, uri); startActivityForResult(intent, 100); } } } 

在onActivityResult處理

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 100) { if (Build.VERSION.SDK_INT >= 23 && Settings.canDrawOverlays(this)) { testWindow(); } else { ToastUtil.showToast("permission denied."); } } } 

當然,AndroidManifest里添加權限(沒添加權限,在前面申請出來的框框中,就不能授權了)

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> 

網上查了好久,明明已經授權了啊,為毛還拋出這個錯誤,今天就根據代碼來排查下。
先根據異常定位下代碼。(后面的就不大需要了,這些就夠了)

Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@6518342 -- permission denied for window type 2009 at android.view.ViewRootImpl.setView(ViewRootImpl.java:702) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93) at android.app.Dialog.show(Dialog.java:316) at com.felix.windowndemo.MainActivity.testWindow(MainActivity.java:96) 

首先是因為調用了show而引起的,show中會添加view到Windows,報錯的底層定位到ViewRootImpl,直接點開查看相關代碼

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { //some other code try{ res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } } if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; mAdded = false; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); switch (res) { case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?"); case WindowManagerGlobal.ADD_NOT_APP_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not for an application"); case WindowManagerGlobal.ADD_APP_EXITING: throw new WindowManager.BadTokenException( "Unable to add window -- app for token " + attrs.token + " is exiting"); case WindowManagerGlobal.ADD_DUPLICATE_ADD: throw new WindowManager.BadTokenException( "Unable to add window -- window " + mWindow + " has already been added"); case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED: // Silently ignore -- we would have just removed it // right away, anyway. return; case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON: throw new WindowManager.BadTokenException("Unable to add window " + mWindow + " -- another window of type " + mWindowAttributes.type + " already exists"); case WindowManagerGlobal.ADD_PERMISSION_DENIED: throw new WindowManager.BadTokenException("Unable to add window " + mWindow + " -- permission denied for window type " + mWindowAttributes.type); case WindowManagerGlobal.ADD_INVALID_DISPLAY: throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow + " -- the specified display can not be found"); case WindowManagerGlobal.ADD_INVALID_TYPE: throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow + " -- the specified window type " + mWindowAttributes.type + " is not valid"); } throw new RuntimeException( "Unable to add window -- unknown error code " + res); } //other code } 

res==WindowManagerGlobal.ADD_PERMISSION_DENIED
的時候,拋出如圖異常,那就繼續看res如何獲取的

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);

mWindowSession的定義在構造函數中

public ViewRootImpl(Context context, Display display) { mContext = context; mWindowSession = WindowManagerGlobal.getWindowSession(); //other code } 

繼續看

public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { InputMethodManager imm = InputMethodManager.getInstance(); IWindowManager windowManager = getWindowManagerService(); sWindowSession = windowManager.openSession( new IWindowSessionCallback.Stub() { @Override public void onAnimatorScaleChanged(float scale) { ValueAnimator.setDurationScale(scale); } }, imm.getClient(), imm.getInputContext()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } return sWindowSession; } } 

WindowManagerService.openSession得來的,直接查找WindowManagerService代碼(這里就不用糾結為毛是WindowManagerService了,看下名字就行,其他的不在本文研究范圍內)

@Override public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client, IInputContext inputContext) { if (client == null) throw new IllegalArgumentException("null client"); if (inputContext == null) throw new IllegalArgumentException("null inputContext"); Session session = new Session(this, callback, client, inputContext); return session; } 

直接是new出來的,剛才是addToDisplay這個函數,直接進去查看

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outOutsets, outInputChannel); } 

調用mService.addWindow,這里的mService定義是

final WindowManagerService mService; 

繼續看WindowManagerService.addWindow

public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { int[] appOp = new int[1]; int res = mPolicy.checkAddPermission(attrs, appOp); if (res != WindowManagerGlobal.ADD_OKAY) { return res; } //other code } 

在這里,因為知道返回的是ADD_PERMISSION_DENIED,不是ADD_OKAY,所以后面的也不用繼續看了,這里調用的是mPolicy.checkAddPermission(attrs, appOp);
mPolicy直接看定義final WindowManagerPolicy mPolicy = new PhoneWindowManager();
所以直接看PhoneWindowManager.checkAddPermission

public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) { int type = attrs.type; outAppOp[0] = AppOpsManager.OP_NONE; if (!((type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) || (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) || (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW))) { return WindowManagerGlobal.ADD_INVALID_TYPE; } if (type < FIRST_SYSTEM_WINDOW || type > LAST_SYSTEM_WINDOW) { // Window manager will make sure these are okay. return WindowManagerGlobal.ADD_OKAY; } String permission = null; switch (type) { case TYPE_TOAST: // XXX right now the app process has complete control over // this... should introduce a token to let the system // monitor/control what they are doing. outAppOp[0] = AppOpsManager.OP_TOAST_WINDOW; break; case TYPE_DREAM: case TYPE_INPUT_METHOD: case TYPE_WALLPAPER: case TYPE_PRIVATE_PRESENTATION: case TYPE_VOICE_INTERACTION: case TYPE_ACCESSIBILITY_OVERLAY: case TYPE_QS_DIALOG: // The window manager will check these. break; case TYPE_PHONE: case TYPE_PRIORITY_PHONE: case TYPE_SYSTEM_ALERT: case TYPE_SYSTEM_ERROR: case TYPE_SYSTEM_OVERLAY: permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW; outAppOp[0] = AppOpsManager.OP_SYSTEM_ALERT_WINDOW; break; default: permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; } if (permission != null) { if (android.Manifest.permission.SYSTEM_ALERT_WINDOW.equals(permission)) { final int callingUid = Binder.getCallingUid(); // system processes will be automatically allowed privilege to draw if (callingUid == Process.SYSTEM_UID) { return WindowManagerGlobal.ADD_OKAY; } // check if user has enabled this operation. SecurityException will be thrown if // this app has not been allowed by the user final int mode = mAppOpsManager.checkOpNoThrow(outAppOp[0], callingUid, attrs.packageName); switch (mode) { case AppOpsManager.MODE_ALLOWED: case AppOpsManager.MODE_IGNORED: // although we return ADD_OKAY for MODE_IGNORED, the added window will // actually be hidden in WindowManagerService return WindowManagerGlobal.ADD_OKAY; case AppOpsManager.MODE_ERRORED: try { ApplicationInfo appInfo = mContext.getPackageManager() .getApplicationInfo(attrs.packageName, UserHandle.getUserId(callingUid)); // Don't crash legacy apps if (appInfo.targetSdkVersion < Build.VERSION_CODES.M) { return WindowManagerGlobal.ADD_OKAY; } } catch (PackageManager.NameNotFoundException e) { /* ignore */ } return WindowManagerGlobal.ADD_PERMISSION_DENIED; default: // in the default mode, we will make a decision here based on // checkCallingPermission() if (mContext.checkCallingPermission(permission) != PackageManager.PERMISSION_GRANTED) { return WindowManagerGlobal.ADD_PERMISSION_DENIED; } else { return WindowManagerGlobal.ADD_OKAY; } } } if (mContext.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { return WindowManagerGlobal.ADD_PERMISSION_DENIED; } } return WindowManagerGlobal.ADD_OKAY; } 

我們設置的是TYPE_KEYGUARD_DIALOG,所以權限是android.Manifest.permission.INTERNAL_SYSTEM_WINDOW
然后調用mContext.checkCallingOrSelfPermission(permission)看是否是PackageManager.PERMISSION_GRANTED我們可以看下Context的checkCallingOrSelfPermission這個函數。具體實現在ContextImpl

public int checkCallingOrSelfPermission(String permission) { if (permission == null) { throw new IllegalArgumentException("permission is null"); } return checkPermission(permission, Binder.getCallingPid(), Binder.getCallingUid()); } 

傳入調用的pid和uid,繼續看checkPermission這個函數    注:P開始這里有變更,但是最終處理邏輯不變

public int checkPermission(String permission, int pid, int uid) { if (permission == null) { throw new IllegalArgumentException("permission is null"); } try { return ActivityManagerNative.getDefault().checkPermission( permission, pid, uid); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } 

ActivityManagerNative.getDefault()返回的即是ActivityManagerService直接看對應的函數

 public int checkPermission(String permission, int pid, int uid) { if (permission == null) { return PackageManager.PERMISSION_DENIED; } return checkComponentPermission(permission, pid, uid, -1, true); } 

繼續看checkComponentPermission

int checkComponentPermission(String permission, int pid, int uid, int owningUid, boolean exported) { if (pid == MY_PID) { return PackageManager.PERMISSION_GRANTED; } return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported); } 

前面有一句


if (pid == MY_PID) { return PackageManager.PERMISSION_GRANTED; } 

MY_PID的定義為static final int MY_PID = Process.myPid();也就是說調用的pid是當前(AMS)所在線程,則直接允許,我們的肯定是我們自己的進程,所以,這個判斷是fasle的,繼續看ActivityManager.checkComponentPermission

public static int checkComponentPermission(String permission, int uid, int owningUid, boolean exported) { // Root, system server get to do everything. final int appId = UserHandle.getAppId(uid); if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) { return PackageManager.PERMISSION_GRANTED; } // Isolated processes don't get any permissions. if (UserHandle.isIsolated(uid)) { return PackageManager.PERMISSION_DENIED; } // If there is a uid that owns whatever is being accessed, it has // blanket access to it regardless of the permissions it requires. if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) { return PackageManager.PERMISSION_GRANTED; } // If the target is not exported, then nobody else can get to it. if (!exported) { /* RuntimeException here = new RuntimeException("here"); here.fillInStackTrace(); Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid, here); */ return PackageManager.PERMISSION_DENIED; } if (permission == null) { return PackageManager.PERMISSION_GRANTED; } try { return AppGlobals.getPackageManager() .checkUidPermission(permission, uid); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } 

主要看兩個,第一個就

            return PackageManager.PERMISSION_GRANTED; } 

如果是超級用戶或者系統用戶,直接允許,換句話說,有root權限的或者系統服務的,根本不需要申請任何權限,直接都是允許的。
最后返回的是

AppGlobals.getPackageManager()
                    .checkUidPermission(permission, uid);``` 這里```AppGlobals.getPackageManager()```返回的是```PackageManagerService```,如果是23以下的代碼,主要是查詢在```AndroidManafest.xml```里定義的權限,如果是23以上的,還要檢查下是否granted過的。涉及到的代碼比較復雜,有空再繼續寫。但是可以肯定的是 

android.Manifest.permission.INTERNAL_SYSTEM_WINDOW

這貨沒定義,就算定義了,其實在判斷的時候也加不進去,因為這個權限聲明的時候就表明是系統權限。所以,這個需求是只能系統進程或者有root才能做到的,普通app就只能到此了。
最后,有人可能會問,type2009啥意思,這2009就是```TYPE_KEYGUARD_DIALOG```這個的值了,看定義 

public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;

public static final int FIRST_SYSTEM_WINDOW = 2000;


至於授予的權限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

在這里其實並沒啥卵用,要設置type是```TYPE_SYSTEM_ALERT```這個才需要


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM