zoukankan      html  css  js  c++  java
  • Android

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

    原文链接: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```这个才需要
  • 相关阅读:
    Mysql 数据库高级
    Mysql 数据库
    并发编程
    网络编程
    1113
    1112
    P相遇游戏
    中位数
    PETS
    打暴力程序的正确做法
  • 原文地址:https://www.cnblogs.com/hustcser/p/11138886.html
Copyright © 2011-2022 走看看