WindowManager 源码分 TYPE_TOAST 悬浮窗权限检探究

WindowManager类是一个接口类,继承制ViewManager,Viewmanager是一个简单的接口类,很简单的理解可以知道是对View进行管理的类,里面只有三个方法,下面是源码:

 

源码分析:WindowManager,TYPE_TOAST悬浮窗权限检测问题


WindowManager类是一个接口类,继承制ViewManager,Viewmanager是一个简单的接口类,很简单的理解可以知道是对View进行管理的类,里面只有三个方法,下面是源码:
package android.view;

/** Interface to let you add and remove child views to an Activity. To get an instance
  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
  */
public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     *

Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     *

Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}


下面是WindowMananger的源码头部:

/**
 * The interface that apps use to talk to the window manager.
 *


 * Use Context.getSystemService(Context.WINDOW_SERVICE) to get one of these.
 *


 * Each window manager instance is bound to a particular {@link Display}.
 * To obtain a {@link WindowManager} for a different display, use
 * {@link Context#createDisplayContext} to obtain a {@link Context} for that
 * display, then use Context.getSystemService(Context.WINDOW_SERVICE)
 * to get the WindowManager.
 *


 * The simplest way to show a window on another display is to create a
 * {@link Presentation}.  The presentation will automatically obtain a
 * {@link WindowManager} and {@link Context} for that display.
 *


 *
 * @see android.content.Context#getSystemService
 * @see android.content.Context#WINDOW_SERVICE
 */
public interface WindowManager extends ViewManager 
我们从@see 可以看到我们获取WindowManager的方法是Context的getSystemService方法,只需要传入参数WINDOW_SERVICE就行,比如下面

WindowManager mWm = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);


WindowManager的实现类是WindowManagerImpl,见下面源码,
这个WindowManagerIpml有两个,分别位于不同的包下面,
package android.view;
package com.android.layoutlib.bridge.android.view;

第一个:WindowManagerImpl
package android.view;

package android.view;

import android.annotation.NonNull;
import android.os.IBinder;

/**
 * Provides low-level communication with the system window manager for
 * operations that are bound to a particular context, display or parent window.
 * Instances of this object are sensitive to the compatibility info associated
 * with the running application.
 *
 * This object implements the {@link ViewManager} interface,
 * allowing you to add any View subclass as a top-level window on the screen.
 * Additional window manager specific layout parameters are defined for
 * control over how windows are displayed.  It also implements the {@link WindowManager}
 * interface, allowing you to control the displays attached to the device.
 * 
 *

Applications will not normally use WindowManager directly, instead relying
 * on the higher-level facilities in {@link android.app.Activity} and
 * {@link android.app.Dialog}.
 * 
 *

Even for low-level window manager access, it is almost never correct to use
 * this class.  For example, {@link android.app.Activity#getWindowManager}
 * provides a window manager for adding windows that are associated with that
 * activity -- the window manager will not normally allow you to add arbitrary
 * windows that are not associated with an activity.
 *
 * @see WindowManager
 * @see WindowManagerGlobal
 * @hide
 */
public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;
    private final Window mParentWindow;

    private IBinder mDefaultToken;

    public WindowManagerImpl(Display display) {
        this(display, null);
    }

    private WindowManagerImpl(Display display, Window parentWindow) {
        mDisplay = display;
        mParentWindow = parentWindow;
    }

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mDisplay, parentWindow);
    }

    public WindowManagerImpl createPresentationWindowManager(Display display) {
        return new WindowManagerImpl(display, mParentWindow);
    }

    /**
     * Sets the window token to assign when none is specified by the client or
     * available from the parent window.
     *
     * @param token The default token to assign.
     */
    public void setDefaultToken(IBinder token) {
        mDefaultToken = token;
    }

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

    private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
        // Only use the default token if we don't have a parent window.
        if (mDefaultToken != null && mParentWindow == null) {
            if (!(params instanceof WindowManager.LayoutParams)) {
                throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
            }

            // Only use the default token if we don't already have a token.
            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
            if (wparams.token == null) {
                wparams.token = mDefaultToken;
            }
        }
    }

    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }

    @Override
    public Display getDefaultDisplay() {
        return mDisplay;
    }

第二个WindowManagerImpl:
package com.android.layoutlib.bridge.android.view;

import android.hardware.display.DisplayManagerGlobal;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.DisplayAdjustments;
import android.view.DisplayInfo;
import android.view.View;
import android.view.WindowManager;
import android.view.ViewGroup.LayoutParams;

public class WindowManagerImpl implements WindowManager {
    private final DisplayMetrics mMetrics;
    private final Display mDisplay;

    public WindowManagerImpl(DisplayMetrics metrics) {
        this.mMetrics = metrics;
        DisplayInfo info = new DisplayInfo();
        info.logicalHeight = this.mMetrics.heightPixels;
        info.logicalWidth = this.mMetrics.widthPixels;
        this.mDisplay = new Display((DisplayManagerGlobal)null, 0, info, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
    }

    public Display getDefaultDisplay() {
        return this.mDisplay;
    }

    public void addView(View arg0, LayoutParams arg1) {
    }

    public void removeView(View arg0) {
    }

    public void updateViewLayout(View arg0, LayoutParams arg1) {
    }

    public void removeViewImmediate(View arg0) {
    }
}


从源码来看,package com.android.layoutlib.bridge.android.view;中的WindowManagerImpl似乎没有对WindowManager的几个重要方法进行处理,这个类具体用来做什么,我现在还不知道,这里先不做介绍,我们主要来讨论package android.view包中的WindowManagerIpml,这里我们可以看到,WindowManagerImpl只是做了个桥接,引入了WindowManageGlobal,该类才是真正做事的类,我们常使用的addView,可以在这里仔细看看源码:
public void addView(View view, ViewGroup.LayoutParams params,  Display display, Window parentWindow) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (display == null) {
        throw new IllegalArgumentException("display must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
   ... ...

    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
        // Start watching for system property changes.
        ... ...
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);

        mViews.add(view);//WindowManager用集合保存当前窗口每一个view
        mRoots.add(root);//WindowManager用集合保存当前窗口每一个ViewRoot
        mParams.add(wparams);
    }

