首先,先介紹下背景環(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è)才需要