In project development, in order to ensure the security of user information, there will be a need to forbid screen capture and screen recording of the page. This kind of information, there are a lot of online, generally by setting the Activity Flag to solve, such as:

// Disable screen capture and screen recording
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
Copy the code

This setting can solve the general anti-screen, screen recording needs. If there is a Popupwindow in the page, the effect in the screen-recording video is:

The non-popupWindow area is black but the Popupwindow area is still visible

As shown in the two giFs below:

When FLAG_SECURE is not set, the screen is recorded as shown below (the watermark in the middle of the git image is ignored) :

With FLAG_SECURE set, the screen will look like this (the watermark in the middle of the Git image will be ignored) :

Cause analysis,

Seeing the above effect, we might wonder that PopupWindow does not have its own window object like Dialog, but instead uses the WindowManager.addView method to display the View on the Activity form. So, if the Activity has set FLAG_SECURE, why can I see PopupWindow when I record the screen?

We first through the getWindow (). AddFlags (WindowManager. LayoutParams. FLAG_SECURE); To analyze the source code:

1, Window. Java

// Window layout parameter
private final WindowManager.LayoutParams mWindowAttributes =
        new WindowManager.LayoutParams();

// Add the identifier
public void addFlags(int flags) {
        setFlags(flags, flags);
    }

// Set the marker via mWindowAttributes
public void setFlags(int flags, int mask) {
        final WindowManager.LayoutParams attrs = getAttributes();
        attrs.flags = (attrs.flags&~mask) | (flags&mask);
        mForcedWindowFlags |= mask;
        dispatchWindowAttributesChanged(attrs);
    }

// Get the layout parameter object, mWindowAttributes
public final WindowManager.LayoutParams getAttributes(a) {
        return mWindowAttributes;
    }
Copy the code

As you can see, the source code for setting the window properties is very simple, that is, by setting the identifier of the mWindowAttributes layout parameter object in the window.

2, PopupWindow. Java

/ / display PopupWindow
public void showAtLocation(View parent, int gravity, int x, int y) {
        mParentRootView = new WeakReference<>(parent.getRootView());
        showAtLocation(parent.getWindowToken(), gravity, x, y);
    }

/ / display PopupWindow
public void showAtLocation(IBinder token, int gravity, int x, int y) {
        if (isShowing() || mContentView == null) {
            return;
        }

        TransitionManager.endTransitions(mDecorView);

        detachFromAnchor();

        mIsShowing = true;
        mIsDropdown = false;
        mGravity = gravity;
        
        // Create the Window layout parameter object
        final WindowManager.LayoutParams p =createPopupLayoutParams(token);
        preparePopup(p);

        p.x = x;
        p.y = y;

        invokePopup(p);
    }

// Create the Window layout parameter object
protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
        p.gravity = computeGravity();
        p.flags = computeFlags(p.flags);
        p.type = mWindowLayoutType;
        p.token = token;
        p.softInputMode = mSoftInputMode;
        p.windowAnimations = computeAnimationResource();
        if(mBackground ! =null) {
            p.format = mBackground.getOpacity();
        } else {
            p.format = PixelFormat.TRANSLUCENT;
        }
        if (mHeightMode < 0) {
            p.height = mLastHeight = mHeightMode;
        } else {
            p.height = mLastHeight = mHeight;
        }
        if (mWidthMode < 0) {
            p.width = mLastWidth = mWidthMode;
        } else {
            p.width = mLastWidth = mWidth;
        }
        p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
                | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
        p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
        return p;
    }

// Add PopupWindow to Window
private void invokePopup(WindowManager.LayoutParams p) {
        if(mContext ! =null) {
            p.packageName = mContext.getPackageName();
        }

        final PopupDecorView decorView = mDecorView;
        decorView.setFitsSystemWindows(mLayoutInsetDecor);

        setLayoutDirectionFromAnchor();

        mWindowManager.addView(decorView, p);

        if(mEnterTransition ! =null) { decorView.requestEnterTransition(mEnterTransition); }}Copy the code

Through PopupWindow source code analysis, it is easy to see that the call showAtLocation, will create a WindowManager alone. LayoutParams layout parameter object, used to display PopupWindow, There is no screen-blocking Flag set on the layout parameter object.

How to solve

Now that the reason has been found, how to deal with it? Let’s go back to the key code for Window:

// Set the marker via mWindowAttributes
public void setFlags(int flags, int mask) {
        final WindowManager.LayoutParams attrs = getAttributes();
        attrs.flags = (attrs.flags&~mask) | (flags&mask);
        mForcedWindowFlags |= mask;
        dispatchWindowAttributesChanged(attrs);
    }
Copy the code

You only need to get WindowManager. LayoutParams object, set the flag again. However, PopupWindow does not have the same method of obtaining the window directly as Activity, let alone setting the Flag. PopupWindow (PopupWindow) :

// Add PopupWindow to Window
private void invokePopup(WindowManager.LayoutParams p) {
        if(mContext ! =null) {
            p.packageName = mContext.getPackageName();
        }

        final PopupDecorView decorView = mDecorView;
        decorView.setFitsSystemWindows(mLayoutInsetDecor);

        setLayoutDirectionFromAnchor();

        / / add the View
        mWindowManager.addView(decorView, p);

        if(mEnterTransition ! =null) { decorView.requestEnterTransition(mEnterTransition); }}Copy the code

