因为跟博主碰到了一样的问题,所以记录一下分析原理

原文链接: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```这个才需要

Android - Unable to add window android.view.ViewRootImpl$W@6518342 -- permission denied for window type 2133的更多相关文章

  1. Android Studio:Unable to add window android.view.ViewRootImpl$W@5e2d85a -- permission denied for this window 第一行代码

    学习<第一行代码>的时候,出现的错误. java.lang.RuntimeException: Unable to start receiver com.example.sevenun.l ...

  2. Android Unable to add window -- token android.os.BinderProxy@3a067204 is not valid错误分析记录

    打开APP时,出现闪退的情况,查看android studio报错信息,主要为: Unable to add window -- token android.os.BinderProxy@3a0672 ...

  3. Android 读写权限,已经授权情况下,仍然(Permission denied)

    首次安装APP,获取读写权限以后, 当读取文件时候,仍然会遇见(Permission denied)错误,解决方案是杀掉APP,重新打开APP即可. 应该属于部分版本系统的bug,直到APP所有的pr ...

  4. 关于android 5.0报错:dlopen failed: couldn't map ... Permission denied

    问题描述: 我的应用当中集成了一个安全相关的sdk,而这个sdk中使用的so是加过壳的. 它加载native so的方式是:Java System.loadLibrary --> native ...

  5. permission denied for window type 2003

    今天在做系统悬浮窗的时候出现权限拒绝,类型是2003,这里要说下,做系统悬浮窗需要申请权限,6.0以上的 还需要动态申请下,这里我就不过多描述了, 我在申请完权限后仍然不行,这里主要是出现在了这个类型 ...

  6. WindowManager$BadTokenException: Unable to add window permission denied for this window type

    10-11 11:47:27.472: E/AndroidRuntime(12804): java.lang.RuntimeException: Unable to start activity Co ...

  7. Activity has leaked window that was originally added -界面退出时未关闭对话框异常 android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running? -

    退出Activity时弹出登录框,点击确定finish当前Activity,结果报了这个错,随后查找资料知道 原因: 是因为退出Activity时没有关闭弹出框,出现了这个错误 解决方法: 只需要在a ...

  8. bug_ _ android.view.WindowManager$BadTokenException: Unable to add window -- token

    ========4       关于android的一个常见错误:Unable to add window --token is not valid android.view.WindowManage ...

  9. android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application

      原博客地址:http://aijiawang-126-com.javaeye.com/blog/662336 在Activity中newSpinner是我把mContext传入,但是出了 andr ...

随机推荐

  1. SQLSERVER调用OPENROWSET的方法

    前言:正好这两天在同步生产环境的某张表数据到测试环境,之前用过一些同步数据软件,感觉不太可靠,有时候稍有操作不当,就会出现生产环境数据被清空等情况,还要去恢复数据.如果能恢复还好,不能恢复那么.... ...

  2. 在vue下引入jquery bootstrap

    在vue 项目中引入jquery bootstrap 引入jquery npm install jquery --save-dev 在项目根目录下的build/webpack.base.conf.js ...

  3. strtok的使用

    /* strtok函数的使用 */ #include <stdio.h> #include <stdlib.h> #include <string.h> // 函数 ...

  4. java.sql.SQLException: Access denied for user 'root'@'d001' (using password: YES)

    在安装CDH的时候报错了: root@d001:/var/cache//base# /opt/cm-/share/cmf/schema/scm_prepare_database.sh mysql cd ...

  5. 《Head First 软件开发》阅读四

    构建代码:自动化构建 代码的完成不只是能运行,还包括编译代码和打包成可配置的单元.学会一个构建工具来编写自己的说明处理源代码.新的团队成员需要立刻知道软件的关联组件和主要类去做测试,但开发人员不是心理 ...

  6. linux 环境下安装MySQL5.7(yum)

    安装环境: CentOS7 64位,MySQL5.7 原文链接:https://blog.csdn.net/xyang81/article/details/51759200 1. 配置yum源 在My ...

  7. 9. ClustrixDB主从复制

    一.在线添加从库 主集群: 10.1.1.23:5306 从集群: 10.1.3.88:5306 主库开启binlog MySQL [(none)]> CREATE BINLOG 'clustr ...

  8. POJ 3275 Ranking the cows ( Floyd求解传递闭包 && Bitset优化 )

    题意 : 给出 N 头牛,以及 M 个某些牛之间的大小关系,问你最少还要确定多少对牛的关系才能将所有的牛按照一定顺序排序起来 分析 : 这些给出的关系想一下就知道是满足传递性的 例如 A > B ...

  9. ELK+Filebeat+redis整合

    前面的博客,有具体的ELK安装配置步骤,此处在其基础上修改 修改配置文件并启动 [root@topcheer filebeat-6.2.3-linux-x86_64]# vim filebeat.ym ...

  10. 解决运行webpack --config webpack.dev.config.js 报错ReferenceError: _dirname is not defined

    控制台报错信息如下 将webpack.dev.config.js中的_dirname 改为 __dirname 注意这里是两个下划线谢谢!感觉好坑