SurfaceView not working inside PopupWindow

2.3k views Asked by At

I gonna show a preview using a PopupWindow based on AirView feature of Samsung SPen

But the problem is that the SurfaceView is not created and non of the SurfaceHolder.Callback methods are called.

The surface region becomes transparent when the popup is displayed because the surface is not created at all.

SurfaceView is not created and is transparent:

enter image description here

HoverPreview:

public class HoverPreview extends LinearLayout implements View.OnHoverListener, SurfaceHolder.Callback {
    private static final String TAG = "HoverPreview";
    private SurfaceHolder mHolder = null;
    View mAnchorView = null;
    String videoPath;
    int position;
    private boolean IsMediaPlayerReady = false;
    private MediaPlayer mMediaPlayer;
    private SurfaceView mSurfaceView;
    Context context;

    public HoverPreview(Context context, String videoPath, int position) {
        super(context);
        this.videoPath = videoPath;
        this.position = position;
        setupLayout(context);
    }

    public HoverPreview(Context context, AttributeSet attrs) {
        super(context, attrs);
        setupLayout(context);
    }

    public HoverPreview(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setupLayout(context);
    }

    private void setupLayout(Context context) {
        this.context = context;
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.media_browser_hover, this);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        Log.d(TAG, "HoverSurface created");

        final Surface surface = surfaceHolder.getSurface();
        if (surface == null) return;
        if (!surface.isValid()) return;

        mHolder = surfaceHolder;

        mMediaPlayer = new MediaPlayer();

        try {
            mMediaPlayer.setDataSource(videoPath);
        } catch (IOException e) {
            e.printStackTrace();
        }

        mMediaPlayer.setDisplay(mHolder);
        mAnchorView.setTag(mMediaPlayer);

        mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
            @Override
            public void onVideoSizeChanged(MediaPlayer mediaPlayer, int i, int i2) {
                mHolder.setFixedSize(i, i2);
            }
        });

        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                Log.d(TAG, "MediaPlayer preview is prepared");
                IsMediaPlayerReady = true;

                if (mMediaPlayer != null && IsMediaPlayerReady) {
                    if (position > 0)
                        mMediaPlayer.seekTo(position);
                    mMediaPlayer.start();
                }
            }
        });

        Log.d(TAG, "MediaPlayer is created");
        try {
            mMediaPlayer.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i2, int i3) {
        Log.d(TAG, "HoverSurface changed");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        Log.d(TAG, "HoverSurface destroyed");
        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.release();
            //thumbnailImageView.setTag(null);
        }
    }

    public void setAnchorView(View view) {
        mAnchorView = view;
    }

    @Override
    public boolean onHover(View view, MotionEvent motionEvent) {
        try {
            if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
                Log.d(TAG, "ACTION_HOVER_ENTER");

                mSurfaceView = (SurfaceView) findViewById(R.id.media_browser_hoverSurfaceView);

                mHolder = mSurfaceView.getHolder();
                if (mHolder != null) {
                    mHolder.addCallback(this);
                }

            } else if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
                Log.d(TAG, "ACTION_HOVER_EXIT");
                if (mAnchorView.getTag() != null) {
                    MediaPlayer mMediaPlayer = (MediaPlayer) mAnchorView.getTag();
                    mMediaPlayer.stop();
                    mMediaPlayer.release();
                    mAnchorView.setTag(null);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, e.getMessage() + Utils.toString(e.getStackTrace()));
        }
        return false;
    }
}

The code to show the preview:

