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

首先,先介紹下背景環(huán)境魔吐,第一扎筒,是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();
    }

代碼很簡單,出問題的罪魁禍?zhǔn)拙褪沁@貨了

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

設(shè)置這貨就是為了能夠捕獲到home鍵辞色,當(dāng)然骨宠,調(diào)用這句話前提是申請了權(quán)限。

@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.");
            }
        }
    }

當(dāng)然相满,AndroidManifest里添加權(quán)限(沒添加權(quán)限层亿,在前面申請出來的框框中,就不能授權(quán)了)

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

網(wǎng)上查了好久立美,明明已經(jīng)授權(quán)了啊匿又,為毛還拋出這個(gè)錯(cuò)誤,今天就根據(jù)代碼來排查下建蹄。
先根據(jù)異常定位下代碼碌更。(后面的就不大需要了裕偿,這些就夠了)

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)

首先是因?yàn)檎{(diào)用了show而引起的,show中會添加view到Windows痛单,報(bào)錯(cuò)的底層定位到ViewRootImpl击费,直接點(diǎn)開查看相關(guān)代碼

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
}

當(dāng)res==WindowManagerGlobal.ADD_PERMISSION_DENIED
的時(shí)候,拋出如圖異常桦他,那就繼續(xù)看res如何獲取的

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

mWindowSession的定義在構(gòu)造函數(shù)中

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

繼續(xù)看

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代碼(這里就不用糾結(jié)為毛是WindowManagerService了,看下名字就行快压,其他的不在本文研究范圍內(nèi))

@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這個(gè)函數(shù),直接進(jìn)去查看

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);
    }

調(diào)用mService.addWindow蔫劣,這里的mService定義是

final WindowManagerService mService;

繼續(xù)看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
}

在這里坪郭,因?yàn)橹婪祷氐氖?code>ADD_PERMISSION_DENIED,不是ADD_OKAY脉幢,所以后面的也不用繼續(xù)看了歪沃,這里調(diào)用的是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;
    }

我們設(shè)置的是TYPE_KEYGUARD_DIALOG,所以權(quán)限是android.Manifest.permission.INTERNAL_SYSTEM_WINDOW
然后調(diào)用mContext.checkCallingOrSelfPermission(permission)看是否是PackageManager.PERMISSION_GRANTED我們可以看下Context的checkCallingOrSelfPermission這個(gè)函數(shù)嫌松。具體實(shí)現(xiàn)在ContextImpl

public int checkCallingOrSelfPermission(String permission) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }

        return checkPermission(permission, Binder.getCallingPid(),
                Binder.getCallingUid());
    }

傳入調(diào)用的pid和uid沪曙,繼續(xù)看checkPermission這個(gè)函數(shù)

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直接看對應(yīng)的函數(shù)

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

繼續(xù)看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();也就是說調(diào)用的pid是當(dāng)前(AMS)所在線程,則直接允許萎羔,我們的肯定是我們自己的進(jìn)程液走,所以,這個(gè)判斷是fasle的贾陷,繼續(xù)看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();
        }
    }

主要看兩個(gè)缘眶,第一個(gè)就

            return PackageManager.PERMISSION_GRANTED;
        }

如果是超級用戶或者系統(tǒng)用戶,直接允許髓废,換句話說巷懈,有root權(quán)限的或者系統(tǒng)服務(wù)的,根本不需要申請任何權(quán)限慌洪,直接都是允許的顶燕。
最后返回的是

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

android.Manifest.permission.INTERNAL_SYSTEM_WINDOW

這貨沒定義,就算定義了剂买,其實(shí)在判斷的時(shí)候也加不進(jìn)去惠爽,因?yàn)檫@個(gè)權(quán)限聲明的時(shí)候就表明是系統(tǒng)權(quán)限癌蓖。所以,這個(gè)需求是只能系統(tǒng)進(jìn)程或者有root才能做到的婚肆,普通app就只能到此了租副。
最后,有人可能會問较性,type2009啥意思用僧,這2009就是```TYPE_KEYGUARD_DIALOG```這個(gè)的值了,看定義

public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;

public static final int FIRST_SYSTEM_WINDOW = 2000;


至于授予的權(quán)限

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

在這里其實(shí)并沒啥卵用赞咙,要設(shè)置type是```TYPE_SYSTEM_ALERT```這個(gè)才需要
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末责循,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子攀操,更是在濱河造成了極大的恐慌院仿,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件速和,死亡現(xiàn)場離奇詭異歹垫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)颠放,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進(jìn)店門排惨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慈迈,你說我怎么就攤上這事若贮。” “怎么了痒留?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蠢沿。 經(jīng)常有香客問我伸头,道長,這世上最難降的妖魔是什么舷蟀? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任恤磷,我火速辦了婚禮,結(jié)果婚禮上野宜,老公的妹妹穿的比我還像新娘扫步。我一直安慰自己,他們只是感情好匈子,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布河胎。 她就那樣靜靜地躺著,像睡著了一般虎敦。 火紅的嫁衣襯著肌膚如雪游岳。 梳的紋絲不亂的頭發(fā)上政敢,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天,我揣著相機(jī)與錄音胚迫,去河邊找鬼喷户。 笑死,一個(gè)胖子當(dāng)著我的面吹牛访锻,可吹牛的內(nèi)容都是我干的褪尝。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼期犬,長吁一口氣:“原來是場噩夢啊……” “哼河哑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起哭懈,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤灾馒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后遣总,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體睬罗,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年旭斥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了容达。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,912評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡垂券,死狀恐怖花盐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情菇爪,我是刑警寧澤算芯,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站凳宙,受9級特大地震影響熙揍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜氏涩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一届囚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧是尖,春花似錦意系、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春作郭,著一層夾襖步出監(jiān)牢的瞬間陨囊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工夹攒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜘醋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓咏尝,卻偏偏與公主長得像压语,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子编检,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評論 2 361

推薦閱讀更多精彩內(nèi)容