首先,先介绍下背景环境,第一,是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
                    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