final PopupWindow popupWindow = new PopupWindow(context);
final HoverPreview hoverPreview = new HoverPreview(context, videoPath, 0);
hoverPreview.setAnchorView(thumbnailImageView);
thumbnailImageView.setOnHoverListener(new View.OnHoverListener() {
    @Override
    public boolean onHover(View view, MotionEvent motionEvent) {
        hoverPreview.onHover(view, motionEvent);
        if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
            popupWindow.setContentView(hoverPreview);
            popupWindow.setWidth(600);
            popupWindow.setHeight(400);
            popupWindow.showAtLocation(thumbnailImageView, ToolHoverPopup.Gravity.NO_GRAVITY, 10, 10);
            Log.d(TAG, "Manual Hover Enter");
        } else if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
            Log.d(TAG, "Manual Hover Exit");
            if (popupWindow != null)
                popupWindow.dismiss();
        }
        return true;
});
1

There are 1 answers

0
Mohsen Afshin On BEST ANSWER

Here's my complete working solution:

I borrowed some code from ToolHoverPopup class from SPen library, also I customized for this special popup so that nothing is created or inflated until the actual hovering is happened so that we don't consume resources for enabling such a preview in lists.

We need to have our preview attached to a Window so because of this we have to manage all the underlying job of positioning which is normally done by PopupWindow, so I completely removed the dependency on the PopupWindow and now my HoverPreview class is fully working and manages all the jobs, also it has the ability to determine the Hover Detection delay in milliseconds.

Screenshot (SurfaceView is created)

enter image description here

Usage: (Since the layout contains SurfaceView and is resource intensive, I manually trigger onHover event so that the real surface creation is performed only when the real hover is performed. Also by this, I don't create any object of HoverPreview before it's needed)

        thumbnailImageView.setOnHoverListener(new View.OnHoverListener() {
            @Override
            public boolean onHover(View view, MotionEvent motionEvent) {
                HoverPreview hoverPreview;
                if (thumbnailImageView.getTag() == null) {
                    hoverPreview = new HoverPreview(context, getActivity().getWindow(), videoPath, 0);
                    hoverPreview.setHoverDetectTime(1000);
                    thumbnailImageView.setTag(hoverPreview);
                } else
                    hoverPreview = (HoverPreview) thumbnailImageView.getTag();

                hoverPreview.onHover(null, motionEvent);

                if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_EXIT)
                    thumbnailImageView.setTag(null);

                return true;
            }
        });

HoverPreview:

public class HoverPreview extends LinearLayout implements View.OnHoverListener, SurfaceHolder.Callback {

    private static final int MSG_SHOW_POPUP = 1;

    private static final int MSG_DISMISS_POPUP = 2;

    private static final int HOVER_DETECT_TIME_MS = 300;

    private static final int POPUP_TIMEOUT_MS = 60 * 1000;

    protected int mHoverDetectTimeMS;

    private static final String TAG = "HoverPreview";
    private SurfaceHolder mHolder = null;
    String videoPath;
    int position;
    private boolean IsMediaPlayerReady = false;
    private MediaPlayer mMediaPlayer;
    private SurfaceView mSurfaceView;
    Context context;
    private HoverPopupHandler mHandler;
    Window window;

    public HoverPreview(Context context, Window window, String videoPath, int position) {
        super(context);
        this.mHoverDetectTimeMS = HOVER_DETECT_TIME_MS;
        this.videoPath = videoPath;
        this.position = position;
        this.window = window;
        setupLayout(context);
    }

    private void setupLayout(Context context) {
        this.context = context;

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        rootView = inflater.inflate(R.layout.media_browser_hover, this);

        mSurfaceView = (SurfaceView) findViewById(R.id.media_browser_hoverSurfaceView);
    }

    View rootView;

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        Log.d(TAG, "HoverSurface created");

        final Surface surface = surfaceHolder.getSurface();
        if (surface == null) return;
        if (!surface.isValid()) return;

        mHolder = surfaceHolder;

        mMediaPlayer = new MediaPlayer();