    // do this last because it fires off messages to start doing things
    try {
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        ... ...
        throw e;
    }
}
在addView方法中,初始化了ViewRootImpl,并调用其setView方法将View添加进去,ViewRootImpl是当前Window的根View,setView方法特别长,只看重要的部分:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            .. ... ... ...
            try {
                ... ... ... ...
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } catch (RemoteException e) {
                ... ... ... ...
                throw new RuntimeException("Adding window failed", e);
            } finally {
                if (restore) {
                    attrs.restore();
                }
            }
             ... ... ... ...
        }
    }
}

看重点:
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
mWindowSession的类型是IWindowSession, mWindow的类型是IWindow.Stub, 这句代码就是利用AIDL进行IPC, 实际被调用的是Session.addToDisplay:

@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
        InputChannel outInputChannel) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
            outContentInsets, outStableInsets, outInputChannel);
}
mService就是WindowManagerService

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, InputChannel outInputChannel) {
        int[] appOp = new int[1];
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }
        ......
        final int type = attrs.type;

        synchronized(mWindowMap) {
            ......

            mPolicy.adjustWindowParamsLw(win.mAttrs);
            ......
        }
        ......

        return res;
    }

mPolicy是标记为final的成员变量:
final WindowManagerPolicy mPolicy = PolicyManager.makeNewWindowManager();
继续看PolicyManager.makeNewWindowManager:

public final class PolicyManager {
    private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";

    private static final IPolicy sPolicy;

    static {
        // Pull in the actual implementation of the policy at run-time
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
        } catch (InstantiationException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        }
    }

    // Cannot instantiate this class
    private PolicyManager() {}

    ......
    public static WindowManagerPolicy makeNewWindowManager() {
        return sPolicy.makeNewWindowManager();
    }
    ......
}
这里sPolicy是com.android.internal.policy.impl.Policy对象, 再看看它的makeNewWindowManager方法返回的是什么:
public WindowManagerPolicy makeNewWindowManager() {
    return new PhoneWindowManager();
}
现在我们知道mPolicy实际上是PhoneWindowManager, 那么
int res = mPolicy.checkAddPermission(attrs, appOp);
实际调用的代码是PhoneWindowManager中的checkAddPermission:
@Override
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:
            // 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 (mContext.checkCallingOrSelfPermission(permission)
                != PackageManager.PERMISSION_GRANTED) {
            return WindowManagerGlobal.ADD_PERMISSION_DENIED;
        }
    }
    return WindowManagerGlobal.ADD_OKAY;
}

上面截取的是4.4_r1的代码, 我们最关心的部分其实一直没有变, 那就是TYPE_TOAST根本没有做权限检查, 直接break出去了, 最后返回WindowManagerGlobal.ADD_OKAY.
不需要权限显示悬浮窗的原因已经找到了, 接着刚才addWindow方法的分析, 继续看下面一句:

mPolicy.adjustWindowParamsLw(win.mAttrs);

也就是PhoneWindowManager.adjustWindowParamsLw, 注意这里我给出了三个版本的实现, 一个是2.0到2.3.7实现的版本, 一个是4.0.1到4.3.1实现的版本, 一个是4.4实现的版本:
//Android 2.0 - 2.3.7 PhoneWindowManager
    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
        switch (attrs.type) {
            case TYPE_SYSTEM_OVERLAY:
            case TYPE_SECURE_SYSTEM_OVERLAY:
            case TYPE_TOAST:
                // These types of windows can't receive input events.
                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
                break;
        }
    }

//Android 4.0.1 - 4.3.1 PhoneWindowManager
    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
        switch (attrs.type) {
            case TYPE_SYSTEM_OVERLAY:
            case TYPE_SECURE_SYSTEM_OVERLAY:
            case TYPE_TOAST:
                // These types of windows can't receive input events.
                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
                attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
                break;
        }
    }


//Android 4.4 PhoneWindowManager
    @Override
    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
        switch (attrs.type) {
            case TYPE_SYSTEM_OVERLAY:
            case TYPE_SECURE_SYSTEM_OVERLAY:
                // These types of windows can't receive input events.
                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
                attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
                break;
        }
    }

grepcode上没有3.x的代码, 我也没查具体是什么, 没必要考虑3.x.
可以看到, 在4.0.1以前, 当我们使用TYPE_TOAST, Android会偷偷给我们加上FLAG_NOT_FOCUSABLE和FLAG_NOT_TOUCHABLE, 4.0.1开始, 会额外再去掉FLAG_WATCH_OUTSIDE_TOUCH, 这样真的是什么事件都没了. 而4.4开始, TYPE_TOAST被移除了, 所以从4.4开始, 使用TYPE_TOAST的同时还可以接收触摸事件和按键事件了, 而4.4以前只能显示出来, 不能交互.


API level 18及以下使用TYPE_TOAST无法接收触摸事件的原因也找到了.
 

你可能感兴趣的:(WindowManager 源码分 TYPE_TOAST 悬浮窗权限检探究)