We call showAtLocation, and we end up executing mWindowManager.addView(decorView, p); So if I can get before addView WindowManager. LayoutParams?

The obvious answer is no by default. Because the PopupWindow is not publicly accessible WindowManager. LayoutParams method, and mWindowManager is private.

How can we solve this? We can solve this problem by hook. First we use dynamic proxy intercept PopupWindow addView method of the class get WindowManager. LayoutParams object, set the corresponding Flag, reflection get mWindowManager objects to perform addView method.

Risk analysis:

However, hook method also has certain risks, because mWindowManager is a private object, unlike Public API, Google will not consider its compatibility in the subsequent upgrade of Android version, so it is possible to change its name in the subsequent Android version. Then we have a problem getting the mWindowManager object from reflection. However, from the perspective of the Android source code of previous versions, the probability of mWindowManager being changed is not large, so hooks can also be used. We try to consider this risk when writing code, so as to avoid problems in the future.

public class PopupWindow {...privateWindowManager mWindowManager; . }Copy the code

The addView method is a public method of the ViewManger interface, so we can use it with confidence.

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
Copy the code

Function implementation

For the sake of maintainability and extensibility of hooks, let’s encapsulate the related code into a separate utility class.

package com.ccc.ddd.testpopupwindow.utils;

import android.os.Handler;
import android.view.WindowManager;
import android.widget.PopupWindow;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class PopNoRecordProxy implements InvocationHandler {
    private Object mWindowManager;//PopupWindow class mWindowManager object

    public static PopNoRecordProxy instance(a) {
        return new PopNoRecordProxy();
    }

    public void noScreenRecord(PopupWindow popupWindow) {
        if (popupWindow == null) {
            return;
        }
        try {
            // Get the private object of the PopupWindow class: mWindowManager by reflection
            Field windowManagerField = PopupWindow.class.getDeclaredField("mWindowManager");
            windowManagerField.setAccessible(true);
            mWindowManager = windowManagerField.get(popupWindow);
            if(mWindowManager == null) {return;
            }
            // Create a dynamic proxy object for WindowManager
            Object proxy = Proxy.newProxyInstance(Handler.class.getClassLoader(), new Class[]{WindowManager.class}, this);

            // Inject dynamic proxy object (i.e., the mWindowManager object is represented by the proxy object)
            windowManagerField.set(popupWindow, proxy);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch(NoSuchFieldException e) { e.printStackTrace(); }}@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // Intercept method mWindowManager.addView(View View, viewGroup.layoutParams Params);
            if(method ! =null&& method.getName() ! =null && method.getName().equals("addView") && args ! =null && args.length == 2) {
                / / get WindowManager. LayoutParams, i.e., ViewGroup. LayoutParams
                WindowManager.LayoutParams params = (WindowManager.LayoutParams) args[1];
                // Disable screen recordingsetNoScreenRecord(params); }}catch (Exception ex) {
            ex.printStackTrace();
        }
        return method.invoke(mWindowManager, args);
    }

    /** * Disable screen recording */
    private void setNoScreenRecord(WindowManager.LayoutParams params) {
        setFlags(params, WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
    }

    /** * Allow screen recording */
    private void setAllowScreenRecord(WindowManager.LayoutParams params) {
        setFlags(params, 0, WindowManager.LayoutParams.FLAG_SECURE);
    }

    / * * * set the WindowManager. LayoutParams flag attributes (reference system class Window. SetFlags (int flags, int mask)) * *@param params WindowManager.LayoutParams
     * @param flags  The new window flags (see WindowManager.LayoutParams).
     * @param mask   Which of the window flag bits to modify.
     */
    private void setFlags(WindowManager.LayoutParams params, int flags, int mask) {
        try {
            if (params == null) {
                return;
            }
            params.flags = (params.flags & ~mask) | (flags & mask);
        } catch(Exception ex) { ex.printStackTrace(); }}}Copy the code

Popwindow disables the screen recording utility class. Example code for this rule:

    / / create the PopupWindow
    // In normal projects, this method can be changed to a factory class
    // In normal projects, you can also customize PopupWindow, in its class set to disable screen recording
    private PopupWindow createPopupWindow(View view, int width, int height) {
        PopupWindow popupWindow = new PopupWindow(view, width, height);
        //PopupWindow Disables screen recording
        PopNoRecordProxy.instance().noScreenRecord(popupWindow);
        return popupWindow;
    }

   / / display Popupwindow
   private void showPm(a) {
        View view = LayoutInflater.from(this).inflate(R.layout.pm1, null);
       PopupWindow  pw = createPopupWindow(view,ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        pw1.setFocusable(false);
        pw1.showAtLocation(this.getWindow().getDecorView(), Gravity.BOTTOM | Gravity.RIGHT, PopConst.PopOffsetX, PopConst.PopOffsetY);
    }
Copy the code

Screen effect drawing:

The Demo address

wwa.lanzoui.com/iY6kyrunb2d