        try {
            mMediaPlayer.setDataSource(videoPath);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        mMediaPlayer.setDisplay(mHolder);

        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                Log.d(TAG, "MediaPlayer preview is prepared");
                IsMediaPlayerReady = true;

                int videoWidth = mMediaPlayer.getVideoWidth();
                int videoHeight = mMediaPlayer.getVideoHeight();
                Point size = new Point();
                int screenHeight = 0;
                int screenWidth = 0;
                Display display = getDisplay();

                display.getSize(size);
                screenWidth = size.x - (350 + 30); // margin + padding
                screenHeight = size.y;

                FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mSurfaceView.getLayoutParams();

                lp.width = screenWidth;
                lp.height = (int) (((float) videoHeight / (float) videoWidth) * (float) screenWidth);
                mSurfaceView.setLayoutParams(lp);

                if (mMediaPlayer != null && IsMediaPlayerReady) {
                    if (position > 0)
                        mMediaPlayer.seekTo(position);
                    mMediaPlayer.start();
                    findViewById(R.id.media_browser_hoverRootFrameLayout).setVisibility(VISIBLE);
                }
            }
        });

        Log.d(TAG, "MediaPlayer is created");
        try {
            mMediaPlayer.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i2, int i3) {
        Log.d(TAG, "HoverSurface changed");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        Log.d(TAG, "HoverSurface destroyed");
        try {
            if (mMediaPlayer != null) {
                mMediaPlayer.stop();
                mMediaPlayer.release();
            }
        } catch (Exception e) {

        }
    }

    @Override
    public boolean onHover(View view, MotionEvent motionEvent) {
        try {
            if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
                Log.d(TAG, "ACTION_HOVER_ENTER");
                show(); // checks the timing
            } else if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
                Log.d(TAG, "ACTION_HOVER_EXIT");
                dismiss();
            }
        } catch (Exception e) {
            Log.e(TAG, e.getMessage() + Utils.toString(e.getStackTrace()));
        }
        return false;
    }

    /**
     * Sets the time that detecting hovering.
     *
     * @param ms The time, milliseconds
     */
    public void setHoverDetectTime(int ms) {
        mHoverDetectTimeMS = ms;
    }

    public void dismiss() {
        dismissPopup();
    }

    private void dismissPopup() {
        // remove pending message and dismiss popup
        getMyHandler().removeMessages(MSG_SHOW_POPUP);
        getMyHandler().removeMessages(MSG_DISMISS_POPUP);

        try {
            if (mMediaPlayer != null) {
                mMediaPlayer.stop();
                mMediaPlayer.release();
            }
        } catch (Exception e) {

        }

        if (getParent() != null)
            ((ViewGroup) getParent()).removeView(this);
    }

    private Handler getMyHandler() {
        if (mHandler == null)
            mHandler = new HoverPopupHandler();
        return mHandler;
    }

    public void show() {
        // send message to show.
        if (getMyHandler().hasMessages(MSG_SHOW_POPUP)) {
            return;
            // getHandler().removeMessages(MSG_SHOW_POPUP);
        }
        getMyHandler().sendEmptyMessageDelayed(MSG_SHOW_POPUP, mHoverDetectTimeMS);
    }

    private void showPopup() {
        if (getParent() == null) {
            final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                    WindowManager.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                    PixelFormat.TRANSLUCENT);

            params.gravity = Gravity.CENTER_VERTICAL;
            params.x = 350;

            window.addContentView(this, params);
        }

        mHolder = mSurfaceView.getHolder();
        if (mHolder != null) {
            mHolder.addCallback(this);
        }
    }

    ;

    private class HoverPopupHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {

//            if (DEBUG)
//                android.util.Log.e(TAG, "handleMessage : " + ((msg.what == MSG_SHOW_POPUP) ? "SHOW" : "DISMISS"));

            switch (msg.what) {
                case MSG_SHOW_POPUP:
                    showPopup();
                    sendEmptyMessageDelayed(MSG_DISMISS_POPUP, POPUP_TIMEOUT_MS);
                    break;
                case MSG_DISMISS_POPUP:
                    dismissPopup();
                    break;
            }
        }
    }
}